Files
SoundGame/docs/design/18-detective.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

601 lines
49 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.
# 18 · 《雨夜专案》完整设计(detective / Rainy Night Case)
> **给开发者的实现要点摘要**
> 1. 本格推理 RPG:**核心动词=听证词找矛盾**。案卷 7 条目(4 证人证词 + 现场勘验 + 法医报告 + 结案陈词),全部音频呈现;证词=证人 NPC 平铺单音色(声音演技核心,5 个 speaker 池满配 + 专属签名 earcon 防混淆),旁白双性别只做系统/指引/现场自勘。每句证词预打**线索轴标签**,指证判定=两句是否「同轴异值」——判定与归因全部从数据单源推导,机制上不可能出现「台词说对得上、逻辑上对不上」的假话。
> 2. 手势:证词板左右滑换条目、单击听取/停止、**双击=重听当前句**(证词按句切分,单句 ≤8s,链播句间 0.8s——≤40s 听觉负荷以「句粒度可停可重听」兑现)、上滑状态链(已问几份/疑点几处)、下滑菜单(无豁免)、**长按=进入指证**(仅限无声空闲时受理,与长按快进零冲突,豁免 E1/E2 登记 §2.6)。指证/结案菜单走「左右滑预览+单击两段确认」非模态范式,双击保持全局重听语义——**全游戏零手势偏离,仅 2 条已教学豁免**。
> 3. 单局 15-25 分钟;结案=三问(真凶/凶器/动机),指认错误有不在场证明复述+真话归因+无限再机会,**无惩罚性失败,案子保底必结**(无进展轨看门狗三级提示最终直接给出可指证的矛盾对)。评级 S-C 账目全部真实(疑点数/冗余指证分档播报,播报值=运行值)。
> 4. 随机性全走种子 PRNG(mulberry32,`opts.seed`/`opts.pack` 可注入):选案包(pack A 周先生真凶 / pack B 小顺真凶——同模板同证词骨架、fork 句替换)、洗牌只动 filler 句位与问题选项顺序、提示轮询顺序。**矩阵不变式:全案卷「同轴异值」语句对集合 ≡ 预设计矛盾表**(dev 断言 + playtest 断言双保险)。
> 5. 台词表 [18-detective-voice-ids.md](18-detective-voice-ids.md):旁白 106 句 ×2 性别 + NPC 平铺 52 句 = **158 句 ≤160 上限**;操作提示句带键盘变体(`_k` 后缀,`gesture.inputMode()` 选播,V 组集中登记)。
> 6. 规模对标 radio:单文件 game.js 预计 900-1100 行;bgm 3 + sfx 12 + voice 264 条(×2 格式 = 528 文件)。裁决仪式链不可打断(VERDICT 态 800ms 手势死窗 + 链锁,dungeon H1 同款)。
---
## 1. 概述
- **一句话**:你是专案组探长,一桩雨夜命案。翻听六份卷宗,比对证词,指出矛盾,锁定真凶。
- **题材**:本格推理。核心命题「证词会撒谎,证据不会」——纯音频天然适合:证词本来就是"说"出来的信息,翻卷宗=翻语音段落,比对=重听+指证;视觉游戏需要的证词板 UI 在这里是一套**规整的听觉导航**(条目→句→指证对)。方法论 §4.1 无现成行(新模式探索),状态层重点自定为:**信息可回读**(每句可重听)+ **导航规整**(同一套左右滑/单击范式贯穿)+ **归因真话**(指证判定从数据推导)。
- **为什么适合纯音频**:① 记忆与比对的工作记忆负载全在听觉通道——删 BGM 抢占(对白 duck 引擎自动);② 证人=声音演技(speaker+instruct 区分 + 签名 earcon),认人是听感本身;③ 本格逻辑链可完全用「谁说了什么」表述,无空间/动作信息需要降维。
- **单局**:一周目(含教学)约 15-20 分钟;二周目(跳教学+变体案)约 13-15 分钟。结案必达(保底),评级 S-C 由过程质量决定。
- **与现有 8 游戏的关系**:选项列表导航承接 rpg(左右滑+单击确认)、证词重听承接 simon/escape(重听=生命线)、状态档位链承接 dungeon(上滑 5 段链)、看门狗双轨承接 dungeon E0-1/E0-2(无操作轨+无进展轨);指证菜单是新交互增量(两级选择+两段确认),变体案包是内容增量。
## 2. 手势映射与豁免登记
### 2.1 证词板(BOARD,主循环)
BOARD 分**空闲**(无语音在播)与**播放**(条目链播中)两个子行为:
| 手势 | BOARD 空闲 | BOARD 播放 |
|---|---|---|
| 左滑 | 上一条目(循环):签名 earcon + 条目名 | 静默忽略(纯聆听保护,simon playback 先例) |
| 右滑 | 下一条目(循环):签名 earcon + 条目名 | 静默忽略 |
| 单击 | 播放当前条目(从首个未听句起,听过的整体重听) | **停止播报**,回空闲 |
| 双击 | 重听当前条目最后一句 | **重听当前句**(≤8s,免费零消耗) |
| 上滑 | 状态链(§2.5) | 状态链(打断证词;双击可重听当前句) |
| 下滑 | 模态菜单(§2.4) | 模态菜单(打开即停播报) |
| 长按 | **进入指证**(CONFRONT;仅空闲受理) | 引擎快进(旁白 1.7×,保持默认,不豁免) |
| 长按松开 | — | 恢复常速 |
条目共 7 项:老葛证词 → 小顺证词 → 周先生证词 → 温太太证词 → 现场勘验 → 法医报告 → 结案陈词。每项播报=专属签名 earcon(0.3-0.5s,§8.2)+ 条目名整句;切到「结案陈词」+单击 → FINISH。
### 2.2 指证(CONFRONT)与结案(FINISH)——同一导航范式
两态统一为「列表预览 + 两段确认」非模态菜单(00 §5.2 非模态约定,rpg 探索同款),**双击全局语义=重听当前句,不偏离**:
| 手势 | CONFRONT(选证词对) | FINISH(三问) |
|---|---|---|
| 左/右滑 | 切换:选条目阶段=6 证词条目(同 2.1 循环);选句阶段=上一句/下一句(播该句全文) | 切换选项(播选项名) |
| 单击 | ①选条目阶段=确认条目 → 进选句;②选句阶段=进入确认(重播该句+`cf_confirm`);③确认态=**定下这句**(第一份选定后自动进第二份,流程同前;第二份定下 → 判定) | 凶器/动机问:确认选项即答题;真凶问:进入确认(`fin_who_confirm` 重播+确认,二次单击定下=正式指认) |
| 双击 | 重听当前预览句(宪法语义保留) | 重听当前问句/选项 |
| 上滑 | 状态链(简版:疑点数) | 同左 |
| 下滑 | 菜单(可「返回证词板」退出指证) | 菜单 |
| 长按 | error 震(选择语境不作输入) | error 震 |
指证判定结果(§3.5)播出后自动回 BOARD 空闲。全程无倒计时、无尝试次数上限。
### 2.3 标题(TITLE)/ 结算(SETTLE)/ 裁决(VERDICT)
| 手势 | TITLE | VERDICT(认罪链播中) | SETTLE |
|---|---|---|---|
| 单击 | 继续(有档)/ 新案(无档) | **吞掉**(手势死窗+链锁,§7.4) | 接新案(pack 翻转+新 seed) |
| 右滑 | 换一桩新案(重掷 seed/pack,meta 保留,run 清空) | 吞掉 | error |
| 双击 | 重听 continue_ask | 吞掉 | 重听结算链 |
| 上滑 | 重听案情简介(intro_1..3) | 吞掉 | 重听评级段 |
| 下滑 | 菜单 | 吞掉 | 菜单 |
| 左滑/长按 | error | 吞掉 | error |
### 2.4 模态菜单(遵守 00 文档 §5.2 全球约定,无豁免)
下滑呼出(同时停播报/停看门狗)→ 单击循环切换(每切播项名)→ 双击确认 → 上滑=帮助 → 下滑=关闭。选项 5 项:返回证词板 / 去指证(=长按捷径的菜单冗余入口,P4 容错)/ 保存并回大厅 / 重新听帮助 / 重新查这桩案子。本作无实时玩法,菜单=天然暂停。
### 2.5 每状态手势映射总表(6 手势 + 长按松开,全状态覆盖)
| 手势 | TITLE | DRILL | BOARD停 | BOARD播 | CONFRONT | FINISH | VERDICT | MENU | SETTLE |
|---|---|---|---|---|---|---|---|---|---|
| 单击 | 继续/新案 | 步骤作答 | 播放条目 | 停止 | 确认(两段) | 确认/答题 | 吞掉 | 循环切项 | 接新案 |
| 双击 | 重听 | 重听教学句 | 重听末句 | 重听当前句 | 重听预览句 | 重听问句 | 吞掉 | 确认执行 | 重听结算链 |
| 左滑 | error | 教学指定 | 上一条目 | 忽略 | 上一项/上一句 | 上一项 | 吞掉 | 上一项 | error |
| 右滑 | 换新案 | 教学指定 | 下一条目 | 忽略 | 下一项/下一句 | 下一项 | 吞掉 | 下一项 | error |
| 上滑 | 案情简介 | 状态(教过后) | 状态链 | 状态链 | 状态链(简) | 重听问句 | 吞掉 | 帮助 | 评级段 |
| 下滑 | 菜单 | 跳过教学 | 菜单 | 菜单 | 菜单 | 菜单 | 吞掉 | 关闭 | 菜单 |
| 长按 | error | 步骤作答 | 进入指证 | 快进(引擎) | error | error | 吞掉 | error | error |
| 长按松开 | — | — | — | 恢复常速 | — | — | — | — | — |
无效手势一律 error 震;full 档 `gd_invalid`(onceKey 同局 1 次,之后仅 error 震);idle-only(纠错/看门狗句在播时跳过,引擎 guide 已实现)。
### 2.6 豁免登记(必须显式)
| # | 豁免条目 | 理由(先例) |
|---|---|---|
| E1 | BOARD 单击=播放/停止(非"确认") | "检查/翻阅"类非即时操作,300ms tap 延迟无碍(escape 单击=检查、dungeon 单击=脉冲同款);停止播报是纯聆听期的标准出口 |
| E2 | BOARD 空闲长按=进入指证(核心动词快捷入口) | 绑定条件=无语音在播(`!audio.isVoicePlaying()`),与长按快进(仅在播时生效)**在时间上互斥、零冲突**,故不需 dungeon E3 式的对局期豁免;播放中长按仍走引擎快进。教学 D5 讲清(tut_confront),菜单另有「去指证」冗余入口 |
教学(DRILL)逐步讲清 E1/E2;无 immediateTap;模态菜单无豁免。
## 3. 核心动词:听证词找矛盾(信息设计)
### 3.1 信息结构总览
```
案卷(7 条目)── 4 证人证词(各 6 句,NPC 平铺)
├─ 现场勘验(6 句,旁白=探长自勘)
├─ 法医报告(5 句,NPC 秦法医)
└─ 结案陈词(FINISH 入口)
每句 = 1 个音频文件 + 数据侧「线索轴:值」标签(0-2 个)
矛盾 = 两句共享同一轴且值不同;全案卷该集合 ≡ 预设计矛盾表(4 条)
```
- 证词链播:句间 0.8s 停顿;**句粒度重听**(双击)+ 句粒度停止(单击)——单段连续听觉负荷 ≤35s 且随时可停可回,满足「证词单段 ≤40s、可分段」要求。
- 三套信息单源自洽(dungeon §3.2 同思路):证词=人的主张、现场/法医=物证主张、状态链=进度账目——进度账目(已问 X/疑点 n)从 `heard/doubts` 实时读出,播报值=运行值(账目真实)。
### 3.2 线索轴定义(pack A《雨夜·聚宝斋》)
案情:古董商沈万川死于自家后院柴房,后脑钝器伤,当夜暴雨。
| 轴 | 含义 | 取值 |
|---|---|---|
| rain | 雨何时开始 | 21:40 |
| dep_s | 小顺离店时刻 | 21:40 |
| away_s | 小顺当晚去处 | 城南 |
| visitor | 后巷访客 | 22:15 高个微驼男性 |
| dep_z | 周先生离店时刻 | 21:50 |
| **lamp** | 店堂灯 | 熄 / 亮 |
| **key** | 库房钥匙位置 | 挂柴房铜钩 / 贴身 |
| **weapon** | 凶器材质 | 松木劈柴 / 铜器 |
| **scale** | 铜秤下落 | 已卖 / 在库房 |
| wall | 柴房后墙 | 有人翻爬 |
| door | 后门开法 | 死者亲自为熟人开(规范值 victim_opened) |
| death | 死亡时间 | 22:00-22:30 |
| motive | 账上短银 | 月底要清账 |
| struggle | 搏斗痕迹 | 无 |
(粗体 4 轴=矛盾承载轴;其余轴全部同值互证或单断言,见 3.4 验算。)
### 3.3 案卷正文(pack A;全部台词 ≤31 字,全文与台词表一致)
**老葛(门卫)**:w1_1「昨儿夜里雨大,我守着街门,一步没敢挪窝。」w1_2「九点四十,雨点子刚砸下来,小顺打着伞出的街门。」w1_3「十点一刻,一位先生收着黑伞进了后巷。个儿高,微驼背。」w1_4「九点五十,周账房出的街门,跟我说了声,回家。」w1_5「后半夜,温太太还来门房借过一回热水。」w1_6「听说店里账上短了银子,沈老板撂了话,月底清账。」
**小顺(学徒)**:w2_1「夜里,师父打发我去城南,给刘掌柜送匣子。」w2_2「雨太大,刘掌柜留我住的,今早才回店。」w2_3「库房钥匙?师父嫌沉,平日就挂在柴房墙上的铜钩。」w2_4「师父的账,向来只信周先生一个人。」w2_5「后门那闩,生人叫不开。熟人来了,师父才亲自去开。」w2_6「周先生常念叨,账房的活,越到夜里越清静。」
**周先生(账房,真凶)**:z_1「我七点进店上夜账。九点五十对完,锁了前门回家。」z_2「临走,我顺手熄了店堂的灯。那会儿,店里就剩老板。」z_3「库房钥匙?沈老板贴身带着,从不离身。」z_4「那伤,是松木劈柴砸的。铜秤?上个月卖给收废品的了。」z_5「我到家倒头就睡,夜里的事,一概不知。」z_6「你们要是不信,去问我家婆娘。」
**温太太(房东)**:wen_1「雨是九点四十下的。我正收晾的衣裳,记得真真的。」wen_2「十点二十,我出门倒垃圾。聚宝斋店堂的灯,亮着呢。」wen_3「刚回屋,柴房后墙咯噔一响,有人爬墙!花盆翻了一个。」wen_4「吓得我蒙头半宿没睡着,天亮才敢吱声。」wen_5「店里短银子的事,街坊都有耳闻。上礼拜他还摔了账本。」wen_6「周账房那人,见人三分笑,账算得比谁都精。」
**现场勘验(旁白)**:sc_0「你打伞进了后院。雨水顺着瓦檐,淌成一道帘子。」sc_1「柴房门口,沈万川面朝下倒着。后脑一处伤,现场没有凶器。」sc_2「后门门闩完好,是死者自己开的门。生人叫不开这扇门。」sc_3「泥地上只有进来的脚印,没有出去的。墙根的花盆,翻了一个。」sc_4「库房铜秤立在架上,秤砣不见了。」sc_5「柴房墙上,挂钥匙的铜钩,空着。」
**法医报告(秦法医)**:fo_1「验尸报告,念给你听。」fo_2「死亡时间,昨夜十点到十点半。」fo_3「后脑单次钝器伤,一击毙命。创口嵌有铜屑,凶器是铜器。」fo_4「死者双手干净,没有搏斗痕迹。」fo_5「鞋底是干的。当夜,死者没出过后门。」
(sc_5 铜钩空为叙事佐证,**不挂 key 轴**——「平日挂墙上」是习惯断言、「今晨钩空」是状态断言,二者不构成值冲突;数据侧 sc_5 clues={}。)
### 3.4 矛盾预设计表 + 矩阵验算(playtest 直证对象)
**矛盾表(pack A,4 条)**:
| # | 轴 | 语句 A(真凶侧) | 值 A | 语句 B | 值 B | hit 反应 |
|---|---|---|---|---|---|---|
| M1 | lamp | z_2 周先生「顺手熄了店堂的灯」 | 熄 | wen_2 温太太「十点二十……灯,亮着呢」 | 亮 | z_react_1 |
| M2 | key | z_3 周先生「贴身带着,从不离身」 | 贴身 | w2_3 小顺「平日就挂在柴房墙上的铜钩」 | 挂墙上 | z_react_2 |
| M3 | weapon | z_4 周先生「是松木劈柴砸的」 | 木 | fo_3 法医「创口嵌有铜屑,凶器是铜器」 | 铜 | z_react_3 |
| M4 | scale | z_4 周先生「铜秤上个月卖给收废品的了」 | 已卖 | sc_4 现场「铜秤立在架上,秤砣不见了」 | 在 | z_react_4 |
注:M3/M4 共用语句 z_4(一句两断言),指证时选 z_4 两次、分别对 fo_3 与 sc_4——设计内行为。指证对**无序**(任一端在前都判 hit)。
**矩阵验算(全轴枚举)**——「同轴异值对 ≡ {M1..M4}」:
| 轴 | 断言语句(值) | 结论 |
|---|---|---|
| rain | w1_2(21:40)/ wen_1(21:40) | 同值一致(互证) |
| dep_s | w1_2(21:40) | 单断言 |
| away_s | w2_1(城南)/ w2_2(城南) | 一致 |
| visitor | w1_3 | 单断言 |
| dep_z | z_1(21:50)/ w1_4(21:50) | 一致 |
| lamp | z_2(熄)/ wen_2(亮) | **M1,唯一冲突对** |
| key | z_3(贴身)/ w2_3(挂墙上) | **M2,唯一冲突对** |
| weapon | z_4(木)/ fo_3(铜) | **M3,唯一冲突对** |
| scale | z_4(已卖)/ sc_4(在架) | **M4,唯一冲突对** |
| wall | wen_3(有人爬墙)/ sc_3(翻墙离开) | 一致 |
| door | w2_5(victim_opened)/ sc_2(victim_opened) | 一致(习惯断言与状态断言取同一规范值) |
| death | fo_2 | 单断言(wen_3 的 22:25 落在窗内,旁证) |
| motive | w1_6 / wen_5 | 一致 |
| struggle | sc_1(无)/ fo_4(无) | 一致 |
| (无轴) | w1_1、w1_5、w2_4、w2_6、z_5、z_6、wen_4、wen_6、sc_0、sc_5、fo_1、fo_5 | 叙事/filler,不参与判定 |
验算结论:除 M1-M4 外不存在任何同轴异值对;一致对 6 组(cf_same 的真话来源)、无轴句 13 句(cf_diff 的真话来源)——三条归因档全部真实可达。
### 3.5 指证交互与判定算法
```
judge(a, b):
if a.id === b.id → cf_self(同一句,比自己)
shared = axes(a) ∩ axes(b)
if shared 为空 → cf_miss(钝音)+ cf_diff「说的不是一件事」
else if 存在轴 v: a[v] ≠ b[v]:
if {a,b} ∈ 矛盾表(无序) → contra_hit + cf_hit + react_n + doubts[m]=true(疑点+1)
else → 数据错误(dev 断言禁止;playtest 失败项)
else(共享轴全同值) → cf_miss + cf_same「说得一致,不矛盾」
每次判定(无论中未中)→ cfMiss 判定计数、自动回 BOARD;无资源惩罚、无次数上限
```
- hit 反应句按真凶**累计被坐实次数**取 z_react_1..4(递进:记岔了→支吾→拒绝→想走),声音演技核心段;pack B 用 b_shun_react_1..2(第 3/4 次复用 react_2)。
- 疑点 4/4 齐时播一次 `hint_fin`(引导去结案);疑点数进状态链与存档,账目同源。
### 3.6 归因真话律(指证/指认双处)
| 场景 | 播报 | 真话依据 |
|---|---|---|
| 指证 miss·同轴同值 | cf_same | 轴数据同值(如 w1_2 vs wen_1 都说雨 21:40 起) |
| 指证 miss·不同轴 | cf_diff | 无共享轴(绝大多数跨话题句对) |
| 指证 miss·同一句 | cf_self | 选择器同一句 |
| 指认错人 | 该证人 acc_NPC(自述不在场证明)+ fin_clear | 各无辜者的 alibi 在证词中已铺(w2_1/w2_2 小顺、w1_1/w1_5 老葛、wen_4+visitor 男性访客排除温太太)+ pack B 的 acc_zhou |
| 凶器答错 | fin_wp_wrong(「铜屑不答应」) | fo_3 物证 |
| 动机答错 | fin_mv_wrong | w1_6/wen_5 账短银 |
**禁止**:任何「差一点」「再想想」式无信息归因;播报值与数据值必须一致(playtest 失败项锁定)。
### 3.7 信息密度与记忆设计
- 单次播报 ≤3 选项:所有列表(条目/句/凶器/动机/嫌疑人)都是**逐项预览**(左右滑一次听一项),绝不通读清单;帮助句(sys_help)例外(dungeon help 先例)。
- 证人区分三板斧:① 5 speaker 满配 + instruct 常量(§8.3);② 每证人**专属签名 earcon**(条目切换/指证选择/状态复述时前置,0.3-0.5s);③ 周先生(与男旁白同 speaker)与秦法医(与女旁白同 speaker)是仅有的两处重叠,靠签名+语境+instruct 兜底——重叠从不在相邻句出现(证词链内无旁白插话)。
- P1 工作记忆:疑点无需记忆——状态链随时报数;已选第一份证词在选第二份时**自动重播一次**(选句确认后链上接播),不需要玩家脑内持有。
## 4. 状态机
### 4.1 状态列表
`TITLE / DRILL / BOARD / CONFRONT / FINISH / VERDICT / SETTLE` + `MENU`(挂起 overlay,记录返回态)。BOARD 含空闲/播放两子行为(非独立态)。FINISH 子相位:who → weapon → motive;CONFRONT 子相位:item1 → line1 → confirm → item2 → line2 → confirm → judged。
### 4.2 转移图
```
start ──> TITLE ──单击(有档)──> BOARD(恢复存档+局面复述)
│单击(无档) ├──────> DRILL(仅首次, meta.drillDone 控制) ──完成/下滑跳过──> BOARD
│右滑=换新案(重掷 seed/pack) ──> BOARD(新案)
v
BOARD(证词板) ⇄ 条目链播(子行为)
│ 长按(空闲) / 菜单「去指证」 │ 选中「结案陈词」条目+单击
v v
CONFRONT ──判定(hit/miss)──> BOARD FINISH(三问)
│(经菜单「返回证词板」退出) ├─指认错→ acc 链 + fin_clear → BOARD
│ └─指认对→ 凶器问→动机问→ VERDICT
VERDICT(认罪链, 手势死窗+链锁) ──> SETTLE ──单击──> TITLE(新案: pack=1-pack)
任意正交态 ──下滑──> MENU ──关闭/返回──> 原状态
```
### 4.3 语音链推进契约
- 证词链=onComplete 串行(句间 0.8s 定时器);**兜底看门狗**:单句 8s 无 onComplete(资产缺失)→ 告警续链(「屏蔽任意 voice 资产流程不卡死」契约)。
- 裁决链(VERDICT)与结算链(SETTLE)为**独占链**:链首停一切看门狗,链内不受理手势(§7.4)。
- 教学链 `_speakGuarded` 串行(radio/dungeon 同款,杜绝同 tick 双调度抢占——D0-1 教训已固化引擎保险丝)。
## 5. 教学编排(DRILL,首局内嵌)
每步=一句短旁白→等待实操→反馈;做错重教同一步(新短句 ≤2 次)后自动演示通过;下滑=跳过教学(`tut_skip`),`meta.drillDone` 持久化,二周目不再进。教学寄生于真实案卷(D2 就听老葛真实证词,无假内容——教学承诺必须兑现)。
| 步 | 旁白 | 等待动作 | 通过条件/反馈 |
|---|---|---|---|
| D0 | intro_1→intro_2→intro_3→tut_d0 | 左滑或右滑任一 | 签名 earcon+条目名;confirm 震。**首输入 ≤60s 达成点 ≈t+20s** |
| D1 | tut_listen | 单击 | 老葛证词链播(真实内容) |
| D2 | tut_replay | 双击 | 当前句重播(免费) |
| D3 | tut_status | 上滑 | 状态链(st_asked_1→st_doubt_0) |
| D4 | tut_confront | 长按 | 进 CONFRONT + cf_start |
| D5 | tut_cf_try | 选条目1(滑+单击)→选句(滑)→单击确认→选条目2→选句→单击确认 | **判定被教学截获**:任何选择组合都播 tut_cf_ok(练习不算数、不计 cfMiss、不动 doubts),回 BOARD。**首正反馈 ≤3min 达成点(success 震)** |
| D6 | tut_done | — | 进 BOARD 自由调查;此后上滑/菜单随时可用 |
- D5 教学局不真判定的原因:教学时玩家只听过 1 份证词,真判定必 miss,会把「指证=挫败」印进第一印象;练习路径承诺「真矛盾要靠耳朵找」并在 2 份证词听齐后自然兑现(预期玩家首个真 hit 在 t+5-8min)。
- FTUE 核对:遮罩→intro 三句(~13s)→D0 完成约 t+20s(<60s ✓);D5 完成 ≈t+2.5min(≤3min ✓);全程零惩罚(无资源、无倒计时)✓。
## 6. 案卷数据结构与随机性
### 6.1 JSON 草案(pack A 全量 + pack B fork;台词全文见台词表)
```json
{
"schema": 1,
"packs": [
{
"id": 0, "name": "雨夜·聚宝斋", "culprit": "w3", "culpritName": "周先生",
"items": [
{ "id": "w1", "kind": "witness", "name": "item_w1", "sig": "sig_ge",
"intro": null, "lines": [
{ "id": "w1_1", "shuffle": false, "clues": {} },
{ "id": "w1_2", "shuffle": true, "clues": { "rain": "2140", "dep_s": "2140" } },
{ "id": "w1_3", "shuffle": false, "clues": { "visitor": "male_tall_hunched" } },
{ "id": "w1_4", "shuffle": true, "clues": { "dep_z": "2150" } },
{ "id": "w1_5", "shuffle": true, "clues": {} },
{ "id": "w1_6", "shuffle": false, "clues": { "motive": "short" } } ] },
{ "id": "w2", "kind": "witness", "name": "item_w2", "sig": "sig_shun",
"intro": null, "lines": [
{ "id": "w2_1", "shuffle": false, "clues": { "away_s": "south" } },
{ "id": "w2_2", "shuffle": true, "clues": { "away_s": "south" } },
{ "id": "w2_3", "shuffle": false, "clues": { "key": "hook" } },
{ "id": "w2_4", "shuffle": true, "clues": {} },
{ "id": "w2_5", "shuffle": false, "clues": { "door": "victim_opened" } },
{ "id": "w2_6", "shuffle": true, "clues": {} } ] },
{ "id": "w3", "kind": "witness", "name": "item_w3", "sig": "sig_zhou",
"intro": "intro_w3", "lines": [
{ "id": "z_1", "shuffle": false, "clues": { "dep_z": "2150" } },
{ "id": "z_2", "shuffle": false, "clues": { "lamp": "off" } },
{ "id": "z_3", "shuffle": false, "clues": { "key": "body" } },
{ "id": "z_4", "shuffle": false, "clues": { "weapon": "wood", "scale": "sold" } },
{ "id": "z_5", "shuffle": true, "clues": {} },
{ "id": "z_6", "shuffle": true, "clues": {} } ] },
{ "id": "w4", "kind": "witness", "name": "item_w4", "sig": "sig_wen",
"intro": null, "lines": [
{ "id": "wen_1", "shuffle": false, "clues": { "rain": "2140" } },
{ "id": "wen_2", "shuffle": false, "clues": { "lamp": "on_2220" } },
{ "id": "wen_3", "shuffle": false, "clues": { "wall": "climbed" } },
{ "id": "wen_4", "shuffle": true, "clues": {} },
{ "id": "wen_5", "shuffle": false, "clues": { "motive": "short" } },
{ "id": "wen_6", "shuffle": true, "clues": {} } ] },
{ "id": "scene", "kind": "scene", "name": "item_scene", "sig": "sig_scene",
"lines": [
{ "id": "sc_0", "shuffle": false, "clues": {} },
{ "id": "sc_1", "shuffle": false, "clues": { "struggle": "none" } },
{ "id": "sc_2", "shuffle": false, "clues": { "door": "victim_opened" } },
{ "id": "sc_3", "shuffle": false, "clues": { "wall": "climbed" } },
{ "id": "sc_4", "shuffle": false, "clues": { "scale": "present" } },
{ "id": "sc_5", "shuffle": false, "clues": {} } ] },
{ "id": "fore", "kind": "forensic", "name": "item_fore", "sig": "sig_fore",
"lines": [
{ "id": "fo_1", "shuffle": false, "clues": {} },
{ "id": "fo_2", "shuffle": false, "clues": { "death": "2200_2230" } },
{ "id": "fo_3", "shuffle": false, "clues": { "weapon": "bronze" } },
{ "id": "fo_4", "shuffle": false, "clues": { "struggle": "none" } },
{ "id": "fo_5", "shuffle": false, "clues": {} } ] }
],
"contradictions": [
{ "id": "m1", "a": "z_2", "b": "wen_2", "axis": "lamp", "hint": "hint_c1" },
{ "id": "m2", "a": "z_3", "b": "w2_3", "axis": "key", "hint": null },
{ "id": "m3", "a": "z_4", "b": "fo_3", "axis": "weapon", "hint": null },
{ "id": "m4", "a": "z_4", "b": "sc_4", "axis": "scale", "hint": "hint_c2" }
],
"verdict": { "accuseLine": "fin_right",
"break": ["z_break_1", "z_break_2", "z_break_3"] },
"wrongAccuse": { "w1": "acc_ge", "w2": "acc_shun", "w4": "acc_wen" },
"reacts": ["z_react_1", "z_react_2", "z_react_3", "z_react_4"],
"questions": {
"weapon": { "options": ["wp_1", "wp_2", "wp_3", "wp_4"], "answer": "wp_2" },
"motive": { "options": ["mv_1", "mv_2", "mv_3"], "answer": "mv_1" } }
},
{
"id": 1, "name": "雨夜·聚宝斋(变体)", "culprit": "w2", "culpritName": "小顺",
"base": 0,
"overrides": {
"w2": { "lines": [
{ "id": "w2_1", "shuffle": false, "clues": { "away_s": "south" } },
{ "id": "w2_2", "shuffle": true, "clues": { "away_s": "south" } },
{ "id": "b_shun_1", "shuffle": false, "clues": { "lamp": "off" } },
{ "id": "b_shun_2", "shuffle": false, "clues": { "key": "body" } },
{ "id": "b_shun_3", "shuffle": false, "clues": { "weapon": "wood", "scale": "sold" } },
{ "id": "b_shun_4", "shuffle": true, "clues": {} } ] },
"w3": { "lines": [
{ "id": "z_1", "shuffle": false, "clues": { "dep_z": "2150" } },
{ "id": "b_zhou_1", "shuffle": true, "clues": {} },
{ "id": "b_zhou_2", "shuffle": false, "clues": { "key": "hook" } },
{ "id": "b_zhou_3", "shuffle": true, "clues": {} },
{ "id": "z_5", "shuffle": true, "clues": {} },
{ "id": "z_6", "shuffle": true, "clues": {} } ] },
"contradictions": [
{ "id": "m1b", "a": "b_shun_1", "b": "wen_2", "axis": "lamp", "hint": "b_hint_c1" },
{ "id": "m2b", "a": "b_shun_2", "b": "b_zhou_2", "axis": "key", "hint": null },
{ "id": "m3b", "a": "b_shun_3", "b": "fo_3", "axis": "weapon", "hint": null },
{ "id": "m4b", "a": "b_shun_3", "b": "sc_4", "axis": "scale", "hint": "b_hint_c2" } ],
"verdict": { "accuseLine": "fin_right_b",
"break": ["b_shun_break_1", "b_shun_break_2", "b_shun_break_3"] },
"wrongAccuse": { "w1": "acc_ge", "w3": "acc_zhou", "w4": "acc_wen" },
"reacts": ["b_shun_react_1", "b_shun_react_2", "b_shun_react_2", "b_shun_react_2"]
}
}
]
}
```
### 6.2 seed 用法
```
rng = mulberry32(seed) // 与 dungeon 同族
pack = opts.pack ?? (opts.seed != null ? seed % 2 : null)
正式局:seed = Math.floor(Math.random()*1e9);pack 首局=0,结案后新案 pack = 1 - pack(强制换案)
洗牌(生成期一次性,存入 run.layout):
① 各证人内部 shuffle:true 的 filler 句在其槽位集合内置换(首句与全部矛盾承载句固定)
② 凶器问 4 选项、动机问 3 选项:Fisher-Yates by rng
③ 保底提示 hint 轮询顺序:permuted by rng
复现断言口径:同 (seed, pack) 两次构造 → game._case 深相等(布局/选项序/提示序逐项相等,不写魔法值)
```
### 6.3 变体生成规则(pack B 逻辑摘要)
- **同模板**:同一桩雨夜命案、同 4 证人位、同现场/法医/温太太/老葛证词(音频全复用)、同 4 轴矛盾结构(lamp/key/weapon/scale)。
- **不同真凶**:pack B 真凶=学徒小顺。fork=替换两份证词的句集:小顺换成 4 句谎(b_shun_1..4,复用其真实离店叙事作伪 alibi),周先生换成 3 句中性真话(b_zhou_1..3,其中 b_zhou_2「钥匙挂铜钩」成为 M2b 的对立端)。
- **矩阵自洽**:pack B 的验算与 pack A 同构——lamp 轴唯一冲突对 (b_shun_1, wen_2);key 轴唯一冲突对 (b_shun_2, b_zhou_2);weapon/scale 由 b_shun_3(一句两断言)对 fo_3/sc_4。老葛 w1_3 的访客描述(高个微驼)由 b_shun_break_2「棉袄反穿,弓着背」解释,不产生新轴冲突。
- **动机/凶器答案跨包一致**:监守自盗(小顺拿库房货换钱,b_shun_break_3 交代)与铜秤砣——问题反馈句两包共用。
- **内容成本**:fork 仅 12 句 NPC + 1 句旁白(fin_right_b)+ 2 句提示(b_hint_c1/2)。
### 6.4 矩阵不变式(dev/playtest 双保险)
启动时与测试中断言:`{(a,b,axis) : a,b 同轴且值不同} ≡ contradictions`。任何台词改稿若引入新的同轴异值对,此断言立即失败——防止「文案事故变成指证归因假话」。
## 7. 评级系统与裁决仪式
### 7.1 结案三问(FINISH)
1. **真凶**(4 选 1:老葛/小顺/周先生/温太太;两段确认)。错→该证人 acc_NPC 自述不在场证明 + fin_clear → 回 BOARD;**无限再机会**,wrongWho 计数。
2. **凶器**(4 选 1:松木劈柴/铜秤砣/铁门闩/青瓷花盆;选项序由 seed 洗牌)。对=fin_wp_right,错=fin_wp_wrong(真话归因,不重答,记账)。
3. **动机**(3 选 1:监守自盗/寻仇泄愤/劫财入户)。同上。
### 7.2 评级标准(S-C)
变量:`doubts`(疑点 0-4)、`weaponOk`、`motiveOk`、`wrongWho`(指认错次数)、`cfMiss`(冗余指证次数)、`hints`(保底提示已播数)。
| 档 | 条件(自上而下首个命中) | 播报 |
|---|---|---|
| S | doubts=4 ∧ weaponOk ∧ motiveOk ∧ wrongWho=0 ∧ cfMiss≤2 ∧ hints=0 | rate_s「教科书结案。四处疑点,一击即中。」 |
| A | doubts≥3 ∧ weaponOk ∧ motiveOk ∧ wrongWho≤1 ∧ hints≤1 | rate_a「干净利落的结案。」 |
| B | doubts≥2 ∧ wrongWho≤2 | rate_b「案子结了,卷宗上有疏漏。」 |
| C | 其余(案子必然已结) | rate_c「案子是结了,卷宗上补丁不少。」 |
- 结案必达:无失败态;C 档覆盖「全靠保底提示/疑点<2/指认错 ≥3」。
- 措辞-账目对账:S 的「一击即中」=wrongWho 0;「四处疑点」=doubts 4;C 的「补丁」=hints≥2 或 cfMiss≥3 或 wrongWho≥3——每句评级文案与触发条件一一对应(E1-2 档位措辞真实教训)。
### 7.3 结算账目播报(真实)
SETTLE 链:`settle_open → rate_X → st_doubt_n(疑点实数)→ st_miss_档(cfMiss: 0/1-2/≥3 → 三档真话)→ settle_hook(单击接新案,键盘态用 settle_hook_k)`。所有分档句按运行值选播,播报值=存储值(playtest 锁定)。
### 7.4 裁决仪式(VERDICT,结案宣判完整弧线)
指认正确后进入,**链不可打断**(dungeon H1 教训:结算链不许被在途手势截杀):
```
fin_right(证据链闭合)→ sfx/rain_s swell 起
→ z_break_1 → z_break_2 → z_break_3(认罪,声音演技高潮;bgm/verdict)
→ sfx/gavel(法槌)→ settle_open → SETTLE(接 §7.3 链)
```
- **手势死窗**:进 VERDICT 即盖 `VERDICT_GRACE_MS=800` 戳(同 dungeon SETTLE_GRACE:touchstart+450ms 长按判定+300ms 双击窗 ≈750ms,800 全盖);死窗内一切手势吞掉(ghost 手势不算输入、不碰看门狗)。
- **链锁**:VERDICT→SETTLE 全链播完(settle_hook 落声)前,单击/右滑/长按一律吞掉;链完成后单击=接新案(settle_hook 承诺「单击,接新案」言行一致——simon round1-live 教训)。双击=重听结算链仅在链完成后受理。
## 8. 音频三层
### 8.1 BGM(3 首,Mureka instrumental,-20 LUFS,ogg+m4a,无缝循环;雨声底烤进曲目,省第三声流)
| id | 状态映射 | Mureka prompt(英文) |
|---|---|---|
| bgm/menu | TITLE / MENU / SETTLE(结算 volume 0.5) | "Rainy night noir jazz ballad, slow brushed drums, lazy saxophone, soft rain ambience, melancholy and calm, no vocals, seamless loop" |
| bgm/board | DRILL / BOARD / CONFRONT / FINISH | "Moody jazz trio for a detective's office, muted piano walking, upright bass, soft rain on window, focused and mysterious, no vocals, seamless loop" |
| bgm/verdict | VERDICT(认罪链起播,交叉淡入) | "Tense noir jazz climax, brooding low strings under sparse piano, rain intensifying then settling, dramatic restraint, no vocals, seamless loop" |
切换全走引擎 crossfade;对白 duck 20% 引擎自动。choice/danger 不换曲(状态区分由 guide earcon 承担,氛围连续性优先)。预算:3 曲 ×2 候选 = 6 任务 $0.18(项目上限 $2.00 内)。
### 8.2 SFX(12 个;SA3=Stable Audio 3,SYN=程序合成;双格式、真峰值 ≤-3dBFS)
| 类别 | id | 方式 | 说明 |
|---|---|---|---|
| 卷宗 | `paper_turn` | SA3 | 翻卷宗页(条目切换内容反馈,"single paper page turn on desk, dry, close") |
| 证人签名 | `sig_ge` | SA3 | 搪瓷缸顿桌一下(老葛) |
| | `sig_shun` | SYN | 布掸子拍柜台两下(小顺) |
| | `sig_zhou` | SA3 | 算盘珠拨两响(周先生) |
| | `sig_wen` | SYN | 门环叩两下(温太太) |
| | `sig_fore` | SYN | 纸页翻动+钢笔划线(法医报告) |
| | `sig_scene` | SA3 | 雨中皮鞋踩水两步+伞抖水(现场,0.5s) |
| 指证 | `contra_hit` | SYN | 不和谐金属二度相击+盖章尾(矛盾证实音,全游戏最强确认音) |
| | `contra_miss` | SYN | 钝木鱼一下(对不上——非 error 音色,不威吓) |
| 裁决 | `gavel` | SA3 | 法槌一击("wooden gavel single strike, small room reverb") |
| | `rain_swell` | SA3/SYN | 雨声渐强再落(认罪链垫,6s one-shot) |
| 看门狗 | `turn_tick` | SYN | 880Hz 木鱼族轻响(无进展轨 L1 复用 dungeon 配方,自存一份) |
并发纪律:签名 earcon(≤0.5s)→ 条目名语音,串行不叠;contra_hit 先响、cf_hit 语音后接。声流恒 ≤2(BGM + voice)。
### 8.3 voice(158 句:旁白 106 ×2 性别 + NPC 平铺 52)
- **旁白**(系统/指引/教学/状态/现场自勘/裁决宣读):female=Serena / male=Uncle_Fu,instruct 常量全表统一(见台词表头);数字全分档整句(st_asked 7 档/st_doubt 5 档/st_miss 3 档),**句序号一律不播**(指证选句用重播该句代替报数——数字拼接禁令的交互侧落实)。
- **NPC 平铺单音色**(证词=证人 NPC;不做双性别;落 `voice/` 根目录,引擎平铺回退命中):
| 角色 | speaker | instruct 常量 | 区分度风险 |
|---|---|---|---|
| 老葛(门卫 62) | Eric | 苍老沙哑的男声,守夜人,慢悠悠,带倦意 | 低:与两侧旁白均不同 speaker |
| 小顺(学徒 19) | Dylan | 年轻清亮的男声,学徒,怯生生的,慌张诚实 | 低:同上 |
| 周先生(账房,真凶 A) | Uncle_Fu | 平板谨慎的男声,账房,字斟句酌,被追问时渐慌 | **中**:与 male 旁白同 speaker——靠 sig_zhou 前缀+账房腔 instruct+证词链内零旁白插话;试听确认 |
| 温太太(房东 50) | Vivian | 快嘴泼辣的中年女声,房东太太,嗓门亮,热心肠 | 低:与两侧旁白均不同 speaker |
| 秦法医 | Serena | 冷静干练的女声,验尸官,临床腔,短句平直 | **中**:与 female 旁白同 speaker——靠 sig_fore 前缀+报告语境固定(只在整个案卷固定条目出现);试听确认 |
- **声音演技核心**:周先生 z_react_1..4(记岔了→支吾→拒答→想走)与 z_break_1..3(认罪三段)是本作表演高潮;instruct 恒定、情绪写在句内文本(标点与省略号引导),不靠句级 instruct 变体(音色漂移禁令)。pack B 对应 b_shun_react/b_shun_break。
- 全表、触发时机、键盘变体(V 组)与多音字自查见 [18-detective-voice-ids.md](18-detective-voice-ids.md)。
## 9. 向导层接入(engine/guide.js,不新增 earcon 词表)
### 9.1 状态签名(进入状态时播)
| 状态 | guide.state() | 时点 |
|---|---|---|
| TITLE / MENU / SETTLE | `menu` | 进标题、开菜单、进结算 |
| DRILL / BOARD | `explore` | 进教学、进证词板(含指证判定后返回) |
| CONFRONT / FINISH | `choice` | 进指证、进结案三问 |
| VERDICT | `settle` | 认罪链起 |
| danger | 不使用 | 本作无生命/倒计时压力(登记) |
### 9.2 turn cue 与看门狗(双轨——批次 1 教训固化)
| 等待点 | 机制 | at | say(full 档;第 2 级一律纯 nudge) |
|---|---|---|---|
| CONFROST/FINISH 各选择相位入口 | guide.turn | — | turn earcon + confirm 震 |
| BOARD 空闲 | watchdog 无操作轨 | [10s, 25s] | gd_board / null |
| CONFROST 选句/确认 | watchdog 无操作轨 | [15s, 30s] | gd_cf / null |
| FINISH 三问 | watchdog 无操作轨 | [20s, 40s] | gd_fin / null |
| DRILL 每步 | watchdog 无进展轨(共享) | [30s/60s/90s] | earcon → 短句 → tut_skip 自动滚出(E0-2 先例) |
### 9.3 无进展轨(调查态卡关三级,**证词板乱按也要被抢救**)
- 进展事件=听完新条目 / 指证判定 / 结案相位推进(`_progressTrack()` 挂点:条目链 onComplete、judge()、FINISH 相位切换)。**只由进展重置**——乱按(疯狂换条目不听完、播放中狂点停起)永远攒不满进展,三级必达:
- [45s] L1:turn_tick earcon(不占台词通道)→ [90s] L2:`hint_a`(还有未听条目时)/ `hint_b`(都听过但疑点未齐)→ [150s] L3:**保底给出可指证的矛盾对**(hint_c1/hint_c2 按未发现清单轮询,seed 定序);4 疑点齐 → `hint_fin`(去结案)。L3 后每 150s 重复(每句全局限 2 次,之后静默——不轰炸)。
- 无操作轨(手势重置)与无进展轨并行取先到;两轨短句内容同级交叉登记防重复。纠错/提示 idle-only、过期丢弃(引擎已实现)。
## 10. 存档设计
```json
{
"schema": 1, "game": "detective", "updatedAt": 0,
"summary": "雨夜·聚宝斋 · 已问3/6 · 疑点2/4",
"meta": { "drillDone": true, "casesClosed": 1, "bestGrade": "A", "lastPack": 0 },
"run": {
"seed": 42, "pack": 0, "layout": { "w1": [...], "wpOpts": [...], "hintOrder": [...] },
"heard": ["w1", "scene", "fore"],
"doubts": [false, true, false, false],
"cfMiss": 1, "wrongWho": 0, "zHits": 1,
"hintsGiven": ["hint_c1"], "weaponAns": null, "motiveAns": null
}
}
```
- summary 生成规则:`{案名} · 已问{heard.length}/6 · 疑点{doubts 中 true 数}/4`(结案后 run=null,summary=`已结案{casesClosed}起 · 最佳{bestGrade}`)。
- 保存时机:条目链播完 / 指证判定后 / 菜单「保存并回大厅」/ 结案入 meta。localStorage + PUT 服务器。
- 读取:有 run → TITLE 播 sys_continue_ask,单击继续 → 恢复(layout 复用存档值,不重洗)→ **局面复述链**:`st_asked_n →(未听条目 ≤3 个时逐个播 item_* 名;>3 个只报计数)→ st_doubt_m → recap_go`,≤10s 回到可操作;右滑=换新案(seed/pack 重掷,meta 保留)。
- 归因真话依据:评级各变量全部持久化(doubts/cfMiss/wrongWho/hintsGiven/answers),结算播报从同源读取。
## 11. playtest 断言清单(登记 13 号文档新增 §detective;mock 对齐 web/rpg/test/playtest.mjs;一律 guide:'off')
**关键路径**(seed=42, pack=0,≤200 手势):
1. 启动→TITLE(无档)→单击→DRILL:D0..D6 逐步断言 voice 序与 drillStep 递进;D5 任意选择组合都播 tut_cf_ok 且 doubts/cfMiss 零变化(教学承诺兑现);下滑跳过路径 + meta.drillDone 二周目不再进。
2. **seed 复现**:(42,0) 两次构造 → `_case` 深相等(条目/句序/filler 洗牌/选项序/提示序);(42,1) 与 (43,1) 布局不同。
3. **矩阵不变式**:对 `_case` 全句对枚举轴交集——同轴异值对集合 ≡ contradictions(pack A 与 pack B 各验一遍)。
4. 听取:6 条目链完整(句序=layout)、双击=当前句重播零消耗、单击停止、播完 st_asked 升档;播放中左右滑静默忽略(无 error 震)。
5. **指证 4 矛盾逐条走通**:M1-M4 各自导航→两段确认→contra_hit+cf_hit+react_n(zHits 递进取句)→doubts 置位;M3/M4 共用 z_4(选 z_4 两次分别对 fo_3/sc_4);指证对无序(反向选也 hit)。
6. **指证 miss 三档真话**:同轴同值对(w1_2 vs wen_1)→cf_same;无共享轴对(z_5 vs sc_1)→cf_diff;同句→cf_self;cfMiss 计数、无任何资源/次数惩罚。
7. **指认全路径**:错指小顺/老葛/温 → acc_NPC+fin_clear+回 BOARD(无限次);对指周先生 → 凶器问(选项序=layout)→ 动机问 → VERDICT。
8. **裁决死窗**:指认对瞬间同拍与 +300ms 各补一记在途 tap → 均被吞(仍 VERDICT);认罪链 4 句+gavel+settle 链完整有序;链后单击=接新案(pack 翻转、seed 重掷、meta 入账)。
9. **评级账目**:注入 outcomes 矩阵(doubts∈{0..4}×weapon×motive×wrongWho×cfMiss×hints 代表组合 ≥10 组)→ 断言 grade 判定与 §7.2 表一致 + 结算链播报句(rate_X/st_doubt_n/st_miss_档)与运行值一致(假话即失败)。
10. **看门狗双轨**:BOARD 10s/25s(gd_board→nudge);无操作 96s 乱按零进展 → 无进展轨 45s earcon/90s 短句/150s hint_c(给出未发现矛盾对且该对可走通指证);有真实进展零提示;4 疑点齐后 150s → hint_fin。
11. **中断复述**:存档重进→sys_continue_ask→单击→复述链序(st_asked→未听 ≤3 时 item 名链→st_doubt→recap_go);上滑同链可重放。
12. **二周目变体**:seed'/pack=1 → 矛盾表指向 b_shun_*/b_zhou_2、culprit=w2、错指周先生走 acc_zhou、认罪链 b_shun_break、hint 为 b_hint_c1/2;矩阵不变式复验。
13. **键盘变体**:`settings.input='key'` 后播 sys_help_k/tut_d0_k 等 V 组(inputMode 联动);'touch' 回基础句。
14. 菜单/帮助/存档:模态菜单循环-确认-关闭;「保存并回大厅」→PUT data.summary 命中生成规则;「去指证」= 长按同效。
**边界断言**:
- 教学期零惩罚:D5 任意乱选不产生 cfMiss/doubts 变化;全程无倒计时(全 run 无 timeout 惩罚断言)。
- 长按豁免:BOARD 播放中长按=引擎快进(不进指证);空闲长按→CONFRONT;CONFRONT/FINISH 长按=error 震。
- 状态链随时可用(含播放中,打断后双击可重听当前句);播报值=heard/doubts 实值。
- 指证第二份选择时自动重播第一份选定句(P1 记忆契约)。
- 屏蔽任意 voice 资产:链 8s 兜底推进,流程到结案不死锁。
- guide=off:全静、零定时器;资产齐套:voice 双性别 id 集合一致 + NPC 平铺存在 + sfx/bgm 双格式。
- VERDICT/SETTLE 链内上滑/下滑/双击全部吞掉(链锁),链完成前无任何状态逃逸。
**失败即不通过**:归因假话(cf_same/cf_diff/评级句与数据实值不符);矩阵外冲突对存在;同 (seed,pack) 两次构造不一致;教学练习计入账目;裁决链被手势截杀或链句缺失;保底 hint 给出的矛盾对走不通指证。
## 12. 节奏曲线与单局核算
| 阶段 | 时长 | 内容 | 奖励点(≤60s 一个) |
|---|---|---|---|
| 开场链 | 0:20 | intro 三句 | — |
| DRILL | 0:20-2:40 | D0-D6(老葛证词首听在 D1) | D5 完成 success 震(t+2.5min) |
| 自由调查 | 2:40-9:00 | 5 条目听取+句级重听+状态核对 | 每条目听毕 st_asked 升档 |
| 指证穿插 | 5:00-12:00 | 4 矛盾(两证词齐后即可开始,玩家自定节奏) | 每个 hit:contra_hit+真凶 react 递进 |
| 结案三问 | 12:00-14:00 | 真凶/凶器/动机 | 指认对=fin_right(高潮起) |
| 裁决+结算 | 14:00-15:30 | 认罪链+法槌+评级 | 评级播报+开环钩子 |
- 一周目 15-20 分钟(重听多的松玩家 ≤25);二周目(跳教学+已熟导航)13-15 分钟。15-30 分钟章程达标。
- 悬念曲线:听证词(信息积累)→ 首个 hit(「证词会撒谎」兑现)→ react 递进(真凶渐慌)→ 三问(主动权全在玩家)→ 认罪(演技高潮)——每 30-60s 必有可感知进展(条目数/疑点数两个可见账本)。
- 开环钩子:settle_hook「同一场雨里,还有别的案子」+ pack 强制翻转(二周目必遇变体真凶)。
## 13. 自检清单
| # | 硬约束/教训 | 自检 |
|---|---|---|
| 1 | FTUE 首输入 <60s | D0 完成约 t+20s ✓(§5) |
| 2 | 首个正反馈 ≤3min | D5 教学完成 success 震 ≈t+2.5min;首个真 hit 预期 t+5-8min(本作类型内最近可达)✓ |
| 3 | 学习期零惩罚 | 无资源无倒计时全域;D5 练习不算账 ✓ |
| 4 | 失败三问归因+真话律 | 指证 miss 三档从轴数据推导;指认错=不在场证明复述;凶器/动机错=物证归因;评级句与账目一一对应 ✓(§3.6/§7,playtest 失败项锁定) |
| 5 | 手势宪法三件套不豁免 | 上滑状态/下滑菜单/双击重听全状态保留;仅 E1/E2 登记且教学讲清 ✓(§2.6) |
| 6 | 向导层 | 签名 4 态(choice 用、danger 登记不用);双轨看门狗(无操作+无进展,乱按必被抢救);保底 hint 给可走通矛盾对;idle-only/queue/onceKey ✓(§9) |
| 7 | 教学承诺兑现 | tut 承诺的每个手势在正片同语义可用;settle_hook「单击接新案」言行一致(死窗+链锁后)✓ |
| 8 | 结算链完整 | VERDICT 手势死窗 800ms+链锁,认罪→法槌→评级全链不可截(H1 教训)✓(§7.4) |
| 9 | 档位措辞真实 | S-C 条件与文案一一映射;st_asked/st_doubt/st_miss 播报值=运行值 ✓(§7.2/7.3) |
| 10 | 信息密度 | 所有列表逐项预览(≤3 选项/次);证词句粒度 ≤8s、链播 ≤35s 可停可重听(≤40s 听觉负荷)✓(§3.7) |
| 11 | P4 容错 | 无惩罚性失败;错指无限再机会;保底必破案(hint_c 直接给对子);零新手势 ✓ |
| 12 | P1 视障 | 句级免费重听;导航规整(条目→句→指证同一范式);状态链随时可查;账目真实;5 speaker+签名 earcon 防混淆(2 处重叠风险已登记缓解);2.2× 语速引擎既有 ✓ |
| 13 | P3 通勤 | 中断复述(已问谁/待问谁 ≤3 名直报/疑点几处);单手免视;任意时刻菜单退出存档;3 分钟颗粒=一份证词完整听取+比对 ✓ |
| 14 | P2 大学生 | react 递进/认罪演出钩子;pack 翻转二周目变体;评级 S 追求(全疑点+零冗余)✓ |
| 15 | seed 可复现 | (seed,pack) 注入+布局洗牌全 PRNG+深相等断言;变体包数据驱动 ✓(§6.2) |
| 16 | 矛盾矩阵验算 | 全轴枚举表:同轴异值对 ≡ M1-M4;一致对 6/单断言/无轴句清单 ✓(§3.4/§6.4 不变式) |
| 17 | 指证全路径可走通 | hit(4 矛盾×双向)/miss 三档/同句/错指三人(A)/错指含周(B)/三问对错/保底 hint 对——全部有专属资产与断言 ✓(§11 路径 5-10、12) |
| 18 | 台词规范 | 全句 ≤40 字(实测 ≤31);数字全分档整句;句序号不播(重播代替报数);「重新听」写法;同角色 speaker+instruct 恒定;总句数 158 ≤160 ✓(台词表) |
| 19 | 键盘通道 | 操作提示句 V 组 10 变体集中登记,inputMode() 选播;空格/回车/方向键全手势可达(§5.5 平行通道,游戏层零改动)✓ |
| 20 | 引擎能力边界 | 无实时节点(链播 onComplete+setTimeout);长按快进与长按指证时间互斥;资产缺失静默跳过+8s 兜底;30MB 预算内 ✓ |