# Lane K — LED badges & bench kit (M23 firmware+docs, M24 self-test) You build the wearable side: badge firmware speaking the frozen blink protocol, the hardware guide, and the bench self-test John runs before a shoot. Spec: `30-phase6.md` M23/M24 + contract #4. Branch `lane/k-badges`, own worktree. ## M23 — firmware + docs, the parts that bite - Contract #4 is FROZEN: 133 ms bit, `11100` preamble + 6-bit ID + even parity, continuous repeat, ID 63 = sync beacon. Keep the constants in ONE header (`hardware/badge/protocol.h`) shared by badge.ino and beacon.ino; document the manual-sync rule with `tracker_detect.py`'s decoder constants (can't import across languages — state both locations in the header comment and in docs/hardware.md). - badge.ino: plain Arduino APIs (millis()-paced, no delay()-drift), single bright LED on a PWM pin OR a WS2812 (compile-time #define), ID set by #define (document per-badge flashing). Hardware is never ideal on paper: expose a `BIT_TRIM_US` calibration knob for clock drift. - beacon.ino: ID 63, double LED brightness/size guidance. - docs/hardware.md: parts list with rough prices (ESP32 dev board / ATtiny85, LED + resistor or WS2812, LiPo/coin cell, diffuser ping-pong ball), wiring, flashing (`arduino-cli` commands), wearing guidance (chest/hat, diffusion, one badge per person, ≥24 fps cameras, exposure caveat: blown-out video kills OOK contrast). - Compile check: `arduino-cli compile --fqbn esp32:esp32:esp32` (and attiny if the core is installable). If arduino-cli isn't available in your environment, document the exact commands and mark the box unverified — do NOT claim compiled without output. ## M24 — bench self-test, the parts that bite - `scripts/badge_selftest.py` must run lane H's REAL decoder (`tracker_detect` blink path) — import it, don't re-implement; if lane H hasn't merged yet, code against the frozen contract #3/#4 shapes and mark the integration box pending (integration3 re-runs it). - `--synthetic` mode: render a small blinking clip using the fixture's marker-rendering helpers (foundation3 exposes them in synthetic.py) and assert the ID round-trips. - Clear, John-readable failure messages: no blob found / decoded wrong ID / clip too short (< 1 word). Exit codes: 0 ok, 1 decode-fail, 2 usage. ## Acceptance (evidence in plan/status/lane-K.md) - [ ] protocol.h constants byte-identical to contract #4; badge + beacon sketches compile (output pasted) or compile commands documented + marked unverified - [ ] docs/hardware.md complete enough that John can order parts from it - [ ] `uv run python scripts/badge_selftest.py --synthetic` decodes the rendered ID (`test_badge_selftest.py` covers it); failure paths print actionable text - [ ] Backend suite green; `npm` untouched