From 9e4d67aa84002106247d7022300043fffba4c0e6 Mon Sep 17 00:00:00 2001 From: xiangsilian Date: Thu, 1 Oct 2026 23:26:47 +0800 Subject: [PATCH] save --- docs/design/00-global-ux.md | 321 ++++--- docs/design/01-escape-redesign.md | 370 +++++--- docs/design/01-escape-voice-ids.md | 8 + docs/design/02-simon-redesign.md | 330 ++++--- docs/design/02-simon-voice-ids.md | 8 + docs/design/03-rhythm-rebuild.md | 296 +++--- docs/design/03-rhythm-voice-ids.md | 8 + docs/design/04-space-rebuild.md | 307 ++++--- docs/design/04-space-voice-ids.md | 8 + docs/design/05-rpg-addendum.md | 173 ++-- docs/design/05-rpg-ch1-voice-ids.md | 8 + docs/design/05-rpg-chapter1.md | 322 ++++--- docs/design/05-rpg.md | 337 ++++--- docs/design/10-feedback2-state-bgm.md | 113 +-- docs/design/11-rpg-battle-rebalance.md | 168 ++-- docs/design/12-skip-and-longpress-ux.md | 123 ++- docs/design/13-playtest-criteria.md | 908 ++----------------- docs/design/14-guide-layer.md | 337 +++---- docs/design/15-radio-voice-ids.md | 8 + docs/design/15-radio.md | 733 +++++---------- docs/design/16-dungeon-voice-ids.md | 8 + docs/design/16-dungeon.md | 852 ++++------------- docs/design/17-abyss-voice-ids.md | 8 + docs/design/17-abyss.md | 619 +++---------- docs/design/18-detective-voice-ids.md | 8 + docs/design/18-detective.md | 740 +++------------ docs/design/19-cipher-voice-ids.md | 8 + docs/design/19-cipher.md | 706 +++----------- docs/design/20-teahouse-voice-ids.md | 8 + docs/design/20-teahouse.md | 643 +++---------- docs/design/21-keeper-voice-ids.md | 8 + docs/design/21-keeper.md | 693 +++----------- docs/design/22-bridge-voice-ids.md | 8 + docs/design/22-bridge.md | 729 +++------------ docs/design/23-hunt-voice-ids.md | 8 + docs/design/23-hunt.md | 612 +++---------- docs/design/24-rhythm-drummer.md | 482 ++++------ docs/design/24-rhythm-voice-ids.md | 8 + docs/design/25-escape-map-flow.md | 772 ++++------------ docs/design/26-apply-escape.md | 2 +- docs/design/26-apply-simon.md | 2 +- docs/design/26-apply-storyline.md | 6 +- docs/design/26-audio-interaction-playbook.md | 2 + docs/design/README.md | 103 ++- docs/design/roadmap.md | 154 ++-- docs/design/round1-review.md | 2 + docs/design/round2-review.md | 2 + docs/design/round3-4-review.md | 2 + docs/design/round5-notes.md | 67 +- docs/design/storyline-voice-ids.md | 8 + 50 files changed, 3913 insertions(+), 8243 deletions(-) diff --git a/docs/design/00-global-ux.md b/docs/design/00-global-ux.md index f56d0b1d..b540ef2d 100644 --- a/docs/design/00-global-ux.md +++ b/docs/design/00-global-ux.md @@ -1,227 +1,214 @@ # 00 · 全局交互规范(Global UX) > **给开发者的实现要点摘要** -> 1. 设置项(voice / speed / bgm / guide / input)只在大厅页用 ``(旁白音色:女声/男声;语速:正常/快/特快/特快++;背景音乐:正常/小/关闭;向导帮助:详细/只听提示音/关闭),变更即 `settings.set()` 写 localStorage。 -- 试听句(设计有、R1 未做):变更后播一句样例语音演示 voice+speed,列为 R3 打磨项,非阻塞。 -- 游戏内:**只读**。`audio.js` 自动应用三项设置,游戏代码无需改动;下滑呼出的菜单里用语音播报当前设置(如"当前:女声,正常语速"),并提示"请回到大厅修改设置"。 +设置作为特殊游戏键 `_settings` 走进度接口。登录后服务器覆盖本地。离线纯 localStorage。 -### 1.3 服务器同步(已实现,形式与初稿不同) +--- -- 实现:**没有独立的 `/api/settings`**。设置作为特殊游戏键 `_settings` 存进进度接口:大厅在变更时 `PUT /api/progress { game: '_settings', data: settings.get() }`;登录后 `GET /api/progress?game=_settings` 拉回覆盖本地(服务器为准)。 -- 离线/未登录:纯 localStorage,行为不变。 +## 2. 输入回执与震动 -## 2. 震动语言规范(已实现:engine/haptics.js) +### 2.1 回执是主通道(26 号原则 4) -### 2.1 动机 +三种合成系统音,全项目统一,接引擎 master,不走 sfx 内容资产: -用户反馈⑦:提示音(确认音、错误音)与游戏音效/线索音混在一起,盲玩时无法区分"这是反馈还是游戏内容"。震动是独占的触觉通道,不会与任何音频信息混淆。所有"操作反馈"一律用震动;音频只承载游戏内容与叙事。 - -### 2.2 引擎模块(以实现为准) - -`web/engine/haptics.js`(R1 已落地): - -```js -import { vibrate, hapticsSupported, HAPTIC_PATTERNS } from '../engine/haptics.js'; -vibrate('error'); // 预定义名称(小写) -vibrate([15, 40, 15]); // 自定义震型数组 -``` - -已实现预定义震型(R2 已补全,**以此 10 种为准**): - -| 名称 | 震型 (ms) | 语义用途 | +| 回执 | 何时 | 听感(起步参数) | |---|---|---| -| `confirm` | [20] | 确认:点按确认、菜单选中 | -| `error` | [60, 40, 60] | 错误:两连震(做错手势、无效操作) | -| `success` | [100] | 成功:长震(过关、解谜成功、升级) | -| `warning` | [30, 50, 30, 50, 30] | 警告:三连短震(血量低、卡关提示) | -| `swipe` | [10] | 滑动识别成功(最轻,引擎内置) | -| `heartbeat` | [40, 100, 40] | 心跳/倒计时紧张感 | -| `pickup` | [15] | 获得物品/机会(单轻震,不与 error 混淆) | -| `hit` | [80] | 受击/终结性失败:单次重震 | -| `beat` | [8] | 节拍锚点(simon/rhythm 领奏专用,极轻) | -| `danger` | [25, 40, 25, 40, 25] | 危险逼近(可循环,每遍间隔 ≥400ms;RPG 无声前摇用它代替听觉提示) | +| `ok` | 操作被接受并生效 | 三角波 1600Hz,35ms,增益 0.08,「嗒」 | +| `invalid` | 收到了,但现在用不上 | 正弦 260→200Hz,120ms,增益 0.10,低沉「咚」 | +| `busy` | 游戏在说关键句或正在处理 | 正弦 520Hz,40ms,增益 0.04,很轻「嗯」 | -### 2.3 语义绑定规则(含引擎内置反馈的分工) +规则: +- 100ms 内出声;回执 ≤0.2s。 +- 游戏已为这次操作播了明显内容音(脚步、开门),可省 `ok`(记 `silent-ok`)。`invalid` 与 `busy` 永远播。 +- 系统音一律合成;内容音一律录音。两类永不混用。 +- 单击执行「继续/跳过」后 **400ms 内**再来的单击只给 `busy`,不连跳、不误进下一段。 -- **gesture.js 已内置基础反馈**(R1 实现):滑动识别成功→自动 `swipe`;tap/doubletap→自动 `confirm`。因此**游戏层不要再对"手势被识别"本身调 `confirm`/`swipe`,否则双重震动**。游戏层只在语义事件处调用:`error`(操作被判定为错)、`success`(过关/成就)、`warning`(警告)、以及自定义 PICKUP/HIT/BEAT/DANGER。 -- 震型按**语义**选用,不按游戏选用:任何游戏的"操作错误"都是 `error`,玩家在不同游戏间迁移时触觉语言一致。 -- 单次震动总时长 ≤ 500ms;DANGER 循环时每遍间隔 ≥ 400ms,事件结束立即 `navigator.vibrate(0)` 停止。 -- 震动的触发源只有两类:a) 玩家的手势被系统接收/拒绝(引擎内置或游戏层语义判定);b) 明确的离散游戏事件(受击、警告、节拍点)。**禁止**持续型/背景型震动。 +### 2.2 震动是补充(已实现:engine/haptics.js) -### 2.4 绝不用震动的场景 +十种震型名不变:`confirm/error/success/warning/swipe/heartbeat/pickup/hit/beat/danger`。语义仍按语义选用,不按游戏选用。 -1. **长对白播放中**(voice 层有音频在播):旁白叙事期间不震动,避免震动机理掩盖语音细节、也让玩家误以为需要操作。例外:教学流程里"现在请向左滑"这类等待玩家操作的指令句,在语音**播完后**允许一次轻震提示可以操作——震动发生在 voice 结束后,不重叠。注意:教学里玩家出手后引擎已自动震 swipe/confirm,游戏层通常无需再补。 -2. 序列/乐句播放中需要玩家纯聆听记音高的阶段(simon/节奏游戏领奏时例外:BEAT 本身就是节拍信息通道,见 02/03 文档)。 -3. 同一 200ms 窗口内多个事件只震一次(实现上去抖)。 -4. 开场遮罩、大厅页等可视化 UI 不震动。 +- gesture.js 内置:滑动→`swipe`,tap/doubletap→`confirm`。游戏层不要对「手势被识别」再震一次。 +- 单次 ≤500ms;danger 循环间隔 ≥400ms;200ms 窗口去抖。 +- 长对白、纯聆听记音阶段不震(节拍锚点 `beat` 除外)。 +- iOS 无震动:no-op。**不再把「绝不回退提示音」理解成「无任何声音反馈」**——回执声仍然要响。旧教学口径「iPhone 用户听结果声」改为「听回执声和结果声」。 -### 2.5 iOS Safari 降级策略 +--- -iOS Safari 不支持 `navigator.vibrate()`。策略:**静默降级**——`haptics.js` 特性检测 `'vibrate' in navigator`,不支持即 no-op(已实现)。**不允许**回退到提示音(反馈⑦的根因就是提示音混淆;iOS 用户没有反馈音,好过错乱的反馈音)。iOS 玩家依赖已有的音频结果反馈(脚步、撞击、机关声),这些属于游戏内容音,保留。各游戏开场教学统一口径:"做对做错,手机会震动告诉你;iPhone 用户听结果声"。 +## 3. 旁白质量与台词写作 -## 3. 旁白质量规范 +整句生成、分段数字、同游戏同 speaker+instruct、管线 `tools/gen_voice_dual.py`、双格式 ogg+m4a:沿用原规范。 -### 3.1 根因 +**26 号增补(写作强制)**: -用户反馈⑥的"音色漂移、发音不准"来自两类做法(旧版 escape/simon/space): +| 用途 | 规则 | 例子 | +|---|---|---| +| 指令 | 「动作 + 结果」,≤12 字 | 「向左滑,开门。」 | +| 短句档 | ≤6 字,只留动作 | 「向左滑。」 | +| 叙事 | 单句 ≤25 字 | | +| 我在哪 | 位置。目标。一个动作。≤20 字 | 「厨房。找钥匙。左右滑。」 | +| 归因 | 原因 + 下次怎么做;必须真实 | 「慢了。听到就滑。」 | +| 肯定 | ≤4 字 | 「对。」 | +| 解释无效 | 现在怎样。能做什么。每个状态×手势一次 | 「还在讲故事。点一下可以跳过。」 | -1. **碎片拼接**:`voice/di` + `voice/num_5` + `voice/lun` 拼"第 5 轮"。每个碎片独立生成,音色、气息、响度逐片漂移,接缝明显。 -2. **同游戏多 instruct / 长短不一**:有的句带情感指令有的不带,批次间模型状态不一致。 +每句台词标类别:`critical` / `content` / `hint` / `feedback`(26 §4.6)。`critical` ≤3s。`hint`/`feedback` 空闲才播,过期丢弃。 -### 3.2 规范(所有游戏强制执行) +禁止:「如果」「可以尝试」;一句里教两个手势;游戏主动原样重复。 -1. **一句一文件,整句生成**。凡要播报给玩家的信息,整句一次 TTS 生成,禁止运行时拼接。例外:>40 字的长台词由资产管线在生成期拆句拼接(R1 管线已支持,见 docs/audio/round1-report.md),运行时仍不拼。 -2. **数字信息改分段整句**。需要报数字的场景(轮数、成绩、存活时间、用时、准确率),按取值分段预生成整句: - - 取值少且可数(如 simon 轮数):直接为每个值生成整句,"第一轮"…"第二十轮",超出范围用无数字兜底句。 - - 取值连续(如存活秒数、准确率):按区间分段整句,如"不到三十秒""超过一分钟""准确率超过八成五"。宁粗勿拼。 - - 各游戏的具体分段在各自设计文档中给出。 -3. **同游戏同音色**:每款游戏的旁白用固定 speaker 常量 + 固定 INSTRUCT 常量。R1 音色分配表(docs/audio/round1-report.md §4,**以此为准**):male 一律 Uncle_Fu;female:storyline=Vivian、escape=Serena、simon=Serena、rhythm=Vivian、space=Serena。角色音(storyline/RPG 的 NPC)可另有角色 speaker,但**同一角色**所有句子的 speaker+instruct 必须是同一常量,写死在 gen 脚本的台词表表头注释里;NPC 台词不做双性别,平铺存 `voice/` 根目录(引擎平铺回退会命中)。 -4. **短句优先**:单句 ≤ 25 个汉字为宜;长信息拆成多句链播(onComplete 串行),每句仍是完整句,不是碎片。 -5. **生成管线**:统一用 `tools/gen_voice_dual.py`(R1 已建,台词表驱动,female/male 双跑、断点续跑、ogg+m4a 双格式、峰值归一 0.89)。新台词表由策划以"voice-ids 文档"形式交付(id + 全文 + speaker + instruct),音频总监照录进该脚本。 -6. **验收**:自动 QC(时长/峰值/两性时长离群)+ 人工试听;多音字风险句在台词表中标注替代写法(如"重听"→"重新听")。 +--- -## 4. 用户系统(服务器进度,已实现:web/server/server.js) +## 4. 用户系统(已实现) -### 4.1 形态(已实现) +形态、API、`summary` 必填、`_settings` 保留键:不变。 -- 大厅页"用户"面板:用户名 + 4-6 位数字 PIN 注册/登录;token 存 localStorage key `soundgame.auth`(形如 `{ username, token }`),请求头 `Authorization: Bearer `。 -- 服务端 `web/server/server.js`(Node 零依赖,静态托管 + API 一体,`serve.sh` 拉起);数据落盘 `web/server/data/`。局域网玩具级认证,勿暴露公网。 -- 未登录/服务器不可达:静默降级为仅本地模式,进度只存 localStorage(key `soundgame.progress.`)。 +**26 号增补**:退出自动存;重进 5 秒内播一句局面复述(位置 + 目标 + 现在做什么),然后交出话轮。复述句 id 随等待点写入进度。 -### 4.2 API 约定(以实现为准) +--- -``` -POST /api/register { username, pin } -> 201 { ok, username } -POST /api/login { username, pin } -> 200 { ok, token, username } -GET /api/progress?game= (Bearer) -> 200 { game, data } (无存档则 data=null) -PUT /api/progress { game, data } (Bearer) -> 200 { ok, game } -``` +## 5. 手势宪法(26 号原则 6) -与初稿的差异(**以实现为准**):进度读写用 `?game=` 查询参数 / 请求体字段,不是路径段;`data` 是各游戏**自由格式**的 JSON(服务器不校验结构);没有统一的 `{schema, game, updatedAt, data}` 信封——游戏自行在 `data` 内放 `schema`/`updatedAt` 字段(建议保留,便于迁移)。 +### 5.1 新手期三手势(全部游戏共用) -### 4.3 进度 data 的两条约定 +| 手势 | 唯一含义 | 游戏在说时 | 等你操作时 | +|---|---|---|---| +| 单击 | 继续 / 确定 / 听一下 | 只跳过当前这句(`skipVoice`),给 `ok` | 执行继续或确定 | +| 左滑 / 右滑 | 选择 / 移动 | 打断并执行 | 执行 | +| 上滑 | 我在哪、我该干嘛(一句) | 打断后回答 | 回答 | -1. **`summary` 字符串(必须)**:大厅游戏卡片会直接显示 `data.summary`(已实现,index.html `loadProgress()`)。每个游戏的存档 data 里必须维护一句人话摘要,如 `"第一章·溪流 · Lv3 · 金币120"`、`"最高纪录:12 轮"`。 -2. 保留键 `_settings`(设置同步用,见 §1.3),游戏不得占用。 -3. 写进度时机:局内关键节点(过关、死亡结算、退出菜单选"保存并回大厅")自动 PUT + 写 localStorage;不要求实时。 - -## 5. 手势一致性约定 - -### 5.1 全局默认映射 - -| 手势 | 全局语义 | +| 手势 | 新手期 | |---|---| -| 上滑 | 帮助 / 当前状态播报 | -| 下滑 | 菜单(含:重听帮助、播报当前设置、重新开始、保存并回大厅) | -| 单击 | 确认 / 继续 / 检查 | -| 双击 | 重听(重播当前语音/线索/乐句) | -| 左/右滑 | 选择 / 移动 | +| 双击 | **不存在**。`doubleTapWindow: 0`。键盘回车按单击处理 | +| 下滑 | 未解锁:`invalid` + 首次解释 | +| 长按 | 未占用帮助时:旁白加速(12 号)。四向滑动是玩法时:长按=帮助,关闭加速 | -### 5.2 模态菜单约定(R3 修订:采纳 R2 开发实现,round2-review 裁决②) +滑动、上滑:打断并执行。单击:只打断(跳句),不顺带执行「继续下一段」。 -全球统一的模态菜单交互:**下滑呼出(模态)→ 单击循环切换选项(每切换播选项名)→ 双击确认执行 → 上滑=帮助/规则 → 下滑或"返回游戏"项关闭**。菜单打开时暂停计时类玩法(有计时器的游戏进入暂停态)。 +### 5.2 解锁阶梯(跨局记住,存进度或 `_settings`) -- 标准选项集:重新开始 / 回大厅(存档游戏为"保存并回大厅")/ 返回游戏;打开时先播 `menu_open` + 第一项。 -- 为什么单击=切换而非确认:结算页"单击=再来一局"已被占用,菜单内单击若作确认会与重开冲突;单击循环 + 双击确认把"浏览"与"执行"分离,误操作成本为零(escape/simon R2 已一致落地)。 -- **RPG《剑与声》豁免**:探索态菜单保持"左右滑切换 + 单击确认"——RPG 探索本身就是"左右滑选项 + 单击确认"范式,菜单与探索同构反而少学一套;战斗态不设菜单(下滑=逃跑)。 -- 选曲/存档确认等**非模态**选项界面(如 rhythm 选曲)不属于菜单,仍用"左右滑切换 + 单击确认"。 +| 阶段 | 条件 | 解锁 | +|---|---|---| +| 0 | 首次进入 | 单击、左右滑 | +| 1 | 第一个小目标 | 上滑(或长按帮助,若滑动是玩法) | +| 2 | 第一局结束 | 下滑=菜单 | +| 3 | 累计 3 局 | 双击=重听**当前等待点完整句**(加「再说一遍:」)。禁止绑整段重播、重开、连跳 | +| 4 | 大厅一键或专属教学 | 游戏专属高级手势 | -### 5.3 豁免表(以滑动为核心玩法的游戏) +大厅提供「全部解锁」给老玩家。 -四向滑动被玩法占用的游戏,"上滑=帮助 / 下滑=菜单"让位,偏离必须在开场交互教学中明确讲出: +### 5.3 四向滑动是玩法:帮助换长按 -| 游戏 | 滑动用途 | 帮助入口 | 菜单入口 | -|---|---|---|---| -| escape 暗室 | 四向滑=移动 | PLAY 中双击无目标时兜底播报目标与提示;卡关计时器自动三级提示 | 仅结算后下滑=模态菜单(PLAY 中无菜单,R3 打磨拟加双击二级入口) | -| simon 音序回响 | 六手势全是答题输入 | 非输入阶段上滑=重听规则 | 结算后下滑=模态菜单 | -| rhythm 节拍行者 | 四向滑=演奏输入 | 乐句间隙上滑=重听规则 | 结算后下滑=模态菜单 | -| space 盲航 | 四向滑=闪避 | 坠毁结算后上滑=重听教学要点 | 结算后下滑=模态菜单 | -| rpg 剑与声 | 左右滑=选择(符合默认) | 上滑=角色状态(符合默认) | 下滑=菜单(豁免:左右滑+单击,见 §5.2) | -| storyline 声命线 | 左右滑=抉择(符合默认) | 上滑=帮助+进度(符合默认) | 下滑=菜单(R3 打磨统一到 §5.2 模态约定) | +适用:escape、space、rhythm/drummer、dungeon、hunt,以及任何四向滑=移动/闪避/演奏的游戏。 -### 5.4 通则 +- 帮助 = 长按。所有这类游戏同一条,不许再各换双击/上滑。 +- 菜单仍到阶段 2 才开放下滑;对局中下滑若与玩法冲突,对局中不开菜单,结算后开。 +- 新手期双击仍按单击处理。 -- 任何游戏里,**做了一个手势 3 秒内必须有反馈**(引擎内置震动 + 内容音/语音),被判定无效的手势给 `error` 震动;教学阶段除外(教学里错误手势给纠正语音)。 -- 单击有 ~300ms 引擎级延迟:即时判定(格挡、节奏打击)一律绑滑动或双击,单击只用于非即时操作(确认、检查、菜单)。新设计继续遵守。**唯一例外**:rhythm 开启引擎 `immediateTap`(按下即发 tap、双击补发改判),因它的单击就是核心演奏输入;其他游戏不得开(每次按下都先发 tap,会污染滑动/长按语义)。 -- 结算画面统一:单击=再来一局,下滑=菜单(回大厅)。教学可跳过:任何游戏的引导阶段,下滑=跳过引导(首次强制完整引导的标志位存进度里)。 +**六手势都是音色/答题(simon)**:帮助也换长按;音色按实力渐进开放,一次只学一样。 -### 5.5 键盘平行通道(P0-A 已实现:engine/gesture.js 内置通道) +### 5.4 模态菜单(阶段 2 以后) -> 调研与业界依据:[research/platform/02-input-parity.md](../../research/platform/02-input-parity.md)(audiogames.net 社区惯例、A Blind Legend / Blind Drive 双端先例)。 +**下滑呼出 → 左右滑切换(每项报名)→ 单击确认 → 上滑或长按=重听菜单说明 →「返回游戏」或再下滑关闭。** -PC 键盘是触屏手势的**平行输入通道**,不是另一套手势宪法:键盘事件在 -`attachGestures` 内部喂入与 touch/mouse **同一状态机**,游戏层收到的 -7 种手势事件(up/down/left/right/tap/doubletap/longpress/longpressend -+ immediateTap 的 tapcancel)与内置震动**完全同构**——游戏代码零改动, -§5.1-§5.4 的全部手势语义(含各游戏豁免表)原封不动。 +废止「单击循环 + 双击确认」。原因:新手期没有双击;单击的全局含义是确定,菜单里不应改成切换。 -**键位表(裁定方案,`e.code`):** +标准选项:重新开始(破坏性,二次确认)/ 保存并回大厅 / 返回游戏。打开时暂停计时玩法。 -| 手势(§5.1 语义不变) | 键盘主键 | 别名 | 行为差异 | -|---|---|---|---| -| 左滑 / 右滑 | ← / → | A / D | keydown 即发(无位移/速度判定,零延迟) | -| 上滑 / 下滑 | ↑ / ↓ | W / S | 同上 | -| 单击 | 空格 | — | 走完整 tap 状态机:**保留 ~300ms 双击窗**(与触屏单击同感) | -| 双击 | **回车** | 空格×2 | 回车**即时**触发(不等双击窗);空格两连也判双击(状态机路径) | -| 长按 / 长按结束 | 按住空格 ≥450ms / 松开 | — | 与触摸长按完全一致(含旁白快进 window 事件) | +RPG 探索菜单与探索同构:左右滑 + 单击,符合本条。战斗态下滑=逃跑,不设菜单。 -设计裁定(P0-A,均已实现并回归): +选曲等非模态选项界面:左右滑 + 单击,不变。 -1. **空格也走 300ms 双击窗**(与触摸共享窗口,混合设备"触屏 tap + 空格 - tap"判 doubletap = 同一虚拟指针语义);**回车是唯一的即时双击键**—— - 确认类操作零延迟,同时保留空格×2 兼容触屏肌肉记忆。 -2. **不做 Esc 映射**:下滑已覆盖菜单语义(§5.1/§5.2),Esc 留给浏览器 - 自身/未来全屏退出,不进入手势域。 -3. **守卫**:`e.repeat` 抑制(按住只发一次);映射键 preventDefault - (防滚页/焦点激活);表单控件焦点(input/textarea/select/ - contenteditable)放行;未映射键(Tab 等)不拦截;tracking 单飞行 - 指针互斥(与多指忽略一致)。 -4. **提示随输入模式**(settings.input,见 §1.1):三通道永远并行在线, - 模式只切提示文案(遮罩「轻触开始」/「按空格开始」),供后续游戏内 - 提示与语音变体(input-parity 路线 a)接入。 -5. 遮罩键盘解锁:空格/回车 keydown 即可解锁音频(audio.js - showStartOverlay),PC 玩家全程无需鼠标。 -6. 测试:`web/harness/engine-input.test.mjs`(61 断言);live 管线 - `POST /gesture {"keyboard":true,"key":"ArrowLeft"|"Space"|"Enter"}` - 或 `{"type":"tap","keyboard":true}`(CDP Input.dispatchKeyEvent, - 会话剧本加 `keyboard:true` 即可键盘复跑)。 +结算:单击=再来一局;菜单已解锁则下滑=菜单。 -## 6. 向导层(R7 增补,已实现:engine/guide.js) +### 5.5 通则 -> 设计裁定与台词表全录:[14-guide-layer.md](14-guide-layer.md)。本节只列与全局约定交叉的部分。 +- 矩阵无空格:每个「可感知状态 × 已解锁手势」都必须有声音(回执或内容音)。见各游戏设计单。 +- 连点/乱滑不绑定破坏性操作。 +- 引导可跳过:已完成过引导的玩家,开场用一句问「点一下开始」;重听引导放到帮助手势,不占用下滑(新手期下滑未解锁)。 +- `immediateTap` 仅 rhythm/drummer 演奏期。新手期其他游戏用 `doubleTapWindow: 0` 获得即时单击,不要开 immediateTap。 +- 教学:第一句话就动手;一次只教一个;做对立刻肯定(≤4 字)并进入下一步,禁止成功后再灌补救。 -1. **四个全局机制**(方法论 research/02 §3 的 L3/L4 落地):状态签名 earcon(6 状态+2 功能音,运行时合成,全项目统一词表);回合标记 `turn`(等待输入开始处);静默看门狗(等待输入无操作→轻提醒→升级上下文提示,用尽即停不轰炸);纠错 `error`(error 震+一句"该做什么")。 -2. **等级语义**:`full`=earcon+向导语音+震动;`standard`=earcon+震动(**无向导语音**);`off`=全部 no-op 且不注册定时器。向导语音一律 `queue:true`,**绝不顶掉在播对白**(A3 信息分层)。 -3. **与既有约定的边界**:向导不改变任何手势映射与玩法判定;向导震动只用既有震型(confirm/error/nudge 复用),不新增语义震;向导 earcon 是**系统音**(P2 系统音/叙事音分离),不占用 sfx 内容音语义域。 -4. **看门狗通用参数**(各游戏已按此接):第 1 级 8~10s(nudge+一句提示),第 2 级 20~25s(上下文提示/复述),之后停止;玩家任意手势复位。escape 三级卡关计时器(60/120/180s)保持独立,不受影响。 -5. **测试约定**:既有 playtest 一律 `settings.set({guide:'off'})` 验证纯玩法逻辑;向导行为由 `web/harness/guide.test.mjs`(full 档)覆盖。 +### 5.6 键盘平行通道 + +键位表不变(方向键/WASD=滑,空格=单击,回车=双击,按住空格=长按)。 + +新手期差异: +- `doubleTapWindow: 0` 时触屏与空格均立即单击。 +- 回车仍发 `doubletap`:游戏层在新手期把它当成 `tap`。 +- 提示文案随 `settings.input`:触屏说「点一下」,键盘说「按空格」。 + +守卫、遮罩解锁、测试约定沿用原实现。 + +--- + +## 6. 发言权、等待声、向导 + +实现骨架见 26 号 §4.1~§4.6;接线裁定见 [14-guide-layer.md](14-guide-layer.md)。 + +| Floor 状态 | 听感 | 玩家操作 | +|---|---|---| +| `SPEAKING` | 语音;BGM 整链压低;句间不起等待声 | 可打断句:立刻停,按 §5.1 处理;关键句:`busy` | +| `YOUR_TURN` | 交接提示音 `turn` → 400ms 后等待声,直到操作 | 等待声 80ms 淡出,交给游戏 | +| `BUSY` | 只有内容演出,无等待声 | `busy` | +| `ACTION` | 玩法声本身就是状态(节奏、闪避、探索环境音常驻) | 直接交给游戏 | + +等待声:合成、轻、全项目一套(26 §4.1 起步参数)。剧情类可用很轻的环境循环替代,但必须全游戏统一。 + +迷茫:从乱点/连无效/反复重听识别,三级接管(短提示 → 只留一个手势 → 自动演示)。静默看门狗只负责「不动」:动作类 5~8s,剧情类 15~20s,最多两级后安静,只留等待声。 + +状态签名 earcon(menu/explore/battle/choice/settle/danger + turn/nudge)保留。新手期只教玩家能感知的 ≤3 种,第一次出现配一句解释。 + +--- + +## 7. 横切验收(每款游戏开工前) + +设计单必须交齐 26 号附录 C:一句话核心动作、≤3 个可感知状态、新手手势与解锁、状态×手势矩阵、等待点、首 3 分钟逐秒脚本、失败表、台词类别、断点复述句。 + +合格指标见 26 §6.4。出现 26 §7 任一反模式,设计不通过。 diff --git a/docs/design/01-escape-redesign.md b/docs/design/01-escape-redesign.md index 09277d77..5c28da60 100644 --- a/docs/design/01-escape-redesign.md +++ b/docs/design/01-escape-redesign.md @@ -1,148 +1,276 @@ -# 01 · 《暗室》重设计(escape redesign) +# 01 · 《暗室》重设计(26 号对齐) > **给开发者的实现要点摘要** -> 1. 保留 3x3 网格与谜题链骨架(钥匙→铁柜→保险丝→电箱→大门),重写开场:前 ~2 分钟为**引导式脚本**(带玩家走到储物间、教会移动/检查/双击使用),之后进入自由探索。 -> 2. 新增**卡关检测 + 三级提示**:以上一次"进展事件"起算,60s 无进展给暗示、120s 给明示、180s 给带路级提示;提示由计时器自动播出,不依赖玩家发现隐藏手势。 -> 3. 环境线索音**可重听**:单击检查总是先重播当前房间环境音再播描述;9 个房间环境音去重(关键房间专属线索音)。 -> 4. 操作反馈全部改震动(确认/撞墙/拾取/错误),iOS 静默降级;删除数字拼接播报,用时改为分段整句。 -> 5. 手势映射(偏离全局默认,教学中讲清):四向滑=移动,单击=检查/重听,双击=使用物品/背包与目标,引导阶段下滑=跳过引导。 +> 1. **交互权威**:[26-audio-interaction-playbook.md](26-audio-interaction-playbook.md)。施工设计单摘要见 [26-apply-escape.md](26-apply-escape.md)。地图 as-built 见 [25-escape-map-flow.md](25-escape-map-flow.md)。冲突以 26 号为准。 +> 2. **保留**:3×3 网格、谜题链(钥匙→铁柜→保险丝→电箱→大门)、房间环境音签名、引导走到储物间拿钥匙。 +> 3. **探索 = Floor `ACTION`**:四向滑=移动;**单击=检查并使用**(站在目标上点一下就完成拾取/开柜/修电/开门);**长按=我在哪**(位置→目标→一个动作)。新手期 `doubleTapWindow: 0`,双击不存在。 +> 4. 第一句就让玩家滑;第一次成功(脚步声)≤10s。回执 ok/invalid/busy ≤100ms,禁止只震不响。 +> 5. 卡关不再按 60/120/180s 原样重播说明书。走迷茫三级接管 + 三档提示(完整句→短句→提示音)。 --- -## 1. 现版诊断:"第一个房间都出不去"断在哪 +## 0. 设计单(26 号附录 C) -读 `web/escape/game.js` 现版流程,新手断点有四: +### 0.1 一句话核心动作 -1. **教学是两段可跳过的长旁白,零交互**(`voice/intro` → `voice/tutorial`,game.js:56-87)。玩家听完(或单击跳过)后被直接丢进 `PLAY`,没有任何一次"做对了一件事"的体验确认。移动、检查、使用物品三个核心动作都没有被验证过玩家真的会了。 -2. **进入自由探索后没有任何方向感支撑**。起点 (1,1) 播一句 `voice/start_room`,玩家不知道房间有 9 个、不知道自己在哪、不知道目标在西南角。环境音只有 3 种(hum/wind/drip)铺给 9 个房间(`_playAmbient`, game.js:134-145),相邻房间声音雷同,靠环境音导航不成立。 -3. **环境线索音只播一次**。进房间播一遍就没了,没听清就永远错过;`voice/wall` 撞墙提示也只播一次(`wallVoicePlayed`)。 -4. **提示系统存在但不可达**。`hint_*` 四级提示语音已生成,但唯一入口是"双击且无可用目标"的兜底(game.js:195-198)——玩家根本不知道双击这个手势存在,更不知道要在"错误的地方"双击才能听到提示。卡关玩家恰恰是不会操作的人,提示入口藏在操作里,死循环。 +**朝四个方向滑动在房间里移动,单击检查和使用,按住屏幕问路。** -## 2. 新开场:引导式脚本(GUIDE 阶段) +### 0.2 玩家可感知的状态(3 个)与声音身份 -目标:前 2 分钟是"手把手",玩家每做一个动作都得到确认,引导结束时已天然完成谜题链第一环(拿到钥匙)。 - -### 2.1 流程 - -阶段 `GUIDE`(脚本化,步骤由玩家手势推进,不是定时旁白): - -| 步 | 旁白(整句,功能) | 等待的玩家动作 | 反馈 | +| 状态 | Floor | 听感 | 第一次出现时教一句 | |---|---|---|---| -| G0 | 剧情引入(2 句以内,比现版 intro 短):醒来、漆黑、要逃出去 | 单击继续 | CONFIRM 震动 | -| G1 | "先活动一下。试着朝任意方向滑一下,走一小步。" | 任意滑动 | 脚步声 + SWIPE_OK 震动 + "很好,你迈出了第一步。" | -| G2 | "这个房间不大,边界是墙。再滑两下,试着碰碰墙。" | 滑动 2 次(其中撞墙也计数) | 撞墙:wall 闷响 + ERROR 震动 + "那边是墙,换个方向。"(每次撞墙都播,取消 wallVoicePlayed 一次性限制);未撞墙:脚步 + SWIPE_OK | -| G3 | "跟着我指的路走。向左滑。" | 左滑(做错给纠正语音重等) | 脚步 + "对。再向下滑。" | -| G4 | "再向下滑。" | 下滑 | 脚步 + 到达储物间,播储物间专属环境音 | -| G5 | "听到水声和铁锈味了吗?单击一下,仔细检查这个房间。" | 单击 | pickup 音 + PICKUP 震动 + "你摸到了一把钥匙!"(谜题链第一环完成) | -| G6 | "钥匙应该能开什么。双击是把物品用出去的用法。现在先记住:双击。" | 双击 | 兜底播报背包 + CONFIRM 震动 + "对,就是这样。接下来靠你自己了。" | -| G7 | 目标陈述:"找到能用这把钥匙打开的东西。需要帮助时,在原地双击。" | — | 进入 PLAY | +| 探索(轮到你) | `ACTION` | 当前房间环境音常响(水滴/风/电流) | 「听到房间的声音,就可以走。」 | +| 描述在播 | `SPEAKING` | 房间描述语音;BGM 整链压低;句间不起等待声 | (开场指令即 SPEAKING) | +| 撞墙 / 无效 | — | 闷响 + `invalid` 回执(低「咚」) | 「那边是墙,换个方向。」 | -- 引导期间任何步骤可**下滑跳过**(播"跳过引导"确认后直达 PLAY);跳过标志存 localStorage/进度,第二次进游戏开场直接问"上滑重听引导,单击直接开始"。 -- 引导期间做错手势:不惩罚,播一句短纠正("不是这个方向,向左滑")继续等待;同一个步骤错 3 次给 `WARNING` 震动 + 更直白的重述。 -- 引导脚本写死路线 (1,1)→(0,1)? 不,(1,1)→左=(0,1),再下滑=(0,2) 储物间。路线合法(现版图 (0,1) 是空房·风)。引导期间超出脚本路线的滑动被温和拦回("先跟我走")。 +结算、菜单是元状态,用 `bgm/menu` + 结算句标识,新手期不新教第 4 种状态音乐。 -### 2.2 教学覆盖核对 +### 0.3 新手手势表与解锁(四向滑动是玩法 → 帮助一律长按) -引导结束时玩家已亲手做过:滑动移动(含撞墙)、单击检查、拾取、双击使用/查看。全部六个手势里只剩"上滑"未用——escape 不用上滑(豁免,见 00 文档 §5.2),无需教。 - -## 3. 卡关检测与三级提示 - -### 3.1 进展事件 - -以下任一发生即重置卡关计时器 `lastProgressAt`:拾取钥匙、打开铁柜、拾取保险丝、修好电箱、首次到达某关键房间(铁柜房/电箱房/大门,各记一次)、成功逃脱。普通移动/检查空房不算进展。 - -### 3.2 三级提示(计时器自动触发,无需手势) - -| 无进展时长 | 级别 | 内容(整句预生成,每级每阶段一句) | 附带 | -|---|---|---|---| -| 60s | L1 暗示 | 面向当前目标的一句意象提示,如"铁锈味最重的地方,值得仔细看看" | WARNING 震动 + 语音提示前播"需要帮助吗?" | -| 120s | L2 明示 | 直接点明目标房间与动作,如"去西南角的储物间,单击检查,找钥匙" | WARNING 震动 | -| 180s | L3 带路 | 以玩家当前坐标算路,逐步导航:"从这里,向左滑,再向下滑"(每次触发按当前位置重算,滑动方向词为预生成的整句组合:方向指令句 4 句 × 顺序连读,非数字拼接,允许两句链播) | WARNING 震动,此后每 60s 重复 L3 直至有进展 | - -- 提示级别按当前谜题链阶段(`_hintVoice()` 的 4 阶段:找钥匙/开铁柜/修电箱/开大门)× 3 级 = 12 句新增语音,全部是**整句**。 -- 提示播完不重复骚扰:同 level 对同阶段只自动播一次(L3 除外,可周期重复)。 -- 主动求提示保留:双击无目标时的兜底语音升级——先播背包,再播 L2 级明示(原来是 L1 级 hint_*)。 - -## 4. 环境线索音可重听 + 房间声音去重 - -1. **单击检查 = 重播环境音 + 描述**:`_inspect()` 改为先 `play(当前房间环境音)` 再链播描述语音(现版只播描述)。玩家随时可重听线索。 -2. **环境音从 3 种扩到 6 种**,关键房间各有专属签名(Stable Audio 3 生成或合成,双格式): - - 储物间 (0,2):滴水 + 金属杂物轻响(`amb_storage`) - - 铁柜房 (2,1):嗡鸣 + 铁皮共振(`amb_cabinet`) - - 电箱房 (0,0):电流嗡鸣 + 偶尔火花(`amb_power`) - - 大门 (2,2):风声 + 门外空旷感(`amb_gate`) - - 起点 (1,1):单一滴水(`amb_drip`,保留) - - 其余 4 间空房:风声/远处嗡鸣两种弱化版轮换(`amb_wind`/`amb_hum` 保留) -3. 进房间自动播环境音(保留现行为),且**每次进入都播**——不再是"错过就没了",配合单击重听,线索可随时复核。 -4. 大门房间通电后环境音切换为带电流声的版本(`amb_gate_live`),听觉上确认"电通了"这条谜题状态。 - -## 5. 反馈改震动(对齐 00 文档 §2) - -| 事件 | 原反馈 | 新反馈 | +| 手势 | 含义 | 解锁 | |---|---|---| -| 移动成功 | 脚步声 | 脚步声 + SWIPE_OK | -| 撞墙 | wall 闷响 | wall 闷响 + ERROR | -| 拾取/开柜/修电箱 | sfx + 语音 | sfx + 语音 + PICKUP / SUCCESS | -| 双击无目标(无效使用) | ui_deny 提示音 | **删除 ui_deny**,改 ERROR 震动(内容语音保留) | -| 逃脱成功 | 音效+语音 | 音效+语音 + SUCCESS | -| 提示出现 | 无 | WARNING | +| 四向滑动 | 移动 | 阶段 0(核心玩法,不锁) | +| 单击 | 检查 + 使用(站在目标上即用) | 阶段 0 | +| 长按 | 我在哪(目标 + 方位一句,≤20 字) | 阶段 1(拿到钥匙时教,马上用一次) | +| 下滑(对局中) | 仍是「向下走」,**不是菜单、不是跳过引导** | — | +| 下滑(结算后) | 菜单 | 阶段 2(第一次逃脱后) | +| 双击 | 重听最近描述 | 阶段 3(累计 3 局);未解锁按单击处理 | -`sfx/ui_deny` 这类"反馈提示音"全部退役;内容音(脚步、撞墙、机关)保留。 +`attachGestures(..., { doubleTapWindow: 0 })`。键盘回车当单击。 -## 6. 语音重做清单(整句化 + 双音色) +### 0.4 状态 × 手势矩阵(新手期) -- **删除**:`n0`-`n9`/`nshi`/`unit_min`/`unit_sec`/`time_prefix` 数字拼接链。用时播报改 4 段整句:"不到两分钟""五分钟内""十分钟内""超过十分钟"(`time_band1..4`),前缀"你逃出来了,用时"并入各段整句一次生成。 -- **新增**:引导脚本 8 步全部语音(每步 1-2 短句)、12 句三级提示、跳过引导/二周目开场句、储物间等 4 个关键房间的新描述。 -- **保留可用**:`empty_*`、`inspect_*`、`found_key`、`unlock_cabinet`、`fix_box`、`escape`、`replay`、`inv_*` 已是整句,重录一版 male 目录即可(文本不变,speaker=Uncle_Fu,instruct 与 female 版同款)。 -- 全部语音按 00 文档 §3 生成:同 speaker+instruct 常量、female/male 双目录、ogg+m4a 双格式。 +| 状态 \ 手势 | 单击 | 四向滑动 | 长按 | 双击(未解锁) | 连点 / 乱滑 | +|---|---|---|---|---|---| +| 引导答题 | 按步骤答 / `invalid`+首次纠正 | 按步骤答(脚步=`silent-ok`;撞墙=闷响+`invalid`) | 帮助一句 | 按单击处理 | 只处理第一下,其余 `busy` | +| 探索 ACTION | 检查+使用(环境音/描述即反馈) | 移动=脚步;撞墙=闷响+`invalid`+方位归因 | 帮助(目标+方位) | 按单击处理 | 计入迷茫 | +| 描述在播 SPEAKING | 跳过这句 + `ok`(关键获得 `critical`→`busy`) | 打断并移动 | 打断,帮助 | 按单击处理 | 400ms 内 `busy` | +| 结算 | 再来一局 + `ok` | `invalid` + 首次解释 | 重听结算 | 按单击处理 | 只处理第一下 | +| 菜单(阶段 2) | 确认当前项 | 左右滑切换(报名) | 重听菜单说明 | 按单击处理 | `busy` | +| 迷茫 L2 | 照常 | 只放行接管方向,其余 `invalid`(不移动) | 照常 | 照常 | 计入冷却 | -## 7. 流程图(新) +无空格、无「没有声音」的格子。连点不绑定重播、重开、跳过引导。 -``` -start → GUIDE(可下滑跳过)→ PLAY(自由探索 + 三级提示计时器)→ ENDING → 单击重开 / 下滑菜单 -``` +### 0.5 等待点配置 -PLAY 阶段手势:四向滑=移动 / 单击=重播环境音+检查 / 双击=使用物品(无目标→背包+L2 明示)。ENDING:单击=重开,下滑=菜单(回大厅)。 +引导等待点(静默 6s 短句 / 15s 提示音): + +| id | 期待 | `_full` | `_short` | L2 只留 | L3 演示 | +|---|---|---|---|---|---| +| `g0_swipe` | 任意滑动 | 「黑暗。朝任意方向,滑一下。」 | 「滑一下。」 | 任意滑 | 游戏自己走一步 | +| `g2_wall` | 滑动×2(含撞墙) | 「再滑两下,试试墙。」 | 「再滑。」 | 任意滑 | 演示撞墙闷响一次 | +| `g3_left` | 左滑 | 「现在,向左滑。」 | 「向左滑。」 | 左滑 | 游戏向左走 | +| `g4_down` | 下滑 | 「对。再向下滑。」 | 「向下滑。」 | 下滑 | 游戏向下走进储物间 | +| `g5_tap` | 单击 | 「单击一下,仔细检查。」 | 「单击。」 | 单击 | 「听我做一次。」→ 拾钥匙 | + +探索期无固定等待点。静默(约 40s 起,之后每 20s 喂一次静默信号)与乱点一并进迷茫: + +| 级 | 做什么 | 台词 | +|---|---|---| +| 1 | 说目标方位短句(30s 台账去重,说过就只给提示音) | `nav_*`「向右滑。」 | +| 2 | 只放行第一导航方向 | 「现在,只要向左滑。」 | +| 3 | 游戏自己朝目标走一步 | 「听我做一次。」→ 脚步 →「现在你来。」 | + +同一等待点升到 3 后停止接管,只留房间环境音。**禁止**同级原样重播 26 秒说明书。 + +### 0.6 首 3 分钟逐秒脚本 + +| 时间 | 游戏说 / 播 | 期待玩家做 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 环境水滴 →「黑暗。朝任意方向,滑一下。」(`critical`,≤3s) | 任意滑动 | 6s:「滑一下。」再 6s 只提示音 | 单击:`invalid`+「先滑一下。」 | +| 3~10s | 脚步声(第一次成功)→「对。你被困在地下设施里。再滑两下,试试墙。」 | 滑动(撞墙也算) | 「再滑。」 | 方向错也有闷响(试错无代价) | +| 10~25s | 闷响 +「那边是墙,换个方向。」+ 方位句 →「现在,向左滑。」 | 左滑 | 「向左滑。」 | 其他方向:纠正,不惩罚 | +| 25~40s | 脚步 →「对。再向下滑。」→ 储物间环境音 | 下滑 | 「向下滑。」 | 纠正 | +| 40~55s | 「听到水声了吗?单击一下,仔细检查。」→ 拾取音 →「你摸到了一把钥匙!」 | 单击 | 「单击。」 | 滑动:纠正 | +| 55~70s | 「想知道自己在哪,就按住屏幕。」(马上等一次长按)→「找到能用钥匙打开的东西。」 | 长按一次,然后自由探索 | 「按住屏幕。」 | 其他手势:`invalid`+首次解释 | +| 70~180s | 首到房间描述、谜题链(钥匙→铁柜→保险丝→电箱→大门) | 探索:滑到目标,单击使用 | 迷茫三级 | 撞墙归因;用错地方由检查句说明 | + +节奏检查:3s 内动手;10s 内第一次成功(脚步);每 30s 有可听进展(新房间环境音/描述)。无纯规则长说明。 + +### 0.7 失败表 + +| 失败类型 | 判定 | 归因句 | 回到哪里 | 新手期代价 | +|---|---|---|---|---| +| 方向错(撞墙) | 越界 | `wall` + `wall_dir_*`(每方向整局语音一次,之后只闷响+`invalid`) | 原地 | 无 | +| 引导错手势 | 与当前步不符 | 短纠正(「向左滑。」) | 本步重等 | 无 | +| 用错地方 | 站位不对 | 检查描述告诉你这里有什么;不扣物品 | 原地 | 无 | +| 迷路 / 乱点 | 迷茫信号 ≥2 | L1 短目标 → L2 只留方向 → L3 走一步 | 原地 | 无 | +| 同步失败 3 次 | 引导同一等待点 | 「听我做一次。」演示后交还 | 本步 | 无 | + +归因 2s 内播出;玩家一操作,排队中的 `feedback` 作废。 + +### 0.8 台词类别 + +- `critical`:`g0_new`(开场指令 ≤3s);关键获得句拆短后的那一句(摸到钥匙 / 通电确认),≤3s。 +- `content`:引导指令、房间描述、`found_key` 链其余句、`unlock_cabinet`、`fix_box`、`escape`、`time_band*`、`start_room`、`inspect_*`、`gd_*_dir`、`gd_next_fuse`。 +- `hint`:长按帮助 `lp_*`、`nav_*`、三档短句、`sys_demo` / `sys_now_you`、菜单说明。 +- `feedback`:`wall`、`wall_dir_*`、引导纠正、`inv_*`(首次解释无效手势)。 + +完整清单见 `tools/gen_voice_dual.py` 的 `ESCAPE_LINES` 与 [01-escape-voice-ids.md](01-escape-voice-ids.md)(本文不改 voice-ids 文件)。须停用或重录任何「双击使用 / 双击求提示」旧句(含 `g6_doubletap`)。 + +### 0.9 断点复述 + +- 每步移动/进展写 `soundgame.escape.save`(坐标 + 谜题旗标 + 用时)。 +- 回来 5s 内:状态声(房间 amb)+ 复述句(位置。目标。一个动作。)+ 交出探索。 +- 二周目开场:`sys_continue_ask` 语义——单击继续上次;右滑重新开始。不再用上滑重听整段引导。 + +### 0.10 测试记录 + +无头 playtest:`web/escape/test/playtest.mjs`。交互验收按 [13-playtest-criteria.md](13-playtest-criteria.md) §3:3s 内任意滑;引导走到储物间;**单击**检查得钥匙;自由探索同链逃脱。真人冷启动待测。 --- -## 修订记录(追加不改旧文,编号自 R1 起) +## 1. 保留的空间与谜题(不变) -### R1 · escape-state round2 状态引导改进(2026-09-15,方案见 25 号文档 §4.1) +### 1.1 3×3 网格 -按 25 号文档 §4.3 实施清单 F1-F9 落地(F6/B5b 按方案"可分期"留档): +坐标 x 0 西→2 东,y 0 北→2 南。内部全连通,无锁门。外圈撞墙。起点 (1,1)。 -1. **撞墙报方位(B1/F1)**:§2.1 G2 表格的撞墙反馈在 PLAY 阶段升级为 - `wall` 播完接播陈述式方位句 `wall_dir_`「左边是墙。」等 4 句 - (同向 10s 限频、被打断作废)。这是对 round1-live"回退教学腔方位句"的 - 部分再修订——回退掉的是措辞,方位信息以世界内陈述句回归。 -2. **目标句带目标名(B2/F2)**:三处进展链尾方位句由「右面的房间。」升级为 - "目标名+方位"一体句(`gd_cabinet_dir`/`gd_box_dir`/`gd_gate_dir`), - 原 `gd_room_*` 保留作异常路径回落。 -3. **无进展计时器回收(B3/F3)**:§3.1"以上一次进展事件起算"的原始意图以 - **平行通道**部分回收——无进展 150s 且期间 ≥5 次有效手势(活跃迷路)播 - 不泄题的进度重述 `gd_prog_1/2/3`(每进展段限 1 次);现行"无操作 60/120/ - 180s"三级提示骨架不动(round1-live 修订继续有效)。 -4. **§3.2 末条修订(B4/F4)**:「主动求提示保留:先播背包,再播 L2 级明示」 - 修订为「先播背包/use_fail,再播 **L1 级意象暗示**」——live 两次实录没卡关 - 的玩家被白送 L2 答案(泄漏 > 帮助),L2/L3 仍锁在计时器里(修订留痕于此, - 25 号文档 §4.1 B4 有完整证据链)。 -5. **空房首访描述(B5a/F5)**:§4 房间声音去重补一条——空房首访一次性补播 - 现有 `empty_*`(不算进展事件;B5b 含方位特征的改词重录留后续轮)。 -6. **引导 busy 手势缓存(B6/F7)**:§2.1 补——步骤推进语音播放中(busy) - 的首个非跳过分手势被缓存,解锁后按新步骤补判(合法=作答,不合法=答错 - 路径;缓存手势不触发下滑跳过),符合 00 文档 §5.4"做了手势 3 秒内必须有 - 反馈"。 -7. **结算链单击守卫(B7/F8)**:§7 流程图 ENDING 补——结算链播完前单击不 - 立即重开(`_settleBusy`,链完/30s 兜底解除),保障用时播报与操作指引的 - 完整到达权。 -8. **目标句全局可达(B8/F9)**:§2.1"跳过引导直达 PLAY"修订——跳过确认句 - 后与 REOPEN 直进路径都补播一次 `g7_goal`(任何路径进 PLAY 都至少听过 - 一次目标句;二周目可单击跳过)。 +| 房间 | 坐标 | 环境音 | 单击(检查+使用) | +|---|---|---|---| +| 电箱房 | (0,0) | `amb_power` | 有保险丝 → 修电(`fix_box`);否则描述未修/已修 | +| 空房 | (1,0) | `amb_wind` | `empty_1` | +| 空房 | (2,0) | `amb_hum` | `empty_3` | +| 空房 | (0,1) | `amb_hum` | `empty_2` | +| 起点 | (1,1) | `amb_drip` | `start_room`(操作总览改为:滑动移动,单击检查) | +| 铁柜房 | (2,1) | `amb_cabinet` | 有钥匙且未开 → 开柜得保险丝;否则锁着/已开描述 | +| 储物间 | (0,2) | `amb_storage` | 未拾钥匙 → 拾取;否则空描述 | +| 空房 | (1,2) | `amb_wind` | `empty_3` | +| 大门 | (2,2) | `amb_gate` / 通电后 `amb_gate_live` | 已通电 → 逃脱;否则门的状态描述 | -新断言登记(13 号文档不动):escape playtest 场景 G-N(B1/B2/B3/B4/B5a/ -B6/B7/B8 各场景,含边界:B3 进展复位、B4 降级后 L2 仍可达、B6 缓存按序 -补判、B7 结算链完整到达)共 +85 条至 235 全绿;guide.test ⑩c 换线 + -⑩d B6 full 档共 132 全绿;逐条勾账与既有断言变更登记见 25 号文档 -「阶段四 · 实施勾账」。新语音 10 句台词表见 01-escape-voice-ids.md -「escape-state round2 新增主表句」。 +### 1.2 谜题链 + +``` +储物间单击 ──得钥匙──▶ 铁柜房单击 ──得保险丝──▶ +电箱房单击 ──通电──▶ 大门单击 ──▶ 逃脱 +``` + +物品即进度:`hasKey` / `hasFuse` / `cabinetOpen` / `powerOn`。阶段:`key → cabinet → box → gate`。 + +最短路径(移动次数与旧路线相同,使用全部改为单击): +(1,1)→左(0,1)→下(0,2)【单击拾钥匙】→右(1,2)→右(2,2)→上(2,1)【单击开柜】→左(1,1)→左(0,1)→上(0,0)【单击修电】→右(1,0)→右(2,0)→下(2,2)【单击逃脱】。 + +### 1.3 环境音规则(保留) + +1. 单击检查 = 先重播当前房间环境音,再播描述。 +2. 每次进入房间都播环境音(错过可再听)。 +3. 关键房间专属签名:储物间滴水杂物、铁柜铁皮共振、电箱火花、大门风声;通电后大门切 `amb_gate_live`。 +4. 空房首访补播 `empty_*`((1,0)→1 / (0,1)→2 / (1,2)→3 / (2,0)→3),不算进展事件。 +5. 探索期环境音常驻,承担「还活着」;等待声可更轻或由 amb 代替(全游戏统一,见 [10-feedback2-state-bgm.md](10-feedback2-state-bgm.md))。 + +--- + +## 2. 引导(GUIDE)——删除 G6 双击 + +引导仍写死路线:(1,1)→左(0,1)→下滑(0,2) 储物间。超出路线的滑动温和拦回(「先跟我走。」)。做错不惩罚。 + +| 步 | 指令 | 等待 | 成功 | +|---|---|---|---| +| G0 | 「黑暗。朝任意方向,滑一下。」 | 任意滑动 | 脚步 +「对。」 | +| G1–G2 | 再滑、碰墙 | 滑动(撞墙计数) | 闷响 + 方位 | +| G3 | 「现在,向左滑。」 | 左滑 | 脚步 | +| G4 | 「再向下滑。」 | 下滑 | 储物间 amb | +| G5 | 「单击一下,仔细检查。」 | 单击 | 拾钥匙(谜题第一环) | +| G6 | **废止**(原「双击使用」) | — | — | +| G7 | 「找到能用钥匙打开的东西。迷路了,按住屏幕。」 | 长按一次(阶段 1 教学)后进 PLAY | 帮助句 + 自由探索 | + +- **没有「下滑跳过引导」**。下滑是向下走。已完成过引导的玩家:开场一句「点一下开始」直进 PLAY(可跳过引导复述);帮助手势重听「我在哪」,不重念整段教案。 +- 引导指令标 `content`(G0 开场那句 `critical`)。迷茫接管后再演示,不靠玩家发现隐藏手势。 +- `_guideBusy` 期间:缓存首个非帮助手势,语音结束后按新步骤补判(合法=作答,不合法=纠正)。缓存 1 个,不跨步。 + +进 PLAY 的任何路径(含二周目直进)都至少听过一次目标句(`g7_goal` 或等价短句)。二周目可单击跳过该句。 + +--- + +## 3. 单击 = 检查 + 使用 + +站在正确房间、满足前置时,**一次单击**同时完成「听这里有什么」和「用身上的东西」。 + +| 房间 | 前置 | 单击结果 | +|---|---|---| +| 储物间 | 尚无钥匙 | 拾钥匙(内容音 `pickup`,可省 `ok`) | +| 铁柜房 | 有钥匙且未开 | 开柜得保险丝 | +| 电箱房 | 有保险丝且未通电 | 修电 | +| 大门 | 已通电 | 逃脱 | +| 其他 / 前置不满足 | — | 只播检查描述(告诉你这里没有可做的,或还缺什么) | + +废止:双击使用、双击无目标=背包+L2 答案。背包信息不作为「乱点奖励答案」;需要时由长按帮助一句带出(「储物间。找铁柜。向右滑。」),不点明未到达的解法步骤。 + +关键获得链标 `critical` 的那一句 ≤3s,其余可跳。结算链播完前单击不重开(`_settleBusy`,链完或 30s 兜底),给 `busy`。 + +--- + +## 4. 迷茫接管(取代 60/120/180 卡关广播) + +**进展事件**(重置迷茫、进入下一目标):拾钥匙、开铁柜、拾保险丝、修电箱、逃脱;首到储物间/铁柜房/电箱房/大门各记一次。普通移动、检查空房不算进展。 + +旧「无操作 60s L1 / 120s L2 / 180s L3 且 L3 每 60s 原样重播带路句」**废止**。改为 26 号 §4.5: + +- 信号:连续无效、乱滑、同步骤失败、静默(探索约 40s 无进展也计静默)。 +- 权重到 2 → 接管,15s 冷却。 +- L1 短目标句(不泄完整解法)→ L2 只放行导航方向 → L3 游戏走一步。 +- 同一目标段升到 3 后只留环境音。 +- 主动帮助 = 长按,回答一句(位置→目标→一个动作),**不是** L2 明示房间+动作的白送答案。 + +撞墙方位句 `wall_dir_*`、进展链尾目标名+方位 `gd_cabinet_dir` / `gd_box_dir` / `gd_gate_dir`、不泄题进度句 `gd_prog_*` **保留**,但: + +- `gd_prog_*` 并入迷茫 L1 或进展事件当下,不另开 150s 平行广播与 60/120/180 叠炸。 +- 游戏主动提示永不原样重复;玩家长按才给完整句,前加「再说一遍:」。 + +--- + +## 5. 回执、震动、发言权 + +| 事件 | 声音(主通道) | 震动(补充) | +|---|---|---| +| 移动成功 | 脚步 = `silent-ok` | swipe(引擎内置) | +| 撞墙 | 闷响 + `invalid` | error | +| 检查/使用成功 | 内容音(拾取/开门)可省 `ok` | pickup / success | +| 无效手势 | `invalid`;该状态×手势首次加一句解释 | error 可叠加 | +| 关键句期间操作 | `busy` | — | +| 逃脱 | 内容音 + 结算链 | success | + +禁止:无效只震;iOS 无震动则无反馈;删除内容音只留震。`sfx/ui_deny` 仍退役。系统回执用合成音;脚步/墙/机关用录音。 + +Floor:描述链 `SPEAKING`(整链 duck);探索 `ACTION`;结算链 `SPEAKING`/`BUSY`;结算后 `YOUR_TURN`(单击再来)。 + +--- + +## 6. 语音与用时(整句,保留分段) + +- 用时仍 4 段整句 `time_band1..4`,禁止数字拼接。 +- 引导句按 §0.6 缩短为「动作+结果」;`start_room` 去掉「双击使用」。 +- 男女双目录、ogg+m4a,规范见 [00-global-ux.md](00-global-ux.md) §3。 + +--- + +## 7. 流程 + +``` +start → 首局 GUIDE(G0–G5 + 长按教学)→ PLAY → ENDING + → 二周目:单击继续 / 右滑重开 +ENDING:单击=再来一局;阶段 2 后下滑=菜单(左右滑切换,单击确认) +``` + +BGM:探索 `bgm/drone`;菜单/结算 `bgm/menu`。禁止结算黑屏寂静。见 10 号文档。 + +--- + +## 8. 废止清单 + +| 废止 | 改为 | +|---|---| +| G6 双击使用 / 双击求提示 | 单击检查+使用;长按=我在哪 | +| 新手期识别双击;`doubleTapWindow` 300ms | `doubleTapWindow: 0`;双击当单击 | +| 引导期下滑=跳过引导 | 下滑=向下走;回访者开场单击跳过教案 | +| 60/120/180s 原样重播 L1/L2/L3;L3 每 60s 再念带路 | 迷茫三级 + 三档提示,不原样重复 | +| 双击兜底直通 L2 答案 | 长按一句状态;答案不白送 | +| 操作反馈只靠震动;iOS 静默 | 回执声必响 | +| 开场两段可跳过长旁白、零交互 | 第一句就滑 | +| 菜单:单击循环、双击确认 | 左右滑切换、单击确认 | +| 看门狗重播完整说明书 | 完整句 → 短句 → 提示音 | +| 句间恢复 BGM | 整链压低 | + +仍有效:3×3 地图与谜题链、环境音 6 类、撞墙方位、目标名+方位进展句、空房首访、busy 手势缓存、结算链守卫、用时分段整句、存档字段。 + +地图格点与 as-built 差异对照见 [25-escape-map-flow.md](25-escape-map-flow.md)。交互以本文与 26-apply-escape 为准,不以旧代码双击语义为准。 diff --git a/docs/design/01-escape-voice-ids.md b/docs/design/01-escape-voice-ids.md index 9b2f0219..a2ca0bbf 100644 --- a/docs/design/01-escape-voice-ids.md +++ b/docs/design/01-escape-voice-ids.md @@ -1,5 +1,13 @@ # 01b · 《暗室》语音台词表(voice ids) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > 主台词表(80 句,双性别)的唯一事实源是 `tools/gen_voice_dual.py` 的 > `ESCAPE_LINES`(R2 重设计全文见 [01-escape-redesign.md](01-escape-redesign.md); > 史源 app/src/escape/gen/gen_voice.py)。本文档登记 P1 键盘变体(输入平权 diff --git a/docs/design/02-simon-redesign.md b/docs/design/02-simon-redesign.md index fac79860..167c6763 100644 --- a/docs/design/02-simon-redesign.md +++ b/docs/design/02-simon-redesign.md @@ -1,158 +1,220 @@ -# 02 · 《音序回响》重设计(simon redesign) +# 02 · 《音序回响》重设计(26 号对齐) > **给开发者的实现要点摘要** -> 1. **删除 BGM**:`bgm/theme` 不再播放,游戏只有交互音色 + 语音两条线(响应反馈⑥)。 -> 2. **旁白全部整句化**:删除 `di/lun/num_*/score_pre/best_pre` 拼接体系,轮次与成绩改预生成整句(轮数 1-20 逐句 + 无数字兜底;成绩分段整句)。 -> 3. **交互节奏**:播放序列时每个音前给 `BEAT` 轻震提供节拍感;复现阶段每个手势给 `CONFIRM` 震动确认;对/错用 SUCCESS/ERROR 震动,`sfx/correct`、`sfx/wrong` 提示音删除。 -> 4. **教学改交互式**:每教一个音色就让玩家亲手做一次该手势,做对才教下一个。 -> 5. **人性化与复活**:每局初始 1 次"重听机会"(输入阶段双击=重听当前序列,消耗机会;答错时若有机会则消耗一次机会重听而非直接结算),每过 4 轮 +1 次(上限 2);速度爬升放缓;前 5 轮避免同手势三连。 +> 1. **交互权威**:26 号手册。设计单摘要见 [26-apply-simon.md](26-apply-simon.md)。 +> 2. **玩法保留**:听一串音色,原样复现。对局/教学**无 BGM**;仅菜单与结算挂 `bgm/menu`(0.3)。六手势音色映射保留,但**按实力渐进开放**,新手只有单击/左滑/右滑。 +> 3. **帮助=长按**(六手势都是答题输入)。新手期无双击音色;`doubleTapWindow: 0`。 +> 4. **失败不扣机会账**:第 1~2 次答错=真话归因 + 免费重听整串;第 3 次放慢;再错才结算。新手期零资源消耗。 +> 5. 听序列 = Floor `BUSY`(任何手势=`busy`,序列不变);答题 = `YOUR_TURN`(`input_open` + 风铃 + 等待声)。回执 ≤100ms,禁止只震不响。 --- -## 1. 现版诊断 +## 0. 设计单(26 号附录 C) -读 `web/simon/game.js`: +### 0.1 一句话核心动作 -1. **BGM 是干扰项**(`bgm/theme` 音量 0.4 常驻,game.js:77)。记忆类游戏的工作记忆负载全在听觉通道,BGM 与六个手势音色同通道竞争,是纯负面。反馈⑥正确,整层去掉。 -2. **数字拼接是音色漂移重灾区**:报轮次 `di + num_5 + lun`、报成绩 `score_pre + num_* + lun`(game.js:47-57, 142, 219-222)。这正是反馈⑥"音色漂移发音不准"的来源。 -3. **教学是 8 句连续旁白**(`_runTutorial`, game.js:92-117),玩家只能听不能动,听完直接开考。 -4. **错一音即死**(`_onInput` → `_gameOver`),无缓冲;轮次越高挫败越强。 -5. **反馈音与内容音同质**:`sfx/correct`/`sfx/wrong` 与手势音色同为合成短音,盲听时"我按对了还是错了"要靠音色分辨,混淆。 +**听一串音色,原样复现。** -## 2. 音频层重构 +### 0.2 玩家可感知的状态(3 个) -- BGM 层:不播放、不加载。开场静默 0.5s 后直接进教学语音。间隙的"空白感"由环境底噪填充?——**不填**。纯静更利于记忆,这是设计选择不是缺失。 -- 手势音色(`tone_*` 6 个):保留现有映射(上=上行琶音 / 下=下行低音 / 左=左声道弹拨 / 右=右声道弹拨 / 单击=底鼓 / 双击=双连响),这是游戏内容,不动。 -- 反馈提示音(`sfx/correct`、`sfx/wrong`、`sfx/newrecord`):**删除**,改震动(见 §4)。`sfx/gameover` 保留为内容性结束音(低沉闷响,标识一局终结,不与任何手势音色相似)。 +| 状态 | Floor | 听感 | 第一次教 | +|---|---|---|---| +| 听序列 | `BUSY` | 音色序列;手势=`busy`「嗯」 | 「先听,不要动手。」 | +| 轮到你 | `YOUR_TURN` | `input_open` → `turn` 风铃 → 等待声;答题立即回声 | 「听到这个声音,就是轮到你了。」 | +| 结算 | — | `gameover` + 结算链 + menu pad | — | -## 3. 旁白整句化清单 +不要用 BGM 区分听/答。对局全程无 BGM。 -### 3.1 删除的拼接件 +### 0.3 新手手势与音色阶梯 -`voice/di`、`voice/lun`、`voice/num_shi`、`voice/num_0..9`、`voice/score_pre`、`voice/best_pre` 全部退役(资产文件可留,代码不再引用)。 +全部六个手势在本作都是**音色**。帮助换到长按。音色按历史最佳轮数开放,一次只学一样: -### 3.2 需要重录的句子类型(整句,female/male 双目录) +| 音色 / 手势 | 解锁条件 | +|---|---| +| 单击 / 左滑 / 右滑 | 阶段 0(选项 ≤3) | +| 上滑 | 清过第 1 轮 | +| 下滑(同时=菜单手势,仅结算/菜单相位) | 清过第 3 轮;对局答题中下滑仍是音色(阶段 2 后结算才开菜单) | +| 双击 | 清过第 6 轮(阶段 3 专属高级音色);未入池按单击处理 | +| 长按 | 帮助:状态句(轮次/段位),不闭窗、不耗资源、不判错 | + +新音色第一次进序列时现场教一句(「向上滑,是这个声音。」)马上用。`best>0` 的老玩家跳过已会的教学。 + +### 0.4 状态 × 手势矩阵(新手期) + +| 状态 \ 手势 | 单击 | 已解锁滑动 | 长按 | 双击(未解锁) | 连点 / 乱滑 | +|---|---|---|---|---|---| +| 教学指令 `critical` | `busy` | `busy` | 帮助 | `busy` | `busy` | +| 教学答题 | 答题 / 纠错(回声即反馈) | 答题 / 纠错 | 状态句 | 按单击处理 | 计入迷茫 | +| 播报 / 序列 `BUSY` | `busy` | `busy` | `busy` | `busy` | `busy`(序列不变) | +| 答题 `YOUR_TURN` | 作答(回声=`silent-ok`) | 作答 | 状态句 | 未入池=`invalid`+首次解释 | 乱滑计入迷茫;有效作答照收 | +| 结算 | 再来一局 | `invalid`+首次解释 | 重听结算 | 按单击处理 | 只处理第一下 | +| 菜单 | 确认 | 左右滑切换 | 重听说明 | 按单击处理 | `busy` | + +### 0.5 等待点 + +| id | 期待 | `_full` | `_short` | 静默 | L2 | L3 | +|---|---|---|---|---|---|---| +| `tut0` | 单击 | (指令刚说过) | 「点一下。」 | 6s / 15s | 单击 | 提示音 | +| `tut_` | 当前教的音色 | 教学句 | 「再试一次。」 | 6s / 15s | 该手势 | 提示音 | +| `input` | 已解锁音色 | 「轮到你了。」 | (提示音) | 10s / 25s | 免费重听整串 | 放慢 + 重听 | +| `settle` | 单击 | 「单击,再来一局。」 | 「再试一次。」 | 20s / 45s | — | 提示音 | +| `menu` | 单击/左右 | 菜单说明 | 同左 | 20s / 45s | — | 提示音 | + +### 0.6 首 3 分钟逐秒脚本 + +| 时间 | 游戏说 / 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 「音序回响。点一下屏幕。」(`critical`) | 单击 | 6s:「点一下。」 | 滑动:`invalid`+「先点一下。」 | +| 3~10s | 单击音色回响(第一次成功)→「向左滑,是这个声音。」+ 音色 | 左滑 | 「再试一次。」 | 纠正(30s 内不重播同一句) | +| 10~30s | 「对。」→ 右滑、单击各练一次 | 左/右/单击 | 短句档 | 纠正 | +| 30~40s | 「记住顺序,原样复现。答错,我们就再听一遍。」→「第一轮,听好了。」→ 2 音序列(仅已教音色) | 听 | — | 播放期=`busy` | +| 40~50s | `input_open` + 风铃 + 等待声 | 复现 2 音 | 10s:「轮到你了。」 | 见失败表 | +| 50~90s | 每轮 +1 音;答对 ≤4 字肯定 | 复现 | 三档 + 接管 | 归因 + 免费重听 | +| 90~180s | 第 4 轮后可教学菜单(结算相位);序列渐长;上滑音色在清第 1 轮后入池 | 复现 | L2 免费重听 / L3 放慢 | 第 3 次失败放慢 | + +3s 内动手;第一次成功=第一击回响 ≤10s;每轮有进展回报。 + +### 0.7 失败表(取代机会经济) + +| 失败次数(本局同序列) | 处理 | 台词 | +|---|---|---| +| 1~2 | 2s 内归因(「错了。是向左滑。」)→ 免费重听整串,进度清零重输 | `wr_*` + 重听 | +| 3 | 「我放慢一些。」→ 间隔 ×1.6,序列可减 1 音 | `slow_down` | +| 再错 | 结算(成绩段位 + 破纪录 +「单击,再来一局」) | `gameover` + `score_band*` | + +归因必须点名真实按错的音色。没出手不得说「错了」。新手期不扣「机会」、不加「机会加一」。 + +### 0.8 台词类别 + +- `critical`:`t0_new`、各教学指令(每句 ≤3s)、结算承诺段。 +- `content`:`round_01..20`、`score_band1..6`、`gameover`、`new_record`、`t_ok`、`correct`。 +- `hint`:`your_turn`、`t_retry`、状态查询句、`sys_demo`、菜单说明。 +- `feedback`:`wr_*`、`slow_down`、教学纠正、`inv_busy`。 + +全文见 `SIMON_LINES` / [02-simon-voice-ids.md](02-simon-voice-ids.md)。停用「机会加一 / 重听机会用完 / 双击重听」旧句。 + +### 0.9 断点复述 + +对局不存档(单局 2~5 分钟)。`soundgame.simon.best` 跨局持久,决定音色池与是否跳过教学。summary:「最佳成绩:第 N 轮」。 + +### 0.10 测试 + +`web/simon/test/playtest.mjs`。关键路径:3s 内单击 → 只教已解锁音色 → 第 1 轮 2 音复现 → 结算。序列期任何手势=`busy`。 + +--- + +## 1. 现版诊断(问题仍在,解法已换) + +1. BGM 与音色抢通道 → **对局不播 BGM**(保留,与 10 号一致)。 +2. 数字拼接 → 轮次 1–20 整句 + 无数字兜底;成绩 6 段整句(保留)。 +3. 8 句连播教学、零交互 → 交互式,一次一音色,且**先只教 3 个**。 +4. 错一音即死 / 「机会账」+ 双击耗机会 → **免费重听 + 第三次放慢**,无机会货币。 +5. `sfx/correct`/`wrong` 与音色同质 → 对错用语音短句 + 回执;内容结束音 `sfx/gameover` 保留。 + +--- + +## 2. 音频层 + +- 对局不加载 BGM。间隙保持静默,利于记忆。 +- 六音色映射不变:上=上行琶音 / 下=下行低音 / 左=左声道弹拨 / 右=右声道弹拨 / 单击=底鼓 / 双击=双连响。**未解锁的音色不进序列、不进教学。** +- `sfx/gameover` 保留。`sfx/correct`、`sfx/wrong` 不作为对错主通道。 +- `sfx/input_open`:进入答题的边界标记(S2 保留)。无语音链的轮次不要再叠 `turn` earcon 与 `input_open`。 +- 播放序列时每个音可叠极轻 `beat` 震作 chunking;**回执声仍要响**(播放期误触=`busy` 声,不再静默忽略)。 + +--- + +## 3. 旁白整句(保留句数,改交互口径) | 类型 | 句数 | 说明 | |---|---|---| -| 轮次播报 | 21 | "第一轮,听好了。"…"第二十轮,听好了。"逐轮整句;第 21 句为无数字兜底:"新的一轮,听好了。"(round>20 时用,轮次仍可继续) | -| 成绩播报 | 6 段 | 按完成轮数分段整句:"一轮都没坚持下来,别灰心。"/"你坚持了三轮以内。"/"你坚持了五轮左右。"/"你坚持了将近十轮。"/"你坚持了十轮以上,很厉害。"/"你坚持了十五轮以上,大师级。"(代码按 score 映射分段) | -| 新纪录 | 1 | "新纪录!"(已是整句,保留文本重录) | -| 教学句 | 8 | 见 §5,每句 ≤20 字 | -| 流程句 | 6 | "轮到你了。"/"重听机会用完了,小心。"/"用一次机会,再听一遍。"/"单击屏幕,再来一局。"/"上滑重听规则,下滑打开菜单。"/规则重听句 | -| 反馈短句 | 3 | "答对了!"/"错了。"/"机会加一。"(均已有文本,整句重录) | +| 轮次 | 21 | 「第一轮,听好了。」…「第二十轮」;>20:「新的一轮,听好了。」 | +| 成绩 | 6 | 按完成轮数分段,原文档位保留 | +| 新纪录 | 1 | 「新纪录!」 | +| 教学 | 按已解锁音色,一次一句 | 禁止开场把六种声音一次讲完 | +| 流程 | 含「轮到你了。」「单击,再来一局。」 | 删除机会账相关句 | +| 反馈 | 「对。」「错了。是向左滑。」 | 肯定 ≤4 字;归因含正确手势 | -生成规范按 00 文档 §3:同一 SPEAKER 常量(female=Serena / male=Uncle_Fu)+ 同一 INSTRUCT 常量(建议"平静清晰的女声/男声,语速平稳"——不写进 instruct 的语速词,instruct 只控情绪:"平静、清晰、像游戏主持人")。 - -## 4. 交互节奏与震动设计 - -### 4.1 播放序列阶段(playback) - -- 每个手势音播放的**同时**给 `BEAT`(8ms 轻震):节拍锚点帮助玩家分段记忆(把 N 个音切成节拍组),这是 simon 类游戏经典的"chunking"辅助。 -- 播放间隔维持"逐个播放",节奏均匀(见 §6 速度曲线),最后一个音播完 400ms 后给一次 `CONFIRM` 震动,表示"轮到你了"(第 1 轮同时播语音,之后轮次只用震动,省时间)。 -- 播放阶段手势一律忽略(保留现行为),但给 `ERROR` 震动一次告知"现在不用操作"?——**不给**,播放期误触静默忽略,避免打断聆听(00 文档 §2.4:纯聆听阶段不震动,BEAT 除外因其本身是节拍信息)。 - -### 4.2 复现阶段(input) - -| 事件 | 震动 | 音频 | -|---|---|---| -| 玩家做出手势 | CONFIRM | 立即回声放对应手势音色(保留现行为) | -| 手势正确(序列推进) | (并入上面的 CONFIRM,不额外震) | — | -| 整轮复现成功 | SUCCESS | "答对了!"(每轮都播太密——改为每 4 轮播一次,其余轮次仅震动) | -| 答错且有机会 | ERROR + WARNING(间隔 300ms 先后) | "错了。用一次机会,再听一遍。" → 自动重播序列 | -| 答错且无机会 | ERROR + HIT | `sfx/gameover` + 成绩播报 | -| 双击(消耗机会主动重听) | PICKUP | "再听一遍。"+ 重播序列(inputIdx 重置为 0) | -| 机会 +1(每过 4 轮) | PICKUP | "机会加一。" | - -## 5. 交互式教学(tutorial) - -现版 8 句连播改为"教一个、练一个": - -| 步 | 语音(整句) | 机制 | -|---|---|---| -| T0 | "欢迎来到音序回响。我会演奏一串声音,请你记住顺序,原样复现。先认识六种声音。" | 播完进 T1 | -| T1-T6 | 每个手势一句:"向上滑,是这个声音。"→ 播 tone → "你试试。" | **等待玩家做出该手势**;做对:tone 回响 + CONFIRM 震动 + "对了。"进下一个;做错:ERROR 震动 + "不是这个,向上滑试试。"重等;同一手势错 3 次给更直白描述("手指贴着屏幕,向上推一下") | -| T7 | "六种声音都记住了。序列会越来越长,答错可以消耗机会重听。双击随时重听当前序列。" | 进 T8 | -| T8 | "第一轮,听好了。" | 进正式对局 | - -教学时长约 60-80s,全程玩家动手 6 次以上。教学期间下滑=跳过教学(播确认句后直达第一轮)。 - -## 6. 难度曲线与复活机制 - -### 6.1 速度曲线(放缓) - -现版 `_interval()`: 720ms 起、每轮 -36ms、下限 340ms(game.js:152)——第 11 轮即触底。新版: - -``` -interval(round) = max(420, 700 - (round - 1) * 25) // 第 12 轮才到 420ms 下限 -``` - -### 6.2 序列生成约束 - -- 长度 = round + 1(保留,从 2 开始)。 -- **前 5 轮**:禁止同一手势连续出现 3 次;第 1 轮两个音不同手势。 -- 全部轮次:禁止连续 4 个同手势(防恶意随机)。 -- 复活重听的序列**不变**(重听就是重听同一序列,不重新随机)。 - -### 6.3 机会(retry)规则 - -- 每局初始 `chances = 1`;每完成 4 轮 `chances = min(2, chances + 1)` 并播报。 -- 消耗途径二选一:a) 输入阶段**双击**主动重听当前序列(进度清零重输);b) 答错时自动消耗一次机会,重播序列后再试。 -- **双击语义冲突的裁定(R3 修订,round2-review 裁决①)**:输入阶段双击按上下文判定——当前期望输入恰为 doubletap 时双击=**答题**(序列必须永远可答,答题优先),否则双击=**主动重听**(耗机会)。机会为 0 时双击非期望手势:不判答错、不消耗,仅播 `chances_out` 提示。重听播报期间切到非输入阶段,避免手势被吞。 -- 无机会时答错 → gameover:成绩分段整句播报 + 新纪录判定 + "单击屏幕,再来一局"。 -- `best`(历史最高)从会话内存改为写进度存储(localStorage + 服务器同步,进度 data 带 `summary`,格式"最佳成绩:第 N 轮")。 - -## 7. 状态机与手势汇总 - -阶段:`tutorial → announce → playback → input → (replay) → roundend → … → gameover` - -| 阶段 | 上滑 | 下滑 | 双击 | 单击 | 四向滑/点按 | -|---|---|---|---|---|---| -| tutorial | — | 跳过教学 | — | — | 教学输入 | -| playback | — | — | — | — | 忽略 | -| input | — | — | 重听(耗机会) | 答题输入 | 答题输入 | -| gameover | 重听规则 | 菜单(重开/回大厅) | — | 重开 | — | - -非 input 阶段上滑=重听规则(豁免见 00 文档 §5.2:六手势全是输入,帮助让位到非输入阶段)。 - -## 8. 资产清单变动 - -- 删:bgm/theme 引用(文件可保留不播)、sfx/correct、sfx/wrong、拼接数字件。 -- 新录:§3.2 全部(约 45 句 × 2 音色 = 90 条 × 2 格式)。 -- 保留:6 个 tone、sfx/gameover。 -- 更新 `web/simon/README.md` 玩法描述与 `gen/gen_voice.py` 的 `LINES` 表(文本表驱动,female/male 两跑)。 +SPEAKER 常量沿用 female=Serena / male=Uncle_Fu。 --- -## 9. 修订记录(追加;旧文不动) +## 4. 播放 / 答题节奏 -### round5(2026-09-14,research/simon-polish/round1 取证 → round2 修复;S1-S7) +**播放 `BUSY`**:序列不可打断。任何手势=`busy`,序列不变。最后一个音完 400ms 后 `yieldTurn`:`input_open` + `turn` + 等待声。 -用户真机反馈"玩法保留,但不清楚自己当前的状态"。round1 取证(剧本转写 + live 两会话: -认真新手局 13 轮 + 急性子乱按局)落成 S1-S8(research/simon-polish/round1/findings.md), -本轮修复 S1-S7,**玩法零改动,只加状态透明层**: +**答题 `YOUR_TURN`**: -1. **S2 · input 开放标记音 `sfx/input_open`**:`_toInput` 播放(游戏层资产,三档 - guide / iOS 无震动全可闻)。confirm 震只标记"播放完毕",占用预告/your_turn 语音 - 链播完才真开放——取证中该窗口内作答被静默吞(live-A 行 108-115)。无语音链的 - 轮次不再叠向导 turn earcon,input_open 独立作边界标记(二选一不叠响)。 -2. **S1/S3/S4 · input 阶段长按=状态查询**(`_statusQuery`,13 句 `status_*` 分档 - 整句,见 02b 追加节):长度 4 档 × 进度 4 档 × 重听账目 5 档(下一次重听消耗哪个 - 桶,与 §6.3 两账口径一致)。长按是六手势全被玩法占用下唯一的空闲键位(键盘= - 按住空格,00 §5.5 通道自动获得);玩家主动查询,三档 guide 全可用,不耗账、 - 不判错、不清进度、不闭作答窗口。查询只绑定 input 阶段——playback 维持纯聆听 - 豁免(§4.1),查询语音会污染记忆任务。 -3. **S5 · announce/roundend 误触给 error 震**:非单击手势在播报/表扬链期间不再 - 静默吞(00 §5.4 三秒反馈律);playback 维持 §4.1 静默豁免不动。 -4. **S6 · 结算语音链阶段看守**:链只在结算屏交付——gameover 瞬间下滑进菜单则挂起, - 回结算屏补播(`_deliverSettle`/`_backToSettle`)。承诺「单击屏幕,再来一局」 - 永远与所处相位一致(取证:链在菜单内播完 → 承诺在菜单内是假的 → 关菜单后结算 - 屏死寂 8.4s,live-B 行 140-174)。重开/回大厅作废挂起链。 -5. **S7 · `your_turn` 每局一次**:第 1 轮复活/重听的重播不再重念(取证一局三遍, - live-B 行 67/96-98)。 +| 事件 | 声音 | +|---|---| +| 做出已解锁手势 | 立即回声该音色(`silent-ok`) | +| 整轮正确 | 每轮「对。」或仅提示音(连续 3 次对后撤离语音) | +| 答错 1~2 | `invalid` 语义用内容错音可选 + 归因 + 免费重听 | +| 长按 | 状态句,不闭窗 | +| 未解锁手势 | `invalid` + 该格首次解释 | -验证:playtest 156 断言全绿(新增 S2-S7 断言 + 场景三专项);guide.test 122 断言 -全绿;check_all_assets 15 游戏全绿;其余 14 款游戏 playtest 回归全绿。修后 live -两会话复跑(research/simon-polish/round2/):核对局 V 14/14、压力局 P 8/8 断言通过, -ASR 非 OK 行全部归因玩家自裁(跳旁白截断/短句同音近失/补播抢占),无游戏自残; -四人设复审闭环(round2/p*.md,状态感知 2/5→4/5 across 四席,P0 清零)。 +结算链只在结算屏交付;进菜单则挂起,回结算屏补播(S6 保留)。承诺「单击,再来一局」与相位一致。`your_turn` 每局教一次,复活/重听不再重念(S7 保留)。 + +--- + +## 5. 交互式教学(只教已解锁) + +| 步 | 内容 | +|---|---| +| T0 | 「音序回响。点一下屏幕。」立刻动手 | +| T1 | 单击音色 → 玩家点一次 | +| T2 | 左滑音色 → 玩家左滑 | +| T3 | 右滑音色 → 玩家右滑 | +| T4 | 「记住顺序,原样复现。」→ 第一轮 2 音 | + +上/下/双击**不在首局教学出现**。做对:「对。」马上下一步。做错:纠正,不惩罚。同一手势错 3 次:迷茫 L3 只给提示音或演示该手势。 + +禁止:下滑跳过教学绑在「向下=音色」上。回访者 `best>0`:开场「点一下开始」,跳过已会的音色课。 + +--- + +## 6. 难度曲线(保留数字,去掉机会) + +``` +interval(round) = max(420, 700 - (round - 1) * 25) // 第 12 轮到 420ms 下限 +``` + +- 长度 = round + 1,从 2 开始。 +- 前 5 轮:禁止同一手势连 3 次;第 1 轮两音不同手势。 +- 全轮次:禁止连 4 个同手势。 +- 重听序列不变。 +- 序列只从**当前已解锁音色池**抽样。 + +`best` 写进度存储,带 `summary`。 + +--- + +## 7. 状态机 + +`tutorial → announce → playback → input → (replay) → roundend → … → gameover` + +菜单:阶段 2 以后,结算下滑呼出;左右滑切换、单击确认。废止双击确认、双击耗机会重听。 + +--- + +## 8. 资产变动 + +- 不播 `bgm/theme`。 +- 新录/停用:机会账句、六音色一次讲完的旧 T7、双击重听教学。 +- 保留:6 个 `tone_*`、`sfx/gameover`、`sfx/input_open`、轮次/成绩整句、状态查询分档句(删「重听账目/机会桶」枝,只保留轮次与进度档)。 + +--- + +## 9. 废止清单 + +| 废止 | 改为 | +|---|---| +| 每局机会货币;双击耗机会重听;答错扣机会 | 1~2 次免费重听;第 3 次放慢;无机会账 | +| 开场教完全部六音色 | 先 3 个,其余按轮次解锁 | +| 新手期双击=重听或答题 | 双击不存在;入池前当单击 | +| 播放期误触静默 | `busy` 回执,序列不变 | +| 对错只靠震动 / 删除提示音后无声 | 回执 + 短语音;震动可叠加 | +| 菜单单击循环+双击确认 | 左右滑+单击 | +| `your_turn` 每轮重念 | 每局一次 | +| 上滑与双击双义重听 | 帮助=长按;主动重听=迷茫 L2 或阶段 3 双击(仅重听当前序列完整播放,不绑破坏) | + +S1–S7 状态透明层(`input_open`、长按查询、结算链看守)在不与上表冲突的范围内保留。 diff --git a/docs/design/02-simon-voice-ids.md b/docs/design/02-simon-voice-ids.md index a9163e3c..0ff14a38 100644 --- a/docs/design/02-simon-voice-ids.md +++ b/docs/design/02-simon-voice-ids.md @@ -1,5 +1,13 @@ # 02b · 《音序回响》语音台词表(voice ids) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > 主台词表(78 句,双性别)的唯一事实源是 `tools/gen_voice_dual.py` 的 > `SIMON_LINES`(R2 重设计全文见 [02-simon-redesign.md](02-simon-redesign.md))。 > 本文档登记 P1 键盘变体(输入平权路线 a)——变体只覆盖含操作词的句子, diff --git a/docs/design/03-rhythm-rebuild.md b/docs/design/03-rhythm-rebuild.md index 72439985..bdf5f8c1 100644 --- a/docs/design/03-rhythm-rebuild.md +++ b/docs/design/03-rhythm-rebuild.md @@ -1,68 +1,145 @@ -# 03 · 《节拍行者》重构施工稿(rhythm rebuild,R3 施工依据) +# 03 · 《节拍行者》跟随领奏模式(26 号对齐) > **给开发者的实现要点摘要** -> 1. 玩法 = **跟随领奏(call & response)**:系统奏 1 小节节奏型(2-4 个手势乐器音),玩家在下一小节的相同拍点用相同手势跟打。3 首曲目(90/105/120 BPM)解锁制。完整谱面见 §3(每首前 8 句全部写出,后续句按 §3.5 规则生成)。 -> 2. 判定沿用现版常量:PERFECT ±0.12s / GOOD ±0.25s / TAP_DELAY 0.30s 补偿 / LOOKAHEAD 0.6s / 调度泵 40ms;tap 只允许出现在第 1、3 拍(强拍)。 -> 3. 判定反馈全改震动(`engine/haptics.js`):出手震动引擎 gesture.js 已内置(swipe/confirm),游戏层只加 Miss=`error`、16 连击=`success`、乐句全 Perfect=自定义 [20,40,20];领奏音符锚点自定义 BEAT=[8]。`judge_*` 提示音删除。 -> 4. 语音台词全表在 **[03-rhythm-voice-ids.md](03-rhythm-voice-ids.md)**(音频总监照录):旁白 female=Vivian / male=Uncle_Fu(R1 音色分配表),准确率分档整句播报。 -> 5. 进度 data:`{ summary, unlocked, grades, bestCombo }`(§7),大厅卡片显示 summary。 +> 1. **现行施工依据是 [24-rhythm-drummer.md](24-rhythm-drummer.md)**(屏幕分区鼓手)。本文是「手势=乐器」跟随领奏变体的 26 号对齐规格:判定常量、评级、解锁、BPM 网格、call & response **保留**;交互按手册重写。 +> 2. 演奏期 = Floor **`ACTION`**:不起等待声;手势即演奏。Miss = **不播该乐器音** + 回执/真话归因(没接 / 早 / 晚 / 方向)。禁止只震。 +> 3. **新手谱面不得出现双击音符。** 四向滑是乐器 → 帮助=长按。`doubleTapWindow: 0`;`immediateTap` 仅演奏期。 +> 4. 第一句就动手;第一次成功(跟上一击)≤10s。暂停恢复必须 count-in。 +> 5. 语音全表仍见 [03-rhythm-voice-ids.md](03-rhythm-voice-ids.md)(不在本次改写范围);须停用「双击是军鼓」教学句,直至双击解锁后再启用。 --- -## 1. 现版问题与重构方向(结论,不再展开) +## 0. 设计单(26 号附录 C) -现版"听令行动"(指令音→下一拍做手势)认知负荷四层(认音色→忆映射→换算拍点→精确出手),且 judge 提示音制造"考试感"。重构为跟随领奏:记忆负荷压到 ≤4 音短句,负荷转移到节奏精度;玩家动作触发与 BGM 同调性的**乐器音**,做对时听到自己在演奏。现版的 AudioContext 预排程泵(`_pump`/`_scheduleSfx`/tap 补偿)工程质量好,**保留复用**,只重写谱面与判定逻辑。 +### 0.1 一句话核心动作 -## 2. 常量与判定(施工契约,直接抄) +**我奏一句,你在下一小节同一拍点用同一手势跟打。** + +### 0.2 玩家可感知的状态(3 个) + +| 状态 | Floor | 听感 | 第一次教 | +|---|---|---|---| +| 演奏(含教学跟打) | `ACTION` | 曲目 BGM + 领奏乐器 | 「跟着我敲。」 | +| 选曲 / 结算旁白 | `SPEAKING` / `YOUR_TURN` | menu pad + 交接提示音 | 「点一下选这首。」 | +| 暂停菜单 | `BUSY` | 音乐停,不叠 menu pad | — | + +### 0.3 新手手势与解锁 + +| 手势 | 含义 | 解锁 | +|---|---|---| +| 单击、左滑、右滑 | 乐器(底鼓 / 闭镲 / 开镲) | 阶段 0 | +| 上滑、下滑 | 高 tom / 低 tom | 教学 T3 或曲目一中段,一次只加一个 | +| 长按 | 帮助(选曲/结算/教学讲解期) | 阶段 1 | +| 双击 | 军鼓 | 完成曲目一(第一局)且阶段 3 条件满足后才入谱;未解锁谱面不含双击 | +| 下滑(结算后) | 菜单 | 阶段 2 | + +演奏期长按不加速旁白、不帮腔(避免误触成「空敲」);给 `invalid` 或忽略为 ghost(见判定)。选曲期长按=「选曲。左右滑切歌。单击开始。」 + +### 0.4 状态 × 手势矩阵(新手期) + +| 状态 \ 手势 | 单击 | 左/右滑 | 上/下滑 | 长按 | 双击(未解锁) | 连点 | +|---|---|---|---|---|---|---| +| intro/结算 SPEAKING | 跳句 + `ok` | 打断并执行选曲语义 | 同左 | 帮助 | 当单击 | 400ms `busy` | +| 选曲 YOUR_TURN | 进入当前曲 | 切曲(报名) | `invalid`+解释 或 切曲若已映射 | 帮助 | 当单击 | `busy` | +| 教学/演奏 ACTION | 敲底鼓 | 敲对应镲 | 敲 tom(已教) | `invalid`(死输入) | 当单击(不当军鼓) | 各算一击 | +| 暂停菜单 | 确认 | 左右切换 | — | 重听说明 | 当单击 | `busy` | +| 结算 | 同曲再来 | 右滑=下一首(已解锁) | 阶段 2 下滑=菜单 | 重听评级 | 当单击 | 只处理第一下 | + +### 0.5 等待点 + +| id | 期待 | `_full` | `_short` | 静默 | L3 | +|---|---|---|---|---|---| +| `tut_tap` | 应答小节单击 | 「第 1 拍,点一下。」 | 「点一下。」 | 该窗 Miss | 领奏再响一次 | +| `tut_lr` | 左然后右 | 「左,再右。」 | 「左右滑。」 | Miss | 演示 | +| `select` | 单击 | 「单击,开始。」 | 「单击。」 | 18s | — | +| `settle` | 单击 | 「单击,再来一遍。」 | 「单击。」 | 20s | — | + +演奏 ACTION 不挂等待声。教学连败:第 3 次「我放慢一些。」降速,不卡死。 + +### 0.6 首 3 分钟逐秒脚本 + +| 时间 | 游戏说 / 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 数拍底鼓 →「点一下,跟上。」领奏 `1:tap` | 下一小节第 1 拍单击 | 「这拍没接。」重来 | 早/晚真话;滑=「先点一下。」 | +| 3~10s | 跟上一击=第一次成功(乐器声)→「对。」 | — | — | — | +| 10~30s | 领奏 `1:left, 3:right` | 左右跟打 | 短句 | 方向归因 | +| 30~60s | 曲目一乐句 1–2(仅 tap/left/right) | 跟打 | 三档;连败降速 | Miss 无乐器音 | +| 60~120s | 引入上或下(一句+马上用) | 跟打 | 接管 | 归因 | +| 120~180s | 打完或过半 `mid_song`;结算 | 单击再来 | — | — | + +### 0.7 失败表 + +| 类型 | 判定 | 归因 | 回到 | 新手代价 | +|---|---|---|---|---| +| 没出手 | 窗内无对应手势 | 「这拍没接。」 | 教学:该句领奏重来;正片:歌继续 | 不中断 | +| 早 / 晚 | 手势对但 \|dt\| 超窗 | 「早了。」/「晚了。」 | 教学重来;正片 Good 或 Miss | 不扣生命 | +| 方向错 | 手势种类错 | 「是向左滑。」 | 同上 | 无 | +| 双击未解锁 | 误当作军鼓 | 当单击判定;不报「你双击了」 | — | 无 | +| 教学连败 3 | 同一步 3 Miss | 「我放慢一些。」 | 该步降速 | 无 | + +正片不掉血、不中断,歌照唱完。Miss **不播玩家乐器音**(错过=没出声)。 + +### 0.8 台词类别 + +- `critical`:开场「点一下,跟上。」(≤3s)。 +- `content`:曲名、解锁、评级、`mid_song`。 +- `hint`:教学短句、`gd_count` 恢复数拍。 +- `feedback`:`gd_miss_none` / early / late / 方向。 + +### 0.9 断点复述 + +暂停记 BGM offset。恢复:**4 拍 count-in**(`gd_count` 每局限 1 次)后再入应答窗,第一拍不得注定 Miss。退出存 `tutorialDone / unlocked / grades / bestCombo / summary`。 + +### 0.10 测试 + +见 13 号 §5 与 24 号断言(鼓手模式)。若实现本文手势变体:新手谱面零 `doubletap` 音符;演奏期零等待声;Miss 无乐器音;恢复有 count-in。 + +--- + +## 1. 玩法与判定契约(数字保留) ```js -const PERFECT = 0.12; // Perfect 窗口 ±120ms -const GOOD = 0.25; // Good 窗口 ±250ms(蓝牙延迟由它吸收) -const TAP_DELAY = 0.30; // 引擎 tap 回调延迟补偿(沿用现版) -const LOOKAHEAD = 0.6; // 调度前瞻(秒) -const PUMP_MS = 40; // 调度泵周期 +const PERFECT = 0.12; +const GOOD = 0.25; +const TAP_DELAY = 0.30; // 仅非 immediateTap 通道;演奏期优先 immediateTap,单击零补偿 +const LOOKAHEAD = 0.6; +const PUMP_MS = 40; ``` -- **tap 补偿**:判定时 `if (g === 'tap') t -= TAP_DELAY`;tap 音符的 Miss 超时检查放宽 `TAP_DELAY + 0.08`,其他手势放宽 `0.05`(现版 game.js:159-164 原样保留)。 -- **tap 位置约束**:谱面中 tap 只允许落在第 1、3 拍(强拍),给补偿留足余量;doubletap 不落在 x.5 反拍(曲目三之前)。 -- 判定结果: - - 手势对 + |dt|≤0.12 → Perfect:玩家手势乐器音 + accent 亮音层叠加。 - - 手势对 + |dt|≤0.25 → Good:只播乐器音。 - - |dt|>0.25 或手势错 → Miss:**不播乐器音**(错过=没出声,本身就是反馈)+ `vibrate('error')`。 - - 手势对但窗口内找错音符归属:按现版"最近未判定音符"归属逻辑(game.js:278-293)保留。 -- 计分:Perfect=2 / Good=1 / Miss=0;`rate = 得分 / (2 × 总音符数)`;评级 S≥0.95 / A≥0.85 / B≥0.70 / C<0.70。不掉血、不中断,歌照唱完。 -- 连击:每 16 连击 `vibrate('success')`(不打断演奏,不播报);乐句全部 Perfect 时乐句末 `vibrate([20,40,20])`。 +- 手势对 + \|dt\|≤0.12 → Perfect:乐器 + accent。 +- 手势对 + \|dt\|≤0.25 → Good:只播乐器。 +- 否则 Miss:不播乐器 + `invalid` 或闷音反馈 + 教学期真话归因。 +- 计分 Perfect=2 / Good=1 / Miss=0;S≥0.95 / A≥0.85 / B≥0.70 / C 其余。 +- tap 只落第 1、3 拍。**双击音符:曲目一禁用;曲目二仅当双击已解锁;未解锁则改谱或降级为 tap。** -## 3. 谱面 +工程质量:AudioContext 预排程泵、BGM `src.start(t0)` 对网格 **保留**。 -### 3.1 记号与结构 +--- -- 拍点记号:`拍号:手势`,1 小节 = 4 拍(1, 2, 3, 4),`x.5` = 八分音符反拍。 -- 手势:`tap`(T) `doubletap`(D) `left`(L) `right`(R) `up`(U) `down`(N)。 -- 每个乐句 = **领奏 1 小节 + 应答 1 小节**(应答拍点 = 领奏拍点 + 4 拍)。 -- 曲目流程:`数拍 1 小节(BGM 内鼓点,无独立节拍器)→ 逐乐句 → 结算`。 -- 乐句间休止:曲目一前 4 句应答后各休 1 小节;曲目二前 2 句各休 1 小节;曲目三不休。 +## 2. 谱面(新手无双击) -### 3.2 曲目一《启程》90 BPM(SPB=0.667s,10 句,≈70s) +记号:`拍号:手势`。乐句 = 领奏 1 小节 + 应答 1 小节。流程:数拍 1 小节 → 逐乐句 → 结算。 -定位:教学曲。全强拍,2→4 音,无反拍。 +### 2.1 曲目一《启程》90 BPM(教学曲,无双击、无反拍) | 句 | 领奏节奏型 | 音符数 | |---|---|---| | 1 | `1:tap, 3:tap` | 2 | | 2 | `1:left, 3:right` | 2 | -| 3 | `1:tap, 2:doubletap, 3:tap` | 3 | -| 4 | `1:left, 2:doubletap, 3:right` | 3 | -| 5 | `1:tap, 3:left, 4:doubletap` | 3 | -| 6 | `1:right, 3:tap, 4:doubletap` | 3 | -| 7 | `1:up, 3:down, 4:doubletap` | 3 | -| 8 | `1:tap, 2:up, 3:right, 4:doubletap` | 4 | -| 9 | `1:left, 2:right, 3:tap, 4:doubletap` | 4 | -| 10 | `1:tap, 2:doubletap, 3:tap, 4:doubletap` | 4(终句,全鼓组收束) | +| 3 | `1:tap, 2:left, 3:tap` | 3 | +| 4 | `1:left, 2:up, 3:right` | 3 | +| 5 | `1:tap, 3:left, 4:up` | 3 | +| 6 | `1:right, 3:tap, 4:down` | 3 | +| 7 | `1:up, 3:down, 4:left` | 3 | +| 8 | `1:tap, 2:up, 3:right, 4:down` | 4 | +| 9 | `1:left, 2:right, 3:tap, 4:up` | 4 | +| 10 | `1:tap, 2:left, 3:tap, 4:right` | 4 | -### 3.3 曲目二《追风》105 BPM(SPB=0.571s,12 句,≈75s) +休止:前 4 句应答后各休 1 小节。 -定位:引入反拍(第 5 句起)与 4 音密集句。 +### 2.2 曲目二《追风》105 BPM(SPB=0.571s,12 句) + +未解锁双击时:表中 `doubletap` 改写为已解锁手势(优先整拍 `up`/`down`,或 1/3 拍 `tap`)。已解锁则按表演奏。反拍从第 5 句起,每句至多 1 个。 | 句 | 领奏节奏型 | 音符数 | |---|---|---| @@ -70,18 +147,20 @@ const PUMP_MS = 40; // 调度泵周期 | 2 | `1:left, 2:doubletap, 3:tap` | 3 | | 3 | `1:up, 3:down, 4:doubletap` | 3 | | 4 | `1:tap, 2:right, 3:left, 4:doubletap` | 4 | -| 5 | `1:tap, 2.5:right, 3:tap` | 3(首个反拍 2.5) | +| 5 | `1:tap, 2.5:right, 3:tap` | 3 | | 6 | `1:left, 2:up, 3.5:right, 4:doubletap` | 4 | | 7 | `1:tap, 2:doubletap, 3:down, 3.5:up` | 4 | | 8 | `1:right, 1.5:left, 3:tap, 4:doubletap` | 4 | | 9 | `1:tap, 2.5:left, 3:tap, 4:doubletap` | 4 | | 10 | `1:up, 2:right, 3:down, 3.5:left` | 4 | | 11 | `1:tap, 2:doubletap, 2.5:right, 3:tap` | 4 | -| 12 | `1:left, 1.5:right, 2:doubletap, 3:tap, 4:doubletap` | 5(终句) | +| 12 | `1:left, 1.5:right, 2:doubletap, 3:tap, 4:doubletap` | 5 | -### 3.4 曲目三《星爆》120 BPM(SPB=0.5s,12 句,≈70s) +前 2 句应答后各休 1 小节。 -定位:4-6 音密集句、切分、doubletap 上反拍。不设十六分音符(盲玩极限)。 +### 2.3 曲目三《星爆》120 BPM(SPB=0.5s,12 句) + +双击可落反拍,**仅双击已入池**。未入池不得进入曲目三,或进曲前先做军鼓解锁课。 | 句 | 领奏节奏型 | 音符数 | |---|---|---| @@ -92,76 +171,65 @@ const PUMP_MS = 40; // 调度泵周期 | 5 | `1:left, 1.5:right, 2.5:left, 3:tap` | 4 | | 6 | `1:tap, 2:up, 2.5:down, 3:tap, 4:doubletap` | 5 | | 7 | `1:right, 2:doubletap, 3:left, 3.5:right, 4:up` | 5 | -| 8 | `1:tap, 1.5:doubletap, 2.5:left, 3:tap, 3.5:right, 4:doubletap` | 6(高潮句) | +| 8 | `1:tap, 1.5:doubletap, 2.5:left, 3:tap, 3.5:right, 4:doubletap` | 6 | | 9 | `1:left, 2:doubletap, 2.5:up, 3:right, 4:doubletap` | 5 | | 10 | `1:tap, 2.5:down, 3:tap, 3.5:up, 4:doubletap` | 5 | | 11 | `1:right, 1.5:left, 2:right, 3:tap, 4:doubletap` | 5 | -| 12 | `1:tap, 2:doubletap, 2.5:left, 3:tap, 3.5:right, 4:doubletap` | 6(终句) | +| 12 | `1:tap, 2:doubletap, 2.5:left, 3:tap, 3.5:right, 4:doubletap` | 6 | -### 3.5 难度曲线规则(谱面设计约束,续作/改谱遵守) +全曲不休。 -1. 乐句长度:曲目一 2→4 音,曲目二 2→5 音,曲目三 4→6 音;同曲内单调不减(终句允许 +1 爆发)。 -2. 反拍(x.5)引入:曲目一禁用;曲目二第 5 句起、每句至多 1 个反拍音;曲目三不限。 -3. tap 只落 1/3 拍(全曲目铁律);doubletap 曲目一/二只落整拍,曲目三可落反拍。 -4. 同一手势连续出现 ≤2 次(跨音符);同一乐句内手势种类 ≥2(第 1、10 句的全鼓句除外)。 -5. up/down 作为"音高对"尽量成对出现在同一乐句(↑↓ 或 ↓↑),利于听觉记忆。 -6. 左右声道手势不放在相邻反拍连续位(如 `1:left, 1.5:right` 只允许曲目三)——声像跳变过快在耳机上易混。 +### 2.4 改谱约束 -## 4. 手势 → 乐器音映射(sfx 资产) +1. 曲目一禁止反拍、禁止双击。 +2. tap 只落 1/3 拍。 +3. 同一手势连击 ≤2;乐句内手势种类 ≥2(全鼓收束句除外)。 +4. up/down 尽量成对。 +5. 左右不放在相邻反拍(仅曲目三允许 `1:left, 1.5:right`)。 -| 手势 | 乐器音 | sfx id | 声像/音高 | +--- + +## 3. 手势 → 乐器(资产保留) + +| 手势 | 乐器 | sfx | 何时入池 | |---|---|---|---| -| tap | 底鼓 kick | `sfx/inst_kick` | 中央,低频 | -| doubletap | 军鼓 snare | `sfx/inst_snare` | 中央偏上 | -| left | 闭合镲 closed hat | `sfx/inst_hat_l` | 极左 | -| right | 开放镲 open hat | `sfx/inst_hat_r` | 极右 | -| up | 高通 tom | `sfx/inst_tom_hi` | 中央,升调感 | -| down | 低通 tom | `sfx/inst_tom_lo` | 中央,降调感 | -| (叠加层) | Perfect accent 亮音 | `sfx/accent` | 共用 1 个,叠在乐器音上 | +| tap | 底鼓 | `inst_kick` | 开场 | +| left | 闭镲 | `inst_hat_l` | 开场 | +| right | 开镲 | `inst_hat_r` | 开场 | +| up | 高 tom | `inst_tom_hi` | 曲目一第 4 句前教 | +| down | 低 tom | `inst_tom_lo` | 曲目一第 6 句前教 | +| doubletap | 军鼓 | `inst_snare` | 曲目一通关且手势阶段 3 | +| accent | Perfect 亮音 | `accent` | — | -全部与 BGM 同调性(曲目一/二/三 BGM 根音需在 Mureka prompt 里统一,建议 A minor)。乐器音时长 ≤0.4s,起音要硬(盲玩靠起音对拍)。生成方式:Stable Audio 3(打击乐短采样质量足够)或程序合成兜底;accent 可用程序合成(高频短琶音)。 +--- -## 5. 流程与教学(30 秒上手) - -### 5.1 状态机 +## 4. 流程与教学 ``` -idle → intro(语音) → tut(3 步教学) → select(选曲,二周目起) → countin → main(逐乐句) → end(结算) - ↑_________________| +idle → intro → tut → select(二周目)→ countin → main → end ``` -- `select`:左右滑切曲目(播曲目名 + 已解锁状态),单击进入,下滑回大厅。未解锁曲目播报"上一首拿到 B 级以上解锁"。 -- 教学只在首次进游戏出现(`progress.data.tutorialDone`);之后 intro 缩减为一句"跟着音乐,我奏一句,你跟一句。" +教学 3 步,一次一击,**T3 不再含双击**: -### 5.2 教学 3 步(台词见 voice-ids 文档 T 组) - -| 步 | 内容 | 机制 | +| 步 | 领奏 | 机制 | |---|---|---| -| T1 | 领奏 `1:tap`(第 1 拍单底鼓)→ 玩家在第 2 小节同位置跟打 | 做错:error 震动 + 纠正语音(早了/晚了/不是这个手势,按 dt 与手势选句)→ 重播领奏重试;2 次失败跳过进 T2 | -| T2 | 领奏 `1:left, 3:right` → 跟打 | 同上 | -| T3 | 领奏 `1:tap, 2:doubletap, 3:tap` → 跟打 | 通过后进曲目一 | +| T1 | `1:tap` | 做错:归因 + 重来;2 败降速;3 败演示通过 | +| T2 | `1:left, 3:right` | 同上 | +| T3 | `1:tap, 2:up, 3:tap` | 通过后进曲目一 | -纠正判定复用正式判定窗口,只是不计分。教学期间 BGM 已在播(从 intro 第 6 秒起),数拍感来自 BGM 本身。 +`tutorialDone` 后 intro 缩为一句「我奏一句,你跟一句。」点一下开始。 -## 6. 震动与语音在流程中的落点 +选曲:左右滑切曲,单击进入。未解锁:「上一首拿到 B 级以上解锁。」 -| 时机 | 震动 | 语音 | -|---|---|---| -| 领奏每个音符 | [8](BEAT,自定义) | — | -| 玩家出手 | (引擎内置 swipe/confirm,游戏层不重复) | 乐器音 | -| Miss | error | — | -| 16 连击 | success | — | -| 乐句全 Perfect | [20,40,20] | — | -| 曲目过半 | — | `mid_song`("过半了,保持。") | -| 结算 | success(S/A)/ 无(B/C) | `grade_*` + `restart_hint` | -| 解锁新曲 | success + [20,40,20] | `unlock_song2/3` | +结算:单击=同曲再来;右滑=下一首;阶段 2 下滑=菜单(左右滑+单击确认)。 -## 7. 进度存储 +--- + +## 5. 进度 ```json { "schema": 1, - "updatedAt": "2026-08-29T12:00:00Z", "summary": "《追风》B级 · 已解锁2/3首", "tutorialDone": true, "unlocked": 2, @@ -170,25 +238,31 @@ idle → intro(语音) → tut(3 步教学) → select(选曲,二周目起) } ``` -- 评级提升(如 b→a)、解锁变化、bestCombo 刷新时在结算后 PUT;`summary` 格式:`《曲名》X级 · 已解锁N/3首`(取最高评级的曲目)。 -- 解锁规则:曲目 n 拿到 B 级以上 → 解锁 n+1。 - -## 8. 实现要点(给开发 agent) - -1. 保留现版:`AudioContext.currentTime` 预排程、40ms 泵、LOOKAHEAD、`TAP_DELAY` 补偿与 Miss 放宽(现 game.js:26-30, 149-176, 267-293)、BGM 手动 `src.start(t0)` 对网格(game.js:221-239)。 -2. 重写:谱面数据从 40 指令流改为 `phrases: [{ notes: [{ beat, gesture }] }]`(本文 §3 表格直接转 JSON);调度时领奏音符排第 k 小节、应答 `_cues` 期望时刻 = 领奏时刻 + 4 拍;间奏休止按曲目参数插入空小节。 -3. 玩家乐器音不走预排程:出手即播(sfx 即发即弃),Perfect 时叠播 `sfx/accent`。 -4. BGM:3 首重生成(Mureka,90/105/120 BPM 精确、鼓组清晰、A minor、60-90s 无缝循环,沿用现版 groove 归一化工艺);现版 `bgm/groove` 弃用。数拍融入 BGM,删独立 tick 节拍器。 -5. 震动接 `engine/haptics.js`;BEAT 用自定义数组 `vibrate([8])`,领奏音符在调度时同步排一个 `setTimeout` 对齐(或用 pump 在音符时刻触发,误差 <40ms 可接受)。 -6. 语音全表见 03-rhythm-voice-ids.md;现版 `voice/welcome/rules/tut_start/tut_done` 弃用,`grade_*`/`restart` id 沿用但文案变更(重录)。 -7. 结算画面:单击=再来一遍(同曲),右滑=下一首(若已解锁),下滑=回大厅。 -8. 更新 `web/rhythm/README.md`(玩法、手势表、进度结构)。 +曲目 n 拿到 B → 解锁 n+1。 --- -> **取代指针(2026-09-14,24 号裁定)**:本文档(及 03-rhythm-voice-ids.md)的 -> 施工依据地位已被 [24-rhythm-drummer.md](24-rhythm-drummer.md) + -> [24-rhythm-voice-ids.md](24-rhythm-voice-ids.md) 取代——《节拍行者》重构为 -> 屏幕分区鼓手模式(6 区打击乐 + 无鼓伴奏,跟随领奏核心/评级/解锁/BPM 精确性 -> 延续)。本文仅作历史档案保留,正文一字不改;13 号文档 §4 rhythm 断言段 -> 同理冻结,新断言见 24 号文档 §11。 +## 6. 实现要点 + +1. 保留泵、LOOKAHEAD、网格对齐。演奏期 `immediateTap`;其它相位 `doubleTapWindow: 0`。 +2. 谱面 JSON 按本文 §2;领奏第 k 小节、应答 = 领奏 + 4 拍。 +3. 玩家乐器即发即弃;Perfect 叠 accent。 +4. 三首 BGM 无缝循环工艺保留。数拍融入 BGM 或鼓手数拍(鼓手模式见 24)。 +5. 暂停恢复:网格平移 + **count-in**。 + +--- + +## 7. 废止清单 + +| 废止 | 改为 | +|---|---| +| 曲目一句 3 起双击音符;T3 教双击 | 新手零双击;军鼓后解锁 | +| Miss 只震不响 / 仍播乐器 | Miss 静音该乐器 + 回执 + 真话归因 | +| 演奏期等待声、插播长教学 | ACTION,应答小节零语音 | +| 帮助用上滑(与 tom 冲突) | 长按(非演奏期) | +| 菜单双击确认 | 左右滑+单击 | +| 暂停恢复直接进窗导致必 Miss | count-in | +| 开场念完整规则 | 第一句就跟打 | +| 本文作为唯一施工依据 | **以 24 号鼓手模式为准**;本文仅手势变体/档案 | + +震动连击里程碑可保留为补充;主通道永远是声音。 diff --git a/docs/design/03-rhythm-voice-ids.md b/docs/design/03-rhythm-voice-ids.md index 01a07809..183bcb65 100644 --- a/docs/design/03-rhythm-voice-ids.md +++ b/docs/design/03-rhythm-voice-ids.md @@ -1,5 +1,13 @@ # 03a · 《节拍行者》语音台词表(voice ids,R3 录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - 旁白双性别:**female = Vivian,male = Uncle_Fu**(R1 音色分配表,docs/audio/round1-report.md §4)。每句两个性别各录一条,分别落 `web/rhythm/audio/voice/female/.{ogg,m4a}` 与 `.../male/`。 > - instruct 全表统一(同游戏同 instruct,00 文档 §3.2): diff --git a/docs/design/04-space-rebuild.md b/docs/design/04-space-rebuild.md index df647c34..e2bbd53d 100644 --- a/docs/design/04-space-rebuild.md +++ b/docs/design/04-space-rebuild.md @@ -1,157 +1,216 @@ -# 04 · 《盲航》重构方案(space rebuild) +# 04 · 《盲航》重构方案(26 号对齐) > **给开发者的实现要点摘要** -> 1. 47 秒连续旁白教学(intro1-4 链播)改为**交互式演练**:每次只教一件事,玩家立刻实操一颗慢速陨石,做错不惩罚、当场重教。四个方向各练一次,单击/双击在开局后第 20s / 45s 分批引入。 -> 2. **闪避方向改为同向**:听到左边的呼啸就向左滑——"把陨石拨开"。反向映射(现版左来右滑)是"没搞懂就死了"的最大认知负担,同向映射让方位声直接映射到同侧手势。 -> 3. **前 60 秒保护期**:陨石慢(逼近 4s)、稀(间隔 4s)、撞了不掉血(训练护盾吸收,语音解释)。60s 后语音宣告"保护解除",难度曲线从现版起点再降一档重新爬坡。 -> 4. **死亡即教学**:每次被撞,语音说清陨石从哪来、该怎么做;坠毁结算播报分因果。 -> 5. 数字拼接全部改整句:里程碑 5 句固定整句,存活时间分 4 段整句。撞击/护盾等反馈改震动。 +> 1. **交互权威**:26 号手册。四向滑动是核心玩法 → **帮助一律长按**;新手期无双击;`doubleTapWindow: 0`。 +> 2. **保留同向闪避**:听到哪边来,就向哪边滑(把陨石拨开)。判定窗、whoosh 四向资产、前 60s 保护期、死亡归因分方位,全部保留。 +> 3. **单击加速、护盾**改为解锁阶梯后的高级手势,**开场不教、保护期内不塞第二种机制**。 +> 4. 对局 = Floor `ACTION`。第一句就让玩家拨一颗;第一次成功 ≤10s。回执 ≤100ms;挥空/冷却拒绝必须出声,禁止只震。 +> 5. 暂停恢复后必须 count-in 式的再瞄准(至少一颗慢速预告),不能一恢复就注定撞上。 --- -## 1. 现版诊断:"没搞懂就死了" +## 0. 设计单(26 号附录 C) -读 `web/space/game.js`: +### 0.1 一句话核心动作 -1. **教学是 4 句连播旁白**(`voice/intro1..4`, game.js:84-87),约 47 秒。玩家零操作听完,然后第一颗陨石 1.2 秒后就来(`_scheduleSpawn(1200)`, game.js:111)。方位声→反向手势的映射只在旁白里说过一遍,没有任何一次实操验证。 -2. **反向闪避违反直觉**(`ESCAPE_SWIPE = {left:'right',...}`, game.js:26)。声音从左边来,手却要往右滑——玩家在前几秒的生存窗口里要完成"定位→取反→出手"三步推理,取反这一步是死亡率主因。 -3. **三个机制同时压上来**:闪避、单击加速、双击护盾,47 秒旁白里全部讲完,开局全部可用。玩家还没分清 whoosh 的方位,护盾冷却的播报已经来了。 -4. **难度起点过高**:首颗逼近时长 2.8s、刷怪间隔 3.2s(game.js:42-44),对没建立映射的新手等于即死。 -5. **撞击无教学价值**:`voice/hit1/hit2` 只报损,不说陨石从哪来、该怎么躲。玩家死了都不知道为什么。 +**听到陨石从哪边来,就向哪边滑,把它拨开。** -## 2. 核心决策:同向闪避 +### 0.2 玩家可感知的状态(3 个) -**听到哪边来,就向哪边滑**(`PUSH_SWIPE = {left:'left', right:'right', up:'up', down:'down'}`),叙事包装为"挥手把陨石拨开/推引擎避开"。 +| 状态 | Floor | 听感 | 第一次教 | +|---|---|---|---| +| 演练 / 对局 | `ACTION` | 引擎 `bgm/engine` + whoosh/alarm;玩法声即状态 | 「陨石有声音。向声音那边滑。」 | +| 游戏在说 | `SPEAKING` | 演练指令、归因;整链 duck | 开场第一句 | +| 坠毁结算 | — | 停 engine → 结算链 → `bgm/menu` | — | -- 方位声(左声道)→ 同侧手势(左滑):零推理,反射直达,这是盲玩能成立的前提。 -- 上=高频呼啸→上滑,下=低频轰隆→下滑,保留现有 sfx 方位设计(whoosh_* 四个资产全部复用,只改映射表和文案)。 -- 判定窗口沿用现版结构(撞击前 1.5s 内滑动有效 + 250ms 宽限),仅 `need` 从 `ESCAPE_SWIPE[dir]` 改为 `dir` 本身。 -- 单击加速、双击护盾机制保留(现版"持续增益规避 tap 延迟"的设计是对的,game.js:146-153 注释写得很清楚),只是引入时机后移(§3.3)。 +危险预警用 alarm(内容音)+ 可选 danger 震;**不能只靠震动**。新手期只教这 3 种听感。 -## 3. 新开场:交互式演练(DRILL 阶段) +### 0.3 新手手势与解锁 -### 3.1 演练流程 - -每步 = 一句短旁白 → 一颗**演练陨石**(逼近 4.5s,撞了不掉血)→ 玩家操作 → 反馈。做错/超时:ERROR 震动 + 一句纠正 + 同一颗重来,**最多重教 2 次后自动演示通过**(不卡流程)。 - -| 步 | 旁白(整句,≤20字/句) | 演练内容 | +| 手势 | 含义 | 解锁 | |---|---|---| -| D0 | "欢迎登上盲航。你只有一双耳朵。" + "陨石来了,会有声音告诉你方位。" | 无陨石,建立预期 | -| D1 | "听,左边有陨石。向左滑,把它拨开!" | 左来,需左滑 | -| D2 | "这次在右边。" | 右来,需右滑 | -| D3 | "上面有东西,声音又尖又高。向上滑!" | 上来,需上滑 | -| D4 | "下面是低频的轰隆。向下滑!" | 下来,需下滑 | -| D5 | "四个方向你都会了。引擎点火,出发!" | 进保护期 | +| 四向滑动 | 同向闪避 | 阶段 0(核心) | +| 单击 | 演练里=继续;对局未解锁加速时=`invalid`+首次「现在只要滑。」 | 加速=阶段 4 或「完成第一局保护期且闪避成功 ≥3」后教一次 | +| 长按 | 我在哪(「引擎舱。躲开呼啸。向声音那边滑。」) | 阶段 1(第一次成功闪避后马上用一次) | +| 双击 | 新手期不存在;护盾同加速,更晚解锁 | 阶段 4 大厅或累计 3 局后专属教学 | +| 下滑(对局) | 向下闪避,不是菜单 | — | +| 下滑(结算后) | 菜单 | 阶段 2 | -- 每次成功拨开:dodge 音 + SWIPE_OK 震动 + 短肯定("漂亮。"仅 D1/D3 播,避免啰嗦)。 -- 每步旁白在**演练陨石的 whoosh 响起之前**播完(语音先、音效后,不叠放),whoosh 响起时玩家已知该做什么。 -- 教学期间下滑=跳过演练(二周目直接问"上滑重听演练,单击出发")。 +### 0.4 状态 × 手势矩阵(新手期) -### 3.2 前 60 秒保护期 +| 状态 \ 手势 | 单击 | 四向滑 | 长按 | 双击(未解锁) | 连点 / 乱滑 | +|---|---|---|---|---|---| +| 演练指令 `critical` | `busy` | `busy`(等指令说完再出陨石) | 帮助 | `busy` | `busy` | +| 演练陨石 ACTION | `invalid`+「向声音那边滑。」 | 同向=拨开;错向=纠正+重来 | 帮助(不闭窗) | 当单击 | 乱滑计入迷茫 | +| 对局 ACTION | 未解锁加速=`invalid` 首次解释;已解锁=加速或冷却拒绝声 | 同向闪避 / 挥空 `whiff`+`invalid` | 帮助 | 当单击 | 计入迷茫 | +| 归因 / 结算 SPEAKING | 跳过这句(归因是 `feedback`,已操作则作废不播) | 打断并闪避(对局中) | 打断,帮助 | 当单击 | 400ms `busy` | +| 结算 | 再飞一次 | `invalid`+解释 | 重听结算 | 当单击 | 只处理第一下 | +| 菜单 | 确认 | 左右滑切换 | 重听说明 | 当单击 | `busy` | -演练结束进入 `playing` 后启动保护期(`protectionUntil = runStart + 60s`): +### 0.5 等待点 -| 参数 | 保护期 | 保护期后 | +| id | 期待 | `_full` | `_short` | 静默 | L2 | L3 | +|---|---|---|---|---|---|---| +| `d1_left` | 左滑 | 「听,左边有陨石。向左滑。」 | 「向左滑。」 | 演练窗结束=超时 | 左滑 | 游戏演示拨开 | +| `d2_right` | 右滑 | 「这次在右边。」 | 「向右滑。」 | 同 | 右滑 | 演示 | +| `d3_up` | 上滑 | 「上面,向上滑。」 | 「向上滑。」 | 同 | 上滑 | 演示 | +| `d4_down` | 下滑 | 「下面,向下滑。」 | 「向下滑。」 | 同 | 下滑 | 演示 | +| `settle` | 单击 | 「单击,再飞一次。」 | 「单击。」 | 20s / 45s | — | 提示音 | + +对局 ACTION 不起等待声。迷茫:10s 内 3 次挥空/错向 → L1 短句「向声音那边滑。」→ L2 只认当前最近一颗的正确方向 → L3 自动拨开最近一颗(保护期或不扣血演示)。 + +### 0.6 首 3 分钟逐秒脚本 + +| 时间 | 游戏说 / 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 「陨石在左边。向左滑。」(whoosh 在指令后) | 左滑 | 超时重来;2 次后演示通过 | 错向:纠正「在左边。」 | +| 3~10s | dodge 音 +「对。」→「这次在右边。」 | 右滑 | 同上 | 同上 | +| 10~30s | 上、下各一颗慢速演练 | 上滑、下滑 | 演示通过 | 纠正 | +| 30~40s | 「四个方向你会了。出发。」→ 保护期第一颗 | 同向滑 | 保护期撞了不掉血 + 归因 | 归因含方位 | +| 40~60s | 保护期:慢、稀、撞了不掉血 | 闪避 | 长按帮助 | 挥空 `whiff` | +| 约 50s | 「训练护盾还剩十秒。」(`hint`,空闲才播) | — | — | — | +| 约 60s | 「训练护盾关闭。」 | 继续闪避 | — | — | +| 60~180s | 难度爬坡;不插入加速/护盾课 | 闪避 | 迷茫接管 | 真话归因;没出手不报「慢了」 | + +第一次成功=第一颗拨开 ≤10s。每 30s 有可听进展(拨开声 / 里程碑句)。 + +### 0.7 失败表 + +| 类型 | 判定 | 归因 | 回到哪里 | 新手代价 | +|---|---|---|---|---| +| 错向 | 滑向 ≠ 陨石方向 | 「在左边。向左滑。」 | 演练:同一颗重来;对局:该颗结算 | 保护期不掉血 | +| 挥空 | 无悬置陨石的方向 | `whiff` + 首次「现在没有这边的陨石。」 | 继续 | 无 | +| 没出手 | 窗口内无滑 | 「没拨到。声音一响就滑。」**禁止说慢了** | 演练重来;对局掉血或保护吸收 | 前两次保护/锁血 | +| 撞击(保护后) | 窗口过且未拨开 | `hit_` 方位 + 正确动作 | 继续;HP=0 结算 | 第 3 次可降刷怪 | +| 第三次同向失败 | 演练同方向 3 次 | 「听我做一次。」演示 | 通过该方向 | 无 | + +坠毁结算:时间段整句 + 躲避段整句 +「单击,再飞一次。」破纪录加「新纪录!」。 + +### 0.8 台词类别 + +- `critical`:各方向演练指令(≤3s)。 +- `content`:出发句、保护结束、里程碑、结算段。 +- `hint`:`protect_soon`、长按帮助、迷茫短句。 +- `feedback`:`hit_left/right/up/down`、`hit_last`、挥空解释、冷却拒绝。 + +`hit_*` / `protect_end` 被呼喊顶断时必须登记残留并补播一次(R5-2 保留),但仍是 `feedback`:玩家已进入下一威胁则过期丢弃,不排在长链尾。 + +### 0.9 断点复述 + +对局可中途退出自动存:存活秒、闪避数、是否过保护期。回来:「你在航道上。躲开呼啸。向声音那边滑。」然后 count-in 一颗慢速陨石再恢复刷怪。 + +进度:`{ bestSec, totalRuns, summary }`。 + +### 0.10 测试 + +关键路径:四向各一次(做错重教,2 次后演示通过)→ 保护期被撞不掉血且归因 → 保护解除后闪避 → 结算。同向闪避。帮助=长按。加速/护盾不得开场同时教。 + +--- + +## 1. 核心决策:同向闪避(保留) + +`PUSH_SWIPE = {left:'left', right:'right', up:'up', down:'down'}`。 + +- 左 whoosh → 左滑,上=高频→上滑,下=低频→下滑。whoosh_* 四资产复用,只改映射与文案。 +- 判定:撞击前 1.5s 内滑动有效 + 250ms 宽限;`need` = `dir` 本身。 +- 反向映射表 `ESCAPE_SWIPE` **废止**。 + +--- + +## 2. 交互式演练(DRILL) + +每步 = 一句短指令 → 一颗演练陨石(逼近 4.5s,`harmless`)→ 操作 → 反馈。语音 `onComplete` 后再 spawn,不与 whoosh 叠。做错/超时:纠正 + 同一颗重来;**最多 2 次后自动演示通过**。 + +| 步 | 指令 | 内容 | |---|---|---| -| 逼近时长 | 4.0s(恒定) | 3.2s → 2.0s 爬坡(比现版 2.8→1.8 各抬 0.4s) | -| 刷怪间隔 | 4.0s(恒定) | 3.2s → 1.4s 爬坡 | -| 撞击 | 不掉血:训练护盾吸收,播 shield_block 音 + WARNING 震动 + 语音解释("护盾接住了。那颗在左边,听到左声道的呼啸就向左滑。"按实际方位选句) | 正常掉血 | -| 单击/双击 | 第 20s / 45s 才解锁(见 §3.3) | 可用 | +| D1 | 「听,左边有陨石。向左滑。」 | 左来左滑 | +| D2 | 「这次在右边。」 | 右 | +| D3 | 「上面,向上滑。」 | 上 | +| D4 | 「下面,向下滑。」 | 下 | +| D5 | 「四个方向你会了。出发。」 | 进保护期 | -第 60s 播:"训练护盾关闭,接下来靠你自己了。" + WARNING 震动。之后难度从低档开始 120s 爬满(沿用 RAMP_MS=120s)。 +删除 D0 两句零操作欢迎。成功:dodge +「对。」(仅 D1 语音肯定,其余只内容音)。 -### 3.3 单击/双击分批引入 +回访者:开场「点一下出发」,跳过演练。 -- **第 20 秒**(保护期内):暂停刷怪 6 秒,旁白:"试试单击,引擎会短促加速,直接冲过空隙。" → 玩家单击 → boost 音 + CONFIRM 震动 + "感觉到了吗?加速期间撞不上你。" → 恢复刷怪。 -- **第 45 秒**:同样暂停 6 秒:"双击展开护盾,能挡一次撞击,用完要 30 秒充能。" → 玩家双击 → shield 音 + CONFIRM 震动 → 恢复。 -- 若玩家 8 秒内没做,不等待直接继续(机制仍在,之后用上也正常生效)。 -- 护盾充能完毕语音(`voice/shield_ready`)保留。 +--- -## 4. 死亡即教学 + 结算 +## 3. 保护期与难度(数字保留) -### 4.1 撞击反馈(保护期后) +| 参数 | 保护期 60s | 之后 | +|---|---|---| +| 逼近 | 4.0s | 3.2s → 2.0s(120s 爬满) | +| 间隔 | 4.0s | 3.2s → 1.4s | +| 撞击 | 不掉血:shield_block + 归因(方位+正确滑向) | 正常掉血 | +| 加速 / 护盾 | **不可用**(未解锁) | 仅已解锁玩家 | -每次被撞:`sfx/hit` + HIT 震动 + 一句归因语音(按方位 4 句预生成:"左边撞上的。听到左声道呼啸,向左滑。"),替换现版 `voice/hit1/hit2` 的纯报损。第二次被撞额外加半句船体状态:"船体只剩最后一次了。" +第 50s:`protect_soon`「训练护盾还剩十秒。」第 60s:保护关闭。 -### 4.2 坠毁结算(整句化) +**废止**:保护期内第 20s/45s 暂停刷怪插入单击加速课、双击护盾课。 -删除 `numClips` 拼接体系(`voice/num*`/`n_time`/`n_sec`/`n_dodge`/`n_ge`, game.js:47-65): +同一窗口内「撞击与下一颗 spawn 重叠」导致归因被杀:保留 R5 的「呼喊优先 + 被顶断补播」;`protect_*` 上一句未完则新吸收只播 shield_block、跳过重复教学句。 -- **存活时间** 4 段整句:"不到三十秒,再试一次。""超过三十秒,有进步。""超过一分钟,合格的飞行员。""超过两分钟,王牌飞行员。" -- **躲避数** 4 段整句(合并进上句的第二句):"只拨开几颗。""拨开了十来颗。""拨开了几十颗。""拨开了一大群,漂亮。" -- 每局结算 = 时间段整句 + 躲避段整句 + "单击再飞一次,下滑回大厅。" -- **里程碑播报**:`dodges % 10` 的数字拼接改为 5 句固定整句(10/20/30/40/50 颗各一句:"十颗了,稳。"…"五十颗,传奇。"),超出 50 不再播报。 +--- -### 4.3 最高分 +## 4. 高级手势解锁(加速 / 护盾) -存活时间最高纪录存进度(`data = { bestSec: n, totalRuns: n }`),结算时若破纪录加一句"新纪录!"(SUCCESS 震动)。 +机制代码可保留(持续增益规避 tap 延迟等),**新手局不暴露**: -## 5. 震动映射(对齐 00 文档) +| 能力 | 手势 | 解锁 | 教学 | +|---|---|---|---| +| 短促加速 | 单击 | 第一局结束或大厅全部解锁 | 一句:「点一下,加速。现在试试。」马上用一次 | +| 护盾 | 双击(仅阶段 3/4 已识别双击后) | 累计 3 局或大厅 | 单独一课,不与加速同句 | -| 事件 | 震动 | +未解锁:单击在对局中=`invalid`+解释一次。冷却中已解锁加速:`boost_not_ready` 必须出声(R5-4),可叠心跳震,**禁止静默**。挥空:`sfx/whiff` + `invalid`。 + +护盾充能完毕 `shield_ready` 保留,仅已解锁玩家播放。 + +--- + +## 5. 死亡即教学 + 结算整句 + +每次被撞:`sfx/hit` + `hit_`(「左边撞上的。向左滑。」)。第二次加 `hit_last`。没出手用「没拨到」,不用「慢了」。 + +删除 `numClips` 拼接。存活 4 段 + 躲避 4 段整句保留。里程碑 10/20/30/40/50 固定句,超出 50 不播。 + +结算单击=再飞;阶段 2 下滑=菜单(左右滑+单击)。禁止坠毁后死寂。 + +--- + +## 6. 回执与刺耳修复(R5 音频保留) + +alarm / dodge / shield_block / boost / shield / whiff 程序合成峰值标准保留(≤−3.4 dBFS)。回执 ok/invalid/busy 走引擎合成,与内容音不混用。 + +激活加速/护盾:内容音 + `ok` 可省(已有 boost/shield 音)+ 可选 confirm 震。 + +--- + +## 7. 实现要点 + +1. 同向映射一行;判定常量不动。 +2. `phase='drill'`:参数化 approachMs / harmless / 指定 dir。 +3. 保护期撞击不掉血 + 归因。 +4. 暂停:停刷怪;恢复先播复述或 count-in 慢速一颗,再 `_scheduleSpawn`。 +5. 语音约 35 句×2 性别;删 num 拼接;删加速/护盾开场课句或改为解锁课。 +6. README 手势表:同向闪避;长按帮助;新手无双击。 + +--- + +## 8. 废止清单 + +| 废止 | 改为 | |---|---| -| 成功闪避 | SWIPE_OK(dodge 音保留) | -| 警报渐强(撞击前 1s / 0.5s 两段) | DANGER 渐强脉冲(随 alarm 两档,第二档更密) | -| 撞击 | HIT | -| 护盾挡下 | CONFIRM | -| 加速/护盾激活 | CONFIRM | -| 坠毁 | ERROR + HIT(间隔 300ms) | -| 演练做对 | SWIPE_OK;演练做错 ERROR | +| 反向闪避 | 同向拨开 | +| 47s 连播旁白后即死 | 交互式四向演练,第一句就滑 | +| 开场同时教闪避+加速+护盾 | 先只会滑;加速/护盾后解锁 | +| 保护期 20s/45s 插入单击/双击课 | 删除 | +| 新手双击护盾 | 双击不存在 | +| 对局无帮助手势 | 长按=我在哪 | +| 加速冷却静默;挥空静默 | 拒绝句 / `whiff` + 回执 | +| 撞击只报损不报方位 | `hit_` 真话 | +| 没出手报「慢了」 | 「没拨到」 | +| 反馈只靠震动 | 回执必响;iOS 无震仍有声 | +| 菜单双击确认 | 左右滑+单击 | +| 帮助用上滑(与向上闪避冲突) | 长按 | -现版 `sfx/alarm` 两档警报音**保留**(它是方位+距离的内容信息,不是反馈提示音),震动 DANGER 与其同步叠加。 - -## 6. 实现要点(给开发 agent) - -1. `ESCAPE_SWIPE` 改同向映射(一行改动),`DODGE_WINDOW_MS`/`DODGE_GRACE_MS` 判定结构不动。 -2. 新增 `phase='drill'`:复用 `_spawnObstacle` 但参数化(approachMs/spawnInterval/harmless 标志),演练陨石带 `harmless: true` 与指定 `dir`。 -3. `_resolve()` 增加保护期分支:`now < protectionUntil` 时撞击走"训练护盾"路径(不掉血、播归因语音)。 -4. 暂停刷怪:`_clearTimers()` 后重排 `_scheduleSpawn`(现版已有清晰的 timer 管理,game.js:250-261)。 -5. 归因语音按方位选句:`voice/hit_left/hit_right/hit_up/hit_down` + `voice/hit_last`。 -6. 语音全部整句重录:演练 8 句、保护期 3 句、单击/双击引入 4 句、归因 5 句、结算 10 句(时间段 4 + 躲避段 4 + 新纪录 + 重开)、里程碑 5 句 ≈ 35 句 × 2 音色。删除全部 num 拼接件。 -7. 教学语音与 whoosh 的时序:语音 onComplete 后再 spawn 演练陨石,保证不叠放。 -8. 更新 `web/space/README.md` 手势表(同向闪避是玩家可见的最大变化,README 必须同步)。 - ---- - -## 修订记录 R5(space polish:刺耳 + 状态/动作反馈盲区,research/space-polish/) - -取证(round1):live 两会话(research/space-polish/round1/live-{A,B}-transcript.md)+ -4 人设评审(同目录 p1-blind/p2-student/p3-office/p4-worker)+ 全 sfx 客观频谱 -(同目录 sfx-spectrum-analysis.txt)。核心事实: - -1. **刺耳(客观)**:alarm 每颗陨石响两遍且 ≥3kHz 能量 82.3%/≥5kHz 63.9%/峰值 16kHz; - dodge 真峰值 -2.30dBFS + 63.6%≥3kHz;shield_block -2.60+52.2%;boost -2.70+53.0%; - shield -2.90 超标。四件最高暴露音效全部违反 one-shot 标准(≤-3dBFS)。 -2. **保护期结束死亡窗(P0,live A t+151-163 与 t+300-310 两轮复现)**:裸飞期逼近 4.0s - = 刷怪 4.0s,每颗撞击与下一颗 spawn 同窗——呼喊维持生存优先(顶断归因)的代价是 - hit_left/hit_down **零毫秒被杀**,玩家 8 秒三连撞至死不知方位;protect_end 亦被顶得 - 只剩 0.5-3.5s。 -3. **保护期 protect_* 七连截断**:6.96s 教学句对 4.0s 刷怪间隔,每句播 4.0s 就被 - 下一句顶断(live A 7 连 TRUNCATED),信息重复又残缺。 -4. **动作反馈盲区**:加速冷却中单击全静默(护盾拒绝有回音、加速没有——归因真话不均); - 挥空/滑错方向与成功闪避同静默(dodge 音只在判定时才响);boost/shield 激活无 - CONFIRM 震(§5 承诺未兑现)。 - -R5 裁定与实现(逐条对应 playtest.mjs 新断言): - -- **R5-1 刺耳重合成**:alarm/dodge/shield_block/boost/shield 五件程序合成重制 - (`tools/gen_space_syn.py`,低谐波设计:alarm 双音 700/560Hz、dodge 中频气声扫频, - 真峰值 ≤-3.4dBFS、编码后 ≥3kHz 能量 0%,解码回读校验)。SA3 非必需,程序合成达标。 - 新增 `sfx/whiff`(挥空轻叹,0.55 音量)。 -- **R5-2 归因真话·防顶断 replay**:呼喊生存优先裁定**不变**(呼喊照常同窗直出、照常 - 顶断归因);但 hit_/protect_end 起播经 `_defendVoice` 登记,被顶断时登记残留, - 呼喊播完的 onComplete 走 `_replayDefended` queue 接 replay(不顶新威胁信息), - 保证每句归因/保护结束宣告完整送达至少一次;归因 replay 播完仍接 hit_last(先因后果)。 -- **R5-3 protect_* 去重**:上一句 protect_ 在播时新吸收只发 shield_block 音 + - WARNING 震、跳过语音,每句教学完整播完(不再 7 连截断同一句开头)。 -- **R5-4 加速冷却拒绝**:`_deny(now, voiceId)` 共用 4s 去抖(护盾拒绝同路径、原句 - feat_shield_done 不变);加速冷却单击 → HEARTBEAT 震 + 新句 `boost_not_ready` - 「引擎还在冷却,稍等一下。」。 -- **R5-5 protect_soon**:开局第 50s 播报「训练护盾还剩十秒。」(queue 不顶句; - 菜单/分批引入打断由 _rearmRunTimers 补播),给反应慢的玩家 10s 提前量。 -- **R5-6 激活确认**:boost/shield 激活(对局期与 feature 期)补 CONFIRM 震(兑现 §5)。 -- **R5-7 挥空反馈**:playing 期滑向无悬置陨石的方向 → `sfx/whiff` 轻叹(引擎 swipe - 震动已有"收到",whiff 补"无用"),与 dodge 成功音形成对偶。 -- **状态查询手势**:维持 00 §5 豁免表裁定(四向滑=闪避,space 无对局期 status key; - 坠毁结算后上滑=重听教学要点)。状态透明全部由事件反馈句承担(本 R5 已补齐 - 保护期倒计时/冷却拒绝/归因可达性),不新增手势。 +R5 刺耳重合成、归因补播、protect 去重、protect_soon、挥空音:**保留**,并补齐回执。 diff --git a/docs/design/04-space-voice-ids.md b/docs/design/04-space-voice-ids.md index f5963808..2a576cf0 100644 --- a/docs/design/04-space-voice-ids.md +++ b/docs/design/04-space-voice-ids.md @@ -1,5 +1,13 @@ # 04a · 《盲航》重构版语音台词表(voice ids,R4 录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - 设计依据:[04-space-rebuild.md](04-space-rebuild.md)。闪避为**同向**(左来左滑),台词已全部按此写死,不存在反向表述。 > - 旁白双性别:**female = Serena,male = Uncle_Fu**(R1 音色分配表)。落 `web/space/audio/voice/female|male/.{ogg,m4a}`。 diff --git a/docs/design/05-rpg-addendum.md b/docs/design/05-rpg-addendum.md index e69672aa..3d4e6feb 100644 --- a/docs/design/05-rpg-addendum.md +++ b/docs/design/05-rpg-addendum.md @@ -1,106 +1,131 @@ -# 05c · 《剑与声》增补规范(rpg addendum,R3 补漏) +# 05c · 《剑与声》增补规范(26 号对齐) -> 本文件补 [05-rpg-chapter1.md](05-rpg-chapter1.md) 的施工遗漏:①战斗事件 × 音效 × 震动配合总表;②Boss 无声前摇的震动协议;③音频层叠规则修正(amb 与 BGM 冲突);④若干流程细节。与主文档冲突处以本文件为准。 +> 补 [05-rpg-chapter1.md](05-rpg-chapter1.md):战斗事件×音效、无声前摇、amb 与 BGM、流程细节。与主文档冲突时:**数值与资产以 chapter1 为准;交互以 26 号 + 05 号 §0 为准。** --- -## 1. 战斗事件 × 音效 × 震动配合总表(施工对照表) +## 0. 设计单增量 -震动一律用 `engine/haptics.js` 预定义名;手势识别震动(swipe/confirm)由 gesture.js 内置,下表不再重复列出。 +格挡教学手势=前摇内单击。无声敌人窗口=系统 `danger` earcon(必出声)+ 可选震动补充。防御指令全平台兜底。回执 ok/invalid/busy ≤100ms,禁止只震。 -| 战斗事件 | sfx | 震动 | 语音 | -|---|---|---|---| -| 遭遇开始 | `tel_`(登场即第一次前摇不播,登场用 `enemy_die` 无——登场无专属 sfx,直接语音) | — | `btl_start_` | -| 指令切换(左右滑) | — | (内置 swipe) | 指令名 `cmd_*` | -| 攻击命中 | `atk_swing` + `atk_hit` | — | —(敌方半血/死亡见下) | -| 技能·破空斩 | `skill_slash` | — | mp 不足时 `btl_nomp` | -| 防御指令 | — | — | —(标记生效,无播报,下回合减伤自明) | -| 使用金疮药 | `potion` | `pickup` | 无药时 `btl_noitem` | -| **敌方前摇(有声敌人)** | `tel_`(1.0s) | 窗口开启同时 `beat` 一次(提示"现在可以格挡",不干扰听觉辨音) | — | -| **格挡成功** | `parry` | `confirm`(即时,双击无延迟) | — | -| 格挡窗口内做错手势 | — | `error` | — | -| 受击(未格挡) | `hurt` | `hit` | 血量档位降档时 `st_hp_mid` / `st_hp_low`(降档才播,不每击都播) | -| 敌方半血 | — | — | `npc_*_half`(有半血句的敌人)/ Boss 见 §4 | -| 击杀 | `enemy_die` | `success` | `btl_enemy_die` | -| 战斗胜利 | — | — | `btl_win` + `btl_loot_*`(分档) | -| 升级 | `levelup` | `success` | `btl_levelup` | -| 逃跑成功 | `flee` | — | `btl_flee` | -| 逃跑失败(Boss/剧情战) | — | `error` | `btl_noflee` | -| 战败 | `hurt`(最后一击) | `hit` + 300ms 后 `error` | `btl_lose` → `btl_lose_hint` | +--- -要点:**前摇窗口的 `beat` 轻震是"格挡窗口已开"的通道标识**——有声敌人靠前摇音辨招式、靠 beat 确认窗口时机,两者分工。iOS 无震动时靠前摇音本身即可(前摇音起始即窗口起始),无信息损失。 +## 1. 战斗事件 × 音效 × 回执(震动只能补充) -## 2. Boss 无声前摇 · 震动协议(影卫 / 白无瞳三阶段) +手势识别震(swipe/confirm)由 gesture.js 内置,下表不重复。 -无声敌人是第三章核心机制(耳朵最强的剑客遇到没有声音的敌人),触觉成为唯一提示通道。协议: +| 战斗事件 | sfx | 回执 | 震动(补充) | 语音 | +|---|---|---|---|---| +| 遭遇开始 | — | — | — | `btl_start_` | +| 指令切换 | — | `ok` 或报名即 silent-ok | swipe 内置 | `cmd_*` | +| 攻击命中 | `atk_swing` + `atk_hit` | silent-ok | — | 半血/死亡见下 | +| 破空斩 | `skill_slash` | silent-ok | — | mp 不足 `btl_nomp` | +| 防御指令 | — | `ok` | — | 无播报,下回合减伤自明 | +| 金疮药 | `potion` | silent-ok | pickup | 无药 `btl_noitem` | +| **有声前摇** | `tel_` | — | 可选一次 `beat` | — | +| **无声前摇开窗** | **`danger` earcon(系统合成,必响)** | — | 可选 danger 型 | — | +| **格挡成功**(窗口单击或同向滑) | `parry` | silent-ok | confirm 可选 | — | +| 窗口内无效手势 | — | `invalid` | error 可选 | 该格首次:「现在点一下挡住。」 | +| 受击未格挡 | `hurt` | — | hit 可选 | 降档才 `st_hp_mid/low` | +| 敌方半血 | — | — | — | `npc_*_half` / Boss §4 | +| 击杀 | `enemy_die` | — | success 可选 | `btl_enemy_die` | +| 胜利 | `victory` | — | — | `btl_win` + `btl_loot_*` | +| 升级 | `levelup` | — | success 可选 | `btl_levelup` | +| 逃跑成功 | `flee` | silent-ok | — | `btl_flee` | +| 逃跑失败 | — | `invalid` | error 可选 | `btl_noflee` 或 `btl_flee_fail` | +| 战败 | `hurt` | — | hit 可选 | `btl_lose` → `btl_lose_hint` | -| 时刻 | 震动 | 说明 | +**前摇窗口标识**:有声敌人靠 `tel_*`;无声敌人靠 **danger earcon**。iOS 无震动:**零信息损失**——earcon + 格挡单击 + 防御指令都在。废止「iOS 无声敌人不可格挡」。 + +--- + +## 2. Boss 无声前摇协议(影卫 / 白无瞳) + +| 时刻 | 声音(主) | 震动(补充) | |---|---|---| -| 窗口开启(t=0) | `danger` [25,40,25,40,25] | 代替前摇音——**无声前摇的"声音"就是这段震型** | -| 窗口中点(t=0.5s) | `heartbeat` [40,100,40] | 催促:窗口过半,不出手就来不及了 | -| 格挡成功 | `confirm` | 同有声敌人 | -| 窗口关闭未格挡 | `hit` | 受击 | +| 窗口开 t=0 | `danger` earcon | 可选 danger 脉冲 | +| 窗口中点 t=0.5s(听风剑则 0.65s) | 可选极轻 nudge,不得新语言 | 可选 heartbeat | +| 格挡成功 | `parry` | confirm 可选 | +| 未格挡关闭 | `hurt` | hit 可选 | 规则: -1. **只有无声敌人**用这套协议;有声敌人前摇窗口只震一次 `beat`(§1 表),绝不用 danger——danger 语义专属于"无声威胁",玩家一听(一感)就知道是影卫/白无瞳。 -2. iOS 无震动降级:**静默降级,不回退提示音**(00 §2.5)。无声敌人在 iOS 上不可格挡是已知代价,设计上用数值兜住——影卫 ATK16 对 lv7+ 铁鳞甲玩家 dmg≈10,52+ HP 可吃 5 击,不格挡也能硬打过普通影卫;白无瞳三阶段前由 NPC 台词明示(见 §5),iOS 玩家知情后可用"防御"指令(减伤 50%,不需要时机)作为等效手段。**防御指令因此对 iOS 是必要兜底,不是可选项。** -3. 听风剑(格挡窗口 1.0s→1.3s)对无声敌人同样生效:heartbeat 顺延到 t=0.65s。 -4. 无声敌人的 `_resolve` 判定窗口与有声一致(1.0s/1.3s 内双击有效),仅提示通道不同——代码上前摇 = `{ sfx: tel_ | null, haptic: 'beat' | 'danger+heartbeat' }` 二元组。 +1. 只有无声敌人用 danger earcon 当窗口;有声敌人只用 `tel_*`,不要叠 danger(danger 语义=「听不见招,但系统告诉你现在挡」)。 +2. **防御指令对所有平台必要**(减伤 50%,不看时机)。不是 iOS 专属补丁。 +3. 听风剑窗口 1.0→1.3s,中点顺延。 +4. 判定与有声相同:窗口内单击或(有方向时)同向滑。无声敌人无方向,只认单击。 +5. NPC 进场句可暗示「点一下」或「选防御」,**不要教双击、不要暗示只有震动能挡**。 -## 3. 音频层叠规则修正(amb 与 BGM 冲突) +--- -05-rpg-chapter1.md §7.2 让 `amb_*` 走 bgm 层 loop,与 `bgm/explore` 等同层冲突(引擎 BGM 层同时只有一首)。裁定为: +## 3. 音频层叠(保留裁定,对齐 10 号) -| 场景/状态 | bgm 层播放 | 说明 | +| 场景/状态 | bgm 层 | amb(sfx loop) | |---|---|---| -| village | `bgm/village`(音乐) | 安全区给音乐,情绪锚点 | -| 其他探索场景 | `amb_`(环境音循环,volume 0.6) | **探索期不放音乐**——纯环境音,战斗音乐进来才有对比 | -| 普通战斗 | `bgm/battle` | 进战斗 crossfade 替换 amb;战后 crossfade 回当前场景 amb | -| Boss 战 | `bgm/boss` | 同上 | -| 章结算/剧情(evt_ch1_end 等) | `bgm/ending` | 播完回场景 amb | +| village | `bgm/village` | `amb_village`(bgm=0 时垫底) | +| 其他探索 | `bgm/explore` 0.35 | `amb_` | +| 普通战斗 | `bgm/battle` | amb 停 | +| Boss | `bgm/boss` | amb 停 | +| 章结算 | `bgm/ending` | amb 停 | +| 玩家回合 | battle 不切 | —;起等待声或靠指令名 | -资产清单变动:`bgm/explore` **取消**(探索期用 amb,不需要探索音乐),BGM 从 5 首减为 4 首(village/battle/boss/ending);`amb_village` 保留(village 的音乐若停播设置 bgm=0 时用 amb_village 垫底——bgm=0 时 bgm 层整体静默,village 也播 amb_village)。sfx 总数 35 不变。 +`bgm=0`:不加载任何 `bgm/`;amb 仍循环。等待声、回执、earcon 仍在。amb 实现:`audio._load` + BufferSource loop 接 `_sfxBus`。 -`bgm=0`(设置关闭音乐)时的行为:village/battle/boss/ending 全部静默,探索场景 amb 照播(amb 属内容环境音不是 BGM——**例外裁定**:amb_* 虽走 bgm 层,但不受 bgm 设置关闭影响;实现上 amb 用 `play(id, {volume})` 时引擎按 bgm 层处理会被设置关停,因此 **amb 改走 sfx 层 loop**——sfx 层即发即弃不支持 loop,需要开发在 game.js 里用 `audio._load` + 手动 BufferSource loop 接 `_sfxBus`(rhythm 现版手动接总线已有先例)。这是本增补对实现的唯一新要求。) +整条语音链期间 BGM duck,句间不得抬回。 -## 4. Boss 阶段转换表现 +--- + +## 4. Boss 阶段转换(保留) | Boss | 阶段 | 触发 | 表现 | |---|---|---|---| -| 黑风寨主 | 二阶段(ATK 10→12) | HP ≤50% | `warning` 震动 + `npc_boss1_2` + 前摇音 `tel_boss1` 从单声双刀变急促(同一 sfx 以 playbackRate 1.2 重播即可,不加资产) | -| 腐沼祭主 | 二阶段 | HP ≤50% | `warning` + 召唤 2 毒蛛(连战队列插入),播专属半血句(R4 补录) | -| 白无瞳 | 三阶段(完全无声) | HP ≤50% / ≤25% | 每阶段 `warning`;三阶段进场播 §5 教学句,此后前摇走 §2 震动协议 | +| 黑风寨主 | 二阶段 ATK 10→12 | HP≤50% | warning 可选 + `npc_boss1_2` + `tel_boss1` playbackRate 1.2 | +| 腐沼祭主 | 二阶段 | HP≤50% | 召唤 2 毒蛛入连战队列 + 半血句 | +| 白无瞳 | 三阶段完全无声 | HP≤50% / ≤25% | 每阶段 warning 可选;三阶段进场教学句后走 §2 earcon 协议 | -## 5. 补充台词需求(R4 录不进 ch1 表的,列入 ch2/3 台词表) +--- -| id | 角色 | 台词全文 | 用途 | +## 5. 补充台词(ch2/3 最小集,不改 ch1 voice-ids 文件) + +| id | 角色 | 全文 | 用途 | |---|---|---|---| -| `npc_boss3_warn` | 旁白 | 他没有声音。闭上眼睛没用,竖起耳朵也没用。这一次,相信你的手。 | 白无瞳三阶段进场(同时向 iOS 玩家暗示:防御指令是兜底) | -| `btl_half_shaman` | 蛊师 | 蛊师半血句(R4 写 ch2 台词表时补全文) | 半血钩子 | -| `btl_half_shadow` | 影卫 | 影卫半血句 | 同上 | -| `menu_hint_escape` | — | (属 escape,见 round2-review §5.1,非本游戏) | — | +| `npc_boss3_warn` | 旁白 | 他没有声音。竖起耳朵也没用。听到叮一声,点一下挡住。来不及,就选防御。 | 白无瞳三阶段;明示单击+防御,不提双击/只靠手震 | +| `btl_half_shaman` | 蛊师 | (ch2 表补全文) | 半血 | +| `btl_half_shadow` | 影卫 | (ch3 表补全文) | 半血 | -规则:ch2/3 台词表沿用 05-rpg-ch1-voice-ids.md 格式,R4 由策划补发;上表是必须包含的最小集。 +ch2/3 表格式沿用 ch1 voice-ids。 -## 6. 流程细节补遗 +--- -1. **上滑状态播报链顺序**(探索态与战斗态共用):`st_lv_*` → `st_hp_*` → `st_gold_*` → `st_weapon_*` → `st_armor_*`,链播可被打断(再次上滑重播,下滑进菜单)。 -2. **战斗态上滑**:`st_hp_*` + 敌方血量档位(敌方档位句复用 st_hp_* 文案?——**不复用**,敌人档位用:`foe_hp_high`"敌人气焰正盛。"/`foe_hp_mid`"敌人露出疲态。"/`foe_hp_low`"敌人撑不住了。"——R4 补录 3 句 ×2 性别,列入上表最小集)。 -3. **连战转换**(enc_bandits 等):第一只死亡 → `btl_enemy_die` → 不停 BGM → 直接播下一只 `btl_start_` + 前摇,玩家 HP/MP 不回、指令重置为攻击。 -4. **`stats.playSec` 口径**:累计探索态+战斗态实际时长(菜单/结算暂停不计),存档时累加。 -5. **存档 schema 迁移**:R5 内允许丢弃重来(读档 schema≠1 时播 `sys_new_game` 重开,stats 保留)。 -6. **菜单豁免确认**:RPG 探索态菜单=左右滑切换+单击确认(00 §5.2 豁免条款,round2-review 裁决②);战斗态下滑=逃跑,不设菜单——与 05-rpg.md §2.2 一致,不变。 +## 6. 流程细节 -## 7. R4/R5 施工就绪核对单 +1. 上滑链:探索 `st_lv_*` → `st_hp_*` → `st_gold_*` → `st_weapon_*` → `st_armor_*`,可打断。 +2. 战斗上滑:己方档 + `foe_hp_high/mid/low` 三句(不复用玩家档)。 +3. 连战:第一只死 → `btl_enemy_die` → 不停 BGM → 下一只 `btl_start_*` + 前摇;HP/MP 不回,指令重置为攻击。 +4. `stats.playSec`:探索+战斗实际时长,菜单/结算不计。 +5. schema≠1:可 `sys_new_game` 重开,stats 保留。 +6. 探索菜单=左右滑+单击(与 00 §5.4 同构)。战斗下滑=逃跑,不设菜单。 +7. 教学战看门狗:每步静默走三档,不原样复读长说明书;第 3 次演示通过。 -- [x] 场景图 JSON / 遭遇战表 / 战斗状态机(05-rpg-chapter1.md §1-3) -- [x] 全数值表 + 装备节点(05-rpg-chapter1.md §4-5) -- [x] 存档字段 + summary(05-rpg-chapter1.md §6) -- [x] 第一章 120 句台词表(05-rpg-ch1-voice-ids.md) -- [x] 战斗音效/震动配合(本文件 §1) -- [x] 无声前摇协议 + iOS 兜底(本文件 §2) -- [x] 音频层叠规则(本文件 §3,含 amb 走 sfx 层的实现要求) -- [x] BGM 4 首 + sfx 35 个 prompt 草稿(05-rpg-chapter1.md §7 + 本文件 §3 修正) -- [ ] ch2/3 台词表(R4 策划补发,最小集见本文件 §5) -- [ ] ch2/3 场景正文(R5 开发按 ch1 模板扩写) +--- + +## 7. 施工核对 + +- [x] 场景图 / 遭遇 / 10 状态机(chapter1) +- [x] 数值 + 装备节点 +- [x] 存档 + summary + battlesWon +- [x] 格挡=窗口单击;无声=earcon+防御 +- [x] amb 走 sfx loop + 10 号 BGM 表 +- [ ] ch2/3 台词与正文 + +--- + +## 8. 废止 + +| 废止 | 改为 | +|---|---| +| 格挡成功靠「双击无延迟」 | `doubleTapWindow: 0` 或前摇期 immediateTap;手势是单击 | +| 无声窗口 = 仅 danger 震;iOS 不可格挡 | earcon 必响;防御全平台 | +| 「绝不回退提示音」理解成无声反馈 | 回执与 earcon 永远出声 | +| 探索期取消 `bgm/explore` 与 10 号打架 | 10 号:explore 宏观 + amb 景 | +| 菜单双击执行 | 单击确认 | diff --git a/docs/design/05-rpg-ch1-voice-ids.md b/docs/design/05-rpg-ch1-voice-ids.md index c20602ea..53e88b80 100644 --- a/docs/design/05-rpg-ch1-voice-ids.md +++ b/docs/design/05-rpg-ch1-voice-ids.md @@ -1,5 +1,13 @@ # 05b · 《剑与声》第一章语音台词表(voice ids,R4 录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - **旁白**(A/S/D/O/E/B/SH 组,104 句):双性别。**female = Serena,male = Uncle_Fu**(新游戏,沿用 R1 分配惯例)。落 `web/rpg/audio/voice/female|male/.{ogg,m4a}`。 > - female instruct 常量:`平静清晰的年轻女声,像在讲武侠故事` diff --git a/docs/design/05-rpg-chapter1.md b/docs/design/05-rpg-chapter1.md index ebffb692..7a5c2d56 100644 --- a/docs/design/05-rpg-chapter1.md +++ b/docs/design/05-rpg-chapter1.md @@ -1,11 +1,27 @@ -# 05a · 《剑与声》第一章施工稿(rpg chapter 1,R4 资产 / R5 开发依据) +# 05a · 《剑与声》第一章施工稿(26 号对齐) > **给开发者的实现要点摘要** -> 1. 本文件 + [05-rpg-ch1-voice-ids.md](05-rpg-ch1-voice-ids.md)(台词表)+ [05-rpg.md](05-rpg.md)(总设计)三者齐全即可施工。场景图、战斗状态机、数值表、存档结构全部定死,不允许实现时自由发挥。 -> 2. 战斗是**定时器驱动的回合状态机**(§3 状态转换表):敌方前摇 1.0s 内双击=格挡(双击即时回调,规避 tap 延迟);指令循环 攻击→技能→防御→道具。 -> 3. 第一章 8 场景、5 场固定遭遇战(明雷)、2 件装备 + 道具在商店/宝箱/支线获取(§5)。 -> 4. 存档 data 结构 §6(含大厅卡片要显示的 `summary` 字符串);保存时机:场景切换/战斗结束/商店交易后。 -> 5. 资产清单 §7:5 首 BGM(Mureka prompt 草稿)+ 35 个 sfx(Stable Audio 3 prompt 草稿 / 程序合成标注)。语音 ~114 句见台词表文件。 +> 1. 场景图 JSON、遭遇战表、数值、存档字段、BGM/sfx 清单 **以本文为准**(与 05 号总设计冲突时,数据以本文、交互以 26 号+05 号 §0)。 +> 2. 战斗仍是定时器驱动的 10 状态机。**格挡 = 前摇窗口内再单击**(或朝来招方向滑)。禁止要求新手双击。 +> 3. 探索:左右滑切选项、单击确认、上滑状态。`doubleTapWindow: 0`。 +> 4. 第一章 8 场景、5 场明雷、商店/宝箱/支线获取不变。 +> 5. 语音仍只引用 [05-rpg-ch1-voice-ids.md](05-rpg-ch1-voice-ids.md) 表内 id;须停用「双击格挡」教学句或改录为「点一下挡住」(改词在实现时登记,本文不改 voice-ids 文件)。 + +--- + +## 0. 设计单(第一章范围) + +一句话、三状态、手势、矩阵、失败表、首 3 分钟、台词类别、断点复述:**见 [05-rpg.md](05-rpg.md) §0**。本章等待点补: + +| id | 场景 | 期待 | `_short` | +|---|---|---|---| +| `village_opt` | village | 左右/单击 | 「滑一下。」 | +| `path_go` | path | 前进 | 「单击。」 | +| `fork_pick` | fork | 左右选路 | 「左右滑。」 | +| `stream_teach` | stream | 教学战四步 | 见 11 号 | +| `gate_pick` | gate | 强攻/绕行 | 「左右滑。」 | + +Floor:探索 `YOUR_TURN`(amb 常驻可作床);战斗选指令 `YOUR_TURN`(`turn_player`);前摇 `ACTION`;演出 `BUSY`/`SPEAKING`。 --- @@ -20,117 +36,154 @@ const CH1 = { fork: { name: '岔路口', amb: 'amb_fork', options: ['go_chest', 'go_stream', 'back'] }, chest: { name: '枯树洞', amb: 'amb_chest', options: ['open_chest', 'back'] }, stream: { name: '溪流', amb: 'amb_stream', options: ['drink', 'go_hunter', 'back'], - encounter: 'enc_teach_wolf' }, // 首次进入先触发教学战 + encounter: 'enc_teach_wolf' }, hunter: { name: '猎人小屋', amb: 'amb_hunter', options: ['talk_hunter', 'go_deepwood', 'back'] }, deepwood: { name: '密林深处', amb: 'amb_deepwood', options: ['go_gate', 'back'], - encounter: 'enc_bandits' }, // 首次进入触发山贼连战 + encounter: 'enc_bandits' }, gate: { name: '黑风寨门前', amb: 'amb_gate', options: ['assault', 'sneak', 'back'] }, boss1: { name: '聚义厅', amb: 'amb_hall', options: [], - encounter: 'enc_boss1' }, // 由 gate 两个选项分别进入 + encounter: 'enc_boss1' }, }, }; ``` -选项语义(选项 id → 行为): +选项语义(不变): | 选项 id | 播报语音 | 行为 | |---|---|---| -| `go_*` | `opt_go_<场景>` | 切换场景(播脚步 sfx + 新场景 desc) | +| `go_*` | `opt_go_<场景>` | 切换场景(脚步 + 新场景 desc) | | `back` | `opt_back` | 回上一场景 | -| `shop` | `opt_shop` | 进商店子菜单(§4) | -| `rest` | `opt_rest` | HP/MP 回满,播 `sys_rest`;仅 village | -| `status` | `opt_status` | 角色状态播报(同上滑) | -| `investigate` | `opt_investigate` | 一次性:+15 金币(flag `chest_path`),播 `evt_gold15`;之后播 `evt_nothing` | -| `go_chest` | `opt_go_chest` | 岔路左支线 | -| `open_chest` | `opt_open_chest` | 一次性:金疮药×2(flag `chest_fork`) | -| `drink` | `opt_drink` | HP 回满,播 `sys_drink` | -| `talk_hunter` | `opt_talk_hunter` | 支线对话(flag `hunter_quest`:金疮药×1 + 商店九折 flag `shop_discount`) | -| `assault` | `opt_assault` | 战 `enc_elite` → 胜后进 boss1 | -| `sneak` | `opt_sneak` | 战 `enc_sneak`(较弱)→ 进 boss1 且 HP/MP 回满 | +| `shop` | `opt_shop` | 商店子菜单 | +| `rest` | `opt_rest` | HP/MP 回满,`sys_rest`;仅 village | +| `status` | `opt_status` | 与上滑相同 | +| `investigate` | `opt_investigate` | 一次性 +15 金(`chest_path`) | +| `go_chest` | `opt_go_chest` | 岔路左 | +| `open_chest` | `opt_open_chest` | 金疮药×2(`chest_fork`) | +| `drink` | `opt_drink` | HP 回满 | +| `talk_hunter` | `opt_talk_hunter` | 药×1 + 九折 `shop_discount` | +| `assault` | `opt_assault` | `enc_elite` → 胜后 boss1 | +| `sneak` | `opt_sneak` | `enc_sneak` → 进 boss1 且 HP/MP 回满 | -## 2. 遭遇战表(第一章,全部明雷固定) +--- + +## 2. 遭遇战表(不变) | 遭遇 id | 敌人 | 触发 | 特殊 | |---|---|---|---| -| `enc_teach_wolf` | 教学野狼 ×1 | stream 首次进入 | 教学战:玩家 HP 锁底 1(不败),分步教学(台词表 B 组) | -| `enc_wolf` | 野狼 ×1 | 教学战后 stream 再入 50% 刷新一次 | 可逃 | -| `enc_bandits` | 山贼 ×2(连战两场) | deepwood 首次进入 | 不可逃;两场间不回血 | -| `enc_elite` | 寨丁头目 ×1 | gate 选 assault | 不可逃 | -| `enc_sneak` | 山贼 ×1 + 毒蛛 ×1(连战) | gate 选 sneak | 不可逃;胜后进 boss 前回满 | -| `enc_boss1` | 黑风寨主 ×1 | boss1 进入 | Boss 战,不可逃;半血进二阶段 | +| `enc_teach_wolf` | 教学野狼 ×1 | stream 首次 | HP 锁底 1;四步教学(格挡=窗口单击) | +| `enc_wolf` | 野狼 ×1 | 教学后再入 50% | 可逃 | +| `enc_bandits` | 山贼 ×2 | deepwood 首次 | 不可逃;间不回血 | +| `enc_elite` | 寨丁头目 ×1 | assault | 不可逃 | +| `enc_sneak` | 山贼 + 毒蛛 | sneak | 不可逃;胜后回满 | +| `enc_boss1` | 黑风寨主 | boss1 | 不可逃;半血二阶段 | -## 3. 战斗状态机(完整状态转换表) +--- + +## 3. 战斗状态机(格挡输入改写) 状态:`B_START → (回合循环) → VICTORY | DEFEAT | FLED` | 状态 | 进入时副作用 | 出口转换 | |---|---|---| -| `B_START` | 播 `btl_start_` + 敌方登场 sfx; init 回合数=1 | 自动 → `ENEMY_TELEGRAPH`(敌方先攻的蛊师类)或 `PLAYER_SELECT`(默认玩家先攻)。第一章全部玩家先攻 | -| `PLAYER_SELECT` | 播当前指令名(`cmd_attack`),左右滑循环切换(攻击→技能→防御→道具),单击执行;上滑播战场状态;下滑请求逃跑 | 单击 → `PLAYER_EXECUTE`;下滑 → `FLEE_CHECK` | -| `PLAYER_EXECUTE` | 按指令结算:攻击=伤害公式;技能=atk×2 耗 mp3(mp 不足播 `btl_nomp` 留在本状态重选);防御=置 halfDamage 标记;道具=金疮药回 40%maxHp(无药播 `btl_noitem` 重选)。播对应 sfx + 敌方受击音 | 结算完 → `CHECK_ENEMY_DEAD` | -| `CHECK_ENEMY_DEAD` | 敌 HP≤0:播 `btl_enemy_die`;连战则拉下一个敌人回 `B_START`(不重置玩家状态) | 敌死 → `VICTORY`(无下一只);敌活 → `ENEMY_TELEGRAPH` | -| `ENEMY_TELEGRAPH` | 播敌方前摇音(`tel_`,1.0s);启动格挡窗口 1.0s | 窗口内双击 → 格挡标记=true(`vibrate('confirm')` + `sfx/parry`),→ `ENEMY_RESOLVE`;窗口结束 → `ENEMY_RESOLVE`;窗口内其他手势 → `vibrate('error')`,留在窗口 | -| `ENEMY_RESOLVE` | 结算敌方伤害:`dmg×0.3`(格挡)/`×0.5`(防御)/全值;播受击音 + `vibrate([80])`(格挡成功不震 HIT,已震 confirm);血量档位变化时播 `btl_hp_*` | 玩家 HP≤0 → `DEFEAT`;否则回合数+1 → `PLAYER_SELECT` | -| `FLEE_CHECK` | 非 Boss 战:播 `btl_flee` 脱离;Boss/剧情战:播 `btl_noflee` 回 `PLAYER_SELECT` | → `FLED`(回场景,遭遇不消失)或 `PLAYER_SELECT` | -| `VICTORY` | 播 `btl_win` + 经验/金币获得句(整句分档,见台词表)→ 结算升级(升级播 `btl_levelup` + `vibrate('success')`)→ 写存档 | → 场景探索态 | -| `DEFEAT` | 播 `btl_lose` + 死亡归因句 → 章首(village)重生:HP/MP 回满,等级/金币/装备/flags 保留,本章非 Boss 遭遇重置 | → village 探索态 | +| `B_START` | `btl_start_`;回合=1 | 第一章 → `PLAYER_SELECT`(蛊师类先攻除外) | +| `PLAYER_SELECT` | `sfx/turn_player`;Floor=`YOUR_TURN`;播当前指令名;左右滑循环 攻击→技能→防御→道具;单击执行;上滑战场状态;下滑逃跑 | 单击 → `PLAYER_EXECUTE`;下滑 → `FLEE_CHECK` | +| `PLAYER_EXECUTE` | 攻击=伤害公式;技能=atk×2 耗 mp3(不足 `btl_nomp` 留在 SELECT);防御=halfDamage;道具=金疮药 40%maxHp(无药 `btl_noitem`)。内容音 = `silent-ok` | → `CHECK_ENEMY_DEAD` | +| `CHECK_ENEMY_DEAD` | 敌 HP≤0:`btl_enemy_die`;连战拉下一只回 `B_START`(不重置玩家) | 无下一只 → `VICTORY`;否则 `ENEMY_TELEGRAPH` | +| `ENEMY_TELEGRAPH` | Floor=`ACTION`。有声:`tel_` 1.0s(保护档见 11 号)。无声:`danger` earcon(系统合成,必出声)。窗口内:**单击 = 格挡**;**朝来招方向滑 = 格挡**。成功:`sfx/parry`,标记 true。反向滑 / 上滑 / 下滑:`invalid`,留在窗口。连点 400ms 内第二下 `busy` | 窗口结束或已格挡 → `ENEMY_RESOLVE` | +| `ENEMY_RESOLVE` | `dmg×0.3` 格挡 / `×0.5` 防御 / 全值。受击 `hurt`+回执;格挡成功不播 hurt。降档才播 `st_hp_*` | HP≤0 → `DEFEAT`;否则 → `PLAYER_SELECT` | +| `FLEE_CHECK` | 见 11 号成功率 | `FLED` 或回 SELECT;失败则 `ENEMY_TELEGRAPH`(可格挡) | +| `VICTORY` | `btl_win` + 分档掉落 + 升级 | 探索态,写存档 | +| `DEFEAT` | `btl_lose` + 归因 → village 重生(等级金币装备 flags 保留,本章非 Boss 遭遇重置) | village,写存档 | -半血叙事钩子:敌人 HP 首次 ≤50% 时在 `CHECK_ENEMY_DEAD` 前插播 `btl_half_`(无此语音的敌人跳过)。 +半血钩子:HP 首次 ≤50% 于结算前插播 `btl_half_`。 -## 4. 数值表 +来招方向约定(滑动格挡,不单独教学): + +| 敌人 | 声像 / 约定方向 | +|---|---| +| 野狼、教学狼 | 中央;滑动任意左右也认,或只认单击 | +| 山贼 / 头目 | 前摇脚步声像侧 | +| 毒蛛 | 高频,中央;认单击 | +| 无声 | 无方向;**只认单击**(滑=`invalid`+「点一下。」) | + +--- + +## 4. 数值表(原样保留) ### 4.1 玩家 ``` -maxHp = 20 + 8*(lv-1) // lv1=20, lv3=36, lv5=52 +maxHp = 20 + 8*(lv-1) atk = 5 + 2*(lv-1) + 武器atk def = 护甲def -mp = 3 + lv // 破空斩耗 3 +mp = 3 + lv expToNext = lv * 20 dmg = max(1, round((atk攻 - def守) * (0.85~1.15))) ``` -### 4.2 敌人(全游戏 8 种 + 3 Boss;第一章用前 4 种 + Boss1) +### 4.2 敌人 -| 敌人 | 章 | HP | ATK | DEF | EXP | 金币 | 前摇音 sfx id | 前摇特征 | 半血句 | +| 敌人 | 章 | HP | ATK | DEF | EXP | 金币 | 前摇 sfx | 前摇特征 | 半血句 | |---|---|---|---|---|---|---|---|---|---| -| 教学野狼 | 1 | 6 | 3 | 0 | 5 | 0 | `tel_wolf` | 扑击前低吼 | 无 | +| 教学野狼 | 1 | 6 | 3 | 0 | 5 | 0 | `tel_wolf` | 扑击低吼 | 无 | | 野狼 | 1 | 10 | 4 | 0 | 8 | 5 | `tel_wolf` | 低吼 | 无 | | 山贼 | 1 | 16 | 6 | 1 | 12 | 12 | `tel_bandit` | 脚步+拔刀 | `btl_half_bandit` | -| 毒蛛 | 1 | 12 | 7 | 0 | 12 | 8 | `tel_spider` | 高频肢节摩擦 | 无 | -| 寨丁头目 | 1 | 24 | 8 | 2 | 20 | 25 | `tel_elite` | 重甲碰撞 | `btl_half_elite` | -| **Boss 黑风寨主** | 1 | 45 | 10 | 2 | 50 | 100 | `tel_boss1` | 双刀互击两声 | 二阶段:ATK→12,播 `boss1_phase2` | +| 毒蛛 | 1 | 12 | 7 | 0 | 12 | 8 | `tel_spider` | 高频肢节 | 无 | +| 寨丁头目 | 1 | 24 | 8 | 2 | 20 | 25 | `tel_elite` | 重甲 | `btl_half_elite` | +| **Boss 黑风寨主** | 1 | 45 | 10 | 2 | 50 | 100 | `tel_boss1` | 双刀两声 | 二阶段 ATK→12 | | 沼泽鳄 | 2 | 30 | 11 | 2 | 22 | 18 | `tel_croc` | 水花+低吼 | 无 | -| 蛊师 | 2 | 22 | 12 | 1 | 26 | 30 | `tel_shaman` | 吟唱(敌方先攻) | 有 | -| 剑傀 | 3 | 38 | 14 | 3 | 40 | 45 | `tel_puppet` | 机括咔哒 | 无 | -| 影卫 | 3 | 30 | 16 | 2 | 45 | 50 | 无声(震动 DANGER 代替) | 寂静 | 有 | -| **Boss 腐沼祭主** | 2 | 70 | 14 | 3 | 90 | 200 | `tel_boss2` | 人声哼鸣 | 二阶段召唤 2 毒蛛 | -| **Boss 白无瞳** | 3 | 110 | 18 | 5 | 150 | 500 | 三阶段完全无声 | 寂静+DANGER | 三次阶段转换 | +| 蛊师 | 2 | 22 | 12 | 1 | 26 | 30 | `tel_shaman` | 吟唱(先攻) | 有 | +| 剑傀 | 3 | 38 | 14 | 3 | 40 | 45 | `tel_puppet` | 机括 | 无 | +| 影卫 | 3 | 30 | 16 | 2 | 45 | 50 | 无(danger earcon) | 寂静 | 有 | +| **Boss 腐沼祭主** | 2 | 70 | 14 | 3 | 90 | 200 | `tel_boss2` | 哼鸣 | 二阶段 2 毒蛛 | +| **Boss 白无瞳** | 3 | 110 | 18 | 5 | 150 | 500 | 三阶段无声+earcon | 寂静 | 三次转换 | -经验校验:第一章可得 exp = 5+8+12×2+20+12+12+50 = 131+(教学狼 5 + 野狼 8 + 山贼 24 + 头目/偷袭线 20~24 + Boss 50)≈ 110-135 → 打完第一章玩家 lv3(20+40+60=120 可到 lv4 门槛前),数值节奏成立。 +经验校验原文成立:打完第一章约 lv3。 -### 4.3 装备与道具(获取节点) +### 4.3 装备获取(不变) | 物品 | 效果 | 获取 | |---|---|---| -| 铁剑 | atk+3 | village 商店 100 金(开局 50 金 + 小路调查 15 + 若干战斗;猎人支线后九折 90) | -| 皮甲 | def+2 | village 商店 80 金(九折 72) | -| 金疮药 | 回 40% maxHp | 商店 30 金;枯树洞宝箱 ×2;猎人支线 ×1;叠加上限 5 | -| 青钢剑 | atk+6 | 第二章商店 300 | -| 铁鳞甲 | def+4 | 第二章商店 250 | -| 听风剑 | atk+9,格挡窗口 1.0s→1.3s | 第三章商店 600 | +| 铁剑 | atk+3 | 商店 100(九折 90) | +| 皮甲 | def+2 | 商店 80(九折 72) | +| 金疮药 | 40% maxHp | 30 金;宝箱×2;猎人×1;上限 5 | +| 青钢剑 | atk+6 | 二章 300 | +| 铁鳞甲 | def+4 | 二章 250 | +| 听风剑 | atk+9,窗口 1.0→1.3s | 三章 600 | -### 4.4 商店子菜单(village) +### 4.4 商店 -进入播 `shop_open` → 左右滑循环:铁剑/皮甲/金疮药/离开 → 单击购买(播物品名+价格档句 `shop_price_`;钱不够播 `shop_poor`;已购装备播 `shop_owned`)→ 购买成功 `sfx/coin` + `vibrate([15,40,15])` → 写存档。 +`shop_open` → 左右滑:铁剑/皮甲/金疮药/离开 → **单击购买**(不再双击)。钱不够 `shop_poor`;已购 `shop_owned`。成功 `sfx/coin` + 回执。写存档。 -## 5. 探索态手势(复读总设计,施工口径) +--- -左右滑=切选项(播选项名)/ 单击=确认 / 双击=重听场景描述+环境音 / 上滑=状态播报(`st_lv`、`st_hp_*`、`st_gold_*`、`st_equip` 链播,全整句分档)/ 下滑=菜单(继续 / 保存并回大厅 / 重听帮助)。血量档位:>70% `btl_hp_high`("你状态很好")、30-70% `btl_hp_mid`("你受了伤")、<30% `btl_hp_low`("你伤势很重")。 +## 5. 探索态手势(施工口径) -## 6. 存档 JSON(精确定义) +- 左右滑=切选项(报名)=`silent-ok` 或 `ok` +- 单击=确认 +- 上滑= `st_lv_*` → `st_hp_*` → `st_gold_*` → `st_weapon_*` → `st_armor_*`(可打断) +- 下滑=菜单(阶段 2):继续 / 保存回大厅 / 返回;破坏性二次确认 +- 双击新手当单击(不重听整段描述;描述可用单击跳句后的帮助短句) -`PUT /api/progress { game: 'rpg', data: ... }` 的 `data`: +血量档:>70% / 30–70% / <30% 原句 id 保留。 + +教学战逐步旁白(替旧「双击格挡」): + +| 步 | 意图 | 玩家 | +|---|---|---| +| T0 | 左右滑听指令名 | 滑 ≥2 | +| T1 | 攻击单击 | 出剑一次 | +| T2 | 前摇内**单击**格挡 | 成功或受击都通过 | +| T3 | 选防御单击 | 演示减伤 | +| T4 | 自由击杀 | 敌亡 | + +完整参数见 [11-rpg-battle-rebalance.md](11-rpg-battle-rebalance.md)。 + +--- + +## 6. 存档 JSON(精确定义,保留) + +`PUT /api/progress { game: 'rpg', data: ... }`: ```json { @@ -159,80 +212,97 @@ dmg = max(1, round((atk攻 - def守) * (0.85~1.15))) "boss1_done": false }, "chapters": { "1": "open", "2": "locked", "3": "locked" }, - "stats": { "kills": 0, "deaths": 0, "parries": 0, "playSec": 0 } + "stats": { "kills": 0, "deaths": 0, "parries": 0, "playSec": 0, "battlesWon": 0 } } ``` -字段规则: - | 字段 | 默认 | 说明 | |---|---|---| -| `schema` | 1 | 升版时 R5+ 写迁移或丢弃重来 | -| `summary` | `"新的旅程"` | 大厅卡片显示;格式 `第X章·场景名 · LvN · 金币M` | -| `chapter`/`scene` | 1 / `village` | 继续游戏落点 | -| `player.*` | lv1 exp0 hp20 maxHp20 mp4 gold50 weapon null armor null potions 1 | 开局 50 金为村长赠送剧情款 | +| `schema` | 1 | 不匹配可重开,stats 保留 | +| `summary` | `"新的旅程"` | `第X章·场景名 · LvN · 金币M` | +| `chapter`/`scene` | 1 / `village` | 落点 | +| `player.*` | lv1 exp0 hp20 maxHp20 mp4 gold50 weapon null armor null potions 1 | 开局 50 金 | | `weapon`/`armor` | null | `iron_sword`/`steel_sword`/`wind_sword`;`leather`/`iron_scale` | -| `flags.*` | false | 一次性事件锁 | -| `chapters` | 见上 | `locked`/`open`/`cleared` | -| `stats.*` | 0 | 只增不减,新游戏也保留 | +| `flags.*` | false | 一次性 | +| `chapters` | 见上 | locked/open/cleared | +| `stats.*` | 0 | 只增不减;`battlesWon` 供保护档 | -- 保存时机:场景切换后、战斗结算后、商店交易后、菜单"保存并回大厅"。localStorage `soundgame.progress.rpg` 与服务器双写。 -- 进游戏有存档时:播 `sys_continue_ask`("继续上次的旅程?单击继续,右滑开始新游戏。");新游戏清空 data 保留 `stats` 累计。 +保存:场景切换、战斗结算、商店、菜单保存。localStorage `soundgame.progress.rpg` 与服务器双写。有档:`sys_continue_ask` 单击继续、右滑新游戏。 -## 7. 资产需求清单 +--- -### 7.1 BGM(5 首,Mureka instrumental,60-90s 无缝循环,沿用归一化工艺) +## 7. 资产需求清单(保留 prompt) -| id | 用途 | Mureka prompt 草稿(英文) | +### 7.1 BGM(与 10 号对齐) + +| id | 用途 | Mureka prompt 草稿 | |---|---|---| -| `bgm/explore` | 第一章探索底 | "Atmospheric Chinese wuxia ambient, bamboo forest mist, sparse guzheng plucks over low drone, slow tempo 60 bpm, mysterious but calm, no vocals, seamless loop" | -| `bgm/battle` | 普通战斗 | "Tense wuxia battle music, driving taiko drums 100 bpm, sharp erhu stabs, urgent but not chaotic, no vocals, seamless loop" | -| `bgm/boss` | Boss 战 | "Epic dark wuxia boss theme, heavy taiko 120 bpm, dissonant strings, dramatic tension, no vocals, seamless loop" | -| `bgm/village` | 村庄/安全区 | "Warm peaceful Chinese village ambiance music, soft dizi flute melody, gentle plucked strings, cozy 70 bpm, no vocals, seamless loop" | -| `bgm/ending` | 章结算/剧情 | "Melancholic wuxia emotional theme, solo erhu over soft pads, bittersweet, slow, no vocals, seamless loop" | +| `bgm/explore` | 探索宏观 | Atmospheric Chinese wuxia ambient, bamboo forest mist, sparse guzheng plucks over low drone, slow tempo 60 bpm, mysterious but calm, no vocals, seamless loop | +| `bgm/battle` | 普通战斗 | Tense wuxia battle music, driving taiko drums 100 bpm, sharp erhu stabs, urgent but not chaotic, no vocals, seamless loop | +| `bgm/boss` | Boss | Epic dark wuxia boss theme, heavy taiko 120 bpm, dissonant strings, dramatic tension, no vocals, seamless loop | +| `bgm/village` | 村庄 | Warm peaceful Chinese village ambiance music, soft dizi flute melody, gentle plucked strings, cozy 70 bpm, no vocals, seamless loop | +| `bgm/ending` | 章结算 | Melancholic wuxia emotional theme, solo erhu over soft pads, bittersweet, slow, no vocals, seamless loop | -### 7.2 SFX(35 个;SA3 = Stable Audio 3 生成,SYN = 程序合成) +amb_* 走 **sfx 层 loop**(addendum),`bgm=0` 时 amb 仍在。village 在 bgm=0 时用 `amb_village`。 -| 类别 | id | 方式 | prompt 草稿 / 说明 | +### 7.2 SFX(35;SA3 = Stable Audio 3,SYN = 程序合成) + +| 类别 | id | 方式 | prompt / 说明 | |---|---|---|---| -| 敌方前摇 | `tel_wolf` | SA3 | "wolf growl then lunge, short, dry, close mic" | -| | `tel_bandit` | SA3 | "footsteps on dirt then sword unsheathing shing, 1 second" | -| | `tel_spider` | SA3 | "insect legs skittering, high frequency chitin clicks, eerie, 1 second" | -| | `tel_elite` | SA3 | "heavy armor plates clanking, menacing, 1 second" | -| | `tel_boss1` | SA3 | "two metal blades striking each other twice, dramatic ring" | -| | `tel_croc` | SA3 | "water splash with low reptile growl" | -| | `tel_shaman` | SA3 | "low guttural chant humming, ritualistic, dark" | -| | `tel_puppet` | SA3 | "wooden mechanism clicks and winding gears" | -| 战斗 | `atk_swing` | SA3 | "sword whoosh through air, fast, clean" | -| | `atk_hit` | SA3 | "sword hitting flesh, dull impact, not gory" | -| | `parry` | SA3 | "metal parry clang, bright ring, short" | -| | `skill_slash` | SA3 | "powerful sword slash with air rip, dramatic" | -| | `hurt` | SYN | 低频闷击(程序合成,避免与 atk_hit 混淆) | -| | `enemy_die` | SA3 | "body falling to ground with weapon clatter" | -| | `potion` | SA3 | "drinking liquid gulps, glass bottle" | +| 敌方前摇 | `tel_wolf` | SA3 | wolf growl then lunge, short, dry, close mic | +| | `tel_bandit` | SA3 | footsteps on dirt then sword unsheathing shing, 1 second | +| | `tel_spider` | SA3 | insect legs skittering, high frequency chitin clicks, eerie, 1 second | +| | `tel_elite` | SA3 | heavy armor plates clanking, menacing, 1 second | +| | `tel_boss1` | SA3 | two metal blades striking each other twice, dramatic ring | +| | `tel_croc` | SA3 | water splash with low reptile growl | +| | `tel_shaman` | SA3 | low guttural chant humming, ritualistic, dark | +| | `tel_puppet` | SA3 | wooden mechanism clicks and winding gears | +| 战斗 | `atk_swing` | SA3 | sword whoosh through air, fast, clean | +| | `atk_hit` | SA3 | sword hitting flesh, dull impact, not gory | +| | `parry` | SA3 | metal parry clang, bright ring, short | +| | `skill_slash` | SA3 | powerful sword slash with air rip, dramatic | +| | `hurt` | SYN | 低频闷击(避免与 atk_hit 混淆) | +| | `enemy_die` | SA3 | body falling to ground with weapon clatter | +| | `potion` | SA3 | drinking liquid gulps, glass bottle | | | `flee` | SYN | 渐远脚步 | -| 探索/系统 | `step` | SYN | 单步脚步(复用 escape 工艺) | -| | `coin` | SYN | 硬币叮当(短合成) | -| | `chest` | SA3 | "old wooden chest creaking open" | -| | `drink_water` | SA3 | "scooping and drinking stream water" | -| | `rest` | SA3 | "settling down by campfire, fabric rustle" | -| | `levelup` | SYN | 上行钟声琶音(内容音非提示音,允许) | -| | `door_heavy` | SA3 | "heavy wooden gate slowly opening, deep creak" | -| | `ui_deny` | — | **不做**(反馈走震动 error) | -| 场景环境 | `amb_village` | SA3 | "night village ambience, campfire crackle, distant chatter, crickets, loop" | -| | `amb_path` | SA3 | "forest path ambience, wind through leaves, birds distant, loop" | -| | `amb_fork` | SA3 | "forest crossroads ambience, wind split feel, crows, loop" | -| | `amb_chest` | SA3 | "hollow tree hole ambience, drips, muffled wind, loop" | -| | `amb_stream` | SA3 | "flowing stream over rocks, gentle water, loop" | -| | `amb_hunter` | SA3 | "wood cabin ambience, firewood crackle, knife sharpening occasionally, loop" | -| | `amb_deepwood` | SA3 | "deep forest ambience, insects, distant wolf howl, ominous, loop" | -| | `amb_gate` | SA3 | "bandit camp outside gate ambience, torches burning, rowdy distant voices, loop" | -| | `amb_hall` | SA3 | "large hall ambience, fire brazier, echoing space, tense, loop" | -| 二/三章备用 | `tel_croc/tel_shaman/tel_puppet`(已列) + `amb_swamp`、`amb_peak`、`tel_boss2` | SA3 | 沼气泡/山峰风/哼鸣,R4 一起生成 | -| 格挡无声敌人 | (无 sfx,震动 DANGER) | — | 影卫/白无瞳三阶段 | +| | `turn_player` | SYN | 上行双音剑鸣,≤0.4s(11 号) | +| | `victory` | SA3 | triumphant short brass fanfare, 3 rising notes, wuxia style, under 1.5 seconds | +| 探索/系统 | `step` | SYN | 单步脚步 | +| | `coin` | SYN | 硬币叮当 | +| | `chest` | SA3 | old wooden chest creaking open | +| | `drink_water` | SA3 | scooping and drinking stream water | +| | `rest` | SA3 | settling down by campfire, fabric rustle | +| | `levelup` | SYN | 上行钟声琶音 | +| | `door_heavy` | SA3 | heavy wooden gate slowly opening, deep creak | +| | `ui_deny` | — | **不做**(回执走引擎) | +| 场景环境 | `amb_village` | SA3 | night village ambience, campfire crackle, distant chatter, crickets, loop | +| | `amb_path` | SA3 | forest path ambience, wind through leaves, birds distant, loop | +| | `amb_fork` | SA3 | forest crossroads ambience, wind split feel, crows, loop | +| | `amb_chest` | SA3 | hollow tree hole ambience, drips, muffled wind, loop | +| | `amb_stream` | SA3 | flowing stream over rocks, gentle water, loop | +| | `amb_hunter` | SA3 | wood cabin ambience, firewood crackle, knife sharpening occasionally, loop | +| | `amb_deepwood` | SA3 | deep forest ambience, insects, distant wolf howl, ominous, loop | +| | `amb_gate` | SA3 | bandit camp outside gate ambience, torches burning, rowdy distant voices, loop | +| | `amb_hall` | SA3 | large hall ambience, fire brazier, echoing space, tense, loop | -计数:前摇 8 + 战斗 9 + 探索系统 8 + 环境 9 + Boss2 前摇 1 = **35**(`ui_deny` 不做,无声敌人不占 sfx)。环境音 amb_* 走 bgm 层 loop(低音量 volume 0.5),不占 sfx 并发。 +无声敌人(影卫 / 白无瞳三阶段)不占 sfx:窗口用引擎 `danger` earcon + 回执。amb 走 sfx 层 loop。计数仍约 35 件内容音 + 2 个战斗状态音。 -## 8. 与台词表的对应 +--- -语音 id 共 114 个(旁白双性别 ~70 + NPC 平铺单音色 ~44),见 [05-rpg-ch1-voice-ids.md](05-rpg-ch1-voice-ids.md)。场景 desc、选项名、战斗播报、商店、状态档位、教学全部覆盖;开发 agent 只许引用表内 id。 +## 8. 与台词表 + +开发只许引用 05-rpg-ch1-voice-ids.md。教学格挡句若仍写「双击」,运行时改播等价「点一下挡住」或停用旧 id 待重录——**不在本次改 voice-ids 文件**。 + +--- + +## 9. 废止(本章范围内) + +| 废止 | 改为 | +|---|---| +| `ENEMY_TELEGRAPH` 双击格挡 | 单击;同向滑可选 | +| 探索双击重听 | 新手当单击;阶段 3 再重听当前描述 | +| 商店双击购买 | 单击购买 | +| 无声窗口仅震动 | danger earcon + 回执 | +| amb 与 explore BGM 抢同一层导致互删 | BGM 宏观 + amb 走 sfx loop(addendum) | + +JSON、数值、获取节点、flags、summary 格式:**未改**。 diff --git a/docs/design/05-rpg.md b/docs/design/05-rpg.md index 434b57d3..5a1c3e33 100644 --- a/docs/design/05-rpg.md +++ b/docs/design/05-rpg.md @@ -1,209 +1,208 @@ -# 05 · 新 RPG《剑与声》完整设计(rpg / Sword & Sound) +# 05 · 《剑与声》完整设计(26 号对齐) > **给开发者的实现要点摘要** -> 1. 纯音频回合制 RPG:节点式场景图(不是网格),左右滑=切换选项、单击=确认/前进、双击=重听、上滑=角色状态、下滑=菜单。回合制战斗:左右滑选指令、单击执行;敌方攻击有 1s 前摇音,前摇内**双击=格挡**(减伤 70%)——这是唯一的即时操作,绑双击规避 tap 延迟。 -> 2. 规模按"一轮开发(R5)可完成"控制:3 章、每章 6-8 个场景节点、共 8 种敌人 + 3 个 Boss、装备 6 件、数值一套线性公式。复杂度对标 escape(单文件 game.js + ~120 条语音 ×2 音色)。 -> 3. 进度存服务器:存档 JSON 结构见 §7,在场景切换、战斗结束、商店交易后自动保存。 -> 4. 第一章《迷雾林》内容全部给出(§5):8 场景、敌人表、对白功能大纲。第二、三章只给结构骨架与 Boss 设定,正文沿用第一章模式由开发 agent 扩写。 -> 5. 语音全部整句(禁止数字拼接):血量/等级/金币的播报用分段整句("毫发无损/受了轻伤/伤势很重/命悬一线"),数值细节进上滑状态播报的档位句。 -> -> **R2 增补**:施工级文档已就位,以这两份为准—— -> - [05-rpg-chapter1.md](05-rpg-chapter1.md):第一章场景图 JSON、战斗状态机转换表、全数值表、存档字段定义、BGM/sfx 资产清单(含 Mureka/SA3 prompt 草稿)。 -> - [05-rpg-ch1-voice-ids.md](05-rpg-ch1-voice-ids.md):第一章 120 句台词表(旁白 104×2 性别 + NPC 16 单音色)。 -> 本文 §7 的存档结构、§9 的资产数量为设计初稿,若与施工稿不一致,**以 05-rpg-chapter1.md 为准**(主要差异:存档 data 为服务器自由格式 + 必带 `summary` 字段;语音总量 104×2+16)。 +> 1. **交互权威**:26 号手册。施工数据以 [05-rpg-chapter1.md](05-rpg-chapter1.md) 的场景图 JSON、数值、存档为准。语音表 [05-rpg-ch1-voice-ids.md](05-rpg-ch1-voice-ids.md) 不在本次改写。 +> 2. **探索**:左右滑切换选项、单击确认。上滑=我在哪/状态(阶段 1)。新手期无双击。菜单与探索同构(阶段 2)。 +> 3. **格挡禁止绑新手双击。** 敌方前摇窗口内:**再单击**挡住(只教这一个)。窗口内朝攻击方向滑也算格挡(空间冗余,不单独开课)。无声敌人:系统 danger earcon + 回执仍出声;**防御指令**始终可用(减伤 50%,不看时机)。iOS 无震动时格挡与防御照常可完成。 +> 4. 第一句就让玩家滑选项或点一下;第一次成功 ≤10s。回执 ≤100ms。玩家回合 = `YOUR_TURN`;前摇窗口 = `ACTION`。 +> 5. 规模不变:3 章、第一章 8 场景、8 种敌人 + 3 Boss、装备 6、线性公式。战败章首重生(等级金币保留)。 --- -## 1. 游戏定位与核心循环 +## 0. 设计单(26 号附录 C) -- **题材**:武侠/奇幻。玩家是"盲剑客"沈听澜——眼盲,耳即眼。题材与纯音频玩法互为叙事 justification,敌人方位、招式前摇全部天然用声音呈现。 -- **单局时长**:一章 15-25 分钟;全三章 60-80 分钟。死亡从章首重生(背包/等级保留),鼓励反复推图。 -- **核心循环**:探索场景 → 听环境音与描述 → 选择行动(前进/调查/战斗/商店/休息)→ 回合制战斗(格挡是技巧点)→ 获得经验金币 → 升级换装备 → 打 Boss → 下一章。 +### 0.1 一句话核心动作 -## 2. 手势映射(符合全局默认,无需豁免) +**听选项,左右滑挑选,单击去做;战斗里听前摇,再点一下挡住。** -### 2.1 探索态 +### 0.2 玩家可感知的状态(3 个) -| 手势 | 作用 | -|---|---| -| 左/右滑 | 在当前场景的选项间切换(每切换播选项名 + SWIPE_OK 震动)。选项循环 | -| 单击 | 确认执行当前选项(CONFIRM 震动) | -| 双击 | 重听当前场景描述 + 环境音(重听是探索游戏的生命线) | -| 上滑 | 角色状态播报(等级档位、血量档位、金币档位、装备名,全整句) | -| 下滑 | 菜单:继续 / 保存并回大厅 / 重听帮助 | - -### 2.2 战斗态 - -| 手势 | 作用 | -|---|---| -| 左/右滑 | 切换指令:攻击 → 技能(破空斩,耗内劲)→ 防御(本回合受伤减半)→ 道具(金疮药,回 40% HP)→ 循环 | -| 单击 | 执行当前指令 | -| **双击** | **格挡**:仅在敌方攻击前摇音播放的 1.0s 窗口内有效,成功减伤 70% 并播格挡音;窗口外双击无效(ERROR 震动) | -| 上滑 | 战场状态(己方血量档位 + 敌方血量档位) | -| 下滑 | 尝试逃跑(Boss 战无效,播"避无可避") | - -格挡窗口的 300ms tap 延迟问题不存在——格挡绑双击(即时回调),这是全局规范"即时反馈绑滑动/双击"的应用。 - -## 3. 数值框架(简单可平衡) - -### 3.1 玩家 - -``` -等级 lv: 1..10 -maxHp = 20 + 8*(lv-1) // lv1=28? 不——lv1=20, lv10=92 -atk = 5 + 2*(lv-1) + 武器atk // lv1 裸装=5 -def = 护甲def -内劲 mp = 3 + lv // 破空斩耗 3,伤害 = atk*2 -升级所需 exp = lv * 20 // lv1→2 需 20,lv9→10 需 180;全三章总 exp ≈ 900 -``` - -伤害公式:`dmg = max(1, round((攻方atk - 守方def) * (0.85 ~ 1.15 随机)))` -格挡:`dmg = max(0, round(dmg * 0.3))`;防御指令:`dmg = round(dmg * 0.5)`(与格挡不叠加)。 - -### 3.2 敌人表(三章通用模板 + 第一章实例) - -| 敌人 | 章 | HP | ATK | DEF | EXP | 金币 | 前摇音特征 | -|---|---|---|---|---|---|---|---| -| 野狼 | 1 | 10 | 4 | 0 | 8 | 5 | 扑击前低吼 | -| 山贼 | 1 | 16 | 6 | 1 | 12 | 12 | 挥刀前的脚步 | -| 毒蛛 | 1 | 12 | 7 | 0 | 12 | 8 | 肢节摩擦(高频) | -| 精英·寨丁头目 | 1 | 24 | 8 | 2 | 20 | 25 | 重甲碰撞 | -| **Boss 黑风寨主** | 1 | 45 | 10 | 2 | 50 | 100 | 双刀互击(两下金属声) | -| 沼泽鳄 / 蛊师 / … | 2 | 按 lv4-6 玩家数值 +30% 调 | | | 18-30 | | | -| **Boss 腐沼祭主** | 2 | 70 | 14 | 3 | 90 | 200 | 吟唱(人声哼鸣) | -| 剑傀 / 影卫 / … | 3 | 按 lv7-9 玩家数值 +30% 调 | | | 35-50 | | | -| **Boss 盲剑宿敌·白无瞳** | 3 | 110 | 18 | 5 | 150 | 500 | 无声!前摇是"寂静"(特殊机制:格挡窗口用震动 DANGER 提示,教学点在 Boss 战开场由旁白讲明) | - -第三章 Boss"无声前摇"是全游戏叙事+玩法高潮:耳朵最强的剑客遇到没有声音的敌人,格挡提示从听觉转触觉——呼应全局震动语言。 - -### 3.3 装备与道具 - -| 物品 | 类型 | 效果 | 价格 | +| 状态 | Floor | 听感 | 第一次教 | |---|---|---|---| -| 铁剑 | 武器 | atk+3 | 100(一章商店) | -| 青钢剑 | 武器 | atk+6 | 300(二章商店) | -| 听风剑 | 武器 | atk+9,格挡窗口 +0.3s | 600(三章商店,特色装) | -| 皮甲 | 护甲 | def+2 | 80 | -| 铁鳞甲 | 护甲 | def+4 | 250 | -| 金疮药 | 道具 | 回复 40% maxHp | 30,可叠 5 瓶 | +| 探索 / 选指令 | `YOUR_TURN` | `bgm/explore` 或 `village` + amb;指令名循环即「轮到你」;可加等待声 | 「左右滑,点一下。」 | +| 旁白 / NPC | `SPEAKING` | 语音;整链 duck | 开场 | +| 前摇窗口 / 危险 | `ACTION` | 有声:`tel_*`;无声:`danger` earcon(系统音,不是敌人叫声) | 「听到这个,点一下挡住。」 | -平衡校验:lv3 玩家(atk≈9+3=12 铁剑)打野狼 dmg≈11-13 → 一刀秒;打山贼 2 刀;Boss 黑风寨主 45HP 需 ~5 刀,Boss dmg 对皮甲玩家 ≈ 8-10/刀,玩家 36HP 可吃 4 刀,格挡成功只吃 2-3 → 不格挡会死、会格挡能过,技巧有真实收益。 +战斗 BGM、Boss BGM 是宏观层,新手期不单开第 4 种「必须记住的状态课」。 -## 4. 场景与探索结构 +### 0.3 新手手势与解锁 -- 每章 = **有向节点图**(非网格):节点 = 场景,边 = 选项。探索时当前场景播环境音循环底(长底噪用 bgm 层 loop、低音量)+ 首次进入播描述语音。 -- 每章结构:`起点(教学/商店) → 主线节点 ×4~5 → Boss`,其中 2 个节点带岔路(一条主线、一条奖励支线:宝箱/支线 NPC/休息点)。 -- 战斗触发:进入战斗节点即触发(暗雷不做了——纯音频里"突然遇敌"没有视觉游戏的信息缓冲,全部明雷:场景描述里明说"有杀气")。 -- 场景环境音要求:每场景一个专属 amb 循环(同 escape 重设计的做法,Stable Audio 3 生成)。 +| 手势 | 探索 | 战斗选指令 | 前摇 ACTION | 解锁 | +|---|---|---|---|---| +| 左 / 右滑 | 切选项 | 切指令 | **朝来招方向滑=格挡**(不教,做对算成功) | 阶段 0 | +| 单击 | 确认 | 执行指令 | **格挡**(唯一教学手势) | 阶段 0 | +| 上滑 | 状态(位置/目标/HP 档) | 战场状态 | `invalid`(窗口内只认格挡) | 阶段 1 | +| 下滑 | 菜单(阶段 2) | 逃跑 | `invalid` | 阶段 2;战斗始终=逃跑不是菜单 | +| 双击 | 当单击 | 当单击 | 当单击(可格挡,因当单击) | 阶段 3 才识别为重听场景句 | +| 长按 | 旁白加速(探索不占帮助) | 加速 | 不占用格挡 | 默认加速;格挡不绑长按 | -## 5. 第一章《迷雾林》详细内容 +### 0.4 状态 × 手势矩阵(新手期) -### 5.1 场景列表(8 节点) +| 状态 \ 手势 | 单击 | 左右滑 | 上滑 | 下滑(未解锁) | 双击 | 连点 | +|---|---|---|---|---|---|---| +| SPEAKING | 跳句 + `ok` | 打断并切选项/指令 | 打断,状态句 | `invalid`+解释 | 当单击 | 400ms `busy` | +| 探索 YOUR_TURN | 执行选项 | 切选项+报名 | 状态 | `invalid` | 当单击 | `busy` | +| 商店 / 菜单 | 购买/确认 | 切项 | 重听说明 | — | 当单击 | `busy` | +| PLAYER_SELECT | 执行指令 | 切指令+报名 | 战场状态 | 逃跑检定 | 当单击 | `busy` | +| ENEMY_TELEGRAPH ACTION | 格挡 | 朝来招方向=格挡;反方向=`invalid` | `invalid`+首次「现在点一下挡住。」 | `invalid` | 当单击→格挡 | 第一下格挡,其余 `busy` | +| 结算 / 战败旁白 | 继续 | `invalid` | 状态 | 菜单若已解锁 | 当单击 | `busy` | -| # | 场景 id | 名称 | 环境音 | 选项 | 说明 | -|---|---|---|---|---|---| -| 1 | `village` | 雾口村 | 篝火+人语 | 前进 / 商店 / 休息(回满 HP) / 状态 | 起点兼教学关;商店卖铁剑/皮甲/金疮药 | -| 2 | `path` | 林间小路 | 风+落叶 | 前进 / 调查(找到 15 金币) | 教学:双击重听、上滑状态 | -| 3 | `fork` | 岔路口 | 风分两路(立体声) | 左(宝箱:金疮药×2) / 右(主线) | 教学:左右滑选择 | -| 4 | `stream` | 溪流 | 流水 | 饮水(回满) / 前进 | 回复点;此处触发教学战:野狼×1(教学格挡,必不死) | -| 5 | `hunter` | 老猎人小屋 | 柴火+磨刀 | 对话(支线:获金疮药×1+铁剑九折) / 前进 | NPC 角色音首登场 | -| 6 | `deepwood` | 密林深处 | 虫鸣+远处狼嚎 | 前进(遭遇山贼×2 连战) | 本章难度峰值前 | -| 7 | `gate` | 黑风寨门前 | 火把+人声嘈杂 | 强攻(战寨丁头目) / 绕行(多打一场但 Boss 战前满状态) | 战术选择 | -| 8 | `boss1` | 聚义厅 | 大厅混响+火盆 | Boss 黑风寨主 | 章 Boss;胜利→第二章解锁 | +### 0.5 等待点 -### 5.2 教学编排(探索与战斗各自交互式) +| id | 期待 | `_full` | `_short` | 静默 | L2 | L3 | +|---|---|---|---|---|---|---| +| `village_opt` | 左右或单击 | 「左右滑,选。单击,去做。」 | 「滑一下。」 | 18s | 左右滑 | 替玩家切到「前进」 | +| `shop_buy` | 单击 | 「单击,买下。」 | 「单击。」 | 12s | 单击 | — | +| `tut_b0` | 左右滑 ≥1 | 「滑一滑,听招式名。」 | 「滑一下。」 | 8s | 左右 | 游戏切一次指令 | +| `tut_atk` | 单击攻击 | 「选攻击,单击。」 | 「单击。」 | 8s | 单击 | 代打一刀 | +| `tut_parry` | 前摇内单击 | 「听到低吼,点一下挡住。」 | 「点一下。」 | 窗结束=失败通过 | 单击 | 演示格挡 | +| `tut_def` | 选防御并单击 | 「选防御,单击。」 | 「选防御。」 | 8s | 防御 | 代选防御 | +| `player_turn` | 单击或滑 | (指令名刚播过) | 「单击出手。」 | 8s | — | — | -- 场景 1:旁白教"左右滑选、单击确认",让玩家亲手在商店完成一次"听价→选→买"(给 50 金币启动金)。 -- 场景 3:教左右滑选择岔路 + 双击重听。 -- 场景 4 教学战(野狼,HP 锁 1 不死保护):旁白分步——"它要扑了,听前摇!"→ 低吼响起 → "双击格挡!"→ 成功/失败都继续(失败只掉 1 血),然后教"左右滑选攻击,单击出剑"。玩家亲手砍出第一刀后教学结束。 -- 之后不再教学,上滑随时可重听帮助。 +### 0.6 首 3 分钟逐秒脚本 -### 5.3 对白大纲(每句注明功能与语气;正文由开发 agent 按语气扩写,整句、≤25字/句) +| 时间 | 游戏说 / 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 「左右滑,选。点一下,去做。」(选项已在) | 左右滑或单击 | 「滑一下。」 | 上滑未解锁=`invalid` | +| 3~10s | 切到一项 + 报名 = 第一次成功 → 若点「前进」则走路 | 再点确认 | 短句 | — | +| 10~30s | 村长赠金;「去商店。左右滑,听价钱。」 | 进商店买或不买 | 三档 | 钱不够=`shop_poor` | +| 30~60s | 小路选项;阶段 1:「想看自己怎么样,向上滑。」 | 上滑一次 | 「向上滑。」 | — | +| 60~120s | 岔路左右;溪流进教学战 T0→攻击 | 滑指令、单击出剑 | 看门狗推进 | 纠正 | +| 120~180s | 「它要扑了。」前摇 →「点一下挡住。」→ 防御课 → 自由击杀 | 窗口单击 | 受击锁血 1,仍通过 | 窗外汇报「等声音再点。」 | -**序章旁白**(旁白音:Serena/Uncle_Fu,平静叙述) -1. "你的眼睛看不见了。但你的耳朵,比谁都灵。"——立人设,沉稳 -2. "黑风寨掳走了你唯一的徒弟。追进迷雾林,把他们连根拔起。"——给动机,冷 -3. "先记住怎么用耳朵走路。"——转教学,缓和 +### 0.7 失败表 -**村长**(village,长者音:Uncle_Fu 同款 speaker 不同 instruct?"同一角色 speaker+instruct 固定"——村长=Uncle_Fu+慈和 instruct;旁白 male 版=Uncle_Fu+平静 instruct。同 speaker 不同角色时 instruct 不同是允许的,规范约束的是"同一角色恒定") -4. 问候+送 50 金币:"外乡人,林子里不太平。这点钱拿着防身。"——慈和 -5. 商店引导 2 句——功能性,慢速清晰 +| 类型 | 判定 | 归因 | 回到 | 新手代价 | +|---|---|---|---|---| +| 格挡没出手 | 窗口内无单击/同向滑 | 「这下没挡。声音一响就点。」 | 下一玩家回合;教学锁血 | HP 锁底 1(教学) | +| 格挡太早/太晚 | 窗外单击 | 「等低吼再点。」(有声)/「等叮一声再点。」(无声 earcon) | 该击按未格挡结算 | 保护档减伤 | +| 滑反方向 | 窗口内反向滑 | 「招从左边来。向左滑,或点一下。」 | 未格挡 | 无 | +| 战败 | HP≤0 | 「被打倒了。回村子再来。」 | village,保留等级金币 | 非教学掉一次;仍存档 | +| 逃跑失败 | Lv≥3 且 30% | 「没甩掉。听前摇。」 | ENEMY_TELEGRAPH | 可格挡 | +| 钱不够 / 无药 / 无 MP | 执行失败 | 对应句 + `invalid` | 留在选指令 | 无 | -**老猎人**(hunter,沙哑老者 instruct) -6. 戒备开场:"站住。……哦,是个用耳朵的。"——先冷后奇 -7. 支线给予:"寨子里有我旧识。带上这个,别死在路上。"——粗中有暖(功能:给药+折扣 flag) +### 0.8 台词类别 -**寨丁头目**(deepwood/gate,粗哑) -8. 遭遇喝骂 1 句——凶 -9. 半血狠话 1 句——凶转慌(功能:血量反馈的叙事化) +- `critical`:开场动作句;教学格挡那一句 ≤3s。 +- `content`:场景 desc、NPC、选项名、`btl_start_*`、半血/胜利。 +- `hint`:等待点短句、`sys_where`、Boss 门前 `boss1_warn`。 +- `feedback`:格挡失败归因、`btl_nomp`、`btl_noitem`、`btl_flee_fail`。 -**黑风寨主**(boss1,低沉反派) -10. 登场宣言:"盲剑客?正好,省得挖你眼睛。"——狂 -11. 半血:"有两下子。可惜你听不懂死期的声音。"——狠(功能:阶段转换信号) -12. 败亡遗言——惊愕→不甘(功能:章结束钩子,暗示白无瞳存在) +无声敌人**禁止**把窗口提示做成「只有震动」:必须 `danger` earcon(合成系统音)+ 回执。叙事仍是「敌人没有脚步」,系统可以出声。 -**章尾旁白** -13. "寨主倒了。但徒弟不在这里——一张字条,指向南方的腐沼。"——收束+引第二章 +### 0.9 断点复述 -## 6. 第二、三章骨架 +场景切换/战斗结束/商店后自动存。回来 5s:「你在雾口村。去黑风寨。左右滑,选前进。」模板:位置 + 目标 + 一个动作。`sys_continue_ask`:单击继续,右滑新游戏。 -- **第二章《腐沼泽》**:7 节点。新机制:毒状态(每回合 -3 HP,金疮药可解)、蛊师敌人(先攻)。Boss 腐沼祭主二阶段(半血召唤 2 小蛛)。解锁青钢剑/铁鳞甲。 -- **第三章《无瞳峰》**:7 节点。新机制:黑暗战斗(部分敌人无声前摇,靠震动格挡——为终 Boss 铺垫)、听风剑。终 Boss 白无瞳三阶段,终阶段完全无声前摇。 -- 两章正文沿用第一章模板(场景表+敌人表+对白功能大纲)由开发 agent 扩写,规模与第一章相当。 +### 0.10 测试 -## 7. 存档结构(progress JSON) +进村 → 左右滑+单击前进 → 一场战斗(单击攻击;前摇内单击格挡)→ summary 更新。教学战锁血 1。战败重生也存档。 -信封按 00 文档 §4.2(`{schema, game:"rpg", updatedAt, data}`),`data`: +--- -```json -{ - "chapter": 1, - "scene": "stream", - "player": { - "lv": 3, "exp": 45, - "hp": 28, "maxHp": 36, "mp": 6, - "gold": 120, - "weapon": "iron_sword", "armor": "leather", - "potions": 2 - }, - "flags": { - "tutorial_explore_done": true, - "tutorial_battle_done": true, - "hunter_quest": true, - "chest_fork": false, - "shop_discount": true - }, - "chapters": { "1": "cleared", "2": "unlocked", "3": "locked" }, - "stats": { "kills": 12, "deaths": 1, "parries": 9, "playSec": 1500 } -} -``` +## 1. 定位与循环(保留) -- **保存时机**:场景切换后、战斗结算后、商店交易后、菜单选"保存并回大厅"。写 localStorage 并 PUT 服务器(未登录只写本地)。 -- **读取**:进游戏检测到存档 → 语音问"继续上次的旅程?单击继续,右滑开始新游戏"。新游戏清空 data 保留 stats 累计。 -- **版本**:`schema` 升版时开发 agent 写迁移或丢弃重来(R 轮内允许丢弃,文档标注即可)。 +盲剑客沈听澜。一章 15–25 分钟;死亡章首重生(背包/等级保留)。循环:探索听场景 → 选行动 → 回合战斗(格挡是技巧)→ 经验装备 → Boss → 下一章。 -## 8. 战斗回合时序(实现契约) +--- + +## 2. 手势(26 号,无豁免双击格挡) + +探索、战斗选指令见 §0.3。**格挡窗口(1.0s,听风剑 1.3s,保护档见 11 号)**: + +1. **教:单击。** 「听到前摇,点一下挡住。」 +2. **也认:朝来招方向滑。** 来招方位由 `tel_*` 声像或约定(狼正面=上或「扑击」默认单击即可;有左右声像的敌人:左声道→左滑)。反方向=`invalid`,不算格挡。 +3. 成功:`sfx/parry` + `ok` 可省 + 减伤 70%。与防御指令不叠加(取更优)。 +4. 窗口外单击:若在 PLAYER_SELECT 则执行指令;若仍在结算间隙=`invalid`+「等前摇。」 + +废止:双击格挡、用 300ms 双击窗规避 tap 延迟。演奏/格挡要即时:前摇期可 `immediateTap` 或全局 `doubleTapWindow: 0`(松手即单击)。 + +--- + +## 3. 数值框架(保留公式与表) ``` -回合开始 → 若敌人存活:播敌人行动意图音(前摇 1.0s,期间双击=格挡判定) - → 前摇结束结算敌方攻击(应用格挡/防御减伤)→ 播受击/格挡音 + 震动(HIT / CONFIRM) - → 玩家指令阶段:左右滑切指令(播指令名)→ 单击执行 → 播攻击音 + 敌方受击音 - → 敌方血量档位播报仅在上滑查询时播;半血/死亡自动播叙事短句 - → 循环。玩家 HP≤0 → 战败旁白 → 章首重生(等级金币保留,hp 回满) +maxHp = 20 + 8*(lv-1) +atk = 5 + 2*(lv-1) + 武器atk +def = 护甲def +mp = 3 + lv // 破空斩耗 3,伤害 atk*2 +expToNext = lv * 20 +dmg = max(1, round((atk攻 - def守) * (0.85~1.15))) +格挡: round(dmg * 0.3);防御指令: round(dmg * 0.5) ``` -- 敌方前摇音 = 敌人表"前摇音特征"列的 sfx(每敌人一个专属前摇音,这是"听声辨招"的核心资产,优先级高于环境音)。 -- 内劲技能"破空斩":atk*2,mp-3,mp 不足时指令播报"内劲不足"并跳过该选项。 +敌人 HP/ATK/DEF/EXP/金币/前摇特征:见 chapter1 §4.2 原表(野狼 10/4/0 … 白无瞳 110/18/5)。装备价格与听风剑窗口 +0.3s 不变。平衡校验(lv3 铁剑打 Boss 会格挡能过)不变。 -## 9. 资产清单(规模核对) +第三章无声前摇:**系统 danger earcon 标识窗口**;iOS 无震时 earcon + 回执足够完成「点一下」格挡。防御指令是不看时机的兜底,对所有平台必要,不是 iOS 专享补丁。 -| 类别 | 数量 | 备注 | -|---|---|---| -| bgm | 5 | 3 章环境底各 1 + 战斗 1 + Boss 1(Mureka,循环) | -| sfx | ~35 | 8 敌人前摇 + 3 Boss 前摇 + 攻击/受击/格挡/技能/买药/开箱/升级 + 8 场景 amb 循环(或 amb 走 bgm 层) | -| voice | ~120 句 ×2 音色 | 旁白 ~30、NPC ~40、教学 ~15、战斗播报 ~20、状态/菜单 ~15;全部整句、角色 speaker+instruct 常量化 | -| 对标 | escape 现版约 40 句语音 + 15 sfx;《剑与声》约为其 3 倍,R4 备资产 + R5 开发一轮可行(场景/敌人/指令全部数据驱动,game.js 单文件预计 800-1000 行) | +--- -## 10. 开发次序建议(R5 一轮内) +## 4. 场景结构(保留) -1. 数据表(场景图/敌人/物品/对话 id)→ 2. 探索循环 + 菜单 + 状态播报 → 3. 战斗回合机(含格挡窗口)→ 4. 商店/道具/升级 → 5. 存档读写 → 6. 第一章内容灌入 → 7. 第二、三章灌入 → 8. 教学关与打磨。资产(R4 已生成)缺失时用 console.error 静默跳过的引擎特性兜底,先通流程后补音。 +有向节点图,明雷。第一章 8 节点表(village…boss1)见 chapter1 JSON。二章毒状态/蛊师先攻;三章黑暗战斗铺垫终 Boss。岔路、商店、休息点保留。 + +--- + +## 5. 第一章内容与教学(手势改写) + +场景列表、对白功能大纲、村长/猎人/寨主台词意图:**保留**(正文仍 ≤25 字/句,指令 ≤12 字)。 + +教学: + +- 场景 1:左右滑+单击,商店买一次(50 金启动)。 +- 场景 3:左右滑选岔路。**不教双击重听**;重听=阶段 3 或上滑状态/长按加速旁白中的跳句。 +- 场景 4 教学战:HP 锁 1。顺序见 11 号四步,其中格挡步改为「点一下挡住」。 + +--- + +## 6. 存档(保留结构,chapter1 为准) + +`chapter/scene/player/flags/chapters/stats` + 必填 `summary`。保存时机不变。新游戏清空 data 保留 stats。 + +建议 `stats.battlesWon` 供 11 号保护档使用(若 chapter1 未列则实现时补字段,不改其它数值)。 + +--- + +## 7. 战斗时序(格挡改单击) + +``` +回合开始 + → 玩家先攻(第一章):PLAYER_SELECT(turn_player + 等待声或指令名) + 左右滑切指令,单击执行 + → CHECK_ENEMY_DEAD + → ENEMY_TELEGRAPH(ACTION,1.0s):tel_* 或 danger earcon + 窗口内单击 或 同向滑 → 格挡 + → ENEMY_RESOLVE → 玩家 HP 检定 + → 循环 +HP≤0 → 败句 → 章首 +``` + +蛊师等先攻:B_START 后先 TELEGRAPH。半血叙事钩子保留。破空斩 mp 不足留在选指令。 + +--- + +## 8. 资产规模(保留) + +BGM 以 10 号为准:menu / explore / village / battle / boss / ending。amb 走 sfx 层循环(addendum)。sfx ~35、语音 ~114 见台词表。`ui_deny` 不做,回执走引擎。 + +--- + +## 9. 开发次序(保留) + +数据表 → 探索循环 → 战斗机(格挡=窗口单击)→ 商店升级 → 存档 → 灌第一章 → 二/三章 → 打磨。 + +--- + +## 10. 废止清单 + +| 废止 | 改为 | +|---|---| +| 双击=格挡;双击=重听场景 | 窗口单击格挡;新手双击当单击 | +| 无声敌人只靠震动格挡;iOS 不可格挡 | danger earcon + 回执;防御指令全平台兜底 | +| 开场先讲完规则再玩 | 第一句就滑选项 | +| 菜单单击循环+双击确认 | 与探索同构:左右滑+单击 | +| 看门狗原样重播帮助长句 | 三档提示 | +| 失败重开整章教学 | 就近:教学该步;战败回村 | +| 无效只震 | 回执必响 | + +故事、节点、数值、装备、Boss 阶段:**保留**。 diff --git a/docs/design/10-feedback2-state-bgm.md b/docs/design/10-feedback2-state-bgm.md index c3350860..41e79ece 100644 --- a/docs/design/10-feedback2-state-bgm.md +++ b/docs/design/10-feedback2-state-bgm.md @@ -1,93 +1,96 @@ -# 10 · 全局"状态即音乐"规范(state-driven BGM,音频总监生产依据) +# 10 · 全局「状态即音乐」规范(26 号对齐) -> **给音频总监的生产摘要**:本轮新增 7 条 BGM——每游戏一条 `bgm/menu`(6 条)+ RPG 恢复 `bgm/explore`(1 条)。各游戏 prompt 草稿见 §8。所有新 BGM:60-90s 无缝循环、-20 LUFS、ogg+m4a 双格式、归一化工艺沿用现有管线。 -> **给开发的摘要**:状态→BGM 映射表见 §2-§7(逐游戏)。切换全部走引擎 `play('bgm/...')` 交叉淡入(默认 800ms),特例见各表"切换"列。`bgm=0` 设置时全部静默(含 menu pad)。RPG 的探索 amb 环境音保持在 sfx 层 loop(05-rpg-addendum §3 不变),**BGM 层专用于状态音乐**——两层叠加正是"听 BGM 辨状态、听 amb 辨场景"的设计意图。 +> **给音频总监**:状态 BGM 身份仍要做。菜单 pad 生产要求不变。新增一条硬约束:语音整条链期间 BGM 保持压低,句间不得抬回来。 +> **给开发**:状态→BGM 映射表 §2~§7。切换走 `play('bgm/...')` 交叉淡入。`bgm=0` 时全部静默(含 menu pad)。**等待声不是 BGM**,由发言权管理器合成,不受 `bgm` 设置关闭——`bgm=0` 时玩家仍要听得出「轮到你了」。 + +交互权威:[26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 原则 1、10、§4.1、§4.11。 --- -## 1. 总则 +## 1. 总则(26 号修订) -1. **每个可感知状态都要有 BGM 身份**:菜单/结算 = `bgm/menu`(轻 pad);游玩 = 各游戏既有主题曲;战斗/Boss(RPG)= 专属曲。玩家闭上眼只听 2 秒音乐,就该知道自己在菜单里还是在游戏里。 -2. **menu pad 的共性要求**:音量基底 -24 LUFS(比主题曲低一档)、无鼓点或极弱脉冲、和声静止(pad 性质)、长度 ≥60s 无缝循环。它是"系统界面"的声音,不能抢戏。 -3. **simon 特例**:游玩/教学/序列播放**全程无 BGM**(用户明确要求,02 文档既定)。只在菜单与结算挂 `bgm/menu`,且以 volume 0.3 播放(比别的游戏更轻)。 -4. 切换时机一律在**状态进入时**(不是退出时);语音 ducking 行为不变(voice 播放时 BGM duck 到 20%)。 -5. `bgm=0`(设置关闭)时:所有状态音乐不加载不播放;RPG 场景 amb(sfx 层)不受影响;space 的 `bgm/engine` 属内容音?——**裁定:engine 是 BGM 层资产,bgm=0 时静默**,盲航的方位信息全部由 whoosh/alarm(sfx)承载,无信息损失。 +1. **玩家只需要听出两种基本状态**:游戏在说 vs 轮到你。BGM 负责宏观场景/紧张度;「轮到你」靠交接提示音 + 等待声,不靠切一首新 BGM。 +2. **每个可感知宏观状态仍要有 BGM 身份**(菜单/探索/战斗/结算),闭眼 2 秒可辨。但新手期最多教 3 种状态声(含 earcon),教过才算存在。 +3. **menu pad**:-24 LUFS、无鼓或极弱脉冲、和声静止、≥60s 无缝循环。系统界面声,不抢戏。 +4. **simon 特例保留**:对局/教学全程无 BGM;只在菜单与结算挂 `bgm/menu`(volume 0.3)。对局的「轮到你」完全靠等待声与音色回声。 +5. 切换时机:状态**进入时**切,不是退出时。 +6. **整链压低**:voice 播放时 BGM duck 约 12dB(原 20% 增益口径可沿用)。游戏层用 `onComplete` 串句时,句间不得 `_unduckBGM`。Floor 持有一次压低,或 `audio.holdDuck(true/false)`。 +7. `bgm=0`:不加载任何 `bgm/`。RPG 场景 amb(sfx 层)不受影响。space 的 `bgm/engine` 仍算 BGM 层,关闭后方位信息由 whoosh/alarm(sfx)承担。等待声、回执、earcon 仍在。 +8. 同一时间能听清的声音流最多 2 条。BGM + 语音已经占满时,不要再叠第三条语言。 ## 2. storyline《声命线》 | 状态 | BGM | 切换 | |---|---|---| -| 剧情播放(含抉择) | `bgm/dark_drone`(保留) | 开局即播,全剧不切 | -| 菜单(下滑呼出) | `bgm/menu` | 进菜单 crossfade 500ms;关菜单淡回 dark_drone 800ms | -| 结局结算 | 保持 `bgm/dark_drone` 不切 | 结局语音 duck 即可,氛围连贯 | +| 听讲 / 抉择 | `bgm/dark_drone` | 开局即播,全剧不切 | +| 菜单 | `bgm/menu` | 进菜单 500ms;关菜单淡回 800ms | +| 结局结算 | 保持 dark_drone | 结局语音 duck 即可 | + +抉择等待:不切 BGM。交出话轮后起等待声。 ## 3. escape《暗室》 | 状态 | BGM | 切换 | |---|---|---| -| 引导 + 自由探索 | `bgm/drone`(保留) | 不变 | -| 菜单(ENDING 下滑呼出) | `bgm/menu` | 进菜单 crossfade 400ms | -| 逃脱结算(ENDING) | `bgm/menu`(volume 0.5) | 现有 `stopBgm(2500)` 淡出 drone 后,escape 语音播完淡入 menu pad 1500ms——结算画面从"空洞的静"变成"松一口气的亮" | +| 引导 + 探索 | `bgm/drone` | 不变。探索期环境音常驻(ACTION),等待声可更轻或由房间 amb 承担「还活着」 | +| 菜单 | `bgm/menu` | 进菜单 400ms | +| 逃脱结算 | `bgm/menu` volume 0.5 | drone 淡出后,escape 语音完再淡入 menu 1500ms。禁止结算黑屏寂静 | -## 4. simon《音序回响》(特例) +## 4. simon《音序回响》 | 状态 | BGM | 切换 | |---|---|---| -| 教学 / 对局全流程 | **无**(静默) | 不加载任何 bgm | -| 菜单(结算后下滑) | `bgm/menu`(volume 0.3) | 淡入 600ms | -| 结算(gameover) | `bgm/menu`(volume 0.3) | sfx/gameover 播完 1s 后淡入 1000ms | -| 单击重开 | 停 menu pad | `stopBgm(300)`,回静默 | +| 教学 / 对局 | **无** | 不加载 | +| 菜单 / 结算 | `bgm/menu` 0.3 | 淡入 600~1000ms | +| 单击重开 | 停 menu | `stopBgm(300)` 回静默 | -## 5. rhythm《节拍行者》 +听序列 = BUSY(busy 回执);答题 = YOUR_TURN(等待声)。不要用 BGM 区分这两态。 + +## 5. rhythm《节拍行者》/ 鼓手模式 | 状态 | BGM | 切换 | |---|---|---| -| 曲目游玩(含教学) | `bgm/track90/105/120`(不动) | 现有手动网格排程不变 | -| 选曲界面 | `bgm/menu` | 进选曲淡入 800ms;选定曲目后 stopBgm(400) 再按现有网格起曲目 BGM | -| 模态菜单(暂停) | 现有断点续播机制**保留**,menu pad **不叠**——暂停 = 音乐停(实现已如此,是最干净的"暂停感") | 不变 | -| 结算 | 曲目 BGM 现有 0.6s 淡出后,评级语音播完淡入 `bgm/menu` 800ms | 新增 | +| 曲目游玩(含教学) | 曲目 BGM(网格排程) | 不变。演奏期 = ACTION,不起等待声 | +| 选曲 | `bgm/menu` | 进选曲 800ms;选定后停 pad 再起曲目 | +| 暂停菜单 | 音乐停,不叠 menu pad | 恢复必须 count-in(26 原则:暂停恢复后第一拍不能注定失败) | +| 结算 | 曲目淡出后淡入 `bgm/menu` | 新增保留 | ## 6. space《盲航》 | 状态 | BGM | 切换 | |---|---|---| -| 演练 + 对局 | `bgm/engine`(保留) | 不变 | -| 模态菜单(暂停) | `bgm/menu` | 进菜单 crossfade 300ms(暂停要快);回对局淡回 engine 500ms | -| 坠毁结算 | 现有 stopBgm(600) 后,结算语音链播完淡入 `bgm/menu` 800ms | 新增 | +| 演练 + 对局 | `bgm/engine` | 对局 = ACTION | +| 暂停菜单 | `bgm/menu` | 进 300ms,回 engine 500ms | +| 坠毁结算 | 停 engine 后结算链完淡入 menu | 禁止坠毁后死寂 | -## 7. rpg《剑与声》(重点:听觉状态语言) +## 7. rpg《剑与声》 -用户要求"通过背景音乐判断当前处于菜单/地图/战斗状态"。RPG 的状态语言 = **BGM 层报状态 + sfx 层 amb 报场景**(双层各司其职): +BGM 报宏观状态,amb(sfx 层)报场景: -| 状态 | BGM 层 | sfx 层 amb | 听感设计 | +| 状态 | BGM | sfx amb | 听感 | |---|---|---|---| -| 菜单(模态) | `bgm/menu` | amb 继续(音量不变) | "游戏还在,界面开了" | -| 地图/探索 | `bgm/explore`(**恢复**,volume 0.35 轻垫) | `amb_` 主角 | 音乐是"底",环境音是"景" | -| 村庄(安全区特例) | `bgm/village`(保留) | amb_village(bgm=0 时的垫底) | 温暖,与探索的紧张对比 | -| 普通战斗 | `bgm/battle`(保留) | amb 停 | 进战斗 crossfade **400ms**(战斗要快切, urgency) | -| Boss 战 | `bgm/boss`(保留) | amb 停 | 同上 400ms | -| 战斗胜利 → 探索 | 关键过渡:`sfx/victory`(胜利标识音,见 11 文档 §5)→ battle/boss **淡出 1500ms** → `bgm/explore` **淡入 2000ms** + amb 恢复 | — | "从紧绷到松弛"是可听的,这是用户要的过渡 | -| 战败 → 章首 | `bgm/battle` stopBgm(2000) → `voice/btl_lose` → village:bgm/village 淡入 1500ms | — | 重生即安全区音乐 | -| 章结算/剧情(evt_ch*_end) | `bgm/ending`(保留) | amb 停 | 播完淡回 explore | +| 菜单 | `bgm/menu` | amb 继续 | 游戏还在,界面开了 | +| 探索 | `bgm/explore` 0.35 | `amb_` | 底 + 景 | +| 村庄 | `bgm/village` | amb_village | 安全 | +| 战斗 | `bgm/battle` | amb 停 | 进战斗 400ms | +| Boss | `bgm/boss` | amb 停 | 400ms | +| 胜 → 探索 | victory sfx → battle 淡出 1500ms → explore 淡入 2000ms + amb | 可听的松弛 | +| 败 → 章首 | battle 停 2000ms → 败句 → village 1500ms | 重生即安全 | +| 章结算 | `bgm/ending` | amb 停 | 完再回 explore | -**裁定(修订 05-rpg-addendum §3)**:addendum 当时取消了 `bgm/explore`(探索期纯 amb);按用户本轮反馈恢复 explore 作为低音量状态垫乐(volume 0.35),amb 仍在 sfx 层不受影响。BGM 需求回到 5 首(village/explore/battle/boss/ending)+ menu。 +玩家回合等待:battle BGM 不切,起等待声(或指令菜单循环的报名本身即「轮到你」)。 -## 8. 新 BGM 生产清单(Mureka prompt 草稿) +## 8. new8 / hunt / 其他 -共性后缀:`", ambient pad, very sparse, no drums or minimal pulse, static harmony, calm, no vocals, seamless loop, 60-90 seconds"`。 +各游戏自己的状态 BGM 表仍有效,但必须满足 §1:整链 duck、等待声不走 BGM 层、新手期不新教第 4 种状态音乐。 -| 资产 | prompt 草稿(英文) | -|---|---| -| storyline `bgm/menu` | "Dark post-apocalyptic menu pad, low warm drone with faint radio static texture, sibling mood to a dark ambient drone track" + 共性 | -| escape `bgm/menu` | "Underground facility menu pad, soft low hum with distant water drip feel, slightly hopeful resolution tone" + 共性 | -| simon `bgm/menu` | "Minimal memory-game menu pad, gentle mallet harmonics over soft sine pad, meditative, extremely light" + 共性 | -| rhythm `bgm/menu` | "Electronic music-menu pad, slowed-down synthwave chords at 90 bpm feel, airy, between-songs breather" + 共性 | -| space `bgm/menu` | "Space cockpit menu pad, soft engine-room hum with airy synth wash, weightless" + 共性 | -| rpg `bgm/menu` | "Wuxia menu pad, single sustained guzheng harmonic over warm drone, ink-painting stillness" + 共性 | -| rpg `bgm/explore` | "Wuxia exploration ambient music, sparse guzheng and dizi fragments over low forest drone, 60 bpm, mysterious calm, no vocals, seamless loop, sits quietly under ambience"(轻垫乐,混音时音量再降) | +## 9. 生产清单 -## 9. 验收点 +menu pad 与 rpg `bgm/explore` 的 Mureka prompt 草稿沿用原文(暗色无人机 / 地下设施 / 记忆游戏 / synthwave / cockpit / 武侠古琴)。新游戏的 menu pad 同样遵守 §1.3 共性。 -- 每游戏闭眼测试:菜单开/关、战斗进/出(RPG)仅靠 BGM 可辨。 -- `bgm=0.3`(小声)与 `bgm=0`(关闭)两档在各状态切换处不破音、不残响(引擎 crossfade 与增益总线叠加正确)。 -- menu pad 与主题曲同调性优先(storyline/escape/rpg 的 menu 与主题曲同 key;simon/space 无强约束)。 +## 10. 验收 + +- 闭眼:菜单 vs 游玩 2 秒可辨。 +- 旁白两句之间,BGM **不得**突然变响(整链 duck)。 +- `YOUR_TURN` 时,即使 `bgm=0`,也能听到等待声或交接提示音。 +- `bgm=0.3` / `0` 切换不破音。 diff --git a/docs/design/11-rpg-battle-rebalance.md b/docs/design/11-rpg-battle-rebalance.md index 8dc454f5..db94bd6d 100644 --- a/docs/design/11-rpg-battle-rebalance.md +++ b/docs/design/11-rpg-battle-rebalance.md @@ -1,98 +1,136 @@ -# 11 · 《剑与声》战斗重构规范(rpg battle rebalance) +# 11 · 《剑与声》战斗重构规范(26 号对齐) > **给开发的实现要点摘要** -> 1. 首战教学化:教学战从"学格挡+学攻击"扩为**四步强制分步**(认识指令→格挡→攻击→防御),教学战敌人 ATK 降为 2 且玩家 HP 锁底 1(不死)。§2。 -> 2. 新手难度保护:按**已完成战斗场数**分两档——第 1-3 场前摇 1.5s、敌方伤害 ×0.6;第 4-5 场过渡(1.25s、×0.8);第 6 场起恢复正常(1.0s、×1.0)。判定依据 `stats.battlesWon`,与等级无关(等级通过数值自然体现)。§3 数值表。 -> 3. 逃跑新手保护:Lv<3 逃跑 100% 成功;Lv≥3 普通战斗逃跑 70%,失败代价 = 敌方一个正常回合(有前摇、可格挡,不白打)。Boss/剧情战维持不可逃。§4。 -> 4. 战斗四状态声音标识:战斗开始 / 玩家回合 / 敌方前摇 / 胜利各有专属 sfx(新增 2 个:`turn_player`、`victory`)。§5。 -> 5. 现状衔接:现版(web/rpg/game.js)前摇固定 1.0s(+听风剑 0.3s)、逃跑 flee 布尔、教学战两步。本规范是**参数化 + 步骤扩展**,不改状态机骨架(05-rpg-chapter1 §3 的 10 状态转换表不变)。 +> 1. 教学战四步强制分步:**认识指令 → 攻击 → 格挡(窗口单击)→ 防御**。教学狼 ATK=2,玩家 HP 锁底 1。交互权威 26 号:格挡**不得**教双击。 +> 2. 新手保护按 `stats.battlesWon`:前 3 场前摇 1.5s、伤害 ×0.6;第 4–5 场 1.25s、×0.8;第 6 场起 1.0s、×1.0。Boss 始终标准档。数字保留。 +> 3. 逃跑:Lv<3 普通战 100%;Lv≥3 为 70%,失败走可格挡前摇。Boss/剧情不可逃。 +> 4. `turn_player` / `victory` 状态音保留;玩家回合另起等待声或靠指令名。回执必响。 +> 5. 状态机骨架仍是 chapter1 10 状态;只改格挡输入与教学台词口径。 --- -## 1. 现状问题(对应用户反馈) +## 0. 设计单(战斗切片) -- "第一次进战斗必须说明清楚交互方式":现教学战只有格挡(tut_b1/b2)+ 攻击(tut_b3)两步,**防御指令和指令切换本身没教**——玩家不知道左右滑能选招式、不知道有防御。 -- "战斗太难、要循序渐进":现版所有战斗前摇恒 1.0s、敌人满伤害,第一场正式战斗(enc_wolf)和 Boss 战用同一反应标准;逃跑非 Boss 必成但没有"失败"概念,缺少梯度。 +**核心动作(战斗)**:左右滑选招,单击出手;听到前摇,再点一下挡住。 -## 2. 首战教学化(enc_teach_wolf 重排为四步) +**三状态**:选指令 `YOUR_TURN`(`turn_player`);前摇 `ACTION`(`tel_*` 或 danger earcon);胜负旁白 `SPEAKING`。 -教学战流水(teachStage 状态扩为 4 段,每段旁白引导 → 玩家亲手执行 → 确认后进下一段;每段 8s 无操作给一次重复提示,绝不卡死): +**矩阵 / 失败表 / 首 3 分钟**:见 05 号 §0。本文件规定教学逐步通过条件与数值档。 -| 步 | stage | 旁白(新增语音 id,台词表见 §6) | 玩家动作 | 通过条件 | +--- + +## 1. 现状问题(仍成立,解法已换手势) + +- 指令切换、防御没教。 +- 第一场正式战与 Boss 同一反应标准过难。 +- 旧教学把格挡绑双击,违反新手手势宪法。 + +--- + +## 2. 首战教学化(enc_teach_wolf 四步) + +每段:旁白 → 玩家亲手做 → 确认进下一段。静默走三档提示(完整→短→earcon),**不原样复读长句**。绝不卡死:3 次失败演示通过。 + +| 步 | stage | 旁白意图(改录前可沿用 id、改词) | 玩家动作 | 通过 | |---|---|---|---|---| -| T0 | `menu` | `tut_b0`:"战斗里,左右滑选招式,单击出手。先试试,滑一滑。" | 左右滑 ≥2 次(听到指令名切换) | 切换 2 次 | -| T1 | `attack` | `tut_b3`(现有):"轮到你了。左右滑选攻击,单击出剑。" | 选中攻击并单击 | 攻击命中一次 | -| T2 | `parry` | `tut_b1`+`tut_b2`(现有):"它要扑了,听前摇!""就是现在,双击格挡!" | 前摇内双击 | 格挡成功或受击均可通过(受击掉血,教学点已到) | -| T3 | `defend` | `tut_b6`(新增):"来不及格挡,就选防御。选防御,单击。" | 选中防御并单击 | 执行防御一次(敌方攻击减伤 50% 演示) | -| T4 | — | `tut_b5`(现有):"好剑法!接下来,靠你自己了。" | 自由击杀 | 敌亡 | +| T0 | `menu` | `tut_b0`:战斗里左右滑选招,单击出手。听到 `turn_player`,就是你的回合。先滑一滑。 | 左右滑 ≥2 | 切 2 次 | +| T1 | `attack` | `tut_b3`:选攻击,单击出剑。 | 攻击命中一次 | 出剑 | +| T2 | `parry` | `tut_b1`+`tut_b2`:**它要扑了,听前摇。就是现在,点一下挡住。** | 前摇内单击(或同向滑) | 格挡成功**或**受击均通过(锁血) | +| T3 | `defend` | `tut_b6`:来不及挡,就选防御。选防御,单击。 | 执行防御一次 | 减伤 50% 演示 | +| T4 | — | `tut_b5`:好剑法。接下来靠你自己。 | 自由击杀 | 敌亡 | -- 教学战参数:teach_wolf **ATK 3→2**、HP 6 不变、前摇 **1.8s**(比正常宽 80%)、玩家 HP 锁底 1(不死,现版已有 clamp)、不可逃(现有)。 -- 顺序理由:先会"怎么选"(T0)再会"打"(T1)再会"保命"(T2/T3);格挡放在攻击后是让它成为"回合交换"的一部分而非孤立动作。 +- 教学狼:**ATK 3→2**、HP 6、前摇 **1.8s**、HP 锁底 1、不可逃。 +- 顺序:先会选,再会打,再会挡与防。 +- `tut_b2` 旧文「双击格挡」**废止**;实现改播「点一下挡住」。不改 voice-ids 文件,改引用或待重录。 -## 3. 难度曲线(新手保护分档) +--- -保护档位由 `stats.battlesWon`(已完成战斗场数)决定,**每场战斗开始时取档**: +## 3. 难度曲线(数字保留) -| 档 | battlesWon | 敌方前摇 | 敌方伤害系数 | 说明 | -|---|---|---|---|---| -| 教学 | 教学战 | 1.8s | ATK 2 特例 | §2 | -| 保护 | 0-2(前 3 场) | **1.5s** | **×0.6**(向下取整,min 1) | 反应窗口宽 50%,掉血痛感低 | -| 过渡 | 3-4(第 4-5 场) | 1.25s | ×0.8 | 渐回标准 | -| 标准 | ≥5(第 6 场起) | 1.0s(听风剑 +0.3s) | ×1.0 | 现版数值 | +每场开始时按 `stats.battlesWon` 取档: -### 第一章遭遇战数值表(最终版,含推荐等级) +| 档 | battlesWon | 敌方前摇 | 伤害系数 | +|---|---|---|---| +| 教学 | 教学战 | 1.8s | ATK 2 特例 | +| 保护 | 0–2 | **1.5s** | **×0.6**(向下取整,min 1) | +| 过渡 | 3–4 | 1.25s | ×0.8 | +| 标准 | ≥5 | 1.0s(听风剑 +0.3s) | ×1.0 | -| 遭遇战 | 敌人 | HP | ATK(标准) | 前摇 | 经验/金币 | 推荐等级 | 说明 | +### 第一章遭遇(最终版) + +| 遭遇 | 敌人 | HP | ATK(标准) | 前摇 | 经验/金币 | 推荐等级 | 说明 | |---|---|---|---|---|---|---|---| -| enc_teach_wolf | 教学野狼 | 6 | 2(教学特例) | 1.8s | 5 / 0 | Lv1 | 四步教学,锁血不死 | -| enc_wolf | 野狼 | 10 | 4(保护档实际 2) | 按档位 | 8 / 5 | Lv1 | 可逃;保护档内打它=2 伤/刀 | -| enc_bandits | 山贼 ×2 连战 | 16+16 | 6(过渡档实际 4) | 按档位 | 24 / 24 | Lv2 | 不可逃;连战不回血 | -| enc_elite | 寨丁头目 | 24 | 8 | 标准 | 20 / 25 | Lv2-3 | 强攻线;半血有狠话 | -| enc_sneak | 山贼+毒蛛 | 16+12 | 6/7 | 标准 | 24 / 20 | Lv2 | 绕行线;胜后进 Boss 前回满 | -| enc_boss1 | 黑风寨主 | 45 | 10(半血 12) | 标准 | 50 / 100 | **Lv3** | 不可逃;进 Boss 战前强制提示(§4) | +| enc_teach_wolf | 教学野狼 | 6 | 2 | 1.8s | 5 / 0 | Lv1 | 四步;锁血;格挡=单击 | +| enc_wolf | 野狼 | 10 | 4(保护档实际 2) | 按档 | 8 / 5 | Lv1 | 可逃 | +| enc_bandits | 山贼 ×2 | 16+16 | 6(过渡实际 4) | 按档 | 24 / 24 | Lv2 | 不可逃 | +| enc_elite | 寨丁头目 | 24 | 8 | 标准 | 20 / 25 | Lv2–3 | 强攻线 | +| enc_sneak | 山贼+毒蛛 | 16+12 | 6/7 | 标准 | 24 / 20 | Lv2 | 绕行;胜后回满 | +| enc_boss1 | 黑风寨主 | 45 | 10(半血 12) | **始终标准** | 50 / 100 | **Lv3** | 不可逃 | -等级节奏校验:教学狼 5 + 野狼 8 + 山贼 24 + 头目/偷袭 20-24 + 沿途 ≈ 60 exp 到 Boss 前 → Lv3(20+40=60 正好升 3)。Lv3 玩家(HP36、铁剑 atk12、皮甲 def2):保护档已过,Boss 10-2=8~11 伤/刀,可吃 4 刀;格挡成功 2-3 伤。会格挡=稳过,不格挡=紧过,符合"技巧有收益"原则。 +等级节奏:教学 5+野狼 8+山贼 24+头目/偷袭 20–24≈60 exp → Boss 前 Lv3。Lv3 铁剑皮甲:会格挡稳过,不格挡紧过。 -**Boss 保护例外**:enc_boss1 始终标准档(battlesWon 再多也不再放宽;battlesWon 不足 5 时打 Boss 也不享受 1.5s 前摇——Boss 是毕业考)。实现:`enc.boss` 已存在,取档时 `boss ? 标准 : 按 battlesWon`。 +`enc.boss === true` 时取标准档,不享受 1.5s。 -## 4. 逃跑规则 +--- -| 玩家等级 | 普通战斗 | Boss/剧情战(flee:false) | +## 4. 逃跑规则(保留数字) + +| 玩家等级 | 普通战斗 | Boss/剧情 `flee:false` | |---|---|---| -| Lv < 3 | **100% 成功**(新手保护) | 不可逃(btl_noflee) | -| Lv ≥ 3 | 70% 成功;失败:播 `btl_flee_fail`(新增)→ 敌方直接进入正常前摇回合(**可格挡**,不白打) | 不可逃 | +| Lv<3 | 100% 成功 | `btl_noflee` | +| Lv ≥ 3 | 70%;失败 `btl_flee_fail` → `ENEMY_TELEGRAPH`(**可单击格挡**) | 不可逃 | -实现:现版 `_fleeCheck` 是 flee 布尔直出;改为 `{ allowed, rate }`:`allowed=false` 播 btl_noflee;`rate = lv<3 ? 1.0 : 0.7`,失败走 ENEMY_TELEGRAPH。 +首次进 enc_boss1 且 Lv<3:插播 `boss1_warn`(回村练练也来得及)。gate 的 back 仍在。 -**进 Boss 战提示**:首次进入 enc_boss1 且 Lv<3 时,门前插播 `boss1_warn`(新增):"里面就是黑风寨主。你现在回去练练再来,也来得及。"——给低等级玩家一个知情回头机会(gate 场景 back 选项本来就在)。 +--- -## 5. 战斗状态声音标识(听觉状态语言的战斗部分) +## 5. 战斗状态声音 -| 状态 | 声音标识 | 资产 | +| 状态 | 声音 | +|---|---| +| 战斗开始 | `bgm/battle` 400ms + `btl_start_` | +| 玩家回合 | `sfx/turn_player`(≤0.4s)+ 等待声或指令名循环 | +| 敌方前摇 | `tel_*` 或 danger earcon | +| 胜利 | `sfx/victory`(≤1.2s)+ BGM 1.5s 出 / 2s 入 explore | +| 战败 | `hurt` + `btl_lose` + battle 2s 淡出 | + +`turn_player` 于 PLAYER_SELECT 进入时播;与前摇/指令语音留 200ms 间隙。T0 用它当「轮到你」锚点。 + +--- + +## 6. 新增语音(台词意图;×2 性别) + +| id | 全文(26 号口径) | 用途 | |---|---|---| -| 战斗开始 | `bgm/battle` 400ms 切入(10 文档 §7)+ 现有 `btl_start_` | 已有 | -| **玩家回合开始** | 新增 `sfx/turn_player`:一声轻亮的剑鸣/木鱼短音("轮到你"的听觉标点,每回合都出现,必须极短 ≤0.4s、不烦) | **新增(程序合成即可,上行双音)** | -| 敌方前摇 | 现有 `tel_` + 窗口开启 `beat` 轻震(addendum §1) | 已有 | -| **胜利** | 新增 `sfx/victory`:3 音上行短号角(≤1.2s),随后 BGM 过渡(10 文档 §7 的 1.5s 出/2s 入) | **新增(SA3:"triumphant short brass fanfare, 3 rising notes, wuxia style, under 1.5 seconds")** | -| 战败 | 现有 `hurt` + `btl_lose` + battle BGM 2s 淡出 | 已有 | +| `tut_b0` | 战斗里,左右滑选招式,单击出手。听到这个声音,就是你的回合。先滑一滑。 | T0 | +| `tut_b2` | 就是现在,点一下挡住。 | T2(替换双击句) | +| `tut_b6` | 来不及挡,就选防御。左右滑,选防御,单击。 | T3 | +| `btl_flee_fail` | 没甩掉!它追上来了。 | 逃跑失败 | +| `boss1_warn` | 里面就是黑风寨主。你现在回去练练再来,也来得及。 | 低级进门 | -`turn_player` 在每回合 PLAYER_SELECT 进入时播;教学战 T0 的"指令切换"教学正好以它为锚("听到这个声音,就是你的回合"——写进 `tut_b0` 台词)。 +instruct 沿用 ch1。若资产仍是旧「双击格挡」句:游戏层改播新 id 或临时改词,**不在本次改 voice-ids 文件**。 -## 6. 新增语音(补录清单,×2 性别,交音频总监) +--- -| id | 台词全文 | 用途 | -|---|---|---| -| `tut_b0` | 战斗里,左右滑选招式,单击出手。听到这个声音,就是你的回合。先滑一滑,试试。 | 教学 T0("这个声音"= turn_player,旁白落音后 0.5s 播 turn_player 示范) | -| `tut_b6` | 来不及格挡,就选防御。左右滑,选防御,单击。 | 教学 T3 | -| `btl_flee_fail` | 没甩掉!它追上来了。 | Lv≥3 逃跑失败 | -| `boss1_warn` | 里面就是黑风寨主。你现在回去练练再来,也来得及。 | Lv<3 首次到 boss1 | +## 7. 验收 -instruct 沿用 ch1 台词表常量(female=Serena / male=Uncle_Fu,讲武侠故事/说书人)。 +- 首战:乱点也不死(锁底 1);四步都能被迷茫/看门狗推进。 +- 格挡课:**单击**通过;双击因当单击也可通过,但不得把「请双击」写进提示。 +- 第 2 场野狼每刀 ≤2、前摇可听变慢。 +- Lv2 逃跑必成;Lv3 后失败可格挡。 +- Lv2 到 Boss 门前听到 `boss1_warn`。 +- `turn_player` 每回合出现且不与前摇叠。 +- 无声操作 0;iOS 无震时格挡仍有 tel/earcon。 -## 7. 验收点 +--- -- 首战:不做任何正确操作也不死(锁底 1);四步每步都能被看门狗兜底推进。 -- 保护档实测:第 2 场战斗野狼每刀 ≤2 伤、前摇明显变慢(可听)。 -- Lv2 逃跑必成 ×10 次;Lv3 后逃跑出现失败且失败后可格挡。 -- Lv2 到 Boss 门前听到 `boss1_warn`;Lv3 到则不播。 -- `turn_player` 每回合出现且不与前摇音/指令语音叠放(回合切换处留 200ms 间隙)。 +## 8. 废止 + +| 废止 | 改为 | +|---|---| +| T2「双击格挡」 | 窗口单击;同向滑冗余 | +| 教学复读同一句说明书 | 三档提示 + 第 3 次演示 | +| 逃跑失败「白打」不可挡 | 失败进入可格挡前摇 | +| 状态只靠震 | `turn_player` + 等待声/指令名 + 回执 | + +保护档数字、遭遇表、Boss 例外、`turn_player`/`victory` 资产需求:**保留**。 diff --git a/docs/design/12-skip-and-longpress-ux.md b/docs/design/12-skip-and-longpress-ux.md index 6e965f5e..aae0d87a 100644 --- a/docs/design/12-skip-and-longpress-ux.md +++ b/docs/design/12-skip-and-longpress-ux.md @@ -1,10 +1,13 @@ -# 12 · 跳过旁白 + 长按加速 · 全局 UX 规范(含男女声边界核查) +# 12 · 跳过旁白、长按加速、帮助手势(26 号对齐) > **给开发的实现要点摘要** -> 1. 单击跳过当前句:§1 逐游戏裁定表——核心规则是"**voice 播放中单击=跳过本句;无 voice 时单击=原语义**",语音链衔接统一"跳下一句"(不是跳到节点末)。 -> 2. 长按加速(引擎临时 1.7× voice 语速):§2 豁免表——rhythm 演奏阶段全局豁免,其余游戏默认允许。引擎层拦截 longpress,游戏不收此手势。 -> 3. 首局提示时机:§3——不进语音资产,教学自然涵盖 + 大厅 footer 文案。 -> 4. §4 男女声边界核查结论:storyline/rpg 实际行为**符合**"只有旁白跟随设置、角色音固定",无需修复;两处命名/计数勘误。 +> 1. 单击跳过:voice 在播且本句可跳 → 跳**当前这句**(`skipVoice`,链继续)。无 voice 时单击 = 原语义。 +> 2. 跳过后 400ms 内再单击 = `busy`,不连跳两句、不误进下一段。 +> 3. 新手期无双击,**不存在「双击=重听对偶」**。重听是阶段 3 解锁,或玩家在等待点上滑/长按问「我在哪」。 +> 4. 长按:默认仍是旁白 1.7× 加速。若该游戏帮助手势是长按(四向滑动是玩法),对局中长按=帮助,关闭加速。 +> 5. 男女声边界核查结论保留在文末,无需修复。 + +交互权威:26 号原则 5、6、§4.1 打断规则;[00-global-ux.md](00-global-ux.md) §5。 --- @@ -12,82 +15,72 @@ ### 1.1 总则 -- **语境判定**:`voice` 层有音频在播时,单击 = 跳过当前句(`stopVoice()` 截断当前句,链播自然进下一句——引擎语义"被打断不触发 onComplete",因此**实现上不是 stopVoice 而是"截断当前句但保住链"**:需引擎或游戏层支持 `skipVoice()`(截断当前句并触发 onComplete 续链)。若引擎暂无此 API,游戏层用等价实现:手动推进链。以引擎最终提供的 API 为准)。 -- **无 voice 时单击 = 各游戏原语义**(检查/确认/继续),两态有明确的听觉区分(有语音在播 vs 没有),不混淆。 -- **跳到节点末 vs 跳下一句**:统一**跳下一句**。跳到节点末会让玩家误过后续信息且实现要感知"节点"边界;"逐句跳"心智最简单:不想听就多点几下。 -- 跳过不给震动(跳过是浏览操作不是确认;双击=重听的对偶操作,保持安静)。 +- 语境:`SPEAKING` 且本句非 `critical` → 单击 = `skipVoice()`(截断当前句,视为播完,链续)。给 `ok`。 +- `critical` 句:单击 = `busy`,不跳。 +- 无 voice:单击 = 各游戏原语义。 +- 统一跳下一句,不跳到节点末。 +- 跳过不给震动。 +- 连点:执行跳过或继续后 400ms 内,后续单击只 `busy`。 -### 1.2 逐游戏裁定表 +废止:靠 300ms 双击窗区分「跳过」和「重听」。新手期没有双击窗。 -| 游戏 | 阶段 | 单击=跳过本句? | 说明 | +### 1.2 逐游戏裁定 + +| 游戏 | 阶段 | 单击跳本句? | 说明 | |---|---|---|---| -| storyline | 剧情对白 | ✅ 可跳 | 节点最后一句被跳 = 进下一节点(现有"单击继续"语义自然延伸) | -| storyline | 抉择提示(prompt) | ❌ 不可跳 | 左右滑抉择前的选项说明必须听完(盲玩公平性) | -| escape | 引导 G0-G7 | ❌ 不可跳(下滑=跳整个引导,已有) | 引导句即操作指令,跳句会破坏步骤机 | -| escape | PLAY 检查描述/卡关提示/背包 | ✅ 可跳 | 熟练玩家快速刷检查 | -| escape | 关键获得(found_key/unlock_cabinet/fix_box/escape) | ❌ 不可跳 | 谜题链进展的叙事回报,总长 <10s | -| simon | 教学 T0-T7 | ❌ 不可跳(下滑=跳教学,已有) | 同 escape 引导 | -| simon | 轮次播报 round_*、成绩播报 | ✅ 可跳 | 高轮次玩家最需要 | -| simon | input 阶段 | — 无语音 | 无冲突 | -| rhythm | intro/tut/结算/选曲 | ✅ 可跳 | 演奏阶段(main)无语音,无冲突 | -| rhythm | mid_song | ❌ 不可跳(仅 2s,不值得跳) | 演奏中,手势是演奏输入 | -| space | 演练 D0-D5 | ❌ 不可跳(下滑=跳演练,已有) | 教学即玩法 | -| space | feat_boost/feat_shield 引入句 | ✅ 可跳 | 二周目熟练玩家 | -| space | 归因/保护期/结算播报 | ✅ 可跳 | | -| rpg | 场景 desc、NPC 对白、事件句 | ✅ 可跳 | 刷图玩家核心诉求 | -| rpg | 战斗指令名 cmd_*、教学战 tut_b* | ❌ 不可跳 | 指令名 <1s 且正在选指令,跳过会打乱选择节奏;教学句同引导逻辑 | -| rpg | 状态播报链(st_*) | ✅ 可跳 | 逐句跳过即"快进状态查询" | +| 叙事类(storyline / radio / abyss / detective / cipher / keeper / teahouse) | 剧情对白 | 是 | 最后一句被跳 = 进入等待点,不起连跳进下节点(400ms 防抖) | +| 同上 | 抉择提示 | 否(`critical` 或 hold) | 选项必须听完再选。单击 = 再听选项(「再说一遍:」+ 完整句) | +| escape | 引导指令 | 否 | 指令即步骤。迷茫接管后再演示 | +| escape | PLAY 描述/卡关 | 是 | | +| escape | 关键获得 | 否,且 ≤3s 标 `critical` | 旧「<10s 不可跳」改为拆短:关键句本身 ≤3s | +| simon | 教学指令 | 否 | busy | +| simon | 轮次/成绩播报 | 是 | | +| simon | 序列播放 | — 无 voice;手势 = busy | 聆听不可打断序列 | +| rhythm / drummer | intro/结算/选曲 | 是 | | +| rhythm / drummer | 演奏 | 手势是演奏,不走跳句 | ACTION | +| space | 演练指令 | 否 | | +| space | 归因/结算/引入句 | 是 | 归因是 `feedback`,玩家已操作则作废不播 | +| rpg / dungeon | 场景/NPC | 是 | | +| rpg / dungeon | 战斗指令名、教学战、格挡窗口 | 否 | | +| 全部 | 菜单项名 | 否 | 正在选 | -### 1.3 与"单击 300ms 延迟"的相容性 +### 1.3 滑动打断 -跳过是非即时操作,300ms 延迟无影响。注意双击=重听与单击=跳过的对偶关系:引擎的双击窗口保证两者不误判(现有机制天然支持)。 +游戏在说时,左/右滑(及玩法所需的四向滑)= `stopVoice()` 作废当前链,然后执行这次滑动。排队中的 `hint`/`feedback` 清空。 -## 2. 长按加速(引擎:按住 = voice 层临时 1.7×) +上滑/长按帮助:同样打断,然后播一句「我在哪」。 -**核查注记**:本规范按用户声明的既定事实("引擎已实现长按=临时 1.7 倍速")撰写;当前 `web/engine/gesture.js` 输出仍为 6 手势、未见 longpress 实现——若引擎接口与本节假设不同,**以引擎实际 API 为准**,游戏侧豁免规则不变。 +--- -### 2.1 总则 +## 2. 长按:加速 vs 帮助 -- 长按由**引擎层拦截**:按住 ≥400ms 未位移 → 当前 voice 句 playbackRate 临时 1.7(松手或句终恢复 settings.speed),**不产生手势回调**(游戏永远收不到 longpress)。BGM/sfx 不受影响。 -- 与设置语速的关系:长按倍率 = max(1.7, 当前 settings.speed) 之上的"再快一档"语义——settings.speed=1.7 时长按无效果(已是特快)。 -- 加速中的 onComplete 时序自然提前(句长缩短),语音链不断。 +### 2.1 默认:旁白加速 -### 2.2 豁免表 +voice 播放中长按 ≥450ms → 当前句临时 `max(1.7, settings.speed)`,松手恢复。BGM/sfx 不变。由引擎拦截,游戏收 `longpress` 与否见下。 -| 游戏 | 豁免 | 理由 | +### 2.2 帮助占用长按时,关闭加速 + +四向滑动是核心玩法的游戏(00 §5.3):对局与探索中长按 = 「我在哪」,`setRateBoostEnabled(false)`。菜单/结算旁白仍允许加速。 + +| 游戏 | 对局长按 | 旁白加速 | |---|---|---| -| rhythm | **main(演奏)+ tut(教学跟打)阶段全局豁免**(游戏向引擎注册关闭 rate boost) | 演奏中手指可能长按屏幕边缘蓄力;且 mid_song 加速会破坏"语音提示与节拍网格对齐"的幻觉。教学跟打同理 | -| rhythm | select/end/菜单正常允许 | 结算播报快进是刚需 | -| simon | playback(序列播放)阶段豁免 | 序列是记忆对象,虽然 tone 是 sfx 不受 rate 影响,但该阶段无 voice,豁免无成本;input/announce/结算允许 | -| space | playing 阶段豁免 | 对局中无长语音(归因句 2-3s),豁免避免长按与连续滑动的手势边界模糊;演练/结算允许 | -| escape / storyline / rpg | 不豁免(全局允许) | 叙事类游戏正是长按加速的主战场 | -| RPG 战斗 | 允许但有边界:敌方前摇是 **sfx**(tel_*)不受 rate 影响,格挡窗口时长不变——加速 voice 不影响战斗判定,安全 | 裁定:允许 | +| escape / space / dungeon / hunt | 帮助 | 关 | +| rhythm / drummer 演奏+教学跟打 | 不用(演奏输入) | 关 | +| simon 序列播放 | busy/忽略 | 关(该阶段无 voice) | +| simon 答题 | 帮助(状态句) | — | +| storyline / rpg 探索 / new8 叙事 | 不占用 | 开 | +| rpg 战斗 | 不占用;格挡不绑长按 | 开(前摇是 sfx,加速 voice 不影响判定) | -豁免实现:游戏在阶段切换时调引擎开关(如 `audio.setRateBoostEnabled(false)`,以引擎实际 API 为准);默认开启。 +### 2.3 首局发现性 -## 3. 首局提示时机(两个功能的发现性) +大厅 footer:**删除**「单击有 300ms 延迟」。改为:「旁白中点一下跳过这句。想知道自己在哪,就向上滑(有的游戏是按住屏幕)。」 -原则:**不新增语音资产、不打断首局沉浸**。 +跳过与帮助都是核心能力,不再藏着等玩家摸索。首 3 分钟脚本里:第一次 SPEAKING 后的单击自然就是跳过;阶段 1 用一句教帮助,马上用一次。 -1. **大厅 footer 文案**(可视化,零成本):footer 已有"单击判定有约 300ms 延迟"一行,追加一行:"播放旁白时:单击=跳过本句,按住=加速播放"。 -2. **各游戏教学内自然涵盖**: - - 有交互教学的游戏(simon/space/rhythm/rpg/escape)教学本身已教六手势;跳过/长按是**增强功能而非必需操作**,不教——玩家从大厅文案或摸索中发现即可(发现成本低:单击本来就常用)。 - - storyline(无教学)在首个抉择点 prompt 播完后追加一次现有系统提示?——**不加**。storyline 的"单击继续"已是事实上的跳过来由,长按从大厅文案发现。 -3. 若后续数据显示玩家长语音中途退出率高,再考虑在第二局长语音加一次性提示句(届时补录 `sys_skip_hint`,本规范预留 id)。 +--- -## 4. 附:男女声设置边界核查(用户反馈"可选男女声只是旁白,不是游戏内容") +## 3. 男女声边界核查(保留) -核查方法:读 `web/storyline/game.js`、`web/rpg/game.js` 实际播放路径 + 资产目录结构 + `tools/gen_voice_dual.py` 台词表归属。 +旁白走 `voice//`,角色/NPC 走平铺 `voice/`。storyline / rpg 已核对符合,无需修复。 -### 结论:符合规范,无需修复 - -| 检查项 | storyline | rpg | -|---|---|---| -| 旁白/系统音在 `voice//` 双目录 | ✅ 36 id 双性别 | ✅ 旁白 id 双性别 | -| 角色/NPC 音平铺 `voice/`(固定音色,引擎平铺回退命中) | ✅ n02_linwan(Serena)、n07_dog(Dylan)、e1_child/n03_msg/n05_bcast(Vivian) 均只存在于平铺 | ✅ npc_* 14 id 只存在于平铺,female/male 目录均无 npc_ 文件 | -| 角色音被错误切性别的路径 | **未发现** | **未发现** | - -### 两处勘误(非 bug) - -1. **storyline `n11_child` 命名误导**:该 id 在两个性别目录都存在,初看像角色"小满"被切性别——实际它是**旁白句**(`gen_voice_dual.py:95`,speaker 组 `_SL_NARR`,文本是旁白叙述中嵌入小满的话)。行为正确,但文件名易误读;**维持不改**(改 id 要动代码+资产两端,无实际收益),此处留档说明。 -2. **RPG NPC 台词计数勘误**:05-rpg-ch1-voice-ids.md 表头写"NPC 16 句",实际表内 14 id(村长 4 + 猎人 4 + 头目 3 + 寨主 3)。以 14 为准,音频资产已按 14 落盘,文档表头笔误不另改文件,记于此。 +勘误保留:storyline `n11_child` 是旁白句不是角色音;RPG NPC 实际 14 id 不是 16。 diff --git a/docs/design/13-playtest-criteria.md b/docs/design/13-playtest-criteria.md index ad43d28c..0ed04840 100644 --- a/docs/design/13-playtest-criteria.md +++ b/docs/design/13-playtest-criteria.md @@ -1,857 +1,131 @@ -# 13 · 六游戏试玩通过标准(playtest criteria,无头测试 agent 断言检查单) +# 13 · 试玩通过标准(26 号对齐) -> **给测试 agent 的使用说明**:每游戏分三节——**关键路径**(从启动到通关/结算必须按序达成的事件序列,附最大手势/步数预算)、**边界断言**(必须显式验证的容错/保护行为)、**失败即不通过**。测试可用测试钩子(如直接读取 game 实例字段、快进计时器)模拟时间,但手势只能通过引擎回调注入(模拟真实判定路径)。语音缺失不应导致卡死(看门狗是各游戏的既定实现,属被测项)。 +> **给测试 agent**:交互验收以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) §6 为准。本文分两层: +> 1. **新手交互**(`guide=full`,新手手势):乱点机器人 + 矩阵走查 + 真人冷启动。 +> 2. **玩法逻辑**(`guide=off` 可跑):关键路径到达通关/结算,手势只经引擎回调注入。 +> +> 旧文 §8 起各轮 live 变更记录已废止为交互依据,细节见 `research/04-playtests/`。与 26 号冲突的断言(双击=整段重播、无效只震、双击使用物品等)一律作废。 --- -## 1. storyline《声命线》 +## 0. 合格指标(每款游戏) -**关键路径**(单结局线,≤40 次手势): -1. 启动遮罩点按 → 音频解锁 → `n00` 旁白开始(voice 层有播放记录)。 -2. 沿任一抉择路径行进:每个抉择点断言 `pendingChoice/choiceArmed` 生效,左/右滑各至少使用一次。 -3. 到达任一结局节点(4 结局之一)→ 结局语音链 → 结算态单击重开回 `n00`。 +| 指标 | 目标 | +|---|---| +| 第一次操作 | ≤10s | +| 第一次成功 | ≤20s | +| 无声操作 | 0 | +| 被动原句重复 | 0 | +| 打断失败(非 critical) | 0 | +| 过期 hint/feedback | 0 | +| 真人 5 分钟主动放弃 | 5 人中 ≤1 | +| 真人说「现在呢?」 | 人均 ≤2 | -**边界断言**: -- 语音播放中双击 = 重听当前节点(同一 voice id 重播)。 -- 下滑呼出菜单 → 返回剧情后节点不丢(mode 恢复,不重头)。 -- 中途选择产生剧情标记:`n09` 右滑后 `knowTrap === true`、`n11` 左滑后 `companion === true`、`n18` 左滑后 `betrayed === true`(标记设置即断言;knowTrap 无下游消费是已知项,roadmap §2.2,不作为失败项)。 -- 语音链看门狗:屏蔽某一 voice 资产时流程仍可推进到结局。 - -## 2. escape《暗室》 - -**关键路径**(引导 8 步 + 解谜链,≤80 次手势): -1. 启动 → GUIDE 进入(g0_intro 播放)→ 依次:单击(G0)→ 任意滑动(G1)→ 滑动 ×2(G2)→ 左滑(G3)→ 下滑(G4)→ 单击拾钥匙(G5,断言 `hasKey === true`)→ 双击(G6)→ 进入 PLAY(断言 `phase === 'PLAY'`,localStorage `soundgame.escape.guideDone === '1'`)。 -2. PLAY 路径:(0,2)出发 → 右滑×2 到铁柜房 (2,1) → 双击开柜(`cabinetOpen === true && hasFuse === true`)→ 左滑×2+上滑到电箱房 (0,0) → 双修复电箱(`powerOn === true`)→ 右滑×2+下滑×2 到大门 (2,2) → 双击逃脱。 -3. ENDING:断言 time_band 语音播出 + 单击重开回 PLAY。 - -**边界断言**: -- 引导期错手势:G3 右滑 → 播 guide_wrong_left;连错 3 次 → guide_blunt_left + warning 震动(监听 vibrate 调用)。 -- 撞墙:PLAY 中 (0,0) 左滑 → wall 音效 + error 震动 + 位置不变。 -- 卡关提示:计时器快进 60s/120s/180s → 依次播 stuck_*_1 / stuck_*_2 / stuck_*_3 + nav_*;任意进展事件后重置(`_stuckShown` 清空)。 -- 单击检查 = 环境音先播、描述 800ms 后播(断言播放顺序)。 -- 双击无目标兜底:播背包 + L2 明示,不崩。 - -## 3. simon《音序回响》 - -**关键路径**(教学 + 5 轮 + 复活耗尽,≤120 次手势): -1. 启动 → 0.5s 后教学 T0(t0_welcome 播放)→ T1-T6 每步:听到 tone → 做出对应手势(断言 `teachIdx` 递进)→ T7 → 进入第 1 轮。 -2. 第 1-3 轮:playback 阶段手势被忽略(注入手势断言 sequence 不受影响)→ input 阶段按 `game.sequence` 原样复现 → `round` 递增。 -3. 触发复活:第 4 轮故意答错 → 断言 `chances` 1→0、序列**不变**重播(`sequence` 引用值相等)→ 复现成功 → 第 4 轮完成时 chances 回 1(每 4 轮 +1)。 -4. 主动重听:input 阶段且期望输入非 doubletap 时双击 → 断言 chances 减少、inputIdx 清零、序列重播。 -5. 耗尽后再答错 → gameover → 断言 score_band 语音与 `round-1` 匹配 → 单击重开跳过教学直达第 1 轮。 - -**边界断言**: -- 无 BGM:全程无 bgm 层播放调用。 -- 双击冲突(裁定①):期望输入恰为 doubletap 时双击 = 答题(inputIdx 前进),不触发重听。 -- chances=0 时双击非期望手势:不判错(round 不变、inputIdx 不变)、播 chances_out。 -- 序列约束:前 5 轮无三连同手势;全程无四连。 -- 速度:第 1 轮 interval 700ms、第 12 轮起 420ms 下限。 - -## 4. rhythm《节拍行者》 - -**关键路径**(教学 + 曲目一通关 + 解锁,≤200 次手势): -1. 启动 → welcome → 教学 T1-T3:按 cues 的 expected 时刻(测试用 AudioContext 时钟对齐)做对应手势 → `tutorialDone === true` → 进曲目一 main。 -2. 曲目一:按 `_cues` 全部应答(Perfect 精度注入)→ 结算 phase 'end' → 断言评级 S(全 Perfect)→ `grades.song1 === 's'`、`unlocked === 2`、unlock_song2 播报。 -3. 结算单击 = 同曲再来一遍;右滑 = 下一首(已解锁 song2 进入 main)。 - -**边界断言**: -- tap 补偿:tap 音符以"回调时刻 -0.30s"判定(注入 tap 手势的实际回调延迟下仍判 Perfect)。 -- Miss=无乐器音:窗口内做错手势 → 无 inst_* 播放 + error 震动 + combo 清零。 -- 下滑折中:main 中无临近 down 音符时下滑 → 菜单(phase 'menu'、BGM 断点);有临近 down 应答音符时下滑 = 演奏判定,不开菜单。 -- 菜单暂停恢复:关菜单后 cue.expected 平移、BGM 断点续播(断言 `_bgmT0` 平移量 = 暂停时长)。 -- 教学失败 2 次 → retry_skip 跳过该步不卡死。 -- **结算末位**:song3 结算页右滑 → 不播 song_locked(round3-4 裁定①的回归断言;修正落地前此断言预期失败,标记 known-issue)。 - -## 5. space《盲航》 - -**关键路径**(演练 + 保护期 + 坠毁,≤150 次手势): -1. 启动 → 演练 D1-D4:按 `DRILLS[d].dir` 同向滑 → `drillIdx` 递进 → D5 后进 playing(`drillDone === '1'`)。 -2. 保护期(runStart 起 60s):等一颗陨石 → **不滑动任其撞** → 断言 `hits === 0`(训练护盾吸收)+ protect_ 播报。 -3. 第 20s:feature 阶段单击 → boost 生效(`boostUntil > now`);第 45s:双击 → 护盾(`shieldUpUntil > now`)。 -4. 60s 后:保护解除播报;之后正常闪避 3 颗(同向滑,断言 dodges=3)→ 再任撞 3 次 → 坠毁 gameover。 -5. 结算:time_/dodge_ 分档句与实际值匹配 → 单击重开。 - -**边界断言**: -- 演练做错:D1 右滑(应为左)→ drill_retry_left 重教 + 同一步重试;失败 2 次 → drill_moveon 带过。 -- 保护期撞击方位归因:撞左来陨石播 protect_left(方向句与 ob.dir 一致)。 -- 同向判定:撞击前 1.5s 内同向滑 = dodge(dodges+1);反向滑 ≠ dodge。 -- 第 2 次被撞后追加 hit_last;hits===3 才 gameover。 -- 下滑折中:下方有悬置陨石时下滑 = 闪避;无下方陨石时下滑 = 菜单(phase 'menu')。 -- 护盾充能:doubletap 后 30s 内 shieldReadyAt 锁定,到时播 shield_ready。 -- 里程碑:dodges=10 播 ms_10(测试可快进或直接设置 dodges 后触发一次 dodge)。 - -## 6. rpg《剑与声》(含 11 号文档重构项;重构未落地处标 known-issue) - -**关键路径**(第一章通关,≤300 次手势): -1. 启动 → 无存档时序章 intro_1-3 → village;断言场景进入 = amb/desc 播报 + 选项可左右滑切换。 -2. village:商店买铁剑(金币 50→断言不够 → 先调查/战斗攒钱或直接断言 shop_poor 播报路径);上滑状态播报链完整(st_lv→st_hp→st_gold→st_weapon→st_armor)。 -3. path:investigate 一次性 +15 金(再次调查播 evt_nothing)。 -4. stream:**教学战不败**——全程乱按/不动,断言玩家 HP 不低于 1(锁底);按 11 号文档重构后:四步教学(menu→attack→parry→defend)依次断言 teachStage 流转(重构落地前按现版两步 parry→attack 断言)。 -5. hunter:talk_hunter → hunter_quest flag + potions+1 + shop_discount。 -6. deepwood:山贼连战(两只依次上场,连战间不回血)→ enc_bandits_done。 -7. gate:assault → elite 战 → boss1:黑风寨主(半血二阶段 ATK 12 + npc_boss1_2)→ 胜利 → evt_ch1_end → `chapters['2'] === 'open'`。 -8. 存档断言:任一战斗结算后 PUT/localStorage 有最新 data(summary 含场景名与 Lv);杀进程重进 → sys_continue_ask → 单击继续落点正确。 - -**边界断言**: -- 格挡:前摇窗口内双击 → 伤害 ×0.3(`dmg` 断言或 HP 差值断言);窗口外双击 → error 震动不生效。 -- 防御指令:当回合伤害 ×0.5。 -- 逃跑:Lv<3 普通战 100% 成功(11 号文档;重构前现版为 flee 布尔,按现版断言 flee:true 必成);Boss 战下滑 → btl_noflee。 -- 新手保护(11 号文档,known-issue 至重构落地):前 3 场前摇 1.5s、伤害 ×0.6;enc_boss1 恒标准档。 -- 战败:HP≤0 → btl_lose → village 重生,等级/金币/flags 保留、非 Boss 遭遇重置。 -- 菜单豁免:探索态下滑=菜单(左右滑+单击确认),战斗态下滑=逃跑。 -- 看门狗:屏蔽任意 voice 资产,战斗与场景流转不卡死。 - -## 7. 全局通用断言(每游戏都跑) - -1. 启动遮罩点按前无音频播放(引擎解锁语义)。 -2. 全程无未捕获异常;资产缺失仅 console.error 不崩。 -3. 设置边界:settings.voice 切换后下一句起用对应目录(voice 缓存键含性别);settings.bgm=0 时无 bgm 层加载;speed=1.7 时下一句 playbackRate 生效。 -4. 双击=重听在其声明可用的阶段生效(escape 检查/simon 规则/rpg 场景 desc 等)。 -5. 菜单约定(裁决②):适用游戏的模态菜单 = 单击循环 / 双击确认 / 上滑帮助 / 下滑关闭(RPG 豁免)。 +出现 26 §7 任一反模式 = 不通过。 --- -## 8. round3 变更记录(simon 手势映射例外) +## 1. 全游戏通用断言 -> round3 G5(research/04-playtests/round2/synthesis.md §2)改变了 simon 的**玩法** -> 手势映射(不是向导层断言):input 阶段双击永远=作答(第 6 音色),主动重听改绑 -> 上滑。按本研究「playtest 断言零改动」红线的例外流程,本节只做追加记录 §3 受影响 -> 断言的替换口径;§3 既有文字不回改,其余 5 个游戏断言零改动。 +每游戏必跑: -- §3 关键路径 4「主动重听:input 阶段且期望输入非 doubletap 时双击 → 断言 chances - 减少、inputIdx 清零、序列重播」→ 替换为:**input 阶段上滑(且期望输入非 up)= - 主动重听**,断言目标不变(chances 减少、inputIdx 清零、序列重播)。 -- §3 边界「双击冲突(裁定①):期望输入恰为 doubletap 时双击 = 答题(inputIdx 前进), - 不触发重听」→ 保留并强化为:**input 阶段双击永远=作答**——期望非 doubletap 时 - 照常判错(耗机会/死亡),不再触发重听。 -- §3 边界「chances=0 时双击非期望手势:不判错(round 不变、inputIdx 不变)、播 - chances_out」→ 替换为:**chances=0 时上滑非期望手势**,断言目标不变。 -- 新增边界断言:期望输入恰为 up 时上滑=作答(六手势全是答题输入,00 号文档 §5.3 - 豁免同款);非输入阶段(gameover/菜单)上滑行为不变。 -- 随上述改口的测试脚手架微调(非断言变更):两处「错手势注入」的取值从 - `GESTURES.find(g => g !== exp && g !== 'doubletap')` 增加排除 `up`(round3 后 - up=重听键,注入 up 会触发重听而非判错,与旧版注入 doubletap 需避开是同一道理)。 -- 依据:docs/design/14-guide-layer.md「round3 v1.2」§8/G5;simon playtest 断言仅 - 上述 3 处改口(108 条总数不变),其余断言(含教学、复活、结算、菜单、序列约束、 - 速度下限)零改动。 +1. **回执**:每个已解锁手势在每个可感知状态 100ms 内有回执或内容音。矩阵无空格。 +2. **新手无双击**:`doubleTapWindow=0`;300ms 内连点两下不得重播整段;第二下为 `busy` 或第二次单击(400ms 内 busy)。 +3. **键盘回车**:新手期当单击,不触发旧双击语义。 +4. **打断**:SPEAKING 时滑动 → 当前非 critical 语音在 150ms 内停。 +5. **整链 duck**:两句旁白之间 BGM 不得抬回。 +6. **YOUR_TURN 有声**:等待输入时有等待声或常驻环境音,禁止 >5s 无声死局(剧情回味点等短句后起等待声)。 +7. **三档提示**:同一等待点第 2 次被动提示 ≠ 第 1 次全文。 +8. **迷茫**:10s 内 3 次 invalid → 接管,且不原样罚听。 +9. **失败**:归因 2s 内且与行为一致;新手前两次不扣资源。 +10. **断点**:退出再进 5s 内一句复述。 +11. **语音缺失不卡死**:看门狗/超时兜底仍在。 +12. **summary**:有存档的游戏必须写 `summary`。 +13. **设置只读**:局内改 settings 不提供入口。 +14. **iOS**:无震动时回执声仍在。 + +乱点机器人参数见 26 §6.2。矩阵走查:设计单里每一格实机点一次。 --- -## 9. round1-live 变更记录(simon 结算/菜单/反馈修正) +## 2. storyline《声命线》 -> 依据 research/04-playtests/live/round1/simon-guoguo.md(果果,10 岁 persona 全拟真 -> 试玩)。按「playtest 断言零改动」红线的例外流程(§8 先例),本节只追加登记 §3 -> 受影响断言的替换口径与新增断言;§3 既有文字不回改,其余 5 个游戏断言零改动。 +**交互**:设计单 [26-apply-storyline.md](26-apply-storyline.md)。 -- **结算屏单击改口(P0 言行不一)**:gameover 阶段单击**不再跳句**,无论语音是否 - 在播一律直接重开(_restart,跳过教学直达第 1 轮)——结算播报承诺「单击屏幕, - 再来一局。」,单击必须言行一致。原「结算语音播放中单击=跳过本句(不重开)」 - 断言替换为:**结算语音链播放中单击 → 立即进入新局第 1 轮**(phase='announce'、 - round=1、round_01 开播、结算链被打断)。无语音时单击重开的原断言不变。结算屏 - 上滑=重听规则、下滑=打开菜单,与 rules_prompt 播报一致,维持不变。 -- **新增边界断言(P1 菜单导航静音)**:菜单内右滑=下一项、左滑=上一项(首项再 - 左滑回绕末项),切换即播报当前项(menu_restart/menu_home/menu_back);菜单内 - 下滑=关闭菜单回结算页(§7.5「下滑关闭」约定在 simon 的落地补全)。 -- **新增边界断言(P1 未支持手势被吞)**:input 阶段 longpress → error 震动兜底, - 不判错、不耗机会、不进重听(同时修复 TONE['longpress'] undefined 导致 - audio.play(undefined) 抛 TypeError 的隐性崩溃——live 中"长按 9 秒零反馈"的 - 实际成因);longpressend(同一动作收尾)静默不重复反馈。 -- **新增边界断言(P1 正反馈稀缺)**:每轮答对必播一句表扬——既有台词池 - [voice/correct, voice/gd_clear] 按轮轮播(第 1 轮 correct、第 2 轮 gd_clear、 - 交替),替换原 G6「correct 每 4 轮一次 + gd_clear 每局限 1 次」口径; - gd_record 每局限 1 次、chance_plus 每 4 轮发放,均维持不变。 -- **组合占用提示改口(P2 每轮必念)**:gd_occ_up/dt/both 改为**每局每种组合只在 - 首次出现轮播一次**;之后仅在玩家答错消耗机会重听时随 retry_use 链尾重播本轮 - 组合提示。替换原 H2「每轮序列播放完成后按序列播占用预告」口径。新增断言: - 同一 gd_occ_* id 不在两个轮次开场重复播报;答错重听链尾重播当前组合提示 - (序列含 up/doubletap 时计数 +1,不含时不变)。 -- **guide.test.mjs G6 simon 段同步改口**:原「轮成功链尾播 gd_clear(每局 1 次)」 - 断言替换为「第 1 轮表扬 = correct」;「gd_clear 每局仅第 1 次轮成功播」替换为 - 「第 2 轮表扬轮播到 gd_clear」;gd_record 断言保留,但需等表扬+纪录两句链播完 - 再计数(链多句时立即计数会采到链中——一并修正原断言采样过早的时序问题)。 +**关键路径**(≤40 手势):解锁音频 → 3s 内单击 → 沿任一抉择到结局 → 结算单击重开。 ---- +**边界**: +- 旁白中单击=跳句,400ms 内再点=busy,不连跳两节点。 +- 抉择点单击=再听选项(再说一遍),不跳过选项句、不盲选。 +- 非抉择点左右滑=invalid + 首次解释。 +- 新手期双击当单击,不得 `replayNode`。 +- 标记:`knowTrap` / `companion` / `betrayed` 设置即断言(消费点见各设计文)。 -## 10. live round1 变更记录(storyline《声命线》老陈全拟真试玩修复) +## 3. escape《暗室》 -> 依据:research/04-playtests/live/round1/storyline-laochen.md(P2-1 结局双击重听 -> 失效 / P2-2 猎狗台词不认分支 / P2-3 静默无兜底 / P3-2 结局后状态播报滞后)。 -> 本节只做追加;§1 既有断言与 §7 全局断言零改动,其余 5 游戏不受影响。 +**交互**:设计单 [26-apply-escape.md](26-apply-escape.md)。谜题链骨架仍是钥匙→铁柜→保险丝→电箱→大门。地图见 [25-escape-map-flow.md](25-escape-map-flow.md)。 -新增边界断言(storyline): -- **结局双击重听**:结局链播完(结算态 awaitingRestart)双击 → 结局语音重播; - 重播期间单击 = 跳过本句(不触发重开,nodeId 不变);重播链播完回到结算态 - (awaitingRestart === true),此后单击仍可重开。 -- **分支台词条件**:`n15` 右滑(钢索)路径进 `n18` **不播** `n18_dog`(无涵洞经历, - 台词「涵洞也拦不住你」不得出现,n18 只播抉择提示 p6);`n15` 左滑(涵洞)路径 - 照播 `n18_dog`。台词文本不变、不新增语音 id。 -- **结局后状态播报**:结算态上滑 → help 后播**当前结局语音**(不再报 status3 - 「这是最后一段路了」);播完直接回结算态(不二次重播节点)。 -- **静默兜底(standard 档)**:非抉择节点播完等待时,13s 无操作播 `gd_continue`、 - 30s 播 `gd_again`(复用向导层看门狗计时/复位;玩家手势后重置,下一等待点 - 重新挂载)。`guide=full` 节奏维持 [25s,50s] 不变(guide.test 锁定); - `guide=off` 不挂任何定时器(既定全静契约)。 +**关键路径**:开场 3s 内任意滑 → 引导走到储物间 → **单击**检查得钥匙(不再双击使用)→ 自由探索同链逃脱 → 结算单击重开。 ---- +**边界**: +- 撞墙:闷响 + invalid + 方向归因(每方向整局一次语音)。 +- 长按=我在哪(目标+方位一句),不加速旁白。 +- 无进展走迷茫三级,不周期重播 26 秒说明。 +- 引导错手势不惩罚,短纠正。 -## 11. live round1 变更记录(space《盲航》小赵全拟真试玩修复) +## 4. simon《音序回响》 -> 依据 research/04-playtests/live/round1/space-xiaozhao.md(P1×4 / P2×2)。只改 -> 触发时机/判定逻辑,台词文本与语音 id 零变更。按 §8 先例只追加登记 §5 受影响 -> 断言的替换口径与新增断言;§5 既有文字不回改,其余 5 个游戏断言零改动。 +**交互**:设计单 [26-apply-simon.md](26-apply-simon.md)。 -- §5 边界「同向判定:撞击前 1.5s 内同向滑 = dodge(dodges+1);反向滑 ≠ dodge」 - → 替换为:**同向滑判定窗口 = 呼啸全程**——陨石 spawn(呼啸响起)到撞击点 - +250ms 宽限之间的同向滑均判 dodge;spawn 之前的同向滑不判(`ob.spawnAt` - 下界)。新增回归断言:呼啸响起后 0.2s 内早滑(距撞击 >1.5s)也算躲开 - (P1「早滑被静默吞掉」)。 -- §5 边界「护盾充能:doubletap 后 30s 内 shieldReadyAt 锁定,到时播 shield_ready」 - → 替换为:doubletap 后 **15s** 内 shieldReadyAt 锁定,到时播 shield_ready - (queue 排队播,不顶 protect_end/归因句)。新增:充能中/未引入时双击 → - 播 feat_shield_done 应答 + HEARTBEAT 震动(4s 去抖,连刷不重播)。 -- 新增边界断言(P1 方向语音永远迟到):裸飞陨石 spawn 当刻 voice/dir_ - 与 whoosh 同窗播出(播放日志同一时刻),撞击前 500ms 的 loud 警报档不再带 - 方向呼喊;撞击点落在保护期内的陨石 spawn 时不播 dir_(protect_ - 长句教学优先)。 -- 新增边界断言(P1 保护期断崖过渡):protect_end 后第 1~3 颗裸飞陨石逼近时长 - 固定 = PROTECT_APPROACH_MS(4.0s,不收缩),第 4 颗起 < 4.0s 接入难度曲线。 -- 新增边界断言(P2 最后一命警告健壮性):hits===2 时 hit_ 在播中被 - dir_ 呼喊打断,呼喊播完后 hit_last 仍自动接播(挂起 + 呼喊 onComplete - 接力,不再整链静默丢失)。 -- 新增边界断言(P2 feat 教学仅首局):同一 SpaceGame 实例第 2 局 advance 过 - 20s/45s 不进 feature phase、不播 feat_boost/feat_shield;双击护盾/单击加速 - 第 2 局开局即用(introDone 标记挂实例,不随 _resetRunState 重置)。 -- 依据:round1-live 试玩报告问题清单 1/2/3/4/5/6;space playtest 其余断言零改动。 -- 随上述改口的测试脚手架微调(非断言变更):dir 呼喊断言前补一次 `await tick()` - ——round4 R1 活体复验追加修复「voice 播放安全门」(_playVoice 异步竞态:同 tick - 第二个 play 漏杀前一个 → 双管线并播 + 节点泄漏;游戏层串行门 + 对局语音全量 - prewarm 缓存热修复,引擎零改动),播放调用改为 microtask 落位,断言语义不变。 - guide.test.mjs §⑨ G7 段同步改口为「呼喊与呼啸同窗、与 loud 警报错峰 ≥2s」。 +**关键路径**:3s 内单击 → 交互式只教已解锁音色 → 第 1 轮 2 音复现 → 打到结算。 ---- +**边界**: +- 序列播放期任何手势=busy,序列不变。 +- 无 BGM(对局)。 +- 答错 1~2:归因真话 + 免费重听;第 3 次放慢;不扣「机会账」。 +- 帮助=长按。新手期无双击音色。 +- 结算单击=再来一局(跳过教学若已完成)。 -## 12. round1-live 变更记录(escape《暗室》阿May全拟真试玩修复) +## 5. rhythm / 鼓手模式 -> 依据:research/04-playtests/live/round1/escape-amay.md(P1 三项:卡关提示失控 / -> 语音中单击吞检查 / 撞墙教学腔,唯一事实源)。本节只做**追加**,§2 既有文字不 -> 回改;受影响断言的替换口径与新增断言登记如下,escape playtest 其余断言零改动。 +玩法判定常量以 [24-rhythm-drummer.md](24-rhythm-drummer.md) 为准(若鼓手模式已取代 03)。 -- §2 边界「卡关提示:计时器快进 60s/120s/180s → 依次播 stuck_*_1 / stuck_*_2 / - stuck_*_3 + nav_*;任意进展事件后重置(`_stuckShown` 清空)」→ 替换为:**卡关 - 提示按"无操作时长"计时**——PLAY 阶段任何有效手势重置计时(`_lastActiveAt`); - 连续无操作 60s/120s/180s → 依次播 L1(stuck_ask + stuck_*_1)/ L2(stuck_*_2)/ - L3(stuck_*_3 + nav_*,此后每 60s 无操作重复);任意进展事件后 `_stuckShown` - 清空(下一段静默期从 L1 重新分级)。三级阈值与分级内容不变(01 号文档 §3.2)。 -- 新增边界断言(提示排队礼仪):卡关提示到点时若有语音在播,**只入待播队列, - 等当前语音自然播完才播,绝不顶掉进行中的语音**(断言:在播句未被截断、提示 - 句开播时刻 ≥ 在播句开播时刻 + 其时长);待播期间玩家任何操作 → 该提示作废, - 未播级别不计入 `_stuckShown`(下段静默期可重新触发)。测试允许用钩子直写 - `_lastActiveAt` 并直调 `_checkStuck()` 模拟"语音播放中到点"。 -- 新增边界断言(活跃免扰):PLAY 中每 ~25s 一个普通手势、持续 ~250s 且无任何 - 进展事件 → 全程零 stuck_* 播报;停止操作 60s 后 L1 如期而至(计时器仍武装)。 -- §2 中"voice 播放中单击 = 跳过本句"的行为口径补全:可跳句播放中单击 = 跳过 - 本句(链播继续)**并补发检查**——环境音即时重播、检查描述进待播队列(链播 - 未完则排队,不顶句;链播已完则照常 800ms 后播)。不可跳句(关键获得/L3 带路) - 播放中单击 = 忽略,原断言(不跳句、不触发检查)不变。 -- §2 边界「撞墙」补充口径:PLAY 阶段撞墙 = wall 音效 + error 震动 + 位置不变 + - `voice/wall`("那边是墙,换个方向。"),**不再接播 guide_blunt_* 方位句**; - 教学腔方位句(guide_blunt_*)仅限 GUIDE 阶段(G2/G3 纠错路径断言不变)。 -- 移除向导 full 档「连续撞墙 ≥2 次 → 卡关阈值提前一级(L1 即刻/L2 60s/L3 120s)」 - 的 escape 接线(14 号文档 v1 裁定 5/§2 escape 行):该机制在"无操作时长"语义 - 下等价 L1 永触发,是 round1-live「机关枪提示边玩边扫射」的主因;`_wallBumps` - 状态一并移除。web/harness/sessions/escape.mjs 剧本第 58-64 行相关旁白(连撞 - 两墙等提前提示)随本变更过时,剧本本身不断言、不影响运行。 +**关键路径**:3s 内动手 → 教学一次一击 → 曲目一打完结算 → 单击再来。 ---- +**边界**: +- 演奏期=ACTION:不起等待声;手势即演奏。 +- Miss=无乐器音 + 回执/归因(教学期分档真话:没接/早/晚/方向)。 +- 暂停恢复必须 count-in。 +- 教学连败降速,不卡死。 +- 新手期谱面不得要求双击;双击入池前曲目只用已解锁手势。 -## 13. round1-live 变更记录(rhythm《节拍行者》小江全拟真试玩修复) +## 6. space《盲航》 -> 依据 research/04-playtests/live/round1/rhythm-xiaojiang.md(P0×2 / P1×4 / P2×3)。 -> 只改触发逻辑/判定参数/反馈音复用:台词文本与语音/音效资产零新增零变更 -> (Miss 音 = 既有 inst_hat_l 以 playbackRate 0.5 降调复用;教学上/下滑步复用 -> gd_answer「现在跟着我打」作引导句)。按 §8 先例只追加登记 §4 受影响断言的 -> 替换口径与新增断言;§4 既有文字不回改,其余 5 个游戏断言零改动。 +**关键路径**:演练四向各一次(做错重教,2 次后演示通过)→ 保护期被撞不掉血且归因 → 保护解除后闪避 → 结算。 -- §4 关键路径 1「教学 T1-T3」→ 替换为:**教学 5 步**(tut_1 单击 → tut_2 左右 → - 上滑(gd_answer)→ 下滑(gd_answer)→ tut_3 三音带双击压轴)——上/下滑音色 - 教学补全(P3-11;tut_3「最后一句」台词保持为真,故上/下滑步插在其前)。 - 下滑步中临近 down 音符时下滑 = 演奏应答(不跳教学);远离时下滑 = 跳过教学 - (原行为不变)。 -- §4 边界「tap 补偿:回调时刻 -0.30s 判定」→ 替换为:**判定时刻 = 触摸时刻, - 按手势补偿识别延迟**:immediateTap(index.html 已开启,按下即发 tap)下 tap - 补偿 0(expected+0 注入判 Perfect);双击事件前 350ms 内无 tap 事件 → 闩锁 - 回退旧引擎行为(补偿 TAP_DELAY 0.30s,expected+0.30 注入判 Perfect;测试钩子 - `game._immediateTap = false` 直接置位验证旧路径);滑动补偿 SWIPE_DELAY - 0.15s、双击 DOUBLE_DELAY 0.20s(识别均在 touchend;expected+补偿 注入判 - Perfect)。Miss 超时放宽 = 对应补偿 + 0.10s。 -- §4 边界「Miss=无乐器音」→ 替换为:Miss 不播**该音符的**乐器音(错过=没出声 - 不变),但必播 **Miss 反馈音**(inst_hat_l 降调 0.5× 闷镲)+ error 震动 - (P2-8 Miss 无声);长按 = 死输入同款反馈(Miss 音 + error 震动,不判定不 - 归属,P0-2 附项);longpressend 静默。 -- 判定窗放宽(P2-10 / P0-1):PERFECT ±0.12 → **±0.15s**、GOOD ±0.25 → - **±0.35s**(吸收滑动/双击识别延迟与蓝牙余量);评级线不变(B≥0.70,与 - grade_b 台词「超过七成」一致——窗口+补偿校准后真人认真打可达 B)。 -- 新增边界断言(P0-2 下滑折中加宽):down 应答音符自其领奏响起(expected-4 拍) - 至判定死线(expected+GOOD+SWIPE_DELAY+0.10)之间,下滑一律判演奏——慢滑 - (识别 +0.35s)踩点下滑判 Good 不弹菜单;该窗口之外下滑才呼出菜单(不变)。 -- 判定归属改口(密集谱防抢占):归属改为**同手势最近未判定音符优先**——同句 - 0.5 拍相邻的异手势音符(如 song2 第 7 句 down/up 相邻)不再被"最近归属" - 跨手势偷走;无同手势在窗音符时,最近异手势音符落在错手势窗(GOOD 的一半) - 内 = 窗口内做错动作 Miss(原"窗口内错手势判 Miss"语义保留,窗口收紧); - **tap 豁免异手势 Miss**(immediateTap 序曲 tap 防误判,真正错手势 tap 由该 - 音符超时自动 Miss 兜底,同样有 Miss 音)。 -- 新增边界断言(P1-4 暂停真冻结):菜单开启 3s 期间无新 Miss(判定冻结)、无 - 新排程音符(谱面冻结);关菜单即停菜单语音链(isVoicePlaying === false, - 菜单语音不骑恢复后的领奏——live「菜单不暂停」感知的主因)。 -- 新增边界断言(P1-3 结算 BGM 状态管理):结算 menu pad(bgm/menu)只在语音链 - 末句(voice/restart)播完后起播(playLog 顺序断言),且只在仍处于结算态时 - 起播(重开/下一首后过期看门狗 fire 不得把 menu pad 漏进正片)。 -- 新增边界断言(P1-5 回大厅):结算屏下滑 → 进 leaving 态 + 播确认语音 - menu_lobby → 随后 window.location.href 置 '../'(真实跳转,不留尸体页面)。 -- 新增边界断言(P3-12 零通过不夸):教学全程零通过(逐步失败 2 次跳过)收尾播 - retry_skip 不播 tut_done;有任一步通过则照常播 tut_done。 -- 新增边界断言(immediateTap 序曲 tap 治理):非演奏阶段(intro/select/menu/ - end/leaving)tap 延迟 500ms 生效,期间任何其他手势到达即作废(防滑动/双击/ - 长按的序曲 tap 误触发选曲/重开/菜单项/跳句);演奏阶段(tut/main)tap 只 - 认领 tap 音符——应答 up/down 等音符前的序曲 tap 出声(kick)但不产生尝试 - 记录、不判 Miss、不抢占归属。 -- 新增边界断言(滞留语音链看门狗作废,live 复验深挖):教学纠正链在播时下滑 - 跳教学(链被顶掉)→ 进入 main 后越过 10s 看门狗窗口,phase 保持 main 不被 - 拖回 tut。机制:`_speakGuarded` 看门狗只在"自己这条链仍是当前链"时可 fire - (语音链序号 `_voiceSeq`:任何更新的 _speak/_speakGuarded/_cleanup 都会作废 - 更旧的滞留看门狗)——否则滞留看门狗会把 onDone(如 _tutLead)砸进毫不相干 - 的相位(live 实测:纠正链被顶 10s 后游戏从 main 被拖回 tut,遗留的 mid_song - 定时事件跨相位乱入)。 -- P1-3 附记(证据保留):live 事件流中「重开后 track90 排程 0.5s 被 stop」实际 - 停的是结算 menu pad(sid 对得上);混音 40-120Hz 频段能量各局一致,track90 - 全程在播——「重开后 BGM 消失」是事件误读。真实缺陷 = 结算看门狗 10s 早火 - (menu pad 骑语音链尾,报告 P3-15 同因)+ 过期 fire 无相位守卫,本条已修。 +**边界**: +- 同向闪避。 +- 对局=ACTION;帮助=长按。 +- 单击加速、护盾等高级手势按解锁阶梯,不得开场同时教。 +- 归因句必须含方位与正确动作;没出手不得报「慢了」。 ---- +## 7. rpg《剑与声》 -## 14. round1-live 变更记录(rpg《剑与声》小周全拟真试玩修复) +**关键路径**:进村 → 左右滑选项 + 单击前进 → 一场战斗(单击攻击)→ 存档 summary 更新。 -> 依据 research/04-playtests/live/round1/rpg-xiaozhou.md(P1 格挡与血量播报矛盾 / -> P2 闲置提示结算中插话被掐 / P2 回合提示口吃 / P2 教学"挡住了"与实际不符+重播 / -> P3 选项循环误选返回)。设计裁定:格挡**仍有擦伤**(05-rpg §2「减伤 70%」、 -> 11-rpg-battle-rebalance §3「格挡成功 2-3 伤」),非全免伤——故修数值对齐设计 + -> 教学明说减伤经济 + 播报与战况匹配,不做格挡全免。台词文本与语音 id 零新增零 -> 变更(TTS 环境缺失);只改触发逻辑/判定参数/选播既有句。按 §8 先例只追加登记 -> §6 受影响断言的替换口径与新增断言;§6 既有文字不回改,其余 5 个游戏断言零改动。 +**边界**: +- 探索:左右滑切换、单击确认、上滑状态(阶段 1)。 +- 格挡:不得绑新手期双击。改绑「前摇窗口内再单击」或「窗口内与攻击同向滑」——具体见 05 重写文,测试按该文。 +- 教学战锁血最低 1,失败归因后就近重试。 +- 战败重生也存档。 +- 菜单与探索同构。 -- §6 关键路径 4(教学战四步)中 T1 播报口径改口:attack 步**不再播 tut_b3** - (其实录为旧两步教学"格挡→攻击"台词「挡住了!轮到你了。左右滑选攻击,单击 - 出剑。」,新顺序 T1 在格挡之前播「挡住了!」与实际不符),改播**重播既有 - tut_battle_2**「左右滑切换招式,攻击、破空斩、防御、金疮药。单击,出手。」 - (语义一致,不新增语音 id);tut_b3 资产保留但退出教学链(TTS 缺失不可重录, - 列遗留)。原断言「T0 切换 2 次 → T1 attack(tut_b3)」替换为「T1 attack 重播 - tut_battle_2(段内计数 ≥2)且教学全程 tut_b3 计数 = 0」。教学 8s 重复提示 - 补口径:挂载后玩家有任何操作则跳过本次重复提示(16s 兜底推进不变; - B1「全程不动 → tut_b0 重播 ≥2」断言不变)。 -- §6 关键路径 4 的 T2 结算链改口:parry 步结算(成功/失败两路径)在 tut_b2b / - tut_b4 与 tut_b6 之间插播**既有 tut_battle_4**「没把握就防御,伤害减半。金疮药 - 可以回血。」——格挡体验点明说减伤经济(格挡≠免伤、防御减半、药回血;无 - "格挡也有擦伤"原句资产,选播现有最近说明句,补录列遗留)。原断言 - 「tut_b4 → tut_b6 有序」替换为「tut_b4 → tut_battle_4 → tut_b6 有序」。配套: - 该三句链真实时长 ~16s,兜底 10s→**20s**(原 10s 会在链中误触发进 - PLAYER_SELECT、cmd 指令名拦腰顶断 tut_b6);defend 步教学看门狗改为**链播完 - 进回合时才挂载**(8s/16s 计时从玩家可操作起算,不再抢在 tut_b6 播完前跳步 - ——live 复验发现,本轮一并修复)。 -- §6 边界「格挡:前摇窗口内双击 → 伤害 ×0.3」补充口径:擦伤在 ×0.3(四舍五入) - 基础上**钳 1~3**——对齐 11 文档 §3「格挡成功 2-3 伤」(原实现无封顶,Boss - 二阶段 base 12 单发擦伤可达 4;min 1 保住"擦伤必有"的设计痛感)。B3 既有 - 断言(base 4 → 1)不受影响。 -- 新增边界断言(P1 全格挡不掉 low):Lv3+铁剑+皮甲标准出装 Boss 战全格挡打法 - (每次前摇内双击、不喝药)→ 全部敌方攻击均被格挡且擦伤 ∈[1,3];全程**不播 - st_hp_low**;战后血量比率 ≥0.3;全格挡可击杀 Boss。确定性探针:base 12 的 - 一刀被格挡 → 擦伤恒为 3。 -- 新增边界断言(P2 闲置提示不顶断结算语音,guide=full):PLAYER_SELECT 闲置 - 30s → gd_stall 开播;其开播中玩家出手(击杀)→ 结算语音链首句 - (btl_enemy_die)**queue:true 排队等 gd_stall 播完**,不拦腰顶断;无闲置提示 - 在播时击杀语音不排队(对照,默认顶句语义不变)。机制:看门狗 cond 增加 - `!b.resolving`(回合结算中禁止插入)+ `_speak` 在向导句余波 6s 内首句排队。 -- 新增边界断言(P2 指令名口吃):650ms 指令名定时器按 PLAYER_SELECT 代数 - (selectSeq)作废旧回合滞留;同一指令名 1.2s 内已播过则跳过本次——折返滑回 - 攻击+滞留定时器 → cmd_attack 不双播;相邻两回合连进 → cmd_attack 只播一次。 -- 新增边界断言(P3 返回项末位提示,guide=full):探索态右滑落到"返回"项 - (各场景恒为末项)→ 报名 opt_back 后接播既有 gd_wrap_last「最后一个了。」 - (每场景一次,与回绕提示共用 _wrapShown 去重;商店"离开"项同款,每次进商店 - 一次);序号播报无现成语音资产,按任务裁定做前者,不新增语音 id。 -- 依据:round1-live 试玩报告问题清单 P1-2 / P2-1 / P2-2 / P2-3 / P3-3;rpg - playtest 断言 139 → 159(新增 20 条),其余断言零改动。 +## 8. new8 / hunt / 其他 ---- +各游戏关键路径 = 其设计单「一句话核心动作」走通一局。通用断言 §1 全开。叙事类照 storyline 的跳句/抉择/防连点;空间类照 escape 的长按帮助 + 单击检查;战斗类照 rpg 的就近重试。 -## 15. round2-live 变更记录(simon《音序回响》果果全拟真复测修复) +hunt 另验:音频拥挤 R1~R8、三源可辨、服务端权威。不改变新手三手势。 -> 依据 research/04-playtests/live/round2/simon-guoguo.md(P1-N1 免费重听货不对板 -> 冤死 / P2-N2 下滑首次进序列无提示 / P2-N3 菜单不教双击确认 / P2-N4 重播期手势 -> 被吞 / P3-N5、N6)。台词文本与语音 id 零新增零变更(TTS 环境缺失);只改计数 -> 逻辑。按 §8 先例只追加登记 §3 受影响断言的替换口径与新增断言;§3 既有文字不 -> 回改,其余 5 个游戏断言零改动。 +## 9. 玩法逻辑回归(guide=off) -- **N1 修复:重听改两层账(规则级冤死根治)**。教学 gd_economy 承诺「免费机会 - 有两次,用完要扣一次机会」,旧实现免费第二次要打完第 4 轮才随 chances +1 - 发放——玩家第 1 次重听(chances 1→0)即听报「免费重听用完了」,第 2 次求 - 重听被拒,之后答错一题立即 game over("听 2 遍+错 1 题=死")。§3 关键路径 4 - 「主动重听 → 断言 chances 减少」口径改口为:**免费重听每局 2 次开局即给足 - (`freeRelistens`,新实例字段,_beginGame 重置),主动重听(input 上滑)先耗 - 免费额度、不碰 chances;免费用尽后第 3 次起重听才扣 chances;免费与真机会 - 皆空时求重听 → 拒绝(error 震 + chances_out,不判错、不耗账)**。播报沿用 - H3 既有句:免费 2→1 播 gd_chances_left1、1→0 播 gd_chances_left0;扣真机会 - 且扣空(chances→0)追加 chances_out 如实报。答错自动消耗路径(retry_use + - chances_out,只走 chances 机会账)与每 4 轮 chances +1(上限 2)均不变。 -- §3 断言变更登记(simon playtest 123 → 132,净 +9):第 4 轮新增 1 条 - 「免费重听每局 2 次开局即给足(freeRelistens === 2)」;第 5 轮原 6 条 - (「主动重听耗机会:chances 1→0」「播 relisten 并重播」「重听后输入进度 - 清零」「chances=0 上滑非期望:不判错」「chances=0 上滑播 chances_out」 - 「chances 不变(不为负)」)替换为 14 条四层账块——①第 1 次重听用免费额度 - (free 2→1、chances 不动、播 relisten+gd_chances_left1、重播后 inputIdx 清零); - ②第 2 次仍免费(free 1→0、chances 不动、播 gd_chances_left0);③第 3 次才 - 扣真机会(chances 1→0、追加 chances_out);④两账皆空拒绝重听(不判错、 - 两账不为负、播 chances_out)。链尾句断言一律在链播完(回到 input)后计数。 -- guide.test.mjs 同步(121 → 122):⑧ G5 simon 段两处加测试钩子 - `game.freeRelistens = 0`(免费层置空,专测扣真机会路径,断言文本零改动); - ⑩a H3 段 5 条断言按两层账改口(旧钩子 `game.chances = 2` 模拟"免费第二次" - 作废)——余量句改锚免费余量(free 2→1 播 left1、1→0 播 left0、chances 全程 - 不动),新增 1 条「免费重听每局 2 次开局即给足」;拒绝路径改用钩子 - `game.chances = 0` 置空真机会后验证(两账皆空 → 拒绝 + chances_out)。 -- **N3 菜单不教"双击确认"**:查 voice_text.json simon 条目无可复用操作法句 - (rules_prompt「下滑,打开菜单」在菜单内语义相反、skip_hint/t6_double 语义 - 不符;rhythm/space 的 menu_main「菜单。单击切换选项,双击确认。」语义虽 - 与 simon 菜单完全一致,但引入属新增语音 id,本轮约束不允许)。**记遗留 - (需要新台词)**;菜单手势语义不做任何改动。 - -### 遗留清单(round2-live simon,本轮不修) - -1. **菜单操作教学句(N3,需要新台词)**:补录 simon 版 menu_main(参照 - rhythm/space 既有句「菜单。单击切换选项,双击确认。」,与 simon 菜单语义 - 一致),进入菜单时链尾补播。 -2. **大厅零反馈 / 误入其他游戏(复验项 7,跨游戏)**:web/index.html 大厅对 - 6 种手势全部零反馈、无回游戏路径;属跨游戏大厅问题且需要新台词(入口语音 - + 每步音效),本轮不修。 -3. **N2 down 首次进序列无提示**:gd_occ_* 占用预告只覆盖双义手势 up/doubletap - (down 在 input 阶段无第二含义);要补"下"首次提示需新语音 id,列遗留。 -4. **N4 序列重播期间手势被吞**:playback「纯聆听阶段手势静默忽略」是 02 §4.1 - 既定裁定且有断言锁定;加"等等哦"反馈需新音效/设计复议,列遗留。 -5. **长按等未教手势只有震动没有语音(round1 复验项 3 遗留)**:需要新台词 - ("这个手势不算哦"类),TTS 环境缺失,列遗留。 -6. **N5 每局"第一次提示"重新计算 / N6 序列"前缀+1"累加**:报告自评可接受 / - 属玩法设计层面,本轮不处理。 - ---- - -## 16. round2-live 变更记录(storyline《声命线》老陈全拟真复测修复) - -> 依据 research/04-playtests/live/round2/storyline-laochen.md(N1 冷启动 intro -> 末句被 30s 看门狗吃掉 / N2 钢索路径猎狗对峙戏整段失踪——round1 P2-2 修复 -> 引入的回归 / N3 抉择节点无静默兜底 / N4 结局后上滑无进度播报)。台词文本与 -> 语音 id 零新增零变更;引擎零改动(看门狗根因在 game.js 侧,用引擎公开 -> `isVoicePlaying()` 判定)。按 §8 先例只追加登记 §10 受影响断言的替换口径与 -> 新增断言;§10 既有文字不回改,其余 5 个游戏断言零改动。 - -- **§10「分支台词条件」断言翻案(N2,回归修复)**:恢复涵洞/钢索两条路径都播 - `n18_dog`——round1 按 `!viaCulvert` 整段滤掉 n18_dog,钢索路径猎狗堵桥对峙戏 - 蒸发、p6 抉择(交出坐标还是拒绝)失去对手戏上下文,玩家裁定"比穿帮更糟"。 - 替换口径:**n15 右滑(钢索)路径进 n18 照播 `n18_dog` + 抉择提示 p6**;钢索 - 路径接受「涵洞也拦不住你」的轻微穿帮,标 known-issue 待 TTS 补录分支句后按 - `viaCulvert` 选播(标记保留;不新增语音 id、台词文本不变)。 -- **新增边界断言(N1 看门狗不误掐在播长语音)**:单句看门狗(VOICE_WATCHDOG_MS - 30s)触发时先查引擎 `isVoicePlaying()`——为真(资产真实在播,自然播完必触发 - onComplete)→ 续约再等一轮,不掐不告警;为假(资产缺失、引擎静默跳过)→ 照旧 - 告警自动续链(§1 不卡死契约与"屏蔽资产 30s 告警续链"断言均不变)。根因:冷启动 - 链为 intro+skip_hint,30s 固定阈值续链时 skip_hint 顶掉在播的 intro(71.7s, - 2.2x 也需 32.6s),末句「现在,单击屏幕,睁开眼睛」被吃;菜单重开时链上无 - 下一句可顶故不咬人。常速下 e1/e2/e4(48~51s)越线风险同除。 -- **新增边界断言(N3 抉择节点静默兜底)**:抉择提示(p*)播完 25s 完全无操作 → - 重播当前 `voice/p*` 提示句(复用既有资产),50s 再复述一次后彻底静默(两级 - 上限,与继续节点同一礼仪);玩家任何手势复位(抉择等待态空点重新挂载 = 计时 - 重置;已缓存 pendingChoice 直接推进不挂);`guide=off` 不挂(全静契约)。 - full/standard 档提示句均由游戏层播(p* 是剧情内容不是向导句)。与 14 号文档 - 「抉择态(p* 播完)不插语音不挂看门狗」裁定的兼容性:该裁定本义是"提示播完 - 立即可选、不插语音延迟抉择"(W2 契约,保留——choiceArmed 播完当刻即真、零 - 插播),本兜底只在 25s 完全无操作后复述提示本身,不延迟抉择;变更依据 = - round2 玩家复测证据(p6/p8 实测 35~39s 零提醒,抉择点最易漏听选项)。 -- **N4 遗留(结局后上滑无进度播报)**:「已到达结局:XX」类播报需新语音资产, - 受"不新增语音 id"约束本轮不修;现状(help + 重播当前结局语音)维持 §10 口径, - 待 TTS 补录(如 status_end)后补办。 -- 依据:round2-live 试玩报告 N1/N2/N3/N4;storyline playtest 断言相应新增/替换 - (钢索 n18 翻案 1 条 + N1 长语音 6 条 + N3 抉择兜底 7 条),guide.test 全绿不变, - 其余 5 个游戏断言零改动。 - ---- - -## 17. round2-live 变更记录(escape《暗室》阿May全拟真复测修复) - -> 依据 research/04-playtests/live/round2/escape-amay.md(P1-新① 引导链断裂 -> g1_move 永不播放 / P1-机关枪复验"部分修复" / P2-新② 逃脱台词开头丢失 / -> P3-新③「先跟我走」信息量为零)。台词文本与语音 id 零新增零变更;只改 -> 触发逻辑与音效时序。按 §8 先例只追加登记 §2 受影响断言的替换口径与新增 -> 断言;§2 既有文字不回改,其余 5 个游戏断言零改动。 - -- **P1-新① 引导链断裂修复:G0 干等"单击继续"却无人告知该手势(根因)**。 - 复盘 events/transcript:g0_intro 完整播完后游戏停在 G0 等单击,但全部语音 - 里没有一句说过"单击继续"——round1 靠解锁单击漏进游戏误推 G0→G1 被掩盖, - round1-live 修复解锁漏触后暴露为 intro 后 17~91s 死寂;玩家滑动只被 - guide_offpath「先跟我走」拦回(不教方向),最后一次下滑触发跳过,8 步 - 引导整体蒸发(三次复现路径同因)。修复(web/escape/game.js): - ① `_startGuide`:g0_intro 自然播完且 1.2s 内玩家未单击 → 自动 - `_advance(1, [g1_move, skip_hint])`——**g1_move 必播**(台词零改动, - 单击仍可随时立即推进);② G0 在 intro 播放中的滑动改为忽略(原拿 - guide_offpath 顶掉开场白、且 intro 被打断后 onDone 不发、自动推进永不 - 挂载——死寂第二成因);③ **下滑跳过仅限答案不含滑动的步骤 - (G0/G3/G5/G6)**:G1/G2 的正确答案是"任意滑动"(g1_move 原话「朝任意 - 方向滑一下」)、G4 的答案就是下滑——这些步骤下滑 = 作答不可跳过 - (G4 既有裁定同逻辑扩展;13 号文档原"任何步骤下滑跳过、G4 除外"口径 - 就此改口)。§2 断言变更:关键路径 A 的 G0 段口径不变(单击仍即推 G1); - 新增场景 A2「冷启动引导链」14 条——intro 播完零手势 1.2s 自动播 - g1_move、intro 播放中滑动不顶开场白且 g1_move 仍自动播出、G1/G2 下滑 - = 作答(不播 guide_skip)、零 G0 单击走完全链进 PLAY、G3 下滑仍跳过。 -- **"卡关提示机关枪"复验翻案:计时器无 bug,stuck_key_2 来自双击兜底**。 - 逐秒复盘 round2 会话 transcript(t=556.9 重开 → 603.9 tap / **605.4 - doubletap** / 607.1 swipeup → 608.0 stuck_key_2):该句是 `_useItem` - 无目标兜底的设计内主动求提示(§3.2「双击无目标:先播背包,再播 L2 级 - 明示」——双击后 2.6s 链播,非 5s 轮询到点);`_restart`/`_enterPlay`/ - `_resetState` 均重置 `_lastActiveAt`,`_checkStuck` 按"无操作时长" - (60/120/180s)分级,重开后 61s 无操作的 stuck_ask(t=670.1)触发正确。 - 代码零改动;新增场景 C4 冻结语义:重开后每秒操作 90s 零 stuck_*、停手 - 60s L1 如期(计时器仍武装)。live 复验口径:高频操作 90 秒零卡关提示 - (双击兜底属主动求提示,不计入)。 -- **P2-新② 逃脱台词开头丢失修复:音效后移避让(不改语音/不增 id)**。 - 根因:`_escape` 同帧并发 gate_open(2.8s 轰鸣)+ win_chime 与 escape - 人声(14.92s),轰鸣恰好盖住头句「你扳动开关。电机轰鸣,」(三连复现 - heard 均从「沉重的铁门缓缓升起」起)。修复:gate_open/win_chime 经 - `_timer` 后移 2600ms(会话守卫,重开/停页即清)——人声头部(1.0x - ≈2.4s、1.7x ≈1.4s)无遮挡进耳朵,轰鸣落在「电机轰鸣,铁门缓缓升起」 - 附近,声画同步反而更准。§2 关键路径断言替换口径:原「逃脱音效 - gate_open + win_chime 各播 1 次(双击当下)」替换为「双击当下音效计数 - = 0(人声头部 2.6s 无对撞)+ tick 2600ms 后各播 1 次」,断言条数 +1。 -- **P3-新③「先跟我走」信息量为零**:受"不得改台词文本/不新增语音 id" - 约束无法改写该句;本轮以 ①②③ 将其压缩为极小概率兜底(仅 intro 播完 - 后 1.2s 空档内的滑动可触发,自动推进到点即被 g1_move 顶掉),记遗留 - (待 TTS 补录带方向的兜底句,如"等我说方向,先跟我走")。 -- 依据:round2-live 试玩报告「如果只能改 3 件事」全部 3 条;escape - playtest 断言 121 → 140(新增 19 条:A2×14 + 逃脱音效时序×1 + C4×4), - 既有 121 条语义零回改(仅逃脱音效 1 条按上条口径替换计时),其余 5 个 - 游戏断言零改动。 - ---- - -## 18. round2-live 变更记录(space《盲航》小赵全拟真复测修复) - -> 依据 research/04-playtests/live/round2/space-xiaozhao.md(B1 高速段误暂停 -> 两次复现 ta=664.99/1670.73 / B2 恢复后 3 秒暴毙 / B3 回大厅落无声死界面 / -> B4+A5② 警告与结算叠播 / B5+A5① 高速段最后一命警告缺席)。台词文本与语音 id -> 零新增零变更;只改触发逻辑与手势路由。按 §8 先例只追加登记受影响断言的 -> 替换口径与新增断言;§2 既有文字不回改,其余 5 个游戏断言零改动。 - -- **B1(P1)高速撞击时偶发自动暂停跳菜单修复:下滑开菜单加判定冷静期**。 - 根因:旧折中仅"无下方陨石悬置时下滑=菜单"——高速段玩家在撞击瞬间 - 下滑躲避(陨石已判定出集合),被误判为开菜单(两次复现均为撞击后 - 0.13s/0.43s 的下滑)。修复(web/space/game.js):`_resolve` 任何结局都记 - `_lastResolveAt`;`_onPlayGesture` 下滑在**最近一次判定(撞/躲/格挡/吸收) - 后 800ms(MENU_CALM_MS)内一律按躲避意图忽略**,绝不开菜单;其余时刻 - 保留旧折中(无下方陨石悬置即可开菜单)——躲避手势永不误开菜单, - 主动暂停可达性不变。 -- **B2(P1)暂停恢复无缓冲修复:恢复后前 3 颗裸飞陨石回 4.0s 慢节奏**。 - `_closeMenu` 重置 `_bareCount = 0`,复用保护期断崖过渡机制(4.0s 逼近 + - 4.0s 刷怪间隔),首颗撞击点落在恢复后约 4.8s——与"保护期结束 3 颗慢过渡" - 同款软重启,不再"继续飞行 = 3 秒暴毙"。 -- **B3(P1)回大厅落无声死界面修复:rhythm round1 同款收尾流程**。 - `_goHome` 重写:幂等 `_goingHome` 标记 → `leaving` 收尾态(停刷怪/BGM, - 不再受理对局/菜单/结算手势,单击仍可跳确认句)→ `_speakGuarded` 播 - menu_lobby 确认句(2s 看门狗兜底)→ `stop()` + 跳转大厅。旧版无确认 - 直接跳,跳转失败时 tap/滑动全无响应,纯听觉玩家被关黑屋。 -- **B5/A5①(P1)高速段"最后一命"警告播不出修复:呼喊接力保留 + hit_last 在播期间呼喊抑制**。 - 根因:round4 R1 呼喊接力机制里,归因句被下一颗 spawn 呼喊顶断后由呼喊 - onComplete 接播 hit_last,但接播本身无保护——高速段刷怪 1.2s/颗,hit_last - 刚起播就被下一颗呼喊顶没(或致命撞击先至,链整体蒸发)。修复:新增 - `_hitLastPlaying` 在播标记,hit_last 在播期间 spawn **不播方向呼喊** - (警告播完前不被顶断);归因在播期间呼喊保留生存优先级(顶断 → 呼喊播完 - 接力,round1 实测 0.8s 低延迟警告路径不变);链被菜单/分批引入旁白顶断时 - 恢复对局经 `_rearmRunTimers` 立即补播;里程碑与护盾不可用应答在链期间 - 改 queue 排队不顶链。链结束呼喊恢复。 -- **B4/A5②(P2)死亡瞬间语音打架修复:坠毁即掐断警告链**。`_gameOver` - 清 `_hitLastPending/_hitLastPlaying` 并 `stopVoice()`——旧版 hit_last/ - gd_last_life 与 1.2s 后的结算 time_* 叠播互相掐(live ASR 实录混成一团), - 现结算链在静音后干净开场。 -- 遗留(本轮不改):A7 保护期归因长句每颗重播(P3 烦感已降)、A8 - second_run_ask 仅页面进入时播(restart 路径不经过,设计内)、B6 保护期 - 时长恒定 60s 无缩水(报告按 shield_block 估算有误差)、B7 菜单选项播报 - 延迟感(voice 安全门串行代价,可接受)。 -- 依据:round2-live 试玩报告「如果只能改 3 件事」全部 3 条 + B4/B5。 - space playtest 断言 138 → 155:新增 17 条(hit_last 在播标记×2、接播期间 - 呼喊抑制×1、呼喊恢复×1、坠毁前警告在播×1、坠毁掐链×1、恢复缓冲×4、 - 误暂停守卫×2、回大厅收尾×5——含 leaving 态/确认句/播完前不跳转/滑动忽略/ - 跳过即走),替换 1 条(菜单下滑前提补"过冷静期"),hit_last 段既有 - "呼喊顶断+接力"断言语义零回改;guide.test 全绿(space G7 呼喊断言 - 不受影响:该链路 hits=0)。 -- **live 复验**(web/harness/live/out/space-20260901063255,LIVE_PORT=8140, - 8128 被占按预案自起;voice=male/speed=1.35/bgm=1.0/guide=full 全拟真驱动): - ① 误暂停守卫——判定(dodge)ta=6.28 后下滑落点 ta=6.76(Δ=0.48s<0.8s - 冷静期)无 paused/bgm-menu;过冷静期 +0.9s 非下方悬置下滑正常开菜单 - (paused ta=10.22),爬升段仍可开菜单(paused ta=82.65)。② 恢复缓冲—— - 恢复 ta=83.23 后首颗 +0.80s 刷出,前 3 颗逼近实测 4.00/4.00/4.00s、 - 刷怪间隔 4.00/4.00s,第 3 间隔起回曲线(2.68s)。③ 回大厅——双击确认后 - menu_lobby 确认句起播(ta=118.94),随后探针在新文档重新就绪 - (probe_ready t=12.3 ta=-0.882 = 跳转 '../' 成功)。④ 警告链——第 2 撞 - ta=104.94 → spawn 呼喊顶断归因(生存优先级保留)→ 接力 hit_last - ta=105.61(+0.67s,与 round1 低速好评路径同型),在播 [105.61,107.42] - 无 dir_* 插队;坠毁 ta=107.42 即掐(vend kill 同刻),crash 后 1.0s 零 - 新开语音,结算 time_3 于 +1.30s 干净开场,坠毁后无 hit_last/gd_last_life - 迟到。复验驱动与探针脚本:web/harness/live/out/verify-space-r2.mjs、 - probe-gohome.mjs(out/ 不入库)。 - ---- - -## 19. round2-live 变更记录(rpg《剑与声》小周全拟真复测修复) - -> 依据 research/04-playtests/live/round2/rpg-xiaozhou.md(P1-2 复验「未修复·形式 -> 改变」:格挡与受伤播报错误连线 / P1-新① 探索菜单语音播放中吞 tap / P2-新② -> 教学提示自动轮播互掐 / P2-新③ 杀敌瞬间腰斩回合提示音;P3 酌情)。设计依据: -> 11-rpg-battle-rebalance §3 + 05-rpg-addendum §1 受击行「血量档位降档时 -> st_hp_mid/st_hp_low(降档才播,不每击都播)」——该播报本就是"受击(未格挡)" -> 的附属,不是血量变化的全局钩子。台词文本与语音 id 零新增零变更;只改触发 -> 逻辑/排队语义。按 §8 先例只追加登记受影响断言的替换口径与新增断言;§6 与 -> §14 既有文字不回改,其余 5 个游戏断言零改动。 - -- **P1-2 格挡受伤播报重连线(擦伤不触发 + 档位一致 + 同档节流)**。live 事件流 - 复盘:全程 20 次格挡成功、0 hurt 音效,st_hp_mid/low 却精确落在格挡音后 - 0.09s 播出(bandit 636.5s/841.2s、Boss 962.1s/984.0s)——根因:旧实现把降档 - 播报挂在"血量变化"上(bandBefore/bandAfter 跨档即播),格挡擦伤(1~3)跨档 - 同样触发,全格挡完胜被全程喊受伤,警报被驯化成背景音。逐秒重建血量轨迹另 - 证实:播报时刻档位本身正确(984.0s 时 7/28=0.25 确属 low),玩家感知"喊 low - 实际 mid"来自胜利升级 +8 回血使旧播报变陈旧(997.1s 升级后 12/36=0.33)。 - 裁定(game.js `_enemyResolve`,注释明写):a) 格挡擦伤静默走血,**不触发** - 任何受伤类播报(痛感由格挡音+confirm 震动承担,11 文档 §3"格挡成功 2-3 伤" - 是技巧收益而非受伤警报);b) 只在实质受击(格挡失败/未格挡)时播,且播报档 - = 结算后当前实际档——`b.hpBandVoice` 记"玩家最后被告知档"(开战=当前档), - 比它差才播并同步(擦伤暗中跨档后,第一刀实质受击按当前实际档补播,绝不 - 错位);c) 节流:同档不重复喊(low 档内再受击不重播);喝药回血升档静默, - 之后经中间档再次跌破视为新降档可重播一次(回血有 potion 音可感知,不属 - "狼来了")。§14「新增边界断言(P1 全格挡不掉 low)」口径加强:全格挡 - Boss 战不播 st_hp_low **也不播 st_hp_mid**(擦伤一律静默),该断言其余 - 部分(擦伤 ∈[1,3]、战后血量 ≥0.3、可击杀)不变。新增场景 B10 断言 13 条 - (擦伤跨 0.7 边界零播报/被告知档不变、实击降档播 st_hp_mid 且与实际一致、 - 同档再击不重播、跌 low 播 st_hp_low、low 内再击不重播、回血直接跌回 low - 不重播、回血经中间档再跌破=新降档重播一次)。 -- **P1-新① 探索菜单语音播放中吞 tap(跳句 + 补发语义动作)**。live 证据: - 1264.6s 选项报名开播 2.3s 时单击被整个吞掉(语音被掐、场景未切、死寂 - 72s);1370.1s「最后一个了。」开播 0.16s 时单击"返回"同样被吞;而战斗里 - 语音中单击有效(844.5s),规则必须一致。12 文档 §1 跳句惯例在 rpg 探索态 - 的口径补全:语音中单击**一律先跳句**;在播句为选项报名/边界提示 - (`_exploreVoiceKind==='option'`)时单击同时**补发确认当前选项**;desc/ - NPC/事件/状态链在播 = 只跳句不确认(A5/B6 契约不变)。实现要点:类别先 - 取样再 skipVoice——skipVoice 同步触发 onComplete 推进链(末句接播选项报名 - 会改写类别),跳句后读类别会把"跳 desc"误判成"确认选项"。新增场景 B11 - 断言 7 条(报名在播单击=跳句+进商店、desc 在播单击=只跳句场景不变、 - gd_wrap_last 在播单击"返回"=跳句+回上一张图,guide=full 段)。P3-3② - 「提示音播放期间 tap 被吞」随本条一并修复(同一路径)。 -- **P2-新② 教学提示自动轮播互掐("播完再上"统一治理)**。live 证据: - 333.09s tut_b0 自动重播播到 7.9s 处被游戏自己的 16s 兜底推进 - (tut_battle_2)掐断——系统自己的提示链互相顶。裁定:自动轮播路径 - (8s 重复提示 / 16s 兜底推进)一律"播完再上"——8s 重复提示到点遇语音 - 在播则跳过本次(不重播不顶句);16s 兜底到点遇语音在播则每 1.5s 重试、 - 等声道空出再推进(延后不死锁)。B1 既有断言不变(无语音占用时 8s 重播、 - 16s 兜底原样触发);新增场景 B12 断言 5 条(占用声道时重复提示跳过、 - 兜底延后且 teachStage 不变、声道空出后推进且推进语音开播时刻 ≥ 声道 - 空出时刻)。 -- **P2-新③ 杀敌瞬间腰斩回合提示音(指令名礼让排队)**。live 证据:843.9s - 「攻击。」开播 0.9s 被 btl_enemy_die 掐成残句。裁定:`_speak` 礼让规则 - 扩展(与 round1-live gd_stall 排队一脉相承)——刚开播的指令名 - (cmd_* ≤1.2s 短句)还在播时(1.6s 余波窗),结算语音链首句 queue:true - 排队等它播完(杀敌/半血/降档/教学推进链同受此规则);余波窗外恢复默认 - 顶句语义。B8 对照断言(无语音在播时击杀语音不排队)不受影响。新增场景 - B13 断言 4 条(指令名开播 1.6s 内击杀语音 queue:true、排队后胜利链正常 - 走完不死锁)。 -- **P3 酌情与遗留**:P3-1 商店折扣报价仍报原价("铁剑,一百金")、购后无 - 折后/余额播报——无折扣价目与余额整句资产,受"不得改台词文本、不新增 - 语音 id"约束无法修复,列遗留(同 tut_b3 先例,待 TTS 补录折扣价目句与 - 购后余额句;九折扣款 `shop_discount` 逻辑本身正确,A 场景断言覆盖)。 - P3-2 前摇节奏"敌人等台词说完再出手"需教程明说:无现成教学句资产,列 - 遗留。P3-3① 左端反绕到末项无提示:round1-live 已落地"落到末项即播 - (不分方向,每场景/每次进商店一次)"——167.4s 系同次进商店第二次落 - 末项被 `_wrapShown` 去重,属设计内防抖,维持不变。P1-1 NPC male 语音 - 资产缺失:资产层遗留(非代码),照旧列遗留。ASR 短句误识:备查非游戏 - bug,不处理。 -- 依据:round2-live 试玩报告 P1-2 复验 + 新发现问题 1/2/3 +「如果只能改 - 3 件事」全部 3 条;rpg playtest 断言 159 → 188(新增 29 条:B10×13 + - B11×7 + B12×5 + B13×4),既有 159 条语义零回改(§14 全格挡断言按上文 - 口径加强,不删减),其余 5 个游戏断言零改动。 - ---- - -## 15. round2-live 变更记录(rhythm《节拍行者》小江全拟真第 2 轮复测修复) - -> 依据 research/04-playtests/live/round2/rhythm-xiaojiang.md(N1 immediateTap -> 爆音计入判定 / A2 正片踩点下滑弹菜单 / N2 菜单瞬穿 / A4 BGM 死亡)。选型 -> 任务书方案 a:引擎 gesture.js 增加 `tapcancel` 事件(仅 immediateTap 模式 -> 补发,其余 5 游戏零行为变化);rhythm 游戏层配套判定账簿与复活机制。 -> 台词文本与音频资产零新增零变更。按 §8 先例只追加登记;§6/§13 既有文字 -> 不回改。 - -- **事件流对质结论(A4 BGM 死亡 = 事件误读,§13 P1-3 附记结论成立)**: - round2 三个"BGM 排程 0.45s 即被 stop"现场(out/rhythm-20260901020139 ta - 378.67/691.56、rhythm-20260901022139 ta 257.99)被 stop 的 sid 全部是 - **结算/选曲的 menu pad**(引擎层 bgm/menu:sid 108/235/1),由 `_startSong` - 的 `stopBgm(400)` 淡出停止(设计行为,防双 BGM 叠加);曲目 BGM track90 - (sid 109/236/2)均按网格正常起播并持续到下一次菜单/结算。混音光谱对质: - retry 局 40-150Hz 低频段 RMS −21.47dB 与"玩家确认有 BGM"的教学后首进段 - (−21.50dB)一致,menu pad 只有段 −31.84dB——**retry/选曲路径 BGM 实际 - 在播,"BGM 死亡"是 stop 事件的 sid 张冠李戴**。唯一真实的 BGM 中断是菜单 - 劫持连带(下滑误判弹菜单 → `_stopBgmSrc`),随正片下滑修复(下条)根治。 - BGM 生命周期代码零改动。 -- **N1(P0)immediateTap 爆音判定治理(引擎 tapcancel + 游戏判定账簿)**: - gesture.js immediateTap 模式补发 `tapcancel`——按下已发 tap 后,移动超 - tapSlop 转滑动 / longpress 成立 / 触摸 cancel(含无 move 事件快滑在滑动 - 事件前补发)时各补发一次;双击序曲不发(归属由游戏按双击窗口处理)。 - rhythm 新增序曲 tap 判定账簿 `_tapJudg`:演奏阶段(tut/main)每次 tap - 判定(命中/自由演奏/教学尝试)记账;`tapcancel` 到达撤销最近 1 笔 - (≤0.6s),`doubletap` 到达撤销最近 2 笔(≤0.5s)——声音已出收不回, - 判定/统计回滚且不计 Miss;被误认领的命中撤销后音符回到未判定(真不接 - 由超时 Miss 兜底)。另:非演奏阶段的 500ms 延迟裁决 tap 由 tapcancel - 提前作废(慢滑在裁决窗内超 slop 即撤,选曲慢滑不再误触发"选定曲目")。 -- **超时 Miss 复活(N1 判定污染的事件流真凶之一)**:round2 事件流实证 - miss 全部落在泵的超时死线(expected+GOOD+补偿+0.10)上而非序曲 tap 的 - 错手势认领——滑动识别 +0.3s/双击 +0.2s/触程余量仅 +0.10s,手势还在 - 路上时音符常先被拖死几十 ms。新增归属①′:同手势、超时类(kind= - timeout,错手势类不复活)、REVIVE_WINDOW_S 0.6s 内、补偿 dt ≤ - REVIVE_DT_S 0.5s(GOOD 0.35 + 慢滑/慢双击补偿误差 0.15)→ `_reviveMiss` - 回滚统计后按 dt 改判 Perfect/Good。tap 不参与复活(immediateTap 下 - tap 识别零延迟,回调晚于死线 = 触摸本身已晚;且防双击序曲误复活)。 -- **A2(P1)正片踩点下滑弹菜单续修**:§13 的 `_downNotePending` 窗口在 - 正片从未失效,但事件流 3/3 复现的时序是"超时 Miss 先于下滑回调 2~114ms" - ——音符在滑动触程中已被泵拖死,回调时 `_downNotePending` 判假 → 弹菜单。 - 修复:down 音符刚被超时拖死(REVIVE_WINDOW_S 内)同样算"临近"——下滑 - 不弹菜单,先进复活改判(dt ≤ 0.5s)/ 超时差自由演奏 tom_lo;该期间下滑 - 豁免异手势 Miss(不顺杀邻音)。窗口过后(>0.6s)下滑呼出菜单的折中不变。 -- **N2(P1)菜单去抖独占期**:菜单打开 1s(MENU_DEBOUNCE_MS)内不收任何 - 手势——演奏途中被弹菜单时,玩家已在路上的手势(双击确认/下滑关闭) - 不得穿透菜单(实测 menu_main 说明语音 0.6s 未读完菜单即被"确认")。 - 配套:guide.test.mjs §⑥ G3 两处关菜单手势前补 1.05s 等待(行为变更的 - 既有断言适配,断言语义不变);rhythm playtest 场景 A/B 既有菜单用例 - (开菜单后 2s/1.5s 才关)天然越过独占期,零改动。 -- **新增断言(rhythm playtest 109 → 141,新增 32 条,既有零回改)**: - F3 教学 tapcancel 撤销序曲 tap 尝试记录;F1 正片 tapcancel 撤销序曲 tap - 命中(音符回未判定、统计回滚、可重接 Good);F2 双击序曲 tap+tap+ - doubletap 归属(双击改判 Perfect、序曲零 Miss、不误伤更早真实命中); - F2c 序曲 tap 误认领命中被双击撤销、撤销后真不接由超时 Miss 兜底;F4 - 上滑超时拖死(missKind=timeout)后慢滑回调复活改判 Good、Miss 统计 - 回滚;F5 正片 down 音符超时拖死后下滑不弹菜单且复活改判 Good 播 - tom_lo;F5b 窗口内晚下滑(dt 超 0.5s)不弹菜单、Miss 维持、tom_lo - 自由演奏;F5c 窗口过后下滑正常呼出菜单;F6 菜单 1s 去抖期(双击/下滑 - 不穿透、菜单语音不被掐)+ 期后双击正常确认恢复。 -- **引擎回归**:gesture.js 改动 opt-in(immediateTap 默认 false 零行为 - 变化);6 个 playtest 全绿(escape 140 / rpg 188 / simon / space 152 / - storyline / rhythm 141)+ guide.test 122 断言全绿。 - -## 20. 新游戏登记:《不眠频率》(radio) 与《回声地窟》(dungeon)(new8 批次 1) - -两款新 RPG 的断言清单与实现核对**住各自设计文档**(断言条目随轮次在原地维护): - -- radio:[15-radio.md](15-radio.md) §测试(playtest 清单)+ 文末「实现核对」(193 断言逐条 ✅/合并/舍弃)。 - 要点:4 结局矩阵路径、pendingChoice 盲玩公平、C3 埋线索/C4 消费差分、rep 剧本验算、 - RING 看门狗两级不同句(gd_ring1/gd_ring2)、模态菜单循环-确认-关闭不丢节点、中断复述、存档 schema。 -- dungeon:[16-dungeon.md](16-dungeon.md) §测试 + 文末「实现核对」(1277 断言逐条)。 - 要点:固定 seed 网格复现+不变式、回声调度全扫描(echo_1..4 延迟/音量/材质/pan)、DRILL 九步、 - 学期零死、格挡 80%/逃跑必成挨一下/Boss 拒逃、死亡归因真话(3 死因矩阵)、元进度遗产解锁、 - 菜单暂停冻结、上滑 5 段状态链。 - -R1 起每轮的增量断言(修复回归)在各自设计文档的轮次小节追加,不在本文件重复。 - -## 23. new8 批次 2-4 六款新游戏断言登记(2026-09) - -与 §20(radio/dungeon)同规:断言清单与实现核对**住各自设计文档**,随轮次原地维护;本文件只留指针。 - -- abyss《深海回音》:[17-abyss.md](17-abyss.md) §断言(剧本 A-F 六路径/氧气门三形态/sonarGate/死窗/双轨看门狗/中断复述/尾句保护首播制/链末看门狗不拆) -- detective《雨夜专案》:[18-detective.md](18-detective.md) §断言(seed 矛盾复现+矩阵不变式/指证三路径/评级账本/板导航/双轨/死窗/结案确认闸/选句记忆位/VERDICT 链锁实弹) -- cipher《暗码》:[19-cipher.md](19-cipher.md) §断言(摩尔斯固定字符逐元素/四结局矩阵/三层兜底/暴露度档位/morseGate) -- teahouse《深夜茶馆》:[20-teahouse.md](20-teahouse.md) §断言(泡茶三步时机窗/经济逐晚对账/弧线三方向/时机窗内 voice=0 分层) -- keeper《守塔人》:[21-keeper.md](21-keeper.md) §断言(时空签名教学-兑现/插叙触发回返/trust×truth 档位/4 结局) -- bridge《星环舰长》:[22-bridge.md](22-bridge.md) §断言(巡检节奏逐毫秒/危机×系统态矩阵/仲裁 7 场景/忠诚差分/评级验算/死窗) - -## 21. 新游戏登记:《深海回音》(abyss)(new8 批次 2) - -叙事款(radio 同骨架),断言清单与实现核对**住设计文档**(§20 先例): - -- abyss:[17-abyss.md](17-abyss.md) §9(playtest 清单)。要点:四结局氧气剧本验算(lefts 公式 + 剧本 A-D 逐检查点 - oxy 档位句)、第四潜氧气门三形态(双载/单载/取消)、氧气账目真实(floor(oxy/3) 档位、补给恰一次、mid-dive - floor 干涸链)、声呐双通道仲裁(echo/pipe_knock 与 voice 零重叠 + sonarGate 排队)、教学承诺兑现清单 - (金属=钢板/单击=脉冲/四潜/氧气门/扣三格/敲管/补给)、看门狗双轨(乱按 96s 必被抢救 + 有进展零打扰)、 - 结算链 800ms 手势死窗(dungeon H1 内化)、键盘 _k 变体按 inputMode 选播、pendingChoice/p* 不可跳、 - 中断复述、存档 schema。**R2 增量(2026-09-13,round1 synthesis F1-F10)**:ping 即时性(gd 散句在播时单击先掐后响)、 - 尾句保护认实际链尾(dyn 档位句纳入)+ 签收链死窗、回波 wait=实长+静默一拍、dv4_dual/oxy_crit_deck/prog_saved1..5 - 新句断言、叙事尾句首播保护+复播可即掐(G5)、full 档链末看门狗 [13,30]、danger 震 1100ms、crit 震按档去重。 - 详见 [17-abyss.md](17-abyss.md) §12 修订记录。 - -## 22. 新游戏登记:《暗码》(cipher)(new8 批次 3) - -叙事款(radio/abyss 同骨架 + 电文解码小局),断言清单与实现核对**住设计文档**(§21 先例): - -- cipher:[19-cipher.md](19-cipher.md) §9(playtest 清单)。要点:**摩尔斯固定字符断言**(运行时 SYN 模块 `morse.js` - 事件钩子,BAY/TOP/HAND/SPY 逐元素 + 时值容差 ±5ms + 三档速度 u=80/110/160;**不随 settings.speed 变**); - **三层兜底剧本各自走通**(纯耳朵剧本 A / 纯慢放剧本 F / 提示流剧本 E:T1 免费页 + 3 页逐值 book_2/1/0); - **译错归因真话**(T1 dv1_wrong 句内字母与真词一致 + 无限重选零惩罚;正片判定出口零对错语音;ep_* 与 - t/picks flags 单源对应);**暴露度剧本验算**(剧本 A-D 逐 NIGHT 检查点档位句 = wrong+risk 公式,收网门=hot 档 - 阈值 5);四结局矩阵(expose 档 × p9);morseGate 零重叠(摩尔斯与任一 voice 区间);密码本页数账目(逐值 - 整句、页数 0 翻本=error 震+翻空本音零惩罚);看门狗双轨(纯浏览/重听不计进展,乱按 96s 必被抢救); - 结算链 800ms 手势死窗(判定出口/NIGHT/d9/e*);键盘 _k 变体 11 对按 inputMode 选播;pendingChoice/p* 不可跳/ - 摩尔斯首播不可跳;中断复述(含 DECODE 态恢复链 prog_t→book 档→gd_tune2);存档 schema(summary 带 - 暴露档名)。 - - ---- - -## 24. hunt《围猎》(联机切片;设计 [23-hunt.md](23-hunt.md),台词表 23b) - -> 本作是首个联机游戏:断言分两侧——**服务器侧**(`web/server/test/` 新增 hunt-room 单测,直接 import 房间模块 + 假 WS 连接,固定 seed 复现)与**客户端侧**(`web/hunt/test/playtest.mjs`,mock 引擎 + mock transport 注入 ev/snap)。协议字段以 23 号文档 §6 为唯一契约。测试钩子可读 game 实例字段与房间实例字段;手势只经引擎回调注入。 - -**关键路径**(首局教学房 → 5 波 → 结算 → 排行榜,≤150 次手势): -1. 启动遮罩点按 → 音频解锁(此前零播放)→ TITLE;无存档单击 → 断言客户端发出 `join { room:"quick", teach:true, ver:1 }`。 -2. **教学房**:注入 `joined`(phase:'teach',slot 0,roster 含 2 bot,seed=42)→ T0(intro_1→intro_2→tut_rule 有序)→ 单击 → T1(tut_attack + 注入 spawn 狼 N eta12)→ 先错向滑 W(断言 `act attack dir:W` → 注入 `ev miss` → atk_swish + error 震 + gd_idle 重教,怪物不受伤)→ 向 N 滑 → 断言发出 `act attack dir:N` → 注入 `ev kill` → tut_kill_ok;T2 单击 → `act callout` 发出 → 注入 bot help_ack(断言 `sfx/npc_yan_help_ack` 播放且 pan=该 bot 防区中心)→ tut_callout_ok;T3 双击 → `act skill` → 注入 `ev skill cleared=[2]` → tut_skill_cd;T4 上滑 → 状态链 4 段有序(hp_band→st_wave→team_3→kill_band)→ tut_breach→tut_done → 注入 `ev wave 1` 进 WAVE。**教学狼突入零惩罚**:注入教学波 breach → dur 不变。 -3. **bot 补位**:非首局(tutDone=true)→ LOBBY(room_wait 播)→ 单击发 `act start` → 开局 roster ev 断言猎人总数 = max(3, 人类数)、bot 槽 char 齐全、bot greet 逐只间隔 ≥1.5s。 -4. **5 波推进**(seed=42 服务器侧快进 tick):每波 spawn 事件数/型别/逼近时长与 23 §3.4 表逐波一致;方位分配 ∈ 各座位防区;狼击杀(滑向 → act → ev kill)断言归属 slot 与 kills 账;漏一只狼 → ev breach,dur -1,`breach` 播报 + hit 震;熊漏 → dur -2 + `breach_heavy`。 -5. **终局**:注入 `ev end { win:true, dur:8, grade:"A", kills }` → 结算链有序(end_win→grade_a→kill_band_*→end_rank→end_again)→ 断言 PUT `/api/progress { game:"hunt" }` 且 `data.summary` 命中生成规则(`最佳评级A · 击杀14 · 营地余8 · 共N局`)、best 按"评级>耐久>击杀"序更新;单击 → 重新进匹配。失守路径(dur 归零):end_lose 首句、无 grade_*、无 end_rank、不写 best、runs+1。 - -**边界断言**: -- **断线转 bot**:关连接 30s(服务器侧快进)→ roster ev 该槽 bot=true 且 char 沿用 → 该槽开始产出 act(tick 内断言);剩余人类 ≥3 时改为槽位移除(roster 缩短)。 -- **重连回槽**:断开 15s 内带 `resume` 重连 → joined 同 slot/seed/char + 紧随 snap 全量 → 客户端怪表与 snap 一致、net_back 播报、战斗续接;超 30s → err `room_gone` → net_gone → LOST。 -- **旋风斩冷却**:放出后 8s 内双击 → 无 act 发出、播 threat_*(按内圈只数选档);冷却完成 → skill_ready(queue:true 不顶句);伪造 `act skill` 越冷却 → 服务器 err `skill_cd`。 -- **出击冷却 2.5s**:冷却内滑 → 仅 error 震、零 act;伪造连发 → 服务器只裁决首发,其余 err `cooldown`。 -- **支援呼喊 bot 补位**:呼喊后 10s 内,服务器侧断言补位 bot 的下一次 act.dir ∈ 呼喊者防区(偏置 ×2 生效),且 `help_ack` 恰播一次;呼喊节流 5s(连点只出一条 callout ev)。 -- **听觉裁剪上限**:eta>6s 怪零 loop;任意时刻 loop ≤3 路且为 eta 最小 3 只(注入 5 只内圈怪断言);threat_* 选档只计 eta≤4 只数;状态链/威胁句无任何逐方位枚举(台词表结构保证)。 -- **拥挤控制(R1-R8)**:第 5 波峰值剧本下断言——同刻 voice ≤1;同刻 `npc_*` ≤1 且 10s 窗 ≤2 条;旁白在播时喊话延迟 ≤2s 或丢弃;one-shot 定位音同刻 ≤2;breach/danger_low/wave 播报零丢失。 -- **snap 兜底**:客户端随机丢弃 50% ev,仅凭 snap(2Hz)收敛 → 怪表与服务器真值一致(id 集合 + eta 偏差 ≤0.5s 校正)。 -- **菜单不暂停(E3)**:菜单开着注入 breach → dur 照扣、菜单不收;菜单 3 项固定、单击循环/双击确认/下滑关闭。 -- **学期零惩罚**:教学波 breach 不扣 dur;T1 做错 2 次自动放过不卡死。 -- **结算死窗**:end 到达前后 800ms 内注入 tap → 被吞(不进 TITLE/不重开);死窗后单击正常再来一局。 -- **屏蔽任意 voice 资产**:结算链/警报链照走完(看门狗兜底推进),不卡死。 -- **资产齐套**:voice 双性别 60 句集合一致、key 变体 11 对 ⊆ 主集、`sfx/npc_*` 24 条双格式(assetExists 口径)。 -- **opts.pan 回归**:无 pan 调用路径零 panner 节点(引擎行为不变);pan=-1/0.7/1 时 panner 值正确;老 Safari 无 createStereoPanner 静默降级。 - -**全局通用(§7 五条全跑,另加本作条目)**: -- 启动遮罩点按前零音频播放;遮罩手势不被游戏误收(引擎双保险)。 -- **guide 三档**:full=签名+语音+震;standard 无向导语音;**off=全静且零看门狗定时器**(WAVE 30s 空闲零向导播放,内容层 loop/音效除外)。 -- **震动语义**:逐条对照 23 §2 表——击杀 pickup / 挥空 error / 旋风斩 success / 突入 hit / 收到呼喊 warning / 耐久 ≤2 danger / 波次 heartbeat;手势本身震动由引擎内置承担,游戏层不重复(无双震)。 -- 设置边界:speed 切换下一句生效;bgm=0 全程无 bgm 加载;voice 切换下一句用对应目录。 -- 服务器不可达:TITLE 单击 → net_lost → net_gone → LOST 回大厅(明示失败,不静默死屏)。 - -**失败即不通过**:评级与 dur 不符(归因假话);服务器接受违规 act(冷却/方位/近战窗外命中);ev 与 snap 矛盾不收敛;bot 喊话压过/打断旁白(R1/R2 破);loop >3 路;教学狼扣耐久;失守局写入 best 排行榜。 - -### §24 补遗 · hunt v2 修订断言(2026-09-19,设计依据 [23-hunt.md](23-hunt.md) §16 / research/hunt-sim/) - -- **S1 下滑语义**:WAVE 战斗态下滑(含弧内无怪)→ 恒发 act attack dir:S(空挥真话), - 菜单绝不打开;waveclear 后 8s 休整窗内下滑 → 菜单打开。(playtest Block 3 已按此改写) -- **S2 冷却反馈**:同一冷却窗内第二次起的被拦滑动手势 → 无 error 震(仅首拦震); - 本窗被拦过 → 冷却就绪时刻恰一记 beat 震。 -- **S3/S9 语音纪律**:个人击杀 0 的局,kill_band0 在状态链与结算链均不出现;状态链 - 连续两次上滑(无任何分档变化)→ 第二次不重复播报。 -- **S4 看门狗**:WAVE 内本波已有出手/击杀后闲置 10s → 无 gd_idle 语音(纯震); - 语音闲置每局 ≤1 次。 -- **S5 协作人声**:真人杀熊/三连杀 → 该座角色 kill 喊话广播;满编真人房(无 bot) - callout 后 2.5s → 邻座角色 help_ack(priority);help_call/help_ack 待播不被 - 2s 超时丢弃。 -- **S7 难度**:4 人房每波 spawn 数 = 3 人基准 ×4/3(waveCounts 口径);第 3-5 波 - 存在同秒进内圈的跨座位齐压对;bot 在人类座位怪进内圈头 2s 内不出手该怪; - ev end.grade 按 gradeForEx 双轴(S 需 ≥2 人各 6 杀)。 -- **S8 教学**:T1/T2/T3 作答步内下滑 → 不触发 tut_skip(T1 按 miss 答错处理)。 +允许用测试钩子快进计时器;手势必须走引擎。用于防回归:谜题旗标、战斗数值、谱面判定窗口、存档字段。**不要**在 off 档断言向导语音。新手交互必须另跑 full 档乱点机器人。 diff --git a/docs/design/14-guide-layer.md b/docs/design/14-guide-layer.md index e26b3e28..94ee5a70 100644 --- a/docs/design/14-guide-layer.md +++ b/docs/design/14-guide-layer.md @@ -1,248 +1,139 @@ -# 14 · 向导交互层设计裁定(v1 基线) +# 14 · 发言权、向导与迷茫接管(26 号对齐) -> 依据:research/02-methodology/methodology.md §3(L3 状态层 / L4 引导层)→ -> research/03-schemes/schemes.md §1/§2(实施蓝图,唯一事实源)。 -> 本文存档 v1 已落地的裁定与新增语音资产台词表,供后续轮次回溯。 -> 实现载体:`web/engine/guide.js`(引擎向导模块)+ 各游戏 game.js 接线。 +> **给开发者的实现要点摘要** +> 1. 本文取代 v1~v4「只靠 earcon + 静默看门狗 + queue 插播」的向导层。新骨架:Floor(发言权)+ 回执 + 三档提示 + 迷茫检测。模块示意见 26 号 §4.1~§4.5。 +> 2. `guide.js` 的 8 个 earcon 词表保留(menu/explore/battle/choice/settle/danger/turn/nudge)。`turn` 仍是交接提示音。 +> 3. 看门狗不再原样重播帮助长句。同一等待点用三档:完整句 → 短句 → 只响 `turn`/`nudge`。 +> 4. 纠错、提示类语音:**空闲才播,过期丢弃**;玩家一操作就清空。禁止排到长链尾。 +> 5. 文末档案保留 v1~v4 台词 id,供资产对照。**交互规则以本文前半与 26 号为准。** -## 1. 裁定摘要 +交互权威:[26-audio-interaction-playbook.md](26-audio-interaction-playbook.md);全局口径:[00-global-ux.md](00-global-ux.md) §6。 -| # | 裁定 | 内容 | +--- + +## 1. 六个引擎模块(施工) + +| 模块 | 职责 | 关键 API(示意) | |---|---|---| -| 1 | 分期 | v1 只做「全局基线」= 引擎向导层 + 逐游戏接线;场景参数包(A 通勤/B 摸鱼/C 睡前/D 下班)与人设旋钮在试玩验证后分期(schemes §3)。 | -| 2 | 等级 | `settings.guide`:`'full'`(默认,earcon+语音+震动)/ `'standard'`(earcon+震动,无语音)/ `'off'`(全静 no-op,**不挂任何定时器**)。大厅设置面板「向导帮助:详细/只听提示音/关闭」。 | -| 3 | earcon | 8 个签名(6 状态 menu/explore/battle/choice/settle/danger + 2 功能 turn/nudge)**全项目统一**,全部运行时 WebAudio 合成(零资产、可复现);总长 0.25~0.4s、单音 ≥0.08s、125Hz~5kHz、总增益 ≈0.12;家族=波形音色、子类=节奏轮廓(schemes §1.4)。 | -| 4 | 语音纪律 | 向导插播语音一律 `queue:true`(等当前对白播完),**绝不顶掉正在播的内容**;抉择提示(storyline p*)播完即可选的等待点不插任何语音。 | -| 5 | 看门狗 | 统一模板:静默 N 秒 → nudge 轻 earcon(+ full 档语音)→ 二次升级播上下文提示 → 级数用尽自动停止;任何手势/向导动作复位。escape 的三级卡关骨架保留,仅加「连续撞墙 ≥2 次 → full 档阈值提前一级」。 | -| 6 | 引擎配套 | `audio.isVoicePlaying()` 公开只读 API(收编 5 处私有 `_voice` 读);`audio.setRateBoostEnabled(bool)` 长按快进 per-game 开关(rhythm/space 对局期豁免);`settings.speed` 增加 2.2 档(视障档,250+ WPM)。 | -| 7 | 语义分工铁律 | 语音说「你要做什么」、earcon 说「哪类事/哪个状态」、BGM 说「宏观状态」、震动说「刚才那下对不对」——向导层不越界(L2 铁律:earcon 只做粗标记,语义靠语音)。 | +| Floor | SPEAKING / YOUR_TURN / BUSY / ACTION;整链 duck;等待声 | `speak(ids)` `yieldTurn(waitPoint)` `handleInput(g)` | +| Receipts | ok / invalid / busy 合成短音 | `play('ok'\|'invalid'\|'busy')` | +| Hints | 三档选择 + 30s 台账去重 | `pick(waitPoint)` `replayOnRequest()` | +| Confusion | 乱点/无效/重听/失败/静默加权 | `record*` → `onConfused(level)` | +| Novice | 手势解锁、`doubleTapWindow: 0`、回车当单击 | `unlocked(g)` `map(g)` | +| Pipeline | 手势先过 Floor → Confusion → 游戏 → 回执 | `makeInputPipeline(...)` | -## 2. 逐游戏接线裁定(v1 落地范围) +`settings.guide`: +- `full`:等待声 + 三档语音 + 回执 + 迷茫接管。 +- `standard`:等待声 + 回执 + 提示音,无向导语音。 +- `off`:回执仍在;不挂看门狗与迷茫定时器。回归测试用。 -| 游戏 | 接线 | +## 2. 发言权规则 + +1. `speak()` 进入 SPEAKING,整条链结束才离开。句间不 unduck、不起等待声。 +2. 链结束只能去 YOUR_TURN 或 BUSY,禁止「什么都不是」的安静。 +3. `yieldTurn()`:`guide.turn` → 400ms 后等待声 → 挂该等待点的提示计时与迷茫。 +4. 打断:单击跳句;滑动/帮助打断整链并执行。`critical` 句给 busy。 +5. 等待声参数见 26 §4.1。超过 20s 间隔拉长到 2.5s。玩家操作 80ms 内淡出。 + +## 3. 三档提示与看门狗 + +每个等待点配置 `full` / `short` / earcon,外加静默阈值。 + +| 游戏类型 | 第 1 级 | 第 2 级 | 之后 | +|---|---|---|---| +| 动作 / 探索 | 5~8s 播 short 或 full(第一次) | 再 8~12s 只 earcon | 停,只留等待声 | +| 剧情 | 15~20s 只说操作方式 | 再 15~20s earcon | 停 | + +禁止第 2 次仍播与第 1 次相同的完整句。玩家主动重听:`sys_say_again` + full。 + +旧 escape 60/120/180s 卡关计时器并入迷茫接管:无进展视为静默信号,L1 短目标句、L2 只放行导航方向、L3 游戏走一步。不再单独「带路级长句周期轰炸」。 + +## 4. 迷茫三级接管 + +信号与权重见 26 §4.5。达到 2 分触发,15s 冷却。 + +| 级 | 行为 | |---|---| -| storyline | W1/W3 静默看门狗 [8s]=nudge+`gd_continue`、[20s]=复用 `playHelpAndResume`;选择节点外左右滑的静默 return 处加 error 震+`gd_invalid`;抉择态(p* 播完)不插语音不挂看门狗;状态签名 explore/menu 与状态 BGM 同点。 | -| simon | input 看门狗 [10s]=nudge+`gd_relisten`、[25s]=免扣机会自动重听(每局 2 次);`your_turn` 泛化为 turn earcon(每轮,第 1 轮保留原语音);状态签名 settle/menu。 | -| escape | REOPEN 提示播完与引导步解锁给 turn cue;连续撞墙 ≥2 次 → full 档卡关三级阈值提前一级;其余(wrong/blunt/stuck/nav)保持既有完备体系。 | -| rhythm | 每曲首次领奏→应答切换推 `gd_answer`(谱面排程处入 `_timed`);连续 2 Miss 播 `gd_miss`(queue 插播,同曲一次);结算链换 `_speakGuarded`;main 演奏期 `setRateBoostEnabled(false)`;状态签名 menu/battle/settle。 | -| space | 对局第 1 次受击(保护期外)追加 `gd_hit_hint`;结算链换 `_speakGuarded`;对局期 `setRateBoostEnabled(false)`;状态签名 battle/menu/settle。 | -| rpg | 格挡窗口内做错手势 → error 震+`gd_parry_miss`(每局一次);PLAYER_SELECT 挂看门狗 [60s]=nudge+`gd_stall`;状态签名 explore/battle/menu/settle 与四状态 BGM 同点补齐。 | +| 1 | 该等待点 short 句 | +| 2 | 只保留一个正确手势,其余 invalid | +| 3 | 「听我做一次。」演示一步 →「现在你来。」 | -## 3. 新增语音资产台词表(8 句 ×2 性别 = 16 文件) +同一等待点升到 3 后停止接管,只留等待声。 -定稿依据 schemes.md §1.5,台词全文一字不改;speaker/instruct 沿用各游戏旁白常量 -(见 `tools/gen_voice_dual.py` SPEAKERS 与各 `_XXX` pair): +## 5. 语音类别与 idle-only -| 游戏 | id | 全文 | 触发 | female speaker / instruct | male speaker / instruct | -|---|---|---|---|---|---| -| storyline | `gd_continue` | 轻触屏幕,继续。 | W1/W3 叙事节点播完静默 8s(看门狗第 1 级) | Vivian / _SL_NARR[1] | Uncle_Fu / _SL_NARR[0] | -| storyline | `gd_invalid` | 这个操作在这里用不上。 | 无效手势(选择节点外左右滑静默 return,同局限次) | Vivian / _SL_NARR[1] | Uncle_Fu / _SL_NARR[0] | -| simon | `gd_relisten` | 需要的话,双击可以重新听一遍序列。 | input 阶段 10s 无输入(不改变重听扣机会规则) | Serena / _SIMON | Uncle_Fu / _SIMON | -| rhythm | `gd_answer` | 现在跟着我打。 | 每曲首次领奏→应答切换 | Vivian / _RHY | Uncle_Fu / _RHY | -| rhythm | `gd_miss` | 慢了半拍,下一小节跟上我就行。 | 连续 2 次 Miss(同曲限 1 次,queue 插播) | Vivian / _RHY | Uncle_Fu / _RHY | -| space | `gd_hit_hint` | 被撞了。听方位,同向滑动就能拨开。 | 对局第 1 次受击(保护期外,同局 1 次) | Serena / _SPC | Uncle_Fu / _SPC | -| rpg | `gd_parry_miss` | 前摇声起的时候双击,才能格挡。 | 格挡窗口内做错手势(每局限次) | Serena / _RPG | Uncle_Fu / _RPG | -| rpg | `gd_stall` | 想好了吗?左右滑动选择,单击出手。 | PLAYER_SELECT 60s 无输入 | Serena / _RPG | Uncle_Fu / _RPG | +| 类别 | 打断 | 过期 | +|---|---|---| +| critical | 否(busy) | 否 | +| content | 是 | 否 | +| hint | 是 | 1.5s 播不出则丢 | +| feedback | 是 | 1.0s 播不出则丢 | -资产落位:`web//audio/voice/{female,male}/gd_*.{ogg,m4a}`(管线 -`tools/gen_voice_dual.py --game --only gd_xxx` 生成 + `--convert` 落盘, -双格式配对由 `tools/check_all_assets.py` 终检保证)。 +旧 `guide._say` 对 error/watchdog 的 idle-only 保留并推广到所有 hint/feedback。turn 的 earcon 立即发,不排队。 -## 4. 已知边界与后续候选 +## 6. 状态 earcon 教学义务 -- `onceKey` 语义为「同局(游戏会话)一次」;schemes §1.5 中 storyline `gd_invalid` - 「同局限 2 次」、rpg `gd_parry_miss`「每场战斗 1 次」为更细粒度限次,v1 统一收敛到 - onceKey(一次教学提示足矣),如试玩反馈不足再引入 per-run 限次 API。 -- 教学义务(schemes §1.4「full 档开场教学带教签名 earcon」):v1 未在各游戏教学链中 - 逐个口播签名音,列为 v2 候选(需逐游戏改教学台词,涉及台词表返工)。 -- 向导插播语音目前挂在引擎 voice 队列;若游戏层 `stopVoice()` 会顺带丢弃排队中的 - 向导句(作废语义,可接受)。 +8 个签名音保留。新手期只暴露该游戏用到的 ≤3 个。第一次出现必须配一句,例如「听到这个声音,就是轮到你了。」(配 `turn`)。未教过的 earcon 对新手等于没有。 + +## 7. 全项目共用新台词(各游戏旁白音色各录一份,或引擎共享 sys_*) + +| id | 全文 | 类别 | +|---|---|---| +| `sys_first_tap` | 点一下屏幕。 | critical | +| `sys_turn_teach` | 听到这个声音,就是轮到你了。 | content | +| `sys_say_again` | 再说一遍: | content | +| `sys_where` | (由游戏填:位置。目标。一个动作。) | content | +| `sys_demo` | 听我做一次。 | hint | +| `sys_now_you` | 现在你来。 | hint | +| `sys_only_tap` | 现在只要点一下。 | hint | +| `sys_unlock_up` | 想知道自己在哪,就向上滑。 | content | +| `sys_unlock_hold` | 想知道自己在哪,就按住屏幕。 | content | +| `sys_unlock_menu` | 向下滑,可以打开菜单。 | content | +| `sys_unlock_replay` | 没听清,就双击,再听一遍。 | content | +| `sys_confirm_ok` | 对。 | feedback | + +游戏专属 short 句在各自 voice-ids 的「26 号增补」节。 + +## 8. 与旧逐游戏接线的对照(废止项) + +| 旧接线 | 废止原因 | +|---|---| +| storyline 看门狗重播 help+进度 | 反模式:原样重复长说明书 | +| storyline 双击=整节点重播 | 连点误触;改为阶段 3 才重听当前句 | +| simon 双击重听 / 上滑重听双义 | 新手期无双击;帮助改长按;免费重听改迷茫 L2 | +| simon「免费机会有两次」账目 | 新手期失败不扣资源(原则 15) | +| rhythm 只在静默时提醒 | 补乱点迷茫;演奏期 ACTION 不插等待声 | +| escape 双击使用物品 / 双击求提示 | 单击合并检查+使用;帮助改长按 | +| 向导语音永不顶掉 | 玩家操作必须顶掉非 critical | +| onceKey 同局只说一次纠错 | 改为每个状态×手势解释一次,之后只回执;连续 3 次无效进迷茫 | + +playtest 在 `guide='off'` 时仍测纯玩法。新增乱点机器人必须 `guide='full'`(26 §6.2)。 --- -# 14 · 向导交互层设计裁定(round2 v1.1 增补) +## 档案:v1~v4 裁定与台词 id(交互规则已废止) -> 依据:research/04-playtests/round1/synthesis.md(F1~F9 + §3 新语音表,唯一事实源)。 -> 本文只做**追加**,上文 v1 基线裁定不回改;实现载体同 v1(`web/engine/guide.js` -> + 各游戏 game.js 接线),全部改动已落地。 +以下各节原文是 R7 / round2~4 的落地记录,**只作资产对照**:已生成的 `gd_*` 文件不要无故删除;文案与新手期冲突的(如「双击重新听」)须重录或停用。 -## 5. round2 裁定落地摘要(F1~F9) +### A. earcon 词表(仍有效) -| # | 裁定 | 落地 | -|---|---|---| -| F1.1 | storyline 看门狗礼仪 | at `[8s,20s]`→**`[25s,50s]`**;say `[gd_continue, gd_again]`;**删除升级重播 help+进度+当前节点的 `playHelpAndResume` 逻辑**(`_guideHelped` 一并移除);两级用尽彻底静默(引擎既有)。 | -| F1.2 | simon 25s 升级去泄题 | **取消免扣机会自动重播序列**(`_guideAutoRelisten`/`GUIDE_AUTO_RELISTEN_MAX` 删除)→ 改为 `guide.turn({haptic:true})`(confirm 震 + turn earcon,纯标记无语音)。 | -| F1.3 | rpg PLAYER_SELECT 看门狗 | 60s→**30s**(`gd_stall` 不变)。 | -| F1.4 | 空闲才播(idle-only) | `guide._say` 对 **error / watchdog** 两类:`audio.isVoicePlaying()` 为 true 时**跳过语音只发事件(事件带 `skipped:true`)**——修「纠错句排到语音链尾迟到误导」;**turn 保持 queue 不变**。error 的 onceKey 只在语音真的播出时才计数(没听到不算说过)。space 的 `gd_hit_hint`/`gd_last_life` 接线点随之外移到受击归因链播完后的空闲点,否则会被自家归因句判为「在播」整段跳过。 | -| F2.1 | rhythm 教学首次归因 | 教学步**首次失败**即在纠正链(retry_* + retry_listen)播完后的空闲点发分档句:方向错 `gd_miss_wrong` / 抢拍 `gd_miss_early` / 慢拍 `gd_miss_late`;判定沿用 `_tutAttempts`(手势错 > dt<0 > dt>0),**无窗内尝试(没出手)归入 `gd_miss_late`**;第二次失败走 retry_skip 不再叠加归因。正片连 2 Miss 仍用 `gd_miss`。 | -| F2.2 | space 受击经济透明 | 第 2 次受击在 `hit_`+`hit_last` 链播完后发 `gd_last_life`(见 F1.4 接线点说明)。 | -| F2.3 | escape 撞墙报方位 | 撞墙在既有 `voice/wall` 播完后接既有 `guide_blunt_` 方向句(资产 v1 已有,纯逻辑接线;被下一次移动打断则自然作废)。全档位生效(游戏反馈层,非 full 档专属)。 | -| F3.1 | rhythm 交接提前 | `gd_answer` 不再走 guide 的 queue——由 `guide.turn({})` 发 turn earcon、语音由**曲谱定时队列**(`_timed`,首个应答窗口前一小节头)在 full 档直接落下,保证窗口前听到。 | -| F3.2 | 断点续播 count-in | 暂停菜单恢复时网格/BGM 整体后移一小节,前 4 拍用底鼓(`inst_kick` 0.7)+beat 轻震做 count-in(走既有 `_sched` 网格排程);**off 档保持原即时续播**(零改动路径)。 | -| F4 | rpg 教学看表现 | T2 格挡**成功**通过时插 `tut_b2b`「挡得漂亮。」再进 T3;失败路径(tut_b4)不变。 | -| F5 | simon 经济透明 | 教学收尾链改为 t7_ready→t7_retry→**`gd_economy`**→开局(guard 12s→20s)。作为游戏规则句随教学链播(全档位),与 skip_hint 同类。 | -| F6 | space 警报叠爆 | 警报音效按 soft/loud 两档各自 **600ms 窗口去重**(双陨石同时逼近只起一波渐强警报,单颗两段渐强不受影响);danger 震全局限 **≥400ms** 间隔。 | -| F7 | rpg 菜单回绕 | 探索选项列表与战斗指令循环:右滑回首项→`gd_wrap_first`、左滑到末项→`gd_wrap_last`;full 档 queue 跟在本项报名之后,**同一次菜单打开内每句最多 1 次**(`_wrapShown` 在进场景/进入玩家回合时重置);被下一次滑动打断则自然作废。 | -| F8 | storyline 开场瘦身 | 开场链最前插 `gd_brief`(full 档,brief→intro);intro 长句资产保留不动。 | -| F9 | 语速演示 | round2 会话剧本在 storyline 段演示 `settings.speed=2.2` 切换(转写可见),产品代码零改动(v1 已支持)。 | - -## 6. round2 新增语音资产台词表(10 句 ×2 性别 = 20 文件) - -全文与 synthesis §3 逐字一致;speaker/instruct 沿用各游戏旁白 pair 常量: - -| 游戏 | id | 全文 | 触发 | +| 名 | 音色家族 | 节奏 | 语义 | |---|---|---|---| -| storyline | `gd_brief` | 单击继续剧情。请选择的时候,左右滑动。 | 开场链最前(F8,full 档) | -| storyline | `gd_again` | 还在吗?单击继续。 | 看门狗 50s 升级(F1.1,替代重播 help) | -| rhythm | `gd_miss_early` | 抢拍了。等声音落下再滑。 | 教学 Miss 且抢拍(F2.1) | -| rhythm | `gd_miss_late` | 慢了半拍。跟着节拍走。 | 教学 Miss 且慢拍/没出手(F2.1) | -| rhythm | `gd_miss_wrong` | 方向不对。跟着音色滑。 | 教学 Miss 且手势方向错(F2.1) | -| simon | `gd_economy` | 记不住就双击重新听,免费机会有两次,用完要扣一次机会。 | 开局教学末尾(F5,全档位) | -| space | `gd_last_life` | 这是最后一次机会,撑住。 | 第 2 次受击(F2.2,归因链后空闲点) | -| rpg | `tut_b2b` | 挡得漂亮。 | 教学 T2 格挡成功(F4,全档位) | -| rpg | `gd_wrap_first` | 回到第一个。 | 菜单/指令循环切回首项(F7,full 档) | -| rpg | `gd_wrap_last` | 最后一个了。 | 循环切到末项(F7,full 档) | +| menu | 柔和正弦双音 | 下行二度 | 系统界面 | +| explore | 温暖三角波单音 | 单音长尾 | 探索/安全 | +| battle | 略紧方波双音 | 上行纯五度 | 战斗 | +| choice | 清亮正弦三音 | 上行琶音 | 抉择 | +| settle | 柔和三音 | 下行三连 | 结算 | +| danger | 低频双音 | 急促重复 | 危险 | +| turn | 风铃单音 | 短 | 轮到你了 | +| nudge | 木鱼单音 | 极短 | 轻提醒 | -## 7. round2 验证记录 +参数:总长 0.25~0.4s,单音 ≥0.08s,125Hz~5kHz,总增益 ≈0.12。 -- 6 playtest(guide=off):**断言零改动全绿**(storyline 54 / escape 103 / simon 108 / - rhythm 81 / space 113 / rpg 139,与 v1 基线一致)。 -- `web/harness/guide.test.mjs` 52 断言全绿:storyline 25s/50s 新节奏 + 无重播断言、 - simon 25s 无序列重播 + confirm 震/turn 事件、F1.4 idle-skip 直连断言(watchdog/ - error 在播跳过 + 空闲播放 + turn 保持 queue)、off 档全静零定时器。 -- `tools/check_all_assets.py` 全绿(双格式配对、无 0 字节、全可解码、双性别集合一致); - `tools/extract_voice_text.py` 已刷新 `web/harness/voice_text.json`(428 句)。 -- 转写渲染适配:实际播出的 `voice/gd_*` 由 play 捕获照常渲染;被 idle-skip 的 - 向导句单独标注「本句按 idle-only 跳过未播」。 +### B. 仍可复用的短句(需核对手势口径) ---- +可用(含义不绑双击):`gd_continue`「轻触屏幕,继续。」、`gd_brief`、`gd_again`「还在吗?单击继续。」、`gd_clear`「漂亮,过了。」、`tut_b2b`「挡得漂亮。」、`gd_wrap_first/last`、`gd_next_fuse`、`gd_room_*`、`dir_*`、`gd_miss_none`「这拍没接。」、`gd_slow`「我放慢一些,跟着试试。」、`gd_count`「四拍之后,继续。」 -# 14 · 向导交互层设计裁定(round3 v1.2 增补) +须停用或重录:任何「双击可以重新听」类(`gd_relisten` 旧文、`t7_retry` 旧文、`gd_economy` 旧文)。帮助入口改为上滑或长按后,占用预告 `gd_occ_*` 仅在阶段 3 双击入池后才有意义。 -> 依据:research/04-playtests/round2/synthesis.md(G1~G8 + §3 新语音表,唯一事实源)。 -> 本文只做**追加**,上文 v1 / round2 裁定不回改;实现载体同前(`web/engine/guide.js` -> + 各游戏 game.js 接线),全部改动已落地。G8(不做项)照裁定书记录,不再列。 - -## 8. round3 裁定落地摘要(G1~G7) - -| # | 裁定 | 落地 | -|---|---|---| -| G1 | rhythm「轮到你」迟到 bug | **根因诊断**:round2 F3 把 gd_answer 排在 `leadBarT0 + 4*spb`——那是首个应答窗口**起点本身**(领奏小节的末尾),不是窗口前;P4 转写(窗口 t+79~95、gd_answer t+93.7)即窗口开后才响,起不到预警作用。**修复**:排程点改到 `leadBarT0`(窗口前整整 1 小节 = 领奏小节头);`_armAnswerCue`/`_fireAnswerCue` 承接——菜单恢复也是一次「进 main」,旧标记已消费时对恢复后首个未判定窗口重排同款标记(已迫近则 0.15s 内尽早补上);上一句结束后的下一句不适用(裁定原文)。gd_answer 语音 full 档经曲谱定时队列直达(不走 guide queue),turn earcon **standard 也发**(off 全静不变)。guide.test 增转写级断言:真实跑 90BPM 一曲,gd_answer 时刻 < 首窗口起点,且窗口起点 − gd_answer ≈ 4×spb。 | -| G2 | rhythm 教学降难 DDA | 同一步连续 2 次失败 → 该步领奏**降速 25% 重演**(`_tutRate=0.75`:该步领奏与应答网格间隔 ×1/0.75,领奏小节头仍对齐 BGM 小节,BGM 不变速;排程照走 `_sched` 绝对时间 + 40ms 泵,泵与判定零改动)+ full 档先播 `gd_slow`「我放慢一些,跟着试试。」(经 `_speakGuarded` 串行,播完才重演);每教学局 1 次封顶(`_tutSlowUsed`);该步做对或离开(跳步)恢复正常速度;降速重演后该步重新给两次机会。**off 档维持 round2 的 retry_skip 跳步原路径**(6 playtest 断言零改动红线)。 | -| G3 | rhythm 恢复 count-in 可感知化 | **根因诊断**:round2 F3.2 的 count-in 排了但**从未按时响过**——底鼓 push 进 `_sched` 后没有重排,队头是数秒外的领奏音符,40ms 泵的 `LOOKAHEAD(0.6s)` 前瞻窗口永远轮不到队尾的 count-in(复现实测:恢复后 4 记底鼓与 beat 震全部迟到聚堆),P3 听到的正是「恢复后 5 秒死寂」。**修复**:push 后 `_sortSched()`;底鼓音量 0.7→**1.0**(sfx 层独立于 BGM,每拍 beat 震保持);full 档恢复时先经 `_speakGuarded` 播 `gd_count`「四拍之后,继续。」(每局恢复限 1 次,`_gdCountUsed`;链完/缺失兜底后才开始数拍);恢复用令牌 `_resumeSeq` 防 count-in 语音期间再开菜单的旧恢复串场。**off 档保持 v1 即时续播**(playtest 断言零改动红线)。 | -| G4 | escape 保险丝→新目标断链 | 拾取保险丝的 `unlock_cabinet`(关键获得不可跳)播完后接 `gd_next_fuse`「保险丝到手。接下来,去找电箱。」——游戏反馈层**全档位**(走游戏语音链不走向导通道,gd_economy 先例)。 | -| G5 | simon 双击双义根治 | **改全局规则**:input 阶段**双击永远=作答(第 6 音色)**——期望恰为 doubletap 判对,否则照常判错,不再承担重听;主动重听改绑**上滑**(期望输入恰为 up 时上滑=作答,六手势全是答题输入的 00 §5.3 豁免同款);非输入阶段行为不变。`gd_economy` 按裁定书新文案**重录**(记不住就向上滑…);教学第 6 音色步资产 `t6_double`「双击,是这个声音。」不含重听语义、无需改动,教学步与新文案不矛盾。**超出裁定书 §3 清单的同口径修正(2 句,见 §9 注)**:`gd_relisten`(input 看门狗提示)与 `t7_retry`(教学收尾链)旧文案都在教「双击重听」,不清掉等于保留 P0,随 G5 一并重录为上滑口径,台词改动仅「双击→向上滑」机械替换。 | -| G6 | simon 局尾反馈轻量化 | 每轮成功在既有轮结算链(`_roundClear` 的 correct/chance_plus 链)尾追加 `gd_clear`「漂亮,过了。」(每局 1 次);本轮完成数超过历史最佳且确有旧纪录(`best>0`)时追加 `gd_record`「破纪录了。」(每局 1 次;结算另有 new_record,此处是局中破纪录的即时一刻)。游戏反馈层全档位。 | -| G7 | space 方向呼喊第二通道 | loud 警报档(撞击前 500ms)回调里同窗直达播 `voice/dir_`「左!/右!/上!/下!」——与 sfx/alarm 分属 voice/sfx 两通道天然同时不互顶(引擎对白 duck 压 BGM 保证可闻);**直达式播放不排队**(呼喊必须赶在撞击前,宁可顶掉里程/充能类短句也不排到链尾迟到);游戏反馈层全档位。 | - -## 9. round3 新增语音资产台词表(11 句 ×2 性别 = 22 文件;其中 G5 为重录 3 句) - -全文与 synthesis §3 逐字一致(speaker/instruct 沿用各游戏旁白 pair 常量,dir_* 按表注 -用 space 旁白 pair);另 2 句为 G5 手势映射的同口径机械修正(裁定书 §3 未列,落地 -原因见 §8/G5——不清掉「双击重听」旧文案,input 看门狗与教学收尾仍在教已废除的手势): - -| 游戏 | id | 全文 | 触发 | -|---|---|---|---| -| rhythm | `gd_slow` | 我放慢一些,跟着试试。 | 教学连 2 失败降速重演前(G2,full 档,每教学局 1 次) | -| rhythm | `gd_count` | 四拍之后,继续。 | 菜单恢复 count-in 前(G3,full 档,每局恢复限 1 次) | -| escape | `gd_next_fuse` | 保险丝到手。接下来,去找电箱。 | 拾取保险丝链尾(G4,全档位) | -| simon | `gd_economy`(重录) | 记不住就向上滑,重新听一遍。免费机会有两次,用完要扣一次机会。 | 开局教学末尾(G5,全档位) | -| simon | `gd_clear` | 漂亮,过了。 | 每轮成功链尾(G6,全档位,每局 1 次) | -| simon | `gd_record` | 破纪录了。 | 局中破个人纪录(G6,全档位,每局 1 次) | -| space | `dir_left` / `dir_right` / `dir_up` / `dir_down` | 左!/ 右!/ 上!/ 下! | loud 警报档方向呼喊(G7,全档位) | -| simon | `gd_relisten`(同口径修正) | 需要的话,向上滑可以重新听一遍序列。 | input 10s 看门狗(原「双击可以重新听」,G5) | -| simon | `t7_retry`(同口径修正) | 答错可以消耗机会重听。向上滑,可以随时重听。 | 教学收尾链(原「双击,可以随时重听」,G5) | - -资产落位与管线同前:`web//audio/voice/{female,male}/.{ogg,m4a}` -(`tools/gen_voice_dual.py --game --only ` 生成 → `--convert` 落盘 → -`tools/check_all_assets.py` 终检)。 - -## 10. round3 验证记录 - -- 6 playtest(guide=off):除 simon 按 13 号文档 round3 变更记录改口 3 处断言 - (input 双击=作答、上滑=重听;108 条总数与 round2 基线一致)外,**断言零改动 - 全绿**:storyline 54 / escape 103 / simon 108 / rhythm 81 / space 113 / rpg 139。 -- `web/harness/guide.test.mjs` **93 条断言全绿**(round2 基线 52 条既有断言零改动, - 其余为新增):G1(gd_answer 时间窗=窗口前整整 1 小节、恢复后重排仍在窗前)、 - G2(连 2 失败降速 ×1/0.75 + gd_slow + 做对恢复 + 每局封顶后回退 retry_skip)、 - G3(恢复先 gd_count 后 count-in、底鼓重排入队头、beat 震、每局限 1 次)、 - G5(双击作答/上滑重听/期望恰为 up 时上滑=作答,full 与 off 双档)、 - G6(gd_clear 每局 1 次 + 破纪录 gd_record)、G7(dir_* 与 loud 警报同窗 ≤50ms)。 - 为此给 harness mock 补了 setInterval/虚拟 ctx.currentTime/用例间 localStorage 隔离。 -- 转写 harness 修复(`web/harness/mock.mjs`,工具层非玩法):decodeAudioData 原来 - 按「最近一次 fetch 的 url」估时长,prewarm 并发下 voice 会随机继承 bgm 的 90s - 时长,语音结束定时器形同虚设(链全靠兜底看门狗推进、教学 tap 被滞留语音当跳过 - 吃掉)——改为按 ArrayBuffer 自身 `__url` 估算,与各 playtest mock 对齐。 -- `tools/check_all_assets.py` 全绿(双格式配对、无 0 字节、全可解码、双性别集合 - 一致);`tools/extract_voice_text.py` 已刷新 `web/harness/voice_text.json` - (437 句)。`gen_voice_dual.py --check` 中 rpg/tut_b0 等 4 句「缺 staging」为 - R5 既有已知项(资产已先行落盘、staging 无 wav),非本轮引入。 -- 转写抽查(research/04-playtests/transcripts/,6 份全部重跑、零资产缺失): - rhythm gd_answer 落在首窗口前整整 1 小节(t+67.0 vs 窗口 t+69.7)、教学第 2 句 - 连 2 失败触发「我放慢一些」+ 慢速领奏(拍距 ×1/0.75)+ 做对恢复、菜单恢复 - 「四拍之后,继续」+ 4 记底鼓 count-in 在;escape 保险丝后紧接 gd_next_fuse、 - 其后再走两步;simon 看门狗提示上滑重听 + 期望恰为 up 时上滑作答 + 双击音 - 双击作答,全程无双击歧义时刻;space 每次撞击前 500ms 警报与「左!/右!/上!/ - 下!」同窗到达。 - ---- - -# 14 · 向导交互层设计裁定(round4 v1.3 增补) - -> 依据:research/04-playtests/round3/synthesis.md(H1~H5 + §3 新语音表,唯一事实源)。 -> 本文只做**追加**,上文 v1 / round2 / round3 裁定不回改;实现载体同前 -> (`web/engine/guide.js` + 各游戏 game.js 接线),全部改动已落地。 - -## 11. round4 裁定落地摘要(H1~H5) - -| # | 裁定 | 落地 | -|---|---|---| -| H1 | escape 目标带方位 | 目标句接线点盘点:**found_key**(储物间拾钥匙→cabinet (2,1))、**gd_next_fuse**(开柜得保险丝→box (0,0))、**fix_box**(修电箱通电→gate (2,2))三处,句尾接播 `gd_room_l/r/u/d`(游戏反馈层全档位,gd_next_fuse 先例)。方位按房间图对当前坐标实时计算(x 0西..2东 / y 0北..2南;方向语义 = escape 移动坐标系,up/down/left/right 滑动方向即方位);斜对角只播一句、轴序与 `_navVoices` 一致(先东西后南北);目标在同一房间不播。**引导期 g7_goal 不接**:引导本身逐句带路(g3_left/g4_down),方位句冗余,且引导链被 escape playtest 场景 A/D 的 `finishVoices(3)→PLAY` 断言冻结(断言零改动红线)。 | -| H2 | simon 占用手势预告 | `_playSequence` 序列播完 400ms 的「轮到你了」回调里按本轮序列插占用预告链:含 up→`gd_occ_up`、含 doubletap→`gd_occ_dt`、两者皆有→`gd_occ_both`(都不含则不播、直达 input);链尾接第 1 轮 your_turn → `_toInput`(`_speakGuarded`,每句 2.5s 缺失兜底)。游戏反馈层全档位,插在轮次链中(**预告先于「轮到你」**)。被占用轮次(序列含 up)input 看门狗 `say` 动态置空——只发 nudge earcon 不播重听建议句(实现从简,say 按 arm 时刻序列选择);仅 doubletap 占用不影响建议(建议只关联上滑重听键)。 | -| H3 | simon 重听账目真实 | **诊断结论:教学/演练/看门狗均无隐性扣减**(教学期不触 chances;watchdog 提示不消耗机会——guide.test 既有断言)。缺口在**初始发放**:`_beginGame()` chances=1,而教学句 gd_economy 承诺「免费机会有两次」——第二次免费要打完第 4 轮才发放(每 4 轮 +1、上限 2),P1 在第 1 轮用掉唯一一次即听到绝对化「用完了」,账面对不上。**修法=使用时如实报余量**:`_relisten()` 消耗后余 1 播 `gd_chances_left1`、余 0 播 `gd_chances_left0`(替换该路径旧「用完了」句,全档位)——「用完了」从此只在真实余量为 0 时出现。答错自动消耗(retry_use + chances_out)与余 0 再求重听的拒绝(chances_out)两处保留:前者是 t7_retry 明说过的「答错可以消耗机会重听」的机会账、后者播报时点余量确为 0;且两处被 simon playtest 断言锁定(零改动红线)。初始发放数值 1 不动(playtest「初始 1 次重听机会」断言冻结;「不动玩法数值」红线仅豁免 H3 的账目真实化,不做经济重发)。 | -| H4 | simon input 看门狗首级提前 | `GUIDE_INPUT_WATCHDOG_AT` [10000,25000]→**[5000,25000]**:仅 input 首级提前(P4「10 秒干等」),升级级 25s 与两级次数不变;该常量只用于 input 看门狗,非 input 阶段不动。 | -| H5 | rhythm 归因真话 | `_tutEvaluate` 教学首次失败归因改四档:先判**应答窗口无任何尝试**(任一 cue 在窗内无尝试——出窗的自由演奏不算尝试)→ `gd_miss_none`「这拍没接」(none 优先级最高);真出手再分 wrong/early/late(沿用 `_tutAttempts` 判据)。纠正链(retry_*)不变;正片连 2 Miss 仍 `gd_miss`(`_applyMiss` 不动)。 | - -## 12. round4 新增语音资产台词表(10 句 ×2 性别 = 20 文件) - -全文与 synthesis §3 逐字一致(speaker/instruct 沿用各游戏旁白 pair 常量): - -| 游戏 | id | 全文 | 触发 | -|---|---|---|---| -| escape | `gd_room_l` / `gd_room_r` / `gd_room_u` / `gd_room_d` | 左面的房间。/ 右面的房间。/ 上面的房间。/ 下面的房间。 | 目标句(found_key/gd_next_fuse/fix_box)播完后接播(H1,全档位) | -| simon | `gd_occ_up` | 这一轮,向上滑是作答。 | 序列含 up(H2,全档位) | -| simon | `gd_occ_dt` | 这一轮,双击是作答。 | 序列含 doubletap(H2,全档位) | -| simon | `gd_occ_both` | 这一轮,向上滑和双击都是作答。 | 两者皆含(H2,全档位) | -| simon | `gd_chances_left1` | 免费重听还剩一次。 | 免费重听用掉后余 1(H3,全档位) | -| simon | `gd_chances_left0` | 免费重听用完了。之后重听会花一次机会,别怕,慢慢来。 | 用掉后余 0(H3,全档位) | -| rhythm | `gd_miss_none` | 这拍没接。我停下的时候,就轮到你了。 | 教学应答窗口无尝试(H5,full 档,guide.error 通道) | - -资产落位与管线同前:`tools/gen_voice_dual.py --game --only ` GPU 逐条生成 → -`--convert` 落盘 → `tools/check_all_assets.py` 终检。 - -## 13. round4 验证记录 - -- 6 playtest(guide=off)**断言零改动全绿**:storyline 54 / escape 103 / simon 108 / - rhythm 81 / space 113 / rpg 139(与 round3 基线一致)。 -- `web/harness/guide.test.mjs` **120 条断言全绿**(round3 基线 93 条中仅 ② 块两条 - input 看门狗时刻断言随 H4 10s→5s 更新,并固定第 1 轮序列以隔离占用预告的动态 - say;其余既有断言零改动):新增 H 组 27 条——H1 方向映射按图校验(l/r/u/d + - 同房间 null)+ 三处目标句接线在真实解谜链上验证;H2 三种占用预告 + 预告先于 - 「轮到你」+ up 占用轮看门狗无建议句 + 仅 dt 占用不影响建议;H3 余量句与真实 - 余量一致(2→1 播 left1、1→0 播 left0、余 0 拒绝仍 chances_out);H4 首级 5s - (升级级不变);H5 无尝试 gd_miss_none 且不再误报 late/wrong。 -- `tools/check_all_assets.py` 全绿(双格式配对、无 0 字节、全可解码、双性别集合 - 一致);`tools/extract_voice_text.py` 已刷新 `web/harness/voice_text.json` - (447 句)。`gen_voice_dual.py --check` 新增 10 句无时长离群;rpg/tut_b0 等 - 4 句「缺 staging」为 R5 既有已知项,非本轮引入。 -- 转写抽查(research/04-playtests/transcripts/,6 份全部重跑、零资产缺失): - simon——R1 占用预告 t+60.1 先于「轮到你了」t+62.2,5s 首级(t+69.3)与 25s - 升级(t+89.3)两级均无重听建议、期望恰为 up 时上滑作答;R5/R7 建议句 5s 准时 - (t+125.4 / t+179.7),两次免费重听分别跟播「免费重听还剩一次」(t+127.5)与 - 「免费重听用完了。之后重听会花一次机会」(t+181.8),收工态余量 0 与播报一致; - escape—— - gd_next_fuse(t+64.2)后 2.2s 接「左面的房间。」(t+66.4)、fix_box(t+85.0)后 - 2.1s 接「右面的房间。」(t+87.1),均在目标句后 3s 内,剧本照方位句多跨四个 - 房间摸到大门;rhythm——教学整拍不出手归因「这拍没接。我停下的时候,就轮到你了」 - (t+23.2)而非「慢了半拍」,正片首句全漏仍 gd_miss(t+71.6,正片规则不变); - storyline/space/rpg 行为零改动。 +v1~v4 的 F1~H5 实现细节见 git 历史与 `research/04-playtests/`。新开发不要再按那些看门狗秒数接线。 diff --git a/docs/design/15-radio-voice-ids.md b/docs/design/15-radio-voice-ids.md index c614489e..78e6047e 100644 --- a/docs/design/15-radio-voice-ids.md +++ b/docs/design/15-radio-voice-ids.md @@ -1,5 +1,13 @@ # 15b · 《不眠频率》(radio) 语音台词表(voice ids,录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - **双性别组**(主播 HOST + 导播/系统 PROD,103 句):落 `web/radio/audio/voice/female|male/.{ogg,m4a}`,两性别 id 集合完全一致。 > - **HOST(主播=玩家)**:female = `Serena`,instruct 常量 `温暖清晰的年轻女声,深夜电台主播,沉稳亲切,不甜腻`;male = `Uncle_Fu`,instruct 常量 `低沉温和的中年男声,深夜电台主播,沉稳亲切,有磁性`。 diff --git a/docs/design/15-radio.md b/docs/design/15-radio.md index fafee176..362b3a89 100644 --- a/docs/design/15-radio.md +++ b/docs/design/15-radio.md @@ -1,539 +1,256 @@ -# 15 · 《不眠频率》(radio) — 深夜电台热线叙事 RPG 设计文档 +# 15 · 《不眠频率》(radio) — 深夜电台热线叙事 RPG > **给开发的实现要点摘要** -> 1. 叙事模式(声命线范式):节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;复制 `web/storyline/` 结构起步,状态机用 6 宏状态(INTRO/RING/ONAIR/BRIDGE/ENDING + MENU 覆盖层)。 -> 2. 手势零豁免:全部走全局默认映射(单击=继续/接听/跳句、双击=重听、左右滑=抉择、上滑=状态、下滑=模态菜单 §5.2 约定、长按=引擎快进)。 -> 3. 台词依据 [15-radio-voice-ids.md](15-radio-voice-ids.md)(132 句:双性别 103 + 平铺 NPC 29);NPC 人声电话滤波在**资产侧** ffmpeg 带通(storyline n03_msg 先例),引擎零改动。 -> 4. 向导层:storyline 同款看门狗节奏 + RING 专用两级新句提示;`gd_ring1/gd_ring2` 为本游戏新增 gd_* 资产。 -> 5. 章程任务 N1(research/new8/00-program.md):单局 15-25 分钟、9 抉择点、4 结局、分支局部化。 - -## 1. 概述 - -- **一句话**:你是午夜档电台热线主播「今夜不眠」,直播间里一晚接进五个来电——你说什么话、接不接、转不转接,改变五个陌生人的夜晚,也改变你自己。 -- **题材(声音即世界)**:全部叙事靠电话音质感(每位来电者一种滤波声)、电台垫乐、直播间环境音呈现——来电者永远不露面,只有声音;「声音即存在」与纯音频载体天然同构。 -- **单局**:一个完整夜班(一条抉择路径)15-22 分钟;五个来电共享主干,分歧点局部化在每通电话内部;9 个抉择点(单局实走 8-9 个),4 结局。 -- **核心循环**:接听 → 听 → 抉择(左=走心介入 / 右=克制程序)→ 结果与口碑结算 → 幕间喘息 → 下一通。跨来电积累「今晚状态」(听众口碑 + 各来电好感),收播前一个抉择决定你自己的心结是否解开。 - -## 2. 状态机 - -### 2.1 状态列表 - -| 状态 | 内容 | BGM | guide.state | -|---|---|---|---| -| `INTRO` | 开场链:台标口播 + 一句规矩 + 今晚目标 | `bgm/onair` | explore | -| `RING` | 热线接入:铃声循环,等待单击接听 | `bgm/onair`(不切) | 无新签名(turn cue 承担) | -| `ONAIR` | 通话叙事:对白链 + 抉择点 + 结果 | `bgm/onair` | explore;抉择点=choice | -| `BRIDGE` | 幕间:口碑跨档播报(如有)+ 喘息垫乐,自动链 | `bgm/bridge` | settle | -| `ENDING` | 结局链 + 尾声回响(epilogue),结算态 | `bgm/ending` | settle | -| `MENU` | 模态覆盖层(任意叙事态下滑呼出,暂停一切链) | `bgm/menu` | menu | - -### 2.2 转移图(文本) - -``` - ┌─────────────── 结算单击=新夜班 ───────────────┐ - ▼ │ - INTRO ──链完──> RING(c1) ──单击接听──> ONAIR(c1) ──链完──> BRIDGE ──┤→ RING(c2) - │铃声循环/看门狗两级,无超时惩罚 │ …(c3,c4 同型)… - └────────────────────────────────────────────────┘ - ONAIR(c5) ──链完──> BRIDGE ──链完──> SIGNOFF - │ - └──> ◆p9 抉择 ──> d9 结果链 ──> ENDING(e1..e4 + ep 回响链) - - 任意叙事态(INTRO/RING/ONAIR/BRIDGE/ENDING):下滑 → MENU(模态) - MENU:单击=循环切换选项 → 双击=确认执行 → 上滑=帮助 → 下滑/「回到直播」项=关闭回原状态(重播当前节点) -``` - -- INTRO/RING/ONAIR/BRIDGE/ENDING 均为节点链驱动(storyline playNode 模式);`SIGNOFF` 是 ONAIR 的最后一个节点组(◆p9),不单设状态。 -- BRIDGE 自动链 ≤12s:口碑跨档则播 rep_*(播报真实档位,账目真实),随后直接进下一通 RING。 -- MENU 打开时 `guide.disarm()` + 取消一切看门狗/自动链;关闭后重播当前节点(局面复述,storyline closeMenu 先例)。 - -### 2.3 每状态手势映射(6+2 全覆盖,零豁免) - -| 手势 | INTRO | RING | ONAIR | BRIDGE | ENDING | MENU | -|---|---|---|---|---|---|---| -| 上滑 | 帮助+状态,随后重播当前段 | 状态播报(第几通+口碑档),留在 RING | 帮助+状态,随后重播当前节点 | 帮助+状态,自动链继续 | 帮助+状态(结局名+口碑) | 帮助(sys_help) | -| 下滑 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 关闭菜单 | -| 左滑 | 无效 → error 震+gd_invalid | 无效 → gd_invalid | 抉择=左(走心) | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 | -| 右滑 | 无效 → gd_invalid | 无效 → gd_invalid | 抉择=右(克制);存档询问态=新夜班 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 | -| 单击 | 播放中=跳过本句;链末=进 RING | **接听** | 播放中=跳过本句(p* 不可跳);链末=继续 | 跳过本句(自动链) | **再来一局**(新夜班) | 循环切换选项 | -| 双击 | 重听当前段 | 重听上一通话的收尾句 | 重听当前节点台词(含抉择提示) | 重听幕间链 | 重听结局链 | **确认执行**(菜单约定覆盖重听) | -| 长按 | 引擎接管:旁白临时 1.7×(12 号 §2.2,叙事类不豁免) | 同左 | 同左 | 同左 | 同左 | 同左(快进菜单语音) | -| 长按松开 | 引擎接管:恢复 settings.speed | 同左 | 同左 | 同左 | 同左 | 同左 | - -- 豁免登记:**无**。六个基础手势全部符合全局宪法默认语义(单击=确认/继续/**接听**,宪法 §5.1 原文即含「接听」);菜单走 §5.2 R3 模态约定(单击循环+双击确认),不引用 rpg 探索态豁免。 -- 抉择 prompt(voice/p1..p9)播放中单击不可跳、左右滑缓存 `pendingChoice` 播完即生效(storyline 盲玩公平先例)。 -- 无效手势零静默:所有「无效」格均有 error 震 + gd_invalid(onceKey 同局限次,F1.4 idle-only:语音在播时跳过语音只发震)。 - -## 3. 抉择树 - -### 3.1 全局骨架(来电共享主干,分歧局部化) - -``` -INTRO: b_open → b_rain → b_rule → b_goal ──> 铃① -【C1 老周·走丢的老人】教学通 · 2 抉择 ──> BRIDGE ──> 铃② -【C2 小鹿·想退票的年轻人】单抉择 · 呼吸口 ──> BRIDGE ──> 铃③ -【C3 糖糖·失眠的孩子】2 抉择 · 埋 heardArgument ──> BRIDGE ──> 铃④ -【C4 陈姐·争吵的夫妻】2 抉择 · 消费 heardArgument ──> BRIDGE ──> 铃⑤ -【C5 老韩·雨夜司机】高潮 · 1 抉择 + 危机脚本拍 ──> 签收 ◆p9(心结)──> ENDING -``` - -分支纪律:**每通电话内部分支、通话末尾收敛**到导播收尾句 → BRIDGE;跨来电只传三个数据(rep 数值、bond 好感、heardArgument 布尔),不生成组合节点。最坏路径与最好路径节点数相同。 - -### 3.2 分通结构(◆=抉择点,〔〕=条件差分) - -``` -【C1 老周】 -铃① → z1_ring_in → z1_a → z1_b → ◆p1 - ├左 哼老歌: z1_song → z1_songo → z1_sdone ─┐ - └右 问路牌: z1_ask → z1_asko → z1_adone ───┤ - ▼ - ◆p2 - ├左 陪他等: z1_stay → z1_stayo → z1_staye ─┐ - └右 转接: z1_pol → z1_polo → z1_pole ────┤ (仅 p1=右 时 p2=右 改走 z1_bad → z1_bade 归因) - ▼ - z1_ok1(首通确认,全局一次)→ BRIDGE - -【C2 小鹿】 -铃② → l1_in → l1_a → l1_b → ◆p3 - ├左 劝上车: l1_go → l1_goo → l1_goe ──┐ - └右 还自由: l1_free → l1_freeo → l1_freee ─┴→ BRIDGE - -【C3 糖糖】 -铃③ → k1_in → k1_a → k1_b(垫 arg_muffled 争吵声)→ ◆p4 - ├左 哄睡: k1_lull → k1_lullo → k1_lulle ──────────────┐(错过线索,本通结束) - └右 问她: k1_ask → k1_asko → ◆p5 │ - ├左 真话: k1_truth → k1_trutho → k1_truthe ────────┤(heardArgument=true) - └右 谎言: k1_lie → k1_lieo → k1_liee ──────────────┴→ BRIDGE - -【C4 陈姐】 -铃④ → w1_in → w1_a → w1_b → ◆p6 - ├左 替孩子带话:〔heardArgument ? w1_say 组 : w1_say2 组〕→ ◆p7 - │ ├左 留在直播: w1_air → w1_airo → w1_aire ─┐ - │ └右 换垫乐私聊: w1_priv → w1_privq → w1_prive ─┤ - └右 劝她回家: w1_home → w1_homeo → w1_homee ──────────┴→ BRIDGE(右路本通结束,无 p7) - -【C5 老韩】高潮 -铃⑤ → h1_in → h1_a → h1_b → ◆p8 - ├左 陪聊闺女: h1_talk → h1_talko → 【危机拍: danger 签名+brake_screech(前置2s)】 - │ → h1_danger → h1_dango → h1_talke ─┐ - └右 服务区: h1_stop → h1_stopo → h1_stope ────────┤ - 〔bond_zhou=true 时插播 h1_echo 回响句〕 ▼ - BRIDGE → g9_in → ◆p9 - ├左 直面(播出老主播的晚安): d9_face → d9_facee → knot=解 - └右 回避(照常收播): d9_avoid → d9_avoide - ▼ -ENDING: e*_a → e*_b(按 rep×knot 四选一)→ ep_lead → ep_×5(按 bond 选播) -``` - -### 3.3 分支局部化说明 - -- 跨来电状态只有三个标量:`rep`(数值)、`heardArgument`(布尔)、`bond`(5 个 0/1)。没有任何「来电 i 的分支 × 来电 j 的分支」组合节点。 -- 唯一的跨来电内容差分:C4 的 w1_say/w1_say2 两组(由 heardArgument 选播)与 C5 的 h1_echo 插播句(由 bond_zhou 选播)——都是**节点内选播**,不改树形。 -- 树规模:132 句台词、约 60 个节点,任一路径实走 8-9 个抉择点(C3 左路/ C4 右路各少一个)。 - -### 3.4 四结局达成条件 - -| 结局 | 条件 | 主题 | 开环钩子 | -|---|---|---|---| -| e1 天亮之前 | rep ≥ 5 且 p9=左(直面) | 城市有了心跳,话筒接住了 | 「明晚零点,接着不眠」 | -| e2 热线常亮 | rep ≥ 5 且 p9=右(回避) | 满座夜班,留言机红灯未看 | 红灯「灭得很慢」 | -| e3 一个人的频道 | rep < 5 且 p9=左(直面) | 夜班难,但晚安说出去了 | 「师父,收到了吗」 | -| e4 静音 | rep < 5 且 p9=右(回避) | 谁也接不住谁的夜班 | 「明晚再说吧」 | - -- 尾声回响(P2 回响感):epilogue 对 5 个来电者各播一句 bond ? 救到的后续 : 错过的后续(ep_zhou/deer/kid/wife/han_ok/no),玩家每晚的救与错过都被点名。 - -## 4. 数值设计 - -### 4.1 变量 - -| 变量 | 类型 | 初值 | 说明 | -|---|---|---|---| -| `rep`(听众口碑) | int | 0 | 抉择与结果积累,收播时结算档位 | -| `knot`(心结) | bool | false | 仅由 p9=左 置 true | -| `bond_zhou/deer/kid/wife/han` | 0/1 | 0 | 只影响尾声回响选播,不影响结局 | -| `heardArgument` | bool | false | p4=右 置 true,C4 消费 | -| `nights` / `endings{}` | int / map | — | 重玩计数与结局收集(存档展示用) | - -### 4.2 抉择计分表(左=走心 / 右=克制) - -| 抉择 | 左(走心) | 右(克制) | 备注 | -|---|---|---|---| -| p1 哼歌/问路 | +1 | 0 | 左路老周稳住(首个正反馈) | -| p2 陪等/转接 | +2(p1=左)/ +1(p1=右) | +1(p1=左)/ **-1(p1=右,砸)** | 唯一负分组合,触发归因句 | -| p3 劝上车/还自由 | +2 | +1 | 两个选项都是体面的(教学后建立信任) | -| p4 问她/哄睡 | +1,heardArgument=true | 0 | 左=倾听,右=敷衍即错过线索 | -| p5 真话/善意谎言 | +1 | 0 | p5 仅 p4=右 可达 | -| p6 替孩子带话/劝回家 | +2(heardArgument)/ +1(否则) | +0(本通结束) | | -| p7 留在直播/换垫乐 | +2 | +1 | p7 仅 p6=左 可达 | -| p8 陪聊/服务区 | +1(含危机拍) | 0 | 两路都活着(P4 零死亡) | -| p9 直面/回避 | knot=解 | knot=未解 | 不动 rep | - -### 4.3 阈值与导向 - -- 收播结算:`rep ≥ 5` = 高口碑(e1/e2),`rep < 5` = 低口碑(e3/e4)。 -- 理论上限 12、全克制路径 0;「多数走心 + 容忍 1-2 次失误」落在 5-8 档——阈值 5 把「今晚是否立住」压在玩家自己的重量上,而非单点失误。 -- 口碑跨档被动播报(BRIDGE):进入 ≥5 区间播 rep_high、跌回 1-4 播 rep_mid、≤0 播 rep_low——分档整句、账目真实(A4 资源账目律)。 -- 归因真话律落地:每个负向结果节点(z1_bade、k1_lulle、k1_liee、e4 链)的导播收尾句必须指向玩家的**实际选择**(「地址没报清就转接」「你没听见她在怕什么」),禁止「运气不好」类假归因;成功路径不播任何补救教学(A5)。 - -## 5. 音频三层映射 - -### 5.1 BGM(状态→曲目,Mureka instrumental,4 首 ≈ $0.12) - -| 状态 | 曲目 | 切换 | Mureka prompt 草稿(英文,seamless loop) | -|---|---|---|---| -| INTRO/RING/ONAIR | `bgm/onair` | 开局淡入 800ms,全夜班不切;语音 duck 引擎自理 | "Midnight radio broadcast music bed, warm lo-fi neo-soul chords, soft vinyl crackle, gentle night-drive groove with brushed drums, intimate late-night host mood, no vocals, seamless loop, 60-90 seconds" | -| BRIDGE | `bgm/bridge` | crossfade 800ms 进;进下一通 RING 淡回 onair 600ms | "Radio interlude pad, sparse felt piano notes over warm analog drone, distant rain mood, lonely but safe, no drums, no vocals, seamless loop, 60-90 seconds" | -| MENU | `bgm/menu` | crossfade 500ms 进;关菜单淡回原状态曲 800ms | "Late-night radio studio menu pad, warm minor pad with faint tape hiss, static harmony, very sparse, no vocals, seamless loop, 60-90 seconds" + 共性后缀(10 号 §8) | -| ENDING | `bgm/ending` | p9 结果链播完后淡入 1500ms | "Dawn sign-off theme, soft piano and warm strings, hopeful sunrise after a long night shift, gentle resolution, no vocals, seamless loop, 60-90 seconds" | - -- `bgm=0` 时全部静默(引擎约定);menu pad 与 onair 同调性优先。 - -### 5.2 SFX 清单(Stable Audio 3,one-shot 真峰值 ≤-3dBFS;amb 循环由游戏层 buffer.loop 实现,escape amb 先例) - -| id | 用途 | Stable Audio prompt(英文) | -|---|---|---| -| `sfx/onair_sting` | 红灯亮起台标 sting(INTRO/重开) | "Short broadcast on-air light sting, warm chime with vintage radio click, 1 second" | -| `sfx/phone_ring` | 热线铃(RING 循环体,每遍 ≥4s 间隔) | "Old studio telephone bell, two-strike bright metallic ring, clean" | -| `sfx/line_connect` | 接听咔哒+线路接通音 | "Telephone line connect click and short dial tone fade, subtle" | -| `sfx/line_drop` | 挂断/转接忙音 | "Telephone hang-up click with short busy tone, soft" | -| `sfx/amb_studio` | 直播间底噪 loop(调音台嗡+空调) | "Quiet radio studio room tone, console hum and soft air conditioning, seamless loop" | -| `sfx/amb_rain_city` | C1 老人侧:街道雨夜 loop | "Heavy rain on city street at night, distant traffic hiss, seamless loop" | -| `sfx/amb_station` | C2 小鹿侧:深夜车站 loop | "Empty train station hall at night, distant echo and rolling suitcase, seamless loop" | -| `sfx/amb_kid_room` | C3 糖糖侧:儿童房 loop | "Quiet child bedroom at night, soft lamp buzz, muffled house silence, seamless loop" | -| `sfx/arg_muffled` | C3 线索音:闷住的争吵+摔门(信息承载,k1_b 时起) | "Muffled argument behind a door, dull thud of door slam, indistinct voices, 4 seconds" | -| `sfx/amb_street_rain` | C4 陈姐侧:公车站雨 loop | "Rain at an empty bus stop at night, distant wet road traffic, seamless loop" | -| `sfx/amb_cab` | C5 老韩侧:车内 loop(引擎+雨刷节奏) | "Inside a moving truck cab at night, steady engine hum, rhythmic windshield wipers, rain outside, seamless loop" | -| `sfx/brake_screech` | C5 危机拍:急刹+护栏摩擦 | "Sudden brake screech on wet asphalt, brief tire friction, 2 seconds" | -| `sfx/tape_static` | p9 直面:留言机电流声 | "Old cassette tape static and tape hiss with a click stop, 3 seconds" | -| `sfx/ui_confirm` | 菜单/接听确认(程序合成兜底) | —(程序合成,同全局先例) | -| `sfx/ui_menu` | 菜单呼出(程序合成兜底) | —(程序合成) | - -- C3/C4 的 amb 是**信息层**:arg_muffled 在 k1_b 起播 2-3 次,玩家听没听见由 p4 抉择兑现(声音即世界)。 -- 预警窗纪律:h1_danger 前 2s 先起 brake 渐强(L2 前置预警),danger 签名与 brake 同窗。 - -### 5.3 Voice 划分(详见 15-radio-voice-ids.md) - -| 组 | 声部 | 落位 | 数量 | -|---|---|---|---| -| 主播(你)HOST | 双性别(female=Serena / male=Uncle_Fu + HOST instruct 常量) | `voice/female|male/` | 21 句 | -| 导播/系统/向导 PROD | 双性别(female=Vivian / male=Dylan + PROD instruct 常量) | `voice/female|male/` | 82 句 | -| 来电者 NPC ×5 | 平铺单音色(老周=Uncle_Fu / 小鹿=Serena / 糖糖=Vivian / 陈姐=Vivian / 老韩=Eric),**不做双性别** | `voice/` 根(平铺回退命中)+ 电话滤波转码 | 29 句 | - -- NPC ≤5 个约束满足;玩家自己的声音跟随全局 `settings.voice`(男/女主播皆可代入),导播耳语是向导人设(L4 角色化系统声)。 -- 合计 132 句(≤170 GPU 预算);音频文件 = 103×2 + 29 = 235 条(×2 格式 = 470 文件)。 - -## 6. 来电音质感设计(声音即世界的实现) - -### 6.1 引擎能力边界(对照 web-engine-api.md) - -| 能力 | 引擎现状 | 方案 | -|---|---|---| -| voice 运行时 EQ/滤波 | ❌ voice 链 = SoundTouch → gain → voiceBus,无滤波插入点 | **资产侧转码滤波**(storyline n03_msg/n05_bcast 的 convert.sh 带通先例):ffmpeg `highpass + lowpass + 压缩器` 逐来电者定档 | -| voice 逐句声像/空间定位 | ❌ voice 单声道总线 | 不使用;空间感交给 sfx amb(sfx 资产内 pan,escape amb 先例) | -| sfx 循环 | sfx 即发即弃,无 loop 选项 | 游戏层 `buffer.loop=true` 或 onEnded 重触发(escape amb_loop 先例) | -| 语音 duck 垫乐 | ✅ 内置(duck 20%,300ms) | 直接依赖 | -| 运行时混响/失真(「信号变差」差分) | ❌ | 降级:用 amb 响度强弱 + 台词停顿节奏表达,不做动态滤波 | -| 变速不变调 | ✅ SoundTouch(settings.speed + 长按 1.7×) | 直接依赖 | - -### 6.2 每位来电者的声音签名(资产侧 ffmpeg 参数) - -| 来电者 | 滤波档 | 质感设计 | 背景 amb(sfx 层) | -|---|---|---|---| -| 老周 | highpass 500 + lowpass 2800 + 轻压缩 | 老旧手机,窄而颤抖 | amb_rain_city | -| 小鹿 | highpass 400 + lowpass 3400 | 车站手机,干净偏冷 | amb_station | -| 糖糖 | highpass 450 + lowpass 3000 | 家用座机,小而近 | amb_kid_room + arg_muffled | -| 陈姐 | highpass 420 + lowpass 3100 | 户外手机,风噪 | amb_street_rain | -| 老韩 | highpass 300 + lowpass 3400 | 车载蓝牙,宽而轰 | amb_cab | - -- **可读性底线**(L2 反模式「写实但不可读」):滤波保持在语音可懂频带内,宁窄勿脏;QC 试听不可读的句子回退**未滤波原声**——叙事不破,只损失质感(显式降级方案,不阻塞管线)。 -- 主播/导播人声**不滤波**(直播间级音质);「电话里的人」与「身边的人」靠这一层音质差立住,无需任何视觉。 - -## 7. 向导层接入 - -### 7.1 状态签名 earcon / turn cue - -| 时机 | 向导动作 | 说明 | -|---|---|---| -| INTRO 进入 | `guide.state('explore')` | 叙事态签名 | -| 每个抉择点 p* 起播 | `guide.state('choice')` | 选项句本身即 turn cue(播完当刻 choiceArmed 即真,W2 契约零插播) | -| RING 铃响 | `guide.turn({haptic:true})`(无语音) | 铃声+turn 签名=「轮到你接听」;首次铃前 b_goal 已口播规矩 | -| BRIDGE 进入 | `guide.state('settle')` | 喘息/结算签名 | -| ENDING 进入 | `guide.state('settle')` | 同上 | -| MENU 打开 | `guide.state('menu')` | 菜单签名 | -| C5 危机拍 | `guide.state('danger')` | 全局唯一 danger 使用点(刹车前 2s,与 brake_screech 同窗) | -| 无效手势 | `guide.error({voice:'gd_invalid', onceKey})` | error 震 + full 档语音,idle-only(F1.4) | - -- earcon 词表:只用全项目统一 8 音(menu/explore/battle/choice/settle/danger/turn/nudge),**零新增**;battle 本游戏不用。开场台词自然教 3 个最高频签名(铃=接听、短促提示音=轮到你选择、上滑/下滑口令);settle/menu 首遇即学;逐签名口播教学列 v2 候选(14 号 §4 同款)。 - -### 7.2 看门狗参数(秒;任何手势经 guide.disarm 复位;guide=off 全不挂) - -| 等待点 | full 档 | standard 档 | 级别内容 | 礼仪 | -|---|---|---|---|---| -| RING 无接听 | [10, 25] | [10, 25](onEscalate 游戏层补播) | ① gd_ring1「热线在闪。单击,接听。」② gd_ring2「灯还亮着,听众在等。」 | **两级不同句**(绝不逐字重播);两级用尽只剩铃声循环,永不惩罚 | -| ONAIR/INTRO/BRIDGE 叙事链末等待 | [25, 50] | [13, 30](同 storyline P2-3 先例) | ① gd_continue ② gd_again | storyline F1.1 同款,两级后彻底静默 | -| 抉择点 p* 播完无操作 | [25, 50] | [25, 50] | 重播**当前 p* 提示句本身**(复述选项=合法帮助,13 号 §16 N3 先例) | 两级后静默;空点单击=重挂计时 | -| ENDING 结算等待 | [25, 50] | [13, 30] | gd_continue / gd_again | 结算单击即重开,不阻拦 | - -- 向导插播一律 `queue:true`,绝不顶掉在播对白;error/watchdog 类语音 idle-only(在播则跳过且过期丢弃,F1.4)。 -- 单句语音看门狗(资产缺失兜底):30s 超时且 `isVoicePlaying()===false` 才自动续链,长句续约不掐(storyline N1 先例照抄)。 - -## 8. 存档设计 - -### 8.1 进度 data 结构(PUT /api/progress {game:'radio'}) - -```json -{ - "schema": 1, - "updatedAt": "2026-09-01T12:00:00Z", - "summary": "第3通·失眠的孩子 · 口碑+2 · 结局1/4", - "phase": "ONAIR", - "node": "k1_asko", - "rep": 2, - "knot": false, - "heardArgument": true, - "bond": { "zhou": 1, "deer": 1, "kid": 0, "wife": 0, "han": 0 }, - "endings": { "e2": true }, - "nights": 3 -} -``` - -- 写入时机:每通电话 RING 进入、每个抉择结算后、ENDING 进入、菜单「保存并回大厅」。 -- 断点续玩:登录/本地读到存档 → `sys_continue_ask`(单击=继续上次 / 右滑=新夜班);继续 = 恢复 phase+node 并**重播当前节点链** = 一句话局面复述(L5:恢复成本游戏承担);重进 ≤5s 可玩。 -- summary 字符串格式:进行中 `第N通·<来电者> · 口碑±X · 结局K/4`;已收播 `已收播·<结局名> · 结局K/4`(大厅卡片直接显示,人话)。 - -## 9. playtest 断言清单(登记 13 号文档 radio 节,格式同 §1) - -**关键路径**(E1 全程,≤60 次手势): -1. 启动遮罩点按 → INTRO 链(b_open 起,voice 层有播放记录)→ 链完自动进 RING(c1),turn 事件与铃声断言。 -2. RING 单击接听 → ONAIR(c1):p1 左 → p2 左 → z1_ok1 → BRIDGE(rep 断言 +1+2=3)→ RING(c2)。 -3. 依序走 p3 左 / p4 右 / p5 左 / p6 左 / p7 左 / p8 左(断言每步后 rep 与 §4.2 一致;p4 右后 `heardArgument === true` 且 C4 播 w1_say 组)。 -4. p9 左 → knot=true → ENDING(e1):结局链 + ep_lead + 5 条 ep_*_ok → 结算态单击重开回 INTRO。 - -**边界断言**: -- **四结局可达**:剧本 B(路径同上但 p9 右)→ e2;剧本 C(p1右 p2左 p3右 p4右 p5右 p6右 p8右 p9左,rep=3)→ e3;剧本 D(p1右 p2右 p3右 p4左 p6右 p8右 p9右,rep=0)→ e4;各局 rep 终值与设计表一致。 -- **双击重听**:ONAIR/ENDING 播完态双击 → 当前节点链同 id 序列重播;重播期间单击=跳本句(nodeId 不变),播完回原等待态。 -- **菜单返回不丢节点**:ONAIR 中下滑开菜单 → 上滑听帮助 → 双击「回到直播」→ phase/node 不变、当前节点重播;菜单内单击循环顺序 = [回到直播, 重新开始今晚, 保存并回大厅];确认回大厅 → sys_menu_lobby 播出且 PUT/localStorage 有最新 data。 -- **中断复述**:杀进程重进(存档 node=k1_asko)→ sys_continue_ask → 单击 → 重播该节点链;上滑播 sys_help+prog_c*+rep_* 链(顺序断言)。 -- **看门狗**:RING 快进 10s → gd_ring1、25s → gd_ring2(断言第二级 id ≠ 第一级 id,绝不逐字重播;两级后仅铃声);ONAIR 链末 25s/50s → gd_continue/gd_again;p* 播完 25s/50s → 当前 p* 重播两次后静默;`settings.guide='off'` 时全程零定时器零向导事件。 -- **pendingChoice**:p* 播放中左右滑 → 播完立即生效(不丢、不重复)。 -- **p* 不可跳**:p* 播放中单击 → 不跳句(PROMPT_RE 门)。 -- **idle-only**:向导 gd_* 在对白在播时被跳过且不再补播(F1.4 契约);queue 句排在当前句后(在播句不被截断)。 -- **归因真话**:剧本 D 中 z1_bade 播出(含真实归因);剧本 A 全程无任何归因/补救句(成功不教学)。 -- **资产缺失**:屏蔽任意 voice 资产 → 30s 看门狗自动续链到结局,不卡死。 -- **数字规范**:prog_*/rep_* 全部整句,转写审计无运行时拼接碎片。 -- **坏音质降级**:滤波资产缺失时原声可播(回退路径不断链)。 - -**失败即不通过**:任何等待 >5s 无声(静默死局);无效手势零反馈;结局计数与 §3.4 不符。 - -## 10. 难度 / 节奏曲线 - -| 段 | 内容 | 目标时长 | 设计目标 | -|---|---|---|---| -| 开场 | 台标 + 一句规矩 + 第一声铃 | 1.5-2 min | 首输入 <60s(链 ~30s 后铃响即接听);规矩一句讲完(P4) | -| C1 老周 | 教学通:2 个二选一,条条有出路 | ~3.5 min | **首个正反馈 ≤3 min**(p1 左 → 老周声音稳住 + 导播确认);唯一 -1 组合不扣资源(学习期零惩罚) | -| C2 小鹿 | 单抉择重话术 | ~2.5 min | 节奏呼吸口;建立「两个选项都体面」的信任 | -| C3 糖糖 | 双抉择 + amb 线索埋设 | ~3.5 min | 环境音首次承载剧情(声音即世界);错过线索可被尾声点名 | -| C4 陈姐 | 最复杂:2 抉择 + 条件差分 | ~4 min | 情感高潮;选项句最长(p7 ≤40s 播完) | -| C5 老韩 | 高潮:危机拍(danger+急刹,前置 2s 预警) | ~4 min | 紧张峰值但零死亡、零倒计时(P4);h1_echo 兑现 C1 回响 | -| 签收+结局 | p9 + 结局链 + 5 句回响 | ~3 min | 心结抉择 + epilogue 回响感(P2);开环钩子拉下一局 | - -- 合计 ≈ 21-22 min(15-25 min 达标);每 30-60s 一个反馈颗粒(导播短句/结果句/口碑档),无沉默尴尬期(P3)。 -- 难度曲线 = 信息复杂度(1 抉择 → 条件差分 → 双方对峙 → 生死时速),操作复杂度恒定(永远二选一)——P4「规则一遍听懂」由恒定操作面保证。 - -## 11. 自检清单 - -| # | 硬约束 | 落地 | ✓ | -|---|---|---|---| -| 1 | FTUE 首输入 <60s | INTRO 链 ~30s 后第一声铃即接听(§10) | ☐ | -| 2 | FTUE 首个正反馈 ≤3min | p1 左 outcome ~2.5min(z1_songo+z1_sdone) | ☐ | -| 3 | 学习期零惩罚 | C1 全组合不扣资源;RING 不接听无超时惩罚 | ☐ | -| 4 | 失败三问归因 + 归因真话律 | z1_bade/k1_liee/e4 链指向实际选择;§4.3 禁假归因 | ☐ | -| 5 | 手势宪法(6 手势默认语义,零豁免) | §2.3 全覆盖;单击=确认/继续/接听、菜单走 §5.2 模态约定 | ☐ | -| 6 | 长按豁免显式登记 | 无豁免:长按=引擎快进(12 号叙事类不豁免),§2.3 登记在案 | ☐ | -| 7 | 向导层:状态签名/turn cue/看门狗 | §7.1/§7.2;RING 两级**不同句**,绝不逐字重播 | ☐ | -| 8 | 插播一律 queue 不顶对白;纠错 idle-only 过期丢弃 | §7.2(F1.4 契约) | ☐ | -| 9 | 提示永不逐字重复 | gd_ring1≠gd_ring2;gd_continue→gd_again;抉择点复述 p* 为 13 号 §16 先例 | ☐ | -| 10 | 成功后不灌补救教学 | 归因句只挂负向结果(§9 断言) | ☐ | -| 11 | 单次播报 ≤3 选项 | 全部抉择恒 2 选项 | ☐ | -| 12 | 并发声流 ≤2 | voice+amb 两流;无第二 voice 通道 | ☐ | -| 13 | earcon ≤7 个、显式教一次、语义靠语音 | 只用统一 8 音中的 6 个(battle 不用),零新增;选项内容全语音 | ☐ | -| 14 | 台词一句一文件整句、≤40 字、数字分档整句、无运行时拼接 | voice-ids 全表 ≤39 字;prog/rep 分档整句 | ☐ | -| 15 | 多音字规避(重新听)、同游戏同 instruct 常量、无语速词 | voice-ids 头部注记与 instruct 常量表 | ☐ | -| 16 | P1:可打断/重听、status key、菜单规整、不当白痴 | skipVoice/doubletap/上滑状态链/模态菜单;成功零重复教学 | ☐ | -| 17 | P2:头 3 分钟见效、结局回响 | C1 即时改变对方情绪;ep_* 救/错过点名 | ☐ | -| 18 | P3:单手免视、一句话复述局面、3min 反馈颗粒 | 全手势免视;重进重播当前节点;BRIDGE 口碑播报 | ☐ | -| 19 | P4:规则一遍懂、高容错、无高压倒计时、NPC 语速感 | 一句规矩;零死亡零倒计时;NPC 句 ≤18 字+逗号停顿(不用 instruct 语速词,禁令遵守) | ☐ | -| 20 | 抉择树 4 结局可走通 | §9 剧本 A-D 四条实路径 + rep 断言 | ☐ | -| 21 | 单局 15-25 min | §10 曲线 ≈21-22 min | ☐ | -| 22 | 分支不爆炸 | 跨来电仅 3 标量;树 ~60 节点固定深度(§3.3) | ☐ | -| 23 | 台词 ≤170 句、NPC ≤5 | 132 句 / 5 个来电者(voice-ids 账目) | ☐ | -| 24 | 引擎边界诚实 | §6.1 能力表:做不到的写明资产侧/降级方案 | ☐ | +> 1. 交互权威:[26-audio-interaction-playbook.md](26-audio-interaction-playbook.md)。本文件按附录 C 填写;与旧手势表冲突时以本文为准。范式对齐 [26-apply-storyline.md](26-apply-storyline.md)。 +> 2. 叙事骨架不变:节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;6 宏状态 INTRO / RING / ONAIR / BRIDGE / ENDING + MENU。 +> 3. 新手三手势:单击继续/接听、左右滑抉择、上滑「我在哪」。**不识别双击**。无四向滑动玩法,帮助不上长按。菜单:左右滑切换、单击确认。 +> 4. 台词 [15-radio-voice-ids.md](15-radio-voice-ids.md)(132 句:双性别 103 + 平铺 NPC 29)。NPC 电话滤波在资产侧 ffmpeg 带通,引擎零改动。 +> 5. 章程:单局 15–25 分钟、9 抉择点、4 结局、分支局部化。开场砍长规矩:3 秒内动手,10 秒内接听成功。 --- -## 实现核对(测试 agent 登记) +## 附录 C · 设计单 -> 断言实现:`web/radio/test/playtest.mjs`,193 条,`tools/node/bin/node web/radio/test/playtest.mjs` -> 退出码 0 全绿。会话剧本:`web/harness/sessions/radio.mjs`(`session` 主线完整弧线 + -> `sessionStress` 乱按压力),经 `tools/node/bin/node web/harness/transcribe.mjs radio -> [--session sessionStress]` 跑通(transcribe.mjs 已登记 radio/支持 `--session` 选剧本)。 -> 回归:六游戏 playtest + guide.test 全绿不受影响。 +### 1. 一句话核心动作 -### §9 关键路径逐条 +**听一段热线,单击继续或接听;听到选项时左右滑做决定。** -| # | 断言 | 状态 | +### 2. 玩家能感知的状态(3 个) + +| 状态 | 听感 | Floor | |---|---|---| -| 1 | 遮罩点按 → INTRO 链(b_open 起)→ 链完自动进 RING(c1),turn 事件与铃声 | ✅已断言(INTRO 四句逐句计数 + turn 事件 + phone_ring 循环) | -| 2 | 单击接听 → p1 左 → p2 左 → z1_ok1 → BRIDGE(rep=1+2=3)→ RING(c2) | ✅已断言 | -| 3 | p3 左/p4 右/p5 左/p6 左/p7 左/p8 左逐点 rep 与 §4.2 一致;p4 右 heardArgument=true 且 C4 播 w1_say 组 | ✅已断言 | -| 4 | p9 左 → knot=true → ENDING(e1) + ep_lead + 5 条 ep_*_ok → 结算单击重开回 INTRO | ✅已断言(含手势预算 55≤60) | +| 听讲 | 主播/来电者语音,BGM 整链压低 | SPEAKING | +| 轮到你 | 风铃 `turn` → 400ms 后等待声(RING 用铃声循环代替合成等待声) | YOUR_TURN | +| (菜单/结算为元状态,`menu`/`settle` earcon + 结算句标识,不占新手三态) | | | -### §9 边界断言逐条 +RING 的铃声循环即等待声身份;首次出现配一句:「灯亮了,点一下接听。」 -| 断言 | 状态 | -|---|---| -| 四结局可达(剧本 B→e2 / C→e3 / D→e4,rep 终值与设计表一致) | ✅已断言(另补剧本 E:全左但 p4 左 → heardArgument=false → C4 播 w1_say2 组、rep=9,补齐 w1_say/w1_say2 双差分覆盖;剧本 C/D 额外断言带话差分不播与 ep_deer_no) | -| 双击重听:ONAIR 播完态/ENDING 播完态同 id 序列重播,重播期间单击=跳句 | ✅已断言 | -| 菜单返回不丢节点;单击循环顺序 [回到直播, 重新开始今晚, 保存并回大厅];确认回大厅播 sys_menu_lobby 且 localStorage 有最新 data | ✅已断言(含报名 queue 零截断顺序断言) | -| 中断复述:存档 node=k1_asko → sys_continue_ask → 单击恢复 → 重播节点链;上滑 sys_help→prog_c3→rep_mid 顺序 | ✅已断言(另补 ASK 右滑=新夜班) | -| 看门狗:RING 10s/25s 两级不同句、两级后仅铃声;ONAIR 链末 25s/50s gd_continue/gd_again;p* 25s/50s 复述两级后静默;ENDING 25s/50s;guide=off 全程零定时器零向导语音 | ✅已断言(full 档事件钩子计数,choice 签名 ×9) | -| pendingChoice:p* 播放中滑动感存、播完即生效(不丢不重复) | ✅已断言 | -| p* 不可跳(PROMPT_RE 门) | ✅已断言 | -| idle-only:对白在播时纠错跳过且不占 onceKey(F1.4);queue 句不顶在播句 | ✅已断言(skipped 事件 + 补播对照 + 菜单报名顺序) | -| 归因真话:剧本 D 播 z1_bade;剧本 A/E 全程零归因/补救句 | ✅已断言(含 k1_lulle/k1_liee 挂点) | -| 资产缺失:屏蔽 voice → 30s 看门狗续链到结局不卡死 | ✅已断言 | -| 数字规范:prog_*/rep_*/gd_ring* 分档整句字面量、无运行时拼接 | ✅已断言(静态审计) | -| 坏音质降级:滤波资产缺失回退原声 | 合并(回退是资产侧转码工序,代码无分支;NPC 性别目录→平铺回退由静态审计覆盖) | -| 失败即不通过(>5s 无声死局 / 无效手势零反馈 / 结局计数与 §3.4 不符) | ✅已断言(无效手势 error 震逐次计数;四结局矩阵即计数核对) | +### 3. 新手手势与解锁 -### 本轮修复的实现 bug(web/radio/game.js) - -1. **ONAIR 叙事链末不等待(规格 vs 实现最大偏差)**:`_onNodeSeqDone` 无条件 `advance(node.next)`, - 节点表的 `auto:true` 标志(INTRO 链/危机拍/签收链/p9 结果链)从未被读取——与 §2.3「链末=继续」、 - §7.2 ONAIR 链末看门狗行、§9「ONAIR 链末 25s/50s」及文件头注释均不符。修复:仅 `node.auto` 或 - `@x` 过场转移自动衔接,其余 ONAIR 叙事节点链末等待单击并挂 gd_continue/gd_again 看门狗。 -2. **longpress 误报无效**:gesture.js 把 longpress/longpressend/tapcancel 派发进 onGesture,RING/ - BRIDGE/ENDING/MENU/ASK 的 default 分支把它们当无效手势报 gd_invalid/error 震——§2.3 长按行是 - 「引擎接管快进」,全状态合法。修复:仅 left/right(ASK 另含 up/down/doubletap)报无效。 -3. **菜单单击循环的引擎队列竞态**:`audio.play(item, {queue:true})` 在引擎 `_finishVoice` 中先跑 - onComplete(游戏链播下一句)后 shift 队列,排队报名会把 sys_menu→sys_menu_resume 链播句顶掉。 - 修复:菜单报名改游戏侧 `_menuQueue` + `_drainMenuQueue()` 排水,当前报名播完接播、零截断。 -4. **RING 双击首通不报无效**:INTRO 链节点完成会写 `lastCloser`,首通双击重听了 b_goal 而未按开发 - 注记「首通前无上一通 → 无效手势零静默」报错。修复:`callN <= 1` 时报无效。 -5. **ENDING 上滑缺口碑**:§2.3 ENDING 上滑=「帮助+状态(结局名+口碑)」,原实现只播 sys_help+结局 - 主句。修复:链尾补 `TIER_VOICES[tierOf(rep)]`。 -6. **stop() 后中断链复活**:被 stopVoice 掐断的 `_playSeq` 链可经 30s 单句看门狗「资产缺失」路径续链, - 已拆除实例会走到 enterRing 留下无人接听的永久铃声定时器。修复:stop() 先 `seqToken++` 作废在途链。 -7. **newNight 不清抉择武装**:菜单重开/结算重开到新夜班首链起播前,choiceArmed/pendingChoice 残留, - 窗口期手势会在旧节点上误抉择。修复:newNight() 显式清零。 -8. **_ringOnce 双链防护**:closeMenu(RING)/_resume 直接调 _ringOnce 不清旧计时器,可能双铃声链。 - 修复:_ringOnce 先 clearTimeout 旧 ringTimer。 - -### 规格歧义 / 实现取舍(登记不改设计) - -- §4.2 表「p4 问她/哄睡」选项列序与 §3.2 树形(左=哄睡/右=问她)相反,实现按 §3.2 + p4 提示句原文 - (game.js 已注记);断言按 §9 口径(p4 右 → heardArgument=true,+1)。 -- `sys_saved`(15b G0 组,触发=存档写入后)未接线:自动存档每局 ≥7 次,逐次播报会持续打断节目流; - 菜单「保存并回大厅」已有 sys_menu_lobby 确认句。列遗留(如需可挂菜单「重新开始今晚」确认后)。 -- INTRO 上滑=「帮助+状态」:开播前无通号/口碑可报,实现只播 sys_help 后重播当前段(RING 起状态链完整)。 -- 存档 data 在 §8.1 字段外增加 `call`/`ending`/`lastCloser`(断点续玩与 RING 双击需要,超集兼容)。 -- ASK 右滑新夜班的 `nights` 按新实例计数(不继承被拒绝存档的 nights),与结算重开 +1 口径不一致, - 无游戏性影响,列遗留。 - -### 资产生产缺口(列遗留,本轮不修) - -> **→ 已闭环(2026-09-02 晚)**:12 件 SA3 条目全部补产(onair_sting / phone_ring / line_connect / -> line_drop / amb_rain_city / amb_station / amb_kid_room / arg_muffled / amb_street_rain / amb_cab / -> brake_screech / tape_static;amb_studio 文档有、未接线,产出备用),`check_all_assets` radio 254+254 -> 全过、playtest 221 断言全绿。详见 `research/new8/batch1-assets-report.md` §8。 - -- §5.2 SFX 15 件中 **12 件 Stable Audio 3 条目未产出**(`gen/gen_sfx.py` 头注:HF token 未配置跳过): - onair_sting / phone_ring / line_connect / line_drop / amb_rain_city / amb_station / amb_kid_room / - arg_muffled / amb_street_rain / amb_cab / brake_screech / tape_static。缺失走引擎 console.error - 静默降级(§6.1 降级方案),玩法与断言不受阻;playtest 静态审计锁定「缺失集合 ⊆ 该清单」, - 资产补齐后断言自动收敛。已产出:ui_confirm / ui_menu(程序合成)+ 132 句 voice + 4 首 bgm - (ogg+m4a 双格式零缺失,见静态审计)。 - -## R2 修复回归(round1 synthesis F1-F5 + V1/V2,2026-09-02) - -> 证据源:research/new8/radio/round1/{synthesis,live-player}.md。断言全部落在 -> `web/radio/test/playtest.mjs`「R2 修复回归」块 + 主路径/看门狗/静态审计改断; -> **playtest 断言总数 R1=193 → R2=221(+28)**,全绿。 - -| 项 | 修复(文件 → 行为) | 回归断言(✅=已断言) | +| 手势 | 含义 | 解锁 | |---|---|---| -| F1 (P1) | `game.js`:`onGesture` 入口 `guide.disarm()` 拆掉等待态看门狗后,无效手势/引擎接管手势分支按态立即重挂——ONAIR/INTRO 链末(`_inStoryWait()` 与 `_onNodeSeqDone` 等待分支同口径)、ASK 空闲、RING 空闲(`_rearmRingWatchdog`)、ENDING 结算态;BRIDGE 本无 guide 看门狗(restTimer 为游戏层),不涉及 | ✅ 链末等待中发无效手势,gd_continue 25s 整准点、gd_again 50s 整准点(旧实现 34.2s 零事件);✅ RING 无效手势后 gd_ring1 仍 10s 准点;✅ ASK 无效手势后看门狗仍 25s 准点 | -| F2 (P4/P2) | 台词表两处同步改词(先 `docs/design/15-radio-voice-ids.md` 后 `tools/gen_voice_dual.py`,两表一致):`rep_low`「今晚的口碑,有点砸了。」→「今晚的口碑,还没立起来。」(零起步语义,与 rep_high「在听众里立住了」同尺度;rep=0 开播前不再读成「砸了」);重生成 `voice/{male,female}/rep_low.{ogg,m4a}`(m=1.92s / f=2.32s,`--only rep_low` → `--convert` → `check_all_assets` radio 242+242 全过:双格式配对、零 0 字节、全可解码、性别目录 id 一致) | ✅ 静态审计锁定 rep_low 字面量存在;live 转写复验归 R2 协议第 2 步(重新生成转写) | -| F3 (P2) | `gen/gen_sfx.py` 增加 `phone_ring` 程序合成:440+480Hz 柔和双音一响 1.8s + 静默 3.4s = 5.2s 循环周期烘进文件(≥4s);无缝 loop 工艺 = 逐声道去直流、**不做首尾淡化**(`save_loop`)、解码回读校验真峰值 ≤-3dBFS(ogg -3.87 / m4a -3.84dBFS)+ 循环头/尾样本 ≤-60dBFS 边界静音;落 `audio/sfx/phone_ring.{ogg,m4a}`。`game.js` RING 接循环垫:引擎 sfx 层无 loop/stop → 游戏侧经 ctx 起循环 BufferSource 持句柄(`_ringSrc`,guide earcon 同款直用总线),接听/菜单/重开 `_stopRing` 即停;移除定时重触发(`ringTimer`/`ringJitterMs`/`RING_INTERVAL_MS`) | ✅ 进 RING 循环垫起播且挂起态虚拟时钟可空闲(零重触发定时器,可复现);✅ 单击接听循环垫即停不拖尾;✅ RING 无效手势不拆铃声垫;✅ 两级铃看门狗用尽后循环垫仍在(永不惩罚);✅ 静态审计 phone_ring 出待补清单(Stable Audio 待补 12→11 件) | -| F4 (P2) | `game.js`:断点续玩恢复后的首句播完再武装跳句——`_resume()` 置 `_resumeGuard`,`_playSeq` 消费为 `_skipLockId`(恢复链首句),该句播完/链完/被新链顶掉即解锁;ONAIR 与 ENDING(live 陷阱场景)两态 tap 生效 | ✅ 恢复后首句(w1_say / e4_a)播完前单击不跳句;✅ 首句播完后跳句重新武装(w1_sayo / e4_b 被正常跳过,链照常走) | -| F5 (P2) | `game.js`:链末句跳过保护——`_isTailSentence()`:当前句为本节点末句且节点把控制权交出(链末等待单击 / `@x` 过场转移 / 直入抉择提示)时屏蔽「跳过本句」;自动链中段(b_open/b_rain、危机拍)与 w1_say 组前两句跳句自由保留。与 F1 同提交复核链末状态机:`_inStoryWait` 与 `_onNodeSeqDone` 口径一致 | ✅ 交接抉择提示前的末句(w1_saye)单击不可跳、完整播出后照常交接 p7;✅ @ending 过场前末句(d9_facee,live 被吃掉的点题句)单击不可跳、完整播出后自动进 ENDING(e1) | -| V1 | 复核结论:pendingChoice 生效时序无 bug(W2 契约实现正确),按裁定补显式断言关案 | ✅ p* 播放中连点不跳句、不清缓存、不提前生效(rep 未动);✅ 播中再滑覆盖缓存(最后一次为准);✅ 生效时序 = p1 播完 → ui_confirm 结算音 → 结果句;✅ 缓存恰好生效一次(rep=1、缓存清空、不丢不重复) | -| V2 | 复核结论:菜单逻辑无缺失(三项循环 + 回绕正确),按裁定补全项循环断言关案。注:菜单为 3 项(sys_menu + sys_menu_resume/restart/lobby,15-radio.md §实现核对「菜单单击循环顺序」口径)——任务书所写「4 项/sys_menu_save」对应菜单标题句 + 3 菜单项;「保存并回大厅」确认句即 `sys_menu_lobby`(无 sys_menu_save 资产) | ✅ 单击循环两个整周期逐项可达(resume→restart→lobby→resume→restart→lobby,第三项不再缺席)、6 次回绕模 3 无漂移;✅ 双击确认「保存并回大厅」→ sys_menu_lobby 播出 + leaving 置位 + 存档落盘 | +| 单击 | 继续 / 接听 / 跳过当前这句 | 阶段 0 | +| 左滑 / 右滑 | 抉择(左=走心,右=克制) | 阶段 0 | +| 上滑 | 我在哪(通号 → 口碑 → 一个动作,≤20 字) | 阶段 1(第一次抉择后) | +| 下滑 | 菜单 | 阶段 2(第一次到达结局) | +| 双击 | 新手期不存在,按单击处理。阶段 3 起=重听**当前等待点完整句**(加「再说一遍:」)。禁止绑整节点重播 | 阶段 3(累计 3 局) | +| 长按 | 新手期 `invalid`。未占用帮助:旁白加速(12 号) | — | -V3(arg_muffled/brake_screech 等 11 件 SA3 资产)维持「等待 HF token」,接线代码已备,不在本轮。 -**→ 已闭环(2026-09-02 晚)**:SA3 12 件补产落地(含 phone_ring 真铃声替换),接线复核零代码改动, -详见 `research/new8/batch1-assets-report.md` §8。 +### 4. 状态 × 手势矩阵(新手期) -### 遗留(R2 收尾时需协同) +| 状态 \ 手势 | 单击 | 左/右滑 | 上滑 | 下滑(未解锁) | 双击 | 连点 | +|---|---|---|---|---|---|---| +| 游戏在说 | 跳过这句 + ok(p* / 链末关键句除外 → busy) | 打断后:抉择点缓存 pendingChoice、不盲选;非抉择点 invalid+解释一次 | 打断,答「我在哪」 | invalid + 首次解释 | 按单击 | 400ms 内 busy | +| 等你继续 | 继续 + ok | invalid(「现在不用选。单击继续。」) | 我在哪 | invalid | 按单击 | busy | +| RING 等接听 | 接听 + line_connect(silent-ok) | invalid + 首次解释 | 「第 N 通。点一下接听。」 | invalid | 按单击 | 400ms 内 busy | +| 等你选择 | 再听选项(「再说一遍:」+ 完整句,hold) | 抉择 + ok | 「正在抉择。左右滑。」 | invalid | 按单击 | 计入迷茫 | +| 结算 | 新夜班 + ok | invalid | 重播结局+口碑一句 | 菜单(阶段2) | 按单击 | 只处理第一下 | +| 菜单 | **确认当前项** + ok | 右=下一项 / 左=上一项,每项报名 | 重听菜单说明 | 关闭 | 按单击 | busy | -- `web/harness/voice_text.json` 的 `rep_low` 期望文本仍是旧词「有点砸了」(harness 归属协议/ - 转写侧,本轮改动手清单之外):R2 协议第 2 步重新生成转写前需同步为新词「还没立起来」, - 否则 live 复测该句会 MISMATCH。 -- dungeon 款全量回归本轮 FAIL:另一 agent 并行改动 `web/engine/{audio,gesture}.js` 与 - `web/dungeon/**` 进行中(失败点为 dungeon 网格/侧墙与 tut 旁白断言,与 radio 无关); - radio与其余 6 款全绿。 +验收:无空格;无「没有声音」;连点无重播/重开。 -## R3 修复回归(round2 synthesis G1-G6 打磨轮,2026-09-02) +**跳过纪律(G5,无双击重播)**:首播必完整——p* 不可跳、链末交接句不可跳、ASK 恢复首句锁。复播(关菜单复述)可掐。p* 播放中左右滑只缓存,句尾生效。 -> 证据源:research/new8/radio/round2/{synthesis,live-player,p1-blind,p3-office}.md。 -> 断言全部落在 `web/radio/test/playtest.mjs`「R3 修复回归」块 + 主路径/中断复述两处顺序断言改写; -> **playtest 断言总数 R2=221 → R3=246(+25)**,全绿。未新增台词/资产(G1 选纯音频标记方案, -> 零 TTS 管线动作;voice_text.json / voice-ids 无改动)。 +### 5. 等待点配置 -### G5 显式规则登记(P1 要求,F4/F5 隐式行为升格) +| id | 期待 | full | short | 静默 | L2 只留 | L3 演示 | 失败归因 | +|---|---|---|---|---|---|---|---| +| intro | tap | 「点一下屏幕。」 | 「点一下。」 | 6s / 15s | tap | 替点一下 | — | +| ring | tap | 「热线在闪。点一下,接听。」 | 「点一下。」 | 8s / 18s | tap | 替接听 | — | +| continue | tap | 「单击,继续。」 | 「单击。」 | 18s / 40s | tap | 替继续 | — | +| choice | left/right | 当前 pN | 「左右滑,选。」 | 25s / 50s | 左右滑 | 「左右滑,都可以。现在你来。」 | — | +| settle | tap | 「单击,重新开始。」 | 「单击。」 | 20s / 45s | tap | 提示音 | — | +| reopen | tap/right | sys_continue_ask | 「单击。」 | 12s / 30s | — | 提示音 | — | +| menu | tap/left/right | menu_help | 「左右滑,点一下定。」 | 20s / 45s | — | 提示音 | — | -**规则语句**:radio 的跳过公平契约 = **「首播必完整、复播可即掐」**—— +三档 + 30s 台账去重走引擎 `hints.js`。RING 两级不同句,用尽只留铃声,永不惩罚。 -- **首播必完整**(公平底线,玩家没听过的内容不许被抢跑): - 1. 抉择提示句 p*(voice/p1..p9)首播不可跳(PROMPT_RE 门),播中左右滑仅缓存 - `pendingChoice`、句尾当刻才生效(W2 契约零插播;播中连滑取最后一次); - 2. 链末关键句(等待单击 / `@x` 过场转移 / 直入抉择提示交接前的末句,如蛋糕句、点题句) - 首播不可跳(F5 链末句保护); - 3. **ASK 恢复首句例外**:断点续玩恢复链本属局面复述(复播),但其首句仍受 F4 跳句锁 - 保护——首句播完才武装跳句。锁只锁这一句、播完即释放,**不得升级为永久保护** - (live 局 B + 会话 C 双向验证:恢复链第二句 0.2s 即可跳)。 -- **复播可即掐**(效率面,已完整交付过的内容不再强制重听):F4 锁只锁首播一句且播完即释放, - **锁不随复播重新武装**(关菜单局面复述 / 双击重听回放同一句时单击照常跳过);F5 只护交接前 - 末句本身,链中段句首播复播均可跳;复播期间播中滑动照常预缓存抉择、句尾即生效(V1 断言)。 +### 6. 首 3 分钟逐秒脚本 -注:p* 复播(双击重听/看门狗复述)仍不可跳、仍句尾生效——比 P1 建议的「复播即掐即生效」更保守; -R2 live 已将 4 处截断全数归因玩家 tap 且 P1/P3 均接受现状,按代码实际契约登记并断言。 +| 时间 | 游戏说 / 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | onair_sting + amb_studio →「点一下屏幕。」(critical) | 单击 | 6s:「点一下。」 | 滑动:invalid +「先点一下。」 | +| 3~8s | ok →「热线灯亮了。」→ 铃声 + turn | 单击接听 | 「点一下,接听。」 | 滑动:invalid | +| 8~10s | line_connect + 老周开口(z1_a) | 听 | | | +| 10~15s | 「听到这个声音,就是轮到你了。」→ turn + 等待声 | 单击继续 | 三档 | inv_nosel | +| 15~45s | z1 链 → p1「左滑,哼歌。右滑,问路。」 | 左/右滑 | 复述 p1 | 滑早了:跳去选项句,不盲选 | +| ≈50s | 老周声音稳住 +「对。」解锁上滑:「想知道自己在哪,就向上滑。」 | 上滑 | 「向上滑。」 | 单击=再听选项 | +| 50~180s | p2 与 BRIDGE 口碑;第二通铃 | 单击/滑动 | 迷茫 L1→L3 | 归因真话 | -### G1-G6 逐条 +节奏:3s 内动手 ✓;10s 内接听成功 ✓;每 30s 有通号/对方情绪变化 ✓;无开场念规则 ✓。 -| 项 | 选型 / 改法(web/radio/game.js) | 回归断言(✅=已断言) | +### 7. 失败表 + +| 失败类型 | 判定 | 归因句 | 回到哪里 | 新手期代价 | +|---|---|---|---|---| +| 非抉择点滑动 | 手势与状态不符 | 「现在不用选。单击继续。」 | 原地 | 无 | +| RING 滑错 | RING 左右滑 | 「点一下,接听。」 | 原地 | 无 | +| 静默 | 阈值到点 | 三档提示 | 原地 | 无 | +| 迷茫 | 信号 ≥2 | L1 短句 / L2 只留期待手势 / L3 演示 | 原地 | 无 | +| 负向结果(p1右+p2右) | 计分表 −1 | z1_bade 指向实际选择 | 下一通 | 不扣资源 | + +叙事无资源死亡。旧「双击=整节点重播」废除。 + +### 8. 台词类别 + +- `critical`:sys_first_tap(开场 ≤3s);RING 首次接听指令 +- `content`:b_*、各通对白、p1–p9、e1–e4、ep_*、sys_turn_teach、sys_unlock_*、menu_item* +- `hint`:sys_hint_*_full/short、gd_ring1/2、status/prog/rep_*、sys_continue_ask +- `feedback`:inv_nosel、inv_generic、z1_bade、k1_lulle、k1_liee +- 主动重听前缀:`sys_say_again` +- 完整清单见 `tools/gen_voice_dual.py` RADIO 表(132 句 × 双性别主播/导播 + 29 NPC 平铺 = 235 条,×2 格式 470 文件) + +### 9. 断点复述 + +- 保存:每通 RING、每个抉择后、ENDING、菜单「保存并回大厅」写 `soundgame.radio.save`(phase + node + rep/bond/heardArgument)。 +- 回来 5s 内:sys_continue_ask「检测到上次的进度。单击,继续上次。右滑,从头开始。」单击后重播当前节点(局面复述)。 +- 上滑复述模板:位置(第 N 通·来电者)。目标(口碑档)。一个动作。 + +### 10. 测试记录 + +- 无头:`web/radio/test/playtest.mjs`。乱点机器人须 `guide=full`:无声操作 0、被动原句重复 0、打断失败 0、第一次操作 ≤3s、第一次成功 ≤10s。 +- 真人冷启动:待测(26 §6.3)。 + +--- + +## 1. 概述 + +- **一句话**:你是午夜档电台热线主播「今夜不眠」,一晚接进五个来电——你说什么、接不接、转不转接,改变五个陌生人的夜,也改变你自己。 +- **声音即世界**:来电者永远不露面,只有滤波后的电话声;直播间垫乐与环境音是空间。 +- **单局**:完整夜班 15–22 分钟;五通共享主干,分歧在通内局部化;实走 8–9 个抉择,4 结局。 +- **核心循环**:铃响接听 → 听 → 左右滑(走心/克制)→ 结果与口碑 → 幕间喘息 → 下一通。跨通积累 `rep` 与各通 `bond`,收播前 p9 决定心结。 + +## 2. 状态机 + +| 状态 | 内容 | BGM | earcon | +|---|---|---|---| +| INTRO | 极短开场后交 RING | `bgm/onair` | explore | +| RING | 铃声循环,等单击接听 | 不切 | turn(铃声承担等待声) | +| ONAIR | 通话链 + 抉择 + 结果 | `bgm/onair` | explore;p*=choice | +| BRIDGE | 口碑跨档(如有)+ 喘息,auto ≤12s | `bgm/bridge` | settle | +| ENDING | 结局 + 五句回响 | `bgm/ending` | settle | +| MENU | 模态覆盖,暂停链 | `bgm/menu` | menu | + +``` +INTRO ──链完──> RING(c1) ──单击接听──> ONAIR ──链完──> BRIDGE ──> RING(c2) … RING(c5) +ONAIR(c5) ──> BRIDGE ──> ◆p9 ──> ENDING(e1..e4 + ep) +任意叙事态:阶段2 后下滑 → MENU(左右滑切项,单击确认,上滑重听说明,再下滑关闭) +``` + +- 仅 `node.auto` 或过场自动衔接;其余 ONAIR 链末等单击。 +- ASK:单击继续 / 右滑新夜班。 +- 关菜单后重播当前节点(局面复述)。 + +## 3. 抉择树 + +### 3.1 骨架 + +``` +INTRO ──> 铃① +【C1 老周】教学通 · p1 p2 ──> BRIDGE ──> 铃② +【C2 小鹿】p3 ──> BRIDGE ──> 铃③ +【C3 糖糖】p4 p5 · 埋 heardArgument ──> BRIDGE ──> 铃④ +【C4 陈姐】p6 p7 · 消费 heardArgument ──> BRIDGE ──> 铃⑤ +【C5 老韩】p8 + 危机拍 ──> ◆p9 ──> ENDING +``` + +跨通只传 `rep`、`bond`(5×0/1)、`heardArgument`。无组合节点。最坏/最好路径节点数相同。树约 60 节点、132 句。 + +### 3.2 分通(◆=抉择,〔〕=条件差分) + +**C1 老周**:铃 → z1_ring_in → z1_a/b → ◆p1 左哼歌 / 右问路 → ◆p2 左陪等 / 右转接(仅 p1=右且 p2=右走 z1_bad 归因)→ z1_ok1(全局一次)→ BRIDGE。 + +**C2 小鹿**:◆p3 左劝上车 / 右还自由 → BRIDGE。 + +**C3 糖糖**:k1_b 垫 `arg_muffled` → ◆p4 左哄睡(本通结束)/ 右问她 → ◆p5 左真话(heardArgument=true)/ 右谎言 → BRIDGE。 + +**C4 陈姐**:◆p6 左带话〔heardArgument ? w1_say : w1_say2〕→ ◆p7 左直播 / 右私聊;p6 右劝回家本通结束 → BRIDGE。 + +**C5 老韩**:◆p8 左陪聊闺女(danger + brake_screech 前置 2s)/ 右服务区;bond_zhou 时插 h1_echo → ◆p9 左直面 / 右回避 → ENDING。 + +**结局** + +| 结局 | 条件 | 主题 | |---|---|---| -| G1 帮助链去重 | **选型:纯音频标记(不加新台词)**。「只震+earcon+sys_help 现有句」方案被否——sys_help 为单文件 26 字整句,无「首句」子资产,重播即逐字重复,恰是待修问题。实现:`_helpSeen`(Set,实例级,不跨实例持久化,sys_skip 每实例一次同款口径)按**等待点**键控(ONAIR/INTRO 按 nodeId、BRIDGE 整段、ENDING 按结局、菜单整段);`_helpSpokenOnce`:同键第 1 次全量口播,第 2 次起 `vibrate('beat')` + nudge earcon(复用统一 8 音词表零新增;guide 无公开单发 earcon API,经 `_playEarcon` 直用,`_startRing` 直用引擎总线同款先例;guide=off 时只震)。状态句照播(复述状态=合法帮助,P1 先例);新等待点首滑仍全量口令,非全局静默 | ✅ 同一等待点第 2 次上滑不重播 sys_help(ONAIR/ENDING 两态);✅ 状态句照播(prog/rep 各 2 次);✅ beat 轻震标记;✅ nudge earcon;✅ 新等待点(z1_a)首滑仍全量口令 | -| G2 rep_low warning 震 | R1 P3-3 遗留。原实现 `enterBridge` 跨档分支 `vibrate('warning')` **实际不可达**(rep 唯一减分组合 p1右+p2右 从 0→-1 不跨档,`lastAnnouncedTier` 初值即 low)。修:`_tierCue()` 挂在**全部 rep_low 播报位**——RING/ONAIR/BRIDGE/ENDING 四处上滑复听,rep_low 整句出口同时手腕有感(BRIDGE 跨档分支保留不动) | ✅ RING 上滑复听 rep_low 附 warning 震(live 局 B t=198.5/497.5 证据位);✅ ONAIR 上滑同款;✅ ENDING 上滑同款 | -| G3 k1_a/k1_b male 回退 | **核查结论:回退正常,非静默,不修。** k1_a/k1_b 为糖糖 NPC 平铺组资产(设计即单音色 Vivian 童声、不做双性别,§5.3);male 设置下引擎候选链 `voice/male/k1_a.{ogg,m4a}`(404,符合预期)→ `voice/k1_a.ogg`(200)命中平铺版播出(web-engine-api.md voice 路径解析)。**文档注记**:live 局 B 观测的「同通电话声线跳变」实为主播声部(随 settings.voice 换声)与糖糖童声(恒定)在同通内交替,属「电话音质感」设计使然;不补双性别资产(违反 NPC ≤5 音色约束与 §5.3 设计) | ✅ male 先尝试性别目录 male/k1_a、k1_b(404 属设计);✅ 平铺版播出(voicePlays 各 1);✅ 零「资产缺失」console.error(真静默则修——实测无需修) | -| G4 空点确认轻震 | R1 P3-1 / R2 P3-4 遗留。不可跳句(p* 首播 / F5 尾句 / F4 锁)上的单击「点了但没选上」,gesture 层确认震 [20] 游戏侧无法撤销(engine/gesture.js 不在本轮改动清单)——游戏层补一记最轻 beat [8] 形成可区分签名(20→8 连击 ≠ 真确认的单记 20),消解「像点上了」误导;落点 `_onStoryGesture` / `_onEndingGesture` 两处 tap 屏蔽分支 | ✅ F4 锁内单击 beat 震;✅ F5 尾句单击 beat 震(「p9 后空点」证据位);✅ p* 不可跳句单击 beat 震(R1 P3-1 证据位) | -| G5 显式规则 | 纯登记 + 断言,无代码改动(F4/F5/V1 现有实现即规则本体);规则语句见上节。新增断言锁「锁不随复播重新武装」(复播可即掐的反向面:保护不得扩大化) | ✅ ASK 恢复首句锁内单击不跳(例外);✅ 复播(关菜单局面复述)首句单击照常跳过;✅ 链末关键句首播不可跳(F5 引用);✅ p* 首播单击不跳(引用);(F4/F4b/F5/F5b/V1 既有断言为规则其余各半) | -| G6 状态链重排 | P1 两次建议、纯排序改动 → 本轮做。`_withHelp()` 把帮助句接在状态链尾:「当前通号 → 口碑 → 帮助」。ONAIR/INTRO(有状态时 prog → rep → sys_help)、BRIDGE(下一通 prog → rep → sys_help)、ENDING(结局主句 → rep → sys_help);RING 本就状态在前且无帮助句(§2.3 RING 行),天然满足;MENU 链无通号/口碑状态句,G6 不适用(仅 G1 去重,帮助保持在前)。§2.3 表内「帮助+状态」措辞由本节登记更新(状态在前),正文不改 | ✅ ONAIR 顺序 prog_c1 → rep_low → sys_help;✅ BRIDGE 顺序 prog_c2 → rep_mid → sys_help(原断言改写);✅ ENDING 顺序 e4_a → rep_low → sys_help;✅ 中断复述 ONAIR 顺序 prog_c3 → rep_mid → sys_help(原断言改写) | +| e1 天亮之前 | rep≥5 且 p9 左 | 话筒接住了这座城 | +| e2 热线常亮 | rep≥5 且 p9 右 | 满座夜班,留言机未看 | +| e3 一个人的频道 | rep<5 且 p9 左 | 晚安说出去了 | +| e4 静音 | rep<5 且 p9 右 | 谁也接不住谁 | -### 顺带补的 SA3 音效接线断言 +epilogue 对五人各播 bond? 救到 : 错过。 -主路径补 `sfx/onair_sting`(开场台标)/`sfx/line_drop`(幕间挂断)/`sfx/tape_static`(p9 直面) -接线计数断言(arg_muffled ×2 / brake_screech / phone_ring 已有);静态审计 -`STABLE_AUDIO_PENDING` 注释更新为「SA3 12 件已全部补产,清单降级为守卫」。 +注:p4 表列「问她/哄睡」与树形(左=哄睡 / 右=问她)以树形与 p4 提示句为准;p4 右 → heardArgument=true。 -### 观察项(按裁定不动代码) +## 4. 数值 -- 复播晚点 +7.6s 一次性事件(live P3-3):一次性观测,未加断言盯防(避免为仪器怪癖加约束)。 -- mid-prompt 生效掐选项句尾(live P3-6):设计备注维持,G5 规则已按现状登记。 -- harness 丢 tap 仪器怪癖(live P3-5):记入 R3 协议(tap 后 listen 一次确认再重发),非游戏侧。 +| 变量 | 初值 | 说明 | +|---|---|---| +| `rep` | 0 | 听众口碑 | +| `knot` | false | 仅 p9 左 | +| `bond_*` ×5 | 0 | 只影响尾声 | +| `heardArgument` | false | p4 右,C4 消费 | -### 遗留 +| 抉择 | 左 | 右 | +|---|---|---| +| p1 | +1 | 0 | +| p2 | +2(p1左)/ +1(p1右) | +1(p1左)/ **−1(p1右)** | +| p3 | +2 | +1 | +| p4 | 0(哄睡) | +1 + heardArgument | +| p5 | +1 | 0 | +| p6 | +2(有线索)/ +1 | 0,本通结束 | +| p7 | +2 | +1 | +| p8 | +1 | 0 | +| p9 | knot=解 | 未解 | -- 无新增遗留。G1 去重集合为实例级(重进游戏首滑仍是完整口令)——如 R3 复评希望跨实例 - 记忆(localStorage 持久化「已听过口令」),列 v2 候选,本轮不加(保持与 sys_skip 每实例 - 一次同口径,避免老玩家重进被静默)。 +收播 `rep≥5` 高口碑。理论上限 12。BRIDGE 跨档播 rep_high / rep_mid / rep_low(rep_low 措辞:「今晚的口碑,还没立起来。」零起步为真)。负向结果句必须指向实际选择。 + +## 5. 音频三层 + +### 5.1 BGM(4 首,Mureka) + +| 状态 | 曲目 | 切换 | +|---|---|---| +| INTRO/RING/ONAIR | `bgm/onair` | 开局淡入 800ms,全夜不切 | +| BRIDGE | `bgm/bridge` | crossfade 800ms | +| MENU | `bgm/menu` | 500ms 进,关后淡回 | +| ENDING | `bgm/ending` | p9 后淡入 1500ms | + +### 5.2 SFX + +onair_sting、phone_ring(循环 ≥4s 间隔,游戏层 BufferSource 持柄)、line_connect、line_drop、amb_studio、amb_rain_city、amb_station、amb_kid_room、arg_muffled、amb_street_rain、amb_cab、brake_screech、tape_static。C5 预警:h1_danger 前 2s 起 brake 渐强,与 danger 签名同窗。回执 ok/invalid/busy 走引擎合成,不靠震动。 + +### 5.3 Voice + +| 组 | 落位 | 数量 | +|---|---|---| +| 主播 HOST | 双性别 | 21 | +| 导播 PROD | 双性别 | 82 | +| NPC ×5(老周/小鹿/糖糖/陈姐/老韩) | 平铺 + 电话滤波 | 29 | + +滤波:老周 HP500/LP2800;小鹿 400/3400;糖糖 450/3000;陈姐 420/3100;老韩 300/3400。不可读则回退未滤波原声。主播/导播不滤波。 + +## 6. 来电音质与 Floor + +Floor:SPEAKING 整链 duck;YOUR_TURN 才起等待声;RING 用铃声循环当等待声。单击跳句;滑动打断并执行。critical ≤3s 给 busy。hint/feedback 空闲才播,过期丢弃。C5 唯一 danger 点。battle 耳控不用。新手只教 turn(接听/选择)+ 可选 settle/menu 首遇一句。 + +## 7. 存档 + +```json +{ + "schema": 1, "summary": "第3通·失眠的孩子 · 口碑+2 · 结局1/4", + "phase": "ONAIR", "node": "k1_asko", "rep": 2, "knot": false, + "heardArgument": true, + "bond": { "zhou": 1, "deer": 1, "kid": 0, "wife": 0, "han": 0 }, + "endings": { "e2": true }, "nights": 3 +} +``` + +summary:进行中 `第N通·<来电者> · 口碑±X · 结局K/4`;已收播 `已收播·<结局名> · 结局K/4`。 + +## 8. 节奏 + +开场 ≤10s 接到第一通;C1 ~3.5min 教学(学习期零惩罚);C2 呼吸;C3 线索音;C4 最复杂;C5 危机零死亡;签收+回响 ~3min。合计 ≈21min。操作面恒为二选一。 + +## 9. playtest 指针 + +关键路径、四结局、pendingChoice、p* 不可跳、归因真话、资产缺失续链:仍登记 `web/radio/test/playtest.mjs` 与 13 号文档。**改断言**:废除「双击重听整节点」;菜单改为左右滑+单击确认;RING/看门狗改为三档不同句;首次操作 ≤3s、首次成功 ≤10s;无效手势必有回执声(不接受只震)。 diff --git a/docs/design/16-dungeon-voice-ids.md b/docs/design/16-dungeon-voice-ids.md index 78e0b57d..8d2fdde7 100644 --- a/docs/design/16-dungeon-voice-ids.md +++ b/docs/design/16-dungeon-voice-ids.md @@ -1,5 +1,13 @@ # 16b · 《回声地窟》语音台词表(voice ids,录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - **全部旁白,无 NPC**:本作怪物不说话,无角色音(主设计 §1/§9.3)。125 句双性别。**female = Serena,male = Uncle_Fu**(沿用 R1 分配惯例)。落 `web/dungeon/audio/voice/female|male/.{ogg,m4a}`。 > - female instruct 常量:`平静清晰的女声,像探窟向导,沉稳可信` diff --git a/docs/design/16-dungeon.md b/docs/design/16-dungeon.md index 387409e3..1f35b0a0 100644 --- a/docs/design/16-dungeon.md +++ b/docs/design/16-dungeon.md @@ -1,714 +1,236 @@ -# 16 · 《回声地窟》完整设计(dungeon / Echo Cavern) +# 16 · 《回声地窟》(dungeon / Echo Cavern) > **给开发者的实现要点摘要** -> 1. 听声辨位 Roguelike:网格地窟 + 面朝转向。**单击=声波脉冲**(回声"看"地形:正前距离 4 档×材质 4 种,左右侧 墙/开口 2 档,预 pan 立体声资产),**按住=前进、左右滑=转身**(豁免登记见 §2),双击=重发脉冲(免费重听)、上滑=状态、下滑=菜单(三件套不豁免)。战斗是 rpg 式回合制(前摇双击格挡),Boss 战加入绕位(脚步声报方位、转身对准才打得到)。 -> 2. 方位全程降维 ≤4:探索报前/左/右三向(身后不报,转身即查),战斗相对方位只有面前/左/右/背后。回声=静态地形、脚步=动态怪物、播报=绝对方位——三套信息由同一 `grid` 单源生成,机制上不可能互相矛盾。 -> 3. 单局=一命一局,7 层满通约 24 分钟;死了重开但金币进元进度("遗产"5 项永久强化,死亡不清档)。学习期零惩罚:L1 怪 ATK=0、L2 ATK=1,学期内不可能死;药水自动喝;撞墙无伤;无任何倒计时。 -> 4. 随机性全走种子 PRNG(mulberry32,`opts.seed` 可注入,playtest 传固定 seed 复现);回声/脚步全部用「预生成 sfx 资产 + setTimeout 调度 + volume」实现,不用引擎外实时节点(playtest 虚拟时钟可断言)。 -> 5. 台词表 [16-dungeon-voice-ids.md](16-dungeon-voice-ids.md)(125 句 ×2 性别,无 NPC——怪物不说话);本作向导句 gd_* 需在开发轮次登记进 14 号文档增补(§6 纪律:queue:true、idle-only、onceKey)。 -> 6. 规模对标 escape+rpg 折中:单文件 game.js 预计 900-1100 行,bgm 4 + sfx ~34 + voice 125×2。 +> 1. 交互权威:[26-audio-interaction-playbook.md](26-audio-interaction-playbook.md)。空间范式对齐 [26-apply-escape.md](26-apply-escape.md) 与 00 §5.3:**四向滑动是玩法 → 帮助一律长按**。 +> 2. 核心动词:单击=声波脉冲(听回声看地形)。四向滑动=按面朝相对移动(前/左/右/后)。**废除**长按=前进、双击=重发脉冲。 +> 3. 听声辨位 Roguelike:网格 + 面朝。回声=静态地形,脚步=动态怪物,播报=绝对方位——三套信息单源 `grid`。 +> 4. 一命一局,7 层满通约 24 分钟;金币进元进度(5 项遗产)。L1 ATK=0、L2 ATK=1,学期不可能死。 +> 5. 台词 [16-dungeon-voice-ids.md](16-dungeon-voice-ids.md)(127 句 ×2 性别,无 NPC)。随机性 mulberry32,`opts.seed` 可注入。 +> 6. 规模:bgm 4 + sfx ~36 + voice 127×2。回声用预生成资产 + setTimeout + volume,不用引擎外实时节点。 + +--- + +## 附录 C · 设计单 + +### 1. 一句话核心动作 + +**朝四个方向滑动在洞窟里走,单击发出声波脉冲听地形,按住屏幕问路。** + +### 2. 玩家能感知的状态(3 个) + +| 状态 | 听感 | Floor | +|---|---|---| +| 探索 | 洞穴环境 loop(amb_cave)常响;脚步/回声是内容 | ACTION | +| 听讲 | 教学/房间/战斗报名语音,BGM 整链压低 | SPEAKING | +| 危险 | battle BGM 或 danger earcon + 心跳震(血量≤25% / 敌前摇) | ACTION + danger 签名 | + +探索期撞墙=闷响 + invalid,不算第四态(一次性教)。 + +### 3. 新手手势与解锁(四向滑动是玩法) + +| 手势 | 含义 | 说明 | +|---|---|---| +| 四向滑动 | **面朝相对移动**:上=前进一格;左=向左侧走一格并转向该向;右=向右侧走一格并转向;下=后退一格(面朝不变) | 阶段 0 即核心,不按阶梯锁 | +| 单击 | **脉冲**(探索);战斗玩家回合=挥击;敌方前摇窗内=格挡 | 核心动词 | +| 长按 | **我在哪**(层/血/面朝/一个动作,≤20 字) | 帮助入口,全空间游戏同一条 | +| 双击 | 新手期按单击处理。阶段 3=重听最近回声编码(冷却外再脉冲,不扣资源) | 禁止「双击=立刻免冷却重播」绑死连点 | +| 下滑 | 对局中=后退(玩法占用)。菜单仅阶段 2 且 **SETTLE/TITLE** 开放 | 00 §5.3 | + +对局期关闭长按旁白加速(帮助占用长按)。 + +### 4. 状态 × 手势矩阵(新手期) + +| 状态 \ 手势 | 单击 | 四向滑动 | 长按 | 双击(未解锁) | 连点/乱滑 | +|---|---|---|---|---|---| +| 教学作答 | 按步骤(脉冲/确认) | 按步骤走/转向 | 帮助 | 按单击 | 计入迷茫 | +| 探索 ACTION | 脉冲 + 回声组(冷却中 invalid) | 移动=脚步 silent-ok;撞墙=闷响+invalid+归因一次 | 帮助 | 按单击(冷却后再脉冲=合法「再听」) | 乱滑计入迷茫;冷却连点只 busy/invalid | +| 描述在播 | 跳过这句 + ok | 打断并移动 | 打断,帮助 | 按单击 | 第一下有效 | +| 战斗·我的回合 | 挥击(面前才中) | 左/右转面对敌人;下=尝试逃跑(普通怪必成挨一下;Boss 拒绝) | 帮助(血量+敌方位) | 按单击 | 计入迷茫 | +| 战斗·敌前摇 | **格挡**(面对来源减伤 80%) | 转身(窗口内来得及则下一击面对) | 帮助 | 按单击=格挡 | 只处理第一下 | +| 结算 | 再来一局 + ok | invalid + 解释一次 | 重听结算一句 | 按单击 | 只处理第一下 | +| 菜单 | 确认当前项 | 右=下一项 / 左=上一项 | 重听菜单说明 | 按单击 | busy | + +脉冲已有明显内容音时可 silent-ok。invalid/busy 永远出声。 + +### 5. 等待点配置 + +**教学(6s/15s)** + +| 步 | 期待 | 指令 | +|---|---|---| +| D0 | 单击 | 「黑暗。点一下,听回声。」 | +| D1 | 再单击 | 回声演示(前方保证有墙) | +| D2 | 上滑(前进) | 「朝前,滑一下。」 | +| D3 | 左滑或右滑 | 「向左滑,转身走。」 | +| D4 | 单击 | 拾到本房金币(首胜) | +| D5 | 长按 | 「迷路了,按住屏幕。」 | +| D6 | 走向教学怪:单击挥击 | ATK=0,乱打不死 | + +探索期无固定等待点。迷茫:静默 40s 起 / 乱滑 / 连续无效。L1 短目标句(「向右滑。」)→ L2 只放行导航方向 → L3 游戏朝楼梯走一步。卡关带路并入迷茫,不再 60/120/180 独立轰炸。 + +战斗回合:turn + 等待声;8s short「点一下,打。」15s earcon。 + +### 6. 首 3 分钟逐秒脚本 + +| 时间 | 游戏说 / 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | amb_cave →「黑暗。点一下,听回声。」(critical) | 单击脉冲 | 「点一下。」 | 滑动:invalid +「先点一下。」 | +| 3~8s | pulse + echo + 材质 →「对。回声越快,墙越近。」 | 再点(巩固) | 短句 | 冷却内连点:busy | +| 8~10s | 奖励音(听懂回声=第一次成功) | — | | | +| 10~25s | 「朝前,滑一下。」 | 上滑前进 | 「滑一下。」 | 撞墙:闷响 +「那边是墙。」 | +| 25~40s | 「向左滑。」→ 脚步 | 左移 | 短句 | 纠错一次 | +| 40~60s | 「按住屏幕,听自己在哪。」 | 长按 | 「按住。」 | 其他:invalid | +| 60~90s | 自由探索;保证房内金币 | 走+脉冲 | 迷茫接管 | 撞墙无伤 | +| 90~180s | 教学蝠 ATK=0:脚步 → 前摇窗单击格挡 → 单击挥击至击杀 | 战斗 | L3 演示格挡 | 学期不掉血 | + +节奏:3s 内脉冲 ✓;10s 内第一次听懂回声 ✓;每 30s 新房间/金币/击杀 ✓。 + +### 7. 失败表 + +| 失败类型 | 判定 | 归因 | 回到 | 新手期 | +|---|---|---|---|---| +| 撞墙 | 越界 | 「那边是墙。」每方向整局一次,之后只闷响+invalid | 原地 | 无伤 | +| 挥空 | 怪不在面前 | 「它不在正面。左右滑。」 | 本回合 | 无 | +| 格挡失败 | 前摇内未点或未面对 | 「慢了。听到就点。」/「在身后。」 | 下回合 | L1–2 怪打不死你 | +| 死亡 | HP≤0 | die_cause_* 按 lastHit;die_adv_* 按真实格挡数 | TITLE,金币入 meta | 学期不可能 | +| 迷路 | 迷茫信号 | L1→L3 带路 | 原地 | 无 | + +### 8. 台词类别 + +- `critical`:开场「点一下,听回声」;结算承诺句 +- `content`:intro、层/血/金/面朝/威胁档、btl_start_*、tut_*(改词后)、unlock_* +- `hint`:lp_* 长按帮助、nav_*、stair_hint_*、sys_continue_ask、gd_pulse 短句 +- `feedback`:wall、btl_swish、btl_behind、btl_no_turn、die_cause_*、tut_parry_miss +- 停用旧句(须重录或下架):`tut_walk`「按住屏幕不放,你就往前走。」`tut_replay`「双击,再发一次脉冲」`tut_parry`「双击格挡」`tut_status`「上滑听状态」`tut_menu`「下滑打开菜单」`tut_flee_hold`「想逃跑,按住别松手」 + +资产账:127 句 ×2 性别(含 gold_some)。新教学句替换上列,总数可持平或 +3(tut_move / tut_help_hold / tut_parry_tap),上限 130。 + +### 9. 断点复述 + +- 保存:拾取/战斗/下潜/菜单保存/死亡。data 含 meta + run(seed/floor/pos/face/hp…)。 +- 回来:sys_continue_ask;单击恢复 + 长按帮助句作复述(位置。目标。滑一下。);右滑新周目(meta 保留)。 +- summary:`第{floor}层 · {血量档} · 金币{meta.gold} · 遗产{n}/5`。 + +### 10. 测试记录 + +- `web/dungeon/test/playtest.mjs`。改口径:D0 3s 内脉冲;移动断言改四向滑;长按=帮助不位移;战斗前摇单击=格挡;下滑动=后退不打开菜单;双击不重发免冷却。无声操作 0。真人冷启动待测。 --- ## 1. 概述 -- **一句话**:你是盲眼探窟者,单击发出声波脉冲、用回声"看"每次都不同的地窟,听脚步辨怪,逐层下潜,死了带着金币重来、越死越强。 -- **题材**:黑暗洞窟探险。核心命题「伸手不见五指,声音就是你的眼睛」——题材与纯音频互为 justification,与《剑与声》的"耳即眼"呼应但玩法完全不同:rpg 是选项列表 RPG,本作是**空间探索型 roguelike**(方法论 §4.1:状态层重点=方位参数映射+撞墙归因+目标方向信标)。 -- **为什么适合纯音频**:回声定位本来就是"用声音看空间"——视觉游戏的 minimap 在这里天然就是一段回声;黑暗=无画面是叙事设定而非技术妥协。移动/转身/距离全部可离散化(≤4 方位×4 距离档),不需要连续角度(文献结论:方位降维 ≤4)。 -- **单局**:一命一局,出生到死亡/通关。7 层,满通约 24 分钟,普通玩家死于 4-6 层(约 14-19 分钟)。死亡结算 ≤15s 必答归因三问,金币入元进度,**死亡不清档位进度**。 -- **与现有 6 游戏的关系**:空间感承接 escape(网格+带路提示),回合战斗承接 rpg(前摇格挡),演练教学承接 space(D 步逐项实操),元进度为本作新增系统(8 款新模式探索点的核心增量)。 +盲眼探窟者用脉冲「看」每次不同的地窟,听脚步辨怪,逐层下潜。死了金币留在遗产里,越死越强。黑暗是设定不是妥协。方位降维 ≤4。与 escape 同为空间探索,战斗回合承接 rpg,元进度为本作增量。 -## 2. 手势映射与豁免登记 +## 2. 状态机 -### 2.1 探索态(EXPLORE / DRILL / DESCEND) - -| 手势 | 作用 | 反馈 | -|---|---|---| -| 单击 | **发出声波脉冲**(核心动词,1.2s 冷却防连打糊声流) | sfx/pulse + 调度回声组(§3) | -| 按住(longpress,450ms 起) | **前进**:按住朝面朝方向连续走(每格 0.5s),松开(longpressend)即停;撞墙自动停 | 每格 step_own + swipe 震;撞墙 bump + 停,**无伤** | -| 左滑 | 左转 90°(面朝逆时针 N→W→S→E) | step_turn + swipe 震 | -| 右滑 | 右转 90° | step_turn + swipe 震 | -| 双击 | **重发脉冲**(免费重听回声,不消耗任何资源、无冷却惩罚) | 同单击(宪法"双击=重听"的本作落地:线索=回声) | -| 上滑 | 状态播报链:层 → 血量档 → 金币档(元进度总账)→ 面朝 → 周围威胁 | 5 段链播 | -| 下滑 | 模态菜单(§2.4) | menu earcon | - -### 2.2 战斗态(BATTLE) - -| 手势 | 作用 | 反馈 | -|---|---|---| -| 单击 | 挥击:怪在你面前才命中;不在(Boss/绕位怪换位后)= 挥空 | atk_swing→命中 atk_hit / 挥空 + `btl_swish`(归因真话) | -| 双击 | **格挡**:仅敌方前摇音窗口内(1.2s)有效;面对攻击来源减伤 80%,未面对 0%(背后挨刀) | parry / hurt + hit 震 | -| 左滑/右滑 | 转身(只对绕位怪/Boss 有意义;缠斗怪时无效) | step_turn / 纠错(§10) | -| 按住 | **尝试逃跑**:普通怪必成、但撤退挨一下(真实掉血);Boss 战拒绝 | `btl_flee` / `btl_flee_boss` | -| 上滑 | 战斗状态:我方血量档 → 敌方血量档 →(Boss/绕位战)它的当前方位 | 3 段链播 | -| 下滑 | 模态菜单(战斗暂停,回合制无实时流,无需 count-in) | menu earcon | - -### 2.3 标题态(TITLE)/ 结算态(SETTLE) - -| 手势 | TITLE | SETTLE | -|---|---|---| -| 单击 | 继续(有存档)/ 开始探窟(无存档) | 再来一局(新周目,重掷 seed) | -| 右滑 | 放弃当前周目,重掷新周目(元进度保留) | —(无效,纠错) | -| 双击 | 重听 continue_ask / 元进度播报 | 重听结算链 | -| 上滑 | 元进度播报:金币档 → 最深层数档 → 遗产清单(已解锁各一句)→ `unlock_none`(不够时) | 重听元进度段 | -| 下滑 | 菜单(帮助/返回两项) | 菜单 | -| 按住/左右滑 | 无效(error 震 + `gd_invalid`) | 同左 | - -### 2.4 模态菜单(遵守 00 文档 §5.2 全球约定,无豁免) - -下滑呼出 → 单击循环切换(每切播项名)→ 双击确认 → 上滑=帮助 → 下滑=关闭。打开即暂停(战斗回合制天然可暂停)。选项:返回探索(战斗态同)→ 保存并回大厅 → 重新听帮助 → 重新开始本周目。 - -### 2.5 豁免登记(必须显式) - -| # | 豁免条目 | 理由(先例) | -|---|---|---| -| E1 | 单击=脉冲(非"确认/继续") | 核心动词必须是最快手势;脉冲是"检查"类非即时操作,300ms tap 延迟无碍(escape 单击=检查同款) | -| E2 | 左右滑=转身、按住=前进(探索态四向滑+长按被玩法占用) | 网格移动玩法(escape 四向滑=移动先例);上下滑让位给不可豁免的状态/菜单三件套,前进改绑长按——"按住就走"零认知负荷(P4),单手免视(P3) | -| E3 | 对局期 `audio.setRateBoostEnabled(false)`(探索/战斗/教学),菜单/结算恢复 true | 长按=移动是潜在玩法输入,长按快进必须豁免(rhythm/space 对局期豁免先例) | -| E4 | 战斗态下滑=菜单保留,但菜单在战斗内语义为暂停 | 回合制,菜单即暂停,无并发压力;不沿用 rpg"战斗态下滑=逃跑"豁免(本作逃跑绑长按,更贴"转身就跑"直觉) | - -教学(DRILL)中逐条讲清 E1/E2/E4 的偏离(tut_* 句),符合 §5.4"偏离必须在开场交互教学中讲清"。longpressend 仅作停走信号,不承载第二语义;不开启 immediateTap(无 tapcancel 路径)。 - -## 3. 核心动词:声波脉冲与回声定位 - -### 3.1 声学编码(全在引擎能力内) - -| 维度 | 编码 | 实现 | -|---|---|---| -| 正前距离 | 4 档:贴身(1格)/近(2格)/中(3-4格)/远(5+格) | 脉冲后 setTimeout 调度 `echo_1..4`,延迟 0.25/0.50/0.80/1.10s,volume 0.9/0.7/0.5/0.35 | -| 正前材质 | 4 种:石墙/木门/铁梯(楼梯)/水 | 回声到达后 +120ms 播材质尾音 `mat_stone/door/stair/water`(0.2s 短音色) | -| 左右侧 | 2 档:墙(闷)/开口(空旷回灵) | 脉冲后 +0.35s 同时播 `side_wall_l/r` 或 `side_open_l/r`(**预 pan 立体声资产**:能量只在一个声道,引擎 play() 无实时 pan,space whoosh_left/right 同工艺) | -| 身后 | **不报** | 刻意降维:回声只照面前和两侧,转身即查(教学句 tut_side_rule 讲明规则;不报≠信息矛盾) | - -- 节点说明:**不用引擎外实时 WebAudio 节点**(playtest mock 只 mock bufferSource/gain)。全部=预渲染 sfx 资产 + `play()` volume + setTimeout 调度——虚拟时钟下回声调度可精确断言。 -- 降级方案:若 SA3 立体声分离度不达标,侧向回声改 SYN 程序合成(直接写立体声 AudioBuffer 的 L/R 通道数据,管线能力内);材质尾音全部 SYN 可兜底。 -- 频带分离防糊:己方脚步/怪物脚步=低频(100-300Hz),脉冲+回声=中频(800Hz-2kHz),BGM 垫底——探索期并发声流 ≤2(你的动作反馈 + 怪物脚步)恒成立。 -- 脉冲冷却 1.2s:防连打;双击重发不受额外惩罚(P1"回声可重听且免费")。冷却期单击/双击 → 仅 error 震(无语音惩罚)。 - -### 3.2 三套信息自洽(P1 专项,机制级保证) - -| 信息 | 声源 | 内容 | 坐标系 | -|---|---|---|---| -| 回声 | 脉冲触发 | 静态地形(墙/门/楼梯/水 + 距离) | 相对(前/左/右) | -| 怪物脚步 | 怪物移动即播(每步,间隔 1.5-2.5s) | 动态威胁(左右 pan + 距离档音量;背后=中央音量减半,转身后恢复=方位反馈) | 相对(左/前/右/后) | -| 播报 | 上滑/事件 | 层数/血量/金币/面朝(绝对)/威胁(相对左右) | 面朝=绝对,其余=相对 | - -三者全部由同一 `game._floor.grid` 与怪物位置**单源查询**生成,无独立状态;模板句固定(方位句 4 句、威胁句 4 句)。回声永不报怪物、脚步永不报地形——语义域零重叠。威胁播报只报左右不报背后(同回声降维,规则在教学讲明)。 - -## 4. 状态机 - -### 4.1 状态列表 - -`TITLE / DRILL / EXPLORE / BATTLE / MENU(overlay) / DESCEND / SETTLE`。MENU 是挂起 overlay(记录返回态),其余为正交主态。 - -### 4.2 转移图 +`TITLE / DRILL / EXPLORE / BATTLE / MENU / DESCEND / SETTLE`。MENU 仅 TITLE/SETTLE(及阶段 2)。 ``` -start ──> TITLE ──单击(有档)──> EXPLORE(恢复存档+局面复述) - │ │单击(无档) │ - │ ├─────> DRILL(仅首次) ──完成/下滑跳过──> EXPLORE(L1) - │ └右滑=重掷新周目 ──────────────┘(已学过教学,直达) - v - EXPLORE <──────────────┐ - │ 走进怪格/怪走进格 │ 胜利(原地) - v │ - BATTLE ────────────────┘ - │ HP≤0 - v - SETTLE(死亡) <── EXPLORE(第7层终Boss胜=通关SETTLE) - │单击 - v - TITLE(元进度入账后) - - EXPLORE ──踏上楼梯格+单击──> DESCEND ──> EXPLORE(floor+1) - EXPLORE/BATTLE ──下滑──> MENU ──返回──> 原状态 +TITLE ──单击(无档)──> DRILL(仅首次)──完成──> EXPLORE(L1) + ──单击(有档)──> EXPLORE 恢复+复述 +EXPLORE ⇄ BATTLE(走进怪格) +EXPLORE ──楼梯格+单击──> DESCEND ──> 下层 +HP≤0 或 L7 通关 ──> SETTLE ──单击──> TITLE ``` -DESCEND 演出:stairs_down 轰隆 + `evt_descend` + 下一层生成 + `st_floor_N`,约 5s。 +DESCEND:stairs_down + evt_descend + st_floor_N,约 5s。 -### 4.3 每状态手势映射总表 +## 3. 声波脉冲 -| 手势 | TITLE | DRILL | EXPLORE | BATTLE | MENU | SETTLE | -|---|---|---|---|---|---|---| -| 单击 | 继续/开始 | 步骤作答 | 脉冲 | 挥击 | 循环切项 | 再来一局 | -| 双击 | 重听 | 步骤作答(D6) | 重发脉冲 | 格挡(前摇窗口) | 确认执行 | 重听结算 | -| 左滑 | error | 步骤作答(D5) | 左转 | 转身(绕位怪有效) | 上一项 | error | -| 右滑 | 重掷新周目 | 步骤作答(D5) | 右转 | 转身(绕位怪有效) | 下一项 | error | -| 上滑 | 元进度 | 状态(教学后可用) | 状态链 | 战斗状态链 | 帮助 | 重听元进度段 | -| 下滑 | 菜单 | 跳过教学 | 菜单 | 菜单(暂停) | 关闭 | 菜单 | -| 长按 | error | 步骤作答(D4) | 前进 | 尝试逃跑 | — | error | -| 长按松开 | — | 停 | 停 | — | — | — | +| 维度 | 编码 | +|---|---| +| 正前距离 | 4 档,延迟 0.25/0.50/0.80/1.10s,volume 0.9/0.7/0.5/0.35,echo_1..4 | +| 正前材质 | +120ms mat_stone/door/stair/water | +| 左右侧 | +0.35s 预 pan:side_wall_l/r 或 side_open_l/r(第一格地板=开口) | +| 身后 | 不报;转身即查 | -无效手势一律 error 震;full 档语音纠错 `gd_invalid`(onceKey 同局限 1 次,之后仅 error 震)。idle-only:纠错/看门狗语音在播时跳过(引擎 guide 已实现)。 +冷却 1.2s。冷却内单击=invalid(无第二次回声)。需要再听:冷却后**再单击**,或长按问路。站楼梯格踏入即自动贴身铁器回声(到达即报,不占单击——单击仍是下潜确认)。 -## 5. 教学编排(DRILL,首局内嵌于第 1 层) +三套信息单源 grid:回声不报怪,脚步不报地形。威胁播报只报左右。 -每步=一句短旁白→一次实操→反馈;做错重教同一步(**新短句**,不逐字重播)≤2 次后自动演示通过(不卡流程,space 先例)。教学寄生局内:DRILL 就在 L1 地图起点房内进行,D8 的教学怪是 L1 两只怪之一。全程可下滑跳过(`tut_skip`),跳过标志存进度,二周目不再进 DRILL。 +频带:脚步 100–300Hz,脉冲中频,BGM 垫底。并发前景 ≤2。 -| 步 | 旁白 | 等待动作 | 通过条件/反馈 | -|---|---|---|---| -| D0 | intro_1+intro_2+tut_d0 | 单击 | confirm 震 | -| D1 | — | 单击 | pulse+回声组 + `tut_echo_come`。**首输入≤60s 达成点(≈t+15s)** | -| D2 | tut_front_rule | 单击继续 | 纯听规则句 | -| D3 | tut_side_rule | 单击继续 | 纯听规则句 | -| D4 | tut_walk | 按住走 ≥2 格 | 脚步声(撞墙也算过,bump+`那边是墙`由既有 bump 路径播) | -| D5 | tut_turn | 左滑+右滑各 1 | 转身声 | -| D6 | tut_replay | 双击 | echo 重播 + `tut_echo_come` 复用 | -| D7 | tut_status + tut_menu | 上滑(状态链播)→ 下滑开菜单→再关 | 两键各一次 | -| D8 | tut_mob → 走向教学怪触发战斗(盲眼蝠 ATK=0)→ tut_parry →(前摇)双击 | 格挡成/败都继续:成功 `tut_parry_ok`,失败 `tut_parry_miss` | 然后 tut_attack → 单击挥击至击杀 `tut_kill`。**首正反馈≤3min 达成点** | -| D9 | tut_stairs | 脉冲听到铁器回声后自行走到楼梯 | 踏上楼梯格 `tut_stairs_on` → 单击下潜 → EXPLORE | +## 4. 地窟生成 -D9 后不再教学;上滑=状态链随时可查。生成约束(教学局 L1,优先于随机):起点房前方 2 格内保证有墙(D1 回声演示)、起点房门口固定一只盲眼蝠(D8)、房内 1 处金币(拾取体验)。 +`rng(floor) = mulberry32((seed ^ (floor * 0x9E3779B9)) >>> 0)`。房间矩形 + L 走廊 MST;楼梯=BFS 最远房;怪距起点 ≥3(教学局非门怪 ≥4);主路径每 ≤12 格必有内容。 -FTUE 核对:遮罩点按→D0 三句(~8s)→D1 首次脉冲 ≈ t+15s(<60s ✓);首个正反馈=教学战胜利 ≈ t+2min(≤3min ✓);学习期 L1-2 怪 ATK 0/1(死亡不可能 ✓)。 +| 层 | 尺寸 | 怪物 | 宝物 | 目标分 | +|---|---|---|---|---| +| 1 | 7×7 | 盲眼蝠×2 ATK0 | 金币×1 | 3.0 | +| 2 | 7×7 | 窟鼠×3 | 金+药 | 3.0 | +| 3 | 9×9 | 菇×3 + 石甲蟹 | 金+短刃 | 3.5 | +| 4 | 9×9 | 绕行者×2+洞蛛×2 | 金+皮甲 | 3.5 | +| 5 | 11×11 | 洞蛛×3+绕行者×2 | 金+药 | 3.5 | +| 6 | 11×11 | 洞蛛×2+绕行者×2+腐沼蟒 | 金+阔刃 | 3.5 | +| 7 | 11×11 | 石像卫×3+窟中之王 | 金 | 4.0 | -## 6. 地窟生成 +窟主守楼梯;未击败踏格播 evt_stairs_lock。 -### 6.1 seed 用法 +## 5. 战斗 + +明雷:脚步持续可闻。遭遇自动面对。 ``` -rng(floor) = mulberry32((seed ^ (floor * 0x9E3779B9)) >>> 0) // 每层独立子种子 +玩家回合 → 单击挥击 / 下滑逃跑 / 左右转 +怪行动(绕位先报脚步)→ 前摇 1.2s tel_* +前摇内单击=格挡(面对 ×0.2 / 未面对 ×1.0) +HP≤25% 且有药 → 自动喝 +HP≤0 → 死亡链 ``` -- `opts.seed` 可注入(playtest 传 42);正式局 `seed = Math.floor(Math.random()*1e9)`(开局掷一次,存入 run)。同 seed → L1..L7 全部可复现。 -- 生成步骤(每层):① 尺寸定表(§6.2)→ ② 放 3-6 个矩形房间(2×2~3×4,不重叠)→ ③ L 型走廊按 MST 顺序连通全房间 → ④ 起点房=第 1 房中心,楼梯房=BFS 距起点最远房间中心,楼梯格=其中心 → ⑤ 宝物撒入房间格 → ⑥ 怪物放在 BFS 距起点 ≥3 格的格位(开局安全区)。 -- 复现断言口径:同 seed 两次构造 → grid/怪物/宝物/楼梯逐格相等(测试读 `game._floor` 重算不变式,**不写魔法坐标**)。 +缠斗怪左右滑:btl_no_turn 每场解释一次后只 invalid。绕位怪每 2 回合换左/右/后。Boss 半血 ATK+1,拒绝逃跑。逃跑必成、挨一下、怪留原格。每 4 杀 atkLv+1。 -### 6.2 层规格与难度曲线 +**格挡改绑单击(敌前摇窗)**:新手无双击;窗外单击若在玩家回合才是挥击,前摇期单击只判格挡。 -| 层 | 尺寸 | 怪物(型×数) | 宝物 | 目标用时 | 累计 | -|---|---|---|---|---|---| -| 1 教学 | 7×7 | 盲眼蝠×2(ATK0) | 金币×1 | 3.0 | 3.0 | -| 2 | 7×7 | 窟鼠×3 | 金币×1+药水×1 | 3.0 | 6.0 | -| 3 窟主 | 9×9 | 蘑菇怪×3 + 窟主石甲蟹 | 金币×1+短刃 | 3.5 | 9.5 | -| 4 | 9×9 | 绕行者×2+洞蛛×2 | 金币×1+皮甲 | 3.5 | 13.0 | -| 5 | 11×11 | 洞蛛×3+绕行者×2 | 金币×1+药水×1 | 3.5 | 16.5 | -| 6 窟主 | 11×11 | 洞蛛×2+绕行者×2 + 窟主腐沼蟒 | 金币×1+阔刃 | 3.5 | 20.0 | -| 7 终局 | 11×11 | 石像卫×3 + 窟中之王 | 金币×1 | 4.0 | 24.0 | +## 6. 数值与遗产 -- 窟主守楼梯格:未击败时踏上楼梯格播 `evt_stairs_lock`(楼梯不可用);击败后解锁。终 Boss 击败=通关。 -- 敌型见 §7.2 表。每层开场氛围:`st_floor_N` + 层 BGM 不变(探索曲统一),深度感靠 `evt_descend` 与怪物构成。 +maxHp=20(+vigor 6);atk=2+atkLv+武器。L1–2 学期零惩罚。 -## 7. 遭遇与战斗 +| 敌 | HP | ATK | 行为 | 金 | +|---|---|---|---|---| +| 蝠 | 4 | 0 | 缠斗 | 3 | +| 鼠 | 6 | 1 | 缠斗 | 5 | +| 菇 | 8 | 2 | 缠斗 | 8 | +| 石甲蟹 | 20 | 3 | Boss 绕位 | 50 | +| 绕行者 | 10 | 2 | 绕位 | 10 | +| 洞蛛 | 12 | 3 | 缠斗 | 12 | +| 腐沼蟒 | 26 | 4 | Boss | 80 | +| 石像卫 | 16 | 4 | 绕位 | 15 | +| 窟中之王 | 36 | 5→6 | Boss | 通关 | -### 7.1 遭遇 +遗产(meta.gold,死亡不清):药 90 / 体魄 150 / 护符 220 / 回声强化 300 / 武器 450。金币档 0=空手句;0<金<50 播 gold_some「带回来一点,不多。」 -- 全**明雷**:怪物脚步声持续可闻(rpg 暗雷教训——纯音频里突袭没有信息缓冲)。你走进怪格或怪走进你格 → `btl_start_` + battle earcon + BGM 切换。 -- 遭遇瞬间自动"面对"怪物(播报名即=它在面前)。怪移动的脚步:每步播 `mob_step__`,pan 按相对方位(左/前/右),**背后用中央资产音量减半**;距离档音量 ≤2 格 0.9 / 3-4 格 0.6 / 5-8 格 0.35 / 更远不播。距离 ≤2 格加 heartbeat 震(威胁前置预警窗,方法论 L2)。 +## 7. 音频 -### 7.2 回合时序(实现契约) +BGM:menu / explore / battle / boss。SFX:pulse、echo_1..4、mat_*、side_*、step_own/turn、bump、coin/potion/gear、stairs_down、mob_step_{small,bug,heavy}_{l,c,r}、tel_*、atk_swing/hit、parry、hurt、enemy_die、levelup、amb_cave、turn_tick。侧向能量只在单声道。 -``` -进 BATTLE → 玩家回合(guide.turn) → 单击挥击/长按逃跑/转身 → 结算 - → 若怪活:怪行动(绕位怪先移动:脚步报新方位;缠斗怪原地)→ 攻击前摇 1.2s(tel_* 音) - → 前摇内双击=格挡判定(面对攻击源:×0.2 / 未面对:×1.0)→ 受击/格挡音+震 - → 玩家 HP≤25% 且腰带有药 → 自动喝药(potion 音+播报) - → 回到玩家回合。HP≤0 → 死亡归因链 → SETTLE -``` +Voice 127×2,数字全分档整句。 -- 缠斗怪(蝠/鼠/菇/蛛):不绕位,恒正面,左右滑无效(`btl_no_turn` 纠错,每场 1 次)。 -- 绕位怪(绕行者/石像卫):每 2 回合移动到 左/右/背后 之一(脚步 pan 报位),攻击前摇随其方位(tel 派生 pan 变体);`btl_behind` 在背后挨刀时播(归因真话)。 -- Boss(石甲蟹/腐沼蟒/窟中之王):绕位+高血,半血播 `btl_half_*` + ATK+1(王二阶段)。Boss 战长按逃跑拒绝(`btl_flee_boss`)。 -- 格挡判定与 rpg 一致绑双击(规避 tap 300ms 延迟);窗口外双击=error 震不生效。 +## 8. Floor 与迷茫 -### 7.3 逃跑 +| 状态 | earcon | +|---|---| +| TITLE/MENU | menu | +| DRILL/EXPLORE/DESCEND | explore | +| BATTLE | battle | +| SETTLE | settle | +| 血≤25% / Boss 半血 | danger | -长按 = 尝试逃跑:普通怪/绕位怪**必成**(无 RNG,P4 友好),撤退挨一下(掉血=怪 ATK,真实账目),落回探索态、怪留在原格;拉开距离后脚步声自然远去。Boss 拒绝。逃跑无次数限制(容错拉满,代价=掉血+丢进攻回合)。 +choice 不用。对局中下滑=后退,不播 menu。等待声:探索环境 loop 已承担;战斗 turn 后 400ms 合成等待声。纠错每个状态×手势一次,之后只回执;连续 3 次无效进迷茫。 -### 7.4 掉落与元进度 - -- 战斗胜利:金币入账 + `btl_loot_1..3`(按单场所得 3 档)+ 概率掉药水/装备(§6.2 宝物表为拾取点,怪掉落仅金币)。 -- 每击杀 4 只 atkLv+1,播 `btl_lvup`("这层我变强了"的即时成长,P2 钩子)。 -- **元进度(遗产)**:金币永续累计(meta.gold),死亡/通关结算入账,不清档。5 项解锁(下一局开场自动生效+播报): - -| 遗产 | 价格(金币) | 效果 | -|---|---|---| -| unlock_potion 行囊药 | 90 | 开局自带 1 瓶腰带药 | -| unlock_vigor 体魄 | 150 | 起始 maxHp 20→26 | -| unlock_veil 护符 | 220 | 每层首次受击减半 | -| unlock_echo 回声强化 | 300 | 侧向回声加报距离档(墙闷/开口各 2 档→按远近换 2 组资产音量) | -| unlock_blade 武器 | 450 | 开局自带短刃(atk+1) | - -- 经济核算:一局 40-120 金(怪 3-15/只 + 堆 10-30 + 窟主 50-80)→ 约 2-5 局解锁一项,死亡有可见成长(P2「下周目你会更强」)。 - -## 8. 数值表 - -### 8.1 玩家与敌人 - -``` -maxHp = 20 (+vigor 6);atk = 2 + atkLv(0..4, 每4杀+1) + 武器(0..2) -dmg = max(0, atk - 敌def) ;敌打你 = max(0, 敌atk - 甲) × 格挡系数(0.2/1.0) -格挡钳制:擦伤 min 0;L1-2 怪 ATK 0-1 → 学期零惩罚 -``` - -| 敌型 | 层 | HP | ATK | 行为 | 脚步音色类 | 掉金币 | -|---|---|---|---|---|---|---| -| 盲眼蝠 | 1 | 4 | 0 | 缠斗 | small | 3 | -| 窟鼠 | 2 | 6 | 1 | 缠斗 | small | 5 | -| 蘑菇怪 | 3 | 8 | 2 | 缠斗 | bug | 8 | -| 窟主·石甲蟹 | 3 | 20 | 3 | Boss 绕位 | heavy | 50 | -| 绕行者 | 4 | 10 | 2 | 绕位 | bug | 10 | -| 洞蛛 | 5 | 12 | 3 | 缠斗 | bug | 12 | -| 窟主·腐沼蟒 | 6 | 26 | 4 | Boss 绕位 | heavy | 80 | -| 石像卫 | 7 | 16 | 4 | 绕位 | heavy | 15 | -| 窟中之王 | 7 | 36 | 5→6 | Boss 绕位 | heavy | 通关 | - -装备(拾取即自动装备,仅播报不管理):短刃 atk+1(L3 起)、阔刃 atk+2(L6 起)、皮甲 敌伤-1(L4 起)。药水:腰带限 1 瓶,HP≤25% 自动喝(回 40% maxHp,向上取整)。 - -平衡校验:L1 atk2 打蝠 2 刀;L3 atk3+短刃=4 打蟹 5 刀、蟹打裸装 3/下 vs 20HP+药 → 不格挡危险、会格挡稳过;L7 atk2+3+2=7 打王 6 刀、王打 5-6/下 vs 26HP → 必须格挡+药,Boss 战有真实技巧收益。 - -## 9. 音频三层 - -### 9.1 BGM(4 首,Mureka,-20 LUFS,ogg+m4a,无缝循环) - -| id | 状态映射 | Mureka prompt(英文) | -|---|---|---| -| bgm/menu | TITLE / MENU / SETTLE(结算 volume 0.5) | "Dark cavern ambient pad, deep sub drone with distant water drips, calm and mysterious, no drums, no vocals, seamless loop" | -| bgm/explore | DRILL / EXPLORE / DESCEND | "Dark cave exploration ambient, low ominous drone, sparse echoing sonar-like pings, slow dripping rhythm, tense but calm, no vocals, seamless loop" | -| bgm/battle | BATTLE 普通怪 | "Tense dungeon battle music, pounding tribal drums 95 bpm, dissonant low stabs, claustrophobic urgency, no vocals, seamless loop" | -| bgm/boss | BATTLE 窟主/终 Boss | "Epic dark cavern boss theme, heavy drums 115 bpm, low brass swells, rising dread, no vocals, seamless loop" | - -切换全部走引擎 crossfade;语音 duck 20% 引擎自动。danger 不切 BGM(guide danger earcon + heartbeat 震 + st_hp_4 承担)。 - -### 9.2 SFX(~34 个;SA3=Stable Audio 3,SYN=程序合成;全部双格式、真峰值 ≤-3dBFS) - -| 类别 | id | 方式 | prompt 草稿 / 说明 | -|---|---|---|---| -| 脉冲 | `pulse` | SYN | 180→60Hz 正弦扫频+噪声瞬态,0.15s,中频突出 | -| 正前回声 | `echo_1..4` | SA3 | "close stone wall echo slap-back, bright, short, dry"(2:"mid distance cave echo, moderate tail" 3:"far cave echo, darker longer" 4:"very far cavern echo, long dark tail, huge space") | -| 材质尾音 | `mat_stone` `mat_door` `mat_stair` `mat_water` | SYN | 闷石敲/空心木咚/金属当啷/水滴湿滑,各 0.2-0.25s | -| 侧向回声 | `side_wall_l/r` `side_open_l/r` | SYN | 立体声文件:能量只在 L 或 R 声道;闷哒 vs 空旷回灵(SA3 不达标时程序写 L/R 通道兜底) | -| 移动 | `step_own` `step_turn` `bump` | SYN | 单步/拖步转身/撞墙闷响 | -| 拾取 | `coin` `potion` `gear` | SYN | 金币叮当/喝药咕咚/换装金属轻响 | -| 转层 | `stairs_down` | SA3 | "descending old metal stairs, deep cavern reverb" | -| 怪物脚步 | `mob_step_small_l/c/r` `mob_step_bug_l/c/r` `mob_step_heavy_l/c/r` | SA3 ×3 + ffmpeg pan 派生 ×6 | "small beast skittering steps, dry";"insect chitin crawling clicks";"heavy armored thudding steps"——各生成 1 条单声道,派生左/中/右三份 | -| 攻击前摇 | `tel_small` `tel_bug` `tel_heavy`(+派生 l/r) | SA3 | 小怪扑击嘶声/虫肢节摩擦/重怪蓄力重踏低吼 | -| 战斗 | `atk_swing` `atk_hit` `parry` `hurt` `enemy_die` | SYN/SA3 | 挥拳风声(SYN)/击中甲壳钝响/格挡亮叮(SYN)/受击闷击(SYN)/倒地(SA3) | -| 升档 | `levelup` | SYN | 上行钟声琶音(内容音,允许) | - - monsters 脚步 pan 派生工艺:`ffmpeg -i in.ogg -af "stereotools=mlev=0.7:sbal=-.8" out_l.ogg` 等三向派生,省 SA3 任务数(SA3 实际 ~14 个)。 - -### 9.3 voice - -125 句 ×2 性别(Serena / Uncle_Fu),无 NPC(怪物不说话,遭遇靠 `btl_start_*` 报名)。分组、台词、触发全表见 [16-dungeon-voice-ids.md](16-dungeon-voice-ids.md)。数字全分档整句(层 7 句、血量 4、金币 4、面朝 4、威胁 4、战利 3、死因 3、元进度 3),无运行时拼接。 - -## 10. 向导层接入(engine/guide.js,本作不新增 earcon 词表) - -### 10.1 状态签名(进入状态时播) - -| 状态 | guide.state() | 时点 | -|---|---|---| -| TITLE / MENU | `menu` | 进标题、开菜单 | -| DRILL / EXPLORE / DESCEND | `explore` | 进教学、进探索、下楼完成 | -| BATTLE | `battle` | 遭遇即播(与 btl_start_* 同点) | -| SETTLE | `settle` | 死亡/通关结算进入 | -| danger | `danger` | 血量首次 ≤25%(每层 1 次)+ Boss 半血 | - -choice 签名本作不使用(无抉择态;菜单是 menu)。菜单回绕不播 wrap 句(菜单 ≤4 项,循环成本低,省台词预算)。 - -### 10.2 turn cue 与看门狗 - -| 等待点 | arm | at | say(full 档;L2 级一律纯 nudge,绝不逐字重播) | -|---|---|---|---| -| BATTLE 玩家回合(每回合) | guide.turn | — | turn earcon + confirm 震 | -| BATTLE 玩家回合无操作 | watchdog | [15s, 30s] | `gd_turn` / null | -| EXPLORE 静止 | watchdog | [10s, 25s] | `gd_pulse` / null | -| DRILL 每步等待 | watchdog | [8s] | 无语音,纯 nudge(教学句不重播) | - -### 10.3 卡关三级提示(EXPLORE,escape 先例;"无操作时长"计时,任何有效手势重置) - -进展事件=拾取/战斗胜利/踏上楼梯格/击败窟主/下潜。无进展 60/120/180s →: - -| 级 | 内容 | 附带 | -|---|---|---| -| L1 暗示 | 楼梯未找到 → `stair_hint_a`;窟主未清 → `stair_hint_b` | warning 震 | -| L2 明示 | `stair_dir_e/s/w/n`(绝对方位;与上滑面朝句同坐标系,自洽) | warning 震 | -| L3 带路 | 进入导航模式:每步播 `nav_e/s/w/n`,抵达播 `nav_done`;此后每 60s 重复直至进展 | warning 震 | - -提示到点遇语音在播 → 入待播队列(escape round2 礼仪),玩家操作即作废未播级别。纠错句 idle-only + 过期丢弃(引擎已实现)。 - -## 11. 存档设计 +## 9. 存档 ```json { - "schema": 1, "game": "dungeon", "updatedAt": 0, + "schema": 1, "game": "dungeon", "summary": "第3层 · 伤势不轻 · 金币268 · 遗产2/5", "meta": { "gold": 268, "unlocks": ["potion", "vigor"], "bestFloor": 5, "runs": 7, "kills": 23 }, - "run": { - "seed": 42, "floor": 3, "pos": [5, 2], "face": "E", - "hp": 11, "atkLv": 2, "weapon": 1, "armor": 0, "potion": 1, - "veilUsedFloor": false, "killsRun": 9, "looted": [], "cleared": [], "parries": 4 - } + "run": { "seed": 42, "floor": 3, "pos": [5, 2], "face": "E", "hp": 11 } } ``` -- **summary 生成规则**:`第{floor}层 · {血量档名} · 金币{meta.gold} · 遗产{unlocks.length}/5`;死亡后 run=null,summary=`深处归来 · 金币{gold} · 遗产{n}/5`。 -- 保存时机:拾取/战斗结算/下潜/菜单"保存并回大厅"/死亡结算。localStorage + PUT 服务器(00 文档 §4)。 -- 读取:有 run → TITLE 播 `sys_continue_ask`,单击继续 → 恢复 `run` + 重放该层 seed 生成 + **局面复述链**(st_floor+st_hp+st_face+st_threat,≤5s 可操作);右滑 = 重掷新周目(run 清空重掷 seed,meta 保留)。无 run 单击 = 新周目(meta.unlocks 生效播报后进 DRILL/EXPLORE)。 -- 归因真话依据:`parries`(全程格挡数)、lastHit 记录(击杀者类型/是否背刺/是否未格挡)在死亡结算选 `die_cause_*` 与 `die_adv_*`,句必须与真实行为对应。 +SETTLE 进态 800ms 手势死窗(吞死亡瞬间在途单击,防结算链被截进 TITLE)。 -## 12. playtest 断言清单(登记 13 号文档新增 §dungeon;mock 机制对齐 web/rpg/test/playtest.mjs) +## 10. playtest 指针 -**关键路径**(seed=42,首周目 L1→L3 窟主,≤260 手势): -1. 启动 → TITLE(无存档)→ 单击 → DRILL:D0..D9 逐步断言 voice 序(tut_d0→…→tut_done)与 drillStep 递进;D1 断言 pulse 与 echo 播出;D4 长按断言 pos 前进 ≥2;D8 乱按/不格挡断言 HP 不降(教学怪 ATK=0);下滑跳过教学路径断言(二周目不再进 DRILL)。 -2. **seed 复现**:`opts.seed=42` 两次构造 → `_floor.grid`、楼梯/怪物/宝物坐标逐格相等;L1 尺寸 ===7;起点安全区(怪 BFS 距离 ≥3)。 -3. **脉冲回声信息正确**:起点面朝 'N',按 grid 计算正前墙距 → 断言 echo_N 于脉冲后对应延迟±50ms 播出、volume 档正确、材质尾音 id 正确;左右侧向 side_* id 与 grid 一致;遍历 seed=42 的 L1 全网格 ×3 面向抽查 ≥30 组(回声编码 === grid 真值);**双击重发**断言资源零消耗且回声 id 与上次一致(未移动时)。 -4. 拾取:走到金币格 → coin 音 + meta.gold 增加且 st_gold 档位相应跳档;药水满腰带 → evt_potion_full 且药留在原地。 -5. 战斗路径(L2 窟鼠):进入 BATTLE → 回合流转(玩家回合 turn→敌前摇→结算循环);前摇内双击 → 敌伤 ×0.2(HP 差值断言);窗口外双击 → error 震、全额伤;长按逃跑 → 必成且 HP 真实减 ATK、怪留原格;击杀 → loot 档句 + meta.gold 入账。 -6. 下潜:踏上楼梯格 → sys 提示句 → 单击 → DESCEND → floor=2 且 grid 由 floorSeed 重生成(再断言复现)。 -7. **死亡与元进度**:L3 窟主战挂机至死 → 归因链序 `die_intro → die_cause_*(与 lastHit 真实来源一致,Boss 重击 → die_cause_2)→ die_adv_*(按 parries=0 → die_adv_1)→ st_floor_3`;断言 meta.gold = 死亡前+本局所得、`bestFloor` 更新;单击 → TITLE → 新周目 seed≠42、meta.unlocks 按金币阈值播 unlock_* 且起始 maxHp 生效(vigor)。 -8. 菜单/帮助:探索下滑 → 模态菜单(单击循环播项名、双击确认、上滑=help、下滑=关闭);"保存并回大厅" → PUT 断言 data.summary 命中生成规则。 -9. **中断复述**:存档重进 → sys_continue_ask → 单击继续 → 落在存档点 → 自动局面复述链(st_floor→st_hp→st_face→st_threat 顺序断言);上滑同链可重放。 - -**边界断言**: -- 学习期零惩罚:L1 全怪 ATK===0、L2 ≤1(读表断言);L1 全程乱按 HP 不变。 -- 撞墙:长按顶墙 → bump + pos 不变 + HP 不变。 -- 方位自洽:任意格转身后重脉冲 → 左右 side_* 与 grid 随面朝交换;身后方向从不产生回声资产。 -- 怪物脚步:怪每步播 mob_step__,pan/音量档与相对方位、距离一致(含背后=中央减半口径)。 -- 绕位怪背刺:未转身挨打 → 全额伤 + `btl_behind`;转身后格挡 → ×0.2。 -- 缠斗怪左右滑 → `btl_no_turn`(每场 1 次)+ error 震。 -- 自动喝药:HP≤25% 且有药 → HP 回升档 + potion 音;无药时不播不炸。 -- 卡关三级:无操作快进 60/120/180s → stair_hint_* → stair_dir_* → nav_* 链;任意进展事件后重置分级;提示在语音在播时排队不顶句。 -- 看门狗:探索 10s/25s、战斗回合 15s/30s 逐级触发且第 2 级纯 nudge 无语音。 -- 脉冲冷却 1.2s:冷却期单击/双击 → error 震、无 echo。 -- 屏蔽任意 voice 资产:流程不卡死(看门狗兜底推进)。 -- guide=off:全静、零定时器(全局既定契约)。 -- 资产齐套:voice 双性别目录 id 集合一致、sfx/bgm 双格式(assetExists 口径)。 - -**失败即不通过**:死亡归因句与真实死因不符(归因假话);同 seed 两次生成不一致;教学怪造成 HP 损失;双击重发扣资源;回声编码与 grid 矛盾。 - -## 13. 节奏曲线与单局核算 - -- 每层节奏:进层签名句(5s) → 探索(脉冲-移动循环,2-3 分钟)→ 1-3 场战斗(每场 40-60s,窟主 90s)→ 拾取 1-3 处 → 楼梯。每 30-60s 一个奖励点(金币/击杀升档/装备),满足方法论 L5。 -- 满通 24.0 分钟(§6.2 累计列);普通玩家死于 L4-6 → 单局约 14-19 分钟;二周目起(遗产加持)更深。首局若速死(L2-3,~8 分钟)由元进度钩子拉重开——roguelike 死亡结算 ≤15s 且必带「下周目更强」,重开入口单击 ≤2s(A4/L5)。 -- 无任何倒计时(P4);高手速度感=长按连走+免听完整回声直走(P2),脉冲冷却不限制移动。 - -## 14. 自检清单 - -| # | 硬约束 | 自检 | -|---|---|---| -| 1 | FTUE 首输入 <60s | D1 首次脉冲 ≈t+15s ✓(§5) | -| 2 | 首个正反馈 ≤3min | D8 教学战胜利 ≈t+2min;L1 首金币 ≈t+2.5min ✓ | -| 3 | 学习期零惩罚 | L1 怪 ATK=0、L2 ≤1,学期内死亡不可能;撞墙无伤;格挡失败无额外惩罚 ✓ | -| 4 | 失败三问归因 + 真话律 | 死亡链 die_cause_*/die_adv_* 按 lastHit 与 parries 真实选择;挥空/背刺/逃跑挨打均有真话句 ✓(§12 失败即不通过项锁定) | -| 5 | 手势宪法三件套不豁免 | 上滑=状态、下滑=菜单、双击=重听(重发脉冲) 全状态保留 ✓;移动/脉冲偏离已登记 E1-E4 且教学讲清 ✓ | -| 6 | 向导层 | 签名 5 态(不新增 earcon);turn cue 每回合;看门狗 [10/25][15/30]s 第 2 级纯 nudge;卡关 60/120/180 三级不报答案(带路只给方向);纠错 idle-only ✓(§10) | -| 7 | 方法论反模式 | 提示永不逐字重播(L2 级纯音频);成功后只给新短句肯定;单次播报 ≤3 选项(菜单逐项);并发声流 ≤2(频带分离+冷却);语义靠语音、earcon 用引擎统一词表 ✓ | -| 8 | 方位降维 ≤4 | 回声前/左/右(身后不报);战斗面前/左/右/背后;无连续角度 ✓ | -| 9 | 台词规范 | 一句一文件整句;全部 ≤25 字(<40 上限);数字全分档整句;"重听"写"重新听"、"重发"写"再发一次";同游戏同 instruct 常量;无语速词 ✓(voice-ids 注记) | -| 10 | P1 视障评委 | 双击免费重发脉冲;status key 全状态可用;方位/威胁模板句固定;归因真话;可打断/长按快进/2.2 档语速引擎既有;绝不当白痴(不简化深度:绕位/Boss/元进度保留)✓ | -| 11 | P3 通勤 | 单手免视全手势可达;上滑一句话复述局面(层/血量/面朝/威胁);任意时刻菜单存档退出;死亡不清 meta ✓ | -| 12 | P4 工人 | 教学逐字跟练+失败 2 次自动过;容错高(ATK0 怪/自动喝药/必成逃跑);无倒计时;零新手势(复用 swipe/tap/longpress)✓ | -| 13 | P2 大学生 | 元进度结算「下周目你会更强」;击杀升档+装备即时成长;长按连走速度感;死亡重开入口 ≤2s ✓ | -| 14 | seed 可复现 | floorSeed 派生公式 + opts.seed 注入 + 复现断言(§6.1/§12.2),与 playtest 种子 PRNG/强制队列/虚拟时钟机制兼容 ✓ | -| 15 | 三套信息自洽 | 单源 grid 查询;回声=静态地形/脚步=动态怪物/播报=绝对面朝,模板句固定,语义域零重叠 ✓(§3.2) | -| 16 | 引擎能力边界 | 无实时 pan(预 pan 资产);无引擎外节点(setTimeout+volume 调度);tap 300ms 延迟规避(格挡绑双击);30MB 预算内 ✓ | - ---- - -## 实现核对(测试 agent,2026-09-02) - -> 测试载体:`web/dungeon/test/playtest.mjs`(mock 层+虚拟时钟照 `web/rpg/test/playtest.mjs`, -> 1277 断言全绿,`tools/node/bin/node web/dungeon/test/playtest.mjs` 退出码 0); -> 会话剧本 `web/harness/sessions/dungeon.mjs`(`session` 主线弧线 + `sessionStress` 乱按压力, -> 经 `web/harness/transcribe.mjs dungeon [--session sessionStress]` 跑通,全程零资产缺失、零死锁)。 - -### §12 关键路径 - -| # | 断言 | 结果 | -|---|---|---| -| 1 | 启动→TITLE→DRILL:D0..D9 逐步 voice 序(intro_1/2+tut_d0→…→tut_done)与 drillStep 递进;D1 pulse+echo;D4 长按 pos ≥2;D8 乱按 HP 不降;下滑跳过+二周目不再进 DRILL | ✅(场景 2;D2 错手势重教、D3 连错 2 次自动通过、D8 乱按 80 手势 HP=20 一并覆盖) | -| 2 | seed=42 两次构造 grid/怪/宝/楼梯逐格相等;L1 尺寸 7;起点安全区 BFS≥3(教学局非门怪 ≥4) | ✅(场景 1:7 层全查 + 实流 `_floor` 与纯函数一致) | -| 3 | 回声编码 === grid 真值:全地板格 ×4 面向扫描(≥30 组),echo_N 延迟/音量、材质尾音 +120ms、侧向 +350ms 预 pan 资产 L/R;双击重发零消耗且编码一致 | ✅(场景 2:104 组合全扫,代表组精确到 0ms;身后不报=恰两路侧向) | -| 4 | 拾取:coin 音+meta.gold+金币档跳档(45+堆≥55→st_gold_2);药水满腰带 evt_potion_full 且药留原地 | ✅(场景 4) | -| 5 | 战斗:单击命中、缠斗 btl_no_turn 每场 1 次、前摇内双击 ×0.2(钩子 atk=10 → 伤 2)、窗口外/不格挡全额、长按逃跑必成挨一下怪留原格、击杀 loot 档+meta.gold | ✅(场景 5) | -| 6 | 下潜:踏上楼梯 sys 提示→单击→DESCEND→floor=2 且 grid 重生成复现 | ✅(场景 2) | -| 7 | 死亡与元进度:归因链序+真话、meta.gold 入账、bestFloor/runs、遗产购置播报、新周目 seed 重掷+继承(vigor 26/potion 1) | ✅(场景 6a/6b/6c 三种死因矩阵) | -| 8 | 菜单:单击循环/双击确认/上滑帮助/下滑关闭;「保存并回大厅」→ sys_saved → `'../'`;summary 生成规则 | ✅(场景 7 + 场景 2 D7) | -| 9 | 中断复述:sys_continue_ask→单击→st_floor→st_hp→st_face→st_threat 有序;上滑同链可重放 | ✅(场景 8) | - -### §12 边界断言 - -| 断言 | 结果 | -|---|---| -| 学习期零惩罚:L1 全怪 ATK=0、L2 ≤1(读 `_battle.atk`);L1 乱按 HP 不变 | ✅ | -| 撞墙:bump + pos 不变 + HP 不变 | ✅ | -| 方位自洽:转身后重脉冲左右 side 随面朝交换(含全扫描 4 面向) | ✅ | -| 怪物脚步:pan/音量档(0.9/0.6/0.35)、背后中央减半(1 格 0.45、6 格 0.175)、>8 格不播、≤2 格 heartbeat | ✅ | -| 绕位怪:每 2 回合换位(右→后→左确定性轮换)、脚步报位、背刺 btl_behind+全额、转身追击命中、转身后格挡 ×0.2 | ✅ | -| 缠斗怪左右滑 → btl_no_turn(每场 1 次)+ error 震 | ✅ | -| 自动喝药:HP≤25% 有药 → 回 40% maxHp(ceil)+ potion 音+档播报;Boss 半血 ATK+1 + btl_half_*;Boss 逃跑拒绝 btl_flee_boss | ✅ | -| 卡关三级 60/120/180s:stair_hint_a→stair_dir_*→nav_* + 导航每步播方向 + nav_done;进展重置分级(重置后从 L1 重新触发);语音在播入队不顶句、操作作废、未播级别可重新触发 | ✅(场景 9) | -| 看门狗:探索 10s gd_pulse、战斗 15s gd_turn(guide=full);DRILL 8s 纯 nudge 无语音 | ✅(场景 10) | -| 脉冲冷却 1.2s:冷却期单击仅 error 震、无第二次回声 | ✅ | -| 屏蔽任意 voice:遭遇报名/下潜旁白被屏蔽 → guard 兜底推进不卡死 | ✅(场景 11) | -| guide=off:30s 空闲零播放、零定时器 | ✅ | -| 资产齐套:voice 双性别 125 句集合一致、sfx/bgm 双格式、game.js 静态引用展开全存在、运行期零缺失 | ✅ | -| E3:对局期 setRateBoostEnabled(false)(DRILL/EXPLORE/BATTLE/DESCEND)、TITLE/MENU/SETTLE 恢复 true | ✅(rateLog 断言) | -| E4:菜单=暂停——探索游走冻结、战斗前摇剩余窗口冻结、恢复恰结算一次(无重复回合) | ✅(场景 7;前摇内下滑原本不可达,见 bug ⑧) | - -**§12 失败即不通过项**:死亡归因真话(cause/adv 与 `_deathInfo.lastHit` 一致,三种死因矩阵验证)、同 seed 两次生成一致、教学怪零伤害、双击重发零消耗、回声编码与 grid 一致——全部锁定 ✅。 - -### 与设计文档的偏差(记录不大改) - -1. **归因句粒度**:台词表死因仅 3 档(小怪拖垮/Boss 重击/背刺),无「指认具体怪 A」整句资产;真话律落地为 `_deathInfo.lastHit.type` 与 cause 档位一致(playtest 断言 `lastHit.type==='crab'/'rat'/behind` ↔ die_cause_2/1/3)。资产表内不存在更细句子,不算偏差。 -2. **战斗上滑第 2 段**:§2.2「敌方血量档」以 `btl_half_*`(半血阶段句)近似——台词表无 foe_hp_* 分档资产。 -3. **§5 D4「撞墙也算过」**:实现额外把 D4 自动面向东侧 2 格走道+边界,保证 ≥2 格可达成(生成约束注释已写明)。 -4. **L1 安全区**:非门怪 BFS≥4(严于规格 ≥3),避免 D4/D8 演练路径撞上第二只怪,注释已写明。 -5. **die_adv 兜底**:挡过且跑过(parries>0 && flees>0)死于非背刺 → die_adv_1(建议句非过去事实句,规格未覆盖该象限,取最无风险句)。 -6. **格挡成功播 `btl_parried`**:规格表 B 组 trigger=「格挡成功(减伤 80%)」,原实现该资产死引用,已接线(见 bug ②)。 - -### 修过的 bug(web/dungeon/game.js) - -1. **归因真话律**:`run.parries++` 原在双击瞬间计(背对双击也计),歪曲 die_adv 选句依据——改为仅结算判成「面对且格挡成功」时计数。 -2. **`voice/btl_parried` 死资产**:格挡成功(减伤 80%)从不播报——接线进 `_enemyResolve` 反馈链。 -3. **卡关提示「未播级别计入 shown」**:入待播队列即标已提示,被玩家操作作废后该级永不重触发(违反 §10.3/escape round2 礼仪)——改为真正播出才计入,另加待播去重。 -4. **终局层 L3 带路死路**:`_navRouteVoices`/`_navSpeakStep` 只查 `stairs`,L7 无楼梯致导航哑火——回退 `stairs || throne`(与 L2 明示同口径)。 -5. **SETTLE 双击=重听结算链**:原只重播 `settle_again` 尾句——存 `_settleChain` 整链重放(§2.3)。 -6. **DRILL D1 无看门狗**:`_drillEnter(1)` 无分支——补 `_drillWatchdog(1)`(§10.2 教学每步 [8s])。 -7. **DRILL D8 上滑=做错**:§4.3 状态键「教学后可用」(D7 已教)——D8 补 up→状态链。 -8. **E4 豁免半实现**:战斗前摇(telegraph)内下滑走 error 分支、菜单打不开,`_menuPause` 冻结机制不可达——前摇内补 `down→_openMenu()`,战斗任意时刻可暂停。 - -### 遗留 TODO - -- 教学期 L1 游走静默:DRILL 内非教学怪不游走(`_startMobs` 仅 EXPLORE 调用),设计未明确,维持现状(D9 后下潜即恢复)。 -- 木门/水材质(mat_door/mat_water):生成器不产出,资产与编码路径已备(`_echoScan` 注释),待未来层型。 -- `meta.runs` 不统计 TITLE 右滑放弃的周目(仅死亡/通关计入);规格未明确。 -- 会话剧本主线弧在随机图极小概率把窟主打死(玩家成长超预期时)——剧本有「没死就继续走」兜底分支,不影响零死锁承诺。 - -### R2 修复回归(2026-09-02,D0-1..D0-3 + D1-1..D1-6 结构修复轮) - -> 证据源:research/new8/dungeon/round1/(synthesis + live-player + p1-blind)。本轮只修结构不加新内容; -> 引擎侧按产品裁定允许改动(web/engine/audio.js、gesture.js)。回归:8 套 playtest + guide.test 全绿 -> (dungeon 1277→**1610** 断言;escape 140 / radio 221 / rhythm 141 / rpg 188 / simon 132 / space 155 / -> storyline 82 / guide 122,0 失败,radio 在并行修复中仍全绿)。 - -#### D0-1 语音抢占死循环(P0) - -- **根因(引擎级竞态,非 DRILL 泄漏本身)**:`WebAudioEngine._playVoice` 的「顶掉当前对白」检查在 - `await this._load(id)` **之前**,而 `this._voice = token` 在其后无条件赋值——同一 tick 双调度语音 - (教学 D1:反馈句 `tut_echo_come` 裸 `_speak` + `_drillPass` 立即 `_speakGuarded` 下一句指令)在加载 - 竞速后,输家的 token 被静默顶号:**ScriptProcessorNode 永远无人 disconnect(僵尸节点空转抽 buffer)**, - 其 endTimer 亦因 `_finishVoice` 的 token 校验提前 return 而永不清理。R1 live 证据完全吻合:t=130.2s - D1 首脉冲后 tut_echo_come 僵尸节点持续抽读,探针按「voice buffer 被抽读」记段 → 71 分钟 35,745 次 - 「播放」99.7% 被「掐死」、nav_n↔tut_echo_come 互掐 33,297 次;跳教学二周目 0 抢占(无 D1 双调度)。 -- **修**:① `audio.js _playVoice` 在 await 之后、赋值之前补杀当前 token(顶掉语义在加载完成后复核, - 后到者胜、败者必被杀);② 游戏 D1/D6 反馈句并入 `_speakGuarded` 推进链(消灭同 tick 双 play 的源头), - `_drillDone`/`_drillSkip` 收尾显式 `guide.disarm()`;③ **全线风暴保险丝**(`audio.js play()` voice 入口): - 同一 voiceId 3s 窗口内被调度 ≥5 次 → 丢弃后续并 console.error(每 id 一次)。 -- **断言**:`保险丝:同 id 3s 窗口内前 4 次调度放行 / 第 5 次起丢弃 / 其他 id 不受累 / 窗口滑出恢复 / - 只作用于 voice 层`(真实 WebAudioEngine 单元断言,场景 12A);`D1: 反馈句与下一步指令句串行`(场景 2); - `纯探索 60s 零操作语音调度有上界(≤3 句;R1 风暴同期 >2000 句)`(场景 12C)。 - -#### D0-2 回声信息真实性(P0) - -- **根因**:① 侧向 `_sideScan` 把「整条侧射线到边界全通」才判开口——房间/走廊地型恒为假 → side_open - 两局 40+ 脉冲 0 次、能走的方向报墙;② 站在楼梯格上脉冲走射线扫描(楼梯在脚下不在面前)→ mat_stair - 0 兑现,教学「铁器=楼梯」成谎言(P1 专项:165 脉冲 0 铁器,含站在楼梯上 t+142.2)。 -- **修**:回声与 grid 单源重接——侧向第一格地板=开口(§3.1「墙=闷/开口=空旷」2 档口径,墙保留射线距离); - `_echoScan` 站楼梯格 → 贴身(dist1)铁器回声。 -- **断言**:场景 2 D9 全扫描(全地板格 × 4 面向真脉冲)truthScan 同步更新;场景 12B **全网格一致性**: - seed=42 L1..L7 每格 × 4 面向 `_echoScan`/`_sideScan` 与测试侧独立真值逐格相等(≥200 格)+ - `全网格存在侧向开口(R1 全程 0 次 → 现扫到 N 格次)` + 各层站楼梯=贴身铁器。转写复核:主线局 - mat_stair/side_open 均稳定出现。 - -#### D0-3 探索内容真空(P0) - -- **① 密度下限**:`_fillMainPathDensity`——start→楼梯(终局=王座)最短路(BFS 距离梯度回溯,确定性) - 每 ≤12 格必有内容遭遇,超限段中点补本层已有普通怪型(不加新怪种,≤8 只,安全区口径不变)。 - 断言:场景 1 各层 `主路径内容密度(最长真空 ≤12 格)` + 构成 ⊇ 规格表。 -- **② 探索呼吸层**:新 SYN 资产 `sfx/amb_cave`(gen_sfx.py:45-320Hz 带通空气声 + 4 整周期/10s 呼吸 - LFO + 两粒远处水滴;10s 无缝循环源:去直流、环回交叉淡化、**不首尾淡化**,解码回读 ogg -4.20dBFS / - m4a -4.00dBFS、|直流|≤0.001、循环接缝 -25dB(rel peak);20 文件 ×2 格式全过 QC)。接线:EXPLORE 静默 - ≥20s(无手势无人声)播 amb_cave(volume 0.35,20s 节奏不连播)。guide=off 契约不变(环境垫属内容层, - 场景 4 的零播放断言已按此口径更新并加 amb 专项断言)。 -- **③ 下潜演出**:游戏代码本身完好(playtest 场景 2 一直断言 stairs_down→evt_descend→st_floor_N 通过); - R1 转写零演出为**剧本伪影**:(a) navigate 按 1 格/按建模,但 harness `waitVirtual(120)` 在虚拟时钟会 - 越过 +500ms 步进计时器(advance 至少推进到下一到期定时器)致每按多走 1+ 格、永远踩不准楼梯格; - (b) D9 用 `pulse:false` 违反「玩家靠耳朵」红线且无到站重试。修:navigate 立即松开(恰 1 格)+ `until: - 'stairs'` 到站口径 + 逐层验证的下潜循环(tapDescend 只在真踩楼梯时出手,DRILL D9 同样受理)。 - 复核:主线转写两次运行弧线完全一致(两次下潜全套演出:stairs_down→evt_descend→st_floor_N)。 -- **④ 卡关计时器**:计时与分级 R1 live 已证完好(64s/120s/180s 准时);转写 350s 未触发系剧本持续手势 - 重置 lastAct(§10.3 口径「任何有效手势重置」)+ 上句的导航伪影。断言:场景 12C `卡关 L1 暗示 60s 自然 - 触发(纯探索、无 hook)`。 - -#### D1 级修复 - -- **D1-1 菜单关闭无回执**:live 复核实为「关了但零反馈,下次下滑又重开」——路由本身无恙。修:`_closeMenu` - 补状态签名 earcon(回 EXPLORE/DRILL=explore、BATTLE=battle、SETTLE=settle、TITLE=menu)。断言:场景 10 - `菜单关闭播回程状态签名(menu → explore)`。 -- **D1-2 D9 看门狗兜底**:`_drillWatchdog9()`(8s nudge、30s 升级播 stair_hint_a——自寻路步必须有救援); - D9 每次手势后重挂(onGesture 的 disarm 会清掉它,而 D9 的脉冲/转身不推进步骤)。断言:场景 10 两级触发 - + 升级句。 -- **D1-3 导航指墙 + 蘑菇软封锁**:① 带路改为**最短路第一步方向**(`_navFirstStepDir`:从目标反向 BFS 取 - 梯度下降邻格),逐步校正;同向 ≥1.5s 才提醒(改向必播)——既不指墙也不撞全线保险丝(导航句同 id 连发 - 会计熔断)。断言:场景 9 `带路方向与 grid 最短路一致 + 第一步必可通行 + 节流单测(同向连呼 3 次只播 1)`。 - ② 蘑菇/所有游走怪**相邻即扑击接战**(`_mobStep` 移动后与玩家正交相邻 → `_encounter`),杜绝 R1 L3 - 「贴身绕圈、堵格不开战」35 分钟软封锁。断言:场景 12C 相邻怪必然开战。 -- **D1-4 战斗结束 BGM**:教学战胜利/逃跑回 DRILL 的两条路径原先不切回 bgm/explore(只有菜单关闭路径恢复 - ——R1 转写 t=35.8 起战斗鼓点泡全程的组成因素之一)。两条路径补 `bgm/explore` 淡入。断言:场景 5 胜利/ - 逃跑后 `lastBgmEntry === bgm/explore`。 -- **D1-5 金币档**:SETTLE(`_die`/`_settleWin`)原用 run 金币自设阈值(<20/<60),TITLE 用 - `_goldBand(meta.gold)`(<50/≤200/≤600)——同一句 id 两套口径(live:SETTLE meta_gold_3 vs TITLE - meta_gold_2)。统一为同一分档函数同一账本(遗产购置后的 meta.gold)。断言:场景 6a `SETTLE 金币档 = - _goldBand(meta.gold)`。 -- **D1-6 三件**:① 长按 boost 闪跳——E3 豁免期快进本就不生效,但 gesture.js 仍外发 - `soundgame:longpress(+end)` 窗口事件(仪器记为 boost on→off 闪跳):`setRateBoostEnabled` 现同步经 - `setLongpressBoostEventsEnabled` 抑制窗口事件(游戏手势回调不受影响)。② D7「菜单被系统自动收」经 live - 事件流复核为风暴伪影(菜单开场链 sys_menu→首项名被僵尸节点反复重放,被听成自动执行 sys_menu_resume) - ——D0-1 修复即愈,无额外代码;playtest 场景 2 已有「菜单保持打开直到玩家下滑关闭」断言。③ 战斗回合 - 15s 无操作播合成回执 `sfx/turn_tick`(880Hz 木鱼族,gen_sfx.py,-3.87dBFS)——standard 档无 gd_turn - 语音也有「还到你」的确认。断言:场景 10 turn_tick + 引擎豁免事件抑制(rateLog 既有断言不受影响)。 - -#### 伪影修正(web/harness/sessions/dungeon.mjs) - -- 「元进度不兑现」关案依据 + 三处剧本脱轨根因:① `waitVirtual(120)` 虚拟时钟越表多走(见 D0-3③); - ② tapDescend 不受理 DRILL(D9 最后一击点不响);③ 单发 navigate+tapDescend 无重试,任何被怪打断即脱轨 - ——逐层验证下潜循环修复。另修 navigate 中「走位途中被扑→长按按到战斗里=瞬间逃跑」(R1 live btl_flee×9 - 的剧本同款)。复核:主线转写 2 次弧线逐句一致(含归因链与金币档);压力转写 2 次均零死锁走到死亡-重开。 - -#### R2 新增/变更断言口径登记 - -- 场景 1:怪物构成由「逐字相等」放宽为「⊇ 规格表 + 补怪限本层怪种 ≤8」(密度下限所需)。 -- 场景 4:`guide=off 30s 零播放`改为「零语音/零向导播放,唯 amb_cave 按呼吸层口径除外」(内容层 vs - 向导层切分)+ 呼吸层节奏/重置断言。 -- 场景 9:L3 带路从「整段先东西后南北 + 每步必播」改为「最短路首步 + 改向必播/同向 1.5s 节流」 - (§10.3 L3 口径的听感与保险丝友好修订)。 -- 台词/资产表:无新增 voice;新增 sfx `amb_cave`、`turn_tick`(SYN,双格式,QC 数字见上)。 - -### R3 修复回归(2026-09-02,E0-1/E0-2/E1-1/E1-2 收口轮) - -> 证据源:research/new8/dungeon/round2/(synthesis 裁定 + live-player + p1/p2/p3/p4)。 -> 本轮**引擎零改动**(E0-1 看门狗改造全部在游戏层);radio 并行改动中,回归时全绿。 -> 回归:8 套 playtest + guide.test 全绿,dungeon 1610 → **1642** 断言(escape 140 / radio 246 / -> rhythm 141 / rpg 188 / simon 132 / space 155 / storyline 82 / guide 122,0 失败)。 - -#### E0-1 看门狗饿死(P0,四票共识) - -- **根因**:`_stuck.lastAct` 按 §10.3「任何有效手势重置」口径单轨计时——「一直在动手的迷路者」 - (乱按/无效手势)永远攒不满 60s,暗示→明示→带路三级链零触发。live 实证:压力局 253 对 earcon、 - 最长 2223s 无语音窗、0 句提示;P1 结论「『每次手势后重挂』的口径恰好被乱按永久饿死」。 -- **修(双轨制)**: - - ① **无操作轨**(§10.3 原样保留,仅探索态生效):任何有效手势重置,60/120/180s。 - - ② **无进展轨**(新增):只由**状态推进**重置(楼层/格位置/战斗回合/拾取,`_progressTrack()`, - 挂点在 `_walkStep` 移动、`_playerTurn` 回合推进、`_progress()` 事件与各状态进入点),30/60/90s - 三级升级:**earcon**(复用 `sfx/turn_tick`,不占台词通道、不排队)→ **短句**(`stair_hint_a/b`)→ - **导航接管**(`_navRouteVoices()` 最短路首步方向 + 楼梯指向,进入导航模式每步校正、60s 周期重报)。 - - 两轨短句内容同级(无操作轨 L1 = 无进展轨 L2)交叉登记防重复播;明示级 `stair_dir_*` 只由无操作轨 - 兑现登记。站立不动(两轨同时走时)经无进展轨 30/60/90s 即升级——比 §10.3 的 180s 更早拿到带路, - 两轨并行取先到;§10.3 的 60/120/180 口径在无操作轨原样保留。 -- **断言(场景 13A/13B)**:`E0-1: 乱按 96s 全程零位移(无操作轨持续被手势重置)`、 - `E0-1: 无进展 30s 级 → earcon(复用合成回执音,乱按饿不死)`、`E0-1: 无进展 60s 级 → 短句 stair_hint_a`、 - `E0-1: 无进展 90s 级 → 导航接管(播最短路首步方向)`、`E0-1: 接管后进入导航模式`、 - `E0-1: 接管首步方向必可通行(楼梯指向,不指墙)`、`E0-1: 乱按 96s 语音总量有上界(≤4)`、 - `E0-1: 有真实进展(格位置持续推进)零卡关语音零 earcon(不误伤正常玩家)`。 - -#### E0-2 跳教学路径密度回归(P0,P3 抓获) - -- **根因**:压力局 t=52.5 起困在教学 D9(tap=脉冲,零位移零步骤推进)39 分钟——D9 看门狗 30s 升级句 - 同样被「每次手势重挂」饿死;skip 后再 79 分钟零遭遇零指向。R1 乱按局 75s 即滚出教学的逃生口在 R2 - 形同虚设(2403s)。 -- **修**:① 教学态共享无进展轨(`_stuckArm` 5s 轮询扩到 DRILL;`_drillEnter` 步骤推进/格位置/战斗回合 - 均重置):30s earcon → 60s 短句(仅 D9 给 `stair_hint_a`——教学句不重播 §10.2,非寻路步骤无方向话 - 可给、短句级跳过登记)→ **90s 教学接管 = `_drillSkip()` 自动滚出教学**(播 `tut_skip`、drillDone 入账、 - 落回探索态由导航链接手),恢复 R1「乱按 ~75s 出教学」口径并落在 P3 要求的 90-120s 内;做对了 - (步骤推进/走动)永不误触发。② 密度保底对跳教学局本就全路径生效(`_fillMainPathDensity` 在 - `_tryGenFloor` 内无条件执行),本轮补上「跳教学种子局实测 `_floor`」的直证断言;skip 后进探索即享 - E0-1 导航接管。 -- **断言(场景 13C/13D)**:`E0-2: 跳教学种子局主路径密度不变式(最长真空 ≤12 格)`、 - `E0-2: 跳教学后 Ns 内必有指向语音(≤120s;R2 压力局 skip 后 79 分钟零指向)`、 - `E0-2: 教学乱按零进展 ≤95s 内自动滚出教学(R2 压力局 2403s)`、`E0-2: 教学乱按 30s 级 earcon 先到`、 - `E0-2: D9 乱按 60s 级给方向话 stair_hint_a(看门狗长嘴)`、`E0-2: 接管播 tut_skip`。 - -#### E1-1 到达即报(P1,live) - -- **根因**:站楼梯格 tap 被下潜模态截走(补测局 t=1288.7 实演),「铁器=楼梯」贴身回声只能靠相邻格 - 脉冲兑现,教学 D9 承诺在输入层不可达(本局两次到站都靠 tut_stairs_on 而非铁器音找到楼梯)。 -- **修(产品裁定「到达即报」)**:`_onStairCell` 踏格瞬间自动播贴身铁器回声(`echo_1` 即刻 + - `mat_stair` +120ms,编码同脉冲贴身档)+ 既有提示句(每层首达 `tut_stairs_on` / 窟主未清 - `evt_stairs_lock`)。**不占输入通道**:单击仍是下潜确认,脉冲/其他手势语义零变化。 -- **断言**:场景 2 `R3 E1-1 到达即报:踩上楼梯格瞬间自动播贴身铁器回声(echo_1+mat_stair,全程零玩家脉冲)` - + `R3 E1-1: 到达即报不占输入通道——tap 仍是下潜确认(照常下潜)`;场景 13E - `E1-1: 踏上(被锁的)楼梯格自动播贴身铁器回声` + `E1-1: 到达置位 _onStairs` + - `E1-1: 窟主未清 → 到达即报铁器 + evt_stairs_lock`。 - -#### E1-2 band1 档位账目(P1,P2/P4 共识) - -- **根因**:`_goldBand` 把 0<金<50 与 =0 同播「口袋空空」/「这趟没带回什么」——教学拾取(蝠 3 金、 - 堆 10-30 金)后即谎报(P2:49 秒自打脸,其后 6600s 里 27 次「口袋空空」)。 -- **修**:新录 band1 真话句 `gold_some`「带回来一点,不多。」(8 字,多音字自查无风险);**=0 保留 - `st_gold_1`/`meta_gold_1` 空手措辞(band0 口径不变)**。新增 `_stGoldVoice()`/`_metaGoldVoice()` - 统一出口:上滑状态链 / SETTLE / TITLE 三处共用同一条(管线单条、账本仍同一 `_goldBand`)。 -- **管线**:16-dungeon-voice-ids.md 与 gen_voice_dual.py 两表一致 → `source env.d/tts.sh` + - `flock .gpu.lock .venv-tts/bin/python tools/gen_voice_dual.py --game dungeon --gender both - --only gold_some,tut_flee_hold,st_best_2` 单批重录(gold_some m 1.84s / f 2.08s)→ `--convert` - (dungeon 254 文件 ogg+m4a)→ `tools/extract_voice_text.py` 刷新 voice_text.json(8 游戏 706 句)→ - `tools/check_all_assets.py` 全过(dungeon 307+307 双格式、双性别各 127、零解码失败)。 -- **断言**:场景 4 `上滑链前 4 段有序(…gold_some band1 真话…)`(教学蝠 3 金实证)、场景 13E - `E1-2: 上滑金币档 0<金<50 播 gold_some(不再谎报「口袋空空」)`、 - `E1-2: SETTLE band1(0<金<50)同步播 gold_some`、`E1-2: 金=0 保留 meta_gold_1 空手措辞`; - 场景 6a 死亡链序第 5 段与金币档断言改 gold_some。 - -#### 打磨(P2/P3 顺手带) - -- **逃跑长按 ≥900ms**:`_fleeArm()`/`_battleFleeCancel()`——战斗态 longpress(引擎 450ms 判定)先武装, - 按满 `FLEE_HOLD_MS=900` 才真正 `_flee()`;未满松手 = 取消 + error 震 + 每场 1 句区分提示 - `tut_flee_hold`「想逃跑,按住别松手。」(新录)。探索态 450ms 走步不变;<450ms tap 优先判定不变 - (引擎层)。断言:场景 5 `战斗长按 ~650ms(<900ms)松手 = 不逃(R3 打磨)`、 - `短长按取消伴随 error 震`、`取消播区分提示 tut_flee_hold(每场 1 次)`;原「长按逃跑必成挨一下」 - 「Boss 拒绝」断言原样通过(按住不放的路径不变,harness 短步进 longpress 配对不受影响)。 -- **st_best_2 措辞**:「你到过中层。」→「你最远到过中层。」(live P3:bestFloor 是前世纪录,防误读为 - 「本局到过」;同 id 重录)。st_floor 序列经复核无缺陷(settle 链报深度正是 P3 表扬项),不动。 -- **台词账目**:125 → **127 句**(+`gold_some` +`tut_flee_hold`;`st_best_2` 重录不加句); - playtest 资产校验同步改 127。 - -#### R3 老断言口径变更登记 - -- 场景 4 上滑链第 3 段:`st_gold_1` → `gold_some`(教学蝠 3 金落 band1 真话档)。 -- 场景 6a 死亡链序与金币档断言:`meta_gold_1` → `gold_some`(settle 金 40 = band1 且 >0)。 -- 资产校验:voice 台词总数 125 → 127。 -- 其余全部原样通过:卡关三级 60/120/180(场景 9 全链)、导航逐步校正/节流/nav_done、进展重置分级、 - 队列礼仪、探索 60s 语音上界、呼吸层 20s 节奏、guide=off 30s 静默(earcon 属 sfx 内容层不违约)、 - E3/E4、屏蔽兜底、D0-D9 教学全链。 - -#### 遗留(本轮未动,沿用 R3 synthesis 悬置项) - -- 8s nudge「earcon+首句短词」半可验改造(synthesis §2 P2/P3,未列入本轮修复清单)与 amb_cave 听感 - 人测(roadmap §4)。 -- 教学跳过标志跨会话不一致、探针通道隔离:harness/仪器侧。 -- P3 建议「上滑加第 6 句最近铁器方位」「撞墙归因句」:未列入本轮清单,留终审裁定。 - -### H1 热修(dev,2026-09-02):死亡结算链被「在途手势」截杀——结算入口手势死窗 - -- **现象(R3 live A 局 2787.057,唯一阻塞交付的 P1)**:Boss 重击致死后结算链只播了 - `die_intro` 前 0.18s(vstart 2786.88 → vend kill 2787.057),`die_cause_2`/`die_adv_*`/ - `st_floor_3`/金币档/`settle_stronger`/`settle_again` 后 6 句零 fetch 零 vstart,结算屏 - 静默 76s,直到玩家下一次单击——而那次单击开出的是 `sys_new_run`(新周目),不是结算重听。 -- **根因(events.jsonl 逐事件推演,非 live 报告猜想的语音链调度竞态)**: - ① 引擎 gesture.js 非即时模式下单击要等双击窗(`doubleTapWindow=300ms`)关闭才回调 - `onGesture('tap')`(gesture.js `pendingTapTimer`);② 致命前摇 resolve(t=2786.78 - `_enemyResolve` → `_die`)落地时,玩家上一记 tap 还压在双击窗里;③ 2787.057 tap 回调时 - `onGesture` 按「当时」状态路由——state 已是 SETTLE → `_onSettleGesture('tap')` → - **`_enterTitle()`**:`_session++` 作废结算会话 + `audio.stopVoice()` 无回调掐死 - `die_intro`(引擎约定「打断即作废整链」,onComplete 不触发 → 后 6 句永不接续), - 且 run 已清 → TITLE 空链全静默。证据闭环:vend 时刻恰有 gesture.js 的 confirm 震 - ([20],2787.057),三次 tap 的「记录→回调」间隔均为 ~360ms(touchend+300ms), - 「单击开出新周目」正是 TITLE 态 tap 语义。虚拟时钟 playtest 未抓到:旧 mock 直通 - `onGesture`,从不在死亡后补派在途手势。 -- **修法(web/dungeon/game.js,最小面)**:新增 `SETTLE_GRACE_MS=800` 手势死窗—— - `_die()`/`_settleWin()` 进结算时盖 `this._settleGraceUntil` 戳;`onGesture` 在 SETTLE 态、 - 非菜单、戳内的一律吞掉(ghost 手势不算玩家输入,不碰 `_stuck.pending`)。窗口上界取 - 手势最迟回调时刻 touchstart+450ms(长按判定)+300ms(双击窗)≈750ms,800ms 全盖; - 死窗后结算手势照常(tap 回标题 / 双击重听链 / 下滑菜单),设计出口不受影响。 - 战斗收尾链不需 abort:死亡时战斗语音链早已自然播完(swish onComplete 才会进前摇), - 死亡链独占通道的前提本就成立——本次截杀者是 stopVoice 而非排队顶号,与 R2 - 「`_finishVoice` token 校验/加载竞态」语义无涉,引擎零改动。 -- **断言(playtest 场景 6d,7 条)**:mock 构造 live 同款序列(挥空 → btl_swish 收尾链 → - 敌前摇 → 致命 resolve),死亡后同拍与 +300ms 各补派一枚在途 tap:①两枚 tap 均被吞 - (仍在 SETTLE);②结算链 7 句完整有序播出(结算屏有内容);③`die_intro` 恰播一次 - (未被 `_enterTitle`/stopVoice 截杀);④死窗后 tap 正常 → TITLE(出口不受死窗影响)。 - **旧代码验证**:stash 修复后同场景 5/7 断言即挂(tap 跳 TITLE、链断),与 live 签名一致。 -- **回归**:dungeon 1642→**1649 全绿**;escape 140 / radio / rhythm 141 / rpg 188 / - simon 132 / space 155 / storyline 八套件全 PASS。 -- **遗留**:真机 live 定向复验待下一轮(「挥空瞬间被打死 + 死亡瞬间按着屏幕」场景); - `_settleWin`(终局通关结算)同栅已顺手盖住,但通关路径无 live 负样本。 +seed 复现、回声===grid、学习期 ATK、死亡归因真话、到达即报铁器、gold_some 档位:保留。删除:长按位移、双击免冷却重发、下滑开战斗菜单、双击格挡。新增:3s 内脉冲、长按播帮助且 pos 不变、前摇单击减伤 ×0.2、下滑动 pos 后退。 diff --git a/docs/design/17-abyss-voice-ids.md b/docs/design/17-abyss-voice-ids.md index f2670e0f..da79fd27 100644 --- a/docs/design/17-abyss-voice-ids.md +++ b/docs/design/17-abyss-voice-ids.md @@ -1,5 +1,13 @@ # 17b · 《深海回音》(abyss) 语音台词表(voice ids,录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - **双性别组**(潜航员 DIVER + 调度/系统 DECK,126 句):落 `web/abyss/audio/voice/female|male/.{ogg,m4a}`,两性别 id 集合完全一致(含全部 `_k` 变体)。 > - **DIVER(潜航员=玩家)**:female = `Serena`,instruct 常量 `低沉冷静的年轻女声,深潜救援员,干练沉着,水下通讯的轻微气声`;male = `Uncle_Fu`,instruct 常量 `低沉冷静的男声,深潜救援员,干练沉着,水下通讯的轻微气声`。 diff --git a/docs/design/17-abyss.md b/docs/design/17-abyss.md index 4f46e338..5d69ca2d 100644 --- a/docs/design/17-abyss.md +++ b/docs/design/17-abyss.md @@ -1,519 +1,184 @@ -# 17 · 《深海回音》(abyss) — 深海救援叙事 RPG 设计文档 +# 17 · 《深海回音》(abyss) — 深海救援叙事 RPG > **给开发的实现要点摘要** -> 1. 叙事模式(声命线范式,radio 同骨架):节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;复制 `web/radio/` 结构起步,状态机 6 宏状态(INTRO/DIVE/COMMS/SURFACE/ENDING + MENU 覆盖层)。 -> 2. 手势零豁免:全部走全局默认映射(单击=继续/**检查(搜索等待=发声呐脉冲,宪法 §5.1「检查」义,escape 先例)**/跳句、双击=重听、左右滑=抉择、上滑=状态、下滑=模态菜单、长按=引擎快进)。 -> 3. 台词依据 [17-abyss-voice-ids.md](17-abyss-voice-ids.md)(148 句:双性别 127 + 平铺 NPC 21);NPC 水声电台滤波在**资产侧** ffmpeg 带通(radio 先例),引擎零改动。 -> 4. 氧气=步进资源非实时倒计时:只在潜次边界与阈值跨越时播报(分档整句、账目真实);软失败=「被迫取消第四潜/单载」而非玩家死亡,结算链完整播出(含 800ms 手势死窗)。 -> 5. 声呐双通道:回波类信息音与语音**绝不重叠**(游戏层 sonarGate 空闲门,R1 规则 §6.2);并发声流 ≤2。 -> 6. 章程任务 N2(research/new8/00-program.md §2):单局 15-25 分钟、9 抉择点(实走 7-9)、4 结局、分支局部化。 -> 7. 断言清单住本文档 §9(radio §20 先例);13 号文档已登记指针(§21)。 +> 1. 交互权威:26 号手册。叙事范式对齐 [26-apply-storyline.md](26-apply-storyline.md)。搜索等待的单击=发声呐脉冲,等同宪法「检查」,**不是**四向滑动玩法,帮助用上滑。 +> 2. 骨架同 radio:节点链 + seqToken + pendingChoice。宏状态 INTRO / DIVE / COMMS / SURFACE / ENDING + MENU。 +> 3. 氧气=步进资源,无实时倒计时、无中途死亡。软失败=少一潜/少带一人。声呐与语音互斥(sonarGate)。 +> 4. 台词 [17-abyss-voice-ids.md](17-abyss-voice-ids.md)(148 句:双性别 127 + NPC 21)。废除双击重听回波:搜索等待再单击=再发脉冲(自然再听)。 +> 5. 章程 N2:15–25 分钟、9 抉择(实走 7–9)、4 结局。 + +--- + +## 附录 C · 设计单 + +### 1. 一句话核心动作 + +**听一段救援,单击继续或发声呐;听到选项时左右滑做决定。** + +### 2. 玩家能感知的状态(3 个) + +| 状态 | 听感 | Floor | +|---|---|---| +| 听讲 | 调度/船员语音,BGM 整链压低 | SPEAKING | +| 轮到你 | turn → 等待声;搜索等待用深海 amb + 余音 | YOUR_TURN | +| 危险 | danger 签名 + 火花/滑坡(仅 D2/D4) | SPEAKING/BUSY | + +氧气档靠跨档句与 breath_loop(low/crit),不单开第四态。 + +### 3. 新手手势与解锁 + +| 手势 | 含义 | 解锁 | +|---|---|---| +| 单击 | 继续 / 搜索等待发脉冲 / 跳过当前句 | 阶段 0 | +| 左/右滑 | 抉择 | 阶段 0 | +| 上滑 | 我在哪(潜次 → 已救人数 → 氧档 → 一个动作) | 阶段 1(第一次脉冲成功后) | +| 下滑 | 菜单 | 阶段 2(第一次结局) | +| 双击 | 按单击。阶段 3=重听当前等待点完整句 | 阶段 3 | +| 长按 | 新手 invalid;之后旁白加速 | — | + +### 4. 状态 × 手势矩阵(新手期) + +| 状态 \ 手势 | 单击 | 左/右滑 | 上滑 | 下滑(未解锁) | 双击 | 连点 | +|---|---|---|---|---|---|---| +| 游戏在说 | 跳句 + ok(p*/锚句/档位句不可跳 → busy) | 抉择点缓存,不盲选;否则 invalid | 打断,我在哪 | invalid | 按单击 | 400ms busy | +| 等你继续 | 继续 | invalid | 我在哪 | invalid | 按单击 | busy | +| 搜索等待 | **脉冲** sonar_ping(silent-ok) | invalid +「点一下,发脉冲。」 | 「第 N 潜。点一下发脉冲。」 | invalid | 按单击=再脉冲 | 冷却/连点 busy | +| 等你选择 | 再听选项 | 抉择 | 「左右滑。」 | invalid | 按单击 | 迷茫 | +| 结算 | 新救援 | invalid | 结局+氧档一句 | 菜单(阶段2) | 按单击 | 第一下 | +| 菜单 | 确认当前项 | 左上一项 / 右下一项 | 重听说明 | 关闭 | 按单击 | busy | + +搜索中再点=再听回波,废除「双击=重听上一条回波」。玩家脉冲**先于**向导散句(先掐 gd 再出 ping)。 + +### 5. 等待点 + +| id | 期待 | full | short | 静默 | L2 | L3 | +|---|---|---|---|---|---|---| +| intro | tap | 「点一下屏幕。」 | 「点一下。」 | 6s/15s | tap | 替点 | +| ping | tap | 「单击,发一枚脉冲。」 | 「点一下。」 | 8s/18s | tap | 替发脉冲 | +| continue | tap | 「单击,继续。」 | 「单击。」 | 13s/30s | tap | 替继续 | +| choice | L/R | pN | 「左右滑,选。」 | 25s/50s | 左右滑 | 演示 | +| settle | tap | 「单击,重新开始。」 | 「单击。」 | 20s/45s | tap | 提示音 | + +COMMS 链末 full 档静默 13s/30s(不对齐旧 25/50)。乱滑不推进节点:迷茫 L1 短句 → L2 只留期待手势 → L3 演示脉冲或选择。 + +### 6. 首 3 分钟 + +| 时间 | 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 甲板风浪 →「点一下屏幕。」 | 单击 | 「点一下。」 | 滑动 invalid | +| 3~8s | ok → 极短目标句 → 下潜声 →「点一下,发脉冲。」 | 单击脉冲 | 「点一下。」 | 滑动 invalid | +| 8~10s | sonar_ping → 静默一拍 → echo_metal →「金属回波,钢板。」 | —(第一次成功) | | | +| 10~20s | turn 教学 → 接通小舟 | 单击继续 | 三档 | inv_nosel | +| 20~60s | p1 听暗号/直接上 | 左右滑 | 复述 p1 | 不盲选 | +| ≈60s | 解锁上滑 | 上滑 | 「向上滑。」 | | +| 60~180s | p2 → 上浮扣 3 格 → 档位句 | 单击/滑 | 接管 | 归因真话 | + +### 7. 失败表 + +| 类型 | 判定 | 归因 | 回到 | 新手期 | +|---|---|---|---|---| +| 搜索滑动 | DIVE 等待滑 | 「点一下,发脉冲。」 | 原地 | 无 | +| 非抉择滑动 | | 「现在不用选。」 | 原地 | 无 | +| 氧气门取消第四潜 | SURFACE(3) oxy≤2 | 取消链指向实际花气的左选择 | 签收 p9 | 已救者不失去 | +| 干涸 | mid-dive floor 0 | dry_b 指向最后那个左选择 | 上浮结算 | 人已上吊索仍救出 | +| 迷茫 | 信号≥2 | L1–L3 | 原地 | 无 | + +### 8. 台词类别 + +- `critical`:sys_first_tap;dv1_teach「点一下,发脉冲」 +- `content`:b_*、dv*、船员对白、p1–p9、e*、ep_*、dv4_dual、oxy_*(水面 crit 用 oxy_crit_deck「一潜都下不去了。」) +- `hint`:gd_ping1/2、prog_dN、prog_savedN、oxy 档、sys_continue_ask +- `feedback`:inv_*、dry_b、gao_final 留别句 +- 停用:任何「双击重听回波」句 + +148 句(含 `_k` 11 对);音频 127×2+21=275 条 ×2 格式。 + +### 9. 断点复述 + +保存:每潜进入、抉择后、SURFACE/ENDING、菜单保存。回来 sys_continue_ask;单击重播当前节点(恢复首句锁)。上滑:第 N 潜。已救 K 人。氧档。点一下或左右滑。 + +### 10. 测试记录 + +`web/abyss/test/playtest.mjs`。改:双击不重听回波;菜单左右+单击确认;首次脉冲 ≤10s;回波与 voice 零重叠仍失败即不通过。真人待测。 + +--- ## 1. 概述 -- **一句话**:你是深潜救援员,声呐里是被困海沟的潜艇「海燕号」——氧气在倒数,每次下潜都是抉择:先救谁、花多少气、留不留、上不上去。 -- **题材(声音即世界)**:黑暗深海里视觉归零,**声呐回波=空间、电台人声=生命、氧气=代价**——「用耳朵救人」与纯音频载体天然同构。声呐脉冲/回波/管路敲击/船体呻吟是叙事本体,不是装饰。 -- **单局**:一次完整救援(一条抉择路径)20-22 分钟;四次下潜共享主干,分歧局部化在每潜内部;9 个抉择点(单局实走 7-9 个),4 结局。 -- **核心循环**:下潜(声呐搜索拍:单击发脉冲→回波→调度解读)→ 通讯(船员对话+抉择)→ 带人上浮 → 水面结算(氧气扣账+档位播报)→ 下一潜。跨潜积累氧气存量与船员生死/好感,最后一潜的氧气余量决定救援形态(双载/单载/取消),收队前 p9 决定你自己的心结。 -- **与 radio 的差异点**(骨架同、压力源不同):radio 零压力(口碑只涨不跌);abyss 引入**氧气稀缺**——但全部宽限化:无实时倒计时、无中途死亡、软失败只损失「机会」不损失「已有」、最优省气路径恰好全员生还(P4 友好默认)。 +深潜救援员,声呐里是「海燕号」。氧气在倒数,但全是事件点扣格:无实时倒计时、无死亡。最优省气路径全员生还。与 radio 同骨架,压力源=氧气稀缺(宽限化)。 ## 2. 状态机 -### 2.1 状态列表 - -| 状态 | 内容 | BGM | guide.state | -|---|---|---|---| -| `INTRO` | 开场链:甲板口播 + 一句规矩 + 今晚目标 | `bgm/deck` | explore | -| `DIVE` | 下潜过程:下潜链(auto)→ 搜索等待(单击=发脉冲,声呐主导)→ 回波+接通 | `bgm/dive` | explore;搜索等待由 turn cue 承担 | -| `COMMS` | 通讯叙事:船员对白链 + 抉择点 + 结果 | `bgm/comms` | explore;抉择点=choice | -| `SURFACE` | 上浮结算:出水确认 + 氧气扣账/档位 + 补给(D2 后),自动链 | `bgm/deck` | settle | -| `ENDING` | 结局链 + 尾声回响(epilogue),结算态 | `bgm/ending` | settle | -| `MENU` | 模态覆盖层(任意叙事态下滑呼出,暂停一切链) | `bgm/deck`(低响度淡入) | menu | - -### 2.2 转移图(文本) +| 状态 | 内容 | BGM | +|---|---|---| +| INTRO | 极短甲板口播 | deck | +| DIVE | 下潜 auto → 搜索单击脉冲 → 回波+接通 | dive | +| COMMS | 对白+抉择 | comms | +| SURFACE | 扣账+档位+补给(D2),auto | deck | +| ENDING | 结局+回响 | ending | +| MENU | 覆盖 | deck 低响 | ``` - ┌──────────── 结算单击=新救援 ─────────────┐ - ▼ │ - INTRO ──链完单击──> DIVE(1) ──脉冲找到──> COMMS(1) ──链完──> SURFACE(1) ─┤→ DIVE(2) - │下潜链 auto;搜索等待=单击脉冲 │ …(D2/D3 同型)… - └─────────────────────────────────────────┘ - SURFACE(3) ──氧气门──┬─ oxy≥4: DIVE(4 双载) ──链完──> SURFACE(4) ──链完──> 签收 ◆p9 - ├─ oxy=3: DIVE(4 单载: p7_chen|p7_zheng) ──链完──> SURFACE(4) ─┘ - └─ oxy≤2: 终局取消链(dv4_none→gao_final) ─────────────────────┘ - ▼ - d9 结果链 ──> ENDING(e1..e4 + ep 回响链) - - 任意叙事态(INTRO/DIVE/COMMS/SURFACE/ENDING):下滑 → MENU(模态) - MENU:单击=循环切换选项 → 双击=确认执行 → 上滑=帮助 → 下滑/「回到水下」项=关闭回原状态(重播当前节点) +INTRO ──单击──> DIVE(1) ──脉冲找到──> COMMS ──> SURFACE ──> DIVE(2)… +SURFACE(3) 氧气门:oxy≥4 双载 / =3 单载 / ≤2 取消链 +──> ◆p9 ──> ENDING ``` -- INTRO/DIVE/COMMS/SURFACE/ENDING 均为节点链驱动(radio playNode 模式);下潜链与上浮结算链 `auto:true`,COMMS 叙事节点链末等待单击(radio 实现核对 #1 同口径)。 -- SURFACE 自动链 ≤15s:扣账句(d*_surf)→ 补给句(仅 D2 后)→ 档位句(跨档才播)→ 直接进下一 DIVE;第四潜位置由**氧气门**分三形态(§4.3)。 -- 终局取消链与结局链入口处盖 `_settleGraceUntil = now+800ms` 手势死窗(dungeon H1 热修同款),窗内手势一律吞掉,结算链完整播出。 -- MENU 打开时 `guide.disarm()` + 取消一切看门狗/自动链;关闭后重播当前节点(局面复述,radio closeMenu 先例)。 - -### 2.3 每状态手势映射(6+2 全覆盖,零豁免) - -| 手势 | INTRO | DIVE(下潜/搜索) | COMMS | SURFACE | ENDING | MENU | -|---|---|---|---|---|---|---| -| 上滑 | 帮助+状态,随后重播当前段 | 帮助+状态(潜次+氧档),留在搜索等待 | 帮助+状态,随后重播当前节点 | 帮助+状态,自动链继续 | 帮助+状态(结局名+氧档) | 帮助(sys_help) | -| 下滑 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 关闭菜单 | -| 左滑 | 无效 → error 震+gd_invalid | 无效 → gd_invalid(喂无进展轨) | 抉择=左 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 | -| 右滑 | 无效 → gd_invalid | 无效 → gd_invalid(喂无进展轨) | 抉择=右;ASK 态=新救援 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 | -| 单击 | 播放中=跳过本句;链末=开始下潜 | 播放中=跳过本句;**搜索等待=发一枚声呐脉冲** | 播放中=跳过本句(p* 不可跳);链末=继续 | 跳过本句(自动链) | **再来一局**(新救援) | 循环切换选项 | -| 双击 | 重听当前段 | **重听上一条回波+其解读句**(尚无回波=无效,首潜前零静默) | 重听当前节点台词(含抉择提示) | 重听结算链 | 重听结局链 | **确认执行**(菜单约定覆盖重听) | -| 长按 | 引擎接管:旁白临时 1.7×(12 号 §2.2,叙事类不豁免) | 同左 | 同左 | 同左 | 同左 | 同左(快进菜单语音) | -| 长按松开 | 引擎接管:恢复 settings.speed | 同左 | 同左 | 同左 | 同左 | 同左 | - -- 豁免登记:**无**。六个基础手势全部符合全局宪法默认语义——单击在搜索等待=「检查」(宪法 §5.1 原文即含检查,escape 检查房间同款);菜单走 §5.2 R3 模态约定(单击循环+双击确认)。 -- ASK(存档询问态,radio 同款):单击=继续上次 / 右滑=新救援;上滑/下滑/左滑/双击=无效(error 震+gd_invalid)。 -- 抉择 prompt(voice/p1..p9)播放中单击不可跳(PROMPT_RE 门)、左右滑缓存 `pendingChoice` 播完即生效(radio W2 契约照抄)。 -- 继承 radio G5 跳过公平契约原文:**首播必完整(p* 首播不可跳 / 链末关键句保护 / ASK 恢复首句锁)、复播可即掐(锁不随复播重新武装、链中段句可跳、复播滑动照常预缓存)**;p* 复播(双击重听/看门狗复述)仍不可跳、句尾生效。 -- 无效手势零静默:所有「无效」格均有 error 震 + gd_invalid(onceKey 同局限次,F1.4 idle-only:语音在播时跳过语音只发震)。 -- 键盘通道:三通道并行,游戏代码零改动;操作提示句按 `inputMode()` 选 `_k` 变体资产(§7.3、voice-ids 集中分组)。 +取消链/干涸链/结局链/签收链入口 800ms 手势死窗。档位句纳入尾句保护(不可单击掐)。非 auto 叙事尾句**首播**保护,复播可掐。 ## 3. 抉择树 -### 3.1 全局骨架(四潜共享主干,分歧局部化) +D1 找船·小舟:教学脉冲 → p1 听暗号(−1, knowsPipe) / 直接上 → p2 等日志(−1, hasLog) / 不等 → 扣 3。 -``` -INTRO: b_open → b_rule → b_goal → b_go ──> 下潜① -【D1 找船·教学潜】救小舟 · 2 抉择(p1/p2)──> SURFACE(1) ──> 下潜② -【D2 机舱·老白】危机拍#1 · 2 抉择(p3/p4)──> SURFACE(2·补给+3) ──> 下潜③ -【D3 病舱·生死抉择】回波预警 · 2 抉择(p5/p6)──> SURFACE(3) ──氧气门──> 下潜④ 或 终局取消链 -【D4 指挥舱·高潮】滑坡危机 · p7(双形态)+ p8 ──> SURFACE(4) ──> 签收 ◆p9(心结)──> ENDING -``` +D2 机舱·老白:p3 慢撬(knowsPipe?0:−1) / 快切(危机拍火花)→ p4 关阀门(−2, valve) / 走 → 应急瓶 +3(全局一次)。 -分支纪律:**每潜内部分支、上浮时收敛**到结算句 → SURFACE;跨潜只传标量(oxy、saved{}、stayer、5 个 flag),不生成组合节点。最坏路径与最好路径节点数同型(剧本 A 与剧本 C 深度相同)。 +D3 病舱:p5 先陈 / 先郑(stayer)→ p6 留瓶(−2, spareGift) / 约定。 -### 3.2 分潜结构(◆=抉择点,〔〕=条件差分) +D4:双载 p7 慢浮(−2)/快浮;单载 p7 带船长/带留守;p8 说出来(gaoPeace)/不问。 -``` -【D1 找船·小舟】(教学潜) -下潜链 dv1_down → dv1_teach →【搜索等待:单击脉冲】→ echo_metal_l + dv1_metal(教「金属回波=钢板」) - → dv1_link → COMMS: z1_a → z1_b → z1_knock(垫 pipe_knock)→ ◆p1 - ├左 听暗号: d1_listen → pipe_knock → d1_code(knowsPipe=true,-1 气)─┐ - └右 直接上: d1_up → z1_bye ─────────────────────────────────────────┤ - ▼ - 小舟拉住你 → ◆p2 - ├左 等日志: d1_log(hasLog=true,-1 气)─┐ - └右 不等: d1_nolog ────────────────────┴→ 上浮 → d1_surf(扣 3 格)→ 档位 → SURFACE(1) +p9 取钟(需 oxy≥1)/ 进医疗舱;oxy<1 封锁左路(g9_dry,不摆假选项)。 -【D2 机舱·老白】 -dv2_down →【搜索:脉冲 → echo_debris+pipe_knock → dv2_found】→ bai_a → bai_b → ◆p3 - ├左 泄压慢撬: d2_pry →〔knowsPipe ? d2_pry_ok(-0): d2_pry_slow(-1)〕─┐ - └右 切割快切: d2_cut →【危机拍: danger 签名+spark_flash 前置 2s】→ d2_flash ─┤ - ▼ - bai_out → ◆p4 - ├左 陪关阀门: d2_valve → valve_close → d2_valve_e(valve=true,-2)→ bai_thanks ─┐ - └右 直接上浮: d2_leave ──────────────────────────────────────────────────────────┴→ d2_surf - → d2_supply + oxy_gain(应急瓶+3,全局唯一一次)→ 档位 → SURFACE(2) - -【D3 病舱·陈医官/小郑(小郑无台词,吊床固定)】 -dv3_down →【搜索:脉冲 → echo_groan → dv3_groan(船体呻吟预警)】→ chen_a → chen_b → ◆p5 - ├左 先带陈医官: d3_chen → chen_up → d3_chen_e(小郑留守)─┐ - └右 先带小郑: d3_zheng → d3_zheng_e → chen_stay(stayer=chen)┤ - ▼ - ◆p6 - ├左 留备用瓶: d3_spare → d3_spare_e(spareGift=true,-2,floor 至 0 触发干涸链)─┐ - └右 约定来接: d3_promise ────────────────────────────────────────────────────────┴→ d3_surf → 档位 → SURFACE(3) - -【SURFACE(3) 氧气门】oxy≥4 → D4 双载 | oxy=3 → D4 单载 | oxy≤2 → 终局取消链(dv4_none → gao_final,800ms 死窗)→ ◆p9 - -【D4 指挥舱·高船长】 -dv4_down →【搜索:脉冲 → echo_groan 渐强 → 危机拍: danger+landslide 前置 2s → dv4_slide】→ gao_a → gao_b - 双载形态 → ◆p7(快慢浮) - ├左 慢浮: d4_slow → d4_slow_e(-2)─┐ - └右 快浮: d4_fast → d4_fast_e ──────┤ - 单载形态 → ◆p7_chen|p7_zheng(按 stayer 选提示,谁上) - ├左 带船长: d4_pick_gao →〔stayer=chen ? chen_last : d4_stay_lost〕─┐ - └右 带留守者: d4_pick_chen|d4_pick_zheng → gao_last(p8 跳过)─────┴→ 直达 d4_surf - ▼(船长在乘时) - 〔hasLog ? gao_confess : gao_c〕→ ◆p8 - ├左 说出来: d4_talk → gao_peace(gaoPeace=true)─┐ - └右 不问: d4_silent ────────────────────────────┴→ d4_surf → 档位 → SURFACE(4) - -【签收】g9_in → g9_knot → ◆p9 - ├左 取船钟(需 oxy≥1): d9_bell → bell_toll → d9_bello(bell=true)─┐ - └右 进医疗舱: d9_rest ────────────────────────────────────────────────┤ - 〔oxy<1 时左路封锁: g9_dry 播出后自动走右,不摆假选项〕 ▼ -ENDING: e*_a → e*_b(按 savedCount × p9 四选一)→ ep_lead → ep_*(按好感/存活/flag 选播) -``` - -### 3.3 分支局部化说明 - -- 跨潜状态全部是标量:`oxy`(int)、`saved`(5 个 0/1)、`stayer`(null/chen/zheng)、flag 5 个(knowsPipe/hasLog/valve/spareGift/gaoPeace)+ p9 派生 bell。没有任何「潜 i 分支 × 潜 j 分支」组合节点。 -- 条件差分全部是**节点内选播**(radio w1_say/w1_say2 先例):d2_pry_ok/slow(knowsPipe)、gao_c/gao_confess(hasLog)、p7 双/单形态(氧气门)、p7_chen/p7_zheng 与留别句(stayer)、ep_* 选播。树形不变。 -- 树规模:148 句台词、约 70 个节点;任一路径实走 7-9 个抉择点(D4 取消路径少 p7/p8;单载右路少 p8)。 - -### 3.4 四结局达成条件 - -| 结局 | 条件 | 主题 | 开环钩子 | -|---|---|---|---| -| e1 海燕归钟 | saved=5 且 p9=左 | 全员生还+船钟出水,告别完整 | 「明天,想再潜一次」 | -| e2 满载的黎明 | saved=5 且 p9=右 | 人都救回来了,钟留在沟底 | 「半夜总听见钟在响」 | -| e3 最后一潜 | saved<5 且 p9=左 | 没能都救回,但下去敲响了钟 | 「钟声替他们响了一声」 | -| e4 按规程上浮 | saved<5 且 p9=右 | 规程之内,难过之外 | 「安静得像三年前那天」 | - -- 尾声回响(P2 回响感):epilogue 逐人点名——小舟/老白/陈医官/小郑/船长各按「好感/生死」双线播(voice-ids EP 组),沟底一句按 bell 二选一;玩家每一潜的救与错过都被点名(radio ep_* 同款)。 - -## 4. 数值设计 - -### 4.1 变量 - -| 变量 | 类型 | 初值 | 说明 | -|---|---|---|---| -| `oxy`(氧气格) | int | 12 | 步进资源:只在事件点扣(下潜/抉择),无实时倒计时 | -| `saved` | map | zhou:0 其余 0 | 小舟/老白/陈医官/小郑/高船长 各 0/1 | -| `stayer` | str | null | p5 后留守病舱者('chen'/'zheng') | -| `knowsPipe/hasLog/valve/spareGift/gaoPeace/bell` | bool | false | p1 左/p2 左/p4 左/p6 左/p8 左/p9 左 置位 | -| `supply` | bool | false | D2 上浮后应急瓶已发(全局唯一) | -| `endings{}` / `runs` | map/int | — | 结局收集与重玩计数(存档展示用) | - -### 4.2 抉择计分表(左=走心·多花气 / 右=克制·省气) - -| 抉择 | 左(走心) | 右(克制) | 备注 | -|---|---|---|---| -| p1 听暗号/直接上 | -1 气,knowsPipe=true | 0 | 左路获 D2 情报(下潜 EFF 的唯一免气左路) | -| p2 等日志/不等 | -1 气,hasLog=true | 0 | hasLog 兑现 D4 船长坦白差分 | -| p3 慢撬/快切 | knowsPipe?0:-1 气 | 0 | 右路挂危机拍#1(火花虚惊,两路都活着) | -| p4 关阀门/直接走 | -2 气,valve=true | 0 | 左路兑现在回响(干净海) | -| p5 先带医官/先带小郑 | 0(chen 上,stayer=zheng) | 0(zheng 上,stayer=chen) | 唯一无气差的双义抉择;stayer 决定 D4 单载差分 | -| p6 留备用瓶/约定来接 | -2 气,spareGift=true(可触发干涸链) | 0 | 左路兑现在留守者存活回响 | -| p7 慢浮/快浮(双载) | -2 气 | 0 | 右路兑现在 e1/e2 回响(医疗舱灯) | -| p7 带船长/带留守者(单载) | 0 | 0 | 另一人随海燕号滑入沟底(措辞克制) | -| p8 说出来/不问 | 0,gaoPeace=true | 0 | hasLog 时 gao_confess 差分前置 | -| p9 取船钟/进医疗舱 | 需 oxy≥1,bell=true | 0 | oxy<1 时左路封锁(g9_dry,不摆假选项) | - -### 4.3 氧气经济(单位/消耗/宽限/软失败) - -| 项 | 规则 | +| 结局 | 条件 | |---|---| -| 单位 | 「格」(内部 int)。初始 12(叙事=双瓶满气,够四次往返);D2 上浮后应急瓶 +3(`oxy_gain` 播报,全局唯一),总预算 15 | -| 固定消耗 | 每次下潜开始扣 3(往返);D4 双载额外 +1(慢浮+减压成本按 p7 计) | -| 抉择消耗 | 见 §4.2;**mid-dive 扣至负数一律 floor 到 0**,不中断当前救援(人已挂上吊索,照常带上) | -| 阈值播报 | 分档整句(下表),**只在跨档时播**(挂上浮结算链尾);上滑 status 随时可查;oxy_crit 各播报位附 warning 震(radio G2 同款) | -| 第四潜氧气门 | SURFACE(3) 结算时:oxy≥4 → 双载(带船长+留守者);oxy=3 → 单载(p7 谁上);oxy≤2 → **终局取消链**(第四潜取消,船长与留守者随海燕号滑入沟底——措辞克制、零描写) | -| 宽限设计 | ① 学习期零惩罚:D1 两个左路合计 -2,恰好被 D2 后应急瓶 +3 兜底,教学潜永不亏局;② 到点不是死:软失败只损失「机会」(少带一人/少下一潜),已救上的人**永不失去**;③ 无实时倒计时、无 mid-dive 打断(P4 无高压);④ p9 左路是「氧气的礼物」——省下的气兑换终局抉择 | -| 干涸链(软失败结算) | mid-dive floor 至 0 触发:dry_a →(当前救援照常完成)→ 上浮结算内 dry_b(归因真话:指向实际花掉最后两格的那个左选择)。链入口 800ms 手势死窗 | +| e1 海燕归钟 | saved=5 且 p9 左 | +| e2 满载的黎明 | saved=5 且 p9 右 | +| e3 最后一潜 | saved<5 且 p9 左 | +| e4 按规程上浮 | saved<5 且 p9 右 | -**氧气档位表(账目真实:每格取值下句义皆为真)** +跨潜仅标量:oxy、saved×5、stayer、flag×6。约 70 节点。 -| 档 | oxy 区间 | 整句 | 真实性核验 | -|---|---|---|---| -| full | ≥9(floor(oxy/3)≥3) | 「气瓶还足,救得动。」 | ≥3 潜气量 ✓ | -| ok | 6-8(floor=2) | 「氧气还够两潜。」 | 恰可下 2 潜 ✓ | -| low | 3-5(floor=1) | 「氧气告急,只够最后一潜。」 | 恰可下 1 潜 ✓ | -| crit | 0-2(floor=0) | 「氧气见底,必须上浮。」 | mid-dive=必须上浮 ✓ / 水面=一潜都下不去 ✓ | +## 4. 氧气经济 -- 档位以 `floor(oxy/3)`(可下潜次数)划界而非绝对半数——**句子说的就是判定用的**(radio rep_low 教训内化:措辞与数值对得上)。 -- 低氧混音签名:tier=low/crit 期间 DIVE/COMMS 垫 `breath_loop`(低响度呼吸声,SA3 循环);引擎无运行时滤波,BGM 闷化降级为该呼吸层(显式降级,不阻塞管线)。 +初值 12;D2 后 +3,总预算 15。每潜起扣 3。抉择耗气见上。mid-dive 负数 floor 0。档位=floor(oxy/3):full≥9 / ok 6–8 / low 3–5 / crit 0–2。水面 crit 用 oxy_crit_deck。双载 D4 口播 dv4_dual「这趟往返,扣四格气。」 -### 4.4 船员好感/存活矩阵 +SURFACE(3) 时 oxy=6−lefts → 双载 lefts≤2;单载=3;取消≥4。教学潜与机舱潜预算上永不卡死。 -| 船员 | 好感来源(bond) | 存活条件 | 回响句 | -|---|---|---|---| -| 小舟(声呐兵) | p1 左 或 p2 左 | **恒活**(D1 教学潜保底救上) | ep_zhou_ok / ep_zhou_no | -| 老白(轮机长) | p3 左 或 p4 左 | **恒活**(D2 恒可成——预算数学保证,见 §9 验算) | ep_bai_ok / ep_bai_no | -| 陈医官 | p5 左(当场救上);或留守时 p6 左 | p5 左即活;p5 右留守 → D4 双载或被选则活,否则失 | ep_chen_ok / ep_chen_lost | -| 小郑(无台词角色) | —(吊床上的沉默存在,radio 老主播「带子里只有电流声」先例) | p5 右即活;p5 左留守 → D4 双载或被选则活,否则失 | ep_zheng_ok / ep_zheng_lost | -| 高船长 | p8 左(gaoPeace) | D4 双载 或 单载选左则活;单载右/终局取消则失 | ep_gao_peace / ep_gao_up / ep_gao_lost | +船员:小舟/老白恒活;陈/郑/船长按 p5/p6/氧气门/p7。好感只影响 ep 选播。 -- 好感只影响回响选播(情绪差分),**不影响结局判定**——结局由 savedCount × p9 决定,账目单一可验算。 -- 生死分支覆盖 3 人(陈医官/小郑/高船长),措辞全部克制(「滑进了沟底」「灯还亮着」),无临场描写;失败归因走干涸链/取消链的真话句。 +## 5. 声呐双通道 -### 4.5 氧气收支验算(设计期穷举,playtest 剧本 A-D 引用) +信息类回波与 voice **零重叠**(sonarGate)。搜索段:脉冲 → 静默一拍 → 回波 → 解读。回波时长:金属 3300 / 敲管 3500 / 呻吟 5000ms。玩家脉冲抢先于向导散句。语音在播时远程脉冲垫 −6dB。并发前景 ≤2。 -设 `lefts` = 前 3 潜左路气耗之和(p7/p9 不在前 3 潜): +教学承诺:金属回波=船身(凡「船身」节点必播 echo_metal*);单击=脉冲;四潜=基础 3×4=12;氧气门=oxy<3 取消;每潜扣 3;敲管=老白;补给恰 +3 一次。 -- SURFACE(3) 时 oxy = 15 − 9(基础)− lefts = **6 − lefts** → 双载 ⟺ lefts≤2;单载 ⟺ lefts=3;取消 ⟺ lefts≥4。 -- 双载收队 oxy_end = 2 − lefts(p7 右)或 0 −(lefts−2)(p7 左,floor 0)→ p9 左可用 ⟺ lefts≤1。 -- 单载收队 oxy_end = 3 − lefts = 0(lefts=3)→ p9 左恒封锁 → 单载路径必 e4。 -- 取消收队 oxy_end = 6 − lefts ∈ {2,1,0}(lefts=4,5,≥6)→ lefts≤5 时 p9 左可用 → e3。 -- 前 3 潜恒可完成的预算证明:D1 最大支出 5(12→7);D2 最大支出 6(7→1)后应急瓶 +3(→4)≥3 ✓;D3 恒可下(最差 4→1,p6 左 floor 0 走干涸链)✓。**教学潜与机舱潜永不被氧气卡死**(P4)。 +## 6. 音频资产 -## 5. 音频三层映射 +BGM:deck / dive / comms / ending。SFX:sonar_ping、echo_metal_l/r、echo_debris、echo_groan、pipe_knock、link_beep、static_burst、amb_deep、amb_deck、breath_loop、spark_flash、valve_close、landslide、surf_splash、ascent_chime、bell_toll。 -### 5.1 BGM(状态→曲目,Mureka instrumental,4 首 ≈ $0.12) +Voice:潜航员 30 + 调度 97(双性别)+ 船员 21 平铺滤波。D4 danger 震间隔 1100ms。crit 档 warning 震按「进入该档」去重。 -| 状态 | 曲目 | 切换 | Mureka prompt 草稿(英文,seamless loop) | -|---|---|---|---| -| INTRO/SURFACE/MENU | `bgm/deck` | 开局淡入 800ms;MENU 呼出低响度淡入 500ms | "Night sea surface from a rescue ship deck, sparse cello and low piano over gentle waves, distant rigging creak and engine hum, melancholic but purposeful, no vocals, seamless loop, 60-90 seconds" | -| DIVE | `bgm/dive` | crossfade 800ms 进;接通 COMMS 淡至 comms 600ms | "Deep ocean descent ambience, dark ambient drone with a slow sonar-like pulse, immense water pressure, distant whale-like tones, subtle tension, no vocals, seamless loop, 60-90 seconds" | -| COMMS | `bgm/comms` | crossfade 600ms;上浮淡回 deck | "Underwater radio contact scene, warm muted piano over a deep water drone, faint hydrophone static, intimate and quietly hopeful, no vocals, seamless loop, 60-90 seconds" | -| ENDING | `bgm/ending` | p9 结果链播完后淡入 1500ms | "Dawn on the sea after a deep sea rescue, warm strings and soft piano, exhausted hope and quiet triumph, no vocals, seamless loop, 60-90 seconds" | - -- `bgm=0` 时全部静默(引擎约定);menu 用 deck 同曲(省 1 首,海面场景同构);共性后缀(10 号 §8)照 radio 追加。 - -### 5.2 SFX 清单(SA3=Stable Audio 3 / SYN=程序合成;one-shot 真峰值 ≤-3dBFS;amb 循环由游戏层 `buffer.loop` 实现,radio phone_ring 先例) - -| id | 用途 | 来源 | prompt(英文)/ 合成参数 | -|---|---|---|---| -| `sfx/sonar_ping` | 你发出的声呐脉冲(搜索单击) | SYN | 1.4→0.9kHz 正弦 chirp 220ms,指数衰减,-6dBFS(回波家族统一音头) | -| `sfx/echo_metal_l` / `_r` | 钢板回波(左/右声像;「金属回波=海燕号」承诺音) | SA3 | "Sonar echo returning from a steel submarine hull, resonant metallic ping with slow decay, panned slightly left(→right), underwater, 2 seconds" | -| `sfx/echo_debris` | 碎石回波(D2 机舱外) | SA3 | "Sonar echo returning from an underwater rubble and debris field, diffuse granular reverberation, 2 seconds" | -| `sfx/echo_groan` | 船体呻吟(D3 预警/D4 滑坡前奏,可变增益复用) | SA3 | "Stressed submarine hull groaning deep underwater, long metallic moan under immense pressure, 4 seconds" | -| `sfx/pipe_knock` | 管路敲击暗号(老白 SOS) | SYN | 三短三长三短固定节拍金属敲击(节拍精确可复现、断言可数) | -| `sfx/link_beep` | 电台接通 | SYN | 双音上行 0.3s | -| `sfx/static_burst` | 通讯静电/断续 | SA3 | "Shortwave radio static burst with faint ghost of a distorted voice, 2 seconds" | -| `sfx/amb_deep` | 深潜底噪 loop | SA3 | "Deep ocean ambient loop, low pressure hum, distant current wash, occasional far creak, seamless loop" | -| `sfx/amb_deck` | 甲板底噪 loop(风+浪+缆绳) | SA3 | "Night ship deck ambience, wind, gentle waves against hull, distant rigging creak and engine hum, seamless loop" | -| `sfx/breath_loop` | 低氧呼吸 loop(仅 low/crit 档垫,混音签名) | SA3 | "Slow scuba regulator breathing loop, calm but slightly labored, underwater, seamless loop" | -| `sfx/spark_flash` | D2 危机拍:切割火花+氢气虚惊 | SA3 | "Underwater cutting torch sparks with a brief muffled gas pop, 2 seconds" | -| `sfx/valve_close` | 重阀关闭(p4 左) | SA3 | "Heavy submarine valve wheel shutting, metal rotation clunk and water seal, 2 seconds" | -| `sfx/landslide` | D4 危机拍:海底滑坡轰鸣(前置 2s 渐强) | SA3 | "Underwater landslide rumble, rolling sediment and rocks, ominous low roar building, 4 seconds" | -| `sfx/surf_splash` | 破水出水(每潜上浮) | SA3 | "Diver breaking through the sea surface, splash and one gasp, 2 seconds" | -| `sfx/ascent_chime` | 出水确认音 | SYN | 上行三音琶音 0.4s | -| `sfx/bell_toll` | 船钟(p9 左 payoff 音) | SA3 | "Old ship brass bell, single clean toll with warm resonance, 3 seconds" | -| `sfx/ui_confirm` / `ui_menu` | 菜单/确认(程序合成兜底) | SYN | 同全局先例 | - -- 预警窗纪律:D2 火花与 D4 滑坡均**前置 2s 起渐强**(L2),danger 签名与音效同窗(radio C5 刹车先例);D4 另叠 `danger` 震循环(每遍间隔 ≥400ms)。 -- 承诺兑现绑定:dv1_metal 教「金属回波=钢板」后,凡叙事提到船身/找到船的节点必播 echo_metal*;D2 找人必播 pipe_knock(§6.3 清单 + §9 断言)。 - -### 5.3 Voice 划分(详见 17-abyss-voice-ids.md) - -| 组 | 声部 | 落位 | 数量 | -|---|---|---|---| -| 潜航员(你)DIVER | 双性别(female=Serena / male=Uncle_Fu + DIVER instruct 常量) | `voice/female\|male/` | 30 句 | -| 调度/系统/向导 DECK | 双性别(female=Vivian / male=Dylan + DECK instruct 常量) | `voice/female\|male/` | 97 句 | -| 船员 NPC ×4 | 平铺单音色(小舟=Serena / 老白=Uncle_Fu / 陈医官=Vivian / 高船长=Eric),**不做双性别** | `voice/` 根(平铺回退命中)+ 电台滤波转码 | 21 句 | - -- 船员 ≤4 个单音色约束满足;玩家自己的声音跟随全局 `settings.voice`;调度是向导人设(L4 角色化系统声,radio 导播同型)。 -- 合计 148 句(≤170 GPU 预算 ✓,含 `_k` 键盘变体 11 对);音频文件 = 127×2 + 21 = 275 条(×2 格式 = 550 文件)。 - -## 6. 声呐双通道设计(本作核心差异层) - -### 6.1 引擎能力边界(对照 web-engine-api.md) - -| 能力 | 引擎现状 | 方案 | -|---|---|---| -| voice 运行时 EQ/滤波 | ❌(voice 链无滤波插入点) | **资产侧转码滤波**(radio §6.2 先例):ffmpeg `highpass+lowpass+压缩器` 逐船员定档(§5.3/voice-ids) | -| sfx 循环 | ❌ 即发即弃 | 游戏层 `buffer.loop=true` 持句柄(amb_deep/amb_deck/breath_loop),切换档位时停(radio `_ringSrc` 先例) | -| 语音 duck 垫乐 | ✅ 内置(duck 20%,300ms) | 直接依赖;BGM 不再手工让路 | -| sfx vs voice 互让 | ❌ sfx 总线独立,无 duck | **游戏层 sonarGate 空闲门**(§6.2 R1):信息类回波只在 `!audio.isVoicePlaying()` 时播出,否则挂队列表、voice onComplete/句间轮询补发 | -| sfx 声像 | ❌ 运行时无 pan API | 资产侧 pan(echo_metal_l/_r 两条,escape 先例);其余方向信息由调度语音明说(「左前方」) | -| 变速不变调 | ✅ SoundTouch | 直接依赖(长按 1.7× / 2.2× 视障档) | - -### 6.2 通道仲裁规则(并发声流 ≤2 约束) - -- **通道定义**:声呐通道 = sfx 层的脉冲/回波/敲击/呻吟/环境 loop;通讯通道 = voice 层(调度+船员+你)。BGM 是第三层背景(voice 播时引擎自动 duck)。 -- **R1 回波不叠语音**:信息类回波(echo_*、pipe_knock、groan 渐强段)与任何 voice 播放区间**零重叠**——sonarGate 只在 voice idle 时放行,在播则排队到句间空隙(≥600ms);玩家主动脉冲只在搜索等待(voice 恒静)受理,按构造不重叠。 -- **R2 声呐主导段**(DIVE 搜索等待):前景=脉冲+回波余音+amb_deep,BGM=dive 曲低响度;voice 只出调度短句(解读句),且解读句固定在回波**结束后**接播(回波→静默一拍→解读,给耳朵留定位时间)。 -- **R3 常驻脉冲退位**:语音在播时,环境内的周期性声呐元素(母船远程脉冲垫)降 -6dB 或暂停(游戏层 gain 控制),不消失(保持声场存在感,A2 无静默死局)。 -- **R4 危机拍优先**:D2/D4 危机音(spark/landslide)为即发即弃 sfx,可叠在 amb 上;danger 签名同窗;当前 voice 句播完后危机链才插续(radio C5 同款;呼救类短句若必须抢时,走 sfx 层不占 voice)。 -- **R5 回波可重听**:搜索等待双击=重听「上一条回波+其解读句」(P1 关键线索可重听);COMMS 双击=重听当前节点(radio 同款)。 -- 声流账:任意时刻 ≤2 条前景流(voice 串行 1 + 声呐信息音 1),BGM/amb 为背景层——满足 ASA ≤2 注意流约束。 - -### 6.3 教学承诺清单(dungeon「铁器=楼梯」教训:每句承诺的机制必须真实存在且可感知) - -| 教学句 | 承诺 | 兑现点(断言锁定) | -|---|---|---| -| dv1_metal「金属回波,钢板。听见这个,就是海燕号的船身」 | 金属回波=找到船 | D1 找到/D4 接近指挥舱均播 echo_metal*;全剧本任何「船身」节点零漏播 | -| dv1_teach「单击,发一枚脉冲」(+gd_ping1) | 单击=脉冲 | 搜索等待单击恒出 sonar_ping(非跳句、非无效);无超时不惩罚 | -| b_goal「五个人。你的双瓶,够下四潜」 | 5 人/4 潜 | 基础消耗恰 3 格×4=12(§4.5 验算);剧本 A 恰 4 潜走完 | -| b_rule「气不够一潜,人就必须上浮」 | 氧气门 | 取消门恰在 oxy<3(§4.3/§4.5);剧本 C/D 实证 | -| d1_surf「往返,扣三格气」 | 每潜扣 3 | 每次下潜开始扣 3(账目断言) | -| d1_code「记住这个敲法」 | 敲管=老白的信号 | D2 搜索以 pipe_knock 引导找到机舱 | -| oxy_gain「应急瓶接上了,多三格」 | +3 | 恰一次、恰 +3、恰在 D2 上浮后 | - -## 7. 向导层接入 - -### 7.1 状态签名 earcon / turn cue - -| 时机 | 向导动作 | 说明 | -|---|---|---| -| INTRO 进入 | `guide.state('explore')` | 叙事态签名 | -| 搜索等待武装(下潜链末) | `guide.turn({haptic:true})`(无语音) | turn 签名+confirm 震=「轮到你发脉冲」;首次前 dv1_teach 已口播 | -| 每个抉择点 p* 起播 | `guide.state('choice')` | 选项句本身即 turn cue(W2 契约零插播) | -| SURFACE 进入 | `guide.state('settle')` | 结算签名 | -| ENDING 进入 | `guide.state('settle')` | 同上 | -| MENU 打开 | `guide.state('menu')` | 菜单签名 | -| D2/D4 危机拍 | `guide.state('danger')` | 音效前置 2s 渐强同窗(全游戏仅两处) | -| 无效手势 | `guide.error({voice:'gd_invalid', onceKey})` | error 震 + full 档语音,idle-only(F1.4) | - -- earcon 词表:只用全项目统一 8 音(menu/explore/battle/choice/settle/danger/turn/nudge),**零新增**;battle 不用。 - -### 7.2 看门狗双轨(radio F1 + dungeon E0-1 教训:乱按也要被抢救) - -**无操作轨**(guide watchdog,任何手势复位;两级不同句、用尽即停、永不惩罚): - -| 等待点 | full 档 | standard 档 | 级别内容 | -|---|---|---|---| -| 搜索等待无脉冲 | [10, 25] | [10, 25](onEscalate 游戏层补播) | ① gd_ping1 ② gd_ping2(**必须不同句**);两级后仅 amb+余音 | -| INTRO/COMMS/SURFACE 链末等待 | [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 | - -**无进展轨**(游戏层计时,dungeon E0-1 ②同型;仅挂搜索等待与 COMMS 链末两个可饿死点): - -| 项 | 规则 | -|---|---| -| 进展定义 | 节点推进 / 发出脉冲 / 抉择结算。**无效手势(滑错、空点)不计进展**——无操作轨被它们复位、无进展轨不被它们复位,乱按 96s 也必被抢救 | -| 阈值 | 60s → gd_prog1;120s → gd_prog2;用尽即停(每等待点一轮) | -| 有真实进展 | 全程零 prog 播出(不误伤正常玩家,dungeon 断言同款) | -| 双轨去重 | 两轨短句内容同级交叉登记;同一等待点先播过 gd_ping* 则无进展轨跳过同义级 | - -- 向导插播一律 `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//_k.*`,touch → 无后缀版;**auto 跟随最近使用通道**(keydown 后即切 key)。`_k` 缺失回退无后缀版(资产侧保证成对,check_all_assets 校验)。 -- 变体清单(11 对,集中分组见 voice-ids §G0):gd_brief / gd_continue / gd_again / gd_ping1 / gd_ping2 / sys_help / sys_menu / sys_skip / sys_continue_ask / dv1_teach / p1。 -- 抉择提示 p2..p9 措辞统一「向左滑/向右滑」;键盘玩家由 p1_k 与 sys_help_k 完成首次绑定(左=←,右=→),后续提示语义不变。 -- 启动遮罩文案引擎自理(「按空格开始」/「轻触开始」),游戏零改动。 - -### 7.4 继承 radio 三轮验证机制(照抄不重设计) - -| 机制 | 来源 | 落点 | -|---|---|---| -| 帮助链去重(同等待点第 2 次上滑只震+nudge) | radio G1 | `_helpSeen` 按等待点键控 | -| 状态链顺序「状态在前、帮助在后」 | radio G6 | prog_dN → oxy 档 → sys_help | -| 空点确认轻震(不可跳句上单击补 beat [8]) | radio G4 | p*/链末句/F4 锁各 tap 屏蔽分支 | -| oxy_crit 播报位附 warning 震 | radio G2 | 全部 crit 出口(含上滑复听) | -| 菜单报名游戏侧排队零截断 | radio 实现修复 #3 | `_menuQueue` 排水 | -| stop() 先 `seqToken++` 作废在途链 | radio 实现修复 #6 | 同款 | -| 新局清 choiceArmed/pendingChoice | radio 实现修复 #7 | newRun() | -| 长按误报无效(longpress 全状态合法) | radio 实现修复 #2 | 仅 left/right(ASK 另含 up/down/doubletap)报无效 | - -## 8. 存档设计 - -### 8.1 进度 data 结构(PUT /api/progress {game:'abyss'}) +## 7. 存档 ```json { - "schema": 1, - "updatedAt": "2026-09-09T12:00:00Z", - "summary": "第2潜·机舱 · 氧气6格 · 结局1/4", - "phase": "COMMS", - "node": "bai_b", - "dive": 2, - "oxy": 6, - "supply": true, + "phase": "COMMS", "node": "bai_b", "dive": 2, "oxy": 6, "saved": { "zhou": 1, "bai": 0, "chen": 0, "zheng": 0, "gao": 0 }, - "stayer": null, - "flags": { "knowsPipe": true, "hasLog": false, "valve": false, "spareGift": false, "gaoPeace": false, "bell": false }, - "endings": { "e2": true }, - "runs": 3 + "flags": { "knowsPipe": true } } ``` -- 写入时机:每潜 DIVE 进入、每个抉择结算后、SURFACE 进入、ENDING 进入、菜单「保存并回大厅」。 -- 断点续玩:读档 → sys_continue_ask(_k)(单击=继续上次 / 右滑=新救援);继续=恢复 phase+node+全部数值并**重播当前节点链**=一句话局面复述(L5);重进 ≤5s 可玩;恢复首句受 F4 跳句锁(radio G5 例外条款)。 -- summary 格式:进行中 `第N潜·<舱段> · 氧气K格 · 结局X/4`;收队 `救援收队·<结局名> · 结局X/4`(大厅卡片直接显示)。 +summary:`第N潜·<舱段> · 氧气K格 · 结局X/4`。 -## 9. playtest 断言清单(登记 13 号 §21 指针;`settings.set({guide:'off'})` 纯玩法验证,向导行为住 guide.test) +## 8. playtest 指针 -**关键路径**(剧本 A:全右+左p9 → e1,≤70 次手势): -1. 遮罩点按 → INTRO 链(b_open 起,voice 层有播放记录)→ 链末单击 → DIVE(1) 下潜链 auto → 搜索等待武装(turn 事件 + amb_deep 循环起播)。 -2. 搜索单击 → sonar_ping → 回波静默一拍 → echo_metal_l → dv1_metal(承诺音)→ link_beep → COMMS(1):z1_a/b/knock → p1 右 → p2 右 → 上浮:surf_splash+ascent_chime → d1_surf → oxy=12-3=9 → oxy_full 播出(账目断言)。 -3. DIVE(2):脉冲 → echo_debris+pipe_knock → dv2_found → bai_a/b → p3 右(danger 签名前置 2s 与 spark_flash 同窗断言)→ p4 右 → d2_surf → d2_supply → oxy_gain(6→9,恰一次)。 -4. DIVE(3):脉冲 → echo_groan → dv3_groan → chen_a/b → p5 右 → p6 右 → d3_surf(oxy=6,ok 档)。 -5. 氧气门:6≥4 → DIVE(4) 双载:滑坡危机拍(landslide 前置 2s 渐强 + danger 签名 + danger 震)→ gao_a/b → p7 右 → gao_c → p8 右 → d4_surf → oxy=2(crit 档+warning 震)。 -6. 签收:g9_in → g9_knot → p9 左(oxy=2≥1 可用)→ d9_bell + bell_toll → ENDING(e1):e1_a/b + ep_lead + ep 链 → 结算单击 → 新救援回 INTRO。 - -**边界断言**: -- **四结局可达(含氧气逐点验算)**:剧本 B(同 A 但 p9 右)→ e2;剧本 C(p1左 p2左 p4左,余右):D1 后 oxy=7(ok)/D2 后 2→supply→5(gain+low 档)/D3 后 2(crit)→ 氧气门走**终局取消链**(dv4_none→gao_final 完整播出)→ p9 左(2≥1)→ e3(saved=3);剧本 D(p1左 p4左,余右):D1 后 8(ok)/D2 后 3→supply→6(ok)/D3 后 3(low)→ **单载形态**(p7 按 stayer 选 p7_chen|p7_zheng)→ p7 左=带船长 → 留守者留别句(chen_last|d4_stay_lost)→ p8 右 → oxy_end=0 → p9 左**封锁**:g9_dry 播出后自动右 → e4(saved=4)。各局 oxy 逐检查点与 §4.5 公式一致。 -- **氧气账目真实**:每次扣减后播出的档位句与 floor(oxy/3) 桶一致;上滑 status 链顺序 prog_dN → oxy 档 → sys_help(G6);supply 全局恰一次;剧本 E(p2左 p3[无knows]左 p4左 p6左)mid-dive floor 至 0 → dry_a/dry_b 播出且当前救援者照常救上(人已挂上吊索不失去)。 -- **双通道仲裁**:全剧本转写断言——echo_*/pipe_knock/groan 信息音播出时刻与任一 voice 播放区间**零重叠**(sonarGate 排队语义:晚于在播句结束);搜索等待单击恒=脉冲(sonar_ping 计数=1,无跳句无 error);双击搜索等待=回波+解读句同序重播;语音在播时 amb_deep 持续、远程脉冲垫降 -6dB(R3)。 -- **教学承诺兑现**(§6.3 清单逐条):dv1_metal 后所有「船身」节点 echo_metal* 零漏播;D2 找到机舱前 pipe_knock 恒在;b_rule 与取消门一致(剧本 C 证明);b_goal 四潜=剧本 A 恰 4 潜;每潜起播扣 3 断言。 -- **看门狗双轨**:搜索等待快进 10s→gd_ping1、25s→gd_ping2(两级 id ≠,绝不逐字重播;两级后仅 amb);**乱按 96s**(每 2s 注入一个无效滑动):无操作轨零播出(被手势复位)+ 无进展轨 60s→gd_prog1、120s→gd_prog2(乱按饿不死);有真实进展(单击脉冲推进)全程零 prog 播出;COMMS 链末 25s/50s → gd_continue/gd_again;p* 播完 25s/50s 重播 p* 两次后静默;`guide=off` 全程零定时器零向导事件。 -- **键盘变体**:settings.input='key' 会话播出 gd_brief_k/sys_help_k/gd_ping1_k/dv1_teach_k/p1_k 且对应无后缀版零播出;'touch' 反之;'auto':keydown 后下一句用 _k。 -- **中断复述**:杀进程重进(存档 node=bai_b)→ sys_continue_ask → 单击 → 重播该节点链(恢复首句不可跳,第二句起可跳);右滑=新救援(runs 口径同结算重开)。 -- **结算链死窗(H1 内化)**:终局取消链/干涸链/e* 结局链开播后 800ms 内注入在途 tap(mock 补派 touchend+300ms 同款序列)→ 手势被吞、链完整有序播出、结算出口手势照常。 -- **pendingChoice**:p* 播放中左右滑 → 播完立即生效(不丢、不重复、连滑取最后一次)。 -- **p* 不可跳**:p* 播放中单击 → 不跳句(PROMPT_RE 门)+ beat 轻震(G4)。 -- **idle-only**:向导 gd_* 在对白在播时跳过且不占 onceKey(F1.4);queue 句排在当前句后零截断。 -- **菜单**:下滑开菜单 → 上滑帮助 → 双击「回到水下」→ phase/node 不变、当前节点重播;单击循环顺序 = [回到水下, 重新开始这次救援, 保存并回大厅];确认回大厅 → sys_menu_lobby 播出且 PUT/localStorage 有最新 data。 -- **资产缺失**:屏蔽任意 voice 资产 → 30s 看门狗自动续链到结局不卡死;NPC 滤波资产缺失回退原声可播。 -- **数字规范**:prog_*/oxy_*/gd_* 全部整句字面量,静态审计无运行时拼接碎片。 -- **低氧混音签名**:tier=low/crit 期间 breath_loop 在播、升档/收队即停。 - -**失败即不通过**:任何等待 >5s 无声(静默死局);无效手势零反馈;回波与语音重叠;结局计数与 §3.4 不符;氧气档位句与 oxy 值不符。 - -## 10. 节奏曲线(每次下潜目标时长) - -| 段 | 内容 | 目标时长 | 设计目标 | -|---|---|---|---| -| 开场+下潜链 | 甲板简报+第一潜下水+搜索 | 1-1.5 min | 首输入 <60s(开场链 ~20s 链末单击,或 ~40s 搜索首脉冲);规矩一句讲完(P4) | -| D1 找船 | 教学潜:脉冲/回波/接通/2 抉择 | 4-4.5 min | **首个正反馈 ≤3min**(~2.5min 金属回波+接通小舟=「看见它了」);两抉择条条有出路;左路气耗被补给兜底=学习期零惩罚 | -| D2 机舱 | 老白:撬/切+阀门抉择 | 3.5-4 min | 首个危机拍(火花,前置 2s);补给+3 兑现宽限;暖拍(热粥,P4) | -| D3 病舱 | 生死抉择+回波预警 | 3.5-4 min | 船体呻吟埋 D4 险;留守约定=段落边界(P3 中断友好:每潜一个完整存档颗粒) | -| D4 指挥舱 | 滑坡高潮+氧气门三形态 | 4-4.5 min | 紧张峰值:danger+滑坡渐强+双人竞速;生死分支收敛 | -| 终局+结局 | p9 心结+结局链+逐人回响 | 2.5-3 min | 氧气余量兑换终局抉择;ep 点名(P2 回响);开环钩子拉下一局 | - -- 合计 ≈ 20-22 min(15-25 达标);每 30-60s 一个反馈颗粒(回波/解读短句/结果句/档位句),无沉默尴尬期(P3)。 -- 难度曲线 = 道德复杂度(教学二选一 → 情报投资 → 生死取舍 → 自己的心结),操作复杂度恒定(永远二选一+单击脉冲)——P4「规则一遍听懂」由恒定操作面保证。 - -## 11. 自检清单(策划交付自验;☐ = 待开发/测试轮填) - -| # | 硬约束/教训 | 落地 | ✓ | -|---|---|---|---| -| 1 | FTUE 首输入 <60s | 开场链 ~20s 链末单击;~40s 搜索首脉冲(§10) | ☑ | -| 2 | FTUE 首个正反馈 ≤3min | ~2.5min 金属回波+接通小舟(§10 D1) | ☑ | -| 3 | 学习期零惩罚 | D1 左路 -2 被 D2 后 +3 兜底;搜索无超时惩罚;D1/D2 恒可完成(§4.5) | ☑ | -| 4 | 失败三问归因+归因真话律 | 干涸链 dry_b 指向实际左选择;gao_final/d4 留别句克制不假安慰;§4.4 无假归因句 | ☑ | -| 5 | 资源账目真实(radio rep_low 教训) | 氧档以 floor(oxy/3) 划界、每档句义逐格为真(§4.3 表);补给恰一次 +3;扣 3 恰每次下潜 | ☑ | -| 6 | 手势宪法 6 手势默认语义零豁免 | §2.3 全覆盖;单击搜索=「检查」(宪法 §5.1+escape 先例);菜单 §5.2 模态约定 | ☑ | -| 7 | 长按显式登记 | 无豁免:长按=引擎快进(12 号叙事类不豁免),§2.3 在案 | ☑ | -| 8 | 向导:签名/turn cue/看门狗两级不同句 | §7.1/§7.2;gd_ping1≠gd_ping2;gd_continue→gd_again | ☑ | -| 9 | 看门狗双轨(dungeon E0-1/radio F1 教训) | 无操作轨+无进展轨(§7.2);乱按 96s 必被抢救;有进展零打扰 | ☑ | -| 10 | 插播 queue 不顶对白;纠错 idle-only 过期丢弃 | §7.2(F1.4 契约) | ☑ | -| 11 | 死亡/失败结算链完整(dungeon H1 教训) | 终局取消链/干涸链/结局链 800ms 手势死窗;已救者永不失去(§2.2/§4.3) | ☑ | -| 12 | 教学承诺兑现(dungeon「铁器=楼梯」教训) | §6.3 承诺清单 7 条逐条绑兑现点+断言 | ☑ | -| 13 | 单次播报 ≤3 选项 | 全部抉择恒 2 选项;菜单 3 项循环 | ☑ | -| 14 | 并发声流 ≤2 + 双通道仲裁 | voice 串行+声呐信息音互斥(R1-R5,§6.2);断言零重叠 | ☑ | -| 15 | earcon ≤7、统一词表、语义靠语音 | 只用统一 8 音中 7 个(battle 不用),零新增 | ☑ | -| 16 | 台词一句一文件 ≤40 字、数字分档整句、无运行时拼接 | voice-ids 全表 ≤30 字(最长 p8=30);prog/oxy 分档整句;句内数字写死 | ☑ | -| 17 | 多音字规避/同 instruct 常量/无语速词 | voice-ids 头部+文末自查(重写→补、脏/晃/闷标注试听) | ☑ | -| 18 | PC 键盘变体(提示句集中+标注) | 11 对 _k 变体集中分组(voice-ids §G0);inputMode() 运行时选择 | ☑ | -| 19 | P1:可打断/重听(含回波)/status key/不当白痴 | 跳句+双击(回波可重听 R5)+上滑状态链+成功零补救教学 | ☑ | -| 20 | P2:3 分钟见效/结局回响 | ~2.5min 首个改变世界的时刻;ep 逐人点名+bell 差分;e1 巧妙路径(p1左+p3左 省气取钟) | ☑ | -| 21 | P3:3 分钟颗粒/中断复述/单手 | 一潜=3.5-4.5min 完整段落+边界自动存档;重进重播当前节点;全手势免视单手 | ☑ | -| 22 | P4:一遍懂/容错/无高压倒计时/不骂人 | 一句规矩;无实时倒计时(步进资源+跨档才播);软失败零死亡;NPC 句 ≤24 字+逗号停顿 | ☑ | -| 23 | 抉择树 4 结局可走通验算 | §4.5 公式+§9 剧本 A-D 逐 oxy 检查点(e1:lefts≤1+双载;e2:lefts≤2;e3:lefts=4-5+取消;e4:单载 lefts=3 或取消右) | ☑ | -| 24 | 单局 15-25 min | §10 曲线 ≈20-22 min | ☑ | -| 25 | 分支不爆炸(radio 纪律) | 跨潜仅标量(oxy/saved/stayer/6 flag);~70 节点固定深度(§3.3) | ☑ | -| 26 | 台词 ≤170、船员 ≤4 单音色 | 148 句(双 127+NPC 21)/4 个船员音色(voice-ids 账目) | ☑ | -| 27 | 引擎边界诚实 | §6.1 能力表:滤波/loop/声像/互让全写明资产侧或降级方案 | ☑ | -| 28 | 存档带 summary、断点续玩 | §8:data+summary 格式、≥5 恢复、局面复述 | ☑ | - -### 批 1 教训内化确认(5 条全落地) - -| 教训 | 本作机制 | 断言 | -|---|---|---| -| 教学承诺必须兑现 | §6.3 七条承诺清单(金属=钢板/单击=脉冲/四潜/氧气门/扣三格/敲管/补给) | §9「教学承诺兑现」块 | -| 看门狗双轨 | §7.2 无操作轨+无进展轨;无效手势不复位进展轨 | §9「看门狗双轨」块 | -| 档位措辞真实 | §4.3 档位=floor(oxy/3) 可下潜次数,句义逐格为真 | §9「氧气账目真实」块 | -| 失败结算链完整不被打断 | 800ms 手势死窗盖取消链/干涸链/结局链;seqToken 作废在途链 | §9「结算链死窗」块 | -| PC 键盘操作提示 | 11 对 _k 变体+inputMode() 选择+集中分组标注 | §9「键盘变体」块 | - -## 12. R2 修订记录(2026-09-13,依据 research/new8/abyss/round1/synthesis.md) - -> 追加记录,不改旧裁定;凡本节与上文冲突,以本节为准。全部改动已落地 web/abyss/game.js,断言增量见文末注记。 - -| # | 旧裁定 | 新裁定(R2 起生效) | 依据 | -|---|---|---|---| -| R2-F1 | 玩家脉冲走 sonarGate 空闲门(§6.2 R1 按构造不重叠) | 玩家脉冲**先于门**:搜索等待态 voice 层只可能是向导散句(gd_ping*/gd_prog*),单击发脉冲时先掐散句、ping 立即出音。信息类回波仍走空闲门不变 | live R1 P1-1:ping 迟到 12~17s「回波先于脉冲」(3/5 复现),教学潜核心承诺错序 | -| R2-F2 | F5 尾句保护按 \_nodeSteps 重算尾句;签收链无死窗 | 尾句保护改认**实际播放链尾**(dyn 注入的档位句纳入保护:档位句不可被单击掐断);签收链(g9_in)入口盖 800ms 死窗(与取消/干涸/结局链同款) | live R1 P1-3:oxy_low 播 0.71s 被掐、g9_in 被在途 tap 剁到 0.28s | -| R2-F3 | 回波 wait 常数 2200/2600/4000ms | W_Metal/W_Knock/W_Groan = 3300/3500/5000ms(资产实长 2.76/2.96/4.42s + 静默一拍),兑现 §6.2 R2「回波→静默一拍→解读」。玩家单击仍可快进回波余音(_sonarSkip 不变) | live R1 P2-1 + P1 评审:回波尾巴压解读句开头 0.35~0.55s,定位一拍被吃 | -| R2-F4 | D4 双载额外 +1 不口播 | 双载形态下潜链追加 `dv4_dual`「两个人下同一潜。这趟往返,扣四格气。」(3+1=4 扣账口播化) | R1 P1:心算账 12−3−3+3−3−3=3 与耳朵听到的 crit 档差一格 | -| R2-F5 | crit 档全场景播「氧气见底,必须上浮。」 | 水面场景(SURFACE/ENDING)播新句 `oxy_crit_deck`「氧气见底,一潜都下不去了。」;水下(DIVE/COMMS)保留原句。档界 floor(oxy/3)=0 不变,两句各自逐格为真 | R1 P1:人已出水后「必须上浮」是说错地点的正确话 | -| R2-F6 | G5「首播必完整、复播可即掐」(§2.3):实现只兑现前半句(尾句保护为永久态) | 非 auto 叙事节点尾句保护改**首播制**:链自然播完登记 `_tailHeard`,此后该节点重播(菜单/状态复述)尾句可单击跳过;auto 过场尾句、直入抉择尾句、p* 提示、F4 锁恒保护不变。sys_skip(_k) 同步改词(见 voice-ids R2-F6) | live R1 P1-2:跳句承诺名存实亡(B 局全程零句可跳);radio F5 吃尾根因由首播保护继续挡住 | -| R2-F7 | 叙事链末/ENDING 无操作轨 full [25,50] / standard [13,30](§7.2) | full 档对齐 standard:**[13,30]**(搜索 [10,25]、抉择点 [25,50]、无进展轨 60/120s 不变) | R1 P2+P3:接通小舟后 25s 静默掐在钩子落地上(走神/怀疑锁屏) | -| R2-F8 | D4 danger 震循环 500ms(§5.2,≥400ms) | **1100ms**(危机拍 4s 窗内约 4 遍;≥400ms 下限仍满足) | R1 P2+P4:4 秒约 10 连震,贴身躺玩/年长者过刺激 | -| R2-F9 | 状态链每逢 crit 档附 warning 震(G2) | 按「进入 crit 档」去重:档不变复听不重复震;s_tier 跨档播报位的 warning 震保留 | R1 P4:压力局摸状态十几回,回回挨三连震 | -| R2-F10 | 状态链 = 潜次 → 氧档 → 帮助(G6) | 状态链 = 潜次 → **已救人数(prog_saved1..5,saved=0 不播)** → 氧档 → 帮助 | R1 P3:被打断后捡局面缺「船上躺着谁」 | - -### §9 断言增量(R2 登记,已随 playtest.mjs 落地) -- F1:gd 行在播(stub 引擎在播)时搜索单击 → sonar_ping 立即起播且 gd 行被掐(不再入队迟到)。 -- F2:档位句(oxy_* dyn 注入)播放中单击不跳(beat 轻震);SURFACE(4)→g9_in 过场 800ms 内在途 tap 被吞、签收链完整。 -- F3:回波起播→解读句起播间隔 ≥ 对应 wait 常数量级(含静默一拍)。 -- F4:双载 D4 播 dv4_dual;单载/取消不播。 -- F5:SURFACE 的 crit 播报 = oxy_crit_deck;水下状态链 crit = oxy_crit。 -- F6:首播中叙事尾句单击不跳;链自然播完后经菜单复述重播,尾句单击即跳(复播可即掐)。 -- F7:full 档 COMMS 链末 13s→gd_continue、30s→gd_again(原 25s/50s 断言同步改 13s/30s)。 -- F8:D4 危机拍 danger 震间隔 ≥1000ms(实测 = 1100ms)。 -- F9:同档 crit 期间连续两次上滑状态链,warning 震仅一次。 -- F10:状态链含 prog_savedN 且 N=当前 savedCount;顺序 潜次→人数→氧档→帮助。 +四结局 + 氧气逐点、sonarGate 零重叠、教学承诺、结算死窗、pendingChoice:保留。删除双击回波重听与菜单双击确认。乱按 96s 必须被迷茫/三档救出,不得静默死局。 diff --git a/docs/design/18-detective-voice-ids.md b/docs/design/18-detective-voice-ids.md index b2a56a59..d45b09f5 100644 --- a/docs/design/18-detective-voice-ids.md +++ b/docs/design/18-detective-voice-ids.md @@ -1,5 +1,13 @@ # 18b · 《雨夜专案》语音台词表(voice ids,录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **R2 修订(2026-09-13)**:总句数 158→**171**(旁白基表 96→104、V 组键盘变体 10→14、NPC 52→53),上限裁定 160→176;改词 2 句(fin_who_confirm/tut_skip);新增 13 个录制单位(基表 8:fin_pre、st_wrong、recap_cf、gd_board_cf、item_who_w1..w4;NPC 1:z_react_w;V 组 4 对)。逐条修订理由见主设计文末「R2 修订记录」(research/new8/detective/round1/synthesis.md F1-F11)。 > **实测句长登记**(F1 配套):male/intro_1 8.80s、male/sys_help 9.28s、male/key/sys_help 10.48s、female/key/sys_help 9.52s 四句超 8s——游戏侧兜底已升 15s(§4.3),证词链全部 ≤7.4s 不受影响;如重录这 4 句请保持内容不变。 diff --git a/docs/design/18-detective.md b/docs/design/18-detective.md index ec4c6e0b..ec9eb01c 100644 --- a/docs/design/18-detective.md +++ b/docs/design/18-detective.md @@ -1,625 +1,169 @@ -# 18 · 《雨夜专案》完整设计(detective / Rainy Night Case) +# 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. 交互权威:26 号手册。听证词找矛盾,**不是**四向空间玩法:帮助=上滑。废除长按=指证、双击=重听当前句。 +> 2. 核心:案卷条目左右滑切换,单击听取/停止。指证改为案卷上的一项,滑到「指证」再单击进入。结案陈词仍有确认闸。 +> 3. 判定=两句同轴异值,与矛盾表单源。无惩罚性失败,案子保底必结。 +> 4. 台词 [18-detective-voice-ids.md](18-detective-voice-ids.md):旁白 104×2 + 键盘变体 14×2 + NPC 53 = **171 句**(上限 176)。 +> 5. 单局 15–25 分钟;seed/pack 可注入;VERDICT 800ms 死窗。 + +--- + +## 附录 C · 设计单 + +### 1. 一句话核心动作 + +**左右滑换卷宗,单击听取;滑到「指证」点一下,对比两句矛盾。** + +### 2. 玩家能感知的状态(3 个) + +| 状态 | 听感 | Floor | +|---|---|---| +| 听讲 | 证词/旁白,BGM 整链压低 | SPEAKING | +| 翻卷宗 | 条目签名 earcon + 等待声(空闲 BOARD) | YOUR_TURN | +| 指证/结案 | choice 签名 + 选项句 | YOUR_TURN | + +雨声烤进 BGM,不单开第四态。 + +### 3. 新手手势与解锁 + +| 手势 | 含义 | 解锁 | +|---|---|---| +| 单击 | 播放/停止当前条目;列表里确认;指证两段确认 | 阶段 0 | +| 左/右滑 | 换条目 / 换句 / 换结案选项 | 阶段 0 | +| 上滑 | 我在哪(已问几份。疑点几处。滑到指证或再听。) | 阶段 1(听完第一份证词后) | +| 下滑 | 菜单 | 阶段 2(结案后) | +| 双击 | 按单击(停止或再播)。阶段 3=重听当前句 | 阶段 3 | +| 长按 | 新手 invalid。播放中以后可加速 | 不绑指证 | + +BOARD 播放中左右滑:打断并切条目(滑动有明确意图),不「静默忽略」。 + +### 4. 状态 × 手势矩阵(新手期) + +| 状态 \ 手势 | 单击 | 左/右滑 | 上滑 | 下滑(未解锁) | 双击 | 连点 | +|---|---|---|---|---|---|---| +| 证词在播 | 停止 + ok | 打断,切条目 | 打断,我在哪 | invalid | 按单击=停止 | 400ms busy | +| 板上空闲 | 播放当前项;在「指证」=进 CONFRONT;在「结案」=进确认闸 | 换条目 + 签名+报名 | 我在哪 | invalid | 按单击=再播 | busy | +| 结案闸 | 进 FINISH | 滑走撤闸 | 我在哪 | invalid | 按单击 | busy | +| 指证选条目/句 | 确认进入下一阶段 | 预览下一项/句(每条目记忆上次位置) | 「正在指证。左右滑。」 | invalid | 按单击 | 迷茫 | +| 指证确认 | 定下这句 | 取消回预览 | 简状态 | invalid | 按单击 | busy | +| 结案三问 | 确认/答题(真凶两段确认,中性「就指认这位?」) | 换选项(真凶问播纯人名) | 重听问句 | invalid | 按单击 | busy | +| 裁决链 | 吞掉(死窗+链锁) | 吞 | 吞 | 吞 | 吞 | 吞 | +| 结算 | 接新案 | invalid | 评级一句 | 菜单(阶段2) | 按单击 | 第一下 | +| 菜单 | 确认 | 左右切项 | 重听说明 | 关闭 | 按单击 | busy | + +### 5. 等待点 + +| id | 期待 | full | short | 静默 | L2 | L3 | +|---|---|---|---|---|---|---| +| intro | tap | 「点一下屏幕。」 | 「点一下。」 | 6s/15s | tap | 替点 | +| board | tap 或 L/R | 「左右滑换卷宗。单击听取。」 | 「左右滑。」 | 18s/40s | L/R | 替滑到下一份 | +| listen | tap 停或听完 | (不催听) | 「点一下,停。」 | 播放中不静默惩罚 | — | — | +| confront_item | L/R+tap | 「左右滑,选一份证词。」 | 「左右滑。」 | 15s/30s | L/R | 演示选第一份 | +| confront_line | L/R+tap | 「单击,定下这句。」 | 「单击。」 | 15s/30s | tap | 替确认 | +| finish | L/R+tap | 当前问句 | 「左右滑,选。」 | 20s/45s | L/R | 演示正确项(保底) | +| settle | tap | 「单击,接新案。」 | 「单击。」 | 20s/45s | tap | 提示音 | + +无进展:疑点 0 且 90s → L1 暗示矛盾轴 → L2 缩小到可指证条目 → L3 直接给出一对可指证句(保底必结)。 + +### 6. 首 3 分钟 + +| 时间 | 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 雨声 →「点一下屏幕。」 | 单击 | 「点一下。」 | 滑:invalid | +| 3~8s | 极短案情一句 → 签名+「老葛的证词」→「单击,听取。」 | 单击 | 「单击。」 | | +| 8~10s | 老葛真实首句(第一次成功=听到证词) | 听 | | | +| 10~25s | 听完或点停 →「向右滑,下一份。」 | 右滑 | 「向右滑。」 | 点=再听 | +| 25~50s | 再听一份 →「想知道查到哪了,向上滑。」 | 上滑 | 「向上滑。」 | | +| 50~90s | 「滑到『指证』,点一下。」 | 滑+点 | 短句 | 练习指证不计入 miss | +| 90~180s | 自由听证;练习判定一律 tut_cf_ok | 查 | 接管 | 零惩罚 | + +### 7. 失败表 + +| 类型 | 判定 | 归因 | 回到 | 新手期 | +|---|---|---|---|---| +| 指证同句 | a.id=b.id | cf_self | 板 | 无 | +| 不同轴 | 无共享轴 | cf_diff「说的不是一件事」 | 板 | 无 | +| 同轴同值 | | cf_same「说得一致」 | 板 | 无 | +| 指认错人 | | 该证人 alibi + fin_clear + st_wrong | 板,无限再试 | 无 | +| 凶器/动机错 | | fin_wp_wrong / fin_mv_wrong | 下一问,不重答 | 记账评级 | +| 迷茫 | | 保底提示矛盾对 | 板 | 无 | + +禁止「再想想」无信息归因。 + +### 8. 台词类别 + +- `critical`:开场点一下;fin_pre 结案闸 +- `content`:intro、证词 NPC、现场/法医、z_break_*、rate_* +- `hint`:tut_*(改词)、hint_c*、st_asked/st_doubt、lp 改为 up 帮助句 +- `feedback`:cf_hit/miss/same/diff/self、acc_*、fin_wp_wrong +- 停用:tut_replay「双击」、tut_confront「按住屏幕指证」、tut_skip 旧词。新 tut_confront:「滑到指证,点一下。」 + +171 句账目不变(改词重录,尽量不增条)。 + +### 9. 断点复述 + +保存 heard/doubts/pack/seed/layout。回来 sys_continue_ask + 上滑句(已问 X。疑点 n。左右滑。)VERDICT/SETTLE 链锁至 settle_hook。 + +### 10. 测试记录 + +改:长按不进指证;双击不重听句;菜单左右+单击;3s 内点屏;10s 内听到第一句证词。矩阵不变式仍失败即不通过。 --- ## 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(无操作轨+无进展轨);指证菜单是新交互增量(两级选择+两段确认),变体案包是内容增量。 +专案组探长,雨夜命案。翻六份卷宗加指证入口,比对证词,指出矛盾,三问结案。纯音频:证词天生是「说」的信息;证人=声音演技+签名 earcon。 -## 2. 手势映射与豁免登记 +## 2. 状态机 -### 2.1 证词板(BOARD,主循环) +`TITLE / DRILL / BOARD / CONFRONT / FINISH / VERDICT / SETTLE` + MENU。 -BOARD 分**空闲**(无语音在播)与**播放**(条目链播中)两个子行为: +``` +BOARD ⇄ 条目链播 + │ 滑到「指证」+单击 → CONFRONT → 判定回 BOARD + │ 滑到「结案陈词」+单击 → 闸 → FINISH 三问 +FINISH 错人 → alibi → BOARD;全对 → VERDICT → SETTLE +``` -| 手势 | BOARD 空闲 | BOARD 播放 | -|---|---|---| -| 左滑 | 上一条目(循环):签名 earcon + 条目名 | 静默忽略(纯聆听保护,simon playback 先例) | -| 右滑 | 下一条目(循环):签名 earcon + 条目名 | 静默忽略 | -| 单击 | 播放当前条目(从首个未听句起,听过的整体重听);**停在「结案陈词」时=进确认闸(fin_pre),闸内再单击才进 FINISH,滑走撤闸留板(R2 F5)** | **停止播报**,回空闲 | -| 双击 | 重听当前条目最后一句(闸内=重听闸句 fin_pre) | **重听当前句**(≤8s,免费零消耗) | -| 上滑 | 状态链(§2.5) | 状态链(打断证词;双击可重听当前句) | -| 下滑 | 模态菜单(§2.4) | 模态菜单(打开即停播报) | -| 长按 | **进入指证**(CONFRONT;仅空闲受理) | 引擎快进(旁白 1.7×,保持默认,不豁免) | -| 长按松开 | — | 恢复常速 | +条目:老葛 → 小顺 → 周先生 → 温太太 → 现场 → 法医 → **指证** → 结案陈词。句间 0.8s;单句资产缺失 15s 兜底续链。 -条目共 7 项:老葛证词 → 小顺证词 → 周先生证词 → 温太太证词 → 现场勘验 → 法医报告 → 结案陈词。每项播报=专属签名 earcon(0.3-0.5s,§8.2)+ 条目名整句;切到「结案陈词」+单击 → **确认闸(fin_pre「证据在手,就此结案?单击,定下来;滑走,继续查。」),闸内再单击 → FINISH**(R2 F5:循环列表必经站,单击直坠三问的误闯代价太大——R1 压力线实测误闯 6-7 次)。 +CONFRONT:选条目 → 选句(记忆上次位置)→ 确认 → 第二份同样 → judge。菜单可「返回证词板」。 -### 2.2 指证(CONFRONT)与结案(FINISH)——同一导航范式 +## 3. 矛盾与判定 -两态统一为「列表预览 + 两段确认」非模态菜单(00 §5.2 非模态约定,rpg 探索同款),**双击全局语义=重听当前句,不偏离**: +pack A《雨夜·聚宝斋》:古董商沈万川后院柴房后脑钝器。矛盾四轴 lamp / key / weapon / scale: -| 手势 | 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 | +| # | 轴 | A | B | |---|---|---|---| -| 单击 | 继续(有档)/ 新案(无档) | **吞掉**(手势死窗+链锁,§7.4) | 接新案(pack 翻转+新 seed) | -| 右滑 | 换一桩新案(重掷 seed/pack,meta 保留,run 清空) | 吞掉 | error | -| 双击 | 重听 continue_ask | 吞掉 | 重听结算链 | -| 上滑 | 重听案情简介(intro_1..3) | 吞掉 | 重听评级段 | -| 下滑 | 菜单 | 吞掉 | 菜单 | -| 左滑/长按 | error | 吞掉 | error | +| M1 | lamp | z_2 熄灯 | wen_2 十点二十灯亮 | +| M2 | key | z_3 贴身 | w2_3 挂铜钩 | +| M3 | weapon | z_4 松木 | fo_3 铜屑 | +| M4 | scale | z_4 秤已卖 | sc_4 秤在架上 | -### 2.4 模态菜单(遵守 00 文档 §5.2 全球约定,无豁免) +judge:同句 cf_self;无共享轴 cf_diff;同轴异值且在表内 hit(反应按**轴**取句,非按次数);同轴同值 cf_same。无序。hit 后 doubts+1;4/4 播 hint_fin。 -下滑呼出(同时停播报/停看门狗)→ 单击循环切换(每切播项名)→ 双击确认 → 上滑=帮助 → 下滑=关闭。选项 5 项:返回证词板 / 去指证(=长按捷径的菜单冗余入口,P4 容错)/ 保存并回大厅 / 重新听帮助 / 重新查这桩案子。本作无实时玩法,菜单=天然暂停。 +pack B 同模板,真凶小顺,fork 句替换,矩阵同构。 -### 2.5 每状态手势映射总表(6 手势 + 长按松开,全状态覆盖) +## 4. 结案与评级 -| 手势 | 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 已实现)。 +| 档 | 条件 | +|---|---| +| S | doubts=4 ∧ 凶器动机对 ∧ wrongWho=0 ∧ cfMiss≤2 ∧ hints=0 | +| A | doubts≥3 ∧ 两问对 ∧ wrongWho≤1 ∧ hints≤1 | +| B | doubts≥2 ∧ wrongWho≤2 | +| C | 其余(必结) | -### 2.6 豁免登记(必须显式) +SETTLE:settle_open → rate_X → st_doubt_n → st_miss 档 → settle_hook。 -| # | 豁免条目 | 理由(先例) | -|---|---|---| -| E1 | BOARD 单击=播放/停止(非"确认") | "检查/翻阅"类非即时操作,300ms tap 延迟无碍(escape 单击=检查、dungeon 单击=脉冲同款);停止播报是纯聆听期的标准出口 | -| E2 | BOARD 空闲长按=进入指证(核心动词快捷入口) | 绑定条件=无语音在播(`!audio.isVoicePlaying()`),与长按快进(仅在播时生效)**在时间上互斥、零冲突**,故不需 dungeon E3 式的对局期豁免;播放中长按仍走引擎快进。教学 D5 讲清(tut_confront),菜单另有「去指证」冗余入口 | +VERDICT:fin_right → 雨声 swell → z_break_1..3 → gavel → SETTLE。800ms 死窗+链锁。 -教学(DRILL)逐步讲清 E1/E2;无 immediateTap;模态菜单无豁免。 +## 5. 音频 -## 3. 核心动词:听证词找矛盾(信息设计) +BGM:menu / board / verdict(雨烤进曲)。SFX:签名 earcon×证人、翻页、法槌、雨 swell 等约 12。NPC 5 speaker;周先生与男旁白同 speaker、秦法医与女旁白同 speaker——靠签名+语境,相邻句无旁白插话。 -### 3.1 信息结构总览 +## 6. 存档与 seed -``` -案卷(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 文件。 +mulberry32;pack 首局 0,结案后翻转。洗牌只动 filler 与选项序。summary 含疑点/评级。矩阵不变式:同轴异值对 ≡ 矛盾表。 diff --git a/docs/design/19-cipher-voice-ids.md b/docs/design/19-cipher-voice-ids.md index ce570344..be798daf 100644 --- a/docs/design/19-cipher-voice-ids.md +++ b/docs/design/19-cipher-voice-ids.md @@ -1,5 +1,13 @@ # 19b · 《暗码》(cipher) 语音台词表(voice ids,录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - **双性别组**(报务员 OP + 北风电台 WIND,134 句):落 `web/cipher/audio/voice/female|male/.{ogg,m4a}`,两性别 id 集合完全一致(含全部 `_k` 变体)。 > - **OP(报务员=玩家)**:female = `Serena`,instruct 常量 `压低而冷静的年轻女声,报务员,值夜班,克制的紧张`;male = `Uncle_Fu`,instruct 常量 `压低而冷静的男声,报务员,值夜班,克制的紧张`。 diff --git a/docs/design/19-cipher.md b/docs/design/19-cipher.md index 53853dca..963d06c3 100644 --- a/docs/design/19-cipher.md +++ b/docs/design/19-cipher.md @@ -1,568 +1,154 @@ -# 19 · 《暗码》(cipher) — 1940 密战叙事 RPG 设计文档 +# 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//_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「译错归因真话」 | +> 1. 交互权威:26 号手册。叙事+解码小局,**不**用四向滑动,帮助=上滑。废除 DECODE 双击重听/慢放、长按翻密码本。 +> 2. 摩尔斯由运行时 `morse.js` 合成(不走有损文件)。左右滑预听候选即对照电文——新手靠这一层听辨,不必先学双击。 +> 3. 暴露度隐藏资源;教学封 T1 译错即时归因+无限重选;正片译错不揭穿,尾声兑现。 +> 4. 台词 [19-cipher-voice-ids.md](19-cipher-voice-ids.md)(139 句:双性别 134 + NPC 5)。键盘变体 11 对。 +> 5. 章程 N3:15–25 分钟、9 抉择(4 译词+5 叙事)、4 结局。 --- -## 修订记录(开发/测试轮裁定,追加不改旧文) +## 附录 C · 设计单 -| # | 裁定 | 依据与影响 | +### 1. 一句话核心动作 + +**听电文,左右滑对照候选词,单击选定;叙事段单击继续、左右滑处置。** + +### 2. 玩家能感知的状态(3 个) + +| 状态 | 听感 | Floor | |---|---|---| -| R1 | **SFX 全 SYN 兜底**:§6.2 原标 SA3 的 10 件(tune_seek/static_bed/amb_room/paper_page/key_send/pen_scratch/tea_cup/step_halt/lock_click/dawn_bell)改程序合成(`tools/gen_cipher_syn.py`),仅 ui_confirm/ui_menu 按原计划 SYN | 与摩尔斯文件方案同型的短音 ms 抖动风险 + 管线一致 + 零成本;one-shot 真峰值 ≤-3dBFS 全过(实测 -3.3~-5.1dBFS),loop 2 件首尾 50ms 环形交叉淡化无缝(seam ≤0.01)。原 SA3 prompt 保留作听感设计参考。 | -| R2 | **p8_blind / p9_blind 条件笔误**:19-cipher-voice-ids.md 表内「条件 t4≠top」实为 **t4≠spy** | §4.2 抉择树(p8/p9 由 T4 译文 known/blind 差分)与 §9 断言均为 t4 维度;top 是 T2 的真词,显系复制笔误。实现按 t4==='spy' 选播。 | -| R3 | **§5.5 剧本 C 检查点算术以 §5.2 事件单源表为准**:NIGHT(3) 后 expose=**5(hot 档)**而非 4/mid;签收前 8 ✓ | 单源表:T2-T4 译错各 +1、p6 左 +2。2+1+2=5 恰好达收网门(≥5),「跨档播报 3 次」表述作废,实为 **2 次**(calm→low 于 NIGHT(2)、low→hot 于 NIGHT(3),签收前 8 与 5 同档不重复播)。§9 断言按修正值实现(hot 档提前一夜可视化压力,戏剧效果不降反升)。 | -| R4 | **§5.5 剧本 A「全对全右」为措辞笔误**:操作序以 §9 关键路径为准(p2 左 · p4 右 · p6 右 · p8 右 · p9 左) | §5.5 自身给出的 risk=0 与 ep 点名组合(med_ok/list_no/hand_ok/spy_ok)只有该路径自洽;「全右」若按字面 p2 右则 risk=1、ep_med_no,矛盾。验算 expose=0 → e1 不受影响。 | -| R5 | **菜单低响度实现路径**:§6.1「menu 用同曲低响度」经游戏层 `_bgmBus` 增益 0.4(与摩尔斯 −6dB dip 乘法复合) | 引擎约定「同一首重复 play 不重启」,play(opts.volume) 无法作用于在播 BGM;省一首成立(无 menu 专属曲目)。 | -| R6 | **d9_sleep 自动链**(radio d9_face 同型) | 关机收束节奏与发报线对齐;p9 右的收束不因链末等待拖沓。 | -| R7 | **transcribe.mjs CLASS_NAME 追加 cipher 行**(CipherGame) | 共享 harness 接线(check_all_assets 之外唯一跨游戏触碰点),hunt/teahouse 等泳道同款需求各自追加。 | +| 听讲 | 旁白/处长,BGM 整链压低 | SPEAKING | +| 译电 | 摩尔斯(750Hz 电文 / 620Hz 候选)+ choice 签名 + 等待声 | YOUR_TURN | +| 轮到你(叙事) | turn + 等待声 | YOUR_TURN | -| R8 | **R2 修复三件套**(round1 synthesis F1-F3):① NIGHT 链首按上一封抉择垫纸面标记音(p2右/p4左→paper_page,p4右→pen_scratch,现有 sfx 零新增)——「世界因你而动」可感知化;② book_1(密码本只剩最后一页)各播报位到句附 warning 震(F2);③ 结算链尾新增结局计数分档整句 end_c1..end_c4(F3,+4 句 WIND,见 19b R2 增补) | 均不改树形/不加抉择点/不增人设;断言入 §9 清单(F1 纸面音有/无双向断言、F2 warning 震、F3 计数句)。 | +静电底噪是场景,不单开第四态。 + +### 3. 新手手势与解锁 + +| 手势 | 含义 | 解锁 | +|---|---|---| +| 单击 | 继续;DECODE 浏览=进确认;确认=定下 | 阶段 0 | +| 左/右滑 | 叙事抉择;DECODE=循环候选(自动预听对照档) | 阶段 0 | +| 上滑 | 我在哪(第几封。密码本余页。暴露档。左右滑听词。) | 阶段 1(T1 译对后) | +| 下滑 | 菜单(含「翻密码本」) | 阶段 2(第一局结束) | +| 双击 | 按单击。阶段 3=重听电文原速;再请求=慢放 0.5× | 阶段 3 | +| 长按 | 新手 invalid。之后旁白加速。**不**翻密码本 | — | + +密码本:阶段 2 菜单项;迷茫 L3 也可演示慢放+揭示辨析位(教学 T1 首次翻页仍免费,由系统在 T1 迷茫或菜单未解锁前自动给一页教学)。 + +### 4. 状态 × 手势矩阵(新手期) + +| 状态 \ 手势 | 单击 | 左/右滑 | 上滑 | 下滑(未解锁) | 双击 | 连点 | +|---|---|---|---|---|---|---| +| 游戏在说 | 跳句(摩尔斯首播/p* 不可跳 → busy) | 缓存抉择 / DECODE 首播中 busy 轻声 | 我在哪 | invalid | 按单击 | 400ms busy | +| RX 首播摩尔斯 | busy | busy | 我在哪 | invalid | 按单击 | busy | +| DECODE 浏览 | 进确认(电文原速再播一遍) | 切候选:620Hz 摩尔斯 → 静默一拍 → 词义句 | 状态链 | invalid | 按单击 | 迷茫 | +| DECODE 确认 | 定下(800ms 死窗→揭晓) | 取消回浏览 | 状态链 | invalid | 按单击 | busy | +| 叙事等选择 | 再听选项 | 抉择 | 「左右滑。」 | invalid | 按单击 | 迷茫 | +| 结算 | 新夜班 | invalid | 结局+档 | 菜单(阶段2) | 按单击 | 第一下 | +| 菜单 | 确认(可消耗一页密码本) | 切项 | 重听说明 | 关闭 | 按单击 | busy | + +摩尔斯不叠语音(morseGate)。BGM 摩尔斯期间 −6dB。u 恒定,不受 settings.speed。 + +### 5. 等待点 + +| id | 期待 | full | short | 静默 | L2 | L3 | +|---|---|---|---|---|---|---| +| intro | tap | 「点一下屏幕。」 | 「点一下。」 | 6s/15s | tap | 替点 | +| decode | L/R | 「左右滑,听候选词。」 | 「左右滑。」 | 10s/25s | L/R | 慢放电文一遍再交还 | +| confirm | tap | 「单击,定下这个词。」 | 「单击。」 | 10s/20s | tap | 替确认 | +| choice | L/R | pN | 「左右滑,选。」 | 25s/50s | L/R | 演示 | +| continue | tap | 「单击,继续。」 | 「单击。」 | 13s/30s | tap | 替继续 | + +T1 译错不进失败终局:即时 dv1_wrong(摩尔斯写死在句里)+ 回浏览。正片译错不揭穿。 + +### 6. 首 3 分钟 + +| 时间 | 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 雨+静电 →「点一下屏幕。」 | 单击 | 「点一下。」 | 滑 invalid | +| 3~8s | 调谐 → BAY 摩尔斯首播(不可跳) | 听 | | | +| 8~10s | turn →「向右滑,听下一个词。」 | 右滑 | 「向右滑。」 | 点太早进确认:可左滑取消 | +| 10~25s | 候选对照 → 滑到 BAY → 单击→再单击定下 | 滑+点 | L3 慢放+揭示 B | DAY/RAY:即时归因重选 | +| ≈25s | 「对。」dv1_right(第一次成功) | | | | +| 25~60s | p2 报北风/压日志 | 左右滑 | 复述 | 不盲选 | +| 60~180s | T2 开始;上滑已解锁 | 译词 | 三档 | 正片错译走分支 | + +### 7. 失败表 + +| 类型 | 判定 | 归因 | 回到 | 新手期 | +|---|---|---|---|---| +| T1 译错 | 非 BAY | 「不对。B,一长三短。」 | 浏览,无限 | 暴露 +0 | +| 正片译错 | 非真词 | 不揭穿;揭晓按认知 | 处置链 | 暴露 +1 | +| 页数用尽再翻 | hints=0 | paper_page + invalid,无语音轰炸 | 原地 | — | +| 收网 | p9 前 expose≥5 且发报 | d9_caught 真话 | 结局 e3 | 软失败 | +| 迷茫 | | L3 慢放 | 浏览 | 无 | + +### 8. 台词类别 + +- `critical`:开场点一下;T1 教学「左右滑,听词」 +- `content`:rx/dv/rev/r*/n*/p*/e*/ep_* +- `hint`:gd_tune 改为不含双击;book_3..0;exp 四档;hint_* 密码本 +- `feedback`:dv1_wrong、inv_* +- 停用:gd_tune2「双击再听/再双击放慢」、E1 长按翻本口令 + +139 句;音频 134×2+5=273 ×2 格式。摩尔斯零文件。 + +### 9. 断点复述 + +保存 expose/hints/t1..t4/p flags。回来 sys_continue_ask。DECODE 恢复:重挂浏览并自动预听当前候选。上滑:第 N 封。余页。暴露档。左右滑。 + +### 10. 测试记录 + +改:双击不慢放;长按不扣页;菜单确认翻页;3s 动手;10s 内完成一次候选预听。morse 事件钩子字符断言保留。剧本 E 提示流改走菜单或 L3。 + +--- + +## 1. 概述 + +1940 江城,电讯处夜班报务员,也是「北风」的耳朵。四封密电:怎么译、报不报、报给谁。压力=隐藏怀疑账本+认知风险(译错是另一条真剧情)。无倒计时、无死亡。 + +## 2. 摩尔斯小局 + +| 参数 | 值 | +|---|---| +| u 原速 | 80ms(≈15 WPM),不随语速 | +| 慢放 | u=160ms,阶段 3 或 L3 | +| 对照档 | u=110ms,切候选自动播 | +| 音色 | 电文 750Hz / 候选 620Hz,正弦+5ms 边沿 | + +| 封 | 真词 | 候选 | 辨析位 | 提示 | +|---|---|---|---|---| +| T1 船期 | BAY | DAY/RAY | 1: B/−··· | 第 1 字母 B,一长三短 | +| T2 名单 | TOP | TAP/TIP | 2: O/−−− | 第 2 字母 O,三长 | +| T3 交接 | HAND | SAND/LAND | 1: H/···· | 第 1 字母 H,四短 | +| T4 处置令 | SPY | SAY/SLY | 2: P/·−−· | 第 2 字母 P,短两长短 | + +提示揭示辨析位 ⟹ 候选唯一。密码本 3 页,T1 教学首次不扣;用尽只翻空纸声。 + +流:RX 引入+调谐+电文首播不可跳 → 浏览(滑=预听)→ 单击确认再播电文 → 再单击定下。 + +## 3. 状态机与树 + +INTRO → RX(1) → DECODE(1) → RELAY(1) → NIGHT → … → RX(4) → 签收 p9 → ENDING。 + +T1 恒收敛真词。T2–T4 译错不揭穿,p4/p8/p9 走 known/blind 差分。p6 亲自去 expose+2;p8 反喂 +2;p9 左发报结局用 p9 前暴露。 + +| 结局 | p9 前 expose | p9 | +|---|---|---| +| e1 最后一电 | ≤4 | 左 | +| e2 值完这个班 | ≤4 | 右 | +| e3 孤灯 | ≥5 | 左 | +| e4 长夜 | ≥5 | 右 | + +暴露事件:T2–T4 译错 +1/封;p2 右 +1;p4 左 +1;p6 左 +2;p8 左 +2。档:0 calm / 1–2 low / 3–4 mid / ≥5 hot(=收网门)。跨档才播。 + +## 4. 音频 + +BGM:radio_room / closing(T4 起)/ night / ending。SFX:tune_seek、static_bed、amb_room、paper_page、key_send、pen_scratch、tea_cup、step_halt(前置 2s)、lock_click、dawn_bell。处长 Eric 平铺 5 句。快手七无台词,存在=手法音。 + +## 5. 存档与验算 + +剧本 A 全对全右 p9 左 → e1 expose=0;C 全错全左 → e3。边界 wrong=2+risk=2=4 仍 e1/e2。summary:`第N封 · 暴露档 · 余页K · 结局X/4`。 diff --git a/docs/design/20-teahouse-voice-ids.md b/docs/design/20-teahouse-voice-ids.md index 1593ddc7..02ccb376 100644 --- a/docs/design/20-teahouse-voice-ids.md +++ b/docs/design/20-teahouse-voice-ids.md @@ -1,5 +1,13 @@ # 20b · 《深夜茶馆》语音台词表(voice ids,录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - **旁白双性别组**(98 句):旁白只做系统/指引/教学/状态/账目/氛围客人转述,**不念客人台词**。落 `web/teahouse/audio/voice/female|male/.{ogg,m4a}`,两性别 id 集合完全一致。 > - female = `Serena`,instruct 常量:`温润平和的女声,深夜茶馆掌柜,从容安稳,像守着一炉将开的水` diff --git a/docs/design/20-teahouse.md b/docs/design/20-teahouse.md index 0d091555..037d12e4 100644 --- a/docs/design/20-teahouse.md +++ b/docs/design/20-teahouse.md @@ -1,540 +1,151 @@ -# 20 · 《深夜茶馆》完整设计(teahouse / Late Night Teahouse) +# 20 · 《深夜茶馆》(teahouse / Late Night Teahouse) > **给开发者的实现要点摘要** -> 1. 经营养成 RPG:核心动词=**泡茶**。一晚=3-5 位客人(本文定 4)≈13-17 分钟=单局;每位客人 2-4 分钟完整小循环(P3 三分钟颗粒)。泡茶小局三步:**左右滑选茶+单击选定 → 听水声双击提壶(候汤)→ 听滴答双击出汤(闷泡)**——时机步绑双击(即时,规避单击 300ms 延迟,dungeon 格挡先例),选茶步零豁免。 -> 2. 手势宪法三件套全状态保留(上滑状态/下滑菜单/双击重听);**唯一豁免 E1=时机步双击≠重听**(重听职责由上滑状态链第一段「重播客人点单句」承接),E2=链播中单击=跳句(12 号既有口径),E3=BREW 态关长按快进。教学讲清。 -> 3. **听觉分层铁律**(本作最大风险):注水/沸腾/滴答三重音**时间上串行不重叠**,任意时刻≤(1 条主操作音+BGM)=2 声流;语音只出现在步与步之间的静默点;滴答只在闷泡最后 ~2.5s 关键窗;BREW 时机窗内看门狗只发 earcon 不发语音。 -> 4. 客人=声音群像:5 位弧线客人 NPC 平铺(5 音色,2 处与旁白同 speaker 已标风险)+3 位氛围客人**旁白转述零 NPC 句**。弧线 3 晚×3 方向(好/普通/错茶),结局按方向累计值三选一——**全部从数据单源推导**(点单句→茶参数→时机判定→三档结果→方向值),播报值=运行值。 -> 5. 经济账目真实(radio rep 教训加倍):口碑/收入/房租/欠账每一档播报与累积值一一映射;错茶=免单(收入 0、口碑−1,无其他惩罚);房租不足=「贴钱记账」剧情挫折,**无 game over**。§7.5 有完整 5 晚剧本逐晚验算。 -> 6. 随机性:N1-N5 剧本夜**固定客序**(连续剧结构);seed PRNG(mulberry32)只管 N6+ 氛围客轮换/口味与提示轮询。`opts.seed` 注入可复现。 -> 7. 台词表 [20-teahouse-voice-ids.md](20-teahouse-voice-ids.md):旁白 98 ×2 性别 + NPC 平铺 65 = **163 句 ≤170 上限**;操作提示句键盘变体 `_k` 集中登记(V 组,`gesture.inputMode()` 选播)。CLOSE 结算链 800ms 手势死窗+链锁(dungeon H1 / detective VERDICT 同款)。 -> 8. 规模对标 detective:单文件 game.js 预计 900-1100 行;bgm 3 + sfx ~26 + voice 261 条(×2 格式=522 文件)。断言清单住本文档 §11(13 号文档 §20/§21 先例登记)。 +> 1. 交互权威:26 号手册。经营+时机小局,**不**用四向滑动,帮助=上滑。废除时机步双击=提壶/出汤(新手 `doubleTapWindow:0`,单击已即时)。 +> 2. 泡茶三步:左右滑选茶+单击选定 → 听水声**单击**提壶 → 听滴答**单击**出汤。选茶步单击仍=选定。 +> 3. 听觉分层:注水/沸腾/滴答时间上串行,时机窗内禁止向导语音(只 earcon/等待声)。 +> 4. 台词 [20-teahouse-voice-ids.md](20-teahouse-voice-ids.md):旁白 98×2 + NPC 65 = **163 句**。CLOSE 800ms 死窗。 +> 5. 一晚 4 客 13–17 分钟;无 game over;账目播报=运行值。 + +--- + +## 附录 C · 设计单 + +### 1. 一句话核心动作 + +**左右滑选茶,单击选定;听到水声和滴答时再单击提壶、出汤。** + +### 2. 玩家能感知的状态(3 个) + +| 状态 | 听感 | Floor | +|---|---|---| +| 听讲 | 客人/旁白,BGM serve 压低 | SPEAKING | +| 选茶 | tea_jar + 茶名句 + 等待声 | YOUR_TURN | +| 候汤/闷泡 | 水声时间线或滴答(玩法声=状态) | ACTION | + +打烊账目走 settle earcon,不占新手三态。 + +### 3. 新手手势与解锁 + +| 手势 | 含义 | 解锁 | +|---|---|---| +| 单击 | 继续/选定茶;候汤=提壶;闷泡=出汤;跳过可跳句 | 阶段 0 | +| 左/右滑 | 换茶罐(仅 TEA) | 阶段 0 | +| 上滑 | 我在哪(第一段=重播点单句) | 阶段 1(第一盏茶送出后) | +| 下滑 | 菜单(冻结计时) | 阶段 2(第一晚打烊后);OPENING 教学期下滑不跳过整晚,见等待点 | +| 双击 | 按单击(时机步也是提壶/出汤,不另绑) | 阶段 3 起可重听点单/茶名 | +| 长按 | 新手 invalid。SERVE 链播以后可加速。BREW 全态关加速 | — | + +教学跳过:仅 TITLE/OPENING 未入泡茶前,用帮助长句而非下滑(新手下滑未解锁)。已 drillDone 则开场「点一下开始」。 + +### 4. 状态 × 手势矩阵(新手期) + +| 状态 \ 手势 | 单击 | 左/右滑 | 上滑 | 下滑(未解锁) | 双击 | 连点 | +|---|---|---|---|---|---|---| +| 堂前在说 | 跳句(res_*/rent_* 不可跳 → busy) | invalid | 打断,状态(点单可重听) | invalid | 按单击 | 400ms busy | +| 选茶 TEA | 选定 → 候汤 | 换罐 + tea_jar + 口诀句 | 重播点单 | invalid | 按单击 | busy | +| 候汤 WATER | **提壶**(时机判定) | invalid(不判错步) | 状态;水声照走 | invalid | 按单击=提壶 | 窗内只第一下 | +| 闷泡 STEEP | **出汤** | invalid(不判错步) | 状态;时间线照走 | invalid | 按单击=出汤 | 只第一下 | +| 打烊链内 | 吞掉 | 吞 | 吞 | 吞 | 吞 | 吞 | +| 打烊链完 | 睡下 | invalid | 账目链 | 菜单(阶段2,含置办) | 按单击 | 第一下 | +| 菜单 | 确认 | 切项 | 重听说明 | 关闭 | 按单击 | busy | + +时机窗内看门狗只 earcon,不插语音(分层铁律)。 + +### 5. 等待点 + +| id | 期待 | full | short | 静默 | L2 | L3 | +|---|---|---|---|---|---|---| +| intro | tap | 「点一下屏幕。」 | 「点一下。」 | 6s/15s | tap | 替点 | +| tea | L/R 或 tap | 「左右滑选茶。点一下定。」 | 「左右滑。」 | 12s/30s | L/R | 替选点单那罐 | +| water | tap | 「听到细密泡,点一下。」 | 「点一下。」 | 窗内只 earcon | tap | 替提壶(将沸) | +| steep | tap | 「滴答密了,点一下。」 | 「点一下。」 | 只 earcon | tap | 替出汤 | +| close | tap | 「单击,睡下。」 | 「单击。」 | 15s/35s | tap | 提示音 | + +做错时机不终止:early/late 真话归因后继续。无出手到点自动过老/自动出汤。 + +### 6. 首 3 分钟 + +| 时间 | 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 点灯 →「点一下屏幕。」 | 单击 | 「点一下。」 | 滑 invalid | +| 3~8s | 门铃+老周点龙井 →「向右滑,听听茶。」 | 右滑 | 「向右滑。」 | 点太早:若已在龙井则选定也算过 | +| 8~10s | 茶名口诀「将沸就提」+ 选定舀茶(第一次成功:选对罐) | 单击选定 | 「点一下定。」 | 错茶:本晚教学仍走完,免单真话 | +| 10~25s | 水声时间线 → 细密泡 → 单击提壶 | 单击 | earcon;超时过老仍继续 | 滑:invalid 不判错茶 | +| 25~50s | 滴答密 → 单击出汤 → 好茶反应 | 单击 | 自动出汤 | 偏了真话+继续 | +| 50~90s | 「客人要的茶,向上滑再听。」 | 上滑 | 「向上滑。」 | | +| 90~180s | 邮差等第 2 客,自由营业 | 泡茶 | 接管 L3 替打 | 错茶免单零死亡 | + +首晚窗口 ×1.3,教学客明示龙井。 + +### 7. 失败表 + +| 类型 | 判定 | 归因 | 回到 | 新手期 | +|---|---|---|---|---| +| 错茶 | 茶≠点单 | res_wrong + 免单 | 反应链 | 口碑 −1,无其他罚 | +| 水早/老 | 窗外提壶 | water_early/late | 闷泡 | 茶对则最多普通档 | +| 闷早/苦 | 窗外出汤 | steep_early/late | 上茶 | 同上 | +| 房租不足 | purse<20 | rent_short + danger | 记账 debt | 无 game over | +| 时机步滑动 | | invalid 一次解释 | 原地 | 不判错步 | +| 迷茫 | | L3 替选/替提/替出 | 下一步 | 无 | + +### 8. 台词类别 + +- `critical`:开场点一下;tut 选茶/提壶/出汤各一句 ≤3s +- `content`:night_*、客人进出/点单/反应/结局、close_*、upg_* +- `hint`:hint_water(改「点一下」)、st_guest/rep/purse +- `feedback`:water_*/steep_*、res_wrong、rent_short、inv_* +- 停用:tut_water/steep 里的「双击」 + +163 句;音频 98×2+65。bgm 3 + sfx ~26。 + +### 9. 断点复述 + +每位客人结算后存档。回来:第 N 晚。下一位客人。上滑听点单。summary 含口碑档/存款/欠账。 + +### 10. 测试记录 + +改:WATER/STEEP 单击判定;双击不再是唯一时机键;菜单左右+确认。分层断言:水声/滴答窗内 voice 事件=0。剧本 A/B 账目对账保留。 --- ## 1. 概述 -- **一句话**:你接手巷子深处一家亮到后半夜的小茶馆,客人的故事随你泡的茶展开——泡对了,暖的是人心;泡错了,故事照样走,只是方向不同。 -- **题材**:经营养成。核心命题「茶是热的,夜是长的」——纯音频天然适合:茶的好坏本来就只能靠听(水声、滴答)与「尝」(客人的声音反应)来感知;经营账目是规整的听觉播报链;客人群像是本作最大资产(8 位客人=8 种声音人生)。 -- **玩法定位**(方法论 §4.1 无现成行,状态层重点自定):**状态即节拍**(一晚=固定客序推进,永不迷失「接下来干嘛」)+ **听觉判定**(时机步靠耳朵)+ **账目真实**(经济播报与累积值单源)。 -- **单局=一晚**:4 位客人,13-17 分钟(首晚含教学 17-20);章程 15-25 分钟区间达标。**一章=5 晚**(全部弧线收束);每客人 2-4 分钟= P3 自然切分颗粒,任意客人结算后存档退出、回来一句话接上。 -- **无死亡无惩罚**:泡错茶=客人免单+吐槽+故事走涩向;房租不足=秦婶记账(贴钱营业),永远没有 game over。轻压力=经济账每晚都要对(口碑档/收入档/欠账)。 -- **与现有 10 游戏的关系**:状态档位链承接 dungeon(上滑多段链);双轨看门狗承接 dungeon E0-1/E0-2;NPC 群像+签名 earcon 承接 detective(sig_* 前缀);时机判定承接 rpg 格挡(双击+前摇窗口);「声音演技弧线」承接 radio(多晚连续剧);经营元进度为本作新增系统(8 款新模式探索点)。 +巷子深处后半夜小茶馆。泡对了暖人心,泡错了故事走涩向,但夜还是要过完。一晚=4 客=P3 颗粒。一章=5 晚收束弧线。 -## 2. 手势映射与豁免登记 +## 2. 泡茶规格 -### 2.1 每晚开场(OPENING) - -| 手势 | 首晚(含教学 DRILL) | 常规晚 | -|---|---|---| -| 单击 | 教学步骤作答(D1/D4) | 跳句(开场链可跳) | -| 双击 | 重听教学句 | 重听当前句 | -| 左滑/右滑 | D0 步作答;其余 error | error 震 | -| 上滑 | 状态链(D5 教后可用) | 状态链 | -| 下滑 | **跳过教学**(tut_skip;meta.drillDone) | 菜单 | -| 长按/松开 | error | error | - -开场链:lamp_light(点灯 sfx)→ night_N(分档句)→(首晚 intro_1..3→DRILL)→ 1.5s 后门铃,自动进 SERVE。 - -### 2.2 堂前(SERVE:WELCOME/TALK/REACT 三个自动链子相位) - -| 手势 | 链播中(进门/点单/反应) | 空闲(链完待自动转场间隙) | -|---|---|---| -| 单击 | **跳句**(12 号口径;关键句「不可跳」仅 res_* 结果句与 rent_*) | 提前切下一段(加速) | -| 双击 | **重听当前句**(宪法语义保留) | 重听末句 | -| 左滑/右滑 | 静默忽略(纯聆听保护,simon playback 先例) | 同左 | -| 上滑 | 状态链(queue 排队,链播完后续) | 状态链 | -| 下滑 | 菜单(打开即停链;关闭→**重播被停句**,storyline「节点不丢」先例) | 菜单 | -| 长按/松开 | 引擎快进(旁白 1.7×,不豁免)/ 恢复常速 | error | - -### 2.3 泡茶小局(BREW:TEA 选茶 → WATER 候汤 → STEEP 闷泡三子相位) - -| 手势 | TEA(选茶) | WATER(候汤) | STEEP(闷泡) | -|---|---|---|---| -| 左滑/右滑 | 换茶罐(循环):tea_jar sfx + 茶名句(含口诀) | error 震+`gd_invalid`(**不判错步**) | 同左 | -| 单击 | **选定此茶** → 自动进候汤(scoop sfx) | error 震+纠错(等时机) | 同左 | -| 双击 | 重听当前茶名句(宪法语义保留) | **提壶**(时机判定,即时) | **出汤**(时机判定,即时) | -| 上滑 | 状态链(**第一段=重播当前客人点单句**+sig,需求随时可查) | 同左(链排队;水声时间线**照走**,错过=真话归因「水滚老了」) | 同左 | -| 下滑 | 菜单(冻结全部计时) | 菜单(冻结水声时间线) | 菜单(冻结滴答) | -| 长按/松开 | error(E3:BREW 全态 `setRateBoostEnabled(false)`) | 同左 | 同左 | - -菜单关闭恢复(A2 count-in 口径):TEA→重播当前茶名句;WATER→重播 `hint_water` 口诀;STEEP→重播当前茶名句;水声时间线从冻结点续走。 - -### 2.4 打烊(CLOSE)/ 标题(TITLE) - -| 手势 | CLOSE(结算链) | CLOSE(链完) | TITLE | -|---|---|---|---| -| 单击 | **吞掉**(800ms 死窗+链锁,§4.4) | **睡下**→OPENING(night+1)(settle_night 承诺言行一致) | 继续(有档)/ 新店(无档) | -| 双击 | 吞掉 | 重听结算链 | 重听 continue_ask | -| 右滑 | 吞掉 | error | 重开新店:播 sys_reset_ask,**再右滑一次**确认(其余手势取消) | -| 上滑 | 吞掉 | 重听账目链(st_rep→st_purse→置办清单) | 店铺概况(口碑档→存款档→已置办句重播) | -| 下滑 | 吞掉 | 菜单(含「打烊置办」项) | 菜单 | -| 左滑/长按 | 吞掉 | error | error | - -### 2.5 模态菜单(遵守 00 §5.2 全球约定,无豁免) - -下滑呼出(停链/冻结计时)→ 单击循环切项 → 双击确认 → 上滑帮助 → 下滑关闭。选项:返回堂前 / **打烊置办**(仅 CLOSE;进二级列表同范式:茶柜/茶具/招牌/算了)/ 保存并回大厅 / 重新听帮助。 - -### 2.6 每状态手势映射总表(6 手势+长按松开,全状态覆盖) - -| 手势 | TITLE | OPENING | SERVE | BREW-TEA | BREW-水 | BREW-闷 | CLOSE | MENU | -|---|---|---|---|---|---|---|---|---| -| 单击 | 继续/新店 | 作答/跳句 | 跳句/加速 | 选定茶 | error+纠错 | error+纠错 | 吞/睡下 | 循环切项 | -| 双击 | 重听 | 重听教学句 | 重听当前句 | 重听茶名句 | **提壶** | **出汤** | 吞/重听链 | 确认执行 | -| 左滑 | error | 教学指定/err | 忽略 | 上一罐 | error | error | 吞/err | 上一项 | -| 右滑 | reset(两次) | 教学指定/err | 忽略 | 下一罐 | error | error | 吞/err | 下一项 | -| 上滑 | 店铺概况 | 状态 | 状态链 | 状态(含需求) | 状态链 | 状态链 | 吞/账目链 | 帮助 | -| 下滑 | 菜单 | 菜单/跳教学 | 菜单 | 菜单(冻结) | 菜单(冻结) | 菜单(冻结) | 吞/菜单 | 关闭 | -| 长按 | error | error | 快进(引擎) | error | error | error | 吞/err | error | -| 长按松开 | — | — | 恢复常速 | — | — | — | — | — | - -无效手势一律 error 震;full 档 `gd_invalid`(onceKey 同局 1 次);idle-only(引擎 guide 已实现)。 - -### 2.7 豁免登记(必须显式) - -| # | 豁免条目 | 理由(先例) | -|---|---|---| -| E1 | BREW 时机步(候汤/闷泡)双击=提壶/出汤,非重听 | 即时判定绑双击规避单击 300ms 延迟(dungeon 战斗双击=格挡、rpg 前摇格挡同款);重听职责不缺位——上滑状态链第一段=重播客人点单句(含 sig),选茶步双击仍=重听茶名句。教学 D2/D3 讲清 | -| E2 | SERVE 链播中单击=跳句(非「确认」) | 12 号 §1 既有口径(rpg/storyline/escape 先例);关键句(res_*、rent_*)登记不可跳 | -| E3 | BREW 态 `setRateBoostEnabled(false)` | 长按在 BREW 是无效输入;防误触快进(dungeon/rhythm/space 对局期先例) | - -无 immediateTap;tap 只用于非即时操作(选定/跳句/睡下/菜单)。 - -## 3. 核心动词:泡茶小局(信息设计) - -### 3.1 三步手势序列规格 - -| 步 | 输入 | 选项数 | 音反馈 | 错误容限 | 时长 | -|---|---|---|---|---|---| -| ① 选茶 TEA | 左右滑循环+单击选定 | 茶柜 4 罐(扩容后 6) | 每次切换:tea_jar(0.3s)→ 茶名句(含该茶口诀,**知识随身播报**);选定:tea_scoop | 无限切换无时间压力;**选定后不可改**(错茶=故事走涩向+免单,无惩罚);上滑随时重听客人点单 | 8-25s | -| ② 候汤 WATER | **双击提壶** | 1(时机) | 水声时间线(见 3.2):细密气泡→将沸→滚透→滚老,单条 11s | 窗宽 ±1.5s(茶具升级 ±2.3s;首晚 ×1.3);早=「欠火」晚=「过老」均记偏**不终止流程**;无出手 t=11s 自动过老继续 | 11s 封顶 | -| ③ 闷泡 STEEP | **双击出汤** | 1(时机) | 注水 2s→舒展沙沙 2s→**静默**(仅 BGM)→目标前 2.5s 起 tick 滴答(窗内加速) | 窗宽 [目标−1.0, 目标+1.4]s(茶具 +0.8s;首晚 ×1.3);早=「涩」晚=「苦」记偏;无出手 +4s 自动出汤继续 | 9-21s | - -- 判定与结果(**数据单源**,无独立状态):`{茶对错, 水档, 闷档}` → 茶错⇒res_wrong;茶对∧水 ok∧闷 ok⇒res_good;茶对∧任一偏⇒res_plain。三档结果→客人反应三方向(§6.4)。 -- 每步结算即归因(真话律):`water_early/ok/late`、`steep_early/ok/late` 按真实时机选播;错手势(时机步内滑/单击)**不判错步**——只有时机本身决定品质,绝不报假归因。 - -### 3.2 水声时间线(声学编码,全在引擎能力内) - -不用实时 WebAudio 节点(playtest mock 只覆盖 bufferSource/gain):全部=预生成 sfx one-shot + setTimeout 调度 + volume。 - -``` -t=0 water_pour 注水 one-shot(2s) -t=2.0 boil_blip 单泡音起播:间隔 700ms(温水段) -t=3.5 ── 将沸窗开(绿茶/银针的提壶窗)── blip 间隔 350ms + boil_simmer 低鸣 one-shot 链 -t=6.5 ── 滚透窗开(茉莉/铁观音/滇红/普洱的提壶窗)── boil_roar 连片 one-shot 链(2s/段) -t=9.5 窗闭:boil_over 剧烈嘶鸣(滚老) -t=11.0 无出手→自动过老,播 water_late,进闷泡 -``` - -- 两窗共用一条时间线,茶种决定用哪扇窗(茶名句口诀即判据:「将沸就提」vs「滚透才冲」)。 -- 闷泡时间线:pour(2s)→ steep_rustle 沙沙(2s)→ 静默(BGM only)→ 目标前 2.5s tick_slow 起(1s/拍)→ 入窗 tick_fast(0.6s/拍)→ 窗后 +4s 自动出汤。 -- 目标闷时(自注水毕):快 7s(龙井/茉莉)/ 中 11s(铁观音/银针)/ 久 15s(滇红/普洱)。 - -### 3.3 听觉分层表(本作最大风险预防:并发声流 ≤2) - -| 时刻 | 声流①(内容) | 声流②(床) | 语音 | 断言口径 | +| 步 | 输入 | 音 | 容限 | 时长 | |---|---|---|---|---| -| 客人进门/点单/反应 | —— | BGM serve | 客人句(1 条) | BGM+voice=2 ✓ | -| 选茶 | tea_jar/scoop one-shot(≤0.3s,串行于语音前) | BGM serve | 茶名句 | one-shot 与 voice 不叠(jar 先响完再播句) | -| 候汤 | boil_blip/roar 调度流 | BGM brew | **零语音**(设计禁止) | 水声期间 voice 播放事件=0 | -| 闷泡静默段 | 无 tick | BGM brew | 零语音 | 1 声流(最安静段=呼吸感) | -| 滴答关键窗 | tick 滴答 | BGM brew | **零语音**(看门狗也只发 earcon,§9.2) | tick 活跃窗内 voice 事件=0 | -| 打烊结算 | coin/lamp one-shot | BGM menu | 账目句链 | 串行 | +| 选茶 | 左右滑+单击 | jar→口诀句;scoop | 无限;选定不可改 | 8–25s | +| 候汤 | 单击 | 11s 水声线:细密→将沸→滚透→过老 | ±1.5s(茶具 +0.8;首晚 ×1.3) | ≤11s | +| 闷泡 | 单击 | 注水 2s→沙沙 2s→静默→目标前 2.5s tick | [目标−1.0, +1.4]s | 9–21s | -- 频带分离:沸腾=低频(150-500Hz 咕嘟)、滴答=高频木鱼(~880Hz 族)、人声=中频——即偶发瞬时叠听也不糊(Brewster 指南)。 -- 滴答只在关键窗(需求原文兑现):静默闷泡段绝不加节拍器。 +结果单源:茶错⇒wrong;茶对∧两窗 ok⇒good;否则 plain。绿茶/银针用将沸窗,其余滚透窗。闷时:快 7 / 中 11 / 久 15s。 -## 4. 状态机 +水声调度=预生成 one-shot + setTimeout,无实时节点。 -### 4.1 状态列表 +## 3. 客人与经济 -`TITLE / OPENING / SERVE / BREW / CLOSE` + `MENU`(挂起 overlay,记录返回态)。SERVE 子相位=WELCOME/TALK/REACT(自动链);BREW 子相位=TEA/WATER/STEEP;CLOSE 子相位=TALLY(链)/SHOP(菜单内置办)。 - -### 4.2 转移图 +弧线 5 人×3 晚,方向值 good+1/plain 0/wrong−1,sum≥2 A / |sum|≤1 B / ≤−2 C。茶柜锁普洱/银针=经营重量。氛围客 3 人旁白转述。 ``` -start ──> TITLE ──tap(有档)──> OPENING(night_N) ──自动──> SERVE(客人1) - │tap(无档) │ - │右滑×2=重开新店(run+meta 清) │点单链完,自动 - v v - (大厅) SERVE ⇄ BREW(每位客人一圈) - SERVE(末位客人反应毕) ──自动──> CLOSE(打烊链) - CLOSE ──tap──> OPENING(night+1) [N5 链含 chapter_end;N6+ 无尽营业] - 任意正交态 ──下滑──> MENU ──关闭──> 原状态(SERVE 重播被停句;BREW 冻结后恢复) +rep 初 0,good+1/wrong−1 +pay:good=价+2 / plain=价 / wrong=0 +房租每 3 晚 20;不足入 debt,无 game over +升级:茶柜 15 / 茶具 12 / 招牌 20(rep≥12 且无债) ``` -### 4.3 语音链推进契约 +档位句与阈值一一对应(st_rep_1「口碑还没立起来」=rep<5)。剧本 A 用心掌柜与剧本 B 贴钱营业见原验算:每晚 close_coin=逐客 pay 之和所在档。 -- 客人链/结算链=onComplete 串行(句间 0.8s);单句 8s 无 onComplete(资产缺失)→ 告警续链(「屏蔽任意 voice 流程不卡死」契约)。 -- 教学链 `_speakGuarded` 串行(radio/dungeon D0-1 同款,杜绝同 tick 双调度抢占)。 -- CLOSE 结算链为**独占链**:链首停看门狗;链内不受理手势(§4.4)。 +N1–N5 固定客序;N6+ seed 轮换氛围客。 -### 4.4 打烊死窗与链锁(dungeon H1 / detective VERDICT 内化) +## 4. 音频与签名 -- 进 CLOSE 即盖 `CLOSE_GRACE_MS=800` 死窗戳(touchstart+450ms 长按判定+300ms 双击窗 ≈750ms,800 全盖);死窗内一切手势吞掉(ghost 手势不算输入、不碰看门狗)。 -- 链锁:close_open→账目句→(房租/欠账句)→(chapter_end)→settle_night 落声前,单击/右滑/长按一律吞掉;链完成后单击=睡下(settle_night「单击,睡下」言行一致——simon round1-live 教训)。双击=重听链仅链完成后受理。 +客人 sig_* 进门必响。BGM menu/serve/brew(brew 极疏)。沸腾低频、滴答 ~880Hz、人声中频。 -## 5. 教学编排(DRILL,首晚寄生于第 1 位客人=老周①) +NPC:老周 Eric、苏晚 Vivian、陈拾 Dylan、白老 Uncle_Fu(与 male 旁白同 speaker,靠 sig+腔)、何 Serena(同 female 旁白风险,靠 sig)。 -每步=一句短旁白→等待实操→反馈;做错重教同一步(**新短句 ≤2 次**)后自动演示通过;下滑=跳过(tut_skip),`meta.drillDone` 持久化。教学客老周①**明示茶名**(龙井)+首晚窗口 ×1.3 → 好茶近乎保送、账目照常(首胜≤3min,A5)。 +## 5. 状态机 -| 步 | 旁白 | 等待动作 | 通过条件/反馈 | -|---|---|---|---| -| D0 | intro_1..3 + tut_d0 | 左滑或右滑任一 | tea_jar+茶名句;confirm 震。**首输入 ≈t+20s(<60s ✓)** | -| D1 | tut_tea | 单击 | tea_scoop → 自动坐水 | -| D2 | tut_water(对照口径:泡泡细密是将沸,咕嘟连片是滚透——听到细密,双击提壶) | 双击(将沸窗内) | water_ok(偏了也继续:water_early/late 真话归因+重教短句) | -| D3 | (水毕自动)tut_steep(滴答由稀到密,密起来就是将好——双击出汤) | 双击(滴答密时) | steep_ok(同上容错) | -| D4 | tut_serve | 单击 | pour_cup → 老周 r1 反应句。**首正反馈 ≈t+2.5-3min(≤3min ✓)** | -| D5 | tut_status(含需求重播入口:客人要的茶,上滑灶上随时再听) | 上滑 | 状态链(st_guest→st_rep→st_purse) | -| D6 | (老周出门后)tut_done | — | 第 2 位客人门铃,自由营业 | - -- 滚透窗(茉莉等 4 茶)不设专门教学步:靠茶名句口诀+hint_water+首晚宽窗自然过渡(知识单源=茶名句,承诺必兑现)。 -- 教学无进展轨(E0-2 先例):30s earcon → 60s 短句 → **90s 自动替打当前步**(自动选定/提壶/出汤,演示通过不卡流程)。 - -## 6. 客人系统 - -### 6.1 客人档案 JSON 草案(数据单源:点单句→茶参数→判定→方向→结局) - -```json -{ - "schema": 1, - "teas": { - "longjing": { "name": "tea_longjing", "price": 6, "water": "simmer", "steep": "fast", "shelf": true }, - "moli": { "name": "tea_moli", "price": 4, "water": "rolling", "steep": "fast", "shelf": true }, - "tieguanyin": { "name": "tea_tieguanyin", "price": 8, "water": "rolling", "steep": "mid", "shelf": true }, - "dianhong": { "name": "tea_dianhong", "price": 5, "water": "rolling", "steep": "long", "shelf": true }, - "puer": { "name": "tea_puer", "price": 7, "water": "rolling", "steep": "long", "shelf": "cabinet" }, - "yinzhen": { "name": "tea_yinzhen", "price": 10, "water": "simmer", "steep": "mid", "shelf": "cabinet" } - }, - "arcGuests": [ - { "id": "zhou", "name": "老周", "sig": "sig_zhou", - "enter": "zhou_enter", "orders": ["zhou_o1", "zhou_o2", "zhou_o3"], - "teaByVisit": ["longjing", "dianhong", "longjing"], - "reactions": [["zhou_r1_good", "zhou_r1_plain", "zhou_r1_wrong"], - ["zhou_r2_good", "zhou_r2_plain", "zhou_r2_wrong"], null], - "endings": ["zhou_e_a", "zhou_e_b", "zhou_e_c"] }, - { "id": "su", "name": "苏晚", "sig": "sig_su", "enter": "su_enter", - "orders": ["su_o1", "su_o2", "su_o3"], - "teaByVisit": ["dianhong", "moli", "dianhong"], - "reactions": [["su_r1_good","su_r1_plain","su_r1_wrong"], - ["su_r2_good","su_r2_plain","su_r2_wrong"], null], - "endings": ["su_e_a", "su_e_b", "su_e_c"] }, - { "id": "shi", "name": "陈拾", "sig": "sig_shi", "enter": "shi_enter", - "orders": ["shi_o1", "shi_o2", "shi_o3"], - "teaByVisit": ["moli", "longjing", "yinzhen"], - "reactions": [["shi_r1_good","shi_r1_plain","shi_r1_wrong"], - ["shi_r2_good","shi_r2_plain","shi_r2_wrong"], null], - "endings": ["shi_e_a", "shi_e_b", "shi_e_c"] }, - { "id": "bai", "name": "白老先生", "sig": "sig_bai", "enter": "bai_enter", - "orders": ["bai_o1", "bai_o2", "bai_o3"], - "teaByVisit": ["tieguanyin", "puer", "puer"], - "reactions": [["bai_r1_good","bai_r1_plain","bai_r1_wrong"], - ["bai_r2_good","bai_r2_plain","bai_r2_wrong"], null], - "endings": ["bai_e_a", "bai_e_b", "bai_e_c"] }, - { "id": "he", "name": "何雨眠", "sig": "sig_he", "enter": "he_enter", - "orders": ["he_o1", "he_o2", "he_o3"], - "teaByVisit": ["longjing", "puer", "yinzhen"], - "reactions": [["he_r1_good","he_r1_plain","he_r1_wrong"], - ["he_r2_good","he_r2_plain","he_r2_wrong"], null], - "endings": ["he_e_a", "he_e_b", "he_e_c"] } - ], - "ambientGuests": [ - { "id": "postman", "order": "amb_1", "teaPool": ["moli"] }, - { "id": "embroider", "order": "amb_2", "teaPool": ["longjing"] }, - { "id": "drunk", "order": "amb_3", "teaPool": ["dianhong"] } - ], - "nights": [ - ["zhou", "amb:postman", "su", "shi"], - ["zhou", "bai", "he", "shi"], - ["zhou", "su", "bai", "amb:drunk"], - ["he", "shi", "su", "amb:postman"], - ["bai", "he", "amb:embroider", "amb:postman"] - ], - "nightsEndless": { "pool": ["amb:postman", "amb:embroider", "amb:drunk"], "size": 4 }, - "economy": { "tipGood": 2, "rentEvery": 3, "rent": 20, - "repGain": { "good": 1, "plain": 0, "wrong": -1 }, - "pay": { "good": "price+2", "plain": "price", "wrong": 0 }, - "upgrades": [ - { "id": "cabinet", "voice": "upg_cabinet", "cost": 15 }, - { "id": "teaware", "voice": "upg_teaware", "cost": 12 }, - { "id": "sign", "voice": "upg_sign", "cost": 20, "repReq": 12 } ] } -} -``` - -- 氛围客人点单句由旁白转述(amb_1..3,句内已含茶名与参数暗示);判定同弧线客人(teaPool/口味由 seed 决定 N6+ 变体,N1-N5 用表内固定值)。 -- **矩阵不变式(dev/playtest 双保险)**:每晚每位客人的点单句 ⇒ `(tea, water窗, steep窗)` 三元组与 `teas[teaByVisit[i]]` 严格一致;任何台词改稿若使点单句暗示的茶与 teaByVisit 不符,断言立即失败(防「文案事故变成归因假话」)。 - -### 6.2 弧线节点表(5 位客人 × 3 晚;方向值 好茶+1 / 普通 0 / 错茶−1) - -| 客人 | 晚1 相遇 | 晚2 深入 | 晚3 收束(结局=按累计 sum) | -|---|---|---|---| -| 老周(修车师傅,儿子在外多年) | 点龙井去火;r1×3 | 点浓暖的(滇红);r2×3 起「儿子来电」话头 | 「头一回那盏再来一份」(龙井回环);sum≥2=A 父子回来帮工 / B 电话拌嘴日子照过 / C 关铺去投儿子 | -| 苏晚(夜班护士,去留抉择) | 点暖的不冲(滇红);r1×3 | 点香的(茉莉);r2×3 讲科室一夜 | 「还是头一回那种」(滇红回环);A 调白天班白天来 / B 再值一季看看 / C 递辞呈但记住这盏灯 | -| 陈拾(高三生,大考) | 点香提神不浓(茉莉);r1×3 | 点清火的(龙井);r2×3 问「还能考上吗」 | 「喝口金贵的犒劳自己」(银针,需茶柜);A 超常发挥请全馆 / B 听家里安排 / C 考砸但灯还亮着 | -| 白老先生(独居,老伴忌日) | 点刮油经泡的(铁观音);r1×3 | 点陈味(普洱,需茶柜);r2×3 忆四十年前一盏普洱 | 「就馋那口陈味」(普洱);A 搬棋盘天天来「当她在看着」 / B 空屋但有茶垫梦 / C 搬去养老院留壶给茶馆 | -| 何雨眠(夜班出租车司机,攒学费) | 点清口不浓(龙井);r1×3 | 点最浓的(普洱,需茶柜);r2×3 讲闺女照片贴方向盘 | 「一盏柔的收收心」(银针,需茶柜);A 学费齐了去看闺女 / B 差一点再跑一月 / C 退了夜班「这盏喝得最慢」 | - -- 结局映射:`sum = Σ方向值(3 晚)`;`sum≥2 → A(暖);−1≤sum≤1 → B(平);sum≤−2 → C(涩)`。 -- 茶柜锁与弧线的 roguelike 纹理:白老②③/何②③/陈③ 需要「茶柜扩容」解锁的普洱/银针——没置办的玩家必然错过好茶档,弧线自然走平/涩向(**这是经营决策的重量,不是 bug**;§7.5 剧本 B 验算)。全 A 路径可行性:N2 打烊置办茶柜(15 文)即解锁全部弧线茶(§7.5 剧本 A)。 -- 晚 3 收束夜=「反应与结局合一」:结局句本身回应今晚的茶(A 段必有对茶的正面回应、C 段必有遗憾——见台词表 N 组)。 - -### 6.3 声音群像:区分度(本作最大资产风险,逐人标注) - -| 角色 | speaker | instruct 常量 | 区分度风险 | -|---|---|---|---| -| 旁白(掌柜系统音) | female=Serena / male=Uncle_Fu | 见台词表头 | 基准 | -| 老周(58 修车师傅) | `Eric` | 沙哑洪亮的男声,修车铺老师傅,大嗓门,直爽热乎 | 低:与两侧旁白均不同 speaker | -| 苏晚(29 夜班护士) | `Vivian` | 轻柔平稳的女声,夜班护士,温和疲惫,吐字干净 | 低:同上 | -| 陈拾(17 高三生) | `Dylan` | 年轻清亮的男声,高三学生,紧张,语速快,有点怯 | 低:同上 | -| 白老先生(74) | `Uncle_Fu` | 苍老缓慢的男声,独居老先生,慢条斯理,带茶韵 | **中**:与 male 旁白同 speaker——靠 sig_bai 前缀+苍老腔 instruct+人称差(客人第一人称/旁白第三人称「客人」)+句间 0.6s;试听确认 | -| 何雨眠(36 的姐) | `Serena` | 低哑爽利的女声,夜班出租车司机,利落,带笑带倦 | **中**:与 female 旁白同 speaker——同上缓解;试听确认 | -| 氛围客人 ×3 | 旁白转述 | 同旁白 | 无(零 NPC 句,成本与风险双省) | - -NPC 音色数=5 ≤6(radio/detective 满配先例);每位弧线客人**专属签名 earcon**(sig_*,0.3-0.5s)在进门句/状态链需求重播/反应句前必响——认人是「sig+speaker+instruct」三层冗余。 - -## 7. 经营数值(口碑/收入/房租经济表) - -### 7.1 每晚事件表(N1-N5 剧本夜) - -| 晚 | 客序 | 事件 | -|---|---|---| -| 1 | 老周①/邮差/苏晚①/陈拾① | 教学;首胜保送;无房租 | -| 2 | 老周②/白老①/何①/陈拾② | 普洱银针需求首次逼近(茶柜压力);打烊可置办 | -| 3 | 老周③/苏晚②/白老②/醉汉 | **房租夜①**(20 文);老周弧线收束 | -| 4 | 何②/陈拾③/苏晚③/邮差 | 苏晚/陈拾弧线收束 | -| 5 | 白老③/何③/绣娘/邮差 | **房租夜②**;白老/何收束;chapter_end | - -N6+(无尽营业):4 席=氛围客轮换(seed 定序/口味),房租照收,经济维持。 - -### 7.2 累积公式 - -``` -rep:初始 0,clamp ≥0。每客 good +1 / plain 0 / wrong −1。每晚 Δ∈[−4,+4]。 -purse(存款):初始 0。CLOSE 时 income 计入 → 先还债(debt 清零播 debt_clear)→ 房租夜扣 rent - (不足 → 差额入 debt,播 rent_short + danger 签名)→ 可置办(不透支)。 -income(当晚):Σ 每客 pay = good: 茶价+2 打赏 / plain: 茶价 / wrong: 0(免单)。 -方向值(弧线):good +1 / plain 0 / wrong −1,sum 三晚 → 结局 A/B/C。 -upgrades:cabinet 15(普洱+银针上柜)/ teaware 12(时机窗 +0.8s)/ sign 20(需 rep≥12,每晚结算 rep+1)。 -``` - -### 7.3 档位播报措辞与账目一一映射(radio rep 教训:播报值=运行值,跨档才播、双向都播) - -| 账本 | 档 | 阈值 | 播报句(id) | 措辞↔账目 | -|---|---|---|---|---| -| 口碑 rep | 1 冷清 | 0-4 | st_rep_1「口碑还没立起来」 | 「没立起来」=rep<5 | -| | 2 回头 | 5-11 | st_rep_2「有了回头客」 | 「回头客」=rep≥5 | -| | 3 名声 | 12-19 | st_rep_3「巷子口都在传」 | 「传」=rep≥12 | -| | 4 招牌 | ≥20 | st_rep_4「一盏难求」 | 「难求」=rep≥20 | -| 当晚收入 | 1 | <15 | close_coin_1「不到十五文」 | 与逐客 pay 之和相等 | -| | 2 | 15-24 | close_coin_2「十五到二十五文」 | 同上 | -| | 3 | ≥25 | close_coin_3「二十五文往上」 | 同上 | -| 存款 purse | 1 | <10 | st_purse_1「钱箱见底」 | 结算后实值 | -| | 2 | 10-24 | st_purse_2「周转得开」 | 同上 | -| | 3 | ≥25 | st_purse_3「手头宽裕」 | 同上 | -| 欠账 debt | — | >0 | st_debt_1「账上还欠着秦婶的钱」 | CLOSE 链内、还清播 debt_clear | -| 房租 | — | 每 3 晚 20 文 | rent_due→rent_ok / rent_short | 不足额=差额入账,**无 game over** | - -### 7.4 店铺升级树(roguelike 跨晚进度,localStorage+服务器双存) - -``` -茶柜扩容 15 ──> 普洱(7文·滚·久)+白毫银针(10文·将沸·中)上柜 ──> 解锁白老②③/何②③/陈③ 的好茶路径 -新茶具 12 ──> 候汤/闷泡窗各 +0.8s(容错拉满,P4) -立招牌 20(需 rep≥12 且 debt=0)──> 每晚结算 rep+1(口碑发酵) -``` - -### 7.5 完整 5 晚剧本验算(剧本 A「用心掌柜」——口碑/收入逐晚对账) - -| 晚 | 逐客结果(茶·三档) | 逐客收入 | 当晚收入 | rep 流程(跨档播报点) | purse 流程 | -|---|---|---|---|---|---| -| 1 | 周好(龙井)/邮好(茉莉)/苏好(滇红)/陈好(茉莉) | 8/6/7/6 | 27(coin_3) | 0→4(无跨档) | 0→27 | -| 2 | 周普(滇红)/白好(铁观音)/何错(泡了茉莉)/陈好(龙井) | 5/10/0/8 | 23(coin_2) | 4→5(**st_rep_2**)→4(**st_rep_1**,何错跌回)→5(**st_rep_2**) | 27+23=50;置办茶柜 −15 → **35** | -| 3 | 周好(龙井)/苏好(茉莉)/白好(普洱)/醉普(滇红) | 8/6/9/5 | 28(coin_3) | 5→8;**老周 sum=+1+0+1=2 → 结局 A** | 35+28=63;房租 −20 → 43;置办茶具 −12 → **31** | -| 4 | 何好(普洱)/陈好(银针)/苏错(泡了龙井)/邮普(茉莉) | 9/12/0/4 | 25(coin_3) | 8→9→10→9(**st_rep_2**下沿内,不跨档)→9;陈 sum=3→A;苏 sum=+1+1−1=1→**B** | 31+25=**56** | -| 5 | 白好(普洱)/何好(银针)/绣普(龙井)/邮好(茉莉) | 9/12/6/6 | 33(coin_3) | 9→10→11→11→12(末客后**st_rep_3**);白 sum=3→A;何 sum=−1+1+1=1→**B** | 56+33=89;房租 −20 → 69;**立招牌 −20(rep=12 恰达标)→ 49** | - -终局:rep 12(档3)/ purse 49 / 升级 3/3 / debt 0 / 结局 3A+2B;chapter_end → N6+ 无尽营业。 -**对账断言**:每晚 close_coin 档=逐客 pay 之和所在档;每次 st_rep 播报时的 rep 实值=该档阈值区间;置办价=upg 表——playtest 锁定(假话即失败)。 - -### 7.6 剧本 B「贴钱营业」验算(欠账机制可达性) - -N1-N3 全错(每客免单+rep−1,rep clamp 0):income 0/0/0,purse 0;N3 房租 20 > 0 → **rent_short+danger,debt=20**(贴钱营业,无 game over);无茶柜 → N4 何②/陈③必然错茶(普洱/银针缺位,弧线走涩向——经营决策的真实重量);N4 苏好+邮普 income 11 → 还债 11(debt 9);N5 绣普+邮好 income 12 → 还债 9(**debt_clear**)→ purse 3 → 房租 → debt 17。终局 rep 2(档1)、结局 2C+3B——**活着,且每一句播报与账本一致**。 - -## 8. 音频三层 - -### 8.1 BGM(3 首,Mureka instrumental,-20 LUFS,ogg+m4a,无缝循环) - -| id | 状态映射 | Mureka prompt(英文) | -|---|---|---| -| bgm/menu | TITLE / MENU / CLOSE(结算 volume 0.5) | "Cozy late night tea house, soft guzheng and bamboo flute duet, warm kettle hum and faint vinyl crackle, intimate and unhurried, no drums, no vocals, seamless loop" | -| bgm/serve | OPENING / SERVE | "Gentle Chinese tea house evening theme, sparse pipa and soft erhu, distant crickets and quiet old-street ambience, warm storytelling mood, no vocals, seamless loop" | -| bgm/brew | BREW(三子相位) | "Minimalist zen tea brewing music, very sparse guqin notes over soft water ambience, meditative, wide and quiet dynamics, no vocals, seamless loop" | - -切换全走引擎 crossfade;voice duck 20% 引擎自动。预算:3 曲 ×2 候选=6 任务 $0.18(上限 $2.00 内)。brew 曲**特意稀疏**——给操作音让频带(§3.3)。 - -### 8.2 SFX(~26 个;SA3=Stable Audio 3,SYN=程序合成;双格式、真峰值 ≤-3dBFS) - -| 类别 | id | 方式 | 说明 | -|---|---|---|---| -| 进门 | `door_bell` | SA3 | 铜铃两响(0.5s,客人进门/出门) | -| | `steps_in` `steps_out` | SA3 | 布鞋脚步 1.2s(in 由远及近预 pan 派生,dungeon 工艺) | -| 客人签名 | `sig_zhou` | SYN | 扳手磕搪瓷缸一响 | -| | `sig_su` | SYN | 指尖叩桌面两下 | -| | `sig_shi` | SYN | 拉链+书本落桌 | -| | `sig_bai` | SYN | 棋子落木盘一响 | -| | `sig_he` | SYN | 车钥匙串抖两下 | -| 选茶 | `tea_jar` `tea_scoop` | SYN | 木罐启盖+茶叶沙沙 / 铜勺入壶 | -| 候汤 | `water_pour` | SA3 | 注水 one-shot 2s | -| | `boil_blip` `boil_simmer` `boil_roar` `boil_over` | SA3+SYN | 单泡/低鸣/连片滚沸/过老嘶鸣(one-shot 链调度,§3.2;**不用循环 sfx**——引擎 sfx 层无 loop) | -| | `kettle_lift` | SYN | 提壶离火蒸汽「呲」 | -| 闷泡 | `steep_rustle` | SYN | 干茶舒展沙沙(2s,低响度) | -| | `tick_slow` `tick_fast` | SYN | 木鱼滴答两档(880Hz 族;**只进关键窗**) | -| 上茶 | `pour_cup` | SA3 | 茶汤入盏+盏托轻磕 | -| 账目 | `coin_soft` `coin_stack` | SYN | 单枚铜钱/三枚铜钱落桌(plain/good);wrong=无币声(免单静默+反应句) | -| | `rep_chime` `rep_fall` | SYN | 上行/下行双音(口碑跨档瞬时标记,配合档句) | -| 仪式 | `lamp_light` `lamp_off` | SYN | 开灯「嗒」/熄灯(每晚开张与睡下) | -| | `shop_buy` `upg_install` | SYN | 算盘+钱柜抽屉 / 木工轻敲(置办) | -| 看门狗 | `turn_tick` | SYN | 880Hz 木鱼族轻响(无进展轨 L1,dungeon 配方自存一份) | - -SA3 任务 ~10 个(door_bell/steps×2/water_pour/boil 家族 2 条/pour_cup);其余 SYN 兜底。 - -### 8.3 voice(163 句:旁白 98 ×2 性别 + NPC 平铺 65) - -- **旁白**(系统/教学/状态/账目/氛围客转述):female=Serena / male=Uncle_Fu,instruct 全表统一(台词表头);数字全分档整句(st_guest 5 档/st_rep 4/st_purse 3/close_coin 3/night 6 档),无运行时拼接。 -- **NPC 平铺**(弧线客人台词):5 speaker+instruct 恒定(§6.3 区分度表);不做双性别;落 `voice/` 根目录(引擎平铺回退命中)。台词全文、触发、键盘变体、多音字自查见 [20-teahouse-voice-ids.md](20-teahouse-voice-ids.md)。 -- 声音演技核心:晚 3 结局句(A/B/C 三方向 ×5 客人)与晚 2 反应句的递进(故事感全在句内文本+标点引导,instruct 恒定)。 - -## 9. 向导层接入(engine/guide.js,不新增 earcon 词表) - -### 9.1 状态签名(进入状态时播) - -| 状态 | guide.state() | 时点 | -|---|---|---| -| TITLE / MENU / CLOSE 链 | `menu`(CLOSE 链完)| 进标题、开菜单、进结算 | -| OPENING / SERVE | `explore` | 开场、每位客人进门 | -| BREW | `battle` | 选茶步起(「小局起」的统一签名) | -| CLOSE 链内 | `settle` | 打烊链首 | -| danger | `danger` | rent_short 当刻(每欠账事件 1 次) | - -choice 不使用(无抉择态,登记)。 - -### 9.2 turn cue 与看门狗(双轨——批次 1 教训固化) - -| 等待点 | 机制 | at | say(full 档;第 2 级一律纯 nudge) | -|---|---|---|---| -| BREW 各子相位入口 | guide.turn | — | turn earcon + confirm 震 | -| SERVE 空闲 | 无操作轨 | [10s, 25s] | gd_serve / null | -| BREW-TEA | 无操作轨 | [12s, 30s] | gd_brew / null | -| **BREW-WATER/STEEP 时机窗内** | 只 earcon | [12s] | **语音一律不发**(§3.3 分层铁律;语音会占第 3 声流) | -| CLOSE 链完 | 无操作轨 | [15s, 35s] | gd_close / null | -| DRILL 每步 | 无进展轨 | [30/60/90s] | earcon→短句→自动替打(§5) | - -### 9.3 无进展轨(营业态卡关三级——「乱按也要被营业」) - -进展事件=客人相位推进(进门/点单/上茶/反应毕)/BREW 步骤推进/打烊/置办(`_progressTrack()` 挂点:各链 onComplete、判定函数、状态进入)。只由进展重置——乱按(狂切茶罐不听、反复开菜单)永远攒不满进展,三级必达: - -- [45s] L1:turn_tick earcon(不占台词通道)→ [90s] L2:`hint_water`(泡茶口诀)/`gd_serve`(堂前)→ [150s] L3:**重播当前客人点单句**(BREW/堂前通用的真话提示——把线索原句还给玩家,不替选茶);L3 后每 150s 重复(每句全局限 2 次,之后静默)。 -- 无操作轨与无进展轨并行取先到;两轨短句内容同级交叉登记防重复;纠错/提示 idle-only、过期丢弃(引擎已实现)。 - -## 10. 存档设计(跨晚进度) - -```json -{ - "schema": 1, "game": "teahouse", "updatedAt": 0, - "summary": "第3晚 · 客人2/4 · 口碑·有了回头客 · 存款35文 · 欠账0文", - "meta": { "drillDone": true, "nightsClosed": 2, "rep": 5, "purse": 35, "debt": 0, - "upgrades": ["cabinet"], "chapterEnd": false }, - "run": { "night": 3, "guestIdx": 2, - "results": ["good", "plain"], - "incomeTonight": 5, - "guestStory": { "zhou": 1 }, - "rentCharged": false } -} -``` - -- summary 生成规则:`第{night}晚 · 客人{guestIdx+1}/4 · 口碑·{rep 档名} · 存款{purse}文 · 欠账{debt}文`(打烊后 run 清空,summary=`第{nightsClosed}晚打烊 · 口碑·{档名} · 存款{purse}文`)。 -- 保存时机:每客反应毕 / 打烊链完 / 置办后 / 菜单「保存并回大厅」。localStorage + PUT 服务器。 -- 读取:有 run → TITLE 播 sys_continue_ask,单击继续 → **客人粒度恢复**(当前客人从进门链重放,已结算客人不重复)→ 局面复述链 `st_guest_n → st_rep → (BREW 则加 sig+点单句) → recap_go`,≤5s 回到可操作。右滑 ×2=重开新店(run+meta 清零,播 sys_reset_ask 二次确认)。 - -## 11. playtest 断言清单(登记 13 号文档新增 §teahouse;mock 对齐 web/rpg/test/playtest.mjs;一律 guide:'off') - -**关键路径**(seed=42,N1-N3,≤120 手势;全章 N1-N5 ≤200): -1. 启动→TITLE(无档)→单击→OPENING(night_1):intro 三句+D0..D6 逐步 voice 序与 drillStep 递进;D2/D3 时机步双击;首晚窗口 ×1.3 断言;下滑跳过路径+meta.drillDone 二周目不再进。 -2. **泡茶小局三步**:TEA 左右滑循环(wrap)+茶名句播报+单击选定→WATER 时间线(blip 间隔 700/350ms、roar 链、窗位=茶参数)→双击在窗内=water_ok/窗前=water_early/窗后=water_late;t=11s 无出手自动过老;STEEP tick 只在目标前 2.5s 起、窗内加速、同三档+超时自动出汤;判定三元组与 §3.1 映射(good/plain/wrong)逐组合注入验证(含「茶对双偏=plain」「茶错=wrong 即水闷全 ok」)。 -3. **矩阵不变式**:全弧线客人×全部访问 → teaByVisit 与 teas 表参数一致;氛围客 teaPool 同;同 seed 两次构造 `run._schedule`(N6+ 轮换序)深相等。 -4. **客人弧线三方向**:逐客注入三晚结果矩阵(全好/混合/全错)→ 结局句=A/B/C 与 sum 映射一致;反应句三方向与当晚结果一致(假话即失败);晚 3 结局句替换反应句路径。 -5. **经济账目验算**:按 §7.5 剧本 A 逐晚走(茶种/时机注入)→ close_coin 档=逐客 pay 和;rep 跨档播报点与实值一致(含双向跌档);置办扣款与 upg 表;N3/N5 房租扣款;剧本 B:全错→免单→rent_short+danger+debt 流转+debt_clear+rep clamp 0。 -6. **听觉分层**:WATER/STEEP 时机窗内 voice 播放事件=0(看门狗 suppressed 断言);语音 start 事件与 boil/tick 活跃区间零相交;语音只在步间静默点。 -7. **打烊死窗+链锁**:进 CLOSE 同拍与 +300ms 各补一记在途 tap → 均被吞;结算链完整有序(close_open→coin→[rent]→[debt]→[chapter_end]→settle_night);链完成后单击=睡下→OPENING(night+1);N5 后 night_more+氛围客轮换。 -8. **看门狗双轨**:SERVE 10s/25s、BREW-TEA 12s/30s;乱按 96s 零进展 → 45s earcon/90s 短句/150s 重播点单句;有真实进展零提示;BREW 时机窗内只 earcon 无语音。 -9. **中断复述**:存档重进(客人粒度)→ sys_continue_ask→单击→复述链序 st_guest→st_rep→(BREW 加 sig+点单句)→recap_go;上滑同链可重放。 -10. **E1 豁免**:BREW 时机步双击=提壶/出汤(非重听);选茶步双击=重听茶名句;上滑状态链第一段=sig+点单句重播;BREW 长按=error(rateLog 断言 E3)。 -11. **菜单/置办/存档**:模态菜单循环-确认-关闭;CLOSE 菜单「打烊置办」二级列表;teaware 购后窗口 +0.8s 实测;sign 的 rep≥12 与 debt=0 门(upg_lock/upg_none 真话);「保存并回大厅」PUT data.summary 命中生成规则。 -12. **键盘变体**:`settings.input='key'` 后播 sys_help_k/tut_water_k 等 V 组(inputMode 联动);'touch' 回基础句。 - -**边界断言**: -- 零惩罚:全 run 无 game over 路径;错茶免单但仍推进故事;rep clamp ≥0;purse 不透支。 -- 归因真话:water/steep 三档句与注入时机严格对应;res_wrong 只在茶错时播;「没出手」归「水滚老了/闷过了」(真实:未操作)。 -- SERVE 链播中单击=跳句(res_*/rent_* 不可跳)、双击=重听当前句、左右滑静默忽略;菜单关闭重播被停句。 -- BREW 菜单冻结:水声时间线/滴答全停(快进虚拟时钟断言零调度);关闭恢复从冻结点续走+复述句。 -- 屏蔽任意 voice 资产:链 8s 兜底推进,流程到打烊不死锁。 -- guide=off:全静、零定时器;资产齐套:voice 双性别 id 集合一致+NPC 平铺存在+sfx/bgm 双格式。 -- 首晚 FTUE:首输入 <60s(D0 ≈t+20s);首正反馈 ≤3min(D4 老周反应)。 - -**失败即不通过**:归因假话(档位句/结局句/反应句与数据实值不符);点单句与 teaByVisit 矛盾;同 seed 两次构造不一致;教学计入惩罚;CLOSE 链被手势截杀;时机窗内出现语音;免费重听(状态链)被收费或受限。 - -## 12. 节奏曲线(一晚/一弧线两级) - -### 一晚(单局 13-17 分钟;首晚 17-20) - -| 阶段 | 时长 | 内容 | 奖励点(≤60s 一个) | -|---|---|---|---| -| 开场 | 0:20-0:35 | 点灯+night 句(首晚 intro+教学) | — | -| 客人 ×4 | 各 2.5-4 分钟 | 进门 15s→点单 20s→泡茶 45-75s→反应 25s | 每步 water_ok/steep_ok 即时确认;res 三档;反应句+币声;口碑跨档 chime | -| 打烊 | 1:30 | 账目链→(房租/欠账)→(置办)→睡下 | 账目+置办+settle 钩子 | - -- 每位客人=一个完整起承转合(P3 三分钟颗粒:任意客人结算后退出,回来一句话接上)。 -- 紧张度曲线温和:泡茶小局是每 3 分钟一个的「微专注峰」(时机步),其余是聆听叙事谷——峰谷交替,不疲劳(P4)。 - -### 一条弧线(3 晚,跨局) - -晚 1 相遇(立谜:他是谁/他要什么)→ 晚 2 深入(话头展开+方向累计)→ 晚 3 收束(结局三方向)。跨晚钩子=反应句留白(「儿子的事,改天再说吧」)+ 结算 settle_night「明晚,茶馆还等你开门」;5 晚后 chapter_end 收第一章,无尽营业+二周目变体空间(氛围客 seed 轮换)。 - -## 13. 自检清单 - -| # | 硬约束/教训 | 自检 | -|---|---|---| -| 1 | FTUE 首输入 <60s | D0 ≈t+20s ✓(§5) | -| 2 | 首正反馈 ≤3min | D4 老周反应 ≈t+2.5-3min(首晚宽窗保送好茶)✓ | -| 3 | 学习期零惩罚 | 首晚窗口 ×1.3+教学明示茶名;全域无 game over;错茶免单非死亡 ✓ | -| 4 | 失败三问归因+真话律 | 水闷各三档句按真实时机;res_wrong 只在茶错;「没出手」归「滚老/闷过」;§11 失败项锁定 ✓ | -| 5 | 手势宪法三件套不豁免 | 上滑状态/下滑菜单/双击重听全状态保留(时机步双击偏离=豁免 E1 登记+教学讲清+状态链承接重听)✓ | -| 6 | 教学承诺兑现 | tut_water/tut_steep 承诺的双击时机语义正片恒成立;settle_night「单击睡下」死窗+链锁后言行一致;upg 句报价=扣款 ✓ | -| 7 | 结算链完整 | CLOSE 800ms 死窗+链锁(dungeon H1 内化)✓(§4.4) | -| 8 | 档位措辞真实 | §7.3 表:每句措辞↔阈值区间一一映射;跨档双向播报;播报值=运行值(radio rep 教训+经营玩家对账最敏感)✓ | -| 9 | 经济账目验算 | §7.5 剧本 A 五晚逐晚验算(27/23/28/25/33 收入、rep 0→12、purse 0→49、3A+2B)+ §7.6 剧本 B 欠账可达 ✓ | -| 10 | 弧线三方向可走通 | sum 映射 A(≥2)/B(−1..1)/C(≤−2);每客注入全好/全错/混合三路径(§11.4);茶柜锁=设计内经营重量(剧本 A 证明全 A 可达)✓ | -| 11 | 听觉负荷(本作特有风险) | 三重音时间串行零重叠;语音只在步间;滴答只进关键窗;时机窗内看门狗只 earcon;频带分离;并发声流恒 ≤2(§3.3 表+断言 6)✓ | -| 12 | 信息密度 | 列表逐项预览(茶罐循环一次一罐一句);状态链 ≤4 段+需求重播;客人链句粒度可停可跳可重听 ✓ | -| 13 | 向导层 | 签名 5 态(choice 登记不用);双轨看门狗(乱按必被抢救+时机窗静默特例);提示 idle-only/queue/onceKey ✓(§9) | -| 14 | seed 可复现 | mulberry32+opts.seed;N1-N5 表驱动剧本夜;N6+ 轮换洗牌全 PRNG+深相等断言 ✓ | -| 15 | P1 视障 | 双击=即时时机键(读屏手速友好);状态链=需求+第几位+口碑+存款随时可查;账目真实;5 音色群像+sig 三层冗余认人;2.2× 语速引擎既有 ✓ | -| 16 | P3 通勤 | 每客人 2-4 分钟完整颗粒;中断复述「今晚第几位/口碑档」;菜单随时存档退出 ✓ | -| 17 | P4 工人 | 零新手势;时机窗 ±1.5s 宽+超时自动继续(绝不卡死不挨骂);错茶故事照样走 ✓ | -| 18 | P2 大学生 | 弧线三方向收集+章节完结+升级树+招牌追求(rep≥12)✓ | -| 19 | 台词规范 | 163 句 ≤170;全句 ≤40 字(实测 ≤36);数字全分档整句(guest 5/rep 4/purse 3/coin 3/night 6);「重新听」写法;同角色 speaker+instruct 恒定 ✓(台词表) | -| 20 | 键盘通道 | V 组 10 变体集中登记,inputMode() 选播;方向键/空格/回车全手势可达(§5.5 平行通道,游戏层零改动)✓ | -| 21 | 引擎能力边界 | 无实时节点(one-shot 链+setTimeout 调度);sfx 无 loop 需求(调度链实现);tap 延迟只用于非即时;资产缺失静默跳过+8s 兜底;30MB 预算内 ✓ | -| 22 | 密度下限(dungeon D0-3 教训) | 一晚=固定客序节拍器(结构性密度);营业态无进展轨兜底;客人间隙 1.2s 自动推进无真空 ✓ | - ---- - -## 14. 修订记录(实施裁定) - -对开发自查发现的内部张力逐条登记(旧文不回改,以此为准): - -1. **招牌置办门的欠账条件**:§7.2 与 §6.1 JSON(`economy.upgrades` sign 仅 `repReq:12`)未提欠账;§7.4 升级树与 `upg_lock` 台词明言「口碑得十二分往上,欠账也得清」。裁定为 **rep≥12 且 debt=0 双门**(以 §7.4/台词为准)——欠账时选招牌播 `upg_lock`(真话归因),`upg_none` 只管钱不够;§7.5 剧本 A 验算里 debt 恒 0,两口径对账结果一致,不影响验算数。 -2. **菜单固定项与态差异**:§2.5 选项列四项(返回堂前/打烊置办/保存并回大厅/重新听帮助),其中「打烊置办」仅 CLOSE 态出现(SERVE/BREW/TITLE 态菜单为其余三项);实现按态裁剪菜单项,模态交互范式(单击循环/双击确认/上滑帮助/下滑关闭)不变。 -3. **summary 口碑档名取值**:§10 示例「口碑·有了回头客」。档名映射取各档措辞短式——档1「还没立起来」/档2「有了回头客」/档3「小有名气」/档4「一盏难求」(与 st_rep_* 句首措辞同源),播报与存档共用同一映射函数(单源)。 -4. **房租夜定义**:§6.1 JSON `rentEvery:3` 与 §7.1 事件表(房租夜①=N3、房租夜②=N5)/§7.5/§7.6 验算(N5 均扣房租 20)矛盾——5 晚章节内「每 3 晚」只会命中 N3。裁定为 **本章内房租夜 = 第 3、5 晚**(以 §7.1/§7.5/§7.6 为准,N5 是章节收束夜的双重压力事件);N6+ 无尽营业起回到每 3 晚节奏(N6、N9……)。`rentEvery:3` 字段视为遗漏,不改旧文。 -5. **§7.6 终局 rep 算术修正**:剧本 B 事件列(N1-N3 全错 12 次 −1、N4 两错一好、N5 白老③/何③ 无茶柜必错)按 §7.2 公式与 clamp≥0 复算,终局 rep = **1**(N4 苏好 +1 → 1;N5 邮好 +1、白老/何雨眠错茶各 −1 相抵);§7.6 原文「rep 2」系算术笔误。实现以 §7.2 公式为唯一准绳(假话即失败),本裁定不回改正文。 -6. **R1 台词修订(F1/F3 落地)**:tut_water/tut_steep 对照口径前移(将沸/滚透、滴答由稀到密的判别句写进教学句本身,hint_water 口诀保留作无进展轨复诵,同文复用不算漂移);tut_status 补需求重播入口明示(「客人要的茶,上滑灶上随时再听」)。三句连同 key 变体重录;§5 教学表与 20b 台词表已同步,正文旧行不回改。 +TITLE → OPENING → SERVE ⇄ BREW(每客一圈)→ CLOSE → 下一晚。N5 含 chapter_end。菜单冻结水声时间线,关闭从冻结点续走。 diff --git a/docs/design/21-keeper-voice-ids.md b/docs/design/21-keeper-voice-ids.md index a64dd7bc..2dc7ed59 100644 --- a/docs/design/21-keeper-voice-ids.md +++ b/docs/design/21-keeper-voice-ids.md @@ -1,5 +1,13 @@ # 21b · 《守塔人》(keeper) 语音台词表(voice ids,录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - **双性别组**(守塔人 KEEPER + 岸台/系统 SHORE,121 句):落 `web/keeper/audio/voice/female|male/.{ogg,m4a}`,两性别 id 集合完全一致(含全部 `_k` 变体)。 > - **KEEPER(守塔人=玩家)**:female = `Serena`,instruct 常量 `沉静克制的年轻女声,孤岛守塔人,低而稳,藏着心事`;male = `Uncle_Fu`,instruct 常量 `沉静克制的男声,孤岛守塔人,低而稳,藏着心事`。 diff --git a/docs/design/21-keeper.md b/docs/design/21-keeper.md index 55cf069f..bd7113e8 100644 --- a/docs/design/21-keeper.md +++ b/docs/design/21-keeper.md @@ -1,554 +1,153 @@ -# 21 · 《守塔人》(keeper) — 孤岛灯塔叙事 RPG 设计文档 +# 21 · 《守塔人》(keeper) — 孤岛灯塔叙事 RPG > **给开发的实现要点摘要** -> 1. 叙事模式(声命线范式,radio/abyss/cipher 同骨架):节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;复制 `web/radio/` 结构起步,状态机 6 宏状态(INTRO/WATCH/MEMORY/CALM/ENDING + MENU 覆盖层)。 -> 2. 手势零豁免:全部走全局默认映射(单击=继续/跳句、双击=重听、左右滑=抉择、上滑=状态、下滑=模态菜单 §5.2 约定、长按=引擎快进)。 -> 3. 台词依据 [21-keeper-voice-ids.md](21-keeper-voice-ids.md)(148 句:双性别 121 + 平铺 NPC 27);NPC 电台/老唱片滤波在**资产侧** ffmpeg 带通(radio 先例),引擎零改动。 -> 4. **本作核心差异层=时空双线**(§6):现在(风暴+电台静电)/回忆(老唱片底噪+唱片滤波)全靠音色+底噪签名区分;插叙=节点链 auto(≤6 句/≤90s),进入/回返有固定过渡音时序;**不新增 guide earcon**(统一 8 音词表纪律),时空签名住内容层(sfx+底噪+BGM+锚句)。 -> 5. 信任/真相双轻量标量(radio rep 同款纪律,档位措辞逐值为真);软失败=「灯灭过一刻钟/他自己冲滩」叙事差分,零死亡零倒计时(P4);结算链 800ms 手势死窗(abyss/地下城 H1 同款)。 -> 6. 章程任务 N4(research/new8/00-program.md §2):单局 15-25 分钟、9 抉择点(实选 8-9)、4 结局、分支局部化。 -> 7. 断言清单住本文档 §9(abyss §21 先例;13 号文档 §23 指针待批次测试轮同步登记)。 - -## 1. 概述 - -- **一句话**:风暴夜,孤岛灯塔的电台里,一声比一声近的求救,牵出你家三代守塔人的秘密——守灯,还是救人;说出真相,还是守住秘密。 -- **题材(声音即世界)**:黑暗风暴里视觉归零,**电台人声=另一条命、风暴与雾角=你的处境、老唱片底噪=过去**——「用耳朵守一夜」与纯音频载体天然同构。两个时空只靠音色与底噪区分,声音本身就是叙事本体。 -- **单局**:一个完整风暴夜(一条抉择路径)21-24 分钟;风暴从起到歇=天然三幕,分歧局部化在每幕内部;9 个抉择点(实选 8-9 个),4 结局。 -- **核心循环**:值守(塔内叙事链)→ 电台(阿澈对白+抉择点)→ 抉择结果 →(关键抉择触发**回忆插叙**:唱针落下→父辈/祖辈片段→风声回来)→ 回到现在继续值守 → 幕间喘息。跨幕积累「信任」(阿澈信你有几分)与「真相度」(三代秘密翻开几层),风歇前 p9 决定秘密的归宿。 -- **与 radio/abyss 的差异点**(骨架同、差异层不同):radio 靠电话音质立「声音即存在」,abyss 靠声呐双通道立「用耳朵找人」;keeper 靠**时空音频签名**立「回忆会开口说话」——插叙不是过场动画,是给现在抉择供货的信息与重量(发条知识=插叙2 兑现 p7 生死细节;祖父忏悔=插叙3 给 p9 完整真相可说)。 -- **风险预防(本作特有,策划期显式设计)**:① 时空误认=剧情混乱——签名规格显式教学一次(rev_teach)+上滑时位句+中断复述带时空位(§6.4/§6.5);② 家族人物多音色——三代人+求救者共 3 个 NPC 音色 ≤5,区分度逐人标注(§5.3/voice-ids 头表)。 - -## 2. 状态机 - -### 2.1 状态列表 - -| 状态 | 内容 | BGM | guide.state | -|---|---|---|---| -| `INTRO` | 开场链:塔名口播 + 一句规矩 + 今晚目标 | `bgm/storm` | explore | -| `WATCH` | 值守叙事:现在线对白链 + 抉择点 + 结果 | `bgm/storm` | explore;抉择点=choice | -| `MEMORY` | 回忆插叙:auto 链 ≤6 句,首尾锚句,链末自动回 WATCH | `bgm/memory` | explore(不新增签名) | -| `CALM` | 幕间:风暴进度通报(+跨档信任播报),自动链 ≤12s | `bgm/storm`(不切) | settle | -| `ENDING` | 结局链 + 尾声回响(epilogue),结算态 | `bgm/dawn` | settle | -| `MENU` | 模态覆盖层(任意叙事态下滑呼出,暂停一切链) | `bgm/storm`(低响度淡入) | menu | - -- CALM 自动链 ≤12s:foghorn → lull_N(风暴进度整句)→(跨档才播)信任档位句 → 自动进下一幕;不切 BGM(风暴进程靠 amb_storm 游戏层 gain 分幕:幕一 0.5 / 幕二 1.0 / 幕三 0.7)。 -- 结局链/c3_dark/插叙链入口处盖 `_settleGraceUntil = now+800ms` 手势死窗(abyss §2.2 同款),窗内手势一律吞掉,关键叙事完整播出。 -- MENU 打开时 `guide.disarm()` + 取消一切看门狗/自动链;关闭后重播当前节点(局面复述;MEMORY 中关闭=重播当前回忆节点,底噪仍 vinyl)。 - -### 2.2 转移图(文本) - -``` - ┌───────────── 结算单击=新风暴夜 ─────────────┐ - ▼ │ - INTRO ──链完单击──> WATCH(C1) ──幕一链完──> CALM(1) ──────────┤→ WATCH(C2) - │ …CALM(2) 同型)… - │ p2左/p3左|p4左/p6左 触发: - ▼ - MEMORY ──插叙链 auto──> 回 WATCH(原 now 线续走) - │ - WATCH(C3) ──高潮链完──> CALM-lite(g9) ──> ◆p9 - │ - d9 结果链 ──> ENDING(e1..e4 + ep 回响链) ──┘ - - 任意叙事态(INTRO/WATCH/MEMORY/CALM/ENDING):下滑 → MENU(模态) - MENU:单击=循环切换选项 → 双击=确认执行 → 上滑=帮助 → 下滑/「回到值守」项=关闭回原状态(重播当前节点) -``` - -- 全部叙事态为节点链驱动(radio playNode 模式);CALM 与插叙链 `auto:true`,WATCH 叙事节点链末等待单击(radio 实现核对 #1 同口径)。 -- MEMORY 是显式宏状态(BGM/底噪/存档 timeline 都按它切换),但没有独立等待点:链 auto 完成即回 WATCH;插叙期间开菜单=暂停,关闭=重播当前回忆节点。 - -### 2.3 每状态手势映射(6+2 全覆盖,零豁免) - -| 手势 | INTRO | WATCH | MEMORY | CALM | ENDING | MENU | -|---|---|---|---|---|---|---| -| 上滑 | 帮助+状态,随后重播当前段 | 帮助+状态(时位句+信任/真相档),随后重播当前节点 | 帮助+状态(**prog_mem_g/f 时位句先行**),随后重播当前回忆节点 | 帮助+状态,自动链继续 | 帮助+状态(结局名+信任档) | 帮助(sys_help) | -| 下滑 | 模态菜单 | 模态菜单 | 模态菜单(停插叙链) | 模态菜单 | 模态菜单 | 关闭菜单 | -| 左滑 | 无效 → error 震+gd_invalid | 无效 → gd_invalid(喂无进展轨) | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 | -| 右滑 | 无效 → gd_invalid | 无效 → gd_invalid;ASK 态=新风暴夜 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 | -| 单击 | 播放中=跳过本句;链末=进 C1 | 播放中=跳过本句(p* 与锚句首播不可跳);链末=继续 | 播放中=跳过本句(**锚入/锚出句首播不可跳**);无链末等待(auto 回返) | 跳过本句(自动链) | **再来一夜**(新风暴夜) | 循环切换选项 | -| 双击 | 重听当前段 | 重听当前节点台词(含抉择提示) | **重听当前回忆节点** | 重听幕间链 | 重听结局链 | **确认执行**(菜单约定覆盖重听) | -| 长按 | 引擎接管:旁白临时 1.7×(12 号 §2.2,叙事类不豁免) | 同左 | 同左 | 同左 | 同左 | 同左(快进菜单语音) | -| 长按松开 | 引擎接管:恢复 settings.speed | 同左 | 同左 | 同左 | 同左 | 同左 | - -- 豁免登记:**无**。六个基础手势全部符合全局宪法默认语义;菜单走 §5.2 R3 模态约定(单击循环+双击确认)。 -- ASK(存档询问态,radio 同款):单击=继续上次 / 右滑=新风暴夜(两者 runs 均 +1,cipher §8 统一口径);上滑/下滑/左滑/双击=无效(error 震+gd_invalid)。 -- 抉择 prompt(voice/p1..p9 及 p8_boat/p8_reef/p9a/p9b 变体)播放中单击不可跳(PROMPT_RE 门)、左右滑缓存 `pendingChoice` 播完即生效(radio W2 契约照抄)。 -- 继承 radio G5 跳过公平契约:**首播必完整(p* 首播不可跳 / 插叙首尾锚句与链末关键句保护 / ASK 恢复首句锁)、复播可即掐(锁不随复播重新武装、插叙中段句可跳、复播滑动照常预缓存)**。 -- 无效手势零静默:所有「无效」格均有 error 震 + gd_invalid(onceKey 同局限次,F1.4 idle-only);longpress/longpressend/tapcancel 全状态合法(radio 实现修复 #2 教训)。 -- 键盘通道:三通道并行,游戏代码零改动;操作提示句按 `gesture.inputMode()` 选 `_k` 变体资产(§7.3、voice-ids 集中分组)。 - -## 3. 抉择树 - -### 3.1 全局骨架(三幕共享主干,分歧局部化) - -``` -INTRO: gd_brief → b_open → b_rain → b_rule → b_goal ──链完单击──> C1 -【C1 幕一·风暴起】教学幕 · 2 抉择(p1/p2)+插叙1 ──> CALM(1) ──> C2 -【C2 幕二·风暴顶】2 抉择(p3/p4)+插叙2 +危机拍1 + p5 ──> CALM(2) ──> C3 -【C3 幕三·风歇前】p6(+插叙3) + p7 核心 + 危机拍2 + p8 双形态 ──> 签收 ◆p9 ──> ENDING -``` - -分支纪律:**每幕内部分支、幕末收敛**到 CALM/签收句;跨幕只传标量(trust、rev1/rev2/rev3、p7/p9 派生枚举),不生成组合节点。最坏路径与最好路径节点数同型。 - -### 3.2 分幕结构(◆=抉择点,〔〕=条件差分,【插叙】=MEMORY auto 链) - -``` -【C1 幕一·风暴起】 -c1_tune → amo_a → amo_b → ◆p1 - ├左 接住人: c1_hold → c1_holdo → c1_holde(trust+1)─┐ - └右 要船位: c1_ask → c1_asko → c1_aske(即时纠偏)───┤ - ▼ -amo_c(回澜号伏笔)→ ◆p2 - ├左 翻暗格: c1_open → rev_teach(全局一次) →【插叙1】→ c1_diary(truth+1)─┐ - └右 守灯: c1_focus → c1_focuse ──────────────────────────────────────────┤ - ▼ - CALM(1): foghorn → lull_1 →〔跨档才播 tr_*〕→ C2 - -【插叙1·祖父的年代】memory_in → rev_g1(锚入) → rev_g2 → rev_g3 → rev_g4(锚出) → memory_out - -【C2 幕二·风暴顶】 -c2_back → amo_d → ◆p3 - ├左 口诀: c2_song → c2_songo →【插叙2】→ c2_key(truth+1)─┐ - └右 岸台: c2_shore → c2_shoreo ─────────────────────────────┤ - ▼ -amo_e → ◆p4 - ├左 真话: c4_true →〔插叙2 未触发 ?【插叙2兜底】+c2_key : 直进〕→ c4_trueo(trust+1)─┐ - └右 带过: c4_pass → c4_passo ───────────────────────────────────────────────────────────┤ - ▼ -【危机拍1】hull_crack 前置2s 渐强 + danger 签名/震 → c2_crack → amo_f → ◆p5 - ├左 堵漏: c5_patch → c5_patcho(trust+1)─┐ - └右 救生衣: c5_suit → c5_suito ────────────┤ - ▼ - CALM(2): foghorn → lull_2 →〔跨档才播 tr_*〕→ C3 - -【插叙2·父亲的最后一晚】memory_in → rev_f1(锚入) → rev_f2 → rev_f3 → rev_f4 → rev_f5(锚出) → memory_out - -【C3 幕三·风歇前】 -c3_eye →〔rev2 ? c3_page → ◆p6 : m6_seal 封锁句(播后自动走右,不摆假选项)〕 - ├左 读完:【插叙3】→ c6_reade(truth+1)─┐ - └右 合上: c6_shut ────────────────────────┤ - ▼ -◆p7(核心:守灯还是救人;提示句全表最长) - ├左 离塔救人:〔rev2 ? c7_go(满发条,配 lamp_mech): c7_go2(半卷)〕 - │ →【危机拍2】wave_wall 前置2s+danger → c3_wall → ◆p8_boat - │ ├左 循渔歌: c8_song → c8_songo ─┐ - │ └右 推坐标: c8_math → c8_matho ─┤ - │ ▼ - │ c9_return →〔rev2 ? — : c3_dark(灯灭过一刻钟,800ms 死窗)〕─┐ - └右 守灯引渡: c7_hold →【危机拍2】c3_wall → ◆p8_reef │ - ├左 老航道: c8_old → c8_oldo ─┐ │ - └右 新礁缝: c8_new → c8_newo ─┤ │ - ▼ ▼ - 〔trust≥3 ? c9_land : c9_found〕──────────┴→ 签收(两路通用,电台或塔内对谈) - -【签收】g9_in → g9_knot → ◆p9〔truth=3 ? p9a : p9b〕 - ├左 说出:〔truth=3 ? d9_tell_a : d9_tell_b〕→ amo_g → d9_telle ─┐ - └右 守住: d9_keep → amo_h → d9_keepe ────────────────────────────┤ - ▼ -ENDING: e*_a → e*_b(trust 档 × p9 四选一)→ ep_lead → ep_*(条件选播)→ ep_shore - -【插叙3·祖父的忏悔】memory_in → rev_c1(锚入) → rev_c2 → rev_c3 → rev_c4 → rev_c5(锚出) → memory_out -``` - -### 3.3 分支局部化说明 - -- 跨幕状态全部是标量:`trust`(int 0-4)、`rev1/rev2/rev3`(bool,插叙完成数=truth)、`p7`('boat'|'lamp')、`p9`(bool)。没有任何「幕 i 分支 × 幕 j 分支」组合节点。 -- 条件差分全部是**节点内选播**(radio w1_say/w1_say2、abyss p7 双形态先例):插叙2 兜底(p4 左)、c7_go/c7_go2(rev2)、c9_land/c9_found(trust)、c9_return 后 c3_dark 插播(rev2=false)、p9a/p9b 与 d9_tell_a/b(truth)、ep_* 选播。树形不变。 -- 树规模:148 句台词、约 75 个节点;任一路径到达全部 9 个抉择点,p6 在 rev2=false 时被 m6_seal 自动走右(实选 8-9 个)。 - -### 3.4 四结局达成条件 - -| 结局 | 条件 | 主题 | 开环钩子 | -|---|---|---|---| -| e1 灯与归人 | trust ≥ 3 且 p9=左 | 人接住了,真相也说全了 | 「回澜号的账,翻开了第一页」 | -| e2 守夜人 | trust ≥ 3 且 p9=右 | 人救了,秘密再压一代 | 「有些东西,再压一代人」 | -| e3 半明半暗 | trust < 3 且 p9=左 | 你说了,他半信半疑 | 「碎片也是真话」 | -| e4 各自的塔 | trust < 3 且 p9=右 | 隔海两塔,各自沉默 | 「谁也没再提那晚」 | - -- 尾声回响(P2 回响感):epilogue 逐项点名——阿澈(接回塔/自渡/天亮被找到,按 p7×trust)、灯的记录簿(满发条自转/灭过一刻钟/整夜未眨眼,按 p7×rev2)、祖父日志(见光/压回黑暗,按 p9×truth≥1)、父亲的班次(rev2=true 替他签字);岸台晨报恒播收尾(b_goal 货轮承诺闭环)。玩家每一层翻开的与守住的都被点名。 - -## 4. 数值设计 - -### 4.1 变量 - -| 变量 | 类型 | 初值 | 说明 | -|---|---|---|---| -| `trust`(信任) | int | 0 | 阿澈对你的信任,幕一/二积累,幕三不动 | -| `rev1/rev2/rev3` | bool | false | 插叙1/2/3 完成位;truth = 三者之和 | -| `p7` | str | null | 'boat'(离塔救人)/'lamp'(守灯引渡),核心抉择派生 | -| `endings{}` / `runs` | map/int | — | 结局收集与重玩计数(存档展示用) | - -### 4.2 抉择计分表(左=走心·翻开/说出 / 右=克制·程序/守秘) - -| 抉择 | 左(走心) | 右(克制) | 备注 | -|---|---|---|---| -| p1 接住人/要船位 | trust+1 | 0 | 左路阿澈声音稳住(首个正反馈);右路 c1_aske 即时纠偏不扣分 | -| p2 翻暗格/守灯 | truth+1(rev1=true) | 0 | 左路=插叙1(日志残篇到手,缺最后一页) | -| p3 口诀/岸台 | trust+1,truth+1(rev2=true) | 0 | 左路=插叙2 主触发(发条知识+缺页线索 c2_key) | -| p4 真话/带过 | trust+1;rev2=false 时兜底插叙2(truth+1) | 0 | 插叙2 的第二把钥匙(p3 右的补偿路) | -| p5 堵漏/救生衣 | trust+1 | 0 | 双活路(两路都活着,差分在反应句与尾声心境) | -| p6 读完/合上 | truth+1(rev3=true) | 0 | 左路=插叙3(祖父忏悔全量);**rev2=false 时左路封锁**(m6_seal 播出后自动走右,abyss g9_dry 先例——不摆假选项) | -| p7 离塔救人/守灯引渡 | 0(p7='boat') | 0(p7='lamp') | 核心宿命抉择:不动 trust;后果走 rev2(发条)与 trust(引渡)差分 | -| p8_boat 循渔歌/推坐标 | 0 | 0 | 双活路差分(海上找人方式) | -| p8_reef 老航道/新礁缝 | 0 | 0 | 双活路差分(引渡路线) | -| p9 说出/守住 | 真相出口 | 秘密出口 | 不动 trust;提示句按 truth 选 p9a/p9b | - -### 4.3 信任档位表(账目真实:每档句义对每个取值皆为真) - -| 档 | trust 区间 | 整句 | 真实性核验 | -|---|---|---|---| -| tr_deep | 3-4 | 「他把命,交给了你的声音。」 | ≥3 时 p7 右他完全照做(c9_land)✓;p7 左他海上应答配合 ✓ | -| tr_half | 1-2 | 「他信一半,疑一半。」 | 1-2 时 p7 右他半路改主意(c9_found)✓ | -| tr_thin | 0 | 「今晚,他只信自己的手。」 | 0 = 四个信任点全失,p7 右必自渡 ✓(附 warning 震,radio G2 同款) | - -- 阈值 3 = 「多数走心 + 容忍 1-2 次克制」落在高信任档——把「他是否把命交给你」压在玩家自己的累计上,而非单点(radio rep ≥5 同款纪律)。 -- 跨档被动播报(挂 CALM 链尾,跨档才播);上滑 status 随时可查。 - -**真相度逐值表(truth=插叙完成数,每值一句、逐值为真)** - -| truth | 整句 | 核验 | -|---|---|---| -| 0 | 「旧账压在灯底,你一页没翻。」 | rev1/2/3 全 false ✓(暗格未开) | -| 1 | 「灯下的旧账,你翻开了一角。」 | 仅插叙1(日志残篇)✓ | -| 2 | 「祖父和父亲的事,你都翻到了。」 | 插叙1+2 ✓(缺页在手未读) | -| 3 | 「三代人的账,今夜见底了。」 | 插叙1+2+3 ✓(忏悔全文,p9a 解锁) | - -### 4.4 结局与差分验算(设计期穷举,playtest 剧本 A-F 引用) - -- **trust ∈ {0..4} 全档可达**(四个独立 ±1 抉择 p1/p3/p4/p5);e1/e2 ⟺ trust≥3(如 p1左 p3左 p4左 p5右=3),e3/e4 ⟺ trust≤2(全右=0)。 -- **truth ∈ {0..3} 全档可达**:rev1=p2左;rev2=p3左∨p4左;rev3=p6左∧rev2。0(p2右p3右p4右)、1(p2左+双右)、2(p2左+p3左)、3(再 p6左)。 -- **p6 封锁与 p9 变体联动**:rev2=false → m6_seal 自动右;truth=3 → p9a/d9_tell_a(全真相),truth<3 → p9b/d9_tell_b(碎片:只确认「那晚守塔的是我爷爷、灯灭过一下」——前者是家族常识、后者来自阿澈自己的话,**truth=0 也句句为真**,归因真话律)。 -- **灯三态**(p7×rev2):p7 左+rev2 → ep_lamp_full(满发条自转,rev_f4 知识兑现);p7 左+¬rev2 → c3_dark+ep_lamp_dark(半卷发条走到头,**复刻祖父之夜**,归因指向「没听完父亲那一晚」的实际选择);p7 右 → ep_lamp_hold(整夜未停)。 -- **阿澈三态**(p7×trust):p7 左 → 恒被接回(c9_return,人已上艇不失去);p7 右+trust≥3 → 自渡成功(c9_land);p7 右+trust<3 → 天亮被渔队找到(c9_found,活着,措辞克制——零死亡红线)。 -- **最坏路径完整性**:剧本 C(全右)仍走完全部 9 抉择点、3 结局分支可差分(ep_amo_dawn/ep_lamp_hold/ep_log 不播)——树深固定,无死路。 -- **纸面承诺安全性**:trust≥3 ⟹ p3/p4 至少一左 ⟹ rev2=true ⟹ 缺页在手(c2_key)——e1_a「那页纸攥在手里」/e2_b「缺页放回灯底」对 trust≥3 两结局**恒为真**;e3/e4(trust<3)文案零纸面引用,truth 任意取值皆不穿帮(归因真话律在结局文案侧的落点)。 - -### 4.5 家族秘密 gating 顺序(秘密揭示的三把钥匙与三道门) - -| 层 | 秘密 | 钥匙(抉择) | 门(插叙) | 兑现(现在线收益) | -|---|---|---|---|---| -| 层1 | 灯下藏着东西 | p2 左(翻暗格) | 插叙1(祖父:「灯下压着的东西,比灯重」) | 日志残篇 + 缺页悬念(tru_one) | -| 层2 | 父亲的最后一次值守 | p3 左(口诀)或 p4 左(真话,兜底) | 插叙2(父亲电台:「上满发条能自己转一夜」) | 手电缺页线索(c2_key)+ 发条知识(c7_go 差分) | -| 层3 | 祖父的忏悔(回澜号真相) | p6 左(**前置=层2 已开**) | 插叙3(祖父:「我离灯三刻,回澜号,七个人」) | p9a 完整真相可说 + e1/e2 重量 | - -- 顺序不可跳:层3 的门只认层2 的钥匙(m6_seal 封锁);层2 有两把钥匙(p3/p4)保证「错过 p3 仍可全真相」(补偿路,abyss hasLog 双入口同型)。 -- 阿澈的回环:amo_c(幕一伏笔「我爷爷……白角的灯灭过一下」)→ amo_g(p9 左揭晓「我爷爷,就在那七个人里」)——玩家的抉择把两条家族线缝上。 - -## 5. 音频三层映射 - -### 5.1 BGM(状态→曲目,Mureka instrumental,3 首 ≈ $0.09;台账 tools/mureka_log.md) - -| 状态 | 曲目 | 切换 | Mureka prompt 草稿(英文,seamless loop) | -|---|---|---|---| -| INTRO/WATCH/CALM | `bgm/storm` | 开局淡入 800ms,全夜不切;语音 duck 引擎自理 | "Storm night lighthouse vigil, tense low piano motif over dark strings, restrained and lonely, distant thunder and wind, no vocals, seamless loop, 60-90 seconds" | -| MEMORY | `bgm/memory` | **插叙进入** crossfade 800ms;回返淡回 storm 800ms | "Old phonograph memory waltz, music box and muted strings with vintage vinyl warmth, fragile nostalgia, slightly out-of-time, no vocals, seamless loop, 60-90 seconds" | -| ENDING | `bgm/dawn` | d9 结果链播完后淡入 1500ms | "Calm grey dawn after a storm at sea, soft piano and warm strings, gulls far away, quiet relief, no vocals, seamless loop, 60-90 seconds" | -| MENU | `bgm/storm`(低响度) | 呼出低响度淡入 500ms;关闭淡回 800ms | 同 storm(省 1 首,塔内场景同构;abyss menu 用 deck 先例) | - -- `bgm=0` 时全部静默(引擎约定);幕的推进不靠切曲——靠 amb_storm 游戏层 gain 分幕(0.5/1.0/0.7)+ lull_N 进度整句(账目在语音不在音乐)。 - -### 5.2 SFX 清单(SA3=Stable Audio 3 / SYN=程序合成;one-shot 真峰值 ≤-3dBFS;amb 循环由游戏层 `buffer.loop` 实现,radio phone_ring 先例) - -| id | 用途 | 来源 | prompt(英文)/ 合成参数 | -|---|---|---|---| -| `sfx/amb_storm` | 风暴底噪 loop(现在时签名①;分幕 gain 0.5/1.0/0.7) | SA3 | "Storm wind and heavy waves hitting a lighthouse rock, sustained gale, night, seamless loop" | -| `sfx/amb_room` | 塔内底噪 loop(炉火+木构+雨打玻璃) | SA3 | "Inside a lighthouse room at night, oil stove crackle, wooden beams creak, rain on glass, seamless loop" | -| `sfx/amb_vinyl` | **老唱片底噪 loop(回忆时签名②;插叙期间唯一底噪)** | SA3 | "Vintage 78 rpm phonograph record surface noise, warm crackle and hiss, no music, seamless loop" | -| `sfx/memory_in` | **插叙进入过渡音:唱针落下+转速走稳** | SA3 | "Phonograph needle dropping onto a record, brief run-in groove then steady rotation, 1 second" | -| `sfx/memory_out` | **插叙回返过渡音:唱针抬起+轻划** | SA3 | "Phonograph needle lifting off a record with a short scratch, 0.6 seconds" | -| `sfx/radio_tune` | 调频找台(C1 首通/信号重连) | SA3 | "Turning an old shortwave radio dial through static, settling on a weak signal, 3 seconds" | -| `sfx/static_burst` | 阿澈信号断续气口(信息音,idle-only) | SA3 | "Shortwave radio static burst with faint ghost of a distant voice, 2 seconds" | -| `sfx/radio_beep` | 电台接通确认 | SYN | 双音上行 0.3s(abyss link_beep 同款) | -| `sfx/amb_engine` | 阿澈侧渔船引擎 loop(通话期对侧 amb,voice 在播时 -6dB) | SA3 | "Small fishing boat engine running rough in a storm, water sloshing in bilge, seamless loop" | -| `sfx/hull_crack` | 危机拍1:船板开裂(前置 2s 渐强) | SA3 | "Wooden boat hull cracking under stress, splintering and groaning, 3 seconds" | -| `sfx/wave_wall` | 危机拍2:浪墙(前置 2s 渐强) | SA3 | "Massive wave wall rising and crashing, deep roar, 4 seconds" | -| `sfx/lamp_mech` | 灯座机械上发条(c7_go;rev_f4 兑现音) | SA3 | "Clockwork lighthouse rotation mechanism being wound, heavy ratchet clicks, 3 seconds" | -| `sfx/hatch_open` | 灯座暗格开启(c1_open) | SA3 | "Old hidden compartment sliding open, metal and wood scrape, 2 seconds" | -| `sfx/flash_click` | 手电尾盖旋开两响(c3_page) | SYN | 两次短金属 click(120ms 间隔) | -| `sfx/foghorn` | 雾角两长一短(b_rain 承诺音:开场/幕间×2/ep_shore) | SA3 | "Lighthouse foghorn, two long and one short blast, deep brass, distant storm behind, 4 seconds" | -| `sfx/ui_confirm` / `ui_menu` | 菜单/确认(程序合成兜底) | SYN | 同全局先例 | - -- 预警窗纪律:hull_crack/wave_wall 均**前置 2s 起渐强**(L2),danger 签名与 danger 震同窗(radio C5/abyss D2-D4 先例);全游戏仅此两处 danger。 -- 承诺兑现绑定:foghorn 在 b_rain 口播「两长一短」后,开场/CALM(1)/CALM(2)/ep_shore 四处必播(§6.5 清单 + §9 断言计数)。 - -### 5.3 Voice 划分(详见 21-keeper-voice-ids.md) - -| 组 | 声部 | 落位 | 数量 | -|---|---|---|---| -| 守塔人(你)KEEPER | 双性别(female=Serena / male=Uncle_Fu + KEEPER instruct 常量) | `voice/female\|male/` | 42 句 | -| 岸台/系统/向导 SHORE | 双性别(female=Vivian / male=Dylan + SHORE instruct 常量) | `voice/female\|male/` | 79 句 | -| NPC 平铺 ×3 | 阿澈=Dylan(电台滤波)/ 父亲=Uncle_Fu(唱片滤波)/ 祖父=Eric(唱片滤波),**不做双性别** | `voice/` 根(平铺回退命中)+ 滤波转码 | 27 句 | - -- NPC ≤5 约束满足(3 人);玩家自己的声音跟随全局 `settings.voice`;岸台值班员是向导人设(L4 角色化系统声,radio 导播/abyss 调度同型)。 -- 合计 148 句(≤160 预算 ✓,含 `_k` 键盘变体 8 对);音频文件 = 121×2 + 27 = 269 条(×2 格式 = 538 文件)。 - -## 6. 时空双线设计(本作核心差异层) - -### 6.1 引擎能力边界(对照 web-engine-api.md) - -| 能力 | 引擎现状 | 方案 | -|---|---|---| -| voice 运行时 EQ/滤波 | ❌ voice 链无滤波插入点 | **资产侧转码滤波**(radio §6.2 先例):ffmpeg `highpass+lowpass+压缩器` 逐 NPC 定档(§5.3/voice-ids) | -| BGM crossfade | ✅ fadeMs 800ms 内置 | 插叙进入/回返直接 `play('bgm/...')` 切换 | -| sfx 循环 | ❌ 即发即弃 | 游戏层 `buffer.loop=true` 持句柄(amb_storm/amb_room/amb_vinyl/amb_engine),插叙切换时淡入淡出(radio `_ringSrc` 先例) | -| sfx vs voice 互让 | ❌ sfx 总线独立无 duck | **游戏层 idle 门**(§6.6 R1):memory_in/out、static_burst 只在 `!audio.isVoicePlaying()` 时放行,否则挂队列表句间补发 | -| 语音 duck 垫乐 | ✅ 内置(duck 20%,300ms) | 直接依赖 | -| 变速不变调 | ✅ SoundTouch | 直接依赖(长按 1.7× / 2.2× 视障档) | - -### 6.2 时空签名规格(误认=剧情混乱,规格逐维度显式) - -| 维度 | 现在(风暴夜) | 回忆(老唱片) | -|---|---|---| -| BGM | `bgm/storm`(低音钢琴+暗弦+远雷) | `bgm/memory`(八音盒+老唱片暖染圆舞) | -| 底噪 | amb_storm(分幕 gain)+ amb_room | **amb_vinyl(唯一底噪:storm/room 双双淡出)** | -| 过渡音 | 回返:memory_out + storm 渐起 | 进入:memory_in(唱针落下) | -| 语音音质 | KEEPER/SHORE 不滤波(塔内近讲);阿澈=电台滤波 | 祖父/父亲=唱片滤波;**KEEPER 锚句不滤波(「现在的你」在回忆里旁白——干净人声 vs 唱片人声,强化「你在看回忆」)** | -| 时位句 | prog_m1/m2/m3(上滑链首段) | prog_mem_g / prog_mem_f(上滑链首段 + 恢复链首段) | -| 台词锚 | (now 句自然在场) | 锚入 rev_g1/rev_f1/rev_c1 + 锚出 rev_g4/rev_f5/rev_c5 | - -- **earcon 纪律:时空切换零新增 guide earcon**——统一 8 音词表中 MEMORY/WATCH 同用 `explore` 签名;时空签名住**内容层**(sfx 过渡音+底噪+BGM+台词锚),语义靠语音(L2 铁律:earcon 只做粗标记,训练 8.5 轮结论不赌)。 -- 底噪互斥是硬约束:任意时刻 amb_vinyl 与 amb_storm/amb_room **零重叠**(§9 转写断言);插叙期间阿澈的 amb_engine 也停(叙事自洽:插叙都发生在塔内独处拍,电台此刻静默)。 - -### 6.3 插叙触发与回返时序(精确到步骤) - -| 步 | 事件 | 时序 | -|---|---|---| -| 1 | now 结果句播完 → 静默一拍 | 600ms | -| 2 | `memory_in`(1.0s,idle 门放行)+ BGM crossfade storm→memory 800ms + amb 切换(storm/room 淡出 400ms / vinyl 淡入 400ms) | 与步 2 同时起 | -| 3 | 插叙链 auto:锚入(KEEPER,不可跳首播)→ NPC 2-3 句(中段可跳)→ 锚出(KEEPER,不可跳首播) | ≤6 句 ≤90s | -| 4 | `memory_out`(0.6s)+ crossfade memory→storm 800ms + amb 回切 | 链末即起 | -| 5 | 回返后 now 句接播(c1_diary / c2_key 或 c4_trueo / c6_reade) | memory_out 播完接 | - -- 插叙链入口 800ms 手势死窗(§2.2);链末**不设等待**(auto 回返——回忆不是关卡,是信息颗粒);「停下来回味」由双击重听当前回忆节点承接(叙事类等待点合法回味 + 可重听,A2/P1)。 -- 插叙触发点全集(3 处):p2 左→插叙1;p3 左或 p4 左→插叙2(兜底);p6 左→插叙3(前置 rev2=true)。 - -### 6.4 「我在哪」复述机制(P3 打断回来说得清) - -| 层 | 机制 | -|---|---| -| 被动 | 底噪+BGM 时空签名 2s 可辨(A1:有无唱片底噪/风声) | -| 主动 | 上滑 status 链**首段=时位句**:now → prog_m1..3;memory → prog_mem_g(祖父年代)/ prog_mem_f(父亲最后一晚)——「现在是回忆」可随时重听确认 | -| 台词锚 | 每段插叙首尾锚句 = 台词级时空位(「唱针落下。四十年前……」「风声回来了……」) | -| 中断恢复 | 存档带 `timeline`('now'/'mem_g'/'mem_f');恢复链 = timeline=mem* 时**先播 prog_mem_*** 再重播当前回忆节点(§9 断言:恢复首句=时位句, ≤5s 回到可玩) | -| 菜单复述 | MEMORY 中开菜单关闭后重播当前回忆节点,底噪仍 vinyl(timeline 不丢) | - -### 6.5 教学承诺清单(dungeon「铁器=楼梯」教训:每句承诺必须真实存在且可感知) - -| 教学句 | 承诺 | 兑现点(断言锁定) | -|---|---|---| -| rev_teach「听到唱针落下、风声退后,是回忆。风声回来,就是现在」(全局一次,首插叙前) | 时空签名四要素 | 每次插叙必有 memory_in+vinyl+锚入 / memory_out+storm+锚出;全剧本零漏(§9) | -| b_rain「雾角两长一短——塔在,我也在」 | 雾角=塔的报平安音 | foghorn 播点=开场/CALM(1)/CALM(2)/ep_shore 恰四处 | -| b_rule「风暴夜,灯不能灭」 | 灯=规矩本体 | 灯三态兑现:ep_lamp_full/dark/hold + c3_dark 差分归因(§4.4) | -| b_goal「今夜两艘货轮过境。守住灯,也守住频道」 | 货轮/频道双目标 | c3_dark「过境的货轮,平安」+ ep_shore 晨报「航道平安」闭环 | -| rev_f4「塔上的机械上满发条,能自己转一夜」 | 发条知识 | p7 左 rev2 差分:c7_go/c7_go2、ep_lamp_full/ep_lamp_dark(剧本 A/E 对照) | -| prog_m1..3 幕句=风暴三进程 | 「风暴起/到顶/在退」 | CALM 切幕+amb_storm gain 分幕(0.5/1.0/0.7)一致 | - -### 6.6 通道仲裁规则(并发声流 ≤2) - -- **R1 过渡音不叠语音**:memory_in/out、static_burst 与任何 voice 播放区间零重叠——idle 门只在 voice 空闲放行,在播则排队到句间空隙(abyss sonarGate 同型)。 -- **R2 插叙主导段**:前景=插叙链人声+vinyl 底噪,BGM=memory 曲;电台(阿澈)此刻静默,叙事自洽。 -- **R3 通话期底噪退位**:阿澈通话期间 amb_engine 常驻低响度、voice 在播时游戏层降 -6dB(abyss R3 先例);amb_storm 同步 -4dB——「海况让给人声」。 -- **R4 危机拍优先**:hull_crack/wave_wall 即发即弃可叠底噪;danger 签名+danger 震同窗;当前 voice 句播完后危机链才插续。 -- **R5 回忆可重听**:MEMORY 双击=重听当前回忆节点(关键线索可重听,P1);插叙中段句可跳、首尾锚句首播不可跳(G5)。 -- 声流账:任意时刻 ≤2 条前景流(voice 串行 1 + 信息音/底噪 1),BGM/amb 为背景层——ASA ≤2 约束。 - -## 7. 向导层接入 - -### 7.1 状态签名 earcon / turn cue - -| 时机 | 向导动作 | 说明 | -|---|---|---| -| INTRO 进入 | `guide.state('explore')` | 叙事态签名 | -| WATCH/MEMORY 进入 | `guide.state('explore')` | 同一枚签名(时空不换 earcon,§6.2 纪律) | -| 每个抉择点 p* 起播 | `guide.state('choice')` | 选项句本身即 turn cue(W2 契约零插播) | -| CALM 进入 | `guide.state('settle')` | 喘息/结算签名 | -| ENDING 进入 | `guide.state('settle')` | 同上 | -| MENU 打开 | `guide.state('menu')` | 菜单签名 | -| 危机拍1/2 | `guide.state('danger')` | 音效前置 2s 渐强同窗(全游戏仅两处) | -| 无效手势 | `guide.error({voice:'gd_invalid', onceKey})` | error 震 + full 档语音,idle-only(F1.4) | - -- earcon 词表:只用全项目统一 8 音(menu/explore/battle/choice/settle/danger/turn/nudge),**零新增**;battle 不用。 - -### 7.2 看门狗双轨(radio F1 + dungeon E0-1 教训:乱按也要被抢救) - -**无操作轨**(guide watchdog,任何手势复位;两级不同句、用尽即停、永不惩罚): - -| 等待点 | full 档 | standard 档 | 级别内容 | -|---|---|---|---| -| INTRO/WATCH/CALM 链末等待 | [25, 50] | [13, 30] | ① gd_continue ② gd_again(storyline F1.1 同款) | -| 抉择点 p* 播完无操作 | [25, 50] | [25, 50] | 重播当前 p* 提示句(13 号 §16 先例;空点单击=重挂计时) | -| ENDING 结算等待 | [25, 50] | [13, 30] | gd_continue / gd_again | -| MEMORY 链末 | 不挂(auto 回返) | 不挂 | 资产缺失兜底:30s 单句看门狗且 `isVoicePlaying()===false` 才续(radio N1 先例) | - -**无进展轨**(游戏层计时,dungeon E0-1 ②同型;挂 WATCH 链末与 p* 等待两个可饿死点): - -| 项 | 规则 | -|---|---| -| 进展定义 | 节点推进 / 抉择结算 / 重听。**无效手势(滑错、空点)不计进展**——无操作轨被它们复位、无进展轨不被复位,乱按 96s 必被抢救 | -| 阈值 | 60s → gd_prog1;120s → gd_prog2;用尽即停(每等待点一轮) | -| 有真实进展 | 全程零 prog 播出(不误伤正常玩家) | -| 双轨去重 | 两轨短句内容同级交叉登记;同一等待点先播过 gd_* 同义级则无进展轨跳过 | - -- 向导插播一律 `queue:true`,绝不顶掉在播对白;error/watchdog 类语音 idle-only(在播则跳过且过期丢弃,F1.4)。 - -### 7.3 键盘变体机制(PC 键盘玩家) - -- 运行时按 `gesture.inputMode()`(settings.input auto/touch/key)选 `_k` 变体资产:key → `voice//_k.*`,touch → 无后缀版;auto 跟随最近使用通道。`_k` 缺失回退无后缀版(check_all_assets 校验配对)。 -- 变体清单(8 对,集中分组见 voice-ids §G0.a 与 C1 首个抉择):gd_brief / gd_continue / gd_again / sys_help / sys_menu / sys_skip / sys_continue_ask / **p1**(首个抉择的键位绑定)。 -- 抉择提示 p2..p9 措辞统一「向左滑/向右滑」;键盘玩家由 p1_k 与 sys_help_k 完成首次绑定(左=←,右=→),后续语义不变。启动遮罩文案引擎自理(游戏零改动)。 - -### 7.4 继承 radio 三轮验证机制(照抄不重设计) - -| 机制 | 来源 | 落点 | -|---|---|---| -| 帮助链去重(同等待点第 2 次上滑只震+nudge) | radio G1 | `_helpSeen` 按等待点键控(MEMORY 按插叙段键控) | -| 状态链顺序「状态在前、帮助在后」 | radio G6 | prog_m* →〔mem: prog_mem_*〕→ tr_* → tru_* → sys_help | -| 空点确认轻震(不可跳句上单击补 beat [8]) | radio G4 | p*/锚句/F4 锁各 tap 屏蔽分支 | -| tr_thin 播报位附 warning 震 | radio G2 | 全部 tr_thin 出口(含上滑复听) | -| 菜单报名游戏侧排队零截断 | radio 实现修复 #3 | `_menuQueue` 排水 | -| stop() 先 `seqToken++` 作废在途链 | radio 实现修复 #6 | 同款 | -| 新局清 choiceArmed/pendingChoice | radio 实现修复 #7 | newNight() | -| 长按误报无效(longpress 全状态合法) | radio 实现修复 #2 | 仅 left/right(ASK 另含 up/down/doubletap)报无效 | - -## 8. 存档设计 - -### 8.1 进度 data 结构(PUT /api/progress {game:'keeper'}) - -```json -{ - "schema": 1, - "updatedAt": "2026-09-09T12:00:00Z", - "summary": "第2幕·风暴顶 · 信任:信一半 · 结局2/4", - "phase": "WATCH", - "node": "amo_e", - "act": 2, - "timeline": "now", - "trust": 2, - "flags": { "rev1": true, "rev2": true, "rev3": false }, - "p7": null, - "endings": { "e2": true }, - "runs": 3 -} -``` - -- 写入时机:每幕 CALM 进入、每个抉择结算后、ENDING 进入、菜单「保存并回大厅」。 -- 断点续玩:读档 → sys_continue_ask(_k)(单击=继续上次 / 右滑=新风暴夜,runs 均 +1 统一口径);继续 = 恢复 phase+node+timeline+全部数值并**局面复述**:timeline=mem* 时恢复链 = prog_m* → prog_mem_g/f → 重播当前回忆节点(时位句先行,「我在哪个时空」一句话说清);now 则直接重播当前节点链;重进 ≤5s 可玩(L5);恢复首句受 F4 跳句锁(radio G5 例外条款)。 -- summary 格式:进行中 `第N幕·<幕名> · 信任<档名> · 结局X/4`(幕名=风暴起/风暴顶/风歇前);已收夜 `风暴夜已过·<结局名> · 结局X/4`(大厅卡片直接显示)。 - -## 9. playtest 断言清单(登记 13 号文档 keeper 节,格式同 §21;`settings.set({guide:'off'})` 纯玩法验证,向导行为住 guide.test) - -**关键路径**(剧本 A:信任满·全真相·出海接人·说出 → e1,≤60 次手势): -1. 遮罩点按 → INTRO 链(gd_brief → b_open → b_rain → b_rule → b_goal,voice 层有播放记录)→ 链末单击 → C1:c1_tune → amo_a → amo_b → ◆p1。 -2. p1 左 → c1_hold(o)(e)(trust=1,首个正反馈)→ amo_c → p2 左 → c1_open(配 hatch_open)→ rev_teach(全局一次)→ **插叙1 时序断言**:memory_in → bgm 切 memory → rev_g1..g4 → memory_out → storm 回 → c1_diary(truth=1)→ CALM(1):foghorn+lull_1+tr_half(跨档)→ C2。 -3. C2:c2_back → amo_d → p3 左 → c2_song(o) → 插叙2(rev_f1..f5,同款时序)→ c2_key(truth=2)→ amo_e → p4 左 → c4_true →〔插叙2 已完成:兜底不播〕→ c4_trueo(trust=3)→ 危机拍1(hull_crack 前置 2s 与 danger 同窗)→ c2_crack → amo_f → p5 左(trust=4)→ CALM(2)。 -4. C3:c3_eye → c3_page → p6 左 → 插叙3(rev_c1..c5)→ c6_reade(truth=3)→ p7 左 → c7_go(配 lamp_mech,满发条版)→ 危机拍2(wave_wall+danger+danger 震)→ c3_wall → p8_boat 左 → c8_song(o) → c9_return → g9_in → g9_knot → **p9a**(truth=3 变体)→ 左 → d9_tell_a → amo_g → d9_telle → ENDING(e1):e1_a/b + ep_lead + ep_amo_home + ep_lamp_full + ep_log_told + ep_dad + ep_shore → 结算单击重开回 INTRO。 - -**边界断言**: -- **四结局可达(含信任逐点验算)**:剧本 B(p1左 p2左 p3左 p4左 p5左 p6右 p7右 p8_reef右 p9右,trust=4/truth=2)→ e2:c9_land + p9b + ep_amo_solo/ep_lamp_hold/ep_log_kept/ep_dad;剧本 C(p1右 p2右 p3右 p4右 p5右 → m6_seal 自动右 → p7右 p8_reef左 p9左,trust=0/truth=0)→ e3:c9_found + p9b/d9_tell_b + ep_amo_dawn/ep_lamp_hold,**ep_log_* 两句皆不播**(truth=0 日志未出场)、**ep_dad 不播**(rev2=false);剧本 D(同 C 但 p9 右)→ e4。各局 trust/truth 逐检查点与 §4.2 一致。 -- **插叙触发与回返**:p2 左→插叙1、p3 左→插叙2、p6 左→插叙3 的节点顺序断言;**剧本 F**(p1左 p2左 p3右 p4左 p5左)断言插叙2 兜底在 c4_true 与 c4_trueo 之间播出且 c2_key 随后播出;回返后 now 线下一节点正确(c1_diary / c2_key 或 c4_trueo / c6_reade);插叙链 auto 完成即回 WATCH(无链末等待)。 -- **时空签名断言(核心)**:全剧本转写断言——amb_vinyl 与 amb_storm/amb_room 播放区间**零重叠**;每次插叙 memory_in/memory_out 成对出现且计数=插叙完成数;锚入句(rev_g1/rev_f1/rev_c1)与锚出句(rev_g4/rev_f5/rev_c5)在位且首播不可跳(单击=beat 轻震不跳句);插叙期间 bgm=memory、回返后 bgm=storm;插叙期间 amb_engine 停播。 -- **教学承诺兑现**(§6.5 清单逐条):rev_teach 全局恰一次(第二次插叙前不重播);foghorn 计数=开场+CALM(1)+CALM(2)+ep_shore 恰四处;rev_f4→剧本 A 播 c7_go/剧本 E 播 c7_go2(发条差分);b_goal→c3_dark 与 ep_shore 货轮闭环;prog_m1..3 与三幕 amb gain 档一致。 -- **p6 封锁不摆假选项**:rev2=false 时 p6 提示句不播、m6_seal 播出后自动走 c6_shut,该点左右滑无效果(不丢不误触)。 -- **信任/真相账目真实**:上滑链顺序 prog_m* →〔mem: prog_mem_g/f〕→ tr_* → tru_* → sys_help(G6);档位句与当前值一一对应(tr_deep≥3 / tr_half=1-2 / tr_thin=0;tru 四值逐句);tr_thin 各播报位附 warning 震。 -- **看门狗双轨**:WATCH 链末 25s/50s → gd_continue/gd_again;p* 播完 25s/50s → 当前 p* 重播两次后静默;**乱按 96s**(每 2s 注入一个无效滑动):无操作轨零播出 + 无进展轨 60s→gd_prog1、120s→gd_prog2;有真实进展全程零 prog;MEMORY 链末不挂看门狗;`guide=off` 全程零定时器零向导事件。 -- **键盘变体**:settings.input='key' 会话播出 gd_brief_k/sys_help_k/p1_k 且对应无后缀版零播出;'touch' 反之;'auto':keydown 后下一句用 _k。 -- **中断复述含时空位**:杀进程重进(存档 timeline='mem_f'、node=rev_f3)→ sys_continue_ask → 单击 → 恢复链首句=prog_m2 → prog_mem_f → 重播 rev_f3 起链(恢复首句不可跳,第二句起可跳);now 态存档(node=amo_e)恢复=重播该节点链。 -- **pendingChoice**:p* 播放中左右滑 → 播完立即生效(不丢、不重复、连滑取最后一次)。 -- **p* 不可跳**:p* 播放中单击 → 不跳句(PROMPT_RE 门)+ beat 轻震(G4)。 -- **idle-only**:向导 gd_* 在对白在播时跳过且不占 onceKey(F1.4);queue 句排在当前句后零截断。 -- **菜单**:下滑开菜单 → 上滑帮助 → 双击「回到值守」→ phase/node 不变、当前节点重播;**插叙中开关菜单**后底噪仍 vinyl、重播当前回忆节点;单击循环顺序 = [回到值守, 重新开始今夜, 保存并回大厅];确认回大厅 → sys_menu_lobby 播出且 PUT/localStorage 有最新 data。 -- **结算链死窗**:e* 结局链/c3_dark/插叙链开播后 800ms 内注入在途 tap → 手势被吞、链完整有序播出。 -- **危机拍**:hull_crack/wave_wall 前置 2s 渐强、danger 签名与音效同窗、danger 震遍间隔 ≥400ms;当前 voice 句播完危机链才插续。 -- **资产缺失**:屏蔽任意 voice 资产 → 30s 看门狗自动续链到结局不卡死;NPC 滤波资产缺失回退原声可播。 -- **数字规范**:prog_*/tr_*/tru_*/gd_* 全部整句字面量,静态审计无运行时拼接碎片。 - -**失败即不通过**:任何等待 >5s 无声(静默死局);无效手势零反馈;**vinyl 与 storm/room 底噪重叠**;**插叙缺过渡音或缺锚句**;结局计数与 §3.4 不符;档位句与 trust/truth 值不符。 - -## 10. 节奏曲线(风暴三幕) - -| 段 | 内容 | 目标时长 | 设计目标 | -|---|---|---|---| -| 开场 | 塔名+规矩+目标+第一声呼救 | 1-1.5 min | 首输入 ~30s(INTRO 链 ~30s 链末单击);规矩一句讲完(P4);雾角承诺音埋下 | -| 幕一 风暴起 | p1/p2 + 插叙1 + CALM(1) | 4-5 min | **首个正反馈 ≤3min**(~2.5min p1 左阿澈声音稳住);**时空签名教学+首次兑现**(rev_teach→插叙1 四要素全播);两抉择条条有出路 | -| 幕二 风暴顶 | p3/p4 + 插叙2 + 危机拍1 + p5 | 6-7 min | 情感段(父亲真相 p4);第一危机拍(前置 2s 预警);插叙2 供货发条知识(p7 兑现预告) | -| 幕三 风歇前 | p6+插叙3 + p7 核心 + 危机拍2 + p8 | 7-8 min | 紧张峰值:三代秘密见底→「守灯还是救人」宿命抉择→浪墙下的执行差分;**复刻祖父之夜的暗线**(剧本 E 灯灭) | -| 签收+结局 | p9 + 结局链 + 尾声回响 | 2.5-3 min | 心结抉择(说/瞒×全/碎片)+ ep 逐项点名(P2 回响);开环钩子拉下一夜 | - -- 合计 ≈ 21-24 min(15-25 达标);每 30-60s 一个反馈颗粒(阿澈反应句/结果句/档位句/插叙锚句),无沉默尴尬期(P3)。 -- 难度曲线 = 情感与信息复杂度(接住人→翻开过去→宿命抉择→秘密归宿),操作复杂度恒定(永远二选一)——P4「规则一遍听懂」由恒定操作面保证;风暴强度=氛围曲线(amb gain 三档),不构成玩法压力(无倒计时无死亡)。 - -## 11. 自检清单(策划交付自验;☐ = 待开发/测试轮填) - -| # | 硬约束/教训 | 落地 | ✓ | -|---|---|---|---| -| 1 | FTUE 首输入 <60s | INTRO 链 ~30s 链末单击(§10) | ☑ | -| 2 | FTUE 首个正反馈 ≤3min | ~2.5min p1 左阿澈稳住(c1_holdo+c1_holde) | ☑ | -| 3 | 学习期零惩罚 | 幕一全组合不扣资源;右路只即时纠偏(c1_aske);无超时惩罚 | ☑ | -| 4 | 失败三问归因+归因真话律 | c1_aske/c3_dark/c9_found 指向实际选择;d9_tell_b 在 truth=0 仍句句为真;§4.4 无假归因 | ☑ | -| 5 | 资源账目真实(radio rep_low 教训) | tr_* 三档逐区间为真;tru_* 四值逐值为真;跨档才播 | ☑ | -| 6 | 手势宪法 6 手势默认语义零豁免 | §2.3 全覆盖;菜单 §5.2 模态约定 | ☑ | -| 7 | 长按显式登记 | 无豁免:长按=引擎快进(12 号叙事类不豁免),§2.3 在案 | ☑ | -| 8 | 向导:签名/turn cue/看门狗两级不同句 | §7.1/§7.2;gd_continue→gd_again;MEMORY 链末不挂 | ☑ | -| 9 | 看门狗双轨(dungeon E0-1/radio F1 教训) | 无操作轨+无进展轨(§7.2);乱按 96s 必被抢救 | ☑ | -| 10 | 插播 queue 不顶对白;纠错 idle-only 过期丢弃 | §7.2(F1.4 契约);过渡音 idle 门(§6.6 R1) | ☑ | -| 11 | 死亡/失败结算链完整(dungeon H1 教训) | e* 链/c3_dark/插叙链 800ms 手势死窗;阿澈恒活着(零死亡红线) | ☑ | -| 12 | 教学承诺兑现(dungeon「铁器=楼梯」教训) | §6.5 承诺清单 6 条逐条绑兑现点+断言 | ☑ | -| 13 | 单次播报 ≤3 选项 | 全部抉择恒 2 选项;菜单 3 项循环 | ☑ | -| 14 | 并发声流 ≤2 + 通道仲裁 | voice 串行+底噪/信息音互斥(R1-R5,§6.6);断言零重叠 | ☑ | -| 15 | earcon ≤7、统一词表、语义靠语音 | 只用统一 8 音中 7 个(battle 不用),零新增;时空签名住内容层 | ☑ | -| 16 | 台词一句一文件 ≤40 字、数字分档整句、无运行时拼接 | voice-ids 全表 ≤35 字(最长 p7);prog/tr/tru 分档整句;句内数字写死 | ☑ | -| 17 | 多音字规避/同 instruct 常量/无语速词 | voice-ids 头部+文末自查(落/数/缝/卷/转/重 试听项) | ☑ | -| 18 | PC 键盘变体(提示句集中+标注) | 8 对 _k 变体集中分组(voice-ids §G0.a);inputMode() 运行时选择 | ☑ | -| 19 | P1:可打断/重听(含回忆节点)/status key/不当白痴 | 跳句+双击(MEMORY 重听 R5)+上滑时位句链+成功零补救教学 | ☑ | -| 20 | P2:3 分钟见效/结局回响 | ~2.5min 阿澈声音稳住;ep 逐项点名(阿澈/灯/日志/父亲班次) | ☑ | -| 21 | P3:3 分钟颗粒/中断复述含时空位/单手 | 一幕=4-8min 段落+边界存档;恢复链 prog_mem_* 时位句先行;全手势免视单手 | ☑ | -| 22 | P4:一遍懂/容错/无高压倒计时/不骂人 | 一句规矩;零死亡零倒计时;NPC 句 ≤27 字+逗号停顿 | ☑ | -| 23 | 抉择树 4 结局可走通验算 | §4.4 穷举+§9 剧本 A-D(e1-e4 各一条实路径)+E/F 差分剧本 | ☑ | -| 24 | 单局 15-25 min | §10 曲线 ≈21-24 min | ☑ | -| 25 | 分支不爆炸(radio 纪律) | 跨幕仅 trust+3 bool+p7/p9 派生;~75 节点固定深度(§3.3) | ☑ | -| 26 | 台词 ≤160、NPC ≤5 单音色 | 148 句(双 121+NPC 27)/3 个 NPC 音色(voice-ids 账目) | ☑ | -| 27 | 引擎边界诚实 | §6.1 能力表:滤波/loop/互让全写明资产侧或降级方案 | ☑ | -| 28 | 存档带 summary、断点续玩 | §8:data+summary 格式、timeline 恢复链、≤5s、局面复述 | ☑ | - -### 批 1 教训内化确认(5 条全落地) - -| 教训 | 本作机制 | 断言 | -|---|---|---| -| 教学承诺必须兑现 | §6.5 六条承诺清单(时空签名/雾角/灯/货轮/发条/三幕) | §9「教学承诺兑现」块 | -| 看门狗双轨 | §7.2 无操作轨+无进展轨;无效手势不复位进展轨 | §9「看门狗双轨」块 | -| 档位措辞真实 | §4.3 tr 三档逐区间+tru 四值逐值为真 | §9「信任/真相账目真实」块 | -| 失败结算链完整不被打断 | 800ms 手势死窗盖 e* 链/c3_dark/插叙链;seqToken 作废在途链 | §9「结算链死窗」块 | -| PC 键盘操作提示 | 8 对 _k 变体+inputMode() 选择+集中分组标注 | §9「键盘变体」块 | - -### 时空签名教学-兑现验算(本作特有自检) - -| 承诺(rev_teach 四要素) | 教学点 | 兑现次数(剧本 A) | 断言位 | -|---|---|---|---| -| 唱针落下(memory_in) | rev_teach 全局一次(首插叙前) | 插叙 1/2/3 各一,共 3 | §9 插叙触发与回返 | -| 风声退后(vinyl 底噪、storm/room 淡出) | 同上 | 3 段插叙全程,零重叠 | §9 时空签名断言 | -| 唱针抬起+划针(memory_out) | 同上 | 3 次,与 in 成对 | §9 时空签名断言 | -| 风声回来(storm 回切+锚出句) | 同上 | 3 次(锚句 rev_g4/rev_f5/rev_c5) | §9 时空签名断言 | -| 「我在哪」三层兜底 | 上滑时位句(prog_m*/prog_mem_*)+恢复链时位句先行+菜单关闭底噪不丢 | 全程可查;§8/§9 | §9 中断复述含时空位 | - -### 结局验算(3-4 结局条款 → 定 4) - -e1=trust≥3∧p9左(剧本 A:trust4/truth3);e2=trust≥3∧p9右(剧本 B:trust4/truth2,p9b);e3=trust<3∧p9左(剧本 C:trust0/truth0,p9b+d9_tell_b);e4=trust<3∧p9右(剧本 D)。四格互斥完备、各有一条实路径、trust/truth 全档可达(§4.4)。 - -### 台词 ≤40 字验算 - -voice-ids 全表最长 = p7(35 字),次长 p9a/p9b(各 32 字)、d9_tell_a(31 字);NPC 句最长 amo_c(25 字)——全部 ≤40 ✓(逐句账见 voice-ids 文末自查)。 +> 1. 交互权威:26 号手册。纯叙事,对齐 storyline。帮助=上滑。废除双击重听节点。 +> 2. 核心差异=时空双线:现在(风暴+电台)/回忆(老唱片)。切换靠内容层(sfx+底噪+BGM+锚句),**不新增 earcon**。 +> 3. 信任/真相双标量;软失败=灯灭一刻钟/他自己冲滩,零死亡零倒计时。 +> 4. 台词 [21-keeper-voice-ids.md](21-keeper-voice-ids.md)(148 句:双性别 121 + NPC 27)。结算/插叙入口 800ms 死窗。 +> 5. 章程 N4:15–25 分钟、9 抉择(实选 8–9)、4 结局。 --- -## 修订记录(2026-09-14 开发轮裁定,追加不改旧文) +## 附录 C · 设计单 -策划自查(台词 id 全集/组计数/数字分档/手势映射/剧本 A-F 对 §4.2 计分与 §3.4 结局矩阵穷举)结论:148 句账目闭合、8 对 `_k` 齐全、无手势映射矛盾。以下为实现轮裁定: +### 1. 一句话核心动作 -1. **`_k` 资产路径**:21b 头注/§7.3 写 `voice//_k.*`,管线(`tools/gen_voice_dual.py` KEY_LINES)与引擎(audio.js 候选链)实际约定为 `voice//key/.*`(abyss/detective 同型先例)。裁定:按管线约定落盘,文档 `_k` 表述视为同义。 -2. **字数口径**:p7 实计 42 字(含标点)>40、p9a 40 字;管线按句读自动拆句生成后拼接(句间 0.12s),听觉仍为一句,不改词。21b 自查「最长 35 字」为不含标点口径。 -3. **summary 信任档名**:§8.1「信任<档名>」未给文字。裁定:3-4=深 / 1-2=半 / 0=薄(例:「第2幕·风暴顶 · 信任半 · 结局0/4」)。 -4. **存档扩展字段**:插叙断点恢复需 `memSet`(插叙段 g1/f2/c3)、`ret`(回返节点)、`taught`(rev_teach 已播)、`lastTier`(跨档播报基准)——§8.1 示例未列,按 schema 自由扩展,向后兼容。 -5. **rev_teach 落点**:§3.2 树形将其钉在 c1_open 链内,但「全局一次、首插叙前」语义在 p2 右(跳过插叙1)后首次插叙=插叙2/3 时同样需要教学(§1 风险预防①)。裁定:实现为「进入首个插叙前自动插播一次」,不钉死节点;§9「全局恰一次」断言不变。 -6. **PROMPT_RE 裁定**:`/^voice\/p\d/` 覆盖 p1..p7、p8_boat、p8_reef、p9a、p9b,播放中单击不可跳 + beat 轻震(G4)。 -7. **危机拍结构**:c2_crack/c3_wall 实现为 danger 节点(sfx 前置 2s 渐强 + danger 签名/danger 震同窗,链内自动推进不等待单击)——树形未显式标注 auto,按 radio 危机拍先例裁定。 -8. **CALM 实现形态**:按 radio BRIDGE 先例 = 自动链(foghorn → lull_N →〔跨档才播 tr_*〕)+ 喘息定时器自动进下一幕;上滑 = 状态链后自动链继续(已播账目句不重播);CALM 不切 BGM(§2.1 storm 常驻,游戏层 amb_storm gain 分幕 0.5/1.0/0.7)。 +**听一夜值守,单击继续;听到选项时左右滑:守灯还是救人、说出还是守住。** + +### 2. 玩家能感知的状态(3 个) + +| 状态 | 听感 | Floor | +|---|---|---| +| 现在·听讲 | 风暴底噪 + 电台人声,BGM storm 压低 | SPEAKING | +| 回忆·听讲 | 唱针+vinyl 底噪 + 唱片滤波人声,BGM memory | SPEAKING | +| 轮到你 | turn + 等待声(回忆链 auto 不交等待) | YOUR_TURN | + +两种「听讲」必须 2s 内靠底噪可辨(vinyl vs 风)。上滑时位句确认。不把它们拆成第四态。 + +### 3. 新手手势与解锁 + +| 手势 | 含义 | 解锁 | +|---|---|---| +| 单击 | 继续 / 跳过当前句 | 阶段 0 | +| 左/右滑 | 抉择 | 阶段 0 | +| 上滑 | 我在哪(时位句先行:幕或「这是回忆」→ 信任档 → 一个动作) | 阶段 1(第一次抉择后) | +| 下滑 | 菜单 | 阶段 2(第一结局) | +| 双击 | 按单击。阶段 3=重听当前等待点/当前回忆节点 | 阶段 3 | +| 长按 | 新手 invalid;之后加速 | — | + +### 4. 状态 × 手势矩阵(新手期) + +| 状态 \ 手势 | 单击 | 左/右滑 | 上滑 | 下滑(未解锁) | 双击 | 连点 | +|---|---|---|---|---|---|---| +| 现在在说 | 跳句(p*/锚句首播不可跳 → busy) | 缓存抉择,不盲选 | 时位+信任+动作 | invalid | 按单击 | 400ms busy | +| 回忆在说 | 跳中段句;锚入/锚出首播 busy | invalid(插叙无选择) | 「这是回忆。听完就回到现在。」 | invalid | 按单击 | busy | +| 等你继续 | 继续 | invalid | 我在哪 | invalid | 按单击 | busy | +| 等你选择 | 再听选项 | 抉择 | 「左右滑。」 | invalid | 按单击 | 迷茫 | +| 结算 | 新风暴夜 | invalid | 结局+信任一句 | 菜单(阶段2) | 按单击 | 第一下 | +| 菜单 | 确认 | 切项 | 重听说明 | 关闭 | 按单击 | busy | + +MEMORY 关菜单后重播当前回忆节点,底噪仍 vinyl。 + +### 5. 等待点 + +| id | 期待 | full | short | 静默 | L2 | L3 | +|---|---|---|---|---|---|---| +| intro | tap | 「点一下屏幕。」 | 「点一下。」 | 6s/15s | tap | 替点 | +| continue | tap | 「单击,继续。」 | 「单击。」 | 13s/30s | tap | 替继续 | +| choice | L/R | pN | 「左右滑,选。」 | 25s/50s | L/R | 「左右都可以。现在你来。」 | +| settle | tap | 「单击,重新开始。」 | 「单击。」 | 20s/45s | tap | 提示音 | + +插叙 auto,无等待点。p6 在 rev2=false 时封锁左路(m6_seal 后自动右,不摆假选项)。 + +### 6. 首 3 分钟 + +| 时间 | 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 风暴+雾角 →「点一下屏幕。」 | 单击 | 「点一下。」 | 滑 invalid | +| 3~8s | 极短目标 → 电台静电 → 阿澈求救 | 单击继续 | 三档 | inv_nosel | +| 8~10s | 接住他的声音(第一次成功) | 听 | | | +| 10~40s | p1 接住人 / 要船位 | 左右滑 | 复述 p1 | 不盲选;右路即时纠偏不扣分 | +| ≈45s | 解锁上滑 | 上滑 | 「向上滑。」 | | +| 45~90s | p2 翻暗格/守灯;左则插叙1(唱针教学) | 滑 | 三档 | | +| 90~180s | CALM 雾角+幕句 → C2 | 继续/滑 | 接管 | 归因真话 | + +### 7. 失败表 + +| 类型 | 判定 | 归因 | 回到 | 新手期 | +|---|---|---|---|---| +| 非抉择滑动 | | 「现在不用选。」 | 原地 | 无 | +| 灯灭一刻钟 | p7 左且 ¬rev2 | c3_dark 指向「没听完父亲那晚」 | 签收 | 零死亡 | +| 他半路改主意 | p7 右且 trust<3 | c9_found 克制措辞 | 签收 | 仍活着 | +| 假选项 | rev2=false 时试图 p6 左 | 不出现左项 | — | — | +| 迷茫 | | L1–L3 | 原地 | 无 | + +### 8. 台词类别 + +- `critical`:开场点一下;rev_teach(唱针=回忆,≤3s) +- `content`:b_*、c*、amo_*、rev_* 锚句、p*、e*、ep_* +- `hint`:prog_m1..3、prog_mem_g/f、tr_*、truth 逐值句 +- `feedback`:inv_*、c1_aske 纠偏、c3_dark +- 停用:双击重听回忆的口令(阶段 3 才启用 sys_unlock_replay) + +148 句;音频 121×2+27=269 ×2 格式。`_k` 8 对。 + +### 9. 断点复述 + +存档带 `timeline`(now / mem_g / mem_f)。回来:若在回忆则先播 prog_mem_* 再重播该节点。上滑永远时位句开头。≤5s 可玩。 + +### 10. 测试记录 + +改:双击不重听节点;菜单左右+确认;3s 动手;10s 内听到阿澈。vinyl 与 storm 底噪零重叠仍失败即不通过。foghorn 四处兑现保留。 + +--- + +## 1. 概述 + +风暴夜孤岛灯塔。电台里一声比一声近的求救,牵出三代守塔人的秘密。声音即世界:电台=另一条命,风暴=处境,老唱片=过去。 + +## 2. 状态机与树 + +INTRO → WATCH(C1) → CALM(1) → C2 → CALM(2) → C3 → ◆p9 → ENDING。p2左/p3左|p4左/p6左 触发 MEMORY auto ≤6 句 ≤90s 后回 WATCH。 + +C1:p1 接住(+trust)/要船位;p2 翻暗格(插叙1)/守灯。 +C2:p3 口诀(插叙2)/岸台;p4 真话(可兜底插叙2)/带过;危机拍 hull_crack → p5 堵漏(+trust)/救生衣。 +C3:p6 读完(插叙3,需 rev2)/合上;p7 离塔救人/守灯引渡;p8 双形态活路;p9 说出/守住。 + +| 结局 | trust | p9 | +|---|---|---| +| e1 灯与归人 | ≥3 | 左 | +| e2 守夜人 | ≥3 | 右 | +| e3 半明半暗 | <3 | 左 | +| e4 各自的塔 | <3 | 右 | + +信任档:3–4 深 / 1–2 半 / 0 薄,句义逐值为真。truth=插叙完成数 0–3 各一句。灯三态=p7×rev2。阿澈三态=p7×trust(零死亡:最坏天亮被找到)。 + +秘密三层:暗格 → 父亲发条 → 祖父忏悔(门认上一层钥匙)。 + +## 3. 时空签名 + +| 维度 | 现在 | 回忆 | +|---|---|---| +| BGM | storm | memory | +| 底噪 | amb_storm(幕 gain 0.5/1.0/0.7)+ amb_room | **仅** amb_vinyl | +| 过渡 | memory_out | memory_in 唱针 | +| 滤波 | 阿澈电台;你不滤波 | 父/祖唱片;锚句(你)不滤波 | + +时序:结果句完 → 600ms → memory_in+切曲切 amb → 锚入(不可跳)→ NPC → 锚出(不可跳)→ memory_out → 现在句。WATCH/MEMORY 同用 explore earcon。 + +通道:过渡音不叠语音;通话期 amb_engine 在 voice 时 −6dB;危机拍前置 2s。并发前景 ≤2。 + +雾角承诺:b_rain「两长一短」后,开场 / CALM×2 / ep_shore 必播。 + +## 4. 音频与存档 + +BGM 3 首(menu 用 storm 低响)。SFX:amb_storm/room/vinyl/engine、memory_in/out、radio_tune、static_burst、radio_beep、hull_crack、wave_wall、lamp_mech、hatch_open、flash_click、foghorn。 + +NPC:阿澈 Dylan 电台滤波、父亲 Uncle_Fu 唱片、祖父 Eric 唱片。 + +```json +{ "phase": "WATCH", "node": "amo_d", "timeline": "now", "trust": 2, "rev1": true, "rev2": false } +``` + +summary:`幕N · 信任档 · 真相K层 · 结局X/4`。 diff --git a/docs/design/22-bridge-voice-ids.md b/docs/design/22-bridge-voice-ids.md index ca12e1fa..d407e482 100644 --- a/docs/design/22-bridge-voice-ids.md +++ b/docs/design/22-bridge-voice-ids.md @@ -1,5 +1,13 @@ # 22b · 《星环舰长》语音台词表(voice ids,录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - **旁白双性别组**(142 句):旁白=舰载系统音(系统/教学/状态/账目/结算宣读),不念船员台词。落 `web/bridge/audio/voice/female|male/.{ogg,m4a}`,两性别 id 集合完全一致。 > - female = `Serena`,instruct 常量:`清晰沉稳的女声,舰载系统音,冷静克制,句读干净` diff --git a/docs/design/22-bridge.md b/docs/design/22-bridge.md index 4011dc6f..0ee6b46d 100644 --- a/docs/design/22-bridge.md +++ b/docs/design/22-bridge.md @@ -1,620 +1,155 @@ -# 22 · 《星环舰长》完整设计(bridge / Star Ring Captain) +# 22 · 《星环舰长》(bridge / Star Ring Captain) > **给开发者的实现要点摘要** -> 1. 舰桥指挥 RPG:核心动词=**听声辨型后下令**。四象限系统(引擎=舰尾低频/护盾=舰首中高/扫描=左舷 pan L/武器=右舷 pan R)各有专属 SYN 状态音(正常/受损/离线);危机事件(陨石群/海盗/求救/辐射云)以**预 pan 签名音+固定模板方位句**从四象限之一来袭,玩家在时限窗内**左右滑听选项+单击下令**(规避/迎战/硬闯/驰援/忽略 五个通用动词,选项句自带系统需求)。一局=4 巡航段(段=航线选择→巡航→危机→靠港整备),15-22 分钟。 -> 2. **常态静默/按需巡检的黄金折中**(本作最大认知负荷风险):常态**不播**正常系统底噪(BGM 承担氛围),只有受损系统按仲裁规则发告警脉冲;正常系统状态由**上滑巡检链**按需播(四系统 2.0s 状态音固定顺序+固定间隔——SYN 合成,节奏逐毫秒可断言);离线=死寂+共用平线签名。 -> 3. **告警优先级仲裁+叠爆抑制**(space 警报叠爆/R1 P4 被轰炸一分钟劝退教训):显式规则表 R1-R8——一次只响一条系统告警、轮转公平、600ms 叠爆去重、语音让路、危机窗静默无关告警、双危机 FIFO 排队、修复即止、菜单/结算全静默;§3.5 穷举 7 种并发场景(含双危机)。 -> 4. 手势宪法三件套全状态保留(上滑巡检/下滑菜单/双击重听);唯一豁免 E1=CRUISE 单击=重播最近告警(escape「检查」先例)。下令绑单击(非即时,12-20s 窗口无碍);无 immediateTap;长按非玩法输入,**不开 E3 快进豁免**(登记)。 -> 5. 账目真实三本账:忠诚(事件-忠诚影响表逐行差分,档位句=ledger band)、经济(每段采集/修理/奖励逐笔,档位句跨档才播)、评级(score 公式+一条完整巡逻剧本逐事件验算 §8.4-8.6)。REPORT 结算链 800ms 手势死窗+链锁(dungeon H1/detective VERDICT/teahouse CLOSE 同款)。 -> 6. 随机性全走 seed PRNG(mulberry32,`opts.seed` 注入):危机类型序(首局固定升难度序,之后洗牌+约束 S1≠辐射)、每场方位(方位还决定受殃系统)、求救陷阱 25%(首局恒无)、磨损系统、S4 双危机余波 40%(首局无)。同 seed 深相等。 -> 7. 台词表 [22-bridge-voice-ids.md](22-bridge-voice-ids.md):旁白 142 ×2 性别 + 船员 NPC 平铺 12(3 音色,与两侧旁白零重叠)= **154 句 ≤160 上限**;操作提示句键盘变体 `_k` 集中登记(V 组 10,`gesture.inputMode()` 选播)。全句 ≤40 字(实测 ≤38)。 -> 8. 规模对标 detective/teahouse:单文件 game.js 预计 1000-1200 行;bgm 3 + sfx 30(SA3 任务 4 个,pan 派生 8 文件)+ voice 296 条(×2 格式=592 文件)。断言清单住本文档 §12(13 号文档 §20-22 先例登记)。 +> 1. 交互权威:26 号手册。听声辨型后下令。四象限是**听**的,不是四向滑动玩法 → 帮助=上滑(巡检链即「我在哪」)。废除双击重听、菜单双击确认、单击循环菜单。 +> 2. 左右滑听选项,单击下令。巡航空闲单击=重播最近告警(检查)。 +> 3. 常态不播正常系统底噪;受损系统按 R1–R8 仲裁发告警。叠爆抑制是本作生命线。 +> 4. 台词 [22-bridge-voice-ids.md](22-bridge-voice-ids.md):旁白 142×2 + NPC 12 = **154 句**。REPORT 800ms 死窗。 +> 5. 一局 4 段 15–22 分钟;忠诚/经济/评级三本账播报=运行值。 + +--- + +## 附录 C · 设计单 + +### 1. 一句话核心动作 + +**听危机从哪边来,左右滑听命令,单击下令。** + +### 2. 玩家能感知的状态(3 个) + +| 状态 | 听感 | Floor | +|---|---|---| +| 巡航 | BGM cruise;仅受损系统告警脉冲(一次一条) | ACTION | +| 听讲 | 检测链/结果链语音,BGM 压低 | SPEAKING | +| 决策窗 | choice 签名 + 选项句 +(尾 3s)tick | YOUR_TURN | + +正常系统静默是设计,不是丢状态;上滑巡检给出四系统音。 + +### 3. 新手手势与解锁 + +| 手势 | 含义 | 解锁 | +|---|---|---| +| 单击 | 下令/选定航线/修理/跳过巡检段;巡航空闲=重播最近告警 | 阶段 0 | +| 左/右滑 | 换航线/换命令/换修理项 | 阶段 0 | +| 上滑 | 巡检=我在哪(四系统音 + 舰体档 + 一个动作) | 阶段 1(第一次下令后);教学 D0 提前教这一下 | +| 下滑 | 菜单(冻结计时与告警) | 阶段 2(第一次返航后) | +| 双击 | 按单击。阶段 3=重听当前选项句 | 阶段 3 | +| 长按 | 新手 invalid。检测链以后可加速。决策窗 invalid | — | + +教学 D0 需要上滑:作为阶段 1 提前解锁条件=「完成开场第一句后的巡检教学」,仍一次只教一样。 + +### 4. 状态 × 手势矩阵(新手期) + +| 状态 \ 手势 | 单击 | 左/右滑 | 上滑 | 下滑(未解锁) | 双击 | 连点 | +|---|---|---|---|---|---|---| +| 检测链在说 | 跳句(签名音不可跳 → busy) | invalid(先听完类型+方位) | busy 或打断答方位一句 | invalid | 按单击 | 400ms busy | +| 决策窗 | **下令**当前预览项 | 换选项并播报名 | 巡检简链 | invalid | 按单击=下令 | 窗内 400ms 防连下两令 | +| 巡航空闲 | 重播 60s 内告警;无则 cruise_ping | invalid | 全链巡检 | invalid | 按单击=再听告警 | busy | +| 巡检在播 | 跳段 | 忽略(保护链) | 重播全链 | invalid | 按单击 | busy | +| 选航线 | 选定 | 换线 | 巡检 | invalid | 按单击 | busy | +| 靠港 | 修理或启航 | 切项 | 巡检 | invalid | 按单击 | busy | +| 结算链内 | 吞 | 吞 | 吞 | 吞 | 吞 | 吞 | +| 结算链完 | 返航 | invalid | 评级一句 | 菜单(阶段2) | 按单击 | 第一下 | +| 菜单 | 确认 | 切项 | 重听说明 | 关闭 | 按单击 | busy | +| 星港 PORT | 购买/返回 | 切项 | 概况 | 菜单 | 按单击 | busy | + +超时=该类型默认结局,真话归因。45s 无选航线自动稳妥线。 + +### 5. 等待点 + +| id | 期待 | full | short | 静默 | L2 | L3 | +|---|---|---|---|---|---|---| +| intro | tap | 「点一下屏幕。」 | 「点一下。」 | 6s/15s | tap | 替点 | +| inspect | up | 「向上滑,听四个系统。」 | 「向上滑。」 | 8s/18s | up | 替播巡检 | +| route | L/R+tap | 「左右滑选航线。点一下定。」 | 「左右滑。」 | 12s/30s | L/R | 替选稳妥 | +| decide | L/R+tap | 「左右滑听命令。点一下下。」 | 「点一下。」 | 窗内 tick 已催 | tap | 演示「驰援」 | +| dock | tap | 「点一下,启航。」 | 「单击。」 | 12s/30s | tap | 替启航 | + +危机窗倒计时是玩法时钟,不是看门狗重播说明书。 + +### 6. 首 3 分钟 + +| 时间 | 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 舰桥垫乐 →「点一下屏幕。」 | 单击 | 「点一下。」 | 滑 invalid | +| 3~10s | 「向上滑,听四个系统。」→ 四段 ok 音 →「都正常。」 | 上滑 | 「向上滑。」 | 点:invalid+解释 | +| 10~25s | 求救签名(左舷 pan)+ 类型句+方位句 | 听 | | 滑太早 invalid | +| 25~40s | 「向右滑,听命令。」→ 预览驰援 →「点一下,下令。」 | 滑+点 | 短句;超时=信号消失无伤 | 硬闯等:执行但教学不掉血 | +| ≈40s | pa_chime + 报酬(第一次成功) | | | | +| 40~90s | 靠港仅启航;自由巡航 | 点启航 | 三档 | | +| 90~180s | 采集+可能磨损告警;上滑随时巡检 | 听告警/点重听 | ping | 迷茫 | + +首局 S1 求救恒无陷阱、无磨损,扫描 ok。学期乱按不掉资源。 + +### 7. 失败表 + +| 类型 | 判定 | 归因 | 回到 | 新手期 | +|---|---|---|---|---| +| 超时 | 决策窗到点 | cr_timeout + 该型默认结局 | 结算后巡航 | S1 求救超时无伤 | +| 系统不满足 | 下令时对应系统离线 | sys_*_off 先播再计伤 | 结果链 | 教学段不离线 | +| 弃舰 | hull≤0 | die_cause 按 lastHit;die_adv 按超时/离线失败 | TITLE,功勋入 meta | 学期打不到 0 | +| 巡航乱滑 | | 「现在不用选。点一下听警报,或向上滑。」 | 原地 | 无 | +| 迷茫 | | L1 短命令 / L2 只留正确项 / L3 代下令 | 当前窗 | 无 | + +### 8. 台词类别 + +- `critical`:开场点一下;tut 上滑巡检;cr_decide +- `content`:intro_1..5、evt_*、dir_*、opt_*、res_*、loy_*、rate_* +- `hint`:tut_*(去双击)、st_hull/rp/seg、sys_continue_ask +- `feedback`:sys_*_off、cr_timeout、die_*、inv_*、gd_invalid +- 停用:任何「双击重听选项」新手句 + +154 句;NPC 3 音色 12 句平铺。bgm 3 + sfx ~30。 + +### 9. 断点复述 + +中断链:st_hull → 最紧要异常(无则跳过)→ st_seg → recap_go。与正在响的告警同源。单击继续,右滑新巡逻(meta 保留)。 + +### 10. 测试记录 + +改:菜单左右+单击;双击不重听;3s 动手;10s 内完成巡检。R1–R8 双危机穷举、种子深相等、评级公式验算保留。失败即不通过:两套签名重叠、双告警齐鸣、B 计时偷跑。 --- ## 1. 概述 -- **一句话**:你是巡逻舰星环号的舰长,坐在舰桥里用耳朵指挥全舰——四个系统各守一方,危机从哪个方位来、是什么东西,全凭声音判断,然后下令。 -- **题材**:舰桥指挥。核心命题「危机是从哪个方向、以什么声音来的」——纯音频天然适合:方位=预 pan 立体声+固定模板句,系统状态=可断言的状态音,指挥=听选项后下令;视觉游戏的仪表盘在这里是一套「异常才出声+按需巡检」的听觉状态层。 -- **玩法定位**(方法论 §4.1 无现成行,状态层重点自定):**状态即音色**(四系统三态音+仲裁纪律,A1/A3 的本作落地)+ **方位降维 ≤4**(左舷/右舷/舰首/舰尾,文献结论恰为上限)+ **抉择窗口标记**(检测链→决策窗→下令)+ **账目真实**(忠诚/经济/评级三本账播报值=运行值)。 -- **单局=一次巡逻**:4 个巡航段,每段=航线选择→巡航(采集+磨损+告警管理)→危机→靠港整备,约 15-22 分钟(首局含教学 17-22);章程 15-25 分钟达标。巡逻结束结算评级(S-D),功勋入**跨局舰船成长**(星港改装 6 项,roguelite 轻元进度)。 -- **失败态**:舰体归零=弃舰撤离(死亡归因链 ≤15s 必答三问,dungeon 先例);功勋按已完成段数折算,meta 不清档。 -- **与现有 12 游戏的关系**:方位声学承接 space(pan+频率双轴,本作进阶为系统/危机统一方位语言);状态档位链承接 dungeon(上滑多段链);双轨看门狗承接 dungeon E0-1/E0-2;结算死窗承接 dungeon H1/detective VERDICT;NPC 群像+签名 earcon 承接 detective/teahouse;元进度承接 dungeon 遗产;「异常才出声」承接 rpg 血量档降档才播裁定。 -- **本作三大特有风险**(均在 §3 显式预防):① 多系统并发告警听觉过载;② 状态音常驻的认知负荷;③ 忠诚度账目失真。 +巡逻舰星环号舰长,用耳朵指挥。四系统各守一方(引擎舰尾低频 / 护盾舰首中高 / 扫描左舷 / 武器右舷)。危机以预 pan 签名+方位句来袭。一局=4 段:选航线→巡航→危机→靠港。 -## 2. 手势映射与豁免登记 +## 2. 四象限与告警仲裁 -### 2.1 标题(TITLE)与星港(PORT,TITLE 子相位) +方位 ≤4:左/右预 pan;舰首上行尾音;舰尾下行尾音。危机方位决定受殃系统(已离线则顺时针下一个)。 -| 手势 | TITLE | PORT(右滑进入) | -|---|---|---| -| 单击 | 继续(有档,播 sys_continue_ask 后)/ 新巡逻(无档) | 执行当前项(购买/返回) | -| 双击 | 重听 continue_ask | 重听当前项句 | -| 左滑 | error | 上一项(循环) | -| 右滑 | 进星港(功勋兑换) | 下一项(循环) | -| 上滑 | 舰船概况链(功勋档→已改装项逐项播其 upg_* 效果句→未改装则仅功勋档) | 同 TITLE | -| 下滑 | 菜单(§2.6) | 菜单 | -| 长按/松开 | error | error | +系统音 SYN 可断言:每音 2.0s±50ms、间隔 0.4s,巡检序 引擎→护盾→扫描→武器。告警周期互质 8/6/10/12s。 -有档单击继续 → 恢复 run + 中断复述链(§11)。PORT 列表=未购改装项(armor2 仅 armor1 已购后出现)+返回(port_back);购买 → upg_buy / port_poor(真话)。 - -### 2.2 巡航(CRUISE;含 ROUTE 航线选择子相位) - -| 手势 | ROUTE(选航线) | CRUISE 空闲 | CRUISE 巡检链播中 | -|---|---|---|---| -| 左滑/右滑 | 切换航线选项(播 opt_route_*) | error 震+`gd_invalid`(onceKey) | 静默忽略(保护链播) | -| 单击 | 选定航线 → 进巡航主体 | **重播最近告警**(60s 内的告警脉冲/事件签名;无 → cruise_ping 柔和问询音,E1) | **跳段**(跳过当前巡检段进下一段,rpg 探索跳句先例) | -| 双击 | 重听当前选项句 | 重听最近一句语音(宪法) | 重听当前巡检段 | -| 上滑 | 巡检链 | 巡检链(§3.3) | 重播全链 | -| 下滑 | 菜单 | 菜单 | 菜单(打开即停链) | -| 长按/松开 | error | 快进(语音在播时,引擎默认)/error | 快进/恢复常速 | - -ROUTE 无倒计时:45s 无选择自动走稳妥线(不卡巡航);看门狗 12s/30s(gd_decide/null)。 - -### 2.3 危机(CRISIS:DETECT 检测 → DECIDE 决策两子相位) - -| 手势 | DETECT(签名+类型句+方位句,~8s) | DECIDE(决策窗,倒计时) | -|---|---|---| -| 左滑/右滑 | 静默忽略(纯聆听保护,simon/teahouse 先例) | 切换选项(播 opt_*;循环) | -| 单击 | 静默忽略 | **下令**(选定当前预览选项;窗内非即时,300ms tap 延迟无碍) | -| 双击 | 静默忽略 | 重听当前选项句(宪法语义保留) | -| 上滑 | 静默忽略 | **巡检简链**(tones→舰体档→最紧要异常,§3.3;决策窗不宜全链) | -| 下滑 | 菜单(**冻结检测链与全部计时**) | 菜单(**冻结倒计时+告警**;关闭→重播当前选项句后继续走表) | -| 长按/松开 | 快进(引擎默认) | error 震(选择语境不作输入,detective CONFRONT 先例) | - -- 检测链:签名音(2.5s,预 pan/尾音)→ evt_*(类型句)→ dir_*(方位句)→ cr_decide → 自动预览第一选项 → 倒计时**从 cr_decide 落声起算**(信息先于计时,公平)。检测链顶断在播的巡检链(R4)。 -- 倒计时尾段 3s:tick_fast 每 0.5s + warning 震每 1s;到点 → cr_timeout + 该类型默认结局(§4.2 矩阵,超时=真话归因)。 -- 下令演出:pa_chime(全舰广播音)→ 选项执行音(eva_ok/wpn_fire/shield_bear/warp)→ 判定链(系统态不满足先播 sys_*_off 真话)→ 结果句+账目句 → 自动回 CRUISE 或段末。 - -### 2.4 靠港整备(DOCK) - -非模态列表(rhythm 选曲范式:左右滑+单击确认): - -| 手势 | DOCK | +| # | 规则 | |---|---| -| 左滑/右滑 | 上一项/下一项(循环):修理项(仅受损/离线系统,各一项)→ 启航(dock_go 恒为末项) | -| 单击 | 执行:修理(升一级,扣 2 资源→rep_ok/rep_poor)/ 启航(evt_resume→下段 ROUTE) | -| 双击 | 重听当前项句 | -| 上滑 | 巡检链 | -| 下滑 | 菜单 | -| 长按 | error | +| R1 | 同时至多 1 条系统告警;危机关联优先,否则 引擎>护盾>武器>扫描 | +| R2 | 落选排队;榜首 15s 让次位 5s | +| R3 | 同 id 600ms 去重 | +| R4 | voice 期间 A3 暂停;检测链顶断巡检 | +| R5 | 决策窗只留关联告警 | +| R6 | 双危机 FIFO,绝不两套签名 | +| R7 | 修复即止 | +| R8 | 菜单/结算全静默冻结 | -全系统无异常 → 仅「启航」+dock_empty。修理两级:受损→完好一步(2 资源);离线→受损→完好两步(每步 2 资源)——rep_* 句「升一级,两资源」恒真。 +## 3. 危机卡 -### 2.5 结算(REPORT) +陨石 12s:规避/迎战/硬闯(超时≡硬闯)。海盗 15s:迎战/规避/忽略。求救 20s:驰援/忽略(25% 陷阱,首局无)。辐射 10s:规避/硬闯。每局四类型各一场。系统不满足先播 sys_*_off。 -| 手势 | 链内(死窗+链锁 §6.4) | 链完 | -|---|---|---| -| 单击 | **吞掉** | 返航 → TITLE(settle_hook「单击,返航」言行一致) | -| 双击 | 吞掉 | 重听结算链 | -| 上滑 | 吞掉 | 重听评级段(rate_X→merit_档) | -| 下滑 | 吞掉 | 菜单 | -| 左滑/右滑/长按 | 吞掉 | error | +## 4. 忠诚与经济 -死亡链(die_intro→die_cause→die_adv→rep_open→rate_d→merit→settle_hook)同死窗+链锁。 +船员 3 人 −3..+3。驰援、迎战、硬闯等按表入账。档:离心≤−2 / 平常 / 信赖≥2。支援:大副海盗升一级;工程师段末修 1 级;医师段末 hull+1。 -### 2.6 模态菜单(遵守 00 §5.2 全球约定,无豁免) +hull 初 6;RP 采集 2/4 − 修理每级 2。评级 score=段×2+剩余舰体+驰援×2+击退海盗×2+无离线×1;S≥18。功勋=段+评级加成+floor(RP/3)。星港 6 改装(零件 20 … 应急护盾 70)。 -下滑呼出(冻结全部计时:危机倒计时/告警调度/巡航段计时)→ 单击循环 → 双击确认 → 上滑帮助 → 下滑关闭(关闭=回原状态+危机时重播当前选项句)。选项 4 项:回到舰桥 / 保存并回大厅 / 重新听帮助 / 放弃本次巡逻(run 清空保 meta,二次确认不设——菜单即模态)。 +剧本 A 教科书巡逻 score 19→S 功勋 15;剧本 B 挂机 →B。死亡链三问真话。 -### 2.7 每状态手势映射总表(6 手势+长按松开,全状态覆盖) +## 5. 状态机 -| 手势 | TITLE | DRILL | ROUTE | CRUISE | CRISIS-检测 | CRISIS-决策 | DOCK | PORT | REPORT链内 | REPORT链完 | MENU | -|---|---|---|---|---|---|---|---|---|---|---|---| -| 单击 | 继续/新巡逻 | 步骤作答 | 选定航线 | 重播告警/跳段 | 忽略 | **下令** | 执行项 | 执行项 | 吞 | 返航 | 循环切项 | -| 双击 | 重听 | 重听教学句 | 重听选项 | 重听最近句 | 忽略 | 重听选项句 | 重听项句 | 重听项句 | 吞 | 重听链 | 确认执行 | -| 左滑 | error | 教学指定/err | 上一选项 | err+纠错 | 忽略 | 上一选项 | 上一项 | 上一项 | 吞 | err | 上一项 | -| 右滑 | 进星港 | 同上 | 下一选项 | 同上 | 忽略 | 下一选项 | 下一项 | 下一项 | 吞 | err | 下一项 | -| 上滑 | 概况链 | 巡检链(教后) | 巡检链 | 巡检链 | 忽略 | 巡检简链 | 巡检链 | 概况链 | 吞 | 评级段 | 帮助 | -| 下滑 | 菜单 | 菜单/跳教学 | 菜单 | 菜单 | 菜单(冻结) | 菜单(冻结) | 菜单 | 菜单 | 吞 | 菜单 | 关闭 | -| 长按 | error | error | error | 快进/err | 快进 | error | error | error | 吞 | error | error | -| 长按松开 | — | — | — | 恢复常速 | 恢复 | — | — | — | — | — | — | +TITLE(右滑 PORT)→ DRILL 寄生 S1 → CRUISE ⇄ CRISIS → DOCK → 下段;S4 可余波。hull≤0 或四段完 → REPORT。 -无效手势一律 error 震;full 档 `gd_invalid`(onceKey 同局 1 次);idle-only(引擎 guide 已实现)。 +段长 90/100/110/120s。seed 洗类型序(首局固定求救→陨石→海盗→辐射)、方位、陷阱、磨损、余波。 -### 2.8 豁免登记(必须显式) +## 6. 音频 -| # | 豁免条目 | 理由(先例) | -|---|---|---| -| E1 | CRUISE 空闲单击=重播最近告警(非「确认」) | 「检查」类非即时操作(escape 单击=检查、dungeon 单击=脉冲同款);60s 内无告警播 cruise_ping(空态反馈,绝不静默);巡检链播中单击=跳段(12 号 rpg 探索跳句口径) | -| E2 | CRISIS 决策窗长按=error(快进天然失效) | 选择语境不作输入(detective CONFRONT 同款);决策窗内通常无语音在播,引擎快进无对象,无冲突 | - -**不设 E3**:长按不是本作玩法输入,`setRateBoostEnabled` 全状态保持默认开启(登记,与 dungeon E3 相反的裁定依据)。无 immediateTap;下令绑单击(12-20s 窗口,非即时判定)。 - -## 3. 四象限系统信息设计(核心) - -### 3.1 方位语言与预 pan 方位表(≤4 铁律) - -全作统一一套方位语言,系统与危机共用(教一次通吃;04-space「pan+频率双轴」进阶): - -| 方位 | 声学编码 | 资产工艺 | -|---|---|---| -| 左舷 port | 能量只在左声道 | 预 pan 派生 `_l`(ffmpeg stereotools,dungeon mob_step 工艺) | -| 右舷 starboard | 能量只在右声道 | 预 pan 派生 `_r` | -| 舰首 bow | 中央+**上行尾音** dir_bow(0.3s,600→1200Hz) | center+SYN 尾音 | -| 舰尾 stern | 中央+**下行尾音** dir_stern(0.3s,1200→500Hz) | center+SYN 尾音 | - -- 系统常驻方位(固定舰桥布局,intro_3..5 教学):引擎=舰尾(低频族)、护盾=舰首(中高频族)、扫描=左舷(pan L)、武器=右舷(pan R)。 -- 危机方位:seed 四方位均匀;**方位有机制意义**——未化解危机的「受殃系统」副作用落在方位对应系统(该系统已离线则顺时针下一系统:左舷扫描→右舷武器→舰首护盾→舰尾引擎)。intro_5「危机从哪边来,哪边先遭殃」承诺必兑现(playtest 锁定)。 -- 危机方位播报=固定模板句 dir_port/star/bow/stern(方位,X舷/舰首/舰尾),无连续角度、无数字。 - -### 3.2 系统状态音规格(全 SYN,节奏精确可断言) - -三态:`ok` 正常 / `dmg` 受损 / `off` 离线。巡检链按固定顺序 引擎→护盾→扫描→武器 播放:每音 **2.0s±50ms**、间隔 **0.4s**;SYN 程序合成(参数写死在 gen 脚本,playtest 虚拟时钟逐毫秒断言)。 - -| 系统(方位/频带) | ok(2.0s) | dmg(2.0s 巡检用) | off | 告警脉冲(常态用) | -|---|---|---|---|---| -| 引擎(舰尾/60-110Hz) | 两记低鸣脉冲各 0.45s,t=0.0/1.05 | 三记断奏闷咳 0.25s,t=0.0/0.7/1.4,第三记加失真 | **死寂+平线签名** sys_offline(180Hz×3 慢平音各 0.15s,t=0.0/0.7/1.4,全系统共用) | alm_engine 0.45s 单声闷咳,周期 **8s** | -| 护盾(舰首/1.2-2.8kHz) | 均匀薄雾嗡垫 2.0s 无起伏 | 同垫+8Hz 抖颤,两次熄灭(t=0.5-0.7/1.3-1.6) | 同上 | alm_shield 0.4s 闪烁哔,周期 **6s** | -| 扫描(左舷/800-1600Hz pan L) | 两声匀速扫掠各 0.6s,t=0.2/1.2 | 单声扫掠+尾部杂斑 0.3s(t=1.4) | 同上 | alm_scan 0.5s 失锁双哔,周期 **10s** | -| 武器(右舷/400-900Hz pan R) | 单音渐强充能 1.6s 满充收尾 | 两次短充中断各 0.5s,t=0.2/1.1,各带中断嘶 | 同上 | alm_weapon 0.5s 欠充降调,周期 **12s** | - -- 音量:巡检音 0.7 / 告警脉冲 0.8 / 平线 0.7 / 危机签名 0.9;四系统频带+节奏型零重叠(偶发瞬时叠听不糊,Brewster 指南)。 -- 四系统告警周期互质(6/8/10/12s)——双受损时脉冲落点天然错开,再叠加 R1 串行化,双保险防融合成噪音墙。 - -### 3.3 巡检链(上滑)——「常态静默/按需巡检」黄金折中 - -**常态只播异常系统**(rpg「降档才播」裁定的系统版):正常系统在巡航中**零发声**(氛围由 bgm/cruise 承担),受损系统按 §3.4 仲裁发告警脉冲;全部状态可随时上滑巡检。 - -``` -全链(CRUISE/DOCK/ROUTE,~15-40s): - 4×状态音(固定序) →(全 ok → tut_all_ok;否则逐个 sys_*_dmg/off 异常句) - → st_hull_档 → st_seg_段 → st_rp_档 → st_xo/st_eng/st_doc 三档 → 完 -简链(CRISIS 决策窗,≤10s):4×状态音 → st_hull_档 → 最紧要异常句(仲裁榜首) -中断复述链(恢复存档,§11):st_hull_档 → 最紧要异常句(无则跳过) → st_seg_段 → recap_go -``` - -- 巡检链可被危机检测链顶断(R4)、可被单击跳段(E1)、可被菜单停止——按需查询永远让位于游戏事件。 -- 最紧要异常=§3.4 仲裁榜首系统(与耳朵里正在响的告警同源——听到的与查到的永不矛盾,单源)。 - -### 3.4 告警优先级仲裁与叠爆抑制(显式规则表,R1-R8) - -**总优先级**(高→低):`voice 通道(下令/结算/归因)` > `A1 危机签名+方位链` > `A2 决策窗尾段 tick` > `A3 受损系统告警(轮转)` > `静默`。声流预算恒 ≤2(BGM+1 条内容声流;voice 播出时 BGM duck→voice+≤1 sfx)。 - -| # | 规则 | 内容 | 先例/教训 | -|---|---|---|---| -| R1 | **单告警纪律** | 同一时刻至多 1 条系统告警在响。多系统受损时按序取 1:① 危机关联系统优先(陨石→引擎/护盾/武器;海盗→武器/引擎;求救→扫描;辐射→引擎/护盾)② 无危机时威胁序 引擎>护盾>武器>扫描 | 「一次只报最要紧的」队列纪律;space R1 P4 被警报轰炸一分钟劝退 | -| R2 | **轮转公平** | 落选告警排队不丢弃:榜首连响 15s 让位次位 5s(双受损 3:1 轮转;三受损 10/5/5s);周期到点恰逢他人时段 → 顺延一拍 | 低位告警不被饿死,也不抢话 | -| R3 | **叠爆去重** | 同一告警 id 600ms 窗口内重复调度只响一次;不同 id 由 R1 天然串行 | space F6 裁定原样移植 | -| R4 | **语音让路** | voice 播出期间(isVoicePlaying)A3 告警**暂停调度(延迟不丢弃)**,voice 结束+0.5s 恢复;A1 检测链顶断在播巡检链(游戏事件>主动查询);A2 tick 不让路(voice+tick=2 声流) | A3 信息分层;rpg「指令名礼让」同思路 | -| R5 | **危机窗静默无关告警** | 决策窗内仅危机关联受损系统告警参与 R1;无关系统告警挂起至危机结算 | 决策窗认知带宽保护(P4) | -| R6 | **双危机排队** | 同一时刻至多 1 场危机占决策窗。第二场检测到点落在第一场决策窗/结算链内 → FIFO 顺延至第一场结算句落声+2s 再播检测链;其决策窗从自身检测链起算。**绝不叠加两套签名音** | 结算死窗 H1 同源纪律 | -| R7 | **修复即止** | 修理/支援修复使系统恢复 → 该系统告警立即截断、排队项移除,播 rep_ok/sup_eng | 修复的听觉回报要干净 | -| R8 | **菜单/结算全静默** | MENU 打开 → 告警调度+危机倒计时+段计时全部冻结(恢复按当前榜首续响,不回放积压);REPORT/死亡链 → 全部告警清空(链独占) | 00 §5.2 暂停语义;dungeon H1 | - -### 3.5 双危机并发穷举(仲裁完备性自证) - -S4 有 40%(seed)第二波危机(陨石余波,固定型)。全部并发场景 × 适用规则: - -| # | 场景 | 适用规则 | 行为 | -|---|---|---|---| -| S1 | 危机 A 决策窗内,危机 B 检测到点 | R6 | B 检测链顺延至 A 结算句+2s;窗内零 B 声 | -| S2 | 危机 A 结算链(voice)播中,B 检测到点 | R6+R4 | B 等 A 链完+2s;期间 A3 全让路(R4) | -| S3 | B 已排队,A 玩家下令前菜单打开 | R8+R6 | 菜单冻结一切;关闭后 A 倒计时续走,B 仍排队 | -| S4 | 双受损告警轮转中,危机 A 检测 | R1①+R6 | 签名抢占 2.5s;窗内仅关联系统告警(R5),轮转按 3:1 在关联集内继续 | -| S5 | 语音(结果句)在播,告警周期到点 | R4 | 告警延迟至 voice+0.5s | -| S6 | 决策窗尾段 3s(tick)撞告警周期 | 优先级 A2>A3 | tick 先响,告警顺延一拍 | -| S7 | REPORT 进入时仍有受损系统 | R8 | 告警清空,结算链独占 | - -七场景全部有确定性行为与 playtest 断言(§12.5);不存在「两套签名重叠」「双告警齐鸣」「B 计时偷跑」三条红线(§12 失败即不通过)。 - -## 4. 危机事件卡系统 - -### 4.1 事件类型表 - -| 类型 | 音频签名(SA3 2.5s) | 方位 | 时限 | 选项(通用动词句) | 超时默认 | -|---|---|---|---|---|---| -| 陨石群 | 碎石呼啸雨(多次掠空+碎击) | seed 四方位 | **12s** | 规避/迎战/硬闯 | ≡硬闯 | -| 海盗 | 敌船引擎轰鸣+充能啸音 | seed | **15s** | 迎战/规避/忽略 | 先挨打 hull−2 | -| 求救信号 | 断续三短求救码(背景杂讯) | seed | **20s** | 驰援/忽略 | 信号消失(无伤) | -| 辐射云 | 盖革计数急响渐密 | seed | **10s** | 规避/硬闯 | 受照 hull−1+受殃系统受损 | - -- 每局=四类型各一场(§4.3 生成规则)——「危机全类型路径」每局自然全覆盖。 -- 陷阱变体:求救 25%(首局恒无)——签名尾部混入微弱引擎声,检测链播 trap_hint 提示(+doc_trap NPC 接播;「求救码后面,有引擎声,多留心」——先给线索再做决定,公平)。 - -### 4.2 选项-系统态结果矩阵(判定与账目单源;系统不满足先播 sys_*_off 真话再计结局) - -**陨石群**: - -| 选项 | 引擎/武器/护盾 ok | 相关系统受损 | 相关系统离线 | -|---|---|---|---| -| 规避(引擎) | res_clean 无伤 | 擦伤 hull−1(res_hurt) | sys_engine_off→hull−2(res_bad) | -| 迎战轰开(武器) | res_mine +1 资源,大副+1 | hull−1(res_hurt) | sys_weapon_off→hull−2 | -| 硬闯(护盾) | 无伤但护盾受损(sys_shield_dmg+工程师−1) | hull−2(res_bad) | sys_shield_off→hull−2 | -| 超时 | ≡硬闯(含护盾 ok 时的工程师−1——被动硬闯同为真话账) | | | - -**海盗**: - -| 选项 | ok | 受损 | 离线 | 附注 | -|---|---|---|---|---| -| 迎战(武器) | 胜:战利+2 资源(res_plunder)、大副+1、**基础 hull−1**;大副信赖(≥2)→sup_xo 战果升一级 hull−0;大副离心(≤−2)→hull−2 | 胜但 hull−2、战利+2 | sys_weapon_off→惨败 hull−3(res_bad) | sup_xo 在下令瞬间按 band 判定 | -| 规避(引擎) | 甩脱无伤、大副−1(loy_evade_pirate) | hull−1 擦脱 | 没跑掉 hull−2 | | -| 忽略/超时 | 先挨打 hull−2、医师−1 | 同左 | 同左 | | - -**求救信号**: - -| 选项 | 扫描 ok | 扫描受损 | 扫描离线 | 陷阱变体 | -|---|---|---|---|---| -| 驰援(扫描) | res_reward +2 资源、医师+1 大副+1(loy_aid) | 成功但残骸刮蹭 hull−1 | sys_scan_off→找不到,无事发生(无惩罚无忠诚变化) | 武器 ok→伏击反杀 res_plunder +2、大副+1;武器受损/离线→res_ambush hull−2、医师−1 | -| 忽略/超时 | loy_ignore 医师−1,无其他惩罚 | 同左 | 同左 | 忽略=明智避开,零损益 | - -**辐射云**: - -| 选项 | ok | 受损 | 离线 | -|---|---|---|---| -| 规避(引擎) | res_clean | hull−1(res_hurt) | 规避不动→受照结局 | -| 硬闯(护盾) | 无伤但护盾受损一级、工程师−1 | hull−2(res_bad) | 同受照 | -| 超时 | res_rad:hull−1+**方位对应系统受损一级**(已受损则离线;已离线则顺时针下一系统) | | | - -受创一律 hit_hull 音+hit 震;资源入账 res_pick+pickup 震;舰体跌档跨档播 st_hull(双向)。 - -### 4.3 seed 用法与生成规则 - -``` -rng = mulberry32(seed) // dungeon/detective 同族 -正式局 seed = Math.floor(Math.random()*1e9),开局掷一次存入 run -生成期一次性洗牌/抽取(存 run.plan,读档复用不重洗): - ① 危机类型序:首局固定 [求救,陨石,海盗,辐射](难度递增+教学耦合); - 非首局 = Fisher-Yates 洗牌 + 约束 S1≠辐射(10s 窗不宜开局) - ② 每场方位:uniform{左舷,右舷,舰首,舰尾}(决定签名 pan/尾音与受殃系统) - ③ 求救陷阱:25% Bernoulli(首局恒 false) - ④ 磨损:每段 1 次(S1 无;矿富航线 2 次),目标=当前 ok 系统 uniform - ⑤ S4 双危机余波:40%(首局恒 false;第二波固定陨石余波+seed 方位) -复现断言:同 seed 两次构造 → run.plan 深相等(类型序/方位/陷阱/磨损/余波逐项) -``` - -巡航段长(固定,不随机):S1 90s / S2 100s / S3 110s / S4 120s(节奏曲线 §13)。 - -## 5. 船员忠诚系统 - -船员固定 3 人(大副/工程师/医师,开局全员 0,clamp −3..+3)。忠诚=抉择的长期回声:每一次危机抉择按表入账,档位句从同一 ledger 读出(播报值=运行值)。 - -### 5.1 事件-忠诚影响表(全量;与 §4.2 矩阵同源) - -| 事件·抉择 | 大副 | 工程师 | 医师 | 即时句 | -|---|---|---|---|---| -| 求救·驰援成功(含陷阱反杀) | +1 | 0 | +1 | loy_aid;NPC 接播:普通驰援→doc_aid、陷阱反杀→xo_aid | -| 求救·驰援但扫描离线未遂 | 0 | 0 | 0 | sys_scan_off(无忠诚账) | -| 求救·陷阱受伏(武器不可用) | 0 | 0 | −1 | res_ambush+loy_rad?——用 loy_rad(医师处理伤员) | -| 求救·忽略/超时 | 0 | 0 | −1 | loy_ignore / doc_ignore(NPC) | -| 陨石·迎战轰开 | +1 | 0 | 0 | res_mine+loy_fight_win | -| 陨石·硬闯/超时(护盾扛) | 0 | −1 | 0 | loy_breach / eng_gripe(NPC,≤−2 时) | -| 陨石·规避 | 0 | 0 | 0 | — | -| 海盗·迎战(胜) | +1 | 0 | 0 | loy_fight_win / xo_win(NPC) | -| 海盗·规避 | −1 | 0 | 0 | loy_evade_pirate | -| 海盗·忽略/超时(受创) | 0 | 0 | −1 | res_bad+loy_rad | -| 辐射·硬穿(护盾) | 0 | −1 | 0 | loy_breach | -| 辐射·超时受照 | 0 | 0 | −1 | res_rad+loy_rad | -| 辐射·规避 | 0 | 0 | 0 | — | - -### 5.2 档位与措辞(逐值为真) - -| 档 | 区间 | 句(每成员一句) | 措辞↔账目 | -|---|---|---|---| -| 离心 | ≤−2 | st_xo_1「大副,离心。」等 ×3 成员 | 「离心」=band≤−2(支援缺席+NPC 撂话:xo_uneasy/eng_gripe/doc_ignore 首次各一句,每局 1 次) | -| 平常 | −1..+1 | st_*_2 ×3 | 「平常」=band∈[−1,1] | -| 信赖 | ≥2 | st_*_3 ×3 | 「信赖」=band≥2(关键支援激活) | - -档位只在巡检链播(跨档即时句由 5.1 的 loy_* 承担,二者并存不矛盾:loy 句说事件、band 句说状态)。 - -### 5.3 关键支援触发条件(档位的机制回报) - -| 成员 | 触发点 | 条件 | 效果 | 播报 | -|---|---|---|---|---| -| 大副 | 海盗·迎战 下令瞬间 | band≥2 | 战果升一级(hull−1→0;基础本就含 −1) | sup_xo+xo_fight(NPC) | -| 工程师 | 段末(DOCK 进入前) | band≥2 且有受损/离线系统 | 自动修 1 级(受损最重者;R7 即止) | sup_eng+eng_sup(NPC) | -| 医师 | 段末(DOCK 进入前) | band≥2 且本段 hull 净减 ≥1 | 舰体+1(不超上限) | sup_doc+doc_care(NPC) | -| 任一 | 支援条件不满足(本该触发) | band<2 | 支援缺席 | sup_none(每局限 1 次,之后静默) | - -大副离心(≤−2)附加:海盗迎战战果降一级(hull−1→−2)——与信赖对称,不引入新句子(数值账 playtest 锁定)。工程师/医师离心=支援缺席(无额外惩罚,avoid rep_* 句价目失真)。 - -## 6. 状态机 - -### 6.1 状态列表 - -`TITLE / DRILL(首局教学态,寄生于 S1) / CRUISE(ROUTE+主体) / CRISIS(DETECT+DECIDE) / DOCK / REPORT` + `MENU`(挂起 overlay,记录返回态);PORT=TITLE 子相位。 - -### 6.2 转移图 - -``` -start ──> TITLE ──tap(有档)──> 恢复 run+中断复述链 → CRUISE/当前段 - │tap(无档) ├──────> DRILL(仅首局, meta.drillDone) ──完成/下滑跳过──> CRUISE(S1) - │右滑=PORT(功勋改装) │ - v v - CRUISE(段n) ──段计时到──> CRISIS(类型=plan[n]) ──下令/超时结算──> (n<4) DOCK(evt_dock) ──启航(evt_resume)──> CRUISE(n+1) - │S4 余波:结算后 2s 若有余波 → 再入 CRISIS(余波) → 结算 │n=4 - └──任意时刻 hull≤0──> REPORT(死亡链) v - REPORT(巡逻结算链) ──tap──> TITLE - 任意正交态 ──下滑──> MENU(冻结计时) ──关闭──> 原状态(危机时重播当前选项句) -``` - -### 6.3 语音链推进契约 - -- 检测链/结果链/结算链=onComplete 串行(句间 0.8s);单句 8s 无 onComplete(资产缺失)→ 告警续链(「屏蔽任意 voice 流程不卡死」契约;危机兜底=超时默认结局自动推进)。 -- 教学链 `_speakGuarded` 串行(radio/dungeon D0-1 引擎保险丝先例,杜绝同 tick 双调度抢占)。 -- REPORT/死亡链为**独占链**:链首停看门狗与全部告警(R8)。 - -### 6.4 REPORT 死窗与链锁(dungeon H1 内化) - -进 REPORT 即盖 `REPORT_GRACE_MS=800` 戳(touchstart+450ms 长按+300ms 双击窗 ≈750ms,800 全盖);死窗内一切手势吞掉(ghost 手势不算输入、不碰看门狗)。链锁:链尾 settle_hook 落声前单击/右滑/长按一律吞掉;链完成后单击=返航(settle_hook「单击,返航」言行一致,simon round1-live 教训)。双击=重听链仅链完成后受理。 - -## 7. 教学编排(DRILL,首局寄生于 S1 求救) - -每步=一句短旁白→等待实操→反馈;做错重教同一步(**新短句 ≤2 次**)后自动演示通过;下滑=跳过(tut_skip),`meta.drillDone` 持久化。首局 S1 求救**恒无陷阱**(tut_aid「我们的扫描,稳得很」承诺必兑现——S1 无磨损保证扫描 ok)。 - -| 步 | 旁白 | 等待动作 | 通过条件/反馈 | -|---|---|---|---| -| D0 | intro_1..5 → tut_d0 | 上滑 | 巡检全链(全 ok)+tut_all_ok。**首输入 ≈t+25s(<60s ✓)** | -| D1 | tut_crisis | (检测链自动)听签名+类型句+方位句 | 求救检测链播完(纯聆听) | -| D2 | tut_decide → tut_aid | 左右滑预览 → 单击下令「驰援」 | pa_chime→执行→res_reward+loy_aid+doc_aid→tut_after。**首正反馈 ≈t+2.5-3min(≤3min ✓)** | -| D3 | tut_loyal | 上滑 | 巡检链(听到三档船员句) | -| D4 | tut_done | — | 进自由巡航(S1 主体) | - -- 学期零惩罚:S1 无磨损、求救超时=信号消失(无伤无资源损失,cr_timeout 真话);DRILL 内乱按不可能掉血掉资源。 -- 教学无进展轨(E0-2 先例):30s earcon→60s 短句→90s 自动演示通过(自动驰援)。 - -## 8. 数值与经济 - -### 8.1 资源经济(每局两本账:舰体 hull / 资源点 RP) - -``` -hull:初始/上限 6(armor1→7、armor2→8)。伤害单位 1-3/次。≤0 → 死亡链。 -RP:段首采集入账(稳妥线 2 / 矿富线 4;采集协议改装后 3/5)+ 奖励(矿材 1/战利 2/报酬 2) - − 修理(每级 2)。REPORT 时 剩余 RP ÷3 向下取整 → 折算功勋(资源不白攒)。 -磨损:每段 1 次(矿富 2 次)随机 ok 系统受损一级(受损→离线)。 -st_rp 档:0-2 紧张 / 3-6 够用 / ≥7 充足(跨档才播、双向播,播报值=运行值) -st_hull 档(按比例,兼容上限升级):≥75% 稳固 / 40-74% 带伤 / <40% 危急 -``` - -### 8.2 评级公式(措辞↔账目一一对应) - -``` -score = 完成段数×2(满 8) + 舰体剩余(绝对值) + 驰援成功×2 + 击退海盗×2(含伏击反杀) - + 全程无系统离线×1 -S ≥18 / A ≥14 / B ≥10 / C ≥6(完成巡逻保底 8+0=8 → C 封底) -D = 巡逻失败(hull≤0 弃舰撤离) -功勋 = 完成段数 + 评级加成(S+8/A+5/B+3/C+1/D+0) + floor(剩余RP/3) -``` - -### 8.3 升级树(星港·功勋,跨局保留;dungeon 遗产模式) - -| 改装 | 功勋 | 效果 | -|---|---|---| -| upg_spare 备用零件 | 20 | 开局自带物资(RP +2) | -| upg_harvest 采集协议 | 30 | 每段采集 +1(路线句无数字,恒真) | -| upg_armor1 加装装甲 | 40 | 舰体上限 6→7 | -| upg_vet 老兵船组 | 50 | 开局全员忠诚 +1 | -| upg_armor2 重型装甲(需 armor1) | 60 | 舰体上限 7→8 | -| upg_aegis 应急护盾 | 70 | 每段首次舰体伤害 −1(减至 0 则无伤播报) | - -经济核算:S 局功勋 14-15 / B 局 7-8 / 失败局 3-4 → 早期改装 2-3 局一件、全套 18-25 局(dungeon 遗产 2-5 局一件同量级)。 - -### 8.4 剧本 A「教科书巡逻」逐事件验算(评级账目直证) - -首局,全稳妥线,危机序 求救→陨石→海盗→辐射;开局 hull 6 / RP 0 / 忠诚 0/0/0: - -| # | 阶段/事件 | 抉择与系统态 | 结算 | 账目(hull/RP/大副/工程师/医师) | -|---|---|---|---|---| -| 1 | S1 巡航 90s | 采集 +2 | res_pick | 6 / 2 / 0 / 0 / 0 | -| 2 | S1 求救(左舷,无陷阱) | 驰援(扫描 ok,D2 教学) | res_reward +2;loy_aid | 6 / 4 / **1** / 0 / **1**(RP 2→4 跨档播 st_rp_2 ✓) | -| 3 | DOCK1 | 全好 | dock_empty+dock_go | 6 / 4 / 1 / 0 / 1 | -| 4 | S2 巡航 100s | 采集 +2;磨损:扫描受损 | alm+sys_scan_dmg+eng_wear | 6 / 6 / 1 / 0 / 1 | -| 5 | S2 陨石群(舰尾) | 迎战轰开(武器 ok) | res_mine +1;大副+1 | 6 / 7 / **2** / 0 / 1(RP 6→7 跨档播 st_rp_3 ✓;大副跨档→下次巡检 st_xo_3 信赖) | -| 6 | DOCK2 | 修扫描 −2 | rep_ok(RP 7→5 跨档回 st_rp_2 ✓) | 6 / 5 / 2 / 0 / 1 | -| 7 | S3 巡航 110s | 采集 +2;磨损:引擎受损 | alm+sys_engine_dmg | 6 / 7 / 2 / 0 / 1 | -| 8 | S3 海盗(右舷) | 迎战(武器 ok;**大副 band 2 → sup_xo**:战果升一级 hull−1→0) | res_plunder +2;sup_xo;loy_fight_win | 6 / 9 / **3** / 0 / 1(RP 7→9 跨档 st_rp_3 ✓) | -| 9 | DOCK3 | 修引擎 −2 | rep_ok | 6 / 7 / 3 / 0 / 1 | -| 10 | S4 巡航 120s | 采集 +2;磨损:护盾受损 | alm+sys_shield_dmg | 6 / 9 / 3 / 0 / 1 | -| 11 | S4 辐射云(舰首) | 规避(引擎 ok,已修) | eva_ok+res_clean | 6 / 9 / 3 / 0 / 1 | -| 12 | REPORT | score=4×2+6+1×2+1×2+1=**19 → S**;功勋=4+8+floor(9/3)=**15** | rate_s+merit_3 | 全档位句=ledger ✓ | - -验算结论:每个跨档播报点的实值都在区间内;三次忠诚变动全落 loy 句;支援触发(大副)与 band 一致;S 级在含两次磨损的现实局内可达。 - -### 8.5 剧本 B「挂机超时」验算(受创路径+缺席差分) - -全超时、全稳妥线:S1 求救超时(cr_timeout+loy_ignore,医师−1)RP 2 → S2 陨石超时≡硬闯(护盾 ok→护盾受损+工程师−1)→ DOCK2 修扫描 −2(护盾修不起,rep_poor 可达)→ S3 海盗超时 hull−2+医师−1(→−2 离心)→ DOCK3 修引擎? (S3 磨损=引擎受损)−2 → S4 辐射超时 hull−1+受殃系统受损(扫描再次)。 -REPORT:hull 3、驰援 0、击退 0、无离线 +1 → score=8+3+0+0+1=**12 → B**;功勋=4+3+floor(4/3)=**8**。医师 band −2=离心(st_doc_1 真话;段末 hull 净减但 sup 缺席——与剧本 A 的支援在场形成差分断言对)。 - -### 8.6 失败路径(注入验算) - -注入 hull=3、sys.weapon=off,S3 海盗选迎战 → sys_weapon_off→hull−3→0 → 死亡链:die_intro→die_pirate(最后伤害源=海盗炮火)→die_adv_sys(本局存在因离线失败的抉择)→rep_open→rate_d→merit_1→settle_hook;功勋=3 段+0+floor(RP/3)。die_cause 按 lastHit 真实来源、die_adv 按行为记录(超时≥2→die_adv_to;存在离线失败→die_adv_sys;否则 die_adv_to 泛化建议句)——三问归因真话律锁定。 - -## 9. 音频三层 - -### 9.1 BGM(3 首,Mureka instrumental,-20 LUFS,ogg+m4a,无缝循环) - -| id | 状态映射 | Mureka prompt(英文) | -|---|---|---| -| bgm/menu | TITLE / MENU / DOCK / PORT / REPORT(结算 volume 0.5) | "Quiet spaceport ambient, warm slow synth pads, soft beacon pings, dignified and calm, no drums, no vocals, seamless loop" | -| bgm/cruise | DRILL / CRUISE(含 ROUTE) | "Space patrol cruise theme, steady pulsing synth bass at 80 bpm, sparse bell melody, wide starfield ambience, watchful and hopeful, no vocals, seamless loop" | -| bgm/crisis | CRISIS(检测链起,交叉淡入) | "Tense starship bridge cue, pulsing low ostinato at 100 bpm, sparse alarm-like stabs, rising dread but restrained mix, no vocals, seamless loop" | - -切换全走引擎 crossfade;voice duck 20% 引擎自动。crisis 曲**特意 restrained**——给签名音/告警/语音让频带(§3.4 声流预算)。danger 不换曲(guide danger earcon+st_hull_3 承担)。预算:3 曲 ×2 候选=6 任务 $0.18(上限内)。 - -### 9.2 SFX(30 个基 id;SA3=Stable Audio 3,SYN=程序合成;双格式、真峰值 ≤-3dBFS) - -| 类别 | id | 方式 | 说明 | -|---|---|---|---| -| 系统状态音 | `sys_engine_ok/dmg` `sys_shield_ok/dmg` `sys_scan_ok/dmg` `sys_weapon_ok/dmg` | **SYN** | §3.2 规格(2.0s/间隔 0.4s/频带/节奏全部写死——playtest 逐毫秒断言) | -| 离线签名 | `sys_offline` | SYN | 180Hz×3 慢平音,全系统共用(死寂=统一语言) | -| 告警脉冲 | `alm_engine` `alm_shield` `alm_scan` `alm_weapon` | SYN | 0.4-0.5s 受损签名压缩单响,周期 8/6/10/12s(互质) | -| 方位尾音 | `dir_bow` `dir_stern` | SYN | 上行/下行双音 0.3s | -| 危机签名 | `sig_meteor` `sig_pirate` `sig_distress` `sig_radiation`(center)+ ffmpeg pan 派生 `_l/_r` ×4 | **SA3×4** | "dense meteor swarm passing close, multiple rock whooshes and rubble impacts, dry, tight" / "hostile spacecraft engine roar with weapon charging whine, menacing" / "repeating three-short radio distress beeps through static, deep space" / "geiger counter clicking rapidly intensifying, eerie space ambience";派生工艺同 dungeon mob_step(stereotools sbal ±0.8) | -| 下令/演出 | `pa_chime` | SYN | 全舰广播双音 0.5s(下令前缀仪式) | -| | `eva_ok` `wpn_fire` `shield_bear` `warp` | SYN | 侧推掠风/炮击低频+高吱尾/承击闷鸣/驰援跃迁滑音(各 0.7-1.2s) | -| | `hit_hull` | SYN | 船体受创闷击 0.5s(120Hz+金属残响) | -| | `res_pick` | SYN | 资源入账清脆双击 0.3s | -| | `wrench` | SYN | 扳手两响 0.5s(修理) | -| | `seg_warp` | SYN | 段间跃迁上行滑音 1.2s | -| 计时/看门狗 | `tick_fast` | SYN | 决策窗尾段 tick(1kHz,0.1s/0.5s 拍) | -| | `turn_tick` | SYN | 880Hz 木鱼族(无进展轨 L1,dungeon 配方自存) | -| 巡航问询 | `cruise_ping` | SYN | 柔和 ping 0.4s(CRUISE 空闲单击无近期告警时) | - -SA3 任务 4 个(全部签名);其余 SYN 兜底——**状态音必须 SYN**(节奏可断言是硬要求,§3.2)。 - -### 9.3 voice(154 句:旁白 142 ×2 性别 + NPC 平铺 12) - -- **旁白=舰载系统音**(系统/教学/状态/账目/结算宣读):female=`Serena`「清晰沉稳的女声,舰载系统音,冷静克制,句读干净」/ male=`Uncle_Fu`「低沉沉稳的男声,舰载系统音,冷静克制」——两常量全表统一,禁句级变体、禁语速词。数字全分档整句(hull 3 档/段 4 档/rp 3 档/忠诚 9 句/功勋 3 档),句内量词写死(「两资源」「资源加一」)。 -- **船员 NPC 平铺单音色**(≤3,落 `voice/` 根目录,引擎平铺回退命中;不做双性别): - -| 角色 | speaker | instruct 常量 | 区分度风险 | -|---|---|---|---| -| 大副(XO 34) | `Dylan` | 干脆利落的年轻男声,星舰大副,令出必行,敬重舰长 | **低:与两侧旁白均不同 speaker** | -| 工程师(ENG 52) | `Eric` | 沙哑厚重的中年男声,轮机工程师,惜船如命,慢条斯理 | 低:同上 | -| 医师(DOC 38) | `Vivian` | 温和坚定的女声,舰载医师,轻声但不容置疑 | 低:同上(female 旁白=Serena,不重叠) | - -- 本作 NPC 与旁白**零 speaker 重叠**(detective/teahouse 各有 2 处「中」风险,本作 0)——仍保留 loy_*/sup_* 句前 NPC 语境锚点(第一人称船员 vs 系统音第三人称)。情绪写在句内文本(省略号引导),instruct 恒定。 -- 全表、触发、键盘变体、多音字自查见 [22-bridge-voice-ids.md](22-bridge-voice-ids.md)。 - -## 10. 向导层接入(engine/guide.js,不新增 earcon 词表) - -### 10.1 状态签名(进入状态时播) - -| 状态 | guide.state() | 时点 | -|---|---|---| -| TITLE / MENU / REPORT | `menu` | 进标题、开菜单、进结算 | -| DRILL / CRUISE | `explore` | 进教学、每段巡航 | -| CRISIS | `battle` | 检测链起(检测+决策整段) | -| ROUTE / DOCK / PORT | `choice` | 进选择列表 | -| danger | `danger` | 舰体跌至「危急」档当刻(每段 1 次) | - -### 10.2 turn cue 与看门狗(双轨——批次 1 教训固化) - -| 等待点 | 机制 | at | say(full 档;第 2 级一律纯 nudge) | -|---|---|---|---| -| ROUTE/DOCK/PORT 入口、CRISIS 决策窗开 | guide.turn | — | turn earcon + confirm 震 | -| CRUISE 无操作 | 无操作轨 | [10s, 25s] | gd_cruise / null | -| ROUTE 无操作 | 无操作轨 | [12s, 30s] | gd_decide / null(45s 自动稳妥线兜底) | -| CRISIS 决策窗 | 无操作轨 | [min(窗长×0.5,12s)] 单级 | gd_decide(窗 <10s 不挂——tick 自承时机) | -| DOCK 无操作 | 无操作轨 | [15s, 35s] | gd_dock / null | -| DOCK 无进展 | 无进展轨 | [45/90/150s] | earcon→gd_dock→hint_dock3+自动预览启航项(**不替玩家花资源**) | -| DRILL 每步 | 无进展轨 | [30/60/90s] | earcon→短句→自动演示通过(§7) | - -- CRUISE/ROUTE/CRISIS 均有硬计时兜底(段计时/45s 自动线/超时默认结局)——结构上不存在无限静默;无进展轨仅 DOCK/DRILL 需要(结构性密度,dungeon D0-3 教训的前置化)。 -- 纠错/提示 idle-only、过期丢弃;向导语音 queue:true 绝不顶句(引擎已实现)。 - -## 11. 存档设计(跨局舰船成长) - -```json -{ - "schema": 1, "game": "bridge", "updatedAt": 0, - "summary": "第2巡航段 · 舰体带伤 · 引擎受损 · 资源5", - "meta": { "drillDone": true, "merit": 26, "unlocks": ["spare", "harvest"], - "patrols": 3, "bestRate": "A", "aids": 2, "kills": 1 }, - "run": { - "seed": 42, "seg": 2, "plan": { "types": ["distress","meteor","pirate","radiation"], - "dirs": ["port","stern","star","bow"], "trap": false, "wears": ["scan","engine","shield"], - "echo": false }, - "hull": 4, "rp": 5, - "sys": { "engine": "dmg", "shield": "ok", "scan": "ok", "weapon": "ok" }, - "crew": { "xo": 2, "eng": 0, "doc": 1 }, - "aidOk": 1, "kills": 1, "offlineEver": false, - "hullLostSeg": 1, "timeouts": 0, "offlineFails": 0, "firstRun": false - } -} -``` - -- summary 规则:`第{seg}巡航段 · {hull 档名} · {最紧要异常系统名|无异常} · 资源{rp}`;巡逻终局 run=null → `功勋{merit} · 最佳{bestRate} · 改装{n}/6`。 -- 保存时机:危机结算后 / DOCK 项执行后 / 段末 / 菜单「保存并回大厅」/ REPORT 入 meta。localStorage + PUT 服务器。 -- 读取:有 run → TITLE 播 sys_continue_ask,单击继续 → 恢复到**当前段 ROUTE**(该段危机未结算则整段重走——存档粒度=危机结算点/段边界,登记)→ **中断复述链**(§3.3:舰体档→最紧要异常→巡航段→recap_go,≤8s 回到可操作)。右滑=星港(meta 消费)。 -- 归因真话依据:`timeouts`/`offlineFails`/lastHit 持久化,死亡链选句从同源读取。 - -## 12. playtest 断言清单(登记 13 号文档新增 §bridge;mock 对齐 web/rpg/test/playtest.mjs;一律 guide:'off') - -**关键路径**(seed=42 首局,≤200 手势): -1. 启动→TITLE(无档)→单击→DRILL:D0..D4 逐步 voice 序与 drillStep 递进;D0 巡检全链+tut_all_ok;D2 求救驰援成功链(pa_chime→warp→res_reward→loy_aid→doc_aid);首局 S1 超时路径零损失(学期零惩罚);下滑跳过+meta.drillDone 二周目不再进。 -2. **seed 复现**:seed=42 两次构造 → run.plan 深相等(类型序/方位/陷阱/磨损/余波);首局约束(固定序/无陷阱/无余波/S1 无磨损);非首局 S1≠辐射。 -3. **巡检链节奏断言**(SYN 硬要求):全链 4 音按 引擎→护盾→扫描→武器 顺序播出、每音时长 2.0s±50ms、间隔 0.4s±50ms;scan 音资产左声道能量占比>90%(pan 断言);全 ok → tut_all_ok、无异常语音;每系统 dmg/off 各注入 → 对应 sys_* 句恰在对应 tone 后播出。 -4. **危机全类型路径**:四类型各自完整走 检测链(签名 pan/尾音与方位一致:左舷=左声道、舰首=dir_bow)→类型句→方位句→决策窗→逐选项×逐系统态(ok/dmg/off 矩阵 §4.2 全组合注入)→结果句+账目差分(hull/RP/忠诚);超时=默认结局真话(陨石超时含工程师−1)。 -5. **仲裁规则断言**(R1-R8 + §3.5 七场景逐一注入):双受损 3:1 轮转时序;同 id 600ms 去重;voice 播出期间告警零调度、结束后 +0.5s 恢复;决策窗内无关告警挂起;**双危机**:B 检测落在 A 窗内 → A 结算句+2s 前零 B 声、B 窗从 B 检测链起算;REPORT 进入告警清空;菜单冻结期零调度零倒计时。红线:任意时刻系统告警 ≤1 条(tick 除外)、两套签名零重叠。 -6. **忠诚账本**:§5.1 表逐行注入 → crew 差分断言;clamp −3..+3;band 句=ledger(9 句全覆盖含边界 −2/+2);支援触发三成员(大副=海盗下令瞬间 band 判定、工程师/医师=段末条件)与缺席 sup_none 每局一次;大副离心战果降级。 -7. **评级验算**:§8.4 剧本 A 逐事件注入 → score=19→rate_s、功勋 15、每个跨档播报点实值在区间(含 RP 回落双向);§8.5 剧本 B → score=12→B、医师离心档+支援缺席;§8.6 失败注入 → 死亡链序+die_cause=lastHit+die_adv 选择与 timeouts/offlineFails 一致+功勋折算。 -8. **经济**:采集(稳妥/矿富×2/采集协议改装)、修理两级(离线→受损→ok 两笔 2 资源)、rep_poor、REPORT 折算 floor(RP/3);upg 六项价格/条件(armor2 依赖、port_poor、upg_done 不重复购买)与效果(armor 上限→st_hull 档按新比例、vet 开局+1、aegis 首伤−1 减至 0 无伤播报)。 -9. **看门狗**:CRUISE 10s/25s、ROUTE 12s/30s+45s 自动稳妥、DOCK 15s/35s、第 2 级纯 nudge;DOCK 无进展 45/90/150 三级(L3 不替玩家花资源);DRILL 30/60/90 自动演示。 -10. **中断复述**:存档重进 → sys_continue_ask→单击→落在当前段 ROUTE→复述链序(st_hull→最紧要异常→st_seg→recap_go);无异常时跳过异常句;上滑全链可重放。 -11. **REPORT 死窗+链锁**:进 REPORT 同拍与 +300ms 各补一记在途 tap → 均被吞;结算链完整有序(rep_open→rate_X→merit→settle_hook);链完单击=返航 TITLE;死亡链同款。 -12. **菜单**:模态循环-确认-关闭;危机中开菜单 → 倒计时冻结(快进虚拟时钟断言零超时触发)→ 关闭重播当前选项句续走;「保存并回大厅」PUT data.summary 命中生成规则;「放弃本次巡逻」run 清空 meta 保留。 -13. **键盘变体**:`settings.input='key'` 后播 sys_help_k/tut_d0_k/cr_decide_k/dock_open_k 等 V 组(inputMode 联动);'touch' 回基础句。 -14. **E1/E2 断言**:CRUISE 空闲单击=重播最近告警(60s 窗)、无近期→cruise_ping;巡检链播中单击=跳段;CRISIS 决策窗长按=error;无 E3(全状态快进可用,rateLog 无豁免记录)。 - -**边界断言**: -- 首局 FTUE:首输入 <60s(D0 ≈t+25s);首正反馈 ≤3min(D2 驰援 ≈t+2.5min)。 -- 学期零惩罚:DRILL+S1 乱按 → hull/RP/忠诚零变化(求救超时除外——其本身零损失)。 -- 求救陷阱公平性:陷阱局检测链必播 trap_hint(先线索后抉择);非陷阱局零 trap_hint。 -- 方位机制:辐射/余波受殃系统=方位系统(含已离线顺时针转移);陨石硬闯损护盾自身。 -- 上滑巡检随时可用(决策窗=简链);双击重听在声明处全状态生效。 -- 屏蔽任意 voice 资产:链 8s 兜底推进;危机超时默认结局自动落(永不卡死)。 -- guide=off:全静、零定时器(游戏层计时器:段计时/倒计时属内容层照走)。 -- 资产齐套:voice 双性别 142 id 集合一致+NPC 平铺 12+sfx/bgm 双格式(assetExists 口径)。 - -**失败即不通过**:归因假话(评级句/忠诚档句/超时句/死因句/支援句与 ledger 不符);同 seed 两次构造不一致;巡检链节奏断言失败;双危机并发出现两套签名重叠或双告警齐鸣;危机签名 pan/尾音与方位句矛盾;教学期产生任何损失;受殃系统与方位规则不符。 - -## 13. 节奏曲线 - -### 单局(一次巡逻 15-22 分钟;首局 17-22) - -| 阶段 | 时长 | 内容 | 奖励点(≤60s 一个) | -|---|---|---|---| -| 开场+教学 | 0:00-3:00 | intro 五句+D0..D4(S1 求救=教学危机) | D2 驰援成功(t+2.5min) | -| S1 收尾+DOCK1 | 3:00-4:30 | 巡航余段+整备 | 段结算/采集入账 | -| S2 | 4:30-7:30 | 航线选择+磨损告警+陨石(12s 窗) | res_mine 采矿/大副跨档 | -| S3 | 7:30-10:30 | 巡航+海盗(15s 窗,支援演出高潮) | sup_xo/res_plunder | -| S4 | 10:30-13:30 | 巡航+辐射(10s 窗,最紧)+(40% 余波双危机) | 干净规避/余波胜 | -| REPORT | 13:30-15:00 | 评级链+功勋+星港钩子 | rate_s+改装 | - -- 紧张度曲线:求救(20s 窗,教学温)→陨石(12s)→海盗(15s 但有战斗演出)→辐射(10s,最紧)+余波——窗长递减、S4 双危机为峰;DOCK 是每 3 分钟一个的「呼吸谷」(修理+听船员)。 -- 峰谷结构:巡航(低唤起·听+管理)⇄ 危机(高唤起·决策窗)⇄ 靠港(低唤起·养成)——P4 不疲劳;P2 追求 S 评级与矿富线风险决策。 -- 开环钩子:settle_hook「星港期待你的下一次巡逻」+功勋差一点够下一件改装(经济曲线设计使然);死亡局功勋照入(下一局更强的 dungeon 式拉重开)。 - -### 跨局(元进度) - -首局教学→3-5 局解锁备用零件/采集协议→中期装甲/老兵船组改变 build(vet 开局信赖=支援更早到位)→重型装甲+应急护盾支撑 S 评级冲分;bestRate 与 patrols 记录长期目标。 - -## 14. 自检清单 - -| # | 硬约束/教训 | 自检 | -|---|---|---| -| 1 | FTUE 首输入 <60s | D0 巡检 ≈t+25s ✓(§7) | -| 2 | 首正反馈 ≤3min | D2 驰援成功 ≈t+2.5min ✓ | -| 3 | 学习期零惩罚 | S1 无磨损、求救超时零损失、DRILL 乱按零账 ✓ | -| 4 | 失败三问归因+真话律 | die_cause=lastHit、die_adv 按 timeouts/offlineFails、超时=真话默认结局、系统不满足先播 sys_*_off——§12 失败项锁定 ✓ | -| 5 | 手势宪法三件套不豁免 | 上滑巡检/下滑菜单/双击重听全状态保留;仅 E1/E2 登记且教学讲清;无 E3(登记)✓ | -| 6 | 教学承诺兑现 | tut_aid「扫描稳得很」(S1 无磨损保证);intro_5「哪边先遭殃」(受殃规则锁定);settle_hook「单击返航」死窗+链锁后言行一致 ✓ | -| 7 | 结算链完整 | REPORT 800ms 死窗+链锁(dungeon H1 内化)✓(§6.4) | -| 8 | 档位措辞真实 | hull/rp/忠诚/功勋/评级五本账播报值=运行值、跨档双向播;§8.4-8.6 剧本逐事件验算 ✓ | -| 9 | **告警仲裁表完备性** | R1-R8 显式规则表+§3.5 双危机并发 7 场景穷举(检测排队/结算链撞车/菜单冻结/轮转抢占/语音让路/tick 优先/结算清场),每场景确定性行为+断言;红线三条(签名重叠/双告警齐鸣/B 计时偷跑)入失败项 ✓ | -| 10 | **状态音认知负荷**(本作特有风险) | 常态静默+异常才响(rpg 降档才播裁定系统版)+上滑巡检按需全查+四告警周期互质+R1 串行——常态并发声流 ≤2 恒成立;SYN 节奏逐毫秒断言 ✓(§3.2-3.4) | -| 11 | 方位降维 ≤4 | 左舷/右舷/舰首/舰尾四象限(系统+危机统一语言);方位句固定模板 4 句;无连续角度 ✓(§3.1) | -| 12 | 信息密度 | 选项逐项预览(≤3 选项/次);巡检简链 ≤10s(决策窗);检测链 ~8s 短窗;单次播报 ≤3 选项 ✓ | -| 13 | 向导层 | 签名 5 态(choice 用于三列表态);双轨看门狗+结构性硬计时兜底;纠错 idle-only/queue/onceKey ✓(§10) | -| 14 | seed 可复现 | mulberry32+opts.seed+plan 生成期一次性+深相等断言;首局约束集 ✓(§4.3) | -| 15 | P1 视障 | 巡检链=status key 全状态可用(决策窗简链);双击重听/回车即时键;账目真实;NPC 零 speaker 重叠;2.2× 语速引擎既有 ✓ | -| 16 | P3 通勤 | 中断复述(舰体/最紧要异常/巡航段 ≤8s);菜单冻结随时退出存档;段=3 分钟颗粒 ✓ | -| 17 | P4 工人 | 零新手势;超时有保底默认结局(不卡死不挨骂);DOCK 不替花资源;命令式短句 ✓ | -| 18 | P2 大学生 | S 评级追求+矿富线风险决策+支援演出+星港 build;死亡局功勋照入拉重开 ✓ | -| 19 | 台词规范 | 154 句 ≤160;全句 ≤40 字(实测 ≤35);数字全分档/句内写死;「重新听」写法;同角色 speaker+instruct 恒定 ✓(台词表) | -| 20 | 键盘通道 | V 组 10 变体集中登记,inputMode() 选播;方向键/空格/回车全手势可达(00 §5.5 平行通道,游戏层零改动)✓ | -| 21 | 引擎能力边界 | 无实时节点(告警/倒计时=SYN one-shot+setTimeout 调度);sfx 无 loop 需求;预 pan=派生资产;tap 只用于非即时下令;资产缺失 8s 兜底;30MB 预算内 ✓ | -| 22 | 密度下限(dungeon D0-3 教训) | 段计时/45s 自动线/超时默认结局三层硬推进——结构性无真空;每段 ≥1 危机+1 采集+(1-2)磨损事件 ✓ | -| 23 | **评级账目验算** | §8.4 剧本 A 十二行逐事件(score 19→S、功勋 15、三次跨档播报点实值核对)+§8.5 B 档+§8.6 失败路径——一条完整巡逻剧本验算入文档 ✓ | -| 24 | 台词 ≤40 字 | 台词表逐句实测 ≤38 字(最长 sys_help / sys_help_k 各 38)✓ | - ---- - -## 15. 修订记录(制作人自查裁定,2026-09-14) - -> 按 hunt 先例:自查发现矛盾不改旧文,在此追加裁定。 - -| # | 事项 | 裁定 | -|---|---|---| -| R1 | §9.2 头注「sfx 30 个基 id」与实表不符 | 实表逐行计数 = **31 基 id**(8 状态 + 1 离线签名 + 4 告警 + 2 方位尾音 + 4 危机签名 + 9 下令/演出/入账 + 3 计时/问询),头注 30 为漏计笔误;资产按 31 基 id + 8 pan 派生 = 39 文件执行 | -| R2 | §2.1 TITLE 双击「重听 continue_ask」在无档时无句可播 | 裁定为:有档 → 按表播 sys_continue_ask;无档 → 双击重听 intro_1..5 开场链(宪法双击重听语义,空态不静默) | -| R3 | §9.1 BGM「3 曲 ×2 候选 = 6 任务 $0.18」超制作人 Mureka 份额(≤$0.15) | 裁定为 3 任务 ×1 候选 = **$0.09**;候选取舍「拿不准原则」照常(单候选即选),台账照记 | -| R4 | 台词表头注自查「全表无『处』」与 loy_rad「医师在处理伤员」矛盾 | 「处理」读 chǔ,发音安全,系自查表漏标,不改句;同理 res_bad「损失惨重」重=zhòng,安全 | -| R5 | §12 #13 键盘变体断言口径 | 游戏层恒播 base id(voice/sys_help 等 10 句),`_k` 资产由引擎 inputMode 候选链自动路由(voice//key/,hunt/detective 先例);playtest 断言游戏层选句路径 + check_all_assets 锁定 key 目录两性别一致且 ⊆ 主集 | -| R6 | 多音字复查补充 | 「处置」无;全表另核「重(zhòng,惨重/敬重)」「处(chǔ,处理)」读音安全,与 R4 一并备案 | -| R7 | §4.2 求救陷阱矩阵「忽略/超时=明智避开,零损益」与 §5.1「求救·忽略/超时 医师−1」表面冲突 | 裁定为**分 variant**:非陷阱忽略/超时 → 医师−1(loy_ignore+doc_ignore NPC);陷阱变体忽略/超时 → 零损益(播 res_clean)——§4.2 矩阵为选项-结果权威源,§5.1 行按非陷阱语境理解 | -| R8 | §8.6「功勋=3 段」与「完成段数」口径 | 裁定为:功勋/评级段数口径 = run.seg(死亡局=死时所在段,如 S3 死=3;完成巡逻=4);score 的「完成段数×2」在非死亡局恒=8,死亡局评级恒 D、score 不入句——两口径不矛盾 | -| R9 | §11「PORT 列表=未购改装项」与 §12 #8「upg_done 不重复购买」可达性冲突 | 裁定为 PORT 列表=全部 6 项(armor2 依赖 armor1 已购才出现):未购→预览价格句,已购→预览 upg_done,已购项单击=error 震不重复扣费——upg_done 恒可达且账目不失真 | -| R10 | 非陷阱驰援且扫描受损的「残骸刮蹭 hull−1」致死时死因句无对应 id(台词表无 die_distress) | 裁定为归因族归并:刮蹭伤害物理同源于碎石/残骸撞击,lastHit 记 'meteor'(die_meteor「陨石撕开了船身」为最接近真话句);陷阱受伏记 'pirate'(§8.6「伏击者即海盗」已覆盖) | -| R11 | §7 教学编排与「求救超时=信号消失(零损失)」路径的交互未定义:教学危机在 D2 前被超时/忽略结算时 DRILL 挂在 step1/2——drillDone 永不持久化(次局重进教学)、残留看门狗定时器在后段空响;且 D2 的 90s 自动演示不可达(决策窗 20s≪90s) | 裁定为**收束规则**:教学危机结算时 step≥2 → 走 D3(tut_after/tut_loyal→上滑→tut_done→段末);step≤1 → 直接 tut_done 收束→段末(drillDone 照常持久化);收束期下滑跳过 → tut_skip 后补段末(段计时已停,不补会卡死)。D2 的 90s 自动演示登记为不可达条款(自动演示仅对 D0/D3 有效)。live R1 局 A 实测坐实原缺陷 | +BGM menu/cruise/alert 氛围(具体曲目见 voice 外 mureka 台账)。SFX:四系统 ok/dmg/off、alm_*、危机签名 4 型预 pan、tick_fast、pa_chime、eva_ok、wpn_fire、shield_bear、warp、hit_hull。船员 NPC 与旁白零 speaker 重叠。 diff --git a/docs/design/23-hunt-voice-ids.md b/docs/design/23-hunt-voice-ids.md index 48ff57bb..5ca44054 100644 --- a/docs/design/23-hunt-voice-ids.md +++ b/docs/design/23-hunt-voice-ids.md @@ -1,5 +1,13 @@ # 23b · 《围猎》语音台词表(voice ids,录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > **给音频总监的录制说明** > - **旁白/系统音双性别组(60 句,预算打满)**:落 `web/hunt/audio/voice/female|male/.{ogg,m4a}`,两性别 id 集合完全一致。 > - female = `Serena`,instruct 常量:`清亮沉稳的女声,像守夜猎人的领队,冷静中带着鼓舞` diff --git a/docs/design/23-hunt.md b/docs/design/23-hunt.md index 32505e55..701f453a 100644 --- a/docs/design/23-hunt.md +++ b/docs/design/23-hunt.md @@ -1,541 +1,199 @@ -# 23 · 《围猎》完整设计(hunt / Night Hunt) +# 23 · 《围猎》(hunt / Night Hunt) > **给开发者的实现要点摘要** -> 1. **2-4 人实时联机纯音频协作守卫**,项目"纯音频 MMORPG"路线的**第一阶段最小验证切片**(§1.3)。本作只验证三件事:实时同步(零依赖 WebSocket + 服务端权威)、音频信道拥挤控制(§8,全文重点)、空间音频协作(方位=pan+防区分工)。其余一切从简。 -> 2. 圆形猎场,猎人位于营地中心。野兽从 8 罗盘方位边缘逼近(8-12s),**四向滑=出击**(滑向映射 90° 弧,击杀弧内内圈最近一只,冷却 2.5s/人);突入中心 = 共享营地耐久 -1(巨兽 -2,初始 10);单击=呼喊支援(队友收到你防区方位的空间化喊话 + warning 震,bot 补位);双击=旋风斩(清内圈全部,冷却 8s;**冷却中双击=重听威胁播报**,宪法双击语义由此保留)。上滑=状态链,下滑=菜单(**不暂停**,联机实时豁免 E3)。 -> 3. 服务端权威:怪物生成/移动/命中/冷却/评级全在 `web/server/` 裁决,客户端只发意图(join/act/leave)。bot 由服务器驱动,与真人同一套事件协议。房间 3-4 座,开局空位由 bot 补至 ≥3 猎人;断线 30s 宽限重连回槽,超时转 bot。 -> 4. 协议全文 §6(两条传输消息 + 三类服务端消息,JSON 字段表逐条给出);零依赖 WS 握手与帧解析规范 §6.1(Node 原生 http upgrade + 手写帧,不引 npm)。 -> 5. 拥挤控制硬规则 R1-R8(§8.1):旁白单句队列(voice 层)、bot 喊话 sfx 层同刻 ≤1、怪物移动 loop ≤3 路(最近 3 只)、非语音信息全部 <1s 短音效、听觉视野裁剪只报内圈(<4s 抵达)。 -> 6. **引擎改动唯一一处(后向兼容)**:`engine/audio.js` 的 sfx/voice 播放各加可选 `opts.pan`(-1..1,StereoPannerNode 接入对应总线),缺省行为逐字节不变(§9)。怪物移动 loop 走游戏层句柄(abyss `_startLoop` 先例 + 游戏层自建 panner),不占引擎改动面。 -> 7. 台词表 [23-hunt-voice-ids.md](23-hunt-voice-ids.md):旁白/系统音 **60 句 ×2 性别**(male=Uncle_Fu / female=Serena,预算打满)+ bot 喊话 **3 角色 ×8 句 = 24 条固定单音色**(阿岩=Uncle_Fu / 小筝=Serena / 老周=Eric;因走 sfx 层带 pan,**落盘 `audio/sfx/`**,异例说明见台词表头注)+ 键盘变体 11 对。数字信息全分档整句,无运行时拼接。 -> 8. 设计裁决与对任务书核心案的修正记录(双击矛盾、出击冷却、防区模型、房码 UX 范围等 9 条)见 §15,**实施前必读**。断言清单住 [13-playtest-criteria.md](13-playtest-criteria.md) §24,本文 §13 只留摘要与指针。 +> 1. 交互权威:26 号手册。00 §5.3:四向滑动是出击 → **帮助=长按**。新手只教三件事:朝声音滑、点一下呼喊、按住问路。**不**在第一波教旋风斩、菜单、双击。 +> 2. 2–4 人实时联机纯音频协作。服务端权威。拥挤控制 R1–R8 与协议全文保留。 +> 3. 废除:双击=旋风斩(新手);冷却中双击=重听威胁;战斗下滑开菜单。旋风斩=阶段 4 高级手势(大厅「全部解锁」后双击;冷却中才是重听威胁)。 +> 4. 台词 [23-hunt-voice-ids.md](23-hunt-voice-ids.md):旁白 60×2 + bot 喊话 24 条落 sfx/。引擎唯一改动:`opts.pan`。 +> 5. 单局 5 波约 5 分钟。教学房学期零惩罚。 --- -## 1. 概述 +## 附录 C · 设计单 -### 1.1 一句话 +### 1. 一句话核心动作 -夜幕降临,野兽从四面八方围上来——你和队友背背守住营地篝火,**听声音的方向滑过去就是一刀**,撑过五波,天亮就赢。 +**朝声音来的方向滑动就是一刀;点一下喊队友;按住屏幕问营地。** -### 1.2 题材与纯音频相性 +### 2. 玩家能感知的状态(3 个) -守夜围猎是"方位即生死"的题材:怪物唯一的接近预告就是它逼近的声像。视觉游戏里这是小地图+tower defense;纯音频里,**声像本身就是小地图**——题材与媒介互为 justification,与 dungeon「声音就是你的眼睛」同源,但本作是**实时+协作**,不是回合制探索。 - -### 1.3 定位:纯音频 MMORPG 路线 · 第一阶段最小验证切片 - -本项目长线方向是纯音频 MMORPG(持久世界、多玩家同图协作)。《围猎》是该路线的**第一个可玩切片**,刻意把范围切到最小,只验证三个技术-设计命题: - -| 验证点 | 本文档落点 | 通过信号 | +| 状态 | 听感 | Floor | |---|---|---| -| **实时同步**:零依赖 WS、服务端权威战斗、事件流+快照兜底、断线重连 | §6 协议 | 局域网 2-4 端同局,事件一致,断线 30s 内重连无感回槽 | -| **音频信道拥挤控制**:多源并发(旁白/bot 喊话/怪物 loop/定位音效)不糊 | §8 R1-R8 | 最坏并发 7 声流下威胁可辨、警报不丢(§8.4 验证口径) | -| **空间音频协作**:靠 pan 的方位分工与支援是否成立 | §3 防区 + §7 bot 补位 | 玩家能用呼喊-补位闭环分担压力,无需任何视觉 | +| 围猎 | 最多 3 路怪物 loop(pan+距离档)+ BGM hunt | ACTION | +| 听讲 | 波次/结算/教学旁白,BGM 压低 | SPEAKING | +| 危险 | danger 签名 + 耐久≤2 句(每局一次) | ACTION | -**明确不做(切片纪律)**:无持久世界、无跨房聊天、无跨玩家排行榜("排行榜"= 进度 API 的个人最佳桶 + 大厅卡片 summary,§12)、无房码音频录入 UX(协议保留,客户端 v1 只做快速匹配,§6.4)、无内购/社交。这些列 roadmap,不在本文档。 +呼喊是短 sfx,不单开第四态。 -### 1.4 单局 +### 3. 新手手势与解锁(四向是玩法) -5 波为一局,约 5 分钟(含 lobby ≈ 5.5 分钟;首局含教学 ≈ 6.5 分钟)。营地耐久初始 10、共享;耐久归零 = 营地失守(立即终局,突围结算,不入榜);撑过 5 波 = 胜利结算(评级 S~C + 个人击杀分档 + 排行榜写入)。 - -### 1.5 与现有 12 游戏的关系 - -实时方位感承接 space(呼啸来袭+同向反应),但改为**协作分工**;状态分档链承接 dungeon(上滑多段链);威胁裁剪承接 dungeon 脚步距离档音量规则并升级为显式"听觉视野"(§8.2);bot 喊话平铺资产承接 rpg/radio/detective NPC 先例,但首次走 **sfx 层空间化**(§8 R2);结算链死窗沿用 dungeon H1 先例(800ms 手势死窗);看门狗/向导层全照 00 §6。**两个"第一"**:第一个联机游戏、第一个用到运行时 pan(引擎 opts.pan,§9)。 - -### 1.6 本作三大特有风险(均有显式对策) - -1. **多源并发糊耳**(5 波峰值:旁白+喊话+3 路 loop+定位音效)→ §8 规则表 + §8.4 验证断言。 -2. **实时联机与纯音频教学的根本矛盾**(不能暂停真人局教学)→ 首局教学房(§5):单人+bot、教学波、学期零惩罚,教完同场接续第 1 波。 -3. **协作流于形式**(都能打所有方向=不需要队友)→ 出击冷却 2.5s 限单人吞吐 + 软防区 + bot 补位闭环(§3.3、§7)。 - -## 2. 手势映射与豁免登记 - -### 2.1 战斗态(WAVE,含教学局 TEACH 的战斗段) - -| 手势 | 作用 | 反馈 | +| 阶段 | 条件 | 手势 | |---|---|---| -| 四向滑(上/下/左/右) | **出击**:滑向映射 90° 弧(上→{NW,N,NE}、右→{NE,E,SE}、下→{SE,S,SW}、左→{SW,W,NW}),击杀弧内内圈(eta≤4s)最近一只;冷却 2.5s | 引擎 swipe 震 + `atk_whoosh`;命中 `kill_*`+pickup 震;挥空 `atk_swish`+error 震 | -| 单击 | **呼喊支援**:全队友收到你防区方位的空间化喊话 + warning 震;bot 朝你侧补位 10s(§7.3);节流 5s/人 | 引擎 confirm 震 + 本机播自己座位角色的 help_call(中央) | -| 双击 | **旋风斩就绪** → 放出:清全场内圈(eta≤4s)全部怪物,冷却 8s;**冷却中** → 重听当前威胁播报(threat_*,宪法"双击=重听"落地) | 放出:success 震 + `skill_spin`;冷却中:引擎 confirm 震 + threat_* | -| 上滑 | 状态链:营地耐久档 → 波次档 → 在场猎人数 → 自己击杀档(4 段链播 queue:true) | hp_band_* → st_wave_* → team_* → kill_band_* | -| 下滑 | 模态菜单(**不暂停**,豁免 E3,§2.4) | menu earcon | -| 长按 | 非玩法输入:引擎旁白快进默认行为保留 | — | +| 0 | 首次进入 | **四向滑=出击**(90° 弧);**单击=呼喊**(5s 节流);长按尚未教 | +| 1 | 第一次击杀后 | **长按=我在哪**(耐久档。波次。一个动作:「朝叫声滑。」)≤20 字 | +| 2 | 第一局结束(END) | 下滑=菜单(**仅 LOBBY / 波间 8s 休整 / END**;WAVE 内下滑=出击南方) | +| 3 | 累计 3 局 | 双击=重听威胁档(threat_*)。仍不放技能 | +| 4 | 大厅「全部解锁」或专属教学 | 双击=旋风斩(就绪放出;冷却中=重听威胁) | -- 出击冷却 2.5s 内四向滑 → 仅 error 震(无语音、不发 act;dungeon 脉冲冷却先例)。 -- 单击呼喊是**非即时操作**,300ms tap 延迟无碍(escape 单击=检查先例)。 -- 旋风斩就绪时刻播 `skill_ready`(queue:true,绝不顶句;每轮冷却完成只播一次)。 -- **事件震动总表**:命中 pickup / 挥空 error / 旋风斩放出 success / 突入 hit / 收到呼喊 warning / 波次开始(ev wave)heartbeat / 耐久首次 ≤2 danger(每局 1 次,配 danger_low)。手势识别震动(swipe/confirm)由引擎内置承担,游戏层不重复(00 §2.3)。 +不要一次教 6 个手势。教学波只走 T0 点一下 → T1 滑砍 → T2 点呼喊 →(可选)T3 长按问路。旋风斩口头不出现。 -### 2.2 标题/匹配/大厅等待(TITLE / MATCHING / LOBBY) +对局期长按=帮助,关闭旁白加速。 -| 手势 | TITLE | MATCHING | LOBBY | -|---|---|---|---| -| 单击 | 快速匹配(首局=教学房) | —(无效,error 震) | 立即开局(act start) | -| 双击 | 重听开场/提示 | 重听 | 重听 room_wait | -| 上滑 | 个人最佳播报(有档时:best 评级+击杀档) | error 震 | 在场猎人播报(team_*) | -| 下滑 | 菜单(帮助/回大厅) | 菜单(含"取消匹配") | 菜单(退出房间回大厅) | -| 左右滑 | error 震 | error 震 | error 震 | +### 4. 状态 × 手势矩阵(新手期) -### 2.3 结算态(END) - -单击=再来一局(重新进匹配);双击=重听结算链;上滑=重听个人最佳段;下滑=菜单(回大厅)。**结算链进入时盖 800ms 手势死窗**(dungeon H1 先例:吞掉致命事件前后在途的 ghost 手势,死窗后手势照常)。 - -### 2.4 模态菜单(豁免 E3:联机实时不暂停) - -下滑呼出 → 单击循环 → 双击确认 → 上滑=帮助(播 `help_main`)→ 下滑=关闭(00 §5.2 交互骨架不变)。**菜单打开期间战斗照常进行**(怪物照走、突入照扣)——联机不可单方暂停,这是与 00 §5.2"菜单打开时暂停计时类玩法"的显式豁免。菜单项固定 3 项:`sys_menu_resume`(回到猎场)/ `sys_menu_help`(重新听帮助)/ `sys_menu_home`(退出房间回大厅 = 发 leave,槽位按 §6.6 处理)。无"重新开始"(联机房局不可单方重开)。菜单打开即播 `sys_menu` + 首项;菜单内另播 `settings_hint`(设置只读、回大厅修改,00 §1.2)。 - -### 2.5 豁免登记(显式) - -| # | 豁免条目 | 理由(先例) | -|---|---|---| -| E1 | 四向滑=出击(非选择/移动) | 以滑动为核心玩法(space 四向滑=闪避同款豁免);三件套(上滑/下滑/双击重听)全部保留 | -| E2 | 双击优先=旋风斩,仅冷却中=重听威胁 | 旋风斩是即时技,必须绑零延迟双击(00 §5.4:即时判定绑滑动或双击);冷却中重听保留宪法语义 | -| E3 | 菜单打开**不暂停** | 联机实时,暂停不可能;菜单项压到 3 项、打开即播提示,把"逛菜单被咬"的代价做成可感知风险 | -| E4 | 单击=呼喊(非确认/继续) | 非即时操作,300ms 延迟无碍(escape 单击=检查先例) | - -教学(TEACH)逐条讲清 E1/E2/E4(§5);不占用长按(快进保留,无需 rateBoost 豁免);不开 immediateTap。 - -## 3. 世界模型与数值 - -### 3.1 猎场与怪物 - -- 圆形猎场:猎人位于圆心营地,野兽从边缘(8 罗盘方位 N/NE/E/SE/S/SW/W/NW)沿直线匀速逼近圆心。逼近全程 8-12s(按波次表)。 -- 怪物状态由**服务器**持有:`{ id, mkind, dir, eta }`——eta = 距突入的秒数(服务端单调时钟)。客户端不推算位置,只按事件流+快照渲染(§6.7)。 -- 怪物 2 型: - -| 型 | mkind | 逼近 | 突入扣耐久 | 声音 | -|---|---|---|---|---| -| 饿狼 | `wolf` | 8-10s | -1 | `howl_wolf` 生成呼啸 + `loop_wolf` 移动 | -| 暴熊 | `bear` | 11-12s | **-2** | `howl_bear` 低吼 + `loop_bear` 重脚步 | - -- **距离分档**(听觉视野,§8.2):外圈 eta>6s(仅生成呼啸一声);中圈 4-6s(移动 loop 可闻,低音量);内圈 eta≤4s(可击杀的近战窗,loop 升档,计入威胁播报)。 - -### 3.2 出击与技能(服务端裁决,§6.5) - -- **出击**:`act attack dir` → 弧 = dir±45°(3 个罗盘向)→ 弧内活着且 eta≤4.0 的怪中取 eta 最小者击杀;无 → `miss`。冷却 2.5s/座(服务器计时,违规 `err cooldown`)。 -- **旋风斩**:`act skill` → 清除全场 eta≤4.0 全部怪(不分方向)。冷却 8s/座(违规 `err skill_cd`)。 -- **呼喊**:`act callout` → 广播 `ev callout`;bot 补位(§7.3)。节流 5s/座。 -- 击杀归属 = act 来源槽位(结算个人击杀账)。 - -### 3.3 防区(软分工) - -- 每座位固定**主防区**(连续罗盘弧)与防区中心(喊话 pan 用): - -| 猎人数 | 座位 0 | 座位 1 | 座位 2 | 座位 3 | -|---|---|---|---|---| -| 3 | [N,NE,E],中心 NE | [SE,S,SW],中心 S | [W,NW],中心 WNW | — | -| 4 | [N,NE],中心 NNE | [E,SE],中心 ESE | [S,SW],中心 SSW | [W,NW],中心 WNW | - -- 防区**不是硬限制**:任何玩家可打任何方向(弧映射见 §2.1)。防区用于:① bot 目标选择偏置(§7.2);② 呼喊/喊话的 pan 方位来源;③ 刷怪剧本的方位均衡(§3.5)。 -- 座位角色(voice 身份):座 0=阿岩(yan)、座 1=小筝(zheng)、座 2=老周(zhou);**座 3 仅 4 人局存在,无专属角色**(呼喊=`call_whistle` 哨音,见 §15 裁决 6)。真人"扮演"座位角色:你的呼喊用你座位角色的预录声,断线转 bot 后声音身份无缝延续。 - -### 3.4 波次表(5 波,服务器剧本) - -| 波 | 狼 | 熊 | 狼逼近 s | 熊逼近 s | 刷怪间隔 s | 波时长约 | -|---|---|---|---|---|---|---| -| 1 | 4 | 0 | 10.0 | — | 3.0 | ~21s | -| 2 | 6 | 0 | 9.5 | — | 2.5 | ~24s | -| 3 | 7 | 1 | 9.0 | 12.0 | 2.2 | ~29s | -| 4 | 8 | 1 | 8.5 | 11.5 | 1.8 | ~28s | -| 5 | 10 | 2 | 8.0 | 11.0 | 1.5 | ~30s | - -- 波间休息 8s(`wave_clear` → 休整 → 号角 + 下一波播报)。波内怪物匀速逼近不止步,一波必在有限时间内出清(杀或漏),无需超时兜底。 -- 每波开始播对应整句 `wave_band1`…`wave_band5`(台词表 C 组;先号角 `horn_wave` 后播报)。 -- 全程怪物总数 39,耐久预算 10(熊 -2)。平衡目标:3 bot 水准下 solo 余耐久 6-9(评级 A/B),真人协作越好评级越高。 -- 局时长核算:战斗 ≈ 132s + 休息 32s + lobby ≤12s ≈ **3 分钟战斗 + 首尾 ≈ 5 分钟** ✓(任务书"约 5 分钟")。 - -### 3.5 刷怪方位与种子 - -- 房间 seed 开局掷一次(`joined.seed`),mulberry32 派生:`rng(wave) = mulberry32((seed ^ (wave * 0x9E3779B9)) >>> 0)`。 -- 每波方位分配:把本波怪物按座位数轮转发牌到各座位防区内的随机方位(同一方位同刻最多 2 只在场,防叠声);同 seed → 同 spawn 剧本(服务器单测可复现断言)。 -- 服务端战斗模拟 tick = 100ms;快照 2Hz(§6.3)。 - -### 3.6 耐久与评级 - -| 结算时剩余耐久 | 评级 | -|---|---| -| ≥9 | S | -| 7-8 | A | -| 4-6 | B | -| 1-3 | C | -| 0(波内归零) | 失守(立即终局,无评级,不入榜) | - -评级由服务器算好在 `ev end.grade` 下发(客户端不算分,防双源)。 - -**终局播报链**:胜利 = `end_win → grade_s/a/b/c → kill_band_*(自己击杀档)→ end_rank → end_again`;失守 = `end_lose → kill_band_* → end_again`(失守不播评级、不播 end_rank、不写 best,§12)。进 END 盖 800ms 手势死窗(§2.3)。 - -## 4. 状态机(客户端) - -### 4.1 状态列表 - -`TITLE / MATCHING / LOBBY / TEACH / WAVE / MENU(overlay) / END / LOST`。MENU 为挂起 overlay(记录返回态,**不暂停战斗**);LOST = 连接不可恢复(回大厅出口)。 - -### 4.2 转移图 - -``` -TITLE ──单击──> MATCHING ──joined──> LOBBY ──act start / 12s 到 / 满4人──> (ev wave 1) - │ 首局(tutDone=false) → join teach=true → joined.state.phase='teach' → TEACH -MATCHING ──连接失败──> LOST(net_lost→net_gone) ──单击──> TITLE -TEACH ──教学完成 / 下滑跳过──> WAVE(wave 1) -WAVE ──ev end──> END ──单击──> MATCHING(再来一局) -WAVE/LOBBY ──下滑──> MENU ──返回──> 原态(战斗不暂停) -WAVE ──WS 断开──> 本地静默重连 ≤30s(net_lost) ──joined(resume)──> WAVE(net_back) - └──超时/房已灭──> LOST(net_gone) -``` - -### 4.3 每状态手势映射总表 - -| 手势 | TITLE | MATCHING | LOBBY | TEACH | WAVE | MENU | END | -|---|---|---|---|---|---|---|---| -| 四向滑 | error | error | error | 步骤作答/出击 | 出击 | error | error | -| 单击 | 快速匹配 | error | 立即开局 | 步骤作答/呼喊 | 呼喊 | 循环切项 | 再来一局 | -| 双击 | 重听 | 重听 | 重听 | 步骤作答/旋风斩 | 旋风斩/重听威胁 | 确认执行 | 重听结算链 | -| 上滑 | 最佳播报 | error | team_* | 状态链(T4 起) | 状态链 | 帮助 | 重听最佳段 | -| 下滑 | 菜单 | 菜单(取消匹配) | 菜单 | **跳过教学** | 菜单(不暂停) | 关闭 | 菜单 | -| 长按 | 快进 | 快进 | 快进 | 快进 | 快进(非玩法) | — | 快进 | - -无效手势一律 error 震;full 档语音纠错 `gd_invalid`(onceKey 同局 1 次)。看门狗语音 idle-only。 - -## 5. 教学编排(TEACH,首局教学房) - -**矛盾与解法**:实时联机局不能暂停教学 → 首次进入(进度 `tutDone` 为假)走**教学房**:单人 + 2 bot(教学段 bot AI 挂起)、先行一波**教学波**(3 狼,逼近 12s,学期零惩罚:教学波突入**不扣耐久**),教学完成或下滑跳过后,同一房间直接接续第 1 波(bot 激活)。教学即真联机局——这本身就是切片验证点(首局就跑了完整的 WS/事件流路径)。 - -| 步 | 旁白 | 等待动作 | 通过/反馈 | -|---|---|---|---| -| T0 | `intro_1` + `intro_2` + `tut_rule` | 单击 | confirm 震 | -| T1 | tut_attack(服务器放教学狼 1,方位 N) | 朝声音方向滑 | kill → tut_kill_ok;挥空/错向 → gd_idle 重教(≤2 次后自动放过该狼) | -| T2 | tut_callout | 单击呼喊 | callout ev → bot `npc_*_help_ack` 响应演示(教学 bot 必答)→ tut_callout_ok(呼喊-响应闭环点明) | -| T3 | tut_skill(服务器放教学狼 2、3,贴近 eta 5s) | 双击旋风斩 | cleared=2 → tut_skill_cd(讲冷却与冷却中双击=重听) | -| T4 | tut_status | 上滑(状态链) | 链播完 → tut_breach → tut_done → 进 WAVE 1 | -| 任意 | — | 下滑 | tut_skip → 进 WAVE 1(`tutDone` 写入进度) | - -- 教学波零惩罚:教学狼突入不扣耐久(明说规则前不漏真话);T1 首输入 ≈ t+20s(<60s ✓);首个正反馈 = T1 首杀 ≈ t+40s(≤3min ✓)。 -- 看门狗:每步 8s nudge 纯震(教学句不重播),T1/T3 卡 30s 自动放过当前狼推进(space 先例),不卡流程。 -- 二周目起 TITLE 单击直达快速匹配(tutDone 存进度,§12)。 - -## 6. 多人协议(两个开发 agent 的实现契约) - -### 6.1 传输:零依赖 WebSocket(server.js 增补) - -- 端点:`GET /ws/hunt`,与静态/REST 同一 HTTP 服务器、同一端口。`http.createServer` 的 `'upgrade'` 事件接管:校验 `Upgrade: websocket`、`Sec-WebSocket-Key`,回 `101` + `Sec-WebSocket-Accept = base64(sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))`(node:crypto,无 npm)。 -- 帧解析(手写,约 100 行,建议独立 `web/server/ws.js` 便于单测):客户端→服务器帧**必须带 mask**(不带 → close 1002);支持 opcode 0(continuation 拼装)/1(text)/8(close)/9(ping→回 pong)/10(pong);payload 7/16/64 位长度全支持;单条消息上限 **4KB**(超限 close 1009);二进制帧 close 1003。服务器→客户端帧不 mask。 -- 活性:服务器每 15s ping,45s 无 pong 判死(等同断线走 §6.6);每连接应用消息速率帽 20 条/s(超限 err `rate`,再犯 close)。 -- 发送背压:每连接一个待发队列;`snap` 可丢(新顶旧),`ev/joined/err` 不丢;队列 >200 → close 1013。 -- 每消息 = 1 条 text 帧 = 1 个 JSON 对象,必有 `t` 字段。协议版本 `ver: 1`(join 携带,不符 err `bad_msg`)。 - -### 6.2 客户端→服务器消息(3 条) - -**join**(连接后首条,5s 内不发则 close): - -| 字段 | 类型 | 含义 | -|---|---|---| -| `t` | `"join"` | — | -| `ver` | number | 协议版本,恒 1 | -| `room` | string | `"quick"` = 快速匹配;或 4 位房码(大写无歧义字符 `23456789ABCDEFGHJKMNPQRSTUVWXYZ`) | -| `name` | string | 显示名(1-24 字符,取自档案;服务端截断兜底) | -| `teach` | bool? | true = 请求教学房(首局;服务端强制单人房+教学波) | -| `resume` | string? | 重连凭证(`joined.resume` 原值);携带时按 §6.6 回槽 | - -**act**(对局意图;服务器按到达序裁决): - -| 字段 | 类型 | 含义 | -|---|---|---| -| `t` | `"act"` | — | -| `kind` | `"attack" \| "callout" \| "skill" \| "start"` | start 仅 LOBBY(立即开局) | -| `dir` | `"N"\|"NE"\|"E"\|"SE"\|"S"\|"SW"\|"W"\|"NW"` | attack 必填(客户端四向滑只发 N/E/S/W 四基向,弧解析在服务端;bot 可发 8 向) | -| `seq` | number | 客户端单调递增(去重/乱序检测;乱序不拒,仅日志) | -| `at` | number | 客户端发出时刻 ms(仅诊断用,裁决以服务端到达序为准) | - -**leave**:`{ "t": "leave" }` —— 主动退出(菜单"回大厅"),槽位立即按 §6.6 处理(无 30s 宽限)。 - -### 6.3 服务器→客户端消息(joined / ev / snap / err) - -**joined**(join 受理;resume 重连亦发): - -| 字段 | 类型 | 含义 | -|---|---|---| -| `t` | `"joined"` | — | -| `room` | string | 实际房码 | -| `slot` | number | 你的座位号 0-3 | -| `resume` | string | 重连凭证(16 hex,每槽位签发一次,连接存活期内不变) | -| `char` | `"yan"\|"zheng"\|"zhou"\|null` | 座位角色(座 3 恒 null) | -| `sector` | string[] | 你的主防区方位集(§3.3) | -| `seed` | number | 房间种子(复现用) | -| `state` | object | `{ phase: "lobby"\|"teach"\|"wave"\|"end", wave: number, dur: number }` | -| `roster` | array | 全花名册:`[{ slot, name, bot, char, sector }]`(bot=true 时 name=角色名) | - -**ev**(事件流,即时下发;`ts` = 房间单调时钟 ms(开局为 0)): - -| kind | 附加字段 | 含义 | -|---|---|---| -| `spawn` | `id, mkind("wolf"\|"bear"), dir, eta` | 怪物生成(eta=逼近全程秒数) | -| `kill` | `id, slot, dir, mkind` | 出击命中击杀(归属 slot) | -| `miss` | `slot, dir` | 挥空(弧内无可打怪) | -| `skill` | `slot, cleared: number[]` | 旋风斩放出,cleared=被清怪 id 表 | -| `breach` | `id, mkind, dir, dur` | 突入:耐久新值 dur(wolf -1 / bear -2 已结算) | -| `wave` | `wave` | 第 wave 波开始(1-5) | -| `waveclear` | `wave` | 第 wave 波出清 | -| `callout` | `slot` | 求援呼喊(真人单击或 bot 压力呼救;接收端播该座角色 help_call + warning 震) | -| `botline` | `slot, line` | bot 喊话(line ∈ `greet/help_ack/cover/kill/breach/skill/wave`;客户端按 char+line 播 `sfx/npc__`) | -| `roster` | `roster` | 花名册变更(加入/离开/断线/转 bot;形状同 joined.roster) | -| `end` | `win, dur, grade("S"\|"A"\|"B"\|"C"\|null), kills` | 终局;kills=`{ "0": 5, ... }` 按 slot 的个人击杀 | - -**snap**(2Hz 兜底快照): - -| 字段 | 类型 | 含义 | -|---|---|---| -| `t` | `"snap"` | — | -| `ts` | number | 房间单调时钟 ms | -| `phase` / `wave` / `dur` | — | 同 joined.state | -| `mobs` | array | 在场怪物全量 `[{ id, mkind, dir, eta }]`(eta 为 ts 时刻值) | -| `slots` | array | `[{ slot, alive, bot, cd, skillCd }]`(cd/skillCd=剩余冷却秒,重连恢复冷却 UI/判定用) | - -**err**:`{ "t": "err", "code": "cooldown"|"skill_cd"|"bad_dir"|"not_ingame"|"room_full"|"room_gone"|"bad_msg"|"rate", "msg": "人话描述" }`(cooldown/skill_cd 为正常违规拒绝,客户端不应惊扰玩家——客户端本地已先拦)。 - -### 6.4 房间管理与匹配 - -- 快速匹配:找 `phase='lobby'` 且有空人类座的房 → 进;没有 → 建房(房码 4 位随机)。**客户端 v1 只发 `room:"quick"`**;显式房码 join 协议完整、服务端实现并单测,客户端录入 UX 列 roadmap(§15 裁决 5)。 -- LOBBY 上限 12s:到点、任一人类 `act start`、或满 4 人 → 开局(播 `room_start`,随后 `ev wave 1` + `wave_band1`)。开局时空位由 bot 补至 **max(3, 人类数)** 猎人:1 人→2 bot、2 人→1 bot、3 人→0 bot(3 座)、4 人→0 bot(4 座)。第 4 座仅 4 人局启用。 -- join 只在 lobby 受理;对局中 join → err `room_full`(v1 不做中途加入)。 -- 教学房:join `teach:true` 且本地 tutDone=false → 强制单人房(§5 剧本);教学波完直接续 WAVE 1。 - -### 6.5 服务端权威战斗(hunt-room.js,建议独立模块便于单测) - -- 房间结构:`{ seed, phase, slots[≤4], mobs: Map, wave, dur, tickTimer, snapTimer }`;tick 100ms:eta 递减、突入结算、波剧本推进;snap 500ms。 -- 裁决规则(§3.2 数值的权威实现):弧映射、eta≤4.0 近战窗、冷却 2.5/8s、呼喊节流 5s——全部只在服务端生效;客户端可做本地预扣(冷却 UI),但以 err/ev 为准。 -- bot = 特殊 slot(bot:true,无连接):同一套裁决管线,由 §7 AI 产生 act。 -- **时钟**:ts 为房间单调 ms。客户端在 joined/snap 到达时记 `offset = ts - performance.now()`,渲染用 `nowServer = performance.now() + offset`;局域网无需 RTT 补偿(逼近时长 8-12s,误差淹没在听觉分辨率内)。 - -### 6.6 断线与重连 - -- 连接断开(或 45s 无 pong)→ 槽位标记断线(roster 广播 alive=false),**30s 宽限**:槽位静默(无 act 产出,猎人"愣在原地"——这是真实的协作代价)。 -- 宽限内带 `resume` 重连 → 回槽:重发 joined(同 slot/seed/char)+ 紧随 snap 全量,客户端无缝续渲染(net_back)。 -- 宽限超时:剩余人类 ≥3 → 槽位移除(roster);否则槽位转 bot(roster,bot=true,char 沿用座角色,补位 bot 播 `npc_*_cover`,其余客户端播 `net_to_bot`)。转 bot 不可逆(该局内)。 -- 最后一个人类断开:房间保留至其 30s 宽限耗尽后销毁(无人类即灭,bot 不续命)。房灭后迟到 resume → err `room_gone`(客户端 net_gone → LOST)。 -- 终局 end 后房间保留 30s(各端写排行榜)随后 close 1000。 - -### 6.7 客户端渲染模型(client agent 契约) - -- 本地怪表 = ev 增删 + snap 校正:每 snap 对账——本地多出的 id 删、缺的按 snap 补、eta 偏差 >0.5s 校正。事件流为主、快照兜底,二者矛盾以 snap 为准。 -- 每 500ms 重估渲染集:eta>6s 静默;≤6s 按 eta 升序取前 3 只起 loop(§8 R3);loop 音量按档(6-4s:0.30 / 4-2.5s:0.55 / <2.5s:0.80);pan 按方位角映射(§8.3)。 -- 断网期间:本地怪表按最后已知 eta 继续衰减渲染(怪物照爬——戏剧张力保留),恢复后以 snap 校正。 - -## 7. Bot 设计(服务端驱动) - -### 7.1 角色与声音资产 - -| 座位 | 角色 | char | speaker | instruct 常量 | 性格 | +| 状态 \ 手势 | 单击 | 四向滑动 | 长按 | 双击(未解锁) | 连点/乱滑 | |---|---|---|---|---|---| -| 0 | 阿岩 | yan | Uncle_Fu | `憨厚壮实的中年男声,猎户,话少踏实` | 憨厚 | -| 1 | 小筝 | zheng | Serena | `轻快爽利的年轻女声,猎手,干脆利落` | 轻快 | -| 2 | 老周 | zhou | Eric | `沙哑沉稳的老年男声,老猎手,带点调侃` | 沙哑 | +| 匹配/大厅 | 匹配或开局 | invalid | 帮助(最佳/人数) | 按单击 | busy | +| 教学作答 | 按步骤 | 按步骤出击 | 帮助 | 按单击 | 迷茫;挥空真话 | +| WAVE 战斗 | 呼喊 + 空间化喊话(节流内 invalid) | 出击;冷却内仅首次 invalid 震,其后静默,就绪补 beat | 帮助 | 按单击=呼喊 | 乱滑计入迷茫但不放技能 | +| 波间休整 | 无 | invalid | 帮助 | 按单击 | | +| 旁白在播 | 跳句 | 打断并出击 | 打断,帮助 | 按单击 | 第一下 | +| 结算链内 | 吞(800ms 死窗) | 吞 | 吞 | 吞 | 吞 | +| 结算链完 | 再来一局 | invalid | 重听最佳一句 | 按单击 | 第一下 | +| 菜单(非 WAVE) | 确认 | 切项 | 重听说明 | 按单击 | busy | -每角色 8 句固定短句(事件触发,全集见台词表 N 组):`greet / help_call / help_ack / cover / kill / breach / skill / wave`。**固定单音色平铺、不做双性别**(rpg NPC 先例);因经 sfx 层带 pan 播放,资产落 `audio/sfx/npc__.{ogg,m4a}`(§15 裁决 7)。 +菜单**不暂停**战斗(联机豁免):因此 WAVE 内根本不打开菜单。休整窗打开时战斗已暂停刷怪。 -### 7.2 战斗 AI(tick 500ms,确定性) +### 5. 等待点 -- 出击:冷却就绪且存在目标方向时出手。方向压力 `P(d) = Σ(1/eta)`(d 内活怪,仅计 eta≤4);主防区方向 ×1.5 偏置;取 max P 方向发 `act attack`(dir=该方向精确罗盘向)。**拟人反应带**:只在最优目标 eta ≤ 2.0 + rng()×1.0 时才出手,且每次出手决策带 80% 空转概率(rng 走房间 seed,复现可断言),避免" eta 一进 4 秒就秒"的机械感(数值为 §15 裁决 10 调参后口径)。 -- 旋风斩:全场 eta≤2.5 的怪 ≥3 只且技能就绪,且 60% 概率想起 → `act skill`(裁决 10)。 -- 呼救:主防区压力 ≥3(≥3 只 eta≤6)且 20s 节流 → `act callout`。 -- 难度校准:bot 不失误(挥空不存在——只打有目标的弧),但受 2.5s 冷却、反应带与空转概率上限;3 bot 局实测评级 A/B 为主(§3.4 平衡目标,多种子扫描见 §15 裁决 10),给真人留出"比 bot 强"的空间。 +| id | 期待 | full | short | 静默 | L2 | L3 | +|---|---|---|---|---|---|---| +| intro | tap | 「点一下屏幕。」 | 「点一下。」 | 6s/15s | tap | 替点 | +| teach_hit | 朝狼向滑 | 「朝叫声,滑。」 | 「滑一下。」 | 8s earcon / 15s 短句 | 该向滑 | 游戏杀掉这只教学狼 | +| teach_call | tap | 「点一下,喊人。」 | 「点一下。」 | 8s/15s | tap | 替喊 | +| teach_help | longpress | 「按住屏幕。」 | 「按住。」 | 8s/15s | 长按 | 替播帮助句 | +| lobby | tap | 「点一下,开局。」 | 「单击。」 | 8s(12s 到点自动开) | tap | 自动 start | +| wave | 滑或听 | 不念说明书 | earcon | 本波已出手则永不语音催 | — | — | -### 7.3 补位(呼喊响应) +WAVE 内「等待是合法玩法」:本波有出击/击杀则一级语音永不触发。 -收到 `ev callout`(真人或 bot 发出)后:**空闲 bot**(当前 500ms 无待发 act 者,多取座位号小者)播 `help_ack`,且其后 10s 内目标选择偏置改为呼喊者防区方向 ×2.0(覆盖主防区偏置)——即"朝你侧补位"。补位 bot 首次在新防区击杀后再无怪时播 `cover`。呼喊本身不改变 bot 的冷却/反应带(公平)。 +### 6. 首 3 分钟 -### 7.4 喊话调度(与 §8 R2 联动的服务端纪律) +| 时间 | 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 篝火+一声狼嚎(N)→「朝叫声,滑。」 | 上滑出击 | 「滑一下。」 | 点:invalid「先滑。」;错向:挥空 atk_swish「在另一边。」 | +| 3~10s | kill_wolf +「对。」(第一次成功) | — | | | +| 10~25s | 第二只狼 →「点一下,喊人。」 | 单击 | 「点一下。」 | 滑:仍可砍(不卡死) | +| 25~40s | bot help_ack(空间化) | 听协作闭环 | | | +| 40~60s | 「按住屏幕,听营地。」 | 长按 | 「按住。」 | | +| 60~90s | tut_done → WAVE1(教学狼突入不扣耐久) | 自由滑砍 | 迷茫 L3 清近狼 | 学期不扣耐久 | +| 90~180s | 第 1–2 波;不提旋风斩 | 滑+喊 | 威胁靠 loop | 冷却连滑不连震 | -- botline 触发源:greet(开局补位完成,逐只间隔 ≥1.5s)/ cover(转 bot 接管、补位落定)/ kill(杀熊或三连杀,15s 节流)/ breach(本防区漏怪,20s 节流)/ skill(放旋风斩)/ wave(第 2 波起,轮转一只 bot)。 -- 服务端对 botline/callout 全局节流:**10s 窗口 ≤2 条**,超出丢弃(喊话是氛围,不是信息主通道);help_call/help_ack 优先级最高(呼救语义不可被氛围句顶掉)。 +### 7. 失败表 -## 8. 音频信道拥挤控制(本文档重点章节) +| 类型 | 判定 | 归因 | 回到 | 新手期 | +|---|---|---|---|---| +| 挥空 | 弧内无内圈怪 | atk_swish(内容音,可省 ok) | 原地 | 无 | +| 冷却出击 | 2.5s 窗 | 每窗首次 invalid,其后静默 | 原地 | 无 | +| 漏怪突入 | eta 到 0 | breach 闷响;狼 −1 / 熊 −2 | 战斗继续 | 教学波 0 伤 | +| 失守 | dur=0 | end_lose + 击杀档;无评级不入榜 | END | 教学到不了 | +| 呼喊节流 | 5s | invalid 一次解释 | 原地 | 无 | +| 迷茫 | 乱滑无击杀 | L1「朝最响的滑。」→ L2 只放行该弧 → L3 清一只近狼 | 战斗 | 无 | -### 8.1 硬规则表 R1-R8 +### 8. 台词类别 -| # | 规则 | 数值 | -|---|---|---| -| R1 | **旁白/系统音单句队列**(引擎 voice 层既有):同刻 1 句,链播 onComplete 串行;警报类(breach/breach_heavy/danger_low)可顶掉状态/播报类(上滑链、skill_ready),教学/结算链不可顶 | 同刻 voice=1 | -| R2 | **bot 喊话走 sfx 层**(带 pan,与旁白并发):同刻在播 ≤1 条;待播队列深 ≤1(新到顶掉未播的过期句);旁白在播时延迟投递 ≤2s,超时丢弃;服务端全局 10s ≤2 条(§7.4) | 同刻喊话=1 | -| R3 | **怪物移动 loop ≤3 路**:只渲染 eta 最小的 3 只(500ms 重估),其余静默;loop 经游戏层句柄 + 自建 StereoPannerNode(abyss `_startLoop` 先例,不占引擎改动面) | 同刻 loop≤3 | -| R4 | **定位 one-shot ≤2 并发**(howl/击杀/突入/旋风斩):同类间隔 ≥0.15s;波次生成潮的呼啸队列深 ≤2,超出丢弃(怪近后 loop 已接管信息,呼啸可弃) | 同刻 one-shot≤2 | -| R5 | **非语音信息全部 <1s 短音效**:号角 0.9s、join_chime 0.4s、call_whistle 0.6s、kill 0.5s 量级;语音通道只给人声 | — | -| R6 | **听觉视野裁剪**(§8.2):威胁播报只计内圈(eta≤4);状态链不报具体方位(方位由 loop pan 承载,语音永不逐只报方位——8 向 × 距离档的语音枚举被明确禁止) | — | -| R7 | **电平分层**:voice 层播时 BGM 自动 duck 20%(引擎既有);bot 喊话(sfx 层)不触发 duck → 游戏层统一 vol 0.55 压一档,防压旁白;loop 音量档上限 0.80 | — | -| R8 | **最坏并发核算**:旁白 1 + 喊话 1 + loop 3 + one-shot 2 = **7 声流**;频带分离(loop 低频 100-300Hz / 人声 300Hz-3kHz / howl 中高 1-3kHz / BGM 垫底),引擎限幅器兜底。峰值场景(第 5 波 + 警报 + 呼喊)不得丢警报、不得叠爆 | ≤7 声流 | +- `critical`:开场「朝叫声,滑。」(≤3s) +- `content`:intro、wave_band*、end_*、grade_*、npc 喊话 +- `hint`:tut_*(去双击/旋风斩)、hp_band、st_wave、team_*、threat_*、长按帮助句 +- `feedback`:atk_swish、gd_invalid、net_*、kill_band 跳过 0 杀羞辱句 +- 停用:tut_skill、tut_skill_cd、新手「双击旋风斩」 -### 8.2 听觉视野裁剪规则 +60 句 ×2 性别 + 24 npc sfx + `_k` 11 对。约 23MB < 30MB。 -8 方位 × 3 距离档: +### 9. 断点复述 -| 档 | eta | 可听内容 | 可被打 | -|---|---|---|---| -| 外圈 | >6s | 仅生成呼啸一声(one-shot,pan+距离音量 0.4) | 否 | -| 中圈 | 4-6s | 移动 loop(vol 0.30;受 R3 路数上限约束) | 否 | -| 内圈 | ≤4s | loop 升档(0.55/0.80);计入 threat_* 威胁播报与 danger 评估 | **是(近战窗)** | +联机局不按单机节点存档。TITLE 有 best 时长按/上滑(阶段 1 后长按)播最佳评级+击杀档。重连:30s 内 resume 回槽 + snap;超时转 bot。回来一句:net_back「你回来了。朝叫声滑。」 -- threat 口径:`threat_none`(内圈 0 只)/ `threat_few`(1-2 只)/ `threat_many`(≥3 只)——**只报数量档,不报方位**;方位信息永远由 loop/呼啸的 pan 实时承载(这本身就是切片要验证的"空间音频协作"命题)。 -- 播报裁剪:状态链/威胁播报里禁止出现逐方位枚举句;台词表不含此类 id(结构性杜绝)。 +### 10. 测试记录 -### 8.3 方位 → pan 映射(全作统一) +改:教学无旋风斩;WAVE 下滑=出击不弹菜单;双击新手=呼喊;长按=帮助。协议、拥挤、断线、评级双轴保留。失败即不通过:loop>3、喊话压旁白、教学扣耐久、服务器接受违规 act。 -方位角 az(N=0°、E=90°、S=180°、W=270°):`pan = sin(az)`(N/S=0、NE/SE=±0.7、E/W=±1、NW/SW=∓0.7)。**前/后歧义**(StereoPanner 不能分前后):后半平面(90° MATCHING ──joined──> LOBBY ──start/12s/满4──> WAVE +首局 tutDone=false → 教学房 TEACH → WAVE1 +WAVE ──ev end──> END ──单击──> MATCHING +WAVE 断线 30s resume 回槽;超时转 bot;末人类断尽灭房 +``` -| id | 方式 | 说明 | -|---|---|---| -| `atk_whoosh` | SYN | 挥击风声 0.3s(带 pan=出击方位) | -| `atk_swish` | SYN | 挥空空荡风声 0.3s(归因真话:没打到) | -| `kill_wolf` / `kill_bear` | SA3 | 狼哀嚎倒地 0.5s / 熊闷重倒地 0.7s(带 pan) | -| `skill_spin` | SA3 | 旋风斩气旋 0.8s(中央,大动态) | -| `breach_thud` | SA3 | 营地被撞闷响+木栅裂 0.6s(中央) | -| `horn_wave` | SA3 | 波次号角 0.9s(中央) | -| `join_chime` | SYN | 队友加入 0.4s(双音上行) | -| `call_whistle` | SYN | 求援哨音 0.6s(座 3 呼喊用,带 pan) | -| `howl_wolf` / `howl_bear` | SA3 | 生成呼啸 0.8s / 低吼 1.0s(带 pan,后半平面 ×0.6) | -| `loop_wolf` / `loop_bear` | SA3 | 移动循环:狼碎步+低呜 / 熊重步+喘息(4s 无缝循环源,游戏层 loop,pan+距离档音量) | -| `npc__` ×24 | TTS | bot 喊话(voice 管线生成、落盘 sfx/,峰值归一 0.89 沿用 voice 策略;游戏层 vol 0.55,§8 R7) | +开局空位 bot 补至 max(3, 人类数)。座 0–2 角色阿岩/小筝/老周;座 3 呼喊=哨音。 -### 10.3 voice(旁白/系统音) +## 4. 协议(实现契约,字段不变) -60 句 ×2 性别(male=Uncle_Fu / female=Serena,全表同一 instruct 常量/性别),分组与全文见 [23-hunt-voice-ids.md](23-hunt-voice-ids.md)。数字全分档整句:耐久 4 档 / 波次 3 档 + 5 整句 / 在场猎人 2 句 / 击杀 3 档 / 威胁 3 档 / 评级 4 整句。键盘变体 11 对(V 组,`gesture.inputMode()` 选播)。 +传输:`GET /ws/hunt`,Node 原生 upgrade + 手写帧,无 npm。mask、opcode 0/1/8/9/10,单条 ≤4KB,15s ping / 45s 死,20 消息/s,snap 可丢。 -规模核算:voice 60×2×2fmt ≈ 12MB + npc 24×2 ≈ 2MB + key 变体 11×2×2 ≈ 2MB + sfx 13×2 ≈ 1MB + bgm 2×2 ≈ 6MB ≈ **23MB < 30MB 预算** ✓。 +客户端→服务器:`join`(ver/room/name/teach/resume) / `act`(attack|callout|skill|start + dir + seq) / `leave`。新手客户端不发 skill,直到阶段 4。 -## 11. 向导层接入(engine/guide.js,不新增 earcon 词表) +服务器→客户端:`joined` / `ev`(spawn kill miss skill breach wave waveclear callout botline roster end) / `snap` 2Hz / `err`。 -| 状态 | guide.state() | 时点 | -|---|---|---| -| TITLE / MATCHING / LOBBY / MENU | `menu` | 进态即播 | -| TEACH / WAVE | `battle` | 开局/波开始 | -| END | `settle` | 结算进入 | -| danger | `danger` | 耐久首次 ≤2(每局 1 次,配 danger 震 + danger_low) | +战斗 tick 100ms,eta 递减。客户端怪表=ev+snap,eta 偏差 >0.5s 校正。渲染:eta>6 静默;≤6 取最近 3 只 loop。 -- turn cue 不使用(无回合制);看门狗:WAVE/LOBBY 无操作 [10s, 25s] → `gd_idle` / null(第 2 级纯 nudge);TEACH 每步 [8s] 纯 nudge(教学句不重播)。玩家任意手势复位;guide=off 全静零定时器(既定契约)。 -- 纠错:无效手势 error 震 + `gd_invalid`(onceKey 同局 1 次,之后仅震)。 -- 无卡关三级提示(本作无"卡住"形态:压力来自怪物而非谜题)。 +断线:30s 愣在原地;resume 回槽;否则人类≥3 移除槽,否则转 bot。房灭 resume → room_gone。 -## 12. 存档与排行榜(进度 API) +## 5. Bot + +500ms tick。方向压力 Σ(1/eta)×防区 1.5;拟人:eta≤2.0+rng×1.0 且 80% 空转。旋风斩 eta≤2.5 且 ≥3 只且 60%(阶段 4 才与真人技能对等)。呼救防区压力≥3、20s 节流。botline 10s 窗 ≤3 条;help_call/ack **永不超时丢弃**。满编真人房 2.5s 后邻座角色代答 help_ack。杀熊/三连杀可由该座角色播 kill/skill 句(真人扮演座位)。 + +## 6. 拥挤控制 R1–R8 + +| # | 规则 | +|---|---| +| R1 | voice 同刻 1 句;警报可顶状态链,教学/结算不可顶 | +| R2 | bot 喊话 sfx 同刻 ≤1,队列深 1;旁白时氛围句延迟 ≤2s 可丢,呼救不丢 | +| R3 | loop ≤3 路,游戏层 panner | +| R4 | 定位 one-shot ≤2,生成潮队列深 2 | +| R5 | 非语音信息 <1s | +| R6 | 威胁只报内圈数量档,永不逐方位枚举 | +| R7 | 喊话 vol 0.55 不抢 duck | +| R8 | 最坏 7 声流;频带分离;峰值不丢警报 | + +后半平面:pan=sin(az),且 90°" }, + "best": { "grade": "A", "kills": 14, "durLeft": 8 }, "runs": 3, "totalKills": 31, "tutDone": true } ``` -- **写入时机**:终局结算(每局一次,胜利/失守都写 runs/totalKills;`best` 只被胜利局按 评级 > 营地余耐久 > 击杀 的序更新)。客户端在收到 `ev end` 后自行 PUT(Bearer = 档案 token;离线只写 localStorage `soundgame.progress.hunt`)。00 §4 约定遵守:data 自由格式 + **必带 summary**(大厅卡片直接显示)。 -- **summary 生成规则**:`最佳评级{grade} · 击杀{kills} · 营地余{durLeft} · 共{runs}局`;无 best(全败)时 `共{runs}局 · 还没守到天亮过`。 -- **排行榜形态**:本切片无跨玩家排行 API——"排行榜"= 个人最佳桶 + 大厅卡片 summary(00 §4.3 既有机制)。跨玩家榜列 roadmap。 -- 读取:TITLE 有 best 时上滑播最佳段(评级+击杀档);tutDone 控制是否进教学房(§5)。 +胜利才按 评级>余耐久>击杀 更新 best。大厅卡片直接显示 summary。 -## 13. playtest 断言(摘要;全表住 13 号文档 §24) +## 9. 设计裁决(仍有效) -- **关键路径**:遮罩解锁 → TITLE → 快速匹配(首局教学房 T0-T4 逐步)→ LOBBY → 开局 bot 补位(roster 断言 3 猎人)→ 5 波(spawn/kill/breach/wave 事件序列与 §3.4 表逐波核对)→ 终局结算链(grade 与 dur 一致)→ PUT progress(summary 命中生成规则)→ 再来一局。 -- **边界**:断线 30s 转 bot / 宽限内 resume 回槽无缝 / 旋风斩冷却与冷却中双击=重听 / 呼喊→bot 补位(10s 内 bot act 方向落入呼喊者防区)/ 听觉裁剪(eta>6 静默、loop ≤3、threat 只计内圈)/ 攻击冷却服务端拒绝 / snap 校正 / 菜单不暂停照扣耐久。 -- **失败即不通过**:评级与 dur 不符(归因假话);服务器接受违规 act;ev 与 snap 矛盾不收敛;bot 喊话压过旁白(R1/R2 破);loop >3 路;教学狼扣耐久。 -- 全表与全局通用断言见 [13-playtest-criteria.md](13-playtest-criteria.md) §24。 - -## 14. 自检清单 - -| # | 硬约束 | 自检 | -|---|---|---| -| 1 | FTUE 首输入 <60s / 首正反馈 ≤3min | T1 首滑 ≈t+20s;首杀 ≈t+40s ✓(§5) | -| 2 | 学习期零惩罚 | 教学波突入不扣耐久;做错重教 ≤2 次自动放过;挥空仅 error 震 ✓ | -| 3 | 手势宪法 | 上滑=状态、下滑=菜单、双击=重听(冷却中落地)保留;E1-E4 登记且教学讲清 ✓(§2.5) | -| 4 | 向导层 | 签名 4 态不新增词表;看门狗 [10/25]s 二级纯 nudge;纠错 onceKey;guide=off 全静 ✓(§11) | -| 5 | 旁白规范 | 60 句 ≤ 预算;整句无拼接;数字全分档;同 instruct 常量;无语速词;"重新听"规范写法 ✓(台词表头注) | -| 6 | 拥挤控制 | R1-R8 可断言;最坏并发 ≤7 声流;警报零丢失口径 ✓(§8) | -| 7 | 方位降维 | 8 方位 × 3 距离档,pan 七值 + 后半平面 ×0.6;无连续角度、无逐方位语音枚举 ✓(§8.2/§8.3) | -| 8 | 协议自洽 | C→S 3 条、S→C 4 类字段表全;服务端权威+快照兜底;断线/重连/房灭路径闭合 ✓(§6) | -| 9 | 引擎边界 | 引擎改动仅 opts.pan 一处且后向兼容;loop 走游戏层先例;30MB 预算内 ✓(§9/§10.3) | -| 10 | 服务器约束 | 零依赖(手写 WS 帧);数据落盘/REST 零改动;单实例约束不恶化(房间全内存) ✓(§6.1) | -| 11 | P1 视障 | 双击免费重听威胁;上滑一键复述;方位由声像承载且规则教学讲明;无倒计时焦虑(波次自然出清) ✓ | -| 12 | P3 通勤 | 单手免视;菜单不暂停但 3 项极简;局 5 分钟随玩随放 ✓ | -| 13 | 失败归因真话 | 评级=dur 服务端直发;结算击杀档=个人真账;挥空/失守有真话反馈(atk_swish/end_lose) ✓ | -| 14 | seed 可复现 | 房间 seed + mulberry32 派生;刷怪剧本/bot 反应带全走 seed;服务器单测可复现 ✓(§3.5/§7.2) | - -## 15. 设计裁决与矛盾修正记录(实施前必读) - -对任务书核心案的细化/修正,逐条登记(核心形态零改动): - -1. **双击矛盾修正**:任务书"双击=旋风斩,8 秒冷却;**无冷却时**双击=重听威胁播报"自相矛盾(无冷却=就绪=放旋风斩)。裁定为:**旋风斩就绪时双击=放出;冷却中双击=重听威胁播报**——既保留技能,又让宪法"双击=重听"在全时段有定义(豁免 E2 登记)。 -2. **补出击冷却 2.5s/人**:任务书未给出击冷却。无冷却则单人滑动吞吐无上限,协作失去必要性(§1.6 风险③)。2.5s 使单人峰值约 1 杀/窗,三人局才有分工意义。数值写在 §3.2,服务端裁决。 -3. **防区=软分工模型**:任务书"玩家位于营地中心"与"bot 朝你侧补位"并存但无站位定义。裁定为**软防区**(§3.3):不设站位硬锁(任何方向都能打),防区只做 bot 偏置、喊话 pan 来源、刷怪均衡——保留"补位"语义且不引入连续几何。 -4. **四向滑 → 90° 弧映射**:任务书"四向滑映射到最近罗盘方位"在 8 方位刷怪下不可玩(斜向怪永远打不到)。裁定:滑向映射 ±45° 弧,击杀弧内内圈 eta 最小者(§2.1/§3.2);协议 act.dir 仍用 8 罗盘枚举(bot 用满 8 向),客户端只发四基向。 -5. **房码 UX 范围**:音频录入 4 位房码需数字语音资产(10 条)会击穿 60 句预算。裁定:协议 join.room 完整支持房码(服务端实现+单测),**客户端 v1 只做快速匹配**;音频录码 UX 列 roadmap(届时加数字整句资产)。 -6. **第 4 座无角色声音**:bot 角色预算 3×8=24(任务书定死),而 4 人局第 4 座真人的呼喊无角色可用。裁定:座 3 呼喊=`call_whistle` 哨音(带 pan,<1s,R5 合规),bot 照常响应补位;哨音本身是有效求援信号,明示于 §3.3。 -7. **bot 喊话落盘 sfx/(异例)**:任务书两处表述有张力——拥挤控制章"bot 喊话走 sfx 层空间化播放"与引擎章"voice 层同样支持 pan(bot 喊话用)"。裁定:喊话必须与旁白并发(R1/R2 的核心),voice 层单流做不到 → 走 sfx 层,引擎按 id 前缀分发故资产落 `audio/sfx/npc_*`(voice 管线生成不变);voice 层 opts.pan 能力同步提供但**本作不使用**。旁白 narrator 与 bot 同 speaker 的区分度风险(阿岩×male 旁白、小筝×female 旁白)按 abyss 先例标注"中",缓解=空间 pan + instruct 差异 + 语境(bot 只讲 1-2s 短喊话)。 -8. **巨兽 -2**:任务书"突入=耐久-1"为默认口径;第 3 波起加入暴熊(-2)作为重威胁,给击杀优先级一个明确信号(重 loop/重倒地音可辨),数值与播报(breach_heavy)配套,不改核心形态。 -9. **失守与入榜**:任务书"评级 S~C"隐含必完 5 波。裁定:耐久波内归零=失守终局(end_lose,无评级、不写 best,runs/totalKills 照记);胜利局才按 §3.6 评级写入 best——排行榜不被败局污染,失守有真话结算。 -10. **bot 强度调参(§7.2 数值修订,协议字段不变)**:初版严格按 §7.2 公式(反应带 eta≤2.2+rng×1.2、无空转、旋风斩 eta≤3≥3 必放)实测**零泄漏**——多种子仿真 3 bot 水准局余耐久恒 10(S),甚至真人全程挂机 2 bot 独守也恒 10,§3.4"3 bot 水准余 6-9"与"协作必要性"(§1.6 风险③)落空。根因是吞吐超配:3 猎人 1.2 杀/s vs 峰值到达 0.67 杀/s,冷却 2.5s 不构成约束。裁定修订为:反应带 **eta ≤ 2.0+rng×1.0**;出手决策加 **80% 空转概率**(500ms tick 内愣神,等效出手周期 ~4.5s);旋风斩 **eta≤2.5 ≥3 只且 60% 概率想起**。调后 24 种子扫描(`web/server/test/hunt-balance-scan.mjs`):挂机局 17/24 失守或余 ≤3(6 场失守,mean 3.3,C/失守口径 ✓);3 bot 水准局 17/24 余 6-9(mean 7.5,A/B 为主 ✓);真人最优仍 23/24 满耐久 S("比 bot 强"空间保留 ✓)。bot"不失误"语义保留(只打有目标的弧),弱化的是**反应速度与出手频率**(拟人迟钝),不是命中率。 - -## 16. v2 修订记录(2026-09-19,10 人并发模拟迭代;依据与数据见 research/hunt-sim/) - -> 迭代章程 research/hunt-sim/00-plan.md;v1 诊断 analysis/01-04;裁定 synthesis.md(S1-S9); -> 复测对比 metrics-v2.md(去留 6/10→10/10,弧错 22%→11%,N/S 可命中 63/69%→75/81%)。 - -1. **E3 修订(S1)**:WAVE 战斗态下滑**恒出击**(弧内无怪=空挥真话 atk_swish); - 模态菜单入口收窄到**波间休整窗**(waveclear 后 8s)与 LOBBY/END。原「无目标下滑= - 菜单」的语境折中废止——v1 实测多人战斗中误触菜单(最多单局 14 次),弹菜单+语音 - 播报双重打断是退游死因之一。上滑无目标=状态链维持不变。 -2. **冷却反馈(S2)**:冷却内被拦只在**每个冷却窗首次**给 error 震(连拦静默); - 本窗被拦过则就绪时刻补一记 `beat` 轻震(无语音占用)。v1 实测全场 666 次乱滑连震。 -3. **kill_band0 下架(S3)**:个人击杀 0 时状态链/结算链**跳过**「你还没开张」 - (沉默优于羞辱;正向改写句列 TTS 批次)。0 杀玩家的去留反转(v2 P08:0杀→12杀) - 是本项最直接证据。 -4. **看门狗重定义(S4)**:WAVE 内本波有出手/击杀 → 一级语音永不触发;一级降纯震; - 语音闲置每局 ≤1 次;重教句只留在教学局。判定线=「耳朵在场」(等待是合法玩法)。 -5. **协作人声包(S5)**:①真人「扮演座位角色」(§3.3 既有设定)正式接线——杀熊/ - 三连杀/旋风斩清 ≥3 时由该座角色播 kill/skill 句(服务端 creditKill/adjudicateSkill - 放开 slot.bot 限制);开局 greet 覆盖全部有角色座位;②满编真人房呼喊无 bot 可应 → - 2.5s 后由邻座真人角色代答 help_ack(服务端 pendingLines 兜底,v3 若加真人应答手势 - 则替换);③help_call/help_ack 客户端**永不超时丢弃**(氛围句才受 R2 ≤2s 上限); - ④喊话全局预算 10s 窗 2→**3** 条。 -6. **后半平面第二线索(S6)**:身后 loop 在 ×0.6 响度之上叠加 **750Hz 低通**(音色 - 发闷 ≠ 响度发闷)。N/S 轴从单线索变双线索,模拟实测可命中 63/69%→75/81%,新手 - 自述「破案」。§8.3 的 pan 红线不变(方位仍由声像承载)。 -7. **难度与评级重构(S7)**:①波次量按座位缩放 `waveCounts`:4 人房 ×4/3(3 人房 - 原值,§3.4 表为 3 人基准);②第 3-5 波插入**齐压拍** `alignPincers`:1-2 组跨座位 - 同种类怪重定时到同秒进内圈(数量与 eta 不变、种子确定性保持;齐压在 startWave - 层应用,buildWaveScript 纯函数不动);③**bot 让位窗**:怪进内圈头 2s,bot 不与 - 该防区人类座位抢(`mobYielded`,只约束 bot——对真人不可见不拦截,避免「挥空 - 真话说谎」的暗墙;真人房的参与真空 v3 用分层匹配解决);④**评级双轴** - `gradeForEx`:S = 耐久 ≥9 **且** ≥2 名猎人各 ≥6 个人击杀(单人带飞局上限 A)。 -8. **教学跳过保护(S8)**:作答步(T1/T2/T3)内下滑=答错处理,不再跳过教学; - 跳过仅在 T0/T4/完成等待态(v1 实测 P04 教学局手滑跳过、记恨三局)。 -9. **状态链按变化播(S9)**:耐久档/波次档/猎人数/击杀档与上次播报一致的分段跳过; - 全部无变化=仅 confirm 轻震(「营地固若金汤」复读投诉归零)。 - -**v3 头号遗留(未修,如实登记)**:营地零损耗未破(v2 复测 18/20 局余 10)—— -4 人房 ×4/3 + 齐压拍不足以穿透专家房吞吐(单人最多 34/51 杀)。下一档参数与 -分层匹配/保底猎物方案见 research/hunt-sim/report.md §5。 +出击必有 2.5s 冷却否则单人吞全场。四向必须 ±45° 弧否则斜向怪打不到。房码协议保留、客户端 v1 只 quick。座 3 哨音。喊话走 sfx 才能与旁白并发。失守不入榜。bot 强度按空转校准到 3 bot 余耐久 6–9。WAVE 永不弹菜单。0 杀不播羞辱句。 diff --git a/docs/design/24-rhythm-drummer.md b/docs/design/24-rhythm-drummer.md index 56d1d913..903c6112 100644 --- a/docs/design/24-rhythm-drummer.md +++ b/docs/design/24-rhythm-drummer.md @@ -1,185 +1,172 @@ -# 24 · 《节拍行者》鼓手模式重构设计(rhythm / Drummer Mode) +# 24 · 《节拍行者》鼓手模式(26 号对齐) -> 用户裁定:现有模式「不错,保留」——跟随领奏的合拍玩法核心、S~C 评级、曲目解锁制、 -> BPM 精确性延续;整体重构为**屏幕分区打击乐**:屏幕划分区域,每区一种打击乐音效, -> 玩家作为乐队鼓手与无鼓伴奏音乐合拍。本文取代 [03-rhythm-rebuild.md](03-rhythm-rebuild.md) -> 的全部施工依据(03 号文档仅作历史档案,文末有取代指针;其 rhythm 判定好评口径 -> 「PERFECT ±0.15 / GOOD ±0.35」经 live 校准,本文沿用)。 -> -> 语音台词全表在 [24-rhythm-voice-ids.md](24-rhythm-voice-ids.md)。断言清单在 §12。 +> **给开发者的实现要点摘要** +> 1. **玩法保留**:3×2 分区鼓组、跟随领奏、PERFECT ±0.15 / GOOD ±0.35、S~C 评级、B 级解锁下一首、无鼓伴奏、BPM 网格。本文取代 03 号作为施工依据。语音表仍见 [24-rhythm-voice-ids.md](24-rhythm-voice-ids.md)。 +> 2. **演奏期 = Floor `ACTION`**:不起等待声;点按即敲。Miss = 不按「敲对」计、该音符无领奏应答感 + 闷镲/回执 + 真话归因。禁止只震。 +> 3. **新手期无双击手势**(`doubleTapWindow: 0`)。同区两记连续点按是两下敲击,不是「双击手势」。菜单禁止双击确认。双击手势直至阶段 3 才存在,且不绑音符类型。 +> 4. 分区点按是核心;选曲/结算/暂停说明的**帮助=长按**(上半屏点按是鼓,上滑易混)。暂停恢复必须 **count-in**。 +> 5. 第一句就摸鼓或跟打;第一次成功 ≤10s。 --- -## 0. 可行性勘察决策(阶段零结论) +## 0. 设计单(26 号附录 C) -### 0.1 引擎手势事件是否携带坐标? +### 0.1 一句话核心动作 -**不携带。** `web/engine/gesture.js` 的 `onGesture(g)` 只传手势名字符串, -触摸坐标只在引擎内部用于滑动/点按判定,回调时丢弃。 +**屏幕是一面 3×2 鼓组。领奏敲一小节,你在下一小节同一拍点敲同一面鼓。** -### 0.2 两条路线的权衡 +### 0.2 玩家可感知的状态(3 个) -| | (a) 引擎扩展:第二参数透传坐标 | (b) 游戏层自建 touch 监听 | -|---|---|---| -| 改动面 | gesture.js 每个 onGesture 调用点补 `{x,y}` 第二参;docs/web-engine-api.md 同步 | 游戏层加 touchstart 记录坐标 | -| 既有游戏影响 | **零**——全部 15 款游戏的回调都是单参 `(g) =>`,多余实参被忽略,手势分类/震动/时序逐字节不变(同 opts.pan / immediateTap / tapcancel 先例) | 零(不碰引擎) | -| 键盘通道坐标 | **免费**:空格虚拟指针本就走 `handleDown(keyCenter)`,天然拿到元素中心坐标 | 无法区分键盘 tap 与残留触摸坐标,需自行加时效启发式(脏) | -| 保持的引擎福利 | haptics 自动震动、多指守卫、tapcancel、长按快进 window 事件、双击状态机、键盘三通道全部保留 | 全部丢失,需在游戏层重写(双击窗/tapcancel/快进事件) | -| 代价 | 全量回归:15 游戏 playtest + guide.test + engine-voice + engine-input + docs 同步(铁律) | 回归只需 rhythm 自身,但收获一个与引擎并行的第二输入状态机(长期维护债) | +| 状态 | Floor | 听感 | 第一次教 | +|---|---|---|---| +| 演奏 / 教学跟打 | `ACTION` | 无鼓伴奏 + 鼓声音色(声像+音高) | 「敲屏幕。摸到哪是哪。」 | +| 选曲 | `YOUR_TURN` | `bgm/menu` + 等待声 | 「左右滑选歌。单击开始。」 | +| 结算 | `YOUR_TURN` | menu pad + 评级句 | 「单击,再来一遍。」 | -### 0.3 决策:路线 (a),引擎后向兼容扩展 +暂停:音乐停,不叠 menu pad。恢复先 count-in,再进窗。 -- `attachGestures` 的全部 `onGesture(...)` 调用补第二实参 `{x, y}`(视口像素 - clientX/clientY 语义):tap 类事件(含 immediateTap 序曲 tap、双击改判、tapcancel) - 与 longpress/longpressend 传**按下点**;四向滑动传**滑动起点**(出手瞄准位置); - 键盘通道传虚拟指针坐标(`keyCenter()`,方向键滑动同样补)。 -- 缺省行为逐字节不变:不加 config 开关、不改任何判定阈值与震动;回调签名扩为 - `(g, info?: {x, y})`,单参回调零感知。 -- 回归义务(已执行,见 research/rhythm-drummer/round0/engine-regression.md): - 15 游戏 playtest + web/harness/guide.test.mjs + engine-voice.test.mjs + - engine-input.test.mjs 全绿,docs/web-engine-api.md §gesture 同步。 +### 0.3 新手手势与解锁 -### 0.4 打击乐音色:程序合成 +| 手势 | 演奏 ACTION | 选曲 / 结算 | 解锁 | +|---|---|---|---| +| 单击(含分区坐标) | 敲该区 | 开始 / 再来 | 阶段 0;演奏期 `immediateTap` | +| 左 / 右滑 | 不作为音符(可 `invalid` 或忽略为非敲) | 切曲 / 右滑下一首 | 阶段 0 | +| 长按 | 死输入:`invalid` 或闷镲,不判定 | **帮助一句** | 阶段 1 | +| 下滑 | 阶段 2 前=`invalid`「现在只要敲。」;阶段 2 后无条件暂停菜单 | 菜单 | 阶段 2(第一局结束) | +| 双击手势 | **不存在**;300ms 内两下=两次 tap | 当单击 | 阶段 3 仅重听选曲说明/评级,禁止绑重开 | -六区音色 + accent 全部 numpy 程序合成(`tools/gen_rhythm_syn.py`, -gen_cipher_syn.py 同款工艺):可精确控制起音/衰减(盲玩靠起音对拍)、 -零授权问题、零网络延迟、与 BGM 调性关系无关(打击乐无调性)。 +键盘:Digit1–6 = 六区,按下即发。空格落 BC(家区)。选曲态数字键无效。 -### 0.5 BGM:无鼓伴奏是硬需求 +### 0.4 状态 × 手势矩阵(新手期) -玩家就是鼓手——BGM 含鼓则叠打混乱。Mureka instrumental 生成时 prompt -显式 `no drums, no percussion`,`tools/bpm_precise.py` 梳状扫描实测 BPM -(节奏类硬性要求,误差 >0.5 则 atempo 拉伸,R3 track90 先例), -无缝循环后处理沿用批次 1-4 工艺(强 onset 起点 → 整数小节裁剪 → -首尾 3s acrossfade → loudnorm I=-20 → ogg q3 + m4a 128k)。 +| 状态 \ 手势 | 分区单击 | 左右滑 | 长按 | 下滑(未解锁菜单) | 双击 | 连点 | +|---|---|---|---|---|---|---| +| intro/tut 讲解 SPEAKING | 跳句 或 摸鼓(T0 探索) | 打断 | 帮助 | `invalid`+解释 | 当单击 | 400ms `busy`(讲解);T0 每下都出声 | +| 教学跟打 ACTION | 敲区判定 | `invalid` | 死输入 | `invalid` | 当两次单击 | 两下两击 | +| 演奏 ACTION | 敲区判定 | 非音符 | 死输入 | 未解锁=`invalid` | 两次单击 | 合法连击 | +| 选曲 | 开始当前曲 | 切曲报名 | 帮助 | 阶段 2 菜单 | 当单击 | `busy` | +| 暂停菜单 | 确认当前项 | 左右切换 | 重听说明 | 关闭菜单 | 当单击 | `busy` | +| 结算 | 同曲再来 | 右=下一首 | 重听评级 | 菜单 | 当单击 | 只处理第一下 | + +### 0.5 等待点 + +| id | 期待 | `_full` | `_short` | 静默 | L2 / L3 | +|---|---|---|---|---|---| +| `tut_probe` | 任意敲 ≥1 | 「随便摸,听鼓在哪。」 | 「摸一下。」 | 8s 自动进 T1 | — | +| `tut_kick` | 应答敲 BC | 「家区在下中。点一下。」 | 「点下中。」 | Miss | 降速重演 → 演示 | +| `tut_sides` | BL 然后 BR | 「左下,再右下。」 | 「左右下。」 | Miss | 同上 | +| `select` | 单击 | 「单击,开始。」 | 「单击。」 | 18s | — | +| `settle` | 单击 | 「单击,再来一遍。」 | 「单击。」 | 20s | — | + +演奏 ACTION 不挂等待声、应答小节内零语音。 + +### 0.6 首 3 分钟逐秒脚本 + +| 时间 | 游戏说 / 播 | 期待 | 没做 | 做错 | +|---|---|---|---|---| +| 0~3s | 「点一下屏幕。」或直接「随便摸。」 | 任意敲 | 8s 自动进 T1 | — | +| 3~10s | 区音色 + 区名(第一次成功)→ T1 数拍 → 领奏 BC | 应答敲家区 | 「这拍没接。」 | 错区:报名实际区 +「敲家区。」 | +| 10~30s | T2 左右下排 | 跟打 | 短句;2 败降速 | 真话归因 | +| 30~60s | T3 上排三音或进曲目一句 1 | 跟打 | 接管 | Miss 无「假成功」乐器归属 | +| 60~180s | 曲目一前句(无双击手势、反拍仅加花) | 敲对区 | 连 2 Miss 才 `gd_miss` 一次 | 早/晚/没接/错鼓分档 | + +### 0.7 失败表 + +| 类型 | 判定 | 归因 | 回到 | 新手代价 | +|---|---|---|---|---| +| 没出手 | expected+GOOD+0.10s 无应答 | 「这拍没接。」 | 教学重来;正片歌继续 | 不中断 | +| 早 / 晚 | 同区超 Perfect 但仍在 Good | 教学事后「早了/晚了」;正片 Good 出声 | 教学重来 | 无 | +| 错鼓 | 异分区音符半窗内被敲 | 教学:区名+「敲错鼓了」;正片:错区音色本身(配额不叠语音) | 同上 | combo 清零 | +| 教学 2 败 | 同一步 | 「我放慢一些。」×0.75 | 该步 | 再败跳过不卡死 | +| 长按演奏期 | 按下≥450ms | 闷镲 + `invalid` | 不归属 | 无 | + +### 0.8 台词类别 + +- `critical`:开场动手句 ≤3s。 +- `content`:曲名、评级、解锁、`tut_map` 拆句后非关键段。 +- `hint`:`gd_answer`、`gd_count`、区名、短教学。 +- `feedback`:`gd_miss_none` / early / late / `retry_wrong`。 + +应答小节内:hint/feedback 播不出则丢。曲尾 3s 内不插 `gd_miss`。 + +### 0.9 断点复述 + +暂停:泵停、BGM 记 offset。恢复:cue.expected / `_bgmT0` 平移 = 暂停时长 + **count-in 小节**;guide full 先播 `gd_count`(每曲 1 次)。退出写进度 schema 1。 + +### 0.10 测试 + +§12 断言清单(已按新手无双击、菜单单击确认、ACTION、count-in 修订)。乱点:无声操作 0;被动原句重复 0。 --- -## 1. 概述与继承/舍弃清单 +## 1. 可行性决策(保留) -**一句话玩法**:屏幕是一面 3列×2行 的鼓组。系统(领奏)在无鼓伴奏上敲一小节 -节奏型,玩家在下一小节的相同拍点敲相同的鼓。曲子从 90 BPM 打到 120 BPM, -拿 B 级以上解锁下一首。 - -### 1.1 继承自旧作(用户裁定「保留」的部分) - -| 项 | 说明 | -|---|---| -| 跟随领奏 call & response 核心循环 | 领奏 1 小节 → 应答 1 小节,逐乐句推进 | -| 时值判定口径 | PERFECT ±0.15s / GOOD ±0.35s(live round1 §13 校准值,吸收蓝牙延迟) | -| 计分与评级 | Perfect=2/Good=1/Miss=0,rate = 得分/(2×总音符),S≥0.95 / A≥0.85 / B≥0.70 / C 其余;不掉血不中断,歌照唱完 | -| 曲目解锁制 | 曲目 n 拿到 B 级 → 解锁 n+1;localStorage + 已登录 PUT 同步 | -| BPM 精确性 | 谱面/判定全部锚定 BGM 实测 BPM 网格(bpm_precise 实测 + atempo 修正) | -| 工程质量遗产 | AudioContext 预排程泵(40ms + LOOKAHEAD 0.6s)、BGM 手动 `src.start(t0)` 对网格、模态菜单断点续播、`_speakGuarded` 看门狗语音链、结算后 menu pad 看门狗、旁白单击跳过/长按快进豁免、guide 层全量接入(状态签名/gd_answer/gd_miss 归因/教学 DDA 降速/count-in 恢复) | - -### 1.2 舍弃/替换 - -| 旧 | 新 | 理由 | -|---|---|---| -| 手势→乐器映射(tap=kick、left=闭镲…6 手势 6 音色) | 屏幕分区→乐器(6 区 6 音色,tap 即敲击) | 用户裁定的新核心 | -| 滑动/双击作为音符类型(SWIPE_DELAY 0.15 / DOUBLE_DELAY 0.20 补偿、双击序曲撤销) | **全部音符 = 分区点按**,immediateTap 按下即判,零识别补偿 | 打击乐每次出手都是「敲一下」,滑动从音符类型降级为纯导航;双击=同区连击两下(两声都合法),**不再需要双击序曲撤销账簿** | -| BGM 含鼓组(track90/105/120 有鼓) | 无鼓伴奏 | 玩家是鼓手 | -| 下滑呼菜单的「down 音符临近」折中(live P0 加窗/复活窗一整套) | 下滑**无条件**呼菜单 | 音符全是点按,下滑永不与音符冲突;序曲 tap 仍出 ghost 音但判定时由 tapcancel 撤销 | -| 教学 T1-T3(手势记忆) | 分区心智模型教学(摸鼓认区 → 跟打) | 「摸到哪是哪」的建立是设计核心(§6) | -| 旧语音表 31 句 | 新表 41 句(+沿用向导 8 句 = 49 句双性别,配额见 §8) | 分区教学与区名播报需要新句 | +引擎 `onGesture(g, info?: {x,y})` 后向兼容扩展;键盘 `keyCenter` 免费得坐标。打击乐 numpy 合成。BGM 硬性 `no drums`。回归义务见 `research/rhythm-drummer/round0/`。 --- -## 2. 屏幕分区与音色映射 - -### 2.1 分区布局(3 列 × 2 行,纯听觉可辨是硬约束) +## 2. 屏幕分区与音色(保留) ``` ┌─────────┬─────────┬─────────┐ -│ TL 左上 │ TC 上中 │ TR 右上 │ 上排:清亮/高频 -│ 踩镲 │ 高嗵 │ 吊镲 │ pan: -0.6 / 0 / +0.6 +│ TL 左上 │ TC 上中 │ TR 右上 │ 上排:清亮 pan -0.6 / 0 / +0.6 +│ 踩镲 │ 高嗵 │ 吊镲 │ ├─────────┼─────────┼─────────┤ -│ BL 左下 │ BC 下中 │ BR 右下 │ 下排:低沉/低频 -│ 军鼓 │ 底鼓 │ 低嗵 │ pan: -0.6 / 0 / +0.6 +│ BL 左下 │ BC 下中 │ BR 右下 │ 下排:低沉 +│ 军鼓 │ 底鼓 │ 低嗵 │ └─────────┴─────────┴─────────┘ ``` -**双轴心智模型**(教学的主话术): -- **列 = 声像**:左列在左耳、中列在中央、右列在右耳(opts.pan 引擎已支持)。 -- **行 = 音高**:上排清亮、下排低沉。 -- 在双轴之上,六件乐器音色本身两两不同(镲/嗵/鼓),盲玩家有 - 「声像 + 音高 + 音色」三重冗余线索。 +| 区 | 乐器 | sfx | pan | 合成要点 | +|---|---|---|---|---| +| TL | 闭合踩镲 | `dm_hat` | -0.6 | 6–10kHz 噪声,0.06s | +| TC | 高音嗵 | `dm_tomhi` | 0 | 200→140Hz,0.25s | +| TR | 吊镲 | `dm_crash` | +0.6 | 长尾 ≤1.2s | +| BL | 军鼓 | `dm_snare` | -0.6 | 鼓皮+带通噪声,0.2s | +| BC | 底鼓 | `inst_kick` | 0 | 130→45Hz,0.25s | +| BR | 低音嗵 | `dm_tomlo` | +0.6 | 120→75Hz,0.3s | -### 2.2 分区 ↔ 音色 ↔ 声像总表 +Perfect 叠 `dm_accent`。`zoneOf(x,y)` 以 `#game-root` 为界;缺 payload 落 BC。领奏与玩家同一批 sfx。 -| 区码 | 位置 | 乐器 | sfx id | pan | 合成要点(起音硬、≤0.5s 残响) | -|---|---|---|---|---|---| -| TL | 左上 | 闭合踩镲 | `sfx/dm_hat` | -0.6 | 6-10kHz 高通噪声,0.06s 急衰减 | -| TC | 上中 | 高音嗵 | `sfx/dm_tomhi` | 0 | 200→140Hz 正弦扫频 + 轻皮噪,0.25s | -| TR | 右上 | 吊镲 | `sfx/dm_crash` | +0.6 | 4-12kHz 噪声 + 非谐波泛音列,1.2s 长尾 | -| BL | 左下 | 军鼓 | `sfx/dm_snare` | -0.6 | 200Hz 鼓皮 + 1-4kHz 带通噪声,0.2s | -| BC | 下中 | 底鼓 | `sfx/inst_kick`(沿用旧 id) | 0 | 130→45Hz 正弦扫频 + 触击瞬态,0.25s | -| BR | 右下 | 低音嗵 | `sfx/dm_tomlo` | +0.6 | 120→75Hz 正弦扫频 + 轻皮噪,0.3s | - -- 附加层:`sfx/dm_accent`(Perfect 叠加亮音,程序合成木鱼 1.5kHz 短音)。 -- 分区判定:`zoneOf(x, y)` 以 `#game-root` 的 `getBoundingClientRect()` 为界, - y 上半 = 上排、y 下半 = 下排;x 三等分 = 左/中/右。无布局环境(mock/harness) - 由调用方显式传 payload;payload 缺失时落 BC(中央家区,兜底不炸)。 -- **键盘平权**(游戏层 keydown,引擎键盘通道之外的补充,数字键引擎本就不拦截): - `Digit1/2/3/4/5/6`(及小键盘)= TL/TC/TR/BL/BC/BR,按下即发、零延迟; - 引擎空格 tap 落在元素几何中心 → 判 BC(「家」)。选曲/菜单/结算态数字键不生效 - (那些态沿用手势导航)。 - -### 2.3 领奏音色 = 玩家音色 - -领奏音符与玩家敲击走**同一批 sfx**(同音色同声像)——做对时听到自己在和 -「另一个鼓手」合奏,这是旧作好评的「演奏感」来源,保留。 +**同区连击**:谱面上 `1:BC, 2:BC` 是两记点按。新手 `doubleTapWindow: 0` 时两下都是 `tap`,各自判定。不要再走「双击改判 / 序曲撤销账簿」——只保留 immediateTap 的 `tapcancel`(转滑动/长按时撤销这一下的认领,ghost 音收不回,统计必须回滚且不计 Miss)。 --- -## 3. 玩法循环与状态机 - -### 3.1 循环 +## 3. 循环与状态机(保留) ``` -选曲 → 数拍导入(1 小节底鼓×4,含节拍轻震) → 逐乐句(领奏 1 小节 + 应答 1 小节 [+ 休止]) - → 过门/加花(领奏 1 小节密集加花 + 应答 1 小节) → 终句吊镲 → 结算评级 +选曲 → 数拍(1 小节底鼓×4 + 每拍轻震)→ 逐乐句 → 过门/加花 → 终句吊镲 → 结算 +idle → intro → tut → select → countin → main → end ``` -### 3.2 状态机 - -``` -idle → intro(语音) → tut(分区教学) → select(选曲,二周目起) → countin → main(逐乐句/加花) → end(结算) - ↑_________________| -``` - -- 数拍小节由游戏排程 4 记数拍(BGM 无鼓,数拍只能靠鼓手自己)+ 每拍 [8] 轻震:正片用底鼓 inst_kick×4(guide.test G3 的 _sched 锚点,13 号冻结文档不可改),教学步用木鱼 click(dm_accent,guide.test G2 要求教学 _sched 里 kick=领奏音)。 -- 每曲第一个应答窗口前 1 小节落 `gd_answer` + turn earcon(旧作 G1 修正沿用)。 -- 模态菜单(下滑呼出):单击循环 [继续演奏 / 回大厅]、双击确认、上滑帮助、 - 下滑关闭;暂停真冻结(泵停、BGM 断点记 offset),恢复时全网格平移 + - 4 拍 count-in(guide full 档先播 `gd_count`,每局限 1 次,round3 G3 全套沿用)。 +- 正片数拍:`inst_kick`×4(guide.test G3 `_sched` 锚点保留)。 +- 教学数拍:木鱼 `dm_accent`(G2:教学 `_sched` 里 kick=领奏音)。 +- 每曲第一个应答窗前 1 小节:`gd_answer` + `turn`。 +- **模态菜单**(阶段 2):下滑呼出;**左右滑切换、单击确认**;长按=重听菜单说明;再下滑关闭。暂停真冻结。恢复全网格平移 + **4 拍 count-in**。 --- -## 4. 谱面与难度曲线 +## 4. 谱面与难度(保留;新手无「双击手势」需求) -### 4.1 记号 +记号 `拍:区码`。加花尾音恒 TR。 -`拍:区码`,1 小节 4 拍,`x.5` = 八分反拍。乐句 = 领奏 1 小节 + 应答 1 小节; -加花 = 特殊乐句(fill 标记),领奏为八分音符密集型,最后一音恒为 TR 吊镲收束。 - -### 4.2 曲目一《启程》90 BPM(教学曲,8 句 + 终曲加花,≈65s) +### 4.1 曲目一《启程》90 BPM(8 句 + 终曲加花,≈65s) | 句 | 领奏节奏型 | 音符数 | |---|---|---| | 1 | `1:BC, 3:BC` | 2 | | 2 | `1:BL, 3:BR` | 2 | -| 3 | `1:BC, 2:BC, 3:BL` | 3(双击底鼓引入同区连击) | +| 3 | `1:BC, 2:BC, 3:BL` | 3(同区两记点按,不是双击手势) | | 4 | `1:BL, 2:BR, 3:BC` | 3 | | 5 | `1:BC, 2:BL, 3:BC, 4:BR` | 4 | -| 6 | `1:TL, 3:BL, 4:BC` | 3(上排首次进正片) | +| 6 | `1:TL, 3:BL, 4:BC` | 3 | | 7 | `1:BC, 2:TL, 3:BC, 4:TR` | 4 | | 8 | `1:BL, 2:BR, 3:TL, 4:BC` | 4 | -| 终曲加花 | `1:BC, 1.5:BC, 2:BC, 2.5:BL, 3:BL, 3.5:BC, 4:TR` | 7(全曲唯一反拍,仅限加花) | +| 终曲加花 | `1:BC, 1.5:BC, 2:BC, 2.5:BL, 3:BL, 3.5:BC, 4:TR` | 7 | -前 4 句应答后各休 1 小节,其余不休。 +前 4 句应答后各休 1 小节。曲目一普通句无反拍;反拍仅加花。 -### 4.3 曲目二《追风》105 BPM(10 句 + 中途加花 + 终曲加花,≈80s) +### 4.2 曲目二《追风》105 BPM | 句 | 领奏节奏型 | 音符数 | |---|---|---| @@ -196,9 +183,9 @@ idle → intro(语音) → tut(分区教学) → select(选曲,二周目起) | 10 | `1:BL, 2:BR, 3:TL, 4:BC` | 4 | | 终曲加花 | `1:BC, 1.5:BL, 2:BC, 2.5:BL, 3:BC, 3.5:BR, 4:TR` | 7 | -前 2 句应答后各休 1 小节;第 5 句与中途加花之间不休。 +前 2 句应答后各休 1 小节。 -### 4.4 曲目三《星爆》120 BPM(10 句 + 中途加花 + 终曲加花,≈75s) +### 4.3 曲目三《星爆》120 BPM | 句 | 领奏节奏型 | 音符数 | |---|---|---| @@ -210,207 +197,114 @@ idle → intro(语音) → tut(分区教学) → select(选曲,二周目起) | 6 | `1:BC, 2:TL, 2.5:BL, 3:BC, 4:TR` | 5 | | 中途加花 | `1:TL, 1.5:TL, 2:BL, 2.5:BL, 3:BC, 3.5:BC, 4:TR` | 7 | | 7 | `1:BR, 2:BC, 3:BL, 3.5:BR, 4:TL` | 5 | -| 8 | `1:BC, 1.5:BC, 2.5:BL, 3:BC, 3.5:TL, 4:TR` | 6(高潮句) | +| 8 | `1:BC, 1.5:BC, 2.5:BL, 3:BC, 3.5:TL, 4:TR` | 6 | | 9 | `1:BL, 2:BC, 2.5:BR, 3:TL, 4:BL` | 5 | | 10 | `1:BC, 2:BR, 3:BC, 3.5:TL, 4:BL` | 5 | | 终曲加花 | `1:BC, 1.5:BL, 2:BC, 2.5:BL, 3:BC, 3.5:BC, 4:TR` | 7 | 全曲不休。 -### 4.5 难度曲线规则(改谱遵守) +### 4.4 改谱规则 -1. 乐句长度:曲目一 2→4 音、曲目二 2→4 音、曲目三 4→6 音;同曲单调不减 - (终曲加花不计入)。 -2. 反拍(x.5):曲目一**只允许出现在加花**;曲目二第 5 句起、普通乐句每句至多 - 1 个反拍音;曲目三不限。加花为八分音符基底,不受此限。 -3. 同一分区连续出现 ≤2 次(同区双击是合法手法,三连击禁止——盲玩极限)。 -4. 同一乐句内分区种类 ≥2(纯底鼓句仅允许曲目一句 1)。 -5. 相邻反拍连续位禁止左列↔右列大跳(`BL↔BR` 相邻反拍只允许曲目三)—— - 声像跳变过快在耳机上易混(旧作 §3.5-6 同款)。 -6. 加花尾音恒为 TR 吊镲(收束感 + 吊镲长尾盖住应答结束)。 +1. 曲目一 2→4 音、曲目二 2→4、曲目三 4→6;加花不计。 +2. 反拍:曲目一只允许加花;曲目二第 5 句起普通句每句 ≤1;曲目三不限。 +3. 同一分区连续 ≤2(两连合法,三连禁止)。 +4. 乐句内分区种类 ≥2(仅曲目一句 1 允许纯底鼓)。 +5. 相邻反拍禁止左列↔右列大跳(仅曲目三允许 BL↔BR)。 +6. 加花尾音恒 TR。 +7. **谱面不得要求「双击手势」**;只需连续 tap。 --- -## 5. 时值判定与计分评级 +## 5. 判定与计分(数字保留) ```js -const PERFECT = 0.15; // Perfect 窗口 ±150ms(live round1 §13 校准) -const GOOD = 0.35; // Good 窗口 ±350ms(吸收蓝牙延迟) -const LOOKAHEAD = 0.6; // 调度前瞻(秒) -const PUMP_MS = 40; // 调度泵周期 +const PERFECT = 0.15; +const GOOD = 0.35; +const LOOKAHEAD = 0.6; +const PUMP_MS = 40; ``` -- **判定时刻 = 触摸/按键时刻**:immediateTap 按下即发 tap(零补偿); - 键盘数字键 keydown 即发(零补偿)。旧作的 TAP_DELAY/SWIPE_DELAY/DOUBLE_DELAY - 补偿与旧引擎闩锁、双击序曲撤销、超时复活窗**整体退役**(没有滑动/双击音符了)。 -- 归属:同分区最近未判定音符 |dt|≤GOOD → 出手即播该区音色(pan 随行), - Perfect 叠 `sfx/dm_accent`;|dt|≤GOOD×0.5 的异分区音符被敲 = 判 Miss - (错鼓);窗口外 = 自由演奏(出声音不判定)。 -- **序曲 tap 治理**(immediateTap 代价,旧作账簿简化版):每次按下先发 tap - (出声+可认领),转滑动/长按/触摸取消时引擎补 `tapcancel` → 撤销该 tap 的 - 认领(声音收不回当 ghost 音,判定/统计必须更正且不计 Miss)。双击不改判 - 账簿——两下敲击各自独立成立。 -- Miss 超时:expected + GOOD + 0.10s 未应答 → 闷镲反馈音 - (`sfx/dm_hat` 降调 0.5× 复用,零新资产)+ error 震动。 -- 长按 = 死输入:Miss 反馈音 + error 震动,不判定不归属(演奏期长按快进 - `setRateBoostEnabled(false)` 豁免照旧)。 -- 计分/评级/连击:Perfect=2 / Good=1 / Miss=0;rate = 得分/(2×总音符); - S≥0.95 / A≥0.85 / B≥0.70 / C 其余;16 连击 success 震、乐句全 Perfect - [20,40,20]、结算 S/A success 震,全部沿用旧作。 +- 判定时刻 = 触摸/数字键按下(immediateTap 零补偿)。 +- 同分区最近未判定音符 \|dt\|≤GOOD → 出该区音;Perfect 叠 accent。 +- 异分区音符 \|dt\|≤GOOD×0.5 被敲 = Miss(错鼓)。 +- 窗外同区 = 自由演奏(出声不判定)。 +- Miss 超时:expected+GOOD+0.10s → 闷镲(`dm_hat` 0.5×)+ `invalid` 回执 + 可选 error 震。**主反馈是声音。** +- 计分/评级/16 连击震/全 Perfect 震:数字沿用;震是补充。 + +退役:TAP_DELAY / SWIPE_DELAY / DOUBLE_DELAY、双击序曲撤销账簿、超时复活窗、滑动音符。 --- -## 6. 教学编排(分区心智模型,设计核心) +## 6. 教学(分区心智,保留四步) -蒙眼玩家如何建立「摸到哪是哪」?分四步,全部零惩罚(教学不计分、不出 -Miss 音),单步重教 ≤2 次(第 2 次失败 → 领奏降速 25% 重演一次 → 再败跳过, -旧作 G2 DDA 沿用): +零惩罚,单步重教 ≤2,第 2 次失败降速 25%,再败跳过。 -| 步 | 内容 | 机制 | -|---|---|---| -| T0 摸鼓认区 | `tut_map` 讲双轴模型(横三列=左中右声像,竖两行=上亮下沉)→ `tut_probe` 进入**探索模式**:随便摸,敲到哪区就播哪区音色 + 区名(`zone_*` 六句) | 摸到 ≥3 个不同区自动进 T1(每区名每轮教学只播一次,重复摸只出声不报名,防刷屏);**防呆:8s 无触摸自动进 T1**(摸过 1-2 区再宽限 8s),探索是礼物不是门槛——guide.test G2 等无触摸驱动依赖此路径 | -| T1 家区底鼓 | `tut_z_kick`:BC 是家。数拍 1 小节 → 领奏 `1:BC` → 下一小节应答 | 做错:纠正链(§7 归因)→ 重播领奏重试 | -| T2 左右下排 | `tut_z_sides`:BL 军鼓、BR 低嗵。领奏 `1:BL, 3:BR` → 跟打 | 同上 | -| T3 上排三音 | `tut_z_patt`:TL 踩镲、TC 高嗵、TR 吊镲。领奏 `1:TL, 2:TC, 3:TR` → 跟打 | 同上;通过后 `tut_done` 进曲目一 | - -- 教学在曲目一无鼓 BGM 上进行;每步领奏前都有 1 小节底鼓数拍(教学期 - count-in 不省)。 -- 下滑跳过教学(`tut_skip`)保留;教学 Miss 归因真话律沿用(没出手报 - 「这拍没接」gd_miss_none,不谎报早晚)。 -- 加花预告:`tut_fill`「最后有加花,跟着我连打。」在**曲目一终曲加花领奏 - 前 1 小节**播,每账号一次。 - ---- - -## 7. 连击/错拍反馈 - -| 时机 | 反馈 | +| 步 | 内容 | |---|---| -| 出手敲对 | 该区音色(pan 随行)+ Perfect 叠 accent | -| 敲错区(异分区音符半窗内被敲) | 该区音色照样出声(自由演奏语义)+ 闷镲 Miss 音 + error 震 + 播报实际敲中的区名(`zone_*`)+ `retry_wrong`「敲错鼓了,再听一遍。」(教学期)/ gd_miss 归因(正片,连 2 Miss 一次) | -| 早/晚(窗内同区但超 Perfect) | 不出错音(Good 照播),教学期事后归因 `retry_early`/`retry_late` | -| 没出手 | 音符超时闷镲 + error 震;教学归因 gd_miss_none「这拍没接」 | -| 16 连击 / 乐句全 Perfect / 解锁 / S·A 结算 | success 震 / [20,40,20] / success+[20,40,20] / success(沿用旧作) | -| 曲目过半 | `mid_song`(ducking 到 1/4 的旧作工艺沿用) | +| T0 摸鼓 | 双轴模型一句 → 探索:敲哪区出哪音+区名。摸 ≥3 区进 T1;**8s 无触摸自动进 T1** | +| T1 家区 | 数拍 → 领奏 `1:BC` → 应答 | +| T2 左右下 | `1:BL, 3:BR` | +| T3 上排 | `1:TL, 2:TC, 3:TR` → `tut_done` → 曲目一 | + +教学 Miss 归因真话。下滑跳过教学仅阶段 2 以后;新手回访者用开场「点一下开始」跳过。`tut_fill` 在曲目一终曲加花前 1 小节,每账号一次。 --- -## 8. 语音配额(拥挤控制) +## 7. 反馈与语音配额(保留数字) -打击乐密度高,语音是稀缺通道。全表 49 id 双性别(41 新 + 8 向导沿用), -运行时配额: - -1. **应答小节内零语音**(含 guide 插播——idle-only 语义天然保证)。 -2. 教学纠正:每次失败至多 1 条纠正链(归因 1 句 + 重听 1 句),同一步不叠加。 -3. 正片:连 2 Miss 才播 gd_miss,**同曲一次**;mid_song 每曲一次; - 加花预告 tut_fill 每账号一次。 -4. 探索模式区名播报每区每轮教学一次(重复摸只出声)。 -5. 结算链 = grade + [new_best_combo] + [unlock] + restart,无其他插话; - 结算 BGM 看门狗按链长缩放(旧作 P1 修复沿用)。 -6. 语音风暴保险丝(引擎 3s/5 次)是上届,游戏层不得绕过它连播同 id。 +应答小节内零语音。教学失败至多 1 条纠正链。正片连 2 Miss 才 `gd_miss`,同曲一次。探索区名每区每轮一次。结算链 = grade + [new_best_combo] + [unlock] + restart。引擎 3s/5 次保险丝不得绕过。 --- -## 9. 音频资产规格 +## 8. 资产规格(保留) -### 9.1 打击乐 sfx(程序合成,tools/gen_rhythm_syn.py 新建) - -7 件 one-shot:`inst_kick(沿用旧 id)/ dm_snare / dm_hat / dm_tomhi / dm_crash / -dm_tomlo / dm_accent`。QC 红线(gen_cipher_syn.py 同款):去直流 + 首尾 8ms cos² 淡化 + -编码后解码回读真峰值 ≤ -3dBFS(MDCT 过冲坑,超限自动降档重编)。起音必须硬 -(盲玩对拍靠起音);kick/snare/hat ≤0.3s,tom ≤0.4s,crash ≤1.3s,accent ≤0.1s。 - -### 9.2 BGM(3 首无鼓伴奏,Mureka ≤$0.30) - -| 曲 | BPM | prompt 特征 | -|---|---|---| -| track90《启程》 | 90 | bright optimistic pop rock backing, bass guitar + strummed acoustic + light synth pads, **no drums no percussion**, steady 90 bpm, A minor | -| track105《追风》 | 105 | driving energetic pop backing, punchy bass + rhythmic guitar chops + synth, **no drums no percussion**, steady 105 bpm, A minor | -| track120《星爆》 | 120 | high energy synth rock backing, aggressive bass + arpeggio synth + power chords, **no drums no percussion**, steady 120 bpm, A minor | - -- `bpm_precise.py` 实测:|偏差|>0.5 BPM 则 atempo 拉伸到标称(R3 track90 先例), - 成品复测记录台账。 -- 后处理:强 onset 起点 → 整数小节裁剪(时长为 4 拍整数倍)→ 末 2 拍与开头 - acrossfade(tri) 无缝循环 → loudnorm I=-20/TP=-2/LRA=11 → 44.1kHz → ogg q3 + m4a 128k。 -- 旧 track90/105/120(含鼓)删除;`bgm/menu`(Mureka 状态曲,无鼓问题不影响 - 玩法——menu pad 不与演奏同响)保留沿用。 - -### 9.3 语音 - -见 24-rhythm-voice-ids.md:41 新 id 双性别(Qwen3-TTS,flock GPU), -gen_voice_dual.py rhythm 段全量替换;`--check`(时长/峰值/两性离群)+ -`--convert`(ogg q3 + m4a 128k)。废弃 id(tut_1/tut_2/tut_3/retry_gesture -及旧 welcome/rules/retry_* 文案)的音频文件随 convert 后清理删除。 +打击乐 7 件程序合成,QC:去直流、8ms 淡化、真峰值 ≤−3 dBFS。BGM 三首 prompt 仍要求 **no drums no percussion**,90/105/120 BPM,A minor,`bpm_precise.py` 误差 >0.5 则 atempo。`bgm/menu` 保留。旧含鼓 track 删除。 --- -## 10. 进度存储 +## 9. 进度 -沿用旧作 schema 1 结构(`{tutorialDone, unlocked, grades, bestCombo, summary}`), -key `soundgame.rhythm.progress` 不变;PUT `/api/progress` 带 summary -(`《曲名》X级 · 已解锁N/3首` 取最高评级曲目)。解锁规则不变(B 级解锁下一首)。 -结算手势:单击=再来一遍(同曲)、右滑=下一首(已解锁)、下滑=回大厅,不变。 +key `soundgame.rhythm.progress`,schema 1:`tutorialDone, unlocked, grades, bestCombo, summary`(`《曲名》X级 · 已解锁N/3首`)。B 级解锁下一首。 + +结算:单击=同曲;右滑=下一首(未解锁播 `song_locked`;song3 右滑播 restart);阶段 2 下滑=菜单。 --- -## 11. 断言清单(web/rhythm/test/playtest.mjs 覆盖全集) +## 10. 断言清单(修订) -> 13 号文档 §4 rhythm 段是旧玩法的断言,已被本文取代但**不回改**(13 号 -> 文档冻结);本节是 24 号文档的断言明细,按 §编号可被测试与评审引用。 +A1–A5、A7–A5、A8–A25 原编号保留,交互口径改: -- **A1 分区映射**:`zoneOf` 3×2 九宫边界正确(角落/边界点逐一断言);payload - 缺失兜底 BC。 -- **A2 判定窗**:|dt|≤0.15 → Perfect(+accent 叠加);0.15<|dt|≤0.35 → Good; - |dt|>0.35 同区出手 = 自由演奏(出声不判定、不判 Miss)。 -- **A3 错鼓 Miss**:异分区音符 GOOD 半窗内被敲 → Miss(闷镲 + error 震 + - combo 清零;区名播报限教学期——正片错音的归因就是那一声敲错的音色本身,不再叠语音(配额)。 -- **A4 超时 Miss**:expected+0.45s 无应答 → Miss(GOOD 0.35 + 放宽 0.10)。 -- **A5 序曲 tap 撤销**:immediateTap 按下认领 → 引擎 tapcancel → 认领撤销 - 且不计 Miss(统计回滚)。 -- **A6 双击=两连击**:300ms 内同区两 tap + doubletap → 两次敲击各自独立 - (可认领两个相邻音符/一次认领一次自由演奏),无撤销、无崩溃。 -- **A7 教学流程**:T0 探索摸 3 区(区名每区一次)→ T1-T3 跟打通过 → - `tutorialDone`;全程零 Miss 音、零计分。 -- **A8 教学重教**:单步失败 2 次 → 降速 25% 重演(gd_slow,每教学局 1 次)→ - 再败跳过(retry_skip)不卡死;归因真话律(没出手 → gd_miss_none)。 -- **A9 数拍**:曲目开始先排 4 记底鼓 count-in(1 小节)再进句 1 领奏; - 网格对齐 BGM t0。 -- **A10 菜单**:main 中下滑**无条件**呼菜单(无 down 音符折中);暂停冻结 - (泵停、BGM 断点);恢复后 cue.expected/_bgmT0 平移量 = 暂停时长 + - count-in 小节;guide full 档恢复先播 gd_count(每曲限 1 次)。 -- **A11 加花**:fill 句领奏为八分密集 + 尾音 TR;应答窗口与普句同规则; - 曲目一终曲加花前 1 小节播 tut_fill(每账号一次)。 -- **A12 结算评级**:全 Perfect → S + `grades.song1==='s'` + `unlocked===2` + - unlock_song2 播报;B 级也解锁;C 级不解锁。rate 公式与阈值逐档断言。 -- **A13 结算导航**:单击=同曲重开、右滑=下一首(未解锁播 song_locked)、 - song3 末位右滑播 restart 不播 song_locked(旧作裁定①回归)。 -- **A14 连击里程碑**:16 连击 success 震、乐句全 Perfect [20,40,20]。 -- **A15 mid_song**:曲中点播报且领奏 duck 到 1/4、播完恢复。 -- **A16 进度同步**:PUT body 含 summary/grades/unlocked/bestCombo;B 级提升 - 才覆写 grades。 -- **A17 语音配额**:应答小节内零 voice 调度;gd_miss 同曲一次;探索区名 - 每区一次(转写级断言放 transcribe 剧本核)。 -- **A18 键盘平权**:Digit1-6 注入 = 对应区命中(零延迟判定);空格 tap - (元素中心坐标)→ BC;选曲态数字键无效。 -- **A19 guide 接入**:select=menu / main=battle / end=settle 状态签名同点; - 首个应答窗口前 1 小节 gd_answer+turn。 -- **A20 资产完整**:check_all_assets.py rhythm 段全绿(含新 sfx/bgm/voice 双性别)。 -- **A21 旁白跳过**:intro/tut(引导语音)/select/end 语音播放中单击=跳句(12 号 - 文档 §1.2);main 演奏期单击是演奏不跳。 -- **A22 全局**:启动遮罩前无音频;无未捕获异常;settings.voice 切换下一句生效; - bgm=0 时不加载 BGM。 -- **A23(R1 F3)** 教学没出手失败的纠正链 retry_listen 只播一次(不二连复读)。 -- **A24(R1 F1)** 曲尾 3s 内连 2 Miss 不插播 gd_miss(结算链不被顶断)。 -- **A25(R1 F2)** 探针三连摸的三个区名全部播完不被掐断,教学句 queue 其后 - (进阶延迟 150ms 起步:引擎入队判定在 await 加载之前,同步连发会误判)。 +- **A6**:新手期 300ms 内同区两 `tap` = 两次独立敲击(可认领两音),**不产生 `doubletap` 手势、无撤销账簿**。阶段 3 若引擎仍发 `doubletap`,游戏层当第二次 `tap`。 +- **A10 菜单**:main 中阶段 2 后下滑呼菜单;**单击确认当前项,不是双击确认**;恢复后平移量 = 暂停 + count-in;`gd_count` 每曲 1 次。 +- **A21**:演奏期单击是演奏;讲解期单击跳句,400ms 内再点=`busy`。 +- 增补 **A26**:新手谱面/教程不依赖 `g==='doubletap'`。 +- 增补 **A27**:演奏期 Floor=ACTION,无等待声;选曲 YOUR_TURN 有等待声或 menu pad。 +- 增补 **A28**:无声操作 0;Miss 有闷镲或回执。 + +其余 A1 分区、A2 窗、A3 错鼓、A4 超时、A5 tapcancel、A9 数拍、A11 加花、A12–A16 评级进度、A17 配额、A18 键盘、A19 guide 签名、A20 资产、A22 全局、A23–A25 R1 修复:**保留**。 + +13 号旧 §4 手势模式断言不回改;鼓手验收以本节 + 13 号 §5 为准。 --- -## 12. 资产预算与台账 +## 11. 预算台账(保留) -- 语音:49 id × 2 性别 = 98 文件(Qwen3-TTS,flock .gpu.lock,零 API 费)。 -- 打击乐:7 件程序合成(零成本)。 -- BGM:3 任务 × $0.03,每首至多 1 次重提 → 上限 $0.18(预算线 $0.30, - 累计 $1.26/$2.00 → 本轮后 ≤$1.44+$0.18=$1.56 上限)。台账追加 - tools/mureka_log.md。 -- 开发/测试/转写:零成本(复用 harness)。 +语音 49 id×2;打击乐 7 件;BGM 上限见原文工艺。开发零 API 费。 + +--- + +## 12. 废止清单 + +| 废止 | 改为 | +|---|---| +| 双击手势=一种音符;双击确认菜单 | 只有分区点按;菜单左右滑+单击 | +| 新手 `doubleTapWindow` 300ms | 0;两下两击 | +| 演奏期等待声 / 帮助插播 | ACTION;应答内零语音 | +| 暂停恢复无 count-in | 4 拍 count-in | +| Miss 只震 | 闷镲 + 回执 + 真话归因 | +| 帮助用上滑(与上排鼓冲突) | 非演奏期长按 | +| 开场长规则 | T0 先摸鼓 | +| 03 号手势乐器谱作为现行施工 | 以本文为准 | + +引擎坐标第二参、分区布局、时值、曲目表、合成与 BGM 工艺:**全部保留**。 diff --git a/docs/design/24-rhythm-voice-ids.md b/docs/design/24-rhythm-voice-ids.md index c4b2349b..15382844 100644 --- a/docs/design/24-rhythm-voice-ids.md +++ b/docs/design/24-rhythm-voice-ids.md @@ -1,5 +1,13 @@ # 24a · 《节拍行者》鼓手模式语音台词表(voice ids,24 号施工录音依据) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > 双性别:male=Uncle_Fu / female=Vivian(gen_voice_dual.py SPEAKERS 沿用)。 > instruct 对:「低沉醇厚的男声,像节奏游戏主持人,平稳有力」/「活力清晰的 > 年轻女声,像节奏游戏主持人,情绪饱满但不夸张」——全表同一常量。 diff --git a/docs/design/25-escape-map-flow.md b/docs/design/25-escape-map-flow.md index 78e8ecfd..570d6f8f 100644 --- a/docs/design/25-escape-map-flow.md +++ b/docs/design/25-escape-map-flow.md @@ -1,24 +1,34 @@ -# 25 · 《暗室》地图与玩家流程文档 + 状态引导改进方案 +# 25 · 《暗室》地图与流程(as-built 地图 + 26 号交互目标) -> **as-built 声明**:本文档为「以代码为准」的现行实现文档化,权威来源是 -> `web/escape/game.js`(837 行,R2 重设计 + round1~round4 历次 live 修复), -> 与 01 号设计文档(01-escape-redesign.md)对账,**凡不一致处以代码为准**, -> 差异全录于 §1.5。语音 id 全集与全文以 `tools/gen_voice_dual.py` 的 -> `ESCAPE_LINES`(80 句)为准。 +> **双轨声明(读本文前先读这一段)** > -> 阶段三「状态引导改进方案」的取证材料在 `research/escape-state/round1/` -> (mock 转写 + live 真机一局 + P1/P3 人设评审)。方案只产出设计,未实施。 +> - **交互目标**以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 与 [01-escape-redesign.md](01-escape-redesign.md) / [26-apply-escape.md](26-apply-escape.md) 为准。单击=检查+使用;长按=我在哪;**无双击使用**;迷茫三级接管,**禁止** 60/120/180s 原样重播说明书。 +> - **地图数据仍以代码 as-built 为准**(`web/escape/game.js`),直到 01 号交互落地。房间坐标、环境音、谜题链、物品旗标、最短路径格点,以本文 §1 为准。 +> - 语音 id 全文仍以 `tools/gen_voice_dual.py` 的 `ESCAPE_LINES` 为准。本文不改 voice-ids 文件。 > -> 轮次:escape-state round1(2026-09-14)。靶问题(用户真机反馈): -> **玩家不知道自己的状态**——不知道自己在哪、不知道目标、不知道进度。 +> 旧「现行交互」(双击使用、G6、60/120/180 卡关广播、菜单双击确认)仅作代码对照,**不是**新实现依据。 --- -# 阶段一 · as-built 文档化 +## 0. 设计单(交互目标,附录 C) + +完整矩阵、等待点、首 3 分钟、失败表见 [01-escape-redesign.md](01-escape-redesign.md) §0 与 26-apply-escape。摘录: + +| 项 | 目标 | +|---|---| +| 核心动作 | 四向滑移动;单击检查并使用;长按问路 | +| Floor | 探索=`ACTION`(房间 amb 常驻);描述=`SPEAKING` 整链 duck | +| 新手手势 | 四向滑 + 单击;帮助=长按;`doubleTapWindow: 0` | +| 卡关 | 迷茫三级(短提示 → 只留方向 → 走一步),提示三档且不原样重复 | +| 第一句 | 「黑暗。朝任意方向,滑一下。」成功=脚步 ≤10s | + +--- + +# 阶段一 · 地图 as-built(代码为准,保留) ## 1. 地图章 -### 1.1 房间总图(3x3,坐标 x 0西→2东,y 0北→2南) +### 1.1 房间总图(3×3,x 0 西→2 东,y 0 北→2 南) ``` x=0(西) x=1 x=2(东) @@ -38,608 +48,218 @@ y=2 │ (0,2) │ (1,2) │ (2,2) │ (通电后 amb_gate → amb_gate_live) ``` -- **连通关系**:全部相邻房间四向连通——`_move()` 只做 0..2 边界检查 - (game.js:394-417),内部 12 条边全部可走。**全图无锁门、无单向通道**, - 唯一"锁"是动作门(双击使用物品的前置条件),不是空间门。 -- **撞墙边界**:仅外圈 12 段墙。撞墙 = 目标坐标出界 → `sfx/wall` 闷响 + - `error` 震 + `voice/wall`「那边是墙,换个方向。」(每次撞都播, - game.js:397-404)。 -- **起点**:(1,1) 正中央,检查语音 `start_room`「你站在设施中央的房间里」 - ——"中央"是全图**唯一一次**给出的绝对方位锚点(见 §3 盲区 S1)。 +- **连通**:相邻四向全通。`_move()` 只做 0..2 边界。无锁门、无单向通道。 +- **动作门**(目标语义):站在正确房间 **单击** 使用物品。代码 as-built 仍可能是双击,落地时改单击。 +- **撞墙**:外圈 12 段。目标出界 → `sfx/wall` + `invalid` 回执 + `wall` / `wall_dir_*`。 +- **起点**:(1,1)。`start_room` 是全图唯一「中央」绝对方位锚点(文案落地时去掉「双击使用」)。 -### 1.2 每房间资产与点位表 +### 1.2 每房间资产与点位 -| 房间 | 坐标 | 环境音 id | 进入描述(仅首到) | 单击检查语音(状态相关) | 双击使用 | -|---|---|---|---|---|---| -| 电箱房 | (0,0) | `sfx/amb_power` | `room_power`「这个房间的墙上,有一只电箱。」 | 未修 `inspect_box_dead` / 已修 `box_fixed` | hasFuse → `fix_box`(修电) | -| 空房 | (1,0) | `sfx/amb_wind` | 无 | `empty_1`「空空荡荡,只有你自己的回声。」 | 兜底 | -| 空房 | (2,0) | `sfx/amb_hum` | 无 | `empty_3`「除了远处的水滴声,这里什么都没有。」 | 兜底 | -| 空房 | (0,1) | `sfx/amb_hum` | 无 | `empty_2`「摸到的只有冰冷粗糙的墙壁。」 | 兜底 | -| 起点 | (1,1) | `sfx/amb_drip` | 无 | `start_room`(中央房间+操作总览) | 兜底 | -| 铁柜房 | (2,1) | `sfx/amb_cabinet` | `room_cabinet`「这个房间里,立着一个铁柜。」 | 未开 `inspect_cabinet_locked` / 已开 `empty_1` | hasKey → `unlock_cabinet`(得保险丝) | -| 储物间 | (0,2) | `sfx/amb_storage` | `room_storage`「货架上堆满杂物,角落里传来水滴声。」 | 有钥匙且柜未开 → **拾取钥匙** `found_key`;否则 `empty_2` | 兜底 | -| 空房 | (1,2) | `sfx/amb_wind` | 无 | `empty_3` | 兜底 | -| 大门 | (2,2) | `sfx/amb_gate`(通电后 `amb_gate_live`) | `room_gate`「面前,是一扇沉重的铁门。」 | 未通电 `inspect_gate` / 已通电 `inspect_gate_ready` | powerOn → 逃脱结算 | +| 房间 | 坐标 | 环境音 | 进入描述(首到) | 单击检查+使用(目标) | +|---|---|---|---|---| +| 电箱房 | (0,0) | `amb_power` | `room_power` | 未修 `inspect_box_dead`;有保险丝 → `fix_box`;已修 `box_fixed` | +| 空房 | (1,0) | `amb_wind` | 无 / 首访 `empty_1` | `empty_1` | +| 空房 | (2,0) | `amb_hum` | 无 / 首访 `empty_3` | `empty_3` | +| 空房 | (0,1) | `amb_hum` | 无 / 首访 `empty_2` | `empty_2` | +| 起点 | (1,1) | `amb_drip` | 无 | `start_room` | +| 铁柜房 | (2,1) | `amb_cabinet` | `room_cabinet` | 未开 `inspect_cabinet_locked`;有钥匙 → `unlock_cabinet`;已开 `empty_1` | +| 储物间 | (0,2) | `amb_storage` | `room_storage` | 无钥匙 → `found_key`;否则 `empty_2` | +| 空房 | (1,2) | `amb_wind` | 无 / 首访 `empty_3` | `empty_3` | +| 大门 | (2,2) | `amb_gate` / `amb_gate_live` | `room_gate` | 未通电 `inspect_gate`;通电 `inspect_gate_ready` → 逃脱 | -要点: -- **环境音 6 类铺 9 房**:4 间空房只有 2 种音(wind×2、hum×2)且成对角分布 - ((1,0)/(1,2) 风、(0,1)/(2,0) 嗡鸣)——**空房之间声学不可区分**(盲区 S5)。 -- **首到描述只播一次**(`visited` 集合,game.js:112,412-416);重访关键房间 - 零语音,只换环境音。 -- **进房间自动播环境音**(每次移动都播,game.js:429-431),单击检查也先重播 - 环境音再播描述(800ms 间隔让环境音先进耳朵,game.js:446-448)。 -- 大门通电后环境音切换 `amb_gate_live` 是**唯一**一条"谜题状态改变可听觉感知" - 的环境通道(01 号文档 §4.4 的设计意图,代码兑现)。 +要点(as-built 仍成立): + +- 环境音 6 类铺 9 房;4 空房仅 wind×2、hum×2 对角,空房之间声学弱区分(B5a 首访 `empty_*` 已补)。 +- 首到描述 `visited` 一次;进房每次播 amb;单击先重播 amb 再描述(约 800ms 间隔)。 +- 大门通电切 `amb_gate_live`:唯一「谜题状态改环境」通道。 ### 1.3 解谜链依赖图 ``` -[储物间 (0,2) 单击] ──得钥匙──▶ [铁柜房 (2,1) 双击] ──得保险丝──▶ -[电箱房 (0,0) 双击] ──通电──▶ [大门 (2,2) 双击] ──▶ 逃脱结算 - hasKey=true cabinetOpen=true powerOn=true - hasFuse=true +[储物间 (0,2) 单击] ──hasKey──▶ [铁柜房 (2,1) 单击] ──hasFuse + cabinetOpen──▶ +[电箱房 (0,0) 单击] ──powerOn──▶ [大门 (2,2) 单击] ──▶ 逃脱 ``` -- 链上 4 个动作,每个都是"在对的房间双击",前置 = 上一环的物品/状态。 -- 物品即进度:`hasKey`/`hasFuse` 为真即代表链推进到对应环;`cabinetOpen`/ - `powerOn` 为真代表该环完成。`_stage()`(game.js:538-543)按此回退链判定 - 当前阶段:`key → cabinet → box → gate`。 -- **最短通关路径**(11 次移动 + 1 单击 + 3 双击,约 2-3 分钟): - (1,1)→左(0,1)→下(0,2)【单击拾钥匙】→右(1,2)→右(2,2)→上(2,1)【双击开柜】 - →左(1,1)→左(0,1)→上(0,0)【双击修电箱】→右(1,0)→右(2,0)→下(2,2)【双击逃脱】。 - 注意这条最短路径**恰好绕过所有空房**,且铁柜房/电箱房的方位句 - (H1)在这条路径上分别给出"右面的房间"(储物间→铁柜房)与 - "左面的房间"(铁柜房→电箱房)。 +`_stage()`:`key → cabinet → box → gate`。 -### 1.4 房间 → 谜题 → 状态映射表 +**最短通关路径**(11 次移动 + **4 次单击**,约 2–3 分钟): -| 阶段 `_stage()` | 判定条件 | 目标房间(L3 带路 STAGES) | L1 暗示 | L2 明示 | 进展事件(清已播级别) | -|---|---|---|---|---|---| -| `key` | `!hasKey && !cabinetOpen` | (0,2) 储物间 | `stuck_key_1` 铁锈味意象 | `stuck_key_2` 西南角储物间单击 | 拾钥匙、**首到储物间** | -| `cabinet` | `hasKey` | (2,1) 铁柜房 | `stuck_cabinet_1` 钥匙在你身上 | `stuck_cabinet_2` 东边铁柜房双击 | 开柜(得保险丝) | -| `box` | `cabinetOpen && !powerOn` | (0,0) 电箱房 | `stuck_box_1` 电流声意象 | `stuck_box_2` 西北角电箱房双击 | 修电箱 | -| `gate` | `powerOn` | (2,2) 大门 | `stuck_gate_1` 电已通 | `stuck_gate_2` 东南角大门双击 | 逃脱 | +(1,1)→左(0,1)→下(0,2)【单击拾钥匙】→右(1,2)→右(2,2)→上(2,1)【单击开柜】→左(1,1)→左(0,1)→上(0,0)【单击修电】→右(1,0)→右(2,0)→下(2,2)【单击逃脱】。 -- 进展事件调 `_progressEvent()`:清空 `_stuckShown`,下段静默期从 L1 重新分级 - (game.js:578-582)。 -- 代码把**首到储物间**也算进展事件(KEY_ROOMS 含 (0,2)),01 号文档 §3.1 - 只列了铁柜房/电箱房/大门——差异见 §1.5-③。 +代码 as-built 路径相同,使用动作仍为双击,直至 01 落地。 -### 1.5 代码 vs 设计文档差异清单(以代码为准) +### 1.4 阶段 → 目标房间(导航用,不是 60/120/180 广播稿) -| # | 01 号设计文档 | 现行代码 | 性质 | +| `_stage()` | 判定 | 目标格 | 进展事件 | |---|---|---|---| -| ① | §3.1/§3.2 卡关计时"以上一次**进展事件**起算"(无进展 60/120/180s) | 按"**无操作时长**"计时:PLAY 阶段任何有效手势重置计时(game.js:26-31,160-164,589-612) | round1-live 修复(原机关枪式报答案+顶语音),设计意图被反向修订 | -| ② | §2.1 G0「单击继续」+ CONFIRM 震 | intro 播完 **1.2s 无操作自动推进 G1**(game.js:205-211);G0 单击仍有效 | round2-live 修复(intro 后死寂 17~91s) | -| ③ | §3.1 进展事件列"首到铁柜房/电箱房/大门" | 首到**储物间**同样计进展(KEY_ROOMS 四房间统一处理) | 代码超集 | -| ④ | §2.1「引导期间任何步骤可下滑跳过」 | 仅 G0/G3/G5/G6 可下滑跳过;G1/G2/G4 下滑=作答(game.js:239-245) | 13 号文档裁定,round2-live 修复教学链断裂 | -| ⑤ | §3.2「双击无目标兜底:先播背包,再播 L2」 | 在**关键房间**内播 `use_fail` 而非背包;非关键房才播背包(game.js:519-527) | 代码细化 | -| ⑥ | 14 号文档 v1 裁定 5/§2:「连续撞墙 ≥2 次 → full 档卡关三级阈值提前一级」 | **未落地**:game.js 无任何撞墙连击/阈值提前逻辑(已核实无 wallStreak 类状态) | 文档先行、代码缺失,以代码为准(=无此功能) | -| ⑦ | §2.1 G2 反馈含"未撞墙:脚步 + SWIPE_OK"分支 | 引导期位置固定,**滑动即撞墙反馈**(每滑必 wall 闷响+error 震+wall 语音),无"未撞墙"分支(game.js:264-272) | 设计草案分支不存在 | -| ⑧ | §2.1 G6 反馈「兜底播报背包 + CONFIRM 震」 | G6 双击答对播 `inv_key`+`g6_done`+`g7_goal` 链(无单独 confirm 震,靠引擎双击内置 confirm) | 等价 | -| ⑨ | (01 号文档无 REOPEN 相) | 存在 REOPEN 二周目阶段:上滑重听引导/单击直接开始(game.js:54-55,143-151) | 01 号文档 §2.1 末段提到过一次,代码为完整实现 | -| ⑩ | §3.2 L1 附"语音提示前播'需要帮助吗?'" | `stuck_ask` 仅 L1 前插播,L2/L3 无(game.js:607-611) | 一致(此处列出防误扩) | -| ⑪ | 01 号文档 §4.2「环境音扩到 6 种」 | 实际 8 个环境音资产 6 类(含 `amb_gate_live` 通电变体) | 一致(超集) | +| `key` | `!hasKey && !cabinetOpen` | (0,2) 储物间 | 拾钥匙、首到储物间 | +| `cabinet` | `hasKey` | (2,1) 铁柜房 | 开柜 | +| `box` | `cabinetOpen && !powerOn` | (0,0) 电箱房 | 修电 | +| `gate` | `powerOn` | (2,2) 大门 | 逃脱 | -另注:14 号文档 round2 F2.3「撞墙接播 guide_blunt_ 方位句」曾被落地、 -后被 round1-live **回退**为只播 `voice/wall` 极简句(game.js:400-403 注释), -教学腔方位句仅限引导期 G3 纠错路径——这条历史是阶段三 B1 方案的前车之鉴 -(方位信息要给,但措辞不能再走教学腔)。 +L1 意象 / L2 明示 / L3 带路的 **旧 stuck_* 句仍是资产**,迷茫接管时:L1 用短方位或意象(不泄完整解法)、L2 只放行第一导航方向、L3 游戏走一步。 **不要**按无操作 60/120/180s 把 stuck_* 全文周期重播。 ---- +### 1.5 代码 vs 01 号:地图相关差异(仍以代码地图为准) -## 2. 玩家流程章 - -### 2.1 全局状态机 - -``` - ┌────────────── 首次开局 ──────────────┐ - ▼ │ - start ─▶ REOPEN(二周目才有:上滑重听引导/单击直接开始) - │ 首局自动 - ▼ - GUIDE(脚本化教学,玩家手势推进,G0→G7) - │ G6 双击答对 → g7_goal 链播完 _finishGuide - │ G0/G3/G5/G6 下滑跳过 → guide_skip → PLAY - ▼ - PLAY(自由探索 + 卡关三级提示计时器;谜题链阶段 key→cabinet→box→gate) - │ 大门双击(powerOn)→ _escape() - ▼ - ENDING(结算链 escape→time_band→again;单击重开 / 下滑菜单) - │ 下滑 - ▼ - MENU(模态:单击循环选项 / 双击执行 / 上滑帮助 / 下滑关闭) -``` - -- **会话令牌** `_session`:重开/跳过/回大厅时递增,使旧计时器与语音链回调 - 全部失效(game.js:98,826-831)。 -- **语音纪律基础设施**:待播队列 `_pendingVoice`(kind=`stuck` 卡关提示 / - `describe` 补发检查),只在当前语音自然播完后播出,**绝不顶句**;待播期间 - 玩家任何操作即作废卡关提示(未播级别不计入已播,game.js:125,783-810)。 -- 单击跳句(12 号文档):可跳句播放中单击 = `skipVoice()` 跳过本句 + 补发检查 - (环境音即时重播、描述进待播队列);不可跳句(引导 G0-G7、关键获得链、 - L3 带路)单击 = 忽略(game.js:377-388,445)。 - -### 2.2 引导子状态 G0-G7(GUIDE 阶段) - -| 步 | 语音(全文见 §3 账本) | 等待手势 | 答对反馈 | 答错(3 次给 blunt+warning 震) | -|---|---|---|---|---| -| G0 | `g0_intro`(剧情) | 单击;**1.2s 无操作自动推进** | g1_move+skip_hint | intro 播放中滑动=忽略;空档滑动=`guide_offpath`「先跟我走。」 | -| G1 | `g1_move`「朝任意方向滑一下」 | 任意滑动(下滑也算作答,不可跳过) | step + `g1_done` + `g2_wall` | `guide_wrong_gesture` | -| G2 | `g2_wall`「再滑两下,试试碰碰墙」 | 滑动×2(每滑必播 wall 闷响+error 震+`wall`) | 满 2 次 → `wall`+`g3_left` | 非滑动 = wrong_gesture | -| G3 | `g3_left`「向左滑。」 | 左滑(→(0,1)) | step + `g4_down` | 其他滑动 = `guide_wrong_left` | -| G4 | `g4_down`「再向下滑。」 | 下滑(→(0,2) 储物间) | step + `amb_storage` + `g5_inspect` | 其他滑动 = `guide_wrong_down` | -| G5 | `g5_inspect`「单击检查」 | 单击 → **拾钥匙** | pickup + `g5_key` | 非单击 = wrong_gesture | -| G6 | `g6_doubletap`「双击试试」 | 双击 | `inv_key`+`g6_done`+`g7_goal` | 非双击 = wrong_gesture | -| G7 | `g7_goal`「找到能用这把钥匙打开的东西…」 | (无,链播完自动进 PLAY) | — | 手势忽略 | - -- 引导期位置是脚本写死的:(1,1)→(0,1)→(0,2),与真实地图一致;G2 的"撞墙" - 是伪撞墙(位置并未移动)。 -- `_guideBusy` 推进语音播放中**忽略一切非跳过分手势**——这是阶段四 live 局 - 抓到的事故点(盲区 S6):G3 答案在 busy 窗口内被吞,玩家紧接着的下滑被 - 当成"跳过引导"。 - -### 2.3 手势语义总表(逐阶段) - -| 手势 | REOPEN | GUIDE | PLAY | ENDING | MENU | -|---|---|---|---|---|---| -| 上滑 | 重听引导 | 下滑跳过仅限 G0/G3/G5/G6,**上滑无跳过语义**;其余步上滑=答错/忽略 | **移动(dy=-1)** | 忽略 | 播 `help_ops` 操作说明 | -| 下滑 | 忽略 | G0/G3/G5/G6=跳过引导;G1/G2/G4=作答 | **移动(dy=+1)** | 打开菜单 | 关闭菜单 | -| 左/右滑 | 忽略 | 按步骤判定(G3 左滑=答案) | **移动(dx=∓1)** | 忽略 | 忽略 | -| 单击 | 直接开始(stopVoice+进 PLAY) | 按步骤判定(G0/G5=推进/拾取) | voice 播放中=跳句+补检查;无 voice=检查 | **重开一局** | 循环切换菜单项(播选项名) | -| 双击 | 忽略 | 按步骤判定(G6=答案) | 使用物品:有目标=解谜动作;无目标=**兜底**(背包或 use_fail + `stuck__2` L2 明示) | 忽略 | 执行当前项 | -| 长按 | 引擎:旁白快进(后松开) | 同左 | 同左 | 同左 | 同左 | - -- escape 的手势映射**全面偏离全局默认**(四向滑=移动、双击=使用而非重听), - 00 号文档 §5.3 豁免表已备案;偏离内容在引导 G1/G2/G5/G6 逐一手把手教过。 -- **PLAY 阶段没有菜单、没有上滑帮助**(00 §5.3:帮助入口 = 双击兜底 + - 卡关计时器)——"主动查状态"在 escape 里只有双击兜底一个口子,而它直通 - L2 明示(盲区 S4)。 - -### 2.4 卡关三级提示(PLAY 阶段自动触发) - -| 级别 | 触发(现行代码:无操作时长) | 内容 | 附带 | 重复规则 | -|---|---|---|---|---| -| L1 暗示 | 60s 无任何手势 | `stuck_ask`「需要帮助吗?」+ `stuck__1` 意象句 | warning 震 | 每进展段 1 次 | -| L2 明示 | 120s | `stuck__2` 直接点明房间+动作 | warning 震 | 每进展段 1 次 | -| L3 带路 | 180s | `stuck__3` + `nav_*` 按当前坐标算路(每轴一句,最多两句,先东西后南北) | warning 震 | 此后每 60s 无操作重复,按当时坐标重算 | - -- **队列与作废**:提示只进待播队列,等当前语音播完;待播期间玩家任何操作 - 即作废(`_dropPendingStuck`,game.js:803-810)。→ 活跃玩家可能连 L1 都 - 听不到,但它本来就不该听到(计时按无操作)。 -- **双击兜底 = 另一条 L2 通道**(game.js:519-527):在关键房间播 use_fail、 - 在非关键房播背包,**都接 `stuck__2`**——玩家主动探状态会被直接 - 喂答案(阶段四 live 两次实录,见盲区 S4)。 -- 14 号文档裁定的"连续撞墙 ≥2 次阈值提前一级"未落地(§1.5-⑥)。 - -### 2.5 关键路径与分支 - -**主路径(引导完整走)**:REOPEN(跳过)→ G0 intro → G1 滑一步 → G2 撞墙×2 -→ G3 左滑 → G4 下滑进储物间 → G5 单击拾钥匙 → G6 双击 → G7 目标陈述 → -PLAY(已持钥匙,阶段=cabinet)→ 自由探索开柜→修电箱→开门。 -引导结束瞬间玩家已知:移动方式、撞墙反馈、检查、双击、背包里有钥匙、 -下一步目标 = 找锁。**这是全游戏信息最充裕的时刻。** - -**分支与边界路径**: -1. **跳过引导**(G0/G3/G5/G6 下滑):`guide_skip`「跳过引导,直接开始自由 - 探索。」→ 直接 PLAY,**不持钥匙、没听过 g7_goal**——开局只有 - `start_room` 一句操作总览,目标维度完全空白(盲区 S4,live 实录)。 -2. **二周目** REOPEN:上滑=重听引导;单击=直接开始(播 start_room)。 -3. **撞墙**:外圈 12 段;反馈 wall 闷响+error 震+`wall` 极简句。无方位 - 信息(教学腔方位句已被 round1-live 回退,§1.5 注)。 -4. **双击兜底**:非关键房 = 背包句 + L2;关键房 = use_fail + L2。 - (live 实录:柜已开后再双击铁柜房,听到 use_fail + `stuck_box_2`—— - 玩家并没有卡关,答案句白送。) -5. **空房**:进入只播环境音(与邻房可能同音),单击才播 empty_*。 -6. **重访关键房间**:零语音(首到描述只一次)。 -7. **结算**:escape 链(不可跳)播完淡入 bgm/menu;单击=立即重开 - (`_restart` 不管结算链是否播完——live 实录结算链被单击截断, - time_band/again 未播,见盲区 S7);下滑=菜单(重开/回大厅/返回)。 -8. **菜单**:暂停一切(escape 无局内计时器,影响仅限语音队列)。 - -### 2.6 各阶段玩家可获得的信息渠道 - -| 阶段 | 语音 | earcon/音效 | 震动 | 环境音 | BGM | -|---|---|---|---|---|---| -| REOPEN | reopen_prompt | turn cue | —(等抉择) | — | bgm/drone | -| GUIDE | 逐句指令+纠错(不可跳) | step/wall/pickup | error(答错)/warning(3 连错)/pickup(拾钥匙)/引擎内置 swipe/confirm | amb_storage(G4 到储物间) | bgm/drone | -| PLAY | 首到房间描述/检查句/进展链(found_key→gd_room_*)/卡关三级/双击兜底 | step/wall/pickup/unlock/door_creak/spark/power_on/gate_open/win_chime | error(撞墙/无效使用)/success(开柜修电/逃脱)/pickup(拾钥匙)/warning(卡关提示)/引擎 swipe | 每房进来自动播,单击可重听 | bgm/drone | -| ENDING | escape+time_band+again(不可跳) | gate_open(后移 2.6s)+win_chime | success | — | drone 淡出→bgm/menu 淡入 | -| MENU | menu_open+选项名/help_ops | — | confirm(双击执行) | — | bgm/menu | - -信息渠道的四division 铁律(14 号裁定 7):语音说"你要做什么"、earcon 说 -"哪类事/哪个状态"、BGM 说"宏观状态"、震动说"刚才那下对不对"。escape 的 -earcon 仅向导层 8 签名(explore/menu/settle/turn)+ 内容音效;**游戏层没有 -任何自研 earcon**——方位/进度类信息目前没有 earcon 通道可用(新增需走 -14 号文档的引擎词表修订,或改用语音/环境音通道)。 - ---- - -## 3. 信息账本(语音 id 全集三类归档 + 盲区清单) - -### 3.1 语音 id 全集(80 句,按「状态告知 / 操作反馈 / 叙事·教学」三类) - -**A 类 · 状态告知**(回答"我在哪/我有什么/进展如何/系统处于什么状态"): - -| id | 全文(摘要) | 覆盖的状态维度 | -|---|---|---| -| `start_room` | 你站在设施中央的房间里。滑动移动,单击检查,双击使用物品。 | 绝对方位(唯一一次)+ 操作总览 | -| `room_storage` / `room_cabinet` / `room_power` / `room_gate` | 储物间/铁柜/电箱/大门 首到描述 | 房间身份(仅首到) | -| `empty_1/2/3` | 空空荡荡/冰冷墙壁/远处水滴 | 空房身份(仅单击检查时) | -| `inspect_cabinet_locked` / `inspect_box_dead` / `box_fixed` / `inspect_gate` / `inspect_gate_ready` | 各互动物状态描述 | 谜题对象状态 | -| `inv_empty/key/fuse/both` | 背包四档 | 物品持有 | -| `wall` | 那边是墙,换个方向。 | 边界负信息(无方位) | -| `stuck_ask` + `stuck__1` ×4 | 需要帮助吗?+ 意象暗示 | 目标(L1 级,意象) | -| `stuck__2` ×4 | 点明房间+动作 | 目标(L2 级,明示) | -| `stuck__3` ×4 + `nav_udlr` ×4 | 带路 | 目标+路线(L3 级) | -| `gd_next_fuse` | 保险丝到手。接下来,去找电箱。 | 目标(进展事件当下) | -| `gd_room_l/r/u/d` | 左/右/上/下面的房间。 | 目标相对方位(无目标名) | -| `time_band1-4` | 用时四档整句 | 结算用时 | -| `use_fail` | 这里没有用得上东西的地方。 | 无效使用(状态否定) | -| `menu_open/restart/home/back` + `help_ops` + `reopen_prompt` | 菜单与帮助 | 系统状态 | -| `box_fixed`(重复列出处归入检查) | — | — | - -**B 类 · 操作反馈**(回答"刚才那下对不对/成了没"): - -| id | 全文(摘要) | 触发 | -|---|---|---| -| `g1_done` / `g5_key` / `g6_done` | 引导答对确认 | 引导 G1/G5/G6 | -| `found_key` | 摸到钥匙(长句,不可跳) | 拾取 | -| `unlock_cabinet` | 开柜得保险丝(不可跳) | 解谜成功 1 | -| `fix_box` | 装保险丝通电(不可跳) | 解谜成功 2 | -| `escape` | 逃脱结算(不可跳) | 通关 | -| `replay` | 黑暗再次合拢(重开) | 重开 | -| `again` | 单击再来一次(结算链尾) | 结算 | -| `use_fail`(兼 A 类) | 无效使用 | 双击无目标 | -| `guide_wrong_up/down/left/right/gesture` ×5 | 不是这个方向/操作 | 引导答错 | -| `guide_blunt_up/down/left/right` ×4 | 直白重述(3 连错) | 引导连续答错 | -| `guide_offpath` | 先跟我走。 | 引导期越轨滑动 | - -**C 类 · 叙事·教学**(铺氛围/教手势,不承担状态): - -| id | 全文(摘要) | +| # | 说明 | |---|---| -| `g0_intro` | 黑暗。你在废弃的地下设施里醒来…… | -| `g1_move` / `g2_wall` / `g3_left` / `g4_down` / `g5_inspect` / `g6_doubletap` / `g7_goal` | 引导指令六步 + 目标陈述 | -| `skip_hint` / `guide_skip` | 跳过旁白提示 / 跳过确认 | -| `reopen_prompt`(兼 A) | 二周目抉择 | +| ③ | 首到储物间也计进展(KEY_ROOMS 四房)。保留。 | +| ⑪ | 环境音 8 文件 6 类(含 `amb_gate_live`)。保留。 | +| 交互类 ①②④⑤⑥⑦⑧⑨⑩ | **作废为交互依据**。落地后以 01 / 26-apply 为准(单击使用、长按帮助、无下滑跳过引导、迷茫接管、结算守卫、busy 缓存等)。 | -### 3.2 状态维度 × 信息渠道矩阵(空白格 = 盲区) - -| 状态维度 | 语音 | 环境音 | 震动 | earcon/BGM | 结论 | -|---|---|---|---|---|---| -| 绝对方位(我在图上的哪格) | 仅开局 `start_room` 一句"中央";L2 角落词 | 无 | 无 | 无 | **盲区 S1** | -| 相对方位(目标在我哪边) | 仅进展链当下(gd_room)+ L3 带路 | 无 | 无 | 无 | 半盲区(时机太窄) | -| 撞墙方位(哪面不通) | 无(教学腔已被回退) | 无 | 无(撞墙=error 通式) | 无 | **盲区 S1b** | -| 已探索地图(我来过这吗) | 无(首到描述不重复,重访零语音) | 无 | 无 | 无 | **盲区 S2** | -| 空房身份(这屋是哪间) | 仅单击检查时 empty_*,且文本无方位 | 2 种音铺 4 房,不可区分 | 无 | 无 | **盲区 S5** | -| 谜题链全局进度(几环完成/还剩几环) | 无主动渠道;inv_* 只报物品 | 仅大门通电有 amb_gate_live | 无 | 无 | **盲区 S3** | -| 当前目标(L0 轻量重述) | 仅引导尾 g7_goal(跳过即永久丢失)+ 进展链当下一句 | 无 | 无 | 无 | **盲区 S4** | -| 主动查询入口 | 双击兜底 = 直通 L2 明示(泄漏) | — | — | — | **盲区 S4b(机制错配)** | -| 物品持有 | inv_* ✓(双击兜底时) | 无 | 拾取时 pickup ✓ | 无 | 基本覆盖 | -| 互动物状态(锁/电/门) | 检查语音 ✓ | 大门通电 ✓ | 解谜 success ✓ | 无 | 覆盖(需玩家记得检查) | -| 卡关计时器状态 | 无(提示到点才出现) | — | — | — | 设计内,不动 | -| 引导 busy 窗口(我手势算数吗) | 无 | 无 | 仅引擎 [10] | 无 | **盲区 S6**(事故级) | -| 结算完整性(我通关用时/重开确认) | 结算链可被单击无条件截断 | — | — | — | **盲区 S7** | - -> S1/S2/S3/S4/S5 是用户真机反馈"不知道自己在哪/目标/进度"的精确落点; -> S6/S7 是 live 局新抓到的机制级盲区。S1b(撞墙无方位)是 P1 评审的死线项。 +撞墙方位 `wall_dir_*`、目标名+方位 `gd_*_dir`、空房首访、busy 缓存、结算 `_settleBusy`、跳过路径补目标句:地图/流程资产 **保留**,接线改挂 26 号提示与迷茫,不再挂 60/120/180 广播。 --- -# 阶段三 · 状态引导改进方案(escape-state round1) +# 阶段二 · 玩家流程(交互目标,非整份 as-built 手势表) -## 4.0 取证摘要 +## 2. 流程章 -- **mock 转写**(`research/escape-state/round1/transcript-mock.md`):引导完整 - 路径(G0-G7 全覆盖),证明"引导结束时信息最充裕";此后唯一信息增量来自 - 进展链(mock 行 107-108、126:gd_next_fuse + 方位句)。 -- **live 真机一局**(`research/escape-state/round1/live-transcript.md`,女声响 - full 档,约 14 分钟,打到通关并重开):52 个语音段,55 条判定行 - (OK 35 / SFX_BGM_ONLY 18 / TRUNCATED 2;两条 TRUNCATED 均有玩家手势 - 归因,非资产缺陷)。 - 关键事件:引导期 busy 吞手势(行 30-35)、误触下滑跳过引导(行 61-74)、 - 双击兜底两次直通 L2 明示(行 161-178、230-243)、结算链被单击截断 - (行 253-275)、L1/L2/L3 三级按时触发且 L3 每 60s 按坐标重算重播 - (行 75-86、288-294、295-346)。 -- **人设评审**:P1 视障(`p1-blind.md`)+ P3 白领(`p3-office.md`),双席对 - 盲区清单达成一致。 +### 2.1 全局状态机(目标) -设计约束(本方案全部条目的共同红线): -- **00 号宪法**:整句预生成、禁运行时拼接;数字信息分档整句;震动语义按 - 00 §2.2 既有 10 震型、不新增语义震;手势映射零改动(上滑仍是移动)。 -- **14 号向导层**:三档语义(full=earcon+语音+震 / standard=耳标+震 / off= - 全静);新 earcon 不进本方案(引擎 8 签名词表修订成本过高,方位与进度信息 - 一律走语音通道,游戏反馈层全档位,与 gd_next_fuse/gd_room 先例同级)。 -- **归因真话律**:进度句报的完成数必须等于真实状态机(hasKey/cabinetOpen/ - powerOn 派生),不许多报少报。 -- **难度不降低**:所有新语音只报"状态与方向",不报"做法";L2/L3 的到达不因 - 新通道提前或变频繁。 -- **节奏不破**:单局 15-30 分钟的宏观节奏由谜题链决定,新播报全部挂在 - "进展事件 / 首次 / 限频"上,不给活跃玩家增加语音负担。 +``` +start → 首局 GUIDE(G0 任意滑 → 碰墙 → 左 → 下储物间 → 单击钥匙 → 长按教学) + → PLAY(ACTION 探索,谜题 key→cabinet→box→gate) + → ENDING(结算链)→ 单击再来 / 阶段 2 下滑菜单 +二周目:单击继续上次 / 右滑重开(不再上滑=整段重听引导) +``` -## 4.1 方案条目 +- `_session` 令牌使旧计时器/语音回调失效:保留。 +- 语音:玩家一动,非 `critical` 即停;`hint`/`feedback` 过期丢弃。废止「卡关提示永不顶句排队到玩家已经走了还播」。 +- 单击跳句:PLAY 描述可跳;引导指令与关键获得 `critical`≤3s 给 `busy`。废止「引导全链绝对不可跳」与「关键获得 <10s 不可跳」的旧长度,改为拆短。 -### B1 · 撞墙报方位(陈述式,非教学腔) +### 2.2 引导 G0–G7(目标;删除 G6 双击) -- **问题(证据)**:撞墙反馈只有 `wall`「那边是墙,换个方向。」——无方位。 - live 行 40-55 连撞两次地图零增量;P1「每次错都学不到东西」、P3「盲操的 - 试错成本全是沉没成本」。14 号 F2.3 曾用 `guide_blunt_*`「手指贴着屏幕,向 - X 推一下」补方位,被 round1-live 以"教学腔中后期出戏"回退——回退掉的是 - 措辞,方位信息本身没有错杀的理由。 -- **目标状态**:玩家每次撞墙都能建立一个"此方向不通"的地图标记,措辞是 - 世界内陈述句而非操作教学。 -- **方案**:新增 4 句整句(游戏反馈层全档位,撞墙既有链路 `sfx/wall`+error - 震+`wall` 播完后接播,被下一次移动打断则自然作废——复用 F2.3 原接线模式): - - `wall_dir_l`「左边是墙。」 / `wall_dir_r`「右边是墙。」 / - `wall_dir_u`「上边是墙。」 / `wall_dir_d`「下边是墙。」 - - 触发:PLAY 阶段撞墙、`voice/wall` 播完后、同方向 10s 内限频 1 次 - (防贴墙连撞连播)。**仅报方位事实,不说"换个方向/推一下"**——后半句 - 现有 `wall` 已覆盖。 - - 方位 = 滑动方向(escape 移动坐标系),按真实撞墙方向取句,归因真话。 - - 震动:沿用 error,不新增。 -- **宪法条款**:00 §2.2(既有 error 震型);00 §3.2(整句预生成);14 裁定 7 - (语音报事实、耳标报类别——本句是纯语音通道);14 F2.3 先例(接线模式)。 -- **拥挤评估**:撞墙在 3x3 图中的理论上限是外圈 12 段;限频后一局新增语音 - ≤6-8 句短句(各 ≤5 字),对 15-30 分钟单局节奏影响可忽略。风险:贴墙探索 - 型玩家会听到成串方位句——10s 同向限频 + 移动即作废已把上限压住。 - -### B2 · 目标方位句并入目标名(H1 的措辞升级) - -- **问题(证据)**:H1 方位句只有方向词无目标名:「右面的房间。」(live 行 - 112-116、156-160、201-208)。P3:嘈杂环境 1.3s 短句被吃掉后只剩"……的 - 房间"(行 206-208 ASR sim 0.75 实证);P1:"右面是哪?我赌不起"。三处 - 触发点(found_key / gd_next_fuse / fix_box)在谜题链正常路径上位置固定, - 方位可枚举。 -- **目标状态**:进展链尾玩家听到"目标名 + 方位"一体句,单句即可行动。 -- **方案**:新增 3 句整句替换三处 `gd_room_*` 接播(接线点不变,全档位): - - found_key 链尾 → `gd_cabinet_dir`「铁柜,在右面的房间。」(储物间 (0,2) - → 铁柜房 (2,1),轴序先东西,dx=+2 固定为右) - - unlock_cabinet/gd_next_fuse 链尾 → `gd_box_dir`「电箱,在左面的房间。」 - (铁柜房 (2,1) → 电箱房 (0,0),dx=−2 固定为左) - - fix_box 链尾 → `gd_gate_dir`「大门,在右面的房间。」(电箱房 (0,0) → - 大门 (2,2),dx=+2 固定为右) - - 原 `gd_room_l/r/u/d` 4 句保留不删(guide.test.mjs 的 H1 方向映射断言与 - playtest 断言引用它们,删除触碰断言零改动红线);新句并行接线,仅替换 - 三处触发点。方位若在异常路径上计算结果与枚举不符(理论上不存在——三处 - 触发点坐标固定),兜底回落 `gd_room_*`。 -- **宪法条款**:00 §3.2(整句);归因真话(方位按房间图枚举核对过:三处 - 触发点→目标均同排/跨排但轴序先东西,方向唯一);14 H1 裁定(接线模式 - 原样,只改词)。 -- **拥挤评估**:3 处替换、0 处新增播出次数;句长 +4 字。对节奏零影响。 - 附带收益:修了 P3 的"关键信息听不清"——信息密度翻倍,重听价值也翻倍。 - -### B3 · 无进展计时器 + L0 进度重述(活跃迷路通道) - -- **问题(证据)**:卡关计时按"无操作"(§1.5-①,round1-live 修订),**活跃 - 但无进展的玩家永远不会收到任何状态输入**——live 局我在解谜链外探路时 - (行 87-94、179-220 各段 transit)游戏只给我脚步和环境音。01 号文档原始 - 设计是"无进展起算",代码把"无进展"改成"无操作"后,"活跃迷路"这个状态 - 从提示体系里消失了。用户反馈"不知道进度"正是这个空洞。 -- **目标状态**:玩家长时间有操作但零进展时,收到一条**不泄题**的进度重述, - 知道自己已完成几环、目标仍是什么,而不是答案。 -- **方案**: - - 新增独立计时:以 `_progressEvent()` 起算的"无进展时长"(与现有无操作 - 计时器并存,互不复位)。触发点:无进展 ≥150s 且期间有 ≥5 次有效手势 - (=活跃探索,排除挂机——挂机交给现有 60s L1 体系)→ 每进展段限 1 次。 - - 播报内容(整句分档,不拼接,归因真话——完成数由 `_stage()` 反推): - - `gd_prog_1`「钥匙已经到手。找到能用它的锁。」(stage=cabinet) - - `gd_prog_2`「铁柜已经打开。电还没有通。」(stage=box) - - `gd_prog_3`「电已经通了。去找那扇大门。」(stage=gate) - - key 阶段无进展 150s 不走本通道(现有 L1/L2/L3 已覆盖"找不到钥匙", - 避免双计时器对同一阶段轰炸)。 - - 队列语义沿用 `_pendingVoice`(绝不顶句,待播期间有操作即作废);warning - 震与 L1-L3 同款。 -- **宪法条款**:00 §3.2(整句分档);归因真话律;14 裁定 5(escape 卡关 - 骨架保留,本通道是**新增平行通道**,不动既有 60/120/180 任何一级);01 号 - 文档 §3.1 原始意图的部分回收。 -- **拥挤评估**:触发频率 = 每进展段最多 1 次 × 最多 3 段(cabinet/box/gate) - = 全局限 3 句;且要求 150s 活跃零进展,正常通关(2-3 分钟/环节)永远触发 - 不到。只补"该响没响"的洞,不给顺畅玩家加噪。 - -### B4 · 双击兜底降级:背包 + L1 意象,不再直通 L2 - -- **问题(证据)**:双击无目标 = 玩家**主动查询**的唯一入口,现状直通 - L2 明示:live 行 161-178(柜已开后再双击铁柜房 → use_fail + `stuck_box_2` - 把电箱房和动作全报出来)、行 230-243(同理 `stuck_gate_2`)。P1 定为 P0 - "谜题被白送"——没卡关的人收到卡关 120s 才该有的答案,既是泄漏也稀释 L2 - 的稀缺性(难度变相降低)。 -- **目标状态**:主动查询返回"背包 + 当前目标的 L1 级暗示",答案仍锁在 - 计时器 L2/L3 与进展链里。 -- **方案**:`_useItem()` 兜底链第二句由 `voice/stuck__2` 改为 - `voice/stuck__1`(资产现成,4 句意象暗示:铁锈味/钥匙在身上/ - 电流声/电已通——全部不点明房间与动作,零新资产)。`use_fail` 与背包句 - 不动。已在 `_stuckShown` 里记过 L1 的进展段不重复播(沿用既有去重)。 -- **宪法条款**:00 §5.3(escape 帮助入口语义不变,只是档位归位);01 号 - §3.2「主动求提示保留:先播背包,再播 L2」——本条是对 01 的**有意修订** - (泄漏 > 帮助,证据双实录),在本文档留痕;归因真话(L1 句无答案可泄漏)。 -- **拥挤评估**:播出次数不变(一次兜底两句);内容变轻。L2 触发总量下降 - (对非卡关玩家),L3 不受影响。对卡关玩家:60s L1 → 120s L2 → 180s L3 - 的阶梯完整保留,难度不降。 - -### B5 · 空房首访描述(复用现有资产)+ 方位线索句(新词,二选一落地) - -- **问题(证据)**:进入空房零语音(live 行 87-94、179-186、209-220), - 4 间空房 2 种环境音不可区分(amb_hum 行 90/182;amb_wind 行 120/212)。 - 玩家对 5/9 的地图面积永远建不起模型——用户反馈"不知道自己在哪"的大头。 -- **目标状态**:每间空房至少有一个可建立心智地图的听觉锚(首访一次性, - 之后可单击复核)。 -- **方案**(两档,按实施预算二选一,建议 a 先行): - - **a(零新资产)**:空房**首访**时补播现有 `empty_1/2/3`(现只在单击 - 检查时播;进入时播、单击时不再重复首访句)。三个 id 恰好够分给 - (1,0)/(0,1)/(1,2)+(2,0)…4 房 3 句,需把 (1,2)/(2,0) 拆开:建议 - (1,0)→empty_1、(0,1)→empty_2、(1,2)→empty_3、(2,0)→复用 empty_3 或 - 保持沉默(两间"远处水滴"空房对称分布也是合法的记忆点)。 - - **b(3-4 句新词,推荐终态)**:empty_* 改词重录为**含固定方位特征**的 - 句子(房间特征是固定的,方位词不随玩家状态变,归因真话): - `empty_1`→「北面墙缝透进一丝风。这里什么都没有。」((1,0) 北外墙)、 - `empty_2`→「回声从东面的墙折回来。这里空空荡荡。」((0,1) 东侧内墙—— - 内墙方位特征需与地图核对后定稿)、`empty_3a/3b` 同理。每句给一个方向 - 锚 + 一个可分辨特征,四间空房从此声学可区分。 - - 触发:进入空房首访(visited 集合扩到空房坐标);震动/earcon 不动。 -- **宪法条款**:00 §3.2(整句);归因真话(方位特征写死前按房间图逐间核对); - 14 裁定 7(描述句走语音通道)。 -- **拥挤评估**:每房一生一次,全局 +≤4 句;单击检查路径不变。对节奏零影响。 - 风险:empty_* 改词会动既有资产文本(playtest 断言若引用文本需同步登记)—— - 方案 a 完全绕开此风险。 - -### B6 · 引导 busy 窗口手势缓存(逻辑修复) - -- **问题(证据)**:live 行 30-35:`g2_wall` 播放中(37.60-43.28)我滑了一下 - (41.57),只有引擎 [10] 轻震——墙没响、G2 计数没走,**静默吞手势**。若玩家 - 信了这一下、只再补一滑,G2 永远不满足"滑两下",引导卡死(虽有 20s 看门狗 - 兜底推进,但那是防资产缺失的,不是交互语义)。P1 特别检查项"失败可否归因" - 判半不过。 -- **目标状态**:busy 窗口内的首个手势在窗口结束后被补判(或明确拒绝并给 - 反馈)。 -- **方案**:`_guideBusy` 期间缓存首个非跳过分手势,推进语音播完(done()) - 后立即按新步骤判定:若缓存手势对新步骤合法则直接作答(玩家体验为"我的 - 输入没丢");不合法则按新步骤的答错路径处理(有纠正语音,可归因)。缓存 - 仅 1 个、只存活到本步骤解锁,杜绝跨步幽灵输入。 -- **宪法条款**:00 §5.4「做了一个手势 3 秒内必须有反馈」(当前 busy 吞手势 - 违反此条,本条是合规修复);14 裁定 5(引导链结构不动)。 -- **拥挤评估**:零资产、零新增语音;只改引导期手势时序。引导链被 playtest - 断言冻结的部分(finishVoices(3)→PLAY 等)不受影响——缓存只在 busy 窗口 - 内生效,脚本化玩家的手势本就在 turn cue 后送达。 - -### B7 · 结算链单击守卫(逻辑修复) - -- **问题(证据)**:live 行 253-275:`inspect_gate_ready` 被随后的逃脱双击 - 截断(行 256-257,属正常链式替换);更严重的是我的习惯性单击(369.22)在 - 结算链 escape 播到 10.7/15.3s 时直接触发重开——`time_band1`(用时)与 - `again` 两句被永久吞掉(行 263-275)。P3 定为 P0:"结算是我唯一确认打完了/ - 打得如何的窗口,一碰就碎"。 -- **目标状态**:结算链播完前,ENDING 单击不立即重开。 -- **方案**:`_escape()` 起设 `_settleBusy=true`(结算链 onDone/30s 兜底处 - 复位);ENDING 单击在 `_settleBusy` 期间忽略(或在 full 档播一句既有 - `again` 前句?—— 不新增语音,直接忽略+引擎 confirm 震已有)。`again` 句 - 本身已含操作指引,链播完后再单击重开的原语义不变。30s 资产缺失兜底保留 - (防静默死锁)。 -- **宪法条款**:00 §5.4(单击 3s 内反馈——忽略路径有引擎 confirm 震兜底, - 合规);00 §3.2(结算整句分档的完整到达权);14 裁定 4(不顶句、不丢句)。 -- **拥挤评估**:零资产;只影响结算后前 ~15s 的重开时序。结算链播完 → 单击 - 重开 → replay 的既有闭环在转写里完整存在(mock 行 144-149),本条只是 - 把闭环的起点从"escape 第一句"推迟到"again 播完"。 - -### B8 · 跳过引导后补播目标句(零资产) - -- **问题(证据)**:`guide_skip` 路径(live 行 61-74)与 REOPEN 单击直进 - 路径都只播 `start_room`(手势总览)——**目标陈述 g7_goal 只在引导 G7 链尾 - 出现一次**,跳过的玩家(恰是最需要目标的一群,P1/P3 共识)永远听不到 - "找到能用这把钥匙打开的东西"。且跳过后阶段=key、身上无物品,60s 内只有 - 意象级 L1(行 75-86)。 -- **目标状态**:任何路径进入 PLAY 时,玩家都至少听过一次目标句。 -- **方案**:`guide_skip` 确认句后、`_enterPlay` 前补播 `g7_goal`(资产现成, - 链式 `_speak`);REOPEN 单击直进路径同样在 `start_room` 后接 `g7_goal`。 - 二周目老手可单击跳过该句(skippable 默认 true,符合 12 号文档——教学 - 义务句已随首局完成,此处是状态告知不是教学)。 -- **宪法条款**:00 §3.2;14 H1 注(g7_goal 不接方位句的红线仅冻结于**引导 - 链内**——本方案接在跳过确认链后,不触碰被冻结的引导链断言);01 号 §2.1 - 设计意图(G7 目标陈述的全局可达)。 -- **拥挤评估**:两句短链、每会话最多一次;二周目玩家 1 次单击即可跳过。 - 对节奏影响 ≤5s,且只发生在"信息最空白"的路径上。 - -### B9 · L2 台词顺带修订(不推荐本轮做,留档) - -live 行 288-294 可见 L2/L3 句仍用"西南角/西北角/东南角"绝对方位词——对 -从未建立坐标系的玩家,绝对角落词不如相对方向可用(L3 带路已经改用相对 -滑动方向)。但 L2 明示句的改词会动 12 句资产与全部相关断言,且 L3 已兜底 -带路,**收益/成本比低,列为后续轮候选**,不在本轮实施清单内。 - -## 4.2 盲区 → 方案对照表 - -| 盲区 | 方案 | 新语音资产 | 逻辑改动 | 证据 | -|---|---|---|---|---| -| S1/S1b 方位(撞墙/平时) | B1 | +4 句 | 撞墙链路接播+限频 | live 行 40-55;P1/P3 P0 | -| 相对方位半盲区(无目标名) | B2 | +3 句 | 三处接线点换 id | live 行 112-116/156-160/201-208 | -| S3 全局进度无渠道 | B3 | +3 句 | 新计时器+触发 | 用户反馈;live transit 段 | -| S4/S4b 目标重述/查询泄漏 | B4 + B8 | 0(复用) | 兜底换 id;跳过链补播 | live 行 61-74/161-178/230-243 | -| S5 空房不可区分 | B5a/b | 0 或 +3~4 句 | 首访描述接线 | live 行 90/182/120/212 | -| S6 busy 吞手势 | B6 | 0 | 缓存补判 | live 行 30-35 | -| S7 结算链截断 | B7 | 0 | 守卫 | live 行 253-275 | - -## 4.3 实施清单(F 类编号,**仅登记,未实施**) - -| # | 内容 | 资产管线步骤 | 断言登记建议 | +| 步 | 语音意图 | 等待 | 答对 | |---|---|---|---| -| F1(B1) | 撞墙方位句 `wall_dir_l/r/u/d` ×4,撞墙链路接播 + 同向 10s 限频 | `tools/gen_voice_dual.py --game escape --only wall_dir_l,wall_dir_r,wall_dir_u,wall_dir_d` → `--convert` → `check_all_assets.py` → `extract_voice_text.py` 刷新 | escape playtest:撞墙后方位句到达/限频生效/移动打断作废;guide.test 不受影(PLAY 期行为) | -| F2(B2) | `gd_cabinet_dir` / `gd_box_dir` / `gd_gate_dir` ×3,替换三处 gd_room_* 接播(原句保留) | 同 F1 管线,3 句 | playtest:三处进展链尾新句到达且方位正确;原 H1 断言(gd_room_* 映射表)保留不动 | -| F3(B3) | `gd_prog_1/2/3` ×3 + 无进展 150s 活跃计时器(≥5 手势闸门、每段限 1 次、队列语义) | 同 F1 管线,3 句 | playtest(guide=off):计时器不误触(挂机不触、进展后清零、限 1 次);guide.test:队列不顶句、作废语义 | -| F4(B4) | 双击兜底第二句 stuck_*_2 → stuck_*_1 | **零资产** | playtest:兜底链内容变更需逐条同步(现有断言若期望 stuck_*_2 必改);L2 计时器触发路径断言不动 | -| F5(B5a) | 空房首访补播 empty_*(零资产先行版) | 零资产 | playtest:首访到达/重访不重复/单击检查语义不变 | -| F6(B5b,可分期) | empty_* 改词带方位特征 ×3~4 | 同 F1 管线;**改词前逐间核对房间图方位特征(归因真话)** | 文本断言同步;双耳一致性 | -| F7(B6) | 引导 busy 手势缓存补判 | 零资产 | guide.test:busy 窗口手势缓存→解锁后补判/答错路径;既有引导推进断言零改动红线 | -| F8(B7) | 结算链单击守卫 `_settleBusy` | 零资产 | playtest:结算链完整播完(time_band/again 到达)后才可重开;30s 兜底仍生效 | -| F9(B8) | 跳过引导/REOPEN 直进补播 g7_goal | 零资产 | playtest:两条路径 start_room 后 g7_goal 到达;二周目单击可跳过 | +| G0 | 黑暗。朝任意方向,滑一下。 | 任意滑(可 1.2s 无操作自动进 G1 的 as-built 可保留为兜底) | 脚步 + 对 | +| G1 | 再活动 | 任意滑 | 脚步 | +| G2 | 再滑两下试试墙 | 滑×2(可伪撞墙) | 闷响+方位 | +| G3 | 向左滑 | 左滑 →(0,1) | 脚步 | +| G4 | 向下滑 | 下滑 →(0,2) | storage amb | +| G5 | 单击检查 | 单击拾钥匙 | pickup + 钥匙句 | +| G6 | **删除** | — | — | +| G7 | 找能用钥匙开的东西。迷路按住屏幕。 | 长按一次后 PLAY | 帮助句 | -统一收尾(每轮 F 实施完必做):`check_all_assets.py` 全绿 → 6 playtest -(guide=off)断言对账(escape 现有 103 条,F4/F5/F7/F9 会改其中若干, -须逐条登记变更原因)→ `guide.test.mjs` 增补 → live 复跑一局验证新句在 -真实混音中可闻(B2/B3 短句尤其要做 ASR 层判定)。 +- 路线写死与地图一致。越轨:「先跟我走。」 +- **下滑不是跳过引导**(下滑=向下走)。回访者开场单击跳过教案。 +- `_guideBusy` 缓存首个手势,结束后补判(B6 保留)。 + +### 2.3 手势语义(目标) + +| 手势 | GUIDE | PLAY | ENDING | MENU(阶段 2) | +|---|---|---|---|---| +| 四向滑 | 按步作答 / 移动 | 移动;撞墙闷响+`invalid`+方位 | `invalid` | 左右切项 | +| 单击 | G5 拾取等 | 检查+使用;SPEAKING 跳句 | 链完后重开;守卫期 `busy` | 确认 | +| 长按 | 帮助 | 我在哪(位置→目标→一个动作) | 重听结算 | 重听说明 | +| 双击 | 当单击 | 当单击(**不是使用**) | 当单击 | 当单击 | +| 下滑 | G4 向下走 | 向下走 | 打开菜单 | 关闭 | + +废止:PLAY 双击使用/兜底 L2 答案;帮助=双击;菜单单击循环+双击执行;对局长按=旁白加速。 + +### 2.4 迷茫三级(取代 60/120/180 原文轰炸) + +信号与权重见 26 号 §4.5(连续无效、乱滑、失败、静默)。探索静默可从约 40s 起计。达到 2 分触发,15s 冷却。 + +| 级 | 做什么 | 用哪些旧资产 | +|---|---|---| +| 1 | 一句短目标/方位,不原样再念 | `nav_*`、`gd_prog_*`、stuck L1 意象(去重) | +| 2 | 只放行第一导航方向,其余滑=`invalid` 不移动 | `sys_only_*` 口径;**不要**播 stuck L2 全文答案 | +| 3 | 「听我做一次。」朝目标走一步 | 脚步 + `sys_now_you`;**不要**每 60s 重播 stuck L3 带路长句 | + +升到 3 后只留房间 amb。长按主动问:完整短状态句,前缀「再说一遍:」仅在玩家请求时。 + +**代码 as-built 仍可能跑** `_checkStuck` 60/120/180 与双击兜底 L1——落地时拆除或改接 Confusion,勿把旧计时器当设计。 + +### 2.5 关键路径(目标) + +引导完整:G0–G5 + 长按 → PLAY 已持钥匙 stage=cabinet → 开柜→修电→开门。 + +分支: + +1. 回访者跳过教案:直进 PLAY,必须补播目标句(B8 保留)。 +2. 撞墙:闷响 + 方位句(每向整局语音一次)。 +3. 空房首访 `empty_*`(B5a)。 +4. 重访关键房:amb + 单击复核状态。 +5. 结算:链完可重开;`_settleBusy`(B7)。 +6. 菜单:暂停语音队列;左右滑+单击。 + +### 2.6 信息渠道(目标分层) + +| 层 | 管什么 | +|---|---| +| 语音 | 要做什么、发生了哪件具体事 | +| 内容音 | 脚步/墙/机关/房间身份 | +| 系统回执 / turn / 等待或 amb | 收到没有、现在能否走 | +| BGM drone | 还在暗室 | + +震动只补充。禁止只震。游戏层不自研第四套 earcon 词表。 --- -# 阶段四 · 实施勾账(escape-state round2,2026-09-15) +## 3. 信息账本(资产保留,语义改挂) -> §4.3 清单已从"仅登记,未实施"转为**已实施**(除 F6 按方案"可分期"留档, -> 见实施裁定 1)。验证档案:`research/escape-state/round2/`(mock 转写 + -> live 真机一局 38 段 OK 37 / SFX_BGM_ONLY 19 / 零截断,S1-S7 逐条核对)。 -> 新断言全部登记在 `web/escape/test/playtest.mjs`(103 → 235 条全绿)与 -> `web/harness/guide.test.mjs`(120 → 132 条全绿);既有断言变更 3 处, -> 逐条登记于下表"断言变更"列。 +### 3.1 语音 id 三类(80 句量级,资产对照) -| # | 落地 | 断言登记(场景.条数) | 断言变更 | live 证据(round2/live-transcript.md 行号) | -|---|---|---|---|---| -| F1(B1) | ✅ `wall_dir_l/r/u/d` ×4 双性别已生成落盘;`_move` 撞墙链路 `wall` 播完接播、同向 10s 限频、被打断作废(game.js `_move`) | playtest G×13(引导期不播/接播顺序/打断作废/限频与恢复)+ 资产校验 F 段扩 wall_dir 族 | 无 | 行 141-143 wall_dir_u、行 166-168 wall_dir_l、199.5s 限频 | -| F2(B2) | ✅ `gd_cabinet_dir/gd_box_dir/gd_gate_dir` ×3 替换三处接播;`_playGoalDir(target, namedId, expectedId)` 异常路径回落 `gd_room_*`(原句保留) | playtest H×12(三处到达+顺序+不再播 gd_room_*+回落+同房间不播);guide.test ⑩c 换线 + 回落断言 | guide.test ⑩c 三处接线断言由 gd_room_* 改为 gd_*_dir(映射表单元断言保留不动) | 行 231-233 gd_box_dir、行 306-312 gd_gate_dir | -| F3(B3) | ✅ `gd_prog_1/2/3` ×3 + `_checkStuck` 平行通道(150s 无进展 ∧ ≥5 手势 ∧ stage≠key,每段限 1 次,队列/作废语义同卡关提示) | playtest I×11(key 不走/闸门/触发/限 1 次/进展复位/box 段再触/入队不顶句/作废计入不重发) | 无 | 行 265-268 gd_prog_2(活跃 154s 触发,期间 L1-L3 零误触) | -| F4(B4) | ✅ `_useItem` 兜底第二句 `stuck_*_2`→`stuck_*_1`,L1 播了即记账沿用 `_stuckShown` 去重 | playtest J×8(背包+L1/不直通 L2/记账/再兜底不重复/60s 不重复 L1/120s L2 仍到达) | playtest 场景 A 兜底断言 stuck_key_2→stuck_key_1(+不播 _2 反向断言) | 行 180-182 use_fail+stuck_cabinet_1 | -| F5(B5a) | ✅ 空房首访补播 `empty_*`((1,0)→1/(0,1)→2/(1,2)→3/(2,0)→3 复用),`_freshEmpty` 实现"本房间内单击不重复首访句";不算进展事件 | playtest K×9(首访到达/非进展事件/房内单击不重复/重访零语音/重访单击照常/四房映射) | 无(单击检查语义不变) | 行 115-118/189-192/199-202 三间首访 | -| F6(B5b) | ⏸ **留档**(实施裁定 1) | — | — | — | -| F7(B6) | ✅ `_guidePending` 缓存首个非跳过分手势,`_advance` done() 解锁后按新步骤补判(fromCache 绕过下滑跳过检查);跳过分手势不缓存;G6 链尾离 GUIDE 缓存作废 | playtest L×15(不当场判定/补判作答/答错路径/不触发跳过/仅 1 个/跳过分手势不缓存/引导链完整);guide.test ⑩d×7(full 档同款) | 无(既有引导推进断言零改动) | live-events 82.3s 缓存→87.9s 补判 | -| F8(B7) | ✅ `_settleBusy` 守卫:`_escape` 挂载,`_startEndingBgm`(链完/30s 兜底汇合点)解除;ENDING 单击守卫期忽略 | playtest M×10(守卫期不重开/链完整 time_band+again/解除后重开/again 缺失 30s 兜底) | 无 | 行 336-347 习惯性单击被守卫挡下,time_band3+again 完整到达 | -| F9(B8) | ✅ `_skipGuide` 链 guide_skip→g7_goal→_enterPlay;REOPEN 单击 `_enterPlay(true, true)` start_room→g7_goal;`_skipGuide` 期间 guideStep=7(裁定 4) | playtest N×6(直进顺序/可单击跳过)+ A2/B 场景跳过链更新(g7_goal 到达、顺序、两句后再进 PLAY) | playtest A2/B 跳过断言由"一句后 PLAY"改为"guide_skip→g7_goal 两句后 PLAY" | g7_goal OK sim=1(行 149.5s) | +**A 状态**:`start_room`、`room_*`、`empty_*`、`inspect_*`、`inv_*`、`wall`/`wall_dir_*`、`gd_next_fuse`、`gd_*_dir`、`gd_prog_*`、`time_band*`、`menu_*`、`reopen_prompt`。长按帮助改走短状态句(可复用 gd_prog / 新 lp_*)。 -统一收尾执行记录:`check_all_assets.py` 全项目 15 游戏全绿(escape voice 各 90 -id 双性别一致);escape playtest 235/0、guide.test 132/0、其余 14 款 playtest -回归全绿、engine-voice 19/0 与 engine-input 72/0 无连带;live 复跑一局 -(端口 8145,打完杀净)S1-S7 每个时刻现在都有落点(round2/README.md 对照表)。 +**B 反馈**:`g1_done`/`g5_key`、`found_key`/`unlock_cabinet`/`fix_box`/`escape`、`replay`/`again`、引导纠正 `guide_wrong_*`/`guide_blunt_*`/`guide_offpath`。`use_fail` 仅「这里没有可做的」;不再接 L2 答案。 -## 实施裁定记录(编号自本轮起,接续追加不改旧文) +**C 叙事教学**:`g0_*`/`g1_move`/`g2_wall`/`g3_left`/`g4_down`/`g5_inspect`/`g7_goal`。**`g6_doubletap` 停用**。`skip_hint` 若仍教「下滑跳过引导」则停用。 -1. **F6(B5b)分期留档**:B5b 草案台词未定稿——`empty_2` 东侧内墙方位特征 - 原文标注"需与地图核对后定稿"、`empty_3a/3b` 无台词草案,无法按"台词与 - 草案逐字一致"的管线铁律生成;且 B5 原文即"建议 a 先行"、F6 标注"可分期"。 - 本轮只落地 F5(B5a 零资产版),B5b 连同 B9(L2 角落词改词)一并列为 - 后续轮候选。 -2. **B4 去重双向化**:方案只规定"已在 `_stuckShown` 记过 L1 的进展段不重复 - 播";实施把兜底播出 L1 也**记账**(播了即加入 `_stuckShown`),否则 60s - 计时器会把玩家刚听过的同一句意象暗示再播一遍(去重只做单向等于没做)。 - 附带效果:L1 整级(含 `stuck_ask`)跳过,L2/L3 阶梯与触发时点不变 - (playtest J 冻结)。 -3. **B3 作废计入已播**:待播 `gd_prog` 在排队期间被玩家操作作废后仍计入 - "每进展段限 1 次"(触发即记账)。方案"每进展段限 1 次"若按播出记账, - 5s 轮询会在条件持续成立时立即重发,形成骚扰循环;按触发记账与该条 - "全局限 3 句"的拥挤评估一致(playtest I⑧ 冻结)。 -4. **B8 跳过链期间 guideStep=7**:`_skipGuide` 的两句链(guide_skip→g7_goal) - 播放期间相位仍是 GUIDE,原 guideStep 下单击/滑动会被当成引导作答(既有 - 竞态:skip 链中单击可误推 G1)。落地把 guideStep 置 7(G7 等待语义: - 手势全忽略),链中输入 robustness 与 G7 自然链尾对齐;语音缺失兜底计时 - 5s→9s(覆盖两句链)。 -5. **B5a 两个二选一**:(2,0) 选"复用 empty_3"(方案原文认可"两间'远处水滴' - 空房对称分布也是合法的记忆点");"单击时不再重复首访句"以 `_freshEmpty` - 实现——仅首访句刚播过的那个房间内抑制同句,离开即恢复单击播句, - 重访后单击检查语义完全不变(playtest K 冻结)。 -6. **B6 缓存手势绕过下滑跳过**:方案"不合法则按新步骤的答错路径处理" - 意味着缓存的下滑在可跳过步骤上也不再触发 `_skipGuide`(fromCache 绕过 - skip 检查)——这正是 live round1 S6 事故(答案被吞后下滑被当成跳过)的 - 修复点;实时注入的下滑跳过语义不变(playtest L⑤/guide.test ⑩d 冻结)。 -7. **B1 限频记账点**:同向 10s 限频按**方位句实际开播时刻**记账;仅调度 - (wall 接播注册)未播出即被打断的不占额度(playtest G③"被作废的方向 - 补撞后正常接播"冻结)。 +`stuck_*_1/2/3`、`nav_udlr`、`stuck_ask`:可拆进迷茫 L1 或停用周期广播。禁止游戏主动 30s 内原样重播同一 id。 -## 盲区清单核销(§3.2 → round2) +### 3.2 状态维度(目标覆盖) -S1/S1b(B1)、相对方位半盲区(B2)、S2(B5a)、S3(B3)、S4/S4b(B4+B8)、 -S5(B5a,B5b 留档)、S6(B6)、S7(B7)——live round2 逐条核对证据见 -`research/escape-state/round2/README.md`,每个盲区时刻现在都有落点。 +| 维度 | 怎么给 | +|---|---| +| 绝对方位 | 开局中央一句;长按含房间名 | +| 相对方位 | 进展链 `gd_*_dir`;迷茫 L1 短滑向 | +| 撞墙哪面 | `wall_dir_*` | +| 来过吗 | 首到描述;重访 amb 不同+单击复核 | +| 空房 | 首访 empty_* | +| 谜题进度 | 长按一句;`gd_prog_*` 作 L1 不泄题 | +| 主动查询 | **长按**,不是双击 L2 | +| 物品 | 检查句 / 长按带「身上有钥匙」 | +| 互动物 | 单击检查 | +| busy 手势 | 缓存补判 + `busy` 回执 | +| 结算完整 | `_settleBusy` | + +--- + +# 阶段三 · 状态改进(B1–B8)在 26 号下如何用 + +取证仍在 `research/escape-state/`。下列条目 **地图与资产保留**,交互改挂: + +| 项 | 保留 | 改挂 | +|---|---|---| +| B1 撞墙方位 | `wall_dir_*` + 同向限频 | 每向整局语音一次后只闷响+`invalid` | +| B2 目标名+方位 | `gd_cabinet_dir` 等 | 仍挂进展链尾 | +| B3 活跃迷路进度句 | `gd_prog_*` | **并入迷茫 L1**,取消与 60/120/180 平行的 150s 第二广播 | +| B4 查询降级 | 不泄 L2 | 查询入口改为长按;双击兜底删除 | +| B5a 空房首访 | empty_* 映射 | 保留 | +| B6 busy 缓存 | 补判 | 保留;回执 `busy`/`ok` | +| B7 结算守卫 | `_settleBusy` | 守卫期回执 `busy` | +| B8 任何路径进 PLAY 有目标句 | g7_goal | 保留;回访者可跳 | + +B9 角落词改词、B5b 空房改词:仍可选分期,不阻塞 26 号落地。 + +F1–F9 已实施的代码事实(round2 勾账)仍是 **as-built 证据**;其中 F4 双击兜底、F3 平行 150s 计时器、卡关 60/120/180 **在交互目标里废止或改接 Confusion**。playtest 条数以落地后 13 号 + 26-apply-escape 为准。 + +实施裁定 1–7(F6 留档、B4 记账、B3 作废计入、skip 链 guideStep=7、empty 映射、缓存绕过跳过、B1 开播记账)在 **不与单击使用/长按帮助/迷茫三档冲突** 的范围内可继续当实现细节。 + +--- + +## 4. 废止清单(本文曾作为交互权威的部分) + +| 废止 | 改为 | +|---|---| +| 本文「以代码手势为准」盖过 01/26 | 地图 as-built;交互 26/01 | +| 双击使用;G6;双击兜底 L2 | 单击检查+使用;长按状态 | +| 无操作 60/120/180 原样 L1/L2/L3;L3 每 60s 重播 | 迷茫三级 + 三档提示 | +| 引导下滑跳过;上滑重听整段引导 | 下滑=向下;回访者单击跳过教案 | +| 菜单单击循环/双击执行 | 左右滑+单击 | +| 只震不响;提示永不打断 | 回执;玩家一动非 critical 即停 | +| 帮助藏在「错误的地方双击」 | 长按,首 3 分钟教一次 | + +房间格点、amb id、谜题旗标、最短路径坐标、wall_dir/gd_*_dir/empty 映射、结算守卫、busy 缓存:**未改。** diff --git a/docs/design/26-apply-escape.md b/docs/design/26-apply-escape.md index 23388ecc..890fb81d 100644 --- a/docs/design/26-apply-escape.md +++ b/docs/design/26-apply-escape.md @@ -31,7 +31,7 @@ | 探索(ACTION) | 检查+使用(环境音/描述即反馈) | 移动=脚步声(silent-ok);撞墙=闷响+invalid+归因 | 帮助(目标+方位) | 按单击处理 | | 描述在播 | 跳过这句 + ok | 打断并移动 | 打断,帮助 | 按单击处理 | | 结算 | 重新开始 + ok | invalid + inv_settle | 重听结算 | 按单击处理 | -| 菜单 | 换一项 + 播报 | 右=确认 / 左=上一项 / 其余 invalid | 重听菜单说明 | 按单击处理 | +| 菜单 | 确认当前项 + ok | 左右滑换项;上下滑 invalid(非移动) | 重听菜单说明 | 当单击 | | 迷茫 L2 期间 | 照常 | 只放行接管方向,其余 invalid(不移动) | 照常 | 照常 | ## 5. 等待点配置表(引导期)与探索期接管 diff --git a/docs/design/26-apply-simon.md b/docs/design/26-apply-simon.md index b6e7bb92..99f55d86 100644 --- a/docs/design/26-apply-simon.md +++ b/docs/design/26-apply-simon.md @@ -38,7 +38,7 @@ | 播报/序列播放(BUSY) | busy 回执 | busy 回执 | busy 回执 | busy 回执 | | 答题(YOUR_TURN) | 作答(音色回声) | 作答 | 状态句(轮次段位,不闭窗) | 未入池=invalid;入池=第 6 音色 | | 结算 | 重新开始(言行一致) | invalid + 解释 | 重听结算 | (阶段3)重听结算 | -| 菜单 | 换一项 | 右=确认/左=上一项 | 重听菜单说明 | 按单击处理 | +| 菜单 | 确认当前项 | 左右滑换项 | 重听菜单说明 | 当单击 | ## 5. 等待点配置表 diff --git a/docs/design/26-apply-storyline.md b/docs/design/26-apply-storyline.md index 6234019c..b36e0fae 100644 --- a/docs/design/26-apply-storyline.md +++ b/docs/design/26-apply-storyline.md @@ -1,7 +1,7 @@ # 26-apply · 《声命线》重构设计单(26 号文档落地) -> 按 26 号文档附录 C 填写。本文是 storyline R3 的施工依据与验收基准; -> 交互规则的权威来源是 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md)。 +> 按 26 号文档附录 C 填写。声命线无独立 01 号正文,**本文即施工设计单**。 +> 交互权威:[26-audio-interaction-playbook.md](26-audio-interaction-playbook.md)、[00-global-ux.md](00-global-ux.md)。菜单=左右滑换项 + 单击确认。 ## 1. 一句话核心动作 @@ -33,7 +33,7 @@ | 等你继续 | 继续 + ok | invalid(inv_nosel) | 我在哪 | invalid | (阶段3=重听) | busy | | 等你选择 | 再听选项(「再说一遍:」+ 完整句,hold) | 抉择 + ok | 「正在抉择。左右滑。」 | invalid | (阶段3=重听) | 计入迷茫 | | 结算 | 重新开始 + ok | invalid(sys_hint_again_full) | 重播状态句 | 菜单(阶段2) | 按单击处理 | 只处理第一下 | -| 菜单 | 换一项 + 播报 | 右=确认 / 左=上一项 | 重听菜单说明 | invalid | 按单击处理 | busy | +| 菜单 | 确认当前项 + ok | 左右滑换项并报名 | 重听菜单说明 | — | 当单击 | busy | 验收:无空格;无「没有声音」的格子;连点无破坏性效果。 diff --git a/docs/design/26-audio-interaction-playbook.md b/docs/design/26-audio-interaction-playbook.md index 95871683..8b7900cc 100644 --- a/docs/design/26-audio-interaction-playbook.md +++ b/docs/design/26-audio-interaction-playbook.md @@ -9,6 +9,8 @@ > 6. 改造现有游戏按 P0~P3 的顺序做,P0 是半天级的小改动,效果最大(§5)。 > > 本文和 `research/02-methodology/methodology.md` 的关系:那一份是基于文献、经模拟玩家验证的方法论。本文补的是它漏掉的部分——真实新玩家的行为:会乱点、没耐心、不会先听完再操作。两份冲突时,以本文为准。 +> +> 本目录其余设计文档已按本文重写。项目施工口径见 [00-global-ux.md](00-global-ux.md);索引见 [README.md](README.md)。 --- diff --git a/docs/design/README.md b/docs/design/README.md index 39bf7b70..d4a82096 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -1,63 +1,72 @@ -# SoundGame 设计文档(5 轮迭代全记录) +# SoundGame 设计文档 -本目录是纯音频游戏合集的设计规范与审查档案。开发 agent 开工前必读对应文档开头的 -"给开发者的实现要点摘要";全局规范(00)对任何游戏改动都生效。 -后续路线见 [roadmap.md](roadmap.md)。 +本目录是纯音频游戏合集的设计规范与审查档案。 -## 设计规范(施工依据) +**交互权威(2026-10 起)**:[26-audio-interaction-playbook.md](26-audio-interaction-playbook.md)。全局施工口径:[00-global-ux.md](00-global-ux.md)。两份与旧豁免表、旧试玩记录冲突时,以 26 号为准。 -| 文档 | 内容 | 对应用户反馈 | -|---|---|---| -| [00-global-ux.md](00-global-ux.md) | 全局交互规范(终审修订版):设置系统、haptics 震动语言(10 震型)、旁白整句化、手势约定与模态菜单、用户系统 API | ⑥⑦ + 全局需求 | -| [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) | 纯音频交互第一性原理与落地手册:对话模型、16 原则、发言权管理器/输入回执/新手手势/三档提示/迷茫检测/语音优先级、三张设计单模板、P0-P3 改造顺序、乱点机器人与真人冷启动验证 | 真人试玩「搞不清状态、点两下就重复、不知道怎么玩」 | -| [26-apply-storyline.md](26-apply-storyline.md) / [26-apply-escape.md](26-apply-escape.md) / [26-apply-simon.md](26-apply-simon.md) | 26 号手册首批落地(R3):三款游戏的设计单(附录 C:状态×手势矩阵/等待点/逐秒脚本/失败表/台词类别)+ 引擎新模块(floor/receipts/confusion/hints/novice/pipeline) | 同上 | -| [01-escape-redesign.md](01-escape-redesign.md) | 《暗室》重设计:引导式开场、三级卡关提示、线索音可重听 | ⑤ | -| [02-simon-redesign.md](02-simon-redesign.md) | 《音序回响》重设计:去 BGM、旁白整句化、交互式教学、复活机制(含双击语义裁定) | ⑥⑦ | -| [03-rhythm-rebuild.md](03-rhythm-rebuild.md) + [03-rhythm-voice-ids.md](03-rhythm-voice-ids.md) | 《节拍行者》重构施工稿:跟随领奏玩法、三首完整谱面、判定契约、31 句台词表 | ⑧ | -| [04-space-rebuild.md](04-space-rebuild.md) + [04-space-voice-ids.md](04-space-voice-ids.md) | 《盲航》重构:交互式演练、同向闪避、60 秒保护期、52 句台词表 | ⑨ | -| [05-rpg.md](05-rpg.md) + [05-rpg-chapter1.md](05-rpg-chapter1.md) + [05-rpg-ch1-voice-ids.md](05-rpg-ch1-voice-ids.md) + [05-rpg-addendum.md](05-rpg-addendum.md) | 新 RPG《剑与声》:总设计 + 第一章施工稿(场景图/战斗状态机/数值/存档/资产 prompt)+ 120 句台词表 + 增补(战斗震动配合、无声前摇协议、层叠规则) | ⑩ | -| [23-hunt.md](23-hunt.md) + [23-hunt-voice-ids.md](23-hunt-voice-ids.md) | 《围猎》2-4 人实时联机协作守卫(纯音频 MMORPG 路线第一阶段切片):零依赖 WS 协议 + 服务端权威战斗、bot 行为与补位、音频拥挤控制 R1-R8、引擎 opts.pan 改动 + 84 台词 id(60 旁白 ×2 性别 + 24 bot 喊话) | 长线方向 | +开发开工前:读 26 号 + 00 + 该游戏正文开头的「给开发者的实现要点摘要」和「设计单」。路线见 [roadmap.md](roadmap.md)。 -## new8 系列设计文档(8 款新 RPG,research/new8/00-program.md) +## 全局规范 | 文档 | 内容 | |---|---| -| [15-radio.md](15-radio.md) + voice-ids | 《不眠频率》深夜电台热线叙事(批次 1 交付,4/4 人设满分档):9 抉择 4 结局、口碑经济、电话滤波资产侧带通 | -| [16-dungeon.md](16-dungeon.md) + voice-ids | 《回声地窟》听声辨位 Roguelike(批次 1 交付,0/4→4/4 翻案):脉冲回声、seed 生成、双轨看门狗、元进度遗产 | -| [17-abyss.md](17-abyss.md) + voice-ids | 《深海回音》深海救援叙事(批次 2):声呐双通道仲裁、氧气软失败经济(512 组合穷举验算)、148 句 | -| [18-detective.md](18-detective.md) + voice-ids | 《雨夜专案》本格推理(批次 2):证词矛盾=数据单源推导、评级 S-C、二周目变体案、158 句 | -| [19-cipher.md](19-cipher.md) + voice-ids | 《暗码》1940 密战叙事(批次 3):摩尔斯运行时合成解码、暴露度经济、三层兜底、139 句 | -| [21-keeper.md](21-keeper.md) + voice-ids | 《守塔人》孤岛灯塔叙事(批次 4):时空双线签名(风暴/老唱片)、回忆插叙三段、信任×真相双标量、148 句 | -| [22-bridge.md](22-bridge.md) + voice-ids | 《星环舰长》舰桥指挥(批次 4):四象限系统状态音+告警仲裁 R1-R8(双危机穷举)、危机事件卡、忠诚账本、评级 S-D、154 句 | -| 20 / 22 | 《深夜茶馆》《星环舰长》(批次 3/4,设计进行中,落地后补登记) | +| [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) | 第一性原理、16 原则、六个引擎模块、三张设计单模板、P0~P3、验证 | +| [00-global-ux.md](00-global-ux.md) | 设置、回执+震动、旁白写作、用户系统、新手三手势、菜单、键盘、Floor | +| [10-feedback2-state-bgm.md](10-feedback2-state-bgm.md) | 状态 BGM;整链 duck;等待声不走 BGM 层 | +| [12-skip-and-longpress-ux.md](12-skip-and-longpress-ux.md) | 跳句、400ms 防连点、长按加速 vs 长按帮助 | +| [13-playtest-criteria.md](13-playtest-criteria.md) | 乱点机器人、矩阵、真人冷启动、玩法回归 | +| [14-guide-layer.md](14-guide-layer.md) | Floor / 三档提示 / 迷茫接管;旧 gd_* 资产对照 | -各 voice-ids 文档的「键盘变体」小节由 KEY_LINES 程序化生成(tools/gen_voice_dual.py 单源),含 PC 键盘措辞(按空格/按回车/按方向键)。 +## 首批落地设计单 -## 审查档案(各轮落实度与裁定记录) +| 文档 | 游戏 | +|---|---| +| [26-apply-storyline.md](26-apply-storyline.md) | 声命线 | +| [26-apply-escape.md](26-apply-escape.md) | 暗室 | +| [26-apply-simon.md](26-apply-simon.md) | 音序回响 | + +各游戏正文已按同一附录 C 重写,与上表重复处以**游戏正文 + 26 号**为准。 + +## 游戏设计(26 号对齐版) | 文档 | 内容 | |---|---| -| [round1-review.md](round1-review.md) | R1 基建审查:6 处出入裁定(haptics 命名、voice 平铺回退、`_settings` 保留键等) | -| [round2-review.md](round2-review.md) | R2 暗室/音序审查:落实度 ~95%;裁决① simon 双击上下文判定、裁决② 全球模态菜单约定 | -| [round3-4-review.md](round3-4-review.md) | R3 节拍/R4 盲航审查:4 项裁定(结算末位语音、menu_main 文案、space 难度不回调、震动总量批准)+ 附裁 A(玩法手势优先的菜单折中模式) | -| [round5-notes.md](round5-notes.md) | 给 RPG 开发 agent 的返工注意级提示(看门狗/会话令牌/末位语境/震动去抖/层叠实现) | -| [roadmap.md](roadmap.md) | 后续路线:RPG 二三章扩展、storyline 打磨(菜单统一/knowTrap 死标记/角色音色)、真机听测 checklist、商业化前置 | +| [01-escape-redesign.md](01-escape-redesign.md) + [25-escape-map-flow.md](25-escape-map-flow.md) | 暗室:四向移动,单击检查+使用,长按问路 | +| [02-simon-redesign.md](02-simon-redesign.md) | 音序:音色渐进解锁,失败免费重听,帮助=长按 | +| [03-rhythm-rebuild.md](03-rhythm-rebuild.md) / [24-rhythm-drummer.md](24-rhythm-drummer.md) | 节拍:03 为历史领奏稿;**现行施工以 24 鼓手模式为准** | +| [04-space-rebuild.md](04-space-rebuild.md) | 盲航:同向闪避,高级手势后解锁 | +| [05-rpg.md](05-rpg.md) + chapter1 / addendum / [11-rpg-battle-rebalance.md](11-rpg-battle-rebalance.md) | 剑与声:前摇窗口再单击格挡 | +| [23-hunt.md](23-hunt.md) | 围猎:联机切片 + 新手三手势叠加 | -## 5 轮迭代计划总表(已执行完毕) +### new8 -| 轮次 | 主题 | 结果 | -|---|---|---| -| **R1** | 基建 + 设计 | 设计文档集;引擎 settings/haptics/voice 目录;大厅设置+登录;服务器 API;删 reflex | -| **R2** | 暗室 + 音序重设计 | 两游戏重写落地,语音 142 id ×2 性别;审查 round2-review | -| **R3** | 节拍重构 | 跟随领奏落地(3 曲目解锁制);审查 round3-4-review §1 | -| **R4** | 盲航重构 + RPG 资产 | 盲航重写落地(演练/保护期/同向);RPG 台词表与资产清单交付;审查 round3-4-review §2 | -| **R5** | RPG 开发 + 总打磨 | 进行中;开发提示 round5-notes;后续路线 roadmap | +| 文档 | 内容 | +|---|---| +| [15-radio.md](15-radio.md) | 不眠频率:叙事,单击继续,左右滑抉择 | +| [16-dungeon.md](16-dungeon.md) | 回声地窟:四向走,单击脉冲,长按问路 | +| [17-abyss.md](17-abyss.md) | 深海回音 | +| [18-detective.md](18-detective.md) | 雨夜专案 | +| [19-cipher.md](19-cipher.md) | 暗码 | +| [20-teahouse.md](20-teahouse.md) | 深夜茶馆 | +| [21-keeper.md](21-keeper.md) | 守塔人 | +| [22-bridge.md](22-bridge.md) | 星环舰长 | -横切约束(每轮验收都要查): +台词表 `*-voice-ids.md` 是**资产清单**(含键盘变体)。文案里的「双击重听」等旧口径已废止,须按各游戏正文重录;生成管线仍以 `tools/gen_voice_dual.py` 为 id 事实源。 -- 旁白:整句预生成、同角色 speaker+instruct 恒定、female/male 双目录、ogg+m4a 双格式(00 §3)。 -- 反馈:操作反馈一律震动(`engine/haptics.js` 10 预定义震型),iOS 静默降级不回退提示音(00 §2)。 -- 手势:默认映射 + 豁免表 + 模态菜单约定(00 §5),任何偏离必须在开场交互教学中讲清。 -- 教学:禁止"连续旁白讲完就开考";一切教学交互式,做错不惩罚、当场重教。 -- 设置:游戏内只读,改动只在大厅页(00 §1)。 -- 进度:data 自由格式 + 必带 `summary`,localStorage 兜底 + 服务器同步,`_settings` 为保留键(00 §4)。 +## 审查档案(历史,交互规则不作依据) + +| 文档 | 内容 | +|---|---| +| [round1-review.md](round1-review.md) | R1 基建出入(haptics 命名、平铺回退、`_settings`) | +| [round2-review.md](round2-review.md) | R2 暗室/音序;其中「双击确认菜单」已废止 | +| [round3-4-review.md](round3-4-review.md) | R3/R4 节拍/盲航工程教训仍可参考 | +| [round5-notes.md](round5-notes.md) | RPG 工程健壮性(会话令牌、语音链兜底);格挡已改为窗口内单击 | + +## 横切约束(每轮验收) + +- 交互:26 号 16 原则 + 00 手势宪法 + 13 合格指标。 +- 旁白:整句、同角色 speaker+instruct、双目录、ogg+m4a;指令 ≤12 字,叙事 ≤25 字。 +- 回执:ok/invalid/busy 必须出声;震动只是补充。 +- 教学:第一句动手;一次只教一个;成功后不灌补救。 +- 设置:游戏内只读。 +- 进度:`summary` 必填;退出存档;重进一句复述。 diff --git a/docs/design/roadmap.md b/docs/design/roadmap.md index bc918a6e..8f8cdc39 100644 --- a/docs/design/roadmap.md +++ b/docs/design/roadmap.md @@ -1,126 +1,94 @@ -# SoundGame 后续路线图(策划,最终轮) +# SoundGame 后续路线图(26 号对齐) -范围:R5(RPG 收尾)之后的事。分五块:RPG 内容扩展、storyline 打磨、真机听测、商业化前置、纯音频 MMORPG 路线。 -设计基准文档索引见 [README.md](README.md);审查结论见 round1/round2/round3-4-review。 +交互改造优先于内容扩展。权威:[26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) §5。设计索引:[README.md](README.md)。 --- -## 1. RPG《剑与声》第二/三章扩展计划 +## 0. 当前最高优先级:按 26 号改交互(文档已齐,实现未做) -第一章模式已定型(场景图 JSON + 战斗状态机 + 台词表三件套),二三章是**内容灌入**而非新开发。 +只改一款验证体感,再推广。建议顺序:`storyline` → `escape` → 其余。 + +| 档 | 做什么 | 验收 | +|---|---|---| +| **P0** | 新手关双击(`doubleTapWindow: 0`);回执三件套;单击 400ms 防连点;滑动打断语音 | 闭眼冷启动:点两下不再整段重复;无效操作有「咚」 | +| **P1** | Floor + 等待声 + 整链 duck;三档提示;hint/feedback 过期丢弃 | 能听出「轮到你了」;无被动原文重复 | +| **P2** | 各游戏首 3 分钟脚本;迷茫三级;状态×手势矩阵无空格 | 10s 内第一次成功;乱点机器人无声操作=0 | +| **P3** | 手势解锁阶梯;失败就近重试;30s 进展点;断点复述 | 真人 5 分钟放弃 ≤1/5 | + +引擎新模块落点:`web/engine/` 的 floor / receipts / hints / confusion / novice / pipeline(示意见 26 §4)。各游戏接线以各自设计单为准。 + +**不要**再按旧 roadmap 去做「storyline 菜单改成单击循环+双击确认」「escape PLAY 双击回大厅」——那些是已废止口径。 + +--- + +## 1. RPG《剑与声》第二/三章(P0~P2 落地后再灌内容) + +第一章模式不变(场景图 JSON + 战斗状态机 + 台词表)。**格挡 = 前摇窗口内再单击**,二三章沿用,不要写回双击格挡。 ### 1.1 第二章《腐沼泽》(7 节点) -| 项 | 内容 | -|---|---| -| 场景 | 沼口村(商店:青钢剑 300/铁鳞甲 250)→ 腐叶径 → 浅滩(岔路:鳄巢宝箱)→ 瘴气林 → 蛊师祭坛(NPC 支线)→ 祭主殿门 → Boss 腐沼祭主 | -| 新机制 | 毒状态(中毒后每回合 -3 HP,金疮药可解;中毒播报走整句分档);蛊师敌方先攻(B_START → ENEMY_TELEGRAPH,状态机已支持) | -| 敌人 | 沼泽鳄(HP30 ATK11 DEF2)、蛊师(HP22 ATK12 DEF1,先攻)+ 一章怪复用降档 | -| Boss | 腐沼祭主 HP70 ATK14 DEF3;半血二阶段召唤 2 毒蛛(连战队列插入,机制已有) | -| 解锁 | 一章 Boss 胜后 `chapters.2 = open`;章首旁白承接 `evt_ch1_end` 字条 | +沼口村(商店:青钢剑 300 / 铁鳞甲 250)→ 腐叶径 → 浅滩(岔路鳄巢)→ 瘴气林 → 蛊师祭坛 → 祭主殿门 → Boss 腐沼祭主。毒状态(每回合 -3 HP,金疮药可解,整句分档)。蛊师先攻。敌人:沼泽鳄 HP30 ATK11 DEF2;蛊师 HP22 ATK12 DEF1 先攻。Boss HP70 ATK14 DEF3,半血插 2 毒蛛。解锁:一章 Boss 胜后 `chapters.2 = open`。 ### 1.2 第三章《无瞳峰》(7 节点) -| 项 | 内容 | -|---|---| -| 场景 | 山道驿站(商店:听风剑 600,格挡窗口 1.0→1.3s)→ 风啸崖 → 断桥(岔路)→ 剑冢(机关战斗)→ 影廊(影卫首战,无声前摇教学)→ 峰顶 → Boss 白无瞳 | -| 新机制 | 无声前摇(震动协议见 05-rpg-addendum §2,影卫铺垫 → 白无瞳三阶段完全无声);iOS 兜底=防御指令 | -| 敌人 | 剑傀(HP38 ATK14 DEF3)、影卫(HP30 ATK16 DEF2,无声) | -| Boss | 白无瞳 HP110 ATK18 DEF5,三阶段(≤50% / ≤25% 转换,三阶段进场播 `npc_boss3_warn`) | -| 完结 | 终章结算用专属完结句(不复用"前往下一章",round5-notes §3 教训) | +山道驿站(听风剑 600,格挡窗口 1.0→1.3s)→ 风啸崖 → 断桥 → 剑冢 → 影廊(无声前摇教学:danger earcon +「点一下挡住」)→ 峰顶 → Boss 白无瞳。无声前摇时防御指令必须有效。终章用专属完结句,不复用「前往下一章」。 -### 1.3 排期与资产 +### 1.3 资产 -- 台词表:ch2/ch3 各一份(沿用 05-rpg-ch1-voice-ids.md 格式,每章约 60-80 句;最小集见 05-rpg-addendum §5)。策划补发 → 音频总监录制(各 1 轮)。 -- BGM:复用现有 4 首 + 每章新增场景 amb(`amb_swamp*`、`amb_peak*`,05-rpg-chapter1 §7.2 已含)。 -- 建议节奏:内容扩展 A 轮(ch2 台词+灌入)→ B 轮(ch3 台词+灌入+终章完结句)→ 与真机听测并行。 +ch2/ch3 各约 60~80 句,格式同 05-rpg-ch1-voice-ids,须标类别与 `_short`。amb_swamp* / amb_peak* 复用 chapter1 清单。 -## 2. storyline《声命线》打磨项(一直没重构,逐项跟进) +--- -| # | 事项 | 现状 | 动作 | -|---|---|---|---| -| 1 | 菜单约定统一 | 现版 down=菜单(左滑重开/右滑单击返回/上滑帮助),是裁决②之前的私有约定 | 统一到 00 §5.2:下滑呼出 → 单击循环 → 双击确认 → 上滑帮助;菜单句 `voice/menu` 需按新口径重录(×2 性别) | -| 2 | knowTrap 死标记修复 | `game.js:229` 只写不读:n09 右滑获得"北区陷阱情报"后无任何分支消费它 | 先读 `docs/games/storyline.md` 剧情树定接入点:在北区相关节点读取 knowTrap,解锁"绕开陷阱"分支或改变死亡结局判定;预计需补录 1-2 句分支台词(角色音:林晚 Serena / 旁白双性别)。若剧情树无合适节点,降级方案=删除该标记与台词里的伏笔句 | -| 3 | 角色音色在双性别目录的处理 | R1 裁定:旁白双性别(male=Uncle_Fu/female=Vivian),角色台词单音色平铺靠引擎回退(round1-report §3),资产侧未复制 | 终案确认:**维持平铺回退**(引擎已支持,零成本);把"平铺回退是正式机制"写进 web-engine-api 的目录约定(开发顺手改)。试听确认点:female 旁白(Vivian)与小满/林晨/广播(同 Vivian 不同 instruct)的区分度 | -| 4 | 提示音替换排查 | `sfx/ui_confirm`、`sfx/ui_menu` 仍在选择确认/菜单处使用 | 按反馈⑦替换:选择确认删 ui_confirm(引擎内置震动已覆盖),菜单打开删 ui_menu;剧情氛围 sfx(water/whistle/wind 等内容音)保留 | -| 5 | 进度 summary | storyline 无存档(单局 5-7 分钟,一次性剧情) | 轻量方案:记录"已达成结局 X/4",summary="已见 2/4 结局";结局解锁存 localStorage+服务器,大厅卡片有内容可显示 | -| 6 | 双性别 QC | 36 id ×2 已生成 | 抽听 female 版旁白与角色音的衔接(旁白切角色时音色跳跃是否出戏) | +## 2. storyline 内容项(交互按 26-apply-storyline,下列为剧情债) -## 3. 各游戏存量打磨小项(来自各轮审查遗留) +| # | 事项 | 动作 | +|---|---|---| +| 1 | 菜单 | 已改为左右滑+单击确认(00 §5.4),按新口径重录 `menu` | +| 2 | knowTrap | 读剧情树接消费点,或删伏笔 | +| 3 | 角色音色平铺 | 维持平铺回退 | +| 4 | 旧 ui_confirm / ui_menu | 删;改回执+menu earcon | +| 5 | summary | 「已见 x/4 结局」 | +| 6 | 双性别衔接试听 | 保留 | -- rhythm:结算右滑末位修正 + 补录 `no_more_song`;震动 200ms 去抖(round3-4-review §3 裁定①④)。 -- escape:PLAY 中双击兜底链末尾加"再双击一次回大厅"二级入口(补录 `menu_hint` ×2 性别,round2-review §5.1)。 -- simon:`voice/wrong` 冗余资产清理或启用(round2-review §5.2)。 -- 大厅:设置变更试听句(00 §1.2,一直未做)。 -- 全合集:平铺旧 voice 文件清理(storyline/rhythm/space 的残留,音频侧执行)。 +--- -## 4. 真机听测 checklist(用户拿手机逐项打钩) +## 3. 存量工程债(仍值得做,但次于 P0) -通用前置:安卓 Chrome + iOS Safari 各过一遍;戴有线耳机测一轮、蓝牙耳机测一轮(延迟差异);iOS 项注意"无震动"是预期行为。 +- 语音链 onComplete + 超时兜底;session token;句尾完整性(真机听测)。 +- 大厅设置变更试听句。 +- 平铺旧 voice 残留清理。 +- hunt 三源分辨真机听测(23 §8.4)。 -**全游戏通用(2026-09 新增,来自真机人耳反馈)**: -- [ ] **句尾完整性**:随机抽 10 句长短不一的对白听到自然收尾,末字/语气词(了/吧/呀)不缺失、不被下一句或音效咬尾(引擎已固化排空机制 + `web/harness/engine-voice.test.mjs` 回归;本项验真机实际听感与打断时序) +--- -### 声命线 storyline -- [ ] 旁白切角色音时不出戏(重点:female 旁白 vs 小满/广播的 Vivian 家族) -- [ ] 8 个抉择点左/右滑方向与选项播报一致 -- [ ] 4 结局至少打通 2 条,结局后单击重开正常 -- [ ] (打磨后)菜单单击循环/双击确认,无误触发重开 +## 4. 真机听测 checklist -### 暗室 escape -- [ ] 引导 G0-G7 全流程不卡(含故意做错 3 次触发直白重述) -- [ ] 9 个房间听环境音能区分储物间/铁柜/电箱/大门(闭眼走一遍) -- [ ] 卡关提示计时手感:60s/120s/180s 出现时机不烦人(真机挂一次 3 分钟) -- [ ] 单击重听环境音:amb 与描述语音的 800ms 间隔自然 -- [ ] 用时 4 段播报与实际耗时匹配 +前置:安卓 Chrome + iOS Safari;有线/蓝牙各一轮。 -### 音序回响 simon -- [ ] 无 BGM 的静默不尴尬(开场 0.5s + 轮次间隙) -- [ ] 播放序列的 beat 轻震与音色对齐(安卓机能感到节拍锚点) -- [ ] 双击主动重听:期望输入是 doubletap 时按答题处理(裁定①场景实测) -- [ ] 第 10 轮后的速度(420ms 下限)可跟 -- [ ] 成绩分档句与实际轮数匹配 +**全游戏(26 号优先)**: +- [ ] 闭眼:3 秒内第一次操作,10 秒内第一次成功 +- [ ] 连点两下不会整段重播 +- [ ] 无效操作听得到「咚」(iOS 无震动也要有) +- [ ] 能听出游戏在说 vs 轮到你 +- [ ] 上滑或长按得到一句「我在哪」(≤5s) +- [ ] 句尾完整(了/吧/呀 不被咬掉) -### 节拍行者 rhythm -- [ ] Perfect ±120ms 窗口:有线耳机能稳定 Perfect,蓝牙耳机 Good 为主(不挫败) -- [ ] tap 补偿:底鼓音(1/3 拍)判定不冤 -- [ ] 下滑菜单折中:演奏中误开菜单 / 想开开不了 各试 5 次 -- [ ] 菜单暂停后 BGM 断点续播节拍不漂 -- [ ] 三首 BGM 与乐器音调性协和、响度平衡(乐器音不被 BGM 淹没) +各游戏玩法项(抉择方向、谜题链、同向闪避、格挡窗口、谱面判定)在 P0 通过后再勾。旧「双击重听序列 / 双击开柜 / 双击格挡」条目作废。 -### 盲航 space -- [ ] 同向闪避直觉性:不测老玩家,找一个没玩过的人 30 秒内能否理解 -- [ ] 演练 D1-D4 每步"语音先、whoosh 后"不叠放 -- [ ] 保护期 60s 内被撞不掉血且归因句听清 -- [ ] 满级段(存活 90s+)密度:过密则把 spawn 下限 1.2→1.35(round3-4 裁定③旋钮) -- [ ] 警报两档渐强 + danger 震动的时间感(撞击前 1s/0.5s) +--- -### 剑与声 rpg(R5 完成后) -- [ ] 格挡窗口:8 种敌人前摇音都能听出并双击成功(重点:头目重甲 vs 野狼低吼的区分) -- [ ] 无声敌人(若 ch3 已做):danger+heartbeat 震动节奏可格挡;iOS 上确认防御指令兜底 -- [ ] 战斗 BGM 与场景 amb 切换不突兀;bgm=0 设置下 amb 照播 -- [ ] 商店购买/缺钱/已购播报语境正确 -- [ ] 存档继续:杀进程重进,summary 与落点正确 +## 5. 商业化前(公网化时再启动) -## 5. 商业化前要做的事 +Stable Audio 3 / Mureka / Qwen3-TTS 许可;字体 OFL;隐私政策(PIN 哈希、token 过期、HTTPS、删除入口);付费墙不要埋进现有 data。 -1. **Stable Audio 3 许可确认**:核对当前账号/模型的商用授权条款(训练数据与生成物的商用权利、署名要求);所有 SA3 生成 sfx 的资产清单(id → 生成日期 → prompt)落盘存档(`docs/audio/asset-manifest.md` 扩展)。 -2. **Mureka 曲目版权落盘**:确认 API 生成音乐的商用授权范围与归属条款;把 `tools/mureka_log.md` 整理为正式曲目登记表(曲目 → 生成时间 → 授权凭证/订单号);离线保存原始生成文件与凭证截图。 -3. **Qwen3-TTS 许可**:核对模型协议(Apache 2.0 系/通义许可)对生成语音商用的要求;在 About/致谢页列模型名。 -4. **字体许可**:封面生成用 NotoSansSC(OFL),保留 OFL 许可证副本,允许商用但需随附声明。 -5. **隐私政策**:用户系统当前是局域网玩具级(PIN 明文、token 不过期——web/server/README 已自声明)。公网化前必须:PIN 加盐哈希、token 过期机制、HTTPS、最小化收集声明、账号删除入口;写隐私政策页(收集:用户名+游戏进度;不收集:设备信息/位置)。 -6. **付费墙设计预留**(如需):进度 API 加 `entitlements` 字段的位置先留好;不要在现有 data 自由格式里埋付费标记。 -7. **合规扫尾**:无年龄分级素材问题(武侠战斗无血腥音效——atk_hit prompt 已注明 not gory,保持);商店/内购若上 iOS 需走 IAP 不能用网页支付(届时再议)。 +--- -## 6. 纯音频 MMORPG 路线(第一阶段切片已落地) +## 6. 纯音频 MMORPG -- **第一阶段(已落地)**:《围猎》hunt([23-hunt.md](23-hunt.md))——2-4 人实时联机协作守卫, - 验证三个切片命题:零依赖 WS 实时同步 + 服务端权威战斗、音频信道拥挤控制 R1-R8、 - pan 空间音频协作(呼喊-补位闭环)。 -- **后续步骤**(切片纪律外事项,先列 research/ 方向调研,不在本 roadmap 排期): - 持久世界与跨房聊天、跨玩家排行榜、房码音频录入 UX(协议 join.room 已留,客户端 v1 只做快速匹配)、 - 更大规模同图协作的拥挤模型扩展。真机听测时按 23-hunt §8.4 口径补一轮"三源分辨"听测项。 +《围猎》已作切片。交互叠加 23 号重写文的新手手势后再扩持久世界。真机补三源分辨。 + +--- ## 7. 建议顺序 -1. R5 收尾(RPG 第一章上线)→ 2. §3 存量打磨小项(半天级)→ 3. 真机听测 §4(用户执行,反馈回来后定参数旋钮)→ 4. storyline §2(与听测并行)→ 5. RPG ch2/ch3 §1 → 6. 商业化 §5(决定公网化时再启动)。 +1. **P0 在 storyline 真机闭眼过关** → 2. P0 推到 escape/simon → 3. P1 Floor 进引擎 → 4. 各游戏 P2 首 3 分钟 → 5. 真机 §4 → 6. RPG ch2/ch3 → 7. 商业化 §5。 diff --git a/docs/design/round1-review.md b/docs/design/round1-review.md index 6a9703b0..19a92626 100644 --- a/docs/design/round1-review.md +++ b/docs/design/round1-review.md @@ -1,5 +1,7 @@ # R1 落地一致性审查报告(策划,R2 轮) +> **档案带(26 号之后)**:本文记录 R1 实现与当时设计的出入,工程裁定(haptics 文件名、voice 平铺回退、`_settings`)仍有效。**手势宪法、只靠震动做反馈、向导接线已由 00/26 号废止**,勿再当施工依据。 + 审查对象:`docs/web-engine-api.md`(R1 变更)、`docs/audio/round1-report.md`、 `web/index.html`(大厅新 UI)、`web/engine/{settings,haptics,audio}.js`、`web/server/server.js`。 对照基准:`docs/design/00-global-ux.md`(设计规范初稿)。 diff --git a/docs/design/round2-review.md b/docs/design/round2-review.md index 21f9d8b6..eab24096 100644 --- a/docs/design/round2-review.md +++ b/docs/design/round2-review.md @@ -1,5 +1,7 @@ # R2 落地一致性审查报告(策划,R3 轮) +> **档案带(26 号之后)**:工程质量结论可参考。裁决②「单击循环 + 双击确认菜单」、simon 双击上下文、escape 双击使用/求提示 **已废止**(见 00 §5、01、02)。交互施工以 26 号为准。 + 审查对象:`web/escape/game.js`、`web/simon/game.js`(R2 重写版)、`docs/audio/round2-voice-ids.md`、`docs/audio/round2-report.md`、`web/engine/haptics.js`(R2 增补)。 对照基准:`docs/design/01-escape-redesign.md`、`02-simon-redesign.md`、`00-global-ux.md`(R2 修订版)。 diff --git a/docs/design/round3-4-review.md b/docs/design/round3-4-review.md index f9869aa5..66f0afc0 100644 --- a/docs/design/round3-4-review.md +++ b/docs/design/round3-4-review.md @@ -1,5 +1,7 @@ # R3/R4 落地一致性审查报告(策划,最终轮) +> **档案带(26 号之后)**:谱面直转、保护期、断点恢复等工程教训仍有效。附裁 A「玩法手势优先的菜单折中」、结算双击语义等 **让位于 26 号**(演奏期 ACTION、菜单阶段 2 才开放、新手无双击)。 + 审查对象:`web/rhythm/game.js`(R3 重构)、`web/space/game.js`(R4 重构)。 对照基准:`docs/design/03-rhythm-rebuild.md`(+03-rhythm-voice-ids.md)、`docs/design/04-space-rebuild.md`(+04-space-voice-ids.md)、`00-global-ux.md`。 diff --git a/docs/design/round5-notes.md b/docs/design/round5-notes.md index b247f158..a2350ef4 100644 --- a/docs/design/round5-notes.md +++ b/docs/design/round5-notes.md @@ -1,47 +1,50 @@ -# R5 开发提示(给《剑与声》开发 agent · 返工注意级) +# R5 开发提示(26 号对齐 · 工程注意) -> 定位:RPG 开发已在并行进行。以下**不是推翻性意见**,是 R3/R4 审查中发现的共性问题在 RPG 语境下的对应检查点——如果你的实现已经正确处理,忽略即可;如果需要返工,按对应条目修。设计依据:05-rpg.md / 05-rpg-chapter1.md / 05-rpg-ch1-voice-ids.md / 05-rpg-addendum.md / 00-global-ux.md。 +> 给《剑与声》及后续游戏:下列是工程健壮性,不是交互宪法。交互以 [05-rpg.md](05-rpg.md) 与 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 为准。 +> +> **已废止**:双击格挡、菜单单击循环+双击确认、iOS 无震动=无反馈、无声敌人只能靠震动格挡。 -## 1. 已验证可照抄的健壮性模式(escape/simon/rhythm/space 四款一致落地) +## 1. 照抄的健壮性 -1. **语音链看门狗**:所有"语音播完才推进流程"的地方都要双通道(onComplete + 超时兜底,二者先到先触发、只触发一次),参考 simon `_speakGuarded`。RPG 的场景切换、战斗回合开场、商店对话都依赖语音推进,缺看门狗会在资产缺失/解码失败时卡死整局。 -2. **会话令牌**:重开/跳场景时 `session++` 使旧计时器与语音链回调失效。RPG 状态多(探索/战斗/商店/菜单),比前四款更需要。 -3. **打断即作废**:状态切换入口统一 `stopVoice()`,不要依赖逐条清理。 +1. **语音链双通道**:onComplete + 超时兜底,先到一次。场景切换、战斗开场、商店对话都要。 +2. **会话令牌**:重开/跳场景 `session++`,旧回调作废。 +3. **打断**:状态入口 `stopVoice()`(Floor 会区分 skip vs 作废整链)。 +4. **回执**:每次手势 100ms 内 ok/invalid/busy,不依赖震动。 -## 2. 菜单与手势冲突(round2 裁决② + round3-4 附裁 A) +## 2. 菜单与战斗手势(新口径) -- RPG 是**菜单豁免游戏**:探索态菜单=左右滑切换+单击确认(00 §5.2),战斗态下滑=逃跑不设菜单。不要套用 escape/simon 的"单击循环+双击确认"。 -- ch1 台词表 `sys_menu` 文案("菜单。左右滑选择,单击确认。")与豁免口径一致,**不用改**。 -- 若你在菜单里加了"保存并回大厅"项:保存成功的反馈=`sys_saved` 语音 + `pickup` 震动,不要用提示音。 +- 探索菜单 = 左右滑 + 单击确认(与探索同构,00 §5.4)。 +- 战斗下滑 = 逃跑,不设菜单。 +- **格挡 = 前摇窗口内再单击**(朝来招方向滑也算)。教学只教单击。 +- 保存成功:`sys_saved` + pickup 震 + ok 回执。 -## 3. 导航/播报语音的末位语境检查(rhythm 裁定①的教训) +## 3. 末位语境 -rhythm 在"最后一首"右滑播了"还没解锁"造成语义错位。RPG 同类风险点: +- 终章不要播「前往下一章」。 +- 死端选项不要念不存在的去向。 +- 商店按语境选 shop_owned / shop_poor。 -- **章末**:第三章打完 Boss 后播"下一章"类语音会错位——终章结算需单独的完结句(ch3 台词表补发时会含 `evt_ch3_end`,若你先行实现,先用 `btl_win` + `evt_ch1_end` 模式临时顶替,勿复用"前往下一章"句)。 -- **场景图边界**:village 的 `back`、boss1 无选项等死端,播报选项时不要念出"返回"之外不存在的去向。 -- **商店已购/缺钱的播报**已在台词表(shop_owned/shop_poor),按语境选句,别统一播 shop_poor。 +## 4. 震动与无声敌人 -## 4. 震动落点(按 05-rpg-addendum §1 总表 + 两条新收紧) +- 有声前摇:内容音 `tel_*`;窗口可用轻 `beat` 补充。 +- 无声前摇:**系统** danger earcon(不是敌人叫声)+ 回执通道仍出声。防御指令减伤 50%,全平台有效。 +- 200ms 去抖。iOS 无震动时格挡仍可靠单击 + earcon。 -- 前摇窗口开启震 `beat`(有声敌人)/ `danger`+`heartbeat`(无声敌人)的通道分工**不要混用**:danger 专属于无声威胁。 -- 新增收紧(round3-4 裁定④同款):200ms 去抖——格挡成功的 `confirm` 若与 gesture.js 内置双击 confirm 同刻,只震一次;升级 success 与战斗胜利播报前震动同理。 -- iOS 无震动:无声敌人不可格挡是已知设计代价(addendum §2.2),**防御指令必须可用且有效**(减伤 50% 不依赖时机),这是 iOS 兜底,不要在任何分支里把防御禁用。 +## 5. 音频层叠 -## 5. 音频层叠(addendum §3 的实现要求) +- 场景 amb 走 sfx 层,勿挂 bgm 层。 +- 战斗 BGM 走 bgm 层。整条语音链期间 duck,句间不抬。 +- 玩家回合可加等待声,不另切 BGM。 -- 场景 amb_* **不走 bgm 层**(会被 bgm=0 设置误杀,且与战斗 BGM 冲突):用 `audio._load` + 手动 BufferSource loop 接 `_sfxBus`,rhythm `_startBgm`/`_scheduleSfx` 的手动总线接法可直接参考。 -- 战斗 BGM(bgm/battle、bgm/boss)走正常 bgm 层,受 bgm 设置控制;进出战斗 crossfade 切换即可。 -- 注意 decodeAudioData 内存:amb 循环 6-10s、BGM 60-90s,全章节资产控制在 ~30MB 压缩内(web-engine-api §移动浏览器的坑 7)。 +## 6. 存档 -## 6. 存档(05-rpg-chapter1.md §6 + 审查共性) +- 每次保存更新 `summary`:「第X章·场景 · LvN · 金币M」。 +- 场景切换 / 战斗结算 / 商店 / 菜单保存 / **战败重生**都存。 +- 新开游戏二次确认(破坏性)。 +- 重进 5 秒内一句复述。 -- `summary` 必须随每次保存更新(大厅卡片直接显示);格式"第X章·场景名 · LvN · 金币M"。 -- 保存时机四件套:场景切换/战斗结算/商店交易/菜单保存——**战败重生也算战斗结算**,要存(否则死亡丢进度,比死亡本身更劝退)。 -- localStorage 与服务器双写,离线静默(四款游戏的 `_putProgress` 模式照抄)。 -- 继续/新开二选一(sys_continue_ask):右滑=新游戏是**破坏性操作**,建议加一次确认(单击确认句可用现有 `sys_new_game` 前的自答:"右滑确定重新开始?再右滑确认。"——如需新台词列入 ch2/3 补录,先用现有句拼接流程也行)。 +## 7. 教学战 -## 7. 教学战(05-rpg-chapter1.md §5.2) - -- 锁血保护(HP 最低 1)别忘了在 `_resolve` 类函数里实现为 clamp,不是免疫——教学战要让玩家**看到**掉血才理解格挡的价值。 -- 教学句 tut_b1-b5 的触发点:tut_b1 在首次 ENEMY_TELEGRAPH 前、tut_b2 与前摇音**同时**(这是唯一允许语音与前摇叠放的位置,设计如此)、tut_b3 格挡成功后。 +- HP 锁底 1,要让玩家听到掉血。 +- 四步:滑选 → 单击攻击 → 听前摇再单击格挡 → 选防御。 +- 成功即时「挡得漂亮。」失败才补救。同一句提示不原样罚听。 diff --git a/docs/design/storyline-voice-ids.md b/docs/design/storyline-voice-ids.md index a44a880b..d7aee810 100644 --- a/docs/design/storyline-voice-ids.md +++ b/docs/design/storyline-voice-ids.md @@ -1,5 +1,13 @@ # storyline-voice-ids · 《声命线》语音台词表(voice ids) +> **26 号对齐(资产清单,不是交互宪法)** +> - 交互与新手手势以 [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) 和对应游戏正文为准。 +> - 本表仍是 TTS id 事实源的**登记副本**(生成以 `tools/gen_voice_dual.py` 为准)。含「双击重听 / 双击使用 / 双击格挡 / 单击循环菜单」的句子 **停用或按游戏正文重录**。 +> - 写作:指令 ≤12 字,短句档 ≤6 字,叙事 ≤25 字;每句标 `critical` / `content` / `hint` / `feedback`。游戏主动提示不得原样重复;重听前加「再说一遍:」。 +> - 键盘变体:新手期回车当空格(单击)。删除「按回车=重听」类旧口径,或仅在阶段 3 解锁双击后启用。 +> - 共用新句见 [14-guide-layer.md](14-guide-layer.md) §7(`sys_first_tap` 等)。各游戏 `_full` / `_short` 在游戏正文设计单里点名,补进本表后再进生成脚本。 + + > 主台词表(41 句旁白/系统音,双性别)的唯一事实源是 `tools/gen_voice_dual.py` > 的 `STORYLINE_LINES`(角色台词为平铺单音色,沿用 `web/storyline/audio/voice/` > 原文件,不在双性别管线内;主表史源 app/src/storyline/gen/gen_voice.py)。