[sync1] wire TimelineUI to real Stage, null-mixer guard, M2 unlock + orchestrator updates
- index.html: mount TimelineUI, setMixerAuto(false) when timeline present - timeline.js: _evalClips guards no-blocks / null-mixer (real Stage returns null for non-characters; the stub never did) - tldev.html removed; stagestub.js kept as the node-test fixture - lane files: M1 accepted for all three, answers to open questions, M2 priorities (A: clipdrop event, PiP; B: row/scene-bar refresh; C: straight into M3 render pipeline) - SCENEGOD launch.json (informational; preview uses MESHGOD's entry) SYNC 1 verified in-browser: character+backdrop+retargeted clip scene loads, scrub interpolates frame-exact, scene save/load round-trips via server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
de3416869f
commit
1f8ad13ed6
11
.claude/launch.json
Normal file
11
.claude/launch.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "scenegod",
|
||||
"runtimeExecutable": "/Users/johnking/Documents/SCENEGOD/.venv/bin/uvicorn",
|
||||
"runtimeArgs": ["scenegod.server:app", "--port", "8020", "--app-dir", "/Users/johnking/Documents/SCENEGOD"],
|
||||
"port": 8020
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -97,4 +97,26 @@ disposeRoot). Port rules:
|
||||
- Page loads clean with timeline.js absent AND with server absent (mock=1).
|
||||
|
||||
## ORCHESTRATOR UPDATES
|
||||
(none yet — check back each session)
|
||||
|
||||
### 2026-07-18 — M1 ACCEPTED, SYNC 1 done. **M2 is UNLOCKED.**
|
||||
M1 verified end-to-end in-browser by the orchestrator (character + backdrop +
|
||||
retargeted clip + timeline scrub, all through the real Stage). Answers + M2 order:
|
||||
|
||||
1. **/assets/tree item shape confirmed** (from Lane C's server):
|
||||
`{name, path, formats: ["fbx", ...], thumb: relpath|null}` — rigroom-style,
|
||||
no `files` array. Drop your bare-string fallback. `/web/*` absolute imports
|
||||
are confirmed correct as served.
|
||||
2. **Priority 1 — eventize clip drops.** `stage.setMixerAuto(false)` is now
|
||||
active whenever the timeline mounts, so your drop-clip-to-preview path is
|
||||
dead in the integrated page. Replace it: on clip-drop onto a character,
|
||||
dispatch `window CustomEvent 'scenegod:clipdrop'` with
|
||||
`{detail: {id, path, clipIndex}}` (Lane B adds the block at the playhead).
|
||||
Keep bake-and-preview ONLY when no timeline is present.
|
||||
3. **Priority 2 — make sure `onChange` fires for every entity added via
|
||||
`applyState`** (SYNC finding: tlui's names column stayed empty after a
|
||||
programmatic scene load — fix on both sides; B is doing theirs).
|
||||
4. Then per plan: multiple cameras + PiP overlay (`renderActiveCamera` needs
|
||||
to work as the M3 final-render target — treat that contract as hard),
|
||||
backdrop rebuild-on-param-change, drag-from-dock onto viewport.
|
||||
Test assets now live in `assets/{characters,animations,backdrops}/test/`
|
||||
(man.fbx, hiphop.fbx, carrying.fbx, street.jpg — gitignored, on ultra).
|
||||
|
||||
@ -78,4 +78,30 @@ Clip crossfades polish, params lanes UI, camera-cut lane UI, snapping
|
||||
config, undo wiring, box-select + multi-drag of keys.
|
||||
|
||||
## ORCHESTRATOR UPDATES
|
||||
(none yet — check back each session)
|
||||
|
||||
### 2026-07-18 — M1 ACCEPTED, SYNC 1 done. **M2 is UNLOCKED.**
|
||||
Your model survived integration with the real Stage; transform interp and
|
||||
clip retarget playback verified frame-exact via `step()`. Notes:
|
||||
|
||||
1. **Fix applied by orchestrator** (in your file, keep it): `_evalClips` now
|
||||
early-returns when the entity has no clip blocks or no mixer — the real
|
||||
Stage returns `null` from `entityMixer` for backdrops/props/cameras; your
|
||||
stub always returned an object, which hid the crash. Lesson for the stub:
|
||||
mirror the contract's nullable returns.
|
||||
2. **stagestub.js was NOT deleted** — `timeline_test.mjs` imports it. It's
|
||||
now a permanent test fixture, not dev scaffolding. tldev.html is gone.
|
||||
3. **SYNC findings, fix first in M2:** (a) after `load()` + `applyState`,
|
||||
your track-name rows never appeared (only "Cameras") — rebuild rows on
|
||||
`load()` and on `stage.onChange`; (b) the scene-bar name/duration fields
|
||||
don't refresh after `load()` — bind them. Verified repro: load a 2-entity
|
||||
scene programmatically, rows stay empty.
|
||||
4. **New event:** Lane A will dispatch `scenegod:clipdrop`
|
||||
`{detail:{id, path, clipIndex}}` when a clip is dropped on a character —
|
||||
add a clip block on that entity at the playhead (default out = clip
|
||||
duration after preload, fade 0.25 when it abuts a neighbor).
|
||||
5. rAF note: in the embedded preview the tab can be `document.hidden` →
|
||||
rAF fully suspended → `play()` appears frozen. Not a bug in your clock
|
||||
(verified); don't "fix" it. `step()` is the deterministic path and works.
|
||||
6. Injected `<style id=laneB-tl-css>` is fine permanently — keep it.
|
||||
Then per plan: crossfade fade-IN polish, params + camera-cut lane UI,
|
||||
snapping config, box-select multi-drag (undo already wired).
|
||||
|
||||
@ -86,4 +86,23 @@ green = M1 code-done. Run with `| lm -l 2`.
|
||||
- test_server.py green; paste the summary line in your log.
|
||||
|
||||
## ORCHESTRATOR UPDATES
|
||||
(none yet — check back each session)
|
||||
|
||||
### 2026-07-18 — M1 ACCEPTED. **M2 unlocked AND M3 render pipeline green-lit early.**
|
||||
test_server.py green, tree/file/scenes verified live through the browser page
|
||||
(scene save 200 + lossless reload). You're ahead of the other lanes, so skip
|
||||
the wait: after the small M2 items (thumbnails endpoint; validation already
|
||||
solid), go straight into §C5 — implement `/render/begin|frame|end|status|out`
|
||||
+ ffmpeg encode, and `web/render.js`. Notes:
|
||||
1. `timeline.step(frame)` is verified deterministic against the real Stage
|
||||
(orchestrator stepped 0/30/60/90/120 and got exact eased positions) — your
|
||||
final-render loop can rely on it today. `stage.renderActiveCamera` as a
|
||||
render target is Lane A M2 work; until it lands, test `finalRender` with
|
||||
the director view canvas, and test the ffmpeg path with synthetic PNGs
|
||||
from a script (extend test_server.py: begin → POST 30 generated frames →
|
||||
end → poll → assert mp4 exists + ffprobe duration ≈ 1s).
|
||||
2. ffmpeg on ultra: check `shutil.which` at boot as designed; it's installed
|
||||
via homebrew here.
|
||||
3. Test assets for the tree now exist under `assets/*/test/` (gitignored).
|
||||
4. uvicorn for local dev: `.venv/bin/uvicorn scenegod.server:app --port 8020`
|
||||
— there's also a launch entry in MESHGOD/.claude/launch.json ("scenegod")
|
||||
used by the orchestrator's browser preview.
|
||||
|
||||
@ -48,8 +48,11 @@ const dock = new Dock({
|
||||
// Timeline is optional — the page must never break when Lane B's file is absent
|
||||
try {
|
||||
const { Timeline } = await import('/web/timeline.js');
|
||||
const { TimelineUI } = await import('/web/tlui.js');
|
||||
window.timeline = new Timeline(stage);
|
||||
} catch(e){ /* timeline.js not present yet */ }
|
||||
window.tlui = new TimelineUI(window.timeline, document.getElementById('timeline'));
|
||||
stage.setMixerAuto(false); // timeline owns mixer stepping from here on
|
||||
} catch(e){ console.warn('timeline not mounted:', e); }
|
||||
|
||||
window.stage = stage; window.dock = dock;
|
||||
</script>
|
||||
|
||||
@ -218,7 +218,9 @@ export class Timeline {
|
||||
|
||||
_evalClips(e, t) {
|
||||
const blocks = e.tracks.clips;
|
||||
if (!blocks || !blocks.length) return;
|
||||
const mixer = this.stage.entityMixer(e.id);
|
||||
if (!mixer) return; // SYNC1: real Stage returns null for non-characters (stub always had one)
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
const b = blocks[i];
|
||||
const rec = this._clipActions.get(b);
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
<!doctype html>
|
||||
<!-- tldev.html — Lane B DEV-ONLY harness to exercise the timeline UI against
|
||||
the stub before Lane A's index.html exists. Deleted at SYNC 1. -->
|
||||
<meta charset="utf-8">
|
||||
<title>SCENEGOD · timeline dev harness</title>
|
||||
<style>
|
||||
html,body{margin:0;height:100%;background:#0d1117;color:#c9d1d9;font:13px ui-monospace,monospace}
|
||||
#top{padding:8px 12px;border-bottom:1px solid #2b323c}
|
||||
#log{height:120px;overflow:auto;padding:6px 12px;color:#565f6b;font-size:11px;white-space:pre-wrap;border-bottom:1px solid #2b323c}
|
||||
#timeline{position:fixed;left:0;right:0;bottom:0}
|
||||
</style>
|
||||
<div id="top">Lane B dev harness — stub stage. Double-click a lane to add a key,
|
||||
drag keys, ▶ to play, drag the ruler to scrub. setTransform/param calls log below.</div>
|
||||
<div id="log"></div>
|
||||
<div id="timeline"></div>
|
||||
|
||||
<script type="module">
|
||||
import { StageStub } from './stagestub.js';
|
||||
import { Timeline } from './timeline.js';
|
||||
import { TimelineUI } from './tlui.js';
|
||||
|
||||
// mirror stub effects into the on-page log
|
||||
const logEl = document.getElementById('log');
|
||||
const stage = new StageStub();
|
||||
const _st = stage.setTransform.bind(stage), _sp = stage.setParam.bind(stage), _sc = stage.setActiveCamera.bind(stage);
|
||||
const line = (s) => { logEl.textContent = s + '\n' + logEl.textContent.slice(0, 4000); };
|
||||
stage.setTransform = (id, t) => { _st(id, t); line(`setTransform ${id} pos=[${t.pos.map(n=>n.toFixed(2))}]`); };
|
||||
stage.setParam = (id, k, v) => { _sp(id, k, v); line(`setParam ${id} ${k}=${v}`); };
|
||||
stage.setActiveCamera = (id) => { _sc(id); line(`activeCamera ${id}`); };
|
||||
|
||||
const scene = {
|
||||
version: 1, name: 'dev', fps: 30, duration: 6,
|
||||
entities: [
|
||||
{ id: 'lady', kind: 'character', label: 'lady',
|
||||
source: { type: 'assets', path: 'characters/lady.glb' },
|
||||
transform: { pos: [0,0,0], rot: [0,0,0], scale: 1 },
|
||||
tracks: { transform: [
|
||||
{ t: 0, pos: [-3,0,0], rot: [0,0,0], scale: 1, ease: 'linear' },
|
||||
{ t: 4, pos: [3,0,0], rot: [0,1.57,0], scale: 1, ease: 'inout' } ],
|
||||
clips: [ { path: 'animations/walk.fbx', clipIndex: 0, start: 0.5, in: 0, out: 2.4, loop: 1, fade: 0 } ] } },
|
||||
{ id: 'sun', kind: 'light', label: 'sun',
|
||||
params: { type: 'key', color: '#ffcc88', intensity: 1 },
|
||||
transform: { pos: [2,5,2], rot: [0,0,0], scale: 1 },
|
||||
tracks: { params: [ { t: 0, key: 'intensity', value: 0.2, ease: 'inout' }, { t: 5, key: 'intensity', value: 3, ease: 'inout' } ] } },
|
||||
{ id: 'camA', kind: 'camera', label: 'wide', params: { fov: 40 }, transform: { pos: [0,1.5,6], rot: [0,0,0], scale: 1 }, tracks: {} },
|
||||
{ id: 'camB', kind: 'camera', label: 'close', params: { fov: 60 }, transform: { pos: [2,1.6,2], rot: [0,0,0], scale: 1 }, tracks: {} },
|
||||
],
|
||||
cameraCuts: [ { t: 0, camera: 'camA' }, { t: 3, camera: 'camB' } ],
|
||||
audio: [],
|
||||
};
|
||||
|
||||
await stage.applyState(scene);
|
||||
const tl = new Timeline(stage);
|
||||
tl.load(scene);
|
||||
await tl.preload();
|
||||
const ui = new TimelineUI(tl, '#timeline');
|
||||
window._tl = tl; window._ui = ui; window._stage = stage; // console poking
|
||||
line('ready — scene loaded');
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user