下载 App
【聚光灯21天】DAY 12·配音、主题曲,和一次把载体换掉的转向
修改于10/2020 浏览 包含 AI 合成内容

今天很长。上午在填内容(配音、歌曲),下午做了一个改变项目走向的决定:
封存 UrhoX(Lua)版,重开 H5(Vite + TypeScript)版。
这不是推倒重来——规则引擎一行不改,原样复用。
一、配音:从"部分覆盖"到"全量覆盖"
DAY 11 的 voice-manifest 只认三类:主线章节 / 抉择卡 / 结局。真跑起来立刻发现——随机事件、危机事件、历史修正(反噬)全是哑的。
这三种恰恰是玩家最需要声音的地方:随机事件是突如其来的插曲,危机是生死关头,反噬是"你逆天改命的代价"——默片一样滑过去,情绪全丢了。
给 manifest 加一层结构
原结构 chapters[剧本][章节] 装不下"不属于任何章节的事件",加一层 specials:
specials[剧本][事件id] = { segs = {正文音频...}, opts = {选项结算音频...} }
覆盖三类:随机事件(R-1…R-12)、危机事件(C-1…C-DEATH)、历史修正(corr1…corr9)。
反噬事件的 id 是算出来的
修正事件本身没有 id——CORRECTIONS 是个数组,条目只有 title/segs。音频文件得有名字,所以用下标当 id;但引擎侧拿到的是条目对象,不知道自己是第几个,于是反查下标:
lua复制
lualocal corrId = nil for i = 1, #self.d.CORRECTIONS do if self.d.CORRECTIONS[i] == c then corrId = i break end end
反噬事件复用同一批条目,所以也查同一个 corrId。
结果
| 类别 | 条数 |
|---|---|
| 主线章节 | 933 |
| 随机事件 | 285 |
| 抉择卡 | 228 |
| 历史修正 | 133 |
| 危机事件 | 124 |
| 结局 | 82 |
| 其他 | 142 |
| 合计 | 1927 条 / 261MB |
二、五首主题曲
双轨设计:先伴奏,后开唱
每曲分两条音轨——无人声版(inst)与有人声版(vocal):
进对局 → 从头播无人声版 → 放完接有人声版 → 有人声版放完回无人声版,循环
为什么不直接循环人声版:人声版有词,反复循环会烦。所以第一遍先给伴奏(进入状态),第二遍开唱(点题),之后回伴奏循环(不打扰)。
两个工程细节:朗读时自动压低 BGM(DUCK 0.45);人声版提前 1.5 秒切回伴奏(LEAD),避开尾奏留白。还有一处——引擎不暴露音频精确时长,循环点判不了,所以时长是 ffprobe 量出来写死在曲库里的。
五首,五个切口
第一版我自己不满意——因为写成了生平全景:序章、少年、发迹、巅峰、败亡,一段不落。那不是歌,是百科词条。
重写时立了五条原则:史实准确 / 新切口 / 去辞藻堆砌 / 重韵律 / 立意明确。核心是去百科化——一首歌只取"那一刻"。
| 剧本 | 曲名 | 切口 |
|---|---|---|
| 李斯 | 《上蔡叹》 | 刑场临刑前,对儿子说的话 |
| 荆轲 | 《易水寒》 | 易水送别,唱罢就车而去 |
| 韩信 | 《淮阴月》 | 长乐宫钟室之死 |
| 陈胜 | 《无相忘》 | 誓言「苟富贵,无相忘」与其背叛 |
| 项羽 | 《不肯过江东》 | 乌江渡口,亭长撑船来迎,他不肯过 |
每句词都得有史书出处,存档里配了意象对照表(如"上蔡东门一条黄狗追一只兔"出《史记·李斯列传》)。
三、转向:封存 Lua 版,重开 H5 版

