Lane A: grammar.js (shot/angle/focal/light/mark data), presets.js (bbox shot solver, light/mark appliers, DIRECT bar UI, NL box), stage video plates (VideoTexture plane/corner/dome, screen kind, syncVideos), dock video cards. Lane B: scenegod:direct → grouped-undo keys/cuts, 🗣 lip-sync from audio via /rhubarb, Cmd+wheel zoom / wheel scroll, audio trim, video clock-follow via nullable entityVideo seam. Lane C: POST /director (LLM proxy, server-side op validation vs vocab+scene, mock-tested), render.js awaits stage.syncVideos. Sync6 (orchestrator): VIDEO_EXTS in server tree/media types (video primary, img poster as thumb), stage.entityVideo accessor, syncVideos forces videoTex.needsUpdate (rVFC never fires in hidden tabs → black plates). Verified: 9 server groups + timeline + presets suites green; browser SYNC: one-click MCU framing lands camera+fov keys+cut (single undo), golden_hour relight keys, plate scrub-follow frame-exact, finalRender mp4 shows the Flow golden-hour street plate behind an animated character. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
2.3 KiB
HTML
68 lines
2.3 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>SCENEGOD — machinima stage</title>
|
|
<link rel="stylesheet" href="/web/style.css">
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>🎬 SCENEGOD</h1><span class="tag">machinima stage</span>
|
|
<span id="selftest"></span>
|
|
</header>
|
|
<div id="direct"></div>
|
|
<div id="app">
|
|
<aside id="dock"></aside>
|
|
<div id="view"></div>
|
|
<aside id="inspector"><div class="empty">nothing selected</div></aside>
|
|
<div id="timeline"><div class="empty">timeline — Lane B mounts here</div></div>
|
|
</div>
|
|
<script type="importmap">
|
|
{ "imports": {
|
|
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
|
|
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
|
|
}}
|
|
</script>
|
|
<script type="module">
|
|
import { Stage } from '/web/stage.js';
|
|
import { Dock } from '/web/dock.js';
|
|
|
|
// retarget self-check (?selftest=1) — proves the crown-jewel bake before anything else
|
|
if(new URLSearchParams(location.search).has('selftest')){
|
|
const { _selftest } = await import('/web/room3d.js');
|
|
const r = _selftest();
|
|
const el = document.getElementById('selftest');
|
|
el.textContent = `selftest: ${r.ok ? 'PASS' : 'FAIL'} (maxErr=${r.maxErr.toExponential(2)})`;
|
|
el.className = r.ok ? 'ok' : 'bad';
|
|
console.log('[room3d selftest]', r);
|
|
}
|
|
|
|
const stage = new Stage(document.getElementById('view'));
|
|
const dock = new Dock({
|
|
stage,
|
|
dockEl: document.getElementById('dock'),
|
|
inspectorEl: document.getElementById('inspector'),
|
|
viewEl: document.getElementById('view'),
|
|
});
|
|
|
|
// DIRECT panel (M5 director mode) — guarded like the timeline: page survives its absence
|
|
try {
|
|
const { mountDirectPanel } = await import('/web/presets.js');
|
|
mountDirectPanel(stage, document.getElementById('direct'));
|
|
} catch(e){ console.warn('direct panel not mounted:', e); }
|
|
|
|
// 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);
|
|
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>
|
|
</body>
|
|
</html>
|