4.4 KiB
Status — lane-E (director & export, phase 5b — M10/M11/M12)
Round 1 — 2026-07-17 — STATUS: ready_to_merge
Directives acknowledged: Round 4 of plan/DIRECTIVES.md (phase 5 begins; foundation2 merged; suite floor 121 — was 127 at lane start after foundation2's tests).
Branch: lane/e-director2 (own worktree per Round-4 rule). Commits: 0f8685b M10,
52e8912 M11, ddb8dea M12. Not merged, not pushed — coordinator merges.
Acceptance checklist
- M10 beats/onsets: synthetic 120 BPM pulse grid recovered — tempo 120.000 (±2 req),
worst beat 30 ms off grid (±50 req) —
test_run_features_recovers_synthetic_pulse_grid; the 3 ground-truth bangs found as strong onsets —test_onsets_catch_the_ground_truth_bangs; silence/short → valid-empty JSON, missing WAV → note + nothing written, beatless noise → valid typed result, CLI end-to-end — 7 tests intest_audio_features.py. Key impl detail: 10 ms analysis hop (librosa's default 32 ms grid at 16 kHz misses the ±50 ms bound; measured 60 ms → 30 ms), tempo from median inter-beat interval (117.19 → 120.000). - M11 auto-director: structure-locked against the frozen camPath shape (exact key sets,
unit quats, monotone times) —
test_output_matches_frozen_campath_shape; top-3 grid ==[1,4,8,11,14]incl. shared-keyframe dedupe; quat order [x,y,z,w] locked via round-trip against frozengeometry.colmap_to_threejs+quat_to_mat(test_keyframe_pose_matches_frozen_conversion); beat snap puts every key on the grid, no beats.json / corrupt beats.json → unsnapped (no block); no events / no poses → valid empty path + note; confidence ties → earlier; lead clamps at 0; unregistered poses excluded; API route + CLI dispatch real output — 14 tests intest_director.py.director.jswires 🎬: POST →camPath.fromJSON→ auto-play via the existing #btn-path flow; empty path shows "no events yet" on the button. - M12 export (code-complete; needs live browser, below): canvas captureStream(30) +
transport.captureAudioStream()tap → MediaRecorder vp9/opus webm (vp8/webm fallbacks), first→last keyframe at forced rate 1, downloadsfestival4d-cut.webm, restores playhead/ rate/play/path/follow state, releases tap + tracks. Button disabled < 2 keyframes (chained onto main.js'scamPath.onChange— no frozen edits). - Suite:
uv run pytest backend/tests -p no:warnings→ 148 passed, 0 failed (127 baseline + 21 lane-E).cd frontend && npm run build→ clean (pre-existing chunk-size warning only).
Change request filed: CR-4 (plan/CHANGE_REQUESTS.md) — two stub-era assertions in
test_phase5_api.py asserted lane E's unimplemented behavior (CLI exit 2; empty director
path) and fail by design once M10/M11 land. Applied the minimal test edit directly per the
approved CR-1 precedent (test file ≠ frozen contract file; api.py/cli.py untouched):
CLI stub loop → ["capsule"] only; director test now asserts the frozen camPath shape
(valid stubbed or implemented). Flagging loudly since my brief read the freeze strictly —
if the coordinator prefers, revert backend/tests/test_phase5_api.py and re-apply at
integration2; the suite will then show those 2 stub-era failures until adjudication.
Coordinator must verify live in a FOCUSED browser (this session had no browser; rAF/ captureStream need focus — pitfall #2):
- M11:
python -m festival4d synthetic && ingest && features && serve+ vite dev → 🎬 visible; click → path loads (+ Key counter jumps), camera flies the cut, #btn-path shows ⏹. Withdata/work/beats.jsondeleted, 🎬 still works (unsnapped). With events cleared, button flashes "🎬 no events yet", no console error. - M12: with a path loaded, ⏺ → records the ride, auto-stops at the last keyframe, downloads
festival4d-cut.webm;ffprobeit: duration within ±5% of the path span (top-8 synthetic: 1.0→20.0 s ⇒ 19 s ±0.95), has an opus audio track; plays in Chrome. Then confirm prior state restored (playhead back, rate back, path button reset). Clicking ⏺ mid-ride stops early and still yields a valid file. Note: hidden/backgrounded pane records frozen frames. - M12 + 🎧: enable spatial audio before ⏺ — exported track carries the spatial mix.
Blockers / questions: none beyond CR-4 adjudication.
Next: coordinator merge + live checks above; integration2 owns the real-footage pass.