# 26-apply · 《守塔人》重构施工单(26 号文档落地) > **设计单正文见 [21-keeper.md](21-keeper.md) 附录 C**(一句话核心动作、状态×手势 > 矩阵、等待点配置表、首 3 分钟逐秒脚本、失败表——不在本文重复)。本文件是瘦版 > 施工单:只写实现接线、偏差记录、台词类别映射、测试记录。台词登记见 > [21-keeper-voice-ids.md](21-keeper-voice-ids.md) R2 增量节;清单见 > `tools/voice_lines_26/keeper.md`。交互权威: > [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md)。 > 范式对齐 [26-apply-storyline.md](26-apply-storyline.md)(纯叙事主参考)、 > [26-apply-radio.md](26-apply-radio.md) 与 [26-apply-abyss.md](26-apply-abyss.md) > (叙事型 + 双音色组同型——KEEPER/SHORE 与 HOST/PROD 同构;pendingChoice 缓存、 > 打断快照 `_chainSpeaking`、sessions 范式重点参考 abyss)。 ## 1. 一句话核心动作 **听一夜值守,单击继续;听到选项时左右滑:守灯还是救人、说出还是守住。** ## 2. 实现接线(web/keeper/game.js + index.html) 六模块(§4.1/§4.2/§4.3/§4.4/§4.5/§4.12)全部接通,玩法核心(风暴夜时间轴 INTRO→C1→CALM→C2→CALM→C3→p9→ENDING、回忆插叙三段、trust×truth 双标量、 9 抉择 4 结局、KEEPER/SHORE 双音色 + NPC 三代声、800ms 级结算保护、底噪互斥 vinyl/storm、雾角四处兑现、存档 summary)不动,交互层整体换成 26 号模式: - **接线**:`this.receipts = new Receipts(audio)`、`this.floor = new Floor(audio)`、 `this.confusion = new Confusion({ onConfused: (lvl) => waitPoint?.takeover(lvl) })`、 `this.invalidHints = new InvalidHints().bind(this.floor)`、`this.ladder = new GestureLadder()`(叙事类长按不占用:`setRateBoostEnabled(false)`,新手期 longpress 由管道判 invalid)。`index.html` 照 storyline 同构: `makeInputPipeline({ floor, confusion, receipts, invalidHints, game, ladder, opts: game.PIPELINE_OPTS })` + `attachGestures(root, pipe, { doubleTapWindow: 0 })`, 启动遮罩保留。doubleTapWindow: 0 → 未解锁双击由管道折算成 tap。 - **`onGesture` 返回值收口**(§4.2):`'ok'`(继续/抉择/菜单/状态)|`'hold'` (重听、菜单换项——等待点保留)|`'silent-ok'` 本作无(无即时内容音手势)| `'invalid'`(pipeline 给回执 + explainOnce + 迷茫信号 + settleWaitPoint(false))。 - **语音链 = floor.speak(critical/kind)**:节点链全部改走 floor;拆链实现句级 critical(abyss `_speakNodeVoices` parts 模式)。跳句/打断由 floor 收口: tap=consumed 跳本句、滑动=打断并执行、critical=busy——旧 PROMPT_RE 门 / 锚句 空点 beat 震 / F4 恢复锁 / F5 交接尾句保护 / G5 首播锁被结构性消解。 - **等待点(§4.4 附录 A)**:intro(6/15s)|continue(13/30s)|choice(25/50s, full=当前 pN 且链播即登台账)|settle(20/45s)|reopen(12/30s)|menu(20/45s)。 三档提示 + 30s 台账 + 主动重听「再说一遍:」全在 engine/hints.js。 - **开场 §4.10**:冷启动第一句 = `sys_first_tap`(critical 指令句,0~3s 动手); 第一下单击 → `[b_open, b_rain]`(雾角挂 b_rain 播完)→ `b_goal` 极短目标 → continue 等待点;`sys_turn_teach` 挂 amo_a 链尾、「轮到你」状态声首现教学, `sys_skip` 挂 amo_b 链尾(radio z1_a/z1_b 先例)。**b_rule 开场规矩念白与 gd_brief 操作念白废除**;重开新夜链 = sys_new_night → b_open → b_rain(雾角) → b_goal,教学句不再播(原则 11)。 - **手势阶梯(§4.3)**:第一次抉择(choose 首次)→ `ladder.onGoal()` 解锁上滑, 「对。」(sys_praise)挂下一段链头、教学句挂链尾;第一结局(enterEnding)→ `ladder.onGameDone()` 解锁下滑/第 3 局双击,教学句挂结局链尾。`teachLine()` 已带 `voice/` 前缀,游戏侧直接引用不拼前缀。 - **上滑「我在哪」(21 附录 C §3:时位句先行 → 信任档 → 一个动作)**: now 线 = `prog_m{act}` → `tr_{tier}` → `tru_{truth}` → 动作尾巴(choice 等待= status_choice / 其余= sys_hint_continue_full);MEMORY = `prog_mem_*` → `tr_*` → `tru_*` → `sys_mem_back`;ENDING = 结局主句 → `tr_*` → settle 动作句。 状态句全量 markHintSaid(原则 9);播完回原等待点(radio 口径 resumeTurn, 档位计数保留),等待点不在(旁白被打断/自动流)则按来源重建,不重播节点。 G6 顺序(prog → tr → tru)由 playtest 断言锁定;G2 的 tr_thin 附 warning 震 保留首夜首幕豁免(自然起点不是警告)。 - **断点复述(§4.12)**:每抉择结算、CALM/ENDING 进入、菜单「保存并回大厅」写 `soundgame.progress.keeper`(含 timeline 时空位 + goalMet/turnTaught/skipHintShown 教学标志;**summary 必带**;MENU 落盘记回原状态)。回来:sys_continue_ask 询问 (tap=继续上次 / right=新风暴夜);MEMORY 存档恢复链 = prog_m → prog_mem → 重播当前回忆句起链(首句 critical,F4 等价),≤5s 回到可玩。 - **迷茫检测(§4.5)**:confusion 信号(含管道在 invalid 时 recordInvalid、等待点 静默阈值喂 recordSilence)→ 当前等待点三级接管(L1 短句 → L2 只留期待手势 → L3 演示:intro/continue 替玩家走一步,choice 只复述方向不替选)。 ## 3. 偏差记录(与 26 号手册 / 21 号附录 C 的实现偏差) 1. **800ms 结算死窗升级为整句 critical**(abyss 先例):结局链首句(e1~e4_a)、 c3_dark(灯灭差分插播,auto+@x 尾句锁)、rev_teach(时空教学)、插叙锚句首播 (rev_g1/g4、rev_f1/f5、rev_c1/c5)、恢复链首句(_resumeGuard)整句 critical ——tap/滑动给 busy 回执不截链,覆盖旧 `_settleGraceUntil` 800ms 定时窗且更强。 插叙入口 600ms 静默拍期间 floor=BUSY,手势同样 busy 回执(死窗语义等价)。 2. **p* 选项句整句 critical**(radio 口径):p1~p7、p8_boat/p8_reef、p9a/p9b 首播 不可跳(tap=busy),链播即 markHintSaid——被动提示永不逐字复述选项。21 附录 C §8 台词表把 p* 归 content 指内容属性;矩阵 §4「p* 首播不可跳」以 critical 落地。 3. **旁白链滑动 = 缓存抉择 + 跳选项句**(abyss 模式,兑现矩阵 §4「缓存抉择,不 盲选」):旁白(含被打断快照 `_chainSpeaking`)中 left/right 若落在抉择点 (本节点 next 直入 p*),缓存 pendingChoice 并 playNode(p*);选项句整句 critical 播完当刻缓存生效(ui_confirm → 结果节点,W2 契约 + 盲玩公平)。非抉择点滑动 → invalid + `_recoverInterrupted` 交回 continue 等待点(零无声死锁)。 4. **MEMORY 打断恢复 = 重播当前回忆节点**:插叙是 auto 链不设等待点;滑动打断/ 双击重听/菜单关闭/上滑播完都从「当前回忆句」(memIdx,由 audio.play 钩子实时 维护)重播起链,复播不锁锚句(G5 复播可掐);链末照常自动回返现在线。 MEMORY 中 left/right = invalid(插叙无选择,21 附录 C §4)。 5. **CALM 幕间为自动流(BUSY)**:链播中 tap=跳句(consumed)、空档手势=busy; 滑动打断后不重交话轮——`_ensureWait` CALM 分支直接挂喘息定时器推进下一幕 (radio BRIDGE 同款)。跨档 tr_* 账目在链构造时结清(radio enterBridge 口径; 不再逐句起播钩子)。 6. **等待声**:六等待点统一引擎合成「嘟—嘟」bed;插叙/幕间/危机拍为 BUSY 演出 态无等待声(原「统一 8 音词表纪律、时空不换 earcon」继续成立——本作不新增 任何 earcon,时空签名全靠内容层底噪/BGM/锚句)。 7. **过渡音 idle 门简化**:memory_in/out、static_burst 只在语音空闲时播出(原 「挂队句间补发」依赖游戏层句级钩子,floor 时代无此钩子;调用点全在链入口前, 在播即丢弃,playtest 断言成对性不受影响)。 8. **`_chainSpeaking` 打断快照为游戏层补丁**(abyss 同款):pipeline 的 floor.handleInput 对滑动「打断并执行」先杀链置 BUSY,游戏侧 floor.speaking 已 false——快照在 speak 置位、onDone/interrupt 清零,让被掐断的旁白链按矩阵 落「旁白中」分支(上游修复后可撤)。 9. **旧交互债务清除**:双击=重听节点旧口令废止(阶段 3 起=重听当前等待点完整句 /当前回忆节点);菜单改 00 号 §5.4(左右滑换项 + 单击确认,旧「单击循环+双击 确认」= 26 §7 反模式 5);guide 层看门狗双轨/无进展轨/sys_help 帮助溢出全部 废止(三档提示 + 迷茫接管 + 手势阶梯取代)。 ## 4. 台词类别映射(类别标注;增量清单见 `tools/voice_lines_26/keeper.md`) - `critical`:sys_first_tap(开场 ≤3s 唯一指令);p1~p7、p8_boat/p8_reef、p9a/p9b 选项句(整句,首播不可跳);rev_teach(时空签名教学,全局一次);插叙锚句首播 六句(rev_g1/g4、rev_f1/f5、rev_c1/c5);c3_dark(灯灭差分);结局链首句 (e1~e4_a);恢复链首句(F4 等价)。 - `content`:b_*、c*、amo_*、rev_* 中段句、e*_b、ep_*、prog_m1..3、prog_mem_g/f、 tr_deep/half/thin、tru_zero..full、lull_1/2、sys_turn_teach、sys_skip、 sys_unlock_*、sys_new_night、sys_continue_ask、sys_menu、menu_help、 sys_menu_resume/restart/lobby、sys_settings。 - `hint`:sys_hint_continue_full/short、sys_hint_choice_short、sys_hint_again_full、 sys_hint_menu_short、sys_only_choice、sys_demo_continue、sys_demo_choice、 sys_mem_back、status_choice、sys_say_again(「再说一遍:」前缀,engine/hints.js 播出)、sys_first_tap_short。 - `feedback`:inv_nosel、inv_mem、inv_generic、inv_up_locked、inv_down_locked、 sys_praise(「对。」首抉择链头)。 - 音色组:系统/交互层句子全部 `_SHORE`(岸台值班员=系统音,与 radio PROD、 abyss DECK 同型);剧情守塔人 `_KEEPER`、NPC 平铺(阿澈/父/祖)不动。通用系统 句与 STORYLINE_LINES 的 sys_* 段逐字一致;键盘变体照 KEY_LINES["storyline"]; 多音字「重听」→「重新听/再听一遍」。 - 完整登记(新增 24 / 修改 1 / 删除 8)见 `tools/voice_lines_26/keeper.md` 与 [21-keeper-voice-ids.md](21-keeper-voice-ids.md) R2 增量节。 ## 5. 测试记录 - 无头 playtest:`web/keeper/test/playtest.mjs` **272 断言全绿**(`tools/node/bin/node web/keeper/test/playtest.mjs` 退出码 0;`node --check web/keeper/game.js` 通过; `game.floor.bedEnabled = false` 防周期定时器不排水)。覆盖:§4.10 首 3 分钟 (3s 内动手、critical 指令句、规则念白零播出、开局 40s 内听到阿澈);剧本 A~F 六路径 四结局 + 信任/真相逐档验算 + 尾声回响差分;时空签名(vinyl 与 storm/room/engine 播放区间零重叠、memory_in/out 成对 ×3、BGM 切换、危机拍前置 2s);上滑状态链 G6 顺序(prog → tr → tru → 动作)与 tr_thin 首夜豁免;pendingChoice 时序 (旁白滑动缓存 → 选项句播完 → ui_confirm → 结果句);锚句首播 critical / 复播可掐;结算保护(结局链/c3_dark 首句 critical,幽灵手势不截链);等待点三档 (continue 13/30、主动重听「再说一遍:」);迷茫接管三级;手势阶梯(阶段 1/2 教学句挂链头/尾);§4.12 断点复述(ASK 左/右、MEMORY 恢复链时位句先行、F4 恢复首句锁);菜单(左右滑换项 + 单击确认 + 插叙中开关菜单底噪不丢);CALM 幕间打断后喘息照走;资产缺失续链(Floor 哨兵 3s 宽限);键盘变体路由; **§6.2 乱点机器人**:无声操作 0、被动原句重复 0、打断失败 0、第一次操作 ≤10s、第一次成功 ≤20s、乱按不锁死(零死锁收在合法等待态或结局);静态审计 (无运行时 id 拼接、8 句废止台词零引用、20 条游戏引用新台词待生成豁免 + 引擎侧 4 条入清单一致)。 - 玩家感知转写:`web/harness/sessions/keeper.mjs` 已同步 26 号口径(开场第一下 tap、floor.state 链级门控、菜单左右滑换项+单击确认、插叙中不动手——「回忆没有 选择」、CALM/危机拍 BUSY 段等它走完),`tools/node/bin/node web/harness/transcribe.mjs keeper`(含 `--session sessionStress`)两剧本零死锁 跑通(主线虚拟 290s 到 E1 灯与归人;乱按剧本 643s 同样走完结局链)。 - 新语音资产(24 句 SHORE 组 + 键盘变体)由主会话统一生成;playtest mock 与真实 资产解耦(voice 层按可用处理,Floor 哨兵兜底缺失)。 - 真人冷启动:待测(26 §6.3,3~5 人)。