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:
2026-06-13 22:34:29 +08:00
parent 06e639f0df
commit 021080dd56
14 changed files with 841 additions and 16 deletions

View File

@ -266,22 +266,30 @@ async def export_zip():
if not confirmed:
return JSONResponse({"error": "没有 confirmed 事件可导出"}, status_code=422)
# 校验门:任一 confirmed 有 error 即整体拒绝
# 校验门:任一 confirmed 有 error 即整体拒绝
# 同步做预编译探测——捕获 CompileError含 P2 scene 导出 gate D3含 scene 的事件暂不可导出),
# 把编译失败也并入 report避免 compile 抛异常变成 500。编译成功的结果缓存复用不重复编译。
report = {}
compiled = {}
blocked = False
for group, ir in confirmed:
errs, warns = ir_core.validate(ir, dic, points_dir=_POINTSETS_DIR)
if not errs:
try:
compiled[group] = ir_core.compile_ir(ir, dic)
except ir_core.CompileError as e:
errs = errs + ["[编译失败] %s" % e]
report[group] = {"errors": errs, "warnings": warns}
if errs:
blocked = True
if blocked:
return JSONResponse({"error": "存在校验失败的 confirmed 事件,已拒绝导出",
return JSONResponse({"error": "存在校验/编译失败的 confirmed 事件,已拒绝导出",
"report": report}, status_code=422)
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
for group, ir in confirmed:
rows = ir_core.compile_ir(ir, dic)
rows = compiled[group]
z.writestr(group + ".events.json",
json.dumps(rows, ensure_ascii=False, indent=2))
texts = ir_core.extract_texts(ir)