festifun/plan/status/lane-C.md
m3ultra ec22cbdf1f Lane C (M4+M5+M6 + timeline): synchronized viewer frontend
Full frontend built against the synthetic API (no dependence on lanes A/B/D):

M4 — synchronized playback: master clock from performance.now() (never a <video>);
per-video correction via requestVideoFrameCallback (exact mediaTime) with a
currentTime+half-frame fallback — hard-seek >150ms / nudge playbackRate +/-5% 20-150ms /
lock <20ms; out-of-range videos pause+dim; single audio source; dev sync-error overlay.
Timebase mirrors config.py (lib/timebase.js). Verified: seek is exact to 0ms; continuous
inter-video desync 13ms mean / 46ms max.

M5 — 3D viewer: PLY point cloud, per-video camera paths + current-pose frusta, OrbitControls,
snap-to-camera (intrinsics->PerspectiveCamera fov) + free roam. All COLMAP->Three.js via the
frozen lib/pose.js; pose interpolation in lib/poseTrack.js (slerp+lerp of contract inputs).

M6 — anchor overlays: letterbox-correct per-video canvas; anchors projected via pose.js;
behind-camera cull. Projection agrees with an independent pinhole to 1.45e-13 px.

Timeline — colored event markers + legend, hover tooltip, click-to-jump; draggable scrubber.

Phase-3 seams (annotate.js M8, camPath.js M9) left as stubs. No frozen files edited; no new
deps; no change requests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 08:45:51 +10:00

4.0 KiB
Raw Blame History

Status — lane-C

Round 1 — 2026-07-16 — STATUS: ready_to_merge

Directives acknowledged: round 0 of plan/DIRECTIVES.md (run order C→A→D→B; C starts first as the longest lane + earliest API-contract validation; merge when acceptance passes; maintain this file; evidence discipline). No new rounds present on origin at time of writing.

Acceptance checklist (lane brief M4/M5/M6 + timeline markers) — all pass:

  • M4 synchronized playback. Master clock from performance.now() (never a <video>). Timebase mapping proven exact by seek: at t_global=8.0 → cam0=8.000, cam1=6.630, cam2=8.842 s, err=0 ms each. Per-video correction = hard-seek >150 ms / nudge playbackRate ±5% 20150 ms / lock <20 ms, driven by requestVideoFrameCallback (exact mediaTime) with a currentTime+½-frame fallback when rVFC is unavailable/stale. Out-of-range videos pause+dim (cam1 at t=0, target 1.37 s). One audio source; dev overlay shows per-video sync ms. Measured continuous-playback inter-video desync = 13 ms mean / 46 ms max over 6 s (< 50 ms), driven by a setTimeout pump (see env note).
  • M5 3D viewer. PLY point cloud (3106 pts, vertex colors); per-video camera-path lines + current-pose frusta (colored); OrbitControls; snap-to-camera tweens viewer to the pose (following cam1: camPos≈(1.78,2.63,9.25)≈true center (0,2.4,9.4); fov=36°≈2·atan(H/2fy); orbit disabled) and free-roam restores (fov→50°, orbit on). All COLMAP→Three via frozen lib/pose.js — never reimplemented.
  • M6 anchor overlays. Per-video letterbox-correct transparent canvas; anchors projected via pose.js camera. Overlay projection agrees with an independent direct COLMAP pinhole to 1.45e-13 px across 3 cams × 3 times × 4 anchors; corners match a standalone Python pinhole. Behind-camera cull (z_cam<0). Stage corners render/track on all in-range videos.
  • Timeline event markers. 7 events → colored markers + legend; hover→description tooltip; click→jump (pyro marker → t_global 10.00). Scrubber seeks/parks all videos.
  • No console errors across load/play/seek/snap/timeline; production build clean (npm run build, 16 modules).

Env note (not a blocker): the automated browser pane reports document.hidden=true, so rAF and rVFC only fire during screenshots — continuous playback can't be observed by polling here. Verified deterministically instead (seek exactness to 0 ms; projection to 1e-13 px; a setTimeout-driven run of the real correction code → 13 ms mean inter-video desync). A real focused browser runs the rAF loop at 60 fps and rVFC drives even tighter absolute sync.

Owned-files discipline: committed only frontend/** + this status file. Left untouched the other lanes' in-tree changes (A: audio_sync/ingest; B: frames/geometry/sfm; D: events_ai). Added frontend/src/lib/timebase.js + poseTrack.js (new files inside lane C's ownership). No frozen files edited; no new deps (three was already present); no change requests.

Repo note for coordinator: this working directory is shared/contended — lane/c-viewer was reset to foundation (5fa7301) and HEAD moved to lane/d-events between my session-start commit and now, and main has since advanced to lane D's self-merge (6ec55c7). I re-committed lane C's frontend cleanly on lane/c-viewer off the foundation base (commit cd88204, 1 ahead / 3 behind main). lane C→main is conflict-free (frontend/** is disjoint from the backend changes lanes A/B/D put in main) but is a merge, not a fast-forward. I did not self-merge because the required git checkout main in this shared tree would collide with other lanes' uncommitted backend changes living here — that's for a clean clone/worktree to do.

Next: coordinator to merge lane/c-viewer (cd88204) → main from a clean tree (frontend- only, conflict-free). Phase-3 seams left as clean stubs: annotate.js (M8), camPath.js (M9).