这是今天最重要的决定。
封存的是什么
封存日期 2026-10-12
封存标签 archive/urhox-lua-2026-10-11
冻结范围 scripts/(Lua)只读不改;prot/ 继续作为规则真源被 H5 复用
不是废弃,是封存。 Lua 版本身是完整的——规则引擎、五本剧本、60 张抉择卡、UI 全套、存读档、音频,全部跑通。
为什么不重写规则
因为规则早就不在 Lua 里了。
这个项目的规则真源一直是 JS(prot/engine.js)——Lua 是它的逐位移植,两者由 100 局对拍网守护(p2-replay:同种子 ⇒ 同轨迹)。所以换载体时:
规则引擎 → prot/engine.js 原样复用(H5 用 Vite alias 指过去,不拷贝)
剧本数据 → prot/lua-data/*.json 原样 import
对拍网 → 继续守护同一份代码
换掉的只是"渲染层",不是"游戏"。 这是这个项目架构上最值钱的一笔:DAY 6 把规则抽成 JS 真源 + Lua 移植时,没人想到它会在 DAY 12 救回整个项目。
换掉的理由
| 维度 | Lua 版(UrhoX) | H5 版 |
|---|---|---|
| 布局 | Yoga + NanoVG,踩了一路的坑 | 浏览器 CSS,原生能力 |
| 调试 | 只能看日志 | DevTools 断点 / 元素检查 |
| 验收 | 人肉点 | CDP 自动化(真 Chrome 跑) |
| 类型 | EmmyLua 注解 | TypeScript 强类型 |
| 工具链 | 受限 | Vite 生态 |
Lua 版 DAY 8–11 的日志里,一半篇幅在修布局:flexShrink 默认 0、百分比宽在 content-derived 父容器下塌成 auto、nvgIntersectScissor 只裁矩形、立绘贴图比例……这些坑浏览器里根本不存在。
但坑不是白踩的
封存说明里专门留了一节 “已验证的视觉规则(P3 复刻时照搬)”——Lua 版真机趟过的 9 条结论,H5 直接复用,不必重踩:
| 规则 | 说明 |
|---|---|
| 立绘底部锚定 | 按底边对齐,不随容器高度居中 |
| 贴图比例 | 卡片宽高按立绘 512:768 反推,勿用自适应宽 |
| 容器重建须 Destroy | 只 detach 不释放 → 长会话内存单调增长 |
| 跨重建复用的控件先 Remove | 罗盘等常驻控件须先摘除再销毁,避免连坐 |
| clearMain 先 cancel 再 clear | 渐显队列会回填内容,顺序颠倒会残留上一页文本 |
| … | (共 9 条) |
同一个坑不该踩两次。 换了载体,这些结论照样成立——因为它们是布局语义层面的,不是某个引擎的怪癖。
H5 版已经长什么样
h5/ 目录已开工(83 文件 / 2.4MB):
h5/src/
main.ts 145 行 入口 + 启动自检(证明 @engine / @data 两条链路通)
game/session.ts 794 行 会话层
shell/shell.ts 110 行 外壳
shell/scale.ts 122 行 分辨率缩放
ui/kit.ts 112 行 UI 套件
ui/radar.ts 105 行 雷达图
data/scenarios.ts 36 行 数据管线(@data → prot/lua-data/*.json)
styles/ 1440 行 art / base / game / stage
h5/tests/ 浏览器验收:chain / viewports / backtrack
h5/scripts/verify-data.mjs 构建期数据门禁
两处值得记的设计:
① 构建期数据门禁。 dev/build 之前强制跑 prot/p1-verify.js —— 数据一旦跑偏,dev 直接不启动,而不是等对拍到才发现。(这个门禁的存在本身有来历:DAY 10 曾因只重生成 golden.json、漏跑 p1-convert.js 而让 p1-verify 由绿转红。)
② 浏览器验收测试,不引 Playwright/Puppeteer。 Node 18+ 自带 WebSocket,直接说 CDP 就够,省下几百 KB 测试依赖——和首屏体积预算的目标一致。
三套测试各管一件事:
| 文件 | 验什么 |
|---|---|
| chain.cjs | 李斯第一章全链路(序章→词条→行动卡→关键事件→选项→属性增减→章末) |
| viewports.cjs | 8 种视口下布局不塌(窄屏右栏 13rem 定宽会挤爆叙事区,已加 880px 断点降级) |
| backtrack.cjs | 回溯按钮 → 确认浮层 → 回章首(验成就与偏离度不重置、配额按章计、coef 衰减) |
四、附带登记的已知问题
成就图标:64 个缺失
prot/test-ui-contract.js 是测试套件里唯一的红灯:89 通过 / 5 失败,5 个失败是同一契约"成就图标"按剧本各报一次,合计 64 处引用、60 个去重成就 id 缺图。
关键判断:assets/ach/ 目录根本不存在,且 git log --all -- assets/ach 命中 0 条——这一整类资产从未进入过版本控制。
所以不该把它塞进 EXEMPT 豁免表:逐项豁免是"个别项待交付"的性质,把 60 个 id 一次性写进豁免,会让这条契约永久变绿,把"整类资产缺席"这个真实缺口永久掩盖掉。
登记为已知问题,不阻塞 P2(P2 只做最小可玩闭环,不渲染成就面板)。修复留给 P3(美术复刻)。
差分测试抓不到的东西
封存说明里记了一条方法论级的教训:
对拍网能证明「JS 与 Lua 一致」,不能证明「两侧都对」。
实例:抉择卡重复结算——choiceUsed 只过滤强制发牌候选,未从普通抽牌池剔除,导致已落定的"一次性"卡被再次发出并二次结算。JS 与 Lua 完全同错,所以 100/100 全绿仍然放行。DAY 10 由独立审查发现并修复。
建议:H5 阶段为关键规则补不依赖 JS 的独立断言(例如"抉择卡落定后 N 轮内不得再出现于 offer")。
今天的数字
| 项 | 值 |
|---|---|
| 配音 | 1927 条 / 261MB |
| 主题曲 | 5 首 + 待机曲,每曲双轨 |
| Lua 版封存规模 | scripts/ 21 个 .lua / 30,393 行(冻结) |
| 规则真源 | prot/ 35 个 .js / 16,179 行(继续维护) |
| H5 版开工 | 83 文件 / 2.4MB |
| 新登记问题 | 成就图标 64 缺失(不阻塞) |
今天最值钱的一条
封存不是失败,是止损;而复用规则真源,是前面九天里最值钱的一次架构决定。
如果把规则写在 Lua 里,今天的转向就是九天的重写。但因为 DAY 6 把规则抽成了 JS 真源 + Lua 移植(由对拍网锁死等价),今天换载体时:规则一行不改,数据原样 import,测试继续守护同一份代码——换掉的只是渲染层。
“把什么东西放在哪里”,比"把什么东西写得多好"更早决定命运。
写歌也是一样:"写全"和"写准"是两件事。第一版五首我"写全"了(生平一段不落),却没有一首"写准"(没有切口)。删掉四分之三,只留那一刻,歌才立住。
明天
- H5 版 P2 闭环 —— 把 Lua 版跑通的那条链路(序章→…→下一章)在浏览器里重建
- 浏览器验收常跑 —— chain / viewports / backtrack 三套进日常
- 独立断言补强 —— 针对"两侧同错"这类盲区,补不依赖 JS 的断言
- 音频真机验收 —— 1927 条配音 + 5 首曲的播放/循环/压低


载体:转向 H5(Vite + TypeScript),规则引擎复用 prot/engine.js · Lua 版封存于 archive/urhox-lua-2026-10-11 · 状态:配音 1927 条 + 五首主题曲;H5 版开工 · 对拍网:100/100 全绿










