4.9 KiB
4.9 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
(none yet — check back each session)