diff --git a/docs/design/01-escape-redesign.md b/docs/design/01-escape-redesign.md index fd5c3dc8..09277d77 100644 --- a/docs/design/01-escape-redesign.md +++ b/docs/design/01-escape-redesign.md @@ -103,3 +103,46 @@ start → GUIDE(可下滑跳过)→ PLAY(自由探索 + 三级提示计时 ``` PLAY 阶段手势:四向滑=移动 / 单击=重播环境音+检查 / 双击=使用物品(无目标→背包+L2 明示)。ENDING:单击=重开,下滑=菜单(回大厅)。 + +--- + +## 修订记录(追加不改旧文,编号自 R1 起) + +### R1 · escape-state round2 状态引导改进(2026-09-15,方案见 25 号文档 §4.1) + +按 25 号文档 §4.3 实施清单 F1-F9 落地(F6/B5b 按方案"可分期"留档): + +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 都至少听过 + 一次目标句;二周目可单击跳过)。 + +新断言登记(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 新增主表句」。 diff --git a/docs/design/01-escape-voice-ids.md b/docs/design/01-escape-voice-ids.md index 7e900301..9b2f0219 100644 --- a/docs/design/01-escape-voice-ids.md +++ b/docs/design/01-escape-voice-ids.md @@ -53,3 +53,26 @@ web/engine/gesture.js inputMode())时,引擎 voice 候选链前置加载 | `again` | 按空格,再来一次。按下方向键,打开菜单。 | | `help_ops` | 方向键,在房间之间移动。按空格,检查房间,或者重听房间的声音。按回车,使用物品,或者查看背包。 | | `skip_hint` | 按空格可跳过旁白。 | + +--- + +## escape-state round2 新增主表句(10 句,25 号文档 §4.1 方案 B1/B2/B3) + +> 状态引导改进方案([25-escape-map-flow.md](25-escape-map-flow.md) §4.1 B1/B2/B3、§4.3 +> 实施清单 F1/F2/F3)的新增语音,台词与 25 号文档草案**逐字一致**。全部不含操作词, +> 无键盘变体(变体只覆盖含操作词的句子,id 集合 ⊆ 主表的既有约定不变)。 +> 管线:`gen_voice_dual.py --game escape --only ` → `--check` → `--convert` → +> `check_all_assets.py` 终检。 + +| id | 台词全文 | 方案 | +|---|---|---| +| `wall_dir_l` | 左边是墙。 | B1 撞墙方位(陈述式) | +| `wall_dir_r` | 右边是墙。 | B1 | +| `wall_dir_u` | 上边是墙。 | B1 | +| `wall_dir_d` | 下边是墙。 | B1 | +| `gd_cabinet_dir` | 铁柜,在右面的房间。 | B2 目标名+方位一体句(found_key 链尾) | +| `gd_box_dir` | 电箱,在左面的房间。 | B2(gd_next_fuse 链尾) | +| `gd_gate_dir` | 大门,在右面的房间。 | B2(fix_box 链尾) | +| `gd_prog_1` | 钥匙已经到手。找到能用它的锁。 | B3 无进展进度重述(stage=cabinet) | +| `gd_prog_2` | 铁柜已经打开。电还没有通。 | B3(stage=box) | +| `gd_prog_3` | 电已经通了。去找那扇大门。 | B3(stage=gate) | diff --git a/docs/design/25-escape-map-flow.md b/docs/design/25-escape-map-flow.md new file mode 100644 index 00000000..78e8ecfd --- /dev/null +++ b/docs/design/25-escape-map-flow.md @@ -0,0 +1,645 @@ +# 25 · 《暗室》地图与玩家流程文档 + 状态引导改进方案 + +> **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 人设评审)。方案只产出设计,未实施。 +> +> 轮次:escape-state round1(2026-09-14)。靶问题(用户真机反馈): +> **玩家不知道自己的状态**——不知道自己在哪、不知道目标、不知道进度。 + +--- + +# 阶段一 · as-built 文档化 + +## 1. 地图章 + +### 1.1 房间总图(3x3,坐标 x 0西→2东,y 0北→2南) + +``` + x=0(西) x=1 x=2(东) +y=0 ┌───────────┬───────────┬───────────┐ +(北) │ (0,0) │ (1,0) │ (2,0) │ + │ 电箱房 │ 空房·风 │ 空房·嗡鸣 │ + │ amb_power │ amb_wind │ amb_hum │ + ├───────────┼───────────┼───────────┤ +y=1 │ (0,1) │ (1,1) │ (2,1) │ + │ 空房·嗡鸣 │ 起点 │ 铁柜房 │ + │ amb_hum │ amb_drip │ amb_cabinet│ + ├───────────┼───────────┼───────────┤ +y=2 │ (0,2) │ (1,2) │ (2,2) │ +(南) │ 储物间 │ 空房·风 │ 大门 │ + │ amb_storage│ amb_wind │ amb_gate │ + └───────────┴───────────┴───────────┘ + (通电后 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)。 + +### 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 → 逃脱结算 | + +要点: +- **环境音 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 的设计意图,代码兑现)。 + +### 1.3 解谜链依赖图 + +``` +[储物间 (0,2) 单击] ──得钥匙──▶ [铁柜房 (2,1) 双击] ──得保险丝──▶ +[电箱房 (0,0) 双击] ──通电──▶ [大门 (2,2) 双击] ──▶ 逃脱结算 + hasKey=true cabinetOpen=true powerOn=true + hasFuse=true +``` + +- 链上 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)在这条路径上分别给出"右面的房间"(储物间→铁柜房)与 + "左面的房间"(铁柜房→电箱房)。 + +### 1.4 房间 → 谜题 → 状态映射表 + +| 阶段 `_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` 东南角大门双击 | 逃脱 | + +- 进展事件调 `_progressEvent()`:清空 `_stuckShown`,下段静默期从 L1 重新分级 + (game.js:578-582)。 +- 代码把**首到储物间**也算进展事件(KEY_ROOMS 含 (0,2)),01 号文档 §3.1 + 只列了铁柜房/电箱房/大门——差异见 §1.5-③。 + +### 1.5 代码 vs 设计文档差异清单(以代码为准) + +| # | 01 号设计文档 | 现行代码 | 性质 | +|---|---|---|---| +| ① | §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` 通电变体) | 一致(超集) | + +另注:14 号文档 round2 F2.3「撞墙接播 guide_blunt_ 方位句」曾被落地、 +后被 round1-live **回退**为只播 `voice/wall` 极简句(game.js:400-403 注释), +教学腔方位句仅限引导期 G3 纠错路径——这条历史是阶段三 B1 方案的前车之鉴 +(方位信息要给,但措辞不能再走教学腔)。 + +--- + +## 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) | 二周目抉择 | + +### 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 评审的死线项。 + +--- + +# 阶段三 · 状态引导改进方案(escape-state round1) + +## 4.0 取证摘要 + +- **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`),双席对 + 盲区清单达成一致。 + +设计约束(本方案全部条目的共同红线): +- **00 号宪法**:整句预生成、禁运行时拼接;数字信息分档整句;震动语义按 + 00 §2.2 既有 10 震型、不新增语义震;手势映射零改动(上滑仍是移动)。 +- **14 号向导层**:三档语义(full=earcon+语音+震 / standard=耳标+震 / off= + 全静);新 earcon 不进本方案(引擎 8 签名词表修订成本过高,方位与进度信息 + 一律走语音通道,游戏反馈层全档位,与 gd_next_fuse/gd_room 先例同级)。 +- **归因真话律**:进度句报的完成数必须等于真实状态机(hasKey/cabinetOpen/ + powerOn 派生),不许多报少报。 +- **难度不降低**:所有新语音只报"状态与方向",不报"做法";L2/L3 的到达不因 + 新通道提前或变频繁。 +- **节奏不破**:单局 15-30 分钟的宏观节奏由谜题链决定,新播报全部挂在 + "进展事件 / 首次 / 限频"上,不给活跃玩家增加语音负担。 + +## 4.1 方案条目 + +### B1 · 撞墙报方位(陈述式,非教学腔) + +- **问题(证据)**:撞墙反馈只有 `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 到达;二周目单击可跳过 | + +统一收尾(每轮 F 实施完必做):`check_all_assets.py` 全绿 → 6 playtest +(guide=off)断言对账(escape 现有 103 条,F4/F5/F7/F9 会改其中若干, +须逐条登记变更原因)→ `guide.test.mjs` 增补 → live 复跑一局验证新句在 +真实混音中可闻(B2/B3 短句尤其要做 ASR 层判定)。 + +--- + +# 阶段四 · 实施勾账(escape-state round2,2026-09-15) + +> §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 处, +> 逐条登记于下表"断言变更"列。 + +| # | 落地 | 断言登记(场景.条数) | 断言变更 | 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) | + +统一收尾执行记录:`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 对照表)。 + +## 实施裁定记录(编号自本轮起,接续追加不改旧文) + +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③"被作废的方向 + 补撞后正常接播"冻结)。 + +## 盲区清单核销(§3.2 → round2) + +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`,每个盲区时刻现在都有落点。 diff --git a/research/escape-state/round1/live-events.jsonl b/research/escape-state/round1/live-events.jsonl new file mode 100644 index 00000000..790db220 --- /dev/null +++ b/research/escape-state/round1/live-events.jsonl @@ -0,0 +1,271 @@ +{"ev":"probe_ready","t":50.099999994039536,"ta":null} +{"ev":"gesture","type":"tap","x":195,"y":422,"t":30065,"ta":null} +{"ev":"recstart","mime":"audio/webm;codecs=opus","t":30170.90000000596,"ta":0} +{"ev":"fetch","url":"audio/sfx/step.ogg","status":200,"ok":true,"ms":14,"t":30185.29999998212,"ta":0.014} +{"ev":"fetch","url":"audio/sfx/wall.ogg","status":200,"ok":true,"ms":14,"t":30185.40000000596,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/pickup.ogg","status":200,"ok":true,"ms":14,"t":30185.5,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/unlock.ogg","status":200,"ok":true,"ms":14,"t":30185.5,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/door_creak.ogg","status":200,"ok":true,"ms":14,"t":30185.5,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/spark.ogg","status":200,"ok":true,"ms":14,"t":30185.5,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/power_on.ogg","status":200,"ok":true,"ms":14,"t":30185.69999998808,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/gate_open.ogg","status":200,"ok":true,"ms":15,"t":30186.40000000596,"ta":0.016} +{"ev":"fetch","url":"audio/sfx/win_chime.ogg","status":200,"ok":true,"ms":16,"t":30187.79999998212,"ta":0.017} +{"ev":"fetch","url":"audio/sfx/amb_storage.ogg","status":200,"ok":true,"ms":18,"t":30189.69999998808,"ta":0.019} +{"ev":"fetch","url":"audio/sfx/amb_cabinet.ogg","status":200,"ok":true,"ms":18,"t":30190.40000000596,"ta":0.02} +{"ev":"fetch","url":"audio/sfx/amb_power.ogg","status":200,"ok":true,"ms":20,"t":30192.40000000596,"ta":0.022} +{"ev":"fetch","url":"audio/sfx/amb_gate.ogg","status":200,"ok":true,"ms":22,"t":30194,"ta":0.023} +{"ev":"fetch","url":"audio/sfx/amb_gate_live.ogg","status":200,"ok":true,"ms":28,"t":30200.19999998808,"ta":0.029} +{"ev":"fetch","url":"audio/sfx/amb_drip.ogg","status":200,"ok":true,"ms":28,"t":30200.29999998212,"ta":0.029} +{"ev":"fetch","url":"audio/sfx/amb_wind.ogg","status":200,"ok":true,"ms":29,"t":30201.19999998808,"ta":0.03} +{"ev":"fetch","url":"audio/sfx/amb_hum.ogg","status":200,"ok":true,"ms":29,"t":30201.29999998212,"ta":0.03} +{"ev":"fetch","url":"audio/voice/female/g0_intro.ogg","status":200,"ok":true,"ms":30,"t":30202.59999999404,"ta":0.032} +{"ev":"fetch","url":"audio/voice/female/skip_hint.ogg","status":200,"ok":true,"ms":33,"t":30205.29999998212,"ta":0.034} +{"ev":"fetch","url":"audio/voice/female/g1_move.ogg","status":200,"ok":true,"ms":34,"t":30206.69999998808,"ta":0.036} +{"ev":"fetch","url":"audio/voice/female/reopen_prompt.ogg","status":200,"ok":true,"ms":35,"t":30207.90000000596,"ta":0.037} +{"ev":"fetch","url":"audio/voice/female/start_room.ogg","status":200,"ok":true,"ms":36,"t":30208.5,"ta":0.038} +{"ev":"fetch","url":"audio/bgm/menu.ogg","status":200,"ok":true,"ms":36,"t":30209,"ta":0.038} +{"ev":"fetch","url":"audio/bgm/drone.ogg","status":200,"ok":true,"ms":37,"t":30209.69999998808,"ta":0.039} +{"ev":"vstart","url":"audio/voice/female/g0_intro.ogg","t":30259.19999998808,"dur":6.64,"ta":0.088} +{"ev":"start","t":30453.69999998808,"sid":1,"url":"audio/bgm/drone.ogg","layer":"bgm","dur":117,"rate":1,"loop":true,"when":0,"ta":0.283} +{"ev":"vend","url":"audio/voice/female/g0_intro.ogg","t":37066.59999999404,"reason":"kill","ta":6.896} +{"ev":"vstart","url":"audio/voice/female/g1_move.ogg","t":38267.09999999404,"dur":4.56,"ta":8.096} +{"ev":"vend","url":"audio/voice/female/g1_move.ogg","t":42847.59999999404,"reason":"kill","ta":12.677} +{"ev":"vstart","url":"audio/voice/female/skip_hint.ogg","t":42847.90000000596,"dur":1.6,"ta":12.677} +{"ev":"vend","url":"audio/voice/female/skip_hint.ogg","t":44449.5,"reason":"kill","ta":14.279} +{"ev":"gesture","type":"swipeup","x":195,"y":422,"t":64687.79999998212,"ta":34.517} +{"ev":"vibrate","pattern":"[10]","t":65048.29999998212,"ta":34.877} +{"ev":"start","t":65049.09999999404,"sid":2,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":34.878} +{"ev":"fetch","url":"audio/voice/female/g1_done.ogg","status":200,"ok":true,"ms":3,"t":65051.40000000596,"ta":34.881} +{"ev":"vstart","url":"audio/voice/female/g1_done.ogg","t":65055.59999999404,"dur":2.72,"ta":34.885} +{"ev":"vend","url":"audio/voice/female/g1_done.ogg","t":67760.29999998212,"reason":"kill","ta":37.589} +{"ev":"fetch","url":"audio/voice/female/g2_wall.ogg","status":200,"ok":true,"ms":3,"t":67763.09999999404,"ta":37.592} +{"ev":"vstart","url":"audio/voice/female/g2_wall.ogg","t":67768.69999998808,"dur":5.68,"ta":37.598} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":71744.59999999404,"ta":41.574} +{"ev":"vibrate","pattern":"[10]","t":72097.79999998212,"ta":41.927} +{"ev":"vend","url":"audio/voice/female/g2_wall.ogg","t":73448.79999998212,"reason":"kill","ta":43.278} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":76509.09999999404,"ta":46.338} +{"ev":"vibrate","pattern":"[10]","t":76864.19999998808,"ta":46.693} +{"ev":"vibrate","pattern":"[60,40,60]","t":76864.40000000596,"ta":46.694} +{"ev":"start","t":76864.69999998808,"sid":3,"url":"audio/sfx/wall.ogg","layer":"sfx","dur":0.44709750566893425,"rate":1,"loop":false,"when":0,"ta":46.694} +{"ev":"fetch","url":"audio/voice/female/wall.ogg","status":200,"ok":true,"ms":2,"t":76866.79999998212,"ta":46.696} +{"ev":"vstart","url":"audio/voice/female/wall.ogg","t":76870.29999998212,"dur":2.08,"ta":46.699} +{"ev":"vend","url":"audio/voice/female/wall.ogg","t":79207.09999999404,"reason":"kill","ta":49.036} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":83826.69999998808,"ta":53.656} +{"ev":"vibrate","pattern":"[10]","t":84180.59999999404,"ta":54.01} +{"ev":"vibrate","pattern":"[60,40,60]","t":84180.79999998212,"ta":54.01} +{"ev":"start","t":84180.90000000596,"sid":4,"url":"audio/sfx/wall.ogg","layer":"sfx","dur":0.44709750566893425,"rate":1,"loop":false,"when":0,"ta":54.01} +{"ev":"vstart","url":"audio/voice/female/wall.ogg","t":84181,"dur":2.08,"ta":54.01} +{"ev":"vend","url":"audio/voice/female/wall.ogg","t":86520.40000000596,"reason":"kill","ta":56.35} +{"ev":"fetch","url":"audio/voice/female/g3_left.ogg","status":200,"ok":true,"ms":3,"t":86523.29999998212,"ta":56.352} +{"ev":"vstart","url":"audio/voice/female/g3_left.ogg","t":86526.79999998212,"dur":2.96,"ta":56.356} +{"ev":"vend","url":"audio/voice/female/g3_left.ogg","t":89515.59999999404,"reason":"kill","ta":59.345} +{"ev":"gesture","type":"swipedown","x":195,"y":422,"t":91087.40000000596,"ta":60.917} +{"ev":"vibrate","pattern":"[10]","t":91447,"ta":61.276} +{"ev":"fetch","url":"audio/voice/female/guide_skip.ogg","status":200,"ok":true,"ms":2,"t":91449.69999998808,"ta":61.279} +{"ev":"vstart","url":"audio/voice/female/guide_skip.ogg","t":91453.79999998212,"dur":2.88,"ta":61.283} +{"ev":"vend","url":"audio/voice/female/guide_skip.ogg","t":94437.79999998212,"reason":"kill","ta":64.267} +{"ev":"vstart","url":"audio/voice/female/start_room.ogg","t":94438.09999999404,"dur":7.2,"ta":64.267} +{"ev":"start","t":94438.90000000596,"sid":5,"url":"audio/sfx/amb_drip.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":64.268} +{"ev":"vend","url":"audio/voice/female/start_room.ogg","t":101983.69999998808,"reason":"kill","ta":71.813} +{"ev":"vibrate","pattern":"[30,50,30,50,30]","t":155172.90000000596,"ta":125.002} +{"ev":"fetch","url":"audio/voice/female/stuck_ask.ogg","status":200,"ok":true,"ms":3,"t":155176.09999999404,"ta":125.005} +{"ev":"vstart","url":"audio/voice/female/stuck_ask.ogg","t":155179.29999998212,"dur":1.28,"ta":125.008} +{"ev":"vend","url":"audio/voice/female/stuck_ask.ogg","t":156778.09999999404,"reason":"kill","ta":126.607} +{"ev":"fetch","url":"audio/voice/female/stuck_key_1.ogg","status":200,"ok":true,"ms":2,"t":156780.59999999404,"ta":126.61} +{"ev":"vstart","url":"audio/voice/female/stuck_key_1.ogg","t":156784.29999998212,"dur":2.88,"ta":126.613} +{"ev":"vend","url":"audio/voice/female/stuck_key_1.ogg","t":159773.19999998808,"reason":"kill","ta":129.602} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":161038.69999998808,"ta":130.868} +{"ev":"vibrate","pattern":"[10]","t":161394.19999998808,"ta":131.223} +{"ev":"start","t":161394.5,"sid":6,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":131.224} +{"ev":"start","t":161394.59999999404,"sid":7,"url":"audio/sfx/amb_hum.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":131.224} +{"ev":"gesture","type":"swipedown","x":195,"y":422,"t":167817.79999998212,"ta":137.647} +{"ev":"vibrate","pattern":"[10]","t":168177.69999998808,"ta":138.007} +{"ev":"start","t":168178.29999998212,"sid":8,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":138.007} +{"ev":"start","t":168178.29999998212,"sid":9,"url":"audio/sfx/amb_storage.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":138.007} +{"ev":"fetch","url":"audio/voice/female/room_storage.ogg","status":200,"ok":true,"ms":3,"t":168180.79999998212,"ta":138.01} +{"ev":"vstart","url":"audio/voice/female/room_storage.ogg","t":168186.90000000596,"dur":5.36,"ta":138.016} +{"ev":"vend","url":"audio/voice/female/room_storage.ogg","t":173866.79999998212,"reason":"kill","ta":143.696} +{"ev":"gesture","type":"tap","x":195,"y":422,"t":175088.19999998808,"ta":144.917} +{"ev":"vibrate","pattern":"[20]","t":175453.29999998212,"ta":145.282} +{"ev":"vibrate","pattern":"[15]","t":175453.59999999404,"ta":145.283} +{"ev":"start","t":175453.69999998808,"sid":10,"url":"audio/sfx/amb_storage.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":145.283} +{"ev":"start","t":175453.69999998808,"sid":11,"url":"audio/sfx/pickup.ogg","layer":"sfx","dur":0.3470748299319728,"rate":1,"loop":false,"when":0,"ta":145.283} +{"ev":"fetch","url":"audio/voice/female/found_key.ogg","status":200,"ok":true,"ms":3,"t":176256.69999998808,"ta":146.086} +{"ev":"vstart","url":"audio/voice/female/found_key.ogg","t":176266.69999998808,"dur":9.96,"ta":146.096} +{"ev":"vend","url":"audio/voice/female/found_key.ogg","t":186404.29999998212,"reason":"kill","ta":156.233} +{"ev":"fetch","url":"audio/voice/female/gd_room_r.ogg","status":200,"ok":true,"ms":3,"t":186407.40000000596,"ta":156.237} +{"ev":"vstart","url":"audio/voice/female/gd_room_r.ogg","t":186410.29999998212,"dur":1.28,"ta":156.239} +{"ev":"vend","url":"audio/voice/female/gd_room_r.ogg","t":188006.40000000596,"reason":"kill","ta":157.836} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":205702,"ta":175.531} +{"ev":"vibrate","pattern":"[10]","t":206059.29999998212,"ta":175.888} +{"ev":"start","t":206059.59999999404,"sid":12,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":175.889} +{"ev":"start","t":206059.59999999404,"sid":13,"url":"audio/sfx/amb_wind.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":175.889} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":209469.5,"ta":179.299} +{"ev":"vibrate","pattern":"[10]","t":209825.5,"ta":179.655} +{"ev":"start","t":209825.90000000596,"sid":14,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":179.655} +{"ev":"start","t":209825.90000000596,"sid":15,"url":"audio/sfx/amb_gate.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":179.655} +{"ev":"fetch","url":"audio/voice/female/room_gate.ogg","status":200,"ok":true,"ms":2,"t":209828,"ta":179.657} +{"ev":"vstart","url":"audio/voice/female/room_gate.ogg","t":209833,"dur":3.52,"ta":179.662} +{"ev":"gesture","type":"swipeup","x":195,"y":422,"t":213236.40000000596,"ta":183.066} +{"ev":"vibrate","pattern":"[10]","t":213592.19999998808,"ta":183.421} +{"ev":"vend","url":"audio/voice/female/room_gate.ogg","t":213592.40000000596,"reason":"kill","ta":183.422} +{"ev":"start","t":213592.59999999404,"sid":16,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":183.422} +{"ev":"start","t":213592.59999999404,"sid":17,"url":"audio/sfx/amb_cabinet.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":183.422} +{"ev":"fetch","url":"audio/voice/female/room_cabinet.ogg","status":200,"ok":true,"ms":3,"t":213595.19999998808,"ta":183.424} +{"ev":"vstart","url":"audio/voice/female/room_cabinet.ogg","t":213599.19999998808,"dur":2.64,"ta":183.428} +{"ev":"vend","url":"audio/voice/female/room_cabinet.ogg","t":216309.29999998212,"reason":"kill","ta":186.138} +{"ev":"gesture","type":"doubletap","x":195,"y":422,"t":222734.5,"ta":192.564} +{"ev":"vibrate","pattern":"[20]","t":222982.59999999404,"ta":192.812} +{"ev":"vibrate","pattern":"[100]","t":222982.90000000596,"ta":192.812} +{"ev":"start","t":222983.29999998212,"sid":18,"url":"audio/sfx/unlock.ogg","layer":"sfx","dur":0.49777777777777776,"rate":1,"loop":false,"when":0,"ta":192.812} +{"ev":"start","t":222983.29999998212,"sid":19,"url":"audio/sfx/door_creak.ogg","layer":"sfx","dur":1.3999773242630384,"rate":1,"loop":false,"when":0,"ta":192.812} +{"ev":"fetch","url":"audio/voice/female/unlock_cabinet.ogg","status":200,"ok":true,"ms":3,"t":222985.59999999404,"ta":192.815} +{"ev":"vstart","url":"audio/voice/female/unlock_cabinet.ogg","t":222997.40000000596,"dur":11.48,"ta":192.827} +{"ev":"vend","url":"audio/voice/female/unlock_cabinet.ogg","t":234628.09999999404,"reason":"kill","ta":204.457} +{"ev":"fetch","url":"audio/voice/female/gd_next_fuse.ogg","status":200,"ok":true,"ms":3,"t":234630.79999998212,"ta":204.46} +{"ev":"vstart","url":"audio/voice/female/gd_next_fuse.ogg","t":234635.5,"dur":3.28,"ta":204.465} +{"ev":"vend","url":"audio/voice/female/gd_next_fuse.ogg","t":238087.59999999404,"reason":"kill","ta":207.917} +{"ev":"fetch","url":"audio/voice/female/gd_room_l.ogg","status":200,"ok":true,"ms":3,"t":238090.40000000596,"ta":207.92} +{"ev":"vstart","url":"audio/voice/female/gd_room_l.ogg","t":238093.5,"dur":1.68,"ta":207.923} +{"ev":"vend","url":"audio/voice/female/gd_room_l.ogg","t":239968.19999998808,"reason":"kill","ta":209.797} +{"ev":"gesture","type":"doubletap","x":195,"y":422,"t":252754.09999999404,"ta":222.583} +{"ev":"vibrate","pattern":"[20]","t":253003,"ta":222.832} +{"ev":"vibrate","pattern":"[60,40,60]","t":253003.09999999404,"ta":222.832} +{"ev":"fetch","url":"audio/voice/female/use_fail.ogg","status":200,"ok":true,"ms":3,"t":253005.69999998808,"ta":222.835} +{"ev":"vstart","url":"audio/voice/female/use_fail.ogg","t":253013.29999998212,"dur":7.36,"ta":222.842} +{"ev":"vend","url":"audio/voice/female/use_fail.ogg","t":260562.69999998808,"reason":"kill","ta":230.392} +{"ev":"fetch","url":"audio/voice/female/stuck_box_2.ogg","status":200,"ok":true,"ms":3,"t":260565.59999999404,"ta":230.395} +{"ev":"vstart","url":"audio/voice/female/stuck_box_2.ogg","t":260571.29999998212,"dur":4.88,"ta":230.4} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":263135.90000000596,"ta":232.965} +{"ev":"vibrate","pattern":"[10]","t":263489.90000000596,"ta":233.319} +{"ev":"start","t":263490.09999999404,"sid":20,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":233.319} +{"ev":"start","t":263490.09999999404,"sid":21,"url":"audio/sfx/amb_drip.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":233.319} +{"ev":"vend","url":"audio/voice/female/stuck_box_2.ogg","t":265508.09999999404,"reason":"kill","ta":235.337} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":266899.09999999404,"ta":236.728} +{"ev":"vibrate","pattern":"[10]","t":267256.7999999821,"ta":237.086} +{"ev":"start","t":267257.09999999404,"sid":22,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":237.086} +{"ev":"start","t":267257.09999999404,"sid":23,"url":"audio/sfx/amb_hum.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":237.086} +{"ev":"gesture","type":"swipeup","x":195,"y":422,"t":270667.09999999404,"ta":240.496} +{"ev":"vibrate","pattern":"[10]","t":271023.40000000596,"ta":240.853} +{"ev":"start","t":271023.7999999821,"sid":24,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":240.853} +{"ev":"start","t":271023.7999999821,"sid":25,"url":"audio/sfx/amb_power.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":240.853} +{"ev":"fetch","url":"audio/voice/female/room_power.ogg","status":200,"ok":true,"ms":2,"t":271026,"ta":240.855} +{"ev":"vstart","url":"audio/voice/female/room_power.ogg","t":271030.2999999821,"dur":2.64,"ta":240.859} +{"ev":"vend","url":"audio/voice/female/room_power.ogg","t":273750.5,"reason":"kill","ta":243.58} +{"ev":"gesture","type":"doubletap","x":195,"y":422,"t":297793,"ta":267.622} +{"ev":"vibrate","pattern":"[20]","t":298041.1999999881,"ta":267.87} +{"ev":"vibrate","pattern":"[100]","t":298041.40000000596,"ta":267.871} +{"ev":"start","t":298041.6999999881,"sid":26,"url":"audio/sfx/spark.ogg","layer":"sfx","dur":0.8970975056689342,"rate":1,"loop":false,"when":0,"ta":267.871} +{"ev":"start","t":298041.6999999881,"sid":27,"url":"audio/sfx/power_on.ogg","layer":"sfx","dur":2.2,"rate":1,"loop":false,"when":0,"ta":267.871} +{"ev":"fetch","url":"audio/voice/female/fix_box.ogg","status":200,"ok":true,"ms":2,"t":298043.90000000596,"ta":267.873} +{"ev":"vstart","url":"audio/voice/female/fix_box.ogg","t":298051.7999999821,"dur":7.52,"ta":267.881} +{"ev":"vend","url":"audio/voice/female/fix_box.ogg","t":305605.40000000596,"reason":"kill","ta":275.435} +{"ev":"vstart","url":"audio/voice/female/gd_room_r.ogg","t":305605.59999999404,"dur":1.28,"ta":275.435} +{"ev":"vend","url":"audio/voice/female/gd_room_r.ogg","t":307207.59999999404,"reason":"kill","ta":277.037} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":308359.2999999821,"ta":278.188} +{"ev":"vibrate","pattern":"[10]","t":308721.90000000596,"ta":278.551} +{"ev":"start","t":308722.09999999404,"sid":28,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":278.551} +{"ev":"start","t":308722.09999999404,"sid":29,"url":"audio/sfx/amb_wind.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":278.551} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":312131.6999999881,"ta":281.961} +{"ev":"vibrate","pattern":"[10]","t":312488,"ta":282.317} +{"ev":"start","t":312488.2999999821,"sid":30,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":282.317} +{"ev":"start","t":312488.2999999821,"sid":31,"url":"audio/sfx/amb_hum.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":282.317} +{"ev":"gesture","type":"swipedown","x":195,"y":422,"t":315896.1999999881,"ta":285.725} +{"ev":"vibrate","pattern":"[10]","t":316254.90000000596,"ta":286.084} +{"ev":"start","t":316255.1999999881,"sid":32,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":286.084} +{"ev":"start","t":316255.1999999881,"sid":33,"url":"audio/sfx/amb_cabinet.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":286.084} +{"ev":"gesture","type":"tap","x":195,"y":422,"t":325678.09999999404,"ta":295.507} +{"ev":"vibrate","pattern":"[20]","t":326040.59999999404,"ta":295.87} +{"ev":"start","t":326041,"sid":34,"url":"audio/sfx/amb_cabinet.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":295.87} +{"ev":"fetch","url":"audio/voice/female/empty_1.ogg","status":200,"ok":true,"ms":3,"t":326844.5,"ta":296.674} +{"ev":"vstart","url":"audio/voice/female/empty_1.ogg","t":326851.2999999821,"dur":5.2,"ta":296.68} +{"ev":"vend","url":"audio/voice/female/empty_1.ogg","t":332074,"reason":"kill","ta":301.903} +{"ev":"gesture","type":"doubletap","x":195,"y":422,"t":335912.40000000596,"ta":305.742} +{"ev":"vibrate","pattern":"[20]","t":336160,"ta":305.989} +{"ev":"vibrate","pattern":"[60,40,60]","t":336160.1999999881,"ta":305.989} +{"ev":"vstart","url":"audio/voice/female/use_fail.ogg","t":336160.2999999821,"dur":7.36,"ta":305.989} +{"ev":"vend","url":"audio/voice/female/use_fail.ogg","t":343706.1999999881,"reason":"kill","ta":313.535} +{"ev":"fetch","url":"audio/voice/female/stuck_gate_2.ogg","status":200,"ok":true,"ms":3,"t":343708.90000000596,"ta":313.538} +{"ev":"vstart","url":"audio/voice/female/stuck_gate_2.ogg","t":343715.2999999821,"dur":5.44,"ta":313.544} +{"ev":"vend","url":"audio/voice/female/stuck_gate_2.ogg","t":349394.6999999881,"reason":"kill","ta":319.224} +{"ev":"gesture","type":"swipedown","x":195,"y":422,"t":369477.2999999821,"ta":339.306} +{"ev":"vibrate","pattern":"[10]","t":369835.6999999881,"ta":339.665} +{"ev":"start","t":369836,"sid":35,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":339.665} +{"ev":"start","t":369836,"sid":36,"url":"audio/sfx/amb_gate_live.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":339.665} +{"ev":"gesture","type":"tap","x":195,"y":422,"t":379260.40000000596,"ta":349.09} +{"ev":"vibrate","pattern":"[20]","t":379623.90000000596,"ta":349.453} +{"ev":"start","t":379624.2999999821,"sid":37,"url":"audio/sfx/amb_gate_live.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":349.453} +{"ev":"fetch","url":"audio/voice/female/inspect_gate_ready.ogg","status":200,"ok":true,"ms":3,"t":380427.1999999881,"ta":350.256} +{"ev":"vstart","url":"audio/voice/female/inspect_gate_ready.ogg","t":380436,"dur":8.72,"ta":350.265} +{"ev":"gesture","type":"doubletap","x":195,"y":422,"t":388748.40000000596,"ta":358.578} +{"ev":"vibrate","pattern":"[20]","t":388997.1999999881,"ta":358.826} +{"ev":"vibrate","pattern":"[100]","t":388997.5,"ta":358.827} +{"ev":"vend","url":"audio/voice/female/inspect_gate_ready.ogg","t":388998.09999999404,"reason":"kill","ta":358.827} +{"ev":"fetch","url":"audio/voice/female/escape.ogg","status":200,"ok":true,"ms":3,"t":389000.59999999404,"ta":358.83} +{"ev":"vstart","url":"audio/voice/female/escape.ogg","t":389013.90000000596,"dur":15.32,"ta":358.843} +{"ev":"stop","t":391557.7999999821,"sid":1,"when":0,"ta":361.387} +{"ev":"start","t":391599.09999999404,"sid":38,"url":"audio/sfx/gate_open.ogg","layer":"sfx","dur":2.7999773242630384,"rate":1,"loop":false,"when":0,"ta":361.428} +{"ev":"start","t":391599.09999999404,"sid":39,"url":"audio/sfx/win_chime.ogg","layer":"sfx","dur":1.6,"rate":1,"loop":false,"when":0,"ta":361.428} +{"ev":"gesture","type":"tap","x":195,"y":422,"t":399386.7999999821,"ta":369.216} +{"ev":"vibrate","pattern":"[20]","t":399751,"ta":369.58} +{"ev":"vend","url":"audio/voice/female/escape.ogg","t":399751.2999999821,"reason":"kill","ta":369.58} +{"ev":"start","t":399751.59999999404,"sid":40,"url":"audio/bgm/drone.ogg","layer":"bgm","dur":117,"rate":1,"loop":true,"when":0,"ta":369.581} +{"ev":"start","t":399751.59999999404,"sid":41,"url":"audio/sfx/amb_drip.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":369.581} +{"ev":"fetch","url":"audio/voice/female/replay.ogg","status":200,"ok":true,"ms":3,"t":399754.1999999881,"ta":369.583} +{"ev":"vstart","url":"audio/voice/female/replay.ogg","t":399762.90000000596,"dur":8.24,"ta":369.592} +{"ev":"vend","url":"audio/voice/female/replay.ogg","t":407950.6999999881,"reason":"kill","ta":377.78} +{"ev":"vibrate","pattern":"[30,50,30,50,30]","t":460172.90000000596,"ta":430.002} +{"ev":"vstart","url":"audio/voice/female/stuck_ask.ogg","t":460173.1999999881,"dur":1.28,"ta":430.002} +{"ev":"vend","url":"audio/voice/female/stuck_ask.ogg","t":461769.7999999821,"reason":"kill","ta":431.599} +{"ev":"vstart","url":"audio/voice/female/stuck_key_1.ogg","t":461769.7999999821,"dur":2.88,"ta":431.599} +{"ev":"vend","url":"audio/voice/female/stuck_key_1.ogg","t":464764.90000000596,"reason":"kill","ta":434.594} +{"ev":"vibrate","pattern":"[30,50,30,50,30]","t":520173,"ta":490.002} +{"ev":"fetch","url":"audio/voice/female/stuck_key_2.ogg","status":200,"ok":true,"ms":3,"t":520176.09999999404,"ta":490.005} +{"ev":"vstart","url":"audio/voice/female/stuck_key_2.ogg","t":520182.5,"dur":4.48,"ta":490.012} +{"ev":"vend","url":"audio/voice/female/stuck_key_2.ogg","t":524760.099999994,"reason":"kill","ta":494.589} +{"ev":"vibrate","pattern":"[30,50,30,50,30]","t":580172.900000006,"ta":550.002} +{"ev":"fetch","url":"audio/voice/female/stuck_key_3.ogg","status":200,"ok":true,"ms":3,"t":580175.7999999821,"ta":550.005} +{"ev":"vstart","url":"audio/voice/female/stuck_key_3.ogg","t":580180.099999994,"dur":3.12,"ta":550.009} +{"ev":"vend","url":"audio/voice/female/stuck_key_3.ogg","t":583641,"reason":"kill","ta":553.47} +{"ev":"fetch","url":"audio/voice/female/nav_left.ogg","status":200,"ok":true,"ms":3,"t":583643.6999999881,"ta":553.473} +{"ev":"vstart","url":"audio/voice/female/nav_left.ogg","t":583646.7999999821,"dur":1.28,"ta":553.476} +{"ev":"vend","url":"audio/voice/female/nav_left.ogg","t":585243.099999994,"reason":"kill","ta":555.072} +{"ev":"fetch","url":"audio/voice/female/nav_down.ogg","status":200,"ok":true,"ms":3,"t":585245.6999999881,"ta":555.075} +{"ev":"vstart","url":"audio/voice/female/nav_down.ogg","t":585248.599999994,"dur":0.9570975056689343,"ta":555.078} +{"ev":"vend","url":"audio/voice/female/nav_down.ogg","t":586380.7999999821,"reason":"kill","ta":556.21} +{"ev":"vibrate","pattern":"[30,50,30,50,30]","t":640172.900000006,"ta":610.002} +{"ev":"vstart","url":"audio/voice/female/stuck_key_3.ogg","t":640173.099999994,"dur":3.12,"ta":610.002} +{"ev":"vend","url":"audio/voice/female/stuck_key_3.ogg","t":643636.5,"reason":"kill","ta":613.466} +{"ev":"vstart","url":"audio/voice/female/nav_left.ogg","t":643636.599999994,"dur":1.28,"ta":613.466} +{"ev":"vend","url":"audio/voice/female/nav_left.ogg","t":645238.6999999881,"reason":"kill","ta":615.068} +{"ev":"vstart","url":"audio/voice/female/nav_down.ogg","t":645238.6999999881,"dur":0.9570975056689343,"ta":615.068} +{"ev":"vend","url":"audio/voice/female/nav_down.ogg","t":646376,"reason":"kill","ta":616.205} +{"ev":"vibrate","pattern":"[30,50,30,50,30]","t":700173,"ta":670.002} +{"ev":"vstart","url":"audio/voice/female/stuck_key_3.ogg","t":700173.099999994,"dur":3.12,"ta":670.002} +{"ev":"vend","url":"audio/voice/female/stuck_key_3.ogg","t":703631.6999999881,"reason":"kill","ta":673.461} +{"ev":"vstart","url":"audio/voice/female/nav_left.ogg","t":703631.7999999821,"dur":1.28,"ta":673.461} +{"ev":"vend","url":"audio/voice/female/nav_left.ogg","t":705233.6999999881,"reason":"kill","ta":675.063} +{"ev":"vstart","url":"audio/voice/female/nav_down.ogg","t":705233.7999999821,"dur":0.9570975056689343,"ta":675.063} +{"ev":"vend","url":"audio/voice/female/nav_down.ogg","t":706371.2999999821,"reason":"kill","ta":676.2} +{"ev":"vibrate","pattern":"[30,50,30,50,30]","t":760173,"ta":730.002} +{"ev":"vstart","url":"audio/voice/female/stuck_key_3.ogg","t":760173.099999994,"dur":3.12,"ta":730.002} +{"ev":"vend","url":"audio/voice/female/stuck_key_3.ogg","t":763627,"reason":"kill","ta":733.456} +{"ev":"vstart","url":"audio/voice/female/nav_left.ogg","t":763627,"dur":1.28,"ta":733.456} +{"ev":"vend","url":"audio/voice/female/nav_left.ogg","t":765228.900000006,"reason":"kill","ta":735.058} +{"ev":"vstart","url":"audio/voice/female/nav_down.ogg","t":765229,"dur":0.9570975056689343,"ta":735.058} +{"ev":"vend","url":"audio/voice/female/nav_down.ogg","t":766366.6999999881,"reason":"kill","ta":736.196} +{"ev":"vibrate","pattern":"[30,50,30,50,30]","t":820172.900000006,"ta":790.002} +{"ev":"vstart","url":"audio/voice/female/stuck_key_3.ogg","t":820173.1999999881,"dur":3.12,"ta":790.002} +{"ev":"vend","url":"audio/voice/female/stuck_key_3.ogg","t":823622.2999999821,"reason":"kill","ta":793.451} +{"ev":"vstart","url":"audio/voice/female/nav_left.ogg","t":823622.400000006,"dur":1.28,"ta":793.452} +{"ev":"vend","url":"audio/voice/female/nav_left.ogg","t":825224.5,"reason":"kill","ta":795.054} +{"ev":"vstart","url":"audio/voice/female/nav_down.ogg","t":825224.5,"dur":0.9570975056689343,"ta":795.054} +{"ev":"vend","url":"audio/voice/female/nav_down.ogg","t":826362,"reason":"kill","ta":796.191} +{"ev":"vibrate","pattern":"[30,50,30,50,30]","t":880173.099999994,"ta":850.002} +{"ev":"vstart","url":"audio/voice/female/stuck_key_3.ogg","t":880173.2999999821,"dur":3.12,"ta":850.002} +{"ev":"vend","url":"audio/voice/female/stuck_key_3.ogg","t":883617.6999999881,"reason":"kill","ta":853.447} +{"ev":"vstart","url":"audio/voice/female/nav_left.ogg","t":883617.7999999821,"dur":1.28,"ta":853.447} +{"ev":"vend","url":"audio/voice/female/nav_left.ogg","t":885219.900000006,"reason":"kill","ta":855.049} +{"ev":"vstart","url":"audio/voice/female/nav_down.ogg","t":885220,"dur":0.9570975056689343,"ta":855.049} +{"ev":"vend","url":"audio/voice/female/nav_down.ogg","t":886357.2999999821,"reason":"kill","ta":856.186} +{"ev":"recstop","t":918408.5,"ta":888.238} diff --git a/research/escape-state/round1/live-transcript.md b/research/escape-state/round1/live-transcript.md new file mode 100644 index 00000000..544d55b2 --- /dev/null +++ b/research/escape-state/round1/live-transcript.md @@ -0,0 +1,346 @@ +# escape 实时会话 transcript + +- `[ 0.00s]` REC 开始 (audio/webm;codecs=opus) +- `[ 0.09s]` VOICE ▶ g0_intro (dur 6.64s) +- `[ 0.28s]` BGM ♪ drone (loop) +- `[ 6.90s]` VOICE ■ g0_intro (kill) +- `[ 6.90s]` VERDICT g0_intro: **OK** sim=1 + - 期望: 黑暗。你在废弃的地下设施里醒来,什么都想不起来。你必须找到出路。 + - 听到: 黑暗,你在废弃的地下设施里醒来,什么都想不起来,你必须找到出路。 +- `[ 8.10s]` VOICE ▶ g1_move (dur 4.56s) +- `[ 12.68s]` VOICE ■ g1_move (kill) +- `[ 12.68s]` VOICE ▶ skip_hint (dur 1.6s) +- `[ 12.68s]` VERDICT g1_move: **OK** sim=0.9697 + - 期望: 先活动一下。朝任意方向滑一下,走一小步。 + - 听到: 先活动一下,朝任意方向滑一下,走一小。 +- `[ 14.28s]` VOICE ■ skip_hint (kill) +- `[ 14.28s]` VERDICT skip_hint: **OK** sim=0.7143 + - 期望: 单击可跳过旁白。 + - 听到: 单机可跳过旁本。 +- `[ 34.52s]` **GESTURE** swipeup @(195,422) +- `[ 34.88s]` VIBRATE [10] +- `[ 34.88s]` SFX ♪ step +- `[ 34.88s]` VOICE ▶ g1_done (dur 2.72s) +- `[ 34.88s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 37.59s]` VOICE ■ g1_done (kill) +- `[ 37.59s]` VERDICT g1_done: **OK** sim=0.9412 + - 期望: 很好,你迈出了第一步。 + - 听到: 很好,你迈出了第一。 +- `[ 37.60s]` VOICE ▶ g2_wall (dur 5.68s) +- `[ 41.57s]` **GESTURE** swipeleft @(195,422) +- `[ 41.93s]` VIBRATE [10] +- `[ 43.28s]` VOICE ■ g2_wall (kill) +- `[ 43.28s]` VERDICT g2_wall: **OK** sim=0.9268 + - 期望: 这个房间不大,四面都是墙。再滑两下,试试碰碰墙。 + - 听到: 一、这个房间不大,四面都是墙,再划两下试试碰碰墙。 +- `[ 46.34s]` **GESTURE** swipeleft @(195,422) +- `[ 46.69s]` VIBRATE [10] +- `[ 46.69s]` VIBRATE [60,40,60] +- `[ 46.69s]` SFX ♪ wall +- `[ 46.70s]` VOICE ▶ wall (dur 2.08s) +- `[ 46.70s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 49.04s]` VOICE ■ wall (kill) +- `[ 49.04s]` VERDICT wall: **OK** sim=1 + - 期望: 那边是墙,换个方向。 + - 听到: 那边是墙,换个方向。 +- `[ 53.66s]` **GESTURE** swipeleft @(195,422) +- `[ 54.01s]` VIBRATE [10] +- `[ 54.01s]` VIBRATE [60,40,60] +- `[ 54.01s]` SFX ♪ wall +- `[ 54.01s]` VOICE ▶ wall (dur 2.08s) +- `[ 54.01s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 56.35s]` VOICE ■ wall (kill) +- `[ 56.35s]` VERDICT wall: **OK** sim=1 + - 期望: 那边是墙,换个方向。 + - 听到: 那边是墙,换个方向。 +- `[ 56.36s]` VOICE ▶ g3_left (dur 2.96s) +- `[ 59.34s]` VOICE ■ g3_left (kill) +- `[ 59.34s]` VERDICT g3_left: **OK** sim=0.9 + - 期望: 跟着我指的路走。向左滑。 + - 听到: 跟着我指的路走向左画。 +- `[ 60.92s]` **GESTURE** swipedown @(195,422) +- `[ 61.28s]` VIBRATE [10] +- `[ 61.28s]` VOICE ▶ guide_skip (dur 2.88s) +- `[ 61.28s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 64.27s]` VOICE ■ guide_skip (kill) +- `[ 64.27s]` VOICE ▶ start_room (dur 7.2s) +- `[ 64.27s]` VERDICT guide_skip: **OK** sim=1 + - 期望: 跳过引导,直接开始自由探索。 + - 听到: 跳过引导,直接开始自由探索。 +- `[ 64.27s]` SFX ♪ amb_drip +- `[ 71.81s]` VOICE ■ start_room (kill) +- `[ 71.81s]` VERDICT start_room: **OK** sim=1 + - 期望: 你站在设施中央的房间里。滑动移动,单击检查,双击使用物品。 + - 听到: 你站在设施中央的房间里滑动移动,单击检查,双击使用物品。 +- `[ 125.00s]` VIBRATE [30,50,30,50,30] +- `[ 125.01s]` VOICE ▶ stuck_ask (dur 1.28s) +- `[ 125.01s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 126.61s]` VOICE ■ stuck_ask (kill) +- `[ 126.61s]` VERDICT stuck_ask: **OK** sim=0.9091 + - 期望: 需要帮助吗? + - 听到: 需要我帮助吗? +- `[ 126.61s]` VOICE ▶ stuck_key_1 (dur 2.88s) +- `[ 129.60s]` VOICE ■ stuck_key_1 (kill) +- `[ 129.60s]` VERDICT stuck_key_1: **OK** sim=0.9231 + - 期望: 铁锈味最重的地方,值得仔细看看。 + - 听到: 铁锈味最重的地方值得仔细。 +- `[ 130.87s]` **GESTURE** swipeleft @(195,422) +- `[ 131.22s]` VIBRATE [10] +- `[ 131.22s]` SFX ♪ step +- `[ 131.22s]` SFX ♪ amb_hum +- `[ 137.65s]` **GESTURE** swipedown @(195,422) +- `[ 138.01s]` VIBRATE [10] +- `[ 138.01s]` SFX ♪ step +- `[ 138.01s]` SFX ♪ amb_storage +- `[ 138.02s]` VOICE ▶ room_storage (dur 5.36s) +- `[ 138.02s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 143.70s]` VOICE ■ room_storage (kill) +- `[ 143.70s]` VERDICT room_storage: **OK** sim=1 + - 期望: 一间储物间。货架上堆满杂物,角落里传来水滴声。 + - 听到: 一间储物间货架上堆满杂物,角落里传来水滴声。 +- `[ 144.92s]` **GESTURE** tap @(195,422) +- `[ 145.28s]` VIBRATE [20] +- `[ 145.28s]` VIBRATE [15] +- `[ 145.28s]` SFX ♪ amb_storage +- `[ 145.28s]` SFX ♪ pickup +- `[ 146.10s]` VOICE ▶ found_key (dur 9.96s) +- `[ 146.10s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 156.23s]` VOICE ■ found_key (kill) +- `[ 156.23s]` VERDICT found_key: **OK** sim=1 + - 期望: 你蹲下身在储物架的角落里摸索,指尖碰到一个冰凉的金属物件——是一把钥匙!钥匙到手了。 + - 听到: 你蹲下身,在储物架的角落里摸索,指尖碰到一个冰凉的金属物件,是一把钥匙,钥匙到手了。 +- `[ 156.24s]` VOICE ▶ gd_room_r (dur 1.28s) +- `[ 157.84s]` VOICE ■ gd_room_r (kill) +- `[ 157.84s]` VERDICT gd_room_r: **OK** sim=1 + - 期望: 右面的房间。 + - 听到: 右面的房间。 +- `[ 175.53s]` **GESTURE** swiperight @(195,422) +- `[ 175.89s]` VIBRATE [10] +- `[ 175.89s]` SFX ♪ step +- `[ 175.89s]` SFX ♪ amb_wind +- `[ 179.30s]` **GESTURE** swiperight @(195,422) +- `[ 179.66s]` VIBRATE [10] +- `[ 179.66s]` SFX ♪ step +- `[ 179.66s]` SFX ♪ amb_gate +- `[ 179.66s]` VOICE ▶ room_gate (dur 3.52s) +- `[ 179.66s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 183.07s]` **GESTURE** swipeup @(195,422) +- `[ 183.42s]` VIBRATE [10] +- `[ 183.42s]` VOICE ■ room_gate (kill) +- `[ 183.42s]` SFX ♪ step +- `[ 183.42s]` SFX ♪ amb_cabinet +- `[ 183.42s]` VERDICT room_gate: **OK** sim=1 + - 期望: 面前,是一扇沉重的铁门。 + - 听到: 面前是一扇沉重的铁门。 +- `[ 183.43s]` VOICE ▶ room_cabinet (dur 2.64s) +- `[ 186.14s]` VOICE ■ room_cabinet (kill) +- `[ 186.14s]` VERDICT room_cabinet: **OK** sim=1 + - 期望: 这个房间里,立着一个铁柜。 + - 听到: 这个房间里立着一个铁柜。 +- `[ 192.56s]` **GESTURE** doubletap @(195,422) +- `[ 192.81s]` VIBRATE [20] +- `[ 192.81s]` VIBRATE [100] +- `[ 192.81s]` SFX ♪ unlock +- `[ 192.81s]` SFX ♪ door_creak +- `[ 192.83s]` VOICE ▶ unlock_cabinet (dur 11.48s) +- `[ 192.83s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 204.46s]` VOICE ■ unlock_cabinet (kill) +- `[ 204.46s]` VERDICT unlock_cabinet: **OK** sim=0.9714 + - 期望: 钥匙插进锁孔,缓缓转动。咔哒一声,柜门开了。柜子深处,躺着一枚保险丝。保险丝到手了。 + - 听到: 钥匙插进锁孔,缓缓转动,咔嗒一声,柜门开了,柜子深处躺着一枚保险丝,保险丝到手了。 +- `[ 204.47s]` VOICE ▶ gd_next_fuse (dur 3.28s) +- `[ 207.92s]` VOICE ■ gd_next_fuse (kill) +- `[ 207.92s]` VERDICT gd_next_fuse: **OK** sim=1 + - 期望: 保险丝到手。接下来,去找电箱。 + - 听到: 保险丝到手,接下来去找电箱。 +- `[ 207.92s]` VOICE ▶ gd_room_l (dur 1.68s) +- `[ 209.80s]` VOICE ■ gd_room_l (kill) +- `[ 209.80s]` VERDICT gd_room_l: **OK** sim=1 + - 期望: 左面的房间。 + - 听到: 左面的房间。 +- `[ 222.58s]` **GESTURE** doubletap @(195,422) +- `[ 222.83s]` VIBRATE [20] +- `[ 222.83s]` VIBRATE [60,40,60] +- `[ 222.84s]` VOICE ▶ use_fail (dur 7.36s) +- `[ 222.84s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 230.39s]` VOICE ■ use_fail (kill) +- `[ 230.39s]` VERDICT use_fail: **OK** sim=0.9655 + - 期望: 这里没有用得上东西的地方。换个房间试试,或者先单击检查一下周围。 + - 听到: 这里没有用得上东西的地方,换个房间试试,或者先单机检查一下周围。 +- `[ 230.40s]` VOICE ▶ stuck_box_2 (dur 4.88s) +- `[ 232.97s]` **GESTURE** swipeleft @(195,422) +- `[ 233.32s]` VIBRATE [10] +- `[ 233.32s]` SFX ♪ step +- `[ 233.32s]` SFX ♪ amb_drip +- `[ 235.34s]` VOICE ■ stuck_box_2 (kill) +- `[ 235.34s]` VERDICT stuck_box_2: **OK** sim=1 + - 期望: 去西北角的电箱房,双击,把保险丝装进电箱。 + - 听到: 去西北角的电箱房,双击把保险丝装进电箱。 +- `[ 236.73s]` **GESTURE** swipeleft @(195,422) +- `[ 237.09s]` VIBRATE [10] +- `[ 237.09s]` SFX ♪ step +- `[ 237.09s]` SFX ♪ amb_hum +- `[ 240.50s]` **GESTURE** swipeup @(195,422) +- `[ 240.85s]` VIBRATE [10] +- `[ 240.85s]` SFX ♪ step +- `[ 240.85s]` SFX ♪ amb_power +- `[ 240.86s]` VOICE ▶ room_power (dur 2.64s) +- `[ 240.86s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 243.58s]` VOICE ■ room_power (kill) +- `[ 243.58s]` VERDICT room_power: **OK** sim=1 + - 期望: 这个房间的墙上,有一只电箱。 + - 听到: 这个房间的墙上有一只电箱。 +- `[ 267.62s]` **GESTURE** doubletap @(195,422) +- `[ 267.87s]` VIBRATE [20] +- `[ 267.87s]` VIBRATE [100] +- `[ 267.87s]` SFX ♪ spark +- `[ 267.87s]` SFX ♪ power_on +- `[ 267.88s]` VOICE ▶ fix_box (dur 7.52s) +- `[ 267.88s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 275.44s]` VOICE ■ fix_box (kill) +- `[ 275.44s]` VOICE ▶ gd_room_r (dur 1.28s) +- `[ 275.44s]` VERDICT fix_box: **OK** sim=0.9492 + - 期望: 你把保险丝用力按进卡座。火花一闪,嗡鸣声响起——头顶的线路恢复供电了! + - 听到: 你把保险丝用力按静卡座,火花一闪,嗡鸣声响起,头顶的线路恢复供电。 +- `[ 277.04s]` VOICE ■ gd_room_r (kill) +- `[ 277.04s]` VERDICT gd_room_r: **OK** sim=0.75 + - 期望: 右面的房间。 + - 听到: 右面的。 +- `[ 278.19s]` **GESTURE** swiperight @(195,422) +- `[ 278.55s]` VIBRATE [10] +- `[ 278.55s]` SFX ♪ step +- `[ 278.55s]` SFX ♪ amb_wind +- `[ 281.96s]` **GESTURE** swiperight @(195,422) +- `[ 282.32s]` VIBRATE [10] +- `[ 282.32s]` SFX ♪ step +- `[ 282.32s]` SFX ♪ amb_hum +- `[ 285.73s]` **GESTURE** swipedown @(195,422) +- `[ 286.08s]` VIBRATE [10] +- `[ 286.08s]` SFX ♪ step +- `[ 286.08s]` SFX ♪ amb_cabinet +- `[ 295.51s]` **GESTURE** tap @(195,422) +- `[ 295.87s]` VIBRATE [20] +- `[ 295.87s]` SFX ♪ amb_cabinet +- `[ 296.68s]` VOICE ▶ empty_1 (dur 5.2s) +- `[ 296.68s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 301.90s]` VOICE ■ empty_1 (kill) +- `[ 301.90s]` VERDICT empty_1: **OK** sim=1 + - 期望: 房间里空空荡荡,只有你自己的回声。 + - 听到: 房间里空空荡荡,只有你自己的回声。 +- `[ 305.74s]` **GESTURE** doubletap @(195,422) +- `[ 305.99s]` VIBRATE [20] +- `[ 305.99s]` VIBRATE [60,40,60] +- `[ 305.99s]` VOICE ▶ use_fail (dur 7.36s) +- `[ 305.99s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 313.54s]` VOICE ■ use_fail (kill) +- `[ 313.54s]` VERDICT use_fail: **OK** sim=0.9655 + - 期望: 这里没有用得上东西的地方。换个房间试试,或者先单击检查一下周围。 + - 听到: 这里没有用得上东西的地方,换个房间试试,或者先单机检查一下周围。 +- `[ 313.54s]` VOICE ▶ stuck_gate_2 (dur 5.44s) +- `[ 319.22s]` VOICE ■ stuck_gate_2 (kill) +- `[ 319.22s]` VERDICT stuck_gate_2: **OK** sim=1 + - 期望: 去东南角的大门,双击,打开它,离开这里。 + - 听到: 去东南角的大门,双击打开它,离开这里。 +- `[ 339.31s]` **GESTURE** swipedown @(195,422) +- `[ 339.67s]` VIBRATE [10] +- `[ 339.67s]` SFX ♪ step +- `[ 339.67s]` SFX ♪ amb_gate_live +- `[ 349.09s]` **GESTURE** tap @(195,422) +- `[ 349.45s]` VIBRATE [20] +- `[ 349.45s]` SFX ♪ amb_gate_live +- `[ 350.26s]` VOICE ▶ inspect_gate_ready (dur 8.72s) +- `[ 350.26s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 358.58s]` **GESTURE** doubletap @(195,422) +- `[ 358.83s]` VIBRATE [20] +- `[ 358.83s]` VIBRATE [100] +- `[ 358.83s]` VOICE ■ inspect_gate_ready (kill) +- `[ 358.83s]` VERDICT inspect_gate_ready: **TRUNCATED** sim=0.9667 + - 期望: 电子锁的指示灯亮了,发出微弱的绿光。大门已经通电。双击,打开它,离开这里。 + - 听到: 电子锁的指示灯亮了,发出微弱的绿光,大门已经通电,双击打开它,离开。 +- `[ 358.84s]` VOICE ▶ escape (dur 15.32s) +- `[ 361.43s]` SFX ♪ gate_open +- `[ 361.43s]` SFX ♪ win_chime +- `[ 369.22s]` **GESTURE** tap @(195,422) +- `[ 369.58s]` VIBRATE [20] +- `[ 369.58s]` VOICE ■ escape (kill) +- `[ 369.58s]` VERDICT escape: **TRUNCATED** sim=0.7711 + - 期望: 你扳动开关。电机轰鸣,沉重的铁门缓缓升起,刺眼的光线涌了进来。你眯起眼睛,向前迈出了第一步。自由了。你逃出来了。 + - 听到: 开你搬动开关,电击轰,沉重的铁门缓缓升起,刺眼的光线涌了进来,你眯起眼睛向前迈。 +- `[ 369.58s]` BGM ♪ drone (loop) +- `[ 369.58s]` SFX ♪ amb_drip +- `[ 369.59s]` VOICE ▶ replay (dur 8.24s) +- `[ 377.78s]` VOICE ■ replay (kill) +- `[ 377.78s]` VERDICT replay: **OK** sim=0.9545 + - 期望: 黑暗再次合拢。你回到了中央的房间。这一次,你会更快。 + - 听到: 前黑暗再次合拢,你回到了中央的房间,这一次你会更。 +- `[ 430.00s]` VIBRATE [30,50,30,50,30] +- `[ 430.00s]` VOICE ▶ stuck_ask (dur 1.28s) +- `[ 430.00s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 431.60s]` VOICE ■ stuck_ask (kill) +- `[ 431.60s]` VOICE ▶ stuck_key_1 (dur 2.88s) +- `[ 431.60s]` VERDICT stuck_ask: **OK** sim=0.9091 + - 期望: 需要帮助吗? + - 听到: 需要我帮助吗? +- `[ 434.59s]` VOICE ■ stuck_key_1 (kill) +- `[ 434.59s]` VERDICT stuck_key_1: **OK** sim=0.9286 + - 期望: 铁锈味最重的地方,值得仔细看看。 + - 听到: 铁修味最重的地方,值得仔细看看。 +- `[ 490.00s]` VIBRATE [30,50,30,50,30] +- `[ 490.01s]` VOICE ▶ stuck_key_2 (dur 4.48s) +- `[ 490.01s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 494.59s]` VOICE ■ stuck_key_2 (kill) +- `[ 494.59s]` VERDICT stuck_key_2: **OK** sim=1 + - 期望: 去西南角的储物间,单击检查,找到钥匙。 + - 听到: 去西南角的储物间单击检查,找到钥匙。 +- `[ 550.00s]` VIBRATE [30,50,30,50,30] +- `[ 550.01s]` VOICE ▶ stuck_key_3 (dur 3.12s) +- `[ 550.01s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 553.47s]` VOICE ■ stuck_key_3 (kill) +- `[ 553.47s]` VERDICT stuck_key_3: **OK** sim=1 + - 期望: 跟我说的方向走,去西南角的储物间。 + - 听到: 跟我说的方向走,去西南角的储物间。 +- `[ 553.48s]` VOICE ▶ nav_left (dur 1.28s) +- `[ 555.07s]` VOICE ■ nav_left (kill) +- `[ 555.07s]` VERDICT nav_left: **OK** sim=1 + - 期望: 向左滑。 + - 听到: 向左滑。 +- `[ 555.08s]` VOICE ▶ nav_down (dur 0.9570975056689343s) +- `[ 556.21s]` VOICE ■ nav_down (kill) +- `[ 556.21s]` VERDICT nav_down: **OK** sim=1 + - 期望: 向下滑。 + - 听到: 向下滑。 +- `[ 610.00s]` VIBRATE [30,50,30,50,30] +- `[ 610.00s]` VOICE ▶ stuck_key_3 (dur 3.12s) +- `[ 613.47s]` VOICE ■ stuck_key_3 (kill) +- `[ 613.47s]` VOICE ▶ nav_left (dur 1.28s) +- `[ 615.07s]` VOICE ■ nav_left (kill) +- `[ 615.07s]` VOICE ▶ nav_down (dur 0.9570975056689343s) +- `[ 616.21s]` VOICE ■ nav_down (kill) +- `[ 670.00s]` VIBRATE [30,50,30,50,30] +- `[ 670.00s]` VOICE ▶ stuck_key_3 (dur 3.12s) +- `[ 673.46s]` VOICE ■ stuck_key_3 (kill) +- `[ 673.46s]` VOICE ▶ nav_left (dur 1.28s) +- `[ 675.06s]` VOICE ■ nav_left (kill) +- `[ 675.06s]` VOICE ▶ nav_down (dur 0.9570975056689343s) +- `[ 676.20s]` VOICE ■ nav_down (kill) +- `[ 730.00s]` VIBRATE [30,50,30,50,30] +- `[ 730.00s]` VOICE ▶ stuck_key_3 (dur 3.12s) +- `[ 733.46s]` VOICE ■ stuck_key_3 (kill) +- `[ 733.46s]` VOICE ▶ nav_left (dur 1.28s) +- `[ 735.06s]` VOICE ■ nav_left (kill) +- `[ 735.06s]` VOICE ▶ nav_down (dur 0.9570975056689343s) +- `[ 736.20s]` VOICE ■ nav_down (kill) +- `[ 790.00s]` VIBRATE [30,50,30,50,30] +- `[ 790.00s]` VOICE ▶ stuck_key_3 (dur 3.12s) +- `[ 793.45s]` VOICE ■ stuck_key_3 (kill) +- `[ 793.45s]` VOICE ▶ nav_left (dur 1.28s) +- `[ 795.05s]` VOICE ■ nav_left (kill) +- `[ 795.05s]` VOICE ▶ nav_down (dur 0.9570975056689343s) +- `[ 796.19s]` VOICE ■ nav_down (kill) +- `[ 850.00s]` VIBRATE [30,50,30,50,30] +- `[ 850.00s]` VOICE ▶ stuck_key_3 (dur 3.12s) +- `[ 853.45s]` VOICE ■ stuck_key_3 (kill) +- `[ 853.45s]` VOICE ▶ nav_left (dur 1.28s) +- `[ 855.05s]` VOICE ■ nav_left (kill) +- `[ 855.05s]` VOICE ▶ nav_down (dur 0.9570975056689343s) +- `[ 856.19s]` VOICE ■ nav_down (kill) diff --git a/research/escape-state/round1/p1-blind.md b/research/escape-state/round1/p1-blind.md new file mode 100644 index 00000000..0adb23f7 --- /dev/null +++ b/research/escape-state/round1/p1-blind.md @@ -0,0 +1,64 @@ +# P1 视障资深玩家评审:《暗室》(escape-state 取证轮 R1) + +> 评审依据:`live-transcript.md`(真机 live 一局,SenseVoice 转写,行号即该文件行)+ +> `transcript-mock.md`(mock 新手局转写)。本轮只聚焦一个问题:**哪些时刻我 +> 不知道自己在哪 / 该干嘛 / 进展到哪**。我读的是转写,按"如果是我戴着耳机在 +> 75 档语速下听到这些"代入;ASR 近失(如"单机"→"单击")不计入缺陷。 + +### 暗室 +- 状态感知:2/5 —— 引导期每秒都有指令带路,状态满格;一进自由探索就开始 + 失聪。**①撞墙只有"那边是墙",没有方位**(live 行 40-55):我在 (0,0) 连撞 + 两次,听到的句子一字不差,但我的地图上一个标记都没增加——我不知道刚才是 + 哪面墙,只知道"此路不通"。**②空房声学 identical**:(0,1) 是 amb_hum(行 90), + 后来 (2,0) 也是 amb_hum(行 182);(1,2) amb_wind(行 120),(1,0) 也是 + amb_wind(行 212)。四间空房两种音,我对着转写都对不出哪间是哪间,耳朵更 + 不可能。**③重访已探索房间零语音**:我重返铁柜房时(行 220、223)只听到 + 环境音,"这个房间里立着一个铁柜"是首到专属——我作为记图全靠耳朵的人, + 第二次经过没有任何"我来过这"的锚。 +- 下一步明确性:3/5 —— 进展事件当下很明确:found_key 后接"右面的房间。" + (行 112-116),unlock 后"左面的房间。"(行 156-160),修电箱后"右面的房间。" + (行 201-208)——链条本身做得好。但有两个洞:**①方向句没有目标名**: + "右面的房间。"——右面是哪?我下一秒要干什么?我手里钥匙对应的是铁柜, + 句子不说,得我赌。**②引导期手势被静默吞掉**(行 30-35):g2_wall 还没播完 + 我滑了一下,只收到引擎一声 [10] 轻震,墙没响、计数没走——我这一滑到底算数 + 吗?教科书式的无声失败。如果我信了这一下、只再补一滑,就永远卡在 G2。 +- 愿意继续玩:是 —— 解谜链手感对(双击使用、震动脉络清楚),但我要先能 + 建出自己的地图才谈得上"探索",现在建图材料不够。 +- P0 问题(不知道自己在哪/该干嘛/进展到哪的具体时刻): + 1. **跳过引导后目标全丢**(行 61-74):swipedown 被当成跳过(G3 下滑=跳过 + 是设计,但我是在引导语音刚说完"向左滑"之后、还没作答时滑错的),然后 + `guide_skip` + `start_room` 两句了事——**"找到能用钥匙打开的东西"这句 + 目标陈述我一辈子没听到**(g7_goal 只存在于引导链尾)。开局 64s 起我站 + 在 (1,1),知道三个手势,不知道要干什么。 + 2. **主动查状态被直接喂答案**(行 161-178、230-243):我双击铁柜房(柜已开、 + 手里是保险丝),只是想知道"我现在该干嘛",游戏播 use_fail 之后直接上 + `stuck_box_2`「去西北角的电箱房,双击,把保险丝装进电箱」——L2 明示是 + 给卡关 120 秒的人的,我没卡关,谜题被白送。这是提示泄漏,也是唯一一个 + "主动查询"入口被做成答案分发口。 + 3. **地图进度无账本**:全 9 房走了 7 房,没有任何一句告诉我"钥匙✓、铁柜✓、 + 电✓、还差大门"——进展播报只在进展事件当下播一次(found_key 链等), + 中途无法回查(escape 连上滑状态键都没有,00 §5.3 豁免表)。 +- 建议(按对"我愿意玩下去"的影响排序): + 1. 给 escape 一个 L0 级进度重述通道(不泄题:报"已完成几环/还差几环"), + 取代双击兜底直通 L2 的现状(见 25 号文档 B3/B5)。 + 2. 撞墙报方位但用陈述句(「左边是墙。」),别再走"手指贴着屏幕推"教学腔 + (round1-live 回退原因我同意,但方位本身是该给的,见 B1)。 + 3. 目标方位句并入目标名(「铁柜,在右面的房间。」),并给四间空房各自 + 可分辨的声学签名或首访方位线索句(见 B4/B6)。 + +#### 听觉惯例符合度(逐条核对特别检查项) +- 语音可打断:过。单击跳句机制干净,关键获得链(found_key/unlock/fix_box) + 不可跳有事件层兜住(行 106-116 链完整)。 +- 语音可重听:过。单击检查=重播环境音+描述,空房也可重听。 +- 等待有无声:**半不过**。行 72 的 3s 静默是设计内的"辨方向",可以;但自由 + 探索期最长的静默段(行 87-94 我在空房之间移动)只有脚步声和环境音—— + 这不是"死寂",是"零信息",比死寂更糟:我一直在动,游戏一直说话(环境音), + 可我什么也学不到。 +- 失败可否归因:半过。撞墙可归因(有闷响+error 震+短句),但**被吞手势不可 + 归因**(行 30-35)——引导 busy 窗口是静默 return,震都没有第二声。 +- 菜单列表导航:未验证(结算后我直接重开,没进菜单)。 +- status key:**缺席**。PLAY 期上滑=移动,全游戏无任何时刻查询"我在哪/我 + 有什么/还剩几环"——这是我这个人设最依赖的锚,本作没有。双击兜底本可补位, + 现状是答案泄漏口(见 P0-2)。 +- 提示永不逐字重复:过。L3 带路每 60s 重算重播但坐标若没变内容相同属合理 + (行 295-346 从 (1,1) 重复带路,玩家没动当然重复同一路线)。 diff --git a/research/escape-state/round1/p3-office.md b/research/escape-state/round1/p3-office.md new file mode 100644 index 00000000..f22203fe --- /dev/null +++ b/research/escape-state/round1/p3-office.md @@ -0,0 +1,51 @@ +# P3 时间紧张的白领评审:《暗室》(escape-state 取证轮 R1) + +> 评审依据:`live-transcript.md`(真机 live 一局)+ `transcript-mock.md`(mock +> 新手局)。本轮只聚焦一个问题:**哪些时刻我不知道自己在哪 / 该干嘛 / 进展 +> 到哪**。我代入的场景是地铁单程 25 分钟、站立单手持机、随时可能被打断—— +> 被打断后能不能一句话接上,是我的生死线。 + +### 暗室 +- 状态感知:2/5 —— 我被 put off 的第一个时刻是**空房 transit**:行 87-94 + 我从起点滑到储物间,中间经过 (0,1) 只有脚步+嗡鸣,两行事件、零句话; + 行 179-186、209-220 同理((0,1)→(0,0)、(1,0)→(2,0)→(2,1) 全是静默移动)。 + 地铁里听不清环境音细节,这几段对我来说就是"盲滑"。**撞了墙也不知道方位** + (行 40-55):"那边是墙"告诉我此路不通,不告诉我我刚才想去哪——单手持机 + 滑错方向太常见了,每次错都学不到东西。 +- 下一步明确性:3/5 —— 好消息是**进展链很会带路**:拿到钥匙接"右面的房间。" + (行 112-116)、开柜接"左面的房间。"(行 156-160)、修电箱接"右面的房间。" + (行 201-208),只要我在解谜链上,永远不迷路。坏消息有二:**①方向句无 + 目标名**:地铁里"右面的房间。"(1.3s 短句)极易被车厢噪音盖住前半句, + 我只听到"……的房间",不知道去找什么(行 206-208 的 ASR sim 只有 0.75, + 正是短句被环境吃掉的实证)。**②被打断恢复无锚**:行 253-275 我双击大门 + 之后结算链 escape 刚播 10 秒,我一个习惯性单击(想确认是不是要操作)直接 + 重开——time_band 和 again 都没播,我连自己通关用时多少都不知道就进下一局 + 了。25 分钟通勤被打断 3 次就会这样 3 次。 +- 愿意继续玩:否 —— 单局 6 分钟节奏其实合适,但我每次被打断回来都要重新 + 摸一遍地图,"一句话接上"做不到。 +- P0 问题(不知道自己在哪/该干嘛/进展到哪的具体时刻): + 1. **结算链无保护**(行 253-275):ENDING 单击无条件重开,结算语音播一半 + 就没了(行 266 TRUNCATED sim 0.77)——我作为"随时被打断"的人,结算 + 是我唯一确认"我打完了/打得如何"的窗口,这个窗口一碰就碎。 + 2. **目标句短且无名**(行 112-116):嘈杂环境下 1.3s 的"右面的房间。"承载 + 不了关键导航信息;该把目标名并进整句("铁柜在右面的房间"信息量翻倍、 + 时长只多 1 秒)。 + 3. **跳过引导=目标全丢**(行 61-74):误触下滑跳过之后只有 start_room 的 + 手势总览,没有任何目标提示;我这种没耐心的玩家正是最容易跳过引导的人, + 跳过之后 60 秒(行 75-86)才等到第一句 L1 暗示。开局一分钟是纯摸黑。 +- 建议(按对"我愿意玩下去"的影响排序): + 1. 结算链播完前单击重开加守卫(或单击=跳过下一不可跳句的确认):我要听完 + 用时播报(见 25 号文档 B8)。 + 2. 目标方位句并入目标名并重录(「铁柜,在右面的房间。」,见 B4)。 + 3. 跳过引导的确认句后补播 g7_goal 目标句(资产现成,见 B9);撞墙给陈述式 + 方位句(见 B1),让我每次失误都变成地图信息。 + +#### 通勤场景符合度(逐条核对特别检查项) +- 3 分钟内有没有一次完整乐趣:有——mock 局 35s 就到电箱房、2 分钟内通关的 + 关键路径存在(mock 行 144-149),链式解谜本身爽点密。 +- 被打断后能不能一句话接上:**不能**(见 P0-1/P0-3),这是唯一硬伤。 +- 会不会太吵:不会,语音密度低、无高频刺耳件(转写无声频谱数据,此项不评)。 +- 操作能不能单手盲操:能,四向滑+单击+双击单手成立;但盲操的代价是撞墙 + 频繁,而撞墙不给方位(行 40-55),盲操的试错成本全是沉没成本。 +- 关键信息听不清怎么办:**无解**——没有重听上一句的通道(单击只重播环境音), + 短方位句被噪音吃掉就永久丢失,直到下一次进展事件(可能就是 60-180s 后)。 diff --git a/research/escape-state/round1/transcript-mock.md b/research/escape-state/round1/transcript-mock.md new file mode 100644 index 00000000..75572399 --- /dev/null +++ b/research/escape-state/round1/transcript-mock.md @@ -0,0 +1,151 @@ +# 试玩转写:《escape》 + +> 本文件是「玩家感知日志」:一位新手玩家的整局体验转写。语音列出了玩家听到的每句话全文, +> 音效/BGM/震动按发生时间列出。「静默」段 = 玩家什么也听不到。手势是剧本注入的(模拟真人手部动作), +> 非最优操作:包含犹豫、误操作与乱试。语音时长按语速估算(±20%),仅供节奏参考。 +t+ 0.0s (玩家点开游戏,轻触屏幕开始) +t+ 0.0s ♫ BGM 切换 → bgm/drone +t+ 0.0s ♪ 向导音:explore(探索/安全) +t+ 0.0s ▶ 语音:「黑暗。你在废弃的地下设施里醒来,什么都想不起来。你必须找到出路。」 +t+ 0.0s 【状态】黑屏,只有引子的声音,先听完 引导第 1/8 步,等我做手势(语音播放中,语速1) +t+ 0.0s 【状态】引导第 1/8 步 引导第 1/8 步,等我做手势(语音播放中,语速1) + +#### 【玩家动作】照引导单击了一下 + +t+ 0.0s ▶ 语音:「先活动一下。朝任意方向滑一下,走一小步。」 +t+ 2.1s ▶ 语音:「单击可跳过旁白。」 +t+ 4.2s ♪ 向导音:turn(轮到你) +t+ 4.2s 【状态】引导第 2/8 步 引导第 2/8 步,等我做手势(BGM=bgm/drone,语速1) + +#### 【玩家动作】随便滑了一步,有脚步声 + +t+ 4.2s ♪ 音效 sfx/step +t+ 4.2s ▶ 语音:「很好,你迈出了第一步。」 +t+ 6.4s ▶ 语音:「这个房间不大,四面都是墙。再滑两下,试试碰碰墙。」 +t+ 8.5s ♪ 向导音:turn(轮到你) +t+ 8.5s 【状态】引导第 3/8 步 引导第 3/8 步,等我做手势(BGM=bgm/drone,语速1) +t+ 8.5s ♪ 音效 sfx/wall +t+ 8.5s ▶ 语音:「那边是墙,换个方向。」 +t+ 8.5s 〰 震动:error(做错了:两连短震) + +#### 【玩家动作】又滑一步,又撞墙了——墙的方位听出来了 + +t+ 10.6s ♪ 音效 sfx/wall +t+ 10.6s ▶ 语音:「那边是墙,换个方向。」 +t+ 10.6s 〰 震动:error(做错了:两连短震) +t+ 12.7s ▶ 语音:「跟着我指的路走。向左滑。」 +t+ 14.8s ♪ 向导音:turn(轮到你) +t+ 14.8s 【状态】引导第 4/8 步 引导第 4/8 步,等我做手势(BGM=bgm/drone,语速1) + +#### 【玩家动作】这回往左,走通了一步 + +t+ 14.8s ♪ 音效 sfx/step +t+ 14.8s ▶ 语音:「对。再向下滑。」 +t+ 17.0s ♪ 向导音:turn(轮到你) +t+ 17.0s 【状态】引导第 5/8 步 引导第 5/8 步,等我做手势(BGM=bgm/drone,语速1) + +#### 【玩家动作】往下滑,进了储物间 + +t+ 17.0s ♪ 音效 sfx/step +t+ 17.0s ♪ 音效 sfx/amb_storage +t+ 17.0s ▶ 语音:「听到水声了吗?单击一下,仔细检查这个房间。」 +t+ 19.1s ♪ 向导音:turn(轮到你) +t+ 19.1s 【状态】引导第 6/8 步 引导第 6/8 步,等我做手势(BGM=bgm/drone,语速1) + +#### 【玩家动作】单击摸一遍房间,摸到钥匙了 + +t+ 19.1s ♪ 音效 sfx/pickup +t+ 19.1s ▶ 语音:「你摸到了一把钥匙!」 +t+ 19.1s 〰 震动:pickup(获得) +t+ 21.2s ▶ 语音:「钥匙能开锁。双击,是把物品用出去的用法。现在,双击试试。」 +t+ 23.3s ♪ 向导音:turn(轮到你) +t+ 23.3s 【状态】引导第 7/8 步 引导第 7/8 步,等我做手势(BGM=bgm/drone,语速1) + +#### 【玩家动作】双击看了看背包 + +t+ 23.3s ▶ 语音:「你身上带着一把钥匙。」 +t+ 25.4s ▶ 语音:「对,就是这样。接下来,靠你自己了。」 +t+ 27.6s ▶ 语音:「找到能用这把钥匙打开的东西。需要帮助时,在原地双击。」 +t+ 29.7s ♪ 向导音:turn(轮到你) +t+ 29.7s ♪ 音效 sfx/amb_storage +t+ 29.7s 【状态】引导完毕,正式开始。我在储物间 探索中,我在 (0,2)。四向滑挪步,单击摸一遍房间,双击用物品(BGM=bgm/drone,语速1) +t+ 29.7s ……… 静默 3s(辨了辨方向) ……… +t+ 29.7s ♪ 音效 sfx/step +t+ 29.7s ♪ 音效 sfx/amb_hum +t+ 35.0s ♪ 音效 sfx/step +t+ 35.0s ♪ 音效 sfx/amb_power +t+ 35.0s ▶ 语音:「这个房间的墙上,有一只电箱。」 +t+ 37.1s ♪ 音效 sfx/wall +t+ 37.1s ▶ 语音:「那边是墙,换个方向。」 +t+ 37.1s 〰 震动:error(做错了:两连短震) + +#### 【玩家动作】北边是墙,撞了一下——「那边是墙」后面还跟了句方向(向上推的是北墙) + +t+ 39.2s ♪ 音效 sfx/wall +t+ 39.2s ▶ 语音:「那边是墙,换个方向。」 +t+ 39.2s 〰 震动:error(做错了:两连短震) + +#### 【玩家动作】又撞了一下——听到游戏主动来问我了 + +t+ 50.0s ♪ 音效 sfx/step +t+ 50.0s ♪ 音效 sfx/amb_wind + +#### 【玩家动作】往东走,风声换了个方向 + +t+ 55.0s ♪ 音效 sfx/step +t+ 55.0s ♪ 音效 sfx/amb_hum +t+ 60.0s ♪ 音效 sfx/step +t+ 60.0s ♪ 音效 sfx/amb_cabinet +t+ 60.0s ▶ 语音:「这个房间里,立着一个铁柜。」 + +#### 【玩家动作】到了铁柜房,柜子锁着 + +t+ 62.1s ♪ 音效 sfx/unlock +t+ 62.1s ♪ 音效 sfx/door_creak +t+ 62.1s ▶ 语音:「钥匙插进锁孔,缓缓转动。咔哒一声,柜门开了。柜子深处,躺着一枚保险丝。保险丝到手了。」 +t+ 62.1s 〰 震动:success(成功长震) +t+ 64.2s ▶ 向导语音:「保险丝到手。接下来,去找电箱。」 +t+ 66.4s ▶ 向导语音:「左面的房间。」 +t+ 68.5s 【状态】柜子开了。「保险丝到手。接下来,去找电箱。」后面还跟了一句「左面的房间。」——这回知道该往哪边摸了 探索中,我在 (2,1)。四向滑挪步,单击摸一遍房间,双击用物品(BGM=bgm/drone,语速1) +t+ 68.5s ♪ 音效 sfx/step +t+ 68.5s ♪ 音效 sfx/amb_drip + +#### 【玩家动作】照方位句往左(西)摸 + +t+ 75.0s ♪ 音效 sfx/step +t+ 75.0s ♪ 音效 sfx/amb_hum +t+ 80.0s ♪ 音效 sfx/step +t+ 80.0s ♪ 音效 sfx/amb_power + +#### 【玩家动作】电流声越来越清晰——电箱房到了 + +t+ 85.0s ♪ 音效 sfx/spark +t+ 85.0s ♪ 音效 sfx/power_on +t+ 85.0s ▶ 语音:「你把保险丝用力按进卡座。火花一闪,嗡鸣声响起——头顶的线路恢复供电了!」 +t+ 85.0s 〰 震动:success(成功长震) +t+ 87.1s ▶ 向导语音:「右面的房间。」 +t+ 95.0s 【状态】电箱修好了。目标句后面照样跟着方位——「右面的房间。」大门在东边 探索中,我在 (0,0)。四向滑挪步,单击摸一遍房间,双击用物品(BGM=bgm/drone,语速1) +t+ 95.0s ♪ 音效 sfx/step +t+ 95.0s ♪ 音效 sfx/amb_wind + +#### 【玩家动作】照方位句往右(东)摸 + +t+ 100.0s ♪ 音效 sfx/step +t+ 100.0s ♪ 音效 sfx/amb_hum +t+ 105.0s ♪ 音效 sfx/step +t+ 105.0s ♪ 音效 sfx/amb_cabinet +t+ 110.0s ♪ 音效 sfx/step +t+ 110.0s ♪ 音效 sfx/amb_gate_live +t+ 110.0s ▶ 语音:「面前,是一扇沉重的铁门。」 + +#### 【玩家动作】脚步声停下来——面前这间就是大门 + +t+ 112.1s ♪ 向导音:settle(结算) +t+ 112.1s ▶ 语音:「你扳动开关。电机轰鸣,沉重的铁门缓缓升起,刺眼的光线涌了进来。你眯起眼睛,向前迈出了第一步。自由了。你逃出来了。」 +t+ 112.1s 〰 震动:success(成功长震) +t+ 114.2s ▶ 语音:「你逃出来了,用时不到两分钟。了不起!」 +t+ 114.7s ♪ 音效 sfx/gate_open +t+ 114.7s ♪ 音效 sfx/win_chime +t+ 116.4s ▶ 语音:「单击屏幕,再来一次。下滑,打开菜单。」 +t+ 118.5s ♫ BGM 切换 → bgm/menu(淡入 1500ms) +t+ 118.5s 【状态】钥匙插进锁孔,门开了——结算播报来了,这局是完整的赢 逃脱了,结算播报中。播完单击再来,下滑开菜单(BGM=bgm/menu,语速1) diff --git a/research/escape-state/round2/README.md b/research/escape-state/round2/README.md new file mode 100644 index 00000000..8569cfdc --- /dev/null +++ b/research/escape-state/round2/README.md @@ -0,0 +1,44 @@ +# escape-state round2 · 状态引导改进方案(25 号文档 B1-B8)修复验证档案 + +> 轮次:escape-state round2(2026-09-14/15)。对象:25 号文档 §4.3 实施清单 +> F1-F9(F6/B5b 按方案"可分期"留档,见 25 号文档实施裁定 1)。 +> 档案格式对齐 round1:mock 转写 + live 真机一局 + 事件/判定原件。 +> round1 的 P1/P3 人设评审不重做(方案条目即由双席评审产出,本轮验证落地效果)。 + +## 产物 + +| 文件 | 内容 | +|---|---| +| `transcript-mock.md` | mock 转写(`transcribe.mjs escape`,sessions/escape.mjs 已按新行为微调):新手完整一局,覆盖 B1 撞墙方位句+限频、B5a 空房首访、B2 一体句 ×2、结算链完整到达 | +| `live-transcript.md` / `live-events.jsonl` / `verdicts.json` / `mix.webm` | live 真机一局(control.mjs 端口 8145,女声响 full 档,约 10.4 分钟,引导→探索→通关→重开),认真新手人设 | +| `README.md` | 本文件(盲区清单 S1-S7 逐条核对) | + +## live 一局总览 + +- 38 个语音段,56 条判定行:**OK 37 / SFX_BGM_ONLY 19 / TRUNCATED 0 / MISMATCH 0** + (round1 为 OK 35 / SFX_BGM_ONLY 18 / TRUNCATED 2——两条截断均有手势归因; + 本轮零截断,含 B7 守卫挡下的那一次习惯性单击)。 +- 新语音 10 句在真实混音中全部到达且 ASR sim≥0.93:wall_dir_u/l、empty_2/1/3(首访)、 + stuck_cabinet_1(B4 降级)、gd_box_dir/gd_gate_dir(B2 一体句)、gd_prog_2(B3)。 + +## 盲区清单 S1-S7 逐条核对(25 号文档 §3.2) + +| 盲区 | 修复 | live 证据(行号 = live-transcript.md) | 结论 | +|---|---|---|---| +| S1/S1b 方位(撞墙无方位) | B1 wall_dir_* | 行 141-143:wall 后接播「上边是墙。」OK sim=1;行 166-168:「左边是墙。」OK;同向 10s 限频生效(199.5s 第二次撞北墙只有 wall 无 wall_dir_u) | ✅ 每次撞墙建立"此方向不通"地图标记 | +| 相对方位半盲区(无目标名) | B2 gd_*_dir 一体句 | 行 231-233:「电箱,在左面的房间。」OK sim=1;行 306-312:「大门,在右面的房间。」OK sim=1 | ✅ 单句即可行动,不再是"……的房间" | +| S2 已探索地图 | B5a 空房首访描述 | 行 115-118 (0,1) empty_2、行 189-192 (1,0) empty_1、行 199-202 (2,0) empty_3,均首访一次性 OK | ✅ 空房进入即有听觉锚 | +| S3 全局进度无渠道 | B3 gd_prog_* | 行 265-268:开柜后活跃探路 ~154s(10 次手势零进展、避开 (2,2) 防进展重置)→ gd_prog_2「铁柜已经打开。电还没有通。」OK sim=1;期间 L1/L2/L3 零误触 | ✅ 活跃迷路通道补洞,不泄题 | +| S4/S4b 目标重述/查询泄漏 | B4 + B8 | B4:行 180-182 双击兜底播 use_fail + stuck_cabinet_1(L1 意象,OK 0.93),**不再直通 stuck_*_2**;B8:g7_goal 在引导链尾 OK sim=1(跳过/REOPEN 路径补播由 playtest A2/B/N 断言覆盖) | ✅ 主动查询不再白送答案 | +| S5 空房不可区分 | B5a(B5b 留档) | 三间首访空房文本各异(回声/墙壁/水滴),live 中与环境音叠加可分辨 | ✅(B5b 方位特征改词留后续轮) | +| S6 busy 吞手势 | B6 缓存补判 | 行 79-87(live-events.jsonl):82.3s swipeleft 落在 g2_wall busy 窗口(82.2-87.9)只有 [10] 震——87.9s 解锁瞬间补判:error 震 + wall 语音 + G2 计数 1(对照 round1 行 30-35 同场景静默吞手势) | ✅ "我的输入没丢",可归因 | +| S7 结算链截断 | B7 _settleBusy | 行 336-347:587.46s 习惯性单击落在 escape 播到 4.4/15.3s——只发引擎 confirm 震,链继续;time_band3(行 346)、again 完整到达 OK;链播完后单击正常重开(replay 起播) | ✅ 结算完整到达权 + 重开闭环 | + +## 附带观察 + +- escape 长句 live 实测 15.32s(dur 探针值),结算链全长 ~24s(escape+time_band3+again), + B7 守卫窗口即此区间;30s 资产缺失兜底由 playtest 场景 M 第二条路径冻结。 +- time_band3「用时不到十分钟」与本局 ~9.7 分钟一致(归因真话)。 +- female/wall_dir_r 为 3.04s 慢速 take(同句 male 1.04s,ratio 2.92 未触 3x 离群线), + QC:ASR 双性别内容均「右边是墙。」逐字正确,慢速贴合"沉稳平静"instruct,留档备查; + 本局 female 档未触发该句(撞的是北/西墙),male 档或未限频路径如有听感问题再重录。 diff --git a/research/escape-state/round2/live-events.jsonl b/research/escape-state/round2/live-events.jsonl new file mode 100644 index 00000000..bc5b01fc --- /dev/null +++ b/research/escape-state/round2/live-events.jsonl @@ -0,0 +1,289 @@ +{"ev":"probe_ready","t":61.10000002384186,"ta":null} +{"ev":"gesture","type":"tap","x":195,"y":422,"t":2065.4000000059605,"ta":-5.104} +{"ev":"recstart","mime":"audio/webm;codecs=opus","t":7169.100000023842,"ta":0} +{"ev":"fetch","url":"audio/sfx/step.ogg","status":200,"ok":true,"ms":15,"t":7184.100000023842,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/wall.ogg","status":200,"ok":true,"ms":15,"t":7184.200000017881,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/pickup.ogg","status":200,"ok":true,"ms":15,"t":7184.300000011921,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/unlock.ogg","status":200,"ok":true,"ms":15,"t":7184.300000011921,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/door_creak.ogg","status":200,"ok":true,"ms":15,"t":7184.300000011921,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/spark.ogg","status":200,"ok":true,"ms":14,"t":7184.300000011921,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/power_on.ogg","status":200,"ok":true,"ms":14,"t":7184.4000000059605,"ta":0.015} +{"ev":"fetch","url":"audio/sfx/gate_open.ogg","status":200,"ok":true,"ms":16,"t":7186.100000023842,"ta":0.017} +{"ev":"fetch","url":"audio/sfx/win_chime.ogg","status":200,"ok":true,"ms":16,"t":7186.300000011921,"ta":0.017} +{"ev":"fetch","url":"audio/sfx/amb_storage.ogg","status":200,"ok":true,"ms":18,"t":7187.600000023842,"ta":0.019} +{"ev":"fetch","url":"audio/sfx/amb_cabinet.ogg","status":200,"ok":true,"ms":18,"t":7188.5,"ta":0.019} +{"ev":"fetch","url":"audio/sfx/amb_power.ogg","status":200,"ok":true,"ms":19,"t":7189.4000000059605,"ta":0.02} +{"ev":"fetch","url":"audio/sfx/amb_gate.ogg","status":200,"ok":true,"ms":25,"t":7195.700000017881,"ta":0.027} +{"ev":"fetch","url":"audio/sfx/amb_gate_live.ogg","status":200,"ok":true,"ms":25,"t":7195.700000017881,"ta":0.027} +{"ev":"fetch","url":"audio/sfx/amb_drip.ogg","status":200,"ok":true,"ms":27,"t":7196.800000011921,"ta":0.028} +{"ev":"fetch","url":"audio/sfx/amb_wind.ogg","status":200,"ok":true,"ms":27,"t":7197.600000023842,"ta":0.029} +{"ev":"fetch","url":"audio/sfx/amb_hum.ogg","status":200,"ok":true,"ms":28,"t":7198.700000017881,"ta":0.03} +{"ev":"fetch","url":"audio/voice/female/g0_intro.ogg","status":200,"ok":true,"ms":30,"t":7200.100000023842,"ta":0.031} +{"ev":"fetch","url":"audio/voice/female/g1_move.ogg","status":200,"ok":true,"ms":34,"t":7204.5,"ta":0.035} +{"ev":"fetch","url":"audio/voice/female/skip_hint.ogg","status":200,"ok":true,"ms":36,"t":7206.600000023842,"ta":0.038} +{"ev":"fetch","url":"audio/voice/female/reopen_prompt.ogg","status":200,"ok":true,"ms":38,"t":7208.4000000059605,"ta":0.039} +{"ev":"fetch","url":"audio/voice/female/start_room.ogg","status":200,"ok":true,"ms":38,"t":7209,"ta":0.04} +{"ev":"fetch","url":"audio/bgm/menu.ogg","status":200,"ok":true,"ms":38,"t":7209.100000023842,"ta":0.04} +{"ev":"fetch","url":"audio/bgm/drone.ogg","status":200,"ok":true,"ms":39,"t":7209.800000011921,"ta":0.041} +{"ev":"vstart","url":"audio/voice/female/g0_intro.ogg","t":7245.700000017881,"dur":6.64,"ta":0.077} +{"ev":"start","t":7439.800000011921,"sid":1,"url":"audio/bgm/drone.ogg","layer":"bgm","dur":117,"rate":1,"loop":true,"when":0,"ta":0.271} +{"ev":"vend","url":"audio/voice/female/g0_intro.ogg","t":14100.40000000596,"reason":"kill","ta":6.931} +{"ev":"vstart","url":"audio/voice/female/g1_move.ogg","t":15300.90000000596,"dur":4.56,"ta":8.132} +{"ev":"vend","url":"audio/voice/female/g1_move.ogg","t":19910.20000001788,"reason":"kill","ta":12.741} +{"ev":"vstart","url":"audio/voice/female/skip_hint.ogg","t":19910.30000001192,"dur":1.6,"ta":12.741} +{"ev":"vend","url":"audio/voice/female/skip_hint.ogg","t":21530.100000023842,"reason":"kill","ta":14.361} +{"ev":"gesture","type":"swipeup","x":195,"y":422,"t":86221.70000001788,"ta":79.053} +{"ev":"vibrate","pattern":"[10]","t":86578.60000002384,"ta":79.41} +{"ev":"start","t":86579.60000002384,"sid":2,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":79.411} +{"ev":"fetch","url":"audio/voice/female/g1_done.ogg","status":200,"ok":true,"ms":3,"t":86582,"ta":79.413} +{"ev":"vstart","url":"audio/voice/female/g1_done.ogg","t":86586.30000001192,"dur":2.72,"ta":79.417} +{"ev":"vend","url":"audio/voice/female/g1_done.ogg","t":89320.20000001788,"reason":"kill","ta":82.151} +{"ev":"fetch","url":"audio/voice/female/g2_wall.ogg","status":200,"ok":true,"ms":3,"t":89322.80000001192,"ta":82.154} +{"ev":"vstart","url":"audio/voice/female/g2_wall.ogg","t":89328.30000001192,"dur":5.68,"ta":82.159} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":89490,"ta":82.321} +{"ev":"vibrate","pattern":"[10]","t":89844.70000001788,"ta":82.676} +{"ev":"vend","url":"audio/voice/female/g2_wall.ogg","t":95050.10000002384,"reason":"kill","ta":87.881} +{"ev":"vibrate","pattern":"[60,40,60]","t":95050.30000001192,"ta":87.881} +{"ev":"start","t":95050.70000001788,"sid":3,"url":"audio/sfx/wall.ogg","layer":"sfx","dur":0.44709750566893425,"rate":1,"loop":false,"when":0,"ta":87.882} +{"ev":"fetch","url":"audio/voice/female/wall.ogg","status":200,"ok":true,"ms":2,"t":95052.70000001788,"ta":87.884} +{"ev":"vstart","url":"audio/voice/female/wall.ogg","t":95056.40000000596,"dur":2.08,"ta":87.887} +{"ev":"vend","url":"audio/voice/female/wall.ogg","t":97410.10000002384,"reason":"kill","ta":90.241} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":116300.20000001788,"ta":109.131} +{"ev":"vibrate","pattern":"[10]","t":116660.10000002384,"ta":109.491} +{"ev":"vibrate","pattern":"[60,40,60]","t":116660.20000001788,"ta":109.491} +{"ev":"start","t":116660.40000000596,"sid":4,"url":"audio/sfx/wall.ogg","layer":"sfx","dur":0.44709750566893425,"rate":1,"loop":false,"when":0,"ta":109.491} +{"ev":"vstart","url":"audio/voice/female/wall.ogg","t":116660.5,"dur":2.08,"ta":109.491} +{"ev":"vend","url":"audio/voice/female/wall.ogg","t":119030.30000001192,"reason":"kill","ta":111.861} +{"ev":"fetch","url":"audio/voice/female/g3_left.ogg","status":200,"ok":true,"ms":2,"t":119032.80000001192,"ta":111.864} +{"ev":"vstart","url":"audio/voice/female/g3_left.ogg","t":119037.10000002384,"dur":2.96,"ta":111.868} +{"ev":"vend","url":"audio/voice/female/g3_left.ogg","t":122050.10000002384,"reason":"kill","ta":114.881} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":124070.30000001192,"ta":116.901} +{"ev":"vibrate","pattern":"[10]","t":124426.70000001788,"ta":117.258} +{"ev":"start","t":124427.20000001788,"sid":5,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":117.258} +{"ev":"fetch","url":"audio/voice/female/g4_down.ogg","status":200,"ok":true,"ms":2,"t":124429.20000001788,"ta":117.26} +{"ev":"vstart","url":"audio/voice/female/g4_down.ogg","t":124432.80000001192,"dur":1.84,"ta":117.264} +{"ev":"vend","url":"audio/voice/female/g4_down.ogg","t":126340.20000001788,"reason":"kill","ta":119.171} +{"ev":"gesture","type":"swipedown","x":195,"y":422,"t":129835.90000000596,"ta":122.667} +{"ev":"vibrate","pattern":"[10]","t":130192.90000000596,"ta":123.024} +{"ev":"start","t":130193.30000001192,"sid":6,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":123.024} +{"ev":"start","t":130193.40000000596,"sid":7,"url":"audio/sfx/amb_storage.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":123.024} +{"ev":"fetch","url":"audio/voice/female/g5_inspect.ogg","status":200,"ok":true,"ms":2,"t":130195.5,"ta":123.026} +{"ev":"vstart","url":"audio/voice/female/g5_inspect.ogg","t":130200.90000000596,"dur":3.92,"ta":123.032} +{"ev":"vend","url":"audio/voice/female/g5_inspect.ogg","t":134340.10000002384,"reason":"kill","ta":127.171} +{"ev":"gesture","type":"tap","x":195,"y":422,"t":137604.80000001192,"ta":130.436} +{"ev":"vibrate","pattern":"[20]","t":137970.20000001788,"ta":130.801} +{"ev":"vibrate","pattern":"[15]","t":137970.40000000596,"ta":130.801} +{"ev":"start","t":137970.70000001788,"sid":8,"url":"audio/sfx/pickup.ogg","layer":"sfx","dur":0.3470748299319728,"rate":1,"loop":false,"when":0,"ta":130.802} +{"ev":"fetch","url":"audio/voice/female/g5_key.ogg","status":200,"ok":true,"ms":3,"t":137973.10000002384,"ta":130.804} +{"ev":"vstart","url":"audio/voice/female/g5_key.ogg","t":137976.80000001192,"dur":1.52,"ta":130.808} +{"ev":"vend","url":"audio/voice/female/g5_key.ogg","t":139590.20000001788,"reason":"kill","ta":132.421} +{"ev":"fetch","url":"audio/voice/female/g6_doubletap.ogg","status":200,"ok":true,"ms":2,"t":139592.60000002384,"ta":132.424} +{"ev":"vstart","url":"audio/voice/female/g6_doubletap.ogg","t":139602.40000000596,"dur":10.64,"ta":132.433} +{"ev":"gesture","type":"doubletap","x":195,"y":422,"t":148080.20000001788,"ta":140.911} +{"ev":"vibrate","pattern":"[20]","t":148329.60000002384,"ta":141.161} +{"ev":"vend","url":"audio/voice/female/g6_doubletap.ogg","t":150560.10000002384,"reason":"kill","ta":143.391} +{"ev":"fetch","url":"audio/voice/female/inv_key.ogg","status":200,"ok":true,"ms":3,"t":150563,"ta":143.394} +{"ev":"vstart","url":"audio/voice/female/inv_key.ogg","t":150566.70000001788,"dur":1.68,"ta":143.398} +{"ev":"vend","url":"audio/voice/female/inv_key.ogg","t":152460.10000002384,"reason":"kill","ta":145.291} +{"ev":"fetch","url":"audio/voice/female/g6_done.ogg","status":200,"ok":true,"ms":3,"t":152462.70000001788,"ta":145.294} +{"ev":"vstart","url":"audio/voice/female/g6_done.ogg","t":152468,"dur":3.92,"ta":145.299} +{"ev":"vend","url":"audio/voice/female/g6_done.ogg","t":156620.20000001788,"reason":"kill","ta":149.451} +{"ev":"fetch","url":"audio/voice/female/g7_goal.ogg","status":200,"ok":true,"ms":3,"t":156622.80000001192,"ta":149.454} +{"ev":"vstart","url":"audio/voice/female/g7_goal.ogg","t":156629.40000000596,"dur":6.88,"ta":149.46} +{"ev":"vend","url":"audio/voice/female/g7_goal.ogg","t":163760.30000001192,"reason":"kill","ta":156.591} +{"ev":"start","t":163760.70000001788,"sid":9,"url":"audio/sfx/amb_storage.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":156.592} +{"ev":"gesture","type":"swipeup","x":195,"y":422,"t":185967.10000002384,"ta":178.798} +{"ev":"vibrate","pattern":"[10]","t":186324.30000001192,"ta":179.155} +{"ev":"start","t":186324.90000000596,"sid":10,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":179.156} +{"ev":"start","t":186324.90000000596,"sid":11,"url":"audio/sfx/amb_hum.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":179.156} +{"ev":"fetch","url":"audio/voice/female/empty_2.ogg","status":200,"ok":true,"ms":3,"t":186327.30000001192,"ta":179.158} +{"ev":"vstart","url":"audio/voice/female/empty_2.ogg","t":186333.30000001192,"dur":4.88,"ta":179.164} +{"ev":"vend","url":"audio/voice/female/empty_2.ogg","t":191320.30000001192,"reason":"kill","ta":184.151} +{"ev":"gesture","type":"swipeup","x":195,"y":422,"t":192735.70000001788,"ta":185.567} +{"ev":"vibrate","pattern":"[10]","t":193090.80000001192,"ta":185.922} +{"ev":"start","t":193091.20000001788,"sid":12,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":185.922} +{"ev":"start","t":193091.20000001788,"sid":13,"url":"audio/sfx/amb_power.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":185.922} +{"ev":"fetch","url":"audio/voice/female/room_power.ogg","status":200,"ok":true,"ms":3,"t":193093.70000001788,"ta":185.925} +{"ev":"vstart","url":"audio/voice/female/room_power.ogg","t":193097.70000001788,"dur":2.64,"ta":185.929} +{"ev":"vend","url":"audio/voice/female/room_power.ogg","t":195840,"reason":"kill","ta":188.671} +{"ev":"gesture","type":"swipeup","x":195,"y":422,"t":199501.40000000596,"ta":192.332} +{"ev":"vibrate","pattern":"[10]","t":199856.80000001192,"ta":192.688} +{"ev":"vibrate","pattern":"[60,40,60]","t":199856.90000000596,"ta":192.688} +{"ev":"start","t":199857,"sid":14,"url":"audio/sfx/wall.ogg","layer":"sfx","dur":0.44709750566893425,"rate":1,"loop":false,"when":0,"ta":192.688} +{"ev":"vstart","url":"audio/voice/female/wall.ogg","t":199857,"dur":2.08,"ta":192.688} +{"ev":"vend","url":"audio/voice/female/wall.ogg","t":202220.20000001788,"reason":"kill","ta":195.051} +{"ev":"fetch","url":"audio/voice/female/wall_dir_u.ogg","status":200,"ok":true,"ms":3,"t":202223.60000002384,"ta":195.055} +{"ev":"vstart","url":"audio/voice/female/wall_dir_u.ogg","t":202227.10000002384,"dur":1.36,"ta":195.058} +{"ev":"vend","url":"audio/voice/female/wall_dir_u.ogg","t":203840.10000002384,"reason":"kill","ta":196.671} +{"ev":"gesture","type":"swipeup","x":195,"y":422,"t":206268,"ta":199.099} +{"ev":"vibrate","pattern":"[10]","t":206623.40000000596,"ta":199.454} +{"ev":"vibrate","pattern":"[60,40,60]","t":206623.70000001788,"ta":199.455} +{"ev":"start","t":206623.80000001192,"sid":15,"url":"audio/sfx/wall.ogg","layer":"sfx","dur":0.44709750566893425,"rate":1,"loop":false,"when":0,"ta":199.455} +{"ev":"vstart","url":"audio/voice/female/wall.ogg","t":206623.90000000596,"dur":2.08,"ta":199.455} +{"ev":"vend","url":"audio/voice/female/wall.ogg","t":208990.20000001788,"reason":"kill","ta":201.821} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":211033.30000001192,"ta":203.864} +{"ev":"vibrate","pattern":"[10]","t":211390,"ta":204.221} +{"ev":"vibrate","pattern":"[60,40,60]","t":211390.10000002384,"ta":204.221} +{"ev":"start","t":211390.30000001192,"sid":16,"url":"audio/sfx/wall.ogg","layer":"sfx","dur":0.44709750566893425,"rate":1,"loop":false,"when":0,"ta":204.221} +{"ev":"vstart","url":"audio/voice/female/wall.ogg","t":211390.30000001192,"dur":2.08,"ta":204.221} +{"ev":"vend","url":"audio/voice/female/wall.ogg","t":213760.10000002384,"reason":"kill","ta":206.591} +{"ev":"fetch","url":"audio/voice/female/wall_dir_l.ogg","status":200,"ok":true,"ms":3,"t":213762.70000001788,"ta":206.594} +{"ev":"vstart","url":"audio/voice/female/wall_dir_l.ogg","t":213766.10000002384,"dur":1.04,"ta":206.597} +{"ev":"vend","url":"audio/voice/female/wall_dir_l.ogg","t":214920.10000002384,"reason":"kill","ta":207.751} +{"ev":"gesture","type":"doubletap","x":195,"y":422,"t":217799.90000000596,"ta":210.631} +{"ev":"vibrate","pattern":"[20]","t":218046.80000001192,"ta":210.878} +{"ev":"vibrate","pattern":"[60,40,60]","t":218047.20000001788,"ta":210.878} +{"ev":"fetch","url":"audio/voice/female/use_fail.ogg","status":200,"ok":true,"ms":3,"t":218049.80000001192,"ta":210.881} +{"ev":"vstart","url":"audio/voice/female/use_fail.ogg","t":218057.40000000596,"dur":7.36,"ta":210.888} +{"ev":"vend","url":"audio/voice/female/use_fail.ogg","t":225650.10000002384,"reason":"kill","ta":218.481} +{"ev":"fetch","url":"audio/voice/female/stuck_cabinet_1.ogg","status":200,"ok":true,"ms":3,"t":225653.20000001788,"ta":218.484} +{"ev":"vstart","url":"audio/voice/female/stuck_cabinet_1.ogg","t":225659,"dur":3.84,"ta":218.49} +{"ev":"vend","url":"audio/voice/female/stuck_cabinet_1.ogg","t":229790.20000001788,"reason":"kill","ta":222.621} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":263046.3000000119,"ta":255.877} +{"ev":"vibrate","pattern":"[10]","t":263404.2000000179,"ta":256.235} +{"ev":"start","t":263404.60000002384,"sid":17,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":256.236} +{"ev":"start","t":263404.60000002384,"sid":18,"url":"audio/sfx/amb_wind.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":256.236} +{"ev":"fetch","url":"audio/voice/female/empty_1.ogg","status":200,"ok":true,"ms":3,"t":263407.2000000179,"ta":256.238} +{"ev":"vstart","url":"audio/voice/female/empty_1.ogg","t":263413.60000002384,"dur":5.2,"ta":256.245} +{"ev":"vend","url":"audio/voice/female/empty_1.ogg","t":268670.10000002384,"reason":"kill","ta":261.501} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":269814.3000000119,"ta":262.645} +{"ev":"vibrate","pattern":"[10]","t":270170.5,"ta":263.001} +{"ev":"start","t":270171,"sid":19,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":263.002} +{"ev":"start","t":270171,"sid":20,"url":"audio/sfx/amb_hum.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":263.002} +{"ev":"fetch","url":"audio/voice/female/empty_3.ogg","status":200,"ok":true,"ms":3,"t":270173.3000000119,"ta":263.004} +{"ev":"vstart","url":"audio/voice/female/empty_3.ogg","t":270178.60000002384,"dur":3.68,"ta":263.01} +{"ev":"vend","url":"audio/voice/female/empty_3.ogg","t":274030.2000000179,"reason":"kill","ta":266.861} +{"ev":"gesture","type":"swipedown","x":195,"y":422,"t":276579.5,"ta":269.41} +{"ev":"vibrate","pattern":"[10]","t":276937.5,"ta":269.768} +{"ev":"start","t":276937.90000000596,"sid":21,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":269.769} +{"ev":"start","t":276937.90000000596,"sid":22,"url":"audio/sfx/amb_cabinet.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":269.769} +{"ev":"fetch","url":"audio/voice/female/room_cabinet.ogg","status":200,"ok":true,"ms":3,"t":276940.40000000596,"ta":269.771} +{"ev":"vstart","url":"audio/voice/female/room_cabinet.ogg","t":276944.7000000179,"dur":2.64,"ta":269.776} +{"ev":"vend","url":"audio/voice/female/room_cabinet.ogg","t":279690.10000002384,"reason":"kill","ta":272.521} +{"ev":"gesture","type":"doubletap","x":195,"y":422,"t":283348,"ta":276.179} +{"ev":"vibrate","pattern":"[20]","t":283593.90000000596,"ta":276.425} +{"ev":"vibrate","pattern":"[100]","t":283594.10000002384,"ta":276.425} +{"ev":"start","t":283594.3000000119,"sid":23,"url":"audio/sfx/unlock.ogg","layer":"sfx","dur":0.49777777777777776,"rate":1,"loop":false,"when":0,"ta":276.425} +{"ev":"start","t":283594.3000000119,"sid":24,"url":"audio/sfx/door_creak.ogg","layer":"sfx","dur":1.3999773242630384,"rate":1,"loop":false,"when":0,"ta":276.425} +{"ev":"fetch","url":"audio/voice/female/unlock_cabinet.ogg","status":200,"ok":true,"ms":2,"t":283596.60000002384,"ta":276.428} +{"ev":"vstart","url":"audio/voice/female/unlock_cabinet.ogg","t":283607,"dur":11.48,"ta":276.438} +{"ev":"vend","url":"audio/voice/female/unlock_cabinet.ogg","t":295320.10000002384,"reason":"kill","ta":288.151} +{"ev":"fetch","url":"audio/voice/female/gd_next_fuse.ogg","status":200,"ok":true,"ms":3,"t":295323.10000002384,"ta":288.154} +{"ev":"vstart","url":"audio/voice/female/gd_next_fuse.ogg","t":295328.10000002384,"dur":3.28,"ta":288.159} +{"ev":"vend","url":"audio/voice/female/gd_next_fuse.ogg","t":298800.40000000596,"reason":"kill","ta":291.631} +{"ev":"fetch","url":"audio/voice/female/gd_box_dir.ogg","status":200,"ok":true,"ms":3,"t":298803.5,"ta":291.634} +{"ev":"vstart","url":"audio/voice/female/gd_box_dir.ogg","t":298807.3000000119,"dur":2.16,"ta":291.638} +{"ev":"vend","url":"audio/voice/female/gd_box_dir.ogg","t":301180.10000002384,"reason":"kill","ta":294.011} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":317593.90000000596,"ta":310.425} +{"ev":"vibrate","pattern":"[10]","t":317952.10000002384,"ta":310.783} +{"ev":"start","t":317952.3000000119,"sid":25,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":310.783} +{"ev":"start","t":317952.3000000119,"sid":26,"url":"audio/sfx/amb_drip.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":310.783} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":334362.90000000596,"ta":327.194} +{"ev":"vibrate","pattern":"[10]","t":334718.3000000119,"ta":327.549} +{"ev":"start","t":334718.60000002384,"sid":27,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":327.55} +{"ev":"start","t":334718.7000000179,"sid":28,"url":"audio/sfx/amb_hum.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":327.55} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":351128.5,"ta":343.959} +{"ev":"vibrate","pattern":"[10]","t":351484.2000000179,"ta":344.315} +{"ev":"start","t":351484.40000000596,"sid":29,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":344.315} +{"ev":"start","t":351484.5,"sid":30,"url":"audio/sfx/amb_drip.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":344.315} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":367894.40000000596,"ta":360.725} +{"ev":"vibrate","pattern":"[10]","t":368250.40000000596,"ta":361.081} +{"ev":"start","t":368250.7000000179,"sid":31,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":361.082} +{"ev":"start","t":368250.7000000179,"sid":32,"url":"audio/sfx/amb_cabinet.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":361.082} +{"ev":"gesture","type":"swipeup","x":195,"y":422,"t":384660.10000002384,"ta":377.491} +{"ev":"vibrate","pattern":"[10]","t":385016,"ta":377.847} +{"ev":"start","t":385016.2000000179,"sid":33,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":377.847} +{"ev":"start","t":385016.2000000179,"sid":34,"url":"audio/sfx/amb_hum.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":377.847} +{"ev":"gesture","type":"swipedown","x":195,"y":422,"t":416051.60000002384,"ta":408.883} +{"ev":"vibrate","pattern":"[10]","t":416414.8000000119,"ta":409.246} +{"ev":"start","t":416415.10000002384,"sid":35,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":409.246} +{"ev":"start","t":416415.10000002384,"sid":36,"url":"audio/sfx/amb_cabinet.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":409.246} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":432824.8000000119,"ta":425.656} +{"ev":"vibrate","pattern":"[10]","t":433180.7000000179,"ta":426.012} +{"ev":"start","t":433181,"sid":37,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":426.012} +{"ev":"start","t":433181,"sid":38,"url":"audio/sfx/amb_drip.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":426.012} +{"ev":"vibrate","pattern":"[30,50,30,50,30]","t":437170.90000000596,"ta":430.002} +{"ev":"fetch","url":"audio/voice/female/gd_prog_2.ogg","status":200,"ok":true,"ms":3,"t":437173.90000000596,"ta":430.005} +{"ev":"vstart","url":"audio/voice/female/gd_prog_2.ogg","t":437178.10000002384,"dur":2.56,"ta":430.009} +{"ev":"vend","url":"audio/voice/female/gd_prog_2.ogg","t":439920.2000000179,"reason":"kill","ta":432.751} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":449589.90000000596,"ta":442.421} +{"ev":"vibrate","pattern":"[10]","t":449947,"ta":442.778} +{"ev":"start","t":449947.3000000119,"sid":39,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":442.778} +{"ev":"start","t":449947.40000000596,"sid":40,"url":"audio/sfx/amb_hum.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":442.778} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":466357.10000002384,"ta":459.188} +{"ev":"vibrate","pattern":"[10]","t":466713.2000000179,"ta":459.544} +{"ev":"start","t":466713.3000000119,"sid":41,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":459.544} +{"ev":"start","t":466713.5,"sid":42,"url":"audio/sfx/amb_drip.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":459.544} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":483123.3000000119,"ta":475.954} +{"ev":"vibrate","pattern":"[10]","t":483478.8000000119,"ta":476.31} +{"ev":"start","t":483479.10000002384,"sid":43,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":476.31} +{"ev":"start","t":483479.10000002384,"sid":44,"url":"audio/sfx/amb_cabinet.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":476.31} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":534004.3000000119,"ta":526.835} +{"ev":"vibrate","pattern":"[10]","t":534360.1000000238,"ta":527.191} +{"ev":"start","t":534360.3000000119,"sid":45,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":527.191} +{"ev":"start","t":534360.400000006,"sid":46,"url":"audio/sfx/amb_drip.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":527.191} +{"ev":"gesture","type":"swipeleft","x":195,"y":422,"t":538768.8000000119,"ta":531.6} +{"ev":"vibrate","pattern":"[10]","t":539126.8000000119,"ta":531.958} +{"ev":"start","t":539127.1000000238,"sid":47,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":531.958} +{"ev":"start","t":539127.1000000238,"sid":48,"url":"audio/sfx/amb_hum.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":531.958} +{"ev":"gesture","type":"swipeup","x":195,"y":422,"t":543536.400000006,"ta":536.367} +{"ev":"vibrate","pattern":"[10]","t":543893.6000000238,"ta":536.725} +{"ev":"start","t":543893.900000006,"sid":49,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":536.725} +{"ev":"start","t":543893.900000006,"sid":50,"url":"audio/sfx/amb_power.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":536.725} +{"ev":"gesture","type":"doubletap","x":195,"y":422,"t":548303.3000000119,"ta":541.134} +{"ev":"vibrate","pattern":"[20]","t":548551.1000000238,"ta":541.382} +{"ev":"vibrate","pattern":"[100]","t":548551.400000006,"ta":541.382} +{"ev":"start","t":548551.7000000179,"sid":51,"url":"audio/sfx/spark.ogg","layer":"sfx","dur":0.8970975056689342,"rate":1,"loop":false,"when":0,"ta":541.383} +{"ev":"start","t":548551.7000000179,"sid":52,"url":"audio/sfx/power_on.ogg","layer":"sfx","dur":2.2,"rate":1,"loop":false,"when":0,"ta":541.383} +{"ev":"fetch","url":"audio/voice/female/fix_box.ogg","status":200,"ok":true,"ms":3,"t":548554.3000000119,"ta":541.385} +{"ev":"vstart","url":"audio/voice/female/fix_box.ogg","t":548562.400000006,"dur":7.52,"ta":541.393} +{"ev":"vend","url":"audio/voice/female/fix_box.ogg","t":556150.1000000238,"reason":"kill","ta":548.981} +{"ev":"fetch","url":"audio/voice/female/gd_gate_dir.ogg","status":200,"ok":true,"ms":3,"t":556153.3000000119,"ta":548.984} +{"ev":"vstart","url":"audio/voice/female/gd_gate_dir.ogg","t":556157.3000000119,"dur":2.32,"ta":548.988} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":557962.3000000119,"ta":550.793} +{"ev":"vibrate","pattern":"[10]","t":558326.1000000238,"ta":551.157} +{"ev":"start","t":558326.400000006,"sid":53,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":551.157} +{"ev":"start","t":558326.400000006,"sid":54,"url":"audio/sfx/amb_wind.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":551.157} +{"ev":"vend","url":"audio/voice/female/gd_gate_dir.ogg","t":558530,"reason":"kill","ta":551.361} +{"ev":"gesture","type":"swiperight","x":195,"y":422,"t":562736.3000000119,"ta":555.567} +{"ev":"vibrate","pattern":"[10]","t":563092.3000000119,"ta":555.923} +{"ev":"start","t":563092.5,"sid":55,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":555.923} +{"ev":"start","t":563092.5,"sid":56,"url":"audio/sfx/amb_hum.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":555.923} +{"ev":"gesture","type":"swipedown","x":195,"y":422,"t":567502.3000000119,"ta":560.333} +{"ev":"vibrate","pattern":"[10]","t":567859.3000000119,"ta":560.69} +{"ev":"start","t":567859.5,"sid":57,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":560.69} +{"ev":"start","t":567859.5,"sid":58,"url":"audio/sfx/amb_cabinet.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":560.69} +{"ev":"gesture","type":"swipedown","x":195,"y":422,"t":572269.900000006,"ta":565.101} +{"ev":"vibrate","pattern":"[10]","t":572625.3000000119,"ta":565.456} +{"ev":"start","t":572625.8000000119,"sid":59,"url":"audio/sfx/step.ogg","layer":"sfx","dur":0.27709750566893426,"rate":1,"loop":false,"when":0,"ta":565.457} +{"ev":"start","t":572625.8000000119,"sid":60,"url":"audio/sfx/amb_gate_live.ogg","layer":"sfx","dur":6,"rate":1,"loop":false,"when":0,"ta":565.457} +{"ev":"fetch","url":"audio/voice/female/room_gate.ogg","status":200,"ok":true,"ms":3,"t":572628.3000000119,"ta":565.459} +{"ev":"vstart","url":"audio/voice/female/room_gate.ogg","t":572633.400000006,"dur":3.52,"ta":565.464} +{"ev":"vend","url":"audio/voice/female/room_gate.ogg","t":576480.2000000179,"reason":"kill","ta":569.311} +{"ev":"gesture","type":"doubletap","x":195,"y":422,"t":589967.5,"ta":582.798} +{"ev":"vibrate","pattern":"[20]","t":590216.2000000179,"ta":583.047} +{"ev":"vibrate","pattern":"[100]","t":590216.5,"ta":583.047} +{"ev":"fetch","url":"audio/voice/female/escape.ogg","status":200,"ok":true,"ms":3,"t":590219.5,"ta":583.05} +{"ev":"vstart","url":"audio/voice/female/escape.ogg","t":590232.7000000179,"dur":15.32,"ta":583.064} +{"ev":"stop","t":592776.8000000119,"sid":1,"when":0,"ta":585.608} +{"ev":"start","t":592817.400000006,"sid":61,"url":"audio/sfx/gate_open.ogg","layer":"sfx","dur":2.7999773242630384,"rate":1,"loop":false,"when":0,"ta":585.648} +{"ev":"start","t":592817.400000006,"sid":62,"url":"audio/sfx/win_chime.ogg","layer":"sfx","dur":1.6,"rate":1,"loop":false,"when":0,"ta":585.648} +{"ev":"gesture","type":"tap","x":195,"y":422,"t":594627.5,"ta":587.458} +{"ev":"vibrate","pattern":"[20]","t":594990.8000000119,"ta":587.822} +{"ev":"vend","url":"audio/voice/female/escape.ogg","t":605660.1000000238,"reason":"kill","ta":598.491} +{"ev":"fetch","url":"audio/voice/female/time_band3.ogg","status":200,"ok":true,"ms":3,"t":605663.2000000179,"ta":598.494} +{"ev":"vstart","url":"audio/voice/female/time_band3.ogg","t":605667.8000000119,"dur":3.2,"ta":598.499} +{"ev":"vend","url":"audio/voice/female/time_band3.ogg","t":609140.2000000179,"reason":"kill","ta":601.971} +{"ev":"fetch","url":"audio/voice/female/again.ogg","status":200,"ok":true,"ms":2,"t":609142.6000000238,"ta":601.974} +{"ev":"vstart","url":"audio/voice/female/again.ogg","t":609148.400000006,"dur":4.96,"ta":601.979} +{"ev":"vend","url":"audio/voice/female/again.ogg","t":614410.1000000238,"reason":"kill","ta":607.241} +{"ev":"start","t":614410.400000006,"sid":63,"url":"audio/bgm/menu.ogg","layer":"bgm","dur":90,"rate":1,"loop":true,"when":0,"ta":607.241} +{"ev":"gesture","type":"tap","x":195,"y":422,"t":630374.6000000238,"ta":623.206} +{"ev":"vibrate","pattern":"[20]","t":630739.2000000179,"ta":623.57} +{"ev":"start","t":630740,"sid":64,"url":"audio/bgm/drone.ogg","layer":"bgm","dur":117,"rate":1,"loop":true,"when":0,"ta":623.571} +{"ev":"start","t":630740.1000000238,"sid":65,"url":"audio/sfx/amb_drip.ogg","layer":"sfx","dur":5,"rate":1,"loop":false,"when":0,"ta":623.571} +{"ev":"fetch","url":"audio/voice/female/replay.ogg","status":200,"ok":true,"ms":3,"t":630742.5,"ta":623.573} +{"ev":"vstart","url":"audio/voice/female/replay.ogg","t":630751.2000000179,"dur":8.24,"ta":623.582} +{"ev":"stop","t":631600.2000000179,"sid":63,"when":0,"ta":624.431} +{"ev":"vend","url":"audio/voice/female/replay.ogg","t":639000.2000000179,"reason":"kill","ta":631.831} +{"ev":"recstop","t":646344.7000000179,"ta":639.176} diff --git a/research/escape-state/round2/live-transcript.md b/research/escape-state/round2/live-transcript.md new file mode 100644 index 00000000..c5e60181 --- /dev/null +++ b/research/escape-state/round2/live-transcript.md @@ -0,0 +1,362 @@ +# escape 实时会话 transcript + +- `[ -5.10s]` **GESTURE** tap @(195,422) +- `[ 0.00s]` REC 开始 (audio/webm;codecs=opus) +- `[ 0.08s]` VOICE ▶ g0_intro (dur 6.64s) +- `[ 0.27s]` BGM ♪ drone (loop) +- `[ 6.93s]` VOICE ■ g0_intro (kill) +- `[ 6.93s]` VERDICT g0_intro: **OK** sim=1 + - 期望: 黑暗。你在废弃的地下设施里醒来,什么都想不起来。你必须找到出路。 + - 听到: 黑暗,你在废弃的地下设施里醒来,什么都想不起来,你必须找到出路。 +- `[ 8.13s]` VOICE ▶ g1_move (dur 4.56s) +- `[ 12.74s]` VOICE ■ g1_move (kill) +- `[ 12.74s]` VOICE ▶ skip_hint (dur 1.6s) +- `[ 12.74s]` VERDICT g1_move: **OK** sim=0.9697 + - 期望: 先活动一下。朝任意方向滑一下,走一小步。 + - 听到: 先活动一下,朝任意方向滑一下,走一小。 +- `[ 14.36s]` VOICE ■ skip_hint (kill) +- `[ 14.36s]` VERDICT skip_hint: **OK** sim=0.8571 + - 期望: 单击可跳过旁白。 + - 听到: 单击可跳过旁板。 +- `[ 79.05s]` **GESTURE** swipeup @(195,422) +- `[ 79.41s]` VIBRATE [10] +- `[ 79.41s]` SFX ♪ step +- `[ 79.42s]` VOICE ▶ g1_done (dur 2.72s) +- `[ 79.42s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 82.15s]` VOICE ■ g1_done (kill) +- `[ 82.15s]` VERDICT g1_done: **OK** sim=0.9412 + - 期望: 很好,你迈出了第一步。 + - 听到: 很好,你迈出了第一。 +- `[ 82.16s]` VOICE ▶ g2_wall (dur 5.68s) +- `[ 82.32s]` **GESTURE** swipeleft @(195,422) +- `[ 82.68s]` VIBRATE [10] +- `[ 87.88s]` VOICE ■ g2_wall (kill) +- `[ 87.88s]` VIBRATE [60,40,60] +- `[ 87.88s]` VERDICT g2_wall: **OK** sim=1 + - 期望: 这个房间不大,四面都是墙。再滑两下,试试碰碰墙。 + - 听到: 这个房间不大,四面都是墙,再滑两下试试碰碰墙。 +- `[ 87.88s]` SFX ♪ wall +- `[ 87.89s]` VOICE ▶ wall (dur 2.08s) +- `[ 90.24s]` VOICE ■ wall (kill) +- `[ 90.24s]` VERDICT wall: **OK** sim=0.9412 + - 期望: 那边是墙,换个方向。 + - 听到: 墙那边是墙,换个方向。 +- `[ 109.13s]` **GESTURE** swiperight @(195,422) +- `[ 109.49s]` VIBRATE [10] +- `[ 109.49s]` VIBRATE [60,40,60] +- `[ 109.49s]` SFX ♪ wall +- `[ 109.49s]` VOICE ▶ wall (dur 2.08s) +- `[ 109.49s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 111.86s]` VOICE ■ wall (kill) +- `[ 111.86s]` VERDICT wall: **OK** sim=1 + - 期望: 那边是墙,换个方向。 + - 听到: 那边是墙,换个方向。 +- `[ 111.87s]` VOICE ▶ g3_left (dur 2.96s) +- `[ 114.88s]` VOICE ■ g3_left (kill) +- `[ 114.88s]` VERDICT g3_left: **OK** sim=0.9 + - 期望: 跟着我指的路走。向左滑。 + - 听到: 跟着我指的路走,向左画。 +- `[ 116.90s]` **GESTURE** swipeleft @(195,422) +- `[ 117.26s]` VIBRATE [10] +- `[ 117.26s]` SFX ♪ step +- `[ 117.26s]` VOICE ▶ g4_down (dur 1.84s) +- `[ 117.26s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 119.17s]` VOICE ■ g4_down (kill) +- `[ 119.17s]` VERDICT g4_down: **OK** sim=1 + - 期望: 对。再向下滑。 + - 听到: 对,再向下滑。 +- `[ 122.67s]` **GESTURE** swipedown @(195,422) +- `[ 123.02s]` VIBRATE [10] +- `[ 123.02s]` SFX ♪ step +- `[ 123.02s]` SFX ♪ amb_storage +- `[ 123.03s]` VOICE ▶ g5_inspect (dur 3.92s) +- `[ 123.03s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 127.17s]` VOICE ■ g5_inspect (kill) +- `[ 127.17s]` VERDICT g5_inspect: **OK** sim=1 + - 期望: 听到水声了吗?单击一下,仔细检查这个房间。 + - 听到: 听到水声了吗?单击一下,仔细检查这个房间。 +- `[ 130.44s]` **GESTURE** tap @(195,422) +- `[ 130.80s]` VIBRATE [20] +- `[ 130.80s]` VIBRATE [15] +- `[ 130.80s]` SFX ♪ pickup +- `[ 130.81s]` VOICE ▶ g5_key (dur 1.52s) +- `[ 130.81s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 132.42s]` VOICE ■ g5_key (kill) +- `[ 132.42s]` VERDICT g5_key: **OK** sim=1 + - 期望: 你摸到了一把钥匙! + - 听到: 你摸到了一把钥匙。 +- `[ 132.43s]` VOICE ▶ g6_doubletap (dur 10.64s) +- `[ 140.91s]` **GESTURE** doubletap @(195,422) +- `[ 141.16s]` VIBRATE [20] +- `[ 143.39s]` VOICE ■ g6_doubletap (kill) +- `[ 143.39s]` VERDICT g6_doubletap: **OK** sim=0.9787 + - 期望: 钥匙能开锁。双击,是把物品用出去的用法。现在,双击试试。 + - 听到: 匙钥匙能开锁,双击是把物品用出去的用法。现在双击试试。 +- `[ 143.40s]` VOICE ▶ inv_key (dur 1.68s) +- `[ 145.29s]` VOICE ■ inv_key (kill) +- `[ 145.29s]` VERDICT inv_key: **OK** sim=1 + - 期望: 你身上带着一把钥匙。 + - 听到: 你身上带着一把钥匙。 +- `[ 145.30s]` VOICE ▶ g6_done (dur 3.92s) +- `[ 149.45s]` VOICE ■ g6_done (kill) +- `[ 149.45s]` VERDICT g6_done: **OK** sim=1 + - 期望: 对,就是这样。接下来,靠你自己了。 + - 听到: 对,就是这样,接下来靠你自己了。 +- `[ 149.46s]` VOICE ▶ g7_goal (dur 6.88s) +- `[ 156.59s]` VOICE ■ g7_goal (kill) +- `[ 156.59s]` VERDICT g7_goal: **OK** sim=1 + - 期望: 找到能用这把钥匙打开的东西。需要帮助时,在原地双击。 + - 听到: 找到能用这把钥匙打开的东西,需要帮助时在原地双击。 +- `[ 156.59s]` SFX ♪ amb_storage +- `[ 178.80s]` **GESTURE** swipeup @(195,422) +- `[ 179.16s]` VIBRATE [10] +- `[ 179.16s]` SFX ♪ step +- `[ 179.16s]` SFX ♪ amb_hum +- `[ 179.16s]` VOICE ▶ empty_2 (dur 4.88s) +- `[ 179.16s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 184.15s]` VOICE ■ empty_2 (kill) +- `[ 184.15s]` VERDICT empty_2: **OK** sim=1 + - 期望: 你伸出手,摸到的只有冰冷粗糙的墙壁。 + - 听到: 你伸出手摸到的只有冰冷粗糙的墙壁。 +- `[ 185.57s]` **GESTURE** swipeup @(195,422) +- `[ 185.92s]` VIBRATE [10] +- `[ 185.92s]` SFX ♪ step +- `[ 185.92s]` SFX ♪ amb_power +- `[ 185.93s]` VOICE ▶ room_power (dur 2.64s) +- `[ 185.93s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 188.67s]` VOICE ■ room_power (kill) +- `[ 188.67s]` VERDICT room_power: **OK** sim=1 + - 期望: 这个房间的墙上,有一只电箱。 + - 听到: 这个房间的墙上有一只电箱。 +- `[ 192.33s]` **GESTURE** swipeup @(195,422) +- `[ 192.69s]` VIBRATE [10] +- `[ 192.69s]` VIBRATE [60,40,60] +- `[ 192.69s]` SFX ♪ wall +- `[ 192.69s]` VOICE ▶ wall (dur 2.08s) +- `[ 192.69s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 195.05s]` VOICE ■ wall (kill) +- `[ 195.05s]` VERDICT wall: **OK** sim=1 + - 期望: 那边是墙,换个方向。 + - 听到: 那边是墙,换个方向。 +- `[ 195.06s]` VOICE ▶ wall_dir_u (dur 1.36s) +- `[ 196.67s]` VOICE ■ wall_dir_u (kill) +- `[ 196.67s]` VERDICT wall_dir_u: **OK** sim=1 + - 期望: 上边是墙。 + - 听到: 上边是墙。 +- `[ 199.10s]` **GESTURE** swipeup @(195,422) +- `[ 199.45s]` VIBRATE [10] +- `[ 199.46s]` VIBRATE [60,40,60] +- `[ 199.46s]` SFX ♪ wall +- `[ 199.46s]` VOICE ▶ wall (dur 2.08s) +- `[ 199.46s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 201.82s]` VOICE ■ wall (kill) +- `[ 201.82s]` VERDICT wall: **OK** sim=1 + - 期望: 那边是墙,换个方向。 + - 听到: 那边是墙,换个方向。 +- `[ 203.86s]` **GESTURE** swipeleft @(195,422) +- `[ 204.22s]` VIBRATE [10] +- `[ 204.22s]` VIBRATE [60,40,60] +- `[ 204.22s]` SFX ♪ wall +- `[ 204.22s]` VOICE ▶ wall (dur 2.08s) +- `[ 204.22s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 206.59s]` VOICE ■ wall (kill) +- `[ 206.59s]` VERDICT wall: **OK** sim=1 + - 期望: 那边是墙,换个方向。 + - 听到: 那边是墙,换个方向。 +- `[ 206.60s]` VOICE ▶ wall_dir_l (dur 1.04s) +- `[ 207.75s]` VOICE ■ wall_dir_l (kill) +- `[ 207.75s]` VERDICT wall_dir_l: **OK** sim=1 + - 期望: 左边是墙。 + - 听到: 左边是墙。 +- `[ 210.63s]` **GESTURE** doubletap @(195,422) +- `[ 210.88s]` VIBRATE [20] +- `[ 210.88s]` VIBRATE [60,40,60] +- `[ 210.89s]` VOICE ▶ use_fail (dur 7.36s) +- `[ 210.89s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 218.48s]` VOICE ■ use_fail (kill) +- `[ 218.48s]` VERDICT use_fail: **OK** sim=0.9655 + - 期望: 这里没有用得上东西的地方。换个房间试试,或者先单击检查一下周围。 + - 听到: 这里没有用得上东西的地方,换个房间试试,或者先单机检查一下周围。 +- `[ 218.49s]` VOICE ▶ stuck_cabinet_1 (dur 3.84s) +- `[ 222.62s]` VOICE ■ stuck_cabinet_1 (kill) +- `[ 222.62s]` VERDICT stuck_cabinet_1: **OK** sim=0.9333 + - 期望: 钥匙就在你身上。它能打开某一把锁。 + - 听到: 钥匙就在你身上,他能打开某一把锁。 +- `[ 255.88s]` **GESTURE** swiperight @(195,422) +- `[ 256.24s]` VIBRATE [10] +- `[ 256.24s]` SFX ♪ step +- `[ 256.24s]` SFX ♪ amb_wind +- `[ 256.25s]` VOICE ▶ empty_1 (dur 5.2s) +- `[ 256.25s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 261.50s]` VOICE ■ empty_1 (kill) +- `[ 261.50s]` VERDICT empty_1: **OK** sim=1 + - 期望: 房间里空空荡荡,只有你自己的回声。 + - 听到: 房间里空空荡荡,只有你自己的回声。 +- `[ 262.64s]` **GESTURE** swiperight @(195,422) +- `[ 263.00s]` VIBRATE [10] +- `[ 263.00s]` SFX ♪ step +- `[ 263.00s]` SFX ♪ amb_hum +- `[ 263.01s]` VOICE ▶ empty_3 (dur 3.68s) +- `[ 263.01s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 266.86s]` VOICE ■ empty_3 (kill) +- `[ 266.86s]` VERDICT empty_3: **OK** sim=1 + - 期望: 除了远处的水滴声,这里什么都没有。 + - 听到: 除了远处的水滴声,这里什么都没有。 +- `[ 269.41s]` **GESTURE** swipedown @(195,422) +- `[ 269.77s]` VIBRATE [10] +- `[ 269.77s]` SFX ♪ step +- `[ 269.77s]` SFX ♪ amb_cabinet +- `[ 269.78s]` VOICE ▶ room_cabinet (dur 2.64s) +- `[ 269.78s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 272.52s]` VOICE ■ room_cabinet (kill) +- `[ 272.52s]` VERDICT room_cabinet: **OK** sim=1 + - 期望: 这个房间里,立着一个铁柜。 + - 听到: 这个房间里立着一个铁柜。 +- `[ 276.18s]` **GESTURE** doubletap @(195,422) +- `[ 276.43s]` VIBRATE [20] +- `[ 276.43s]` VIBRATE [100] +- `[ 276.43s]` SFX ♪ unlock +- `[ 276.43s]` SFX ♪ door_creak +- `[ 276.44s]` VOICE ▶ unlock_cabinet (dur 11.48s) +- `[ 276.44s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 288.15s]` VOICE ■ unlock_cabinet (kill) +- `[ 288.15s]` VERDICT unlock_cabinet: **OK** sim=0.9714 + - 期望: 钥匙插进锁孔,缓缓转动。咔哒一声,柜门开了。柜子深处,躺着一枚保险丝。保险丝到手了。 + - 听到: 钥匙插进锁孔,缓缓转动,咔嗒一声,柜门开了,柜子深处躺着一枚保险丝,保险丝到手了。 +- `[ 288.16s]` VOICE ▶ gd_next_fuse (dur 3.28s) +- `[ 291.63s]` VOICE ■ gd_next_fuse (kill) +- `[ 291.63s]` VERDICT gd_next_fuse: **OK** sim=1 + - 期望: 保险丝到手。接下来,去找电箱。 + - 听到: 保险丝到手,接下来去找电箱。 +- `[ 291.64s]` VOICE ▶ gd_box_dir (dur 2.16s) +- `[ 294.01s]` VOICE ■ gd_box_dir (kill) +- `[ 294.01s]` VERDICT gd_box_dir: **OK** sim=1 + - 期望: 电箱,在左面的房间。 + - 听到: 电箱在左面的房间。 +- `[ 310.43s]` **GESTURE** swipeleft @(195,422) +- `[ 310.78s]` VIBRATE [10] +- `[ 310.78s]` SFX ♪ step +- `[ 310.78s]` SFX ♪ amb_drip +- `[ 327.19s]` **GESTURE** swipeleft @(195,422) +- `[ 327.55s]` VIBRATE [10] +- `[ 327.55s]` SFX ♪ step +- `[ 327.55s]` SFX ♪ amb_hum +- `[ 343.96s]` **GESTURE** swiperight @(195,422) +- `[ 344.31s]` VIBRATE [10] +- `[ 344.31s]` SFX ♪ step +- `[ 344.31s]` SFX ♪ amb_drip +- `[ 360.73s]` **GESTURE** swiperight @(195,422) +- `[ 361.08s]` VIBRATE [10] +- `[ 361.08s]` SFX ♪ step +- `[ 361.08s]` SFX ♪ amb_cabinet +- `[ 377.49s]` **GESTURE** swipeup @(195,422) +- `[ 377.85s]` VIBRATE [10] +- `[ 377.85s]` SFX ♪ step +- `[ 377.85s]` SFX ♪ amb_hum +- `[ 408.88s]` **GESTURE** swipedown @(195,422) +- `[ 409.25s]` VIBRATE [10] +- `[ 409.25s]` SFX ♪ step +- `[ 409.25s]` SFX ♪ amb_cabinet +- `[ 425.66s]` **GESTURE** swipeleft @(195,422) +- `[ 426.01s]` VIBRATE [10] +- `[ 426.01s]` SFX ♪ step +- `[ 426.01s]` SFX ♪ amb_drip +- `[ 430.00s]` VIBRATE [30,50,30,50,30] +- `[ 430.01s]` VOICE ▶ gd_prog_2 (dur 2.56s) +- `[ 430.01s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 432.75s]` VOICE ■ gd_prog_2 (kill) +- `[ 432.75s]` VERDICT gd_prog_2: **OK** sim=1 + - 期望: 铁柜已经打开。电还没有通。 + - 听到: 铁柜已经打开,电还没有通。 +- `[ 442.42s]` **GESTURE** swipeleft @(195,422) +- `[ 442.78s]` VIBRATE [10] +- `[ 442.78s]` SFX ♪ step +- `[ 442.78s]` SFX ♪ amb_hum +- `[ 459.19s]` **GESTURE** swiperight @(195,422) +- `[ 459.54s]` VIBRATE [10] +- `[ 459.54s]` SFX ♪ step +- `[ 459.54s]` SFX ♪ amb_drip +- `[ 475.95s]` **GESTURE** swiperight @(195,422) +- `[ 476.31s]` VIBRATE [10] +- `[ 476.31s]` SFX ♪ step +- `[ 476.31s]` SFX ♪ amb_cabinet +- `[ 526.84s]` **GESTURE** swipeleft @(195,422) +- `[ 527.19s]` VIBRATE [10] +- `[ 527.19s]` SFX ♪ step +- `[ 527.19s]` SFX ♪ amb_drip +- `[ 531.60s]` **GESTURE** swipeleft @(195,422) +- `[ 531.96s]` VIBRATE [10] +- `[ 531.96s]` SFX ♪ step +- `[ 531.96s]` SFX ♪ amb_hum +- `[ 536.37s]` **GESTURE** swipeup @(195,422) +- `[ 536.73s]` VIBRATE [10] +- `[ 536.73s]` SFX ♪ step +- `[ 536.73s]` SFX ♪ amb_power +- `[ 541.13s]` **GESTURE** doubletap @(195,422) +- `[ 541.38s]` VIBRATE [20] +- `[ 541.38s]` VIBRATE [100] +- `[ 541.38s]` SFX ♪ spark +- `[ 541.38s]` SFX ♪ power_on +- `[ 541.39s]` VOICE ▶ fix_box (dur 7.52s) +- `[ 541.39s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 548.98s]` VOICE ■ fix_box (kill) +- `[ 548.98s]` VERDICT fix_box: **OK** sim=0.9153 + - 期望: 你把保险丝用力按进卡座。火花一闪,嗡鸣声响起——头顶的线路恢复供电了! + - 听到: 你把保险丝用力安静卡座,火花一闪,嗡鸣声响起,头顶的线路恢复供电。 +- `[ 548.99s]` VOICE ▶ gd_gate_dir (dur 2.32s) +- `[ 550.79s]` **GESTURE** swiperight @(195,422) +- `[ 551.16s]` VIBRATE [10] +- `[ 551.16s]` SFX ♪ step +- `[ 551.16s]` SFX ♪ amb_wind +- `[ 551.36s]` VOICE ■ gd_gate_dir (kill) +- `[ 551.36s]` VERDICT gd_gate_dir: **OK** sim=1 + - 期望: 大门,在右面的房间。 + - 听到: 大门在右面的房间。 +- `[ 555.57s]` **GESTURE** swiperight @(195,422) +- `[ 555.92s]` VIBRATE [10] +- `[ 555.92s]` SFX ♪ step +- `[ 555.92s]` SFX ♪ amb_hum +- `[ 560.33s]` **GESTURE** swipedown @(195,422) +- `[ 560.69s]` VIBRATE [10] +- `[ 560.69s]` SFX ♪ step +- `[ 560.69s]` SFX ♪ amb_cabinet +- `[ 565.10s]` **GESTURE** swipedown @(195,422) +- `[ 565.46s]` VIBRATE [10] +- `[ 565.46s]` SFX ♪ step +- `[ 565.46s]` SFX ♪ amb_gate_live +- `[ 565.46s]` VOICE ▶ room_gate (dur 3.52s) +- `[ 565.46s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 569.31s]` VOICE ■ room_gate (kill) +- `[ 569.31s]` VERDICT room_gate: **OK** sim=1 + - 期望: 面前,是一扇沉重的铁门。 + - 听到: 面前是一扇沉重的铁门。 +- `[ 582.80s]` **GESTURE** doubletap @(195,422) +- `[ 583.05s]` VIBRATE [20] +- `[ 583.05s]` VIBRATE [100] +- `[ 583.06s]` VOICE ▶ escape (dur 15.32s) +- `[ 583.06s]` VERDICT (无语音区间): **SFX_BGM_ONLY** +- `[ 585.65s]` SFX ♪ gate_open +- `[ 585.65s]` SFX ♪ win_chime +- `[ 587.46s]` **GESTURE** tap @(195,422) +- `[ 587.82s]` VIBRATE [20] +- `[ 598.49s]` VOICE ■ escape (kill) +- `[ 598.49s]` VERDICT escape: **OK** sim=0.9053 + - 期望: 你扳动开关。电机轰鸣,沉重的铁门缓缓升起,刺眼的光线涌了进来。你眯起眼睛,向前迈出了第一步。自由了。你逃出来了。 + - 听到: 你搬动开关,电击轰,沉重的体能缓缓升起,刺眼的光线涌了进来,你眯起眼睛向前迈出了第一步,自由了,你逃出来了。 +- `[ 598.50s]` VOICE ▶ time_band3 (dur 3.2s) +- `[ 601.97s]` VOICE ■ time_band3 (kill) +- `[ 601.97s]` VERDICT time_band3: **OK** sim=0.88 + - 期望: 你逃出来了,用时不到十分钟。 + - 听到: 你逃出来了,用时不到10分钟。 +- `[ 601.98s]` VOICE ▶ again (dur 4.96s) +- `[ 607.24s]` VOICE ■ again (kill) +- `[ 607.24s]` BGM ♪ menu (loop) +- `[ 607.24s]` VERDICT again: **OK** sim=1 + - 期望: 单击屏幕,再来一次。下滑,打开菜单。 + - 听到: 单击屏幕再来一次下滑打开菜单。 +- `[ 623.21s]` **GESTURE** tap @(195,422) +- `[ 623.57s]` VIBRATE [20] +- `[ 623.57s]` BGM ♪ drone (loop) +- `[ 623.57s]` SFX ♪ amb_drip +- `[ 623.58s]` VOICE ▶ replay (dur 8.24s) +- `[ 631.83s]` VOICE ■ replay (kill) diff --git a/research/escape-state/round2/mix.webm b/research/escape-state/round2/mix.webm new file mode 100644 index 00000000..7e27c75c Binary files /dev/null and b/research/escape-state/round2/mix.webm differ diff --git a/research/escape-state/round2/transcript-mock.md b/research/escape-state/round2/transcript-mock.md new file mode 100644 index 00000000..22e9fe3f --- /dev/null +++ b/research/escape-state/round2/transcript-mock.md @@ -0,0 +1,164 @@ +# 试玩转写:《escape》 + +> 本文件是「玩家感知日志」:一位新手玩家的整局体验转写。语音列出了玩家听到的每句话全文, +> 音效/BGM/震动按发生时间列出。「静默」段 = 玩家什么也听不到。手势是剧本注入的(模拟真人手部动作), +> 非最优操作:包含犹豫、误操作与乱试。语音时长按语速估算(±20%),仅供节奏参考。 +t+ 0.0s (玩家点开游戏,轻触屏幕开始) +t+ 0.0s ♫ BGM 切换 → bgm/drone +t+ 0.0s ♪ 向导音:explore(探索/安全) +t+ 0.0s ▶ 语音:「黑暗。你在废弃的地下设施里醒来,什么都想不起来。你必须找到出路。」 +t+ 0.0s 【状态】黑屏,只有引子的声音,先听完 引导第 1/8 步,等我做手势(语音播放中,语速1) +t+ 0.0s 【状态】引导第 1/8 步 引导第 1/8 步,等我做手势(语音播放中,语速1) + +#### 【玩家动作】照引导单击了一下 + +t+ 0.0s ▶ 语音:「先活动一下。朝任意方向滑一下,走一小步。」 +t+ 2.1s ▶ 语音:「单击可跳过旁白。」 +t+ 4.2s ♪ 向导音:turn(轮到你) +t+ 4.2s 【状态】引导第 2/8 步 引导第 2/8 步,等我做手势(BGM=bgm/drone,语速1) + +#### 【玩家动作】随便滑了一步,有脚步声 + +t+ 4.2s ♪ 音效 sfx/step +t+ 4.2s ▶ 语音:「很好,你迈出了第一步。」 +t+ 6.4s ▶ 语音:「这个房间不大,四面都是墙。再滑两下,试试碰碰墙。」 +t+ 8.5s ♪ 向导音:turn(轮到你) +t+ 8.5s 【状态】引导第 3/8 步 引导第 3/8 步,等我做手势(BGM=bgm/drone,语速1) +t+ 8.5s ♪ 音效 sfx/wall +t+ 8.5s ▶ 语音:「那边是墙,换个方向。」 +t+ 8.5s 〰 震动:error(做错了:两连短震) + +#### 【玩家动作】又滑一步,又撞墙了——墙的方位听出来了 + +t+ 10.6s ♪ 音效 sfx/wall +t+ 10.6s ▶ 语音:「那边是墙,换个方向。」 +t+ 10.6s 〰 震动:error(做错了:两连短震) +t+ 12.7s ▶ 语音:「跟着我指的路走。向左滑。」 +t+ 14.8s ♪ 向导音:turn(轮到你) +t+ 14.8s 【状态】引导第 4/8 步 引导第 4/8 步,等我做手势(BGM=bgm/drone,语速1) + +#### 【玩家动作】这回往左,走通了一步 + +t+ 14.8s ♪ 音效 sfx/step +t+ 14.8s ▶ 语音:「对。再向下滑。」 +t+ 17.0s ♪ 向导音:turn(轮到你) +t+ 17.0s 【状态】引导第 5/8 步 引导第 5/8 步,等我做手势(BGM=bgm/drone,语速1) + +#### 【玩家动作】往下滑,进了储物间 + +t+ 17.0s ♪ 音效 sfx/step +t+ 17.0s ♪ 音效 sfx/amb_storage +t+ 17.0s ▶ 语音:「听到水声了吗?单击一下,仔细检查这个房间。」 +t+ 19.1s ♪ 向导音:turn(轮到你) +t+ 19.1s 【状态】引导第 6/8 步 引导第 6/8 步,等我做手势(BGM=bgm/drone,语速1) + +#### 【玩家动作】单击摸一遍房间,摸到钥匙了 + +t+ 19.1s ♪ 音效 sfx/pickup +t+ 19.1s ▶ 语音:「你摸到了一把钥匙!」 +t+ 19.1s 〰 震动:pickup(获得) +t+ 21.2s ▶ 语音:「钥匙能开锁。双击,是把物品用出去的用法。现在,双击试试。」 +t+ 23.3s ♪ 向导音:turn(轮到你) +t+ 23.3s 【状态】引导第 7/8 步 引导第 7/8 步,等我做手势(BGM=bgm/drone,语速1) + +#### 【玩家动作】双击看了看背包 + +t+ 23.3s ▶ 语音:「你身上带着一把钥匙。」 +t+ 25.4s ▶ 语音:「对,就是这样。接下来,靠你自己了。」 +t+ 27.6s ▶ 语音:「找到能用这把钥匙打开的东西。需要帮助时,在原地双击。」 +t+ 29.7s ♪ 向导音:turn(轮到你) +t+ 29.7s ♪ 音效 sfx/amb_storage +t+ 29.7s 【状态】引导完毕,正式开始。我在储物间 探索中,我在 (0,2)。四向滑挪步,单击摸一遍房间,双击用物品(BGM=bgm/drone,语速1) +t+ 29.7s ……… 静默 3s(辨了辨方向) ……… +t+ 29.7s ♪ 音效 sfx/step +t+ 29.7s ♪ 音效 sfx/amb_hum +t+ 29.7s ▶ 语音:「你伸出手,摸到的只有冰冷粗糙的墙壁。」 + +#### 【玩家动作】路过一间空房,它自己开口了:摸到的只有冰冷粗糙的墙壁 + +t+ 31.8s ♪ 音效 sfx/step +t+ 31.8s ♪ 音效 sfx/amb_power +t+ 31.8s ▶ 语音:「这个房间的墙上,有一只电箱。」 + +#### 【玩家动作】电流声——电箱房到了 + +t+ 33.9s ♪ 音效 sfx/wall +t+ 33.9s ▶ 语音:「那边是墙,换个方向。」 +t+ 33.9s 〰 震动:error(做错了:两连短震) +t+ 36.0s ▶ 语音:「上边是墙。」 + +#### 【玩家动作】北边是墙,撞了一下——「那边是墙」后面跟了句「上边是墙。」,这面不通记住了 + +t+ 36.0s ♪ 音效 sfx/wall +t+ 36.0s ▶ 语音:「那边是墙,换个方向。」 +t+ 36.0s 〰 震动:error(做错了:两连短震) + +#### 【玩家动作】又撞了一下——同方向 10 秒内不重复报方位,游戏不啰嗦 + +t+ 45.0s ♪ 音效 sfx/step +t+ 45.0s ♪ 音效 sfx/amb_wind +t+ 45.0s ▶ 语音:「房间里空空荡荡,只有你自己的回声。」 + +#### 【玩家动作】往东走,一间空房自报家门:空空荡荡只有回声 + +t+ 47.1s ♪ 音效 sfx/step +t+ 47.1s ♪ 音效 sfx/amb_hum +t+ 47.1s ▶ 语音:「除了远处的水滴声,这里什么都没有。」 + +#### 【玩家动作】又一间空房:除了远处的水滴声什么都没有 + +t+ 49.2s ♪ 音效 sfx/step +t+ 49.2s ♪ 音效 sfx/amb_cabinet +t+ 49.2s ▶ 语音:「这个房间里,立着一个铁柜。」 + +#### 【玩家动作】到了铁柜房,柜子锁着 + +t+ 51.4s ♪ 音效 sfx/unlock +t+ 51.4s ♪ 音效 sfx/door_creak +t+ 51.4s ▶ 语音:「钥匙插进锁孔,缓缓转动。咔哒一声,柜门开了。柜子深处,躺着一枚保险丝。保险丝到手了。」 +t+ 51.4s 〰 震动:success(成功长震) +t+ 53.5s ▶ 向导语音:「保险丝到手。接下来,去找电箱。」 +t+ 55.6s ▶ 向导语音:「电箱,在左面的房间。」 +t+ 57.7s 【状态】柜子开了。「保险丝到手。接下来,去找电箱。」后面跟的是「电箱,在左面的房间。」——目标名和方位一句话说全了 探索中,我在 (2,1)。四向滑挪步,单击摸一遍房间,双击用物品(BGM=bgm/drone,语速1) +t+ 57.7s ♪ 音效 sfx/step +t+ 57.7s ♪ 音效 sfx/amb_drip + +#### 【玩家动作】照方位句往左(西)摸 + +t+ 60.0s ♪ 音效 sfx/step +t+ 60.0s ♪ 音效 sfx/amb_hum +t+ 65.0s ♪ 音效 sfx/step +t+ 65.0s ♪ 音效 sfx/amb_power + +#### 【玩家动作】电流声越来越清晰——电箱房到了 + +t+ 70.0s ♪ 音效 sfx/spark +t+ 70.0s ♪ 音效 sfx/power_on +t+ 70.0s ▶ 语音:「你把保险丝用力按进卡座。火花一闪,嗡鸣声响起——头顶的线路恢复供电了!」 +t+ 70.0s 〰 震动:success(成功长震) +t+ 72.1s ▶ 向导语音:「大门,在右面的房间。」 +t+ 80.0s 【状态】电箱修好了。目标句后面照样是目标名+方位——「大门,在右面的房间。」 探索中,我在 (0,0)。四向滑挪步,单击摸一遍房间,双击用物品(BGM=bgm/drone,语速1) +t+ 80.0s ♪ 音效 sfx/step +t+ 80.0s ♪ 音效 sfx/amb_wind + +#### 【玩家动作】照方位句往右(东)摸 + +t+ 85.0s ♪ 音效 sfx/step +t+ 85.0s ♪ 音效 sfx/amb_hum +t+ 90.0s ♪ 音效 sfx/step +t+ 90.0s ♪ 音效 sfx/amb_cabinet +t+ 95.0s ♪ 音效 sfx/step +t+ 95.0s ♪ 音效 sfx/amb_gate_live +t+ 95.0s ▶ 语音:「面前,是一扇沉重的铁门。」 + +#### 【玩家动作】脚步声停下来——面前这间就是大门 + +t+ 97.1s ♪ 向导音:settle(结算) +t+ 97.1s ▶ 语音:「你扳动开关。电机轰鸣,沉重的铁门缓缓升起,刺眼的光线涌了进来。你眯起眼睛,向前迈出了第一步。自由了。你逃出来了。」 +t+ 97.1s 〰 震动:success(成功长震) +t+ 99.2s ▶ 语音:「你逃出来了,用时不到两分钟。了不起!」 +t+ 99.7s ♪ 音效 sfx/gate_open +t+ 99.7s ♪ 音效 sfx/win_chime +t+ 101.4s ▶ 语音:「单击屏幕,再来一次。下滑,打开菜单。」 +t+ 103.5s ♫ BGM 切换 → bgm/menu(淡入 1500ms) +t+ 103.5s 【状态】钥匙插进锁孔,门开了——结算播报来了,这局是完整的赢 逃脱了,结算播报中。播完单击再来,下滑开菜单(BGM=bgm/menu,语速1) diff --git a/research/escape-state/round2/verdicts.json b/research/escape-state/round2/verdicts.json new file mode 100644 index 00000000..e5b42269 --- /dev/null +++ b/research/escape-state/round2/verdicts.json @@ -0,0 +1,1012 @@ +{ + "game": "escape", + "settings": { + "voice": "female", + "guide": "full", + "speed": 1, + "bgm": 1 + }, + "outdir": "/home/xsl/AutoVideo/SoundGame/web/harness/live/out/escape-20260914162023", + "segs": [ + { + "idx": 0, + "voiceId": "g0_intro", + "t0": 0.077, + "t1": 6.931, + "dur": 6.64, + "expEnd": 6.717, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "黑暗,你在废弃的地下设施里醒来,什么都想不起来,你必须找到出路。", + "similarity": 1 + }, + { + "idx": 1, + "voiceId": "g1_move", + "t0": 8.132, + "t1": 12.741, + "dur": 4.56, + "expEnd": 12.692, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "先活动一下,朝任意方向滑一下,走一小。", + "similarity": 0.9697 + }, + { + "idx": 2, + "voiceId": "skip_hint", + "t0": 12.741, + "t1": 14.361, + "dur": 1.6, + "expEnd": 14.341, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "单击可跳过旁板。", + "similarity": 0.8571 + }, + { + "idx": 3, + "voiceId": "g1_done", + "t0": 79.417, + "t1": 82.151, + "dur": 2.72, + "expEnd": 82.137, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "很好,你迈出了第一。", + "similarity": 0.9412 + }, + { + "idx": 4, + "voiceId": "g2_wall", + "t0": 82.159, + "t1": 87.881, + "dur": 5.68, + "expEnd": 87.839, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "这个房间不大,四面都是墙,再滑两下试试碰碰墙。", + "similarity": 1 + }, + { + "idx": 5, + "voiceId": "wall", + "t0": 87.887, + "t1": 90.241, + "dur": 2.08, + "expEnd": 89.967, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "墙那边是墙,换个方向。", + "similarity": 0.9412 + }, + { + "idx": 6, + "voiceId": "wall", + "t0": 109.491, + "t1": 111.861, + "dur": 2.08, + "expEnd": 111.571, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "那边是墙,换个方向。", + "similarity": 1 + }, + { + "idx": 7, + "voiceId": "g3_left", + "t0": 111.868, + "t1": 114.881, + "dur": 2.96, + "expEnd": 114.828, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "跟着我指的路走,向左画。", + "similarity": 0.9 + }, + { + "idx": 8, + "voiceId": "g4_down", + "t0": 117.264, + "t1": 119.171, + "dur": 1.84, + "expEnd": 119.104, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "对,再向下滑。", + "similarity": 1 + }, + { + "idx": 9, + "voiceId": "g5_inspect", + "t0": 123.032, + "t1": 127.171, + "dur": 3.92, + "expEnd": 126.952, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "听到水声了吗?单击一下,仔细检查这个房间。", + "similarity": 1 + }, + { + "idx": 10, + "voiceId": "g5_key", + "t0": 130.808, + "t1": 132.421, + "dur": 1.52, + "expEnd": 132.328, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "你摸到了一把钥匙。", + "similarity": 1 + }, + { + "idx": 11, + "voiceId": "g6_doubletap", + "t0": 132.433, + "t1": 143.391, + "dur": 10.64, + "expEnd": 143.073, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "匙钥匙能开锁,双击是把物品用出去的用法。现在双击试试。", + "similarity": 0.9787 + }, + { + "idx": 12, + "voiceId": "inv_key", + "t0": 143.398, + "t1": 145.291, + "dur": 1.68, + "expEnd": 145.078, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "你身上带着一把钥匙。", + "similarity": 1 + }, + { + "idx": 13, + "voiceId": "g6_done", + "t0": 145.299, + "t1": 149.451, + "dur": 3.92, + "expEnd": 149.219, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "对,就是这样,接下来靠你自己了。", + "similarity": 1 + }, + { + "idx": 14, + "voiceId": "g7_goal", + "t0": 149.46, + "t1": 156.591, + "dur": 6.88, + "expEnd": 156.34, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "找到能用这把钥匙打开的东西,需要帮助时在原地双击。", + "similarity": 1 + }, + { + "idx": 15, + "voiceId": "empty_2", + "t0": 179.164, + "t1": 184.151, + "dur": 4.88, + "expEnd": 184.044, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "你伸出手摸到的只有冰冷粗糙的墙壁。", + "similarity": 1 + }, + { + "idx": 16, + "voiceId": "room_power", + "t0": 185.929, + "t1": 188.671, + "dur": 2.64, + "expEnd": 188.569, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "这个房间的墙上有一只电箱。", + "similarity": 1 + }, + { + "idx": 17, + "voiceId": "wall", + "t0": 192.688, + "t1": 195.051, + "dur": 2.08, + "expEnd": 194.768, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "那边是墙,换个方向。", + "similarity": 1 + }, + { + "idx": 18, + "voiceId": "wall_dir_u", + "t0": 195.058, + "t1": 196.671, + "dur": 1.36, + "expEnd": 196.418, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "上边是墙。", + "similarity": 1 + }, + { + "idx": 19, + "voiceId": "wall", + "t0": 199.455, + "t1": 201.821, + "dur": 2.08, + "expEnd": 201.535, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "那边是墙,换个方向。", + "similarity": 1 + }, + { + "idx": 20, + "voiceId": "wall", + "t0": 204.221, + "t1": 206.591, + "dur": 2.08, + "expEnd": 206.301, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "那边是墙,换个方向。", + "similarity": 1 + }, + { + "idx": 21, + "voiceId": "wall_dir_l", + "t0": 206.597, + "t1": 207.751, + "dur": 1.04, + "expEnd": 207.637, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "左边是墙。", + "similarity": 1 + }, + { + "idx": 22, + "voiceId": "use_fail", + "t0": 210.888, + "t1": 218.481, + "dur": 7.36, + "expEnd": 218.248, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "这里没有用得上东西的地方,换个房间试试,或者先单机检查一下周围。", + "similarity": 0.9655 + }, + { + "idx": 23, + "voiceId": "stuck_cabinet_1", + "t0": 218.49, + "t1": 222.621, + "dur": 3.84, + "expEnd": 222.33, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "钥匙就在你身上,他能打开某一把锁。", + "similarity": 0.9333 + }, + { + "idx": 24, + "voiceId": "empty_1", + "t0": 256.245, + "t1": 261.501, + "dur": 5.2, + "expEnd": 261.445, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "房间里空空荡荡,只有你自己的回声。", + "similarity": 1 + }, + { + "idx": 25, + "voiceId": "empty_3", + "t0": 263.01, + "t1": 266.861, + "dur": 3.68, + "expEnd": 266.69, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "除了远处的水滴声,这里什么都没有。", + "similarity": 1 + }, + { + "idx": 26, + "voiceId": "room_cabinet", + "t0": 269.776, + "t1": 272.521, + "dur": 2.64, + "expEnd": 272.416, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "这个房间里立着一个铁柜。", + "similarity": 1 + }, + { + "idx": 27, + "voiceId": "unlock_cabinet", + "t0": 276.438, + "t1": 288.151, + "dur": 11.48, + "expEnd": 287.918, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "钥匙插进锁孔,缓缓转动,咔嗒一声,柜门开了,柜子深处躺着一枚保险丝,保险丝到手了。", + "similarity": 0.9714 + }, + { + "idx": 28, + "voiceId": "gd_next_fuse", + "t0": 288.159, + "t1": 291.631, + "dur": 3.28, + "expEnd": 291.439, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "保险丝到手,接下来去找电箱。", + "similarity": 1 + }, + { + "idx": 29, + "voiceId": "gd_box_dir", + "t0": 291.638, + "t1": 294.011, + "dur": 2.16, + "expEnd": 293.798, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "电箱在左面的房间。", + "similarity": 1 + }, + { + "idx": 30, + "voiceId": "gd_prog_2", + "t0": 430.009, + "t1": 432.751, + "dur": 2.56, + "expEnd": 432.569, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "铁柜已经打开,电还没有通。", + "similarity": 1 + }, + { + "idx": 31, + "voiceId": "fix_box", + "t0": 541.393, + "t1": 548.981, + "dur": 7.52, + "expEnd": 548.913, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "你把保险丝用力安静卡座,火花一闪,嗡鸣声响起,头顶的线路恢复供电。", + "similarity": 0.9153 + }, + { + "idx": 32, + "voiceId": "gd_gate_dir", + "t0": 548.988, + "t1": 551.361, + "dur": 2.32, + "expEnd": 551.308, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "大门在右面的房间。", + "similarity": 1 + }, + { + "idx": 33, + "voiceId": "room_gate", + "t0": 565.464, + "t1": 569.311, + "dur": 3.52, + "expEnd": 568.984, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "面前是一扇沉重的铁门。", + "similarity": 1 + }, + { + "idx": 34, + "voiceId": "escape", + "t0": 583.064, + "t1": 598.491, + "dur": 15.32, + "expEnd": 598.384, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "你搬动开关,电击轰,沉重的体能缓缓升起,刺眼的光线涌了进来,你眯起眼睛向前迈出了第一步,自由了,你逃出来了。", + "similarity": 0.9053 + }, + { + "idx": 35, + "voiceId": "time_band3", + "t0": 598.499, + "t1": 601.971, + "dur": 3.2, + "expEnd": 601.699, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "你逃出来了,用时不到10分钟。", + "similarity": 0.88 + }, + { + "idx": 36, + "voiceId": "again", + "t0": 601.979, + "t1": 607.241, + "dur": 4.96, + "expEnd": 606.939, + "truncated": false, + "reason": "kill", + "verdict": "OK", + "heard": "单击屏幕再来一次下滑打开菜单。", + "similarity": 1 + }, + { + "idx": 37, + "voiceId": "replay", + "t0": 623.582, + "t1": 631.831, + "dur": 8.24, + "expEnd": 631.822, + "truncated": false, + "reason": "kill", + "verdict": null, + "heard": null, + "similarity": null + } + ], + "rows": [ + { + "t0": 0.077, + "t1": 6.931, + "voiceId": "g0_intro", + "expected": "黑暗。你在废弃的地下设施里醒来,什么都想不起来。你必须找到出路。", + "heard": "黑暗,你在废弃的地下设施里醒来,什么都想不起来,你必须找到出路。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 8.132, + "t1": 12.741, + "voiceId": "g1_move", + "expected": "先活动一下。朝任意方向滑一下,走一小步。", + "heard": "先活动一下,朝任意方向滑一下,走一小。", + "similarity": 0.9697, + "verdict": "OK" + }, + { + "t0": 12.741, + "t1": 14.361, + "voiceId": "skip_hint", + "expected": "单击可跳过旁白。", + "heard": "单击可跳过旁板。", + "similarity": 0.8571, + "verdict": "OK" + }, + { + "t0": 79.417, + "t1": 82.151, + "voiceId": "g1_done", + "expected": "很好,你迈出了第一步。", + "heard": "很好,你迈出了第一。", + "similarity": 0.9412, + "verdict": "OK" + }, + { + "t0": 82.159, + "t1": 87.881, + "voiceId": "g2_wall", + "expected": "这个房间不大,四面都是墙。再滑两下,试试碰碰墙。", + "heard": "这个房间不大,四面都是墙,再滑两下试试碰碰墙。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 87.887, + "t1": 90.241, + "voiceId": "wall", + "expected": "那边是墙,换个方向。", + "heard": "墙那边是墙,换个方向。", + "similarity": 0.9412, + "verdict": "OK" + }, + { + "t0": 109.491, + "t1": 111.861, + "voiceId": "wall", + "expected": "那边是墙,换个方向。", + "heard": "那边是墙,换个方向。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 111.868, + "t1": 114.881, + "voiceId": "g3_left", + "expected": "跟着我指的路走。向左滑。", + "heard": "跟着我指的路走,向左画。", + "similarity": 0.9, + "verdict": "OK" + }, + { + "t0": 117.264, + "t1": 119.171, + "voiceId": "g4_down", + "expected": "对。再向下滑。", + "heard": "对,再向下滑。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 123.032, + "t1": 127.171, + "voiceId": "g5_inspect", + "expected": "听到水声了吗?单击一下,仔细检查这个房间。", + "heard": "听到水声了吗?单击一下,仔细检查这个房间。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 130.808, + "t1": 132.421, + "voiceId": "g5_key", + "expected": "你摸到了一把钥匙!", + "heard": "你摸到了一把钥匙。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 132.433, + "t1": 143.391, + "voiceId": "g6_doubletap", + "expected": "钥匙能开锁。双击,是把物品用出去的用法。现在,双击试试。", + "heard": "匙钥匙能开锁,双击是把物品用出去的用法。现在双击试试。", + "similarity": 0.9787, + "verdict": "OK" + }, + { + "t0": 143.398, + "t1": 145.291, + "voiceId": "inv_key", + "expected": "你身上带着一把钥匙。", + "heard": "你身上带着一把钥匙。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 145.299, + "t1": 149.451, + "voiceId": "g6_done", + "expected": "对,就是这样。接下来,靠你自己了。", + "heard": "对,就是这样,接下来靠你自己了。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 14.361, + "t1": 79.417, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 90.241, + "t1": 109.491, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 114.881, + "t1": 117.264, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 119.171, + "t1": 123.032, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 127.171, + "t1": 130.808, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 149.46, + "t1": 156.591, + "voiceId": "g7_goal", + "expected": "找到能用这把钥匙打开的东西。需要帮助时,在原地双击。", + "heard": "找到能用这把钥匙打开的东西,需要帮助时在原地双击。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 179.164, + "t1": 184.151, + "voiceId": "empty_2", + "expected": "你伸出手,摸到的只有冰冷粗糙的墙壁。", + "heard": "你伸出手摸到的只有冰冷粗糙的墙壁。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 185.929, + "t1": 188.671, + "voiceId": "room_power", + "expected": "这个房间的墙上,有一只电箱。", + "heard": "这个房间的墙上有一只电箱。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 192.688, + "t1": 195.051, + "voiceId": "wall", + "expected": "那边是墙,换个方向。", + "heard": "那边是墙,换个方向。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 195.058, + "t1": 196.671, + "voiceId": "wall_dir_u", + "expected": "上边是墙。", + "heard": "上边是墙。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 199.455, + "t1": 201.821, + "voiceId": "wall", + "expected": "那边是墙,换个方向。", + "heard": "那边是墙,换个方向。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 204.221, + "t1": 206.591, + "voiceId": "wall", + "expected": "那边是墙,换个方向。", + "heard": "那边是墙,换个方向。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 206.597, + "t1": 207.751, + "voiceId": "wall_dir_l", + "expected": "左边是墙。", + "heard": "左边是墙。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 210.888, + "t1": 218.481, + "voiceId": "use_fail", + "expected": "这里没有用得上东西的地方。换个房间试试,或者先单击检查一下周围。", + "heard": "这里没有用得上东西的地方,换个房间试试,或者先单机检查一下周围。", + "similarity": 0.9655, + "verdict": "OK" + }, + { + "t0": 156.591, + "t1": 179.164, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 184.151, + "t1": 185.929, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 188.671, + "t1": 192.688, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 196.671, + "t1": 199.455, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 201.821, + "t1": 204.221, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 207.751, + "t1": 210.888, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 218.49, + "t1": 222.621, + "voiceId": "stuck_cabinet_1", + "expected": "钥匙就在你身上。它能打开某一把锁。", + "heard": "钥匙就在你身上,他能打开某一把锁。", + "similarity": 0.9333, + "verdict": "OK" + }, + { + "t0": 256.245, + "t1": 261.501, + "voiceId": "empty_1", + "expected": "房间里空空荡荡,只有你自己的回声。", + "heard": "房间里空空荡荡,只有你自己的回声。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 263.01, + "t1": 266.861, + "voiceId": "empty_3", + "expected": "除了远处的水滴声,这里什么都没有。", + "heard": "除了远处的水滴声,这里什么都没有。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 269.776, + "t1": 272.521, + "voiceId": "room_cabinet", + "expected": "这个房间里,立着一个铁柜。", + "heard": "这个房间里立着一个铁柜。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 276.438, + "t1": 288.151, + "voiceId": "unlock_cabinet", + "expected": "钥匙插进锁孔,缓缓转动。咔哒一声,柜门开了。柜子深处,躺着一枚保险丝。保险丝到手了。", + "heard": "钥匙插进锁孔,缓缓转动,咔嗒一声,柜门开了,柜子深处躺着一枚保险丝,保险丝到手了。", + "similarity": 0.9714, + "verdict": "OK" + }, + { + "t0": 222.621, + "t1": 256.245, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 261.501, + "t1": 263.01, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 266.861, + "t1": 269.776, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 272.521, + "t1": 276.438, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 288.159, + "t1": 291.631, + "voiceId": "gd_next_fuse", + "expected": "保险丝到手。接下来,去找电箱。", + "heard": "保险丝到手,接下来去找电箱。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 291.638, + "t1": 294.011, + "voiceId": "gd_box_dir", + "expected": "电箱,在左面的房间。", + "heard": "电箱在左面的房间。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 430.009, + "t1": 432.751, + "voiceId": "gd_prog_2", + "expected": "铁柜已经打开。电还没有通。", + "heard": "铁柜已经打开,电还没有通。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 294.011, + "t1": 430.009, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 541.393, + "t1": 548.981, + "voiceId": "fix_box", + "expected": "你把保险丝用力按进卡座。火花一闪,嗡鸣声响起——头顶的线路恢复供电了!", + "heard": "你把保险丝用力安静卡座,火花一闪,嗡鸣声响起,头顶的线路恢复供电。", + "similarity": 0.9153, + "verdict": "OK" + }, + { + "t0": 548.988, + "t1": 551.361, + "voiceId": "gd_gate_dir", + "expected": "大门,在右面的房间。", + "heard": "大门在右面的房间。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 565.464, + "t1": 569.311, + "voiceId": "room_gate", + "expected": "面前,是一扇沉重的铁门。", + "heard": "面前是一扇沉重的铁门。", + "similarity": 1, + "verdict": "OK" + }, + { + "t0": 432.751, + "t1": 541.393, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 551.361, + "t1": 565.464, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 583.064, + "t1": 598.491, + "voiceId": "escape", + "expected": "你扳动开关。电机轰鸣,沉重的铁门缓缓升起,刺眼的光线涌了进来。你眯起眼睛,向前迈出了第一步。自由了。你逃出来了。", + "heard": "你搬动开关,电击轰,沉重的体能缓缓升起,刺眼的光线涌了进来,你眯起眼睛向前迈出了第一步,自由了,你逃出来了。", + "similarity": 0.9053, + "verdict": "OK" + }, + { + "t0": 569.311, + "t1": 583.064, + "voiceId": null, + "expected": null, + "heard": "", + "similarity": null, + "verdict": "SFX_BGM_ONLY" + }, + { + "t0": 598.499, + "t1": 601.971, + "voiceId": "time_band3", + "expected": "你逃出来了,用时不到十分钟。", + "heard": "你逃出来了,用时不到10分钟。", + "similarity": 0.88, + "verdict": "OK" + }, + { + "t0": 601.979, + "t1": 607.241, + "voiceId": "again", + "expected": "单击屏幕,再来一次。下滑,打开菜单。", + "heard": "单击屏幕再来一次下滑打开菜单。", + "similarity": 1, + "verdict": "OK" + } + ] +} \ No newline at end of file diff --git a/tools/gen_voice_dual.py b/tools/gen_voice_dual.py index b7769608..f8a2c78f 100644 --- a/tools/gen_voice_dual.py +++ b/tools/gen_voice_dual.py @@ -237,6 +237,20 @@ ESCAPE_LINES = [ ("gd_room_r", _ESC, "右面的房间。"), ("gd_room_u", _ESC, "上面的房间。"), ("gd_room_d", _ESC, "下面的房间。"), + # ---- escape-state round2(docs/design/25-escape-map-flow.md §4.1 B1/B2/B3,台词与草案逐字一致)---- + # B1(F1):撞墙方位句(陈述式非教学腔),wall 播完后接播,同向 10s 限频。 + ("wall_dir_l", _ESC, "左边是墙。"), + ("wall_dir_r", _ESC, "右边是墙。"), + ("wall_dir_u", _ESC, "上边是墙。"), + ("wall_dir_d", _ESC, "下边是墙。"), + # B2(F2):目标名+方位一体句,替换三处 gd_room_* 接播(原句保留作异常路径兜底)。 + ("gd_cabinet_dir", _ESC, "铁柜,在右面的房间。"), + ("gd_box_dir", _ESC, "电箱,在左面的房间。"), + ("gd_gate_dir", _ESC, "大门,在右面的房间。"), + # B3(F3):无进展 150s 活跃计时器的进度重述(stage=cabinet/box/gate,key 阶段不走本通道)。 + ("gd_prog_1", _ESC, "钥匙已经到手。找到能用它的锁。"), + ("gd_prog_2", _ESC, "铁柜已经打开。电还没有通。"), + ("gd_prog_3", _ESC, "电已经通了。去找那扇大门。"), ] diff --git a/web/escape/audio/voice/female/gd_box_dir.m4a b/web/escape/audio/voice/female/gd_box_dir.m4a new file mode 100644 index 00000000..559a1d7e Binary files /dev/null and b/web/escape/audio/voice/female/gd_box_dir.m4a differ diff --git a/web/escape/audio/voice/female/gd_box_dir.ogg b/web/escape/audio/voice/female/gd_box_dir.ogg new file mode 100644 index 00000000..7579c597 Binary files /dev/null and b/web/escape/audio/voice/female/gd_box_dir.ogg differ diff --git a/web/escape/audio/voice/female/gd_cabinet_dir.m4a b/web/escape/audio/voice/female/gd_cabinet_dir.m4a new file mode 100644 index 00000000..6365ee1c Binary files /dev/null and b/web/escape/audio/voice/female/gd_cabinet_dir.m4a differ diff --git a/web/escape/audio/voice/female/gd_cabinet_dir.ogg b/web/escape/audio/voice/female/gd_cabinet_dir.ogg new file mode 100644 index 00000000..f68da7bd Binary files /dev/null and b/web/escape/audio/voice/female/gd_cabinet_dir.ogg differ diff --git a/web/escape/audio/voice/female/gd_gate_dir.m4a b/web/escape/audio/voice/female/gd_gate_dir.m4a new file mode 100644 index 00000000..82f3caff Binary files /dev/null and b/web/escape/audio/voice/female/gd_gate_dir.m4a differ diff --git a/web/escape/audio/voice/female/gd_gate_dir.ogg b/web/escape/audio/voice/female/gd_gate_dir.ogg new file mode 100644 index 00000000..8b094d6c Binary files /dev/null and b/web/escape/audio/voice/female/gd_gate_dir.ogg differ diff --git a/web/escape/audio/voice/female/gd_prog_1.m4a b/web/escape/audio/voice/female/gd_prog_1.m4a new file mode 100644 index 00000000..beed374a Binary files /dev/null and b/web/escape/audio/voice/female/gd_prog_1.m4a differ diff --git a/web/escape/audio/voice/female/gd_prog_1.ogg b/web/escape/audio/voice/female/gd_prog_1.ogg new file mode 100644 index 00000000..411fa107 Binary files /dev/null and b/web/escape/audio/voice/female/gd_prog_1.ogg differ diff --git a/web/escape/audio/voice/female/gd_prog_2.m4a b/web/escape/audio/voice/female/gd_prog_2.m4a new file mode 100644 index 00000000..c2c49cb1 Binary files /dev/null and b/web/escape/audio/voice/female/gd_prog_2.m4a differ diff --git a/web/escape/audio/voice/female/gd_prog_2.ogg b/web/escape/audio/voice/female/gd_prog_2.ogg new file mode 100644 index 00000000..33c29fff Binary files /dev/null and b/web/escape/audio/voice/female/gd_prog_2.ogg differ diff --git a/web/escape/audio/voice/female/gd_prog_3.m4a b/web/escape/audio/voice/female/gd_prog_3.m4a new file mode 100644 index 00000000..61acb6e3 Binary files /dev/null and b/web/escape/audio/voice/female/gd_prog_3.m4a differ diff --git a/web/escape/audio/voice/female/gd_prog_3.ogg b/web/escape/audio/voice/female/gd_prog_3.ogg new file mode 100644 index 00000000..27535f86 Binary files /dev/null and b/web/escape/audio/voice/female/gd_prog_3.ogg differ diff --git a/web/escape/audio/voice/female/wall_dir_d.m4a b/web/escape/audio/voice/female/wall_dir_d.m4a new file mode 100644 index 00000000..b6196f09 Binary files /dev/null and b/web/escape/audio/voice/female/wall_dir_d.m4a differ diff --git a/web/escape/audio/voice/female/wall_dir_d.ogg b/web/escape/audio/voice/female/wall_dir_d.ogg new file mode 100644 index 00000000..9013daf8 Binary files /dev/null and b/web/escape/audio/voice/female/wall_dir_d.ogg differ diff --git a/web/escape/audio/voice/female/wall_dir_l.m4a b/web/escape/audio/voice/female/wall_dir_l.m4a new file mode 100644 index 00000000..29be1d18 Binary files /dev/null and b/web/escape/audio/voice/female/wall_dir_l.m4a differ diff --git a/web/escape/audio/voice/female/wall_dir_l.ogg b/web/escape/audio/voice/female/wall_dir_l.ogg new file mode 100644 index 00000000..a8147763 Binary files /dev/null and b/web/escape/audio/voice/female/wall_dir_l.ogg differ diff --git a/web/escape/audio/voice/female/wall_dir_r.m4a b/web/escape/audio/voice/female/wall_dir_r.m4a new file mode 100644 index 00000000..141e305f Binary files /dev/null and b/web/escape/audio/voice/female/wall_dir_r.m4a differ diff --git a/web/escape/audio/voice/female/wall_dir_r.ogg b/web/escape/audio/voice/female/wall_dir_r.ogg new file mode 100644 index 00000000..121386e5 Binary files /dev/null and b/web/escape/audio/voice/female/wall_dir_r.ogg differ diff --git a/web/escape/audio/voice/female/wall_dir_u.m4a b/web/escape/audio/voice/female/wall_dir_u.m4a new file mode 100644 index 00000000..19669261 Binary files /dev/null and b/web/escape/audio/voice/female/wall_dir_u.m4a differ diff --git a/web/escape/audio/voice/female/wall_dir_u.ogg b/web/escape/audio/voice/female/wall_dir_u.ogg new file mode 100644 index 00000000..7fe8bbc1 Binary files /dev/null and b/web/escape/audio/voice/female/wall_dir_u.ogg differ diff --git a/web/escape/audio/voice/male/gd_box_dir.m4a b/web/escape/audio/voice/male/gd_box_dir.m4a new file mode 100644 index 00000000..cef690fd Binary files /dev/null and b/web/escape/audio/voice/male/gd_box_dir.m4a differ diff --git a/web/escape/audio/voice/male/gd_box_dir.ogg b/web/escape/audio/voice/male/gd_box_dir.ogg new file mode 100644 index 00000000..f23584be Binary files /dev/null and b/web/escape/audio/voice/male/gd_box_dir.ogg differ diff --git a/web/escape/audio/voice/male/gd_cabinet_dir.m4a b/web/escape/audio/voice/male/gd_cabinet_dir.m4a new file mode 100644 index 00000000..1e8801d5 Binary files /dev/null and b/web/escape/audio/voice/male/gd_cabinet_dir.m4a differ diff --git a/web/escape/audio/voice/male/gd_cabinet_dir.ogg b/web/escape/audio/voice/male/gd_cabinet_dir.ogg new file mode 100644 index 00000000..e9777bae Binary files /dev/null and b/web/escape/audio/voice/male/gd_cabinet_dir.ogg differ diff --git a/web/escape/audio/voice/male/gd_gate_dir.m4a b/web/escape/audio/voice/male/gd_gate_dir.m4a new file mode 100644 index 00000000..7be844f8 Binary files /dev/null and b/web/escape/audio/voice/male/gd_gate_dir.m4a differ diff --git a/web/escape/audio/voice/male/gd_gate_dir.ogg b/web/escape/audio/voice/male/gd_gate_dir.ogg new file mode 100644 index 00000000..e44b650d Binary files /dev/null and b/web/escape/audio/voice/male/gd_gate_dir.ogg differ diff --git a/web/escape/audio/voice/male/gd_prog_1.m4a b/web/escape/audio/voice/male/gd_prog_1.m4a new file mode 100644 index 00000000..432a595a Binary files /dev/null and b/web/escape/audio/voice/male/gd_prog_1.m4a differ diff --git a/web/escape/audio/voice/male/gd_prog_1.ogg b/web/escape/audio/voice/male/gd_prog_1.ogg new file mode 100644 index 00000000..8250439b Binary files /dev/null and b/web/escape/audio/voice/male/gd_prog_1.ogg differ diff --git a/web/escape/audio/voice/male/gd_prog_2.m4a b/web/escape/audio/voice/male/gd_prog_2.m4a new file mode 100644 index 00000000..278ff11d Binary files /dev/null and b/web/escape/audio/voice/male/gd_prog_2.m4a differ diff --git a/web/escape/audio/voice/male/gd_prog_2.ogg b/web/escape/audio/voice/male/gd_prog_2.ogg new file mode 100644 index 00000000..691ea1f1 Binary files /dev/null and b/web/escape/audio/voice/male/gd_prog_2.ogg differ diff --git a/web/escape/audio/voice/male/gd_prog_3.m4a b/web/escape/audio/voice/male/gd_prog_3.m4a new file mode 100644 index 00000000..ad04365f Binary files /dev/null and b/web/escape/audio/voice/male/gd_prog_3.m4a differ diff --git a/web/escape/audio/voice/male/gd_prog_3.ogg b/web/escape/audio/voice/male/gd_prog_3.ogg new file mode 100644 index 00000000..0d7c7bc3 Binary files /dev/null and b/web/escape/audio/voice/male/gd_prog_3.ogg differ diff --git a/web/escape/audio/voice/male/wall_dir_d.m4a b/web/escape/audio/voice/male/wall_dir_d.m4a new file mode 100644 index 00000000..75ff2e34 Binary files /dev/null and b/web/escape/audio/voice/male/wall_dir_d.m4a differ diff --git a/web/escape/audio/voice/male/wall_dir_d.ogg b/web/escape/audio/voice/male/wall_dir_d.ogg new file mode 100644 index 00000000..ee7f878a Binary files /dev/null and b/web/escape/audio/voice/male/wall_dir_d.ogg differ diff --git a/web/escape/audio/voice/male/wall_dir_l.m4a b/web/escape/audio/voice/male/wall_dir_l.m4a new file mode 100644 index 00000000..2c5f6f83 Binary files /dev/null and b/web/escape/audio/voice/male/wall_dir_l.m4a differ diff --git a/web/escape/audio/voice/male/wall_dir_l.ogg b/web/escape/audio/voice/male/wall_dir_l.ogg new file mode 100644 index 00000000..1def6409 Binary files /dev/null and b/web/escape/audio/voice/male/wall_dir_l.ogg differ diff --git a/web/escape/audio/voice/male/wall_dir_r.m4a b/web/escape/audio/voice/male/wall_dir_r.m4a new file mode 100644 index 00000000..1cbe11de Binary files /dev/null and b/web/escape/audio/voice/male/wall_dir_r.m4a differ diff --git a/web/escape/audio/voice/male/wall_dir_r.ogg b/web/escape/audio/voice/male/wall_dir_r.ogg new file mode 100644 index 00000000..03f0995b Binary files /dev/null and b/web/escape/audio/voice/male/wall_dir_r.ogg differ diff --git a/web/escape/audio/voice/male/wall_dir_u.m4a b/web/escape/audio/voice/male/wall_dir_u.m4a new file mode 100644 index 00000000..7aea8e30 Binary files /dev/null and b/web/escape/audio/voice/male/wall_dir_u.m4a differ diff --git a/web/escape/audio/voice/male/wall_dir_u.ogg b/web/escape/audio/voice/male/wall_dir_u.ogg new file mode 100644 index 00000000..dc5815a7 Binary files /dev/null and b/web/escape/audio/voice/male/wall_dir_u.ogg differ diff --git a/web/escape/game.js b/web/escape/game.js index 405905f6..e6a8747f 100644 --- a/web/escape/game.js +++ b/web/escape/game.js @@ -8,14 +8,17 @@ * * 手势映射(偏离全局默认,引导中讲清): * - 四向滑动:在相邻房间间移动,撞墙播闷响 + ERROR 震动 + wall 语音(每次都播; - * PLAY 阶段只播「那边是墙」,教学腔方位句 guide_blunt_* 仅限引导期) + * PLAY 阶段 wall 播完接播陈述式方位句 wall_dir_「左边是墙。」(escape-state + * round2 B1,同向 10s 限频,被打断自然作废);教学腔 guide_blunt_* 仅限引导期) * - 单击:重播当前房间环境音 + 检查描述(储物间拾钥匙); * voice 播放中单击 = 跳过本句(audio.skipVoice(),链播继续,12 号文档) * 并补发检查——环境音即时重播、描述进待播队列等链播播完(不顶句)。 * 不可跳:引导 G0-G7(引导期单击是答题)、关键获得(found_key/ * unlock_cabinet/fix_box/escape 结算链)、L3 带路(stuck_*_3 + nav_*) * - 双击:使用物品(钥匙开柜 / 保险丝修电箱 / 通电后开大门); - * 无可用目标时播报背包 + L2 级明示提示 + * 无可用目标时播报背包/use_fail + L1 级意象暗示(escape-state round2 B4: + * 原直通 L2 明示把答案白送给没卡关的玩家,降级为 L1;答案仍锁在计时器 + * L2/L3 与进展链里) * - 引导阶段下滑:跳过引导(仅限答案不含滑动的步骤 G0/G3/G5/G6;G1/G2 任意 * 滑动即答案、G4 的答案就是下滑——这些步骤下滑 = 作答,不可跳过); * ENDING 下滑:菜单 @@ -31,12 +34,23 @@ * 修电箱/首到关键房间/逃脱)清空已播级别,下段静默期从 L1 重新分级。 * 提示到点只进待播队列:等当前语音自然播完再播、绝不顶掉进行中的语音; * 待播期间玩家任何操作即作废该提示(未播级别不计入已播)。 + * escape-state round2 新增平行通道(B3):无进展 150s 且期间 ≥5 次有效手势 + * (活跃但零进展,挂机仍走 60s L1 体系)→ 播不泄题的进度重述 gd_prog_1/2/3 + * (stage=cabinet/box/gate,key 阶段不走本通道),每进展段限 1 次, + * 队列语义与卡关提示同款(不顶句、待播期间有操作即作废)。 * * round4 H1(docs/design/14-guide-layer.md「round4 v1.3」):阶段目标句 * (found_key / gd_next_fuse / fix_box)播完后,按房间图计算目标房间相对当前 - * 房间的真实方位,接播 gd_room_l/r/u/d 之一(游戏反馈层全档位;目标在同一 - * 房间不播;方向 = escape 移动坐标系,up/down/left/right 滑动方向即方位)。 - * 引导期 g7_goal 不接(引导本身逐句带路,且引导链被 playtest 断言冻结)。 + * 房间的真实方位接播方位句;escape-state round2 B2 把三处固定接线点升级为 + * "目标名+方位"一体句(gd_cabinet_dir/gd_box_dir/gd_gate_dir),异常路径方位 + * 与枚举不符时回落 gd_room_l/r/u/d(原句保留)。引导期 g7_goal 不接(引导链 + * 被 playtest 断言冻结);但跳过引导/REOPEN 直进路径补播 g7_goal(round2 B8: + * 任何路径进 PLAY 都至少听过一次目标句)。 + * + * escape-state round2 逻辑修复:B6 引导 busy 窗口缓存首个非跳过分手势、步骤 + * 解锁后按新步骤补判(合法=作答/不合法=答错路径,缓存手势不触发下滑跳过); + * B7 结算链播完前 ENDING 单击不重开(_settleBusy 守卫,链完/30s 兜底解除); + * B5a 空房首访补播 empty_* 一次性听觉锚(不算进展事件)。 * * 语音 id 权威清单:docs/audio/round2-voice-ids.md(已删除的 n0–n9 数字拼接链、 * hint_ 四级提示、intro/tutorial 旧开场不再引用)。震动分工:滑动/点按的识别反馈由 gesture.js 内置,本层只发语义震动 @@ -80,6 +94,16 @@ const KEY_ROOMS = { // 空房环境音轮换(弱化版风声/嗡鸣) const EMPTY_AMB = { '1,0': 'sfx/amb_wind', '0,1': 'sfx/amb_hum', '1,2': 'sfx/amb_wind', '2,0': 'sfx/amb_hum' }; +// 空房首访描述(escape-state round2 B5a:复用现有 empty_*,进入时一次性播; +// 与 _inspect 单击检查映射一致——两间"远处水滴"空房 (1,2)/(2,0) 对称分布, +// 共用 empty_3 也是合法的记忆点) +const EMPTY_FIRST_VOICE = { + '1,0': 'voice/empty_1', '0,1': 'voice/empty_2', '1,2': 'voice/empty_3', '2,0': 'voice/empty_3', +}; + +// B3 无进展进度重述:stage → gd_prog id(key 阶段不走本通道) +const GD_PROG = { cabinet: 'voice/gd_prog_1', box: 'voice/gd_prog_2', gate: 'voice/gd_prog_3' }; + const PREWARM_SFX = [ 'sfx/step', 'sfx/wall', 'sfx/pickup', 'sfx/unlock', 'sfx/door_creak', 'sfx/spark', 'sfx/power_on', 'sfx/gate_open', 'sfx/win_chime', @@ -110,6 +134,10 @@ export class EscapeGame extends Game { this.cabinetOpen = false; this.powerOn = false; this.visited = new Set(); // 首到关键房间记录(进展事件 + room_* 描述) + this.emptyVisited = new Set(); // 首到空房记录(B5a 首访描述,不算进展事件) + this._freshEmpty = null; // 首访句刚播过的空房 key(本房间内单击不重复同一句) + this._wallDirAt = {}; // B1 撞墙方位句同向 10s 限频:dir -> 上次播出时刻 + this._settleBusy = false; // B7 结算链守卫:播完前 ENDING 单击不重开 this.startElapsedMs = 0; // 单击跳过:当前 voice 句是否可跳(每次 voice 播放时更新,见 _playVoice) this._voiceSkippable = false; @@ -118,10 +146,15 @@ export class EscapeGame extends Game { this.guideSwipes = 0; // G2 撞墙计数 this.guideErrs = 0; // 当前步骤连续错误数 this._guideBusy = false; // 步骤推进语音播放中(忽略非跳过分手势) + this._guidePending = null; // B6:busy 窗口缓存的首个非跳过分手势(仅 1 个、不跨步存活) // 卡关检测(按"无操作时长"计时,见 _checkStuck) this._lastActiveAt = Date.now(); this._stuckShown = new Set(); // 当前进展段内已自动播过的级别 'L1'|'L2'|'L3' this._lastL3At = 0; + // B3 无进展计时(与无操作计时并存互不复位) + this._lastProgressAt = Date.now(); // 上一次进展事件时刻 + this._gesturesSinceProgress = 0; // 进展段内有效手势数(≥5 才触发 gd_prog) + this._progShown = new Set(); // 已播过 gd_prog 的阶段(每进展段限 1 次) this._pendingVoice = []; // 待播语音队列:排队等当前语音播完,绝不顶句 this._playSeq = 0; // voice 播放请求序号(onComplete 内判断是否链播新句) // 菜单 @@ -160,12 +193,14 @@ export class EscapeGame extends Game { if (this.phase === Phase.PLAY) { // 卡关提示按"无操作时长"计时:任何有效手势重置计时,并作废待播提示 this._lastActiveAt = Date.now(); + this._gesturesSinceProgress++; // B3:活跃手势计数(无进展 150s 通道的闸门) this._dropPendingStuck(); } switch (this.phase) { case Phase.REOPEN: if (g === G.UP) { this.audio.stopVoice(); this._startGuide(); } - else if (g === G.TAP) { this.audio.stopVoice(); this._enterPlay(true); } + // B8:REOPEN 单击直进路径在 start_room 后接播目标句 g7_goal + else if (g === G.TAP) { this.audio.stopVoice(); this._enterPlay(true, true); } break; case Phase.GUIDE: this._onGuideGesture(g); @@ -174,7 +209,9 @@ export class EscapeGame extends Game { this._onPlayGesture(g); break; case Phase.ENDING: - if (g === G.TAP) this._restart(); + // B7(escape-state round2):结算链播完前单击不立即重开——time_band/again + // 的完整到达权(live round1 S7:习惯性单击把结算链截断,两句被永久吞掉) + if (g === G.TAP) { if (!this._settleBusy) this._restart(); } else if (g === G.DOWN) this._openMenu(Phase.ENDING); break; case Phase.MENU: @@ -227,6 +264,13 @@ export class EscapeGame extends Game { this._guideBusy = false; this.guide.turn({}); // turn cue:引导步骤解锁输入(W2 播完即静默,向导补标记) if (onDone) onDone(); + // B6(escape-state round2):busy 窗口缓存的手势在步骤解锁后按新步骤补判 + // (onDone 若已离开 GUIDE——G6 链尾进 PLAY——缓存直接作废,不跨阶段存活) + if (this._guidePending !== null) { + const p = this._guidePending; + this._guidePending = null; + if (this.phase === Phase.GUIDE) this._onGuideGesture(p, { fromCache: true }); + } }; this._timer(() => { if (this._guideBusy && this.guideStep === fromStep) done(); @@ -236,16 +280,24 @@ export class EscapeGame extends Game { else done(); } - _onGuideGesture(g) { + _onGuideGesture(g, { fromCache = false } = {}) { // 下滑跳过仅限"答案不含滑动"的步骤(G0/G3/G5/G6):G1/G2 的正确答案是 // 任意滑动、G4 的答案就是下滑——这些步骤下滑 = 作答,不可跳过(13 号文档 // 裁定;round2-live:g1_move 明说"朝任意方向滑一下",玩家下滑却被保送 - // 自由探索,教学链就此断裂) - if (g === G.DOWN && this.guideStep !== 1 && this.guideStep !== 2 && this.guideStep !== 4) { + // 自由探索,教学链就此断裂)。B6:busy 缓存补判的手势不再触发下滑跳过 + // (live round1 S6:答案在 busy 窗口被吞后,玩家紧接着的下滑被当成跳过引导) + if (!fromCache && g === G.DOWN && this.guideStep !== 1 && this.guideStep !== 2 && this.guideStep !== 4) { this._skipGuide(); return; } - if (this._guideBusy) return; // 推进语音播放中,忽略 + if (this._guideBusy) { + // B6:busy 窗口缓存首个非跳过分手势,推进语音播完(_advance done)后按 + // 新步骤补判——合法则直接作答,不合法走答错路径(有纠正语音,可归因)。 + // 仅缓存 1 个、只存活到本步骤解锁,杜绝跨步幽灵输入(00 §5.4:做了手势 + // 3 秒内必须有反馈——原 busy 静默吞手势违反此条,本缓存是合规修复) + if (!fromCache && this._guidePending === null) this._guidePending = g; + return; + } switch (this.guideStep) { case 0: // G0:单击继续(intro 播完 1.2s 未操作也会自动推进 G1,见 _startGuide) if (g === G.TAP) this._advance(1, ['voice/g1_move', 'voice/skip_hint']); // skip_hint 首局插播一次 @@ -316,10 +368,17 @@ export class EscapeGame extends Game { this._session++; // 作废进行中的引导语音链 this._clearTimers(); this.audio.stopVoice(); + this._guidePending = null; try { localStorage.setItem(GUIDE_DONE_KEY, '1'); } catch (_) { /* 隐私模式 */ } - this._speak(['voice/guide_skip'], () => this._enterPlay(true), { skippable: false }); - // 语音缺失兜底 - this._timer(() => { if (this.phase === Phase.GUIDE) this._enterPlay(true); }, 5000); + this.guideStep = 7; // 跳过链期间视作 G7 等待:滑动手势全部忽略(防链中误触误答/二次跳过) + // B8(escape-state round2):跳过确认句后、进 PLAY 前补播目标句 g7_goal—— + // 目标陈述原只在引导 G7 链尾出现一次,跳过的玩家(恰是最需要目标的一群) + // 永远听不到(live round1 S4:跳过后目标维度完全空白) + this._speak(['voice/guide_skip'], () => { + this._playVoice('voice/g7_goal', { onComplete: () => this._enterPlay(true) }); + }, { skippable: false }); + // 语音缺失兜底(两句链:覆盖 guide_skip/g7_goal 缺失情形) + this._timer(() => { if (this.phase === Phase.GUIDE) this._enterPlay(true); }, 9000); } /** @@ -341,14 +400,26 @@ export class EscapeGame extends Game { // ---------- 进入 / 重开 PLAY ---------- - /** @param {boolean} announce 是否播 start_room(跳过引导/二周目直接进入时) */ - _enterPlay(announce) { + /** + * @param {boolean} announce 是否播 start_room(跳过引导/二周目直接进入时) + * @param {boolean} withGoal B8:start_room 后接播目标句 g7_goal(REOPEN 直进 + * 路径;跳过引导路径的 g7_goal 已在 _enterPlay 前播过,此处不重复) + */ + _enterPlay(announce, withGoal = false) { this._session++; this._clearTimers(); this.phase = Phase.PLAY; this.startElapsedMs = Date.now(); this._resetStuck(); - if (announce) this._playVoice('voice/start_room'); + if (announce) { + if (withGoal) { + // B8:start_room(可跳)自然播完/被跳过后接播 g7_goal(可跳, + // 二周目老手可单击跳过——教学义务句已随首局完成,此处是状态告知) + this._playVoice('voice/start_room', { onComplete: () => this._playVoice('voice/g7_goal') }); + } else { + this._playVoice('voice/start_room'); + } + } this._playAmbient(); } @@ -397,22 +468,37 @@ export class EscapeGame extends Game { if (nx < 0 || nx > 2 || ny < 0 || ny > 2) { this.audio.play('sfx/wall'); vibrate('error'); - // 撞墙极简反馈:只播「那边是墙,换个方向。」(round1-live 修复:原接播 - // guide_blunt_「手指贴着屏幕,向 X 推一下」教学腔方位句,中后期出戏; - // 教学腔仅限引导期 G2/G3 纠错路径,见 _onGuideGesture/_guideWrong) - this._playVoice('voice/wall'); + // 撞墙反馈:先播「那边是墙,换个方向。」,播完接播陈述式方位句 + // wall_dir_「左边是墙。」(escape-state round2 B1:round1-live 回退掉的 + // 是教学腔措辞,方位信息本身保留;复用 14-F2.3 原接线模式——被下一次语音 + // 顶掉则自然作废不迟到误导;同方向 10s 限频 1 次防贴墙连撞连播)。 + // 方位 = 滑动方向(escape 移动坐标系),按真实撞墙方向取句,归因真话。 + const dir = dy < 0 ? 'u' : dy > 0 ? 'd' : dx < 0 ? 'l' : 'r'; + const allowDir = !this._wallDirAt[dir] || Date.now() - this._wallDirAt[dir] >= 10000; + this._playVoice('voice/wall', { + onComplete: allowDir + ? () => { this._wallDirAt[dir] = Date.now(); this._playVoice(`voice/wall_dir_${dir}`); } + : undefined, + }); return; } this.x = nx; this.y = ny; this.audio.play('sfx/step', { volume: 0.8 }); this._playAmbient(); + this._freshEmpty = null; // 离开首访句刚播过的空房:单击检查恢复播 empty_* // 首次到达关键房间:进展事件 + 房间描述 const key = `${nx},${ny}`; if (KEY_ROOMS[key] && !this.visited.has(key)) { this.visited.add(key); this._progressEvent(); this._playVoice(KEY_ROOMS[key].room); + } else if (EMPTY_FIRST_VOICE[key] && !this.emptyVisited.has(key)) { + // B5a(escape-state round2):空房首访补播 empty_* 一次性听觉锚 + // (不算进展事件,不清卡关已播级别;重访零语音与关键房间同款) + this.emptyVisited.add(key); + this._freshEmpty = key; + this._playVoice(EMPTY_FIRST_VOICE[key]); } } @@ -454,10 +540,12 @@ export class EscapeGame extends Game { this._progressEvent(); this.audio.play('sfx/pickup'); vibrate('pickup'); - // H1(round4):钥匙到手句后接播铁柜房方位(下一目标 = cabinet (2,1))。 + // H1(round4)/ B2(escape-state round2):钥匙到手句后接播铁柜房 + // "目标名+方位"一体句 gd_cabinet_dir(下一目标 = cabinet (2,1),固定为右)。 this._timer(() => { if (s !== this._session) return; - emit('voice/found_key', false, () => this._playGoalDir(STAGES.cabinet.target)); + emit('voice/found_key', false, + () => this._playGoalDir(STAGES.cabinet.target, 'voice/gd_cabinet_dir', 'voice/gd_room_r')); }, 800); // 先让环境音进耳朵再播描述 } else { describe('voice/empty_2'); @@ -470,12 +558,11 @@ export class EscapeGame extends Game { describe(this.powerOn ? 'voice/inspect_gate_ready' : 'voice/inspect_gate'); } else if (x === 1 && y === 1) { describe('voice/start_room'); - } else if (x === 1 && y === 0) { - describe('voice/empty_1'); - } else if (x === 0 && y === 1) { - describe('voice/empty_2'); } else { - describe('voice/empty_3'); // (1,2)、(2,0) + // 空房 (1,0)/(0,1)/(1,2)/(2,0):B5a 首访句刚播过的房间内,单击不再重复 + // 同一句(_freshEmpty 离开即清;重访后单击检查语义不变,照常播 empty_*) + const key = `${x},${y}`; + if (this._freshEmpty !== key) describe(EMPTY_FIRST_VOICE[key] || 'voice/empty_3'); } } @@ -493,11 +580,12 @@ export class EscapeGame extends Game { // G4(round3):拾取反馈链尾接下一目标句「保险丝到手。接下来,去找电箱。」 // (游戏反馈层全档位,gd_economy 先例——走游戏语音链不走向导通道)——堵上 // P2-round2 P0「保险丝到手后目标悬空:没有新指令、没有下文」。 - // H1(round4):目标句播完接播目标房间方位句(P3 唯一拒因:目标无方位)。 + // H1(round4)/ B2(escape-state round2):目标句播完接播"目标名+方位"一体句 + // gd_box_dir(电箱房 (0,0) 固定为左)。 this._playVoice('voice/unlock_cabinet', { skippable: false, // 关键获得不可跳 onComplete: () => this._playVoice('voice/gd_next_fuse', { - onComplete: () => this._playGoalDir(STAGES.box.target), + onComplete: () => this._playGoalDir(STAGES.box.target, 'voice/gd_box_dir', 'voice/gd_room_l'), }), }); } else if (x === 0 && y === 0 && this.hasFuse && !this.powerOn) { @@ -508,22 +596,28 @@ export class EscapeGame extends Game { this.audio.play('sfx/spark'); this.audio.play('sfx/power_on', { volume: 0.8 }); vibrate('success'); - // H1(round4):通电目标句后接播大门方位(下一目标 = gate (2,2))。 + // H1(round4)/ B2(escape-state round2):通电目标句后接播"目标名+方位" + // 一体句 gd_gate_dir(大门 (2,2) 固定为右)。 this._playVoice('voice/fix_box', { skippable: false, // 关键获得不可跳 - onComplete: () => this._playGoalDir(STAGES.gate.target), + onComplete: () => this._playGoalDir(STAGES.gate.target, 'voice/gd_gate_dir', 'voice/gd_room_r'), }); } else if (x === 2 && y === 2 && this.powerOn) { // 通电后开大门 -> 逃脱 this._escape(); } else { - // 兜底:ERROR 震动 + 背包/use_fail + L2 级明示(ui_deny 已退役) + // 兜底:ERROR 震动 + 背包/use_fail + L1 级意象暗示(escape-state round2 B4: + // 原直通 L2 明示把答案白送给没卡关的玩家——live round1 两次实录;降级为 L1 + // 意象句(不点明房间与动作,零泄漏),答案仍锁在计时器 L2/L3 与进展链里。 + // 本进展段已播过 L1 则不重复;播了即记账(沿用 _stuckShown 既有去重)) vibrate('error'); const inKeyRoom = `${x},${y}` in KEY_ROOMS; - this._speak([ - inKeyRoom ? 'voice/use_fail' : this._inventoryVoice(), - `voice/stuck_${this._stage()}_2`, - ]); + const ids = [inKeyRoom ? 'voice/use_fail' : this._inventoryVoice()]; + if (!this._stuckShown.has('L1')) { + this._stuckShown.add('L1'); + ids.push(`voice/stuck_${this._stage()}_1`); + } + this._speak(ids); } } @@ -556,10 +650,16 @@ export class EscapeGame extends Game { return dy < 0 ? 'voice/gd_room_u' : 'voice/gd_room_d'; } - /** H1:目标句播完后接播目标房间方位句(游戏反馈层全档位,gd_next_fuse 先例)。 */ - _playGoalDir(target) { + /** + * H1(round4)/ B2(escape-state round2):目标句播完后接播目标房间方位句 + * (游戏反馈层全档位,gd_next_fuse 先例)。B2:三处固定接线点传 namedId + + * expectedId,计算方位与枚举一致时播"目标名+方位"一体句;异常路径不符时 + * 回落原 gd_room_*(归因真话,原句保留不删)。 + */ + _playGoalDir(target, namedId = null, expectedId = null) { const dir = this._roomDirVoice(target); - if (dir) this._playVoice(dir); + if (!dir) return; + this._playVoice(namedId && dir === expectedId ? namedId : dir); } // ---------- 卡关检测与三级提示 ---------- @@ -573,12 +673,19 @@ export class EscapeGame extends Game { this._lastActiveAt = Date.now(); this._stuckShown = new Set(); this._lastL3At = 0; + // B3 无进展计时:进入/重开 PLAY 时起算点与闸门一并复位 + this._lastProgressAt = Date.now(); + this._gesturesSinceProgress = 0; + this._progShown = new Set(); } /** 进展事件:清空已播提示级别,下段静默期从 L1 重新分级(计时由手势重置)。 */ _progressEvent() { this._stuckShown.clear(); this._lastL3At = 0; + // B3:无进展计时起算点与活跃手势计数复位(_progShown 按阶段记账,不清) + this._lastProgressAt = Date.now(); + this._gesturesSinceProgress = 0; } /** @@ -609,6 +716,18 @@ export class EscapeGame extends Game { vibrate('warning'); this._enqueueVoice('stuck', ['voice/stuck_ask', `voice/stuck_${stage}_1`], { level: 'L1' }); } + // B3(escape-state round2):无进展 150s 活跃计时器(与无操作计时并存互不复位) + // ——活跃但零进展的玩家收到一条不泄题的进度重述 gd_prog_*(完成数由 _stage() + // 反推,归因真话)。闸门:期间 ≥5 次有效手势(=活跃探索;挂机交给 60s L1 + // 体系);key 阶段不走本通道(L1/L2/L3 已覆盖"找不到钥匙",避免双计时器 + // 对同一阶段轰炸);每进展段限 1 次(触发即记账,待播被操作作废也不重发); + // 队列语义与卡关提示同款(绝不顶句,待播期间有操作即作废),warning 震同款。 + if (stage !== 'key' && !this._progShown.has(stage) + && Date.now() - this._lastProgressAt >= 150000 && this._gesturesSinceProgress >= 5) { + this._progShown.add(stage); + vibrate('warning'); + this._enqueueVoice('stuck', [GD_PROG[stage]], { level: 'PROG' }); + } } /** 从当前坐标到目标房间的导航语音(每轴一句,最多两句)。 */ @@ -677,6 +796,9 @@ export class EscapeGame extends Game { this.audio.stopBgm(2500); this.guide.state('settle'); // 状态签名:结算(逃脱) this._endingBgmStarted = false; + // B7(escape-state round2):结算链守卫——播完前 ENDING 单击不立即重开 + // (_startEndingBgm 处解除:链自然播完或 30s 资产缺失兜底,两条路径汇合) + this._settleBusy = true; const totalSec = Math.floor((Date.now() - this.startElapsedMs) / 1000); this._saveEscape(totalSec); // 结算语音链(关键获得,不可跳);播完淡入 menu pad(10 号文档:volume 0.5,1500ms) @@ -696,6 +818,7 @@ export class EscapeGame extends Game { /** 逃脱结算 BGM:drone 淡出后淡入 bgm/menu(幂等,仅在仍是结算态时生效)。 */ _startEndingBgm() { if (this._endingBgmStarted || this.phase !== Phase.ENDING) return; + this._settleBusy = false; // B7:结算链播完(或 30s 兜底)后解除单击守卫 this._endingBgmStarted = true; this.audio.play('bgm/menu', { volume: 0.5, fadeMs: 1500 }); } diff --git a/web/escape/test/playtest.mjs b/web/escape/test/playtest.mjs index 66019252..5dd59475 100644 --- a/web/escape/test/playtest.mjs +++ b/web/escape/test/playtest.mjs @@ -418,13 +418,16 @@ section('A. 关键路径:引导 → 解谜 → 逃脱 → 结算 → 菜单 assert(rec.count('voice/wall') >= 1, '撞墙播 wall 语音'); await finishVoices(1); - // ---- 边界:双击无目标兜底 = 背包/use_fail + L2 明示,不崩 ---- + // ---- 边界:双击无目标兜底 = 背包/use_fail + L1 意象暗示,不崩(escape-state + // round2 B4 变更登记:原直通 L2 明示 stuck_*_2 把答案白送给没卡关的玩家, + // 降级为 L1 意象句 stuck_*_1——不点明房间与动作;答案仍锁在计时器 L2/L3) ---- const errCnt2 = vibrateCount(ERR_PAT); await gs(game, 'doubletap'); // 电箱房无保险丝 assert(vibrateCount(ERR_PAT) === errCnt2 + 1, '双击无目标发 error 震动'); assert(rec.count('voice/use_fail') === 1, '兜底播 use_fail(关键房间)'); await finishVoices(1); - assert(rec.count('voice/stuck_key_2') === 1, '兜底追加 L2 明示 stuck_key_2'); + assert(rec.count('voice/stuck_key_1') === 1, 'B4:兜底追加 L1 意象暗示 stuck_key_1(原 L2 已降级)'); + assert(rec.count('voice/stuck_key_2') === 0, 'B4:兜底不再直通 L2 明示 stuck_key_2'); await finishVoices(1); game.stop(); @@ -501,7 +504,16 @@ section('A2. 冷启动引导链:intro 后 g1_move 自动播出,零单击走 await gs(g2b.game, 'down'); // G3 答案 = 左滑,下滑 = 跳过 assert(g2b.rec.count('voice/guide_skip') === 1, 'G3 下滑仍跳过引导'); await tick(VOICE_END_SLACK); - assert(g2b.game.phase === 'PLAY', 'G3 下滑跳过直达 PLAY'); + // B8(escape-state round2 变更登记):跳过确认句后、进 PLAY 前补播目标句 + // g7_goal——跳过链从 1 句变 2 句,第一句播完时仍在 GUIDE + assert(g2b.rec.count('voice/g7_goal') === 1, 'B8:guide_skip 后补播 g7_goal'); + assert(g2b.game.phase === 'GUIDE', 'B8:g7_goal 补播期间尚未进 PLAY'); + await tick(VOICE_END_SLACK); + assert(g2b.game.phase === 'PLAY', 'G3 下滑跳过(补播 g7_goal 后)直达 PLAY'); + assert(g2b.rec.count('voice/start_room') === 1, 'B8:跳过路径 g7_goal 后播 start_room'); + const goalT = g2b.rec.last('voice/g7_goal').t; + const startT = g2b.rec.last('voice/start_room').t; + assert(goalT < startT, 'B8:跳过路径播报顺序 guide_skip → g7_goal → start_room'); g2b.game.stop(); } @@ -538,7 +550,7 @@ section('B. 引导纠错与跳过'); assert(game.guideStep === 3 && game._guideBusy, '左滑后正常推进(G4 语音中)'); game.stop(); - // 下滑跳过引导 + // 下滑跳过引导(B8:确认句后补播 g7_goal 再进 PLAY) store.clear(); const g2 = await newGame(); g2.game.start(); @@ -546,7 +558,9 @@ section('B. 引导纠错与跳过'); await gs(g2.game, 'down'); assert(g2.rec.count('voice/guide_skip') === 1, '引导期下滑播 guide_skip'); await tick(VOICE_END_SLACK); - assert(g2.game.phase === 'PLAY', '下滑跳过引导直达 PLAY'); + assert(g2.rec.count('voice/g7_goal') === 1, 'B8:guide_skip 后补播 g7_goal'); + await tick(VOICE_END_SLACK); + assert(g2.game.phase === 'PLAY', '下滑跳过引导(补播 g7_goal 后)直达 PLAY'); assert(store.get('soundgame.escape.guideDone') === '1', '跳过引导也写 guideDone'); assert(g2.rec.count('voice/start_room') === 1, '跳过引导播 start_room'); g2.game.stop(); @@ -807,6 +821,466 @@ section('E. 设置边界'); game.stop(); } +// ================================================================ +// 场景 G:B1 撞墙方位句(wall_dir_* 接播 + 同向 10s 限频 + 打断作废;引导期不播) +// ================================================================ +section('G. B1 撞墙方位句(escape-state round2 F1)'); +{ + // 引导期 G2 伪撞墙不播方位句(B1 仅 PLAY 阶段) + store.clear(); + const gd = await newGame(); + gd.game.start(); + await flush(); + await tick(VOICE_END_SLACK); + await gs(gd.game, 'tap'); // G0 → G1 + await finishVoices(2); + await gs(gd.game, 'up'); // G1 + await finishVoices(2); + await gs(gd.game, 'up'); // G2 ×2(伪撞墙) + await gs(gd.game, 'left'); + await finishVoices(2); + assert(gd.rec.count('voice/wall') >= 2, '前提:G2 撞墙播过 wall 语音'); + for (const d of ['l', 'r', 'u', 'd']) { + assert(gd.rec.count(`voice/wall_dir_${d}`) === 0, `B1:引导期 G2 撞墙不播 wall_dir_${d}(仅 PLAY)`); + } + gd.game.stop(); + + store.clear(); + store.set('soundgame.escape.guideDone', '1'); + const { game, rec } = await newGame(); + game.start(); + await flush(); + await gs(game, 'tap'); // REOPEN → PLAY(start_room → g7_goal) + await finishVoices(2); + await gs(game, 'left'); // (0,1) B5a 首访 empty_2 + await gs(game, 'up'); // (0,0) room_power + await finishVoices(2); + + // ① 撞墙后 wall 播完接播方位句(顺序:wall → wall_dir_u) + await gs(game, 'up'); // 撞北墙 + assert(rec.count('voice/wall_dir_u') === 0, 'B1:wall 播放中方位句未播(接播不抢话)'); + await finishVoices(1); + assert(rec.count('voice/wall_dir_u') === 1, 'B1:wall 播完接播 wall_dir_u「上边是墙。」'); + assert(rec.last('voice/wall_dir_u').t >= rec.last('voice/wall').t + 1000, 'B1:方位句在 wall 自然播完之后'); + await finishVoices(1); + + // ② 打断作废:撞左墙 wall 未播完再撞上墙——左墙方位句作废; + // 上墙 10s 限频内(① 刚播过)不再重复 + await gs(game, 'left'); // wall(左) + await gs(game, 'up'); // wall(上)顶掉上一句 + await finishVoices(2); + assert(rec.count('voice/wall_dir_l') === 0, 'B1:wall 被打断则方位句自然作废(wall_dir_l 未播)'); + assert(rec.count('voice/wall_dir_u') === 1, 'B1:同向 10s 限频——上墙方位句不重复(仍 1 次)'); + + // ③ 同向限频与恢复:左墙首播 → 10s 内再撞不重复 → 窗口过后恢复 + await gs(game, 'left'); // 左墙(② 的接播被作废,未播过 → 本次接播) + await finishVoices(2); + assert(rec.count('voice/wall_dir_l') === 1, 'B1:被作废的方向补撞后方位句正常接播'); + await gs(game, 'left'); // 10s 窗口内再撞左墙 + await finishVoices(2); + assert(rec.count('voice/wall_dir_l') === 1, 'B1:同向 10s 内限频 1 次(wall_dir_l 不重复)'); + await tick(10000); + await gs(game, 'left'); + await finishVoices(2); + assert(rec.count('voice/wall_dir_l') === 2, 'B1:限频窗口过后同向方位句恢复'); + game.stop(); +} + +// ================================================================ +// 场景 H:B2 目标名+方位一体句(gd_*_dir 替换三处接播,异常路径回落 gd_room_*) +// ================================================================ +section('H. B2 目标名+方位一体句(escape-state round2 F2)'); +{ + store.clear(); + store.set('soundgame.escape.guideDone', '1'); + const { game, rec } = await newGame(); + game.start(); + await flush(); + await gs(game, 'tap'); // REOPEN → PLAY (1,1) + await finishVoices(2); // start_room + g7_goal + + // ① found_key 链尾 → gd_cabinet_dir(储物间 (0,2) → 铁柜房 (2,1),固定为右) + game.x = 0; game.y = 2; // 测试钩子:瞬移储物间 + await gs(game, 'tap'); // 检查 → 拾钥匙 + await tick(800); // found_key 起播 + await finishVoices(2); // found_key → gd_cabinet_dir + assert(rec.count('voice/found_key') === 1, '前提:拾钥匙播 found_key'); + assert(rec.count('voice/gd_cabinet_dir') === 1, 'B2:found_key 链尾播 gd_cabinet_dir「铁柜,在右面的房间。」'); + assert(rec.last('voice/gd_cabinet_dir').t > rec.last('voice/found_key').t, 'B2:一体句在 found_key 之后'); + assert(rec.count('voice/gd_room_r') === 0, 'B2:正常路径不再播无目标名的 gd_room_r'); + + // ② unlock_cabinet/gd_next_fuse 链尾 → gd_box_dir(铁柜房 → 电箱房 (0,0),固定为左) + game.x = 2; game.y = 1; // 钩子:瞬移铁柜房 + await gs(game, 'doubletap'); + await finishVoices(3); // unlock_cabinet → gd_next_fuse → gd_box_dir + assert(rec.count('voice/gd_box_dir') === 1, 'B2:gd_next_fuse 链尾播 gd_box_dir「电箱,在左面的房间。」'); + assert(rec.last('voice/gd_box_dir').t > rec.last('voice/gd_next_fuse').t, 'B2:一体句在 gd_next_fuse 之后'); + assert(rec.count('voice/gd_room_l') === 0, 'B2:正常路径不再播 gd_room_l'); + + // ③ fix_box 链尾 → gd_gate_dir(电箱房 → 大门 (2,2),固定为右) + game.x = 0; game.y = 0; // 钩子:瞬移电箱房 + await gs(game, 'doubletap'); + await finishVoices(2); // fix_box → gd_gate_dir + assert(rec.count('voice/gd_gate_dir') === 1, 'B2:fix_box 链尾播 gd_gate_dir「大门,在右面的房间。」'); + assert(rec.last('voice/gd_gate_dir').t > rec.last('voice/fix_box').t, 'B2:一体句在 fix_box 之后'); + + // ④ 异常路径回落:计算方位与枚举不符 → 回落原 gd_room_*(归因真话,原句保留接线) + game.x = 1; game.y = 1; // 钩子:异常位置(正常链上不存在) + game._playGoalDir([0, 1], 'voice/gd_cabinet_dir', 'voice/gd_room_r'); // 目标在西 ≠ 枚举右 + await flush(); + assert(rec.count('voice/gd_room_l') === 1, 'B2:方位与枚举不符时回落 gd_room_*(原句保留接线)'); + await finishVoices(1); + game._playGoalDir([1, 1], 'voice/gd_cabinet_dir', 'voice/gd_room_r'); // 同房间不播(H1 原语义) + await flush(); + assert(rec.count('voice/gd_room_l') === 1 && rec.count('voice/gd_cabinet_dir') === 1, + 'B2:目标在同一房间不播(H1 原语义不变)'); + game.stop(); +} + +// ================================================================ +// 场景 I:B3 无进展 150s 活跃计时器(gd_prog 进度重述) +// 边界:key 阶段不走 / ≥5 手势闸门(挂机不触)/ 每段限 1 次 / +// 有进展时复位 / 待播期间操作作废且计入已播 +// ================================================================ +section('I. B3 无进展活跃计时器(escape-state round2 F3)'); +{ + store.clear(); + store.set('soundgame.escape.guideDone', '1'); + const { audio, game, rec } = await newGame(); + game.start(); + await flush(); + await gs(game, 'tap'); // REOPEN → PLAY + await finishVoices(2); + // 往返手势一律 (1,1)↔(0,1)(空房,避开关键房间的首到进展事件) + const wander = async (n) => { for (let i = 0; i < n; i++) await gs(game, i % 2 ? 'right' : 'left'); }; + + // ① key 阶段不走本通道:无进展 200s + 活跃 5 手势 → 无 gd_prog + await wander(5); + game._lastProgressAt = now - 200000; // 钩子:模拟 200s 无进展 + game._checkStuck(); + await flush(); + assert(rec.count('voice/gd_prog_1') === 0, 'B3:key 阶段不走本通道(L1/L2/L3 已覆盖)'); + await tick(1600); // 消化首访 empty_2 + + // ② 拾钥匙进 cabinet 阶段(进展事件复位计时) + game.x = 0; game.y = 2; // 钩子:瞬移储物间 + await gs(game, 'tap'); + await tick(800); + await finishVoices(2); // found_key + gd_cabinet_dir + assert(game._stage() === 'cabinet' && game._gesturesSinceProgress === 0, + 'B3 前提:cabinet 阶段,进展事件已复位计数'); + + // ③ 闸门:无进展 151s 但活跃手势 <5(挂机/低活跃)→ 不触 + game._lastProgressAt = now - 151000; + game._checkStuck(); + await flush(); + assert(rec.count('voice/gd_prog_1') === 0, 'B3:活跃手势 <5 不触(挂机交给 60s L1 体系)'); + + // ④ 活跃触发:5 手势后无进展 151s → gd_prog_1 到达一次 + game.x = 1; game.y = 1; + await wander(5); + await tick(1600); + game._checkStuck(); // _lastProgressAt 仍为 ② 的钩子值(移动不复位无进展计时) + await flush(); + assert(rec.count('voice/gd_prog_1') === 1, 'B3:活跃(≥5 手势)无进展 150s 播 gd_prog_1'); + + // ⑤ 每进展段限 1 次:再到点不重复 + await finishVoices(1); + game._lastProgressAt = now - 200000; + game._checkStuck(); + await flush(); + assert(rec.count('voice/gd_prog_1') === 1, 'B3:gd_prog_1 每进展段限 1 次'); + + // ⑥ 有进展时复位:开柜(进展事件)→ 计数清零、计时重起,不立即触下一段 + game.x = 2; game.y = 1; + await gs(game, 'doubletap'); // 开柜 + assert(game._gesturesSinceProgress === 0, 'B3:进展事件后活跃手势计数清零(复位)'); + game._checkStuck(); // _lastProgressAt 刚复位,远不足 150s + await flush(); + assert(rec.count('voice/gd_prog_2') === 0, 'B3:有进展时计时器复位(不立即触 gd_prog_2)'); + await finishVoices(3); // unlock 链 + + // ⑦ box 段可再触一次 + game.x = 1; game.y = 1; + await wander(5); + await tick(1600); + game._lastProgressAt = now - 151000; + game._checkStuck(); + await flush(); + assert(rec.count('voice/gd_prog_2') === 1, 'B3:box 阶段活跃无进展 150s 播 gd_prog_2'); + await finishVoices(1); + + // ⑧ 待播期间操作作废且计入已播(每段限 1 次不重发) + game.x = 0; game.y = 0; + await gs(game, 'doubletap'); // 修电 → gate 阶段(进展复位) + await finishVoices(2); // fix_box + gd_gate_dir + game.x = 1; game.y = 1; + await wander(5); + await tick(1600); + await gs(game, 'tap'); // 单击检查 + await tick(800); // 检查描述播放中 + assert(audio.isVoicePlaying(), 'B3 前提:检查描述播放中'); + game._lastProgressAt = now - 151000; + game._checkStuck(); // 到点 → gd_prog_3 只入队 + assert(rec.count('voice/gd_prog_3') === 0, 'B3:语音播放中 gd_prog 只入队不开播(不顶句)'); + await gs(game, 'left'); // 玩家操作 → 待播 gd_prog_3 作废 + await finishVoices(2); + assert(rec.count('voice/gd_prog_3') === 0, 'B3:待播期间操作即作废(不播出)'); + assert(game._progShown.has('gate'), 'B3:作废仍计入已播(每进展段限 1 次)'); + game._lastProgressAt = now - 200000; + game._checkStuck(); + await flush(); + assert(rec.count('voice/gd_prog_3') === 0, 'B3:作废的 gd_prog 不重发'); + game.stop(); +} + +// ================================================================ +// 场景 J:B4 双击兜底降级(背包 + L1 意象;去重;L2 仍由计时器到达) +// ================================================================ +section('J. B4 双击兜底降级(escape-state round2 F4)'); +{ + store.clear(); + store.set('soundgame.escape.guideDone', '1'); + const { game, rec } = await newGame(); + game.start(); + await flush(); + await gs(game, 'tap'); // REOPEN → PLAY (1,1) 非关键房 + await finishVoices(2); + + // 非关键房兜底 = 背包 + L1 意象暗示(不再直通 L2);播了即记账 + await gs(game, 'doubletap'); + assert(rec.count('voice/inv_empty') === 1, 'B4:非关键房兜底先播背包 inv_empty'); + await finishVoices(2); + assert(rec.count('voice/stuck_key_1') === 1, 'B4:兜底接 L1 意象暗示 stuck_key_1'); + assert(rec.count('voice/stuck_key_2') === 0, 'B4:兜底不直通 L2 明示 stuck_key_2'); + assert(game._stuckShown.has('L1'), 'B4:兜底播 L1 即记账(沿用 _stuckShown 去重)'); + + // 同进展段再次兜底不重复 L1 意象句(既有去重) + await gs(game, 'doubletap'); + await finishVoices(2); + assert(rec.count('voice/inv_empty') === 2, 'B4:再次兜底仍播背包'); + assert(rec.count('voice/stuck_key_1') === 1, 'B4:同进展段再次兜底不重复 L1 意象句'); + + // 降级后 L2 仍可达:L1 已记账 → 60s 不再重复(整级跳过,stuck_ask 亦不播); + // 120s 无操作 L2 明示照常到达(难度不降) + await tick(65000); + assert(rec.count('voice/stuck_ask') === 0, 'B4:L1 已记账,60s 无操作不再重复 L1 级'); + await tick(60000); + assert(rec.count('voice/stuck_key_2') === 1, 'B4:降级后 L2 仍由计时器到达(120s 无操作)'); + game.stop(); +} + +// ================================================================ +// 场景 K:B5a 空房首访描述(首访到达 / 重访不重复 / 单击检查语义 / 非进展事件) +// ================================================================ +section('K. B5a 空房首访描述(escape-state round2 F5)'); +{ + store.clear(); + store.set('soundgame.escape.guideDone', '1'); + const { game, rec } = await newGame(); + game.start(); + await flush(); + await gs(game, 'tap'); // REOPEN → PLAY (1,1) + await finishVoices(2); + + // 首访空房不算进展事件:不清卡关已播级别 + game._stuckShown.add('L1'); // 钩子:模拟本段已播 L1 + await gs(game, 'up'); // (1,0) 首访 + assert(game._stuckShown.has('L1'), 'B5a:空房首访不算进展事件(不清 _stuckShown)'); + game._stuckShown.clear(); // 复位钩子 + + // 首访 (1,0) → empty_1;本房间内单击不再重复首访句;重访零语音;重访后单击照常 + assert(rec.count('voice/empty_1') === 1, 'B5a:空房首访补播 empty_1(一次性听觉锚)'); + await finishVoices(1); + await gs(game, 'tap'); // 首访句刚播过的房间内单击 + await tick(900); + assert(rec.count('voice/empty_1') === 1, 'B5a:首访句刚播过的房间内单击不重复同一句'); + await gs(game, 'down'); // (1,1) + await gs(game, 'up'); // (1,0) 重访 + await tick(900); + assert(rec.count('voice/empty_1') === 1, 'B5a:空房重访零语音(不重复首访句)'); + await gs(game, 'tap'); // 重访后单击检查 + await tick(900); + assert(rec.count('voice/empty_1') === 2, 'B5a:重访后单击检查照常播 empty_1(语义不变)'); + await finishVoices(1); + + // 四房映射:(0,1)→empty_2、(1,2)→empty_3、(2,0)→empty_3(对称水滴共用合法记忆点) + await gs(game, 'down'); // (1,1) + await gs(game, 'left'); // (0,1) 首访 + assert(rec.count('voice/empty_2') === 1, 'B5a:(0,1) 首访播 empty_2'); + await finishVoices(1); + await gs(game, 'right'); // (1,1) + await gs(game, 'down'); // (1,2) 首访 + assert(rec.count('voice/empty_3') === 1, 'B5a:(1,2) 首访播 empty_3'); + await finishVoices(1); + game.x = 2; game.y = 1; // 钩子:瞬移铁柜房(已 visited 与否不影响下行) + game.visited.add('2,1'); + await gs(game, 'up'); // (2,0) 首访 + assert(rec.count('voice/empty_3') === 2, 'B5a:(2,0) 首访复用 empty_3(两间"远处水滴"对称分布)'); + await finishVoices(1); + game.stop(); +} + +// ================================================================ +// 场景 L:B6 引导 busy 窗口手势缓存(补判 / 答错路径 / 不触发跳过 / 仅 1 个) +// ================================================================ +section('L. B6 引导 busy 手势缓存(escape-state round2 F7)'); +{ + store.clear(); + const { game, rec } = await newGame(); + game.start(); + await flush(); + await tick(VOICE_END_SLACK); // intro 播完(自动推进 1.2s 未到点) + await gs(game, 'tap'); // G0 → _advance(1) busy + + // ① busy 窗口手势缓存:不当场判定,解锁后按新步骤补判(合法 = 作答) + await gs(game, 'up'); // busy 窗口注入滑动 → 缓存 + assert(game.guideStep === 0 && game._guideBusy, 'B6 前提:busy 窗口内步骤未解锁'); + const stepsAtBusy = rec.count('sfx/step'); + assert(rec.count('sfx/step') === stepsAtBusy, 'B6:busy 窗口手势不当场判定(无脚步声)'); + await finishVoices(2); // g1_move + skip_hint 播完 → 解锁 G1 → 缓存 up 补判 + assert(rec.count('sfx/step') === stepsAtBusy + 1, 'B6:解锁后缓存手势补判(G1 滑动作答,脚步声)'); + assert(game.guideStep === 1 && game._guideBusy, 'B6:补判作答后正常推进(G2 语音中)'); + await finishVoices(2); // g1_done + g2_wall + assert(game.guideStep === 2, 'B6:进入 G2'); + + // ② 缓存手势对新步骤合法:G2→G3 busy 缓存左滑(G3 的答案)→ 解锁直接作答 + await gs(game, 'up'); // G2 第一滑 + await tick(VOICE_END_SLACK); // wall 播完 + await gs(game, 'right'); // G2 第二滑 → _advance(3) busy + await gs(game, 'left'); // busy 缓存左滑 + assert(game.guideStep === 2, 'B6:busy 窗口仍停 G2'); + await finishVoices(2); // wall + g3_left 播完 → 解锁 G3 → 缓存 left 补判 + assert(game.x === 0 && game.y === 1, 'B6:缓存左滑按 G3 补判作答(移到 (0,1))'); + assert(game.guideStep === 3 && game._guideBusy, 'B6:补判作答后推进 G4'); + assert(rec.count('voice/guide_skip') === 0, 'B6:缓存手势不触发下滑跳过(S6 事故修复)'); + await finishVoices(1); // g4_down → G4 解锁 + + // ③ 缓存手势对新步骤不合法:按答错路径处理(有纠正语音,可归因) + await gs(game, 'down'); // G4 作答 → _advance(5) busy + await gs(game, 'up'); // busy 缓存 up + await finishVoices(1); // 解锁 G5 → 补判:G5 期望单击,up = 答错 + assert(game.guideStep === 5, 'B6:进入 G5'); + assert(rec.count('voice/guide_wrong_gesture') === 1, 'B6:缓存手势不合法按答错路径处理(纠正语音可归因)'); + assert(game.guideErrs === 1, 'B6:答错计数照常'); + assert(rec.count('voice/guide_skip') === 0, 'B6:答错路径同样不触发跳过'); + + // ④ 仅缓存 1 个:busy 连注两个手势,解锁后只补判首个 + await gs(game, 'tap'); // G5 作答 → _advance(6) busy + await gs(game, 'doubletap'); // 缓存(恰为 G6 答案) + await gs(game, 'left'); // 不再缓存(仅 1 个) + await finishVoices(2); // g5_key + g6_doubletap → 解锁 G6 → 补判 doubletap + assert(game.guideStep === 6 && game._guideBusy, 'B6:仅首个缓存手势补判(doubletap 作答 G6)'); + await finishVoices(3); // inv_key + g6_done + g7_goal → PLAY + assert(game.phase === 'PLAY', 'B6:引导链完整走完(缓存不破坏既有推进)'); + game.stop(); + + // ⑤ 跳过分手势不缓存:busy 窗口下滑(可跳过步骤)仍立即跳过 + store.clear(); + const g2b = await newGame(); + g2b.game.start(); + await flush(); + await gs(g2b.game, 'tap'); // G0 → _advance(1) busy + assert(g2b.game._guideBusy, '前提:G0→G1 推进语音播放中'); + await gs(g2b.game, 'down'); // busy 窗口下滑 = 跳过分手势 → 立即跳过,不缓存 + assert(g2b.rec.count('voice/guide_skip') === 1, 'B6:busy 窗口下滑跳过语义不变(不缓存)'); + assert(g2b.game._guidePending === null, 'B6:跳过分手势不进缓存'); + g2b.game.stop(); +} + +// ================================================================ +// 场景 M:B7 结算链单击守卫(播完前不重开 / 链完整到达 / 30s 兜底解除) +// ================================================================ +section('M. B7 结算链单击守卫(escape-state round2 F8)'); +{ + store.clear(); + store.set('soundgame.escape.guideDone', '1'); + const { game, rec } = await newGame(); + game.start(); + await flush(); + await gs(game, 'tap'); // REOPEN → PLAY + await finishVoices(2); + // 钩子速通:开柜 → 修电 → 大门 + game.hasKey = true; game.x = 2; game.y = 1; + await gs(game, 'doubletap'); // 开柜 + await finishVoices(3); + game.x = 0; game.y = 0; + await gs(game, 'doubletap'); // 修电 + await finishVoices(2); + game.x = 2; game.y = 2; + await gs(game, 'doubletap'); // 逃脱 + assert(game.phase === 'ENDING' && game._settleBusy, 'B7 前提:ENDING + 结算守卫挂载'); + + // 结算链播放中单击 = 忽略(链不被截断,time_band/again 完整到达) + await gs(game, 'tap'); + assert(game.phase === 'ENDING', 'B7:结算链播完前单击不重开(仍 ENDING)'); + assert(rec.count('voice/replay') === 0, 'B7:守卫期单击不触发重开(无 replay)'); + await finishVoices(1); + assert(rec.count('voice/time_band1') === 1, 'B7:结算链完整——time_band 到达(不再被吞)'); + await finishVoices(1); + assert(rec.count('voice/again') === 1, 'B7:结算链完整——again 到达'); + await finishVoices(1); // 链完 → 守卫解除 + assert(!game._settleBusy, 'B7:链播完守卫解除'); + await gs(game, 'tap'); + assert(game.phase === 'PLAY' && rec.count('voice/replay') === 1, 'B7:守卫解除后单击重开原语义不变'); + game.stop(); + + // 30s 资产缺失兜底:again 缺失 → 链停摆 → 守卫靠兜底解除(防静默死锁) + blockedAssets = ['again']; + store.clear(); + store.set('soundgame.escape.guideDone', '1'); + const g2 = await newGame(); + g2.game.start(); + await flush(); + await gs(g2.game, 'tap'); + await finishVoices(2); + g2.game.hasKey = true; g2.game.x = 2; g2.game.y = 1; + await gs(g2.game, 'doubletap'); + await finishVoices(3); + g2.game.x = 0; g2.game.y = 0; + await gs(g2.game, 'doubletap'); + await finishVoices(2); + g2.game.x = 2; g2.game.y = 2; + await gs(g2.game, 'doubletap'); + assert(g2.game.phase === 'ENDING', '前提:again 缺失下逃脱进 ENDING'); + await finishVoices(2); // escape + time_band 播完,again 缺失链停摆 + await gs(g2.game, 'tap'); + assert(g2.game.phase === 'ENDING', 'B7:again 缺失链停摆时守卫仍生效(单击不重开)'); + await tick(30000); // 兜底 + assert(!g2.game._settleBusy, 'B7:30s 资产缺失兜底解除守卫(防静默死锁)'); + await gs(g2.game, 'tap'); + assert(g2.game.phase === 'PLAY', 'B7:兜底解除后可重开'); + blockedAssets = []; + g2.game.stop(); +} + +// ================================================================ +// 场景 N:B8 REOPEN 直进路径补播目标句(start_room → g7_goal,可单击跳过) +// ================================================================ +section('N. B8 REOPEN 直进补播 g7_goal(escape-state round2 F9)'); +{ + store.clear(); + store.set('soundgame.escape.guideDone', '1'); + const { game, rec } = await newGame(); + game.start(); + await flush(); + assert(game.phase === 'REOPEN', '前提:二周目 REOPEN'); + await gs(game, 'tap'); // 直接开始 + assert(game.phase === 'PLAY', 'REOPEN 单击直进 PLAY'); + assert(rec.count('voice/start_room') === 1, 'B8:直进播 start_room'); + await finishVoices(1); + assert(rec.count('voice/g7_goal') === 1, 'B8:start_room 后接播 g7_goal'); + assert(rec.last('voice/g7_goal').t > rec.last('voice/start_room').t, 'B8:直进路径顺序 start_room → g7_goal'); + // g7_goal 可单击跳过(skippable 默认 true,12 号文档——教学义务句已随首局完成, + // 此处是状态告知不是教学) + const skips0 = rec.skips(); + await gs(game, 'tap'); // g7_goal 播放中单击 = 跳过 + 补发检查 + assert(rec.skips() === skips0 + 1, 'B8:二周目 g7_goal 可单击跳过'); + assert(game.phase === 'PLAY', 'B8:跳过后仍在 PLAY'); + await finishVoices(2); // 补发检查描述消化 + game.stop(); +} + // ================================================================ // 资产引用校验:game.js 引用的 id 在音频目录有 ogg + m4a(voice 双性别),零缺失 // ================================================================ @@ -822,6 +1296,7 @@ section('F. 资产引用校验'); ids.add(`voice/guide_wrong_${d}`); ids.add(`voice/guide_blunt_${d}`); } + for (const d of ['l', 'r', 'u', 'd']) ids.add(`voice/wall_dir_${d}`); // B1 撞墙方位句族 for (const st of ['key', 'cabinet', 'box', 'gate']) { for (const lv of [1, 2, 3]) ids.add(`voice/stuck_${st}_${lv}`); } diff --git a/web/harness/guide.test.mjs b/web/harness/guide.test.mjs index 6bd59a02..dabd44ed 100644 --- a/web/harness/guide.test.mjs +++ b/web/harness/guide.test.mjs @@ -756,9 +756,9 @@ realLog('== ⑩b H5 rhythm:教学应答窗口无任何尝试 → gd_miss_none resetTimers(); } -realLog('== ⑩c H1 escape:目标句后按房间图播 gd_room_*(同房间不播,移动坐标系)=='); +realLog('== ⑩c H1/B2 escape:目标句后按房间图播方位句(round2 B2:三处固定接线点升级为"目标名+方位"一体句,异常路径回落 gd_room_*)=='); { - // 方向映射按图校验(直连单元断言) + // 方向映射按图校验(直连单元断言,gd_room_* 映射表保留不动) const rig0 = makeRig(EscapeGame); const { game: g0 } = rig0; await rig0.audio.unlock(); @@ -768,10 +768,15 @@ realLog('== ⑩c H1 escape:目标句后按房间图播 gd_room_*(同房间 assert(g0._roomDirVoice([1, 0]) === 'voice/gd_room_u', 'H1:目标在北(上面的房间)→ gd_room_u'); assert(g0._roomDirVoice([1, 2]) === 'voice/gd_room_d', 'H1:目标在南(下面的房间)→ gd_room_d'); assert(g0._roomDirVoice([1, 1]) === null, 'H1:目标在同一房间不播'); + // B2 异常路径回落:计算方位与枚举不符 → 原 gd_room_*(原句保留接线,归因真话) + g0._playGoalDir([0, 1], 'voice/gd_cabinet_dir', 'voice/gd_room_r'); // 目标在西 ≠ 枚举右 + await flush(); + assert(rig0.playLog.some((e) => e.id === 'voice/gd_room_l'), 'B2:方位与枚举不符时回落 gd_room_*'); g0.stop(); resetTimers(); // 三处目标句接线点:found_key → cabinet(2,1);gd_next_fuse → box(0,0);fix_box → gate(2,2) + // (B2 换线:gd_room_r/l → gd_cabinet_dir/gd_box_dir/gd_gate_dir,断言同步登记变更) const rig = makeRig(EscapeGame); const { audio, playLog, game } = rig; storageData.set('soundgame.escape.guideDone', '1'); // 二周目直达 REOPEN→PLAY @@ -779,42 +784,74 @@ realLog('== ⑩c H1 escape:目标句后按房间图播 gd_room_*(同房间 game.start(); await advance(() => game.phase === 'REOPEN'); await advance(() => !audio.isVoicePlaying()); // reopen_prompt 播完 - await gesture(game, 'tap'); // 直接开始 → PLAY (1,1) + await gesture(game, 'tap'); // 直接开始 → PLAY (1,1)(B8:start_room → g7_goal 链) await advance(() => game.phase === 'PLAY' && !audio.isVoicePlaying()); assert(game.x === 1 && game.y === 1 && !game.hasKey, '前提:二周目 PLAY 起点、身上无钥匙'); - // ① found_key:储物间拾钥匙 → 铁柜房在东(右面的房间) - await gesture(game, 'left'); // (0,1) + // ① found_key:储物间拾钥匙 → 铁柜房在东 → B2 一体句 gd_cabinet_dir + await gesture(game, 'left'); // (0,1)(B5a:首访播 empty_2) await gesture(game, 'down'); // (0,2) 储物间(首到播 room_storage) await advance(() => !audio.isVoicePlaying()); // 房间描述播完再单击(否则单击=跳句) - await gesture(game, 'tap'); // 检查 → found_key(800ms 后)→ 方位句 - await advance(() => playLog.some((e) => e.id === 'voice/gd_room_r')); + await gesture(game, 'tap'); // 检查 → found_key(800ms 后)→ 一体句 + await advance(() => playLog.some((e) => e.id === 'voice/gd_cabinet_dir')); const ifk = playLog.findIndex((e) => e.id === 'voice/found_key'); - assert(ifk > -1 && playLog.findIndex((e) => e.id === 'voice/gd_room_r') > ifk, - 'H1:found_key 后接播 gd_room_r(目标铁柜房按图在东)'); + assert(ifk > -1 && playLog.findIndex((e) => e.id === 'voice/gd_cabinet_dir') > ifk, + 'B2:found_key 后接播 gd_cabinet_dir(铁柜,在右面的房间)'); + assert(!playLog.some((e) => e.id === 'voice/gd_room_r'), 'B2:正常路径不再播 gd_room_r'); - // ② gd_next_fuse:开柜得保险丝 → 电箱房在西(左面的房间) + // ② gd_next_fuse:开柜得保险丝 → 电箱房在西 → B2 一体句 gd_box_dir await gesture(game, 'right'); await gesture(game, 'right'); // (1,2) (2,2) await gesture(game, 'up'); // (2,1) 铁柜房 await gesture(game, 'doubletap'); // 开柜 - await advance(() => playLog.some((e) => e.id === 'voice/gd_room_l')); + await advance(() => playLog.some((e) => e.id === 'voice/gd_box_dir')); const inf = playLog.findIndex((e) => e.id === 'voice/gd_next_fuse'); - assert(inf > -1 && playLog.findIndex((e) => e.id === 'voice/gd_room_l') > inf, - 'H1:gd_next_fuse 后接播 gd_room_l(目标电箱房按图在西)'); + assert(inf > -1 && playLog.findIndex((e) => e.id === 'voice/gd_box_dir') > inf, + 'B2:gd_next_fuse 后接播 gd_box_dir(电箱,在左面的房间)'); + assert(!playLog.some((e) => e.id === 'voice/gd_room_l'), 'B2:正常路径不再播 gd_room_l'); - // ③ fix_box:修电箱通电 → 大门在东(右面的房间) + // ③ fix_box:修电箱通电 → 大门在东 → B2 一体句 gd_gate_dir + await advance(() => !audio.isVoicePlaying()); // gd_box_dir 播完再走(首访空房句不顶链) await gesture(game, 'left'); await gesture(game, 'left'); // (1,1) (0,1) await gesture(game, 'up'); // (0,0) 电箱房 await gesture(game, 'doubletap'); // 修电箱 - await advance(() => playLog.filter((e) => e.id === 'voice/gd_room_r').length >= 2); + await advance(() => playLog.some((e) => e.id === 'voice/gd_gate_dir')); const ifx = playLog.findIndex((e) => e.id === 'voice/fix_box'); - assert(ifx > -1 && playLog.map((e) => e.id).lastIndexOf('voice/gd_room_r') > ifx, - 'H1:fix_box 后接播 gd_room_r(目标大门按图在东)'); + assert(ifx > -1 && playLog.findIndex((e) => e.id === 'voice/gd_gate_dir') > ifx, + 'B2:fix_box 后接播 gd_gate_dir(大门,在右面的房间)'); assert(game.powerOn, '前提:电箱已通电(接线点均在真实解谜链上)'); game.stop(); resetTimers(); } +realLog('== ⑩d B6 escape:引导 busy 窗口手势缓存(full 档)——解锁后补判、答错路径、不触发跳过 =='); +{ + const rig = makeRig(EscapeGame); + const { audio, playLog, game } = rig; + await audio.unlock(); + game.start(); + await advance(() => game.phase === 'GUIDE' && !game._guideBusy && !audio.isVoicePlaying()); // G0 待输入 + await gesture(game, 'tap'); // G0 → _advance(1) busy(g1_move + skip_hint) + assert(game._guideBusy, '前提:G0→G1 推进语音播放中(busy 窗口)'); + await gesture(game, 'up'); // busy 窗口注入滑动 → 缓存(不当场判定) + assert(game.guideStep === 0 && game.guideSwipes === 0, 'B6:busy 窗口手势不当场判定'); + // 解锁 G1 → 缓存 up 按 G1 补判 = 作答(sfx/step + 推进 G2) + await advance(() => game.guideStep === 2 && !game._guideBusy); + assert(playLog.some((e) => e.id === 'sfx/step'), 'B6:full 档下缓存手势解锁后补判作答(脚步声)'); + + // G2 busy 缓存非答案手势 → 解锁 G3 后按答错路径(纠正语音,不触发下滑跳过) + await gesture(game, 'up'); // G2 第一滑 + await advance(() => !audio.isVoicePlaying()); + await gesture(game, 'right'); // G2 第二滑 → _advance(3) busy + await gesture(game, 'down'); // busy 缓存下滑(非 G3 答案) + assert(game.guideStep === 2, 'B6:busy 窗口仍停 G2'); + await advance(() => game.guideStep === 3 && !game._guideBusy); + assert(voicePlays(playLog, 'voice/guide_wrong_left') === 1, 'B6:缓存手势不合法按答错路径处理(guide_wrong_left)'); + assert(voicePlays(playLog, 'voice/guide_skip') === 0, 'B6:缓存的下滑不触发跳过引导(live round1 S6 事故修复)'); + assert(game.phase === 'GUIDE', 'B6:仍在引导中(未被保送 PLAY)'); + game.stop(); + resetTimers(); +} + // ---------------------------------------------------------------- 汇总 realLog(`\n通过 ${passCount} 条断言,失败 ${failures.length} 条`); diff --git a/web/harness/sessions/escape.mjs b/web/harness/sessions/escape.mjs index 4e78b459..94edf90b 100644 --- a/web/harness/sessions/escape.mjs +++ b/web/harness/sessions/escape.mjs @@ -1,8 +1,8 @@ /** * 《暗室》新手会话剧本:老玩家二周目不算——这里清空过进度走首局引导(G0-G7), - * 每步听清再做;进 PLAY 后先连撞两次墙(触发向导提前一级的卡关提示), - * 然后找到铁柜房开柜拿保险丝、照 round4 H1 的目标方位句跨四个房间修电箱、 - * 再摸到大门收工(目标句后 3s 内都能听到方位句)。 + * 每步听清再做;进 PLAY 后先连撞两次墙(round2 B1:wall 后跟陈述式方位句 + * 「上边是墙。」,同向 10s 限频),经空房首访描述(B5a)找到铁柜房开柜拿保险丝、 + * 照 round2 B2 的"目标名+方位"一体句跨四个房间修电箱、再摸到大门收工。 */ export function describe(game) { switch (game.phase) { @@ -55,31 +55,34 @@ export async function session(ctx, mock) { if (why) ctx.note(why); }; - // 先往北乱撞探探边界:两步走到 (0,0),再连撞两次北墙——向导把卡关提示提前带上来 + // 先往北乱撞探探边界:两步走到 (0,0)——(0,1) 首访会播 empty_2(B5a 空房听觉锚), + // 再连撞两次北墙——wall 后各跟一句陈述式方位「上边是墙。」(B1,同向 10s 限频) ctx.silence(3, '辨了辨方向'); - await move('up'); - await move('up'); - await move('up', '北边是墙,撞了一下——「那边是墙」后面还跟了句方向(向上推的是北墙)'); - await move('up', '又撞了一下——听到游戏主动来问我了'); - await ctx.waitVirtual(5200); // 等卡关轮询的 5s tick:连续撞墙 → 提前一级的暗示 + await move('up', '路过一间空房,它自己开口了:摸到的只有冰冷粗糙的墙壁'); + await move('up', '电流声——电箱房到了'); + await move('up', '北边是墙,撞了一下——「那边是墙」后面跟了句「上边是墙。」,这面不通记住了'); + await move('up', '又撞了一下——同方向 10 秒内不重复报方位,游戏不啰嗦'); + await ctx.waitVirtual(5200); // 缓一缓(卡关轮询 5s tick 空转,活跃操作不触发提示) // 去铁柜房:(0,0) → 右×2 = (2,0) → 下 = (2,1),双击用钥匙 - await move('right', '往东走,风声换了个方向'); - await move('right'); + // ((1,0)/(2,0) 首访各播 empty_1/empty_3——B5a:四间空房从此声学可区分) + await move('right', '往东走,一间空房自报家门:空空荡荡只有回声'); + await move('right', '又一间空房:除了远处的水滴声什么都没有'); await move('down', '到了铁柜房,柜子锁着'); - // 用钥匙 → unlock_cabinet + gd_next_fuse + gd_room_l(round4 H1:目标句带方位, - // 从铁柜房 (2,1) 看电箱房 (0,0) 在西 = 左面的房间) + // 用钥匙 → unlock_cabinet + gd_next_fuse + gd_box_dir(round2 B2:目标名+方位 + // 一体句,从铁柜房 (2,1) 看电箱房 (0,0) 在西 = 「电箱,在左面的房间。」) await ctx.gesture('doubletap'); await ctx.waitVirtual(5600); - ctx.status('柜子开了。「保险丝到手。接下来,去找电箱。」后面还跟了一句「左面的房间。」——这回知道该往哪边摸了'); + ctx.status('柜子开了。「保险丝到手。接下来,去找电箱。」后面跟的是「电箱,在左面的房间。」——目标名和方位一句话说全了'); // round4 H1 剧本:照方位句跨房间去电箱房——(2,1) → 左×2 = (0,1) → 上 = (0,0) await move('left', '照方位句往左(西)摸'); await move('left'); await move('up', '电流声越来越清晰——电箱房到了'); - // 修电箱 → fix_box + gd_room_r(round4 H1:从 (0,0) 看大门 (2,2) 在东 = 右面的房间) + // 修电箱 → fix_box + gd_gate_dir(round2 B2:从 (0,0) 看大门 (2,2) 在东 = + // 「大门,在右面的房间。」) await ctx.gesture('doubletap'); await ctx.waitVirtual(5600); - ctx.status('电箱修好了。目标句后面照样跟着方位——「右面的房间。」大门在东边'); + ctx.status('电箱修好了。目标句后面照样是目标名+方位——「大门,在右面的房间。」'); // 照方位句多跨两个房间摸向大门:(0,0) → 右×2 = (2,0) → 下×2 = (2,2) await move('right', '照方位句往右(东)摸'); await move('right'); diff --git a/web/harness/voice_text.json b/web/harness/voice_text.json index 07b8a739..0fab38f1 100644 --- a/web/harness/voice_text.json +++ b/web/harness/voice_text.json @@ -1038,7 +1038,17 @@ "time_band4": "你逃出来了,用时超过十分钟。", "unlock_cabinet": "钥匙插进锁孔,缓缓转动。咔哒一声,柜门开了。柜子深处,躺着一枚保险丝。保险丝到手了。", "use_fail": "这里没有用得上东西的地方。换个房间试试,或者先单击检查一下周围。", - "wall": "那边是墙,换个方向。" + "wall": "那边是墙,换个方向。", + "wall_dir_l": "左边是墙。", + "wall_dir_r": "右边是墙。", + "wall_dir_u": "上边是墙。", + "wall_dir_d": "下边是墙。", + "gd_cabinet_dir": "铁柜,在右面的房间。", + "gd_box_dir": "电箱,在左面的房间。", + "gd_gate_dir": "大门,在右面的房间。", + "gd_prog_1": "钥匙已经到手。找到能用它的锁。", + "gd_prog_2": "铁柜已经打开。电还没有通。", + "gd_prog_3": "电已经通了。去找那扇大门。" }, "hunt": { "breach": "有野兽冲进营地了!", @@ -1958,4 +1968,4 @@ "zhou_r2_plain": "茶是茶,水是水,差点火候。儿子的事,改天再说吧。", "zhou_r2_wrong": "……今晚这盏不对。儿子的事,不说也罢。" } -} \ No newline at end of file +}