@dsh-gis/dsh-video-studio-skill 0.9.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +424 -0
  2. package/README.md +287 -327
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/package.json +9 -10
  5. package/schemas/design.schema.json +722 -0
  6. package/schemas/film.schema.json +4 -15
  7. package/schemas/proposal.schema.json +133 -0
  8. package/skills/video-studio/SKILL.md +1316 -1123
  9. package/skills/video-studio/assets/design_fixture.json +141 -0
  10. package/skills/video-studio/assets/film.json +1 -2
  11. package/skills/video-studio/assets/golden_frames.json +11 -8
  12. package/skills/video-studio/assets/scenes.py +67 -22
  13. package/skills/video-studio/assets/voice_loudness.json +16 -0
  14. package/skills/video-studio/references/audio-variation.md +15 -11
  15. package/skills/video-studio/references/audio.md +36 -34
  16. package/skills/video-studio/references/change-impact.md +3 -3
  17. package/skills/video-studio/references/frame-qa.md +2 -2
  18. package/skills/video-studio/references/proposal.md +177 -0
  19. package/skills/video-studio/references/quality-gates.md +1 -1
  20. package/skills/video-studio/references/scene-grammar.md +28 -23
  21. package/skills/video-studio/references/sfx-design.md +1 -1
  22. package/skills/video-studio/references/style-packs.md +95 -257
  23. package/skills/video-studio/references/v08-quality.md +8 -8
  24. package/skills/video-studio/references/web-engine.md +1 -1
  25. package/skills/video-studio/references/word-timeline.md +2 -2
  26. package/skills/video-studio/scripts/designkit.py +2120 -0
  27. package/skills/video-studio/scripts/filmcheck.py +7 -33
  28. package/skills/video-studio/scripts/filmkit.py +153 -78
  29. package/skills/video-studio/scripts/grammar.py +67 -7
  30. package/skills/video-studio/scripts/musicgen.py +2043 -0
  31. package/skills/video-studio/scripts/preflight.py +10 -3
  32. package/skills/video-studio/scripts/previewkit.py +849 -0
  33. package/skills/video-studio/scripts/propose.py +825 -0
  34. package/skills/video-studio/scripts/qa_frames.py +45 -10
  35. package/skills/video-studio/scripts/qavmaf.py +77 -11
  36. package/skills/video-studio/scripts/render.py +472 -154
  37. package/skills/video-studio/scripts/scorekit.py +50 -1748
  38. package/skills/video-studio/scripts/selfcheck.py +658 -131
  39. package/skills/video-studio/scripts/stagewatch.py +21 -5
  40. package/skills/video-studio/scripts/stylekit.py +265 -741
  41. package/skills/video-studio/scripts/voicekit.py +502 -380
  42. package/skills/video-studio/scripts/webengine.py +15 -14
  43. package/tools/audio_similarity.py +57 -30
  44. package/tools/ci_check.py +7 -1
  45. package/tools/estimate.py +14 -8
  46. package/tools/golden_frames.py +56 -11
  47. package/tools/silhouette_qa.py +521 -168
  48. package/docs/demo-pil-sheet.png +0 -0
  49. package/docs/styles/demo-results.json +0 -76
  50. package/docs/styles/sheet-8-styles.png +0 -0
  51. package/docs/styles/sheet-ai.png +0 -0
  52. package/docs/styles/sheet-before-after.png +0 -0
  53. package/docs/styles/sheet-craft.png +0 -0
  54. package/docs/styles/sheet-night.png +0 -0
  55. package/skills/video-studio/assets/score_demo.json +0 -307
package/CHANGELOG.md CHANGED
@@ -6,6 +6,430 @@
6
6
  与 `package.json` 的 `version` 字段只差前缀。历史上曾同时出现 `0.7.1` 与 `v0.7.1` 两种写法
7
7
  (前者指向一个未推送的孤儿提交,已删除),此后不再产生不带 `v` 的 tag。
8
8
 
