> **Authorship & copyright.** This repository was produced primarily by a large language model (Claude). LLM-generated output is generally not eligible for copyright in the US and similar jurisdictions, so this code is in the public domain. To the extent any part is copyrightable, it is dedicated to the public domain via [CC0 1.0](LICENSE).
Drop a config at `~/modjam-config.json`. Use [sim/station-a.json](sim/station-a.json) as a template; add `"port": "/dev/ttyUSB0"` (Pi) or `"/dev/tty.usbmodem1301"` (Mac) for the attached MeshCore node.
Run:
```sh
modjam-station # on each Pi
modjam-control # on the Mac
```
## Simulator
Three stations + control, no hardware:
```sh
docker compose build
docker compose up -d station-a station-b station-c
TSV logs appear in `./sim/logs/` (one per station per run, format matches [reference/A.tsv](reference/A.tsv) / [reference/B.tsv](reference/B.tsv)).
`SIMULATOR=true` is what flips the radio backend from MeshCore-USB to UDP. The UDP backend drops cross-config datagrams, so receivers only "hear" senders whose freq/bw/sf/cr/channel match — same behavior as real radios.
The simulator also drops a fraction of received **test packets** at random to mimic real-world packet loss. Default is 15%; override with `SIM_PACKET_LOSS=0.0` (no loss) up to `1.0`. Protocol traffic (START/STOP, heartbeats, next/done) is never dropped — only payloads matching the test-packet pattern. Each station gets a deterministic per-station RNG seed so reruns are repeatable; set `SIM_SEED=...` to vary.
```sh
SIM_PACKET_LOSS=0.3 docker compose up -d station-a station-b station-c
```
## REPL commands
| Command | Effect |
|---|---|
| `start [k=v ...]` | encode and broadcast a START command (see [reference/concept.md](reference/concept.md) for keys: `name`, `f`, `bw`, `sf`, `cr`, `pow`, `size`, `stations`, `duration`, `padding`, `spacing`, `at`) |
├── cuesheet.py # build cuesheet from START params
├── station.py # IDLE/RUNNING state machine
├── control.py # REPL + rx tail
├── log.py # TSV logger
└── radio/
├── base.py # Radio ABC
├── meshcore.py # MeshCore over USB serial
├── udp.py # simulator radio
└── factory.py # picks backend from SIMULATOR env
```
## Tests
```sh
pip install -e '.[test]'
pytest
```
CI runs the same suite via [.forgejo/workflows/test.yml](.forgejo/workflows/test.yml) on push to `main` and on PRs.
## Scope
v1: IDLE + RUNNING only. RESULTS / DOWNLINKING is not implemented yet (see [reference/concept.md](reference/concept.md) and [notes.md](notes.md) for the protocol).