Architecture
MeshBench is one binary on one machine. There is no service, no worker, no backend. The only things that cross the network are *data*: terrain tiles, map tiles, and the optional CoreScope, Beacon and MQTT feeds. Nothing in the simulation depends on anything remote.
The rules that keep it honest#
These are enforced by review and by tests, and each one exists because the alternative produces a plausible answer that is wrong.
The channel does not decide anything. It sums waveforms and adds noise. Never add a rule like "if two transmissions overlap, both fail": capture effect must *emerge*, or the simulator is a packet model with extra steps.
Every GPU kernel has a CPU twin, and the two are tested against each other.
Reachability is asymmetric. A can hear B while B cannot hear A: different antennas, heights, powers and noise figures. Every result states which direction, because one that does not is wrong even when the arithmetic is right.
Antenna gain is directional. The pattern is evaluated in the true direction to the far end, per direction. A scalar gain field is a bug.
Position uncertainty propagates. A node imported at ±5 km does not get a confident answer.
Airtime must match the firmware's own getEstAirtimeFor(). The firmware's CSMA timing is built on it. If the channel disagrees, the two desynchronise silently and every timing result is quietly wrong.
Determinism is a feature. Same seed, same scenario, same result. Counter-based RNG, never a stateful stream shared across goroutines.
Where the code is#
Nine layers, and the rule is mechanical: internal/layers_test.go walks every file and fails if an import points upward, or if a package appears outside the nine.
The engine loop#
The engine steps in fixed increments, 10 ms by default. On each step it advances every node's firmware, collects whatever those nodes handed to their radios, places the resulting transmissions in flight, and delivers to each receiver the sum of everything audible at its antenna.
Simulated time is not wall time. A native run can go faster than real time on a small network and slower on a large one, and neither changes the result. This is exactly what emulation gives up: an emulated node runs at the speed of the emulator, so simulated time is pinned to the wall clock and two runs of one seed do not agree.
The control socket#
The application listens on $XDG_RUNTIME_DIR/meshbench.sock, newline delimited JSON. Every verb drives the same code path a person clicks, so a driven session opens the same panels and shows the operator what happened. See the control socket reference.
Verbs are serviced on the frame thread. That is a real constraint rather than an implementation detail: it is why a headless mode is a separate mode rather than the application running without a window.