# 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):旁白基表 104 句 ×2 性别 + 键盘变体 V 组 14 句 ×2 性别 + NPC 平铺 53 句 = **171 句 ≤176 上限**(R2 修订:158→171,新增 13 个录制单位见文末 R2 修订记录;原裁定「158 ≤160」作废,新上限 176);操作提示句带键盘变体(`_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 + 条目名 | 静默忽略 | | 单击 | 播放当前条目(从首个未听句起,听过的整体重听);**停在「结案陈词」时=进确认闸(fin_pre),闸内再单击才进 FINISH,滑走撤闸留板(R2 F5)** | **停止播报**,回空闲 | | 双击 | 重听当前条目最后一句(闸内=重听闸句 fin_pre) | **重听当前句**(≤8s,免费零消耗) | | 上滑 | 状态链(§2.5) | 状态链(打断证词;双击可重听当前句) | | 下滑 | 模态菜单(§2.4) | 模态菜单(打开即停播报) | | 长按 | **进入指证**(CONFRONT;仅空闲受理) | 引擎快进(旁白 1.7×,保持默认,不豁免) | | 长按松开 | — | 恢复常速 | 条目共 7 项:老葛证词 → 小顺证词 → 周先生证词 → 温太太证词 → 现场勘验 → 法医报告 → 结案陈词。每项播报=专属签名 earcon(0.3-0.5s,§8.2)+ 条目名整句;切到「结案陈词」+单击 → **确认闸(fin_pre「证据在手,就此结案?单击,定下来;滑走,继续查。」),闸内再单击 → FINISH**(R2 F5:循环列表必经站,单击直坠三问的误闯代价太大——R1 压力线实测误闯 6-7 次)。 ### 2.2 指证(CONFRONT)与结案(FINISH)——同一导航范式 两态统一为「列表预览 + 两段确认」非模态菜单(00 §5.2 非模态约定,rpg 探索同款),**双击全局语义=重听当前句,不偏离**: | 手势 | CONFRONT(选证词对) | FINISH(三问) | |---|---|---| | 左/右滑 | 切换:选条目阶段=6 证词条目(同 2.1 循环);选句阶段=上一句/下一句(播该句全文;**R2 F7:每条目记忆上次预览/定下位置,进选句从记忆位起播,不再回回从第一句扫**) | 切换选项(播选项名;**R2 F4:真凶问播纯人名 item_who_w1..w4**,不再念「……的证词」卷宗名) | | 单击 | ①选条目阶段=确认条目 → 进选句;②选句阶段=进入确认(重播该句+`cf_confirm`);③确认态=**定下这句**(第一份选定后自动进第二份,流程同前;第二份定下 → 判定) | 凶器/动机问:确认选项即答题;真凶问:进入确认(`fin_who_confirm` 重播+确认,二次单击定下=正式指认;**R2 F4:确认句中性化「就指认这位?」**——原「就指认他?」对温太太为假) | | 双击 | 重听当前预览句(宪法语义保留) | 重听当前问句/选项 | | 上滑 | 状态链(简版:疑点数) | 同左 | | 下滑 | 菜单(可「返回证词板」退出指证) | 菜单 | | 长按 | **选句相位(语音在播)=引擎快进(R2 F7,宪法内加速通道;空闲=error 震)**;条目/确认相位=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 反应句**按所坐实矛盾的轴取句**(R2 F3 修订;原裁定「按真凶累计被坐实次数取 z_react_1..4 递进」作废——按次数取句在换顺序坐实时答非所问,属演绎层假话,R1 人设 P1 实锤):pack A 映射 {lamp: z_react_1(灯), key: z_react_2(钥匙), weapon: z_react_w(铜器,R2 新增), scale: z_react_3(秤)};疑点 4/4 坐实的一发在轴句后追加收官句 z_react_4(逐客);pack B 用 {lamp: b_shun_react_1, 其余: b_shun_react_2}(弱绑定非轴假话),收官句同 b_shun_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 定时器);**兜底看门狗**:单句 **15s** 无 onComplete(资产缺失)→ 告警续链(「屏蔽任意 voice 资产流程不卡死」契约)。**R2 F1 修订:原 8s 兜底作废**——8s 低于真实长句资产(male/intro_1 实测 8.8s、male/sys_help 9.28s,R1 live 开场白「沈万川死了」被掐尾 0.85s);兜底只防资产缺失/引擎异常,必须高于全部真实资产,不当节奏装置。 - 裁决链(VERDICT)与结算链(SETTLE)为**独占链**:链首停一切看门狗,链内不受理手势(§7.4)。 - 教学链 `_speakGuarded` 串行(radio/dungeon 同款,杜绝同 tick 双调度抢占——D0-1 教训已固化引擎保险丝)。**R2 F2 补充**:D0 通过路径的条目报名移交 D1 链首串行补播(原 `_navItem` 450ms 报名与 tut_listen 并行互掐——R1 live 实测指令句只播 0.4s;mock 转写曾被 8s 兜底掩盖)。 ## 5. 教学编排(DRILL,首局内嵌) 每步=一句短旁白→等待实操→反馈;做错重教同一步(新短句 ≤2 次)后自动演示通过;下滑=跳过教学(`tut_skip`,**R2 F10 改词补核心动词**:「已跳过教学。左右滑换卷宗,单击听取;按住屏幕,指证矛盾。」——原词没提指证入口,跳过教学≠已会玩),`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 + **st_wrong「错指一回,记档。」(R2 F9:评级账目当场可闻,S/A 差距不再靠悟)** → 回 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(**R2 F6:六份听齐且疑点未齐时一级改播 gd_board_cf「都听过了。按住屏幕,去指证。」**——「换卷宗」此情是错指令,R1 三人设同撞) | | 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`(都听过但疑点未齐)/ **`hint_fin`(疑点已齐——R2 F11:此情还播「逐句比对」是指引过时,直指去结案)** → [150s] L3:**保底给出可指证的矛盾对**(hint_c1/hint_c2 按未发现清单轮询,seed 定序);4 疑点齐 → `hint_fin`(去结案)。L3 后每 150s 重复(每句全局限 2 次,之后静默——不轰炸)。 - 无操作轨(手势重置)与无进展轨并行取先到;两轨短句内容同级交叉登记防重复。纠错/提示 idle-only、过期丢弃(引擎已实现)。**R2 F8:提示句震动由 warning 三连震改为 confirm 轻震**(递答案不是训人——R1 人设 P4:「头一下我当自己犯错了」)。 ## 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 服务器。 - R2 补充:结案确认闸(_caseGate)与选句记忆(_cfLineMem)为瞬态 UI 态,**不持久化**(读档回板=闸撤、记忆空——重新调查从干净入口起)。 - 读取:有 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 震);**R2 F6:六份听齐且疑点未齐时状态链尾=recap_cf、疑点齐=hint_fin(原 recap_go 一刀切作废)**。 5. **指证 4 矛盾逐条走通**:M1-M4 各自导航→两段确认→contra_hit+cf_hit+**react_按轴**(R2 F3:lamp→z_react_1 / key→z_react_2 / weapon→z_react_w / scale→z_react_3;4/4 齐追加 reactFinal z_react_4 + hint_fin)→doubts 置位;M3/M4 共用 z_4(选 z_4 两次分别对 fo_3/sc_4);指证对无序(反向选也 hit);**R2 F7:同条目再进选句从记忆位起播(断言入口 lineIdx);选句相位长按在播=引擎快进不震、空闲=error 震**。 6. **指证 miss 三档真话**:同轴同值对(w1_2 vs wen_1)→cf_same;无共享轴对(z_5 vs sc_1)→cf_diff;同句→cf_self;cfMiss 计数、无任何资源/次数惩罚。 7. **指认全路径**:错指小顺/老葛/温 → acc_NPC+fin_clear+**st_wrong 当场记账**(R2 F9)+回 BOARD(无限次);对指周先生 → 凶器问(选项序=layout)→ 动机问 → VERDICT。**R2 F5:进 FINISH 先过确认闸——停「结案陈词」首击播 fin_pre 仍 BOARD,滑走撤闸,闸内再击进三问;R2 F4:真凶问选项播人名 item_who_*,确认句中性化「就指认这位?」**。 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;**R2 F6:听齐未破时一级=gd_board_cf**);无操作 96s 乱按零进展 → 无进展轨 45s earcon/90s 短句/150s hint_c(给出未发现矛盾对且该对可走通指证);**R2 F11:疑点齐时 L2=hint_fin;R2 F8:提示句震动=confirm 轻震**;有真实进展零提示;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 资产:链 15s 兜底推进(R2 F1:原 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 恒定;总句数 171 ≤176(R2 修订)✓(台词表) | | 19 | 键盘通道 | 操作提示句 V 组 14 变体集中登记(R2 新增 fin_pre/fin_who_confirm/recap_cf/gd_board_cf 4 对),inputMode() 选播;空格/回车/方向键全手势可达(§5.5 平行通道,游戏层零改动)✓ | | 20 | 引擎能力边界 | 无实时节点(链播 onComplete+setTimeout);长按快进与长按指证时间互斥(R2 F7 起选句相位也放行快进);资产缺失静默跳过+15s 兜底(R2 F1);30MB 预算内 ✓ | --- ## R2 修订记录(2026-09-13;裁定追加,旧裁定原文保留于上文并就地标注「作废/修订」) 证据源:research/new8/detective/round1/{synthesis.md, live-player.md, p1..p4, transcript-*}。每条对应 R1 synthesis 的 F 编号;全部修复落于 web/detective/game.js + 台词表 + playtest 断言。 | # | 修订 | 原裁定 → 新裁定 | 证据 | |---|---|---|---| | F1 | 单句兜底 8s→15s | 「8s 无 onComplete 告警续链」→「15s」(兜底高于全部真实资产:male/intro_1 8.8s、male/sys_help 9.28s、key/sys_help 10.48/9.52s 登记在台词表头注) | live A t=8.16 intro_1 掐尾丢「川死了」 | | F2 | D0→D1 串行化 | 「D0 报名与 D1 指令并行」→「D0 报名移交 D1 链首串行补播」 | live A t=33.8 tut_listen 只播 0.4s | | F3 | react 按轴取句 | 「按累计坐实次数 z_react_1..4 递进」→「按轴映射 + 4/4 追加 reactFinal;新增 z_react_w(weapon 轴)」 | 人设 P1 建议 1(换顺序即答非所问) | | F4 | 指认措辞真话化 | 「就指认他?」→「就指认这位?」;真凶问选项卷宗名 → item_who_w1..w4 纯人名 | 人设 P1 建议 2(温太太分支为假) | | F5 | 结案确认闸 | 「结案陈词单击进 FINISH」→「首击 fin_pre 闸、闸内再击进、滑走撤闸」;新增 fin_pre 句 | 人设 P2 建议 1 + 压力转写误闯 6-7 次 + live B 自救实测 | | F6 | 听齐未破指路 | 「状态链尾恒 recap_go / gd_board 恒教换卷宗」→「听齐未破:尾句 recap_cf、一级提示 gd_board_cf」;新增两句 | 人设 P1/P3/P4 同撞 + live A t=610 | | F7 | 选句加速 | 「CONFRONT 长按一律 error」→「选句相位在播=引擎快进、空闲=error」;新增按条目选句记忆(瞬态) | 人设 P1/P3/P4 同点 + live A 选句段 25-35s/次 | | F8 | 提示轻震 | 「hint 附 warning 三连震」→「confirm 轻震」 | 人设 P4 建议 3 + 两局 hint 落点实测 | | F9 | 错指当场记账 | 「错指=acc+fin_clear」→「+st_wrong『错指一回,记档。』」新增句 | 人设 P2 建议 2(S/A 差距不可感知) | | F10 | 跳教学带核心动词 | tut_skip 改词补「按住屏幕,指证矛盾。」(重录) | 人设 P2 建议 3 | | F11 | L2 疑点齐分支 | 「L2=hint_a/hint_b 二选」→「疑点齐时 L2=hint_fin」 | live A t=1595 hint_b 指引过时实播 | V 类观察项(本轮不动作):NPC 平铺回退 404 噪音(引擎级,abyss R1 同源);intro_w3 与 male 旁白同嗓背靠背(待人耳试听清单);FINISH 入口 tap 双震(引擎基础震+turn cue 叠加,轻)。 句数账:旁白基表 96→104(+fin_pre/recap_cf/gd_board_cf/st_wrong/item_who×4)、NPC 52→53(+z_react_w)、V 组 10→14 对(+fin_pre/fin_who_confirm/recap_cf/gd_board_cf);总 158→**171 句 ≤176 新上限**(原 160 上限裁定作废);音频 289 条 ×2 格式 = 578 文件。