9
+ ## 1.0.2 — 审查修复:把"文档写着支持、实际不生效"的一批缺陷修掉;零拷贝出片;片内/片间双重反重复(2026-11-11)
10
+
11
+ > 本轮是**全项目审查**(文档 / 脚本 / 可用性 / 成本四个方向并行实测)后的修复版。
12
+ > 审查结论很不客气:v1.0.1 把"模板"删了,但**模板时代的硬约束和死路径还留着**——
13
+ > 最典型的是"从零到第一部片"这一步在文档里 100% 卡死,而三条公开命令里有两条直接崩。
14
+ > 本版不新增"功能好看",只做四件事:**让用户说的话真的生效 / 让首片真的能跑出来 /
15
+ > 让片子之间和片子内部不再大面积重复 / 让删掉的能力明确失败而不是假通过**。
16
+
17
+ ### P0:从零到第一部片(门槛)
18
+
19
+ | 问题(实测) | 修法 |
20
+ |---|---|
21
+ | **quickstart 照抄必崩**:SKILL §1 让人 `cp assets/film.json + scenes.py` 当模板,拷贝清单缺 `designkit/previewkit/grammar/propose` → `selfcheck` 41 PASS / 8 FAIL(全部 `No module named designkit`)、`render --stage all` 退出 1 | **零拷贝主路径**:脚本直接跑技能目录里的,`render --stage all` 不再需要工作目录里有脚本或 `scenes.py`(`--scenes` 缺省时依次找 `<剧本目录>/scenes.py` → 技能内置 `assets/scenes.py`)。工程目录里只剩 `film.json` + `work/` + `out/` |
22
+ | **带约束的 brief 约一半概率直接失败**:配色候选去重按 24° 分桶,而 must_have「深蓝」把色相窗口压到 52°,n=3 需要 3 个不同桶 → 「深蓝」8/20 成功、「暖纸色+女声」0/20 | 去重改为**色相距离 + 结构差异**分档(<6° 必换;<12° 时要求 ≥3 个维度不同);失败信息记录真实原因并给出 `--n 2` / 放宽 brief / `--seed` 三条退路。实测 6 种常见 brief × 10 次 = **0 失败** |
23
+
24
+ ### P0:公开命令必崩("文档支持"变"真的能跑")
25
+
26
+ | 问题 | 修法 |
27
+ |---|---|
28
+ | `render.py --stage style` 100% `KeyError: "style"`(`_print_style` 读 `m["style"]`,而 `design_to_plan` 已删该键;`--stage all` 不调用它,所以自检/CI 全绿发现不了) | 按参数化配乐的真实身份打印(调式/BPM/调性/mood),并顺带打印字幕样式与版式轮换 |
29
+ | `render.py --stage cues` 单跑 100% 崩(未挂 `_style` 就把 script 交给 `scenes.cues()`,钩子回落解析设计并 raise) | `st_cues` 调钩子前幂等解析一次 `resolve_style(script, work)` |
30
+ | `stylekit.py --list/--show` `NameError: ALL_STYLE_IDS`;`--lint` 空表 `exit 0`(**假通过**);`style="auto"` 调不存在的 `pick_style` | CLI 三个动作改成"明确说明能力已随模板删除 + exit 2";`resolve_script_style` 统一 raise 并给两步迁移指引;删掉 8.6 KB 死代码 |
31
+ | `qavmaf.py --selftest` rc=1(合成剧本无设计) | selftest 用 `designkit.sample_designs(seed=1)` **现场造设计**(不引入写死主题),11/11 PASS |
32
+ | `grammar.py --help` 被当文件名(`FileNotFoundError`) | 加 argparse,`--help` exit 0,原有用法输出逐字不变 |
33
+ | `filmcheck.py` 对 `style` 仍读已清空的风格表,报「找不到 stylekit.py」误导 WARN | 改成 inert 键:接受但明确提示"v1.0.1 起不生效,外观来自 work/design.json" |
34
+
35
+ ### 反重复(用户原则 5):片内 + 片间两道
36
+
37
+ | 机制 | 说明 |
38
+ |---|---|
39
+ | **片内版式轮换** `visual.layout_cycle`(新) | 以前 `visual.layout` 是单值 → 全片 8 屏同一骨架。现在按**章节**轮换(同章内稳定、换章才换),`designkit` 默认派生 2–3 种;`storyboard.json` 每段 `layout` 可审计。实测:4 段片子 `center-stack → split-right → left-stack → center-stack` |
40
+ | **字幕样式进设计** `visual.subtitle`(新) | 以前字号/位置/背板/圆角是 `filmkit` 的模块常量,**任何两条片子的字幕带一模一样**。现在 `size / y / align / plate_alpha / radius / family / color / max_lines` 全部可写(缺省 = 过去的固定值,老 design.json 逐像素不变) |
41
+ | **片间去重台账**(新) | `propose.py --confirm` 把已确认设计记进 `~/.dsh/video-studio/design-ledger.json`;下次提案读台账,与最近交付的设计差距 <2 维时自动换 nonce 重采并**明说**。`--no-ledger` 关闭、`--show-ledger` 查看、`VIDEO_STUDIO_LEDGER` 重定向路径 |
42
+ | 修掉"配乐"多样性轴恒失效 | `_AXES` 读已删除的 `audio.music.style` → 配乐维恒 None。改成 `调式/BPM档/情绪` 复合键 |
43
+
44
+ ### 其他"写了不生效"修复
45
+
46
+ - **`design.audio.*` 现在真的落进渲染**:`music.gain_db / duck_db`、`sfx.enabled / density / gain_db` 以前只被校验、从不被读(实测 `sfx.enabled=false` 仍出 8 条音效)。现在由 `design_to_plan` 产出 `audio_overrides`,`render` 填进 script 的缺省位。优先级:**film.json 显式 > design > 内置默认**。
47
+ - **`filmkit.resolve_theme / style_plan` 不再静默回落**:以前 `except Exception` 后返回一份写死的 v0.4 深蓝橙主题 + 120BPM/Dm(模板残留 + 吞错)。现在明确 raise 并给迁移指引,`_FALLBACK_THEME` 整块删除。
48
+ - **G11 VMAF 误杀好片**:参考帧渲染路径没传设计字幕参数(主路径传了),同源两帧字幕字号不同 → 实测 VMAF **91.33 fail**;参数打通后 **97.20 pass**。共享实现 `filmkit.subtitle_kwargs(plan)`,两条路径同一份。
49
+ - **G9 闪烁误杀文字动画**:新增"块内符号一致度"判据(真闪白整块同向 0.75–1.0;文字逐字入场/母题动画有增有减 0.42)。低于阈值**降级为 warning 并保留全部数值**,不静默放过;G9 门禁改为按 `deep.errors` 判而不是按 flicker 条数(否则降级形同虚设)。实测零拷贝首片 t=5.867s 的标题入场不再 fail。
50
+ - **`--list-palettes` 直接 KeyError**(v1.0.1 改成连续空间后 CLI 仍读旧键名);`--list-palettes` 现在打印"连续空间采样点",并说明**这不是可选清单**。
51
+ - **`filmcheck` 的 style 分支**不再误报 `stylekit.py` 缺失。
52
+
53
+ ### 文档与契约
54
+
55
+ - `schemas/design.schema.json`:新增 `visual.subtitle`(8 字段)与 `visual.layout_cycle`;`size/fps` 标注"本版仅支持 16:9,与 film.json video 冲突时报错"。
56
+ - `schemas/film.schema.json`:删掉 8 个已废弃的 style 枚举值,只留 `type: string` + 弃用说明。
57
+ - `skills/video-studio/references/*.md`、`SKILL.md`、`README.md`、`package.json`、`marketplace-entry.json`:清除"模板/内置风格/拷 assets"叙事,改为"零拷贝 + 现场生成";同步 selfcheck 条数与门禁说明。
58
+ - **删除 `docs/` 里的 v0.5/v0.9 快照图**(6 张风格对比图 + `demo-results.json`):它们是“8 套内置风格”时代的产物,
59
+ 与 v1.0.1「不提供任何模板」直接冲突,且已经**不能复现**(那套选型逻辑已删除)。README 不再用海报证明效果,
60
+ 改成「命令 + 你机器上的实测输出」;历史图片仍可从 git 历史取出(`git log -- docs/styles`)。
61
+ - `assets/film.json` 明确定位为**测试夹具**(删除已废弃的 `style` 字段,注释说明它不是给用户抄的模板);金帧基线随之重录(像素 3/3 一致)。
62
+
63
+ ### 验收
64
+
65
+ | 项 | 结果 |
66
+ |---|---|
67
+ | `selfcheck.py` | **66 PASS / 0 FAIL / 0 SKIP**(v1.0.1 是 60/0/1;新增 61–66 六条护栏:字幕样式、版式轮换、约束采样鲁棒性、已删除 API fail-closed、design.audio 贯通、片间去重台账) |
68
+ | `tools/ci_check.py` | **12/12** |
69
+ | `tools/silhouette_qa.py`(G13) | 重写为**零模板时代**版本(v2.0.0):`--designs N` 现场采样设计 → 渲染 → 两两比结构;新增 `--selftest` 3/3 PASS(确定性 / 正控制 / 负控制)。`--designs 4` 实测 6/6 对可区分 |
70
+ | `qavmaf.py --selftest` | 11/11 PASS |
71
+ | `qa_frames.py --selftest` | 22/22 PASS |
72
+ | 零拷贝端到端(空目录只有 film.json) | `propose (2.3s) → confirm (0.1s) → render --stage all` 成功出片;G11 VMAF 97.08 pass、G12 ASR 召回 0.885 pass |
73
+ | 同一 brief 连拍两次 | 配色色相 / 母题 / 版式 / 字幕字号 / BPM 全部不同(受配色约束时色相仍落在约束窗口内) |
74
+
75
+ ### 已知边界(如实说)
76
+
77
+ - **竖屏(9:16)仍然不支持**:安全区与版式按横屏像素写死,`film.json video` 写竖屏会明确报错(不是静默裁切)。
78
+ - **片间去重是"软保证"**:台账只比对离散维度(配色/母题/转场/缓动/编号/配乐/声线/情绪/版式/调式),连续量(色相 ±、字号 ±)的细微差别不算;重采 4 轮仍近时只告警不阻断。
79
+ - **`--engine web` 仍需自己的页面**:PIL 路线已零拷贝;web 路线的页面逻辑天生在页面脚本里,不会内置。
80
+
81
+ ## 1.0.1 — 清除全部模板:仓库只提供生成视频的技术,不提供任何模板(2026-11-11)
82
+
83
+ > 定位是用户拍板的:**本仓库提供"生成视频的技术支持",协助用户用专业的方式生成视频,不提供任何模板**。
84
+ > v1.0.0 删了"自动选风格",但留下了一堆目录表——**有目录就意味着用户的片子只能在目录里挑**,
85
+ > 目录外的第 9 种外观永远不存在。那是模板的另一种形式,本版把它们全部删干净。
86
+ >
87
+ > 对外行为变化只有两条:
88
+ > ① 不再有 `presets/`、--preset、任何内置风格/主题/配乐风格表;
89
+ > ② **同一个 brief 连拍两次会得到不同视频**(以前逐位相同),要复现请传 `--seed`。
90
+ >
91
+ > 详细记录见下方「v1.0.1 清除全部模板(详细记录)」一节。
92
+
93
+ ## 1.0.0 — 提案优先(Proposal-First)第一步:设计 DSL + 离线预览 + 提案闸(2026-11-10)
94
+
95
+ > **这不是一次普通的功能增量,是换地基的第一步。** 用户拍板走 **D3 彻底归零**:
96
+ > 删除 8 套内置风格与 legacy 内核,改成"设计 DSL + 采样器"。本版是 P0,
97
+ > 只交付"能先出方案、能确认、能拦住未确认的出片"这三件事;删表与参数化配乐在 P2–P3。
98
+
99
+ ### 为什么
100
+
101
+ v0.9.1 及以前的病根有三条(本轮实测):
102
+
103
+ | 病 | 实测证据 |
104
+ |---|---|
105
+ | 自动选风格把选择权从用户手里拿走了 | 8 个不同题材只命中 5/8 套风格,其中 3 个落进同一套 tech-deep;不写 `style` 时 8/8 全落 legacy(120BPM/Dm 一套) |
106
+ | 改了不少东西但用户改不动 | 审计实测出 **11 个静默失效的旋钮**(`voice` 写字符串时 map/jitter 全失效、`theme_overrides.ease/transition/item_stagger/layout` 被遮住、嵌套键被忽略、`sfx.cues[].seed` 必崩、`pan` 对立体声配方无效) |
107
+ | 没有"先看后选"的环节 | 四道人工签核全在 mux/qa/accept 收尾,`frames` 之前**零拦截** |
108
+
109
+ ### 新增
110
+
111
+ | 文件 | 作用 |
112
+ |---|---|
113
+ | `scripts/designkit.py` | **设计 DSL**:`validate_design`(fail-closed 校验)/`design_to_plan`(转成 scenes 能吃的计划)/`sample_designs`(确定性采样 N 个两两可区分的候选)/`design_distance`(伪多方案门禁) |
114
+ | `scripts/previewkit.py` | **离线预览**:`FAKE_VOICE_DUR` 合成时间轴 + `grammar.plan_scenes` + `draw_frame` + 联系表。**不联网、不合成旁白** |
115
+ | `scripts/propose.py` | **提案器**:`--brief` 出 N 候选 / `--revise` 重出 / `--diff` 对比 / `--confirm` 冻结 / `--show` 查看 |
116
+ | `schemas/design.schema.json` | 设计契约(六块:structure / visual / audio / pacing / id / pitch) |
117
+ | `schemas/proposal.schema.json` | 提案契约(候选 + 确认状态 + designHash) |
118
+ | `references/proposal.md` | 提案层文档:流程、闸门五档、fail-closed 理由、配色求解、常见误用 |
119
+ | `scripts/presetkit.py` | 预设加载器:读 `presets/*.json`、转 design(8 套预设全部可转且过校验) |
120
+ | `scripts/musicgen.py` | 参数化配乐编排器:无风格表,和弦进行/动机/鼓型/音色全可传参 |
121
+ | `presets/*.json` | 8 套出厂风格下沉为**数据**(全部 `auto:false`,不参与自动选型) |
122
+ | `assets/design_fixture.json` | 金帧基准设计(像素回归绑它,不绑内置风格) |
123
+ | `assets/voice_loudness.json` | 音色响度补偿表(voicekit 与 designkit 共用,避免两处漂移) |
124
+
125
+ ### 改动
126
+
127
+ | 文件 | 改了什么 |
128
+ |---|---|
129
+ | `scripts/render.py` | ① `resolve_style` 增加 **design 优先分支**:`work/design.json` 存在就以它为准;② 新增 `_proposal_gate()` 并在 `frames`/`all` 最前面调用;③ 新增 `--require-proposal`(默认只警告);④ `_sources()` 增加 `design` 源头 |
130
+ | `scripts/stagewatch.py` | `STAGE_IO` 的 `style`/`frames` 增加 `design` 源头;`_src_fp` 支持 design;源头名走 `SRC_LABEL`(不再硬编码两个 kind) |
131
+ | `tools/ci_check.py` | `LOCAL_MODULES` 增加 `designkit` / `previewkit` / `propose` |
132
+ | `SKILL.md` | 新增 **§0.5 铁律:先提案,再出片**(用户没要求也要先给方案;四步流程;三条硬约束) |
133
+
134
+ ### 设计要点(都来自实测,不是拍脑袋)
135
+
136
+ - **配色不是随机取色**:纯 HSV 随机取色的 `theme_lint` 通过率只有 **21/60 = 35%**;改成"色相族 + (s,v) 网格求解"后 **60/60 = 100%**。
137
+ - **设计空间**:8 配色族 × 8 母题 × 6 转场 × 4 缓动 × 8 编号 × 2 调式 × 7 情绪 = **344,064 种**(不含色相微抖),对比现状出厂的 8 种。
138
+ - **预览成本**:单候选 4 帧 1080p 约 **0.8–1.0 s**(热缓存 0.6 s),3 候选(含并排对比图)约 **2.5 s**,全程离线。
139
+ - **fail-closed**:设计缺字段/枚举越界/对比度不达标一律报错,**绝不静默回落默认值**——这是对"11 个静默失效旋钮"的直接反思。
140
+ - **候选必须"第一眼就不同"**:光靠"两两至少 2 维不同"不够——实测出现过两个候选都叫「冷薄荷·rays」。
141
+ 现在采样器用**互质步长轮转**(配色族 +i、母题 +3i、转场 +5i、情绪 +3i),n≤8 时天然保证多轴不同;
142
+ 并排对比图还会给每个候选打中文标签(id + 立意),否则人分不清哪行是哪个。
143
+ - **配色族 8 个**(含 2 个亮底:暖纸白 / 墨纸白),亮暗族都会出现在候选里——不是只有深色一种画风。
144
+
145
+ ### 行为变化
146
+
147
+ | 变化 | 之前 | 现在 | 影响 |
148
+ |---|---|---|---|
149
+ | `frames`/`all` 前 | 不看提案 | 查提案;未确认默认**警告**(`--require-proposal` 才失败) | 有 `proposal.json` 但没确认时 stdout 多两行提示;退出码默认不变 |
150
+ | `work/design.json` 存在时 | 忽略 | **以它为准**解析主题/语言/配乐/声线/节奏 | 想回到旧路径就删掉 `work/design.json` |
151
+ | stagewatch | 2 个源头(script/scenes) | 3 个(+design) | 改 design 后 `--stage explain` 会报 `source-changed` |
152
+ | ci_check | 12 项三档 | 内容不变,白名单加 3 个本地模块 | — |
153
+
154
+ ### P1 / P2 / P4 / P5(本版一并交付)
155
+
156
+ | 期 | 交付 | 实测 |
157
+ |---|---|---|
158
+ | **P1** | `--with-audio` 给每个候选附 **12 秒无旁白样音**;`confirm` 额外绑**剧本指纹**(剧本改了要重新提案) | 单候选样音 0.15–0.17 s;2 候选含静帧+样音 1.46 s;两个候选样音 sha1 不同 |
159
+ | **P2** | 新模块 `musicgen.py`:参数化配乐编排器(**没有风格表**;progressions / motif / drums / voices 全部参与事件展开,六种合成音色带各自的音区/衰减/奏法) | 12 s 合成 **0.30 s**、30 s **0.78 s**;**参数相关性全部 < 0.95**(越低越有效):seed 0.34 / mood 0.33 / voices 0.33 / progressions 0.49 / drums 0.80;同参数跨进程逐采样一致;26 项自检全 PASS |
160
+ | **P4** | `voicekit` 删掉 persona 身份表,改裸 edge voice id + **响度补偿数据表** `assets/voice_loudness.json`(9 把音色的实测补偿,跨度 6.0 dB);`calibrate_voice()` 离线时 `measured=False` 不抛 | 6 把可采样音色**全部有补偿**(改造前采样器产出 7 把、其中 4 把无补偿);`gain_db` 实测 +3.6 / -2.7 |
161
+ | **P5** | 金帧基线改绑**固定 fixture 设计** `assets/design_fixture.json`(不再是"某套内置风格");`selfcheck` 55 → **58 条**(新增 56 设计 DSL / 57 预设下沉 / 58 提案闸) | 金帧 `--write` 后 `--check` 3/3 帧一致;selfcheck 58/58;改写的 4 条老断言(24/26/28/46)全部换成新语义 |
162
+
163
+ > P2 的验收口径写死成「参数相关性 < 0.95」,是因为项目里已经有 9 张风格表被证明是**片间差异的唯一来源**
164
+ > (删表后只靠 bpm/key/mood/seed,实测 seed 0.959、mood 1.000 —— 等于参数白写)。
165
+ > 现在 5 个参数全部显著低于门槛,`musicgen` 才算真的替代了风格表。
166
+ > 已知边界:12 s 默认段落只有 calm/body/drive 三个 tier(没有 climax),所以「只改 climax 的鼓型」
167
+ > 在短片里可能听不出差异;长片(30 s+)会覆盖更多 tier。
168
+
169
+ > **P4 收尾修正**:`presets/*.json` 的 voice 段原本还是 persona 名(`narrator_male` 之类),
170
+ > 而 voicekit 在 D3 之后只认 edge voice id —— 已把这 8 份预设全部翻译成 id,
171
+ > 并让 `presetkit.preset_to_design` 真正读取预设的 voice map(此前它硬编码了一把嗓子)。
172
+ > 连带修掉一处契约不一致:`designkit` 的 voice 正则原本只认 `zh-CN-*`,
173
+ > 但响度补偿表里确实有 `zh-TW-HsiaoChenNeural`(soft 女声,-3.0 dB),模式改为 `zh-CN|TW|HK`。
174
+ > 实测:8/8 预设都能转出合法 design,且每个候选的 `gain_db` 来自**有实测补偿**的音色。
175
+
176
+ > **P1 收尾**:`must_have`/`avoid` 的匹配口径固化下来(中文按子串、英文按**整词**,
177
+ > 否则 `warm-paper` 会顺带命中「纸卷/paper」、`female` 会命中 `male`;同一句每类只取最长命中,
178
+ > 所以「稳稳推进」判 driving 而不是 steady+driving 互斥)。约束先解析成「每类允许的取值」,
179
+ > 再让确定性的 sha1 步进在**允许集**上轮转——不靠重采样碰运气(声线池只有 3 个女声时,
180
+ > 靠碰全中的概率不可用)。实测:`must_have:['女声','深蓝']` 3 个候选全女声、配色全深蓝、两两 n=9/10/8。
181
+ > `_unmatched_constraints` 已补进 `schemas/design.schema.json`(否则采样器吐出的设计自己过不了契约校验)。
182
+
183
+ > **P2 边界修复(climax 与中低频)**:两处都是"参数/素材写了但听不出",属于同一类病。
184
+ > ① **climax 够不到**:默认段落按时长比例切、tier 按小节网格取,两套网格对不齐时峰值段整个落在
185
+ > 小节中心之外(8 s / 96 BPM 中心 1.25/3.75/6.25/8.75,peak 段 4.16–6.08,三个中心全在窗外),
186
+ > 于是 12 s/30 s 片永远没有 climax,调用方传 `{"climax": [...]}` 完全无效。
187
+ > 修法:默认段落改成**按小节切**(每段至少 1 小节、权重和 = 总小节数),
188
+ > bar 能量取小节内 ±0.20 小节窗口的最响值。实测覆盖 21 组(3 bpm × 7 时长)从 0/21 到 **18/21**;
189
+ > 剩余 3 组是**两小节片**(约 5–7 s),没有空间放收尾段,peak 落在末小节属有意设计。
190
+ > ② **低中频挖空**:实测 20–120 Hz 占 50%、120–250 Hz 只有 **2.5%**、500–2000 Hz 占 18%——
191
+ > 听感偏"低频轰、中频薄"。根因是 pad 全在 250 Hz 以上、bass 96% 在 120 Hz 以下,中间没人。
192
+ > 修法:pad 叠一层**根音降八度的中低音层**(MIDI 40–57,基频恰在 120–250 Hz),
193
+ > 并重配总线(pad_hi 1.45 / bass 0.50 / kick 0.62)。
194
+ > 实测 12 s/30 s × 3 个 bpm 的 120–250 Hz 占比从 2.5–3% 升到 **14–27%**,20–120 Hz 从 50% 降到 **35–38%**。
195
+ > 26 项 `musicgen --selftest` 全 PASS,参数相关性仍然全部 < 0.95(seed 0.27 / mood 0.37 /
196
+ > key 0.33 / scale 0.38 / voices 0.37 / drums 0.85)。
197
+
198
+ ### 真实题材试跑(用一份 1.2 MB 分析报告做成片)挖出的 5 个 bug
199
+
200
+ > 这一节不是新功能,是**拿真实材料跑一遍整链**的结果。动机很直白:前六期所有验证都用
201
+ > 仓库自带的模板片(4 段、30 s、8 套内置风格),它绕过了所有与"真实剧本"有关的路径。
202
+ > 换成一份 6 段、58 s、`stats[]`/`series[]`/`sides{}` 全用上的剧本后,一路挖出 5 个 bug,
203
+ > 全部是"单线程跑一遍看不见"的那类。
204
+
205
+ | # | bug | 症状 | 根因 | 修法 |
206
+ |---|---|---|---|---|
207
+ | 1 | **并行渲染出另一部片** | `--jobs 4` 的成片是科技深蓝,`--jobs 1`/QA 参考帧是设计本身的近黑金;MAE 0.059、**VMAF 1.49** | `_render_chunk_worker` 把 snapshot 的 `_style` 弹掉后重解析,但 `resolve_style(script)` **没传 work**,读不到 `design.json`,只能走 stylekit 的 auto 分支 | 调用时把 work 传进子进程;`resolve_style(script, work)` |
208
+ | 2 | **主题元组被 JSON 变成 list** | 子进程 `TypeError: unhashable type: 'list'`(`_soft_dot` 的缓存键) | v0.6 只给 `scenes._bg` 做了冻结兜底,`_soft_dot`/`_plate` 等其它缓存点没覆盖 | 在**入口** `_scene_theme` 统一把 12 个颜色键冻结成元组(缓存点会越加越多,入口只有一个) |
209
+ | 3 | **数字卡只画第一个数** | `stats[]` 的第 2、3 项在画面上只剩"工具调用""Token 重放"两行字,且两行重叠 | 绘制器只画 `stats[0]` 的数值,其余项**只画 label 不画 value**;且行距 30px < 字号(约 28px) | 次要统计改画"数值 + 单位 + 标签",行距按字号 ×1.75 |
210
+ | 4 | **封面角标压标题** | `00 · 开场` 直接盖在标题中间 | hero 的 kicker 用布局表的固定 `kicker_y=250`,而标题 `title_y=300`、字号 96px —— 50px 间距装不下 96px 字形 | kicker 位置按标题字号算(`y - size*1.05 - ksize*0.35`),换字体/字号档不再压 |
211
+ | 5 | **预览会骗人** | 剧本写了 `type=chart`,预览里画成要点列表(一根条形都没有),但成片里是正常的 | 预览用假旁白时长 2.0 s 排轴 -> 段长 3.4 s < chart 的 4.5 s 门槛 -> grammar 把 chart 降级成 list | 假时长提到 3.2 s(段长约 4.6 s);**降级信息写进返回值与提案**,人读输出也打印 `[降级]` |
212
+
213
+ > 第 1、5 条值得单独记住:
214
+ > **① 并行度会改变输出**是最隐蔽的一类 bug——任何单线程验证都发现不了,只有"两条路径产出的帧"逐像素比才会暴露。
215
+ > **② 预览静默改构图**比预览粗糙严重得多:用户以为"数据条没做出来",实际是预览在骗他。
216
+ > 所以预览现在**自曝降级**,宁可多说一句,也不让人误判。
217
+
218
+ > 顺带修的契约不一致:`render._theme_for` / `st_layout` / `filmkit.resolve_theme` 都无条件
219
+ > `THEMES[plan["theme_id"]]`,而设计路径的 `theme_id`(如 `a-44957fb5`)不在内置表里 -> KeyError;
220
+ > 三者统一改成"**先看 plan['theme']**,老计划才回查内置表"。
221
+ > `qavmaf._style_plan` 同样加了 design 优先(它原本在独立进程里解析不到 design,会拿 tech-deep 当参考帧)。
222
+
223
+ | 6 | **QA 单独跑与内联跑结论不同** | `--stage qa` 单独跑 VMAF 1.07,`--stage all` 内联跑 97.15;同一份成片 | `st_qa` 里 `_theme_for(script)` **没传 work**,读不到 `design.json`,回落到 stylekit auto(tech-deep)并把错误的 `_style` **写进 script**;随后 qavmaf 见 `_style` 已存在就直接用,design 优先级失效 | `_theme_for(script, work)`;`qavmaf._prepare_script` 改成 **design 优先于缓存的 `_style`** |
224
+ | 7 | **暗色主题被判成整片黑屏** | 门禁报「首尾之外检测到 4 段长黑帧」,实测真实亮度全程 0.05–0.07、一段真黑屏都没有 | `ffmpeg blackdetect` 的 `pix_th` 是 0.10(默认),而近黑金主题底色亮度只有 0.031 —— 全片 98% 像素都在"黑"的阈值下 | `detect_black` 加 `pix_th` 形参,`qa_report` 按主题底色算(`bg 亮度 × 0.45`,夹在 0.012–0.10) |
225
+
226
+ > 第 6、7 条是同一类:**判据本身跟着"当前主题/当前路径"漂移**,而不是画面变了。
227
+ > 这类问题的危害在于它看起来像"片坏了",实际是尺子坏了——所以修法都是"让尺子跟着对象走"。
228
+
229
+ ### 真实题材跑片的产物(可复现)
230
+
231
+ > 材料:一份 1.2 MB / 488 段 / 43 张表的 DSH 全景分析报告(本机 1093 会话的实证观察)。
232
+ > 剧本:6 段 58 s,`hero / stat / list / compare / chart / outro` 六种构图全用上。
233
+ > 选型:`--brief` 采样出 3 个候选(近黑金·电影遮幅 / 科技深蓝·极简留白 / 暖砂金·放射线),
234
+ > 底色族两两不同;人工选 A(近黑金 + 88BPM 小调 reflective,贴合"复盘"语气)。
235
+
236
+ | 结果项 | 实测 |
237
+ |---|---|
238
+ | 成片 | 1920×1080 / 30fps / h264+aac / **57.93 s** / 37.3 MB / 5.15 Mbps |
239
+ | 响度 | **−16.0 LUFS** / 真峰 −1.5 dBTP |
240
+ | 全片 VMAF | **97.15**(SSIM 0.9937,1738 帧,参考帧=场景函数实时重渲) |
241
+ | 词级时间戳 | 6 句 / 124 词,末词偏差最大 0.09 s |
242
+ | ASR 回读 | 字符召回 0.836(缺句 0),l02 单独 0.57 会记 warning |
243
+ | 门禁 | pass 10 · fail 0 · skipped 1(signoff 未签核,属预期) |
244
+
245
+ > ASR 那条 warning 值得留一句:l02 的原文是"六十天,一千零九十三个会话,十一万七千步,三十三万八千次工具调用,
246
+
247
+ > 二十二点六亿 Token"——密集中文数字连读,small 模型转成"117000部""338000次"。
248
+ > 这不是旁白错了,是**读数类文案对 ASR 天然不友好**;要压召回率就把数字改成阿拉伯数字朗读。
249
+
250
+ ### 修 CI 红:G13 剪影判据看不见 letterbox(v0.9.0 遗留,Linux 必红)
251
+
252
+ > CI 在 `skill selfcheck (Linux)` 上报 `FAIL 55 剪影可区分 -> 1/28 对失败(cinema×epic)`。
253
+ > 查历史发现 **v0.9.1 的 CI 就已经红了**,同一个失败、同一对风格——不是 v1.0.0 引入的回归,
254
+ > 是一直没人看 CI 而已(v0.8.1 是最后一个绿的版本)。
255
+
256
+ **根因不是字体,是判据有盲区。**
257
+
258
+ 前五项指标(F/Cx/Cy/B/S)全部按"前景像素"计算,而前景档的定义是 `|gray - 四角背景| >= 60`。
259
+ `letterbox`(cinema 的母题)是**上下两条纯黑边**:gray=0,而近黑金底色 gray≈9,差 9 < 60 ——
260
+ 黑边被算成背景,cinema 最标志性的结构在判据里**根本不存在**。于是 cinema×epic 只剩文字排版在撑:
261
+
262
+ | 字体 | ΔCy | 结果 |
263
+ |---|---|---|
264
+ | Windows 微软雅黑 | 10.8(阈值 8.0) | 过(侥幸) |
265
+ | Linux Noto CJK | **1.47** | 失败 |
266
+
267
+ > 也就是说,这个门禁在 Windows 上一直是"靠字体侥幸通过"。本机 28/28 从来不代表 Linux 也过。
268
+
269
+ **修法:加第 6 项指标 P = 垂直 9 带明暗剖面的最大偏差。**
270
+
271
+ - 按整行取平均灰度,减自身中位数(抗整体亮度/主题差),再逐带求两套风格的最大差;
272
+ - 为什么它抗字体:字形一变,逐像素全变,**而行均值几乎不动**——实测 P 在 msyh 与 Noto 下
273
+ 数值完全相同(7.99 vs 7.99);
274
+ - 阈值 6.0:cinema×epic 实测 7.99,留 25% 余量。
275
+
276
+ **验证**(两种字体各跑一遍全部 28 对):
277
+
278
+ | 字体 | 结果 | cinema×epic 靠哪项过 |
279
+ |---|---|---|
280
+ | 微软雅黑 | 28/28 通过 | P(list/stat)+ Cy/P(hero) |
281
+ | Noto CJK | 28/28 通过 | **P**(不再依赖 Cy) |
282
+
283
+ `selfcheck` 58/58、`ci_check` 12/12 保持通过。
284
+
285
+ ### v1.0.1 清除全部模板(详细记录)
286
+
287
+ > 用户的定位很明确:**这个仓库提供"生成视频的技术支持",协助用户用更专业的方式生成视频,
288
+ > 不提供任何模板**。v1.0.0 虽然删了"自动选风格",但留下了一大堆目录表——它们是模板的另一种形式:
289
+ > 有目录就意味着用户的片子只能在目录里挑,目录外的第 9 种外观永远不存在。
290
+
291
+ **删掉的东西(全部是"目录/清单",不是技术)**
292
+
293
+ | 删除项 | 位置 | 为什么它算模板 |
294
+ |---|---|---|
295
+ | `presets/*.json` + `presetkit.py` + `--preset` | 8 个预设定档 | 一份现成外观存成文件,最典型的模板 |
296
+ | `THEMES`(8 套命名主题) | `stylekit.py` | 用户只能在"科技深蓝/近黑金/冷薄荷…"里选 |
297
+ | `STYLES`(风格包 + `pick_style` 自动选型) | `stylekit.py` | 一个 id = 主题+配乐+旁白+节奏的整套模板 |
298
+ | `VISUAL_LANGUAGES`(8 套命名结构签名) | `stylekit.py` | 换风格只能在这 8 组母题/转场里挑 |
299
+ | `PALETTE_FAMILIES`(8 个命名配色族) | `designkit.py` | 配色的取值被限制成 8 个离散点 |
300
+ | `_MUSIC_BY_MOOD`(情绪→曲风映射) | `designkit.py` | 把"配乐模板"伪装成参数 |
301
+ | `audio.music.style` 字段 | design DSL | 强制每份设计指向一个内置曲风 |
302
+ | `MUSIC_STYLES`(9 套配乐风格表) | `scorekit.py` | 配乐的 9 张硬编码表 |
303
+
304
+ **换成了什么(技术原语,不是模板)**
305
+
306
+ | 原语 | 作用 |
307
+ |---|---|
308
+ | `hsv_to_rgb` / `rgb_to_hsv` | 让配色可以是**连续空间**里的任意一点 |
309
+ | `_palette_from_hue(h,s,v,dark)` | 从色相/饱和/明度直接算出三段底色 |
310
+ | `_solve_role` / `contrast` / `LINT_LIMITS` | 语义色由对比度网格求解,每套都必须过 `theme_lint` |
311
+ | `visual_lint_language(vl)` | 对**任意**语言字典体检(不再是"某个 style id 的体检") |
312
+ | `musicgen.render(bpm/key/scale/mood/seed/progressions/motif/drums/voices)` | 配乐按参数现场编排 |
313
+ | `LAYOUTS` / `VISUAL_MOTIFS` / `VISUAL_TRANSITIONS` / `NUMBER_STYLES` | **能画什么**(绘制原语的名单),不是"长什么样" |
314
+
315
+ > 这条区分很重要:`motif.kind ∈ {brackets, hairlines, …}` 是"有哪几种装饰画法",
316
+ > 属于技术能力;而"cinema 这套风格用 letterbox + 3.0 字距"才是模板。
317
+
318
+ **"每条视频都不一样"现在是默认行为(这是最关键的一条)**
319
+
320
+ > 早先 `sample_designs` 完全由 `sha1(brief + index + seed + attempt)` 派生,默认 `seed=None`,
321
+ > 于是**同一个 brief 跑两次产出逐位相同的视频**——等于把模板藏进了哈希里,
322
+ > 用户要新片子只能改 brief 措辞。实测两次独立进程指纹完全相同(`31f64edd34e502f9` / `52d385953835379e`)。
323
+
324
+ 现在的语义:
325
+
326
+ | 调用方式 | 行为 |
327
+ |---|---|
328
+ | `sample_designs(brief, n)` | **每次新 nonce**,同一 brief 也得到不同设计(满足"每条都不一样") |
329
+ | `sample_designs(brief, n, seed=42)` | 固定 nonce,逐字节可复现(调试/回归/文档示例) |
330
+
331
+ 实测(同一 brief 连拍两次,各 2 个候选):
332
+
333
+ | | 配色底色 | 母题 | 转场 | 音乐 |
334
+ |---|---|---|---|---|
335
+ | 第 1 次 | (239,240,229) / (227,217,235) | rays / minimal | cut / wipe | hopeful 91 / hopeful 89 |
336
+ | 第 2 次 | (240,249,217) / (200,180,233) | rays / brackets | iris / paper | reflective 82 / uplifting 120 |
337
+
338
+ > 两组设计的"身份"(底色+母题+转场+音乐参数)完全不相交。
339
+
340
+ **顺带修掉的集成缺陷**
341
+
342
+ 1. **成片配乐换成 musicgen(与样音同引擎)**:v1.0.0 里 `previewkit` 用 musicgen 出 12 秒样音、
343
+ `render.st_score` 用 scorekit 出成片音乐,实测同设计下两者相关系数 **-0.0057**(两首不同的曲子)。
344
+ 用户看样音选方案、成片音乐完全不同,等于"确认"在音频上失效。现在两边同一个引擎、同一组参数。
345
+ 2. **`music_plan` 看不见设计里的音乐参数**:设计路径的音乐在 `script["_style"]["music"]`,
346
+ 而 `music_plan` 只看 `script["music"]`(film.json 顶层通常没有),于是**判定为"本片无配乐"**。
347
+ 实测设计里 bpm=122/mood=driving,整段配乐没出。现在两边合并,显式字段优先。
348
+ 3. **`resolve_style` 没有第三优先级了**:以前会回落到"按关键词自动选风格",那是模板库核心;
349
+ 删掉后必须明确报错并给 propose.py 两步迁移指引(不静默回落)。
350
+ 4. **`--jobs >1` 子进程按 design.json 重解设计**:worker 早先重解时没传 work,读不到 design.json,
351
+ 会画出另一套主题(实测并行成片 VMAF 1.49 vs 单线程 97.15)。
352
+ 5. **`selfcheck` 遇 SystemExit 会静默中止**:被调代码用 SystemExit 报用法错误时,
353
+ 自检直接退出、后面的用例一条都不跑,输出留在半截。现在记 FAIL 并继续。
354
+
355
+ **验收**
356
+
357
+ | 项 | 实测 |
358
+ |---|---|
359
+ | `selfcheck` | **57 PASS / 0 FAIL / 1 SKIP**(SKIP 为可选模块缺失,属预期) |
360
+ | `ci_check` | **12 / 12 PASS** |
361
+ | 零模板守门(第 57 条) | 仓库里不许有 `presets/` 与 `presetkit.py`;同 brief 两次必须不同;给 seed 必须可复现 |
362
+ | 连续配色(第 25 条) | 12 套现场配色全部过 `theme_lint`,底色谱相覆盖 >= 180° |
363
+ | 视觉语言(第 53 条) | 12 个设计的结构签名两两不同,>= 6 种母题 |
364
+ | 端到端 | 提案 -> 确认 -> `--stage score`:引擎=`musicgen`,`music.wav` 2.00s 峰值 0.501 |
365
+
366
+ ### 审查修复:用户说的话必须真的生效(v1.0.1 收尾)
367
+
368
+ > 这一节是 v1.0.1 提交后的**全面自查**产出。不是锦上添花——发现的都是"功能号称支持、实际没生效",
369
+ > 正是本项目反复要根除的那类问题。
370
+
371
+ | # | 缺陷 | 症状 | 根因 |
372
+ |---|---|---|---|
373
+ | 1 | **brief 里的要求被静默吃掉** | `--brief "深蓝配色的科技解说"` 采出 h087 / h316(橙 / 紫) | `propose._brief_of` 把整句塞进 `topic`,`must_have`/`avoid` 恒为空数组。designkit 明明有 32 词的关键词表和按色相窗口取色的能力,**没人喂给它** |
374
+ | 2 | **配色约束在连续空间里失效** | 修完 ①之后仍报"重采样 50 次仍不满足" | 约束解析还在按 8 个族名做**枚举过滤**,而配色已改成连续色相 —— 枚举过滤会把可选范围清空 |
375
+ | 3 | **逐 stage 单独跑必挂** | `--stage frames --work work` 报"找不到设计";`--stage sfx` 直接 NameError | `resolve_style` 有 5 处调用没传 `work`;`_sfx_seed_context` 函数里压根没有 `work` 这个名字。`--stage all` 时前面的 stage 已把 `_style` 塞好,**缓存把问题整个盖住** |
376
+ | 4 | **自动选风格死代码残留** | 调用即崩 | `stylekit` 里还留着 `score_styles`/`pick_style`/`_kind_profile`,它们引用已删除的 `STYLES`/`THEMES`(`THEMES[STYLES[sid]["theme"]]` -> KeyError) |
377
+ | 5 | **自检里有一条假断言** | 永久通过、什么也不验 | 第 25 条断言 `len(PALETTE_FAMILIES) == len(_LEGACY_FAMILY_ANCHOR)`,而那两个名字是**同一个对象** |
378
+ | 6 | **自检第 43 条查错了地方** | 重构后误报 | 它靠 grep `render.py` 源码里有没有 `ST.theme_lint(plan.get("theme")` 来判断体检是否生效。体检逻辑搬进 `designkit` 后断言就失效了 —— 查"哪个文件里写了这行"本来就不是好断言 |
379
+
380
+ > 修法(按重要性):
381
+ >
382
+ > - **①③** 是最严重的两条:① 让用户的输入形同虚设;③ 让"分阶段跑"这条正常工作流随时崩。
383
+ > - ① 现在按标点切句、逐句识别关键词,含否定词的片段归 `avoid`("不要放射线"),其余归 `must_have`;
384
+ > 一句都没认出来的原样保留,顶层会提示"这条没生效",**绝不静默丢弃**。
385
+ > - ② 配色约束改为**色相窗口**:"深蓝"= 采 `h∈210°±26°` 且暗色底,窗口内仍是连续量;
386
+ > `avoid` 按色相距离排除(含环形处理:350° 与 10° 只差 20°)。
387
+ > - ③ 全部 `resolve_style` 调用补上 `work`;新增**第 59 条自检**用 AST 静态检查一遍
388
+ > (遍历 `render.py` 的每个函数,凡是调 `resolve_style` 就必须带得上 `work`),
389
+ > 这类"只在单跑某 stage 才发作"的 bug 以后进不来。
390
+ > - ④ 三个死函数整体删除(不是留着报错——留下"看起来还能调用"的入口,下一个人会以为它是活的)。
391
+ > - ⑤⑥ 换成**真的会失败**的断言:⑤ 改成验"12 套配色色相两两不同"(证明配色是连续的、不是清单);
392
+ > ⑥ 改成把一份低对比度设计喂给 `design_to_plan`、必须抛异常(验行为而不是验源码文本)。
393
+
394
+ **实测(修复后)**:
395
+
396
+ | 输入 | 结果 |
397
+ |---|---|
398
+ | `--brief "深蓝配色的科技解说"` | 候选 h220 / h228,底色 `[8,11,17]`(深蓝) |
399
+ | `--revise "换成深蓝,不要放射线"` | 候选 h199 / h217,均在 210°±26° 内 |
400
+ | `must_have: ["暖纸"]` | 浅色 28° / 39°,底色 `[239,230,222]` |
401
+ | `avoid: ["薄荷"]` × 4 次 | 全部避开 168°±26° |
402
+
403
+ **验收**:`selfcheck` **59 PASS / 0 FAIL / 1 SKIP**(55 → 59,新增 59 逐 stage / 60 brief 约束;
404
+ 实际条数 60,减掉 SKIP 那条即为 59);`ci_check` 12/12;金帧 3/3;`scorekit --selftest` 19/19;
405
+ 零模板端到端出片门禁 `pass 12 / fail 0`、VMAF 97.08。
406
+
407
+ ### P3 拆地基(本版一并交付)
408
+
409
+ | 动作 | 结果 |
410
+ |---|---|
411
+ | 8 套风格下沉 | `skills/video-studio/presets/*.json`(8 个文件,往返无损导出,全部 `auto:false`) |
412
+ | 新增 `presetkit.py` | 读预设、转 design(`preset_to_design`),**8/8 套预设都能过 `validate_design`** |
413
+ | `propose.py --preset` | 以预设为起点出候选:配色保持预设精确色值,只扰动母题/转场/版式/编号 |
414
+ | legacy 路径删除 | 不写 `style` 或写 `style:"legacy"` → **报错 + 两步迁移指引**(不再静默落老外观) |
415
+ | `pick_style` 自动选型停用 | 仍工作但打 deprecation 警告(保留是为了不让 100+ 个调用点同时崩) |
416
+ | `scenes.draw_frame` | 删掉不可达的 legacy 分支 |
417
+
418
+ > 实测:`selfcheck 55/55 PASS`、`ci_check 12/12 PASS`(改写 4 条断言以匹配新语义:24 / 26 / 28 / 46)。
419
+
420
+ ### 未完成(P1/P2/P4/P5,见 `.team/proposal-v1.0-proposal-first.md` §十)
421
+
422
+ | 期 | 内容 | 状态 |
423
+ |---|---|---|
424
+ | P0 | 提案契约 + 离线预览 + `--stage propose` | **本版** |
425
+ | P1 | 提案闸增强(签核绑定、stagewatch 结构化、报告/账本行) | 待做 |
426
+ | P2 | 参数化配乐编排器(替代 9 套 `MUSIC_STYLES`) | 待做 |
427
+ | P3 | **拆除 legacy 内核 + 停用 `pick_style` + 删 8 套表** | 待做 |
428
+ | P4 | 声线自校准(补回 6.0 dB 响度补偿跨度)+ `presets/` 下沉 | 待做 |
429
+ | P5 | 重建验收口径(金帧绑 fixture design、自检 53/55 改口径、文档演示图) | 待做 |
430
+
431
+ > 升级提示:本版**不删任何东西**,已有项目不写 `design.json` 时行为与 v0.9.1 一致。
432
+ > "删表"发生在 P3,届时会另发版本并在本文件写明迁移动作。
9
433
  ## 0.9.1 — 把装饰母题烘焙进底版:消掉 5.6 ms/帧的固定开销(2026-11-10)
10
434
 
11
435
  > 0.9.0 落地后量了一笔账:单帧预算 25 ms,而"逐帧把全屏 RGBA 母题 paste 到画面上"