Files
SoundGame/docs/design/19-cipher.md
T
xsl 566241e05d feat(platform/P1): 配对码跨设备 + 认领打磨 + 键盘语音双套资产(两 agent 并行 + 集成收口)
配对码与认领(B):
- POST /api/pair/start|join:6 位码 10 分钟单次有效、仅存内存零泄露面、5 连错 per-IP 封锁 10 分钟;join=新设备档案按桶 LWW 并入老档案+token 改绑(复用 claim-merge);日志脱敏
- 大厅:生成配对码(speechSynthesis 逐位朗读+aria-live+重听)/从其他设备转入(one-time-code 输入框)/从本机移除/401 过期自愈引导/aria 补全/玩家名可听化
- api-smoke 12/12(配对全流程/频控/封锁/过期自愈/移除/digitsToCn)+ CDP 真浏览器 E2E 22/22

键盘语音双套资产(A,路线 a):
- 164 变体句(8 游戏 grep 实测,超出调研预估 153:含轻触/点两下/按住变体)×2 性别=328 生成 656 文件 36.9 分钟;触屏主资产零字节改动(PCM 逐样本比对后还原)
- 引擎 _candidates 三跳路由 key→gender→平铺(inputMode 跟随);缓存键 mode|gender|id;engine-input 新增 K9 路由断言(72 条)
- check_all_assets 增 key 列(两性别一致且 ⊆ 主集)8 游戏全绿;voice_text.json 增 __key__ 表;live control.mjs key 会话对照键盘措辞验收(集成)
- voice-ids 文档:8 游戏键盘变体小节由 KEY_LINES 程序化生成(与台词表逐字断言一致);补齐 storyline/escape/simon 三个历史缺失文档;design README 增 new8 索引
- mock 适配:playtest persona 加 maxTouchPoints=1 保持触屏链断言语义零改动

回归:12 套件全绿(8 playtest + guide 122 + engine-voice 19 + engine-input 72 + api-smoke 12/12)
同批入库:批次 2/3 已完稿设计文档(17-abyss/18-detective/19-cipher 全套,20-22 在途)
2026-09-10 00:43:23 +08:00

