SCENEGOD/scenegod/web/tldev.html
type-two 45debd3c10 [laneB] M1: timeline model+clock, panel UI, stub, self-check
- timeline.js: headless clock/tracks/evaluate (transform slerp, param lerp,
  clip blocks with fade-out, camera cuts, undo stack). No three/DOM import.
- tlui.js: canvas panel — ruler scrub, playhead, key add/drag/delete, clip
  blocks, camera cuts, save/load with localStorage fallback. Injects own CSS.
- stagestub.js: dev Stage per PLAN §4.2 (deleted at SYNC 1).
- timeline_test.mjs: node self-check, all assertions pass.
- tldev.html: dev-only browser harness (deleted at SYNC 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 17:57:18 +10:00

60 lines
3.1 KiB
HTML

<!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>