# 21 · 《守塔人》(keeper) — 孤岛灯塔叙事 RPG 设计文档 > **给开发的实现要点摘要** > 1. 叙事模式(声命线范式,radio/abyss/cipher 同骨架):节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;复制 `web/radio/` 结构起步,状态机 6 宏状态(INTRO/WATCH/MEMORY/CALM/ENDING + MENU 覆盖层)。 > 2. 手势零豁免:全部走全局默认映射(单击=继续/跳句、双击=重听、左右滑=抉择、上滑=状态、下滑=模态菜单 §5.2 约定、长按=引擎快进)。 > 3. 台词依据 [21-keeper-voice-ids.md](21-keeper-voice-ids.md)(148 句:双性别 121 + 平铺 NPC 27);NPC 电台/老唱片滤波在**资产侧** ffmpeg 带通(radio 先例),引擎零改动。 > 4. **本作核心差异层=时空双线**(§6):现在(风暴+电台静电)/回忆(老唱片底噪+唱片滤波)全靠音色+底噪签名区分;插叙=节点链 auto(≤6 句/≤90s),进入/回返有固定过渡音时序;**不新增 guide earcon**(统一 8 音词表纪律),时空签名住内容层(sfx+底噪+BGM+锚句)。 > 5. 信任/真相双轻量标量(radio rep 同款纪律,档位措辞逐值为真);软失败=「灯灭过一刻钟/他自己冲滩」叙事差分,零死亡零倒计时(P4);结算链 800ms 手势死窗(abyss/地下城 H1 同款)。 > 6. 章程任务 N4(research/new8/00-program.md §2):单局 15-25 分钟、9 抉择点(实选 8-9)、4 结局、分支局部化。 > 7. 断言清单住本文档 §9(abyss §21 先例;13 号文档 §23 指针待批次测试轮同步登记)。 ## 1. 概述 - **一句话**:风暴夜,孤岛灯塔的电台里,一声比一声近的求救,牵出你家三代守塔人的秘密——守灯,还是救人;说出真相,还是守住秘密。 - **题材(声音即世界)**:黑暗风暴里视觉归零,**电台人声=另一条命、风暴与雾角=你的处境、老唱片底噪=过去**——「用耳朵守一夜」与纯音频载体天然同构。两个时空只靠音色与底噪区分,声音本身就是叙事本体。 - **单局**:一个完整风暴夜(一条抉择路径)21-24 分钟;风暴从起到歇=天然三幕,分歧局部化在每幕内部;9 个抉择点(实选 8-9 个),4 结局。 - **核心循环**:值守(塔内叙事链)→ 电台(阿澈对白+抉择点)→ 抉择结果 →(关键抉择触发**回忆插叙**:唱针落下→父辈/祖辈片段→风声回来)→ 回到现在继续值守 → 幕间喘息。跨幕积累「信任」(阿澈信你有几分)与「真相度」(三代秘密翻开几层),风歇前 p9 决定秘密的归宿。 - **与 radio/abyss 的差异点**(骨架同、差异层不同):radio 靠电话音质立「声音即存在」,abyss 靠声呐双通道立「用耳朵找人」;keeper 靠**时空音频签名**立「回忆会开口说话」——插叙不是过场动画,是给现在抉择供货的信息与重量(发条知识=插叙2 兑现 p7 生死细节;祖父忏悔=插叙3 给 p9 完整真相可说)。 - **风险预防(本作特有,策划期显式设计)**:① 时空误认=剧情混乱——签名规格显式教学一次(rev_teach)+上滑时位句+中断复述带时空位(§6.4/§6.5);② 家族人物多音色——三代人+求救者共 3 个 NPC 音色 ≤5,区分度逐人标注(§5.3/voice-ids 头表)。 ## 2. 状态机 ### 2.1 状态列表 | 状态 | 内容 | BGM | guide.state | |---|---|---|---| | `INTRO` | 开场链:塔名口播 + 一句规矩 + 今晚目标 | `bgm/storm` | explore | | `WATCH` | 值守叙事:现在线对白链 + 抉择点 + 结果 | `bgm/storm` | explore;抉择点=choice | | `MEMORY` | 回忆插叙:auto 链 ≤6 句,首尾锚句,链末自动回 WATCH | `bgm/memory` | explore(不新增签名) | | `CALM` | 幕间:风暴进度通报(+跨档信任播报),自动链 ≤12s | `bgm/storm`(不切) | settle | | `ENDING` | 结局链 + 尾声回响(epilogue),结算态 | `bgm/dawn` | settle | | `MENU` | 模态覆盖层(任意叙事态下滑呼出,暂停一切链) | `bgm/storm`(低响度淡入) | menu | - CALM 自动链 ≤12s:foghorn → lull_N(风暴进度整句)→(跨档才播)信任档位句 → 自动进下一幕;不切 BGM(风暴进程靠 amb_storm 游戏层 gain 分幕:幕一 0.5 / 幕二 1.0 / 幕三 0.7)。 - 结局链/c3_dark/插叙链入口处盖 `_settleGraceUntil = now+800ms` 手势死窗(abyss §2.2 同款),窗内手势一律吞掉,关键叙事完整播出。 - MENU 打开时 `guide.disarm()` + 取消一切看门狗/自动链;关闭后重播当前节点(局面复述;MEMORY 中关闭=重播当前回忆节点,底噪仍 vinyl)。 ### 2.2 转移图(文本) ``` ┌───────────── 结算单击=新风暴夜 ─────────────┐ ▼ │ INTRO ──链完单击──> WATCH(C1) ──幕一链完──> CALM(1) ──────────┤→ WATCH(C2) │ …CALM(2) 同型)… │ p2左/p3左|p4左/p6左 触发: ▼ MEMORY ──插叙链 auto──> 回 WATCH(原 now 线续走) │ WATCH(C3) ──高潮链完──> CALM-lite(g9) ──> ◆p9 │ d9 结果链 ──> ENDING(e1..e4 + ep 回响链) ──┘ 任意叙事态(INTRO/WATCH/MEMORY/CALM/ENDING):下滑 → MENU(模态) MENU:单击=循环切换选项 → 双击=确认执行 → 上滑=帮助 → 下滑/「回到值守」项=关闭回原状态(重播当前节点) ``` - 全部叙事态为节点链驱动(radio playNode 模式);CALM 与插叙链 `auto:true`,WATCH 叙事节点链末等待单击(radio 实现核对 #1 同口径)。 - MEMORY 是显式宏状态(BGM/底噪/存档 timeline 都按它切换),但没有独立等待点:链 auto 完成即回 WATCH;插叙期间开菜单=暂停,关闭=重播当前回忆节点。 ### 2.3 每状态手势映射(6+2 全覆盖,零豁免) | 手势 | INTRO | WATCH | MEMORY | CALM | ENDING | MENU | |---|---|---|---|---|---|---| | 上滑 | 帮助+状态,随后重播当前段 | 帮助+状态(时位句+信任/真相档),随后重播当前节点 | 帮助+状态(**prog_mem_g/f 时位句先行**),随后重播当前回忆节点 | 帮助+状态,自动链继续 | 帮助+状态(结局名+信任档) | 帮助(sys_help) | | 下滑 | 模态菜单 | 模态菜单 | 模态菜单(停插叙链) | 模态菜单 | 模态菜单 | 关闭菜单 | | 左滑 | 无效 → error 震+gd_invalid | 无效 → gd_invalid(喂无进展轨) | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 | | 右滑 | 无效 → gd_invalid | 无效 → gd_invalid;ASK 态=新风暴夜 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 | | 单击 | 播放中=跳过本句;链末=进 C1 | 播放中=跳过本句(p* 与锚句首播不可跳);链末=继续 | 播放中=跳过本句(**锚入/锚出句首播不可跳**);无链末等待(auto 回返) | 跳过本句(自动链) | **再来一夜**(新风暴夜) | 循环切换选项 | | 双击 | 重听当前段 | 重听当前节点台词(含抉择提示) | **重听当前回忆节点** | 重听幕间链 | 重听结局链 | **确认执行**(菜单约定覆盖重听) | | 长按 | 引擎接管:旁白临时 1.7×(12 号 §2.2,叙事类不豁免) | 同左 | 同左 | 同左 | 同左 | 同左(快进菜单语音) | | 长按松开 | 引擎接管:恢复 settings.speed | 同左 | 同左 | 同左 | 同左 | 同左 | - 豁免登记:**无**。六个基础手势全部符合全局宪法默认语义;菜单走 §5.2 R3 模态约定(单击循环+双击确认)。 - ASK(存档询问态,radio 同款):单击=继续上次 / 右滑=新风暴夜(两者 runs 均 +1,cipher §8 统一口径);上滑/下滑/左滑/双击=无效(error 震+gd_invalid)。 - 抉择 prompt(voice/p1..p9 及 p8_boat/p8_reef/p9a/p9b 变体)播放中单击不可跳(PROMPT_RE 门)、左右滑缓存 `pendingChoice` 播完即生效(radio W2 契约照抄)。 - 继承 radio G5 跳过公平契约:**首播必完整(p* 首播不可跳 / 插叙首尾锚句与链末关键句保护 / ASK 恢复首句锁)、复播可即掐(锁不随复播重新武装、插叙中段句可跳、复播滑动照常预缓存)**。 - 无效手势零静默:所有「无效」格均有 error 震 + gd_invalid(onceKey 同局限次,F1.4 idle-only);longpress/longpressend/tapcancel 全状态合法(radio 实现修复 #2 教训)。 - 键盘通道:三通道并行,游戏代码零改动;操作提示句按 `gesture.inputMode()` 选 `_k` 变体资产(§7.3、voice-ids 集中分组)。 ## 3. 抉择树 ### 3.1 全局骨架(三幕共享主干,分歧局部化) ``` INTRO: gd_brief → b_open → b_rain → b_rule → b_goal ──链完单击──> C1 【C1 幕一·风暴起】教学幕 · 2 抉择(p1/p2)+插叙1 ──> CALM(1) ──> C2 【C2 幕二·风暴顶】2 抉择(p3/p4)+插叙2 +危机拍1 + p5 ──> CALM(2) ──> C3 【C3 幕三·风歇前】p6(+插叙3) + p7 核心 + 危机拍2 + p8 双形态 ──> 签收 ◆p9 ──> ENDING ``` 分支纪律:**每幕内部分支、幕末收敛**到 CALM/签收句;跨幕只传标量(trust、rev1/rev2/rev3、p7/p9 派生枚举),不生成组合节点。最坏路径与最好路径节点数同型。 ### 3.2 分幕结构(◆=抉择点,〔〕=条件差分,【插叙】=MEMORY auto 链) ``` 【C1 幕一·风暴起】 c1_tune → amo_a → amo_b → ◆p1 ├左 接住人: c1_hold → c1_holdo → c1_holde(trust+1)─┐ └右 要船位: c1_ask → c1_asko → c1_aske(即时纠偏)───┤ ▼ amo_c(回澜号伏笔)→ ◆p2 ├左 翻暗格: c1_open → rev_teach(全局一次) →【插叙1】→ c1_diary(truth+1)─┐ └右 守灯: c1_focus → c1_focuse ──────────────────────────────────────────┤ ▼ CALM(1): foghorn → lull_1 →〔跨档才播 tr_*〕→ C2 【插叙1·祖父的年代】memory_in → rev_g1(锚入) → rev_g2 → rev_g3 → rev_g4(锚出) → memory_out 【C2 幕二·风暴顶】 c2_back → amo_d → ◆p3 ├左 口诀: c2_song → c2_songo →【插叙2】→ c2_key(truth+1)─┐ └右 岸台: c2_shore → c2_shoreo ─────────────────────────────┤ ▼ amo_e → ◆p4 ├左 真话: c4_true →〔插叙2 未触发 ?【插叙2兜底】+c2_key : 直进〕→ c4_trueo(trust+1)─┐ └右 带过: c4_pass → c4_passo ───────────────────────────────────────────────────────────┤ ▼ 【危机拍1】hull_crack 前置2s 渐强 + danger 签名/震 → c2_crack → amo_f → ◆p5 ├左 堵漏: c5_patch → c5_patcho(trust+1)─┐ └右 救生衣: c5_suit → c5_suito ────────────┤ ▼ CALM(2): foghorn → lull_2 →〔跨档才播 tr_*〕→ C3 【插叙2·父亲的最后一晚】memory_in → rev_f1(锚入) → rev_f2 → rev_f3 → rev_f4 → rev_f5(锚出) → memory_out 【C3 幕三·风歇前】 c3_eye →〔rev2 ? c3_page → ◆p6 : m6_seal 封锁句(播后自动走右,不摆假选项)〕 ├左 读完:【插叙3】→ c6_reade(truth+1)─┐ └右 合上: c6_shut ────────────────────────┤ ▼ ◆p7(核心:守灯还是救人;提示句全表最长) ├左 离塔救人:〔rev2 ? c7_go(满发条,配 lamp_mech): c7_go2(半卷)〕 │ →【危机拍2】wave_wall 前置2s+danger → c3_wall → ◆p8_boat │ ├左 循渔歌: c8_song → c8_songo ─┐ │ └右 推坐标: c8_math → c8_matho ─┤ │ ▼ │ c9_return →〔rev2 ? — : c3_dark(灯灭过一刻钟,800ms 死窗)〕─┐ └右 守灯引渡: c7_hold →【危机拍2】c3_wall → ◆p8_reef │ ├左 老航道: c8_old → c8_oldo ─┐ │ └右 新礁缝: c8_new → c8_newo ─┤ │ ▼ ▼ 〔trust≥3 ? c9_land : c9_found〕──────────┴→ 签收(两路通用,电台或塔内对谈) 【签收】g9_in → g9_knot → ◆p9〔truth=3 ? p9a : p9b〕 ├左 说出:〔truth=3 ? d9_tell_a : d9_tell_b〕→ amo_g → d9_telle ─┐ └右 守住: d9_keep → amo_h → d9_keepe ────────────────────────────┤ ▼ ENDING: e*_a → e*_b(trust 档 × p9 四选一)→ ep_lead → ep_*(条件选播)→ ep_shore 【插叙3·祖父的忏悔】memory_in → rev_c1(锚入) → rev_c2 → rev_c3 → rev_c4 → rev_c5(锚出) → memory_out ``` ### 3.3 分支局部化说明 - 跨幕状态全部是标量:`trust`(int 0-4)、`rev1/rev2/rev3`(bool,插叙完成数=truth)、`p7`('boat'|'lamp')、`p9`(bool)。没有任何「幕 i 分支 × 幕 j 分支」组合节点。 - 条件差分全部是**节点内选播**(radio w1_say/w1_say2、abyss p7 双形态先例):插叙2 兜底(p4 左)、c7_go/c7_go2(rev2)、c9_land/c9_found(trust)、c9_return 后 c3_dark 插播(rev2=false)、p9a/p9b 与 d9_tell_a/b(truth)、ep_* 选播。树形不变。 - 树规模:148 句台词、约 75 个节点;任一路径到达全部 9 个抉择点,p6 在 rev2=false 时被 m6_seal 自动走右(实选 8-9 个)。 ### 3.4 四结局达成条件 | 结局 | 条件 | 主题 | 开环钩子 | |---|---|---|---| | e1 灯与归人 | trust ≥ 3 且 p9=左 | 人接住了,真相也说全了 | 「回澜号的账,翻开了第一页」 | | e2 守夜人 | trust ≥ 3 且 p9=右 | 人救了,秘密再压一代 | 「有些东西,再压一代人」 | | e3 半明半暗 | trust < 3 且 p9=左 | 你说了,他半信半疑 | 「碎片也是真话」 | | e4 各自的塔 | trust < 3 且 p9=右 | 隔海两塔,各自沉默 | 「谁也没再提那晚」 | - 尾声回响(P2 回响感):epilogue 逐项点名——阿澈(接回塔/自渡/天亮被找到,按 p7×trust)、灯的记录簿(满发条自转/灭过一刻钟/整夜未眨眼,按 p7×rev2)、祖父日志(见光/压回黑暗,按 p9×truth≥1)、父亲的班次(rev2=true 替他签字);岸台晨报恒播收尾(b_goal 货轮承诺闭环)。玩家每一层翻开的与守住的都被点名。 ## 4. 数值设计 ### 4.1 变量 | 变量 | 类型 | 初值 | 说明 | |---|---|---|---| | `trust`(信任) | int | 0 | 阿澈对你的信任,幕一/二积累,幕三不动 | | `rev1/rev2/rev3` | bool | false | 插叙1/2/3 完成位;truth = 三者之和 | | `p7` | str | null | 'boat'(离塔救人)/'lamp'(守灯引渡),核心抉择派生 | | `endings{}` / `runs` | map/int | — | 结局收集与重玩计数(存档展示用) | ### 4.2 抉择计分表(左=走心·翻开/说出 / 右=克制·程序/守秘) | 抉择 | 左(走心) | 右(克制) | 备注 | |---|---|---|---| | p1 接住人/要船位 | trust+1 | 0 | 左路阿澈声音稳住(首个正反馈);右路 c1_aske 即时纠偏不扣分 | | p2 翻暗格/守灯 | truth+1(rev1=true) | 0 | 左路=插叙1(日志残篇到手,缺最后一页) | | p3 口诀/岸台 | trust+1,truth+1(rev2=true) | 0 | 左路=插叙2 主触发(发条知识+缺页线索 c2_key) | | p4 真话/带过 | trust+1;rev2=false 时兜底插叙2(truth+1) | 0 | 插叙2 的第二把钥匙(p3 右的补偿路) | | p5 堵漏/救生衣 | trust+1 | 0 | 双活路(两路都活着,差分在反应句与尾声心境) | | p6 读完/合上 | truth+1(rev3=true) | 0 | 左路=插叙3(祖父忏悔全量);**rev2=false 时左路封锁**(m6_seal 播出后自动走右,abyss g9_dry 先例——不摆假选项) | | p7 离塔救人/守灯引渡 | 0(p7='boat') | 0(p7='lamp') | 核心宿命抉择:不动 trust;后果走 rev2(发条)与 trust(引渡)差分 | | p8_boat 循渔歌/推坐标 | 0 | 0 | 双活路差分(海上找人方式) | | p8_reef 老航道/新礁缝 | 0 | 0 | 双活路差分(引渡路线) | | p9 说出/守住 | 真相出口 | 秘密出口 | 不动 trust;提示句按 truth 选 p9a/p9b | ### 4.3 信任档位表(账目真实:每档句义对每个取值皆为真) | 档 | trust 区间 | 整句 | 真实性核验 | |---|---|---|---| | tr_deep | 3-4 | 「他把命,交给了你的声音。」 | ≥3 时 p7 右他完全照做(c9_land)✓;p7 左他海上应答配合 ✓ | | tr_half | 1-2 | 「他信一半,疑一半。」 | 1-2 时 p7 右他半路改主意(c9_found)✓ | | tr_thin | 0 | 「今晚,他只信自己的手。」 | 0 = 四个信任点全失,p7 右必自渡 ✓(附 warning 震,radio G2 同款) | - 阈值 3 = 「多数走心 + 容忍 1-2 次克制」落在高信任档——把「他是否把命交给你」压在玩家自己的累计上,而非单点(radio rep ≥5 同款纪律)。 - 跨档被动播报(挂 CALM 链尾,跨档才播);上滑 status 随时可查。 **真相度逐值表(truth=插叙完成数,每值一句、逐值为真)** | truth | 整句 | 核验 | |---|---|---| | 0 | 「旧账压在灯底,你一页没翻。」 | rev1/2/3 全 false ✓(暗格未开) | | 1 | 「灯下的旧账,你翻开了一角。」 | 仅插叙1(日志残篇)✓ | | 2 | 「祖父和父亲的事,你都翻到了。」 | 插叙1+2 ✓(缺页在手未读) | | 3 | 「三代人的账,今夜见底了。」 | 插叙1+2+3 ✓(忏悔全文,p9a 解锁) | ### 4.4 结局与差分验算(设计期穷举,playtest 剧本 A-F 引用) - **trust ∈ {0..4} 全档可达**(四个独立 ±1 抉择 p1/p3/p4/p5);e1/e2 ⟺ trust≥3(如 p1左 p3左 p4左 p5右=3),e3/e4 ⟺ trust≤2(全右=0)。 - **truth ∈ {0..3} 全档可达**:rev1=p2左;rev2=p3左∨p4左;rev3=p6左∧rev2。0(p2右p3右p4右)、1(p2左+双右)、2(p2左+p3左)、3(再 p6左)。 - **p6 封锁与 p9 变体联动**:rev2=false → m6_seal 自动右;truth=3 → p9a/d9_tell_a(全真相),truth<3 → p9b/d9_tell_b(碎片:只确认「那晚守塔的是我爷爷、灯灭过一下」——前者是家族常识、后者来自阿澈自己的话,**truth=0 也句句为真**,归因真话律)。 - **灯三态**(p7×rev2):p7 左+rev2 → ep_lamp_full(满发条自转,rev_f4 知识兑现);p7 左+¬rev2 → c3_dark+ep_lamp_dark(半卷发条走到头,**复刻祖父之夜**,归因指向「没听完父亲那一晚」的实际选择);p7 右 → ep_lamp_hold(整夜未停)。 - **阿澈三态**(p7×trust):p7 左 → 恒被接回(c9_return,人已上艇不失去);p7 右+trust≥3 → 自渡成功(c9_land);p7 右+trust<3 → 天亮被渔队找到(c9_found,活着,措辞克制——零死亡红线)。 - **最坏路径完整性**:剧本 C(全右)仍走完全部 9 抉择点、3 结局分支可差分(ep_amo_dawn/ep_lamp_hold/ep_log 不播)——树深固定,无死路。 - **纸面承诺安全性**:trust≥3 ⟹ p3/p4 至少一左 ⟹ rev2=true ⟹ 缺页在手(c2_key)——e1_a「那页纸攥在手里」/e2_b「缺页放回灯底」对 trust≥3 两结局**恒为真**;e3/e4(trust<3)文案零纸面引用,truth 任意取值皆不穿帮(归因真话律在结局文案侧的落点)。 ### 4.5 家族秘密 gating 顺序(秘密揭示的三把钥匙与三道门) | 层 | 秘密 | 钥匙(抉择) | 门(插叙) | 兑现(现在线收益) | |---|---|---|---|---| | 层1 | 灯下藏着东西 | p2 左(翻暗格) | 插叙1(祖父:「灯下压着的东西,比灯重」) | 日志残篇 + 缺页悬念(tru_one) | | 层2 | 父亲的最后一次值守 | p3 左(口诀)或 p4 左(真话,兜底) | 插叙2(父亲电台:「上满发条能自己转一夜」) | 手电缺页线索(c2_key)+ 发条知识(c7_go 差分) | | 层3 | 祖父的忏悔(回澜号真相) | p6 左(**前置=层2 已开**) | 插叙3(祖父:「我离灯三刻,回澜号,七个人」) | p9a 完整真相可说 + e1/e2 重量 | - 顺序不可跳:层3 的门只认层2 的钥匙(m6_seal 封锁);层2 有两把钥匙(p3/p4)保证「错过 p3 仍可全真相」(补偿路,abyss hasLog 双入口同型)。 - 阿澈的回环:amo_c(幕一伏笔「我爷爷……白角的灯灭过一下」)→ amo_g(p9 左揭晓「我爷爷,就在那七个人里」)——玩家的抉择把两条家族线缝上。 ## 5. 音频三层映射 ### 5.1 BGM(状态→曲目,Mureka instrumental,3 首 ≈ $0.09;台账 tools/mureka_log.md) | 状态 | 曲目 | 切换 | Mureka prompt 草稿(英文,seamless loop) | |---|---|---|---| | INTRO/WATCH/CALM | `bgm/storm` | 开局淡入 800ms,全夜不切;语音 duck 引擎自理 | "Storm night lighthouse vigil, tense low piano motif over dark strings, restrained and lonely, distant thunder and wind, no vocals, seamless loop, 60-90 seconds" | | MEMORY | `bgm/memory` | **插叙进入** crossfade 800ms;回返淡回 storm 800ms | "Old phonograph memory waltz, music box and muted strings with vintage vinyl warmth, fragile nostalgia, slightly out-of-time, no vocals, seamless loop, 60-90 seconds" | | ENDING | `bgm/dawn` | d9 结果链播完后淡入 1500ms | "Calm grey dawn after a storm at sea, soft piano and warm strings, gulls far away, quiet relief, no vocals, seamless loop, 60-90 seconds" | | MENU | `bgm/storm`(低响度) | 呼出低响度淡入 500ms;关闭淡回 800ms | 同 storm(省 1 首,塔内场景同构;abyss menu 用 deck 先例) | - `bgm=0` 时全部静默(引擎约定);幕的推进不靠切曲——靠 amb_storm 游戏层 gain 分幕(0.5/1.0/0.7)+ lull_N 进度整句(账目在语音不在音乐)。 ### 5.2 SFX 清单(SA3=Stable Audio 3 / SYN=程序合成;one-shot 真峰值 ≤-3dBFS;amb 循环由游戏层 `buffer.loop` 实现,radio phone_ring 先例) | id | 用途 | 来源 | prompt(英文)/ 合成参数 | |---|---|---|---| | `sfx/amb_storm` | 风暴底噪 loop(现在时签名①;分幕 gain 0.5/1.0/0.7) | SA3 | "Storm wind and heavy waves hitting a lighthouse rock, sustained gale, night, seamless loop" | | `sfx/amb_room` | 塔内底噪 loop(炉火+木构+雨打玻璃) | SA3 | "Inside a lighthouse room at night, oil stove crackle, wooden beams creak, rain on glass, seamless loop" | | `sfx/amb_vinyl` | **老唱片底噪 loop(回忆时签名②;插叙期间唯一底噪)** | SA3 | "Vintage 78 rpm phonograph record surface noise, warm crackle and hiss, no music, seamless loop" | | `sfx/memory_in` | **插叙进入过渡音:唱针落下+转速走稳** | SA3 | "Phonograph needle dropping onto a record, brief run-in groove then steady rotation, 1 second" | | `sfx/memory_out` | **插叙回返过渡音:唱针抬起+轻划** | SA3 | "Phonograph needle lifting off a record with a short scratch, 0.6 seconds" | | `sfx/radio_tune` | 调频找台(C1 首通/信号重连) | SA3 | "Turning an old shortwave radio dial through static, settling on a weak signal, 3 seconds" | | `sfx/static_burst` | 阿澈信号断续气口(信息音,idle-only) | SA3 | "Shortwave radio static burst with faint ghost of a distant voice, 2 seconds" | | `sfx/radio_beep` | 电台接通确认 | SYN | 双音上行 0.3s(abyss link_beep 同款) | | `sfx/amb_engine` | 阿澈侧渔船引擎 loop(通话期对侧 amb,voice 在播时 -6dB) | SA3 | "Small fishing boat engine running rough in a storm, water sloshing in bilge, seamless loop" | | `sfx/hull_crack` | 危机拍1:船板开裂(前置 2s 渐强) | SA3 | "Wooden boat hull cracking under stress, splintering and groaning, 3 seconds" | | `sfx/wave_wall` | 危机拍2:浪墙(前置 2s 渐强) | SA3 | "Massive wave wall rising and crashing, deep roar, 4 seconds" | | `sfx/lamp_mech` | 灯座机械上发条(c7_go;rev_f4 兑现音) | SA3 | "Clockwork lighthouse rotation mechanism being wound, heavy ratchet clicks, 3 seconds" | | `sfx/hatch_open` | 灯座暗格开启(c1_open) | SA3 | "Old hidden compartment sliding open, metal and wood scrape, 2 seconds" | | `sfx/flash_click` | 手电尾盖旋开两响(c3_page) | SYN | 两次短金属 click(120ms 间隔) | | `sfx/foghorn` | 雾角两长一短(b_rain 承诺音:开场/幕间×2/ep_shore) | SA3 | "Lighthouse foghorn, two long and one short blast, deep brass, distant storm behind, 4 seconds" | | `sfx/ui_confirm` / `ui_menu` | 菜单/确认(程序合成兜底) | SYN | 同全局先例 | - 预警窗纪律:hull_crack/wave_wall 均**前置 2s 起渐强**(L2),danger 签名与 danger 震同窗(radio C5/abyss D2-D4 先例);全游戏仅此两处 danger。 - 承诺兑现绑定:foghorn 在 b_rain 口播「两长一短」后,开场/CALM(1)/CALM(2)/ep_shore 四处必播(§6.5 清单 + §9 断言计数)。 ### 5.3 Voice 划分(详见 21-keeper-voice-ids.md) | 组 | 声部 | 落位 | 数量 | |---|---|---|---| | 守塔人(你)KEEPER | 双性别(female=Serena / male=Uncle_Fu + KEEPER instruct 常量) | `voice/female\|male/` | 42 句 | | 岸台/系统/向导 SHORE | 双性别(female=Vivian / male=Dylan + SHORE instruct 常量) | `voice/female\|male/` | 79 句 | | NPC 平铺 ×3 | 阿澈=Dylan(电台滤波)/ 父亲=Uncle_Fu(唱片滤波)/ 祖父=Eric(唱片滤波),**不做双性别** | `voice/` 根(平铺回退命中)+ 滤波转码 | 27 句 | - NPC ≤5 约束满足(3 人);玩家自己的声音跟随全局 `settings.voice`;岸台值班员是向导人设(L4 角色化系统声,radio 导播/abyss 调度同型)。 - 合计 148 句(≤160 预算 ✓,含 `_k` 键盘变体 8 对);音频文件 = 121×2 + 27 = 269 条(×2 格式 = 538 文件)。 ## 6. 时空双线设计(本作核心差异层) ### 6.1 引擎能力边界(对照 web-engine-api.md) | 能力 | 引擎现状 | 方案 | |---|---|---| | voice 运行时 EQ/滤波 | ❌ voice 链无滤波插入点 | **资产侧转码滤波**(radio §6.2 先例):ffmpeg `highpass+lowpass+压缩器` 逐 NPC 定档(§5.3/voice-ids) | | BGM crossfade | ✅ fadeMs 800ms 内置 | 插叙进入/回返直接 `play('bgm/...')` 切换 | | sfx 循环 | ❌ 即发即弃 | 游戏层 `buffer.loop=true` 持句柄(amb_storm/amb_room/amb_vinyl/amb_engine),插叙切换时淡入淡出(radio `_ringSrc` 先例) | | sfx vs voice 互让 | ❌ sfx 总线独立无 duck | **游戏层 idle 门**(§6.6 R1):memory_in/out、static_burst 只在 `!audio.isVoicePlaying()` 时放行,否则挂队列表句间补发 | | 语音 duck 垫乐 | ✅ 内置(duck 20%,300ms) | 直接依赖 | | 变速不变调 | ✅ SoundTouch | 直接依赖(长按 1.7× / 2.2× 视障档) | ### 6.2 时空签名规格(误认=剧情混乱,规格逐维度显式) | 维度 | 现在(风暴夜) | 回忆(老唱片) | |---|---|---| | BGM | `bgm/storm`(低音钢琴+暗弦+远雷) | `bgm/memory`(八音盒+老唱片暖染圆舞) | | 底噪 | amb_storm(分幕 gain)+ amb_room | **amb_vinyl(唯一底噪:storm/room 双双淡出)** | | 过渡音 | 回返:memory_out + storm 渐起 | 进入:memory_in(唱针落下) | | 语音音质 | KEEPER/SHORE 不滤波(塔内近讲);阿澈=电台滤波 | 祖父/父亲=唱片滤波;**KEEPER 锚句不滤波(「现在的你」在回忆里旁白——干净人声 vs 唱片人声,强化「你在看回忆」)** | | 时位句 | prog_m1/m2/m3(上滑链首段) | prog_mem_g / prog_mem_f(上滑链首段 + 恢复链首段) | | 台词锚 | (now 句自然在场) | 锚入 rev_g1/rev_f1/rev_c1 + 锚出 rev_g4/rev_f5/rev_c5 | - **earcon 纪律:时空切换零新增 guide earcon**——统一 8 音词表中 MEMORY/WATCH 同用 `explore` 签名;时空签名住**内容层**(sfx 过渡音+底噪+BGM+台词锚),语义靠语音(L2 铁律:earcon 只做粗标记,训练 8.5 轮结论不赌)。 - 底噪互斥是硬约束:任意时刻 amb_vinyl 与 amb_storm/amb_room **零重叠**(§9 转写断言);插叙期间阿澈的 amb_engine 也停(叙事自洽:插叙都发生在塔内独处拍,电台此刻静默)。 ### 6.3 插叙触发与回返时序(精确到步骤) | 步 | 事件 | 时序 | |---|---|---| | 1 | now 结果句播完 → 静默一拍 | 600ms | | 2 | `memory_in`(1.0s,idle 门放行)+ BGM crossfade storm→memory 800ms + amb 切换(storm/room 淡出 400ms / vinyl 淡入 400ms) | 与步 2 同时起 | | 3 | 插叙链 auto:锚入(KEEPER,不可跳首播)→ NPC 2-3 句(中段可跳)→ 锚出(KEEPER,不可跳首播) | ≤6 句 ≤90s | | 4 | `memory_out`(0.6s)+ crossfade memory→storm 800ms + amb 回切 | 链末即起 | | 5 | 回返后 now 句接播(c1_diary / c2_key 或 c4_trueo / c6_reade) | memory_out 播完接 | - 插叙链入口 800ms 手势死窗(§2.2);链末**不设等待**(auto 回返——回忆不是关卡,是信息颗粒);「停下来回味」由双击重听当前回忆节点承接(叙事类等待点合法回味 + 可重听,A2/P1)。 - 插叙触发点全集(3 处):p2 左→插叙1;p3 左或 p4 左→插叙2(兜底);p6 左→插叙3(前置 rev2=true)。 ### 6.4 「我在哪」复述机制(P3 打断回来说得清) | 层 | 机制 | |---|---| | 被动 | 底噪+BGM 时空签名 2s 可辨(A1:有无唱片底噪/风声) | | 主动 | 上滑 status 链**首段=时位句**:now → prog_m1..3;memory → prog_mem_g(祖父年代)/ prog_mem_f(父亲最后一晚)——「现在是回忆」可随时重听确认 | | 台词锚 | 每段插叙首尾锚句 = 台词级时空位(「唱针落下。四十年前……」「风声回来了……」) | | 中断恢复 | 存档带 `timeline`('now'/'mem_g'/'mem_f');恢复链 = timeline=mem* 时**先播 prog_mem_*** 再重播当前回忆节点(§9 断言:恢复首句=时位句, ≤5s 回到可玩) | | 菜单复述 | MEMORY 中开菜单关闭后重播当前回忆节点,底噪仍 vinyl(timeline 不丢) | ### 6.5 教学承诺清单(dungeon「铁器=楼梯」教训:每句承诺必须真实存在且可感知) | 教学句 | 承诺 | 兑现点(断言锁定) | |---|---|---| | rev_teach「听到唱针落下、风声退后,是回忆。风声回来,就是现在」(全局一次,首插叙前) | 时空签名四要素 | 每次插叙必有 memory_in+vinyl+锚入 / memory_out+storm+锚出;全剧本零漏(§9) | | b_rain「雾角两长一短——塔在,我也在」 | 雾角=塔的报平安音 | foghorn 播点=开场/CALM(1)/CALM(2)/ep_shore 恰四处 | | b_rule「风暴夜,灯不能灭」 | 灯=规矩本体 | 灯三态兑现:ep_lamp_full/dark/hold + c3_dark 差分归因(§4.4) | | b_goal「今夜两艘货轮过境。守住灯,也守住频道」 | 货轮/频道双目标 | c3_dark「过境的货轮,平安」+ ep_shore 晨报「航道平安」闭环 | | rev_f4「塔上的机械上满发条,能自己转一夜」 | 发条知识 | p7 左 rev2 差分:c7_go/c7_go2、ep_lamp_full/ep_lamp_dark(剧本 A/E 对照) | | prog_m1..3 幕句=风暴三进程 | 「风暴起/到顶/在退」 | CALM 切幕+amb_storm gain 分幕(0.5/1.0/0.7)一致 | ### 6.6 通道仲裁规则(并发声流 ≤2) - **R1 过渡音不叠语音**:memory_in/out、static_burst 与任何 voice 播放区间零重叠——idle 门只在 voice 空闲放行,在播则排队到句间空隙(abyss sonarGate 同型)。 - **R2 插叙主导段**:前景=插叙链人声+vinyl 底噪,BGM=memory 曲;电台(阿澈)此刻静默,叙事自洽。 - **R3 通话期底噪退位**:阿澈通话期间 amb_engine 常驻低响度、voice 在播时游戏层降 -6dB(abyss R3 先例);amb_storm 同步 -4dB——「海况让给人声」。 - **R4 危机拍优先**:hull_crack/wave_wall 即发即弃可叠底噪;danger 签名+danger 震同窗;当前 voice 句播完后危机链才插续。 - **R5 回忆可重听**:MEMORY 双击=重听当前回忆节点(关键线索可重听,P1);插叙中段句可跳、首尾锚句首播不可跳(G5)。 - 声流账:任意时刻 ≤2 条前景流(voice 串行 1 + 信息音/底噪 1),BGM/amb 为背景层——ASA ≤2 约束。 ## 7. 向导层接入 ### 7.1 状态签名 earcon / turn cue | 时机 | 向导动作 | 说明 | |---|---|---| | INTRO 进入 | `guide.state('explore')` | 叙事态签名 | | WATCH/MEMORY 进入 | `guide.state('explore')` | 同一枚签名(时空不换 earcon,§6.2 纪律) | | 每个抉择点 p* 起播 | `guide.state('choice')` | 选项句本身即 turn cue(W2 契约零插播) | | CALM 进入 | `guide.state('settle')` | 喘息/结算签名 | | ENDING 进入 | `guide.state('settle')` | 同上 | | MENU 打开 | `guide.state('menu')` | 菜单签名 | | 危机拍1/2 | `guide.state('danger')` | 音效前置 2s 渐强同窗(全游戏仅两处) | | 无效手势 | `guide.error({voice:'gd_invalid', onceKey})` | error 震 + full 档语音,idle-only(F1.4) | - earcon 词表:只用全项目统一 8 音(menu/explore/battle/choice/settle/danger/turn/nudge),**零新增**;battle 不用。 ### 7.2 看门狗双轨(radio F1 + dungeon E0-1 教训:乱按也要被抢救) **无操作轨**(guide watchdog,任何手势复位;两级不同句、用尽即停、永不惩罚): | 等待点 | full 档 | standard 档 | 级别内容 | |---|---|---|---| | INTRO/WATCH/CALM 链末等待 | [25, 50] | [13, 30] | ① gd_continue ② gd_again(storyline F1.1 同款) | | 抉择点 p* 播完无操作 | [25, 50] | [25, 50] | 重播当前 p* 提示句(13 号 §16 先例;空点单击=重挂计时) | | ENDING 结算等待 | [25, 50] | [13, 30] | gd_continue / gd_again | | MEMORY 链末 | 不挂(auto 回返) | 不挂 | 资产缺失兜底:30s 单句看门狗且 `isVoicePlaying()===false` 才续(radio N1 先例) | **无进展轨**(游戏层计时,dungeon E0-1 ②同型;挂 WATCH 链末与 p* 等待两个可饿死点): | 项 | 规则 | |---|---| | 进展定义 | 节点推进 / 抉择结算 / 重听。**无效手势(滑错、空点)不计进展**——无操作轨被它们复位、无进展轨不被复位,乱按 96s 必被抢救 | | 阈值 | 60s → gd_prog1;120s → gd_prog2;用尽即停(每等待点一轮) | | 有真实进展 | 全程零 prog 播出(不误伤正常玩家) | | 双轨去重 | 两轨短句内容同级交叉登记;同一等待点先播过 gd_* 同义级则无进展轨跳过 | - 向导插播一律 `queue:true`,绝不顶掉在播对白;error/watchdog 类语音 idle-only(在播则跳过且过期丢弃,F1.4)。 ### 7.3 键盘变体机制(PC 键盘玩家) - 运行时按 `gesture.inputMode()`(settings.input auto/touch/key)选 `_k` 变体资产:key → `voice//_k.*`,touch → 无后缀版;auto 跟随最近使用通道。`_k` 缺失回退无后缀版(check_all_assets 校验配对)。 - 变体清单(8 对,集中分组见 voice-ids §G0.a 与 C1 首个抉择):gd_brief / gd_continue / gd_again / sys_help / sys_menu / sys_skip / sys_continue_ask / **p1**(首个抉择的键位绑定)。 - 抉择提示 p2..p9 措辞统一「向左滑/向右滑」;键盘玩家由 p1_k 与 sys_help_k 完成首次绑定(左=←,右=→),后续语义不变。启动遮罩文案引擎自理(游戏零改动)。 ### 7.4 继承 radio 三轮验证机制(照抄不重设计) | 机制 | 来源 | 落点 | |---|---|---| | 帮助链去重(同等待点第 2 次上滑只震+nudge) | radio G1 | `_helpSeen` 按等待点键控(MEMORY 按插叙段键控) | | 状态链顺序「状态在前、帮助在后」 | radio G6 | prog_m* →〔mem: prog_mem_*〕→ tr_* → tru_* → sys_help | | 空点确认轻震(不可跳句上单击补 beat [8]) | radio G4 | p*/锚句/F4 锁各 tap 屏蔽分支 | | tr_thin 播报位附 warning 震 | radio G2 | 全部 tr_thin 出口(含上滑复听) | | 菜单报名游戏侧排队零截断 | radio 实现修复 #3 | `_menuQueue` 排水 | | stop() 先 `seqToken++` 作废在途链 | radio 实现修复 #6 | 同款 | | 新局清 choiceArmed/pendingChoice | radio 实现修复 #7 | newNight() | | 长按误报无效(longpress 全状态合法) | radio 实现修复 #2 | 仅 left/right(ASK 另含 up/down/doubletap)报无效 | ## 8. 存档设计 ### 8.1 进度 data 结构(PUT /api/progress {game:'keeper'}) ```json { "schema": 1, "updatedAt": "2026-09-09T12:00:00Z", "summary": "第2幕·风暴顶 · 信任:信一半 · 结局2/4", "phase": "WATCH", "node": "amo_e", "act": 2, "timeline": "now", "trust": 2, "flags": { "rev1": true, "rev2": true, "rev3": false }, "p7": null, "endings": { "e2": true }, "runs": 3 } ``` - 写入时机:每幕 CALM 进入、每个抉择结算后、ENDING 进入、菜单「保存并回大厅」。 - 断点续玩:读档 → sys_continue_ask(_k)(单击=继续上次 / 右滑=新风暴夜,runs 均 +1 统一口径);继续 = 恢复 phase+node+timeline+全部数值并**局面复述**:timeline=mem* 时恢复链 = prog_m* → prog_mem_g/f → 重播当前回忆节点(时位句先行,「我在哪个时空」一句话说清);now 则直接重播当前节点链;重进 ≤5s 可玩(L5);恢复首句受 F4 跳句锁(radio G5 例外条款)。 - summary 格式:进行中 `第N幕·<幕名> · 信任<档名> · 结局X/4`(幕名=风暴起/风暴顶/风歇前);已收夜 `风暴夜已过·<结局名> · 结局X/4`(大厅卡片直接显示)。 ## 9. playtest 断言清单(登记 13 号文档 keeper 节,格式同 §21;`settings.set({guide:'off'})` 纯玩法验证,向导行为住 guide.test) **关键路径**(剧本 A:信任满·全真相·出海接人·说出 → e1,≤60 次手势): 1. 遮罩点按 → INTRO 链(gd_brief → b_open → b_rain → b_rule → b_goal,voice 层有播放记录)→ 链末单击 → C1:c1_tune → amo_a → amo_b → ◆p1。 2. p1 左 → c1_hold(o)(e)(trust=1,首个正反馈)→ amo_c → p2 左 → c1_open(配 hatch_open)→ rev_teach(全局一次)→ **插叙1 时序断言**:memory_in → bgm 切 memory → rev_g1..g4 → memory_out → storm 回 → c1_diary(truth=1)→ CALM(1):foghorn+lull_1+tr_half(跨档)→ C2。 3. C2:c2_back → amo_d → p3 左 → c2_song(o) → 插叙2(rev_f1..f5,同款时序)→ c2_key(truth=2)→ amo_e → p4 左 → c4_true →〔插叙2 已完成:兜底不播〕→ c4_trueo(trust=3)→ 危机拍1(hull_crack 前置 2s 与 danger 同窗)→ c2_crack → amo_f → p5 左(trust=4)→ CALM(2)。 4. C3:c3_eye → c3_page → p6 左 → 插叙3(rev_c1..c5)→ c6_reade(truth=3)→ p7 左 → c7_go(配 lamp_mech,满发条版)→ 危机拍2(wave_wall+danger+danger 震)→ c3_wall → p8_boat 左 → c8_song(o) → c9_return → g9_in → g9_knot → **p9a**(truth=3 变体)→ 左 → d9_tell_a → amo_g → d9_telle → ENDING(e1):e1_a/b + ep_lead + ep_amo_home + ep_lamp_full + ep_log_told + ep_dad + ep_shore → 结算单击重开回 INTRO。 **边界断言**: - **四结局可达(含信任逐点验算)**:剧本 B(p1左 p2左 p3左 p4左 p5左 p6右 p7右 p8_reef右 p9右,trust=4/truth=2)→ e2:c9_land + p9b + ep_amo_solo/ep_lamp_hold/ep_log_kept/ep_dad;剧本 C(p1右 p2右 p3右 p4右 p5右 → m6_seal 自动右 → p7右 p8_reef左 p9左,trust=0/truth=0)→ e3:c9_found + p9b/d9_tell_b + ep_amo_dawn/ep_lamp_hold,**ep_log_* 两句皆不播**(truth=0 日志未出场)、**ep_dad 不播**(rev2=false);剧本 D(同 C 但 p9 右)→ e4。各局 trust/truth 逐检查点与 §4.2 一致。 - **插叙触发与回返**:p2 左→插叙1、p3 左→插叙2、p6 左→插叙3 的节点顺序断言;**剧本 F**(p1左 p2左 p3右 p4左 p5左)断言插叙2 兜底在 c4_true 与 c4_trueo 之间播出且 c2_key 随后播出;回返后 now 线下一节点正确(c1_diary / c2_key 或 c4_trueo / c6_reade);插叙链 auto 完成即回 WATCH(无链末等待)。 - **时空签名断言(核心)**:全剧本转写断言——amb_vinyl 与 amb_storm/amb_room 播放区间**零重叠**;每次插叙 memory_in/memory_out 成对出现且计数=插叙完成数;锚入句(rev_g1/rev_f1/rev_c1)与锚出句(rev_g4/rev_f5/rev_c5)在位且首播不可跳(单击=beat 轻震不跳句);插叙期间 bgm=memory、回返后 bgm=storm;插叙期间 amb_engine 停播。 - **教学承诺兑现**(§6.5 清单逐条):rev_teach 全局恰一次(第二次插叙前不重播);foghorn 计数=开场+CALM(1)+CALM(2)+ep_shore 恰四处;rev_f4→剧本 A 播 c7_go/剧本 E 播 c7_go2(发条差分);b_goal→c3_dark 与 ep_shore 货轮闭环;prog_m1..3 与三幕 amb gain 档一致。 - **p6 封锁不摆假选项**:rev2=false 时 p6 提示句不播、m6_seal 播出后自动走 c6_shut,该点左右滑无效果(不丢不误触)。 - **信任/真相账目真实**:上滑链顺序 prog_m* →〔mem: prog_mem_g/f〕→ tr_* → tru_* → sys_help(G6);档位句与当前值一一对应(tr_deep≥3 / tr_half=1-2 / tr_thin=0;tru 四值逐句);tr_thin 各播报位附 warning 震。 - **看门狗双轨**:WATCH 链末 25s/50s → gd_continue/gd_again;p* 播完 25s/50s → 当前 p* 重播两次后静默;**乱按 96s**(每 2s 注入一个无效滑动):无操作轨零播出 + 无进展轨 60s→gd_prog1、120s→gd_prog2;有真实进展全程零 prog;MEMORY 链末不挂看门狗;`guide=off` 全程零定时器零向导事件。 - **键盘变体**:settings.input='key' 会话播出 gd_brief_k/sys_help_k/p1_k 且对应无后缀版零播出;'touch' 反之;'auto':keydown 后下一句用 _k。 - **中断复述含时空位**:杀进程重进(存档 timeline='mem_f'、node=rev_f3)→ sys_continue_ask → 单击 → 恢复链首句=prog_m2 → prog_mem_f → 重播 rev_f3 起链(恢复首句不可跳,第二句起可跳);now 态存档(node=amo_e)恢复=重播该节点链。 - **pendingChoice**:p* 播放中左右滑 → 播完立即生效(不丢、不重复、连滑取最后一次)。 - **p* 不可跳**:p* 播放中单击 → 不跳句(PROMPT_RE 门)+ beat 轻震(G4)。 - **idle-only**:向导 gd_* 在对白在播时跳过且不占 onceKey(F1.4);queue 句排在当前句后零截断。 - **菜单**:下滑开菜单 → 上滑帮助 → 双击「回到值守」→ phase/node 不变、当前节点重播;**插叙中开关菜单**后底噪仍 vinyl、重播当前回忆节点;单击循环顺序 = [回到值守, 重新开始今夜, 保存并回大厅];确认回大厅 → sys_menu_lobby 播出且 PUT/localStorage 有最新 data。 - **结算链死窗**:e* 结局链/c3_dark/插叙链开播后 800ms 内注入在途 tap → 手势被吞、链完整有序播出。 - **危机拍**:hull_crack/wave_wall 前置 2s 渐强、danger 签名与音效同窗、danger 震遍间隔 ≥400ms;当前 voice 句播完危机链才插续。 - **资产缺失**:屏蔽任意 voice 资产 → 30s 看门狗自动续链到结局不卡死;NPC 滤波资产缺失回退原声可播。 - **数字规范**:prog_*/tr_*/tru_*/gd_* 全部整句字面量,静态审计无运行时拼接碎片。 **失败即不通过**:任何等待 >5s 无声(静默死局);无效手势零反馈;**vinyl 与 storm/room 底噪重叠**;**插叙缺过渡音或缺锚句**;结局计数与 §3.4 不符;档位句与 trust/truth 值不符。 ## 10. 节奏曲线(风暴三幕) | 段 | 内容 | 目标时长 | 设计目标 | |---|---|---|---| | 开场 | 塔名+规矩+目标+第一声呼救 | 1-1.5 min | 首输入 ~30s(INTRO 链 ~30s 链末单击);规矩一句讲完(P4);雾角承诺音埋下 | | 幕一 风暴起 | p1/p2 + 插叙1 + CALM(1) | 4-5 min | **首个正反馈 ≤3min**(~2.5min p1 左阿澈声音稳住);**时空签名教学+首次兑现**(rev_teach→插叙1 四要素全播);两抉择条条有出路 | | 幕二 风暴顶 | p3/p4 + 插叙2 + 危机拍1 + p5 | 6-7 min | 情感段(父亲真相 p4);第一危机拍(前置 2s 预警);插叙2 供货发条知识(p7 兑现预告) | | 幕三 风歇前 | p6+插叙3 + p7 核心 + 危机拍2 + p8 | 7-8 min | 紧张峰值:三代秘密见底→「守灯还是救人」宿命抉择→浪墙下的执行差分;**复刻祖父之夜的暗线**(剧本 E 灯灭) | | 签收+结局 | p9 + 结局链 + 尾声回响 | 2.5-3 min | 心结抉择(说/瞒×全/碎片)+ ep 逐项点名(P2 回响);开环钩子拉下一夜 | - 合计 ≈ 21-24 min(15-25 达标);每 30-60s 一个反馈颗粒(阿澈反应句/结果句/档位句/插叙锚句),无沉默尴尬期(P3)。 - 难度曲线 = 情感与信息复杂度(接住人→翻开过去→宿命抉择→秘密归宿),操作复杂度恒定(永远二选一)——P4「规则一遍听懂」由恒定操作面保证;风暴强度=氛围曲线(amb gain 三档),不构成玩法压力(无倒计时无死亡)。 ## 11. 自检清单(策划交付自验;☐ = 待开发/测试轮填) | # | 硬约束/教训 | 落地 | ✓ | |---|---|---|---| | 1 | FTUE 首输入 <60s | INTRO 链 ~30s 链末单击(§10) | ☑ | | 2 | FTUE 首个正反馈 ≤3min | ~2.5min p1 左阿澈稳住(c1_holdo+c1_holde) | ☑ | | 3 | 学习期零惩罚 | 幕一全组合不扣资源;右路只即时纠偏(c1_aske);无超时惩罚 | ☑ | | 4 | 失败三问归因+归因真话律 | c1_aske/c3_dark/c9_found 指向实际选择;d9_tell_b 在 truth=0 仍句句为真;§4.4 无假归因 | ☑ | | 5 | 资源账目真实(radio rep_low 教训) | tr_* 三档逐区间为真;tru_* 四值逐值为真;跨档才播 | ☑ | | 6 | 手势宪法 6 手势默认语义零豁免 | §2.3 全覆盖;菜单 §5.2 模态约定 | ☑ | | 7 | 长按显式登记 | 无豁免:长按=引擎快进(12 号叙事类不豁免),§2.3 在案 | ☑ | | 8 | 向导:签名/turn cue/看门狗两级不同句 | §7.1/§7.2;gd_continue→gd_again;MEMORY 链末不挂 | ☑ | | 9 | 看门狗双轨(dungeon E0-1/radio F1 教训) | 无操作轨+无进展轨(§7.2);乱按 96s 必被抢救 | ☑ | | 10 | 插播 queue 不顶对白;纠错 idle-only 过期丢弃 | §7.2(F1.4 契约);过渡音 idle 门(§6.6 R1) | ☑ | | 11 | 死亡/失败结算链完整(dungeon H1 教训) | e* 链/c3_dark/插叙链 800ms 手势死窗;阿澈恒活着(零死亡红线) | ☑ | | 12 | 教学承诺兑现(dungeon「铁器=楼梯」教训) | §6.5 承诺清单 6 条逐条绑兑现点+断言 | ☑ | | 13 | 单次播报 ≤3 选项 | 全部抉择恒 2 选项;菜单 3 项循环 | ☑ | | 14 | 并发声流 ≤2 + 通道仲裁 | voice 串行+底噪/信息音互斥(R1-R5,§6.6);断言零重叠 | ☑ | | 15 | earcon ≤7、统一词表、语义靠语音 | 只用统一 8 音中 7 个(battle 不用),零新增;时空签名住内容层 | ☑ | | 16 | 台词一句一文件 ≤40 字、数字分档整句、无运行时拼接 | voice-ids 全表 ≤35 字(最长 p7);prog/tr/tru 分档整句;句内数字写死 | ☑ | | 17 | 多音字规避/同 instruct 常量/无语速词 | voice-ids 头部+文末自查(落/数/缝/卷/转/重 试听项) | ☑ | | 18 | PC 键盘变体(提示句集中+标注) | 8 对 _k 变体集中分组(voice-ids §G0.a);inputMode() 运行时选择 | ☑ | | 19 | P1:可打断/重听(含回忆节点)/status key/不当白痴 | 跳句+双击(MEMORY 重听 R5)+上滑时位句链+成功零补救教学 | ☑ | | 20 | P2:3 分钟见效/结局回响 | ~2.5min 阿澈声音稳住;ep 逐项点名(阿澈/灯/日志/父亲班次) | ☑ | | 21 | P3:3 分钟颗粒/中断复述含时空位/单手 | 一幕=4-8min 段落+边界存档;恢复链 prog_mem_* 时位句先行;全手势免视单手 | ☑ | | 22 | P4:一遍懂/容错/无高压倒计时/不骂人 | 一句规矩;零死亡零倒计时;NPC 句 ≤27 字+逗号停顿 | ☑ | | 23 | 抉择树 4 结局可走通验算 | §4.4 穷举+§9 剧本 A-D(e1-e4 各一条实路径)+E/F 差分剧本 | ☑ | | 24 | 单局 15-25 min | §10 曲线 ≈21-24 min | ☑ | | 25 | 分支不爆炸(radio 纪律) | 跨幕仅 trust+3 bool+p7/p9 派生;~75 节点固定深度(§3.3) | ☑ | | 26 | 台词 ≤160、NPC ≤5 单音色 | 148 句(双 121+NPC 27)/3 个 NPC 音色(voice-ids 账目) | ☑ | | 27 | 引擎边界诚实 | §6.1 能力表:滤波/loop/互让全写明资产侧或降级方案 | ☑ | | 28 | 存档带 summary、断点续玩 | §8:data+summary 格式、timeline 恢复链、≤5s、局面复述 | ☑ | ### 批 1 教训内化确认(5 条全落地) | 教训 | 本作机制 | 断言 | |---|---|---| | 教学承诺必须兑现 | §6.5 六条承诺清单(时空签名/雾角/灯/货轮/发条/三幕) | §9「教学承诺兑现」块 | | 看门狗双轨 | §7.2 无操作轨+无进展轨;无效手势不复位进展轨 | §9「看门狗双轨」块 | | 档位措辞真实 | §4.3 tr 三档逐区间+tru 四值逐值为真 | §9「信任/真相账目真实」块 | | 失败结算链完整不被打断 | 800ms 手势死窗盖 e* 链/c3_dark/插叙链;seqToken 作废在途链 | §9「结算链死窗」块 | | PC 键盘操作提示 | 8 对 _k 变体+inputMode() 选择+集中分组标注 | §9「键盘变体」块 | ### 时空签名教学-兑现验算(本作特有自检) | 承诺(rev_teach 四要素) | 教学点 | 兑现次数(剧本 A) | 断言位 | |---|---|---|---| | 唱针落下(memory_in) | rev_teach 全局一次(首插叙前) | 插叙 1/2/3 各一,共 3 | §9 插叙触发与回返 | | 风声退后(vinyl 底噪、storm/room 淡出) | 同上 | 3 段插叙全程,零重叠 | §9 时空签名断言 | | 唱针抬起+划针(memory_out) | 同上 | 3 次,与 in 成对 | §9 时空签名断言 | | 风声回来(storm 回切+锚出句) | 同上 | 3 次(锚句 rev_g4/rev_f5/rev_c5) | §9 时空签名断言 | | 「我在哪」三层兜底 | 上滑时位句(prog_m*/prog_mem_*)+恢复链时位句先行+菜单关闭底噪不丢 | 全程可查;§8/§9 | §9 中断复述含时空位 | ### 结局验算(3-4 结局条款 → 定 4) e1=trust≥3∧p9左(剧本 A:trust4/truth3);e2=trust≥3∧p9右(剧本 B:trust4/truth2,p9b);e3=trust<3∧p9左(剧本 C:trust0/truth0,p9b+d9_tell_b);e4=trust<3∧p9右(剧本 D)。四格互斥完备、各有一条实路径、trust/truth 全档可达(§4.4)。 ### 台词 ≤40 字验算 voice-ids 全表最长 = p7(35 字),次长 p9a/p9b(各 32 字)、d9_tell_a(31 字);NPC 句最长 amo_c(25 字)——全部 ≤40 ✓(逐句账见 voice-ids 文末自查)。 --- ## 修订记录(2026-09-14 开发轮裁定,追加不改旧文) 策划自查(台词 id 全集/组计数/数字分档/手势映射/剧本 A-F 对 §4.2 计分与 §3.4 结局矩阵穷举)结论:148 句账目闭合、8 对 `_k` 齐全、无手势映射矛盾。以下为实现轮裁定: 1. **`_k` 资产路径**:21b 头注/§7.3 写 `voice//_k.*`,管线(`tools/gen_voice_dual.py` KEY_LINES)与引擎(audio.js 候选链)实际约定为 `voice//key/.*`(abyss/detective 同型先例)。裁定:按管线约定落盘,文档 `_k` 表述视为同义。 2. **字数口径**:p7 实计 42 字(含标点)>40、p9a 40 字;管线按句读自动拆句生成后拼接(句间 0.12s),听觉仍为一句,不改词。21b 自查「最长 35 字」为不含标点口径。 3. **summary 信任档名**:§8.1「信任<档名>」未给文字。裁定:3-4=深 / 1-2=半 / 0=薄(例:「第2幕·风暴顶 · 信任半 · 结局0/4」)。 4. **存档扩展字段**:插叙断点恢复需 `memSet`(插叙段 g1/f2/c3)、`ret`(回返节点)、`taught`(rev_teach 已播)、`lastTier`(跨档播报基准)——§8.1 示例未列,按 schema 自由扩展,向后兼容。 5. **rev_teach 落点**:§3.2 树形将其钉在 c1_open 链内,但「全局一次、首插叙前」语义在 p2 右(跳过插叙1)后首次插叙=插叙2/3 时同样需要教学(§1 风险预防①)。裁定:实现为「进入首个插叙前自动插播一次」,不钉死节点;§9「全局恰一次」断言不变。 6. **PROMPT_RE 裁定**:`/^voice\/p\d/` 覆盖 p1..p7、p8_boat、p8_reef、p9a、p9b,播放中单击不可跳 + beat 轻震(G4)。 7. **危机拍结构**:c2_crack/c3_wall 实现为 danger 节点(sfx 前置 2s 渐强 + danger 签名/danger 震同窗,链内自动推进不等待单击)——树形未显式标注 auto,按 radio 危机拍先例裁定。 8. **CALM 实现形态**:按 radio BRIDGE 先例 = 自动链(foghorn → lull_N →〔跨档才播 tr_*〕)+ 喘息定时器自动进下一幕;上滑 = 状态链后自动链继续(已播账目句不重播);CALM 不切 BGM(§2.1 storm 常驻,游戏层 amb_storm gain 分幕 0.5/1.0/0.7)。