553 lines
53 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 19 · 《暗码》(cipher) — 1940 密战叙事 RPG 设计文档
> **给开发的实现要点摘要**
> 1. 叙事模式(声命线范式,radio/abyss 同骨架):节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;复制 `web/radio/`(或 `web/abyss/`)结构起步,状态机 6 宏状态(INTRO/RX/DECODE/RELAY/NIGHT/ENDING + MENU 覆盖层)。
> 2. 核心增量 = **电文解码小局嵌叙事**(DECODE 态):摩尔斯音由**运行时程序合成模块**(`morse.js`,guide earcon 同先例)直接排程 oscillator——**不走音频文件**(有损编码在 70-160ms 短音上有 ms 级抖动,毁时值精确);事件钩子供 playtest 固定字符断言。
> 3. 手势:全局默认映射 + **唯一豁免 E1**(DECODE 空闲长按=翻密码本求提示,与引擎快进时间互斥,detective E2 同款);菜单走 00 号 §5.2 模态约定。摩尔斯三层兜底:正常译(原速+候选对照)/ 慢放(双击第 2 次 0.5×)/ 提示直译(密码本 3 页,逐值账目)。
> 4. 暴露度 = 步进隐藏资源(radio 口碑同型):译错 +1/封(教学封 T1 恒 0)、危险抉择 +1/+2;分档整句、跨档才播、上滑可查;**档位措辞逐值为真**(book_1..0 / exp 四档均按实数措辞)。
> 5. 译错归因真话:教学封 T1 译错**即时**归因(真词字母的摩尔斯写死在句里)+ 无限重选零惩罚;正片 T2-T4 译错**不揭穿**(进入剧情误读分支),真相在尾声回响兑现(ep_* 与 flags 单源对应,detective 信息单源思想)。
> 6. 台词依据 [19-cipher-voice-ids.md](19-cipher-voice-ids.md)(139 句:双性别 134 + 平铺 NPC 5);键盘变体 11 对 `_k` 集中标注;结算链 800ms 手势死窗(abyss/地下城 H1 同款)。
> 7. 章程任务 N3(research/new8/00-program.md §2):单局 15-25 分钟、9 抉择点(4 译词 3 选 + 5 叙事 2 选)、4 结局、分支局部化。断言清单住本文档 §9;13 号文档已登记指针(§22)。
## 1. 概述
- **一句话**:1940 年江城,你是电讯处的夜班报务员,也是「北风」安在敌台内部的一双耳朵——一晚四封密电过你的手,每个词怎么译、报不报、报给谁,都由你按下。
- **题材(声音即世界)**:谍战的本体就是**听**——摩尔斯的滴答是情报本身、报务员的「手法」是 fingerprint、电台静电是世界底色。「用耳朵当武器」与纯音频载体天然同构:视觉谍战需要的文件/密写/表情,在这里全部是声音。
- **单局**:一个完整夜班(一条抉择路径)19-22 分钟;四封电文共享主干(截获→解码→处置→夜结算),分歧局部化在每封内部;9 个抉择点(4 译词 + 5 叙事,全部恒在),4 结局。
- **核心循环**:截获(调谐+电文摩尔斯首播)→ 解码小局(听音辨词:候选对照/慢放/提示三层兜底)→ 译文揭晓(**你的认知**,对错不揭穿)→ 处置抉择(报北风/压日志/呈处长/亲自交接/反喂假电)→ 夜结算(暴露度跨档播报)→ 下一封。终局 p9 用「发最后一电」把整晚收在你的电键上。
- **与 radio/abyss 的差异点**(骨架同、压力源不同):radio 零压力、abyss 资源稀缺(氧气);cipher 的压力 = **隐藏的怀疑账本**(暴露度)+ **认知风险**(你永远不确定自己译对了没有——译错不是失败,是走另一条真实存在的剧情)。全部宽限化:无倒计时、无中途死亡、软失败只损失「情报效果」不损失玩家已有物。
## 2. 摩尔斯解码小局(信息设计,本作核心差异层)
### 2.1 电码呈现规格
**电码表**(标准摩尔斯;本作 4 封电文只用到 13 个字母,模块内置全 A-Z 表,断言只锁用到的):
| 字母 | 码 | 字母 | 码 | 字母 | 码 | 字母 | 码 |
|---|---|---|---|---|---|---|---|
| A | ·− | H | ···· | O | −−− | T | − |
| B | −··· | I | ·· | P | ·−−· | Y | −·−− |
| D | −·· | L | ·−·· | R | ·−· | S | ··· |
| N | −· | | | | | | |
**时值与音色(运行时合成,`web/cipher/morse.js`)**:
| 参数 | 值 | 说明 |
|---|---|---|
| 单位 u | 原速 80ms(dit=1u 音、dah=3u 音、字内隙 1u、字母隙 3u) | ≈15 WPM 等效;**u 是常量写死,不随 settings.speed**(见边界表) |
| 慢放档 | u=160ms(0.5×),字母隙加宽至 6u | 双击第 2 次触发;三层兜底第 2 层 |
| 候选对照档 | u=110ms,字母隙 5u | 密码本「念给你听」的质感 |
| 音色 | 电文 750Hz / 候选 620Hz,正弦 + 5ms cos 边沿,gain 0.5 | **频率差 = 「电波里的」vs「密码本上的」**,防听感混淆 |
| 首尾 | lead-in 400ms 静音,结尾 400ms 静音 | 排程器自然包含 |
| 时长例 | BAY 原速 ≈2.6s / HAND ≈2.8s / TOP ≈2.8s;慢放 ≈2× | 单次听觉负荷远低于 40s 红线 |
| 事件钩子 | `morse.play(word,{hz,unit})` 逐元素回调 `{type:'dit'|'dah',letter}` | playtest **固定字符断言**挂点(§9) |
- 为什么运行时合成而非 SA3/SYN 文件:①时值精确是玩法命根(H···· vs S··· 只差一个 80ms 的 dit,ogg/m4a 有损编码在短音上有数 ms 抖动与预卷,文件方案直接否决);②三档速度 × 双频率 × 13 字母的组合用文件不可维护;③零资产缺失风险。guide earcon(14 号裁定 3)运行时合成先例。
### 2.2 电文与候选设计表(信息单源:真词/候选/辨析位写死)
| 封 | 电文名 | 真词(义) | 候选(义) | 共享 | 辨析位 | 密码本提示揭示 |
|---|---|---|---|---|---|---|
| T1 | 船期 | **BAY**(货船) | DAY(客车)/ RAY(巡逻艇) | _AY | 1:B(−···)/D(−··)/R(·−·) | 第 1 字母是 B,一长三短 |
| T2 | 名单 | **TOP**(黑名单) | TAP(窃听令)/ TIP(线人费) | T_P | 2:O(−−−)/A(·−)/I(··) | 第 2 字母是 O,三长 |
| T3 | 交接 | **HAND**(今夜交接) | SAND(明晨交接)/ LAND(地点变更) | _AND | 1:H(····)/S(···)/L(·−··) | 第 1 字母是 H,四短 |
| T4 | 处置令 | **SPY**(内线确认) | SAY(约谈)/ SLY(内部排查) | S_Y | 2:P(·−−·)/A(·−)/L(·−··) | 第 2 字母是 P,短两长短 |
- **候选纪律**:三候选等字母数、共享其余字母、唯一辨析位;**提示揭示辨析位字母 ⟹ 候选唯一**(每行验算成立,playtest 断言「提示流可全对」)。
- **难度曲线**:T1(B/D 差一个尾 dit,教学期有慢放+免费提示兜底)→ T2(中间字母 O/A/I 时值差异大,最易)→ T3(H/S/L:H 与 S 只差一个 80ms 尾 dit,全作最狠听辨)→ T4(P/L 需听完整字母,中高)。曲线说明:难度=听辨精度,操作面恒定(左右滑+单击+双击)。
- 电文摩尔斯(固定字符断言对象):BAY=`−··· / ·− / −·−−`;TOP=`− / −−− / ·−−·`;HAND=`···· / ·− / −· / −··`;SPY=`··· / ·−−· / −·−−`。
### 2.3 解码交互流(DECODE 态内)
```
RX 链:rx*_in(引入)→ sfx/tune_seek → 电文摩尔斯首播(原速 750Hz,auto,不可跳)
→ guide.turn → dv*_ask(提示句,choiceArmed)→ 浏览等待
浏览:左右滑 = 循环切换候选(每切自动预听:候选摩尔斯 620Hz 对照档 → 静默一拍 → cand_* 词义句)
双击 = 重听电文(原速);【再双击】= 慢放 0.5×(两级内容不同:速度即差异)
长按(语音空闲)= 翻密码本(§2.5)
单击 = 进确认:cf_confirm + 电文重播原速一遍 → 再单击 = 定下(判定出口 800ms 死窗)
确认态:左右滑 = 取消回浏览;双击 = 照常重听电文
```
- **三层兜底**(成本递增的梯度,全部可独立走通,P3 嘈杂/P4 疲惫的双保险):
| 层 | 手段 | 成本 | 覆盖 |
|---|---|---|---|
| 1 正常译 | 原速电文 + 候选对照预听匹配 | 最快 | 安静环境/耳力好 |
| 2 慢放 | 双击第 2 次 0.5×,**无限次免费** | 时间 | 通勤嘈杂听不清长短音(P3);无限兜底层 |
| 3 提示直译 | 密码本 3 页,每页直译 1 封的辨析位字母 | 资源(页) | P4 保底:提示+候选对照 ⟹ 唯一解(§2.2 验算) |
- 教学期(T1):dv1_teach/dv1_teach2 先教「双击重听→再双击慢放」「左右滑候选、单击选定」;T1 判定有**即时反馈**(对=success 震+dv1_right;错=dv1_wrong 即时真话归因+无限重选,零惩罚)——正片 T2-T4 判定**无对错反馈**,直入剧情(译错是你不知道的分支)。教学承诺「练习有答案,正片靠耳朵」在 dv1_right 说破。
### 2.4 密码本机制(提示额度经济)
| 项 | 规则 |
|---|---|
| 页数 | 初始 3 页(叙事=北风给你的三页「电码补遗」);**全局不再补页** |
| 兑换 | DECODE 空闲长按(E1 豁免)或菜单「翻密码本」项(P4 冗余入口);消耗 1 页 → 播 hint_*(「第 N 个字母,是 ○」+ 摩尔斯口述),留在浏览态 |
| 教学免费 | T1 第 1 次翻页**不扣**(hint_t1:「我教你一页」)——Prime→Teach 承诺兑现 |
| 账目真实 | 状态链随时可查:book_3「还剩三页」/ book_2「还剩两页」/ book_1「只剩最后一页」/ book_0「一页都不剩了」——**逐值整句,不拼接** |
| 页数用尽 | 再翻 = error 震 + sfx/paper_page(翻空本声),无语音(零静默、不轰炸) |
| 与结局关系 | 页数不进结局判定(只换准确度);纯提示流(T1 免费+3 页)可全对(§5.5 剧本 E 验算) |
### 2.5 译错归因真话(detective 信息单源思想)
| 场景 | 播报 | 真话依据 |
|---|---|---|
| T1 译错(教学) | dv1_wrong「不对。听清楚:B,一长,三短。」 | 真词字母的摩尔斯**写死在句里**,与 §2.2 电码表一致;无限重选零惩罚 |
| 正片译错 | 不揭穿:rev_* 按你选的候选播「你的认知」(rev_top/tap/tip…) | 旁白只说你以为的事;真词唯一、由数据表单源决定 |
| 后果分支 | 你的行动按认知展开(报错船期/留错时间/呈报错令);暴露度 +1 | 「译文进了纸面,与对家底档不符」——事件表单源(§5.2) |
| 真相揭晓 | 尾声 ep_hand_no「码头的灯,白亮了一夜」/ ep_spy_no「差的那三个字母,差了一整条命」 | ep_* 由 flags(t3==hand 等)选播,**播报=存储**;禁止「运气不好」类假归因 |
## 3. 状态机
### 3.1 状态列表
| 状态 | 内容 | BGM | guide.state |
|---|---|---|---|
| `INTRO` | 开场链:夜班开场 + 一句规矩 + 今晚目标 + 处长夜令 | `bgm/radio_room` | explore |
| `RX` | 截获:引入句 + 调谐 + 电文摩尔斯首播(auto 链) | `bgm/radio_room`(摩尔斯期间 −6dB) | explore;摩尔斯首播后由 turn cue 承担 |
| `DECODE` | 解码小局:浏览/确认/重听/慢放/提示(§2.3) | 同上 | choice |
| `RELAY` | 处置叙事:rev_* 揭晓链 + 后果 + 叙事抉择 p2/p4/p6/p8 | `bgm/radio_room`;RELAY(4) 起 crossfade 至 `bgm/closing` | explore;抉择点=choice |
| `NIGHT` | 夜结算:n* 过渡句 + 暴露档位跨档播报(如有),auto ≤12s | `bgm/night` | settle |
| `ENDING` | 结局链 + 尾声回响(epilogue),结算态 | `bgm/ending` | settle |
| `MENU` | 模态覆盖层(任意叙事态下滑呼出,暂停一切链) | `bgm/radio_room` 低响度淡入 | menu |
- 附 `ASK`(存档询问态,radio 同款):单击=继续上次 / 右滑=新夜班;上滑/下滑/左滑/双击=无效(error 震+gd_invalid)。
### 3.2 转移图(文本)
```
┌──────────── 结算单击=新夜班 ────────────────┐
▼ │
INTRO ──链完──> RX(1) ──首播完──> DECODE(1) ──两段确认+判定──> RELAY(1) ──链完──> NIGHT ─┤→ RX(2)
(调谐+引入+摩尔斯首播 浏览/确认/重听/慢放/提示 揭晓+p*+结果链 │ …(T2/T3 同型)…
auto,首播不可跳) (T1 判定有即时反馈) │
└──────────────────────────────────────────────────────────────────────┘
RELAY(4) ──链完──> 签收 g9_in → g9_knot → ◆p9 ──> d9 结果链 ──> ENDING(e1..e4 + ep 回响链)
任意叙事态(INTRO/RX/DECODE/RELAY/NIGHT/ENDING):下滑 → MENU(模态)
MENU:单击=循环切换选项 → 双击=确认执行 → 上滑=帮助 → 下滑/「回到电波」项=关闭回原状态
(RELAY 关闭后重播当前节点;DECODE 关闭后重挂浏览并自动预听当前候选——局面复述)
```
- RX 首播链与 NIGHT 链 `auto:true`;DECODE 浏览为武装等待;RELAY 叙事节点链末等待单击(radio 实现核对 #1 同口径)。
- 判定出口(DECODE 第 2 击确认)、NIGHT 链、d9 结果链、e* 结局链入口处盖 `_settleGraceUntil = now+800ms` 手势死窗(dungeon H1/abyss 同款),窗内手势一律吞掉——揭晓与结算链完整播出。
- MENU 打开时 `guide.disarm()` + 取消一切看门狗/自动链 + 停摩尔斯排程;关闭后按原状态复述(L5 局面复述,radio closeMenu 先例)。
### 3.3 每状态手势映射(6+2 全覆盖)
| 手势 | INTRO/RX | DECODE 浏览 | DECODE 确认 | RELAY | NIGHT | ENDING | MENU |
|---|---|---|---|---|---|---|---|
| 上滑 | 帮助+状态,随后重播当前段 | 状态链(prog_t→book 档→exp 档→sys_help,状态在前),留在浏览 | 同左 | 帮助+状态,随后重播当前节点 | 帮助+状态,自动链继续 | 帮助+状态(结局名+档) | 帮助(sys_help) |
| 下滑 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 关闭菜单 |
| 左滑 | 无效 → error 震+gd_invalid | 上一候选(循环,预听);**首播中=beat 轻震**(摩尔斯不可跳) | 取消确认,回浏览 | 抉择=左 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 |
| 右滑 | 无效 → gd_invalid | 下一候选(循环,预听) | 取消确认,回浏览 | 抉择=右 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 |
| 单击 | 播放中=跳过本句;链末=继续 | 进确认:cf_confirm + 电文重播 | **定下判定**(800ms 死窗 → 揭晓链) | 播放中=跳过本句(p* 不可跳);链末=继续 | 跳过本句(自动链) | **再来一局**(新夜班) | 循环切换选项 |
| 双击 | 重听当前段 | **重听电文(原速);第 2 次=慢放 0.5×**(任何其他手势重置计数) | 同左(确认态保留) | 重听当前节点台词(含抉择提示) | 重听夜结算链 | 重听结局链 | **确认执行**(菜单约定覆盖重听) |
| 长按 | 引擎接管:旁白临时 1.7×(12 号 §2.2,叙事类不豁免) | **空闲=翻密码本(E1 豁免)**;语音在播=引擎快进 | 同左 | 引擎快进 | 引擎快进 | 引擎快进 | 引擎快进(菜单语音) |
| 长按松开 | 引擎接管:恢复 settings.speed | 同左 | 同左 | 同左 | 同左 | 同左 | 同左 |
**豁免登记(必须显式,仅 1 条)**:
| # | 豁免 | 理由(先例) |
|---|---|---|
| E1 | DECODE 空闲长按 = 翻密码本求提示(核心资源动词快捷入口) | 绑定条件=无语音在播(`!audio.isVoicePlaying()`),与长按快进(仅在播时生效)**时间互斥、零冲突**(detective E2 同款);摩尔斯为 sfx 域不触发快进;菜单「翻密码本」提供冗余入口(P4 容错) |
- 其余全部符合宪法默认语义:单击=确认/继续(两段确认=确认的两击)、双击=重听(重听对象=电文)、左右滑=选择/移动、上滑=状态、下滑=菜单(00 号 §5.2 模态约定);菜单=单击循环+双击确认。
- 继承 radio G5 跳过公平契约原文:**首播必完整**(摩尔斯首播不可跳、p* 不可跳、链末关键句保护、ASK 恢复首句锁)、**复播可即掐**(锁不随复播重新武装、链中段句可跳、复播滑动照常预缓存)。
- 抉择 prompt(p2..p9)播放中单击不可跳(PROMPT_RE 门)+ beat 轻震(G4);左右滑缓存 `pendingChoice` 播完即生效(radio W2 契约)。
- 无效手势零静默:所有「无效」格 error 震 + gd_invalid(onceKey 同局限次,F1.4 idle-only)。
- **首播保护窗**:电文摩尔斯首播发生在 RX 链尾、DECODE 入口前(§3.2),期间一切玩法手势(左右滑/单击/双击)= beat 轻震(G4),不判无效、不惩罚——摩尔斯走 sfx 域本无跳句通道,「不可跳」由构造保证;首播完 400ms 内 guide.turn + dv*_ask 落地,浏览武装。
### 3.4 摩尔斯/语音双通道仲裁(abyss 声呐双通道 R1-R5 同型)
- **通道定义**:摩尔斯通道 = morse.js 的 osc 排程(电文+候选)+ sfx 层(静电/电键);语音通道 = voice 层(你+北风+处长)。BGM 第三层背景(voice 播时引擎 duck)。
- **R1 摩尔斯不叠语音**:摩尔斯排程只在 `!audio.isVoicePlaying()` 时起振;在播则挂队列表、voice onComplete/句间轮询补发(abyss sonarGate 同款)。候选预听链 = 候选摩尔斯 → 静默一拍(≥500ms)→ cand_* 词义句(给耳朵留对照时间)。
- **R2 电文主导段**(首播/重听/慢放):前景=摩尔斯,BGM dip;voice 只在摩尔斯**结束后**接播(dv*_ask 固定在首播后)。
- **R3 BGM 退位**:摩尔斯排程期间游戏层把 BGM 降 −6dB(abyss 远程脉冲垫先例),不消失(A2 无静默死局)。
- **R4 危机拍优先**:T4 揭晓 danger 拍(step_halt 前置 2s 渐强 + danger 签名 + danger 震);当前 voice 句播完后危机链才插续(radio C5/abyss D2 同款)。
- **R5 摩尔斯可重听**:DECODE 双击=重听电文(P1 关键线索可重听;第 2 次=慢放是本作对 R5 的加强)。
- 声流账:任意时刻 ≤2 条前景流(voice 串行 1 + 摩尔斯/信息音 1),BGM/amb 为背景层——ASA ≤2 约束 ✓。
**引擎能力边界(对照 docs/web-engine-api.md)**:
| 能力 | 引擎现状 | 方案 |
|---|---|---|
| 摩尔斯精确时值 | sfx 走文件(有损编码 ms 抖动) | **运行时 osc 模块**(§2.1;guide earcon 先例),三档速度一个参数 |
| morse 与 voice 互让 | ❌ sfx 总线独立无 duck | 游戏层 morseGate 空闲门(R1) |
| BGM 手动 dip | ❌ 引擎只对 voice duck | 游戏层 gain:摩尔斯期间 −6dB(R3) |
| voice 运行时滤波 | ❌ | **不使用**(detective 先例:区分度靠 speaker+instruct+语境;处长=当面训话不需滤波) |
| sfx 循环 | ❌ 即发即弃 | 游戏层 `buffer.loop=true`(amb_room/static_bed,radio `_ringSrc` 先例) |
| 变速不变调 | ✅ SoundTouch(**仅 voice**) | **摩尔斯不受 settings.speed/长按快进影响**(u 恒定)——边界登记:P1 2.2× 档语速下电文仍原速,慢放兜底;列入 live 真人耳测项 |
## 4. 抉择树
### 4.1 全局骨架(四封电文共享主干,分歧局部化)
```
INTRO: b_open → b_rule → b_goal → b_chief ──> RX(1)
【T1 船期·教学】BAY 译词 ◆p1 + 报/压 ◆p2 ──> NIGHT(1) ──> RX(2)
【T2 名单】TOP 译词 ◆p3 + 暗处理/呈报 ◆p4(known/blind 差分)──> NIGHT(2) ──> RX(3)
【T3 交接】HAND 译词 ◆p5 + 亲自/留书 ◆p6 ──> NIGHT(3) ──> RX(4)
【T4 处置令·高潮】SPY 译词 ◆p7 + 处长试探 ◆p8(反喂/装傻,known/blind 差分)──> 签收 ◆p9 ──> ENDING
```
分支纪律:**每封电文内部分支、夜结算时收敛**;跨电文只传标量(expose、hints、4 个译文 flag、4 个抉择 flag),不生成组合节点。最坏路径与最好路径节点数同型。
### 4.2 分封结构(◆=抉择点,〔〕=条件差分)
```
【T1 船期·教学】
RX(1): rx1_in → tune_seek → 【摩尔斯首播 BAY,不可跳】→ dv1_teach → dv1_teach2 → dv1_ask → 浏览
◆p1 译词(3 选 1,两段确认)
├候选 BAY(对): dv1_right(success 震)──────────────────────┐
└候选 DAY/RAY(错): dv1_wrong(即时真话归因)→ 回浏览无限重选 ┘(T1 恒收敛为 BAY)
RELAY: rev 由 dv1_right 承担 → ◆p2
├左 报北风: r1a_send → r1a_e(med_ok 预埋)──┐
└右 压日志: r1b_hold → r1b_e(expose+1)────┴→ NIGHT(1): n1 → 〔跨档 exp_*〕→ RX(2)
【T2 名单】
RX(2): rx2_in → 首播 TOP → dv2_ask → 浏览 → ◆p3 译词
├TOP(对): rev_top「黑名单。头一个代号,叫码头」→ p4_known ──┐
└TAP/TIP(错,不揭穿): rev_tap / rev_tip → p4_blind ─────────┤(expose 译错+1)
◆p4: ├左 暗处理: r2a_act → r2a_e(expose+1;known=warn 码头 / blind=压下)┐
└右 呈报: r2b_act → m2_chief → r2b_e(mateListed)────────────────┴→ NIGHT(2): n2 …
【T3 交接】
RX(3): rx3_in → 首播 HAND → dv3_ask → 浏览 → ◆p5 译词
├HAND(对): rev_hand → 〔p6 行动按「今夜」〕────────────┐
└SAND/LAND(错): rev_sand / rev_land → 〔按误译时间行动〕┤(expose+1)
◆p6: ├左 亲自去: r3a_go → r3a_e(expose+2)──────────────┤
└右 留书暗号: r3b_act →〔t3==hand ? r3b_e_ok : r3b_e_no〕┴→ NIGHT(3): n3 …
【T4 处置令·高潮】
RX(4): rx4_in → 首播 SPY → dv4_ask → 浏览 → ◆p7 译词
├SPY(对): rev_spy(danger 拍:step_halt 前置 2s+签名+震)→ p8_known ──┐
└SAY/SLY(错): rev_say / rev_sly → p8_blind ──────────────────────────┤(expose+1)
处长试探: m4_chief_a → m4_chief_b → ◆p8
├左 反喂假电: r4a_feed(配 key_send)→ r4a_e(expose+2,feed=true)──┐
└右 装傻: r4b_act → m4_chief_c → r4b_e ────────────────────────────────┴→ 签收
【签收】g9_in → g9_knot → ◆p9〔t4==spy ? p9_known : p9_blind〕
├左 发最后一电: d9_send(key_send)→〔pre≤4 ? d9_send_e : d9_caught_e〕→ ENDING(e1|e3)
└右 关机: d9_sleep → d9_sleepe → ENDING(e2|e4)
ENDING: e*_a → e*_b(按 expose 档 × p9 四选一)→ ep_lead → ep_med_* / ep_list_* / ep_hand_* / ep_spy_*(按 flags 选播)
```
### 4.3 分支局部化说明
- 跨电文状态全部是标量:`expose`(int)、`hints`(int 0-3)、译文 flag `t1..t4`(每封所选候选 id;T1 恒真词)、抉择 flag `p2..p9`(左/右)。无任何「电文 i × 电文 j」组合节点。
- 条件差分全部是**节点内选播**(radio w1_say/w1_say2 先例):p4/p8/p9 的 known/blind 双提示(由 t2/t4 决定)、r3b_e_ok/no(由 t3 决定)、d9_send_e/caught_e(由 expose 档决定)、ep_* 8 句(由 4 组 flag 决定)。树形不变。
- 树规模:139 句台词、约 75 个节点;任一路径恒走 9 个抉择点(4 译词 + p2/p4/p6/p8/p9)——比 radio/abyss 更规整(无跳点路径),操作面完全恒定(P4)。
### 4.4 四结局达成条件
| 结局 | 条件 | 主题 | 开环钩子 |
|---|---|---|---|
| e1 最后一电 | p9 前 expose ≤4 且 p9=左 | 警报发出、北风撤尽,你随挑水的队伍出城 | 「新的密码本,在城外渡口等我」 |
| e2 值完这个班 | p9 前 expose ≤4 且 p9=右 | 平安交班,一切照旧——但北风的呼叫没人接 | 「下一次呼叫,你接不接?」 |
| e3 孤灯 | p9 前 expose ≥5 且 p9=左 | 发报被测向,你拍了三遍警报,坐等门开;码头撤空 | 「审讯室的灯,也很亮」 |
| e4 长夜 | p9 前 expose ≥5 且 p9=右 | 两点五十九分,脚步停在门口;最后一页密码进了炉子 | 「耳机里还有一封译不完的电文」 |
- 处置令的烈度随暴露档缩放(fiction 自洽):低暴露=处长没有足够把握(约谈/排查);高暴露=今夜三时收网。
- 尾声回响(P2):ep_lead 后逐封点名——药品(接住/白过江)、名单(少了一个人/多了红圈)、交接(上船/白亮一夜)、处置令(译对/差三个字母)——玩家每一封的译与错都被点名。
## 5. 数值设计
### 5.1 变量
| 变量 | 类型 | 初值 | 说明 |
|---|---|---|---|
| `expose`(暴露度) | int | 0 | 步进隐藏资源:译错/危险抉择累积,只涨不跌;档位句播报(§5.3) |
| `hints`(密码本余页) | int | 3 | 翻一页 −1;T1 首次免费(不扣);全局不补 |
| `t1..t4`(译文) | str | null | 各封所选候选 id;T1 教学期恒收敛真词;t2/t3/t4 记玩家认知 |
| `p2/p4/p6/p8/p9` | 'L'/'R' | null | 叙事抉择;派生 med_ok/mateListed/handOk/feed 等 flag |
| `endings{}` / `runs` | map/int | — | 结局收集与重玩计数(存档展示用) |
### 5.2 暴露度累积事件表(单源,账目可验算)
| 事件 | 条件 | Δexpose | 叙事依据(真话) |
|---|---|---|---|
| T1 译错 | 教学期无限重选,恒收敛 | **0** | 学习期零惩罚(A5) |
| T2/T3/T4 译错 | 选非真词(无论后续左右) | **+1/封** | 你的译文进了纸面(值机日志/呈报/交接条),与对家底档不符 |
| p2 右·压日志 | T1 | +1 | 值机日志少一封,晨检对不上号 |
| p4 左·暗处理 | T2 | +1 | 卷宗缺页/抽屉藏报,暗手的痕迹 |
| p6 左·亲自去 | T3 | +2 | 值班室空岗一刻钟,日志停了一行 |
| p8 左·反喂 | T4 | +2 | 拍假电的机器记录抹不掉(哪怕手法伪装了) |
| p9 左·发报 | 终局 | +2(**结局判定用 p9 前值**) | 开机信号进了值机室的登记 |
- 上限:3(译错)+1+1+2+2 = 9(p9 前);下限 0。全对全保守 = 0(calm 档),符合「一晚干净值机」。
- 每次事件**不播数字**(隐藏资源);危机感由档位句+氛围音承载;状态链随时可查档位(radio 口碑同型)。
### 5.3 暴露度档位表(措辞逐值为真)
| 档 | expose | 整句 | 真实性核验 |
|---|---|---|---|
| calm 平安 | 0 | 「这一晚,还没人看你第二眼。」 | 0 事件 ✓ |
| low 疑云 | 1-2 | 「处长端着茶,多看了你一眼。」 | 1-2 个纸面破绽=被注意 ✓ |
| mid 被盯 | 3-4 | 「走廊里的脚步声,慢了下来。」 | 3-4 个破绽=例行留意 ✓ |
| hot 危局 | ≥5 | 「门外,好像有人换了岗。」 | ≥5=行动级怀疑(与 p9 收网门 ≥5 一致)✓ |
- 跨档才播(挂 NIGHT 链尾);上滑状态链随时可查;hot 档各播报位附 warning 震(abyss oxy_crit/G2 同款)。
- **p9 收网门 = hot 档阈值 5**:档位句说的就是结局判定用的(radio rep_low/abyss floor(oxy/3) 教训内化——措辞与数值同一个源)。
### 5.4 密码本账(逐值整句)
| 余页 | 整句 |
|---|---|
| 3 | 「密码本,还剩三页。」 |
| 2 | 「密码本,还剩两页。」 |
| 1 | 「密码本,只剩最后一页。」 |
| 0 | 「密码本,一页都不剩了。」 |
### 5.5 暴露度与四结局验算(设计期穷举,playtest 剧本引用)
设 `wrong` = T2/T3/T4 译错封数(0-3),`risk` = 危险抉择分(p2右1 + p4左1 + p6左2 + p8左2,0-6):
- **p9 前 expose = wrong + risk** ∈ [0, 9];结局门:≤4 → e1/e2(p9 左/右);≥5 → e3/e4。
- **剧本 A(全对全右,p9 左)**:wrong=0,risk=0 → expose=0(calm)→ p9_known 左 → d9_send_e → **e1**;ep 全 ok(med_ok/list_no/hand_ok/spy_ok——p4 右呈报=名单进卷宗,ep_list_no 是呈报的真实代价)。
- **剧本 B(同 A,p9 右)**:expose=0 → **e2**(d9_sleep/d9_sleepe)。
- **剧本 C(全错全左,p9 左)**:wrong=3,risk=1+2+2=5 → expose=8(hot)→ p9_blind 左 → d9_caught_e(danger 拍)→ **e3**;逐检查点:NIGHT(1) 后 0(p2 左不加分)→ calm;NIGHT(2) 后 2(wrong T2=1 + p4 左=1)→ low;NIGHT(3) 后 4(+wrong T3 +p6 左 2)→ mid;签收前 8(+wrong T4 +p8 左 2)→ hot。跨档播报 3 次。
- **剧本 D(同 C,p9 右)**:expose=8 → **e4**。
- **剧本 E(提示流 P4)**:T1 免费提示(hints 3 不动)+ T2/T3/T4 各 1 页(3→2→1→0,book_2/1/0 逐值播出)→ 提示揭示辨析位 ⟹ 候选唯一 → 全对;全右 → expose=0 → e1/e2 任选。**纯提示流可通关 ✓**。
- **剧本 F(慢放流 P3)**:T2-T4 每封双击×2 慢放后译对,零提示 → 全对;expose=0。**纯慢放可通关 ✓**。
- 边界带验算:wrong=2 + risk=2(p2右+p4左)= 4 → 仍 e1/e2(宽容:两处失误不毁局,P4);wrong=1 + risk=4 = 5 → e3/e4(风险路径必热)。
## 6. 音频三层映射
### 6.1 BGM(状态→曲目,Mureka instrumental,4 首 ≈ $0.12;共性后缀 10 号 §8 照 radio 追加)
| 状态 | 曲目 | 切换 | Mureka prompt 草稿(英文,seamless loop) |
|---|---|---|---|
| INTRO/RX/DECODE/RELAY/MENU | `bgm/radio_room` | 开局淡入 800ms;MENU 低响度淡入 500ms;摩尔斯期间 −6dB(R3) | "1940s late night wireless station room, warm tube radio hum, distant muffled swing jazz through a wall, rain on window, clock tick, smoky and quiet, no vocals, seamless loop, 60-90 seconds" |
| RELAY(4) 起(T4 高潮至签收) | `bgm/closing` | crossfade 800ms 进 | "Tense noir waltz for a spy's last night, muted brushed drums, low strings, subtle telegraph tick rhythm, restrained dread, no vocals, seamless loop, 60-90 seconds" |
| NIGHT | `bgm/night` | crossfade 600ms 进;进下一 RX 淡回 600ms | "Quiet night interlude, sparse felt piano over warm vinyl static and soft rain, lonely but safe, no drums, no vocals, seamless loop, 60-90 seconds" |
| ENDING | `bgm/ending` | d9 结果链播完后淡入 1500ms | "Dawn after a long night of rain, warm strings and soft piano, exhausted hope, quiet resolution, no vocals, seamless loop, 60-90 seconds" |
- `bgm=0` 时全部静默(引擎约定);menu 用同曲低响度(省 1 首,值班室同场景同构)。
### 6.2 SFX 清单(SA3=Stable Audio 3 / SYN=程序合成;one-shot 真峰值 ≤-3dBFS;amb 循环由游戏层 `buffer.loop` 实现)
| id | 用途 | 来源 | prompt(英文)/ 合成参数 |
|---|---|---|---|
| `sfx/tune_seek` | 调谐找频(每封 RX 起) | SA3 | "Hand turning a vintage radio tuning dial, static sweeps, faint morse ghost, 3 seconds" |
| `sfx/static_bed` | 电台静电底噪 loop(RX/DECODE) | SA3 | "Shortwave radio static bed, soft crackle and hum, seamless loop" |
| `sfx/amb_room` | 值班室 amb loop(挂钟+雨+暖炉) | SA3 | "1940s office night ambience, wall clock tick, rain on window, faint stove hum, seamless loop" |
| `sfx/paper_page` | 密码本翻页(提示兑换/开局发本/页数用尽) | SA3 | "Thin paper page turn of a small codebook, dry, close" |
| `sfx/key_send` | 你的电键拍发(p8 左反喂 / p9 左发报) | SA3 | "Vintage brass telegraph key sending morse, spring clatter and clicks, 4 seconds" |
| `sfx/pen_scratch` | 处长记事笔(试探戏/呈报) | SA3 | "Fountain pen scratching on paper, short strokes, 2 seconds" |
| `sfx/tea_cup` | 茶杯落桌(处长场标记音) | SA3 | "Tea cup set down on wooden desk, porcelain clink, soft" |
| `sfx/step_halt` | 门外脚步停住(T4 danger 拍 / hot 档) | SA3 | "Footsteps in corridor stopping outside a door, heavy boots, tense, 4 seconds" |
| `sfx/lock_click` | 门锁/枪机(e3/e4 链) | SA3 | "Door lock slowly turning, metal click, ominous, 2 seconds" |
| `sfx/dawn_bell` | 黎明远钟(e1/e2 链) | SA3 | "Distant morning bell over light rain, calm after a long night, 4 seconds" |
| `sfx/ui_confirm` / `ui_menu` | 菜单/确认(程序合成兜底) | SYN | 同全局先例 |
| **(摩尔斯)** | 电文/候选/重听/慢放全部音 | **SYN 运行时模块** | §2.1 时值表;**零文件资产** |
- 预警窗纪律:T4 danger 拍 step_halt **前置 2s 渐强**(L2),danger 签名与音效同窗(radio 刹车/abyss 滑坡先例);e3 链首 danger 签名一次。
- 承诺绑定:rx4_in 教「快手七的手法你听过」后,p9 左 d9_send 与 p8 左 r4a_feed 必播 key_send(§7.5 清单+断言)。
### 6.3 Voice 划分(详见 19-cipher-voice-ids.md)
| 组 | 声部 | 落位 | 数量 |
|---|---|---|---|
| 报务员(你)OP | 双性别(female=Serena / male=Uncle_Fu + OP instruct 常量) | `voice/female\|male/` | 20 句 |
| 北风电台(上线联络+系统/向导)WIND | 双性别(female=Vivian / male=Dylan + WIND instruct 常量) | `voice/female\|male/` | 114 句 |
| 处长 NPC | 平铺单音色(Eric),**不做双性别** | `voice/` 根(平铺回退命中) | 5 句 |
- 人物账:有台词人物 = 你 + 北风(上线联络人,向导人设 L4,radio 导播同型)+ 处长(明线上级)——**上级/联络人 ≤2 约束满足**;另有 1 个**无台词角色**「快手七」(对家报务手,存在=电波里的手法音,abyss 小郑先例)。
- 合计 139 句(≤150 预算 ✓,含 `_k` 键盘变体 11 对);音频文件 = 134×2 + 5 = 273 条(×2 格式 = 546 文件)。
## 7. 向导层接入
### 7.1 状态签名 earcon / turn cue
| 时机 | 向导动作 | 说明 |
|---|---|---|
| INTRO 进入 | `guide.state('explore')` | 叙事态签名 |
| DECODE 进入(首播后) | `guide.state('choice')` + `guide.turn({haptic:true})` | 摩尔斯首播完 → turn 签名+confirm 震=「轮到你选词」;dv*_ask 随后落地 |
| 每个叙事抉择 p* 起播 | `guide.state('choice')` | 选项句本身即 turn cue(W2 契约零插播) |
| NIGHT 进入 | `guide.state('settle')` | 夜结算签名 |
| ENDING 进入 | `guide.state('settle')` | 同上 |
| MENU 打开 | `guide.state('menu')` | 菜单签名 |
| T4 揭晓 danger 拍 / e3 链首 | `guide.state('danger')` | 全游戏仅两处(step_halt 前置 2s 同窗 + danger 震) |
| 无效手势 | `guide.error({voice:'gd_invalid', onceKey})` | error 震 + full 档语音,idle-only(F1.4) |
- earcon 词表:只用全项目统一 8 音中的 7 个(menu/explore/choice/settle/danger/turn/nudge),**零新增**;battle 不用。
### 7.2 看门狗双轨(radio F1 + dungeon E0-1 教训:乱按也要被抢救)
**无操作轨**(guide watchdog,任何手势复位;两级不同句、用尽即停、永不惩罚):
| 等待点 | full 档 | standard 档 | 级别内容 |
|---|---|---|---|
| DECODE 浏览无操作 | [10, 25] | [10, 25](onEscalate 游戏层补播) | ① gd_tune1「电文在等。左右滑,听候选词。」② gd_tune2「双击,把电文再听一遍。再双击,会放慢。」(**必须不同句**) |
| INTRO/RELAY 链末等待 | [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 |
**无进展轨**(游戏层计时,仅挂 DECODE 浏览一个可饿死点):
| 项 | 规则 |
|---|---|
| 进展定义 | 译词判定落定 / 叙事抉择结算 / 节点推进 / 翻密码本。**无效手势与纯浏览(切候选/重听)不计进展**——乱滑 96s 也必被抢救 |
| 阈值 | 60s → gd_prog1「光切词没有用。双击,先听电文。」;120s → gd_prog2「别急。停下来,把电文听完整。」;用尽即停(每封一轮) |
| 有真实进展 | 全程零 prog 播出(不误伤) |
| 双轨去重 | 两轨短句内容同级交叉登记;同一等待点先播过 gd_tune* 则无进展轨跳过同义级 |
- 向导插播一律 `queue:true`,绝不顶掉在播对白;error/watchdog 类语音 idle-only(在播则跳过且过期丢弃,F1.4)。
- 单句语音看门狗(资产缺失兜底):30s 超时且 `isVoicePlaying()===false` 才自动续链,长句续约不掐(radio N1 先例照抄)。
### 7.3 键盘变体机制(PC 键盘玩家)
- 运行时按 `gesture.inputMode()`(settings.input auto/touch/key)选 `_k` 变体资产:key → `voice/<gender>/<id>_k.*`,touch → 无后缀版;auto 跟随最近使用通道。`_k` 缺失回退无后缀版(check_all_assets 校验配对)。
- 变体清单(11 对,集中分组见 voice-ids §G0.a + M1):gd_brief / gd_continue / gd_again / gd_tune1 / gd_tune2 / sys_help / sys_menu / sys_skip / sys_continue_ask / **dv1_teach2**(首个候选导航绑定)/ **p2**(首个叙事抉择绑定)。
- 抉择提示 p4..p9 措辞统一「向左滑/向右滑」;键盘玩家由 p2_k 与 sys_help_k 完成首次绑定,后续语义不变。启动遮罩文案引擎自理,游戏零改动。
### 7.4 继承 radio/abyss 三轮验证机制(照抄不重设计)
| 机制 | 来源 | 落点 |
|---|---|---|
| 帮助链去重(同等待点第 2 次上滑只震+nudge) | radio G1 | `_helpSeen` 按等待点键控 |
| 状态链顺序「状态在前、帮助在后」 | radio G6 | prog_t → book 档 → exp 档 → sys_help |
| 空点确认轻震(不可跳上单击补 beat [8]) | radio G4 | p*/摩尔斯首播/链末句各 tap 屏蔽分支 |
| exp_hot 播报位附 warning 震 | radio G2 / abyss | 全部 hot 出口(含上滑复听) |
| 菜单报名游戏侧排队零截断 | radio 实现修复 #3 | `_menuQueue` 排水 |
| stop() 先 `seqToken++` 作废在途链 + 停摩尔斯排程 | radio 实现修复 #6 | 同款(摩尔斯句柄一并 stop) |
| 新局清 choiceArmed/pendingChoice/双击慢放计数 | radio 实现修复 #7 | newNight() |
| 长按误报无效(longpress 全状态合法) | radio 实现修复 #2 | 仅 left/right(ASK 另含 up/down/doubletap)报无效 |
### 7.5 教学承诺清单(dungeon「铁器=楼梯」教训:每句承诺必须真实存在且可感知)
| 教学句 | 承诺 | 兑现点(断言锁定) |
|---|---|---|
| dv1_teach「先双击,把它重新听一遍」 | 双击=重听电文 | DECODE 全程恒可用(原速) |
| gd_tune2/dv1_teach「再双击,会放慢」 | 第 2 次双击=慢放 0.5× | 慢放计数器任何封恒在;时值断言 u=160 |
| dv1_teach2「左右滑,听候选词。单击,选定一个」 | 候选预览/两段确认 | 全部 DECODE 恒同语义(含确认态取消) |
| hint_t1「我教你一页:第一个字母,是 B」 | T1 提示免费 + 提示给辨析位字母 | T1 翻页不扣;hint_* 与 §2.2 辨析位一致 |
| b_rule「规矩就一条:听不清,就再听一遍」 | 重听/慢放无限免费 | 全局无重听惩罚;慢放不限次 |
| b_goal「今晚频道里有四封密电」 | 恒 4 封 | 剧本 A 恰 4 封走完(断言) |
| b_chief「四封电文,一封都不许漏」 | 同上(明线口径) | 同一计数 |
| rx4_in「快手七的手法你听过」→r4a_feed「借快手七的手法」 | 提到发报必闻电键声 | p8 左/p9 左必播 key_send(零漏播断言) |
| dv1_right「练习有答案」(正片无对错反馈) | 教学期即时反馈、正片不揭穿 | T1 判定出口有反馈句;T2-T4 判定出口零对错语音(断言) |
## 8. 存档设计
### 8.1 进度 data 结构(PUT /api/progress {game:'cipher'})
```json
{
"schema": 1,
"updatedAt": "2026-09-09T12:00:00Z",
"summary": "第2封·名单 · 暴露:疑云 · 结局1/4",
"phase": "DECODE",
"node": "dv2_ask",
"tel": 2,
"expose": 2,
"hints": 2,
"t": { "t1": "bay", "t2": null, "t3": null, "t4": null },
"picks": { "p2": "L", "p4": null, "p6": null, "p8": null, "p9": null },
"endings": { "e2": true },
"runs": 3
}
```
- 写入时机:每封 RX 进入、译词判定落定后、每个抉择结算后、NIGHT 进入、ENDING 进入、菜单「保存并回大厅」。
- 断点续玩:读档 → sys_continue_ask(_k)(单击=继续上次 / 右滑=新夜班;两者 runs 均 +1,统一口径修 radio 遗留);继续 = 恢复 phase+node+全部数值并**局面复述**:RELAY 恢复=重播当前节点链;**DECODE 恢复=prog_t → book 档 → gd_tune2 → 重挂浏览(自动预听当前候选,电文可双击重听)**——一句话 ≤5s 回到可玩(L5);恢复首句受 F4 跳句锁(radio G5 例外条款)。
- summary 格式:进行中 `第N封·<电文名> · 暴露:<档名:平安/疑云/被盯/危局> · 结局X/4`;已收台 `已收台·<结局名> · 结局X/4`(大厅卡片直接显示,人话)。
## 9. playtest 断言清单(登记 13 号 §22 指针;`settings.set({guide:'off'})` 纯玩法验证,向导行为住 guide.test)
**关键路径**(剧本 A:全对全右+p9 左 → e1,≤60 次手势):
1. 遮罩点按 → INTRO 链(b_open 起,voice 层有播放记录)→ rx1_in → tune_seek → **摩尔斯固定字符断言**:事件序列 == BAY 编码(`dah,dit,dit,dit / dit,dah / dit,dah,dah,dah`),750Hz,u=80±5ms,BGM 同时段 −6dB(R3)→ dv1_teach → dv1_teach2 → dv1_ask → choiceArmed。
2. DECODE 浏览:右滑 ×3 循环切换,每次预听 = 候选摩尔斯(620Hz,u=110,与 voice **零重叠**——morseGate 断言)→ cand_* 词义句;双击 → 电文原速重播;再双击 → 慢放(u=160,字母隙 6u,750Hz);单击 → cf_confirm + 电文重播 → 单击 → 判定出口 800ms 死窗内注入在途 tap 被吞。
3. T1 译对:dv1_right(success 震)→ RELAY:p2 左(pendingChoice 播完生效)→ r1a_send/r1a_e → n1 → NIGHT(expose=0 calm 档,零跨档播报)→ RX(2)。
4. T2:摩尔斯==TOP 断言;译对 → rev_top → **p4_known** 播出(t2=='top')→ 右 → r2b_act → m2_chief → r2b_e → n2。
5. T3:摩尔斯==HAND;译对 → rev_hand → p6 右 → r3b_e_ok(t3=='hand')→ n3。
6. T4:摩尔斯==SPY;译对 → rev_spy(danger 拍断言:step_halt 前置 2s 渐强 + danger 签名 + danger 震)→ m4_chief_a/b → **p8_known** → 右 → r4b_act → m4_chief_c → r4b_e。
7. 签收:g9_in → g9_knot → **p9_known** 左 → d9_send + key_send → pre=0 ≤4 → d9_send_e → ENDING(e1):e1_a/b + ep_lead + ep_med_ok/ep_list_no/ep_hand_ok/ep_spy_ok → 结算单击 → 新夜班回 INTRO(runs+1)。
**边界断言**:
- **四结局可达(含暴露度逐点验算)**:剧本 B(同 A,p9 右)→ e2;剧本 C(T2/T3/T4 全译错 + p4/p6/p8 全左):NIGHT(1) 后 expose=0(calm)/NIGHT(2) 后 2(low,跨档播 exp_low)/NIGHT(3) 后 4(mid)/签收前 8(hot,exp_hot+warning 震)→ p9_blind 左 → d9_caught_e → e3;剧本 D(同 C,p9 右)→ e4(lock_click 链)。各检查点档位句与 §5.3 区间一致。
- **三层兜底各自可走通**:剧本 E(提示流)——T1 翻页免费(hints 仍 3),T2/T3/T4 各翻 1 页(book_2/1/0 逐值播出,逐页 −1 断言),按提示选定全对 → e1;第 4 封后页数 0 再长按 → error 震 + paper_page,无语音、零惩罚;剧本 F(慢放流)——每封双击×2 慢放后译对,零提示,全对(慢放不限次断言)。
- **摩尔斯正确性**:四封电文事件序列逐元素 == §2.2 表(BAY/TOP/HAND/SPY);候选预听 == 所选候选编码;慢放时值 = 原速 2×(容差 ±5ms);**settings.speed=2.2 会话下 u 仍 80ms**(速度边界断言)。
- **译错归因真话**:T1 错选 DAY/RAY → dv1_wrong 播出且句内字母/码与真词 B 一致 → 无限重选直至对(expose 零变化);剧本 C/D 的 ep_spy_no/ep_hand_no 与 t3/t4 flags 一致;正片判定出口零对错语音(教学承诺)。
- **morseGate**:全剧本转写断言——摩尔斯起振时刻与任一 voice 播放区间零重叠(排队语义:晚于在播句结束);候选词义句在候选摩尔斯结束后 ≥500ms 接播。
- **看门狗双轨**:DECODE 快进 10s→gd_tune1、25s→gd_tune2(两级 id ≠,绝不逐字重播;两级后仅静电底噪);**乱按 96s**(每 2s 注入一次切候选滑动+双击重听):无操作轨零播出(被手势复位)+ 无进展轨 60s→gd_prog1、120s→gd_prog2;有真实进展(译词落定推进)全程零 prog 播出;RELAY 链末 25s/50s → gd_continue/gd_again;p* 播完 25s/50s 重播 p* 两次后静默;`guide=off` 全程零定时器零向导事件。
- **键盘变体**:settings.input='key' 会话播出 gd_brief_k/sys_help_k/gd_tune1_k/dv1_teach2_k/p2_k 且对应无后缀版零播出;'touch' 反之;'auto':keydown 后下一句用 _k。
- **中断复述**:杀进程重进(存档 phase=DECODE, tel=3, hints=1)→ sys_continue_ask → 单击 → 恢复链 prog_t3 → book_1 → gd_tune2 → 浏览重挂 + 当前候选自动预听(电文双击可重听);恢复首句不可跳,第二句起可跳;右滑=新夜班(runs +1 口径同结算重开)。
- **pendingChoice**:p* 播放中左右滑 → 播完立即生效(不丢、不重复、连滑取最后一次)。
- **p* 不可跳**:p* 播放中单击 → 不跳句(PROMPT_RE 门)+ beat 轻震(G4)。
- **idle-only**:向导 gd_* 在对白在播时跳过且不占 onceKey(F1.4);queue 句排在当前句后零截断。
- **菜单**:下滑开菜单 → 上滑帮助 → 双击「回到电波」→ phase/node 不变、原状态复述;单击循环顺序 = [回到电波, 翻密码本, 重新开始今晚, 保存并回大厅];菜单「翻密码本」确认 = 长按同效(回 DECODE 执行提示流程);确认回大厅 → sys_menu_lobby 播出且 PUT/localStorage 有最新 data。
- **资产缺失**:屏蔽任意 voice 资产 → 30s 看门狗自动续链到结局不卡死;摩尔斯为运行时合成,屏蔽 sfx/bgm 全部不影响电文可听(模块零依赖断言)。
- **数字规范**:prog_*/book_*/exp_* 全部整句字面量,静态审计无运行时拼接碎片。
**失败即不通过**:任何等待 >5s 无声(静默死局);无效手势零反馈;摩尔斯字符序列/时值与 §2.2 表不符;摩尔斯与语音重叠;结局计数与 §4.4 不符;档位句与 expose 实值不符;归因假话(dv1_wrong 字母错、ep_* 与 flags 不符)。
## 10. 节奏曲线(每封电文目标时长)
| 段 | 内容 | 目标时长 | 设计目标 |
|---|---|---|---|
| 开场 | 夜班开场 + 一句规矩 + 处长夜令 | 1-1.5 min | 首输入 <60s(INTRO 链 ~25s 后第一封电文首播,浏览滑动即首输入 ~t+45s);规矩一句讲完(P4) |
| T1 船期 | 教学封:两层重听教学 + 免费提示示范 + p2 | 3.5-4 min | **首个正反馈 ≤3min**(译对 dv1_right ≈ t+2min);译错即时归因+无限重选零惩罚 |
| T2 名单 | known/blind 差分首现 | 3-3.5 min | 「你不知道自己译没译对」的第一次心跳;呼吸口(听辨最易) |
| T3 交接 | 最狠听辨(H vs S 差一个 dit) | 3.5-4 min | 慢放价值兑现段(P3 嘈杂场景实测点);NIGHT(3) 档位句把压力可视化 |
| T4 处置令 | 高潮:danger 拍 + 处长试探 + 反喂 | 4-4.5 min | 紧张峰值但无倒计时(P4);bgm/closing 交叉淡入 |
| 签收+结局 | 心结 p9 + 发最后一电 + ep 点名 | 2.5-3 min | 整晚的「听」在你自己的电键上闭环(P2 回响);开环钩子拉下一局 |
- 合计 ≈ 19-22 min(15-25 达标);每 30-60s 一个反馈颗粒(候选预听/摩尔斯/揭晓句/结果句/档位句),无沉默尴尬期(P3)。
- 难度曲线 = 听辨精度(教学 B/D → 最易 O/A/I → 最狠 H/S → 中高 P/L)+ 认知重量(练手 → 名单有同伴 → 时间就是命 → 处置令是你),操作复杂度恒定(左右滑+单击+双击,永远如此)——P4「规则一遍听懂」由恒定操作面保证。
## 11. 自检清单(策划交付自验;☐ = 待开发/测试轮填)
| # | 硬约束/教训 | 落地 | ✓ |
|---|---|---|---|
| 1 | FTUE 首输入 <60s | INTRO 链 ~25s 后第一封首播,~t+45s 首次滑动(§10) | ☑ |
| 2 | FTUE 首个正反馈 ≤3min | 译对 dv1_right ≈ t+2min(§10 T1) | ☑ |
| 3 | 学习期零惩罚 | T1 译错无限重选零资源;DECODE 无超时惩罚;密码本 T1 免费 | ☑ |
| 4 | 失败三问归因+归因真话律 | dv1_wrong 字母真话;正片不揭穿+ep_* 单源对应;§2.5 无假归因 | ☑ |
| 5 | 资源账目真实(radio rep_low 教训) | book_1..0 逐值整句;exp 四档措辞逐值为真;档位阈值=收网门(§5.3/5.4) | ☑ |
| 6 | 手势宪法 6 手势默认语义,豁免显式登记 | §3.3 全覆盖;唯一豁免 E1(长按=密码本,时间互斥 detective E2 同款);菜单 §5.2 模态 | ☑ |
| 7 | 长按显式登记 | E1 在案(空闲=提示/在播=引擎快进,互斥);其余长按=快进不豁免 | ☑ |
| 8 | 向导:签名/turn cue/看门狗两级不同句 | §7.1/7.2;gd_tune1≠gd_tune2;gd_continue→gd_again | ☑ |
| 9 | 看门狗双轨(dungeon E0-1/radio F1 教训) | 无操作轨+无进展轨;纯浏览不计进展,乱按 96s 必被抢救;有进展零打扰 | ☑ |
| 10 | 插播 queue 不顶对白;纠错 idle-only 过期丢弃 | §7.2(F1.4 契约) | ☑ |
| 11 | 死亡/失败结算链完整(dungeon H1 教训) | 判定出口/NIGHT/d9/e* 链 800ms 手势死窗;seqToken+停摩尔斯(§3.2/7.4) | ☑ |
| 12 | 教学承诺兑现(dungeon「铁器=楼梯」教训) | §7.5 九条承诺清单逐条绑兑现点+断言 | ☑ |
| 13 | 单次播报 ≤3 选项 | 译词恒 3 候选逐项预听(≤3 ✓);叙事抉择恒 2;菜单 4 项循环 | ☑ |
| 14 | 并发声流 ≤2 + 双通道仲裁 | voice 串行+摩尔斯互斥(R1-R5,§3.4);断言零重叠 | ☑ |
| 15 | earcon ≤7、统一词表、语义靠语音 | 只用统一 8 音中 7 个(battle 不用),零新增 | ☑ |
| 16 | 台词一句一文件 ≤40 字、数字分档整句、无运行时拼接 | voice-ids 全表 ≤30 字(最长 p8_known);prog/book/exp 分档整句;句内数字汉字写死 | ☑ |
| 17 | 多音字规避/同 instruct 常量/无语速词 | voice-ids 头部+文末自查(长/处/圈/卷/塞/着 试听项) | ☑ |
| 18 | PC 键盘变体(提示句集中+标注) | 11 对 _k 集中分组(voice-ids §G0.a+M1);inputMode() 运行时选择 | ☑ |
| 19 | P1:可打断/重听(含电文)/status key/不当白痴 | 跳句+双击(电文重听+慢放 R5)+上滑状态链+成功零补救教学 | ☑ |
| 20 | P2:3 分钟见效/结局回响 | ~2min 首个「世界因你而动」(药船接应);ep 逐封点名+真词差分 | ☑ |
| 21 | P3:3 分钟颗粒/中断复述/单手/嘈杂 | 一封=3-4min 完整段落+边界存档;重进一句话复述(含 DECODE 态);慢放无限=嘈杂兜底 | ☑ |
| 22 | P4:一遍懂/容错/无高压倒计时/不骂人 | 一句规矩;无倒计时无死亡;译错也是剧情;操作面恒定;NPC 句 ≤23 字 | ☑ |
| 23 | 抉择树 4 结局可走通验算 | §5.5 公式+剧本 A-D 逐检查点(e1/e2: wrong+risk≤4;e3/e4: ≥5) | ☑ |
| 24 | 单局 15-25 min | §10 曲线 ≈19-22 min | ☑ |
| 25 | 分支不爆炸(radio 纪律) | 跨封仅标量(expose/hints/t1..t4/picks);~75 节点固定深度(§4.3) | ☑ |
| 26 | 台词 ≤150、上级/联络人 ≤2 | 139 句(双 134+NPC 5)/处长+北风两人(voice-ids 账目) | ☑ |
| 27 | 引擎边界诚实 | §3.4 能力表:摩尔斯文件方案否决理由/滤波不使用/速度边界写明 | ☑ |
| 28 | 存档带 summary、断点续玩 | §8:data+summary 格式、DECODE 态恢复、局面复述 | ☑ |
### 批 1 教训内化确认(5 条全落地)
| 教训 | 本作机制 | 断言 |
|---|---|---|
| 教学承诺必须兑现 | §7.5 九条承诺清单(重听/慢放/候选/单击/免费页/四封/电键声/教学反馈边界) | §9「摩尔斯正确性」「教学承诺」块 |
| 看门狗双轨 | §7.2 无操作轨+无进展轨;纯浏览不复位进展轨 | §9「看门狗双轨」块 |
| 档位措辞真实 | §5.3 exp 四档逐值为真且阈值=收网门;§5.4 book 逐值 | §9「档位句与实值一致」 |
| 失败结算链完整不被打断 | 800ms 死窗盖判定出口/NIGHT/d9/e*;seqToken+停摩尔斯 | §9 关键路径 2 + 死窗断言 |
| PC 键盘操作提示 | 11 对 _k+inputMode() 选择+集中分组标注 | §9「键盘变体」块 |
### 本作特有风险预防(摩尔斯听觉门槛)
| 风险 | 三层防线 | 验证 |
|---|---|---|
| P3 通勤嘈杂听不清长短音 | ①双击重听原速(无限)②再双击慢放 0.5×(无限、字母隙加倍)③密码本提示直译辨析位字母(3 页,T1 免费)——**三层均可独立走通** | §9 剧本 E/F + 三层兜底断言 |
| P1 视障玩家音频编码接受度高,但语速档位边界 | 摩尔斯不受 settings.speed 影响(u 恒定)——2.2× 档语速下电文仍原速,慢放兜底;**边界登记 + live 真人耳测项**(转写层测不了时值听感,methodology §5 2026-09 教训:媒体播放类必须真人耳测+引擎级断言) | §9「摩尔斯正确性」速度边界断言 + live 轮清单 |
| 摩尔斯时值精度 | 运行时 osc 合成(文件方案否决:有损编码 ms 抖动);事件钩子逐元素断言 | §9 固定字符断言 |
| 「不知道译对没译错」的认知焦虑 | 教学期说破「练习有答案,正片靠耳朵」;档位句+ep 回响给最终真相;译错零惩罚只是分支 | §9「译错归因真话」 |