Files
SoundGame/research/03-schemes/game-inventory.md
T
SoundGame Dev 18f299f446 research: 交互研究项目立项(章程/5路调研4份/游戏交互清点/玩家感知转写harness)
- research/00-charter.md 研究章程:RQ1-8、多人设×场景矩阵、成功标准
- research/01-literature/ 01/03/04/05 四份调研报告(02 sonification 进行中)
- research/03-schemes/game-inventory.md 6游戏状态机与交互接线清单(带行号)
- web/harness/ 玩家感知转写器:虚拟时钟mock+台词全文转写+静默/缺资产如实标注
- tools/extract_voice_text.py 台词表→JSON 提取(主表+旧flavor AST解析)
2026-08-30 21:10:06 +08:00

384 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 六款游戏交互接线清单(「向导交互层」接入前置调研)
> 调研方法:逐行阅读 `web/engine/{game,gesture,audio,haptics}.js` 与 6 款 `web/<game>/game.js` 全文。
> 所有行号均指 `web/<game>/game.js`(engine 文件显式标注 `engine/xxx.js:行号`)。以代码为准;与文件头注释/设计文档不一致处已注明。
> 调研日期:2026-08-30。工作区版本(未提交改动以实际文件为准)。
---
## 0. 引擎公共事实(先读,后文引用)
- 手势集合:`'up'|'down'|'left'|'right'|'tap'|'doubletap'`,另有 `'longpress'|'longpressend'`(gesture.js:5-7)。**tap 回调有 ~300ms 延迟**(双击窗口,engine/gesture.js:11-13、161-166)——即时反馈必须用滑动/双击,rhythm/space/rpg 均做了补偿。
- 手势识别震动由 gesture.js 内置:滑动→`swipe`、tap/doubletap→`confirm`(engine/gesture.js:144、155、164);语义震动(error/success/warning/hit/beat 等)由游戏层自行调 `vibrate()`。
- audio 三层:`bgm/`(循环+交叉淡化)、`voice/`(单通道,播 BGM duck 到 20%;`onComplete` 仅自然播完触发,**被打断不触发**)、`sfx/`(即发即弃可叠加)(engine/audio.js:4-11、184-186)。`skipVoice()` = 截断当前句并**触发其 onComplete**(链继续),`stopVoice()` = 作废整条链(engine/audio.js:216-244)。
- 长按 = 旁白临时 1.7 倍速,audio.js 全局监听 window 事件,游戏零改动(engine/audio.js:97-101、448-458)。rhythm 文件头注明 main/tut 应豁免但引擎无 per-game 开关,属已知限制(rhythm/game.js:36-38)。
- **`audio._voice` 是私有字段**,无公开"是否有对白在播"查询 API;simon:131-140、escape:328、rhythm:472-476、space:646-648、rpg:1054 都直接读它做"单击跳句"的语境判定——向导层若改引擎应把它转正。
- 语音链通行写法:各游戏都有自己的 `_speak(ids, onDone)`(session/runId 令牌守卫)+ `_speakGuarded`(看门狗兜底)变体;storyline 例外,用 `playSeq` + 30s 单句看门狗。
- `Game` 基类(engine/game.js:18-42):`onGesture/start/stop`。`StateMachineGame`(engine/game.js:66-106)**6 款游戏无一使用**,全部直接继承 `Game`、自管状态字段。
---
## 1. storyline《声命线》(web/storyline/game.js,340 行)
### 1.1 状态模型
- 类 `StorylineGame extends Game`(:84)。**手动状态字段,非状态机基类**。
- 字段(:87-101):`mode: 'STORY'|'MENU'`(:76);`nodeId`(剧情节点 id,NODES 表 :31-68 共 32 节点);`voicePlaying`(链播放中);`currentVoiceId`;`promptPlaying`(当前句是抉择提示 `voice/p\d*`,正则 :82);`choiceArmed`(提示音播完、可左右抉择);`pendingChoice`(提示音播放中缓存的抉择);`awaitingRestart`(结局播完);`seqToken`(语音链作废令牌);剧情标记 `companion/knowTrap/betrayed`。
- 流转驱动:**语音 onComplete 链 + 手势**。`playNode`→`playSeq`(:175-196, 230-261)串行播完→`onNodeSeqDone`(:214-227)置 `choiceArmed/awaitingRestart` 或静默等 tap;`choose`(:263-273)驱动跳节点;无任何游戏逻辑定时器(只有语音看门狗)。
### 1.2 等待输入点清单
| # | 状态/条件 | 玩家该做什么 | 此刻声音 | 位置 |
|---|---|---|---|---|
| W1 | 叙事节点链播完(`voicePlaying=false`、`node.next≠null`) | tap=继续下一节点;doubletap=重听本节点;up=帮助;down=菜单。**left/right 静默忽略**(:137 prompt==null return) | 完全静默(仅 `bgm/dark_drone` 循环垫底,:107) | :214-227("否则等待单击继续" :226),tap 分支 :130 |
| W2 | 选择节点提示音播完(`choiceArmed=true`,p1~p8 共 8 个节点) | left/right=抉择。**tap 无效**(选择节点 `next==null`,:130 判空) | 静默 | :217-222、:137-141 |
| W3 | 结局播完(`awaitingRestart=true`,e1~e4) | tap=重开 | 静默 | :224、:126 |
| W4 | 菜单(`mode=MENU`) | left=重开;right/tap=关闭;up=帮助;doubletap=重听菜单语音。**down 无绑定** | `bgm/menu`(:304)+ `voice/menu` 播完即静默 | :151-171 |
| W5 | (特殊)提示音播放中滑 left/right | 不生效,**缓存进 `pendingChoice`**,播完自动执行(缓冲,非忽略) | voice/p* 播放中 | :139、:218-222 |
### 1.3 播放内容但不接受输入的时刻
- 节点语音链播放中(`playSeq` :230-261):tap=跳过当前句(`skipVoice`,**`promptPlaying` 时不可跳**,盲玩公平,:128-129);doubletap=打断重听(:198-211);up/down 随时可用。
- 引导句 `voice/skip_hint`(首局 n00 后追加,:188-191)、彩蛋 `e1_child`(:193)随链播放,可跳。
- 输入行为总述:**从不报错**,不匹配的手势一律静默 return(:130-141 各分支)。
### 1.4 错误路径清单
- **无"错误"概念**:无效手势(非选择节点滑 left/right、选择节点按 tap、菜单按 down)全部静默忽略,无震动、无音效、无语音。代码中无一处 `vibrate('error')`。
- 唯一"负反馈"是结局本身的剧情音效(e3 `sfx/radio_static` :66、e4 `sfx/struggle` :67)。
- **无纠错性语音**(也不需要传统意义的纠错,但"无效手势零反馈"对盲玩家等于手势没发出去)。
### 1.5 已有帮助/状态播报
- up = `playHelpAndResume`(:284-297):`seqToken++` 作废当前链 → 播 `voice/help` + `voice/status1..3`(按 `actOf` :331-339 分幕)→ **自动重播当前剧情节点**。这是全游戏唯一的状态播报,内容是"第几幕",不含节点级进度。
- 菜单内 up 同函数(`backToMenu=true`,:160-162)。
- 无卡关看门狗:某节点静默等 tap 永远不会超时(除 :245-250 的 30s 单句语音资产缺失看门狗)。
### 1.6 超时/定时器
| 定时器 | 时长 | 后果 | 位置 |
|---|---|---|---|
| 单句语音看门狗 `VOICE_WATCHDOG_MS` | 30s | 视为资产缺失,自动续链下一句 | :79、:245-250 |
| (无其他定时器;无卡关/保护/倒计时) | | | |
### 1.7 向导层接入点建议
1. **状态播报**:向导层劫持 `playSeq`(:230)即可掌握全部旁白时机;"你现在的进度"语音可在 `onNodeSeqDone`(:214)后追加(在 voice 链尾部排队,不与 p* 提示冲突——p* 是链的末句,播完即 W2,向导层应避免在此追加,见下)。
2. **轮到你提示**:W1(叙事节点播完)在 `onNodeSeqDone` :226 分支插"轻触继续"提示(现状完全静默,是全 6 款中最盲的等待点)。W2 不可插语音(会破坏"提示音播完即可选"的契约),可改用**震动**(`vibrate('confirm')`)标记 `choiceArmed=true`(:217)。
3. **静默看门狗**:W1/W2/W3 均无超时。建议在 `onNodeSeqDone`(:214)挂 N 秒无手势→重播 `voice/help+status`(复用 `playHelpAndResume` :284)。W3 同理(`awaitingRestart` 置位点 :224)。
4. **纠错反馈**:`onStoryGesture`(:121)各静默 return 处(:137 等)加轻震动(`swipe` 之外的语义提示)或一句 `voice/` 提示,成本极低。
---
## 2. simon《音序回响》(web/simon/game.js,547 行)
### 2.1 状态模型
- 类 `SimonGame extends Game`(:66)。
- `phase` 枚举(:69):`idle|tutorial|announce|playback|input|roundend|gameover|menu`。辅助字段:`round`(序列长 = round+1)、`inputIdx`、`chances`(重听机会,初始 1、每 4 轮 +1、上限 2,:73)、教学字段 `teachIdx/teachAwait/teachErrs`(:76-78)、`_menuIdx`、`_session`(会话令牌)、`_timers`(:80-82)。
- 流转驱动:**定时器 + onComplete 链 + 手势**。教学:`_teachStep`(:151-175)语音链播完→播音色→900ms→`t_try`→播完或 1.5s 兜底→`teachAwait=true` 等手势;对局:`_nextRound`(:236-259)→announce 语音(2.5s 兜底)→`_playSequence`(:290-319)定时器驱动的序列演示→400ms→`confirm` 震→第 1 轮 `your_turn` 语音→`_toInput`(:321)等输入。
### 2.2 等待输入点清单
| # | 状态/条件 | 玩家该做什么 | 此刻声音 | 位置 |
|---|---|---|---|---|
| W1 | tutorial `teachAwait=true` | 做当前教的手势(6 个逐一);down=跳过教学(教 down 时除外,:180 上下文判定) | 静默(t_try 播完后) | :151-175、:177-216 |
| W2 | `phase='input'` | 按序重复序列手势;doubletap 在"非期望双击"时=主动重听(耗机会,:329-332) | **完全静默**(对局全程无 BGM,文件头 :7-9);进入时仅 `confirm` 震 + 第 1 轮 `voice/your_turn` | :321-324、:302-315 |
| W3 | `gameover` 播报链播完 | tap=重开;up=规则;down=菜单 | `bgm/menu` vol0.3(起播后 ~2650ms 淡入,:418-422) | :109-113 |
| W4 | `menu` | tap=循环项;doubletap=确认;up=规则。**down 无绑定** | `bgm/menu` | :449-462 |
**W2 无任何超时**:玩家不动则永远静默等下去(无看门狗)——向导层最大价值点之一。
### 2.3 播放内容但不接受输入的时刻
- `announce`(轮次整句 `round_01..round_20`/`round_more`,:245-256):**所有手势静默忽略**(:117-119 default 分支,裁定见 :118 注释"纯聆听阶段手势静默忽略");2.5s 兜底直接进 playback。
- `playback`(序列演示,每音 `beat` 震,间隔 `max(420, 700-(round-1)*26)`ms,:285-319):同样全部忽略(含第 1 轮 your_turn 期间)。
- `roundend`(答对/答错后的播报链,`_speakGuarded` 10s):忽略;播完自动 `_nextRound` 或重播序列。
- 旁白跳过:`_tapSkipsVoice`(:131-140)按 phase 白名单(tutorial 非等待期/announce/roundend/gameover 可跳;**teachAwait 期 tap=答题、playback/input 期不跳、menu 单击是菜单语义**)。
### 2.4 错误路径清单
- 教学做错(:205-215):`vibrate('error')` + `voice/t_wrong_<gesture>`;**同一步错满 3 次**:`vibrate('warning')` + `voice/t_blunt_<gesture>`(更直白描述)并计数清零。→ 已有纠错语音且带升级。
- input 做错(`_wrongAnswer` :377-391):有机会→`error` 震 + 300ms 后 `warning` 震 + `voice/retry_use`(+归零时 `chances_out`)→ 自动重播同一序列(`phase='roundend'`)。无机会→`_gameOver`(:393-423):`error` 震、300ms 后 `hit` 震、700ms 后 `sfx/gameover`、1400ms 后播 `gameover+score_band+new_record+replay+rules_prompt` 链。
- 重听无机会(:344-349):`error` 震 + `voice/chances_out`。
- 对局输入错误无"该做什么"级纠错语音(retry_use 只说"再试一次",不重复期望手势——靠重播序列弥补)。
### 2.5 已有帮助/状态播报
- gameover up / menu up = `_speakRules`(:435-437):`voice/rules + voice/rules_prompt`。**tutorial 与对局中无帮助手势**(up/down 在 input 是序列手势)。
- 状态播报:每轮 `round_XX` 整句(主动播)、成绩 `score_band1..6`(结算)、机会变动 `chance_plus/chances_out`——播报文化完整。
- 对局无卡关计时器(等输入无限等,见 2.2)。
### 2.6 超时/定时器
| 定时器 | 时长 | 后果 | 位置 |
|---|---|---|---|
| 开场进教学延迟 | 500ms | `_runTutorial` | :92 |
| `t_try` 缺失兜底 | 1.5s | 直接 `teachAwait=true` | :169-171 |
| 教学跳过(down)兜底 | 4s | `_beginGame` | :185 |
| announce 兜底 | 2.5s | 直接 `_playSequence` | :258 |
| 序列末音→"轮到你" | 400ms | `confirm` 震 + 第 1 轮 your_turn | :315、:304 |
| your_turn 兜底 | 2.5s | 直接 `_toInput` | :311 |
| roundend→下一轮 | 300ms | `_nextRound` | :373 |
| `_wrongAnswer` warning 震 | 300ms 后 | 第二段震动 | :382 |
| gameover 各段 | 300/700/1400/2650ms | hit 震/gameover 音/语音链/menu pad | :404-422 |
| `_speakGuarded` 默认 | 10s | 语音缺失也推进链回调 | :518-528 |
| 教学收尾 t7 链兜底 | 12s | `_beginGame` | :201 |
### 2.7 向导层接入点建议
1. **静默看门狗(最优先)**:`_toInput`(:321)挂计时——N 秒无输入先播提示再自动重听(复用 `_relisten` :344 逻辑但可免机会扣减或改为无限次提示);`gameover` 链播完(:413 `_speak(ids, null)` 的 null 回调处)同理。
2. **轮到你提示泛化**:`_playSequence` :304 的 `confirm` 震 + :306 `your_turn`(仅第 1 轮)——向导层把"轮到你"语音做成低打扰版(震型升级或每 K 轮播一次)只需改这两行。
3. **状态播报**:`phase` 字段完备,向导层可直接读;劫持 `_speak`(:502)即可统一插播。
4. **菜单缺口**:对局中(input/playback)无菜单呼出路径,唯一退出是故意答错到 gameover——向导层或需一个不与 6 手势冲突的呼出方案(长按已全局占用为快进,engine/gesture.js:14-19)。
5. `audio._voice` 私有读(:131-140)应随引擎转正。
---
## 3. escape《暗室》(web/escape/game.js,672 行)
### 3.1 状态模型
- 类 `EscapeGame extends Game`(:76)。`Phase` 枚举(:36-42):`REOPEN|GUIDE|PLAY|ENDING|MENU`。
- 字段(:86-110):坐标 `x,y`(3x3)、谜题旗标 `hasKey/hasFuse/cabinetOpen/powerOn`、`visited`、`guideStep 0..7/guideSwipes/guideErrs/_guideBusy`、卡关 `_lastProgressAt/_stuckShown(L1|L2|L3)/_lastL3At`、`_voiceSkippable`、`_menuReturn/_menuIdx`、`_session`。
- 流转驱动:**手势为主 + 语音 onComplete 链**。start 按存档进 REOPEN 或 GUIDE(:121-128);GUIDE 由 `_advance`(:176-194)语音播完回调推进 guideStep;PLAY 是自由探索无严格状态;谜题链阶段由派生函数 `_stage()`(:454-459:key→cabinet→box→gate)给出。
### 3.2 等待输入点清单
| # | 状态/条件 | 玩家该做什么 | 此刻声音 | 位置 |
|---|---|---|---|---|
| W1 | REOPEN:`reopen_prompt` 播完(**不可跳**) | tap=直接开始;up=重听引导。左右滑/双击静默忽略 | 静默(`bgm/drone` 垫底,:120) | :134-137 |
| W2 | GUIDE 各步:推进语音播完(`_guideBusy=false`) | 做该步手势(tap/滑动×2/左/下/tap/双击) | 静默 | `_advance` :180-187,`_onGuideGesture` :196-256 |
| W3 | PLAY 常态 | 四向移动/tap 检查/doubletap 用物 | `amb_*` 环境音(进房自动播,:370-372)+ `bgm/drone` | `_onPlayGesture` :318-336 |
| W4 | PLAY:voice 播放中 | **滑动移动不受阻**(无全局 gate);仅 tap 分流:可跳句=skip,不可跳句=**忽略**(:328-331 注释解释了为何不触发检查) | voice | :324-332 |
| W5 | ENDING:结算链播完(或 30s 兜底) | tap=重开;down=菜单 | `bgm/menu` vol0.5(`_startEndingBgm` :577-581) | :144-147 |
| W6 | MENU | tap=循环;doubletap=确认;up=操作说明;down=关闭 | `bgm/menu` | :524-539 |
| W7 | GUIDE G7:g7_goal 播放中 | 手势忽略(等 `_advance` onDone 进 PLAY) | voice | :253-254 |
### 3.3 播放内容但不接受输入的时刻
- 引导推进链(`_speak(..., {skippable:false})`,:192):`_guideBusy=true` 期**下滑以外的手势一律忽略**(:199-200);下滑随时可跳过引导(G4 除外——G4 答案就是下滑,:197-199)。
- 关键获得句不可跳:`found_key`(:390)、`unlock_cabinet`(:422)、`fix_box`(:431)、逃脱结算链(:570-571)、L3 带路链(:489)、`guide_skip`(:268)。
- 可跳句:`room_*` 首到描述、`start_room`、`empty_*`、检查描述、`wall`、L1/L2 提示。
- PLAY 期**没有缓冲**:voice 中滑动手势立即生效(移动与语音并行),tap 按可跳性分流。
### 3.4 错误路径清单
- 撞墙(`_move` :341-346):`sfx/wall` + `vibrate('error')` + `voice/wall`(**每次都播**,:344 注释"取消一次性限制")。
- 引导做错(`_guideWrong` :278-288):`vibrate('error')` + `guide_wrong_<dir>`(方向错)/`guide_wrong_gesture`(手势类型错);**错满 3 次**:`warning` 震 + `guide_blunt_<dir>` 直白重述(仅方向步有 blunt)。→ 完整纠错语音 + 升级。
- 双击无可用目标(`_useItem` :435-443):`error` 震 + 播 `use_fail`(关键房间)/背包句 `inv_*` + **主动追加当前阶段 L2 明示** `stuck_<stage>_2`。→ 已有"告诉玩家该去哪"级纠错。
- **纠错性语音:全 6 款中最完备**(wrong/blunt/stuck 三级 + nav 带路)。
### 3.5 已有帮助/状态播报
- **PLAY 中无帮助手势**(四向/tap/doubletap 全被占用,上滑=向北走)。状态查询只能靠:tap 重播环境音+描述(:375-409)、doubletap 兜底播报背包+L2(:435-443)。
- MENU up = `voice/help_ops`(:529-530)。
- **三级卡关看门狗**(`_checkStuck` :479-500,每 5s 轮询 :463-466;进展事件 `_progressEvent` :475-477 重置):60s L1 `stuck_ask+stuck_<stage>_1`(暗示);120s L2 `stuck_<stage>_2`(明示);180s L3 `stuck_<stage>_3` + 按坐标实时算路 `nav_*`,此后**每 60s 重复**(:485-490)。全部 `warning` 震;L3 不可跳。
### 3.6 超时/定时器
| 定时器 | 时长 | 后果 | 位置 |
|---|---|---|---|
| 卡关轮询 | setInterval 5s | 见 3.5 | :465 |
| L1/L2/L3 阈值 | 60s/120s/180s,L3 重复 60s | 提示/明示/带路 | :483-499 |
| 检查描述延迟 | 800ms | 先环境音后描述 | :379-381 |
| 引导推进看门狗 | 20s | 语音缺失也切步(带 `guideStep===fromStep` 防误触发) | :188-190 |
| 跳过引导兜底 | 5s | `_enterPlay` | :270 |
| 逃脱结算 pad 兜底 | 30s | `_startEndingBgm` | :573 |
### 3.7 向导层接入点建议
1. **三级卡关即现成的静默看门狗骨架**:向导层接管 `_checkStuck`(:479)或把阈值/级别改由向导层配置;`_progressEvent`(:475)就是标准的"重置看门狗"钩子。
2. **轮到你提示**:W1/W2 播完即静默,可在 `_advance` done(:180-187)和 REOPEN(:126)后加轻提示;PLAY 是开放探索不需要。
3. **帮助缺口**:PLAY 无帮助手势——向导层的"状态播报"可挂到向导专属入口(如连续 N 次撞墙后主动播 `nav`),不必新增手势。
4. `_voiceSkippable`(:96、:642-645)是全项目最干净的"可跳性"封装,向导层插播语音时应复用该机制决定自己的句子可否被打断。
---
## 4. rhythm《节拍行者》(web/rhythm/game.js,767 行)
### 4.1 状态模型
- 类 `RhythmGame extends Game`(:133)。`phase` 枚举(:136):`idle|intro|tut|select|main|menu|end`。
- 字段:`_runId`(作废令牌)、`_timer`(40ms 调度泵句柄)、`_sched`(领奏音符绝对时刻)、`_timed`(mid_song 等定时动作)、`_cues`(应答期望 `{gesture, expected, judged, result, phrase}`)、`_bgmSrc/_bgmT0`(手动 BGM 网格)、`_tutIdx/_tutFails/_tutAttempts`、`_menuIdx/_pauseAt/_bgmOffset`、`_phaseEnd`(实例字段,constructor 未声明,:308/:421 赋值)。
- 流转驱动:**AudioContext 时钟绝对排程 + 40ms 泵(`_pump` :239-268)+ 手势**。无 setTimeout 链播(语音仍走 onComplete `_speak`)。全部节拍按 `ctx.currentTime` 预排(LOOKAHEAD 0.6s,:46)。
### 4.2 等待输入点清单
| # | 状态/条件 | 玩家该做什么 | 此刻声音 | 位置 |
|---|---|---|---|---|
| W1 | tut 应答小节(领奏后 4 拍,`cue.expected`±GOOD) | 按谱跟打(T1 单音→T2 左右→T3 三音) | 领奏音已停,`bgm/track90` 循环 | `_tutLead` :290-310 |
| W2 | select 常态 | 左右换曲/tap 确认(未解锁播 `song_locked`)/up=规则/down=回大厅 | `bgm/menu` + 选项播报 | :366-384 |
| W3 | main 每个 cue 应答窗口(expected±0.25s) | 对应乐器手势 | 领奏 1 小节后 4 拍空窗 + 曲目 BGM | `_onMainGesture` :495-527 |
| W4 | main 长空档(restAfter 休止小节) | 无期望输入(可自由演奏/下滑呼菜单) | BGM | 谱面 :61-116 |
| W5 | end 结算链播完 | tap=同曲再来;right=下一首/locked/最后一首播 `voice/restart`;down=回大厅 | `bgm/menu`(链播完淡入,:679) | :452-459 |
| W6 | menu | tap=循环(resume/lobby 两项);doubletap=确认;up=帮助;down=恢复 | **静默**(模态暂停、断点续播不叠 pad) | :569-608 |
| W7 | intro(welcome 播放中) | **只收 tap 跳旁白**,其余全忽略 | voice + 6s 后起的 track90 | :461-463、:173 |
**注意**:main 阶段"轮到你"无任何显式信号——领奏→应答的切换只靠 BGM 网格与 4 拍静默空窗自悟(对比 simon 有 confirm 震+your_turn、rpg 有 turn_player 标点)。向导层第二价值点。
### 4.3 播放内容但不接受输入的时刻
- intro:非 tap 手势全部忽略(:461-463)。
- tut/select/end:tap=逐句跳过(`_trySkipVoice` :472-476),其余手势按阶段语义处理(tut 中会记尝试 :479-493——**教学领奏期的手势不是忽略而是全量记录进评估**)。
- main:**永不跳语音**(mid_song 不可跳裁定,:447 注释);所有手势按演奏判定——窗口内错手势=Miss(:518-520),窗口外=自由演奏发声不判定(:514-517)。
### 4.4 错误路径清单
- Miss(窗口内错手势/超时缺失):**不播乐器音**(错过=没出声)+ `vibrate('error')`(:548-556);超时判定在泵里 :258-263(tap 余量 TAP_DELAY+0.08,其余 0.05s)。**无任何语音反馈**——对局期纠错全靠震,方向都不提示。→ 向导层最该补的位置。
- 教学评估失败(`_tutEvaluate` :313-354):`error` 震 + 分类纠错语音:手势错=`retry_gesture`、过早=`retry_early`、过晚=`retry_late`、没出手=`retry_listen`(:347-353);**同一步失败 2 次 `retry_skip` 自动跳过**(:335-346)。
- 未解锁曲 tap:`voice/song_locked`(:373-377)。
- 不掉血不中断(文件头 :15,"歌照唱完")。
### 4.5 已有帮助/状态播报
- select up = `voice/rules`(:381-383);menu up = `rules + settings_hint`(:585-587)。**main 中无帮助**(下滑被菜单折中占用,:498-501:仅当无悬置/临近 down 音符时下滑才呼菜单,`_downNotePending` :530-534)。
- 状态播报:无"你打到第几乐句"类播报(节奏游戏特性,可理解);结算链完整(`grade_s..c`+`new_best_combo`+unlock+`restart`)。
### 4.6 超时/定时器
| 定时器 | 时长 | 后果 | 位置 |
|---|---|---|---|
| 调度泵 | setInterval 40ms | 排程音符/Miss 判定/乐句结束 | :270-273 |
| `_speakGuarded` | 默认 10s;welcome 8s | 语音缺失兜底推进 | :756-766、:174 |
| 开场 BGM 延迟 | 6.0s(教学)/1.0s(正式) | 网格起点 | :173、:400 |
| tap 判定补偿 | -0.30s | 手势时刻修正 | :483、:504 |
| `_phaseEnd` | 末 cue + 0.25 + 0.3 + 0.4/0.5s | 教学/曲目结束 | :308、:421 |
| 暂停平移 | resumeAt=now+0.1s | 全部绝对时刻 shift(断点续播) | :595-608 |
| 结算 BGM 淡出 | 0.6s ramp/700ms stop | 曲目 BGM 收 | :634-643 |
**注意**:`_finish` 的结算链用 `_speak`(无看门狗,:679)——`grade_*` 等语音资产若缺失,`bgm/menu` 永远不会淡入(W5 永远纯静默等待)。这是与 space `_gameOver` 同款的代码事实缺口。
### 4.7 向导层接入点建议
1. **对局纠错语音**:`_applyMiss`(:548)加连续 N 次 Miss 触发一次 `voice/` 提示(素材可参考教学 `retry_gesture`);插播通道建议复用 `play('voice/…', {queue:true})`(engine/audio.js:186)避免顶掉 mid_song。
2. **轮到你提示**:在泵里检测"首个未判定 cue 进入窗口"(`_pump` :244 附近可取 `_cues` 首个 `!judged`),首乐句播一次 `your_turn` 型提示;或谱面排程时向 `_timed` 推入(:414-418 旁已有 mid_song 先例)。
3. **结算链补看门狗**:`_finish` :679 `_speak`→`_speakGuarded`(守卫里起 `bgm/menu`)。
4. **状态播报**:select/end 已够;main 中向导层若要插播必须走 `queue:true` 且避开 `_downNotePending` 类判定干扰。
---
## 5. space《盲航》(web/space/game.js,698 行)
### 5.1 状态模型
- 类 `SpaceGame extends Game`(:88)。`phase` 枚举(:91):`idle|ask|drill|playing|feature|menu|over`。
- 字段(:97-116):`hits/dodges/runStart/protectionUntil/_protectAnnounced/swipes[]`(滑动手势环形缓冲)、`obstacles`(Set,`{dir, impactAt}`)、`drillOb/drillIdx/drillFails`、`shieldUpUntil/shieldReadyAt/boostUntil/boostReadyAt`、`boostIntroDone/shieldIntroDone/featureKind`、`_menuIdx`、`_session`。
- 流转驱动:**纯 setTimeout(`_after` :679-687,全部带 session 守卫)+ 手势**。首局 D0-D5 演练(`_startDrill` :178);对局 `_startRun`(:268-281)排三个一次性定时器:20s 单击引入、45s 双击引入、60s 保护期结束宣告;刷怪自递归 `_scheduleSpawn`(:423-431)。
### 5.2 等待输入点清单
| # | 状态/条件 | 玩家该做什么 | 此刻声音 | 位置 |
|---|---|---|---|---|
| W1 | ask:`second_run_ask` 播完 | tap=直接出发;up=重听演练。其余静默忽略 | 静默(无 BGM) | :131-134、:150-153 |
| W2 | drill:每步 `drill_*` 旁白播完→演练陨石逼近 4.5s | 同向滑动拨开(D1 左/D2 右/D3 上/D4 下) | `whoosh_<dir>`(语音先、音效后不叠放,:203 注释) | `_nextDrill` :191-204、`_spawnDrillOb` :206-214 |
| W3 | drill:无 drillOb(旁白/反馈播放中) | down=跳过演练;tap=跳句;**其余忽略** | voice | :217-223 |
| W4 | playing 常态 | 持续同向闪避;tap=加速(20s 引入后);doubletap=护盾(45s 引入后);down=无下方陨石时呼菜单(:295-301 折中) | `bgm/engine` + whoosh + 两档 alarm | `_onPlayGesture` :287-324 |
| W5 | feature:引入句播完后 8s 内 | 做 tap(boost 教学)或 doubletap(shield 教学);其他手势忽略等超时 | 静默(刷怪暂停、陨石清空,:347-349) | `_beginFeature` :339-352、`_onFeatureGesture` :354-374 |
| W6 | menu | tap=循环(resume/lobby/help 三项);doubletap=确认;up=帮助;down=恢复 | `bgm/menu` 300ms 快切 | :526-540 |
| W7 | over:结算链播完 | tap=再飞(voice 中 tap=跳句,:167-170);down=回大厅 | `bgm/menu`(链播完淡入,:573-575) | :166-172 |
### 5.3 播放内容但不接受输入的时刻
- 演练旁白(`_speakGuarded` 链)、`feat_boost/feat_shield` 引入句、对局归因句 `hit_*`/`protect_*`、里程碑 `ms_*`、结算链:播放中 tap=跳句(:221、:305、:357、文件头 :23-25),其余手势按上文 W3/W5 分流。
- playing 中**滑动闪避从不被 voice 阻塞**(:290-293 无 gate)。
- feature 期其他手势"忽略,等超时自动继续"(:359 注释)。
### 5.4 错误路径清单
- 演练做错/超时(`_drillFail` :243-256):`vibrate('error')` + `voice/drill_retry_<dir>`(当场重教重演);**失败 2 次 `drill_moveon` 自动带过**不卡流程。
- 保护期被撞(`_resolve` 第 4 级 :485-490):`sfx/shield_block` + `warning` 震 + `voice/protect_<dir>`(归因:哪边来的、你该怎么拨)。
- 正常被撞(:492-501):`sfx/hit` + `hit` 震 + `voice/hit_<dir>`;第 2 次追加 `hit_last`;第 3 次坠毁 `_gameOver`(:556-577):`error` 震、300ms 后 `hit` 震、`sfx/crash`、1200ms 后结算链 `time_1..4 + new_record + dodge_1..4 + restart_hint`。
- **纠错性语音:归因式**(告诉方位)而非指令式("向左滑")——演练期有指令式(drill_retry 说反了方向语义吗?见语音资产,代码只按 dir 选 id)。
- **注意**:`_gameOver` 结算链 `_speak(ids, ...)` 无看门狗(:573-575)——`time_*` 资产缺失则 `bgm/menu` 永不淡入。
### 5.5 已有帮助/状态播报
- menu up / menu 第三项 = `help_recap + settings_hint`(:530-532、:538)。**playing 中无帮助手势**(手势全是闪避)。
- 状态播报:保护期结束宣告 `protect_end`(:409-415,含 `_rearmRunTimers` :389-405 的暂停补偿补播);护盾充能完毕 `shield_ready`+`pickup` 震(:327-334);里程碑 10/25/50/100(:80、:504-512);受击次数走 `hit_last`。状态可辨识性对局内良好。
### 5.6 超时/定时器(全项目最多)
| 定时器 | 时长 | 后果 | 位置 |
|---|---|---|---|
| 保护期 | 60s | 撞击不掉血;结束播 `protect_end`+`warning` 震 | :56、:275-280、:409-415 |
| 单击/双击引入 | 第 20s/45s | 暂停刷怪进 feature | :60-61、:278-279 |
| feature 等待上限 | 8s | 不做也继续(`_endFeature`) | :62、:351 |
| 刷怪间隔 | 保护期 4000ms;之后 3200→1200ms(120s 爬满) | `_scheduleSpawn` 自递归 | :57-58、:67、:423-431 |
| 逼近时长 | 保护期/演练 4000/4500ms;之后 2800→1800ms | 撞击判定点 | :57-59、:436 |
| 警报两档 | 撞击前 1000ms/500ms | `alarm` vol0.5→1 + `danger` 震×2 | :50、:442-453 |
| 闪避窗口/宽限 | 1500ms / 250ms | `_resolve` 匹配 swipes | :48-49、:469-471 |
| 护盾 | 持续 6s / 充能 30s(完毕播报) | | :51-52、:327-334 |
| 加速 | 持续 1.8s / 冷却 3s | | :53-54 |
| 演练陨石 | 4500ms 超时=做错 | `_resolveDrill` | :59、:213、:238-241 |
| 坠毁结算延迟 | 1200ms | 爆炸声后开口 | :567 |
| `_speakGuarded` | 默认 10s | 演练/收尾链兜底 | :667-677 |
### 5.7 向导层接入点建议
1. **死亡教学升级**:`_resolve` :492-501 的 `hit_<dir>` 已含方位,向导层可在 60s 内第 2 次同向被撞时把归因句升级为指令句("左边的来了就向左滑"),插入点即 :498-500 的 ids 组装处。
2. **静默看门狗**:对局是连续流不需要;drill/feature 已有超时。唯一缺口是 ask(W1)——`second_run_ask` 播完后静默,可加提示或超时直接出发。
3. **结算链补看门狗**:`_gameOver` :573 `_speak`→`_speakGuarded`(守卫里起 `bgm/menu`),照抄 `_escape` :573 的兜底写法。
4. `_voiceBusy`(:646-648)是项目里最干净的 `_voice` 读取封装,可作向导层判断"此刻能否插播"的基准。
---
## 6. rpg《剑与声》(web/rpg/game.js,1105 行)
### 6.1 状态模型
- 类 `RpgGame extends Game`(:115)。`phase` 枚举(:118):`idle|ask|intro|explore|shop|battle|menu|over`。**`over` 从未被赋值**(仅 :241 default 注释提及),预留态;`idle` 仅构造后瞬态。
- 战斗内部二级状态机 `this._battle.state`(:543-561 赋初值):`B_START → PLAYER_SELECT → PLAYER_EXECUTE → CHECK_ENEMY_DEAD → ENEMY_TELEGRAPH → ENEMY_RESOLVE → VICTORY|DEFEAT|FLED|FLEE_FAIL`;另有 `teachStage: 'menu'|'attack'|'parry'|'defend'|null`(四步强制教学 :556)、`tier`(难度档 :559、:567-573)。
- 其他字段:`scene/optIdx/visited/flags/player/chapters/stats`、`_shopIdx/_menuIdx`、`_ambSrc`(手动环境音循环 :961-982)、`_session`。
- 流转驱动:**手势 + 语音 onComplete 链 + setTimeout(`_after` :1091)**。探索 `_enterScene`(:251-279)→desc 链→遭遇检查(:282-305)→`_speakOption` 报当前选项;战斗由 `_playerSelect`(:600)与 `_enemyTelegraph`(:770)交替推进。
### 6.2 等待输入点清单
| # | 状态/条件 | 玩家该做什么 | 此刻声音 | 位置 |
|---|---|---|---|---|
| W1 | ask:`sys_continue_ask` 播完 | tap=继续存档;right=新游戏。其余忽略 | **静默**(此时未进任何场景音乐,:167-179 无 bgm 调用) | :224-227 |
| W2 | explore:desc 链播完→当前选项名 `opt_*` 播完 | 左右=切选项(切一个播一个);tap=确认;doubletap=重听 desc+选项;up=状态链;down=菜单 | `amb_*` 常开 + `bgm/village|explore`(:945-949),选项名播完即静默 | `_speakOption` :307-312、`_onExploreGesture` :314-336 |
| W3 | shop:价格句播完 | 左右=切/tap=买/down=离开/up=状态 | 同上 | :451-464 |
| W4 | battle `PLAYER_SELECT`:`turn_player` 音 + 650ms 后 `cmd_<指令名>` 播完 | 左右=切指令(切一个播一个);tap=执行;up=敌我血量;down=逃跑 | `bgm/battle|boss`,cmd 播完即静默 | `_playerSelect` :600-612、:654-666 |
| W5 | battle `ENEMY_TELEGRAPH`:前摇窗口(tier 1.0/1.25/1.5/1.8s + 听风剑 +0.3s) | **doubletap=格挡**(窗口内);其他手势=`error` 震留在窗口 | 前摇音 `tel_*`(Boss 二阶段 1.2 倍速)+ `beat` 震开窗标识 | `_enemyTelegraph` :770-781、:667-678 |
| W6 | menu | 左右=切(RPG 豁免:菜单与探索同构,文件头 :31-33);tap=确认;up=帮助;down=关闭 | `bgm/menu`(amb 不停,:498) | :509-528 |
| W7 | intro/over | **不收任何手势**(:241 default) | voice 链 | :241 |
- 教学等待点:T0(teachStage='menu',左右切 2 次过关 :658-659)、T1(命中一次 :709-712)、T2(格挡或受击均通过 :819-825)、T3(防御一次 :717-721)——每步都有 8s/16s 看门狗(见 6.6)。
### 6.3 播放内容但不接受输入的时刻
- intro 链(`intro_1..3+skip_hint+sys_new_game`,:198-201):完全不收手势。
- explore 的 desc/NPC/事件链:tap=跳句(`_voicePlaying()` :324、:1054),**左右切选项/up/down 不被 voice 阻塞**(无全局 gate)——即播 desc 时也能盲切选项(切了会重播 `opt_*` 顶掉 desc)。
- 战斗:**tap 永不跳语音**(PLAYER_SELECT 中 tap=执行指令 :660-661);文件头 :24-26 声明 `cmd_*` 与 `tut_b*` 不可跳,代码事实上战斗中根本没有跳过路径。
- 战斗结算链(victory/defeat/ch1_end):`PLAYER_EXECUTE/ENEMY_RESOLVE` 等状态"其他战斗状态:手势忽略"(:679 注释)。
### 6.4 错误路径清单
- 技能无蓝:`btl_nomp` → 4s 后回 PLAYER_SELECT(:688-691);无药:`btl_noitem`(:692-695)。→ 告知型纠错。
- 逃跑:不可逃战 `error` 震 + `btl_noflee`(:839-844);Lv≥3 逃跑 70%,失败 `btl_flee_fail` → 敌方一个可格挡的前摇回合(:848-856)。
- **格挡窗口内做错手势:仅 `vibrate('error')`,无语音**(:675-677)——窗口一过就挨打。教学战有 `tut_b2`(:779),非教学战零提示。→ 向导层价值点。
- 受击:`sfx/hurt` + `hit` 震(:810-811);血量降档:`warning` 震 + `st_hp_low/mid`(:829-832,仅降档才播);战败:`hit`+`error` 震 + `btl_lose+btl_lose_hint` → 回村重生(:886-904)。
- 格挡成功:`sfx/parry` + `confirm` 震(:672-673,即时回调规避 tap 300ms 延迟)。
- 纠错性语音:教学战完整(tut_b0..b6 + 看门狗重播);常规战斗靠 `cmd_*` 循环播报即事实上的纠错。
### 6.5 已有帮助/状态播报
- explore/battle/shop up = `_speakStatus`(:431-447):探索 5 句链(lv/hp/gold/weapon/armor,全部分档整句),战斗 2 句(己方+敌方血量档位);血量 low 附 `warning` 震。**全 6 款最完整的状态播报**。
- menu 帮助 = `help_explore + help_battle + settings_hint`(:521-524)。
- 战斗回合标点:`sfx/turn_player`(≤0.4s)→650ms 间隙→`cmd_*`(:604-611 注释"听觉标点");前摇开窗 `beat` 震(:778)。
- 教学看门狗(`_armTeachWatchdog` :630-649):8s 无操作重播一次提示(tut_b0/b3/b6),16s 仍无进展**兜底自动推进**教学步;parry 步不兜底(前摇自然结算)。
- **探索/常规战斗无卡关计时器**:PLAYER_SELECT 挂机永远等(敌不打你);`_speakGuarded` 全部是语音资产缺失兜底而非卡关逻辑。
### 6.6 超时/定时器
| 定时器 | 时长 | 后果 | 位置 |
|---|---|---|---|
| intro 链兜底 | 30s | 进村 | :201 |
| desc 链兜底 | 20s | 存档→遭遇→报选项 | :278 |
| 战斗开场链兜底 | 60s | `_playerSelect` | :597 |
| 教学提示重播 | 8s | 重播 tut_b0/b3/b6 一次 | :634-638 |
| 教学兜底推进 | 16s | 自动进下一步 | :639-648 |
| 教学步链兜底 | 8s | `_playerSelect`/`_enemyTelegraph` | :619-622 |
| nomp/noitem 回合 | 4s | 回 PLAYER_SELECT | :689、:693 |
| turn_player→cmd | 650ms | 播指令名 | :607 |
| 敌亡链/连战 | 10s | `_nextEnemy`/`_victory` | :748 |
| half/phase2 链 | 8s | `_enemyTelegraph` | :759、:763 |
| 血量降档链 | 5s | 下一回合 | :831 |
| flee 两分支 | 6s | 结束/前摇 | :852、:855 |
| victory/defeat/ch1_end | 15s/15s/15s | 收尾 | :883、:893、:936 |
| 格挡窗口 | 1.0/1.25/1.5/1.8s(+0.3 剑) | 到点 `_enemyResolve` 挨打 | :559、:775-780 |
### 6.7 向导层接入点建议
1. **常规战斗静默看门狗**:`_playerSelect`(:600)挂"教学式"看门狗——非教学战 N 秒无手势重播 `cmd_<当前指令>`(照抄 `_armTeachWatchdog` :630 的 8s 重播模式,去掉 16s 强推)。这是把现成机制推广,改动最小。
2. **格挡窗口纠错语音**:`_onBattleGesture` :675-677 的 else 分支(现仅 error 震)加节流语音(如 `voice/` "现在是双击")——注意窗口只有 ~1s,语音应走 queue 且短,或仅震动升级(`warning`)。
3. **轮到你提示**:`turn_player` 标点已是范本;向导层可统一在 `_playerSelect`(:600)后追加播报型钩子。
4. **状态播报**:`_speakStatus`(:431)即向导层"状态播报"的内容源,接法 = 向导层主动调用即可。
5. `phase`+`_battle.state` 双层状态完备,向导层可精确知道"该做什么手势"(PLAYER_SELECT=指令循环,TELEGRAPH=doubletap),适合生成通用提示语。
---
## 7. 全局观察
### 7.1 对比表
| 维度 | storyline | simon | escape | rhythm | space | rpg |
|---|---|---|---|---|---|---|
| **等待输入时的静默处理** | ★☆☆ W1/W2/W3 全静默无信号、无超时;仅 BGM 垫 | ★★★★ input 前 confirm 震+第1轮 your_turn;但 W2 无限静默等 | ★★★☆ W1/W2 静默;PLAY 有 amb 常开可接受 | ★☆☆ main 无任何"轮到你"信号,全靠 BGM 网格自悟;end 链无看门狗 | ★★★☆ 连续流无轮次问题;ask/feature 有超时兜底 | ★★★★★ turn_player 标点+650ms+cmd 播报,是范本 |
| **错误反馈完整性** | ☆☆☆ 无效手势零反馈(无震无音无语音) | ★★★★ 教学纠错+3次 blunt 升级;对局 retry_use 自动重播 | ★★★★★ wrong/blunt/stuck 三级+nav 带路,全项目最完备 | ★★☆ 教学好(4 类归因+2次跳过);**对局 Miss 仅 error 震无任何语音** | ★★★★ 演练 retry+2次 moveon;对局归因式 protect_*/hit_* | ★★★★ nomp/noitem/noflee/教学看门狗齐全;**格挡窗口错手势仅震动** |
| **状态可辨识性** | ★★☆ 仅手动 up 查"第几幕";节点级不可知;无效手势无法分辨"没收到" | ★★★★ 每轮整句报数+机会播报+分段成绩;但对局无菜单可退 | ★★★★★ REOPEN/引导/三级卡关+amb/BGM 双层+进展重置,可辨识性最强 | ★★☆ 无对局进度播报;结算链完整;教学结束不可知(挂机停在 tut 静默等) | ★★★★ 保护期宣告/护盾就绪/里程碑/两档警报齐备 | ★★★★★ 四状态 BGM+turn 标点+上滑状态链;phase/state 双层机读友好 |
| **卡关/静默看门狗** | 无(仅 30s 资产兜底) | 无对局看门狗(最需要) | ★★★ 三级 60/120/180s+L3 循环 | 无(教学靠 _phaseEnd) | 无(feature 8s 除外) | ★★ 教学 8s/16s(非教学无) |
| **"轮到你"显式提示** | 无 | 震+首轮语音 | 无(开放探索不需) | 无 | 无(连续流不需) | sfx 标点(最好) |
| **单击跳句 gate 实现** | 自查 promptPlaying | `_tapSkipsVoice` 白名单 | `_voiceSkippable` 封装(最干净) | `_trySkipVoice` | `_voiceBusy` | `_voicePlaying` |
### 7.2 结论(谁最好、谁最薄弱)
- **错误反馈最完备:escape**(wrong→blunt 升级 + stuck 三级 + nav 带路,且全部语音化);**最薄弱:storyline**(无效手势零反馈——对剧情游戏可辩护,但盲玩家无法分辨"手势没识别"与"这里没这个手势"),其次 **rhythm 对局期**(Miss 无声无语,只有 error 震)与 **rpg 格挡窗口**(1s 决胜窗口内做错只有震动)。
- **等待输入静默处理最好:rpg**(turn_player+cmd 名构成完整"轮到你+你可选什么"信号链);**最薄弱:rhythm main**(领奏→应答切换零显式信号)与 **storyline W1**(静默即等待);**simon input 是最危险的静默等待**(完全无 BGM、无超时、玩家挂机则永久卡住)。
- **状态可辨识性最好:escape**(被动三级提示 + 主动 doubletap 兜底播报)与 **rpg**(双层状态 + 状态链);**最薄弱:storyline**(只有幕级播报)。
- **向导层的四个统一钩子落点**(所有游戏都已具备,接法成本低):
1. 语音插播通道:各游戏 `_speak`(storyline 用 `playSeq`)+ 引擎 `queue:true`(engine/audio.js:186);
2. "轮到你"注入点:storyline `onNodeSeqDone`:214 / simon `_toInput`:321 / escape `_advance` done:180 / rhythm `_pump` 首 cue:244 / space `_startRun` 首刷怪前:277 / rpg `_playerSelect`:600;
3. 静默看门狗模板:escape `_checkStuck`:479(轮询+进展重置+级别集合)与 rpg `_armTeachWatchdog`:630(N 秒重播+2N 兜底推进)两套现成范式,向导层统一实现后逐游戏替换;
4. 纠错语音注入点:simon :205-215 / escape `_guideWrong`:278 / space `_drillFail`:243 / rhythm `_tutEvaluate`:347 与 `_applyMiss`:548 / rpg :675-677。
- **引擎侧两件待办**(向导层前置):`audio._voice` 转为公开只读 API(5 处私有读);`skipVoice` 语义与 `_speakGuarded` 看门狗模式可下沉进引擎统一提供。另注意 rhythm `_finish`:679 与 space `_gameOver`:573 的结算链**无看门狗**,语音资产缺失会导致结算 BGM 永不淡入——接入向导层时应一并修复。