feat(timeline): P2 并行编排——scene 多轨编辑器 + 白模重叠预览
剧情 Timeline P2 前端 + 共享内核(与 SGame 源真同步): - ir_core/IR_SCHEMA/样张:scene v0.3 + scene 校验 + 导出 gate(D3),与 SGame 仓逐字一致 - timeline.js:appendScene 按 authored start 铺多轨 clip(自然重叠预览),move from 同 actor 跨轨续连(D4); drawStage 改逐 actor 查对话→多人气泡同时计时;导出 _clipDur 纯函数;show() 加 startId 参;常量加 CAMERA_DUR - scene_edit.js(新):演出段编辑模态——拖 clip 改 start(吸附 0.1s)、拖右缘改 dur、增删 clip/轨道、 选中属性条精确编辑、客户端轻量 lint(镜像 validate.py)、▶ 预览此段(复用播放核) - graph.js:scene 节点(KIND_CN/summary/nodeInner 列轨道)+双击进编辑模态 - form.js:右栏 renderScene 精确数值编辑(轨道/clip 的 start/dur/kind/目标)+打开编辑器按钮 - app.py export:捕获 CompileError 并入 report(scene 被拦时不再 500) - test_scene.js:离线 10 断言全过(重叠确凿/晚 1.5s 起步/from 续连);gitignore 忽略本地 _localdemo.db 待浏览器目测拖拽编辑落 IR + 白模重叠演出。
This commit is contained in:
62
test_scene.js
Normal file
62
test_scene.js
Normal file
@ -0,0 +1,62 @@
|
||||
// 离线回归:scene 多轨数据源 + 重叠演出(无浏览器,stub window 加载 timeline.js)。
|
||||
// 跑法:node test_scene.js
|
||||
const fs = require("fs"), path = require("path");
|
||||
|
||||
global.window = {};
|
||||
require("./web/static/timeline.js"); // 设置 window.Timeline(加载期不碰 DOM)
|
||||
const T = global.window.Timeline;
|
||||
|
||||
function loadAnchors(psName) {
|
||||
// 复刻 app.py /api/pointsets:把点位集转成 anchors [{name,pos:[x,y,z]}]
|
||||
const p = path.join("E:/Library/SGame/Assets/StreamingAssets/Story/PointSets", psName + ".points.json");
|
||||
const ps = JSON.parse(fs.readFileSync(p, "utf-8"));
|
||||
return (ps.points || []).map(pt => ({ name: pt.name, pos: pt.pos || [0, 0, 0], rot: pt.rot || 0 }));
|
||||
}
|
||||
|
||||
let fails = 0;
|
||||
function ok(cond, msg) { console.log((cond ? "PASS " : "FAIL ") + msg); if (!cond) fails++; }
|
||||
function overlap(a, b) { return a.start < b.start + b.dur && b.start < a.start + a.dur; }
|
||||
|
||||
const ir = JSON.parse(fs.readFileSync("samples/scene_demo.ir.json", "utf-8"));
|
||||
const anchors = loadAnchors("QY_TLDEMO");
|
||||
const M = T._buildModel(ir, anchors);
|
||||
|
||||
// 1) scene clips 被铺出来:P1/NP1 各有 move,NP2 有 dialogue,镜头有 camera
|
||||
const p1move = M.clips.find(c => c.actor === "P1" && c.kind === "move");
|
||||
const np1move = M.clips.find(c => c.actor === "NP1" && c.kind === "move");
|
||||
const np1say = M.clips.find(c => c.actor === "NP1" && c.kind === "dialogue");
|
||||
const cam = M.clips.find(c => c.kind === "camera");
|
||||
ok(!!p1move, "P1 走位 clip 存在");
|
||||
ok(!!np1move, "NP1 走位 clip 存在");
|
||||
ok(!!np1say, "NP1 对话 clip 存在");
|
||||
ok(!!cam && cam.focus === "PT_CENTER", "镜头 clip 对焦 PT_CENTER");
|
||||
|
||||
// 2) 核心:A 走与 1.5s 后 B 走在时间上重叠(两点同时移动)
|
||||
ok(overlap(p1move, np1move),
|
||||
`P1/NP1 走位重叠:P1[${p1move.start.toFixed(2)},${(p1move.start + p1move.dur).toFixed(2)}] NP1[${np1move.start.toFixed(2)},${(np1move.start + np1move.dur).toFixed(2)}]`);
|
||||
|
||||
// 3) NP1 比 P1 晚 1.5s 起步(authored start 偏移被尊重;scene 整体在前置 narration 之后铺轴)
|
||||
ok(Math.abs((np1move.start - p1move.start) - 1.5) < 1e-6, `NP1 比 P1 晚起步 ${(np1move.start - p1move.start).toFixed(2)}s 应=1.5`);
|
||||
|
||||
// 4) move from 续连:P1 从初始锚点(-6) 起步、终点 PT_CENTER(+1.5)
|
||||
ok(Math.abs(p1move.from.z - (-6)) < 1e-6 && Math.abs(p1move.to.z - 1.5) < 1e-6,
|
||||
`P1 move from.z=${p1move.from.z}→to.z=${p1move.to.z}`);
|
||||
|
||||
// 5) scene 之后能续演到 choice(extendSegment 流过 scene→n_choice)
|
||||
const s = T._prepare(ir, anchors);
|
||||
let r = T._extend(s, T._firstNode(ir));
|
||||
ok(r && (r.kind === "choice"), "scene 后停在 choice:kind=" + (r && r.kind));
|
||||
|
||||
// 6) NP2 对话比 P1 走位晚 4.5s(scene-local start 偏移)
|
||||
const np2say = M.clips.find(c => c.actor === "NP2" && c.kind === "dialogue");
|
||||
ok(np2say && Math.abs((np2say.start - p1move.start) - 4.5) < 1e-6, "NP2 对话比 P1 晚 " + (np2say && (np2say.start - p1move.start).toFixed(2)) + "s 应=4.5");
|
||||
|
||||
// 7) 重叠时刻两人都在走(数据层:取两 move 区间交集中点)
|
||||
const lo = Math.max(p1move.start, np1move.start), hi = Math.min(p1move.start + p1move.dur, np1move.start + np1move.dur);
|
||||
const tau = (lo + hi) / 2;
|
||||
const p1moving = tau >= p1move.start && tau < p1move.start + p1move.dur;
|
||||
const np1moving = tau >= np1move.start && tau < np1move.start + np1move.dur;
|
||||
ok(p1moving && np1moving, `tau=${tau.toFixed(2)}(重叠区间中点)时 P1 走=${p1moving} NP1 走=${np1moving}(应同时为真)`);
|
||||
|
||||
console.log("\n" + (fails ? (fails + " 个断言失败") : "全部通过"));
|
||||
process.exit(fails ? 1 : 0);
|
||||
Reference in New Issue
Block a user