Joust · Firmware & Dev Reference

The keep-still jousting orb: firmware, build/flash, the live console, and how to port to a new board.

WIP. Docs deploy continuously for quick reference and feedback. Content changes fast. The ATOM Matrix prototype is being ported to the AtomS3R (BMI270) — see Porting.

Repo layout

One repo holds both the firmware and the web console. Firmware is PlatformIO C++ in src/*.h + src/main.cpp; the dashboard is a Bun + React app sharing the same src/ tree.
PathWhat
src/main.cppGame loop: 500 Hz sample/detect, state machine, serial protocol
src/board_config.hThe only file you edit to port. Pins, LED geometry, IMU bus per board
src/game_config.hTunable defaults: threshold, jerk gate, grace, free-fall, escalation
src/detector.hKeep-still detector: gravity EMA, hit peak, jerk, free-fall
src/imu.hAccel read (MPU6886 / BMI270)
src/display.h · patterns.hLED glyphs (+, X, meter bar) and colors
src/button.hSingle-button gesture grammar (tap / hold / admin)
src/swarm.hESP-NOW swarm (deferred; compiled behind a flag)
platformio.iniBuild envs and flags
src/App.tsx · index.ts · index.htmlWeb-Serial console (React); index.ts is the Bun server
build.ts · deploy.tsStatic bundle + Vercel deploy scripts

Build & flash (PlatformIO)

Three build envs, selected with -e. Default is atom_matrix.
EnvFlagWhat it adds
atom_matrix-D BOARD_ATOM_MATRIXDefault. Standalone single controller, no radio
atom_matrix_swarm-D SWARM_ENABLEDESP-NOW swarm layer (deferred)
atom_matrix_selftest-D SELFTEST_ON_BOOTRuns still / moving / knock / drop assertions on boot
Build: pio run -e atom_matrix
Build + flash: pio run -e atom_matrix -t upload
Serial monitor: pio device monitor -e atom_matrix (115200)
Self-test on boot: pio run -e atom_matrix_selftest -t upload

Set upload_port / monitor_port in platformio.ini to your USB serial device.

Serial commands

Type these into any 115200 serial monitor (or the console below) to drive and tune a unit live. Value commands take a number, e.g. S2.0.
CmdEffect
fInject a synthetic strike (exercises the real detector)
aArm now (skip grace)
rReset round
kAcknowledge elimination
oMark this unit out
tRun the self-test (still / moving / knock / drop)
S<g>Movement threshold in g (e.g. S2.0)
J<g/s>Jerk gate (J0 = off, any movement counts)
E<0|1>Escalation off / on

Telemetry streams back as T <ms> st=… meter=… hp=… thr=… lines the console graphs live.

Dashboard (Bun + React)

A Web-Serial console for desktop Chrome/Edge over USB: connect, watch the movement meter, drag the threshold. Lives at joust.nyc/console.
CommandWhat
bun installInstall deps
bun devHot-reload dev server at localhost:3000
bun run buildStatic bundle (build.ts)
bun run deployShip to Vercel → joust.nyc
Web Serial API Desktop Chrome / Edge USB · 115200

How detection works

It is a keep-still game. Each round samples the IMU at a fixed 500 Hz. A player is eliminated while armed when the dynamic movement magnitude crosses the threshold, or when the unit is dropped.
More sensitive: lower S Ignore slow pushes: raise J Prevent stalls: E1

Port to a new board

Next up: AtomS3R + BMI270 (in progress). The ATOM Matrix prototype is EOL / out of stock. The orb moves to the M5Stack AtomS3R (ESP32-S3, BMI270 IMU, Grove, USB-C) with an external SK6812 29-LED Grove flex strip as the indicator.
board_config.h is the only file you edit to port. Everything else keys off its BRD_* macros.
  1. Add an [env:…] in platformio.ini with a new -D BOARD_* flag.
  2. Add a matching #elif defined(BOARD_*) block in board_config.h defining the same macro set: BRD_LED_PIN, LED width/height/count/order, BRD_BTN_PIN, IMU bus (SDA/SCL/ADDR), buzzer, and the BRD_AX_S* axis-sign remap if the sensor is mounted rotated.
  3. If the board has a new motion sensor, teach imu.h to read it. BMI270 shares the ATOM's 0x68 I2C address, so mainly the register init differs.

For the AtomS3R: point the LED macros at the Grove pin and the 29-LED strip geometry, and select the BMI270 read path in imu.h. No game-logic changes.

Hosting

The dashboard and these docs deploy to joust.nyc via Vercel. bun run build emits the static bundle; bun run deploy pushes it. The live console is at joust.nyc/console. Docs redeploy continuously, so they stay in sync with the firmware.
Deferred (not in the current build): ESP-NOW swarm Tournament Sound Waterproof orb

One firmware, one board file to port. Source and the live console ship alongside this page.