storyline/escape/simon 首批之后的推广收官:15 款全部按 26 号模式运行 (发言权/回执/三档提示/迷茫接管/手势阶梯/断点复述),旧「双击重听/格挡/ 菜单确认」等反模式口径全线废除,开场零规则念白(首句即动手)。 游戏层(12 款):rpg(格挡=前摇窗口内再单击)/ rhythm / space / radio / dungeon / abyss / detective / cipher / teahouse / keeper / bridge / hunt(联机切片): - 每款 game.js 接引擎六模块 + makeInputPipeline(doubleTapWindow:0); 四向玩法类 swipeCore + 长按帮助;双击是玩法的(space 护盾 / hunt 旋风斩)按手势阶梯阶段 3 分档恢复双击窗口(doubletapCore) - 每款 playtest 重写:玩法断言 + §6.2 乱点机器人(无声操作 / 被动 原句重复 / 打断失败 = 0);15 款 3900+ 断言全绿 - 12 份 docs/design/26-apply-*.md 施工单 + tools/voice_lines_26/ 台词清单 + voice-ids 文档同步 + 转写会话剧本按新口径重写 引擎层(web/engine/,6 处修复):pipeline 增 doubletapCore;novice teachLine 带 voice/ 前缀(此前教学句被静默丢弃);confusion.record 前置到新手门(锁定手势的乱点也计信号);InvalidHints 播完恢复解释前 状态(根治孤儿 BUSY);WaitPoint takeover(2) 台账去重;台账只在 speak 真正播出时登记。遗留引擎债登记 roadmap §3.1。 语音资产:台词表合并(新增 ~250 系统句 / 改 ~50 旧口径 / 删 ~110 废止句;合并器 tools/merge_voice_lines_26.py + 引用校验器 tools/validate_voice_refs.py),GPU 重录 1536 wav → 4204 ogg/m4a 落地,check_all_assets + validate_voice_refs 双绿;键盘变体同步扩表。 大厅/文档:15 款统一新交互口径(「新交互」徽标退役);README / HANDOVER / rpg README 旧口径修正;roadmap P0-P3 置完成。
180 lines
15 KiB
Markdown
180 lines
15 KiB
Markdown
# 26-apply · 《暗码》重构施工单(26 号文档落地)
|
||
|
||
> **设计单正文见 [19-cipher.md](19-cipher.md) 附录 C**(一句话核心动作 / 状态 /
|
||
> 手势表与解锁 / 状态×手势矩阵 / 等待点 / 首 3 分钟脚本 / 失败表 / 台词账目 /
|
||
> 断点复述),本施工单不重复,只写:实现接线(§1)、台词类别映射(§2)、
|
||
> 偏差记录(§3)、测试记录(§4)。交互规则权威来源:
|
||
> [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md)、
|
||
> [00-global-ux.md](00-global-ux.md)。台词登记 [19-cipher-voice-ids.md](19-cipher-voice-ids.md)
|
||
> R4 节;清单 `tools/voice_lines_26/cipher.md`。
|
||
> 范式对齐 [26-apply-storyline.md](26-apply-storyline.md) / [26-apply-radio.md](26-apply-radio.md)
|
||
> (叙事型主参考;OP/WIND 双音色组与 radio 的 HOST/PROD 同构)。
|
||
> 本次只动交互层:电文生成(4 封固定字符)、摩尔斯运行时合成(morse.js)、暴露度
|
||
> 隐藏账本、4 结局、OP/WIND 双音色一概不动。
|
||
|
||
## 1. 实现接线(引擎六模块,对齐 storyline/radio 参考)
|
||
|
||
- **装配**(game.js 构造器 + index.html):`this.receipts / this.floor / this.confusion /
|
||
this.invalidHints(= new InvalidHints().bind(floor))/ this.ladder` 全部暴露;
|
||
`audio.setRateBoostEnabled(false)`(长按不是玩法、也不触发旁白快进;摩尔斯 u 恒定
|
||
不受 settings.speed)。index.html 照 storyline/radio:`makeInputPipeline({ floor,
|
||
confusion, receipts, invalidHints, game, ladder, opts: game.PIPELINE_OPTS || {} })` +
|
||
`attachGestures(root, pipe, { doubleTapWindow: 0 })`——叙事+解码型无核心玩法手势
|
||
opt(doubletapCore/swipeCore/longpressCore 均 false),双击未解锁折算单击由管道
|
||
新手门承担。
|
||
- **onGesture 收口**:返回 `'ok' | 'hold' | 'invalid' | 'silent-ok'`(摩尔斯即内容音
|
||
的操作给 hold 附 ok 回执;无 silent-ok 档位用法——全部操作都有回执或内容音)。
|
||
`stateName()` → menu / ask / decode / confirm / settle / story / choice / night;
|
||
`invalidHint(state, g)` → locked:up=inv_up_locked、locked:down=inv_down_locked、
|
||
locked 其余=gd_invalid;left/right:menu=null(切项合法)、settle=sys_hint_again_full、
|
||
其余=inv_nosel;默认 gd_invalid。
|
||
- **floor 状态流**:叙事/提示链 `floor.speak`(critical:sys_first_tap、dv1_teach、
|
||
揭晓/收尾链首句 dv1_right / d9_send / d9_send_e / d9_caught_e / d9_sleep / d9_sleepe
|
||
——判定出口 800ms 手势死窗的 26 号等价物:抢拍 tap 给 busy 回执不吞揭晓)。
|
||
摩尔斯排程 = BUSY(手势 busy 回执,G4 首播保护的等价物):RX 首播 `_rxGuard` 保护窗
|
||
(floor BUSY + 作废令牌)、T4 danger 拍前置 2s(step_halt 渐强 + danger 签名/震同窗,
|
||
直置 floor.state,见偏差 7)、两段确认电文重播(confirm 等待点在挂但摩尔斯可先起)。
|
||
等待点 8 个全配 full/short/silenceMs/expected/onDemo(19 号附录 C §5 表 + ask/menu)。
|
||
- **morseGate(19 号 §3.4 R1-R5)**:`_busyVoice() = floor.speaking ||
|
||
audio.isVoicePlaying()`(整条链含句间空隙都算语音在播),在播则挂队 150ms 轮询补发;
|
||
候选预听链 = 候选摩尔斯(620Hz / u=110 / 字母隙 5u 对照档)→ 静默一拍 ≥500ms →
|
||
cand_* 词义句(给耳朵留对照时间);转写账本 `morseLog`(kind/word/preset/逐元素
|
||
at/endMs,被掐断时收敛 endMs,零重叠断言口径)。
|
||
- **BGM dip(R3 + §6.1)**:引擎 duck 独占 _bgmBus,游戏层在 _bgmBus → _bgmPrefBus
|
||
之间插私有增益节点:摩尔斯排程期间 ×0.5(−6dB,排程到 endAt 自动恢复)、MENU 同曲
|
||
低响度 ×0.4,复合相乘。
|
||
- **confusion**:信号由管道喂(含锁定手势,2026-10 修复后锁定门在 record 之前);
|
||
`onConfused → 当前等待点 takeover(lvl)`(引擎 takeover(1/2) 已带 30s 台账去重,
|
||
游戏侧零自研路径);双击重听/再说一遍喂 recordReplay;L3 `onDemo` 分档:intro/
|
||
continue 替玩家走一步、choice 只复述不替选、decode = 慢放电文 0.5× 一遍(u=160、
|
||
字母隙 6u)+ T1 附免费教学页(hint_t1,不扣页数,Prime→Teach 兑现)再交还浏览。
|
||
- **novice 阶梯**:T1 译对 = `ladder.onGoal()`(阶段 1),返回的 teachLine **已带
|
||
voice/ 前缀**(2026-10 novice.js 修复,直接进 floor.speak,不要再加前缀)挂
|
||
dv1_right 揭晓链尾;一局结束 = `ladder.onGameDone()`(阶段 2/3),教学句挂结局链尾,
|
||
一局只报一次(_gameDoneCounted);第二夜起 `goalMet = ladder.stage() >= 1` 跨局
|
||
记住不重教(做对过的事不教第二遍,原则 11)。
|
||
- **存档(§8.1)**:schema 1、summary 人话必带(`第{N}封·{名} · 暴露:{档} · 结局{k}/4`
|
||
/ 收台 `已收台·{结局名} · 结局{k}/4`);写入时机 = 每封 RX 进入 / NIGHT 进入 /
|
||
每个抉择结算后 / ENDING 进入 / 菜单「保存并回大厅」。断点回来 5s 内
|
||
sys_continue_ask → 单击恢复(DECODE 恢复链 prog_t → book 档 → 重挂浏览 + 自动
|
||
预听当前候选,30s 台账内不重复;gd_tune2 已停用)/ 右滑新夜班。
|
||
- **环境音**:静电底噪 static_bed(RX/DECODE)与值班室 amb_room 为游戏层 buffer
|
||
loop(sfx 层无 loop API,持句柄起停;无头测试 ambLoopEnabled=false 防周期定时器
|
||
不排水)。
|
||
|
||
## 2. 台词类别映射(瘦版;全量登记见 19-cipher-voice-ids.md R4 节与 `tools/voice_lines_26/cipher.md`)
|
||
|
||
- `critical`:sys_first_tap(开场 ≤3s 唯一一次指令)、dv1_teach(改写版「向右滑,
|
||
听下一个词。」,19 号附录 C §6)、揭晓/收尾链首句(dv1_right、d9_send、d9_send_e、
|
||
d9_caught_e、d9_sleep、d9_sleepe——判定出口死窗等价物,首句 <3s)
|
||
- `content`:b_open/b_goal/b_chief(移驻 NIGHT(1) 链首)、n1..n3、rx1..4_in、rev_*、
|
||
r*/m*_chief_c/g9_*/p2..p9/r1a..r4b、hint_t1..t4、cf_confirm、sys_turn_teach、
|
||
sys_unlock_*(阶梯教学)、sys_menu、sys_menu_resume/book/restart/lobby、
|
||
sys_new_night、prog_t1..t4、book_3..0、exp_calm/low/mid/hot、end_c1..c4、
|
||
e1..e4 主句 + ep_*(OP/WIND/处长按 19b 原组)
|
||
- `hint`:sys_hint_continue_full/short、sys_hint_choice_short、sys_hint_again_full、
|
||
gd_tune1 / gd_tune1_short、cf_hint_full/short、sys_only_choice、sys_demo_continue、
|
||
sys_demo_choice、sys_continue_ask、sys_skip
|
||
- `hint 前缀`:sys_say_again「再说一遍:」(主动重听,全项目一句)
|
||
- `feedback`:inv_up_locked、inv_down_locked、inv_nosel、gd_invalid(改写版补
|
||
「单击,继续。」)、dv1_wrong(T1 译错即时归因真话,摩尔斯写死句内)
|
||
- 音色组:系统/交互层句子全部走 **WIND 北风组**(北风电台=上线联络+系统音,radio
|
||
PROD 同型);剧情句沿用原组(OP 报务员 / 处长 NPC 平铺)。
|
||
- 账目:新增 20 / 修改 3 / 删除 10,主句 132 → **142**;`_k` 变体 11 → 24 对
|
||
(`web/harness/voice_text.json` cipher 主表已同步 142 条)。
|
||
|
||
## 3. 偏差记录(与 26 号/19 号附录 C 的落地差异及理由)
|
||
|
||
1. **DECODE 左右滑切候选返回 hold(附 ok 回执)而非设计单的 silent-ok**:管道无
|
||
「silent-hold」档——silent-ok 会被视为完成当前等待点(摘掉浏览点,三档提示计数
|
||
清零),而切候选没有完成这个点;取 hold 保住等待点,回执由摩尔斯预听的内容音
|
||
+ ok 双保险(§4.2「已有明显内容音」的反向适用)。
|
||
2. **上滑状态链三句(prog_t → book 档 → exp 档),无「动作」尾句**:19 号附录 C §3
|
||
写「第几封。密码本余页。暴露档。左右滑听词。」——落地与 radio 同型(动作由等待
|
||
声/等待点提示承担,原则 3「一句话」);DECODE 态要听词时的动作指令由 decode
|
||
等待点三档提示(gd_tune1 / gd_tune1_short)就近给。
|
||
3. **无 sys_praise「对。」**:19 号附录 C §6 ≈25s「『对。』dv1_right」——首正反馈由
|
||
dv1_right 本句(「货船,译对了。」)+ success 震承担,不再单设肯定句(避免
|
||
「对」与「货船,译对了」连播两拍;radio 的 sys_praise 是其主播线无归因句所致)。
|
||
4. **开场叙事移驻 NIGHT(1) 链首**:§4.10 首夜第一句只剩 critical 指令 + 状态教学,
|
||
b_open/b_goal/b_chief 移驻 NIGHT(1) 链首补全叙事、sys_skip 每夜一次挂链尾;
|
||
b_rule(规矩念白=重听承诺,随双击教学废止)、gd_brief、dv1_teach2、dv1_ask 停用
|
||
(dv1_ask 仍留在 TELS 数据源但 T1 走 dv1_teach 改写版,不可达)。
|
||
5. **阶段 3 双击语义分态**:19 号附录 C 只写「重听电文原速;再请求=慢放」——落地:
|
||
DECODE = 重听电文(dtCount 连击:第 2 次起 0.5×,其他手势重置);叙事/菜单态 =
|
||
等待点 requestReplay(「再说一遍:」+完整句)或无等待点时重播当前节点(NIGHT =
|
||
重播夜链、ENDING = 重听结局链);RX 态 invalid(引入链一句在播,等待点不存在)。
|
||
6. **翻密码本入口 = 菜单项(阶段 2)+ 迷茫 L3**:长按捷径废止(长按 → invalid +
|
||
gd_invalid 解释一次);19 号 §7「页数用尽再翻 = paper_page + 无语音轰炸」照旧;
|
||
非破译态选「翻密码本」= gd_invalid(feedback 通道,话轮保住)。
|
||
7. **danger 拍前置 2s 直置 `floor.state = 'BUSY'`**:引擎无「演出 BUSY 定时」公开
|
||
API(floor 只有 speak/yieldTurn/enterAction),游戏层直置 + seqToken 作废令牌,
|
||
2s 后 _speakPayoff 接管(代码注释已标;dungeon 同款直置先例)。**引擎建议**:
|
||
floor 补 `busyFor(ms)` 公开演出窗 API。
|
||
8. **摩尔斯首播保护窗**:`_rxGuard` + floor BUSY(管道路径 busy 回执)双保险;
|
||
会话/直调(transcribe 的 onGesture 直调不走管道)由 _rxGuard 返回 silent-ok
|
||
兜底不推进状态(G4 轻震等价物移到回执层)。
|
||
9. **intro 等待点 full=null**:指令句刚说过(§4.10 找猫示例同型),静默第一档直接
|
||
short(sys_first_tap_short);第二夜起 newNight 只播 sys_new_night 不再重播
|
||
sys_first_tap。
|
||
10. **menu 等待点 short 复用 gd_tune1_short「左右滑。」**:菜单三档提示省一句资产
|
||
(full=sys_menu 报幕句刚播过,30s 台账自动降档;两处「左右滑」语义一致)。
|
||
11. **confirm 等待点 onDemo = 替确认(_settleDecode)**:迷茫兜底要能推进(19 号
|
||
附录 C §5「替确认」);choice 的 onDemo 不替玩家做有后果的抉择(sys_demo_choice
|
||
复述两向后交回);settle 的 onDemo 只给提示音(不替玩家开新夜班)。
|
||
12. **m4_chief_a/b 登记未接线**:19b M4 组的处长试探 ①/② 在 26 号改造前(HEAD)即
|
||
未进节点表(rev4 → @p8 直连),本轮维持现状不动玩法;台词资产维持登记不删。
|
||
13. **乱点机器人白名单口径(§6.2 被动原句重复=0 的「主动查询」判定)**:a)「再说
|
||
一遍:」前缀后的完整句;b) 玩家 8s 内主动操作索引的句子(候选词/状态句/
|
||
dv1_wrong/dv1_right/cf_confirm/hint_t/rev_ 归因与确认句)。
|
||
|
||
## 4. 测试记录
|
||
|
||
- 无头 playtest:`tools/node/bin/node web/cipher/test/playtest.mjs`,**16 段、断言
|
||
191 条全绿、退出码 0**(2026-10-02;`game.floor.bedEnabled = false` 关等待声周期
|
||
定时器,mock 口径 2s/句)。覆盖:
|
||
1. 开场 §4.10 + RX(1):第一句 critical 指令(抢拍 tap=busy)、3s 内动手、
|
||
gd_brief/b_rule/gd_tune2 零播出、首候选预听 ≤12s、BAY 编码/时值逐元素
|
||
(§2.2 表)、R3 摩尔斯 dip 0.5、首播保护窗 tap/left=busy;
|
||
2. DECODE 浏览 / 两段确认 / T1 译对:右滑 ×3 循环候选、确认取消回浏览、揭晓首句
|
||
critical、success 震、sys_unlock_up 挂链尾、抉择点「再说一遍:」+ 完整句;
|
||
3. T1 译错即时归因 + 无限重选零惩罚(expose 不动、t1 恒收敛);
|
||
4. 剧本 A(E1 全对全右 p9 左,guide=full):NIGHT(1) 开场叙事移驻各一次、四封
|
||
编码逐元素断言(TOP/HAND/SPY)、danger 拍(step_halt+签名+震同窗)、ep 逐封
|
||
点名 flags 单源、end_c1 分档计数、summary、手势数 ≤170(实际 24)、第二夜
|
||
不重播 sys_first_tap;
|
||
5. 剧本 B(E2)/ 剧本 D(E4,全译错全左 endingPre=8)playNight 驱动;
|
||
6. 剧本 C(E3):暴露度逐 NIGHT 验算(0→2→5→8,§5.2 单源表)、跨档播报
|
||
exp_low/exp_hot 各一次、warning 震、同档零重复;
|
||
7. 手势阶梯:locked 首次解释一次(原则 7)、未解锁双击折算单击、上滑状态链
|
||
G6 顺序、sys_help 零播出;
|
||
8. 三档提示:decode 10s/25s、continue 13s/30s、choice 25s(台账降档直接 short)
|
||
/50s(>30s 给完整句)、两级用尽即停;
|
||
9. 迷茫接管:L1 短句 → L2 只放行 expected(L3 白盒 takeover(3):慢放 u=160/
|
||
字母隙 6u + T1 免费教学页不扣页数,演示后重挂浏览);
|
||
10. 菜单(seed 阶段 3):左右滑循环换项、上滑重听说明+设置提示、下滑关闭回
|
||
DECODE(局面复述 + 30s 台账内自动预听不重复)、「翻密码本」= 回 DECODE 提示
|
||
流程(T1 免费页 + paper_page)、「重新开始今晚」= 新夜班、「保存并回大厅」=
|
||
leaving + 存档;MENU 同曲低响度 dip 复合断言;
|
||
11. 阶段 3 双击:重听电文原速 → 再请求慢放 0.5×(字母隙 960ms 时值断言)、
|
||
切候选重置连击;
|
||
12. 断点复述:预置存档 phase=DECODE(tel=3/hints=1/cand=1)→ sys_continue_ask →
|
||
单击恢复(数值全还原、恢复链 prog_t3 → book_1、gd_tune2 零播出、自动预听
|
||
SAND);右滑 = 新夜班标量重置;
|
||
13. §6.2 乱点机器人(120 步、seed 42):无声操作 0/114、第一次操作 2120ms ≤10s、
|
||
第一次成功 2120ms ≤20s、打断失败 0、被动原句重复 0(白名单口径见偏差 13);
|
||
14. morseGate:全剧本摩尔斯起振区间与 voice 零重叠、候选词义句全部 ≥500ms 接播;
|
||
15. 资产缺失:屏蔽 rev_top 仅 console.error/warn,Floor 单句哨兵续链不卡死仍到 e1、
|
||
gd_tune2/gd_prog1 零播出;
|
||
16. 键盘变体(input=key:gd_tune1 走 key/ 目录)与语速边界(speed=2.2 首播 u 仍
|
||
80ms)。
|
||
- 玩家感知转写:`web/harness/sessions/cipher.mjs` 已同步 26 号口径(开场第一下 tap、
|
||
主动重听、上滑状态链、走神听三档、结局后下滑逛菜单、`floor.state` 链级门控,
|
||
radio 范式),`tools/node/bin/node web/harness/transcribe.mjs cipher`(含
|
||
`--session sessionStress`)两剧本零死锁跑通(主线虚拟 174s 到 E1 并开第二夜 /
|
||
乱按 416s:解码 12 步、5 抉择、乱试 66 次、逛菜单 11 次仍到结局)。
|
||
`web/harness/voice_text.json` cipher 主表同步 142 条。
|
||
- 待生成资产:新增 20 + 重录 3(`tools/voice_lines_26/cipher.md`,主会话统一生成;
|
||
playtest 对缺失 voice 资产按可用 mock,与 TTS 生成解耦)。
|
||
- 真人冷启动:待测(26 §6.3,3~5 人)。
|