Plan: Phase 5 spec + directives round 4 (M10-M17, lanes E/F/G)
plan/20-phase5.md is the phase-5 canonical spec: M10 beats/onsets, M11 auto-director, M12 cinematic export (lane E); M13 anchor manager + friend tags, M14 photo mode, M15 moment FX (lane F); M16 server-side camera paths, M17 static memory-capsule export (lane G). foundation2 freezes the new contracts (beats.json, camPath JSON, paths table, PATCH anchors, __F4D_API_BASE__ / manifest.capsule, transport/scene3d hooks) and pre-wires all UI so parallel lanes never touch shared files. DIRECTIVES Round 4 records the fresh-eyes upgrades (FFT caching, WebAudio master clock, spatial audio; suite floor now 121), ratifies the additive contract amendments, and sets run order: foundation2 -> E, G, F (own worktrees, mandatory) -> integration2 (real-footage field test, v0.2.0 proposal). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
db42c42932
commit
545f7a347d
@ -3,6 +3,12 @@
|
|||||||
Parking lot for post-v0.1.0 extensions (spec §5 future-extensions). Not committed work — a place
|
Parking lot for post-v0.1.0 extensions (spec §5 future-extensions). Not committed work — a place
|
||||||
to capture direction so the prototype's scope stays honest. Roughly ordered by payoff-to-effort.
|
to capture direction so the prototype's scope stays honest. Roughly ordered by payoff-to-effort.
|
||||||
|
|
||||||
|
> **Status 2026-07-17:** splatting ✅ shipped (see `modelbeast-crossover.md`); spatial audio ✅
|
||||||
|
> shipped (🎧, WebAudio master clock). Cinematic export → **M12**, multi-modal audio analysis →
|
||||||
|
> **M10/M11**, anchor manager → **M13**, path persistence → **M16** — all now specced in
|
||||||
|
> `plan/20-phase5.md`. Still parked here: realtime ingest, object/artist tracking,
|
||||||
|
> per-project workspaces, sync-graph visualization, per-moment splats (crossover doc §3).
|
||||||
|
|
||||||
## Neural rendering — Gaussian Splatting
|
## Neural rendering — Gaussian Splatting
|
||||||
Replace the sparse COLMAP point cloud with a **3D Gaussian Splatting** model for photorealistic
|
Replace the sparse COLMAP point cloud with a **3D Gaussian Splatting** model for photorealistic
|
||||||
free-roam instead of a dot cloud. COLMAP poses + images already feed splatting trainers
|
free-roam instead of a dot cloud. COLMAP poses + images already feed splatting trainers
|
||||||
|
|||||||
198
plan/20-phase5.md
Normal file
198
plan/20-phase5.md
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
# Phase 5 — Friends, Director's Cut & Capsules (M10–M17)
|
||||||
|
|
||||||
|
Canonical spec for the phase-5 milestones. **M0–M9 live in [`../OPUS_BUILD_INSTRUCTIONS.md`](../OPUS_BUILD_INSTRUCTIONS.md)
|
||||||
|
and every contract frozen there still applies** (timebase, DB schema, pose convention,
|
||||||
|
classifier contract, synthetic fixture). Standing protocol (status files, directives loop,
|
||||||
|
evidence discipline, worktrees) is unchanged from [`README.md`](README.md); the newest round of
|
||||||
|
[`DIRECTIVES.md`](DIRECTIVES.md) always wins.
|
||||||
|
|
||||||
|
## Context — what shipped since v0.1.0 (read before planning anything)
|
||||||
|
|
||||||
|
- **Splat rendering:** `GET /api/splat` + viewer `DropInViewer` with point-cloud fallback
|
||||||
|
(`docs/modelbeast-crossover.md`).
|
||||||
|
- **Live capture:** `FESTIVAL4D_CAPTURE=1` mounts `/capture` (README).
|
||||||
|
- **WebAudio master clock:** `t_global` derives from `AudioContext.currentTime`;
|
||||||
|
`GET /api/audio/{id}` serves a lazily-extracted stereo AAC per video; all `<video>` elements
|
||||||
|
are muted picture-only, with graceful fallback to the old `performance.now()` clock
|
||||||
|
(`frontend/src/transport.js` header comment is the reference).
|
||||||
|
- **Spatial audio (🎧):** per-camera HRTF `PannerNode`s at live poses; listener follows the
|
||||||
|
viewer camera (`transport.updateSpatial`, fed by `scene3d._updateAudio`).
|
||||||
|
- **Sync solver:** pairwise GCC-PHAT now reuses one rFFT per signal (`audio_sync.pairwise_offsets`).
|
||||||
|
- **Suite floor is 121 passed.** It only goes up.
|
||||||
|
|
||||||
|
## Phase map
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 5a (serial, ONE agent) Phase 5b (parallel, one agent per lane) Phase 5c (serial, ONE agent)
|
||||||
|
┌───────────────────────┐ ┌ lane/e-director ── M10 M11 M12 ─┐ ┌───────────────────────────┐
|
||||||
|
│ foundation2 │ ├ lane/f-social ──── M13 M14 M15 ─┤ │ integration2 │
|
||||||
|
│ contracts + stubs + │──merge──┤ ├──merge──│ full-system pass + │
|
||||||
|
│ UI wiring points │ └ lane/g-capsule ─── M16 M17 ─────┘ │ real-footage field test │
|
||||||
|
│ branch: foundation2 │ (independent — merge in any order) │ branch: integration2 │
|
||||||
|
└───────────────────────┘ └─────────────────────────────────── └───────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Lanes depend **only** on foundation2, never on each other. Everything is developed and verified
|
||||||
|
against the synthetic fixture first; real footage is integration2's job.
|
||||||
|
|
||||||
|
## Milestones
|
||||||
|
|
||||||
|
### M10 — Audio features: beats & onsets (lane E, backend)
|
||||||
|
`backend/festival4d/audio_features.py`, entrypoint `run_features()`, CLI `python -m festival4d features`.
|
||||||
|
Load the **reference** video's ingest WAV (16 kHz mono; the reference timeline *is* `t_global`).
|
||||||
|
Beat/tempo tracking + onset-strength detection (librosa is already a dependency). Write
|
||||||
|
`data/work/beats.json` (shape frozen below); `GET /api/beats` serves it, 404 when absent.
|
||||||
|
Quiet/short/beatless audio must produce a *valid, empty* result — never a crash.
|
||||||
|
**Accept:** synthetic master audio's known pulse grid recovered (beats within ±50 ms, tempo ±2 BPM);
|
||||||
|
silence → empty-but-valid JSON; tests in `backend/tests/test_audio_features.py`.
|
||||||
|
|
||||||
|
### M11 — Auto-director (lane E, backend + thin UI)
|
||||||
|
`backend/festival4d/director.py`, entrypoint `generate_path(top_n=8, lead_s=2.0)`, CLI
|
||||||
|
`python -m festival4d direct`, `POST /api/director` (stubbed by foundation2) returning a
|
||||||
|
**camPath-compatible JSON** (contract below). Deterministic v1 rules — no ML:
|
||||||
|
pick the top-N events by confidence (ties → earlier); for each, choose the registered camera
|
||||||
|
with a pose nearest the event time (via `db` + `geometry.colmap_to_threejs`); keyframes at
|
||||||
|
`t_event − lead_s` and `t_event + duration`; when `beats.json` exists, snap keyframe times to
|
||||||
|
the nearest beat; FOV from that camera's intrinsics (same formula as `scene3d.snapTo`).
|
||||||
|
Frontend: fill the stub `frontend/src/director.js` — the hidden **🎬 Auto-path** button fetches
|
||||||
|
`/api/director` and loads it via `camPath.fromJSON` (do not edit camPath.js).
|
||||||
|
**Accept:** output round-trips through `camPath.fromJSON` (structure-locked test against the
|
||||||
|
frozen shape); covers the top-N events; keyframe times land on beats when beats exist; live
|
||||||
|
browser evidence of the path playing.
|
||||||
|
|
||||||
|
### M12 — Cinematic export (lane E, frontend)
|
||||||
|
Fill the stub `frontend/src/exportVideo.js`. The hidden **⏺ Export** button: seek to the first
|
||||||
|
keyframe, play the path, capture `scene3d` canvas via `captureStream(30)` + the soundtrack via
|
||||||
|
the `transport.captureAudioStream()` hook (foundation2 provides it — taps the WebAudio graph
|
||||||
|
post-gain, spatial mix included), mux with `MediaRecorder` (vp9/opus webm), stop at the last
|
||||||
|
keyframe, download `festival4d-cut.webm`. Restore all prior UI/transport state afterwards.
|
||||||
|
**Accept:** exported file duration within ±5% of the path span, contains an audio track, plays
|
||||||
|
in Chrome — browser evidence (file size, duration probe via ffprobe) in the status file.
|
||||||
|
|
||||||
|
### M13 — Anchor manager & friend tags (lane F, frontend)
|
||||||
|
Fill the stub `frontend/src/anchorPanel.js`: an always-available collapsible panel listing every
|
||||||
|
anchor (color dot, label, jump-to, delete, rename/recolor via the new `PATCH /api/anchors/{id}`).
|
||||||
|
Jump-to uses the foundation2 hook `scene3d.focusOn(x, y, z)`. Add the "friend tag" flow: a
|
||||||
|
**+ Tag** action that runs the existing M8 bbox-annotation flow *without* an event and names the
|
||||||
|
resulting anchor (friends become labeled, colored anchors that x-ray onto every video for free).
|
||||||
|
**Accept:** create → rename → recolor → jump → delete round-trip live in the browser; labels
|
||||||
|
visible in the 3D scene and video overlays; anchors survive reload.
|
||||||
|
|
||||||
|
### M14 — Photo mode (lane F, frontend)
|
||||||
|
Fill the stub `frontend/src/photoMode.js`. **📷 / `P`**: pause, hide helpers + gizmos + anchor
|
||||||
|
labels (foundation2 hook `scene3d.setHelpersVisible(false)`), render one frame at 3840-wide
|
||||||
|
(preserving aspect) to an offscreen target, download PNG, restore everything.
|
||||||
|
**Accept:** PNG ≥ 3840 px wide with no grid/axes/frusta/gizmos; works in free-roam and
|
||||||
|
follow-cam; with a splat present it renders the splat (verify on the synthetic point cloud +
|
||||||
|
note the splat path).
|
||||||
|
|
||||||
|
### M15 — Moment FX (lane F, frontend)
|
||||||
|
Fill the stub `frontend/src/fx.js`. When the playhead crosses a `pyro`/`confetti` event, fire a
|
||||||
|
short particle burst at the event's resolved anchor (fall back to the stage centroid); on
|
||||||
|
`bass_drop`, pulse the point-cloud/splat scale for ~a beat. Toggle via the hidden **✨** button;
|
||||||
|
uses only `scene3d.addObject/removeObject`. Must fire when crossing markers in play *and* scrub.
|
||||||
|
**Accept:** browser evidence of both effect types; frame rate stays ≥ 30 fps on the synthetic
|
||||||
|
scene with FX enabled.
|
||||||
|
|
||||||
|
### M16 — Server-side camera paths (lane G, backend + thin UI)
|
||||||
|
Implement the foundation2-stubbed routes over the new `paths` table:
|
||||||
|
`GET /api/paths` (list), `GET /api/paths/{id}`, `POST /api/paths {name, json}`,
|
||||||
|
`DELETE /api/paths/{id}` — `json` is the frozen camPath JSON as text, validated on POST.
|
||||||
|
Fill the stub `frontend/src/pathsStore.js`: save-current-path (name prompt), load dropdown,
|
||||||
|
delete — wired to the hidden path-save controls.
|
||||||
|
**Accept:** endpoint tests (roundtrip deep-equal, 404s, invalid-JSON 422) in
|
||||||
|
`backend/tests/test_paths.py`; live save → reload page → load reproduces keyframes exactly.
|
||||||
|
|
||||||
|
### M17 — Memory capsule: static shareable export (lane G, backend)
|
||||||
|
`backend/festival4d/capsule.py`, CLI `python -m festival4d capsule [--out DIR]` (default
|
||||||
|
`data/capsule/`). Build a **zero-backend** bundle:
|
||||||
|
copy `frontend/dist` (error clearly if missing — tell the user to `npm run build`); copy media;
|
||||||
|
bake every read-only API response to files at the *same relative paths* (`api/manifest`,
|
||||||
|
`api/events`, `api/anchors`, `api/beats`, `api/videos/{id}/poses`, `api/pointcloud`, `api/splat`,
|
||||||
|
`api/audio/{id}` — extracting audio if not yet cached); inject
|
||||||
|
`<script>window.__F4D_API_BASE__=""</script>` into the copied `index.html`; set
|
||||||
|
`"capsule": true` in the baked manifest (the frontend hides all write UI — foundation2 wires
|
||||||
|
the flag); ship a stdlib **`serve.py`** in the bundle root that serves with HTTP Range support
|
||||||
|
(video seeking needs 206s; plain `http.server` can't).
|
||||||
|
**Accept:** `python -m festival4d capsule` on the synthetic project, then `python serve.py` in
|
||||||
|
the bundle: app loads with **zero** requests to :8000, videos play *and seek*, 3D scene +
|
||||||
|
timeline + spatial audio work, write UI absent; `backend/tests/test_capsule.py` covers bundle
|
||||||
|
structure, baked-JSON validity, and serve.py Range (206) behavior.
|
||||||
|
|
||||||
|
## Contracts (frozen at foundation2 merge)
|
||||||
|
|
||||||
|
1. **beats.json** (and the `GET /api/beats` body):
|
||||||
|
`{"tempo_bpm": float|null, "beats_s": [float], "onsets": [{"t_global_s": float, "strength": float 0..1}], "generated_by": "festival4d.audio_features"}`
|
||||||
|
— all times are `t_global` seconds.
|
||||||
|
2. **camPath JSON** (already shipped in `camPath.js`; now frozen):
|
||||||
|
`{"version": 1, "keyframes": [{"t_global": float, "pos": [x,y,z], "quat": [x,y,z,w], "fov": float}]}`
|
||||||
|
— **Three.js scene space**, quat order **[x,y,z,w]** (NOT COLMAP's [w,x,y,z]; convert via
|
||||||
|
`geometry.colmap_to_threejs` on the backend, never inline).
|
||||||
|
3. **paths table:** `paths(id INTEGER PK, name TEXT NOT NULL, created_at TEXT, path_json TEXT NOT NULL)`
|
||||||
|
+ the four routes in M16.
|
||||||
|
4. **`PATCH /api/anchors/{id}`** `{label?, color?}` → updated anchor dict (404 on missing).
|
||||||
|
5. **Runtime API base override:** `state.js` resolves `window.__F4D_API_BASE__` first, then
|
||||||
|
`VITE_API_BASE`, then the localhost default. **`manifest.capsule: true`** ⇒ read-only UI.
|
||||||
|
6. **`transport.captureAudioStream()`** → `MediaStream` tapping the live WebAudio mix (and a
|
||||||
|
matching `releaseAudioStream()`); `scene3d.focusOn(x,y,z)` and `scene3d.setHelpersVisible(bool)`.
|
||||||
|
7. **Frozen files during 5b:** everything frozen in phase 1 **plus**
|
||||||
|
`frontend/src/{transport,scene3d,state,main,camPath,annotate}.js` and `frontend/index.html`
|
||||||
|
(foundation2 pre-wires every button — hidden — and every module import; lanes fill their own
|
||||||
|
stub modules only). Believe a frozen file must change → `plan/CHANGE_REQUESTS.md`, work
|
||||||
|
around locally, integration2 adjudicates.
|
||||||
|
|
||||||
|
## foundation2 scope (Phase 5a, one agent, branch `foundation2`)
|
||||||
|
|
||||||
|
- DB: `paths` table + `db.py` helpers. API: `/api/beats`, `/api/director`, `/api/paths*`,
|
||||||
|
`PATCH /api/anchors/{id}` — implemented against synthetic data where cheap, stubbed with the
|
||||||
|
spec's graceful-degradation pattern where a lane owns the logic. CLI: `features`, `direct`,
|
||||||
|
`capsule` subcommands dispatching into stubs.
|
||||||
|
- Backend stubs: `audio_features.py`, `director.py`, `capsule.py` (raise `NotImplementedError`;
|
||||||
|
API/CLI catch and degrade, exactly like phase 1).
|
||||||
|
- Frontend: create empty stub modules `director.js`, `exportVideo.js`, `anchorPanel.js`,
|
||||||
|
`photoMode.js`, `fx.js`, `pathsStore.js`; import them from `main.js`; add ALL new buttons to
|
||||||
|
`index.html` (hidden until their module reports ready); add the transport/scene3d hooks and
|
||||||
|
the `__F4D_API_BASE__` + `manifest.capsule` wiring from the contracts above.
|
||||||
|
- Tests for every new route shape (mirroring `test_api.py`'s exact-set style — note it locks
|
||||||
|
the manifest key set; add `capsule` consciously).
|
||||||
|
- Update the ownership table below if anything moved. Suite ≥ 121 + your new tests. Merge fast.
|
||||||
|
|
||||||
|
## integration2 scope (Phase 5c, one agent, branch `integration2`)
|
||||||
|
|
||||||
|
- Merge order irrelevant; resolve CHANGE_REQUESTS; full synthetic pass of every milestone
|
||||||
|
(evidence per feature); raise the suite floor to the new count.
|
||||||
|
- **Real-footage field test** (the actual milestone): 2–4 real overlapping clips through
|
||||||
|
`ingest → sync → reconstruct → events → features → direct → serve`, then capsule the result.
|
||||||
|
Log every failure as a `plan/ISSUES.md` entry (ISSUE-1 is the format) — fix nothing mid-test.
|
||||||
|
- Tag `v0.2.0` when accepted by the coordinator.
|
||||||
|
|
||||||
|
## File ownership (Phase 5b)
|
||||||
|
|
||||||
|
| Owner | Files |
|
||||||
|
|---|---|
|
||||||
|
| Lane E | `backend/festival4d/audio_features.py`, `director.py`, `backend/tests/test_audio_features.py`, `test_director.py`, `frontend/src/director.js`, `exportVideo.js` |
|
||||||
|
| Lane F | `frontend/src/anchorPanel.js`, `photoMode.js`, `fx.js` |
|
||||||
|
| Lane G | `backend/festival4d/capsule.py`, `backend/tests/test_paths.py`, `test_capsule.py`, `frontend/src/pathsStore.js`, the paths-route bodies in the area foundation2 marks for lane G |
|
||||||
|
| Every agent | its own `plan/status/<lane>.md` |
|
||||||
|
| **Coordinator only** | `plan/DIRECTIVES.md` |
|
||||||
|
| **Frozen during 5b** | contract item 7 above |
|
||||||
|
|
||||||
|
## Phase-5 pitfalls
|
||||||
|
|
||||||
|
1. **Quaternion order.** camPath is `[x,y,z,w]` (Three.js); COLMAP storage is `[w,x,y,z]`.
|
||||||
|
Every past pose bug in this repo was a convention slip — go through `geometry.colmap_to_threejs`
|
||||||
|
/ `lib/pose.js`, never inline the math.
|
||||||
|
2. **The pane you verify in throttles rAF** (backgrounded tab). Drive the loop with the
|
||||||
|
`window.__f4d` pump (`localStorage.setItem("f4dDebug","1")` in production builds); don't
|
||||||
|
file "playback frozen" issues against your own hidden tab. MediaRecorder export (M12) needs
|
||||||
|
a *focused* tab to capture real frames — say so in your evidence if you couldn't.
|
||||||
|
3. **`test_api.py` locks response key-sets on purpose.** Adding a manifest key or route means
|
||||||
|
consciously updating the exact-set assertion in the same commit — a failing set-lock is a
|
||||||
|
feature, not an obstacle.
|
||||||
|
4. **Range serving.** Any static path that serves video must answer 206 (spec M3 pitfall #3);
|
||||||
|
`python -m http.server` does not — that's why M17 ships `serve.py`.
|
||||||
|
5. **Never block on a missing optional input.** No beats.json → director still works (no beat
|
||||||
|
snapping). No splat → photo mode shoots the point cloud. No audio track → transport already
|
||||||
|
falls back. Follow the degradation pattern everywhere; it's the house style.
|
||||||
|
6. **Decoded audio is big** (~23 MB/min stereo). M12/M17 must not decode all tracks just
|
||||||
|
because they can — reuse what the transport already has.
|
||||||
@ -4,6 +4,47 @@ Written **only** by the coordinator (the human's planning session). Agents: read
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Round 4 — 2026-07-17 — Fresh-eyes upgrades landed; PHASE 5 begins (M10–M17)
|
||||||
|
|
||||||
|
**Coordinator work landed directly on `main` (self-reviewed + live-verified):**
|
||||||
|
- `95b7178` — sync solver caches one rFFT per signal across the pairwise GCC-PHAT stage
|
||||||
|
(1.8× on 6 cams × 10 min; equivalence locked by a new unequal-lengths test).
|
||||||
|
- `c24af80` — **audio is now the master clock** (`GET /api/audio/{id}` AAC + WebAudio;
|
||||||
|
`t_global` from `AudioContext.currentTime`; all `<video>` muted picture-only; graceful
|
||||||
|
fallback) **+ spatial audio** (🎧 per-camera HRTF panners at live poses, listener = viewer
|
||||||
|
camera, gains 1/√N). Verified live: clock drives playback, mid-play source switching is
|
||||||
|
continuous, panners sit at the correct camera positions, toggle on/off mid-play is clean.
|
||||||
|
- `db42c42` — chore: `python-multipart` locked (declared-but-unlocked drift), launch configs.
|
||||||
|
- Suite is **121 passed**; frontend build clean. **The test floor is now 121.**
|
||||||
|
|
||||||
|
**Contract amendments (coordinator-ratified, additive only):** new route `GET /api/audio/{id}`;
|
||||||
|
`transport.js` is now the WebAudio clock owner (its header comment is the reference);
|
||||||
|
the `window.__f4d` debug hook is available in production builds via `localStorage f4dDebug=1`.
|
||||||
|
`test_api.py`'s manifest key-set lock is unchanged.
|
||||||
|
|
||||||
|
**Directives — Phase 5 (spec: `plan/20-phase5.md`; lane briefs: `plan/lane-E/F/G-*.md`):**
|
||||||
|
1. **Now: `foundation2` only** (one agent, branch `foundation2`, scope in 20-phase5.md).
|
||||||
|
Same rule as phase 1: contracts correctness beats speed — three lanes will hard-depend on
|
||||||
|
your route shapes, stubs, hooks, and UI wiring points. Maintain `plan/status/foundation2.md`.
|
||||||
|
2. **After foundation2 merges — lanes in priority order** (all three concurrently if capacity
|
||||||
|
allows): **E** (director — longest, earliest contract validation), **G** (capsule — the
|
||||||
|
shareable payoff), **F** (social UX — smallest, pure frontend).
|
||||||
|
3. **Worktree rule is MANDATORY this time:** every parallel agent creates its own `git worktree`
|
||||||
|
before its first edit (Round 1 standing lesson; the phase-2 shared-tree collision cost us a
|
||||||
|
stash cleanup). Single-agent phases (5a, 5c) may use the main working directory.
|
||||||
|
4. **Merge policy unchanged:** merge to `main` per-milestone where the lane brief allows it;
|
||||||
|
never wait on another lane. Then `integration2` (branch `integration2`), which owns the
|
||||||
|
real-footage field test and the `v0.2.0` tag proposal — failures go to `plan/ISSUES.md`,
|
||||||
|
not into hasty fixes.
|
||||||
|
5. Standing protocol (status files, evidence discipline, blocker rule, DIRECTIVES re-read after
|
||||||
|
every milestone) is unchanged from Round 0.
|
||||||
|
|
||||||
|
**For the human:** (a) the Round 3 focused-browser sync check is still open — now also put on
|
||||||
|
headphones, enable 🎧 3D audio, and fly around; (b) real overlapping clips for the field test
|
||||||
|
remain the gating item for integration2; (c) optional: a trained splat for a real project makes
|
||||||
|
photo mode (M14) and the capsule (M17) dramatically more impressive —
|
||||||
|
`scripts/splat_via_modelbeast.sh`.
|
||||||
|
|
||||||
## Round 3 — 2026-07-16 — Integration review: v0.1.0 ACCEPTED; polish + field test next
|
## Round 3 — 2026-07-16 — Integration review: v0.1.0 ACCEPTED; polish + field test next
|
||||||
|
|
||||||
**Coordinator review of the integration work (independently verified):** `main` == `origin/main`
|
**Coordinator review of the integration work (independently verified):** `main` == `origin/main`
|
||||||
|
|||||||
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
**Read [`../OPUS_BUILD_INSTRUCTIONS.md`](../OPUS_BUILD_INSTRUCTIONS.md) first — it is the canonical spec** (architecture, repo layout, data model, milestone details M0–M9, pitfalls). This directory only organizes *who builds what, in what order, without colliding*. Lane briefs reference spec milestones by number; they do not restate them.
|
**Read [`../OPUS_BUILD_INSTRUCTIONS.md`](../OPUS_BUILD_INSTRUCTIONS.md) first — it is the canonical spec** (architecture, repo layout, data model, milestone details M0–M9, pitfalls). This directory only organizes *who builds what, in what order, without colliding*. Lane briefs reference spec milestones by number; they do not restate them.
|
||||||
|
|
||||||
|
> **Current phase: 5** (M10–M17) — spec and organization in [`20-phase5.md`](20-phase5.md), lane
|
||||||
|
> briefs `lane-E/F/G-*.md`. Phases 1–4 below are complete (v0.1.0 + polish); their protocol
|
||||||
|
> sections (git, status, directives loop, evidence discipline) remain the standing rules.
|
||||||
|
|
||||||
## Phase map
|
## Phase map
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
18
plan/lane-E-director.md
Normal file
18
plan/lane-E-director.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Lane E — Director & export (branch `lane/e-director`)
|
||||||
|
|
||||||
|
**Scope: phase-5 milestones M10 + M11 + M12** (beats/onsets, auto-director, cinematic export) — see [`20-phase5.md`](20-phase5.md).
|
||||||
|
|
||||||
|
- Owned files: `backend/festival4d/audio_features.py`, `backend/festival4d/director.py`,
|
||||||
|
`backend/tests/test_audio_features.py`, `backend/tests/test_director.py`,
|
||||||
|
`frontend/src/director.js`, `frontend/src/exportVideo.js`. Nothing else.
|
||||||
|
- Fill the stub bodies foundation2 left; signatures, routes, and CLI dispatch already exist.
|
||||||
|
- Contracts you consume (frozen): `beats.json` shape, camPath JSON (quat `[x,y,z,w]`, Three.js
|
||||||
|
space — convert COLMAP poses via `geometry.colmap_to_threejs`, never inline),
|
||||||
|
`transport.captureAudioStream()`.
|
||||||
|
- Build order M10 → M11 → M12: the director consumes beats; export consumes the director's path.
|
||||||
|
Each milestone is independently mergeable — don't hold M10/M11 hostage to M12.
|
||||||
|
- Degradation: no beats → director skips beat-snapping; no poses → director returns a clear
|
||||||
|
error body, never a crash; export with no path → button stays disabled.
|
||||||
|
|
||||||
|
**Done when:** M10–M12 acceptance passes (browser evidence for M11/M12 in your status file),
|
||||||
|
`pytest` ≥ 121 + your tests, merged to `main` with synthetic end-to-end intact.
|
||||||
17
plan/lane-F-social.md
Normal file
17
plan/lane-F-social.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Lane F — Social & fun UX (branch `lane/f-social`)
|
||||||
|
|
||||||
|
**Scope: phase-5 milestones M13 + M14 + M15** (anchor manager + friend tags, photo mode, moment FX) — see [`20-phase5.md`](20-phase5.md).
|
||||||
|
|
||||||
|
- Owned files: `frontend/src/anchorPanel.js`, `frontend/src/photoMode.js`, `frontend/src/fx.js`.
|
||||||
|
Nothing else — buttons, imports, and hooks are pre-wired by foundation2.
|
||||||
|
- Contracts you consume (frozen): `PATCH /api/anchors/{id}` + existing anchor GET/POST/DELETE,
|
||||||
|
`scene3d.focusOn(x,y,z)`, `scene3d.setHelpersVisible(bool)`, `scene3d.addObject/removeObject`,
|
||||||
|
the M8 annotation flow (drive it, don't modify `annotate.js`).
|
||||||
|
- This is the "friends connect & remember" lane — labels, colors, and jump-to should feel like
|
||||||
|
tagging people, not editing database rows. Keep interactions one-click where possible.
|
||||||
|
- All verification is in-browser: use the `__f4d` debug pump (phase-5 pitfall #2) and put
|
||||||
|
screenshots/measurements in your status file. No backend tests to write; the suite must
|
||||||
|
simply stay green.
|
||||||
|
|
||||||
|
**Done when:** M13–M15 acceptance passes with browser evidence, frontend `npm run build` clean,
|
||||||
|
merged to `main` with synthetic end-to-end intact.
|
||||||
17
plan/lane-G-capsule.md
Normal file
17
plan/lane-G-capsule.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Lane G — Persistence & capsule (branch `lane/g-capsule`)
|
||||||
|
|
||||||
|
**Scope: phase-5 milestones M16 + M17** (server-side camera paths, static memory-capsule export) — see [`20-phase5.md`](20-phase5.md).
|
||||||
|
|
||||||
|
- Owned files: `backend/festival4d/capsule.py`, `backend/tests/test_paths.py`,
|
||||||
|
`backend/tests/test_capsule.py`, `frontend/src/pathsStore.js`, plus the paths-route bodies in
|
||||||
|
the region foundation2 marks for lane G. Nothing else.
|
||||||
|
- Contracts you consume (frozen): `paths` table + route shapes, camPath JSON (validate on POST),
|
||||||
|
`window.__F4D_API_BASE__` override, `manifest.capsule` read-only flag.
|
||||||
|
- M17 discipline: the capsule bakes API responses to files at the **same relative paths** the
|
||||||
|
live API serves — the frontend must not know the difference. Ship `serve.py` (stdlib,
|
||||||
|
Range-capable) in the bundle root; acceptance explicitly includes video *seeking* under it.
|
||||||
|
- The capsule is the shareable artifact of the whole project — treat "zero requests to :8000"
|
||||||
|
and "write UI absent" as hard acceptance, not nice-to-haves.
|
||||||
|
|
||||||
|
**Done when:** M16–M17 acceptance passes (endpoint tests + a served-capsule browser pass with
|
||||||
|
evidence), `pytest` ≥ 121 + your tests, merged to `main` with synthetic end-to-end intact.
|
||||||
Loading…
Reference in New Issue
Block a user