- 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>
6.2 KiB
6.2 KiB
Lane C — Server: FastAPI, assets, scenes, render pipeline
You own: scenegod/server.py, web/render.js, scripts/*,
requirements.txt, .gitignore. Milestone: M1.
C1. Scaffold (first commit)
requirements.txt:fastapi,uvicorn. Nothing else..gitignore:.env,assets/,scenes/,renders/,__pycache__/,*.pyc,.DS_Store.scenegod/server.py, single file (split only when it hurts — MESHGOD's server.py is one file at 800+ lines and fine).SCENEGOD_PORT8020, bind127.0.0.1(envSCENEGOD_BINDto override later — not now).- Statics:
GET /→web/index.htmlwithCache-Control: no-store(MESHGOD stale-cache lesson);/web/*served with short cache. Use FastAPIFileResponse/StaticFiles, headers set explicitly for the HTML.
C2. Assets endpoints
- Roots from env with defaults:
SCENEGOD_ASSETS=./assets,SCENEGOD_SCENES=./scenes,SCENEGOD_RENDERS=./renders(create scenes/ renders on boot; assets must exist — clear error if not). GET /assets/tree: walkassets/{characters,animations,props,backdrops,audio}. Group rigroom-library style: files sharing dir+stem = one entry with aformatslist ({"name":"lady","path":"characters/pack01/lady.glb", "formats":["glb","fbx"]}). Extensions: glb/gltf/fbx/obj/bvh + jpg/png/webp (backdrops) + wav/mp3 (audio). Scan live, no index file, cache 5s.GET /assets/file?path=: resolve and enforcepath.resolve().is_relative_to(ASSETS_ROOT)— reject.., absolute paths, symlink escapes (resolve first, then check). Correct content-types (model/gltf-binary, etc.). This guard pattern applies to every path-taking endpoint you ever add here (ship-check rule).- Read
/Users/johnking/Documents/MESHGOD/meshgod/library.pyfirst — same idea, steal the grouping approach.
C3. Scenes CRUD
GET /scenes→[{name, mtime, duration}];GET /scenes/{name};POST /scenes/{name}with JSON body.nameslugified[a-z0-9-_], filescenes/{name}.json, atomic write (tmp + rename).- Validate on save (stdlib, no jsonschema dep):
version==1, entity ids unique, every track sorted byt, clip blocks non-overlapping per entity (fade overlap allowed),cameraCutsreference existing camera entities. Reject with a 422 listing every violation (agents and UI both rely on the messages).
C4. scripts/test_server.py (M1 gate)
Stdlib urllib + subprocess (uvicorn on a test port, tmp asset/scene
dirs): asserts tree grouping, file streaming, traversal attempts → 4xx
(?path=../../etc/hosts, absolute, URL-encoded ..), scene round-trip,
validation failures (unsorted keys, dup ids). python3 scripts/test_server.py
green = M1 code-done. Run with | lm -l 2.
C5. Render pipeline — M3, but design lands now (endpoints stubbed 501)
- Session model:
POST /render/begin {name,fps,width,height}→{renderId}(uuid, dirrenders/{id}/frames/).POST /render/{id}/frame/{n}: raw PNG body →frames/{n:06d}.png.POST /render/{id}/end {audio?}→ spawn ffmpeg in a background thread:ffmpeg -framerate {fps} -i frames/%06d.png -c:v libx264 -pix_fmt yuv420p -crf 18 out.mp4(audio inputs +-map/amix in M4).GET /render/{id}/status= queued|encoding|done|error(+log tail);GET /render/{id}/out.mp4. ffmpeg presence checked at boot (shutil.which), endpoint 503s w/ message if absent. Reap render dirs >48h old on boot. web/render.js(M3): exportsdraftRecord(stage, timeline)—canvas.captureStream(fps)+ MediaRecorder whiletimeline.play()s, save webm; andfinalRender(stage, timeline, opts)— resize canvas, looptimeline.step(f)→stage.renderActiveCamera()→canvas.toBlob('image/png')→ POST frame (3-4 in flight, backpressure via await), then/end, poll status, download link. Deterministic: never uses wall-clock time.
C6. M4 (locked): audio, MODELBEAST, graft
- ffmpeg audio mux per scene JSON
audio[]. POST /mb/submitproxy re-implementing the contract in MESHGODmeshgod/ops.py(upload input to /api/assets first; token read from~/Documents/backnforth/.envat request time, never logged; endpoint only active whenSCENEGOD_MB=1).scripts/graft_limb.pyheadless Blender (pattern: MESHGODscripts/glb2fbx.py):--body body.glb --limb hand.glb --bone mixamorig:RightHand→ align limb root bone to target bone head, join armatures, parent-keep-offset, join meshes (weights survive), export. If the body armature ALREADY has the bones the limb provides → exit 2 with "bones exist; mitten weights — re-skin, don't graft". Never route output through any finish/decimate path (rig contract).
M1 acceptance (log it)
uvicorn scenegod.server:app --port 8020serves Lane A's page (or a placeholder index if A hasn't landed — don't block on them).- test_server.py green; paste the summary line in your log.
ORCHESTRATOR UPDATES
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:
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.renderActiveCameraas a render target is Lane A M2 work; until it lands, testfinalRenderwith 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).- ffmpeg on ultra: check
shutil.whichat boot as designed; it's installed via homebrew here. - Test assets for the tree now exist under
assets/*/test/(gitignored). - 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.