# Lane H — Tracker (M19 detection, M20 solving) You turn wearable markers in the videos into 3D friend tracks. Backend only. Spec: `30-phase6.md` M19/M20 + contracts #1–#5. Branch `lane/h-tracker`, own worktree. ## What foundation3 gives you - Stubs `tracker_detect.py` / `tracker_solve.py` with the contracts in their docstrings; API (`POST /api/tracks/solve` etc.), CLI (`track`), DB helpers, and the synthetic fixture's two moving markers + `data/work/track_truth.json` ground truth — all already wired. - You fill the two stub bodies and write the two test files. Nothing else. ## M19 — detection, the parts that bite - Sample frames with the `frames.py` machinery (~8 fps is plenty; document your rate). - Color mode: convert to HSV, gate on **saturation AND value** before hue (pitfall #2); connected components; centroid → normalized cx/cy. Hue buckets from contract #1 + `FESTIVAL4D_MARKER_HUES`. - Blink mode: track candidate bright blobs across the sampled window, integrate brightness per 133 ms bit window **using the video's real fps** (pitfall #3), correlate against the preamble, check parity, emit `code:`. - Empty/dark/markerless video → valid-empty detections. Never crash. ## M20 — solving, the parts that bite - `t_video → t_global` through the frozen helpers ONLY (pitfall #1). - Triangulation through `geometry` primitives exactly as `resolve.py` does — read it first. - Bucket ≈0.25 s; 2+ cameras → triangulate (record `views`, `quality` from residual); 1 camera → ray ∩ ground plane per contract #5 (`views=1`, `quality ≤ 0.5`). - Smooth per track; write through `db` helpers; re-solve replaces that marker's tracks (idempotent — don't stack duplicates on every run). ## Acceptance (evidence in plan/status/lane-H.md) - [ ] Detection: ≥90 % recall on visible synthetic markers, ≤0.008 normalized center error; blink ID 5 decoded (`test_tracker_detect.py`) - [ ] Solve: median 3D error < 0.3 vs `track_truth.json`, ≥80 % coverage; single-view fallback test; idempotent re-solve; degradations (`test_tracker_solve.py`) - [ ] CLI + `POST /api/tracks/solve` run detect→solve end-to-end on the fixture - [ ] Suite ≥ foundation3's count + yours, green; stub-era assertions superseded by your landing → CR entry (CR-1/-4/-5 pattern)