diff --git a/docs/audio/asset-manifest.md b/docs/audio/asset-manifest.md index 365f4472..79332275 100644 --- a/docs/audio/asset-manifest.md +++ b/docs/audio/asset-manifest.md @@ -19,6 +19,8 @@ | rhythm | Uncle_Fu | Vivian | female 沿用原解说音色 | | space | Uncle_Fu | Serena | female 沿用原导航音色 | | rpg | Uncle_Fu | Serena | 旁白;NPC 单音色平铺(见 rpg 章节) | +| radio | Uncle_Fu(HOST)/Dylan(PROD) | Serena(HOST)/Vivian(PROD) | 双性别行同性别双 speaker(VOICE_SPK 逐句覆盖);NPC 单音色平铺 5 音色+逐人带通(见 radio 章节) | +| dungeon | Uncle_Fu | Serena | 旁白全表统一 instruct,无 NPC | 关于 Ryan:冒烟测试样本在 `tools/samples/ryan_zh_*.wav`(3 条中文句)。Ryan 是英语母语音色(见 `env.d/tts.md`),项目已有中文发音投诉(反馈⑥),男声统一采用中文母语的 Uncle_Fu;如需换成 Ryan,改 `tools/gen_voice_dual.py` 的 `SPEAKERS` 表重跑即可。 @@ -89,6 +91,30 @@ - bgm(6,Mureka):`village battle boss ending`(R4)+ R6 新增 `menu`(菜单 pad)与 `explore`(探索轻垫乐,10 文档 §7 恢复,volume 0.35);90s 无缝循环;`amb_*` 由游戏层手动 loop 接 sfx 总线 - 再生成方法见 `web/rpg/gen/README.md` +### radio《不眠频率》(批次 1 新游戏,叙事模式,台词依据 `docs/design/15-radio-voice-ids.md`) + +- voice 双性别版(male/ 与 female/ 各 103 条):**HOST 主播** 29 条(male=Uncle_Fu「低沉温和的中年男声,深夜电台主播,沉稳亲切,有磁性」/ female=Serena「温暖清晰的年轻女声,深夜电台主播,沉稳亲切,不甜腻」:开场 `b_open b_rain`、主播句 `z1_* l1_* k1_* w1_* h1_*` 主播行、心结 `d9_face d9_avoid`、结局 `e1–e4_a/b`)+ **PROD 导播/系统/向导** 74 条(male=Dylan / female=Vivian,instruct「贴近耳机的低语男/女声,年轻电台导播,冷静克制」:`gd_* sys_* prog_c1–5 rep_*`、导播行、抉择提示 `p1–p9`、尾声 `ep_*`)。同性别双 speaker 逐句覆盖见 `tools/gen_voice_dual.py` 的 `VOICE_SPK["radio"]`(同场景高频对话的两者永不同 speaker)。 +- voice NPC 平铺(29 条,落 `voice/` 根,引擎平铺回退;**转码带通**见下):老周 7 条(z1_*,Uncle_Fu·苍老沙哑)、小鹿 4 条(l1_*,Serena·哭腔倔强)、糖糖 6 条(k1_*,Vivian·稚嫩童声)、陈姐 7 条(w1_*,Vivian·疲惫沙哑中年)、老韩 5 条(h1_*,Eric·沙哑粗犷司机)。 +- **通话滤波(资产侧,15-radio.md §6.2)**:convert 转码时按来电者逐档 ffmpeg 带通+轻压缩(老周 500-2800 / 小鹿 400-3400 / 糖糖 450-3000 / 陈姐 420-3100 / 老韩 300-3400 Hz,均 `acompressor threshold=-20dB ratio=3`);参数在 `tools/gen_voice_dual.py` 的 `NPC_BANDPASS`,未滤波原声保留在 staging `tools/gen_voice_dual_wav/radio/npc/` 可重滤/回退。HOST/PROD 不滤波。**待人工试听**:滤波句可读性抽检,不可读句按「宁窄勿脏」回退未滤波原声(staging 重转即可)。 +- sfx(2,SYN 程序合成):`ui_confirm ui_menu`(`web/radio/gen/gen_sfx.py`,全局 storyline 先例同款双音/软音)。**缺失 13 件 SA3**(HF token 未配置,本轮跳过):`onair_sting phone_ring line_connect line_drop amb_studio amb_rain_city amb_station amb_kid_room arg_muffled amb_street_rain amb_cab brake_screech tape_static`。 +- bgm(4,Mureka $0.12):`onair bridge ending`(-20 LUFS)+ `menu`(menu pad,-24 LUFS,10 文档 §1.2);90.00s 无缝循环;后处理脚本 `web/radio/gen/gen_bgm.sh`。 +- 合计 241 ogg + 241 m4a(每游戏音频 = 235 voice + 2 sfx + 4 bgm);台词/生成/终检走 `tools/gen_voice_dual.py --game radio` 与 `tools/check_all_assets.py`。 + +### dungeon《回声地窟》(批次 1 新游戏,Roguelike 模式,台词依据 `docs/design/16-dungeon-voice-ids.md`) + +- voice 双性别版(male/ 与 female/ 各 125 条,无 NPC——怪物不说话):male=Uncle_Fu「低沉醇厚的男声,像探窟向导,沉稳有力」/ female=Serena「平静清晰的女声,像探窟向导,沉稳可信」,两常量全表统一。分组:序章/标题 4、教学 DRILL 19、系统/菜单/状态 35、探索事件 9、战斗 30、卡关/看门狗/纠错 14、结算/元进度 14。 +- sfx(19 文件,全 SYN 程序合成,`web/dungeon/gen/gen_sfx.py`):`pulse`(声波脉冲)、材质尾音 `mat_stone/door/stair/water`、**预 pan 立体声** `side_wall_l/r side_open_l/r`(mono 源 + ffmpeg pan 派生,能量只在单声道,极性脚本断言)、移动 `step_own step_turn bump`、拾取 `coin potion gear`、战斗 `atk_swing parry hurt`、`levelup`(D 小调五声上行钟声琶音)。**缺失 24 文件 SA3**(HF token 未配置,本轮跳过):`echo_1–4 stairs_down mob_step_{small,bug,heavy}_l/c/r tel_{small,bug,heavy}(_l/r 派生) atk_hit enemy_die`(SA3 单声道源 + pan 派生工艺见 16-dungeon.md §9.2)。 +- bgm(4,Mureka $0.12):`menu explore battle boss`(全部 -20 LUFS,16-dungeon.md §9.1);90.00s 无缝循环;battle/boss 的 bpm 仅为 prompt 特征词、无节拍网格玩法,未跑 bpm_precise;后处理脚本 `web/dungeon/gen/gen_bgm.sh`。 +- 合计 273 ogg + 273 m4a(250 voice + 19 sfx + 4 bgm);台词/生成/终检走 `tools/gen_voice_dual.py --game dungeon` 与 `tools/check_all_assets.py`。 + +### 批次 1 资产账(2026-09-01) + +- radio 132 句(双性别 103 ×2 + NPC 平铺 29)= 235 voice 资产;dungeon 125 句 ×2 = 250。两游戏合计 970 文件(ogg+m4a)。 +- GPU 串行(flock):radio male/female/npc 与 dungeon male/female 共 5 轮,`--check` 全绿(无时长离群/短句)。 +- Mureka 8 任务 $0.24(台账见 `tools/mureka_log.md` 2026-09-01 节),全部一次成功。 +- 音色分配表新增:radio(双性别 HOST=Uncle_Fu/Serena + PROD=Dylan/Vivian 双配对 + NPC 平铺 5 音色)、dungeon(Uncle_Fu/Serena)。 +- sfx 真峰值(逐声道解码回读)全部 ≤ -3dBFS、|直流| ≤ 0.001、无边界爆豆(源 8ms cos² 淡入淡出 + 垫音 + hp30 工艺,`web//gen/gen_sfx.py` 内建 QC)。 + ## 响度与爆音标准(R6 起) - sfx one-shot:峰值归一 **-3dBFS**(真峰值经 ogg 解码回读校验,见 round6-report §1);首尾 8ms cos² 淡入淡出;去直流。 diff --git a/docs/design/13-playtest-criteria.md b/docs/design/13-playtest-criteria.md index 97583268..3b7b0d9c 100644 --- a/docs/design/13-playtest-criteria.md +++ b/docs/design/13-playtest-criteria.md @@ -744,3 +744,17 @@ - **引擎回归**:gesture.js 改动 opt-in(immediateTap 默认 false 零行为 变化);6 个 playtest 全绿(escape 140 / rpg 188 / simon / space 152 / storyline / rhythm 141)+ guide.test 122 断言全绿。 + +## 20. 新游戏登记:《不眠频率》(radio) 与《回声地窟》(dungeon)(new8 批次 1) + +两款新 RPG 的断言清单与实现核对**住各自设计文档**(断言条目随轮次在原地维护): + +- radio:[15-radio.md](15-radio.md) §测试(playtest 清单)+ 文末「实现核对」(193 断言逐条 ✅/合并/舍弃)。 + 要点:4 结局矩阵路径、pendingChoice 盲玩公平、C3 埋线索/C4 消费差分、rep 剧本验算、 + RING 看门狗两级不同句(gd_ring1/gd_ring2)、模态菜单循环-确认-关闭不丢节点、中断复述、存档 schema。 +- dungeon:[16-dungeon.md](16-dungeon.md) §测试 + 文末「实现核对」(1277 断言逐条)。 + 要点:固定 seed 网格复现+不变式、回声调度全扫描(echo_1..4 延迟/音量/材质/pan)、DRILL 九步、 + 学期零死、格挡 80%/逃跑必成挨一下/Boss 拒逃、死亡归因真话(3 死因矩阵)、元进度遗产解锁、 + 菜单暂停冻结、上滑 5 段状态链。 + +R1 起每轮的增量断言(修复回归)在各自设计文档的轮次小节追加,不在本文件重复。 diff --git a/docs/design/15-radio-voice-ids.md b/docs/design/15-radio-voice-ids.md new file mode 100644 index 00000000..a4dd96f0 --- /dev/null +++ b/docs/design/15-radio-voice-ids.md @@ -0,0 +1,230 @@ +# 15b · 《不眠频率》(radio) 语音台词表(voice ids,录音依据) + +> **给音频总监的录制说明** +> - **双性别组**(主播 HOST + 导播/系统 PROD,103 句):落 `web/radio/audio/voice/female|male/.{ogg,m4a}`,两性别 id 集合完全一致。 +> - **HOST(主播=玩家)**:female = `Serena`,instruct 常量 `温暖清晰的年轻女声,深夜电台主播,沉稳亲切,不甜腻`;male = `Uncle_Fu`,instruct 常量 `低沉温和的中年男声,深夜电台主播,沉稳亲切,有磁性`。 +> - **PROD(导播/系统音/向导 gd_*)**:female = `Vivian`,instruct 常量 `贴近耳机的低语女声,年轻电台导播,冷静克制`;male = `Dylan`,instruct 常量 `贴近耳机的低语男声,年轻电台导播,冷静克制`。 +> - 区分度设计:主播(播音腔,对外广播)与导播(贴耳低语,只进你耳机)共用 speaker 池但**不同 speaker 配对**——female 侧主播 Serena / 导播 Vivian、male 侧主播 Uncle_Fu / 导播 Dylan,同场景高频对话的两者永不同 speaker。 +> - **NPC 平铺组**(29 句):单音色、**不做双性别**,平铺落 `web/radio/audio/voice/.{ogg,m4a}`(引擎平铺回退命中),转码时按 §通话滤波 加带通。NPC 共 5 人(≤5 约束),同一角色 speaker+instruct 恒定,区分度风险逐个标注: +> | 角色 | speaker | instruct 常量 | 区分度风险 | +> |---|---|---|---| +> | 老周(75 岁走丢老人) | `Uncle_Fu` | `苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖` | **中**:与 male 主播同 speaker,靠年龄感 instruct + 电话滤波(500-2800Hz)区分,请试听确认 | +> | 小鹿(想退票的年轻人) | `Serena` | `年轻女声,倔强,带着哭腔,疲惫` | **中**:与 female 主播同 speaker,靠哭腔 instruct + 电话滤波区分,请试听确认 | +> | 糖糖(失眠的孩子) | `Vivian` | `稚嫩童声,八岁小女孩,困倦,胆怯` | **低**:与 female 导播同 speaker,童声 vs 成年低语,且从不同场 | +> | 陈姐(争吵的夫妻·妻) | `Vivian` | `疲惫沙哑的中年女声,四十五岁,强撑着平静` | **低**:与糖糖/导播同 speaker 不同 instruct,三者从不同场,请试听确认 | +> | 老韩(雨夜司机) | `Eric` | `沙哑粗犷的中年男声,货车司机,困倦强撑` | **低**:male 侧唯一 Eric | +> - **通话滤波(转码侧)**:NPC 全部走带通(参数见 15-radio.md §6.2:老周 500-2800 / 小鹿 400-3400 / 糖糖 450-3000 / 陈姐 420-3100 / 老韩 300-3400,均 + 轻压缩);HOST/PROD 不滤波。试听不可读的句子回退未滤波原声(宁窄勿脏)。 +> - **称呼规则**:来电者对主播的称呼一律中性(「主播」「您」),**全表禁用**「闺女/小哥/叔叔/阿姨/大姐头」类性别化称呼(主播性别随用户设置变化);「大姐/姑娘」仅可作来电者的**自称或被导播描述**。 +> - **数字规避**:序号/口碑全部分档整句(prog_c1..c5、rep_*);句内数字(十一点半、三公里、四十分钟前)写死在整句里,禁止运行时拼接。 +> - **禁语速词**:instruct 不含任何语速描述(引擎 SoundTouch 变速);NPC 句靠短句+逗号停顿实现「语速放缓」听感。 +> - 管线:`tools/gen_voice_dual.py` 照录(SPEAKERS 表需新增 radio 行);多音字自查见文末注记。 + +## G0 组 · 系统/向导/状态(PROD 双性别 ×24) + +| id | 台词全文 | 触发 | +|---|---|---| +| `gd_brief` | 单击继续节目。请选择的时候,向左或向右滑。 | 开场链最前(full 档,F8 先例) | +| `gd_continue` | 单击屏幕,继续。 | 叙事链末静默看门狗第 1 级(full 25s / standard 13s) | +| `gd_again` | 还在吗?单击继续。 | 看门狗第 2 级(50s / 30s),新句不逐字重播 | +| `gd_invalid` | 这个手势,直播间里用不上。 | 无效手势(error 震,idle-only,同局限次) | +| `gd_ring1` | 热线在闪。单击,接听。 | RING 静默 10s(第 1 级) | +| `gd_ring2` | 灯还亮着,听众在等。单击,接听。 | RING 静默 25s(第 2 级,**必须与第 1 级不同句**) | +| `sys_help` | 单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。 | 上滑帮助 / 菜单内上滑 | +| `sys_menu` | 菜单。单击切换选项,双击确认。 | 下滑呼出模态菜单 | +| `sys_menu_resume` | 回到直播。 | 菜单项 | +| `sys_menu_restart` | 重新开始今晚。 | 菜单项 | +| `sys_menu_lobby` | 保存并回大厅。 | 菜单项(确认后跳转前播) | +| `sys_settings` | 音色、语速、背景音乐,请回大厅修改。 | 菜单内设置提示 | +| `sys_skip` | 想跳过这句,单击就行。 | 开场每局一次(skip_hint 先例) | +| `sys_continue_ask` | 检测到你的夜班记录。单击继续上次,右滑开始新的夜晚。 | 有存档进游戏 | +| `sys_new_night` | 新的夜晚,开始了。 | 新夜班开始 / 菜单重开 | +| `sys_saved` | 进度已保存。 | 存档写入后 | +| `prog_c1` | 现在是今晚第一通电话。 | 上滑状态链(按当前通号选播) | +| `prog_c2` | 现在是今晚第二通电话。 | 同上 | +| `prog_c3` | 现在是今晚第三通电话。 | 同上 | +| `prog_c4` | 现在是今晚第四通电话。 | 同上 | +| `prog_c5` | 现在是今晚第五通电话。 | 同上 | +| `rep_high` | 今晚的口碑,在听众里立住了。 | 口碑跨入 ≥5 档(BRIDGE 被动播报) | +| `rep_mid` | 今晚的口碑,不好不坏。 | 口碑回落 1-4 档 | +| `rep_low` | 今晚的口碑,还没立起来。 | 口碑 ≤0 档(零起步语义,R2 F2 改词:rep=0 开播前不再读成「砸了」;附 warning 震动) | + +## G1 组 · 开场(双性别 ×4) + +| id | 声部 | 台词全文 | 触发 | +|---|---|---|---| +| `b_open` | 主播 | 午夜零点,红灯亮起。欢迎回到《今夜不眠》。 | INTRO(配 sfx/onair_sting) | +| `b_rain` | 主播 | 雨下了一整晚。热线灯,马上就要亮了。 | INTRO | +| `b_rule` | 导播 | 台里的规矩就一条:先接住人,再接电话。 | INTRO(一句规矩,P4) | +| `b_goal` | 导播 | 今晚五通电话。说什么话,由你决定。 | INTRO,随后第一声铃 | + +## C1 组 · 第一通·老周(走丢的老人)(双性别 13 + 老周平铺 7 = 20) + +| id | 声部 | 台词全文 | 触发 | +|---|---|---|---| +| `z1_ring_in` | 导播 | 一线来电。七十多岁,雨夜,走丢了。 | 接听后线路引入(配 line_connect) | +| `z1_a` | 老周 | 主播……我,我找不到回家的路了。 | 通话主干 | +| `z1_b` | 老周 | 雨太大了。我本来,只想买副老花镜。 | 通话主干(垫 amb_rain_city) | +| `p1` | 导播 | 老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。 | 抉择提示(播完即可选) | +| `z1_song` | 主播 | 大爷别急,这首歌我也会。咱们一句一句来。 | p1 左(称呼「大爷」指对方,中性合规) | +| `z1_songo` | 老周 | 对,对!路口修车铺,天天放这首! | p1 左·反应(正反馈:声音稳住) | +| `z1_sdone` | 导播 | 他稳住了。老歌,比路牌管用。 | p1 左·收 | +| `z1_ask` | 主播 | 大爷,路口有什么招牌?念一个给我听听。 | p1 右 | +| `z1_asko` | 老周 | 招牌……雨糊住了,我眼花,看不清啊…… | p1 右·反应(慌) | +| `z1_adone` | 导播 | 他慌了。先接住情绪,再走流程。 | p1 右·收(即时纠偏,非归因) | +| `p2` | 导播 | 雨声更密了。向左滑,留在原地陪他等巡逻队。向右滑,转接派出所。 | 抉择提示 | +| `z1_stay` | 主播 | 哪儿也不去。我陪你,这首歌还长着呢。 | p2 左 | +| `z1_stayo` | 老周 | 好,好。有人说话,我就不怕了。 | p2 左·反应 | +| `z1_staye` | 导播 | 十五分钟后,巡逻灯停在路口。全城听见老人笑了。 | p2 左·收(数字整句内嵌) | +| `z1_pol` | 主播 | 大爷,我把您转给派出所,他们马上就到。 | p2 右(p1=左 时) | +| `z1_polo` | 老周 | 好,好。谢谢你啊,谢谢。 | p2 右·反应 | +| `z1_pole` | 导播 | 转接完成,地址报得清。干净利落。 | p2 右·收(p1=左 时) | +| `z1_bad` | 老周 | 说不清……我真是,说不清了…… | p2 右(仅 p1=右 时,替代 z1_polo/z1_pole 前段) | +| `z1_bade` | 导播 | 地址没报清就转接,雨又大。这通砸了。先稳人,再办事。 | 归因句(真话律:指向玩家实际两连选) | +| `z1_ok1` | 导播 | 就这么接。记住这个手感。 | 首通成功收尾确认(全局一次,成功后无其他教学) | + +## C2 组 · 第二通·小鹿(想退票的年轻人)(双性别 6 + 小鹿平铺 4 = 10) + +| id | 声部 | 台词全文 | 触发 | +|---|---|---|---| +| `l1_in` | 导播 | 二线来电。姑娘,声音很轻,像怕吵到谁。 | 接听引入(「姑娘」描述来电者,合规) | +| `l1_a` | 小鹿 | 主播,退票窗口十一点半就关了。我站这儿半小时了。 | 通话主干(垫 amb_station) | +| `l1_b` | 小鹿 | 票是回去看我妈的。可我试镜又黄了,两手空空…… | 通话主干 | +| `p3` | 导播 | 向左滑,告诉她这趟车值得上。向右滑,把选择还给她。 | 抉择提示 | +| `l1_go` | 主播 | 你妈等的不是奖杯,是你进门喊的那声妈。 | p3 左 | +| `l1_goo` | 小鹿 | 呜……嗯。不退了,我现在就去检票。谢谢您。 | p3 左·反应 | +| `l1_goe` | 导播 | 二线收工。这句话,值一个头条。 | p3 左·收 | +| `l1_free` | 主播 | 退不退,都是你的自由。夜还长,慢慢想。 | p3 右 | +| `l1_freeo` | 小鹿 | 嗯……我再想想。窗口要关了,我先过去了。 | p3 右·反应 | +| `l1_freee` | 导播 | 她走了。没上车,也没退票。年轻人的夜,长了点。 | p3 右·收 | + +## C3 组 · 第三通·糖糖(失眠的孩子)(双性别 10 + 糖糖平铺 6 = 16) + +| id | 声部 | 台词全文 | 触发 | +|---|---|---|---| +| `k1_in` | 导播 | 三线来电。听呼吸,是个小孩。 | 接听引入 | +| `k1_a` | 糖糖 | 喂……有人吗?我睡不着。 | 通话主干(垫 amb_kid_room) | +| `k1_b` | 糖糖 | 家里灯关了。可是楼下,一直有声音。 | 通话主干(**配 sfx/arg_muffled 线索音**) | +| `p4` | 导播 | 向左滑,唱一段摇篮曲哄她睡。向右滑,轻轻问她怎么了。 | 抉择提示 | +| `k1_lull` | 主播 | 闭上眼睛。风把云朵盖在被子上,月亮替你值夜班。 | p4 左(错过线索路线) | +| `k1_lullo` | 糖糖 | 月亮也睡了……那我也睡。 | p4 左·反应 | +| `k1_lulle` | 导播 | 睡着了。可你没听见,她在怕什么。 | p4 左·收(诚实复盘,非惩罚) | +| `k1_ask` | 主播 | 睡不着的时候,心里是不是装着事?说给我听听。 | p4 右(置 heardArgument=true) | +| `k1_asko` | 糖糖 | 爸爸妈妈摔门了。是不是……我做错了什么呀。 | p4 右·反应 | +| `p5` | 导播 | 孩子的声音在抖。向左滑,告诉她这不是她的错。向右滑,答应她明天都会好。 | 抉择提示(仅 p4=右 可达) | +| `k1_truth` | 主播 | 糖糖,记住,大人的事,不是小孩的错。一个字都不是。 | p5 左 | +| `k1_trutho` | 糖糖 | 嗯……那我睡了。他们明天,会一起给我切蛋糕吗? | p5 左·反应 | +| `k1_truthe` | 导播 | 蛋糕。记下来——四线的来电,可能跟这个家有关。 | p5 左·收(flag 预告) | +| `k1_lie` | 主播 | 睡吧。明天一睁眼,什么都会好起来的。 | p5 右(善意谎言) | +| `k1_lieo` | 糖糖 | 真的吗?拉钩。晚安…… | p5 右·反应 | +| `k1_liee` | 导播 | 你许了个兑不了的现。孩子睡了,谎留下了。 | p5 右·收(诚实复盘) | + +## C4 组 · 第四通·陈姐(争吵的夫妻)(双性别 13 + 陈姐平铺 7 = 20) + +| id | 声部 | 台词全文 | 触发 | +|---|---|---|---| +| `w1_in` | 导播 | 四线来电。户外风声大,是位大姐。 | 接听引入(垫 amb_street_rain) | +| `w1_a` | 陈姐 | 主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。 | 通话主干 | +| `w1_b` | 陈姐 | 明天孩子生日!蛋糕都订好了,他人要今晚走! | 通话主干 | +| `p6` | 导播 | 摔门声你听见了。向左滑,替孩子给她带句话。向右滑,劝她回家把话说开。 | 抉择提示(固定文本,两条件路线共用) | +| `w1_say` | 主播 | 四十分钟前,有个孩子问我们:爸妈摔门,是不是她的错。 | p6 左·**条件 heardArgument=true** | +| `w1_sayo` | 陈姐 | 什么?她还没睡?这么晚了,她还醒着? | w1_say·反应 | +| `w1_saye` | 导播 | 她攥着电话,很久没出声。风都停了。 | w1_say·收 | +| `w1_say2` | 主播 | 今天有位小朋友,还在等爸妈回家切蛋糕。 | p6 左·**条件 heardArgument=false** | +| `w1_say2o` | 陈姐 | 蛋糕……是他订的。他嘴上不说,心里都记着。 | w1_say2·反应 | +| `w1_say2e` | 导播 | 话递到了。就差最后一脚。 | w1_say2·收 | +| `p7` | 导播 | 她的呼吸平了。向左滑,留在直播里,让全城等这个门铃。向右滑,换垫乐,私下陪她说完。 | 抉择提示(仅 p6=左 可达) | +| `w1_air` | 主播 | 陈姐,别挂。让他接电话——全城都在听。 | p7 左(留在直播) | +| `w1_airo` | 陈姐 | 喂!回来吧!蛋糕要化了,孩子等着呢! | p7 左·反应(对电话那头喊) | +| `w1_aire` | 导播 | 电话那头,传来跑步声。今晚的高光时刻。 | p7 左·收 | +| `w1_priv` | 主播 | 这里只有我们俩了。想说就说,想哭就哭。 | p7 右(换垫乐私聊) | +| `w1_privq` | 陈姐 | 呜……其实他订完蛋糕就跟我道歉了。是我,把门摔得太响。 | p7 右·反应 | +| `w1_prive` | 导播 | 灯牌是你亲手关的,故事还是完整的。稳。 | p7 右·收 | +| `w1_home` | 主播 | 吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。 | p6 右(本通结束,无 p7) | +| `w1_homeo` | 陈姐 | 嗯……我这就往回走。谢谢你,听我瞎嚷嚷。 | p6 右·反应 | +| `w1_homee` | 导播 | 她往回走了。成不成,看他们自己的了。 | p6 右·收 | + +## C5 组 · 第五通·老韩(雨夜司机·高潮)(双性别 8 + 老韩平铺 5 = 13) + +| id | 声部 | 台词全文 | 触发 | +|---|---|---|---| +| `h1_in` | 导播 | 五线来电。引擎声没停过——他在开车! | 接听引入(垫 amb_cab) | +| `h1_a` | 老韩 | 主播,别挂。陪我说话,我的眼皮,沉得很。 | 通话主干 | +| `h1_b` | 老韩 | 跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。 | 通话主干(「闺女」指其女儿,合规) | +| `p8` | 导播 | 方向盘比话筒危险。向左滑,陪他聊聊闺女。向右滑,让他立刻进服务区。 | 抉择提示 | +| `h1_talk` | 主播 | 说说她。开学第一天,她跟你说的头一句话,是什么? | p8 左 | +| `h1_talko` | 老韩 | 她说,爸,我在学校给你赚了朵小红花。哈哈,哈哈…… | p8 左·反应 | +| `h1_danger` | 导播 | 车道偏了——听! | 危机拍(**前 2s 起 brake_screech 渐强 + danger 签名同窗**) | +| `h1_dango` | 老韩 | 嘶——好险!前车急刹,我擦着护栏过去了。彻底醒了! | 危机拍·化解 | +| `h1_talke` | 导播 | 吓醒的,也算醒。这一趟,聊到家。 | p8 左·收 | +| `h1_stop` | 主播 | 听我的,前面三公里有服务区。学费急,命更急。 | p8 右 | +| `h1_stopo` | 老韩 | 行,听你的。进了服务区,我给你们放首歌听。 | p8 右·反应 | +| `h1_stope` | 导播 | 他下了高速。单子黄了,人齐了。 | p8 右·收 | +| `h1_echo` | 导播 | 对了。头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的。 | 插播回响(**条件 bond_zhou=true**,queue) | + +## D9 组 · 签收抉择(心结)(双性别 6) + +| id | 声部 | 台词全文 | 触发 | +|---|---|---|---| +| `g9_in` | 导播 | 五线收工。垫乐起——零点档,还剩最后一段。 | C5 后 BRIDGE→签收(配 bgm/bridge) | +| `p9` | 导播 | 留言机红灯在闪,是老主播三年前没播完的那句晚安。向左滑,替他播出去。向右滑,照常收播。 | 抉择提示(39 字,全表最长句) | +| `d9_face` | 主播 | 师父,三年了。这句晚安,我替您播。各位,晚安。 | p9 左(knot=解;配 tape_static) | +| `d9_facee` | 导播 | 带子里其实只有电流声。但空着的那一秒,你说满了。 | p9 左·收(老主播不配音,电流声留白) | +| `d9_avoid` | 主播 | 各位,今晚到这里。明晚零点,今夜不眠,不见不散。 | p9 右 | +| `d9_avoide` | 导播 | 留言机的红灯,又多亮了一夜。 | p9 右·收(开环钩子) | + +## EP 组 · 结局与尾声回响(双性别 19;结局 8 + 回响 11) + +| id | 声部 | 台词全文 | 触发 | +|---|---|---|---| +| `e1_a` | 主播 | 天亮了,热线灯还亮着。你们的晚安,是这座城市的心跳。 | 结局 e1(rep≥5 且直面)主句 | +| `e1_b` | 主播 | 这把话筒,我总算接得住了。明晚零点,接着不眠。 | e1 收(钩子) | +| `e2_a` | 主播 | 今晚的热线烧得发烫。全城的失眠者,都被接住了。 | 结局 e2(rep≥5 且回避)主句 | +| `e2_b` | 主播 | 只是路过留言机的时候,你还是没敢看它。红灯灭得很慢。 | e2 收(钩子) | +| `e3_a` | 主播 | 口碑掉了一夜。可那句迟到三年的晚安,今晚说出去了。 | 结局 e3(rep<5 且直面)主句 | +| `e3_b` | 主播 | 一个人的频道,也是频道。师父,收到了吗? | e3 收(钩子) | +| `e4_a` | 主播 | 今夜不眠,静音收播。有的夜班,谁也接不住谁。 | 结局 e4(rep<5 且回避)主句 | +| `e4_b` | 主播 | 留言机还在闪。你没看它,它也没怪你。明晚再说吧。 | e4 收(钩子;含 e4 复盘,不另配归因句) | +| `ep_lead` | 导播 | 收播了。导播间替你记了一笔账——今晚的人都怎样了。 | 结局链后尾声引入 | +| `ep_zhou_ok` | 导播 | 老周:孙子替他买了老花镜。回信说,谢谢那首歌。 | 尾声(bond_zhou=1) | +| `ep_zhou_no` | 导播 | 老周在便利店屋檐下坐到天亮。没出事,也没人听见他的歌。 | 尾声(bond_zhou=0) | +| `ep_deer_ok` | 导播 | 小鹿上车了。她妈后来打电话说,那声妈,比奖杯值钱。 | 尾声(bond_deer=1) | +| `ep_deer_no` | 导播 | 小鹿退了票。检票口的灯,记得那趟没上成的车。 | 尾声(bond_deer=0) | +| `ep_kid_ok` | 导播 | 糖糖家的门,这个月没再摔过。她说这是主播魔法。 | 尾声(bond_kid=1) | +| `ep_kid_no` | 导播 | 糖糖后来又打来过一次。还是那句:楼下在吵。 | 尾声(bond_kid=0) | +| `ep_wife_ok` | 导播 | 陈姐家切了蛋糕。那句「回来吧」,是我这晚听过最好的声音。 | 尾声(bond_wife=1) | +| `ep_wife_no` | 导播 | 陈姐在公车站坐到了末班车。那通电话,成了没寄出的信。 | 尾声(bond_wife=0) | +| `ep_han_ok` | 导播 | 老韩到家了。后视镜上,多了朵小红花。 | 尾声(bond_han=1) | +| `ep_han_no` | 导播 | 老韩在服务区睡了一觉。学费齐了,那首歌没放成。 | 尾声(bond_han=0) | + +--- + +**合计:132 句**(GPU 预算 ≤170 ✓) +- 双性别 103 句(HOST 21 + PROD 82)×2 性别 = 206 文件; +- NPC 平铺 29 句(老周 7 / 小鹿 4 / 糖糖 6 / 陈姐 7 / 老韩 5)= 29 文件; +- 合计 235 条音频 ×2 格式(ogg+m4a)= **470 文件**。 +- 每结局专属台词齐备:e1/e2/e3/e4 各 2 句(ep 组前 8 行)+ 尾声回响 11 句共用。 +- 每局实走抉择 8-9 个(p5 仅 p4=右、p7 仅 p6=左 可达),抉择提示 p1..p9 共 9 个。 + +## 多音字 / 发音风险自查注记 + +| 风险词 | 处理 | +|---|---| +| 重听 | 全表写「重新听」(sys_help),无「重听」字样 | +| 切(qiē/qiè) | 「切垫乐」写「换垫乐」(p7);「切蛋糕」「切了蛋糕」为正确常用读法,保留 | +| 挣(zhēng/zhèng) | 「挣钱」写「赚了朵小红花」(h1_talko) | +| 巷(xiàng/hàng) | 全表不用「巷」,写「路口」 | +| 答录机(dā lù jī) | 全表用「留言机」,规避 | +| 颤(chàn) | 台词侧用「发抖」(z1_a 触发/语境、instruct 描述不受影响,instruct 不进语音) | +| 哄(hōng/hǒng) | 「哄她睡」为 hǒng 常用读法,保留,试听确认 | +| 宿/血/露/降/率/传(zhuàn 义) | 全表未使用 | +| 了(le/liǎo)、还(hái/huán)、得(de/děi) | 常规句式常见读法,逐句人工试听项(z1_staye「听见了」、l1_goe 等) | +| 数字 | 句内数字全部写死(十一点半/十五分钟/三公里/四十分钟前/零点),序号用分档整句(prog_c1..c5) | + +## 台词侧自查 + +- 单句 ≤40 字:全表最长 = p9(37 字),次长 sys_help(26 字)✓;NPC 句平均 ≤18 字(P4 语速感靠短句+逗号,不用 instruct 语速词)✓。 +- NPC ≤5 人、speaker+instruct 恒定、区分度风险逐个标注 ✓。 +- 称呼合规:来电者对主播只用「主播/您/谢谢你」,性别化称呼仅指向来电者自身或第三方 ✓。 +- 每个结局(e1..e4)专属主句+钩子各 2 句齐备,尾声救/错过双线齐备 ✓。 +- 归因真话律:负向收尾句(z1_bade/k1_lulle/k1_liee/ep_*_no/e4_b)全部指向玩家实际选择,无假归因句 ✓。 +- 向导 gd_* 6 句(gd_brief/gd_continue/gd_again/gd_invalid/gd_ring1/gd_ring2)符合 14 号 §6 台词规范(一句一义、无重复句式、单句 ≤15 字)✓。 diff --git a/docs/design/15-radio.md b/docs/design/15-radio.md new file mode 100644 index 00000000..a07d0697 --- /dev/null +++ b/docs/design/15-radio.md @@ -0,0 +1,477 @@ +# 15 · 《不眠频率》(radio) — 深夜电台热线叙事 RPG 设计文档 + +> **给开发的实现要点摘要** +> 1. 叙事模式(声命线范式):节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;复制 `web/storyline/` 结构起步,状态机用 6 宏状态(INTRO/RING/ONAIR/BRIDGE/ENDING + MENU 覆盖层)。 +> 2. 手势零豁免:全部走全局默认映射(单击=继续/接听/跳句、双击=重听、左右滑=抉择、上滑=状态、下滑=模态菜单 §5.2 约定、长按=引擎快进)。 +> 3. 台词依据 [15-radio-voice-ids.md](15-radio-voice-ids.md)(132 句:双性别 103 + 平铺 NPC 29);NPC 人声电话滤波在**资产侧** ffmpeg 带通(storyline n03_msg 先例),引擎零改动。 +> 4. 向导层:storyline 同款看门狗节奏 + RING 专用两级新句提示;`gd_ring1/gd_ring2` 为本游戏新增 gd_* 资产。 +> 5. 章程任务 N1(research/new8/00-program.md):单局 15-25 分钟、9 抉择点、4 结局、分支局部化。 + +## 1. 概述 + +- **一句话**:你是午夜档电台热线主播「今夜不眠」,直播间里一晚接进五个来电——你说什么话、接不接、转不转接,改变五个陌生人的夜晚,也改变你自己。 +- **题材(声音即世界)**:全部叙事靠电话音质感(每位来电者一种滤波声)、电台垫乐、直播间环境音呈现——来电者永远不露面,只有声音;「声音即存在」与纯音频载体天然同构。 +- **单局**:一个完整夜班(一条抉择路径)15-22 分钟;五个来电共享主干,分歧点局部化在每通电话内部;9 个抉择点(单局实走 8-9 个),4 结局。 +- **核心循环**:接听 → 听 → 抉择(左=走心介入 / 右=克制程序)→ 结果与口碑结算 → 幕间喘息 → 下一通。跨来电积累「今晚状态」(听众口碑 + 各来电好感),收播前一个抉择决定你自己的心结是否解开。 + +## 2. 状态机 + +### 2.1 状态列表 + +| 状态 | 内容 | BGM | guide.state | +|---|---|---|---| +| `INTRO` | 开场链:台标口播 + 一句规矩 + 今晚目标 | `bgm/onair` | explore | +| `RING` | 热线接入:铃声循环,等待单击接听 | `bgm/onair`(不切) | 无新签名(turn cue 承担) | +| `ONAIR` | 通话叙事:对白链 + 抉择点 + 结果 | `bgm/onair` | explore;抉择点=choice | +| `BRIDGE` | 幕间:口碑跨档播报(如有)+ 喘息垫乐,自动链 | `bgm/bridge` | settle | +| `ENDING` | 结局链 + 尾声回响(epilogue),结算态 | `bgm/ending` | settle | +| `MENU` | 模态覆盖层(任意叙事态下滑呼出,暂停一切链) | `bgm/menu` | menu | + +### 2.2 转移图(文本) + +``` + ┌─────────────── 结算单击=新夜班 ───────────────┐ + ▼ │ + INTRO ──链完──> RING(c1) ──单击接听──> ONAIR(c1) ──链完──> BRIDGE ──┤→ RING(c2) + │铃声循环/看门狗两级,无超时惩罚 │ …(c3,c4 同型)… + └────────────────────────────────────────────────┘ + ONAIR(c5) ──链完──> BRIDGE ──链完──> SIGNOFF + │ + └──> ◆p9 抉择 ──> d9 结果链 ──> ENDING(e1..e4 + ep 回响链) + + 任意叙事态(INTRO/RING/ONAIR/BRIDGE/ENDING):下滑 → MENU(模态) + MENU:单击=循环切换选项 → 双击=确认执行 → 上滑=帮助 → 下滑/「回到直播」项=关闭回原状态(重播当前节点) +``` + +- INTRO/RING/ONAIR/BRIDGE/ENDING 均为节点链驱动(storyline playNode 模式);`SIGNOFF` 是 ONAIR 的最后一个节点组(◆p9),不单设状态。 +- BRIDGE 自动链 ≤12s:口碑跨档则播 rep_*(播报真实档位,账目真实),随后直接进下一通 RING。 +- MENU 打开时 `guide.disarm()` + 取消一切看门狗/自动链;关闭后重播当前节点(局面复述,storyline closeMenu 先例)。 + +### 2.3 每状态手势映射(6+2 全覆盖,零豁免) + +| 手势 | INTRO | RING | ONAIR | BRIDGE | ENDING | MENU | +|---|---|---|---|---|---|---| +| 上滑 | 帮助+状态,随后重播当前段 | 状态播报(第几通+口碑档),留在 RING | 帮助+状态,随后重播当前节点 | 帮助+状态,自动链继续 | 帮助+状态(结局名+口碑) | 帮助(sys_help) | +| 下滑 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 关闭菜单 | +| 左滑 | 无效 → error 震+gd_invalid | 无效 → gd_invalid | 抉择=左(走心) | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 | +| 右滑 | 无效 → gd_invalid | 无效 → gd_invalid | 抉择=右(克制);存档询问态=新夜班 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 | +| 单击 | 播放中=跳过本句;链末=进 RING | **接听** | 播放中=跳过本句(p* 不可跳);链末=继续 | 跳过本句(自动链) | **再来一局**(新夜班) | 循环切换选项 | +| 双击 | 重听当前段 | 重听上一通话的收尾句 | 重听当前节点台词(含抉择提示) | 重听幕间链 | 重听结局链 | **确认执行**(菜单约定覆盖重听) | +| 长按 | 引擎接管:旁白临时 1.7×(12 号 §2.2,叙事类不豁免) | 同左 | 同左 | 同左 | 同左 | 同左(快进菜单语音) | +| 长按松开 | 引擎接管:恢复 settings.speed | 同左 | 同左 | 同左 | 同左 | 同左 | + +- 豁免登记:**无**。六个基础手势全部符合全局宪法默认语义(单击=确认/继续/**接听**,宪法 §5.1 原文即含「接听」);菜单走 §5.2 R3 模态约定(单击循环+双击确认),不引用 rpg 探索态豁免。 +- 抉择 prompt(voice/p1..p9)播放中单击不可跳、左右滑缓存 `pendingChoice` 播完即生效(storyline 盲玩公平先例)。 +- 无效手势零静默:所有「无效」格均有 error 震 + gd_invalid(onceKey 同局限次,F1.4 idle-only:语音在播时跳过语音只发震)。 + +## 3. 抉择树 + +### 3.1 全局骨架(来电共享主干,分歧局部化) + +``` +INTRO: b_open → b_rain → b_rule → b_goal ──> 铃① +【C1 老周·走丢的老人】教学通 · 2 抉择 ──> BRIDGE ──> 铃② +【C2 小鹿·想退票的年轻人】单抉择 · 呼吸口 ──> BRIDGE ──> 铃③ +【C3 糖糖·失眠的孩子】2 抉择 · 埋 heardArgument ──> BRIDGE ──> 铃④ +【C4 陈姐·争吵的夫妻】2 抉择 · 消费 heardArgument ──> BRIDGE ──> 铃⑤ +【C5 老韩·雨夜司机】高潮 · 1 抉择 + 危机脚本拍 ──> 签收 ◆p9(心结)──> ENDING +``` + +分支纪律:**每通电话内部分支、通话末尾收敛**到导播收尾句 → BRIDGE;跨来电只传三个数据(rep 数值、bond 好感、heardArgument 布尔),不生成组合节点。最坏路径与最好路径节点数相同。 + +### 3.2 分通结构(◆=抉择点,〔〕=条件差分) + +``` +【C1 老周】 +铃① → z1_ring_in → z1_a → z1_b → ◆p1 + ├左 哼老歌: z1_song → z1_songo → z1_sdone ─┐ + └右 问路牌: z1_ask → z1_asko → z1_adone ───┤ + ▼ + ◆p2 + ├左 陪他等: z1_stay → z1_stayo → z1_staye ─┐ + └右 转接: z1_pol → z1_polo → z1_pole ────┤ (仅 p1=右 时 p2=右 改走 z1_bad → z1_bade 归因) + ▼ + z1_ok1(首通确认,全局一次)→ BRIDGE + +【C2 小鹿】 +铃② → l1_in → l1_a → l1_b → ◆p3 + ├左 劝上车: l1_go → l1_goo → l1_goe ──┐ + └右 还自由: l1_free → l1_freeo → l1_freee ─┴→ BRIDGE + +【C3 糖糖】 +铃③ → k1_in → k1_a → k1_b(垫 arg_muffled 争吵声)→ ◆p4 + ├左 哄睡: k1_lull → k1_lullo → k1_lulle ──────────────┐(错过线索,本通结束) + └右 问她: k1_ask → k1_asko → ◆p5 │ + ├左 真话: k1_truth → k1_trutho → k1_truthe ────────┤(heardArgument=true) + └右 谎言: k1_lie → k1_lieo → k1_liee ──────────────┴→ BRIDGE + +【C4 陈姐】 +铃④ → w1_in → w1_a → w1_b → ◆p6 + ├左 替孩子带话:〔heardArgument ? w1_say 组 : w1_say2 组〕→ ◆p7 + │ ├左 留在直播: w1_air → w1_airo → w1_aire ─┐ + │ └右 换垫乐私聊: w1_priv → w1_privq → w1_prive ─┤ + └右 劝她回家: w1_home → w1_homeo → w1_homee ──────────┴→ BRIDGE(右路本通结束,无 p7) + +【C5 老韩】高潮 +铃⑤ → h1_in → h1_a → h1_b → ◆p8 + ├左 陪聊闺女: h1_talk → h1_talko → 【危机拍: danger 签名+brake_screech(前置2s)】 + │ → h1_danger → h1_dango → h1_talke ─┐ + └右 服务区: h1_stop → h1_stopo → h1_stope ────────┤ + 〔bond_zhou=true 时插播 h1_echo 回响句〕 ▼ + BRIDGE → g9_in → ◆p9 + ├左 直面(播出老主播的晚安): d9_face → d9_facee → knot=解 + └右 回避(照常收播): d9_avoid → d9_avoide + ▼ +ENDING: e*_a → e*_b(按 rep×knot 四选一)→ ep_lead → ep_×5(按 bond 选播) +``` + +### 3.3 分支局部化说明 + +- 跨来电状态只有三个标量:`rep`(数值)、`heardArgument`(布尔)、`bond`(5 个 0/1)。没有任何「来电 i 的分支 × 来电 j 的分支」组合节点。 +- 唯一的跨来电内容差分:C4 的 w1_say/w1_say2 两组(由 heardArgument 选播)与 C5 的 h1_echo 插播句(由 bond_zhou 选播)——都是**节点内选播**,不改树形。 +- 树规模:132 句台词、约 60 个节点,任一路径实走 8-9 个抉择点(C3 左路/ C4 右路各少一个)。 + +### 3.4 四结局达成条件 + +| 结局 | 条件 | 主题 | 开环钩子 | +|---|---|---|---| +| e1 天亮之前 | rep ≥ 5 且 p9=左(直面) | 城市有了心跳,话筒接住了 | 「明晚零点,接着不眠」 | +| e2 热线常亮 | rep ≥ 5 且 p9=右(回避) | 满座夜班,留言机红灯未看 | 红灯「灭得很慢」 | +| e3 一个人的频道 | rep < 5 且 p9=左(直面) | 夜班难,但晚安说出去了 | 「师父,收到了吗」 | +| e4 静音 | rep < 5 且 p9=右(回避) | 谁也接不住谁的夜班 | 「明晚再说吧」 | + +- 尾声回响(P2 回响感):epilogue 对 5 个来电者各播一句 bond ? 救到的后续 : 错过的后续(ep_zhou/deer/kid/wife/han_ok/no),玩家每晚的救与错过都被点名。 + +## 4. 数值设计 + +### 4.1 变量 + +| 变量 | 类型 | 初值 | 说明 | +|---|---|---|---| +| `rep`(听众口碑) | int | 0 | 抉择与结果积累,收播时结算档位 | +| `knot`(心结) | bool | false | 仅由 p9=左 置 true | +| `bond_zhou/deer/kid/wife/han` | 0/1 | 0 | 只影响尾声回响选播,不影响结局 | +| `heardArgument` | bool | false | p4=右 置 true,C4 消费 | +| `nights` / `endings{}` | int / map | — | 重玩计数与结局收集(存档展示用) | + +### 4.2 抉择计分表(左=走心 / 右=克制) + +| 抉择 | 左(走心) | 右(克制) | 备注 | +|---|---|---|---| +| p1 哼歌/问路 | +1 | 0 | 左路老周稳住(首个正反馈) | +| p2 陪等/转接 | +2(p1=左)/ +1(p1=右) | +1(p1=左)/ **-1(p1=右,砸)** | 唯一负分组合,触发归因句 | +| p3 劝上车/还自由 | +2 | +1 | 两个选项都是体面的(教学后建立信任) | +| p4 问她/哄睡 | +1,heardArgument=true | 0 | 左=倾听,右=敷衍即错过线索 | +| p5 真话/善意谎言 | +1 | 0 | p5 仅 p4=右 可达 | +| p6 替孩子带话/劝回家 | +2(heardArgument)/ +1(否则) | +0(本通结束) | | +| p7 留在直播/换垫乐 | +2 | +1 | p7 仅 p6=左 可达 | +| p8 陪聊/服务区 | +1(含危机拍) | 0 | 两路都活着(P4 零死亡) | +| p9 直面/回避 | knot=解 | knot=未解 | 不动 rep | + +### 4.3 阈值与导向 + +- 收播结算:`rep ≥ 5` = 高口碑(e1/e2),`rep < 5` = 低口碑(e3/e4)。 +- 理论上限 12、全克制路径 0;「多数走心 + 容忍 1-2 次失误」落在 5-8 档——阈值 5 把「今晚是否立住」压在玩家自己的重量上,而非单点失误。 +- 口碑跨档被动播报(BRIDGE):进入 ≥5 区间播 rep_high、跌回 1-4 播 rep_mid、≤0 播 rep_low——分档整句、账目真实(A4 资源账目律)。 +- 归因真话律落地:每个负向结果节点(z1_bade、k1_lulle、k1_liee、e4 链)的导播收尾句必须指向玩家的**实际选择**(「地址没报清就转接」「你没听见她在怕什么」),禁止「运气不好」类假归因;成功路径不播任何补救教学(A5)。 + +## 5. 音频三层映射 + +### 5.1 BGM(状态→曲目,Mureka instrumental,4 首 ≈ $0.12) + +| 状态 | 曲目 | 切换 | Mureka prompt 草稿(英文,seamless loop) | +|---|---|---|---| +| INTRO/RING/ONAIR | `bgm/onair` | 开局淡入 800ms,全夜班不切;语音 duck 引擎自理 | "Midnight radio broadcast music bed, warm lo-fi neo-soul chords, soft vinyl crackle, gentle night-drive groove with brushed drums, intimate late-night host mood, no vocals, seamless loop, 60-90 seconds" | +| BRIDGE | `bgm/bridge` | crossfade 800ms 进;进下一通 RING 淡回 onair 600ms | "Radio interlude pad, sparse felt piano notes over warm analog drone, distant rain mood, lonely but safe, no drums, no vocals, seamless loop, 60-90 seconds" | +| MENU | `bgm/menu` | crossfade 500ms 进;关菜单淡回原状态曲 800ms | "Late-night radio studio menu pad, warm minor pad with faint tape hiss, static harmony, very sparse, no vocals, seamless loop, 60-90 seconds" + 共性后缀(10 号 §8) | +| ENDING | `bgm/ending` | p9 结果链播完后淡入 1500ms | "Dawn sign-off theme, soft piano and warm strings, hopeful sunrise after a long night shift, gentle resolution, no vocals, seamless loop, 60-90 seconds" | + +- `bgm=0` 时全部静默(引擎约定);menu pad 与 onair 同调性优先。 + +### 5.2 SFX 清单(Stable Audio 3,one-shot 真峰值 ≤-3dBFS;amb 循环由游戏层 buffer.loop 实现,escape amb 先例) + +| id | 用途 | Stable Audio prompt(英文) | +|---|---|---| +| `sfx/onair_sting` | 红灯亮起台标 sting(INTRO/重开) | "Short broadcast on-air light sting, warm chime with vintage radio click, 1 second" | +| `sfx/phone_ring` | 热线铃(RING 循环体,每遍 ≥4s 间隔) | "Old studio telephone bell, two-strike bright metallic ring, clean" | +| `sfx/line_connect` | 接听咔哒+线路接通音 | "Telephone line connect click and short dial tone fade, subtle" | +| `sfx/line_drop` | 挂断/转接忙音 | "Telephone hang-up click with short busy tone, soft" | +| `sfx/amb_studio` | 直播间底噪 loop(调音台嗡+空调) | "Quiet radio studio room tone, console hum and soft air conditioning, seamless loop" | +| `sfx/amb_rain_city` | C1 老人侧:街道雨夜 loop | "Heavy rain on city street at night, distant traffic hiss, seamless loop" | +| `sfx/amb_station` | C2 小鹿侧:深夜车站 loop | "Empty train station hall at night, distant echo and rolling suitcase, seamless loop" | +| `sfx/amb_kid_room` | C3 糖糖侧:儿童房 loop | "Quiet child bedroom at night, soft lamp buzz, muffled house silence, seamless loop" | +| `sfx/arg_muffled` | C3 线索音:闷住的争吵+摔门(信息承载,k1_b 时起) | "Muffled argument behind a door, dull thud of door slam, indistinct voices, 4 seconds" | +| `sfx/amb_street_rain` | C4 陈姐侧:公车站雨 loop | "Rain at an empty bus stop at night, distant wet road traffic, seamless loop" | +| `sfx/amb_cab` | C5 老韩侧:车内 loop(引擎+雨刷节奏) | "Inside a moving truck cab at night, steady engine hum, rhythmic windshield wipers, rain outside, seamless loop" | +| `sfx/brake_screech` | C5 危机拍:急刹+护栏摩擦 | "Sudden brake screech on wet asphalt, brief tire friction, 2 seconds" | +| `sfx/tape_static` | p9 直面:留言机电流声 | "Old cassette tape static and tape hiss with a click stop, 3 seconds" | +| `sfx/ui_confirm` | 菜单/接听确认(程序合成兜底) | —(程序合成,同全局先例) | +| `sfx/ui_menu` | 菜单呼出(程序合成兜底) | —(程序合成) | + +- C3/C4 的 amb 是**信息层**:arg_muffled 在 k1_b 起播 2-3 次,玩家听没听见由 p4 抉择兑现(声音即世界)。 +- 预警窗纪律:h1_danger 前 2s 先起 brake 渐强(L2 前置预警),danger 签名与 brake 同窗。 + +### 5.3 Voice 划分(详见 15-radio-voice-ids.md) + +| 组 | 声部 | 落位 | 数量 | +|---|---|---|---| +| 主播(你)HOST | 双性别(female=Serena / male=Uncle_Fu + HOST instruct 常量) | `voice/female|male/` | 21 句 | +| 导播/系统/向导 PROD | 双性别(female=Vivian / male=Dylan + PROD instruct 常量) | `voice/female|male/` | 82 句 | +| 来电者 NPC ×5 | 平铺单音色(老周=Uncle_Fu / 小鹿=Serena / 糖糖=Vivian / 陈姐=Vivian / 老韩=Eric),**不做双性别** | `voice/` 根(平铺回退命中)+ 电话滤波转码 | 29 句 | + +- NPC ≤5 个约束满足;玩家自己的声音跟随全局 `settings.voice`(男/女主播皆可代入),导播耳语是向导人设(L4 角色化系统声)。 +- 合计 132 句(≤170 GPU 预算);音频文件 = 103×2 + 29 = 235 条(×2 格式 = 470 文件)。 + +## 6. 来电音质感设计(声音即世界的实现) + +### 6.1 引擎能力边界(对照 web-engine-api.md) + +| 能力 | 引擎现状 | 方案 | +|---|---|---| +| voice 运行时 EQ/滤波 | ❌ voice 链 = SoundTouch → gain → voiceBus,无滤波插入点 | **资产侧转码滤波**(storyline n03_msg/n05_bcast 的 convert.sh 带通先例):ffmpeg `highpass + lowpass + 压缩器` 逐来电者定档 | +| voice 逐句声像/空间定位 | ❌ voice 单声道总线 | 不使用;空间感交给 sfx amb(sfx 资产内 pan,escape amb 先例) | +| sfx 循环 | sfx 即发即弃,无 loop 选项 | 游戏层 `buffer.loop=true` 或 onEnded 重触发(escape amb_loop 先例) | +| 语音 duck 垫乐 | ✅ 内置(duck 20%,300ms) | 直接依赖 | +| 运行时混响/失真(「信号变差」差分) | ❌ | 降级:用 amb 响度强弱 + 台词停顿节奏表达,不做动态滤波 | +| 变速不变调 | ✅ SoundTouch(settings.speed + 长按 1.7×) | 直接依赖 | + +### 6.2 每位来电者的声音签名(资产侧 ffmpeg 参数) + +| 来电者 | 滤波档 | 质感设计 | 背景 amb(sfx 层) | +|---|---|---|---| +| 老周 | highpass 500 + lowpass 2800 + 轻压缩 | 老旧手机,窄而颤抖 | amb_rain_city | +| 小鹿 | highpass 400 + lowpass 3400 | 车站手机,干净偏冷 | amb_station | +| 糖糖 | highpass 450 + lowpass 3000 | 家用座机,小而近 | amb_kid_room + arg_muffled | +| 陈姐 | highpass 420 + lowpass 3100 | 户外手机,风噪 | amb_street_rain | +| 老韩 | highpass 300 + lowpass 3400 | 车载蓝牙,宽而轰 | amb_cab | + +- **可读性底线**(L2 反模式「写实但不可读」):滤波保持在语音可懂频带内,宁窄勿脏;QC 试听不可读的句子回退**未滤波原声**——叙事不破,只损失质感(显式降级方案,不阻塞管线)。 +- 主播/导播人声**不滤波**(直播间级音质);「电话里的人」与「身边的人」靠这一层音质差立住,无需任何视觉。 + +## 7. 向导层接入 + +### 7.1 状态签名 earcon / turn cue + +| 时机 | 向导动作 | 说明 | +|---|---|---| +| INTRO 进入 | `guide.state('explore')` | 叙事态签名 | +| 每个抉择点 p* 起播 | `guide.state('choice')` | 选项句本身即 turn cue(播完当刻 choiceArmed 即真,W2 契约零插播) | +| RING 铃响 | `guide.turn({haptic:true})`(无语音) | 铃声+turn 签名=「轮到你接听」;首次铃前 b_goal 已口播规矩 | +| BRIDGE 进入 | `guide.state('settle')` | 喘息/结算签名 | +| ENDING 进入 | `guide.state('settle')` | 同上 | +| MENU 打开 | `guide.state('menu')` | 菜单签名 | +| C5 危机拍 | `guide.state('danger')` | 全局唯一 danger 使用点(刹车前 2s,与 brake_screech 同窗) | +| 无效手势 | `guide.error({voice:'gd_invalid', onceKey})` | error 震 + full 档语音,idle-only(F1.4) | + +- earcon 词表:只用全项目统一 8 音(menu/explore/battle/choice/settle/danger/turn/nudge),**零新增**;battle 本游戏不用。开场台词自然教 3 个最高频签名(铃=接听、短促提示音=轮到你选择、上滑/下滑口令);settle/menu 首遇即学;逐签名口播教学列 v2 候选(14 号 §4 同款)。 + +### 7.2 看门狗参数(秒;任何手势经 guide.disarm 复位;guide=off 全不挂) + +| 等待点 | full 档 | standard 档 | 级别内容 | 礼仪 | +|---|---|---|---|---| +| RING 无接听 | [10, 25] | [10, 25](onEscalate 游戏层补播) | ① gd_ring1「热线在闪。单击,接听。」② gd_ring2「灯还亮着,听众在等。」 | **两级不同句**(绝不逐字重播);两级用尽只剩铃声循环,永不惩罚 | +| ONAIR/INTRO/BRIDGE 叙事链末等待 | [25, 50] | [13, 30](同 storyline P2-3 先例) | ① gd_continue ② gd_again | storyline F1.1 同款,两级后彻底静默 | +| 抉择点 p* 播完无操作 | [25, 50] | [25, 50] | 重播**当前 p* 提示句本身**(复述选项=合法帮助,13 号 §16 N3 先例) | 两级后静默;空点单击=重挂计时 | +| ENDING 结算等待 | [25, 50] | [13, 30] | gd_continue / gd_again | 结算单击即重开,不阻拦 | + +- 向导插播一律 `queue:true`,绝不顶掉在播对白;error/watchdog 类语音 idle-only(在播则跳过且过期丢弃,F1.4)。 +- 单句语音看门狗(资产缺失兜底):30s 超时且 `isVoicePlaying()===false` 才自动续链,长句续约不掐(storyline N1 先例照抄)。 + +## 8. 存档设计 + +### 8.1 进度 data 结构(PUT /api/progress {game:'radio'}) + +```json +{ + "schema": 1, + "updatedAt": "2026-09-01T12:00:00Z", + "summary": "第3通·失眠的孩子 · 口碑+2 · 结局1/4", + "phase": "ONAIR", + "node": "k1_asko", + "rep": 2, + "knot": false, + "heardArgument": true, + "bond": { "zhou": 1, "deer": 1, "kid": 0, "wife": 0, "han": 0 }, + "endings": { "e2": true }, + "nights": 3 +} +``` + +- 写入时机:每通电话 RING 进入、每个抉择结算后、ENDING 进入、菜单「保存并回大厅」。 +- 断点续玩:登录/本地读到存档 → `sys_continue_ask`(单击=继续上次 / 右滑=新夜班);继续 = 恢复 phase+node 并**重播当前节点链** = 一句话局面复述(L5:恢复成本游戏承担);重进 ≤5s 可玩。 +- summary 字符串格式:进行中 `第N通·<来电者> · 口碑±X · 结局K/4`;已收播 `已收播·<结局名> · 结局K/4`(大厅卡片直接显示,人话)。 + +## 9. playtest 断言清单(登记 13 号文档 radio 节,格式同 §1) + +**关键路径**(E1 全程,≤60 次手势): +1. 启动遮罩点按 → INTRO 链(b_open 起,voice 层有播放记录)→ 链完自动进 RING(c1),turn 事件与铃声断言。 +2. RING 单击接听 → ONAIR(c1):p1 左 → p2 左 → z1_ok1 → BRIDGE(rep 断言 +1+2=3)→ RING(c2)。 +3. 依序走 p3 左 / p4 右 / p5 左 / p6 左 / p7 左 / p8 左(断言每步后 rep 与 §4.2 一致;p4 右后 `heardArgument === true` 且 C4 播 w1_say 组)。 +4. p9 左 → knot=true → ENDING(e1):结局链 + ep_lead + 5 条 ep_*_ok → 结算态单击重开回 INTRO。 + +**边界断言**: +- **四结局可达**:剧本 B(路径同上但 p9 右)→ e2;剧本 C(p1右 p2左 p3右 p4右 p5右 p6右 p8右 p9左,rep=3)→ e3;剧本 D(p1右 p2右 p3右 p4左 p6右 p8右 p9右,rep=0)→ e4;各局 rep 终值与设计表一致。 +- **双击重听**:ONAIR/ENDING 播完态双击 → 当前节点链同 id 序列重播;重播期间单击=跳本句(nodeId 不变),播完回原等待态。 +- **菜单返回不丢节点**:ONAIR 中下滑开菜单 → 上滑听帮助 → 双击「回到直播」→ phase/node 不变、当前节点重播;菜单内单击循环顺序 = [回到直播, 重新开始今晚, 保存并回大厅];确认回大厅 → sys_menu_lobby 播出且 PUT/localStorage 有最新 data。 +- **中断复述**:杀进程重进(存档 node=k1_asko)→ sys_continue_ask → 单击 → 重播该节点链;上滑播 sys_help+prog_c*+rep_* 链(顺序断言)。 +- **看门狗**:RING 快进 10s → gd_ring1、25s → gd_ring2(断言第二级 id ≠ 第一级 id,绝不逐字重播;两级后仅铃声);ONAIR 链末 25s/50s → gd_continue/gd_again;p* 播完 25s/50s → 当前 p* 重播两次后静默;`settings.guide='off'` 时全程零定时器零向导事件。 +- **pendingChoice**:p* 播放中左右滑 → 播完立即生效(不丢、不重复)。 +- **p* 不可跳**:p* 播放中单击 → 不跳句(PROMPT_RE 门)。 +- **idle-only**:向导 gd_* 在对白在播时被跳过且不再补播(F1.4 契约);queue 句排在当前句后(在播句不被截断)。 +- **归因真话**:剧本 D 中 z1_bade 播出(含真实归因);剧本 A 全程无任何归因/补救句(成功不教学)。 +- **资产缺失**:屏蔽任意 voice 资产 → 30s 看门狗自动续链到结局,不卡死。 +- **数字规范**:prog_*/rep_* 全部整句,转写审计无运行时拼接碎片。 +- **坏音质降级**:滤波资产缺失时原声可播(回退路径不断链)。 + +**失败即不通过**:任何等待 >5s 无声(静默死局);无效手势零反馈;结局计数与 §3.4 不符。 + +## 10. 难度 / 节奏曲线 + +| 段 | 内容 | 目标时长 | 设计目标 | +|---|---|---|---| +| 开场 | 台标 + 一句规矩 + 第一声铃 | 1.5-2 min | 首输入 <60s(链 ~30s 后铃响即接听);规矩一句讲完(P4) | +| C1 老周 | 教学通:2 个二选一,条条有出路 | ~3.5 min | **首个正反馈 ≤3 min**(p1 左 → 老周声音稳住 + 导播确认);唯一 -1 组合不扣资源(学习期零惩罚) | +| C2 小鹿 | 单抉择重话术 | ~2.5 min | 节奏呼吸口;建立「两个选项都体面」的信任 | +| C3 糖糖 | 双抉择 + amb 线索埋设 | ~3.5 min | 环境音首次承载剧情(声音即世界);错过线索可被尾声点名 | +| C4 陈姐 | 最复杂:2 抉择 + 条件差分 | ~4 min | 情感高潮;选项句最长(p7 ≤40s 播完) | +| C5 老韩 | 高潮:危机拍(danger+急刹,前置 2s 预警) | ~4 min | 紧张峰值但零死亡、零倒计时(P4);h1_echo 兑现 C1 回响 | +| 签收+结局 | p9 + 结局链 + 5 句回响 | ~3 min | 心结抉择 + epilogue 回响感(P2);开环钩子拉下一局 | + +- 合计 ≈ 21-22 min(15-25 min 达标);每 30-60s 一个反馈颗粒(导播短句/结果句/口碑档),无沉默尴尬期(P3)。 +- 难度曲线 = 信息复杂度(1 抉择 → 条件差分 → 双方对峙 → 生死时速),操作复杂度恒定(永远二选一)——P4「规则一遍听懂」由恒定操作面保证。 + +## 11. 自检清单 + +| # | 硬约束 | 落地 | ✓ | +|---|---|---|---| +| 1 | FTUE 首输入 <60s | INTRO 链 ~30s 后第一声铃即接听(§10) | ☐ | +| 2 | FTUE 首个正反馈 ≤3min | p1 左 outcome ~2.5min(z1_songo+z1_sdone) | ☐ | +| 3 | 学习期零惩罚 | C1 全组合不扣资源;RING 不接听无超时惩罚 | ☐ | +| 4 | 失败三问归因 + 归因真话律 | z1_bade/k1_liee/e4 链指向实际选择;§4.3 禁假归因 | ☐ | +| 5 | 手势宪法(6 手势默认语义,零豁免) | §2.3 全覆盖;单击=确认/继续/接听、菜单走 §5.2 模态约定 | ☐ | +| 6 | 长按豁免显式登记 | 无豁免:长按=引擎快进(12 号叙事类不豁免),§2.3 登记在案 | ☐ | +| 7 | 向导层:状态签名/turn cue/看门狗 | §7.1/§7.2;RING 两级**不同句**,绝不逐字重播 | ☐ | +| 8 | 插播一律 queue 不顶对白;纠错 idle-only 过期丢弃 | §7.2(F1.4 契约) | ☐ | +| 9 | 提示永不逐字重复 | gd_ring1≠gd_ring2;gd_continue→gd_again;抉择点复述 p* 为 13 号 §16 先例 | ☐ | +| 10 | 成功后不灌补救教学 | 归因句只挂负向结果(§9 断言) | ☐ | +| 11 | 单次播报 ≤3 选项 | 全部抉择恒 2 选项 | ☐ | +| 12 | 并发声流 ≤2 | voice+amb 两流;无第二 voice 通道 | ☐ | +| 13 | earcon ≤7 个、显式教一次、语义靠语音 | 只用统一 8 音中的 6 个(battle 不用),零新增;选项内容全语音 | ☐ | +| 14 | 台词一句一文件整句、≤40 字、数字分档整句、无运行时拼接 | voice-ids 全表 ≤39 字;prog/rep 分档整句 | ☐ | +| 15 | 多音字规避(重新听)、同游戏同 instruct 常量、无语速词 | voice-ids 头部注记与 instruct 常量表 | ☐ | +| 16 | P1:可打断/重听、status key、菜单规整、不当白痴 | skipVoice/doubletap/上滑状态链/模态菜单;成功零重复教学 | ☐ | +| 17 | P2:头 3 分钟见效、结局回响 | C1 即时改变对方情绪;ep_* 救/错过点名 | ☐ | +| 18 | P3:单手免视、一句话复述局面、3min 反馈颗粒 | 全手势免视;重进重播当前节点;BRIDGE 口碑播报 | ☐ | +| 19 | P4:规则一遍懂、高容错、无高压倒计时、NPC 语速感 | 一句规矩;零死亡零倒计时;NPC 句 ≤18 字+逗号停顿(不用 instruct 语速词,禁令遵守) | ☐ | +| 20 | 抉择树 4 结局可走通 | §9 剧本 A-D 四条实路径 + rep 断言 | ☐ | +| 21 | 单局 15-25 min | §10 曲线 ≈21-22 min | ☐ | +| 22 | 分支不爆炸 | 跨来电仅 3 标量;树 ~60 节点固定深度(§3.3) | ☐ | +| 23 | 台词 ≤170 句、NPC ≤5 | 132 句 / 5 个来电者(voice-ids 账目) | ☐ | +| 24 | 引擎边界诚实 | §6.1 能力表:做不到的写明资产侧/降级方案 | ☐ | + +--- + +## 实现核对(测试 agent 登记) + +> 断言实现:`web/radio/test/playtest.mjs`,193 条,`tools/node/bin/node web/radio/test/playtest.mjs` +> 退出码 0 全绿。会话剧本:`web/harness/sessions/radio.mjs`(`session` 主线完整弧线 + +> `sessionStress` 乱按压力),经 `tools/node/bin/node web/harness/transcribe.mjs radio +> [--session sessionStress]` 跑通(transcribe.mjs 已登记 radio/支持 `--session` 选剧本)。 +> 回归:六游戏 playtest + guide.test 全绿不受影响。 + +### §9 关键路径逐条 + +| # | 断言 | 状态 | +|---|---|---| +| 1 | 遮罩点按 → INTRO 链(b_open 起)→ 链完自动进 RING(c1),turn 事件与铃声 | ✅已断言(INTRO 四句逐句计数 + turn 事件 + phone_ring 循环) | +| 2 | 单击接听 → p1 左 → p2 左 → z1_ok1 → BRIDGE(rep=1+2=3)→ RING(c2) | ✅已断言 | +| 3 | p3 左/p4 右/p5 左/p6 左/p7 左/p8 左逐点 rep 与 §4.2 一致;p4 右 heardArgument=true 且 C4 播 w1_say 组 | ✅已断言 | +| 4 | p9 左 → knot=true → ENDING(e1) + ep_lead + 5 条 ep_*_ok → 结算单击重开回 INTRO | ✅已断言(含手势预算 55≤60) | + +### §9 边界断言逐条 + +| 断言 | 状态 | +|---|---| +| 四结局可达(剧本 B→e2 / C→e3 / D→e4,rep 终值与设计表一致) | ✅已断言(另补剧本 E:全左但 p4 左 → heardArgument=false → C4 播 w1_say2 组、rep=9,补齐 w1_say/w1_say2 双差分覆盖;剧本 C/D 额外断言带话差分不播与 ep_deer_no) | +| 双击重听:ONAIR 播完态/ENDING 播完态同 id 序列重播,重播期间单击=跳句 | ✅已断言 | +| 菜单返回不丢节点;单击循环顺序 [回到直播, 重新开始今晚, 保存并回大厅];确认回大厅播 sys_menu_lobby 且 localStorage 有最新 data | ✅已断言(含报名 queue 零截断顺序断言) | +| 中断复述:存档 node=k1_asko → sys_continue_ask → 单击恢复 → 重播节点链;上滑 sys_help→prog_c3→rep_mid 顺序 | ✅已断言(另补 ASK 右滑=新夜班) | +| 看门狗:RING 10s/25s 两级不同句、两级后仅铃声;ONAIR 链末 25s/50s gd_continue/gd_again;p* 25s/50s 复述两级后静默;ENDING 25s/50s;guide=off 全程零定时器零向导语音 | ✅已断言(full 档事件钩子计数,choice 签名 ×9) | +| pendingChoice:p* 播放中滑动感存、播完即生效(不丢不重复) | ✅已断言 | +| p* 不可跳(PROMPT_RE 门) | ✅已断言 | +| idle-only:对白在播时纠错跳过且不占 onceKey(F1.4);queue 句不顶在播句 | ✅已断言(skipped 事件 + 补播对照 + 菜单报名顺序) | +| 归因真话:剧本 D 播 z1_bade;剧本 A/E 全程零归因/补救句 | ✅已断言(含 k1_lulle/k1_liee 挂点) | +| 资产缺失:屏蔽 voice → 30s 看门狗续链到结局不卡死 | ✅已断言 | +| 数字规范:prog_*/rep_*/gd_ring* 分档整句字面量、无运行时拼接 | ✅已断言(静态审计) | +| 坏音质降级:滤波资产缺失回退原声 | 合并(回退是资产侧转码工序,代码无分支;NPC 性别目录→平铺回退由静态审计覆盖) | +| 失败即不通过(>5s 无声死局 / 无效手势零反馈 / 结局计数与 §3.4 不符) | ✅已断言(无效手势 error 震逐次计数;四结局矩阵即计数核对) | + +### 本轮修复的实现 bug(web/radio/game.js) + +1. **ONAIR 叙事链末不等待(规格 vs 实现最大偏差)**:`_onNodeSeqDone` 无条件 `advance(node.next)`, + 节点表的 `auto:true` 标志(INTRO 链/危机拍/签收链/p9 结果链)从未被读取——与 §2.3「链末=继续」、 + §7.2 ONAIR 链末看门狗行、§9「ONAIR 链末 25s/50s」及文件头注释均不符。修复:仅 `node.auto` 或 + `@x` 过场转移自动衔接,其余 ONAIR 叙事节点链末等待单击并挂 gd_continue/gd_again 看门狗。 +2. **longpress 误报无效**:gesture.js 把 longpress/longpressend/tapcancel 派发进 onGesture,RING/ + BRIDGE/ENDING/MENU/ASK 的 default 分支把它们当无效手势报 gd_invalid/error 震——§2.3 长按行是 + 「引擎接管快进」,全状态合法。修复:仅 left/right(ASK 另含 up/down/doubletap)报无效。 +3. **菜单单击循环的引擎队列竞态**:`audio.play(item, {queue:true})` 在引擎 `_finishVoice` 中先跑 + onComplete(游戏链播下一句)后 shift 队列,排队报名会把 sys_menu→sys_menu_resume 链播句顶掉。 + 修复:菜单报名改游戏侧 `_menuQueue` + `_drainMenuQueue()` 排水,当前报名播完接播、零截断。 +4. **RING 双击首通不报无效**:INTRO 链节点完成会写 `lastCloser`,首通双击重听了 b_goal 而未按开发 + 注记「首通前无上一通 → 无效手势零静默」报错。修复:`callN <= 1` 时报无效。 +5. **ENDING 上滑缺口碑**:§2.3 ENDING 上滑=「帮助+状态(结局名+口碑)」,原实现只播 sys_help+结局 + 主句。修复:链尾补 `TIER_VOICES[tierOf(rep)]`。 +6. **stop() 后中断链复活**:被 stopVoice 掐断的 `_playSeq` 链可经 30s 单句看门狗「资产缺失」路径续链, + 已拆除实例会走到 enterRing 留下无人接听的永久铃声定时器。修复:stop() 先 `seqToken++` 作废在途链。 +7. **newNight 不清抉择武装**:菜单重开/结算重开到新夜班首链起播前,choiceArmed/pendingChoice 残留, + 窗口期手势会在旧节点上误抉择。修复:newNight() 显式清零。 +8. **_ringOnce 双链防护**:closeMenu(RING)/_resume 直接调 _ringOnce 不清旧计时器,可能双铃声链。 + 修复:_ringOnce 先 clearTimeout 旧 ringTimer。 + +### 规格歧义 / 实现取舍(登记不改设计) + +- §4.2 表「p4 问她/哄睡」选项列序与 §3.2 树形(左=哄睡/右=问她)相反,实现按 §3.2 + p4 提示句原文 + (game.js 已注记);断言按 §9 口径(p4 右 → heardArgument=true,+1)。 +- `sys_saved`(15b G0 组,触发=存档写入后)未接线:自动存档每局 ≥7 次,逐次播报会持续打断节目流; + 菜单「保存并回大厅」已有 sys_menu_lobby 确认句。列遗留(如需可挂菜单「重新开始今晚」确认后)。 +- INTRO 上滑=「帮助+状态」:开播前无通号/口碑可报,实现只播 sys_help 后重播当前段(RING 起状态链完整)。 +- 存档 data 在 §8.1 字段外增加 `call`/`ending`/`lastCloser`(断点续玩与 RING 双击需要,超集兼容)。 +- ASK 右滑新夜班的 `nights` 按新实例计数(不继承被拒绝存档的 nights),与结算重开 +1 口径不一致, + 无游戏性影响,列遗留。 + +### 资产生产缺口(列遗留,本轮不修) + +- §5.2 SFX 15 件中 **12 件 Stable Audio 3 条目未产出**(`gen/gen_sfx.py` 头注:HF token 未配置跳过): + onair_sting / phone_ring / line_connect / line_drop / amb_rain_city / amb_station / amb_kid_room / + arg_muffled / amb_street_rain / amb_cab / brake_screech / tape_static。缺失走引擎 console.error + 静默降级(§6.1 降级方案),玩法与断言不受阻;playtest 静态审计锁定「缺失集合 ⊆ 该清单」, + 资产补齐后断言自动收敛。已产出:ui_confirm / ui_menu(程序合成)+ 132 句 voice + 4 首 bgm + (ogg+m4a 双格式零缺失,见静态审计)。 + +## R2 修复回归(round1 synthesis F1-F5 + V1/V2,2026-09-02) + +> 证据源:research/new8/radio/round1/{synthesis,live-player}.md。断言全部落在 +> `web/radio/test/playtest.mjs`「R2 修复回归」块 + 主路径/看门狗/静态审计改断; +> **playtest 断言总数 R1=193 → R2=221(+28)**,全绿。 + +| 项 | 修复(文件 → 行为) | 回归断言(✅=已断言) | +|---|---|---| +| F1 (P1) | `game.js`:`onGesture` 入口 `guide.disarm()` 拆掉等待态看门狗后,无效手势/引擎接管手势分支按态立即重挂——ONAIR/INTRO 链末(`_inStoryWait()` 与 `_onNodeSeqDone` 等待分支同口径)、ASK 空闲、RING 空闲(`_rearmRingWatchdog`)、ENDING 结算态;BRIDGE 本无 guide 看门狗(restTimer 为游戏层),不涉及 | ✅ 链末等待中发无效手势,gd_continue 25s 整准点、gd_again 50s 整准点(旧实现 34.2s 零事件);✅ RING 无效手势后 gd_ring1 仍 10s 准点;✅ ASK 无效手势后看门狗仍 25s 准点 | +| F2 (P4/P2) | 台词表两处同步改词(先 `docs/design/15-radio-voice-ids.md` 后 `tools/gen_voice_dual.py`,两表一致):`rep_low`「今晚的口碑,有点砸了。」→「今晚的口碑,还没立起来。」(零起步语义,与 rep_high「在听众里立住了」同尺度;rep=0 开播前不再读成「砸了」);重生成 `voice/{male,female}/rep_low.{ogg,m4a}`(m=1.92s / f=2.32s,`--only rep_low` → `--convert` → `check_all_assets` radio 242+242 全过:双格式配对、零 0 字节、全可解码、性别目录 id 一致) | ✅ 静态审计锁定 rep_low 字面量存在;live 转写复验归 R2 协议第 2 步(重新生成转写) | +| F3 (P2) | `gen/gen_sfx.py` 增加 `phone_ring` 程序合成:440+480Hz 柔和双音一响 1.8s + 静默 3.4s = 5.2s 循环周期烘进文件(≥4s);无缝 loop 工艺 = 逐声道去直流、**不做首尾淡化**(`save_loop`)、解码回读校验真峰值 ≤-3dBFS(ogg -3.87 / m4a -3.84dBFS)+ 循环头/尾样本 ≤-60dBFS 边界静音;落 `audio/sfx/phone_ring.{ogg,m4a}`。`game.js` RING 接循环垫:引擎 sfx 层无 loop/stop → 游戏侧经 ctx 起循环 BufferSource 持句柄(`_ringSrc`,guide earcon 同款直用总线),接听/菜单/重开 `_stopRing` 即停;移除定时重触发(`ringTimer`/`ringJitterMs`/`RING_INTERVAL_MS`) | ✅ 进 RING 循环垫起播且挂起态虚拟时钟可空闲(零重触发定时器,可复现);✅ 单击接听循环垫即停不拖尾;✅ RING 无效手势不拆铃声垫;✅ 两级铃看门狗用尽后循环垫仍在(永不惩罚);✅ 静态审计 phone_ring 出待补清单(Stable Audio 待补 12→11 件) | +| F4 (P2) | `game.js`:断点续玩恢复后的首句播完再武装跳句——`_resume()` 置 `_resumeGuard`,`_playSeq` 消费为 `_skipLockId`(恢复链首句),该句播完/链完/被新链顶掉即解锁;ONAIR 与 ENDING(live 陷阱场景)两态 tap 生效 | ✅ 恢复后首句(w1_say / e4_a)播完前单击不跳句;✅ 首句播完后跳句重新武装(w1_sayo / e4_b 被正常跳过,链照常走) | +| F5 (P2) | `game.js`:链末句跳过保护——`_isTailSentence()`:当前句为本节点末句且节点把控制权交出(链末等待单击 / `@x` 过场转移 / 直入抉择提示)时屏蔽「跳过本句」;自动链中段(b_open/b_rain、危机拍)与 w1_say 组前两句跳句自由保留。与 F1 同提交复核链末状态机:`_inStoryWait` 与 `_onNodeSeqDone` 口径一致 | ✅ 交接抉择提示前的末句(w1_saye)单击不可跳、完整播出后照常交接 p7;✅ @ending 过场前末句(d9_facee,live 被吃掉的点题句)单击不可跳、完整播出后自动进 ENDING(e1) | +| V1 | 复核结论:pendingChoice 生效时序无 bug(W2 契约实现正确),按裁定补显式断言关案 | ✅ p* 播放中连点不跳句、不清缓存、不提前生效(rep 未动);✅ 播中再滑覆盖缓存(最后一次为准);✅ 生效时序 = p1 播完 → ui_confirm 结算音 → 结果句;✅ 缓存恰好生效一次(rep=1、缓存清空、不丢不重复) | +| V2 | 复核结论:菜单逻辑无缺失(三项循环 + 回绕正确),按裁定补全项循环断言关案。注:菜单为 3 项(sys_menu + sys_menu_resume/restart/lobby,15-radio.md §实现核对「菜单单击循环顺序」口径)——任务书所写「4 项/sys_menu_save」对应菜单标题句 + 3 菜单项;「保存并回大厅」确认句即 `sys_menu_lobby`(无 sys_menu_save 资产) | ✅ 单击循环两个整周期逐项可达(resume→restart→lobby→resume→restart→lobby,第三项不再缺席)、6 次回绕模 3 无漂移;✅ 双击确认「保存并回大厅」→ sys_menu_lobby 播出 + leaving 置位 + 存档落盘 | + +V3(arg_muffled/brake_screech 等 11 件 SA3 资产)维持「等待 HF token」,接线代码已备,不在本轮。 + +### 遗留(R2 收尾时需协同) + +- `web/harness/voice_text.json` 的 `rep_low` 期望文本仍是旧词「有点砸了」(harness 归属协议/ + 转写侧,本轮改动手清单之外):R2 协议第 2 步重新生成转写前需同步为新词「还没立起来」, + 否则 live 复测该句会 MISMATCH。 +- dungeon 款全量回归本轮 FAIL:另一 agent 并行改动 `web/engine/{audio,gesture}.js` 与 + `web/dungeon/**` 进行中(失败点为 dungeon 网格/侧墙与 tut 旁白断言,与 radio 无关); + radio与其余 6 款全绿。 diff --git a/docs/design/16-dungeon-voice-ids.md b/docs/design/16-dungeon-voice-ids.md new file mode 100644 index 00000000..59e7c2c1 --- /dev/null +++ b/docs/design/16-dungeon-voice-ids.md @@ -0,0 +1,179 @@ +# 16b · 《回声地窟》语音台词表(voice ids,录音依据) + +> **给音频总监的录制说明** +> - **全部旁白,无 NPC**:本作怪物不说话,无角色音(主设计 §1/§9.3)。125 句双性别。**female = Serena,male = Uncle_Fu**(沿用 R1 分配惯例)。落 `web/dungeon/audio/voice/female|male/.{ogg,m4a}`。 +> - female instruct 常量:`平静清晰的女声,像探窟向导,沉稳可信` +> - male instruct 常量:`低沉醇厚的男声,像探窟向导,沉稳有力` +> - 两常量全表统一(含教学/战斗/结算),禁止句级变体。 +> - 数字规避:层数(7 句)/血量(4 档)/金币(4 档)/面朝(4 句)/威胁(4 句)/战利(3 档)/死因(3 档)/元进度(3 档)全部分档整句,无运行时拼接。 +> - 多音字自查:全表无「血」「重(chóng/zhòng 歧义句)」「爪」「挨」「壳」「栅」;「重听」一律写「重新听」;「重新」为规范 sanctioned 常用词(sys_menu_help / sys_menu_restart)保留。「回声回来越快」已去掉助词「得」。余留风险 2 处已标注:`btl_bat` 的「蝠」(读 fú,试听异常则整句改「盲眼蝙蝠扑了过来」);`unlock_potion` 的「行囊」(xíng náng 固定词,试听确认)。 +> - 向导/卡关通道句(gd_*、stair_*、nav_*):trigger 见表;接线纪律按 14 号文档——watchdog/纠错句 idle-only、onceKey 同局限次、向导插播一律 queue:true;开发轮次须将本表 gd_* 条目登记进 14 号文档增补。 +> - 管线:`tools/gen_voice_dual.py` 照录(--game dungeon);生成后 `--convert` 落盘 + `tools/check_all_assets.py` 终检(双格式配对、双性别集合一致)。 + +## A 组 · 序章/标题(旁白 ×4) + +| id | 台词全文 | 触发/语境 | +|---|---|---| +| `intro_1` | 你在黑暗中醒来。这里是回声地窟。 | 新周目开场 1 | +| `intro_2` | 伸手不见五指,声音就是你的眼睛。 | 新周目开场 2 | +| `sys_continue_ask` | 检测到探窟记录。单击继续,右滑重新开始。 | 有存档进游戏(TITLE) | +| `sys_new_run` | 新的探窟,开始了。 | 新周目开始 | + +## T 组 · 教学演练 DRILL(旁白 ×19) + +| id | 台词全文 | 触发/语境 | +|---|---|---| +| `tut_d0` | 先学会用耳朵看。单击,发出声波脉冲。 | D1 前(首次脉冲引导) | +| `tut_echo_come` | 听,回声回来了。 | D1 首次回声后 | +| `tut_front_rule` | 回声回来越快,墙就越近。 | D2(正前距离规则) | +| `tut_side_rule` | 两侧的回声,发闷是墙,空旷是通路。 | D3(侧向规则) | +| `tut_walk` | 按住屏幕不放,你就往前走。 | D4 | +| `tut_turn` | 左滑右滑,可以转身。 | D5 | +| `tut_replay` | 双击,再发一次脉冲,不要钱。 | D6(重发免费) | +| `tut_status` | 上滑,随时听自己的状态。 | D7 | +| `tut_menu` | 下滑,打开菜单。 | D7 | +| `tut_mob` | 前面有东西!放心,它伤不到你。 | D8 遭遇教学怪前(真话:ATK0) | +| `tut_parry` | 听到它的动静,双击格挡。 | D8 前摇前 | +| `tut_parry_ok` | 挡住了! | D8 格挡成功(成功只给新短句) | +| `tut_parry_miss` | 没挡住也别怕,它伤不到你。 | D8 格挡失败 | +| `tut_attack` | 现在单击,挥拳打它。 | D8 你的回合 | +| `tut_kill` | 打中了,就是这样。 | D8 击杀 | +| `tut_stairs` | 铁器的回声,就是下潜的楼梯。 | D9 | +| `tut_stairs_on` | 楼梯就在脚下。单击,下潜。 | 首次踏上楼梯格 | +| `tut_done` | 教学完了。地窟很深,小心走。 | DRILL 完成 | +| `tut_skip` | 已跳过教学。单击脉冲,先听听四周。 | 下滑跳过教学 | + +## S 组 · 系统/菜单/状态(旁白 ×35) + +| id | 台词全文 | 触发/语境 | +|---|---|---| +| `sys_saved` | 进度已保存。 | 存档写入后 | +| `sys_menu` | 菜单。单击切换,双击确认。 | 下滑呼出菜单 | +| `sys_menu_resume` | 返回探索。 | 菜单项 | +| `sys_menu_save` | 保存并回大厅。 | 菜单项 | +| `sys_menu_help` | 重新听帮助。 | 菜单项 | +| `sys_menu_restart` | 重新开始本周目。 | 菜单项 | +| `help_explore` | 单击脉冲,按住前进,左右滑转身。双击再听,上滑状态,下滑菜单。 | 帮助(探索) | +| `help_battle` | 敌人前摇声响时双击格挡,单击挥击。打不过就按住屏幕逃跑。 | 帮助(战斗) | +| `settings_hint` | 音色、语速、背景音乐,请回大厅修改。 | 菜单内 | +| `st_floor_1` | 这是第一层。 | 上滑状态链 / 进层 | +| `st_floor_2` | 这是第二层。 | 同上 | +| `st_floor_3` | 这是第三层。 | 同上 | +| `st_floor_4` | 这是第四层。 | 同上 | +| `st_floor_5` | 这是第五层。 | 同上 | +| `st_floor_6` | 这是第六层。 | 同上 | +| `st_floor_7` | 这是第七层。 | 同上 | +| `st_hp_1` | 你毫发无损。 | 血量 >75% | +| `st_hp_2` | 你受了点伤。 | 50~75% | +| `st_hp_3` | 你伤势不轻。 | 25~50% | +| `st_hp_4` | 你命悬一线。 | ≤25%(danger earcon + warning 震,降档才播) | +| `st_gold_1` | 口袋空空。 | 元进度总金币 <50 | +| `st_gold_2` | 有些积蓄。 | 50~200 | +| `st_gold_3` | 收获可观。 | 200~600 | +| `st_gold_4` | 腰包鼓鼓。 | >600 | +| `st_face_e` | 你面朝东。 | 上滑状态链(绝对方位) | +| `st_face_s` | 你面朝南。 | 同上 | +| `st_face_w` | 你面朝西。 | 同上 | +| `st_face_n` | 你面朝北。 | 同上 | +| `st_threat_none` | 附近很安静。 | 上滑威胁段(相对方位,只报左右) | +| `st_threat_left` | 左边有脚步声。 | 左侧 5 格内有怪 | +| `st_threat_right` | 右边有脚步声。 | 右侧 5 格内有怪 | +| `st_threat_both` | 两边都有动静。 | 两侧皆有 | +| `st_best_1` | 你还没到过第三层。 | TITLE 元进度·最深层数档 1 | +| `st_best_2` | 你到过中层。 | 档 2(最深 3-5 层) | +| `st_best_3` | 你到过最深层。 | 档 3(最深 ≥6 层) | + +## E 组 · 探索事件(旁白 ×9) + +| id | 台词全文 | 触发/语境 | +|---|---|---| +| `evt_coin_1` | 捡到几枚金币。 | 金币堆·小 | +| `evt_coin_2` | 捡到一小把金币。 | 金币堆·中 | +| `evt_coin_3` | 捡到一大袋金币。 | 金币堆·大 | +| `evt_potion` | 一瓶药水,收进腰带。 | 拾取药水(腰带空) | +| `evt_potion_full` | 腰带满了,药水留在原地。 | 已有药再拾(药不消失) | +| `evt_weapon` | 捡到更快的短刃,出手更狠了。 | 武器掉落(自动装备) | +| `evt_armor` | 换上更硬的护甲,更抗打了。 | 护甲掉落(自动装备) | +| `evt_stairs_lock` | 楼梯被大东西堵住了,先赢它。 | 窟主未清时踏上楼梯格 | +| `evt_descend` | 你顺着楼梯,往下潜。 | 下潜(DESCEND) | + +## B 组 · 战斗(旁白 ×30) + +| id | 台词全文 | 触发/语境 | +|---|---|---| +| `btl_bat` | 盲眼蝠扑了过来! | 遭遇盲眼蝠 | +| `btl_rat` | 窟鼠窜了出来! | 遭遇窟鼠 | +| `btl_mush` | 蘑菇怪拦住了你! | 遭遇蘑菇怪 | +| `btl_crab` | 窟主石甲蟹,就在你面前。 | 遭遇窟主(L3) | +| `btl_crawler` | 绕行者贴上来了,小心背后! | 遭遇绕行者(教学绕位威胁) | +| `btl_spider` | 洞蛛落地了! | 遭遇洞蛛 | +| `btl_leech` | 窟主腐沼蟒,挡住了楼梯。 | 遭遇窟主(L6) | +| `btl_guard` | 石像卫醒了! | 遭遇石像卫 | +| `btl_king` | 窟中之王,就在深处。 | 遭遇终 Boss(L7) | +| `btl_parried` | 挡下了大半! | 格挡成功(减伤 80%) | +| `btl_behind` | 它在你背后,转身再挡。 | 未面对攻击源挨打(归因真话) | +| `btl_swish` | 挥空了,它不在你面前。 | 挥击落空(归因真话) | +| `btl_no_turn` | 它贴着你不放,不用转身。 | 缠斗怪时转身(每场 1 次,之后仅 error 震) | +| `btl_kill` | 它倒下了。 | 击杀 | +| `btl_loot_1` | 战利不多。 | 战利档 1 | +| `btl_loot_2` | 小有收获。 | 战利档 2 | +| `btl_loot_3` | 大有收获。 | 战利档 3 | +| `btl_lvup` | 你的拳脚更利落了。 | 每击杀 4 只攻击升档 | +| `btl_flee` | 你甩掉了它,被抓了一下。 | 逃跑成功(真话:撤退挨一下) | +| `btl_flee_boss` | 退路塌了,跑不掉! | Boss 战逃跑拒绝(真话) | +| `btl_half_crab` | 石甲蟹的甲裂了! | 窟主半血(阶段转换信号) | +| `btl_half_leech` | 腐沼蟒暴怒了! | 窟主半血 | +| `btl_half_king` | 王怒了,脚步更重了! | 终 Boss 半血(二阶段) | +| `die_intro` | 你倒下了。 | 死亡链首句 | +| `die_cause_1` | 你被小怪拖垮了。 | 死因:普通怪连续失血(真话) | +| `die_cause_2` | 你没挡住那记重击。 | 死因:重击未格挡(真话) | +| `die_cause_3` | 你背后的偷袭,要了命。 | 死因:背刺(真话) | +| `die_adv_1` | 下次,前摇声响时就双击。 | 建议:全程格挡数为 0 | +| `die_adv_2` | 打不过,就按住屏幕逃跑。 | 建议:逃跑 0 次且死于连续战 | +| `die_adv_3` | 身后有动静,先转身。 | 建议:死于背刺 | + +## G 组 · 卡关/看门狗/纠错(旁白 ×14) + +| id | 台词全文 | 触发/语境 | +|---|---|---| +| `gd_pulse` | 单击脉冲,听听四周。 | 探索看门狗第 1 级(10s,full 档,idle-only) | +| `gd_turn` | 轮到你了。单击挥击。 | 战斗回合看门狗第 1 级(15s) | +| `gd_invalid` | 这个操作在这里用不上。 | 无效手势纠错(onceKey 同局限 1 次,之后仅 error 震) | +| `stair_hint_a` | 找找铁器的回声,那是楼梯。 | 卡关 L1 暗示(60s,楼梯未找到) | +| `stair_hint_b` | 楼梯有大东西守着,先赢它。 | 卡关 L1 暗示(窟主未清) | +| `stair_dir_e` | 楼梯在东边。 | 卡关 L2 明示(120s,绝对方位) | +| `stair_dir_s` | 楼梯在南边。 | 同上 | +| `stair_dir_w` | 楼梯在西边。 | 同上 | +| `stair_dir_n` | 楼梯在北边。 | 同上 | +| `nav_e` | 往东。 | 卡关 L3 带路(180s,导航模式逐步播) | +| `nav_s` | 往南。 | 同上 | +| `nav_w` | 往西。 | 同上 | +| `nav_n` | 往北。 | 同上 | +| `nav_done` | 到了,就是这里。 | 带路抵达楼梯 | + +## M 组 · 结算/元进度(旁白 ×14) + +| id | 台词全文 | 触发/语境 | +|---|---|---| +| `meta_gold_1` | 这趟没带回什么。 | 死亡/通关结算·金币入账档 1 | +| `meta_gold_2` | 这趟带回一些金币。 | 档 2 | +| `meta_gold_3` | 这趟满载而归。 | 档 3 | +| `settle_stronger` | 下个周目,你会更强。 | 结算钩子句(开环) | +| `settle_win_1` | 你捧起了地窟之心,通关了! | 通关 1 | +| `settle_win_2` | 黑暗的尽头,是光。 | 通关 2 | +| `settle_again` | 单击,再探一次。 | 结算尾(重开入口 ≤2s) | +| `unlock_lead` | 攒够了新的遗产。 | 新周目开场·有新解锁时 | +| `unlock_potion` | 解锁行囊药,开局自带一瓶。 | 遗产播报(90 金) | +| `unlock_vigor` | 解锁体魄,起始更强壮。 | 遗产播报(150 金) | +| `unlock_veil` | 解锁护符,每层头一下减半。 | 遗产播报(220 金) | +| `unlock_echo` | 解锁回声强化,侧向听得更远。 | 遗产播报(300 金) | +| `unlock_blade` | 解锁武器,开局带着短刃。 | 遗产播报(450 金) | +| `unlock_none` | 金币还不够,继续攒。 | TITLE 元进度·距下一解锁尚远 | + +--- + +**合计:125 句 ×2 性别 = 250 条音频**(×2 格式 = 500 文件;无 NPC 平铺件)。死亡结算链 = +`die_intro → die_cause_X → die_adv_Y → st_floor_N(复用)→ meta_gold_X → settle_stronger → settle_again`; +通关链 = `settle_win_1 → settle_win_2 → meta_gold_X → unlock 段 → settle_again`; +上滑局面复述链 = `st_floor_N → st_hp_X → st_gold_X → st_face_X → st_threat_X`。 +主设计触发逻辑对照 [16-dungeon.md](16-dungeon.md) §4-§11;资产规模核对见其 §9。 diff --git a/docs/design/16-dungeon.md b/docs/design/16-dungeon.md new file mode 100644 index 00000000..3bd77ff7 --- /dev/null +++ b/docs/design/16-dungeon.md @@ -0,0 +1,577 @@ +# 16 · 《回声地窟》完整设计(dungeon / Echo Cavern) + +> **给开发者的实现要点摘要** +> 1. 听声辨位 Roguelike:网格地窟 + 面朝转向。**单击=声波脉冲**(回声"看"地形:正前距离 4 档×材质 4 种,左右侧 墙/开口 2 档,预 pan 立体声资产),**按住=前进、左右滑=转身**(豁免登记见 §2),双击=重发脉冲(免费重听)、上滑=状态、下滑=菜单(三件套不豁免)。战斗是 rpg 式回合制(前摇双击格挡),Boss 战加入绕位(脚步声报方位、转身对准才打得到)。 +> 2. 方位全程降维 ≤4:探索报前/左/右三向(身后不报,转身即查),战斗相对方位只有面前/左/右/背后。回声=静态地形、脚步=动态怪物、播报=绝对方位——三套信息由同一 `grid` 单源生成,机制上不可能互相矛盾。 +> 3. 单局=一命一局,7 层满通约 24 分钟;死了重开但金币进元进度("遗产"5 项永久强化,死亡不清档)。学习期零惩罚:L1 怪 ATK=0、L2 ATK=1,学期内不可能死;药水自动喝;撞墙无伤;无任何倒计时。 +> 4. 随机性全走种子 PRNG(mulberry32,`opts.seed` 可注入,playtest 传固定 seed 复现);回声/脚步全部用「预生成 sfx 资产 + setTimeout 调度 + volume」实现,不用引擎外实时节点(playtest 虚拟时钟可断言)。 +> 5. 台词表 [16-dungeon-voice-ids.md](16-dungeon-voice-ids.md)(125 句 ×2 性别,无 NPC——怪物不说话);本作向导句 gd_* 需在开发轮次登记进 14 号文档增补(§6 纪律:queue:true、idle-only、onceKey)。 +> 6. 规模对标 escape+rpg 折中:单文件 game.js 预计 900-1100 行,bgm 4 + sfx ~34 + voice 125×2。 + +--- + +## 1. 概述 + +- **一句话**:你是盲眼探窟者,单击发出声波脉冲、用回声"看"每次都不同的地窟,听脚步辨怪,逐层下潜,死了带着金币重来、越死越强。 +- **题材**:黑暗洞窟探险。核心命题「伸手不见五指,声音就是你的眼睛」——题材与纯音频互为 justification,与《剑与声》的"耳即眼"呼应但玩法完全不同:rpg 是选项列表 RPG,本作是**空间探索型 roguelike**(方法论 §4.1:状态层重点=方位参数映射+撞墙归因+目标方向信标)。 +- **为什么适合纯音频**:回声定位本来就是"用声音看空间"——视觉游戏的 minimap 在这里天然就是一段回声;黑暗=无画面是叙事设定而非技术妥协。移动/转身/距离全部可离散化(≤4 方位×4 距离档),不需要连续角度(文献结论:方位降维 ≤4)。 +- **单局**:一命一局,出生到死亡/通关。7 层,满通约 24 分钟,普通玩家死于 4-6 层(约 14-19 分钟)。死亡结算 ≤15s 必答归因三问,金币入元进度,**死亡不清档位进度**。 +- **与现有 6 游戏的关系**:空间感承接 escape(网格+带路提示),回合战斗承接 rpg(前摇格挡),演练教学承接 space(D 步逐项实操),元进度为本作新增系统(8 款新模式探索点的核心增量)。 + +## 2. 手势映射与豁免登记 + +### 2.1 探索态(EXPLORE / DRILL / DESCEND) + +| 手势 | 作用 | 反馈 | +|---|---|---| +| 单击 | **发出声波脉冲**(核心动词,1.2s 冷却防连打糊声流) | sfx/pulse + 调度回声组(§3) | +| 按住(longpress,450ms 起) | **前进**:按住朝面朝方向连续走(每格 0.5s),松开(longpressend)即停;撞墙自动停 | 每格 step_own + swipe 震;撞墙 bump + 停,**无伤** | +| 左滑 | 左转 90°(面朝逆时针 N→W→S→E) | step_turn + swipe 震 | +| 右滑 | 右转 90° | step_turn + swipe 震 | +| 双击 | **重发脉冲**(免费重听回声,不消耗任何资源、无冷却惩罚) | 同单击(宪法"双击=重听"的本作落地:线索=回声) | +| 上滑 | 状态播报链:层 → 血量档 → 金币档(元进度总账)→ 面朝 → 周围威胁 | 5 段链播 | +| 下滑 | 模态菜单(§2.4) | menu earcon | + +### 2.2 战斗态(BATTLE) + +| 手势 | 作用 | 反馈 | +|---|---|---| +| 单击 | 挥击:怪在你面前才命中;不在(Boss/绕位怪换位后)= 挥空 | atk_swing→命中 atk_hit / 挥空 + `btl_swish`(归因真话) | +| 双击 | **格挡**:仅敌方前摇音窗口内(1.2s)有效;面对攻击来源减伤 80%,未面对 0%(背后挨刀) | parry / hurt + hit 震 | +| 左滑/右滑 | 转身(只对绕位怪/Boss 有意义;缠斗怪时无效) | step_turn / 纠错(§10) | +| 按住 | **尝试逃跑**:普通怪必成、但撤退挨一下(真实掉血);Boss 战拒绝 | `btl_flee` / `btl_flee_boss` | +| 上滑 | 战斗状态:我方血量档 → 敌方血量档 →(Boss/绕位战)它的当前方位 | 3 段链播 | +| 下滑 | 模态菜单(战斗暂停,回合制无实时流,无需 count-in) | menu earcon | + +### 2.3 标题态(TITLE)/ 结算态(SETTLE) + +| 手势 | TITLE | SETTLE | +|---|---|---| +| 单击 | 继续(有存档)/ 开始探窟(无存档) | 再来一局(新周目,重掷 seed) | +| 右滑 | 放弃当前周目,重掷新周目(元进度保留) | —(无效,纠错) | +| 双击 | 重听 continue_ask / 元进度播报 | 重听结算链 | +| 上滑 | 元进度播报:金币档 → 最深层数档 → 遗产清单(已解锁各一句)→ `unlock_none`(不够时) | 重听元进度段 | +| 下滑 | 菜单(帮助/返回两项) | 菜单 | +| 按住/左右滑 | 无效(error 震 + `gd_invalid`) | 同左 | + +### 2.4 模态菜单(遵守 00 文档 §5.2 全球约定,无豁免) + +下滑呼出 → 单击循环切换(每切播项名)→ 双击确认 → 上滑=帮助 → 下滑=关闭。打开即暂停(战斗回合制天然可暂停)。选项:返回探索(战斗态同)→ 保存并回大厅 → 重新听帮助 → 重新开始本周目。 + +### 2.5 豁免登记(必须显式) + +| # | 豁免条目 | 理由(先例) | +|---|---|---| +| E1 | 单击=脉冲(非"确认/继续") | 核心动词必须是最快手势;脉冲是"检查"类非即时操作,300ms tap 延迟无碍(escape 单击=检查同款) | +| E2 | 左右滑=转身、按住=前进(探索态四向滑+长按被玩法占用) | 网格移动玩法(escape 四向滑=移动先例);上下滑让位给不可豁免的状态/菜单三件套,前进改绑长按——"按住就走"零认知负荷(P4),单手免视(P3) | +| E3 | 对局期 `audio.setRateBoostEnabled(false)`(探索/战斗/教学),菜单/结算恢复 true | 长按=移动是潜在玩法输入,长按快进必须豁免(rhythm/space 对局期豁免先例) | +| E4 | 战斗态下滑=菜单保留,但菜单在战斗内语义为暂停 | 回合制,菜单即暂停,无并发压力;不沿用 rpg"战斗态下滑=逃跑"豁免(本作逃跑绑长按,更贴"转身就跑"直觉) | + +教学(DRILL)中逐条讲清 E1/E2/E4 的偏离(tut_* 句),符合 §5.4"偏离必须在开场交互教学中讲清"。longpressend 仅作停走信号,不承载第二语义;不开启 immediateTap(无 tapcancel 路径)。 + +## 3. 核心动词:声波脉冲与回声定位 + +### 3.1 声学编码(全在引擎能力内) + +| 维度 | 编码 | 实现 | +|---|---|---| +| 正前距离 | 4 档:贴身(1格)/近(2格)/中(3-4格)/远(5+格) | 脉冲后 setTimeout 调度 `echo_1..4`,延迟 0.25/0.50/0.80/1.10s,volume 0.9/0.7/0.5/0.35 | +| 正前材质 | 4 种:石墙/木门/铁梯(楼梯)/水 | 回声到达后 +120ms 播材质尾音 `mat_stone/door/stair/water`(0.2s 短音色) | +| 左右侧 | 2 档:墙(闷)/开口(空旷回灵) | 脉冲后 +0.35s 同时播 `side_wall_l/r` 或 `side_open_l/r`(**预 pan 立体声资产**:能量只在一个声道,引擎 play() 无实时 pan,space whoosh_left/right 同工艺) | +| 身后 | **不报** | 刻意降维:回声只照面前和两侧,转身即查(教学句 tut_side_rule 讲明规则;不报≠信息矛盾) | + +- 节点说明:**不用引擎外实时 WebAudio 节点**(playtest mock 只 mock bufferSource/gain)。全部=预渲染 sfx 资产 + `play()` volume + setTimeout 调度——虚拟时钟下回声调度可精确断言。 +- 降级方案:若 SA3 立体声分离度不达标,侧向回声改 SYN 程序合成(直接写立体声 AudioBuffer 的 L/R 通道数据,管线能力内);材质尾音全部 SYN 可兜底。 +- 频带分离防糊:己方脚步/怪物脚步=低频(100-300Hz),脉冲+回声=中频(800Hz-2kHz),BGM 垫底——探索期并发声流 ≤2(你的动作反馈 + 怪物脚步)恒成立。 +- 脉冲冷却 1.2s:防连打;双击重发不受额外惩罚(P1"回声可重听且免费")。冷却期单击/双击 → 仅 error 震(无语音惩罚)。 + +### 3.2 三套信息自洽(P1 专项,机制级保证) + +| 信息 | 声源 | 内容 | 坐标系 | +|---|---|---|---| +| 回声 | 脉冲触发 | 静态地形(墙/门/楼梯/水 + 距离) | 相对(前/左/右) | +| 怪物脚步 | 怪物移动即播(每步,间隔 1.5-2.5s) | 动态威胁(左右 pan + 距离档音量;背后=中央音量减半,转身后恢复=方位反馈) | 相对(左/前/右/后) | +| 播报 | 上滑/事件 | 层数/血量/金币/面朝(绝对)/威胁(相对左右) | 面朝=绝对,其余=相对 | + +三者全部由同一 `game._floor.grid` 与怪物位置**单源查询**生成,无独立状态;模板句固定(方位句 4 句、威胁句 4 句)。回声永不报怪物、脚步永不报地形——语义域零重叠。威胁播报只报左右不报背后(同回声降维,规则在教学讲明)。 + +## 4. 状态机 + +### 4.1 状态列表 + +`TITLE / DRILL / EXPLORE / BATTLE / MENU(overlay) / DESCEND / SETTLE`。MENU 是挂起 overlay(记录返回态),其余为正交主态。 + +### 4.2 转移图 + +``` +start ──> TITLE ──单击(有档)──> EXPLORE(恢复存档+局面复述) + │ │单击(无档) │ + │ ├─────> DRILL(仅首次) ──完成/下滑跳过──> EXPLORE(L1) + │ └右滑=重掷新周目 ──────────────┘(已学过教学,直达) + v + EXPLORE <──────────────┐ + │ 走进怪格/怪走进格 │ 胜利(原地) + v │ + BATTLE ────────────────┘ + │ HP≤0 + v + SETTLE(死亡) <── EXPLORE(第7层终Boss胜=通关SETTLE) + │单击 + v + TITLE(元进度入账后) + + EXPLORE ──踏上楼梯格+单击──> DESCEND ──> EXPLORE(floor+1) + EXPLORE/BATTLE ──下滑──> MENU ──返回──> 原状态 +``` + +DESCEND 演出:stairs_down 轰隆 + `evt_descend` + 下一层生成 + `st_floor_N`,约 5s。 + +### 4.3 每状态手势映射总表 + +| 手势 | TITLE | DRILL | EXPLORE | BATTLE | MENU | SETTLE | +|---|---|---|---|---|---|---| +| 单击 | 继续/开始 | 步骤作答 | 脉冲 | 挥击 | 循环切项 | 再来一局 | +| 双击 | 重听 | 步骤作答(D6) | 重发脉冲 | 格挡(前摇窗口) | 确认执行 | 重听结算 | +| 左滑 | error | 步骤作答(D5) | 左转 | 转身(绕位怪有效) | 上一项 | error | +| 右滑 | 重掷新周目 | 步骤作答(D5) | 右转 | 转身(绕位怪有效) | 下一项 | error | +| 上滑 | 元进度 | 状态(教学后可用) | 状态链 | 战斗状态链 | 帮助 | 重听元进度段 | +| 下滑 | 菜单 | 跳过教学 | 菜单 | 菜单(暂停) | 关闭 | 菜单 | +| 长按 | error | 步骤作答(D4) | 前进 | 尝试逃跑 | — | error | +| 长按松开 | — | 停 | 停 | — | — | — | + +无效手势一律 error 震;full 档语音纠错 `gd_invalid`(onceKey 同局限 1 次,之后仅 error 震)。idle-only:纠错/看门狗语音在播时跳过(引擎 guide 已实现)。 + +## 5. 教学编排(DRILL,首局内嵌于第 1 层) + +每步=一句短旁白→一次实操→反馈;做错重教同一步(**新短句**,不逐字重播)≤2 次后自动演示通过(不卡流程,space 先例)。教学寄生局内:DRILL 就在 L1 地图起点房内进行,D8 的教学怪是 L1 两只怪之一。全程可下滑跳过(`tut_skip`),跳过标志存进度,二周目不再进 DRILL。 + +| 步 | 旁白 | 等待动作 | 通过条件/反馈 | +|---|---|---|---| +| D0 | intro_1+intro_2+tut_d0 | 单击 | confirm 震 | +| D1 | — | 单击 | pulse+回声组 + `tut_echo_come`。**首输入≤60s 达成点(≈t+15s)** | +| D2 | tut_front_rule | 单击继续 | 纯听规则句 | +| D3 | tut_side_rule | 单击继续 | 纯听规则句 | +| D4 | tut_walk | 按住走 ≥2 格 | 脚步声(撞墙也算过,bump+`那边是墙`由既有 bump 路径播) | +| D5 | tut_turn | 左滑+右滑各 1 | 转身声 | +| D6 | tut_replay | 双击 | echo 重播 + `tut_echo_come` 复用 | +| D7 | tut_status + tut_menu | 上滑(状态链播)→ 下滑开菜单→再关 | 两键各一次 | +| D8 | tut_mob → 走向教学怪触发战斗(盲眼蝠 ATK=0)→ tut_parry →(前摇)双击 | 格挡成/败都继续:成功 `tut_parry_ok`,失败 `tut_parry_miss` | 然后 tut_attack → 单击挥击至击杀 `tut_kill`。**首正反馈≤3min 达成点** | +| D9 | tut_stairs | 脉冲听到铁器回声后自行走到楼梯 | 踏上楼梯格 `tut_stairs_on` → 单击下潜 → EXPLORE | + +D9 后不再教学;上滑=状态链随时可查。生成约束(教学局 L1,优先于随机):起点房前方 2 格内保证有墙(D1 回声演示)、起点房门口固定一只盲眼蝠(D8)、房内 1 处金币(拾取体验)。 + +FTUE 核对:遮罩点按→D0 三句(~8s)→D1 首次脉冲 ≈ t+15s(<60s ✓);首个正反馈=教学战胜利 ≈ t+2min(≤3min ✓);学习期 L1-2 怪 ATK 0/1(死亡不可能 ✓)。 + +## 6. 地窟生成 + +### 6.1 seed 用法 + +``` +rng(floor) = mulberry32((seed ^ (floor * 0x9E3779B9)) >>> 0) // 每层独立子种子 +``` + +- `opts.seed` 可注入(playtest 传 42);正式局 `seed = Math.floor(Math.random()*1e9)`(开局掷一次,存入 run)。同 seed → L1..L7 全部可复现。 +- 生成步骤(每层):① 尺寸定表(§6.2)→ ② 放 3-6 个矩形房间(2×2~3×4,不重叠)→ ③ L 型走廊按 MST 顺序连通全房间 → ④ 起点房=第 1 房中心,楼梯房=BFS 距起点最远房间中心,楼梯格=其中心 → ⑤ 宝物撒入房间格 → ⑥ 怪物放在 BFS 距起点 ≥3 格的格位(开局安全区)。 +- 复现断言口径:同 seed 两次构造 → grid/怪物/宝物/楼梯逐格相等(测试读 `game._floor` 重算不变式,**不写魔法坐标**)。 + +### 6.2 层规格与难度曲线 + +| 层 | 尺寸 | 怪物(型×数) | 宝物 | 目标用时 | 累计 | +|---|---|---|---|---|---| +| 1 教学 | 7×7 | 盲眼蝠×2(ATK0) | 金币×1 | 3.0 | 3.0 | +| 2 | 7×7 | 窟鼠×3 | 金币×1+药水×1 | 3.0 | 6.0 | +| 3 窟主 | 9×9 | 蘑菇怪×3 + 窟主石甲蟹 | 金币×1+短刃 | 3.5 | 9.5 | +| 4 | 9×9 | 绕行者×2+洞蛛×2 | 金币×1+皮甲 | 3.5 | 13.0 | +| 5 | 11×11 | 洞蛛×3+绕行者×2 | 金币×1+药水×1 | 3.5 | 16.5 | +| 6 窟主 | 11×11 | 洞蛛×2+绕行者×2 + 窟主腐沼蟒 | 金币×1+阔刃 | 3.5 | 20.0 | +| 7 终局 | 11×11 | 石像卫×3 + 窟中之王 | 金币×1 | 4.0 | 24.0 | + +- 窟主守楼梯格:未击败时踏上楼梯格播 `evt_stairs_lock`(楼梯不可用);击败后解锁。终 Boss 击败=通关。 +- 敌型见 §7.2 表。每层开场氛围:`st_floor_N` + 层 BGM 不变(探索曲统一),深度感靠 `evt_descend` 与怪物构成。 + +## 7. 遭遇与战斗 + +### 7.1 遭遇 + +- 全**明雷**:怪物脚步声持续可闻(rpg 暗雷教训——纯音频里突袭没有信息缓冲)。你走进怪格或怪走进你格 → `btl_start_` + battle earcon + BGM 切换。 +- 遭遇瞬间自动"面对"怪物(播报名即=它在面前)。怪移动的脚步:每步播 `mob_step__`,pan 按相对方位(左/前/右),**背后用中央资产音量减半**;距离档音量 ≤2 格 0.9 / 3-4 格 0.6 / 5-8 格 0.35 / 更远不播。距离 ≤2 格加 heartbeat 震(威胁前置预警窗,方法论 L2)。 + +### 7.2 回合时序(实现契约) + +``` +进 BATTLE → 玩家回合(guide.turn) → 单击挥击/长按逃跑/转身 → 结算 + → 若怪活:怪行动(绕位怪先移动:脚步报新方位;缠斗怪原地)→ 攻击前摇 1.2s(tel_* 音) + → 前摇内双击=格挡判定(面对攻击源:×0.2 / 未面对:×1.0)→ 受击/格挡音+震 + → 玩家 HP≤25% 且腰带有药 → 自动喝药(potion 音+播报) + → 回到玩家回合。HP≤0 → 死亡归因链 → SETTLE +``` + +- 缠斗怪(蝠/鼠/菇/蛛):不绕位,恒正面,左右滑无效(`btl_no_turn` 纠错,每场 1 次)。 +- 绕位怪(绕行者/石像卫):每 2 回合移动到 左/右/背后 之一(脚步 pan 报位),攻击前摇随其方位(tel 派生 pan 变体);`btl_behind` 在背后挨刀时播(归因真话)。 +- Boss(石甲蟹/腐沼蟒/窟中之王):绕位+高血,半血播 `btl_half_*` + ATK+1(王二阶段)。Boss 战长按逃跑拒绝(`btl_flee_boss`)。 +- 格挡判定与 rpg 一致绑双击(规避 tap 300ms 延迟);窗口外双击=error 震不生效。 + +### 7.3 逃跑 + +长按 = 尝试逃跑:普通怪/绕位怪**必成**(无 RNG,P4 友好),撤退挨一下(掉血=怪 ATK,真实账目),落回探索态、怪留在原格;拉开距离后脚步声自然远去。Boss 拒绝。逃跑无次数限制(容错拉满,代价=掉血+丢进攻回合)。 + +### 7.4 掉落与元进度 + +- 战斗胜利:金币入账 + `btl_loot_1..3`(按单场所得 3 档)+ 概率掉药水/装备(§6.2 宝物表为拾取点,怪掉落仅金币)。 +- 每击杀 4 只 atkLv+1,播 `btl_lvup`("这层我变强了"的即时成长,P2 钩子)。 +- **元进度(遗产)**:金币永续累计(meta.gold),死亡/通关结算入账,不清档。5 项解锁(下一局开场自动生效+播报): + +| 遗产 | 价格(金币) | 效果 | +|---|---|---| +| unlock_potion 行囊药 | 90 | 开局自带 1 瓶腰带药 | +| unlock_vigor 体魄 | 150 | 起始 maxHp 20→26 | +| unlock_veil 护符 | 220 | 每层首次受击减半 | +| unlock_echo 回声强化 | 300 | 侧向回声加报距离档(墙闷/开口各 2 档→按远近换 2 组资产音量) | +| unlock_blade 武器 | 450 | 开局自带短刃(atk+1) | + +- 经济核算:一局 40-120 金(怪 3-15/只 + 堆 10-30 + 窟主 50-80)→ 约 2-5 局解锁一项,死亡有可见成长(P2「下周目你会更强」)。 + +## 8. 数值表 + +### 8.1 玩家与敌人 + +``` +maxHp = 20 (+vigor 6);atk = 2 + atkLv(0..4, 每4杀+1) + 武器(0..2) +dmg = max(0, atk - 敌def) ;敌打你 = max(0, 敌atk - 甲) × 格挡系数(0.2/1.0) +格挡钳制:擦伤 min 0;L1-2 怪 ATK 0-1 → 学期零惩罚 +``` + +| 敌型 | 层 | HP | ATK | 行为 | 脚步音色类 | 掉金币 | +|---|---|---|---|---|---|---| +| 盲眼蝠 | 1 | 4 | 0 | 缠斗 | small | 3 | +| 窟鼠 | 2 | 6 | 1 | 缠斗 | small | 5 | +| 蘑菇怪 | 3 | 8 | 2 | 缠斗 | bug | 8 | +| 窟主·石甲蟹 | 3 | 20 | 3 | Boss 绕位 | heavy | 50 | +| 绕行者 | 4 | 10 | 2 | 绕位 | bug | 10 | +| 洞蛛 | 5 | 12 | 3 | 缠斗 | bug | 12 | +| 窟主·腐沼蟒 | 6 | 26 | 4 | Boss 绕位 | heavy | 80 | +| 石像卫 | 7 | 16 | 4 | 绕位 | heavy | 15 | +| 窟中之王 | 7 | 36 | 5→6 | Boss 绕位 | heavy | 通关 | + +装备(拾取即自动装备,仅播报不管理):短刃 atk+1(L3 起)、阔刃 atk+2(L6 起)、皮甲 敌伤-1(L4 起)。药水:腰带限 1 瓶,HP≤25% 自动喝(回 40% maxHp,向上取整)。 + +平衡校验:L1 atk2 打蝠 2 刀;L3 atk3+短刃=4 打蟹 5 刀、蟹打裸装 3/下 vs 20HP+药 → 不格挡危险、会格挡稳过;L7 atk2+3+2=7 打王 6 刀、王打 5-6/下 vs 26HP → 必须格挡+药,Boss 战有真实技巧收益。 + +## 9. 音频三层 + +### 9.1 BGM(4 首,Mureka,-20 LUFS,ogg+m4a,无缝循环) + +| id | 状态映射 | Mureka prompt(英文) | +|---|---|---| +| bgm/menu | TITLE / MENU / SETTLE(结算 volume 0.5) | "Dark cavern ambient pad, deep sub drone with distant water drips, calm and mysterious, no drums, no vocals, seamless loop" | +| bgm/explore | DRILL / EXPLORE / DESCEND | "Dark cave exploration ambient, low ominous drone, sparse echoing sonar-like pings, slow dripping rhythm, tense but calm, no vocals, seamless loop" | +| bgm/battle | BATTLE 普通怪 | "Tense dungeon battle music, pounding tribal drums 95 bpm, dissonant low stabs, claustrophobic urgency, no vocals, seamless loop" | +| bgm/boss | BATTLE 窟主/终 Boss | "Epic dark cavern boss theme, heavy drums 115 bpm, low brass swells, rising dread, no vocals, seamless loop" | + +切换全部走引擎 crossfade;语音 duck 20% 引擎自动。danger 不切 BGM(guide danger earcon + heartbeat 震 + st_hp_4 承担)。 + +### 9.2 SFX(~34 个;SA3=Stable Audio 3,SYN=程序合成;全部双格式、真峰值 ≤-3dBFS) + +| 类别 | id | 方式 | prompt 草稿 / 说明 | +|---|---|---|---| +| 脉冲 | `pulse` | SYN | 180→60Hz 正弦扫频+噪声瞬态,0.15s,中频突出 | +| 正前回声 | `echo_1..4` | SA3 | "close stone wall echo slap-back, bright, short, dry"(2:"mid distance cave echo, moderate tail" 3:"far cave echo, darker longer" 4:"very far cavern echo, long dark tail, huge space") | +| 材质尾音 | `mat_stone` `mat_door` `mat_stair` `mat_water` | SYN | 闷石敲/空心木咚/金属当啷/水滴湿滑,各 0.2-0.25s | +| 侧向回声 | `side_wall_l/r` `side_open_l/r` | SYN | 立体声文件:能量只在 L 或 R 声道;闷哒 vs 空旷回灵(SA3 不达标时程序写 L/R 通道兜底) | +| 移动 | `step_own` `step_turn` `bump` | SYN | 单步/拖步转身/撞墙闷响 | +| 拾取 | `coin` `potion` `gear` | SYN | 金币叮当/喝药咕咚/换装金属轻响 | +| 转层 | `stairs_down` | SA3 | "descending old metal stairs, deep cavern reverb" | +| 怪物脚步 | `mob_step_small_l/c/r` `mob_step_bug_l/c/r` `mob_step_heavy_l/c/r` | SA3 ×3 + ffmpeg pan 派生 ×6 | "small beast skittering steps, dry";"insect chitin crawling clicks";"heavy armored thudding steps"——各生成 1 条单声道,派生左/中/右三份 | +| 攻击前摇 | `tel_small` `tel_bug` `tel_heavy`(+派生 l/r) | SA3 | 小怪扑击嘶声/虫肢节摩擦/重怪蓄力重踏低吼 | +| 战斗 | `atk_swing` `atk_hit` `parry` `hurt` `enemy_die` | SYN/SA3 | 挥拳风声(SYN)/击中甲壳钝响/格挡亮叮(SYN)/受击闷击(SYN)/倒地(SA3) | +| 升档 | `levelup` | SYN | 上行钟声琶音(内容音,允许) | + + monsters 脚步 pan 派生工艺:`ffmpeg -i in.ogg -af "stereotools=mlev=0.7:sbal=-.8" out_l.ogg` 等三向派生,省 SA3 任务数(SA3 实际 ~14 个)。 + +### 9.3 voice + +125 句 ×2 性别(Serena / Uncle_Fu),无 NPC(怪物不说话,遭遇靠 `btl_start_*` 报名)。分组、台词、触发全表见 [16-dungeon-voice-ids.md](16-dungeon-voice-ids.md)。数字全分档整句(层 7 句、血量 4、金币 4、面朝 4、威胁 4、战利 3、死因 3、元进度 3),无运行时拼接。 + +## 10. 向导层接入(engine/guide.js,本作不新增 earcon 词表) + +### 10.1 状态签名(进入状态时播) + +| 状态 | guide.state() | 时点 | +|---|---|---| +| TITLE / MENU | `menu` | 进标题、开菜单 | +| DRILL / EXPLORE / DESCEND | `explore` | 进教学、进探索、下楼完成 | +| BATTLE | `battle` | 遭遇即播(与 btl_start_* 同点) | +| SETTLE | `settle` | 死亡/通关结算进入 | +| danger | `danger` | 血量首次 ≤25%(每层 1 次)+ Boss 半血 | + +choice 签名本作不使用(无抉择态;菜单是 menu)。菜单回绕不播 wrap 句(菜单 ≤4 项,循环成本低,省台词预算)。 + +### 10.2 turn cue 与看门狗 + +| 等待点 | arm | at | say(full 档;L2 级一律纯 nudge,绝不逐字重播) | +|---|---|---|---| +| BATTLE 玩家回合(每回合) | guide.turn | — | turn earcon + confirm 震 | +| BATTLE 玩家回合无操作 | watchdog | [15s, 30s] | `gd_turn` / null | +| EXPLORE 静止 | watchdog | [10s, 25s] | `gd_pulse` / null | +| DRILL 每步等待 | watchdog | [8s] | 无语音,纯 nudge(教学句不重播) | + +### 10.3 卡关三级提示(EXPLORE,escape 先例;"无操作时长"计时,任何有效手势重置) + +进展事件=拾取/战斗胜利/踏上楼梯格/击败窟主/下潜。无进展 60/120/180s →: + +| 级 | 内容 | 附带 | +|---|---|---| +| L1 暗示 | 楼梯未找到 → `stair_hint_a`;窟主未清 → `stair_hint_b` | warning 震 | +| L2 明示 | `stair_dir_e/s/w/n`(绝对方位;与上滑面朝句同坐标系,自洽) | warning 震 | +| L3 带路 | 进入导航模式:每步播 `nav_e/s/w/n`,抵达播 `nav_done`;此后每 60s 重复直至进展 | warning 震 | + +提示到点遇语音在播 → 入待播队列(escape round2 礼仪),玩家操作即作废未播级别。纠错句 idle-only + 过期丢弃(引擎已实现)。 + +## 11. 存档设计 + +```json +{ + "schema": 1, "game": "dungeon", "updatedAt": 0, + "summary": "第3层 · 伤势不轻 · 金币268 · 遗产2/5", + "meta": { "gold": 268, "unlocks": ["potion", "vigor"], "bestFloor": 5, "runs": 7, "kills": 23 }, + "run": { + "seed": 42, "floor": 3, "pos": [5, 2], "face": "E", + "hp": 11, "atkLv": 2, "weapon": 1, "armor": 0, "potion": 1, + "veilUsedFloor": false, "killsRun": 9, "looted": [], "cleared": [], "parries": 4 + } +} +``` + +- **summary 生成规则**:`第{floor}层 · {血量档名} · 金币{meta.gold} · 遗产{unlocks.length}/5`;死亡后 run=null,summary=`深处归来 · 金币{gold} · 遗产{n}/5`。 +- 保存时机:拾取/战斗结算/下潜/菜单"保存并回大厅"/死亡结算。localStorage + PUT 服务器(00 文档 §4)。 +- 读取:有 run → TITLE 播 `sys_continue_ask`,单击继续 → 恢复 `run` + 重放该层 seed 生成 + **局面复述链**(st_floor+st_hp+st_face+st_threat,≤5s 可操作);右滑 = 重掷新周目(run 清空重掷 seed,meta 保留)。无 run 单击 = 新周目(meta.unlocks 生效播报后进 DRILL/EXPLORE)。 +- 归因真话依据:`parries`(全程格挡数)、lastHit 记录(击杀者类型/是否背刺/是否未格挡)在死亡结算选 `die_cause_*` 与 `die_adv_*`,句必须与真实行为对应。 + +## 12. playtest 断言清单(登记 13 号文档新增 §dungeon;mock 机制对齐 web/rpg/test/playtest.mjs) + +**关键路径**(seed=42,首周目 L1→L3 窟主,≤260 手势): +1. 启动 → TITLE(无存档)→ 单击 → DRILL:D0..D9 逐步断言 voice 序(tut_d0→…→tut_done)与 drillStep 递进;D1 断言 pulse 与 echo 播出;D4 长按断言 pos 前进 ≥2;D8 乱按/不格挡断言 HP 不降(教学怪 ATK=0);下滑跳过教学路径断言(二周目不再进 DRILL)。 +2. **seed 复现**:`opts.seed=42` 两次构造 → `_floor.grid`、楼梯/怪物/宝物坐标逐格相等;L1 尺寸 ===7;起点安全区(怪 BFS 距离 ≥3)。 +3. **脉冲回声信息正确**:起点面朝 'N',按 grid 计算正前墙距 → 断言 echo_N 于脉冲后对应延迟±50ms 播出、volume 档正确、材质尾音 id 正确;左右侧向 side_* id 与 grid 一致;遍历 seed=42 的 L1 全网格 ×3 面向抽查 ≥30 组(回声编码 === grid 真值);**双击重发**断言资源零消耗且回声 id 与上次一致(未移动时)。 +4. 拾取:走到金币格 → coin 音 + meta.gold 增加且 st_gold 档位相应跳档;药水满腰带 → evt_potion_full 且药留在原地。 +5. 战斗路径(L2 窟鼠):进入 BATTLE → 回合流转(玩家回合 turn→敌前摇→结算循环);前摇内双击 → 敌伤 ×0.2(HP 差值断言);窗口外双击 → error 震、全额伤;长按逃跑 → 必成且 HP 真实减 ATK、怪留原格;击杀 → loot 档句 + meta.gold 入账。 +6. 下潜:踏上楼梯格 → sys 提示句 → 单击 → DESCEND → floor=2 且 grid 由 floorSeed 重生成(再断言复现)。 +7. **死亡与元进度**:L3 窟主战挂机至死 → 归因链序 `die_intro → die_cause_*(与 lastHit 真实来源一致,Boss 重击 → die_cause_2)→ die_adv_*(按 parries=0 → die_adv_1)→ st_floor_3`;断言 meta.gold = 死亡前+本局所得、`bestFloor` 更新;单击 → TITLE → 新周目 seed≠42、meta.unlocks 按金币阈值播 unlock_* 且起始 maxHp 生效(vigor)。 +8. 菜单/帮助:探索下滑 → 模态菜单(单击循环播项名、双击确认、上滑=help、下滑=关闭);"保存并回大厅" → PUT 断言 data.summary 命中生成规则。 +9. **中断复述**:存档重进 → sys_continue_ask → 单击继续 → 落在存档点 → 自动局面复述链(st_floor→st_hp→st_face→st_threat 顺序断言);上滑同链可重放。 + +**边界断言**: +- 学习期零惩罚:L1 全怪 ATK===0、L2 ≤1(读表断言);L1 全程乱按 HP 不变。 +- 撞墙:长按顶墙 → bump + pos 不变 + HP 不变。 +- 方位自洽:任意格转身后重脉冲 → 左右 side_* 与 grid 随面朝交换;身后方向从不产生回声资产。 +- 怪物脚步:怪每步播 mob_step__,pan/音量档与相对方位、距离一致(含背后=中央减半口径)。 +- 绕位怪背刺:未转身挨打 → 全额伤 + `btl_behind`;转身后格挡 → ×0.2。 +- 缠斗怪左右滑 → `btl_no_turn`(每场 1 次)+ error 震。 +- 自动喝药:HP≤25% 且有药 → HP 回升档 + potion 音;无药时不播不炸。 +- 卡关三级:无操作快进 60/120/180s → stair_hint_* → stair_dir_* → nav_* 链;任意进展事件后重置分级;提示在语音在播时排队不顶句。 +- 看门狗:探索 10s/25s、战斗回合 15s/30s 逐级触发且第 2 级纯 nudge 无语音。 +- 脉冲冷却 1.2s:冷却期单击/双击 → error 震、无 echo。 +- 屏蔽任意 voice 资产:流程不卡死(看门狗兜底推进)。 +- guide=off:全静、零定时器(全局既定契约)。 +- 资产齐套:voice 双性别目录 id 集合一致、sfx/bgm 双格式(assetExists 口径)。 + +**失败即不通过**:死亡归因句与真实死因不符(归因假话);同 seed 两次生成不一致;教学怪造成 HP 损失;双击重发扣资源;回声编码与 grid 矛盾。 + +## 13. 节奏曲线与单局核算 + +- 每层节奏:进层签名句(5s) → 探索(脉冲-移动循环,2-3 分钟)→ 1-3 场战斗(每场 40-60s,窟主 90s)→ 拾取 1-3 处 → 楼梯。每 30-60s 一个奖励点(金币/击杀升档/装备),满足方法论 L5。 +- 满通 24.0 分钟(§6.2 累计列);普通玩家死于 L4-6 → 单局约 14-19 分钟;二周目起(遗产加持)更深。首局若速死(L2-3,~8 分钟)由元进度钩子拉重开——roguelike 死亡结算 ≤15s 且必带「下周目更强」,重开入口单击 ≤2s(A4/L5)。 +- 无任何倒计时(P4);高手速度感=长按连走+免听完整回声直走(P2),脉冲冷却不限制移动。 + +## 14. 自检清单 + +| # | 硬约束 | 自检 | +|---|---|---| +| 1 | FTUE 首输入 <60s | D1 首次脉冲 ≈t+15s ✓(§5) | +| 2 | 首个正反馈 ≤3min | D8 教学战胜利 ≈t+2min;L1 首金币 ≈t+2.5min ✓ | +| 3 | 学习期零惩罚 | L1 怪 ATK=0、L2 ≤1,学期内死亡不可能;撞墙无伤;格挡失败无额外惩罚 ✓ | +| 4 | 失败三问归因 + 真话律 | 死亡链 die_cause_*/die_adv_* 按 lastHit 与 parries 真实选择;挥空/背刺/逃跑挨打均有真话句 ✓(§12 失败即不通过项锁定) | +| 5 | 手势宪法三件套不豁免 | 上滑=状态、下滑=菜单、双击=重听(重发脉冲) 全状态保留 ✓;移动/脉冲偏离已登记 E1-E4 且教学讲清 ✓ | +| 6 | 向导层 | 签名 5 态(不新增 earcon);turn cue 每回合;看门狗 [10/25][15/30]s 第 2 级纯 nudge;卡关 60/120/180 三级不报答案(带路只给方向);纠错 idle-only ✓(§10) | +| 7 | 方法论反模式 | 提示永不逐字重播(L2 级纯音频);成功后只给新短句肯定;单次播报 ≤3 选项(菜单逐项);并发声流 ≤2(频带分离+冷却);语义靠语音、earcon 用引擎统一词表 ✓ | +| 8 | 方位降维 ≤4 | 回声前/左/右(身后不报);战斗面前/左/右/背后;无连续角度 ✓ | +| 9 | 台词规范 | 一句一文件整句;全部 ≤25 字(<40 上限);数字全分档整句;"重听"写"重新听"、"重发"写"再发一次";同游戏同 instruct 常量;无语速词 ✓(voice-ids 注记) | +| 10 | P1 视障评委 | 双击免费重发脉冲;status key 全状态可用;方位/威胁模板句固定;归因真话;可打断/长按快进/2.2 档语速引擎既有;绝不当白痴(不简化深度:绕位/Boss/元进度保留)✓ | +| 11 | P3 通勤 | 单手免视全手势可达;上滑一句话复述局面(层/血量/面朝/威胁);任意时刻菜单存档退出;死亡不清 meta ✓ | +| 12 | P4 工人 | 教学逐字跟练+失败 2 次自动过;容错高(ATK0 怪/自动喝药/必成逃跑);无倒计时;零新手势(复用 swipe/tap/longpress)✓ | +| 13 | P2 大学生 | 元进度结算「下周目你会更强」;击杀升档+装备即时成长;长按连走速度感;死亡重开入口 ≤2s ✓ | +| 14 | seed 可复现 | floorSeed 派生公式 + opts.seed 注入 + 复现断言(§6.1/§12.2),与 playtest 种子 PRNG/强制队列/虚拟时钟机制兼容 ✓ | +| 15 | 三套信息自洽 | 单源 grid 查询;回声=静态地形/脚步=动态怪物/播报=绝对面朝,模板句固定,语义域零重叠 ✓(§3.2) | +| 16 | 引擎能力边界 | 无实时 pan(预 pan 资产);无引擎外节点(setTimeout+volume 调度);tap 300ms 延迟规避(格挡绑双击);30MB 预算内 ✓ | + +--- + +## 实现核对(测试 agent,2026-09-02) + +> 测试载体:`web/dungeon/test/playtest.mjs`(mock 层+虚拟时钟照 `web/rpg/test/playtest.mjs`, +> 1277 断言全绿,`tools/node/bin/node web/dungeon/test/playtest.mjs` 退出码 0); +> 会话剧本 `web/harness/sessions/dungeon.mjs`(`session` 主线弧线 + `sessionStress` 乱按压力, +> 经 `web/harness/transcribe.mjs dungeon [--session sessionStress]` 跑通,全程零资产缺失、零死锁)。 + +### §12 关键路径 + +| # | 断言 | 结果 | +|---|---|---| +| 1 | 启动→TITLE→DRILL:D0..D9 逐步 voice 序(intro_1/2+tut_d0→…→tut_done)与 drillStep 递进;D1 pulse+echo;D4 长按 pos ≥2;D8 乱按 HP 不降;下滑跳过+二周目不再进 DRILL | ✅(场景 2;D2 错手势重教、D3 连错 2 次自动通过、D8 乱按 80 手势 HP=20 一并覆盖) | +| 2 | seed=42 两次构造 grid/怪/宝/楼梯逐格相等;L1 尺寸 7;起点安全区 BFS≥3(教学局非门怪 ≥4) | ✅(场景 1:7 层全查 + 实流 `_floor` 与纯函数一致) | +| 3 | 回声编码 === grid 真值:全地板格 ×4 面向扫描(≥30 组),echo_N 延迟/音量、材质尾音 +120ms、侧向 +350ms 预 pan 资产 L/R;双击重发零消耗且编码一致 | ✅(场景 2:104 组合全扫,代表组精确到 0ms;身后不报=恰两路侧向) | +| 4 | 拾取:coin 音+meta.gold+金币档跳档(45+堆≥55→st_gold_2);药水满腰带 evt_potion_full 且药留原地 | ✅(场景 4) | +| 5 | 战斗:单击命中、缠斗 btl_no_turn 每场 1 次、前摇内双击 ×0.2(钩子 atk=10 → 伤 2)、窗口外/不格挡全额、长按逃跑必成挨一下怪留原格、击杀 loot 档+meta.gold | ✅(场景 5) | +| 6 | 下潜:踏上楼梯 sys 提示→单击→DESCEND→floor=2 且 grid 重生成复现 | ✅(场景 2) | +| 7 | 死亡与元进度:归因链序+真话、meta.gold 入账、bestFloor/runs、遗产购置播报、新周目 seed 重掷+继承(vigor 26/potion 1) | ✅(场景 6a/6b/6c 三种死因矩阵) | +| 8 | 菜单:单击循环/双击确认/上滑帮助/下滑关闭;「保存并回大厅」→ sys_saved → `'../'`;summary 生成规则 | ✅(场景 7 + 场景 2 D7) | +| 9 | 中断复述:sys_continue_ask→单击→st_floor→st_hp→st_face→st_threat 有序;上滑同链可重放 | ✅(场景 8) | + +### §12 边界断言 + +| 断言 | 结果 | +|---|---| +| 学习期零惩罚:L1 全怪 ATK=0、L2 ≤1(读 `_battle.atk`);L1 乱按 HP 不变 | ✅ | +| 撞墙:bump + pos 不变 + HP 不变 | ✅ | +| 方位自洽:转身后重脉冲左右 side 随面朝交换(含全扫描 4 面向) | ✅ | +| 怪物脚步:pan/音量档(0.9/0.6/0.35)、背后中央减半(1 格 0.45、6 格 0.175)、>8 格不播、≤2 格 heartbeat | ✅ | +| 绕位怪:每 2 回合换位(右→后→左确定性轮换)、脚步报位、背刺 btl_behind+全额、转身追击命中、转身后格挡 ×0.2 | ✅ | +| 缠斗怪左右滑 → btl_no_turn(每场 1 次)+ error 震 | ✅ | +| 自动喝药:HP≤25% 有药 → 回 40% maxHp(ceil)+ potion 音+档播报;Boss 半血 ATK+1 + btl_half_*;Boss 逃跑拒绝 btl_flee_boss | ✅ | +| 卡关三级 60/120/180s:stair_hint_a→stair_dir_*→nav_* + 导航每步播方向 + nav_done;进展重置分级(重置后从 L1 重新触发);语音在播入队不顶句、操作作废、未播级别可重新触发 | ✅(场景 9) | +| 看门狗:探索 10s gd_pulse、战斗 15s gd_turn(guide=full);DRILL 8s 纯 nudge 无语音 | ✅(场景 10) | +| 脉冲冷却 1.2s:冷却期单击仅 error 震、无第二次回声 | ✅ | +| 屏蔽任意 voice:遭遇报名/下潜旁白被屏蔽 → guard 兜底推进不卡死 | ✅(场景 11) | +| guide=off:30s 空闲零播放、零定时器 | ✅ | +| 资产齐套:voice 双性别 125 句集合一致、sfx/bgm 双格式、game.js 静态引用展开全存在、运行期零缺失 | ✅ | +| E3:对局期 setRateBoostEnabled(false)(DRILL/EXPLORE/BATTLE/DESCEND)、TITLE/MENU/SETTLE 恢复 true | ✅(rateLog 断言) | +| E4:菜单=暂停——探索游走冻结、战斗前摇剩余窗口冻结、恢复恰结算一次(无重复回合) | ✅(场景 7;前摇内下滑原本不可达,见 bug ⑧) | + +**§12 失败即不通过项**:死亡归因真话(cause/adv 与 `_deathInfo.lastHit` 一致,三种死因矩阵验证)、同 seed 两次生成一致、教学怪零伤害、双击重发零消耗、回声编码与 grid 一致——全部锁定 ✅。 + +### 与设计文档的偏差(记录不大改) + +1. **归因句粒度**:台词表死因仅 3 档(小怪拖垮/Boss 重击/背刺),无「指认具体怪 A」整句资产;真话律落地为 `_deathInfo.lastHit.type` 与 cause 档位一致(playtest 断言 `lastHit.type==='crab'/'rat'/behind` ↔ die_cause_2/1/3)。资产表内不存在更细句子,不算偏差。 +2. **战斗上滑第 2 段**:§2.2「敌方血量档」以 `btl_half_*`(半血阶段句)近似——台词表无 foe_hp_* 分档资产。 +3. **§5 D4「撞墙也算过」**:实现额外把 D4 自动面向东侧 2 格走道+边界,保证 ≥2 格可达成(生成约束注释已写明)。 +4. **L1 安全区**:非门怪 BFS≥4(严于规格 ≥3),避免 D4/D8 演练路径撞上第二只怪,注释已写明。 +5. **die_adv 兜底**:挡过且跑过(parries>0 && flees>0)死于非背刺 → die_adv_1(建议句非过去事实句,规格未覆盖该象限,取最无风险句)。 +6. **格挡成功播 `btl_parried`**:规格表 B 组 trigger=「格挡成功(减伤 80%)」,原实现该资产死引用,已接线(见 bug ②)。 + +### 修过的 bug(web/dungeon/game.js) + +1. **归因真话律**:`run.parries++` 原在双击瞬间计(背对双击也计),歪曲 die_adv 选句依据——改为仅结算判成「面对且格挡成功」时计数。 +2. **`voice/btl_parried` 死资产**:格挡成功(减伤 80%)从不播报——接线进 `_enemyResolve` 反馈链。 +3. **卡关提示「未播级别计入 shown」**:入待播队列即标已提示,被玩家操作作废后该级永不重触发(违反 §10.3/escape round2 礼仪)——改为真正播出才计入,另加待播去重。 +4. **终局层 L3 带路死路**:`_navRouteVoices`/`_navSpeakStep` 只查 `stairs`,L7 无楼梯致导航哑火——回退 `stairs || throne`(与 L2 明示同口径)。 +5. **SETTLE 双击=重听结算链**:原只重播 `settle_again` 尾句——存 `_settleChain` 整链重放(§2.3)。 +6. **DRILL D1 无看门狗**:`_drillEnter(1)` 无分支——补 `_drillWatchdog(1)`(§10.2 教学每步 [8s])。 +7. **DRILL D8 上滑=做错**:§4.3 状态键「教学后可用」(D7 已教)——D8 补 up→状态链。 +8. **E4 豁免半实现**:战斗前摇(telegraph)内下滑走 error 分支、菜单打不开,`_menuPause` 冻结机制不可达——前摇内补 `down→_openMenu()`,战斗任意时刻可暂停。 + +### 遗留 TODO + +- 教学期 L1 游走静默:DRILL 内非教学怪不游走(`_startMobs` 仅 EXPLORE 调用),设计未明确,维持现状(D9 后下潜即恢复)。 +- 木门/水材质(mat_door/mat_water):生成器不产出,资产与编码路径已备(`_echoScan` 注释),待未来层型。 +- `meta.runs` 不统计 TITLE 右滑放弃的周目(仅死亡/通关计入);规格未明确。 +- 会话剧本主线弧在随机图极小概率把窟主打死(玩家成长超预期时)——剧本有「没死就继续走」兜底分支,不影响零死锁承诺。 + +### R2 修复回归(2026-09-02,D0-1..D0-3 + D1-1..D1-6 结构修复轮) + +> 证据源:research/new8/dungeon/round1/(synthesis + live-player + p1-blind)。本轮只修结构不加新内容; +> 引擎侧按产品裁定允许改动(web/engine/audio.js、gesture.js)。回归:8 套 playtest + guide.test 全绿 +> (dungeon 1277→**1610** 断言;escape 140 / radio 221 / rhythm 141 / rpg 188 / simon 132 / space 155 / +> storyline 82 / guide 122,0 失败,radio 在并行修复中仍全绿)。 + +#### D0-1 语音抢占死循环(P0) + +- **根因(引擎级竞态,非 DRILL 泄漏本身)**:`WebAudioEngine._playVoice` 的「顶掉当前对白」检查在 + `await this._load(id)` **之前**,而 `this._voice = token` 在其后无条件赋值——同一 tick 双调度语音 + (教学 D1:反馈句 `tut_echo_come` 裸 `_speak` + `_drillPass` 立即 `_speakGuarded` 下一句指令)在加载 + 竞速后,输家的 token 被静默顶号:**ScriptProcessorNode 永远无人 disconnect(僵尸节点空转抽 buffer)**, + 其 endTimer 亦因 `_finishVoice` 的 token 校验提前 return 而永不清理。R1 live 证据完全吻合:t=130.2s + D1 首脉冲后 tut_echo_come 僵尸节点持续抽读,探针按「voice buffer 被抽读」记段 → 71 分钟 35,745 次 + 「播放」99.7% 被「掐死」、nav_n↔tut_echo_come 互掐 33,297 次;跳教学二周目 0 抢占(无 D1 双调度)。 +- **修**:① `audio.js _playVoice` 在 await 之后、赋值之前补杀当前 token(顶掉语义在加载完成后复核, + 后到者胜、败者必被杀);② 游戏 D1/D6 反馈句并入 `_speakGuarded` 推进链(消灭同 tick 双 play 的源头), + `_drillDone`/`_drillSkip` 收尾显式 `guide.disarm()`;③ **全线风暴保险丝**(`audio.js play()` voice 入口): + 同一 voiceId 3s 窗口内被调度 ≥5 次 → 丢弃后续并 console.error(每 id 一次)。 +- **断言**:`保险丝:同 id 3s 窗口内前 4 次调度放行 / 第 5 次起丢弃 / 其他 id 不受累 / 窗口滑出恢复 / + 只作用于 voice 层`(真实 WebAudioEngine 单元断言,场景 12A);`D1: 反馈句与下一步指令句串行`(场景 2); + `纯探索 60s 零操作语音调度有上界(≤3 句;R1 风暴同期 >2000 句)`(场景 12C)。 + +#### D0-2 回声信息真实性(P0) + +- **根因**:① 侧向 `_sideScan` 把「整条侧射线到边界全通」才判开口——房间/走廊地型恒为假 → side_open + 两局 40+ 脉冲 0 次、能走的方向报墙;② 站在楼梯格上脉冲走射线扫描(楼梯在脚下不在面前)→ mat_stair + 0 兑现,教学「铁器=楼梯」成谎言(P1 专项:165 脉冲 0 铁器,含站在楼梯上 t+142.2)。 +- **修**:回声与 grid 单源重接——侧向第一格地板=开口(§3.1「墙=闷/开口=空旷」2 档口径,墙保留射线距离); + `_echoScan` 站楼梯格 → 贴身(dist1)铁器回声。 +- **断言**:场景 2 D9 全扫描(全地板格 × 4 面向真脉冲)truthScan 同步更新;场景 12B **全网格一致性**: + seed=42 L1..L7 每格 × 4 面向 `_echoScan`/`_sideScan` 与测试侧独立真值逐格相等(≥200 格)+ + `全网格存在侧向开口(R1 全程 0 次 → 现扫到 N 格次)` + 各层站楼梯=贴身铁器。转写复核:主线局 + mat_stair/side_open 均稳定出现。 + +#### D0-3 探索内容真空(P0) + +- **① 密度下限**:`_fillMainPathDensity`——start→楼梯(终局=王座)最短路(BFS 距离梯度回溯,确定性) + 每 ≤12 格必有内容遭遇,超限段中点补本层已有普通怪型(不加新怪种,≤8 只,安全区口径不变)。 + 断言:场景 1 各层 `主路径内容密度(最长真空 ≤12 格)` + 构成 ⊇ 规格表。 +- **② 探索呼吸层**:新 SYN 资产 `sfx/amb_cave`(gen_sfx.py:45-320Hz 带通空气声 + 4 整周期/10s 呼吸 + LFO + 两粒远处水滴;10s 无缝循环源:去直流、环回交叉淡化、**不首尾淡化**,解码回读 ogg -4.20dBFS / + m4a -4.00dBFS、|直流|≤0.001、循环接缝 -25dB(rel peak);20 文件 ×2 格式全过 QC)。接线:EXPLORE 静默 + ≥20s(无手势无人声)播 amb_cave(volume 0.35,20s 节奏不连播)。guide=off 契约不变(环境垫属内容层, + 场景 4 的零播放断言已按此口径更新并加 amb 专项断言)。 +- **③ 下潜演出**:游戏代码本身完好(playtest 场景 2 一直断言 stairs_down→evt_descend→st_floor_N 通过); + R1 转写零演出为**剧本伪影**:(a) navigate 按 1 格/按建模,但 harness `waitVirtual(120)` 在虚拟时钟会 + 越过 +500ms 步进计时器(advance 至少推进到下一到期定时器)致每按多走 1+ 格、永远踩不准楼梯格; + (b) D9 用 `pulse:false` 违反「玩家靠耳朵」红线且无到站重试。修:navigate 立即松开(恰 1 格)+ `until: + 'stairs'` 到站口径 + 逐层验证的下潜循环(tapDescend 只在真踩楼梯时出手,DRILL D9 同样受理)。 + 复核:主线转写两次运行弧线完全一致(两次下潜全套演出:stairs_down→evt_descend→st_floor_N)。 +- **④ 卡关计时器**:计时与分级 R1 live 已证完好(64s/120s/180s 准时);转写 350s 未触发系剧本持续手势 + 重置 lastAct(§10.3 口径「任何有效手势重置」)+ 上句的导航伪影。断言:场景 12C `卡关 L1 暗示 60s 自然 + 触发(纯探索、无 hook)`。 + +#### D1 级修复 + +- **D1-1 菜单关闭无回执**:live 复核实为「关了但零反馈,下次下滑又重开」——路由本身无恙。修:`_closeMenu` + 补状态签名 earcon(回 EXPLORE/DRILL=explore、BATTLE=battle、SETTLE=settle、TITLE=menu)。断言:场景 10 + `菜单关闭播回程状态签名(menu → explore)`。 +- **D1-2 D9 看门狗兜底**:`_drillWatchdog9()`(8s nudge、30s 升级播 stair_hint_a——自寻路步必须有救援); + D9 每次手势后重挂(onGesture 的 disarm 会清掉它,而 D9 的脉冲/转身不推进步骤)。断言:场景 10 两级触发 + + 升级句。 +- **D1-3 导航指墙 + 蘑菇软封锁**:① 带路改为**最短路第一步方向**(`_navFirstStepDir`:从目标反向 BFS 取 + 梯度下降邻格),逐步校正;同向 ≥1.5s 才提醒(改向必播)——既不指墙也不撞全线保险丝(导航句同 id 连发 + 会计熔断)。断言:场景 9 `带路方向与 grid 最短路一致 + 第一步必可通行 + 节流单测(同向连呼 3 次只播 1)`。 + ② 蘑菇/所有游走怪**相邻即扑击接战**(`_mobStep` 移动后与玩家正交相邻 → `_encounter`),杜绝 R1 L3 + 「贴身绕圈、堵格不开战」35 分钟软封锁。断言:场景 12C 相邻怪必然开战。 +- **D1-4 战斗结束 BGM**:教学战胜利/逃跑回 DRILL 的两条路径原先不切回 bgm/explore(只有菜单关闭路径恢复 + ——R1 转写 t=35.8 起战斗鼓点泡全程的组成因素之一)。两条路径补 `bgm/explore` 淡入。断言:场景 5 胜利/ + 逃跑后 `lastBgmEntry === bgm/explore`。 +- **D1-5 金币档**:SETTLE(`_die`/`_settleWin`)原用 run 金币自设阈值(<20/<60),TITLE 用 + `_goldBand(meta.gold)`(<50/≤200/≤600)——同一句 id 两套口径(live:SETTLE meta_gold_3 vs TITLE + meta_gold_2)。统一为同一分档函数同一账本(遗产购置后的 meta.gold)。断言:场景 6a `SETTLE 金币档 = + _goldBand(meta.gold)`。 +- **D1-6 三件**:① 长按 boost 闪跳——E3 豁免期快进本就不生效,但 gesture.js 仍外发 + `soundgame:longpress(+end)` 窗口事件(仪器记为 boost on→off 闪跳):`setRateBoostEnabled` 现同步经 + `setLongpressBoostEventsEnabled` 抑制窗口事件(游戏手势回调不受影响)。② D7「菜单被系统自动收」经 live + 事件流复核为风暴伪影(菜单开场链 sys_menu→首项名被僵尸节点反复重放,被听成自动执行 sys_menu_resume) + ——D0-1 修复即愈,无额外代码;playtest 场景 2 已有「菜单保持打开直到玩家下滑关闭」断言。③ 战斗回合 + 15s 无操作播合成回执 `sfx/turn_tick`(880Hz 木鱼族,gen_sfx.py,-3.87dBFS)——standard 档无 gd_turn + 语音也有「还到你」的确认。断言:场景 10 turn_tick + 引擎豁免事件抑制(rateLog 既有断言不受影响)。 + +#### 伪影修正(web/harness/sessions/dungeon.mjs) + +- 「元进度不兑现」关案依据 + 三处剧本脱轨根因:① `waitVirtual(120)` 虚拟时钟越表多走(见 D0-3③); + ② tapDescend 不受理 DRILL(D9 最后一击点不响);③ 单发 navigate+tapDescend 无重试,任何被怪打断即脱轨 + ——逐层验证下潜循环修复。另修 navigate 中「走位途中被扑→长按按到战斗里=瞬间逃跑」(R1 live btl_flee×9 + 的剧本同款)。复核:主线转写 2 次弧线逐句一致(含归因链与金币档);压力转写 2 次均零死锁走到死亡-重开。 + +#### R2 新增/变更断言口径登记 + +- 场景 1:怪物构成由「逐字相等」放宽为「⊇ 规格表 + 补怪限本层怪种 ≤8」(密度下限所需)。 +- 场景 4:`guide=off 30s 零播放`改为「零语音/零向导播放,唯 amb_cave 按呼吸层口径除外」(内容层 vs + 向导层切分)+ 呼吸层节奏/重置断言。 +- 场景 9:L3 带路从「整段先东西后南北 + 每步必播」改为「最短路首步 + 改向必播/同向 1.5s 节流」 + (§10.3 L3 口径的听感与保险丝友好修订)。 +- 台词/资产表:无新增 voice;新增 sfx `amb_cave`、`turn_tick`(SYN,双格式,QC 数字见上)。 diff --git a/research/new8/00-program.md b/research/new8/00-program.md new file mode 100644 index 00000000..b0771705 --- /dev/null +++ b/research/new8/00-program.md @@ -0,0 +1,85 @@ +# 新 8 款纯音频 RPG 开发计划(Program Charter) + +> 状态:进行中 | 启动:2026-09-01 | 上级文档:[HANDOVER](../../HANDOVER.md)、[研究章程](../00-charter.md) +> 用户指令:再开发 8 款纯音频 RPG(不限题材);4 款用「声命线」叙事模式,4 款探索其他模式; +> 单局 15-30 分钟;每款 ≥6 轮多 agent 迭代;验收用研究章程门槛;节奏 = 2 款一批流水线。 + +## 1. 角色与流水线(多 agent 交互迭代) + +制作人(主 agent)统筹以下五个角色,每轮全员过一遍: + +| 角色 | 职责 | 承载 | +|---|---|---| +| 产品 | 概念定位、验收标准、轮次裁定(P0 必修清单) | 主 agent + 本文档 | +| 策划 | 设计文档:状态机/手势映射/台词表(voice-ids)/BGM 映射/难度曲线 | 策划 subagent,产出 `docs/design/2X-*.md` | +| 开发 | game.js + gen 脚本 + 资产生成 + 大厅/服务器接入 | 开发 subagent,遵守 `docs/web-engine-api.md` | +| 测试 | playtest.mjs 断言 + guide.test + 资产终检 + 语法校验全绿 | 测试 subagent | +| 玩家 | live 试玩(真实混音+ASR 判定)+ 4 人设代理评审 + 愿意继续玩投票 | live 管线 + 玩家 subagent ×4 | + +迭代回合协议(每款游戏,round K = 1..6+): + +1. **开发**:K=1 首次实现;K>1 修复上轮 P0/P1 清单。 +2. **测试**:playtest/guide/资产三绿才可进入玩家评审。 +3. **玩家·live 试玩**:经 `web/harness/live/control.mjs` 打 ≥2 个会话(主线路 + 随机/乱按压力线),产出 events/transcript/verdicts。 +4. **玩家·人设评审**:P1 视障评委 / P2 大学生 / P3 白领通勤 / P4 工人(人设卡见 [personas](../04-playtests/personas.md))各写一份评审(格式同研究项目:状态感知 x/5、下一步明确性 x/5、愿意继续玩、P0 问题、建议 ≤3)。 +5. **产品·synthesis**:汇总 P0(必修)/P1(尽量修)/P2/P3,裁定进入下一轮的修复清单;记录于 `roundK/synthesis.md`。 + +**批次验收门**(连续 2 轮达成才算过):每款游戏 ≥6 轮; charter 门槛 = ≥3/4 人设愿意继续玩 且无 P0 级状态混淆(不知道在哪/该做什么/为何失败)。 + +## 2. 8 款游戏概念(产品定稿 v1) + +命名规范:slug = 目录名(`web//`);设计文档 `docs/design/15..22-.md`; +迭代记录 `research/new8//roundK/`。 + +### 叙事模式 4 款(声命线范式:章节推进 + 左右滑抉择 + 多结局;全部「声音即世界」题材) + +| # | 代号/slug | 题材 | 一句话 | 模式要点 | +|---|---|---|---|---| +| N1 | 不眠频率 `radio` | 深夜电台热线 | 你是午夜档接线主播,一晚五个来电,每通电话的命运由你接话的方式改变 | 电话音质叙事;跨来电的「夜班状态」影响结局;4 结局 | +| N2 | 深海回音 `abyss` | 深海救援 | 你是深潜救援员,声呐里是被困潜艇的船员;氧气在倒数,每次下潜都是抉择 | 声呐/通讯双通道;氧气=全局压力计时;船员生死分支;4 结局 | +| N3 | 暗码 `cipher` | 1940 密战 | 你是双重间谍报务员,截获的每封密电都要你决定「怎么译、报不报」 | 电文解码小局嵌叙事;身份暴露度=隐藏资源;4 结局 | +| N4 | 守塔人 `keeper` | 孤岛灯塔 | 风暴夜,无线电里逐渐靠近的求救声,牵出灯塔家族三代秘密 | 单一夜晚时间轴;回忆插叙(音色区分);3-4 结局 | + +### 其他模式 4 款(玩法核心各不相同,全部角色扮演) + +| # | 代号/slug | 模式 | 一句话 | 模式要点 | +|---|---|---|---|---| +| X1 | 回声地窟 `dungeon` | 听声辨位 Roguelike | 单击发声波脉冲听回声「看」地图,听脚步辨怪,逐层下降,死了重来 | 程序生成楼层;回声定位是核心动词;元进度解锁;死亡=新周目 | +| X2 | 雨夜专案 `detective` | 本格推理 | 你是专案组探长:问询、比对证词、指证矛盾,锁定真凶 | 证词库+对质机制;声纹/细节辨谎;结案评级 S-C | +| X3 | 深夜茶馆 `teahouse` | 经营养成 | 你接手一家深夜茶馆,客人的故事随你泡的茶展开,攒口碑升级店铺 | 多晚连续剧结构;菜单/服务抉择影响客流;店铺成长线 | +| X4 | 星环舰长 `bridge` | 舰桥指挥 | 你是巡逻舰舰长:多系统方位音管理 + 危机抉择 + 船员忠诚 | 四象限系统状态(引擎/护盾/扫描/武器);危机事件卡;舰船升级 | + +批次划分(每批 1 叙事 + 1 新模式,两种风险都早暴露): + +- **批次 1**:N1 `radio` + X1 `dungeon`(打样批次,沉淀管线模板) +- **批次 2**:N2 `abyss` + X2 `detective` +- **批次 3**:N3 `cipher` + X3 `teahouse` +- **批次 4**:N4 `keeper` + X4 `bridge` + +## 3. 全局硬约束(策划/开发必读) + +1. 手势宪法:[00-global-ux](../../docs/design/00-global-ux.md);向导层:[14-guide-layer](../../docs/design/14-guide-layer.md)(新台词必须同步其 §6 规范)。 +2. 引擎 API:[docs/web-engine-api.md](../../docs/web-engine-api.md);新游戏复制 `web/template/` 起步。 +3. 资产管线:台词先进 `docs/design/2X-*-voice-ids.md`(id+全文+instruct),再进 `tools/gen_voice_dual.py` 台词表;两表必须一致;改资产必跑 `tools/check_all_assets.py`。 +4. 语音生产:GPU 串行(`flock .gpu.lock`);多音字避坑与 instruct 规范见 [asset-manifest](../../docs/audio/asset-manifest.md);单句 ≤40 字自动拆句。 +5. BGM:Mureka instrumental $0.03/任务(2 候选),程序预算上限 **$2.00** 整个项目;prompt 用英文 + seamless loop;节奏类必须 `tools/bpm_precise.py` 实测;台账记 `tools/mureka_log.md`。 +6. 音效:Stable Audio 3(需 HF token,环境已就绪)+ 程序合成兜底;one-shot 真峰值 ≤-3dBFS;MDCT 过冲坑见 [asset-pipeline](../../docs/asset-pipeline.md)。 +7. 服务器存档:进度 data 必带 `summary` 字符串;注册/登录/进度 API 见 `web/server/README.md`。 +8. 试玩测试:playtest 一律 `settings.set({guide:'off'})` 验纯玩法;向导行为断言只住 guide.test.mjs;断言清单先登记 [13-playtest-criteria](../../docs/design/13-playtest-criteria.md) 对应章节。 +9. 每局 15-30 分钟:叙事款按「一条完整抉择路径 15-25 分钟」设计(分支重玩覆盖其余结局);模式款按「单局/单周目 15-25 分钟」设计。 +10. 每轮迭代记录齐套:`research/new8//roundK/{p1..p4,synthesis}.md` + live 产物目录;synthesis 的 P0 清单必须逐条带入下一轮修复。 + +## 4. 批次进度账 + +| 批次 | 游戏 | 设计 | 资产 | 开发 | R1..R6 | 验收 | +|---|---|---|---|---|---|---| +| 1 | radio | ☑ | ☑ | ☑ | R1 ☑(5/5 愿意玩,P0=0)→ R2 修复中 | ☐ | +| 1 | dungeon | ☑ | ☑ | ☑ | R1 ☑(0/4 拒绝,语音风暴 P0)→ R2 结构修复中 | ☐ | +| 2 | abyss | ☐ | ☐ | ☐ | ☐ | ☐ | +| 2 | detective | ☐ | ☐ | ☐ | ☐ | ☐ | +| 3 | cipher | ☐ | ☐ | ☐ | ☐ | ☐ | +| 3 | teahouse | ☐ | ☐ | ☐ | ☐ | ☐ | +| 4 | keeper | ☐ | ☐ | ☐ | ☐ | ☐ | +| 4 | bridge | ☐ | ☐ | ☐ | ☐ | ☐ | + +> 概念代号在策划阶段可改易读的正式名称;slug 不变。 diff --git a/research/new8/batch1-assets-report.md b/research/new8/batch1-assets-report.md new file mode 100644 index 00000000..1116ab2d --- /dev/null +++ b/research/new8/batch1-assets-report.md @@ -0,0 +1,84 @@ +# 批次 1 资产报告:radio《不眠频率》+ dungeon《回声地窟》 + +> 音频资产管线 agent 产出 · 2026-09-02 · 覆盖任务书 A(语音)/ B(BGM)/ C(音效)/ D(收尾)。 +> 总耗时 ≈ **35 分钟**(00:47–01:21,含 2 轮 GPU TTS 串行、8 个 Mureka 任务、全部 QC 与文档)。 + +## 1. 语音(TTS,GPU flock 串行) + +| 项 | radio | dungeon | +|---|---|---| +| 台词句数 | 132(双性别 103 + NPC 平铺 29) | 125(×2 性别,无 NPC) | +| 生成文件 | 103×2 + 29 = 235 条 ×2 格式 = 470 文件 | 250 条 ×2 格式 = 500 文件 | +| speaker | HOST=Uncle_Fu/Serena,PROD=Dylan/Vivian(VOICE_SPK 逐句覆盖,同性别双音色) | 全表 Uncle_Fu/Serena,单一 instruct 常量 | +| `--check` | ALL OK(无时长离群/短句) | ALL OK | + +- 台词表进入 `tools/gen_voice_dual.py`(新增 radio/dungeon),**由一次性提取脚本直接解析两份 voice-ids 设计文档生成**,并做了二次闭环校验(重解析文档 vs 脚本内表格逐字 diff)——台词全文与文档一字不差。 +- NPC 平铺按 rpg 先例写入 `NPCS["radio"]`(id, speaker, instruct, 台词),staging 走 `/npc/`,convert 落 `web/radio/audio/voice/.*`。 +- `extract_voice_text.py` 已刷新 `web/harness/voice_text.json`:games=8,radio 132 条、dungeon 125 条。 +- **文档内矛盾检查**:无重复 id、无超 40 字(汉字口径,与文档自查注记一致;p7/p9 含标点 41/43 字,走章程 §3.4 自动拆句机制,0.12s 句间停顿)。发现一处**文档头部账目笔误**(不影响管线,未改文档):15-radio-voice-ids.md 头注「HOST 21 + PROD 82」与逐行表不符,逐行表实数 HOST 29 + PROD 74(总数 103 一致)。 +- **radio 电话音质感(资产侧带通,15-radio.md §6.2)**:convert 转码时按来电者逐档 `highpass+lowpass+轻压缩`(老周 500-2800 / 小鹿 400-3400 / 糖糖 450-3000 / 陈姐 420-3100 / 老韩 300-3400 Hz,均 acompressor threshold=-20dB ratio=3 attack=10 release=150);已验证滤波生效(NPC 高频带能量 ≈ 未滤波 HOST 的 1/4,时长与 staging 完全一致);ogg+m4a 双格式均从同一滤波链转出。未滤波原声保留在 `tools/gen_voice_dual_wav/radio/npc/`(不入库)。 + +## 2. BGM(Mureka API) + +8 首全部一次成功,无重试;**总花费 $0.24(预算 $0.30 内,台账见 `tools/mureka_log.md` 2026-09-01 节,项目累计 $0.87/$2.00)**。 + +| 曲目 | task id | 选用 choice | 候选时长 | 响度目标 | +|---|---|---|---|---| +| radio onair | 158551062544386 | 158551165370369 | 135.5/201.2s | -20 LUFS | +| radio bridge | 158551207313409 | 158551341465604 | 211.2/225.1s | -20 LUFS | +| radio menu | 158551383277574 | 158551500914690 | 188.6/222.0s | **-24 LUFS**(menu pad,10 文档 §1.2) | +| radio ending | 158551538401281 | 158551630872578 | 202.0/189.2s | -20 LUFS | +| dungeon menu | 158551678976001 | 158551817322499 | 162.8/177.8s | -20 LUFS(16-dungeon.md §9.1) | +| dungeon explore | 158551855333377 | 158551978868739 | 177.0/209.8s | -20 LUFS | +| dungeon battle | 158552010457089 | 158552169709569 | 186.4/225.9s | -20 LUFS | +| dungeon boss | 158552209555457 | 158552331321345 | 149.5/202.3s | -20 LUFS | + +- 选用原则:拿不准选时长更接近 90s 的候选(仅 radio ending 选候选 1)。 +- 后处理(脚本入库 `web/{radio,dungeon}/gen/gen_bgm.sh`,可复现):首个强 onset(silenceremove -35dB)→ 93s 裁剪 → 首尾 3s acrossfade(tri) 无缝循环 → loudnorm(TP=-2/LRA=11)→ 44.1kHz → ogg q3 + m4a 128k。**成品全部 90.00s**,mean -20.4~-24.7dB,循环接缝差值与在库 BGM 同量级。 +- 无节奏关键曲目,未跑 bpm_precise(dungeon battle/boss 的 bpm 仅为 prompt 特征词)。 + +## 3. 音效(SYN 程序合成;SA3 全部跳过) + +- `web/radio/gen/gen_sfx.py`:`ui_confirm ui_menu`(storyline 全局先例同款),2 文件 ×2 格式。 +- `web/dungeon/gen/gen_sfx.py`:19 文件 ×2 格式 = 17 id(`pulse`、材质尾音 4、**预 pan 立体声 `side_wall_l/r side_open_l/r`**(mono 源 + ffmpeg `pan` 派生,脚本断言极性:能量只在目标声道)、`step_own step_turn bump coin potion gear atk_swing parry hurt levelup`)。 +- **QC(解码回读法,全部通过)**:逐声道真峰值 ≤ -3dBFS(修复:硬 pan 立体声不能按声道均值测,否则漏检超限;AAC/MDCT 过冲按实测迭代补 volume);|直流| ≤ 0.001(与在库 91 件 sfx 基线一致;修复:源去直流 + 8ms cos² 淡入淡出 + 前后垫 60/120ms 静音 + highpass=f=30 抑制编解码直流再生);无首尾爆豆。最终 42 文件全绿。 +- 可复现:两个 gen 脚本入库,一键重出全部 ogg+m4a;staging(`web//gen/staging/`、`tools/bgm_post_wav/`)已加入 .gitignore 不入库。 + +## 4. 缺失资产清单(SA3 待 HF token,本轮全部跳过、未尝试生成) + +**radio(13 件)**:`onair_sting phone_ring line_connect line_drop amb_studio amb_rain_city amb_station amb_kid_room arg_muffled amb_street_rain amb_cab brake_screech tape_static`(15-radio.md §5.2;prompt 草稿在表内,token 就绪后按 gen_rpg_sfx.sh 工艺补产)。 + +**dungeon(24 文件 = 13 个 SA3 源 + 11 个 pan 派生)**:`echo_1 echo_2 echo_3 echo_4 stairs_down atk_hit enemy_die mob_step_small mob_step_bug mob_step_heavy`(+各 l/c/r 三份派生)`tel_small tel_bug tel_heavy`(+各 l/r 派生)(16-dungeon.md §9.2;mono 源 → `stereotools`/`pan` 派生工艺同表注)。 + +**SA3 缺失影响评估**:radio 的 amb/线索音缺失时游戏层按「资产缺失回退路径」静默降级(设计 §9 断言已有屏蔽资产的兜底断言);dungeon 回声/脚步是核心动词,**开发联调前建议先补产 dungeon 的 SA3 条目**(或临时用 SYN 兜底:side_* 已有 SYN 版,echo/mat 可参考 §9.2 降级条款程序合成)。 + +## 5. check_all_assets 结果(radio/dungeon 已入 GAMES 列表) + +``` +| 游戏 | ogg | m4a | 0字节 | 解码失败 | voice male/female id 一致 | +| radio | 241 | 241 | 0 | 0 | ✅ 各 103(平铺 29 个角色/NPC 句,豁免) | +| dungeon | 273 | 273 | 0 | 0 | ✅ 各 125 | +结论:全部通过(8 游戏全绿,exit=0) +``` + +radio ogg 构成 = 103+103 双性别 + 29 平铺 NPC + 2 sfx + 4 bgm;dungeon = 125+125 + 19 sfx + 4 bgm。 + +## 6. 遗留问题 / 待办 + +1. **SA3 token**:环境未配置(env.d/sfx.sh 无有效 HF token),radio 13 + dungeon 24 件 sfx 待补产(清单见 §4,prompt 均已在设计文档定稿)。 +2. **人工试听两处**:① radio NPC 带通句可读性抽检(不可读回退未滤波原声,staging 重转即可,管线已支持);② 多音字风险句(15-radio-voice-ids.md 文末注记:h1_dango「嘶」、k1_lullo、d9_face 等;16b:btl_bat「蝠」、unlock_potion「行囊」)。 +3. **文档笔误待策划改**:15-radio-voice-ids.md 头注「HOST 21 + PROD 82」应为「HOST 29 + PROD 74」(总数 103 不变,不影响任何资产)。 +4. radio 无「大姐/姑娘」自称合规、gd_* 两级不同句等台词侧设计约束均已在表内落实,开发接线时按 §7.2 看门狗表取用即可。 +5. 红线遵守:GPU 全程 flock 串行(radio → dungeon 两轮,无并行);Mureka $0.24 ≤ $0.30 且每笔记账;未改 web/engine/、未写任何 game.js/index.html/test;key 未入任何入库文件(mureka.sh 本就被 gitignore)。 + +## 7. 产出物索引 + +| 类型 | 路径 | +|---|---| +| 台词表(入库) | `tools/gen_voice_dual.py`(radio/dungeon 表 + SPEAKERS + VOICE_SPK + NPC_BANDPASS) | +| voice 成品 | `web/{radio,dungeon}/audio/voice/{male,female}/`(+ radio 平铺 `voice/.*`) | +| bgm 成品 + 脚本 | `web/{radio,dungeon}/audio/bgm/`;`web/{radio,dungeon}/gen/gen_bgm.sh` | +| sfx 成品 + 脚本 | `web/{radio,dungeon}/audio/sfx/`;`web/{radio,dungeon}/gen/gen_sfx.py` | +| 期望文本映射 | `web/harness/voice_text.json`(games=8) | +| 台账/总账 | `tools/mureka_log.md`(2026-09-01 节);`docs/audio/asset-manifest.md`(radio/dungeon 章节) | +| staging(不入库) | `tools/gen_voice_dual_wav/{radio,dungeon}/`、`web//gen/staging/`、`tools/bgm_post_wav/`、`tools/mureka_out/` | diff --git a/research/new8/dungeon/round1/live-player.md b/research/new8/dungeon/round1/live-player.md new file mode 100644 index 00000000..0f985d16 --- /dev/null +++ b/research/new8/dungeon/round1/live-player.md @@ -0,0 +1,125 @@ +# 《回声地窟》全拟真试玩报告 — 老陈(细心的盲盒 Roguelike 老手)+ 急躁压力局 + +## 基本信息 + +- 身份:老陈,34 岁,Roguelike 玩家,认真跟教学、脉冲探路、爱听完整句旁白;另一局换急性子表弟小飞(1.7x 语速、乱按狂点、菜单开了就乱按)代打压力局。 +- 设置: + - 局 A(新手主线):`voice=male, speed=normal, bgm=normal, guide=full`,两个会话(主会话 + 死亡结算续会话),共约 84 分钟真实时间。 + - 局 B(急躁压力):`voice=female, speed=1.7, bgm=normal, guide=standard`,三段会话共约 7 分钟乱按。 +- 手势统计: + - 局 A 共 424 次(tap 89 / longpress 261 / 双向 swipes 59 / doubletap 9 / swipeup 9 / swipedown 3,另 1 次 longpressend 被控制台拒收);局 B 共 273 次随机乱按。 +- 最深层:**第 3 层**(局 A,死于 L3 蘑菇怪之手;L1 教学层 + L2 完整走完)。 +- 结局:局 A 走完 教学→探索→战斗→下潜→**死亡归因三问→元进度入账→SETTLE→TITLE** 全闭环;局 B 未死亡(HP 1 满血乱按),两次误触「保存并回大厅」退到大厅、一次误启动了别的游戏。 +- 报错:全程无 fetch 失败、无资产 404、无页面崩溃;但局 A 主会话的语音引擎被一个抢占死循环污染到**全程 35745 次语音播放里 35611 次被掐死(99.7%)**(详见 P0)。 + +## 局 A 体验叙述(第一人称,新手主线) + +**0:00–1:20 教学开场,一切正常。** 解锁单击后 TITLE 播 `sys_new_run`「新的探窟,开始了。」、`intro_1`「你在黑暗中醒来。这里是回声地窟。」、`intro_2`「伸手不见五指,声音就是你的眼睛。」、`tut_d0`「先学会用耳朵看。单击,发出声波脉冲。」——四句全部完整、清晰。我单击作答 D0,confirm 震动干净利落。这是我全场唯一听到完整句子的 30 秒。 + +**1:20 D1 脉冲演示 = 灾难开始。** t=130.0s 我按下教学要求的单击:脉冲 `pulse` 正常、回声组正常(echo_2 近墙 0.5s 后到、mat_stone 材质尾音、side_wall 左右预 pan 都在),但紧接着 `tut_echo_come`(「听,回声回来了。」)和下一步旁白 `tut_front_rule`(「回声回来越快,墙就越近。」)开始**互相掐架**——事件流里 `vend reason=preempt` 连环爆发,最密时 0.09 秒一轮,ASR 听到的是两句话交错的碎渣:「越生回来越快」「大了就」。从这一秒起,直到 71 分钟后我关掉页面,**这个循环再也没有真正停止过**。 + +**1:20–12:00 教学步进全靠猜。** D2 到 D9 每一步的旁白(tut_side_rule / tut_walk / tut_turn / tut_replay / tut_status / tut_menu / tut_mob / tut_parry / tut_attack / tut_stairs)全部在开播 0~0.3 秒内被 tut_echo_come 掐死,我只能靠设计文档和步骤反馈(震动音色、回声、脚步)猜该做什么。D4 长按走 2 格+撞墙、D5 左右滑转身、D6 双击免费重发、D7 上滑状态链——这些**动作反馈本身都正常**,但状态链五段播报(st_floor_1→st_hp_1→st_gold_1→st_face_e→威胁)每段只剩 0.2 秒渣音。D7 我按教学下滑开菜单,结果单击把菜单项切到了「保存并回大厅」,吓得我以为要退局——好在教学步骤完成时菜单被自动收掉了。 + +**12:00 教学战能打,但听不见教什么。** 教学蝠撞进我格子触发战斗(btl_bat + battle BGM + 心跳震动预警都到位),我挥击 2 刀杀死它(atk_swing/atk_hit/enemy_die/coin 清脆悦耳)。但 tut_parry「前摇声响时双击」的教学句我一句都没听成——第一次格挡练习直接失败(双击到晚了,好在 ATK=0 无伤)。 + +**12:00–25:00 D9 找楼梯:教学唯一有答案的一步,没有任何兜底。** 脉冲听到铁器回声(echo_3+mat_stair 的金属音色真的很好认)之前,我在 7×7 的 L1 里蛇形摸了半天。设计里 DRILL 每步 8 秒该有 nudge 震动——我静置 72 秒,**事件流一条都没有**。找到楼梯、单击下潜(stairs_down 轰隆+evt_descend+st_floor_2),全程语音照旧被碾碎,但我确认自己下到 L2 了。 + +**25:00–45:00 L2:真正的战斗体验(如果能听见的话)。** 三只窟鼠,全部主动撞进我格子(明雷脚步 mob_step_small + 距离音量 + ≤2 格心跳震动的威胁预警做得好)。我打死了三只(每只 3 刀),学会了:挥击命中 atk_hit 手感扎实;前摇 tel_small 1.2 秒窗口内双击=格挡——我的反应全靠手动计时,第一次成功卡进窗口是在 L3 才做到的。中途两次长按误触**逃跑**(设计:逃跑必成但挨一下),hurt 音+掉血真实入账。上滑查状态链确认面朝、金币档跳档(st_gold_1→st_gold_2),结构完全正确。 + +**45:00–58:00 L2 找楼梯:卡关救援系统在正常走表,但说的每句话都被杀。** 我故意静置测试:64 秒后 `stair_hint_a` 准时触发(带 [30,50,30,50,30] 警告震),再 60 秒 `stair_dir_e`(楼梯在东)准时升级——**计时和分级完全按设计工作**,但提示语音 14 次尝试全部 0~0.3 秒被掐死,作为盲人玩家我一个字都没收到。最后我靠读事件流里的 `stair_dir_e` 才知道方向——真人玩家在这里只能靠瞎撞。带路模式(180 秒)也触发了:nav_e→nav_n 每步报方向,同样全部碎成渣。 + +**58:00–75:00 L3:地图还在,我的耐心没了。** 下潜到 L3(evt_descend+st_floor_3)。三只蘑菇怪在左右踱步,我脉冲探路时发现三件事对不上:侧向回声永远说两侧是「墙」(side_open 整场一次没出现过,哪怕我明明能往"墙"的方向走成功);带路导航咬定北边(nav_n)而北边实测撞墙;出生点脉冲说前方墙在 5 格开外(echo_4)实际 1-2 步就撞。我在这张图上绕了半个多小时,后来复盘事件流才发现真相:**那 13 场蘑菇战全都发生了**(btl_mush ×13),但我每次长按走路都直接变成了「逃跑」——btl_flee ×9,其中一次挨了真实的 2 点伤害。我在不知情的情况下被放血到重伤档(st_hp_3),然后被蘑菇群「贴身绕圈、堵住格子、就是不进格开战」的走位软封锁在原地——我朝它走只 bump,它绕着我转永不接战。 + +**75:00–84:00 死亡与元进度:全场最完整的一段体验。** 我写了脚本接管操作,终于和蘑菇硬碰硬打了一场:挥击命中、它的前摇、我格挡失败挨刀(hurt+[80] 震),再两刀把它打死(enemy_die+coin+btl_kill+btl_loot_2)。第二只蘑菇接战,我血量只剩个位数,两回合后倒地——**死亡归因链一字不差地播完**(这一段因为页面重启过、抢占循环不在,所以全句清晰): + +- `die_intro`(你倒下了……) +- `die_cause_1`「你被小怪拖垮了。」——**真话**,确实是蘑菇怪连续失血致死 +- `die_adv_1`「下次,前摇声响时就双击。」——**真话**,我全程格挡成功数为 0 +- `st_floor_3`(第 3 层)→ `meta_gold_3`「这趟满载而归。」→ `settle_stronger`「下个周目,你会更强。」→ `settle_again`「单击,再探一次。」 + +单击重开入口确实 ≤2 秒。回到 TITLE 上滑元进度播报:`meta_gold_2`(带回一些金币)→ `st_best_2`「你到过中层」(档2=3-5 层,我到过第 3 层,分档正确)→ `unlock_none`。金币入账、最深层数更新、遗产未达解锁线,全部如实播报。这一段的设计完成度是全场最高的。 + +## 局 B 体验叙述(急躁压力:female / 1.7x / standard) + +小飞拿到残局(L3 重伤档存档)直接单击**开了新周目**(教学跳过标志生效,直接 EXPLORE L1,这点很顺)。然后开始乱按轰炸,三段会话共 273 次随机手势: + +- **乱按打得死怪**:L1 教学蝠在纯粹乱点中被拍死(btl_bat→enemy_die→btl_kill→btl_loot_1),乱滑转身被 `btl_no_turn` 正确拒绝(+纠错震动)。 +- **两次误退局**:菜单是下滑呼出的,呼出之后下滑**关不掉**(详见 P1-1),乱点循环菜单项时双击很容易砸在「保存并回大厅」上——两段会话各误退局一次(sys_saved→回大厅),其中一次退到大厅后随机点击**把别的游戏点开了**(事件流里出现 b_rain / z1_ring_in / n00_intro 等他家台词)。 +- **1.7x 语速的转写惨案**(仪器侧记,不算游戏 bug):「你面朝南」→「一面长」sim 0.29、「保存并回大厅」→「保则命为大师」sim 0.33、「检测到探窟记录」→「检测的探污记录,救花出新看」0.55。 +- **没有死锁、没有叠音**:乱按全程语音保持严格串行(二周目不进教学,抢占循环不在),BGM 切换、回合流转、菜单循环全部没卡死。局 B 最大的问题是那个菜单(P1-1),不是稳定性。 + +## 问题清单 + +### P0(破坏核心体验的系统性缺陷) + +1. **教学 D1 通过后,`tut_echo_come` 与一切语音进入无限抢占循环,污染整个页面生命周期** + - 证据:局 A 主会话 events.jsonl:`vend reason=preempt` 共 **35,611 次**,占全部语音播放(35,745 次)的 **99.7%**;71 分钟里只有开场 4 句(sys_new_run / intro_1 / intro_2 / tut_d0)完整播完。 + - 起点:t=130.2s(D1 首次脉冲通过、tut_echo_come 首播被抢那一刻)。此后教学旁白、上滑状态链五段、菜单项名、战斗报名(btl_bat/btl_mush/btl_rat)、卡关提示三级、带路导航、下潜旁白、死亡链——**每一条都被掐成 0~0.3 秒碎渣**,ASR 听到的是两句话的交错残片(「越生回来越快」「大了就」)。 + - 主凶配对:nav_n ↔ tut_echo_come 互掐 **33,297 次**——带路模式的重复播报计时器与教学残留反馈互相触发,越滚越凶。 + - 反证:同设置的二周目会话(教学跳过)**0 次抢占**,所有语音严格串行——污染源锁定在 DRILL。 + - 玩家视角:这是一款纯音频游戏,而我在 71 分钟里实际上处于**失聪状态**,全靠震动、音效音色和文档预读通关。 + +### P1(功能失效/设计违约) + +1. **探索态菜单下滑关不掉(违反设计 §2.4「下滑=关闭」)** + - 确定性复测:局 B 第三会话,swipedown 打开菜单(menu earcon)后,再发 3 次 swipedown 全部零响应(无语音、无音效、无关闭);期间左右滑还在轮流切菜单项(这部分符合设计)。 + - 而教学 DRILL 态打开的菜单下滑能正常关(局 A D7 验证:bgm 切回 explore+返回音)——同一手势两种状态行为不一致。 + - 后果:菜单唯一可靠出口是双击确认「返回探索」,但菜单项循环**没有任何位置播报**,盲手玩家不知道选择器在哪;局 B 两分钟内两次双击误落在「保存并回大厅」直接退局,退到大厅后乱点还会误开别的游戏。 +2. **侧向回声恒报「墙」,side_open 全场从未出现** + - 证据:两局合计 40+ 次脉冲,`side_open_l/r` 出现次数为 **0**,`side_wall_l/r` 每次成对出现;两处行为级反证:L2 面朝北时左侧(西)报「墙」、向西行走成功;带路指示的北向开口沿途所有脉冲仍报「墙」。 + - 设计承诺「回声编码===grid 真值」在侧向维度不成立——「伸手不见五指,声音就是眼睛」的眼睛撒谎了。 +3. **教学 D9(找楼梯)无任何兜底,DRILL watchdog 缺失** + - 证据:D9 状态下静置 72 秒,事件流零输出(设计 §10.2 教学每步 [8s] 应有纯 nudge 震动,一个 vibrate 事件都没有)。D9 又恰好是唯一要求玩家自行寻路的步骤——找不到铁器回声的新手没有任何救援。 +4. **L3 导航三重奏集体失真 + 蘑菇「堵格不开战」软封锁** + - nav_n 长时间指向实测撞墙的北向(带路模式可信度崩塌);L3 出生点脉冲 echo_4(5+ 格)实际 1-2 步撞墙;同一方向 1 分钟内先「通 2 格」后「即撞」。 + - 蘑菇群贴身绕圈:我走过去只 bump(不触发战斗),它永不走进我格;13 场战斗全部由「我长按走路撞进它」无声开启,而我的下一步长按又立刻变成逃跑(btl_flee ×9)——在不知情中被「撤退挨一下」放血。L3 我耗了 35 分钟没找到 Boss,最终是靠脚本化战斗补完的死亡。 + +### P2(不适/可疑) + +1. 卡关三级提示的**计时与分级完全正确**(64s→stair_hint_a、120s→stair_dir_e、180s→nav 带路,警告震 [30,50,30,50,30] 每级都到),但内容 100% 被 P0 掐死——救援体系形同虚设(P0 修复后此条大概率自动痊愈)。 +2. 元进度播报语义疑点:SETTLE 链说 `meta_gold_3`「这趟满载而归」,随后 TITLE 上滑同一元进度段说 `meta_gold_2`「这趟带回一些金币」——两处「这趟」的档位不一致(st_best 分档经台词表核对无 bug)。 +3. 每次长按都伴随 boost on→150ms→off(E3 豁免应在对局期禁用快进,实现变成每次长按闪一下,无害但可疑)。 +4. 教学步进完成时 D7 的菜单被系统自动收掉(sys_menu_resume 未经我操作)——与「下滑手动关闭」的教学目标矛盾(玩家可能根本没学会关菜单,局 B 的乱象与此相关)。 +5. 我方回合 15 秒无操作只震不说(standard 档 gd_turn 缺席是设计使然),配合 P0 留下的悬挂语音死寂,体感像死机。 + +### P3(建议) + +1. 抢占战争平息后会留下悬挂语音(vstart 无 vend)+ 数分钟完全死寂——哪怕 P0 修了,也建议给引擎加"语音空转超时自愈"。 +2. 长按在手势宪法里同时是「前进」「逃跑」「教学作答」三种语义,战斗边界模糊时(L3 静默接战)极易误逃;建议逃跑改为长按+明确音效前摇。 +3. 局 B 大厅乱点会无确认启动其他游戏——虽是大厅层问题,dungeon 的「保存并回大厅」把玩家送进这个环境,建议回大厅后给一句「你在大厅」的定位。 +4. harness 侧记:longpress 为固定 600ms(约 1 格/次)、无独立 longpressend 手势类型,长按行走在 live 测试里非常笨重;1.7x+短句是 ASR 盲区(「你面朝南」→「一面长」sim 0.29),自动化验收需人工复核。 + +## 做得好的地方(实话实说) + +- **死亡归因真话律完美**:死因句(小怪拖垮)、建议句(0 格挡)与实际行为逐字对应——这是全游戏最严苛的承诺,兑现了。 +- **回声定位本体(前向维度)是真的能玩**:echo_1..4 延迟/音量分档+材质尾音(mat_stair 金属回声一眼可辨)引导我在 L1/L2 正确找到楼梯与金币,撞墙 bump 与回声距离一致(L3 的漂移除外)。 +- **战斗音效链全部清晰**:atk_swing/atk_hit/enemy_die/coin/tel_small/hurt/parry 各司其职;回合制状态机经历 100+ 回合、13+ 场战斗零死锁。 +- **中断复述链与状态链结构满分**:继续存档后 st_floor→st_hp→st_face→st_threat 顺序与设计逐字吻合;上滑五段链(层/血/金/面朝/威胁)结构完整。 +- **元进度闭环成立**:死亡→金币入账→最深层数更新→遗产解锁判定(unlock_none)→「下个周目你会更强」→单击 2 秒重开。 +- **学习期零惩罚兑现**:L1 蝠 ATK=0(我 80+ 次乱按无伤)、L2 鼠 ATK=1,撞墙无伤,逃跑必成。 +- **教学跳过标志生效**:二周目直接 EXPLORE 不再进 DRILL,且顺带避开了 P0(讽刺的是不教学反而听得见)。 +- 双击免费重发脉冲、脉冲 1.2s 冷却防糊、怪物明雷脚步三档音量+心跳预警,全部按设计工作。 + +## 玩家情绪时间线 + +- 0:00 进场,开场四句完整清晰:「这游戏声音真舒服」 +- 1:20 第一次脉冲,两句教学开始互相掐:「……它刚才说什么?」 +- 2:00–12:00 靠震动猜教学:「我这简直是在玩盲文版」 +- 12:00 杀死教学蝠,音效爽:「打是打得挺爽,就是没人告诉我怎么格挡」 +- 15:00–25:00 L1 找楼梯,静置 72 秒毫无声息:「喂,说句话啊」 +- 25:00–45:00 L2 三杀,战斗手感上头:「鼠鼠我呀今天全灭」 +- 45:00 卡关提示触发但半句听不见:「求你了把话说完」 +- 58:00–75:00 L3 导航指墙+侧向回声撒谎+蘑菇堵门:「这图是不是在跟我开玩笑」 +- 75:00 硬碰硬杀掉第一只蘑菇:「终于像场正经战斗了!」 +- 79:00 死亡归因三问一字不落:「……这是我今天听到的第一句完整的人话」 +- 84:00 元进度入账,「下个周目你会更强」:「行,冲着这句我愿意再来」 + +## 如果只能改 3 件事 + +1. **杀掉 tut_echo_come 的无限重发(P0)**:D1 通过反馈播一次就该销毁;顺带给语音引擎加"同一 id 连续 N 次 preempt 强制静默"的自愈保险丝。这一个修复能让教学、状态链、卡关提示、带路、菜单播报全部复活。 +2. **修探索态菜单的下滑关闭 + 给菜单项加位置播报**:现在的菜单对盲手玩家是个只能进不能出的口袋,出口全靠双击赌运气,赌输就退局。 +3. **让侧向回声和带路导航说真话**:side_open 按设计触发、nav 方向与碰撞一致、L3 出生点前向距离校准——方位信息是这游戏的生命,三个子系统一起失信比任何单点 bug 都致命。 + +## 一句话裁决 + +**修好那个让游戏失聪 71 分钟的语音抢占死循环之后,我愿意立刻再来一局——地窟的骨头(战斗、回声、元进度、归因真话)是真的好,但现在它一边给我引路一边捂着我的耳朵。** diff --git a/research/new8/dungeon/round1/p1-blind.md b/research/new8/dungeon/round1/p1-blind.md new file mode 100644 index 00000000..8fe9404a --- /dev/null +++ b/research/new8/dungeon/round1/p1-blind.md @@ -0,0 +1,23 @@ +# P1 视障资深玩家评审:《回声地窟》(R1) + +> 评审依据:research/new8/dungeon/round1/transcript-main.md(新手完整弧线,3630 行全读)+ transcript-stress.md(乱按压力局)。 +> 说明:转写里的【状态】行我按「转写器注记」处理,不算我耳朵听到的;我听到的只有 ▶ 语音、♪ 音效/BGM、〰 震动。 + +### 回声地窟 +- 状态感知:2/5 —— 教学期和战斗期我清楚得像装了内耳坐标:教学一句一做(t+6.4「单击,发出声波脉冲」→t+8.5 脉冲+回声组;t+18.6 按住走、t+19.1 转身、t+22.1 双击免费重发),战斗里每回合有 turn 签名+确认震(main t+45.5/t+51.0),遇敌必报名(t+35.8「盲眼蝠扑了过来!」+battle 签名+战鼓),上滑五段链齐活:层/血/金/面朝/威胁(main t+405.8-414.3、stress t+27.2-33.5)。但主线局——新手真正要走的弧线——t+55.2 之后到 t+405.8 之间没有一句语音,整整 350 秒;期间两次下楼零演出(t+142.2、t+273.8:无下楼声、无「往下潜」、无「这是第X层」,对照压力局同一动作全套都有:t+72.8-77.0、t+105.8-107.9);BGM 自 t+35.8 进战斗曲后 370 秒无一次切换记录,我在空走廊里被战鼓泡了六分钟(压力局同流程 BGM 切了 8 次);t+405 的「新周目」没有标题、没有过场、没有「新的探窟」宣告——上一秒我还在第三层走廊走路,下一秒就是「这是第一层」。我不知道我在哪,一半时间也不知道现在是什么局。 +- 下一步明确性:2/5 —— 教学期与战斗期是教科书级:「现在单击,挥拳打它」(t+43.4)、「楼梯就在脚下。单击,下潜。」(stress t+72.3/t+104.4)、战斗发呆 15 秒就有看门狗「轮到你了。单击挥击。」(stress t+166.5/t+231.0)。可主线局 L2/L3:t+143-273 与 t+274-405 各约 130 秒无拾取、无战斗、无楼梯,设计承诺的 60/120/180 秒三级卡关提示两局一次都没响(stair_hint/stair_dir/nav 全为 0);我唯一的导航依据是教学句「铁器的回声,就是下潜的楼梯」(t+55.2),而主线局 165 颗脉冲里铁器尾音出现 0 次——我按规矩听铁器,铁器从头到尾没出过声,只能瞎走。 +- 愿意继续玩:否 —— 就 R1 而言,「听声辨位」在主线局里退化成一台双态节拍机:350 秒零事件零语音、下楼无声、战斗曲泡全程,我找不到继续按下去的理由;但压力局证明骨架能跑通(战斗、pan、心跳、归因、元进度都在),一致性修好我愿意复审。 +- P0 问题: + 1. 不知道自己在哪:主线局两次下楼零反馈(t+142.2、t+273.8),层数变化全靠猜;战斗曲 370 秒不退(t+35.8 后再无任何 BGM 切换记录),「听见 BGM 就知道我在什么状态」这条状态签名彻底失效。 + 2. 不知道该做什么:主线局两段 130 秒无进展期间,60/120/180 秒三级提示全程哑火;「铁器=楼梯」的教学规矩在 165 颗脉冲里 0 次兑现,教学一结束我的导航线索就归零。 + 3. 账目对不上:压力局本局四场胜仗、四回战利入袋(t+55.3、t+131.6、t+168.6、t+233.1 的 coin 音+「战利不多/小有收获」),死亡结算却报「这趟没带回什么。」(t+272.1)——「死亡金币入元进度」的账我核对不出来;主线局 t+405.8 新周目宣称遗产生效(仅存于注记),我耳朵里没有任何遗产播报,上滑还报「口袋空空」(t+410.0)。另外教学承诺的撞墙归因句(「那边是墙」)两局 8 次 bump 全是纯音效,一次没播过(main t+19.6;stress t+13.7、t+69.6 等)。 +- 建议: + 1. 先修「同一动作两种反馈」:下楼演出(下楼声+「往下潜」+「这是第X层」+探索曲回切)在主线局被整个吞了两次,战斗 BGM 六分钟不退——让主线局和压力局听起来像同一个游戏,这是我最基本的续命条件。 + 2. 把回声谱系喂饱:主线局 165 颗脉冲里铁器尾音 0 次、空旷侧回声 0 次、教学期后正面回声只剩「贴身 1 格墙」和「3-4 格墙」两档横跳;把楼梯/房间/开口的声学样本放回常规探索路径,并让三级卡关提示真正开火——现在这套信息量撑不起「辨位」两个字。 + 3. 把元进度做成听得见的账:死亡结算报清「这趟带回什么档」,新周目开场播遗产生效——roguelike 让我死得值的那口气,全押在这笔账上;顺带把撞墙归因句接上,别让教学句开空头支票。 + +#### 回声/脚步/播报三套信息一致性(专项,Shades of Doom 老兵的最狠检查) +- 回声 vs 教学语音:矛盾(主线局)。t+55.2 刚教完「铁器的回声,就是下潜的楼梯」,此后 165 颗脉冲零次铁器尾音——包括我站在楼梯上(t+142.2 的回声组是 echo_1+mat_stone)和下楼那一刻(t+273.8 同样 mat_stone);mat_stair 直到压力局 t+239.9/244.9 才第一次响。资产在、触发看局——这比干脆没有更糟,因为它让教学句变成了谎言。 +- 回声 vs 空间本身:信息量不足以支撑「辨位」。全程 step_turn 483 次 vs step_own 482 次——转身声和脚步声一样多,每格移动伴两声拖步转身音(t+300.5 等全程如此,我听不出这是走路还是跳舞);两侧行向回声 330 次全部是「墙」,教学第二课「空旷是通路」(t+16.5)教的空旷侧回声两局 0 次出现;正面回声教学期之后只在 echo_1/echo_3 两态间抖动。SoD 的墙有材质、远近、回响的连续谱系,这里我听到的不是一片地窟,是一个两态节拍器——我建不成任何空间模型,只能靠「撞了就换边」的穷举。 +- 脚步 vs 播报(威胁层):压力局内自洽且出色——窟鼠脚步从右(t+80.9 small_r)到中(t+83.5 small_c)逼近、≤2 格 heartbeat 跟进(t+82.6)、遭遇「窟鼠窜了出来!」(t+84.7)与脚步收敛严丝合缝;缠斗怪纠错「它贴着你不放,不用转身。」(t+86.8)与行为一致;死亡归因「你没挡住那记重击。」(t+269.9)与我全程不格挡的行为严格对账——这三条我认,是真功夫。但主线局 L2 配 3 只怪、L3 配 3+1 只,260 秒里怪物脚步 0 次(mob_step 仅教学期 3 次)——怪物层在主线局整体缺席,与「全明雷、脚步持续可闻」的规矩直接矛盾。同一个引擎,两局两个世界。 +- 播报内部:上滑五段链模板固定、四段齐整(层/血/金/面朝/威胁,t+405.8-414.3),这句我要表扬;但「口袋空空」与「遗产生效」在同一个新周目里同屏出现(t+410.0 vs 注记),口袋账和遗产账的区别没有任何一句解释——播报层先把自己的账本理清楚。另:菜单两局都只验证过第一项「返回探索。」(main t+32.2、stress t+35.6),循环后三项从未露脸,与 radio 同款的完整性存疑。 diff --git a/research/new8/dungeon/round1/p2-student.md b/research/new8/dungeon/round1/p2-student.md new file mode 100644 index 00000000..dc7faaed --- /dev/null +++ b/research/new8/dungeon/round1/p2-student.md @@ -0,0 +1,20 @@ +# P2 · 时间充裕的大学生 评审——《回声地窟》(R1) + +> 视角:23:30,宿舍躺床,戴耳机,音量正常。基于转写信息流代入;主线转写的动作是剧本注入的,但「我耳朵里听到了什么」就是下面这些。 + +### 回声地窟 +- 状态感知:3/5 —— 教学期满分:每次操作都有即时回执(t=8.5 首次脉冲后侧墙左右+回声+材质尾音一组、t=40.1 前摇里双击秒回「挡住了!」、t=86.8 乱转身有「它贴着你不放,不用转身。」纠错),上滑状态链五段齐全(第二周目 t=405.8-414.3:层/血/金币/面朝/威胁)。但两个硬伤把它拖到 3 分:① t=35.8 战斗 BGM 响起之后,直到 t=416 收工再也没有切回探索曲的记录(t=142.2 进第二层、t=274.2 进第三层、t=405.8 新周目,状态行全标 BGM=bgm/battle)——我在空无一人的走廊里独走了六分多钟,耳机里一直是战斗鼓点,音乐在骗我「在打架」,脚步在说「空无一人」,我对「现在是什么处境」的感知被 BGM 带偏了整整一局;② L2/L3 探索段(t=142.6-273.8、t=274.2-404)除了我自己的脚步和脉冲,没有任何别的声源——没怪脚步、没拾取、没遭遇,这层「有什么」我完全感知不到,因为(听起来)它什么都没有。 +- 下一步明确性:3/5 —— 教学期指哪打哪,每步一句话+一次实操(t=6.4 脉冲、t=18.6 按住走、t=22.1 双击免费重发、t=24.2 上滑状态),D9 教了我「铁器的回声,就是下潜的楼梯」(t=55.2)。出了教学就露馅:目标只教过那一次,之后找楼梯全靠瞎撞——L1 出口我摸了约 85 秒(t=58.7-141.7 连续四十次「按住-松开」),L2 摸了约 130 秒(t=142.6-273.8);脉冲回声只有「近墙/中墙+两侧发闷」,没有「离楼梯越来越近」的梯度,我永远不知道自己是热是冷。更糊涂的是主线两次下楼(t=142.2、t=273.8)都没播「楼梯就在脚下。单击,下潜」(压力场 t=72.3、t=104.4 有这句)——我甚至说不清自己是怎么下的楼。卡关三级提示(60/120/180 秒)在我全程保持移动时一次没触发过,说明它兜不住「在动但全是无效移动」这种真卡关。 +- 愿意继续玩:否(就本轮转写的体验)—— 头 90 秒是惊艳的,之后六分钟空走廊+错乱的战斗鼓点,把「想再探一层」的念头磨没了;单局都没听完,遑论下一局。BGM 修掉、楼层填上内容,我愿意给第二次机会。 +- P0 问题: + 1. 「死了金币进遗产、越死越强」这个核心承诺,在两份转写里没有一次被声音兑现过:压力场一路叮当捡了三次钱(t=131.6、t=168.6、t=233.1 的 coin 音+「小有收获。」),t=267.8 倒下,结算却播「这趟没带回什么。」(t=272.1);主线第二周目上滑还是「口袋空空。」(t=410.0)。钱去哪了?要么死亡没入账、要么播报档在说谎——这是我开下一局的唯一理由,它现在是哑的。 + 2. 主线这条「新手完整弧线」中段是空的:t=51.0 教学战击杀之后到收工,再无一场战斗、一次拾取、一个怪脚步,说好的窟主石甲蟹一次没露面(对照压力场 t=264.5 是有的),L2/L3 各约 130 秒纯脉冲-走路循环。8 分钟的体验里 6 分钟是空层——不管这是剧本绕开了内容还是楼层把内容摆得太偏,对躺床的我都是同一句话:这游戏后半段没东西。 + 3. t=35.8 起战斗 BGM 永不切回探索曲(此后全程零 bgm/explore 切换记录)。BGM 在这游戏里是状态信号,信号错了六分钟就是状态感知事故。 +- 建议: + 1. 让探索层「活」起来,也让脉冲「会指路」:全程 L1-L3 材质尾音只有 mat_stone 一种(t=8.5 到 t=404),侧向永远是「发闷」,门、水、空腔一次没出现过;脉冲除了「近墙/中墙」得能回答「楼梯近了吗」——渐近梯度、或者楼梯铁器声可远闻,都行。这是决定我会不会玩到第三层的头号因素。 + 2. 修 BGM 状态回归,战斗曲只属于「有怪的时候」;顺带把「踏上楼梯格」的播报在主线路径里补稳(对照 t=72.3 有、t=142.2/t=273.8 无)。 + 3. 把元进度做成听得见的承诺:死亡结算播「带回了 X 枚金币,离下一件遗产还差 Y」,解锁时开场播报遗产名(设计里有,转写里没响过)。标题页那句「你还没到过第三层。」(压力场 t=2.1)是全游戏最勾我的钩子——照这个思路把金币线也补上。 + +#### 元进度钩子评估 +- 死后结算想不想立刻开下一局:节奏上,想,非常想——t=267.8「你倒下了。」→ t=269.9/272.1 归因「你没挡住那记重击。」「下次,前摇声响时就双击。」(我全程乱挥不格挡,骂得字字属实)→ t=274.2 已经在开新局。从死到重开约 7 秒,零摩擦、归因真话、不羞辱人,这个死亡-重开手感是全游戏做得最好的部分,比多数商业 roguelike 的结算爽快。内容上,不想——结算只告诉我「没带回什么」「你到过中层」,没有任何「下一局你会更强」的可听证据:没金币入账、没遗产播报、新局上滑还是「口袋空空」(t=410.0)。钩子的形状搭好了,钩尖是钝的。 +- 「脉冲回声」这个核心动词新鲜吗:点子非常新鲜——「单击=喊一声、用回声快慢看距离」是我没玩过的核心动词,t=8.5 第一次听到完整回声组(左墙闷响+右墙闷响+回声+石头尾音)时真的起鸡皮疙瘩,这就是我要的「设计感」。但新鲜感衰减太快:L1-L3 每一次脉冲的读数都坍缩成「前方 1 格或 3-4 格石墙+两侧发闷」(t=142.6-273.8 那 130 秒里就是 echo_1/echo_3 交替+mat_stone 循环),教学里承诺的「空旷是通路」(t=16.5)我全场没等到过一次「空旷」,材质四选一实际只出过一种。同一个动词循环十分钟没有新读数,就从「声呐魔法」退化成「打卡」——动词本身 5 分,这轮的读数丰富度 2 分。 diff --git a/research/new8/dungeon/round1/p3-office.md b/research/new8/dungeon/round1/p3-office.md new file mode 100644 index 00000000..9393c25c --- /dev/null +++ b/research/new8/dungeon/round1/p3-office.md @@ -0,0 +1,22 @@ +# P3 评审:《回声地窟》(dungeon)— 时间紧张的白领 + +> 视角:32 岁,地铁单程 25 分钟,站立单手持机,嘈杂车厢,只玩过消消乐,反应不快。依据 R1 两份转写(新手完整弧线 + 乱按压力局)代入「真机听到这些」。 + +### 回声地窟 +- 状态感知:2/5 —— 教学段很清楚(每步都有人声带着做,t=6.4-55.2s),上滑复述链也好用(新周目 t=405.8-414.3s 五句话报清层/血/钱/朝向/威胁)。但主线局从 t=55.2s 教学战胜利之后,到 t=405.4s,整整 350 秒没有任何一句人声:我在第几层只能靠猜——两次下潜(t=142.6s、t=274.2s)都是一声不吭就换了层,转写里「这是第二层」这类报层句一句没出现(压力局 t=77.0s 反而有);「第三层——窟主的地界」(t=274.2s)我只从状态注释里知道,耳朵里听到的还是同两种回声轮流响。地铁里我早就迷失了。 +- 下一步明确性:2/5 —— 教学教我「铁器的回声,就是下潜的楼梯」(t=55.2s),可我之后走了 158 次脉冲、6 分钟,铁器回声一次都没听到过(全篇 mat_stair 出现 0 次),楼梯全是瞎撞上的(t=142.2s、t=273.8s 单击就下去了,之前毫无预兆)。设计里说好的卡关三级提示(无进展 60/120/180s 给方向)在主线 350 秒无进展期间一次都没来。压力局倒是有「楼梯就在脚下。单击,下潜。」(t=72.3s、t=104.4s)——说明系统存在,但主线局它对我失灵了。我不知道该往哪走,也没人告诉我。 +- 愿意继续玩:否 —— 教学之后 6 分钟只听到「贴身/中距」两种石墙回声轮流响,没怪、没宝、没目标、没提示,我在嘈杂车厢里熬不到第二层就会退出。 +- P0 问题: + 1. 不知道自己在哪+该做什么(双重):主线 t=55.2s→t=405.4s 共 350 秒——0 句人声、0 场战斗、0 次拾取、0 次楼梯提示;教学承诺的目标线索「铁器的回声」全篇 0 次出现,卡关提示 0 次触发。 + 2. 不知道发生了什么:两次下潜(t=142.6s、t=274.2s)无任何语音预告就进了下一层,我以为游戏坏了。 + 3. 核心卖点没兑现:设计说 L2 有 3 只窟鼠、L3 有蘑菇怪和窟主,主线 6 分钟里怪物脚步一声都没有——「听声辨位」在教学战后完全没上场,我不知道这游戏除了听墙还能干嘛。 +- 建议:≤3 条,按对「我愿意玩下去」的影响排序 + 1. 给探索段装上「节拍器」:楼梯加持续可闻的方向性铁器叮当,或把卡关提示阈值从 60/120/180s 压到 30-45s——主线那种 350 秒无反馈的真空,一次都不能有。 + 2. 进层必须报层、上楼梯前必须提示(压力局 t=77.0s「这是第二层」、t=72.3s「楼梯就在脚下」证明句子都有,主线没播就是触发条件有洞),顺带把下潜做成「踏上楼梯格→立刻报」,别让我盲点。 + 3. 每 60-90 秒塞一个正反馈(金币、怪、拾取都行):教学的热度在 t=51s 到顶,之后全凉了;我在地铁上要的是每半分钟有点事发生。 + +#### 认知负荷评估 +- 脉冲-回声-移动-战斗的规则量对我超不超载:探索面 6 个手势(单击脉冲/按住走/左右滑转身/双击重发/上滑状态/下滑菜单)——不超载,单击和按住是我零学习成本的动作;真正的新概念只有一个「回声越快墙越近」,一句就懂(t=8.5s)。战斗面就吃力了:单击挥、双击格挡(还只有敌方前摇 1.2 秒窗口内才算)、按住逃跑、绕位怪还得转身对准——「在特定的 1.2 秒里双击」这种时机概念,对只玩过消消乐的我是全新技能。压力局证明了我这号人实战的真实水平:三次蘑菇怪(t=123.7/140.3/204.8s)我全是硬挥不格挡,靠血厚换赢;石甲蟹一刀就把我送走了(t=267.8s)。规则量本身不超载,超载的是「时机」。 +- 教学一遍能不能懂:能。10 步每步一句旁白+一次实操,10 秒内一个动作;乱按局里我全程乱摸,t=74.9s 也照样从教学里滚出来了,做错还有人哄「没挡住也别怕,它伤不到你」(t=45.6s),不骂人。格挡这个概念教学教了(t=38.0s),但我这种人是「听懂了、实战用不上」——建议实战头两场小怪也放宽格挡窗口,或前摇音更吵一点逼我注意。 +- 死一次损失大不大:很小,我输得起。压力局 t=267.8s 倒下 → 归因两句话(「你没挡住那记重击」「下次,前摇声响时就双击」t=269.9-272.1s)→ 元进度入账(「这趟没带回什么」「你到过中层」)→ t=274.2s 已经在新周目里了,前后约 7 秒,金币不清。死亡不羞辱人、重开零成本,这套我给满分。 +- 附带一条通勤实感:按住=前进意味着拇指要持续压着屏幕——我单手扶着扶手站在晃动的车厢里,一个刹车我就松手了,起步还得重新按;比起点按,长按在地铁里更容易被打断。不算 P0,但确实影响我「顺手玩两把」。 diff --git a/research/new8/dungeon/round1/p4-worker.md b/research/new8/dungeon/round1/p4-worker.md new file mode 100644 index 00000000..dee6b84d --- /dev/null +++ b/research/new8/dungeon/round1/p4-worker.md @@ -0,0 +1,22 @@ +# P4(疲惫的工人)R1 评审 —— 《回声地窟》 + +> 45 岁,工地收工,宿舍 21:00,外放小音量,游戏经验少。 +> 依据:research/new8/dungeon/round1/transcript-main.md(新手完整弧线)+ transcript-stress.md(乱按压力局)。全部按「我真机听到这些」代入。 + +### 回声地窟 +- 状态感知:3/5 —— 教学那几分钟啥都清楚:上滑报层报血报口袋(t=24.2-28.5「这是第一层。你毫发无损。口袋空空。」),压力局下楼也有人报「这是第二层」(t=77.0)。出了教学就迷糊:主线里我按了下潜,却没听见报层,只听见自己的回声(t=142.2-142.6,对比压力局 t=77.0 有人报),底下是第几层全靠猜;我脸朝哪、血剩多少,全得自己记得上滑问。黑地里「咚咚」的心跳震(压力局 t=39.0)和打仗时的警报(t=208.1)没人告诉我是啥意思。最后我都「命悬一线」了(压力局 t=264.5),自己压根不知道。 +- 下一步明确性:3/5 —— 教学一步一句指哪打哪(t=6.4「单击,发出声波脉冲」、t=18.6「按住屏幕不放,你就往前走」),打仗也有人喊「轮到你了。单击挥击」(压力局 t=166.5)。可找楼梯这件事从头到尾没人帮我:主线 t=59-141 打完教学怪后,我在第一层瞎撞了八十多秒、几十回「脉冲—转身—挪一格」,它一声不吭;后来才咂摸出来,指路提示是看我「手停没停」才给的——我手一直动,它就一直当我不卡(t=142.6-272、t=275-403 两整层也一样)。 +- 愿意继续玩:否 —— 教学那几分钟又暖又安全,可出了教学就是一个人在黑地里一格一格数脚步,好几分钟没人搭理我,末了还稀里糊涂撞上大个子三秒就躺了(压力局 t=264.5-267.8)。下了工再熬这个,顶不住。 +- P0 问题: + - 死得莫名其妙:压力局 t=264.5 走着走着「窟主石甲蟹,就在你面前」,它没说这位多厉害、我该打该跑,t=267.8 我就「你倒下了」。躺下之前我一直当它是个普通怪——三秒钟,连怕都没来得及怕。 + - 不知道该做什么、还要走多久:主线 t=142.6-272(一层)加 t=275-403(又一层),七八分钟全是同一个「脉冲—回声—挪一格」,中途没有金币、没有怪、没有一句话,我不知道楼梯在哪、这层还有多远。手不停,指路提示就永远不来。 +- 建议: + 1. 我一路走也得有人指路:别光看手停不停(主线 t=59-141、t=142-272 两段长走就是证据),走了几十步没上楼梯,就隔一会儿提一句楼梯大概朝哪。这一条最要命——我两眼一抹黑地走,比干活还累。 + 2. 打不过的先打招呼:窟主这类大头目,撞见时除了报名(压力局 t=264.5),添一句「这位不好惹,打不过趁早跑」;心跳震和危险警报(t=208.1)也该在教学里讲一遍是啥意思,别让我听着警报干发慌。 + 3. 挪路别那么干,账也要对上:两三分钟没拾没仗就给点动静,金币声、一句旁白都行——主线第二层我走了一整层颗粒无收;还有压力局我明明打了三场「小有收获」(t=233.1),死了却说「这趟没带回什么」(t=272.1),这账对不上,我犯嘀咕。 + +#### 容错核算 +- 教学跟得上吗:跟得上,是我玩过最贴心的开头。一句短语配一个动作(t=6.4 单击、t=18.6 按住走、t=19.1 左右滑转身、t=22.1 双击「不要钱」),t=8.5 我人生第一声脉冲就发出去了;做错了它换新句子重教,不发火(压力局 t=45.6「没挡住也别怕,它伤不到你。」);教学怪真咬不动我,压力局里我乱按一气,血一滴没掉。就是有几样没教:心跳震、警报声、打仗时咋知道自己剩多少血。 +- 死一次多惨、会不会骂我:不骂,还支招。压力局 t=269.9「你没挡住那记重击」、t=272.1「下次,前摇声响时就双击」,末了还给句「你到过中层」(t=274.2),单击一下立马重开(t=276.3 就回到第一层)。惨度不高,就是死得太突然(t=264.5 撞见、t=267.8 躺下),突然到我都不好怪自己。 +- 黑暗里乱走有没有惩罚过重:没有惩罚。撞墙就是一声闷响(主线 t=19.6、压力局 t=67.3),不掉血不骂人;跑路必成,代价明码标价——挨一下(压力局 t=88.9「你甩掉了它,被抓了一下。」)。乱走真正亏的不是罚,是白费腿:走半天啥也没有,也没个消息。 +- 整体玩下来累不累:累。头一分钟教学舒坦,之后主线 t=142 起到 t=403,四分多钟全是同一种回声翻来覆去听,分不清这格跟那格有啥两样;而且打完教学怪以后,那打仗的鼓点一直没停过(主线 t=142.6 起状态里 BGM 一直是 bgm/battle,对比压力局 t=91.1 打完就切回探索曲),鼓点绷着、事又没有,人更乏。俺工地上一天人声嘈杂,睡前想听个动静,可不想听四分钟一样的「咚……咚」。 diff --git a/research/new8/dungeon/round1/synthesis.md b/research/new8/dungeon/round1/synthesis.md new file mode 100644 index 00000000..ce349632 --- /dev/null +++ b/research/new8/dungeon/round1/synthesis.md @@ -0,0 +1,43 @@ +# R1 Synthesis —《回声地窟》(dungeon) + +> 产品裁定 | 证据源:4 人设评审(全拒,状态感知 2-3/5)+ 真机试玩报告(live-player.md,会话 dungeon-20260902*×6)+ 转写(transcript-main/stress.md)+ 产品只读根因排查 + +## 1. R1 裁决 + +| 评审方 | 状态感知 | 下一步明确 | 愿意继续玩 | +|---|---|---|---| +| P1 视障评委 | 2/5 | 2/5 | **否** | +| P2 大学生 | 3/5 | 3/5 | **否**(头 90 秒惊艳) | +| P3 白领 | 2/5 | 2/5 | **否** | +| P4 工人 | 3/5 | 3/5 | **否** | +| 真机玩家 | — | — | **有条件是**(修掉失聪循环「立刻再来一局」) | + +**结论:0/4 拒绝,dungeon R1 不达标,但被真机玩家确认「战斗、回声、归因真话是真的好」——问题集中在探索期体验与一个灾难级语音循环,属于可修复的结构性问题,不是玩法核心不成立。** R2 = 结构修复轮(本轮不许加新内容)。 + +## 2. R2 修复清单(裁定必修) + +### P0 级 +- **D0-1(live P0)语音抢占死循环**:教学 D1 后 `tut_echo_come` 与导航提示无限互抢(nav_n↔tut_echo_come 33,297 次;主会话 35,745 次播放 99.7% 被掐死,71 分钟只听全 4 句;跳教学的二周目 0 抢占 → 污染源锁定 DRILL 泄漏)。 + 修:① DRILL 完成必须注销全部教学再调度器;② 引擎侧风暴保险丝:同 id 短窗内重复调度超阈值即熔断并 console.error(guide/nav 通用,改 web/engine/ 需全量回归)——产品裁定:**允许改引擎**,这是全线保险。 +- **D0-2(P1 专节+live P1②)回声信息真实性**:`mat_stair` 只在预载清单、零调用点 →「铁器=楼梯」教学承诺 165 次脉冲 0 兑现;侧向 `side_open` 全程 0 次(侧向恒报墙,两处行为级反证)。 + 修:回声调度与 grid 单源重接(楼梯=铁器回声、侧向开口播 side_open),加全网格回声一致性断言。 +- **D0-3(P3/P4 转写 t≈142-405)探索期内容真空**:教学后 350s 零人声零战斗零拾取、两次下潜零演出、卡关提示未触发。 + 修(结构,不加新资产):① 生成器保证主路径 N 步内必有内容遭遇(密度下限);② 探索「呼吸层」:静默超 20s 播环境短句/远机音(现有 evt/amb id 内选用,不够则程序合成 amb);③ 下潜加演出(stairs_down + 一句楼层播报已有资产,接线);④ 卡关计时器复核(为何 350s 未触发——疑似被 D0-1 风暴饿死,修 D0-1 后复测再定)。 + +### P1 级(本轮必修) +- **D1-1(live)**:探索态菜单下滑关不掉(DRILL 态却能关;违反设计 §2.4)。 +- **D1-2(live)**:教学 D9 无看门狗无兜底(静置 72s 零事件)。 +- **D1-3(live)**:L3 导航指墙 + 前向回声距离漂移 + 蘑菇「堵格不开战」软封锁(35 分钟未达 Boss)。 +- **D1-4(P2 转写+产品根因)**:战斗结束不切回 `bgm/explore`(restore 只在菜单关闭路径)。 +- **D1-5(live P2)**:SETTLE 与 TITLE 金币档播报不一致(3 vs 2)。 +- **D1-6(live P2)**:长按 boost 闪跳;D7 菜单被系统自动收掉与教学目标矛盾;回合 15s 静默像死机(加回合音确认)。 + +### 伪影澄清(不改游戏,记档) +- 「元进度不兑现」(P2 转写 t=272/410):**live 实测元进度入账与 TITLE 播报全部正确**(meta_gold_3/st_best_2)→ 判定为虚拟会话剧本伪影(剧本拾取与游戏结算路径不一致)。dev 复核 sessions/dungeon.mjs 的拾取路径后关案。 +- 仪器:harness longpress 固定 600ms;ASR 1.7x 短句盲区(记入 R2 协议,评测时以 events 为准)。 +- SA3 缺失 24 件(脚步/环境类部分由 SYN 兜底):等 HF token,不影响本轮结构修复。 + +## 3. R2 轮次协议 +1. dev 修 D0-1..D0-3 + D1-1..D1-6(含引擎保险丝,需全量 9 套件回归)→ playtest 全绿 + 新增断言(回声一致性全网格、教学后 60s 内必有事件、菜单关闭、风暴熔断单元断言)。 +2. 重新生成主线+压力转写;live 复测 2 局(重点:教学后 10 分钟内零抢占风暴、探索期静默 ≤20s、菜单可关)。 +3. 人设复审:全员 4 人设复评(R1 全拒,必须全员翻案或给出修复后新结论;目标 R2 至少 P3/P4 翻正)。 diff --git a/research/new8/dungeon/round1/transcript-main.md b/research/new8/dungeon/round1/transcript-main.md new file mode 100644 index 00000000..3d971361 --- /dev/null +++ b/research/new8/dungeon/round1/transcript-main.md @@ -0,0 +1,3630 @@ +# 试玩转写:《dungeon》 + +> 本文件是「玩家感知日志」:一位新手玩家的整局体验转写。语音列出了玩家听到的每句话全文, +> 音效/BGM/震动按发生时间列出。「静默」段 = 玩家什么也听不到。手势是剧本注入的(模拟真人手部动作), +> 非最优操作:包含犹豫、误操作与乱试。语音时长按语速估算(±20%),仅供节奏参考。 +t+ 0.0s (玩家点开游戏,轻触屏幕开始) +t+ 0.0s ♫ BGM 切换 → bgm/menu +t+ 0.0s ♪ 向导音:menu(系统界面) +t+ 0.0s 【状态】盲眼探窟者睁开了耳朵,准备下窟 标题页:单击开始探窟,上滑听元进度(BGM=bgm/menu,语速1) + +#### 【玩家动作】单击,开始 + +t+ 0.0s ▶ 语音:「新的探窟,开始了。」 +t+ 2.1s ♫ BGM 切换 → bgm/explore +t+ 2.1s ♪ 向导音:explore(探索/安全) +t+ 2.1s ▶ 语音:「你在黑暗中醒来。这里是回声地窟。」 +t+ 2.1s 【状态】教学开始:三句开场白 教学演练第 0 步,跟着旁白做(语音播放中,语速1) +t+ 4.2s ▶ 语音:「伸手不见五指,声音就是你的眼睛。」 +t+ 6.4s ▶ 语音:「先学会用耳朵看。单击,发出声波脉冲。」 + +#### 【玩家动作】单击回应「先学会用耳朵看」 + + +#### 【玩家动作】单击——人生第一声脉冲 + +t+ 8.5s ♪ 音效 sfx/pulse +t+ 8.5s ▶ 语音:「听,回声回来了。」 +t+ 8.5s ▶ 语音:「回声回来越快,墙就越近。」 +t+ 8.5s 【状态】回声回来了,听墙的距离 教学演练第 2 步,跟着旁白做(语音播放中,语速1) +t+ 8.8s ♪ 音效 sfx/side_wall_l +t+ 8.8s ♪ 音效 sfx/side_wall_r +t+ 9.0s ♪ 音效 sfx/echo_2 +t+ 9.1s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】单击继续 + +t+ 16.5s ▶ 语音:「两侧的回声,发闷是墙,空旷是通路。」 + +#### 【玩家动作】单击继续(侧向规则听完了) + +t+ 18.6s ♪ 音效 sfx/step_turn +t+ 18.6s ▶ 语音:「按住屏幕不放,你就往前走。」 + +#### 【玩家动作】按住屏幕不放——脚步声起,往前走 + +t+ 18.6s ♪ 音效 sfx/step_own +t+ 18.6s 〰 震动:swipe(滑动识别) +t+ 19.1s ♪ 音效 sfx/step_own +t+ 19.1s ▶ 语音:「左滑右滑,可以转身。」 +t+ 19.6s ♪ 音效 sfx/bump +t+ 19.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】松开,停下 + + +#### 【玩家动作】左滑,转身 + +t+ 21.2s ♪ 音效 sfx/step_turn + +#### 【玩家动作】右滑,转回来 + +t+ 22.1s ♪ 音效 sfx/step_turn +t+ 22.1s ▶ 语音:「双击,再发一次脉冲,不要钱。」 + +#### 【玩家动作】双击——再发一次脉冲,不要钱 + +t+ 22.1s ♪ 音效 sfx/pulse +t+ 22.1s ▶ 语音:「听,回声回来了。」 +t+ 22.1s ▶ 语音:「上滑,随时听自己的状态。」 +t+ 22.4s ♪ 音效 sfx/echo_1 +t+ 22.5s ♪ 音效 sfx/side_wall_l +t+ 22.5s ♪ 音效 sfx/side_wall_r +t+ 22.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】上滑,听自己的状态 + +t+ 24.2s ▶ 语音:「这是第一层。」 +t+ 26.4s ▶ 语音:「你毫发无损。」 +t+ 28.5s ▶ 语音:「口袋空空。」 + +#### 【玩家动作】下滑,打开菜单 + +t+ 30.1s ♫ BGM 切换 → bgm/menu +t+ 30.1s ♪ 向导音:menu(系统界面) +t+ 30.1s ▶ 语音:「菜单。单击切换,双击确认。」 +t+ 32.2s ▶ 语音:「返回探索。」 + +#### 【玩家动作】再下滑,关上菜单 + +t+ 32.2s ♫ BGM 切换 → bgm/explore +t+ 32.2s ♪ 音效 sfx/step_turn +t+ 32.2s ♪ 音效 sfx/step_turn +t+ 32.2s ▶ 语音:「前面有东西!放心,它伤不到你。」 +t+ 32.2s 【状态】「前面有东西!放心,它伤不到你」——等它扑上来 教学演练第 8 步,跟着旁白做(语音播放中,语速1) +t+ 33.4s ♪ 音效 sfx/mob_step_small_c +t+ 34.6s ♪ 音效 sfx/mob_step_small_c +t+ 34.6s 〰 震动:heartbeat(心跳/倒计时) +t+ 35.6s ♪ 音效 sfx/mob_step_small_c +t+ 35.6s 〰 震动:heartbeat(心跳/倒计时) +t+ 35.8s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 35.8s ♪ 向导音:battle(战斗/对抗) +t+ 35.8s ▶ 语音:「盲眼蝠扑了过来!」 +t+ 35.8s 【状态】教学怪扑到了面前 战斗播报中,先听着(语音播放中,语速1) +t+ 38.0s ▶ 语音:「听到它的动静,双击格挡。」 +t+ 40.1s ♪ 音效 sfx/tel_small +t+ 40.1s 〰 震动:beat(节拍点) + +#### 【玩家动作】听到它的动静——双击,格挡 + +t+ 40.1s ♪ 音效 sfx/parry +t+ 40.1s 〰 震动:confirm(轻点确认) +t+ 41.3s ▶ 语音:「挡住了!」 +t+ 43.4s ▶ 语音:「现在单击,挥拳打它。」 +t+ 45.5s ♪ 向导音:turn(轮到你) +t+ 45.5s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击挥拳,两拳放倒它 + +t+ 45.5s ♪ 音效 sfx/atk_swing +t+ 45.5s ♪ 音效 sfx/atk_hit +t+ 45.5s ♪ 音效 sfx/tel_small +t+ 45.5s 〰 震动:beat(节拍点) +t+ 46.0s ♪ 音效 sfx/parry +t+ 46.0s 〰 震动:confirm(轻点确认) +t+ 46.7s ▶ 语音:「挡住了!」 +t+ 48.8s ▶ 语音:「现在单击,挥拳打它。」 +t+ 51.0s ♪ 向导音:turn(轮到你) +t+ 51.0s 〰 震动:confirm(轻点确认) +t+ 51.0s ♪ 音效 sfx/atk_swing +t+ 51.0s ♪ 音效 sfx/atk_hit +t+ 51.0s ♪ 音效 sfx/enemy_die +t+ 51.0s ♪ 音效 sfx/coin +t+ 51.0s ▶ 语音:「打中了,就是这样。」 +t+ 51.0s 〰 震动:success(成功长震) +t+ 53.1s ▶ 语音:「战利不多。」 +t+ 55.2s ▶ 语音:「铁器的回声,就是下潜的楼梯。」 +t+ 55.2s 【状态】教学战获胜——「打中了,就是这样」 教学演练第 9 步,跟着旁白做(语音播放中,语速1) + +#### 【玩家动作】单击,听听楼梯的铁器回声 + +t+ 55.2s ♪ 音效 sfx/pulse +t+ 55.5s ♪ 音效 sfx/side_wall_l +t+ 55.5s ♪ 音效 sfx/side_wall_r +t+ 56.3s ♪ 音效 sfx/echo_4 +t+ 56.4s ♪ 音效 sfx/mat_stone +t+ 58.7s ♪ 音效 sfx/step_own +t+ 58.7s 〰 震动:swipe(滑动识别) +t+ 59.2s ♪ 音效 sfx/step_own +t+ 59.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 59.7s ♪ 音效 sfx/step_turn +t+ 63.0s ♪ 音效 sfx/step_turn +t+ 63.2s ♪ 音效 sfx/step_own +t+ 63.2s 〰 震动:swipe(滑动识别) +t+ 63.7s ♪ 音效 sfx/step_own +t+ 63.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 64.2s ♪ 音效 sfx/step_turn +t+ 64.2s ♪ 音效 sfx/step_turn +t+ 64.2s ♪ 音效 sfx/step_own +t+ 64.2s 〰 震动:swipe(滑动识别) +t+ 64.7s ♪ 音效 sfx/step_own +t+ 64.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 65.2s ♪ 音效 sfx/step_turn +t+ 65.2s ♪ 音效 sfx/step_turn +t+ 65.2s ♪ 音效 sfx/step_own +t+ 65.2s 〰 震动:swipe(滑动识别) +t+ 65.7s ♪ 音效 sfx/step_own +t+ 65.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 66.2s ♪ 音效 sfx/step_turn +t+ 66.2s ♪ 音效 sfx/step_turn +t+ 66.2s ♪ 音效 sfx/step_own +t+ 66.2s 〰 震动:swipe(滑动识别) +t+ 66.7s ♪ 音效 sfx/step_own +t+ 66.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 67.2s ♪ 音效 sfx/step_turn +t+ 67.2s ♪ 音效 sfx/step_turn +t+ 67.2s ♪ 音效 sfx/step_own +t+ 67.2s 〰 震动:swipe(滑动识别) +t+ 67.7s ♪ 音效 sfx/step_own +t+ 67.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 68.2s ♪ 音效 sfx/step_turn +t+ 68.2s ♪ 音效 sfx/step_turn +t+ 68.2s ♪ 音效 sfx/step_own +t+ 68.2s 〰 震动:swipe(滑动识别) +t+ 68.7s ♪ 音效 sfx/step_own +t+ 68.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 69.2s ♪ 音效 sfx/step_turn +t+ 69.2s ♪ 音效 sfx/step_turn +t+ 69.2s ♪ 音效 sfx/step_own +t+ 69.2s 〰 震动:swipe(滑动识别) +t+ 69.7s ♪ 音效 sfx/step_own +t+ 69.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 70.2s ♪ 音效 sfx/step_turn +t+ 70.2s ♪ 音效 sfx/step_turn +t+ 70.2s ♪ 音效 sfx/step_own +t+ 70.2s 〰 震动:swipe(滑动识别) +t+ 70.7s ♪ 音效 sfx/step_own +t+ 70.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 71.2s ♪ 音效 sfx/step_turn +t+ 71.2s ♪ 音效 sfx/step_turn +t+ 71.2s ♪ 音效 sfx/step_own +t+ 71.2s 〰 震动:swipe(滑动识别) +t+ 71.7s ♪ 音效 sfx/step_own +t+ 71.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 72.2s ♪ 音效 sfx/step_turn +t+ 72.2s ♪ 音效 sfx/step_turn +t+ 72.2s ♪ 音效 sfx/step_own +t+ 72.2s 〰 震动:swipe(滑动识别) +t+ 72.7s ♪ 音效 sfx/step_own +t+ 72.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 73.2s ♪ 音效 sfx/step_turn +t+ 73.2s ♪ 音效 sfx/step_turn +t+ 73.2s ♪ 音效 sfx/step_own +t+ 73.2s 〰 震动:swipe(滑动识别) +t+ 73.7s ♪ 音效 sfx/step_own +t+ 73.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 74.2s ♪ 音效 sfx/step_turn +t+ 74.2s ♪ 音效 sfx/step_turn +t+ 74.2s ♪ 音效 sfx/step_own +t+ 74.2s 〰 震动:swipe(滑动识别) +t+ 74.7s ♪ 音效 sfx/step_own +t+ 74.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 75.2s ♪ 音效 sfx/step_turn +t+ 75.2s ♪ 音效 sfx/step_turn +t+ 75.2s ♪ 音效 sfx/step_own +t+ 75.2s 〰 震动:swipe(滑动识别) +t+ 75.7s ♪ 音效 sfx/step_own +t+ 75.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 76.2s ♪ 音效 sfx/step_turn +t+ 76.2s ♪ 音效 sfx/step_turn +t+ 76.2s ♪ 音效 sfx/step_own +t+ 76.2s 〰 震动:swipe(滑动识别) +t+ 76.7s ♪ 音效 sfx/step_own +t+ 76.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 77.2s ♪ 音效 sfx/step_turn +t+ 77.2s ♪ 音效 sfx/step_turn +t+ 77.2s ♪ 音效 sfx/step_own +t+ 77.2s 〰 震动:swipe(滑动识别) +t+ 77.7s ♪ 音效 sfx/step_own +t+ 77.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 78.2s ♪ 音效 sfx/step_turn +t+ 78.2s ♪ 音效 sfx/step_turn +t+ 78.2s ♪ 音效 sfx/step_own +t+ 78.2s 〰 震动:swipe(滑动识别) +t+ 78.7s ♪ 音效 sfx/step_own +t+ 78.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 79.2s ♪ 音效 sfx/step_turn +t+ 79.2s ♪ 音效 sfx/step_turn +t+ 79.2s ♪ 音效 sfx/step_own +t+ 79.2s 〰 震动:swipe(滑动识别) +t+ 79.7s ♪ 音效 sfx/step_own +t+ 79.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 80.2s ♪ 音效 sfx/step_turn +t+ 80.2s ♪ 音效 sfx/step_turn +t+ 80.2s ♪ 音效 sfx/step_own +t+ 80.2s 〰 震动:swipe(滑动识别) +t+ 80.7s ♪ 音效 sfx/step_own +t+ 80.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 81.2s ♪ 音效 sfx/step_turn +t+ 81.2s ♪ 音效 sfx/step_turn +t+ 81.2s ♪ 音效 sfx/step_own +t+ 81.2s 〰 震动:swipe(滑动识别) +t+ 81.7s ♪ 音效 sfx/step_own +t+ 81.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 82.2s ♪ 音效 sfx/step_turn +t+ 82.2s ♪ 音效 sfx/step_turn +t+ 82.2s ♪ 音效 sfx/step_own +t+ 82.2s 〰 震动:swipe(滑动识别) +t+ 82.7s ♪ 音效 sfx/step_own +t+ 82.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 83.2s ♪ 音效 sfx/step_turn +t+ 83.2s ♪ 音效 sfx/step_turn +t+ 83.2s ♪ 音效 sfx/step_own +t+ 83.2s 〰 震动:swipe(滑动识别) +t+ 83.7s ♪ 音效 sfx/step_own +t+ 83.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 84.2s ♪ 音效 sfx/step_turn +t+ 84.2s ♪ 音效 sfx/step_turn +t+ 84.2s ♪ 音效 sfx/step_own +t+ 84.2s 〰 震动:swipe(滑动识别) +t+ 84.7s ♪ 音效 sfx/step_own +t+ 84.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 85.2s ♪ 音效 sfx/step_turn +t+ 85.2s ♪ 音效 sfx/step_turn +t+ 85.2s ♪ 音效 sfx/step_own +t+ 85.2s 〰 震动:swipe(滑动识别) +t+ 85.7s ♪ 音效 sfx/step_own +t+ 85.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 86.2s ♪ 音效 sfx/step_turn +t+ 86.2s ♪ 音效 sfx/step_turn +t+ 86.2s ♪ 音效 sfx/step_own +t+ 86.2s 〰 震动:swipe(滑动识别) +t+ 86.7s ♪ 音效 sfx/step_own +t+ 86.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 87.2s ♪ 音效 sfx/step_turn +t+ 87.2s ♪ 音效 sfx/step_turn +t+ 87.2s ♪ 音效 sfx/step_own +t+ 87.2s 〰 震动:swipe(滑动识别) +t+ 87.7s ♪ 音效 sfx/step_own +t+ 87.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 88.2s ♪ 音效 sfx/step_turn +t+ 88.2s ♪ 音效 sfx/step_turn +t+ 88.2s ♪ 音效 sfx/step_own +t+ 88.2s 〰 震动:swipe(滑动识别) +t+ 88.7s ♪ 音效 sfx/step_own +t+ 88.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 89.2s ♪ 音效 sfx/step_turn +t+ 89.2s ♪ 音效 sfx/step_turn +t+ 89.2s ♪ 音效 sfx/step_own +t+ 89.2s 〰 震动:swipe(滑动识别) +t+ 89.7s ♪ 音效 sfx/step_own +t+ 89.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 90.2s ♪ 音效 sfx/step_turn +t+ 90.2s ♪ 音效 sfx/step_turn +t+ 90.2s ♪ 音效 sfx/step_own +t+ 90.2s 〰 震动:swipe(滑动识别) +t+ 90.7s ♪ 音效 sfx/step_own +t+ 90.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 91.2s ♪ 音效 sfx/step_turn +t+ 91.2s ♪ 音效 sfx/step_turn +t+ 91.2s ♪ 音效 sfx/step_own +t+ 91.2s 〰 震动:swipe(滑动识别) +t+ 91.7s ♪ 音效 sfx/step_own +t+ 91.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 92.2s ♪ 音效 sfx/step_turn +t+ 92.2s ♪ 音效 sfx/step_turn +t+ 92.2s ♪ 音效 sfx/step_own +t+ 92.2s 〰 震动:swipe(滑动识别) +t+ 92.7s ♪ 音效 sfx/step_own +t+ 92.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 93.2s ♪ 音效 sfx/step_turn +t+ 93.2s ♪ 音效 sfx/step_turn +t+ 93.2s ♪ 音效 sfx/step_own +t+ 93.2s 〰 震动:swipe(滑动识别) +t+ 93.7s ♪ 音效 sfx/step_own +t+ 93.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 94.2s ♪ 音效 sfx/step_turn +t+ 94.2s ♪ 音效 sfx/step_turn +t+ 94.2s ♪ 音效 sfx/step_own +t+ 94.2s 〰 震动:swipe(滑动识别) +t+ 94.7s ♪ 音效 sfx/step_own +t+ 94.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 95.2s ♪ 音效 sfx/step_turn +t+ 95.2s ♪ 音效 sfx/step_turn +t+ 95.2s ♪ 音效 sfx/step_own +t+ 95.2s 〰 震动:swipe(滑动识别) +t+ 95.7s ♪ 音效 sfx/step_own +t+ 95.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 96.2s ♪ 音效 sfx/step_turn +t+ 96.2s ♪ 音效 sfx/step_turn +t+ 96.2s ♪ 音效 sfx/step_own +t+ 96.2s 〰 震动:swipe(滑动识别) +t+ 96.7s ♪ 音效 sfx/step_own +t+ 96.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 97.2s ♪ 音效 sfx/step_turn +t+ 97.2s ♪ 音效 sfx/step_turn +t+ 97.2s ♪ 音效 sfx/step_own +t+ 97.2s 〰 震动:swipe(滑动识别) +t+ 97.7s ♪ 音效 sfx/step_own +t+ 97.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 98.2s ♪ 音效 sfx/step_turn +t+ 98.2s ♪ 音效 sfx/step_turn +t+ 98.2s ♪ 音效 sfx/step_own +t+ 98.2s 〰 震动:swipe(滑动识别) +t+ 98.7s ♪ 音效 sfx/step_own +t+ 98.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 99.2s ♪ 音效 sfx/step_turn +t+ 99.2s ♪ 音效 sfx/step_turn +t+ 99.2s ♪ 音效 sfx/step_own +t+ 99.2s 〰 震动:swipe(滑动识别) +t+ 99.7s ♪ 音效 sfx/step_own +t+ 99.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 100.2s ♪ 音效 sfx/step_turn +t+ 100.2s ♪ 音效 sfx/step_turn +t+ 100.2s ♪ 音效 sfx/step_own +t+ 100.2s 〰 震动:swipe(滑动识别) +t+ 100.7s ♪ 音效 sfx/step_own +t+ 100.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 101.2s ♪ 音效 sfx/step_turn +t+ 101.2s ♪ 音效 sfx/step_turn +t+ 101.2s ♪ 音效 sfx/step_own +t+ 101.2s 〰 震动:swipe(滑动识别) +t+ 101.7s ♪ 音效 sfx/step_own +t+ 101.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 102.2s ♪ 音效 sfx/step_turn +t+ 102.2s ♪ 音效 sfx/step_turn +t+ 102.2s ♪ 音效 sfx/step_own +t+ 102.2s 〰 震动:swipe(滑动识别) +t+ 102.7s ♪ 音效 sfx/step_own +t+ 102.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 103.2s ♪ 音效 sfx/step_turn +t+ 103.2s ♪ 音效 sfx/step_turn +t+ 103.2s ♪ 音效 sfx/step_own +t+ 103.2s 〰 震动:swipe(滑动识别) +t+ 103.7s ♪ 音效 sfx/step_own +t+ 103.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 104.2s ♪ 音效 sfx/step_turn +t+ 104.2s ♪ 音效 sfx/step_turn +t+ 104.2s ♪ 音效 sfx/step_own +t+ 104.2s 〰 震动:swipe(滑动识别) +t+ 104.7s ♪ 音效 sfx/step_own +t+ 104.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 105.2s ♪ 音效 sfx/step_turn +t+ 105.2s ♪ 音效 sfx/step_turn +t+ 105.2s ♪ 音效 sfx/step_own +t+ 105.2s 〰 震动:swipe(滑动识别) +t+ 105.7s ♪ 音效 sfx/step_own +t+ 105.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 106.2s ♪ 音效 sfx/step_turn +t+ 106.2s ♪ 音效 sfx/step_turn +t+ 106.2s ♪ 音效 sfx/step_own +t+ 106.2s 〰 震动:swipe(滑动识别) +t+ 106.7s ♪ 音效 sfx/step_own +t+ 106.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 107.2s ♪ 音效 sfx/step_turn +t+ 107.2s ♪ 音效 sfx/step_turn +t+ 107.2s ♪ 音效 sfx/step_own +t+ 107.2s 〰 震动:swipe(滑动识别) +t+ 107.7s ♪ 音效 sfx/step_own +t+ 107.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 108.2s ♪ 音效 sfx/step_turn +t+ 108.2s ♪ 音效 sfx/step_turn +t+ 108.2s ♪ 音效 sfx/step_own +t+ 108.2s 〰 震动:swipe(滑动识别) +t+ 108.7s ♪ 音效 sfx/step_own +t+ 108.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 109.2s ♪ 音效 sfx/step_turn +t+ 109.2s ♪ 音效 sfx/step_turn +t+ 109.2s ♪ 音效 sfx/step_own +t+ 109.2s 〰 震动:swipe(滑动识别) +t+ 109.7s ♪ 音效 sfx/step_own +t+ 109.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 110.2s ♪ 音效 sfx/step_turn +t+ 110.2s ♪ 音效 sfx/step_turn +t+ 110.2s ♪ 音效 sfx/step_own +t+ 110.2s 〰 震动:swipe(滑动识别) +t+ 110.7s ♪ 音效 sfx/step_own +t+ 110.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 111.2s ♪ 音效 sfx/step_turn +t+ 111.2s ♪ 音效 sfx/step_turn +t+ 111.2s ♪ 音效 sfx/step_own +t+ 111.2s 〰 震动:swipe(滑动识别) +t+ 111.7s ♪ 音效 sfx/step_own +t+ 111.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 112.2s ♪ 音效 sfx/step_turn +t+ 112.2s ♪ 音效 sfx/step_turn +t+ 112.2s ♪ 音效 sfx/step_own +t+ 112.2s 〰 震动:swipe(滑动识别) +t+ 112.7s ♪ 音效 sfx/step_own +t+ 112.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 113.2s ♪ 音效 sfx/step_turn +t+ 113.2s ♪ 音效 sfx/step_turn +t+ 113.2s ♪ 音效 sfx/step_own +t+ 113.2s 〰 震动:swipe(滑动识别) +t+ 113.7s ♪ 音效 sfx/step_own +t+ 113.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 114.2s ♪ 音效 sfx/step_turn +t+ 114.2s ♪ 音效 sfx/step_turn +t+ 114.2s ♪ 音效 sfx/step_own +t+ 114.2s 〰 震动:swipe(滑动识别) +t+ 114.7s ♪ 音效 sfx/step_own +t+ 114.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 115.2s ♪ 音效 sfx/step_turn +t+ 115.2s ♪ 音效 sfx/step_turn +t+ 115.2s ♪ 音效 sfx/step_own +t+ 115.2s 〰 震动:swipe(滑动识别) +t+ 115.7s ♪ 音效 sfx/step_own +t+ 115.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 116.2s ♪ 音效 sfx/step_turn +t+ 116.2s ♪ 音效 sfx/step_turn +t+ 116.2s ♪ 音效 sfx/step_own +t+ 116.2s 〰 震动:swipe(滑动识别) +t+ 116.7s ♪ 音效 sfx/step_own +t+ 116.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 117.2s ♪ 音效 sfx/step_turn +t+ 117.2s ♪ 音效 sfx/step_turn +t+ 117.2s ♪ 音效 sfx/step_own +t+ 117.2s 〰 震动:swipe(滑动识别) +t+ 117.7s ♪ 音效 sfx/step_own +t+ 117.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 118.2s ♪ 音效 sfx/step_turn +t+ 118.2s ♪ 音效 sfx/step_turn +t+ 118.2s ♪ 音效 sfx/step_own +t+ 118.2s 〰 震动:swipe(滑动识别) +t+ 118.7s ♪ 音效 sfx/step_own +t+ 118.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 119.2s ♪ 音效 sfx/step_turn +t+ 119.2s ♪ 音效 sfx/step_turn +t+ 119.2s ♪ 音效 sfx/step_own +t+ 119.2s 〰 震动:swipe(滑动识别) +t+ 119.7s ♪ 音效 sfx/step_own +t+ 119.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 120.2s ♪ 音效 sfx/step_turn +t+ 120.2s ♪ 音效 sfx/step_turn +t+ 120.2s ♪ 音效 sfx/step_own +t+ 120.2s 〰 震动:swipe(滑动识别) +t+ 120.7s ♪ 音效 sfx/step_own +t+ 120.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 121.2s ♪ 音效 sfx/step_turn +t+ 121.2s ♪ 音效 sfx/step_turn +t+ 121.2s ♪ 音效 sfx/step_own +t+ 121.2s 〰 震动:swipe(滑动识别) +t+ 121.7s ♪ 音效 sfx/step_own +t+ 121.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 122.2s ♪ 音效 sfx/step_turn +t+ 122.2s ♪ 音效 sfx/step_turn +t+ 122.2s ♪ 音效 sfx/step_own +t+ 122.2s 〰 震动:swipe(滑动识别) +t+ 122.7s ♪ 音效 sfx/step_own +t+ 122.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 123.2s ♪ 音效 sfx/step_turn +t+ 123.2s ♪ 音效 sfx/step_turn +t+ 123.2s ♪ 音效 sfx/step_own +t+ 123.2s 〰 震动:swipe(滑动识别) +t+ 123.7s ♪ 音效 sfx/step_own +t+ 123.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 124.2s ♪ 音效 sfx/step_turn +t+ 124.2s ♪ 音效 sfx/step_turn +t+ 124.2s ♪ 音效 sfx/step_own +t+ 124.2s 〰 震动:swipe(滑动识别) +t+ 124.7s ♪ 音效 sfx/step_own +t+ 124.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 125.2s ♪ 音效 sfx/step_turn +t+ 125.2s ♪ 音效 sfx/step_turn +t+ 125.2s ♪ 音效 sfx/step_own +t+ 125.2s 〰 震动:swipe(滑动识别) +t+ 125.7s ♪ 音效 sfx/step_own +t+ 125.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 126.2s ♪ 音效 sfx/step_turn +t+ 126.2s ♪ 音效 sfx/step_turn +t+ 126.2s ♪ 音效 sfx/step_own +t+ 126.2s 〰 震动:swipe(滑动识别) +t+ 126.7s ♪ 音效 sfx/step_own +t+ 126.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 127.2s ♪ 音效 sfx/step_turn +t+ 127.2s ♪ 音效 sfx/step_turn +t+ 127.2s ♪ 音效 sfx/step_own +t+ 127.2s 〰 震动:swipe(滑动识别) +t+ 127.7s ♪ 音效 sfx/step_own +t+ 127.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 128.2s ♪ 音效 sfx/step_turn +t+ 128.2s ♪ 音效 sfx/step_turn +t+ 128.2s ♪ 音效 sfx/step_own +t+ 128.2s 〰 震动:swipe(滑动识别) +t+ 128.7s ♪ 音效 sfx/step_own +t+ 128.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 129.2s ♪ 音效 sfx/step_turn +t+ 129.2s ♪ 音效 sfx/step_turn +t+ 129.2s ♪ 音效 sfx/step_own +t+ 129.2s 〰 震动:swipe(滑动识别) +t+ 129.7s ♪ 音效 sfx/step_own +t+ 129.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 130.2s ♪ 音效 sfx/step_turn +t+ 130.2s ♪ 音效 sfx/step_turn +t+ 130.2s ♪ 音效 sfx/step_own +t+ 130.2s 〰 震动:swipe(滑动识别) +t+ 130.7s ♪ 音效 sfx/step_own +t+ 130.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 131.2s ♪ 音效 sfx/step_turn +t+ 131.2s ♪ 音效 sfx/step_turn +t+ 131.2s ♪ 音效 sfx/step_own +t+ 131.2s 〰 震动:swipe(滑动识别) +t+ 131.7s ♪ 音效 sfx/step_own +t+ 131.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 132.2s ♪ 音效 sfx/step_turn +t+ 132.2s ♪ 音效 sfx/step_turn +t+ 132.2s ♪ 音效 sfx/step_own +t+ 132.2s 〰 震动:swipe(滑动识别) +t+ 132.7s ♪ 音效 sfx/step_own +t+ 132.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 133.2s ♪ 音效 sfx/step_turn +t+ 133.2s ♪ 音效 sfx/step_turn +t+ 133.2s ♪ 音效 sfx/step_own +t+ 133.2s 〰 震动:swipe(滑动识别) +t+ 133.7s ♪ 音效 sfx/step_own +t+ 133.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 134.2s ♪ 音效 sfx/step_turn +t+ 134.2s ♪ 音效 sfx/step_turn +t+ 134.2s ♪ 音效 sfx/step_own +t+ 134.2s 〰 震动:swipe(滑动识别) +t+ 134.7s ♪ 音效 sfx/step_own +t+ 134.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 135.2s ♪ 音效 sfx/step_turn +t+ 135.2s ♪ 音效 sfx/step_turn +t+ 135.2s ♪ 音效 sfx/step_own +t+ 135.2s 〰 震动:swipe(滑动识别) +t+ 135.7s ♪ 音效 sfx/step_own +t+ 135.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 136.2s ♪ 音效 sfx/step_turn +t+ 136.2s ♪ 音效 sfx/step_turn +t+ 136.2s ♪ 音效 sfx/step_own +t+ 136.2s 〰 震动:swipe(滑动识别) +t+ 136.7s ♪ 音效 sfx/step_own +t+ 136.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 137.2s ♪ 音效 sfx/step_turn +t+ 137.2s ♪ 音效 sfx/step_turn +t+ 137.2s ♪ 音效 sfx/step_own +t+ 137.2s 〰 震动:swipe(滑动识别) +t+ 137.7s ♪ 音效 sfx/step_own +t+ 137.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 138.2s ♪ 音效 sfx/step_turn +t+ 138.2s ♪ 音效 sfx/step_turn +t+ 138.2s ♪ 音效 sfx/step_own +t+ 138.2s 〰 震动:swipe(滑动识别) +t+ 138.7s ♪ 音效 sfx/step_own +t+ 138.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 139.2s ♪ 音效 sfx/step_turn +t+ 139.2s ♪ 音效 sfx/step_turn +t+ 139.2s ♪ 音效 sfx/step_own +t+ 139.2s 〰 震动:swipe(滑动识别) +t+ 139.7s ♪ 音效 sfx/step_own +t+ 139.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 140.2s ♪ 音效 sfx/step_turn +t+ 140.2s ♪ 音效 sfx/step_turn +t+ 140.2s ♪ 音效 sfx/step_own +t+ 140.2s 〰 震动:swipe(滑动识别) +t+ 140.7s ♪ 音效 sfx/step_own +t+ 140.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + +t+ 141.2s ♪ 音效 sfx/step_turn +t+ 141.2s ♪ 音效 sfx/step_turn +t+ 141.2s ♪ 音效 sfx/step_own +t+ 141.2s 〰 震动:swipe(滑动识别) +t+ 141.7s ♪ 音效 sfx/step_own +t+ 141.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击,下潜 + +t+ 142.2s ♪ 音效 sfx/pulse +t+ 142.4s ♪ 音效 sfx/echo_1 +t+ 142.6s ♪ 音效 sfx/side_wall_l +t+ 142.6s ♪ 音效 sfx/side_wall_r +t+ 142.6s ♪ 音效 sfx/mat_stone +t+ 142.6s 【状态】教学完了。第二层,真正的地窟从这里开始 教学演练第 9 步,跟着旁白做(BGM=bgm/battle,语速1) + +#### 【玩家动作】单击脉冲,探一探第二层 + +t+ 142.6s 〰 震动:error(做错了:两连短震) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 142.6s 〰 震动:error(做错了:两连短震) +t+ 142.6s ♪ 音效 sfx/step_turn +t+ 142.6s ♪ 音效 sfx/step_turn +t+ 142.6s ♪ 音效 sfx/step_own +t+ 142.6s 〰 震动:swipe(滑动识别) +t+ 143.1s ♪ 音效 sfx/step_own +t+ 143.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 143.6s ♪ 音效 sfx/pulse +t+ 143.9s ♪ 音效 sfx/side_wall_l +t+ 143.9s ♪ 音效 sfx/side_wall_r +t+ 144.4s ♪ 音效 sfx/echo_3 +t+ 144.5s ♪ 音效 sfx/mat_stone +t+ 144.5s ♪ 音效 sfx/step_turn +t+ 144.5s ♪ 音效 sfx/step_turn +t+ 144.5s ♪ 音效 sfx/step_own +t+ 144.5s 〰 震动:swipe(滑动识别) +t+ 145.0s ♪ 音效 sfx/step_own +t+ 145.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 145.5s ♪ 音效 sfx/pulse +t+ 145.7s ♪ 音效 sfx/echo_1 +t+ 145.8s ♪ 音效 sfx/side_wall_l +t+ 145.8s ♪ 音效 sfx/side_wall_r +t+ 145.9s ♪ 音效 sfx/mat_stone +t+ 145.9s ♪ 音效 sfx/step_turn +t+ 145.9s ♪ 音效 sfx/step_turn +t+ 145.9s ♪ 音效 sfx/step_own +t+ 145.9s 〰 震动:swipe(滑动识别) +t+ 146.4s ♪ 音效 sfx/step_own +t+ 146.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 146.9s ♪ 音效 sfx/pulse +t+ 147.2s ♪ 音效 sfx/side_wall_l +t+ 147.2s ♪ 音效 sfx/side_wall_r +t+ 147.7s ♪ 音效 sfx/echo_3 +t+ 147.8s ♪ 音效 sfx/mat_stone +t+ 147.8s ♪ 音效 sfx/step_turn +t+ 147.8s ♪ 音效 sfx/step_turn +t+ 147.8s ♪ 音效 sfx/step_own +t+ 147.8s 〰 震动:swipe(滑动识别) +t+ 148.3s ♪ 音效 sfx/step_own +t+ 148.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 148.8s ♪ 音效 sfx/pulse +t+ 149.0s ♪ 音效 sfx/echo_1 +t+ 149.1s ♪ 音效 sfx/side_wall_l +t+ 149.1s ♪ 音效 sfx/side_wall_r +t+ 149.2s ♪ 音效 sfx/mat_stone +t+ 149.2s ♪ 音效 sfx/step_turn +t+ 149.2s ♪ 音效 sfx/step_turn +t+ 149.2s ♪ 音效 sfx/step_own +t+ 149.2s 〰 震动:swipe(滑动识别) +t+ 149.7s ♪ 音效 sfx/step_own +t+ 149.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 150.2s ♪ 音效 sfx/pulse +t+ 150.5s ♪ 音效 sfx/side_wall_l +t+ 150.5s ♪ 音效 sfx/side_wall_r +t+ 150.9s ♪ 音效 sfx/echo_3 +t+ 151.1s ♪ 音效 sfx/mat_stone +t+ 151.1s ♪ 音效 sfx/step_turn +t+ 151.1s ♪ 音效 sfx/step_turn +t+ 151.1s ♪ 音效 sfx/step_own +t+ 151.1s 〰 震动:swipe(滑动识别) +t+ 151.6s ♪ 音效 sfx/step_own +t+ 151.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 152.1s ♪ 音效 sfx/pulse +t+ 152.3s ♪ 音效 sfx/echo_1 +t+ 152.4s ♪ 音效 sfx/side_wall_l +t+ 152.4s ♪ 音效 sfx/side_wall_r +t+ 152.4s ♪ 音效 sfx/mat_stone +t+ 152.4s ♪ 音效 sfx/step_turn +t+ 152.4s ♪ 音效 sfx/step_turn +t+ 152.4s ♪ 音效 sfx/step_own +t+ 152.4s 〰 震动:swipe(滑动识别) +t+ 152.9s ♪ 音效 sfx/step_own +t+ 152.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 153.4s ♪ 音效 sfx/pulse +t+ 153.8s ♪ 音效 sfx/side_wall_l +t+ 153.8s ♪ 音效 sfx/side_wall_r +t+ 154.2s ♪ 音效 sfx/echo_3 +t+ 154.4s ♪ 音效 sfx/mat_stone +t+ 154.4s ♪ 音效 sfx/step_turn +t+ 154.4s ♪ 音效 sfx/step_turn +t+ 154.4s ♪ 音效 sfx/step_own +t+ 154.4s 〰 震动:swipe(滑动识别) +t+ 154.9s ♪ 音效 sfx/step_own +t+ 154.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 155.4s ♪ 音效 sfx/pulse +t+ 155.6s ♪ 音效 sfx/echo_1 +t+ 155.7s ♪ 音效 sfx/side_wall_l +t+ 155.7s ♪ 音效 sfx/side_wall_r +t+ 155.7s ♪ 音效 sfx/mat_stone +t+ 155.7s ♪ 音效 sfx/step_turn +t+ 155.7s ♪ 音效 sfx/step_turn +t+ 155.7s ♪ 音效 sfx/step_own +t+ 155.7s 〰 震动:swipe(滑动识别) +t+ 156.2s ♪ 音效 sfx/step_own +t+ 156.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 156.7s ♪ 音效 sfx/pulse +t+ 157.1s ♪ 音效 sfx/side_wall_l +t+ 157.1s ♪ 音效 sfx/side_wall_r +t+ 157.5s ♪ 音效 sfx/echo_3 +t+ 157.7s ♪ 音效 sfx/mat_stone +t+ 157.7s ♪ 音效 sfx/step_turn +t+ 157.7s ♪ 音效 sfx/step_turn +t+ 157.7s ♪ 音效 sfx/step_own +t+ 157.7s 〰 震动:swipe(滑动识别) +t+ 158.2s ♪ 音效 sfx/step_own +t+ 158.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 158.7s ♪ 音效 sfx/pulse +t+ 158.9s ♪ 音效 sfx/echo_1 +t+ 159.0s ♪ 音效 sfx/side_wall_l +t+ 159.0s ♪ 音效 sfx/side_wall_r +t+ 159.0s ♪ 音效 sfx/mat_stone +t+ 159.0s ♪ 音效 sfx/step_turn +t+ 159.0s ♪ 音效 sfx/step_turn +t+ 159.0s ♪ 音效 sfx/step_own +t+ 159.0s 〰 震动:swipe(滑动识别) +t+ 159.5s ♪ 音效 sfx/step_own +t+ 159.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 160.0s ♪ 音效 sfx/pulse +t+ 160.4s ♪ 音效 sfx/side_wall_l +t+ 160.4s ♪ 音效 sfx/side_wall_r +t+ 160.8s ♪ 音效 sfx/echo_3 +t+ 160.9s ♪ 音效 sfx/mat_stone +t+ 160.9s ♪ 音效 sfx/step_turn +t+ 160.9s ♪ 音效 sfx/step_turn +t+ 160.9s ♪ 音效 sfx/step_own +t+ 160.9s 〰 震动:swipe(滑动识别) +t+ 161.4s ♪ 音效 sfx/step_own +t+ 161.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 161.9s ♪ 音效 sfx/pulse +t+ 162.2s ♪ 音效 sfx/echo_1 +t+ 162.3s ♪ 音效 sfx/side_wall_l +t+ 162.3s ♪ 音效 sfx/side_wall_r +t+ 162.3s ♪ 音效 sfx/mat_stone +t+ 162.3s ♪ 音效 sfx/step_turn +t+ 162.3s ♪ 音效 sfx/step_turn +t+ 162.3s ♪ 音效 sfx/step_own +t+ 162.3s 〰 震动:swipe(滑动识别) +t+ 162.8s ♪ 音效 sfx/step_own +t+ 162.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 163.3s ♪ 音效 sfx/pulse +t+ 163.7s ♪ 音效 sfx/side_wall_l +t+ 163.7s ♪ 音效 sfx/side_wall_r +t+ 164.1s ♪ 音效 sfx/echo_3 +t+ 164.2s ♪ 音效 sfx/mat_stone +t+ 164.2s ♪ 音效 sfx/step_turn +t+ 164.2s ♪ 音效 sfx/step_turn +t+ 164.2s ♪ 音效 sfx/step_own +t+ 164.2s 〰 震动:swipe(滑动识别) +t+ 164.7s ♪ 音效 sfx/step_own +t+ 164.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 165.2s ♪ 音效 sfx/pulse +t+ 165.5s ♪ 音效 sfx/echo_1 +t+ 165.6s ♪ 音效 sfx/side_wall_l +t+ 165.6s ♪ 音效 sfx/side_wall_r +t+ 165.6s ♪ 音效 sfx/mat_stone +t+ 165.6s ♪ 音效 sfx/step_turn +t+ 165.6s ♪ 音效 sfx/step_turn +t+ 165.6s ♪ 音效 sfx/step_own +t+ 165.6s 〰 震动:swipe(滑动识别) +t+ 166.1s ♪ 音效 sfx/step_own +t+ 166.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 166.6s ♪ 音效 sfx/pulse +t+ 166.9s ♪ 音效 sfx/side_wall_l +t+ 166.9s ♪ 音效 sfx/side_wall_r +t+ 167.4s ♪ 音效 sfx/echo_3 +t+ 167.5s ♪ 音效 sfx/mat_stone +t+ 167.5s ♪ 音效 sfx/step_turn +t+ 167.5s ♪ 音效 sfx/step_turn +t+ 167.5s ♪ 音效 sfx/step_own +t+ 167.5s 〰 震动:swipe(滑动识别) +t+ 168.0s ♪ 音效 sfx/step_own +t+ 168.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 168.5s ♪ 音效 sfx/pulse +t+ 168.8s ♪ 音效 sfx/echo_1 +t+ 168.9s ♪ 音效 sfx/side_wall_l +t+ 168.9s ♪ 音效 sfx/side_wall_r +t+ 168.9s ♪ 音效 sfx/mat_stone +t+ 168.9s ♪ 音效 sfx/step_turn +t+ 168.9s ♪ 音效 sfx/step_turn +t+ 168.9s ♪ 音效 sfx/step_own +t+ 168.9s 〰 震动:swipe(滑动识别) +t+ 169.4s ♪ 音效 sfx/step_own +t+ 169.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 169.9s ♪ 音效 sfx/pulse +t+ 170.2s ♪ 音效 sfx/side_wall_l +t+ 170.2s ♪ 音效 sfx/side_wall_r +t+ 170.7s ♪ 音效 sfx/echo_3 +t+ 170.8s ♪ 音效 sfx/mat_stone +t+ 170.8s ♪ 音效 sfx/step_turn +t+ 170.8s ♪ 音效 sfx/step_turn +t+ 170.8s ♪ 音效 sfx/step_own +t+ 170.8s 〰 震动:swipe(滑动识别) +t+ 171.3s ♪ 音效 sfx/step_own +t+ 171.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 171.8s ♪ 音效 sfx/pulse +t+ 172.1s ♪ 音效 sfx/echo_1 +t+ 172.2s ♪ 音效 sfx/side_wall_l +t+ 172.2s ♪ 音效 sfx/side_wall_r +t+ 172.2s ♪ 音效 sfx/mat_stone +t+ 172.2s ♪ 音效 sfx/step_turn +t+ 172.2s ♪ 音效 sfx/step_turn +t+ 172.2s ♪ 音效 sfx/step_own +t+ 172.2s 〰 震动:swipe(滑动识别) +t+ 172.7s ♪ 音效 sfx/step_own +t+ 172.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 173.2s ♪ 音效 sfx/pulse +t+ 173.5s ♪ 音效 sfx/side_wall_l +t+ 173.5s ♪ 音效 sfx/side_wall_r +t+ 174.0s ♪ 音效 sfx/echo_3 +t+ 174.1s ♪ 音效 sfx/mat_stone +t+ 174.1s ♪ 音效 sfx/step_turn +t+ 174.1s ♪ 音效 sfx/step_turn +t+ 174.1s ♪ 音效 sfx/step_own +t+ 174.1s 〰 震动:swipe(滑动识别) +t+ 174.6s ♪ 音效 sfx/step_own +t+ 174.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 175.1s ♪ 音效 sfx/pulse +t+ 175.3s ♪ 音效 sfx/echo_1 +t+ 175.4s ♪ 音效 sfx/side_wall_l +t+ 175.4s ♪ 音效 sfx/side_wall_r +t+ 175.5s ♪ 音效 sfx/mat_stone +t+ 175.5s ♪ 音效 sfx/step_turn +t+ 175.5s ♪ 音效 sfx/step_turn +t+ 175.5s ♪ 音效 sfx/step_own +t+ 175.5s 〰 震动:swipe(滑动识别) +t+ 176.0s ♪ 音效 sfx/step_own +t+ 176.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 176.5s ♪ 音效 sfx/pulse +t+ 176.8s ♪ 音效 sfx/side_wall_l +t+ 176.8s ♪ 音效 sfx/side_wall_r +t+ 177.3s ♪ 音效 sfx/echo_3 +t+ 177.4s ♪ 音效 sfx/mat_stone +t+ 177.4s ♪ 音效 sfx/step_turn +t+ 177.4s ♪ 音效 sfx/step_turn +t+ 177.4s ♪ 音效 sfx/step_own +t+ 177.4s 〰 震动:swipe(滑动识别) +t+ 177.9s ♪ 音效 sfx/step_own +t+ 177.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 178.4s ♪ 音效 sfx/pulse +t+ 178.6s ♪ 音效 sfx/echo_1 +t+ 178.7s ♪ 音效 sfx/side_wall_l +t+ 178.7s ♪ 音效 sfx/side_wall_r +t+ 178.8s ♪ 音效 sfx/mat_stone +t+ 178.8s ♪ 音效 sfx/step_turn +t+ 178.8s ♪ 音效 sfx/step_turn +t+ 178.8s ♪ 音效 sfx/step_own +t+ 178.8s 〰 震动:swipe(滑动识别) +t+ 179.3s ♪ 音效 sfx/step_own +t+ 179.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 179.8s ♪ 音效 sfx/pulse +t+ 180.1s ♪ 音效 sfx/side_wall_l +t+ 180.1s ♪ 音效 sfx/side_wall_r +t+ 180.6s ♪ 音效 sfx/echo_3 +t+ 180.7s ♪ 音效 sfx/mat_stone +t+ 180.7s ♪ 音效 sfx/step_turn +t+ 180.7s ♪ 音效 sfx/step_turn +t+ 180.7s ♪ 音效 sfx/step_own +t+ 180.7s 〰 震动:swipe(滑动识别) +t+ 181.2s ♪ 音效 sfx/step_own +t+ 181.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 181.7s ♪ 音效 sfx/pulse +t+ 181.9s ♪ 音效 sfx/echo_1 +t+ 182.0s ♪ 音效 sfx/side_wall_l +t+ 182.0s ♪ 音效 sfx/side_wall_r +t+ 182.1s ♪ 音效 sfx/mat_stone +t+ 182.1s ♪ 音效 sfx/step_turn +t+ 182.1s ♪ 音效 sfx/step_turn +t+ 182.1s ♪ 音效 sfx/step_own +t+ 182.1s 〰 震动:swipe(滑动识别) +t+ 182.6s ♪ 音效 sfx/step_own +t+ 182.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 183.1s ♪ 音效 sfx/pulse +t+ 183.4s ♪ 音效 sfx/side_wall_l +t+ 183.4s ♪ 音效 sfx/side_wall_r +t+ 183.8s ♪ 音效 sfx/echo_3 +t+ 184.0s ♪ 音效 sfx/mat_stone +t+ 184.0s ♪ 音效 sfx/step_turn +t+ 184.0s ♪ 音效 sfx/step_turn +t+ 184.0s ♪ 音效 sfx/step_own +t+ 184.0s 〰 震动:swipe(滑动识别) +t+ 184.5s ♪ 音效 sfx/step_own +t+ 184.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 185.0s ♪ 音效 sfx/pulse +t+ 185.2s ♪ 音效 sfx/echo_1 +t+ 185.3s ♪ 音效 sfx/side_wall_l +t+ 185.3s ♪ 音效 sfx/side_wall_r +t+ 185.3s ♪ 音效 sfx/mat_stone +t+ 185.3s ♪ 音效 sfx/step_turn +t+ 185.3s ♪ 音效 sfx/step_turn +t+ 185.3s ♪ 音效 sfx/step_own +t+ 185.3s 〰 震动:swipe(滑动识别) +t+ 185.8s ♪ 音效 sfx/step_own +t+ 185.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 186.3s ♪ 音效 sfx/pulse +t+ 186.7s ♪ 音效 sfx/side_wall_l +t+ 186.7s ♪ 音效 sfx/side_wall_r +t+ 187.1s ♪ 音效 sfx/echo_3 +t+ 187.3s ♪ 音效 sfx/mat_stone +t+ 187.3s ♪ 音效 sfx/step_turn +t+ 187.3s ♪ 音效 sfx/step_turn +t+ 187.3s ♪ 音效 sfx/step_own +t+ 187.3s 〰 震动:swipe(滑动识别) +t+ 187.8s ♪ 音效 sfx/step_own +t+ 187.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 188.3s ♪ 音效 sfx/pulse +t+ 188.5s ♪ 音效 sfx/echo_1 +t+ 188.6s ♪ 音效 sfx/side_wall_l +t+ 188.6s ♪ 音效 sfx/side_wall_r +t+ 188.6s ♪ 音效 sfx/mat_stone +t+ 188.6s ♪ 音效 sfx/step_turn +t+ 188.6s ♪ 音效 sfx/step_turn +t+ 188.6s ♪ 音效 sfx/step_own +t+ 188.6s 〰 震动:swipe(滑动识别) +t+ 189.1s ♪ 音效 sfx/step_own +t+ 189.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 189.6s ♪ 音效 sfx/pulse +t+ 190.0s ♪ 音效 sfx/side_wall_l +t+ 190.0s ♪ 音效 sfx/side_wall_r +t+ 190.4s ♪ 音效 sfx/echo_3 +t+ 190.6s ♪ 音效 sfx/mat_stone +t+ 190.6s ♪ 音效 sfx/step_turn +t+ 190.6s ♪ 音效 sfx/step_turn +t+ 190.6s ♪ 音效 sfx/step_own +t+ 190.6s 〰 震动:swipe(滑动识别) +t+ 191.1s ♪ 音效 sfx/step_own +t+ 191.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 191.6s ♪ 音效 sfx/pulse +t+ 191.8s ♪ 音效 sfx/echo_1 +t+ 191.9s ♪ 音效 sfx/side_wall_l +t+ 191.9s ♪ 音效 sfx/side_wall_r +t+ 191.9s ♪ 音效 sfx/mat_stone +t+ 191.9s ♪ 音效 sfx/step_turn +t+ 191.9s ♪ 音效 sfx/step_turn +t+ 191.9s ♪ 音效 sfx/step_own +t+ 191.9s 〰 震动:swipe(滑动识别) +t+ 192.4s ♪ 音效 sfx/step_own +t+ 192.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 192.9s ♪ 音效 sfx/pulse +t+ 193.3s ♪ 音效 sfx/side_wall_l +t+ 193.3s ♪ 音效 sfx/side_wall_r +t+ 193.7s ♪ 音效 sfx/echo_3 +t+ 193.8s ♪ 音效 sfx/mat_stone +t+ 193.8s ♪ 音效 sfx/step_turn +t+ 193.8s ♪ 音效 sfx/step_turn +t+ 193.8s ♪ 音效 sfx/step_own +t+ 193.8s 〰 震动:swipe(滑动识别) +t+ 194.3s ♪ 音效 sfx/step_own +t+ 194.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 194.8s ♪ 音效 sfx/pulse +t+ 195.1s ♪ 音效 sfx/echo_1 +t+ 195.2s ♪ 音效 sfx/side_wall_l +t+ 195.2s ♪ 音效 sfx/side_wall_r +t+ 195.2s ♪ 音效 sfx/mat_stone +t+ 195.2s ♪ 音效 sfx/step_turn +t+ 195.2s ♪ 音效 sfx/step_turn +t+ 195.2s ♪ 音效 sfx/step_own +t+ 195.2s 〰 震动:swipe(滑动识别) +t+ 195.7s ♪ 音效 sfx/step_own +t+ 195.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 196.2s ♪ 音效 sfx/pulse +t+ 196.6s ♪ 音效 sfx/side_wall_l +t+ 196.6s ♪ 音效 sfx/side_wall_r +t+ 197.0s ♪ 音效 sfx/echo_3 +t+ 197.1s ♪ 音效 sfx/mat_stone +t+ 197.1s ♪ 音效 sfx/step_turn +t+ 197.1s ♪ 音效 sfx/step_turn +t+ 197.1s ♪ 音效 sfx/step_own +t+ 197.1s 〰 震动:swipe(滑动识别) +t+ 197.6s ♪ 音效 sfx/step_own +t+ 197.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 198.1s ♪ 音效 sfx/pulse +t+ 198.4s ♪ 音效 sfx/echo_1 +t+ 198.5s ♪ 音效 sfx/side_wall_l +t+ 198.5s ♪ 音效 sfx/side_wall_r +t+ 198.5s ♪ 音效 sfx/mat_stone +t+ 198.5s ♪ 音效 sfx/step_turn +t+ 198.5s ♪ 音效 sfx/step_turn +t+ 198.5s ♪ 音效 sfx/step_own +t+ 198.5s 〰 震动:swipe(滑动识别) +t+ 199.0s ♪ 音效 sfx/step_own +t+ 199.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 199.5s ♪ 音效 sfx/pulse +t+ 199.8s ♪ 音效 sfx/side_wall_l +t+ 199.8s ♪ 音效 sfx/side_wall_r +t+ 200.3s ♪ 音效 sfx/echo_3 +t+ 200.4s ♪ 音效 sfx/mat_stone +t+ 200.4s ♪ 音效 sfx/step_turn +t+ 200.4s ♪ 音效 sfx/step_turn +t+ 200.4s ♪ 音效 sfx/step_own +t+ 200.4s 〰 震动:swipe(滑动识别) +t+ 200.9s ♪ 音效 sfx/step_own +t+ 200.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 201.4s ♪ 音效 sfx/pulse +t+ 201.7s ♪ 音效 sfx/echo_1 +t+ 201.8s ♪ 音效 sfx/side_wall_l +t+ 201.8s ♪ 音效 sfx/side_wall_r +t+ 201.8s ♪ 音效 sfx/mat_stone +t+ 201.8s ♪ 音效 sfx/step_turn +t+ 201.8s ♪ 音效 sfx/step_turn +t+ 201.8s ♪ 音效 sfx/step_own +t+ 201.8s 〰 震动:swipe(滑动识别) +t+ 202.3s ♪ 音效 sfx/step_own +t+ 202.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 202.8s ♪ 音效 sfx/pulse +t+ 203.1s ♪ 音效 sfx/side_wall_l +t+ 203.1s ♪ 音效 sfx/side_wall_r +t+ 203.6s ♪ 音效 sfx/echo_3 +t+ 203.7s ♪ 音效 sfx/mat_stone +t+ 203.7s ♪ 音效 sfx/step_turn +t+ 203.7s ♪ 音效 sfx/step_turn +t+ 203.7s ♪ 音效 sfx/step_own +t+ 203.7s 〰 震动:swipe(滑动识别) +t+ 204.2s ♪ 音效 sfx/step_own +t+ 204.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 204.7s ♪ 音效 sfx/pulse +t+ 205.0s ♪ 音效 sfx/echo_1 +t+ 205.1s ♪ 音效 sfx/side_wall_l +t+ 205.1s ♪ 音效 sfx/side_wall_r +t+ 205.1s ♪ 音效 sfx/mat_stone +t+ 205.1s ♪ 音效 sfx/step_turn +t+ 205.1s ♪ 音效 sfx/step_turn +t+ 205.1s ♪ 音效 sfx/step_own +t+ 205.1s 〰 震动:swipe(滑动识别) +t+ 205.6s ♪ 音效 sfx/step_own +t+ 205.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 206.1s ♪ 音效 sfx/pulse +t+ 206.4s ♪ 音效 sfx/side_wall_l +t+ 206.4s ♪ 音效 sfx/side_wall_r +t+ 206.9s ♪ 音效 sfx/echo_3 +t+ 207.0s ♪ 音效 sfx/mat_stone +t+ 207.0s ♪ 音效 sfx/step_turn +t+ 207.0s ♪ 音效 sfx/step_turn +t+ 207.0s ♪ 音效 sfx/step_own +t+ 207.0s 〰 震动:swipe(滑动识别) +t+ 207.5s ♪ 音效 sfx/step_own +t+ 207.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 208.0s ♪ 音效 sfx/pulse +t+ 208.3s ♪ 音效 sfx/echo_1 +t+ 208.3s ♪ 音效 sfx/side_wall_l +t+ 208.3s ♪ 音效 sfx/side_wall_r +t+ 208.4s ♪ 音效 sfx/mat_stone +t+ 208.4s ♪ 音效 sfx/step_turn +t+ 208.4s ♪ 音效 sfx/step_turn +t+ 208.4s ♪ 音效 sfx/step_own +t+ 208.4s 〰 震动:swipe(滑动识别) +t+ 208.9s ♪ 音效 sfx/step_own +t+ 208.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 209.4s ♪ 音效 sfx/pulse +t+ 209.7s ♪ 音效 sfx/side_wall_l +t+ 209.7s ♪ 音效 sfx/side_wall_r +t+ 210.2s ♪ 音效 sfx/echo_3 +t+ 210.3s ♪ 音效 sfx/mat_stone +t+ 210.3s ♪ 音效 sfx/step_turn +t+ 210.3s ♪ 音效 sfx/step_turn +t+ 210.3s ♪ 音效 sfx/step_own +t+ 210.3s 〰 震动:swipe(滑动识别) +t+ 210.8s ♪ 音效 sfx/step_own +t+ 210.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 211.3s ♪ 音效 sfx/pulse +t+ 211.5s ♪ 音效 sfx/echo_1 +t+ 211.6s ♪ 音效 sfx/side_wall_l +t+ 211.6s ♪ 音效 sfx/side_wall_r +t+ 211.7s ♪ 音效 sfx/mat_stone +t+ 211.7s ♪ 音效 sfx/step_turn +t+ 211.7s ♪ 音效 sfx/step_turn +t+ 211.7s ♪ 音效 sfx/step_own +t+ 211.7s 〰 震动:swipe(滑动识别) +t+ 212.2s ♪ 音效 sfx/step_own +t+ 212.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 212.7s ♪ 音效 sfx/pulse +t+ 213.0s ♪ 音效 sfx/side_wall_l +t+ 213.0s ♪ 音效 sfx/side_wall_r +t+ 213.5s ♪ 音效 sfx/echo_3 +t+ 213.6s ♪ 音效 sfx/mat_stone +t+ 213.6s ♪ 音效 sfx/step_turn +t+ 213.6s ♪ 音效 sfx/step_turn +t+ 213.6s ♪ 音效 sfx/step_own +t+ 213.6s 〰 震动:swipe(滑动识别) +t+ 214.1s ♪ 音效 sfx/step_own +t+ 214.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 214.6s ♪ 音效 sfx/pulse +t+ 214.8s ♪ 音效 sfx/echo_1 +t+ 214.9s ♪ 音效 sfx/side_wall_l +t+ 214.9s ♪ 音效 sfx/side_wall_r +t+ 214.9s ♪ 音效 sfx/mat_stone +t+ 214.9s ♪ 音效 sfx/step_turn +t+ 214.9s ♪ 音效 sfx/step_turn +t+ 214.9s ♪ 音效 sfx/step_own +t+ 214.9s 〰 震动:swipe(滑动识别) +t+ 215.4s ♪ 音效 sfx/step_own +t+ 215.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 215.9s ♪ 音效 sfx/pulse +t+ 216.3s ♪ 音效 sfx/side_wall_l +t+ 216.3s ♪ 音效 sfx/side_wall_r +t+ 216.8s ♪ 音效 sfx/echo_3 +t+ 216.9s ♪ 音效 sfx/mat_stone +t+ 216.9s ♪ 音效 sfx/step_turn +t+ 216.9s ♪ 音效 sfx/step_turn +t+ 216.9s ♪ 音效 sfx/step_own +t+ 216.9s 〰 震动:swipe(滑动识别) +t+ 217.4s ♪ 音效 sfx/step_own +t+ 217.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 217.9s ♪ 音效 sfx/pulse +t+ 218.1s ♪ 音效 sfx/echo_1 +t+ 218.2s ♪ 音效 sfx/side_wall_l +t+ 218.2s ♪ 音效 sfx/side_wall_r +t+ 218.2s ♪ 音效 sfx/mat_stone +t+ 218.2s ♪ 音效 sfx/step_turn +t+ 218.2s ♪ 音效 sfx/step_turn +t+ 218.2s ♪ 音效 sfx/step_own +t+ 218.2s 〰 震动:swipe(滑动识别) +t+ 218.7s ♪ 音效 sfx/step_own +t+ 218.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 219.2s ♪ 音效 sfx/pulse +t+ 219.6s ♪ 音效 sfx/side_wall_l +t+ 219.6s ♪ 音效 sfx/side_wall_r +t+ 220.0s ♪ 音效 sfx/echo_3 +t+ 220.2s ♪ 音效 sfx/mat_stone +t+ 220.2s ♪ 音效 sfx/step_turn +t+ 220.2s ♪ 音效 sfx/step_turn +t+ 220.2s ♪ 音效 sfx/step_own +t+ 220.2s 〰 震动:swipe(滑动识别) +t+ 220.7s ♪ 音效 sfx/step_own +t+ 220.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 221.2s ♪ 音效 sfx/pulse +t+ 221.4s ♪ 音效 sfx/echo_1 +t+ 221.5s ♪ 音效 sfx/side_wall_l +t+ 221.5s ♪ 音效 sfx/side_wall_r +t+ 221.5s ♪ 音效 sfx/mat_stone +t+ 221.5s ♪ 音效 sfx/step_turn +t+ 221.5s ♪ 音效 sfx/step_turn +t+ 221.5s ♪ 音效 sfx/step_own +t+ 221.5s 〰 震动:swipe(滑动识别) +t+ 222.0s ♪ 音效 sfx/step_own +t+ 222.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 222.5s ♪ 音效 sfx/pulse +t+ 222.9s ♪ 音效 sfx/side_wall_l +t+ 222.9s ♪ 音效 sfx/side_wall_r +t+ 223.3s ♪ 音效 sfx/echo_3 +t+ 223.4s ♪ 音效 sfx/mat_stone +t+ 223.4s ♪ 音效 sfx/step_turn +t+ 223.4s ♪ 音效 sfx/step_turn +t+ 223.4s ♪ 音效 sfx/step_own +t+ 223.4s 〰 震动:swipe(滑动识别) +t+ 223.9s ♪ 音效 sfx/step_own +t+ 223.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 224.4s ♪ 音效 sfx/pulse +t+ 224.7s ♪ 音效 sfx/echo_1 +t+ 224.8s ♪ 音效 sfx/side_wall_l +t+ 224.8s ♪ 音效 sfx/side_wall_r +t+ 224.8s ♪ 音效 sfx/mat_stone +t+ 224.8s ♪ 音效 sfx/step_turn +t+ 224.8s ♪ 音效 sfx/step_turn +t+ 224.8s ♪ 音效 sfx/step_own +t+ 224.8s 〰 震动:swipe(滑动识别) +t+ 225.3s ♪ 音效 sfx/step_own +t+ 225.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 225.8s ♪ 音效 sfx/pulse +t+ 226.2s ♪ 音效 sfx/side_wall_l +t+ 226.2s ♪ 音效 sfx/side_wall_r +t+ 226.6s ♪ 音效 sfx/echo_3 +t+ 226.7s ♪ 音效 sfx/mat_stone +t+ 226.7s ♪ 音效 sfx/step_turn +t+ 226.7s ♪ 音效 sfx/step_turn +t+ 226.7s ♪ 音效 sfx/step_own +t+ 226.7s 〰 震动:swipe(滑动识别) +t+ 227.2s ♪ 音效 sfx/step_own +t+ 227.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 227.7s ♪ 音效 sfx/pulse +t+ 228.0s ♪ 音效 sfx/echo_1 +t+ 228.1s ♪ 音效 sfx/side_wall_l +t+ 228.1s ♪ 音效 sfx/side_wall_r +t+ 228.1s ♪ 音效 sfx/mat_stone +t+ 228.1s ♪ 音效 sfx/step_turn +t+ 228.1s ♪ 音效 sfx/step_turn +t+ 228.1s ♪ 音效 sfx/step_own +t+ 228.1s 〰 震动:swipe(滑动识别) +t+ 228.6s ♪ 音效 sfx/step_own +t+ 228.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 229.1s ♪ 音效 sfx/pulse +t+ 229.5s ♪ 音效 sfx/side_wall_l +t+ 229.5s ♪ 音效 sfx/side_wall_r +t+ 229.9s ♪ 音效 sfx/echo_3 +t+ 230.0s ♪ 音效 sfx/mat_stone +t+ 230.0s ♪ 音效 sfx/step_turn +t+ 230.0s ♪ 音效 sfx/step_turn +t+ 230.0s ♪ 音效 sfx/step_own +t+ 230.0s 〰 震动:swipe(滑动识别) +t+ 230.5s ♪ 音效 sfx/step_own +t+ 230.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 231.0s ♪ 音效 sfx/pulse +t+ 231.3s ♪ 音效 sfx/echo_1 +t+ 231.4s ♪ 音效 sfx/side_wall_l +t+ 231.4s ♪ 音效 sfx/side_wall_r +t+ 231.4s ♪ 音效 sfx/mat_stone +t+ 231.4s ♪ 音效 sfx/step_turn +t+ 231.4s ♪ 音效 sfx/step_turn +t+ 231.4s ♪ 音效 sfx/step_own +t+ 231.4s 〰 震动:swipe(滑动识别) +t+ 231.9s ♪ 音效 sfx/step_own +t+ 231.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 232.4s ♪ 音效 sfx/pulse +t+ 232.8s ♪ 音效 sfx/side_wall_l +t+ 232.8s ♪ 音效 sfx/side_wall_r +t+ 233.2s ♪ 音效 sfx/echo_3 +t+ 233.3s ♪ 音效 sfx/mat_stone +t+ 233.3s ♪ 音效 sfx/step_turn +t+ 233.3s ♪ 音效 sfx/step_turn +t+ 233.3s ♪ 音效 sfx/step_own +t+ 233.3s 〰 震动:swipe(滑动识别) +t+ 233.8s ♪ 音效 sfx/step_own +t+ 233.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 234.3s ♪ 音效 sfx/pulse +t+ 234.6s ♪ 音效 sfx/echo_1 +t+ 234.7s ♪ 音效 sfx/side_wall_l +t+ 234.7s ♪ 音效 sfx/side_wall_r +t+ 234.7s ♪ 音效 sfx/mat_stone +t+ 234.7s ♪ 音效 sfx/step_turn +t+ 234.7s ♪ 音效 sfx/step_turn +t+ 234.7s ♪ 音效 sfx/step_own +t+ 234.7s 〰 震动:swipe(滑动识别) +t+ 235.2s ♪ 音效 sfx/step_own +t+ 235.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 235.7s ♪ 音效 sfx/pulse +t+ 236.0s ♪ 音效 sfx/side_wall_l +t+ 236.0s ♪ 音效 sfx/side_wall_r +t+ 236.5s ♪ 音效 sfx/echo_3 +t+ 236.6s ♪ 音效 sfx/mat_stone +t+ 236.6s ♪ 音效 sfx/step_turn +t+ 236.6s ♪ 音效 sfx/step_turn +t+ 236.6s ♪ 音效 sfx/step_own +t+ 236.6s 〰 震动:swipe(滑动识别) +t+ 237.1s ♪ 音效 sfx/step_own +t+ 237.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 237.6s ♪ 音效 sfx/pulse +t+ 237.9s ♪ 音效 sfx/echo_1 +t+ 238.0s ♪ 音效 sfx/side_wall_l +t+ 238.0s ♪ 音效 sfx/side_wall_r +t+ 238.0s ♪ 音效 sfx/mat_stone +t+ 238.0s ♪ 音效 sfx/step_turn +t+ 238.0s ♪ 音效 sfx/step_turn +t+ 238.0s ♪ 音效 sfx/step_own +t+ 238.0s 〰 震动:swipe(滑动识别) +t+ 238.5s ♪ 音效 sfx/step_own +t+ 238.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 239.0s ♪ 音效 sfx/pulse +t+ 239.3s ♪ 音效 sfx/side_wall_l +t+ 239.3s ♪ 音效 sfx/side_wall_r +t+ 239.8s ♪ 音效 sfx/echo_3 +t+ 239.9s ♪ 音效 sfx/mat_stone +t+ 239.9s ♪ 音效 sfx/step_turn +t+ 239.9s ♪ 音效 sfx/step_turn +t+ 239.9s ♪ 音效 sfx/step_own +t+ 239.9s 〰 震动:swipe(滑动识别) +t+ 240.4s ♪ 音效 sfx/step_own +t+ 240.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 240.9s ♪ 音效 sfx/pulse +t+ 241.2s ♪ 音效 sfx/echo_1 +t+ 241.3s ♪ 音效 sfx/side_wall_l +t+ 241.3s ♪ 音效 sfx/side_wall_r +t+ 241.3s ♪ 音效 sfx/mat_stone +t+ 241.3s ♪ 音效 sfx/step_turn +t+ 241.3s ♪ 音效 sfx/step_turn +t+ 241.3s ♪ 音效 sfx/step_own +t+ 241.3s 〰 震动:swipe(滑动识别) +t+ 241.8s ♪ 音效 sfx/step_own +t+ 241.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 242.3s ♪ 音效 sfx/pulse +t+ 242.6s ♪ 音效 sfx/side_wall_l +t+ 242.6s ♪ 音效 sfx/side_wall_r +t+ 243.1s ♪ 音效 sfx/echo_3 +t+ 243.2s ♪ 音效 sfx/mat_stone +t+ 243.2s ♪ 音效 sfx/step_turn +t+ 243.2s ♪ 音效 sfx/step_turn +t+ 243.2s ♪ 音效 sfx/step_own +t+ 243.2s 〰 震动:swipe(滑动识别) +t+ 243.7s ♪ 音效 sfx/step_own +t+ 243.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 244.2s ♪ 音效 sfx/pulse +t+ 244.4s ♪ 音效 sfx/echo_1 +t+ 244.5s ♪ 音效 sfx/side_wall_l +t+ 244.5s ♪ 音效 sfx/side_wall_r +t+ 244.6s ♪ 音效 sfx/mat_stone +t+ 244.6s ♪ 音效 sfx/step_turn +t+ 244.6s ♪ 音效 sfx/step_turn +t+ 244.6s ♪ 音效 sfx/step_own +t+ 244.6s 〰 震动:swipe(滑动识别) +t+ 245.1s ♪ 音效 sfx/step_own +t+ 245.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 245.6s ♪ 音效 sfx/pulse +t+ 245.9s ♪ 音效 sfx/side_wall_l +t+ 245.9s ♪ 音效 sfx/side_wall_r +t+ 246.4s ♪ 音效 sfx/echo_3 +t+ 246.5s ♪ 音效 sfx/mat_stone +t+ 246.5s ♪ 音效 sfx/step_turn +t+ 246.5s ♪ 音效 sfx/step_turn +t+ 246.5s ♪ 音效 sfx/step_own +t+ 246.5s 〰 震动:swipe(滑动识别) +t+ 247.0s ♪ 音效 sfx/step_own +t+ 247.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 247.5s ♪ 音效 sfx/pulse +t+ 247.7s ♪ 音效 sfx/echo_1 +t+ 247.8s ♪ 音效 sfx/side_wall_l +t+ 247.8s ♪ 音效 sfx/side_wall_r +t+ 247.8s ♪ 音效 sfx/mat_stone +t+ 247.8s ♪ 音效 sfx/step_turn +t+ 247.8s ♪ 音效 sfx/step_turn +t+ 247.8s ♪ 音效 sfx/step_own +t+ 247.8s 〰 震动:swipe(滑动识别) +t+ 248.3s ♪ 音效 sfx/step_own +t+ 248.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 248.8s ♪ 音效 sfx/pulse +t+ 249.2s ♪ 音效 sfx/side_wall_l +t+ 249.2s ♪ 音效 sfx/side_wall_r +t+ 249.7s ♪ 音效 sfx/echo_3 +t+ 249.8s ♪ 音效 sfx/mat_stone +t+ 249.8s ♪ 音效 sfx/step_turn +t+ 249.8s ♪ 音效 sfx/step_turn +t+ 249.8s ♪ 音效 sfx/step_own +t+ 249.8s 〰 震动:swipe(滑动识别) +t+ 250.3s ♪ 音效 sfx/step_own +t+ 250.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 250.8s ♪ 音效 sfx/pulse +t+ 251.0s ♪ 音效 sfx/echo_1 +t+ 251.1s ♪ 音效 sfx/side_wall_l +t+ 251.1s ♪ 音效 sfx/side_wall_r +t+ 251.1s ♪ 音效 sfx/mat_stone +t+ 251.1s ♪ 音效 sfx/step_turn +t+ 251.1s ♪ 音效 sfx/step_turn +t+ 251.1s ♪ 音效 sfx/step_own +t+ 251.1s 〰 震动:swipe(滑动识别) +t+ 251.6s ♪ 音效 sfx/step_own +t+ 251.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 252.1s ♪ 音效 sfx/pulse +t+ 252.5s ♪ 音效 sfx/side_wall_l +t+ 252.5s ♪ 音效 sfx/side_wall_r +t+ 252.9s ♪ 音效 sfx/echo_3 +t+ 253.1s ♪ 音效 sfx/mat_stone +t+ 253.1s ♪ 音效 sfx/step_turn +t+ 253.1s ♪ 音效 sfx/step_turn +t+ 253.1s ♪ 音效 sfx/step_own +t+ 253.1s 〰 震动:swipe(滑动识别) +t+ 253.6s ♪ 音效 sfx/step_own +t+ 253.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 254.1s ♪ 音效 sfx/pulse +t+ 254.3s ♪ 音效 sfx/echo_1 +t+ 254.4s ♪ 音效 sfx/side_wall_l +t+ 254.4s ♪ 音效 sfx/side_wall_r +t+ 254.4s ♪ 音效 sfx/mat_stone +t+ 254.4s ♪ 音效 sfx/step_turn +t+ 254.4s ♪ 音效 sfx/step_turn +t+ 254.4s ♪ 音效 sfx/step_own +t+ 254.4s 〰 震动:swipe(滑动识别) +t+ 254.9s ♪ 音效 sfx/step_own +t+ 254.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 255.4s ♪ 音效 sfx/pulse +t+ 255.8s ♪ 音效 sfx/side_wall_l +t+ 255.8s ♪ 音效 sfx/side_wall_r +t+ 256.2s ♪ 音效 sfx/echo_3 +t+ 256.4s ♪ 音效 sfx/mat_stone +t+ 256.4s ♪ 音效 sfx/step_turn +t+ 256.4s ♪ 音效 sfx/step_turn +t+ 256.4s ♪ 音效 sfx/step_own +t+ 256.4s 〰 震动:swipe(滑动识别) +t+ 256.9s ♪ 音效 sfx/step_own +t+ 256.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 257.4s ♪ 音效 sfx/pulse +t+ 257.6s ♪ 音效 sfx/echo_1 +t+ 257.7s ♪ 音效 sfx/side_wall_l +t+ 257.7s ♪ 音效 sfx/side_wall_r +t+ 257.7s ♪ 音效 sfx/mat_stone +t+ 257.7s ♪ 音效 sfx/step_turn +t+ 257.7s ♪ 音效 sfx/step_turn +t+ 257.7s ♪ 音效 sfx/step_own +t+ 257.7s 〰 震动:swipe(滑动识别) +t+ 258.2s ♪ 音效 sfx/step_own +t+ 258.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 258.7s ♪ 音效 sfx/pulse +t+ 259.1s ♪ 音效 sfx/side_wall_l +t+ 259.1s ♪ 音效 sfx/side_wall_r +t+ 259.5s ♪ 音效 sfx/echo_3 +t+ 259.6s ♪ 音效 sfx/mat_stone +t+ 259.6s ♪ 音效 sfx/step_turn +t+ 259.6s ♪ 音效 sfx/step_turn +t+ 259.6s ♪ 音效 sfx/step_own +t+ 259.6s 〰 震动:swipe(滑动识别) +t+ 260.1s ♪ 音效 sfx/step_own +t+ 260.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 260.6s ♪ 音效 sfx/pulse +t+ 260.9s ♪ 音效 sfx/echo_1 +t+ 261.0s ♪ 音效 sfx/side_wall_l +t+ 261.0s ♪ 音效 sfx/side_wall_r +t+ 261.0s ♪ 音效 sfx/mat_stone +t+ 261.0s ♪ 音效 sfx/step_turn +t+ 261.0s ♪ 音效 sfx/step_turn +t+ 261.0s ♪ 音效 sfx/step_own +t+ 261.0s 〰 震动:swipe(滑动识别) +t+ 261.5s ♪ 音效 sfx/step_own +t+ 261.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 262.0s ♪ 音效 sfx/pulse +t+ 262.4s ♪ 音效 sfx/side_wall_l +t+ 262.4s ♪ 音效 sfx/side_wall_r +t+ 262.8s ♪ 音效 sfx/echo_3 +t+ 262.9s ♪ 音效 sfx/mat_stone +t+ 262.9s ♪ 音效 sfx/step_turn +t+ 262.9s ♪ 音效 sfx/step_turn +t+ 262.9s ♪ 音效 sfx/step_own +t+ 262.9s 〰 震动:swipe(滑动识别) +t+ 263.4s ♪ 音效 sfx/step_own +t+ 263.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 263.9s ♪ 音效 sfx/pulse +t+ 264.2s ♪ 音效 sfx/echo_1 +t+ 264.3s ♪ 音效 sfx/side_wall_l +t+ 264.3s ♪ 音效 sfx/side_wall_r +t+ 264.3s ♪ 音效 sfx/mat_stone +t+ 264.3s ♪ 音效 sfx/step_turn +t+ 264.3s ♪ 音效 sfx/step_turn +t+ 264.3s ♪ 音效 sfx/step_own +t+ 264.3s 〰 震动:swipe(滑动识别) +t+ 264.8s ♪ 音效 sfx/step_own +t+ 264.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 265.3s ♪ 音效 sfx/pulse +t+ 265.6s ♪ 音效 sfx/side_wall_l +t+ 265.6s ♪ 音效 sfx/side_wall_r +t+ 266.1s ♪ 音效 sfx/echo_3 +t+ 266.2s ♪ 音效 sfx/mat_stone +t+ 266.2s ♪ 音效 sfx/step_turn +t+ 266.2s ♪ 音效 sfx/step_turn +t+ 266.2s ♪ 音效 sfx/step_own +t+ 266.2s 〰 震动:swipe(滑动识别) +t+ 266.7s ♪ 音效 sfx/step_own +t+ 266.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 267.2s ♪ 音效 sfx/pulse +t+ 267.5s ♪ 音效 sfx/echo_1 +t+ 267.6s ♪ 音效 sfx/side_wall_l +t+ 267.6s ♪ 音效 sfx/side_wall_r +t+ 267.6s ♪ 音效 sfx/mat_stone +t+ 267.6s ♪ 音效 sfx/step_turn +t+ 267.6s ♪ 音效 sfx/step_turn +t+ 267.6s ♪ 音效 sfx/step_own +t+ 267.6s 〰 震动:swipe(滑动识别) +t+ 268.1s ♪ 音效 sfx/step_own +t+ 268.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 268.6s ♪ 音效 sfx/pulse +t+ 268.9s ♪ 音效 sfx/side_wall_l +t+ 268.9s ♪ 音效 sfx/side_wall_r +t+ 269.4s ♪ 音效 sfx/echo_3 +t+ 269.5s ♪ 音效 sfx/mat_stone +t+ 269.5s ♪ 音效 sfx/step_turn +t+ 269.5s ♪ 音效 sfx/step_turn +t+ 269.5s ♪ 音效 sfx/step_own +t+ 269.5s 〰 震动:swipe(滑动识别) +t+ 270.0s ♪ 音效 sfx/step_own +t+ 270.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 270.5s ♪ 音效 sfx/pulse +t+ 270.8s ♪ 音效 sfx/echo_1 +t+ 270.9s ♪ 音效 sfx/side_wall_l +t+ 270.9s ♪ 音效 sfx/side_wall_r +t+ 270.9s ♪ 音效 sfx/mat_stone +t+ 270.9s ♪ 音效 sfx/step_turn +t+ 270.9s ♪ 音效 sfx/step_turn +t+ 270.9s ♪ 音效 sfx/step_own +t+ 270.9s 〰 震动:swipe(滑动识别) +t+ 271.4s ♪ 音效 sfx/step_own +t+ 271.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 271.9s ♪ 音效 sfx/pulse +t+ 272.2s ♪ 音效 sfx/side_wall_l +t+ 272.2s ♪ 音效 sfx/side_wall_r +t+ 272.7s ♪ 音效 sfx/echo_3 +t+ 272.8s ♪ 音效 sfx/mat_stone +t+ 272.8s ♪ 音效 sfx/step_turn +t+ 272.8s ♪ 音效 sfx/step_turn +t+ 272.8s ♪ 音效 sfx/step_own +t+ 272.8s 〰 震动:swipe(滑动识别) +t+ 273.3s ♪ 音效 sfx/step_own +t+ 273.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击,继续下潜 + +t+ 273.8s ♪ 音效 sfx/pulse +t+ 274.1s ♪ 音效 sfx/echo_1 +t+ 274.1s ♪ 音效 sfx/side_wall_l +t+ 274.1s ♪ 音效 sfx/side_wall_r +t+ 274.2s ♪ 音效 sfx/mat_stone +t+ 274.2s 【状态】第三层——窟主的地界 教学演练第 9 步,跟着旁白做(BGM=bgm/battle,语速1) + +#### 【玩家动作】脉冲听路,朝楼梯摸过去 + +t+ 274.2s 〰 震动:error(做错了:两连短震) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 274.2s 〰 震动:error(做错了:两连短震) +t+ 274.2s ♪ 音效 sfx/step_turn +t+ 274.2s ♪ 音效 sfx/step_turn +t+ 274.2s ♪ 音效 sfx/step_own +t+ 274.2s 〰 震动:swipe(滑动识别) +t+ 274.7s ♪ 音效 sfx/step_own +t+ 274.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 275.2s ♪ 音效 sfx/pulse +t+ 275.5s ♪ 音效 sfx/side_wall_l +t+ 275.5s ♪ 音效 sfx/side_wall_r +t+ 276.0s ♪ 音效 sfx/echo_3 +t+ 276.1s ♪ 音效 sfx/mat_stone +t+ 276.1s ♪ 音效 sfx/step_turn +t+ 276.1s ♪ 音效 sfx/step_turn +t+ 276.1s ♪ 音效 sfx/step_own +t+ 276.1s 〰 震动:swipe(滑动识别) +t+ 276.6s ♪ 音效 sfx/step_own +t+ 276.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 277.1s ♪ 音效 sfx/pulse +t+ 277.3s ♪ 音效 sfx/echo_1 +t+ 277.4s ♪ 音效 sfx/side_wall_l +t+ 277.4s ♪ 音效 sfx/side_wall_r +t+ 277.5s ♪ 音效 sfx/mat_stone +t+ 277.5s ♪ 音效 sfx/step_turn +t+ 277.5s ♪ 音效 sfx/step_turn +t+ 277.5s ♪ 音效 sfx/step_own +t+ 277.5s 〰 震动:swipe(滑动识别) +t+ 278.0s ♪ 音效 sfx/step_own +t+ 278.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 278.5s ♪ 音效 sfx/pulse +t+ 278.8s ♪ 音效 sfx/side_wall_l +t+ 278.8s ♪ 音效 sfx/side_wall_r +t+ 279.3s ♪ 音效 sfx/echo_3 +t+ 279.4s ♪ 音效 sfx/mat_stone +t+ 279.4s ♪ 音效 sfx/step_turn +t+ 279.4s ♪ 音效 sfx/step_turn +t+ 279.4s ♪ 音效 sfx/step_own +t+ 279.4s 〰 震动:swipe(滑动识别) +t+ 279.9s ♪ 音效 sfx/step_own +t+ 279.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 280.4s ♪ 音效 sfx/pulse +t+ 280.6s ♪ 音效 sfx/echo_1 +t+ 280.7s ♪ 音效 sfx/side_wall_l +t+ 280.7s ♪ 音效 sfx/side_wall_r +t+ 280.8s ♪ 音效 sfx/mat_stone +t+ 280.8s ♪ 音效 sfx/step_turn +t+ 280.8s ♪ 音效 sfx/step_turn +t+ 280.8s ♪ 音效 sfx/step_own +t+ 280.8s 〰 震动:swipe(滑动识别) +t+ 281.3s ♪ 音效 sfx/step_own +t+ 281.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 281.8s ♪ 音效 sfx/pulse +t+ 282.1s ♪ 音效 sfx/side_wall_l +t+ 282.1s ♪ 音效 sfx/side_wall_r +t+ 282.6s ♪ 音效 sfx/echo_3 +t+ 282.7s ♪ 音效 sfx/mat_stone +t+ 282.7s ♪ 音效 sfx/step_turn +t+ 282.7s ♪ 音效 sfx/step_turn +t+ 282.7s ♪ 音效 sfx/step_own +t+ 282.7s 〰 震动:swipe(滑动识别) +t+ 283.2s ♪ 音效 sfx/step_own +t+ 283.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 283.7s ♪ 音效 sfx/pulse +t+ 283.9s ♪ 音效 sfx/echo_1 +t+ 284.0s ♪ 音效 sfx/side_wall_l +t+ 284.0s ♪ 音效 sfx/side_wall_r +t+ 284.0s ♪ 音效 sfx/mat_stone +t+ 284.0s ♪ 音效 sfx/step_turn +t+ 284.0s ♪ 音效 sfx/step_turn +t+ 284.0s ♪ 音效 sfx/step_own +t+ 284.0s 〰 震动:swipe(滑动识别) +t+ 284.5s ♪ 音效 sfx/step_own +t+ 284.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 285.0s ♪ 音效 sfx/pulse +t+ 285.4s ♪ 音效 sfx/side_wall_l +t+ 285.4s ♪ 音效 sfx/side_wall_r +t+ 285.8s ♪ 音效 sfx/echo_3 +t+ 286.0s ♪ 音效 sfx/mat_stone +t+ 286.0s ♪ 音效 sfx/step_turn +t+ 286.0s ♪ 音效 sfx/step_turn +t+ 286.0s ♪ 音效 sfx/step_own +t+ 286.0s 〰 震动:swipe(滑动识别) +t+ 286.5s ♪ 音效 sfx/step_own +t+ 286.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 287.0s ♪ 音效 sfx/pulse +t+ 287.2s ♪ 音效 sfx/echo_1 +t+ 287.3s ♪ 音效 sfx/side_wall_l +t+ 287.3s ♪ 音效 sfx/side_wall_r +t+ 287.3s ♪ 音效 sfx/mat_stone +t+ 287.3s ♪ 音效 sfx/step_turn +t+ 287.3s ♪ 音效 sfx/step_turn +t+ 287.3s ♪ 音效 sfx/step_own +t+ 287.3s 〰 震动:swipe(滑动识别) +t+ 287.8s ♪ 音效 sfx/step_own +t+ 287.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 288.3s ♪ 音效 sfx/pulse +t+ 288.7s ♪ 音效 sfx/side_wall_l +t+ 288.7s ♪ 音效 sfx/side_wall_r +t+ 289.1s ♪ 音效 sfx/echo_3 +t+ 289.3s ♪ 音效 sfx/mat_stone +t+ 289.3s ♪ 音效 sfx/step_turn +t+ 289.3s ♪ 音效 sfx/step_turn +t+ 289.3s ♪ 音效 sfx/step_own +t+ 289.3s 〰 震动:swipe(滑动识别) +t+ 289.8s ♪ 音效 sfx/step_own +t+ 289.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 290.3s ♪ 音效 sfx/pulse +t+ 290.5s ♪ 音效 sfx/echo_1 +t+ 290.6s ♪ 音效 sfx/side_wall_l +t+ 290.6s ♪ 音效 sfx/side_wall_r +t+ 290.6s ♪ 音效 sfx/mat_stone +t+ 290.6s ♪ 音效 sfx/step_turn +t+ 290.6s ♪ 音效 sfx/step_turn +t+ 290.6s ♪ 音效 sfx/step_own +t+ 290.6s 〰 震动:swipe(滑动识别) +t+ 291.1s ♪ 音效 sfx/step_own +t+ 291.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 291.6s ♪ 音效 sfx/pulse +t+ 292.0s ♪ 音效 sfx/side_wall_l +t+ 292.0s ♪ 音效 sfx/side_wall_r +t+ 292.4s ♪ 音效 sfx/echo_3 +t+ 292.5s ♪ 音效 sfx/mat_stone +t+ 292.5s ♪ 音效 sfx/step_turn +t+ 292.5s ♪ 音效 sfx/step_turn +t+ 292.5s ♪ 音效 sfx/step_own +t+ 292.5s 〰 震动:swipe(滑动识别) +t+ 293.0s ♪ 音效 sfx/step_own +t+ 293.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 293.5s ♪ 音效 sfx/pulse +t+ 293.8s ♪ 音效 sfx/echo_1 +t+ 293.9s ♪ 音效 sfx/side_wall_l +t+ 293.9s ♪ 音效 sfx/side_wall_r +t+ 293.9s ♪ 音效 sfx/mat_stone +t+ 293.9s ♪ 音效 sfx/step_turn +t+ 293.9s ♪ 音效 sfx/step_turn +t+ 293.9s ♪ 音效 sfx/step_own +t+ 293.9s 〰 震动:swipe(滑动识别) +t+ 294.4s ♪ 音效 sfx/step_own +t+ 294.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 294.9s ♪ 音效 sfx/pulse +t+ 295.3s ♪ 音效 sfx/side_wall_l +t+ 295.3s ♪ 音效 sfx/side_wall_r +t+ 295.7s ♪ 音效 sfx/echo_3 +t+ 295.8s ♪ 音效 sfx/mat_stone +t+ 295.8s ♪ 音效 sfx/step_turn +t+ 295.8s ♪ 音效 sfx/step_turn +t+ 295.8s ♪ 音效 sfx/step_own +t+ 295.8s 〰 震动:swipe(滑动识别) +t+ 296.3s ♪ 音效 sfx/step_own +t+ 296.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 296.8s ♪ 音效 sfx/pulse +t+ 297.1s ♪ 音效 sfx/echo_1 +t+ 297.2s ♪ 音效 sfx/side_wall_l +t+ 297.2s ♪ 音效 sfx/side_wall_r +t+ 297.2s ♪ 音效 sfx/mat_stone +t+ 297.2s ♪ 音效 sfx/step_turn +t+ 297.2s ♪ 音效 sfx/step_turn +t+ 297.2s ♪ 音效 sfx/step_own +t+ 297.2s 〰 震动:swipe(滑动识别) +t+ 297.7s ♪ 音效 sfx/step_own +t+ 297.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 298.2s ♪ 音效 sfx/pulse +t+ 298.6s ♪ 音效 sfx/side_wall_l +t+ 298.6s ♪ 音效 sfx/side_wall_r +t+ 299.0s ♪ 音效 sfx/echo_3 +t+ 299.1s ♪ 音效 sfx/mat_stone +t+ 299.1s ♪ 音效 sfx/step_turn +t+ 299.1s ♪ 音效 sfx/step_turn +t+ 299.1s ♪ 音效 sfx/step_own +t+ 299.1s 〰 震动:swipe(滑动识别) +t+ 299.6s ♪ 音效 sfx/step_own +t+ 299.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 300.1s ♪ 音效 sfx/pulse +t+ 300.4s ♪ 音效 sfx/echo_1 +t+ 300.5s ♪ 音效 sfx/side_wall_l +t+ 300.5s ♪ 音效 sfx/side_wall_r +t+ 300.5s ♪ 音效 sfx/mat_stone +t+ 300.5s ♪ 音效 sfx/step_turn +t+ 300.5s ♪ 音效 sfx/step_turn +t+ 300.5s ♪ 音效 sfx/step_own +t+ 300.5s 〰 震动:swipe(滑动识别) +t+ 301.0s ♪ 音效 sfx/step_own +t+ 301.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 301.5s ♪ 音效 sfx/pulse +t+ 301.8s ♪ 音效 sfx/side_wall_l +t+ 301.8s ♪ 音效 sfx/side_wall_r +t+ 302.3s ♪ 音效 sfx/echo_3 +t+ 302.4s ♪ 音效 sfx/mat_stone +t+ 302.4s ♪ 音效 sfx/step_turn +t+ 302.4s ♪ 音效 sfx/step_turn +t+ 302.4s ♪ 音效 sfx/step_own +t+ 302.4s 〰 震动:swipe(滑动识别) +t+ 302.9s ♪ 音效 sfx/step_own +t+ 302.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 303.4s ♪ 音效 sfx/pulse +t+ 303.7s ♪ 音效 sfx/echo_1 +t+ 303.8s ♪ 音效 sfx/side_wall_l +t+ 303.8s ♪ 音效 sfx/side_wall_r +t+ 303.8s ♪ 音效 sfx/mat_stone +t+ 303.8s ♪ 音效 sfx/step_turn +t+ 303.8s ♪ 音效 sfx/step_turn +t+ 303.8s ♪ 音效 sfx/step_own +t+ 303.8s 〰 震动:swipe(滑动识别) +t+ 304.3s ♪ 音效 sfx/step_own +t+ 304.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 304.8s ♪ 音效 sfx/pulse +t+ 305.1s ♪ 音效 sfx/side_wall_l +t+ 305.1s ♪ 音效 sfx/side_wall_r +t+ 305.6s ♪ 音效 sfx/echo_3 +t+ 305.7s ♪ 音效 sfx/mat_stone +t+ 305.7s ♪ 音效 sfx/step_turn +t+ 305.7s ♪ 音效 sfx/step_turn +t+ 305.7s ♪ 音效 sfx/step_own +t+ 305.7s 〰 震动:swipe(滑动识别) +t+ 306.2s ♪ 音效 sfx/step_own +t+ 306.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 306.7s ♪ 音效 sfx/pulse +t+ 306.9s ♪ 音效 sfx/echo_1 +t+ 307.1s ♪ 音效 sfx/side_wall_l +t+ 307.1s ♪ 音效 sfx/side_wall_r +t+ 307.1s ♪ 音效 sfx/mat_stone +t+ 307.1s ♪ 音效 sfx/step_turn +t+ 307.1s ♪ 音效 sfx/step_turn +t+ 307.1s ♪ 音效 sfx/step_own +t+ 307.1s 〰 震动:swipe(滑动识别) +t+ 307.6s ♪ 音效 sfx/step_own +t+ 307.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 308.1s ♪ 音效 sfx/pulse +t+ 308.4s ♪ 音效 sfx/side_wall_l +t+ 308.4s ♪ 音效 sfx/side_wall_r +t+ 308.9s ♪ 音效 sfx/echo_3 +t+ 309.0s ♪ 音效 sfx/mat_stone +t+ 309.0s ♪ 音效 sfx/step_turn +t+ 309.0s ♪ 音效 sfx/step_turn +t+ 309.0s ♪ 音效 sfx/step_own +t+ 309.0s 〰 震动:swipe(滑动识别) +t+ 309.5s ♪ 音效 sfx/step_own +t+ 309.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 310.0s ♪ 音效 sfx/pulse +t+ 310.2s ♪ 音效 sfx/echo_1 +t+ 310.3s ♪ 音效 sfx/side_wall_l +t+ 310.3s ♪ 音效 sfx/side_wall_r +t+ 310.4s ♪ 音效 sfx/mat_stone +t+ 310.4s ♪ 音效 sfx/step_turn +t+ 310.4s ♪ 音效 sfx/step_turn +t+ 310.4s ♪ 音效 sfx/step_own +t+ 310.4s 〰 震动:swipe(滑动识别) +t+ 310.9s ♪ 音效 sfx/step_own +t+ 310.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 311.4s ♪ 音效 sfx/pulse +t+ 311.7s ♪ 音效 sfx/side_wall_l +t+ 311.7s ♪ 音效 sfx/side_wall_r +t+ 312.2s ♪ 音效 sfx/echo_3 +t+ 312.3s ♪ 音效 sfx/mat_stone +t+ 312.3s ♪ 音效 sfx/step_turn +t+ 312.3s ♪ 音效 sfx/step_turn +t+ 312.3s ♪ 音效 sfx/step_own +t+ 312.3s 〰 震动:swipe(滑动识别) +t+ 312.8s ♪ 音效 sfx/step_own +t+ 312.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 313.3s ♪ 音效 sfx/pulse +t+ 313.5s ♪ 音效 sfx/echo_1 +t+ 313.6s ♪ 音效 sfx/side_wall_l +t+ 313.6s ♪ 音效 sfx/side_wall_r +t+ 313.6s ♪ 音效 sfx/mat_stone +t+ 313.6s ♪ 音效 sfx/step_turn +t+ 313.6s ♪ 音效 sfx/step_turn +t+ 313.6s ♪ 音效 sfx/step_own +t+ 313.6s 〰 震动:swipe(滑动识别) +t+ 314.1s ♪ 音效 sfx/step_own +t+ 314.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 314.6s ♪ 音效 sfx/pulse +t+ 315.0s ♪ 音效 sfx/side_wall_l +t+ 315.0s ♪ 音效 sfx/side_wall_r +t+ 315.4s ♪ 音效 sfx/echo_3 +t+ 315.6s ♪ 音效 sfx/mat_stone +t+ 315.6s ♪ 音效 sfx/step_turn +t+ 315.6s ♪ 音效 sfx/step_turn +t+ 315.6s ♪ 音效 sfx/step_own +t+ 315.6s 〰 震动:swipe(滑动识别) +t+ 316.1s ♪ 音效 sfx/step_own +t+ 316.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 316.6s ♪ 音效 sfx/pulse +t+ 316.8s ♪ 音效 sfx/echo_1 +t+ 316.9s ♪ 音效 sfx/side_wall_l +t+ 316.9s ♪ 音效 sfx/side_wall_r +t+ 316.9s ♪ 音效 sfx/mat_stone +t+ 316.9s ♪ 音效 sfx/step_turn +t+ 316.9s ♪ 音效 sfx/step_turn +t+ 316.9s ♪ 音效 sfx/step_own +t+ 316.9s 〰 震动:swipe(滑动识别) +t+ 317.4s ♪ 音效 sfx/step_own +t+ 317.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 317.9s ♪ 音效 sfx/pulse +t+ 318.3s ♪ 音效 sfx/side_wall_l +t+ 318.3s ♪ 音效 sfx/side_wall_r +t+ 318.7s ♪ 音效 sfx/echo_3 +t+ 318.9s ♪ 音效 sfx/mat_stone +t+ 318.9s ♪ 音效 sfx/step_turn +t+ 318.9s ♪ 音效 sfx/step_turn +t+ 318.9s ♪ 音效 sfx/step_own +t+ 318.9s 〰 震动:swipe(滑动识别) +t+ 319.4s ♪ 音效 sfx/step_own +t+ 319.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 319.9s ♪ 音效 sfx/pulse +t+ 320.1s ♪ 音效 sfx/echo_1 +t+ 320.2s ♪ 音效 sfx/side_wall_l +t+ 320.2s ♪ 音效 sfx/side_wall_r +t+ 320.2s ♪ 音效 sfx/mat_stone +t+ 320.2s ♪ 音效 sfx/step_turn +t+ 320.2s ♪ 音效 sfx/step_turn +t+ 320.2s ♪ 音效 sfx/step_own +t+ 320.2s 〰 震动:swipe(滑动识别) +t+ 320.7s ♪ 音效 sfx/step_own +t+ 320.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 321.2s ♪ 音效 sfx/pulse +t+ 321.6s ♪ 音效 sfx/side_wall_l +t+ 321.6s ♪ 音效 sfx/side_wall_r +t+ 322.0s ♪ 音效 sfx/echo_3 +t+ 322.1s ♪ 音效 sfx/mat_stone +t+ 322.1s ♪ 音效 sfx/step_turn +t+ 322.1s ♪ 音效 sfx/step_turn +t+ 322.1s ♪ 音效 sfx/step_own +t+ 322.1s 〰 震动:swipe(滑动识别) +t+ 322.6s ♪ 音效 sfx/step_own +t+ 322.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 323.1s ♪ 音效 sfx/pulse +t+ 323.4s ♪ 音效 sfx/echo_1 +t+ 323.5s ♪ 音效 sfx/side_wall_l +t+ 323.5s ♪ 音效 sfx/side_wall_r +t+ 323.5s ♪ 音效 sfx/mat_stone +t+ 323.5s ♪ 音效 sfx/step_turn +t+ 323.5s ♪ 音效 sfx/step_turn +t+ 323.5s ♪ 音效 sfx/step_own +t+ 323.5s 〰 震动:swipe(滑动识别) +t+ 324.0s ♪ 音效 sfx/step_own +t+ 324.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 324.5s ♪ 音效 sfx/pulse +t+ 324.9s ♪ 音效 sfx/side_wall_l +t+ 324.9s ♪ 音效 sfx/side_wall_r +t+ 325.3s ♪ 音效 sfx/echo_3 +t+ 325.4s ♪ 音效 sfx/mat_stone +t+ 325.4s ♪ 音效 sfx/step_turn +t+ 325.4s ♪ 音效 sfx/step_turn +t+ 325.4s ♪ 音效 sfx/step_own +t+ 325.4s 〰 震动:swipe(滑动识别) +t+ 325.9s ♪ 音效 sfx/step_own +t+ 325.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 326.4s ♪ 音效 sfx/pulse +t+ 326.7s ♪ 音效 sfx/echo_1 +t+ 326.8s ♪ 音效 sfx/side_wall_l +t+ 326.8s ♪ 音效 sfx/side_wall_r +t+ 326.8s ♪ 音效 sfx/mat_stone +t+ 326.8s ♪ 音效 sfx/step_turn +t+ 326.8s ♪ 音效 sfx/step_turn +t+ 326.8s ♪ 音效 sfx/step_own +t+ 326.8s 〰 震动:swipe(滑动识别) +t+ 327.3s ♪ 音效 sfx/step_own +t+ 327.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 327.8s ♪ 音效 sfx/pulse +t+ 328.2s ♪ 音效 sfx/side_wall_l +t+ 328.2s ♪ 音效 sfx/side_wall_r +t+ 328.6s ♪ 音效 sfx/echo_3 +t+ 328.7s ♪ 音效 sfx/mat_stone +t+ 328.7s ♪ 音效 sfx/step_turn +t+ 328.7s ♪ 音效 sfx/step_turn +t+ 328.7s ♪ 音效 sfx/step_own +t+ 328.7s 〰 震动:swipe(滑动识别) +t+ 329.2s ♪ 音效 sfx/step_own +t+ 329.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 329.7s ♪ 音效 sfx/pulse +t+ 330.0s ♪ 音效 sfx/echo_1 +t+ 330.1s ♪ 音效 sfx/side_wall_l +t+ 330.1s ♪ 音效 sfx/side_wall_r +t+ 330.1s ♪ 音效 sfx/mat_stone +t+ 330.1s ♪ 音效 sfx/step_turn +t+ 330.1s ♪ 音效 sfx/step_turn +t+ 330.1s ♪ 音效 sfx/step_own +t+ 330.1s 〰 震动:swipe(滑动识别) +t+ 330.6s ♪ 音效 sfx/step_own +t+ 330.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 331.1s ♪ 音效 sfx/pulse +t+ 331.4s ♪ 音效 sfx/side_wall_l +t+ 331.4s ♪ 音效 sfx/side_wall_r +t+ 331.9s ♪ 音效 sfx/echo_3 +t+ 332.0s ♪ 音效 sfx/mat_stone +t+ 332.0s ♪ 音效 sfx/step_turn +t+ 332.0s ♪ 音效 sfx/step_turn +t+ 332.0s ♪ 音效 sfx/step_own +t+ 332.0s 〰 震动:swipe(滑动识别) +t+ 332.5s ♪ 音效 sfx/step_own +t+ 332.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 333.0s ♪ 音效 sfx/pulse +t+ 333.3s ♪ 音效 sfx/echo_1 +t+ 333.4s ♪ 音效 sfx/side_wall_l +t+ 333.4s ♪ 音效 sfx/side_wall_r +t+ 333.4s ♪ 音效 sfx/mat_stone +t+ 333.4s ♪ 音效 sfx/step_turn +t+ 333.4s ♪ 音效 sfx/step_turn +t+ 333.4s ♪ 音效 sfx/step_own +t+ 333.4s 〰 震动:swipe(滑动识别) +t+ 333.9s ♪ 音效 sfx/step_own +t+ 333.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 334.4s ♪ 音效 sfx/pulse +t+ 334.7s ♪ 音效 sfx/side_wall_l +t+ 334.7s ♪ 音效 sfx/side_wall_r +t+ 335.2s ♪ 音效 sfx/echo_3 +t+ 335.3s ♪ 音效 sfx/mat_stone +t+ 335.3s ♪ 音效 sfx/step_turn +t+ 335.3s ♪ 音效 sfx/step_turn +t+ 335.3s ♪ 音效 sfx/step_own +t+ 335.3s 〰 震动:swipe(滑动识别) +t+ 335.8s ♪ 音效 sfx/step_own +t+ 335.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 336.3s ♪ 音效 sfx/pulse +t+ 336.6s ♪ 音效 sfx/echo_1 +t+ 336.7s ♪ 音效 sfx/side_wall_l +t+ 336.7s ♪ 音效 sfx/side_wall_r +t+ 336.7s ♪ 音效 sfx/mat_stone +t+ 336.7s ♪ 音效 sfx/step_turn +t+ 336.7s ♪ 音效 sfx/step_turn +t+ 336.7s ♪ 音效 sfx/step_own +t+ 336.7s 〰 震动:swipe(滑动识别) +t+ 337.2s ♪ 音效 sfx/step_own +t+ 337.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 337.7s ♪ 音效 sfx/pulse +t+ 338.0s ♪ 音效 sfx/side_wall_l +t+ 338.0s ♪ 音效 sfx/side_wall_r +t+ 338.5s ♪ 音效 sfx/echo_3 +t+ 338.6s ♪ 音效 sfx/mat_stone +t+ 338.6s ♪ 音效 sfx/step_turn +t+ 338.6s ♪ 音效 sfx/step_turn +t+ 338.6s ♪ 音效 sfx/step_own +t+ 338.6s 〰 震动:swipe(滑动识别) +t+ 339.1s ♪ 音效 sfx/step_own +t+ 339.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 339.6s ♪ 音效 sfx/pulse +t+ 339.9s ♪ 音效 sfx/echo_1 +t+ 339.9s ♪ 音效 sfx/side_wall_l +t+ 339.9s ♪ 音效 sfx/side_wall_r +t+ 340.0s ♪ 音效 sfx/mat_stone +t+ 340.0s ♪ 音效 sfx/step_turn +t+ 340.0s ♪ 音效 sfx/step_turn +t+ 340.0s ♪ 音效 sfx/step_own +t+ 340.0s 〰 震动:swipe(滑动识别) +t+ 340.5s ♪ 音效 sfx/step_own +t+ 340.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 341.0s ♪ 音效 sfx/pulse +t+ 341.3s ♪ 音效 sfx/side_wall_l +t+ 341.3s ♪ 音效 sfx/side_wall_r +t+ 341.8s ♪ 音效 sfx/echo_3 +t+ 341.9s ♪ 音效 sfx/mat_stone +t+ 341.9s ♪ 音效 sfx/step_turn +t+ 341.9s ♪ 音效 sfx/step_turn +t+ 341.9s ♪ 音效 sfx/step_own +t+ 341.9s 〰 震动:swipe(滑动识别) +t+ 342.4s ♪ 音效 sfx/step_own +t+ 342.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 342.9s ♪ 音效 sfx/pulse +t+ 343.1s ♪ 音效 sfx/echo_1 +t+ 343.2s ♪ 音效 sfx/side_wall_l +t+ 343.2s ♪ 音效 sfx/side_wall_r +t+ 343.3s ♪ 音效 sfx/mat_stone +t+ 343.3s ♪ 音效 sfx/step_turn +t+ 343.3s ♪ 音效 sfx/step_turn +t+ 343.3s ♪ 音效 sfx/step_own +t+ 343.3s 〰 震动:swipe(滑动识别) +t+ 343.8s ♪ 音效 sfx/step_own +t+ 343.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 344.3s ♪ 音效 sfx/pulse +t+ 344.6s ♪ 音效 sfx/side_wall_l +t+ 344.6s ♪ 音效 sfx/side_wall_r +t+ 345.1s ♪ 音效 sfx/echo_3 +t+ 345.2s ♪ 音效 sfx/mat_stone +t+ 345.2s ♪ 音效 sfx/step_turn +t+ 345.2s ♪ 音效 sfx/step_turn +t+ 345.2s ♪ 音效 sfx/step_own +t+ 345.2s 〰 震动:swipe(滑动识别) +t+ 345.7s ♪ 音效 sfx/step_own +t+ 345.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 346.2s ♪ 音效 sfx/pulse +t+ 346.4s ♪ 音效 sfx/echo_1 +t+ 346.5s ♪ 音效 sfx/side_wall_l +t+ 346.5s ♪ 音效 sfx/side_wall_r +t+ 346.6s ♪ 音效 sfx/mat_stone +t+ 346.6s ♪ 音效 sfx/step_turn +t+ 346.6s ♪ 音效 sfx/step_turn +t+ 346.6s ♪ 音效 sfx/step_own +t+ 346.6s 〰 震动:swipe(滑动识别) +t+ 347.1s ♪ 音效 sfx/step_own +t+ 347.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 347.6s ♪ 音效 sfx/pulse +t+ 347.9s ♪ 音效 sfx/side_wall_l +t+ 347.9s ♪ 音效 sfx/side_wall_r +t+ 348.4s ♪ 音效 sfx/echo_3 +t+ 348.5s ♪ 音效 sfx/mat_stone +t+ 348.5s ♪ 音效 sfx/step_turn +t+ 348.5s ♪ 音效 sfx/step_turn +t+ 348.5s ♪ 音效 sfx/step_own +t+ 348.5s 〰 震动:swipe(滑动识别) +t+ 349.0s ♪ 音效 sfx/step_own +t+ 349.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 349.5s ♪ 音效 sfx/pulse +t+ 349.7s ♪ 音效 sfx/echo_1 +t+ 349.8s ♪ 音效 sfx/side_wall_l +t+ 349.8s ♪ 音效 sfx/side_wall_r +t+ 349.8s ♪ 音效 sfx/mat_stone +t+ 349.8s ♪ 音效 sfx/step_turn +t+ 349.8s ♪ 音效 sfx/step_turn +t+ 349.8s ♪ 音效 sfx/step_own +t+ 349.8s 〰 震动:swipe(滑动识别) +t+ 350.3s ♪ 音效 sfx/step_own +t+ 350.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 350.8s ♪ 音效 sfx/pulse +t+ 351.2s ♪ 音效 sfx/side_wall_l +t+ 351.2s ♪ 音效 sfx/side_wall_r +t+ 351.6s ♪ 音效 sfx/echo_3 +t+ 351.8s ♪ 音效 sfx/mat_stone +t+ 351.8s ♪ 音效 sfx/step_turn +t+ 351.8s ♪ 音效 sfx/step_turn +t+ 351.8s ♪ 音效 sfx/step_own +t+ 351.8s 〰 震动:swipe(滑动识别) +t+ 352.3s ♪ 音效 sfx/step_own +t+ 352.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 352.8s ♪ 音效 sfx/pulse +t+ 353.0s ♪ 音效 sfx/echo_1 +t+ 353.1s ♪ 音效 sfx/side_wall_l +t+ 353.1s ♪ 音效 sfx/side_wall_r +t+ 353.1s ♪ 音效 sfx/mat_stone +t+ 353.1s ♪ 音效 sfx/step_turn +t+ 353.1s ♪ 音效 sfx/step_turn +t+ 353.1s ♪ 音效 sfx/step_own +t+ 353.1s 〰 震动:swipe(滑动识别) +t+ 353.6s ♪ 音效 sfx/step_own +t+ 353.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 354.1s ♪ 音效 sfx/pulse +t+ 354.5s ♪ 音效 sfx/side_wall_l +t+ 354.5s ♪ 音效 sfx/side_wall_r +t+ 354.9s ♪ 音效 sfx/echo_3 +t+ 355.1s ♪ 音效 sfx/mat_stone +t+ 355.1s ♪ 音效 sfx/step_turn +t+ 355.1s ♪ 音效 sfx/step_turn +t+ 355.1s ♪ 音效 sfx/step_own +t+ 355.1s 〰 震动:swipe(滑动识别) +t+ 355.6s ♪ 音效 sfx/step_own +t+ 355.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 356.1s ♪ 音效 sfx/pulse +t+ 356.3s ♪ 音效 sfx/echo_1 +t+ 356.4s ♪ 音效 sfx/side_wall_l +t+ 356.4s ♪ 音效 sfx/side_wall_r +t+ 356.4s ♪ 音效 sfx/mat_stone +t+ 356.4s ♪ 音效 sfx/step_turn +t+ 356.4s ♪ 音效 sfx/step_turn +t+ 356.4s ♪ 音效 sfx/step_own +t+ 356.4s 〰 震动:swipe(滑动识别) +t+ 356.9s ♪ 音效 sfx/step_own +t+ 356.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 357.4s ♪ 音效 sfx/pulse +t+ 357.8s ♪ 音效 sfx/side_wall_l +t+ 357.8s ♪ 音效 sfx/side_wall_r +t+ 358.2s ♪ 音效 sfx/echo_3 +t+ 358.3s ♪ 音效 sfx/mat_stone +t+ 358.3s ♪ 音效 sfx/step_turn +t+ 358.3s ♪ 音效 sfx/step_turn +t+ 358.3s ♪ 音效 sfx/step_own +t+ 358.3s 〰 震动:swipe(滑动识别) +t+ 358.8s ♪ 音效 sfx/step_own +t+ 358.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 359.3s ♪ 音效 sfx/pulse +t+ 359.6s ♪ 音效 sfx/echo_1 +t+ 359.7s ♪ 音效 sfx/side_wall_l +t+ 359.7s ♪ 音效 sfx/side_wall_r +t+ 359.7s ♪ 音效 sfx/mat_stone +t+ 359.7s ♪ 音效 sfx/step_turn +t+ 359.7s ♪ 音效 sfx/step_turn +t+ 359.7s ♪ 音效 sfx/step_own +t+ 359.7s 〰 震动:swipe(滑动识别) +t+ 360.2s ♪ 音效 sfx/step_own +t+ 360.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 360.7s ♪ 音效 sfx/pulse +t+ 361.1s ♪ 音效 sfx/side_wall_l +t+ 361.1s ♪ 音效 sfx/side_wall_r +t+ 361.5s ♪ 音效 sfx/echo_3 +t+ 361.6s ♪ 音效 sfx/mat_stone +t+ 361.6s ♪ 音效 sfx/step_turn +t+ 361.6s ♪ 音效 sfx/step_turn +t+ 361.6s ♪ 音效 sfx/step_own +t+ 361.6s 〰 震动:swipe(滑动识别) +t+ 362.1s ♪ 音效 sfx/step_own +t+ 362.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 362.6s ♪ 音效 sfx/pulse +t+ 362.9s ♪ 音效 sfx/echo_1 +t+ 363.0s ♪ 音效 sfx/side_wall_l +t+ 363.0s ♪ 音效 sfx/side_wall_r +t+ 363.0s ♪ 音效 sfx/mat_stone +t+ 363.0s ♪ 音效 sfx/step_turn +t+ 363.0s ♪ 音效 sfx/step_turn +t+ 363.0s ♪ 音效 sfx/step_own +t+ 363.0s 〰 震动:swipe(滑动识别) +t+ 363.5s ♪ 音效 sfx/step_own +t+ 363.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 364.0s ♪ 音效 sfx/pulse +t+ 364.4s ♪ 音效 sfx/side_wall_l +t+ 364.4s ♪ 音效 sfx/side_wall_r +t+ 364.8s ♪ 音效 sfx/echo_3 +t+ 364.9s ♪ 音效 sfx/mat_stone +t+ 364.9s ♪ 音效 sfx/step_turn +t+ 364.9s ♪ 音效 sfx/step_turn +t+ 364.9s ♪ 音效 sfx/step_own +t+ 364.9s 〰 震动:swipe(滑动识别) +t+ 365.4s ♪ 音效 sfx/step_own +t+ 365.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 365.9s ♪ 音效 sfx/pulse +t+ 366.2s ♪ 音效 sfx/echo_1 +t+ 366.3s ♪ 音效 sfx/side_wall_l +t+ 366.3s ♪ 音效 sfx/side_wall_r +t+ 366.3s ♪ 音效 sfx/mat_stone +t+ 366.3s ♪ 音效 sfx/step_turn +t+ 366.3s ♪ 音效 sfx/step_turn +t+ 366.3s ♪ 音效 sfx/step_own +t+ 366.3s 〰 震动:swipe(滑动识别) +t+ 366.8s ♪ 音效 sfx/step_own +t+ 366.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 367.3s ♪ 音效 sfx/pulse +t+ 367.6s ♪ 音效 sfx/side_wall_l +t+ 367.6s ♪ 音效 sfx/side_wall_r +t+ 368.1s ♪ 音效 sfx/echo_3 +t+ 368.2s ♪ 音效 sfx/mat_stone +t+ 368.2s ♪ 音效 sfx/step_turn +t+ 368.2s ♪ 音效 sfx/step_turn +t+ 368.2s ♪ 音效 sfx/step_own +t+ 368.2s 〰 震动:swipe(滑动识别) +t+ 368.7s ♪ 音效 sfx/step_own +t+ 368.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 369.2s ♪ 音效 sfx/pulse +t+ 369.5s ♪ 音效 sfx/echo_1 +t+ 369.6s ♪ 音效 sfx/side_wall_l +t+ 369.6s ♪ 音效 sfx/side_wall_r +t+ 369.6s ♪ 音效 sfx/mat_stone +t+ 369.6s ♪ 音效 sfx/step_turn +t+ 369.6s ♪ 音效 sfx/step_turn +t+ 369.6s ♪ 音效 sfx/step_own +t+ 369.6s 〰 震动:swipe(滑动识别) +t+ 370.1s ♪ 音效 sfx/step_own +t+ 370.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 370.6s ♪ 音效 sfx/pulse +t+ 370.9s ♪ 音效 sfx/side_wall_l +t+ 370.9s ♪ 音效 sfx/side_wall_r +t+ 371.4s ♪ 音效 sfx/echo_3 +t+ 371.5s ♪ 音效 sfx/mat_stone +t+ 371.5s ♪ 音效 sfx/step_turn +t+ 371.5s ♪ 音效 sfx/step_turn +t+ 371.5s ♪ 音效 sfx/step_own +t+ 371.5s 〰 震动:swipe(滑动识别) +t+ 372.0s ♪ 音效 sfx/step_own +t+ 372.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 372.5s ♪ 音效 sfx/pulse +t+ 372.8s ♪ 音效 sfx/echo_1 +t+ 372.9s ♪ 音效 sfx/side_wall_l +t+ 372.9s ♪ 音效 sfx/side_wall_r +t+ 372.9s ♪ 音效 sfx/mat_stone +t+ 372.9s ♪ 音效 sfx/step_turn +t+ 372.9s ♪ 音效 sfx/step_turn +t+ 372.9s ♪ 音效 sfx/step_own +t+ 372.9s 〰 震动:swipe(滑动识别) +t+ 373.4s ♪ 音效 sfx/step_own +t+ 373.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 373.9s ♪ 音效 sfx/pulse +t+ 374.2s ♪ 音效 sfx/side_wall_l +t+ 374.2s ♪ 音效 sfx/side_wall_r +t+ 374.7s ♪ 音效 sfx/echo_3 +t+ 374.8s ♪ 音效 sfx/mat_stone +t+ 374.8s ♪ 音效 sfx/step_turn +t+ 374.8s ♪ 音效 sfx/step_turn +t+ 374.8s ♪ 音效 sfx/step_own +t+ 374.8s 〰 震动:swipe(滑动识别) +t+ 375.3s ♪ 音效 sfx/step_own +t+ 375.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 375.8s ♪ 音效 sfx/pulse +t+ 376.0s ♪ 音效 sfx/echo_1 +t+ 376.1s ♪ 音效 sfx/side_wall_l +t+ 376.1s ♪ 音效 sfx/side_wall_r +t+ 376.2s ♪ 音效 sfx/mat_stone +t+ 376.2s ♪ 音效 sfx/step_turn +t+ 376.2s ♪ 音效 sfx/step_turn +t+ 376.2s ♪ 音效 sfx/step_own +t+ 376.2s 〰 震动:swipe(滑动识别) +t+ 376.7s ♪ 音效 sfx/step_own +t+ 376.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 377.2s ♪ 音效 sfx/pulse +t+ 377.5s ♪ 音效 sfx/side_wall_l +t+ 377.5s ♪ 音效 sfx/side_wall_r +t+ 378.0s ♪ 音效 sfx/echo_3 +t+ 378.1s ♪ 音效 sfx/mat_stone +t+ 378.1s ♪ 音效 sfx/step_turn +t+ 378.1s ♪ 音效 sfx/step_turn +t+ 378.1s ♪ 音效 sfx/step_own +t+ 378.1s 〰 震动:swipe(滑动识别) +t+ 378.6s ♪ 音效 sfx/step_own +t+ 378.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 379.1s ♪ 音效 sfx/pulse +t+ 379.3s ♪ 音效 sfx/echo_1 +t+ 379.4s ♪ 音效 sfx/side_wall_l +t+ 379.4s ♪ 音效 sfx/side_wall_r +t+ 379.4s ♪ 音效 sfx/mat_stone +t+ 379.4s ♪ 音效 sfx/step_turn +t+ 379.4s ♪ 音效 sfx/step_turn +t+ 379.4s ♪ 音效 sfx/step_own +t+ 379.4s 〰 震动:swipe(滑动识别) +t+ 379.9s ♪ 音效 sfx/step_own +t+ 379.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 380.4s ♪ 音效 sfx/pulse +t+ 380.8s ♪ 音效 sfx/side_wall_l +t+ 380.8s ♪ 音效 sfx/side_wall_r +t+ 381.3s ♪ 音效 sfx/echo_3 +t+ 381.4s ♪ 音效 sfx/mat_stone +t+ 381.4s ♪ 音效 sfx/step_turn +t+ 381.4s ♪ 音效 sfx/step_turn +t+ 381.4s ♪ 音效 sfx/step_own +t+ 381.4s 〰 震动:swipe(滑动识别) +t+ 381.9s ♪ 音效 sfx/step_own +t+ 381.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 382.4s ♪ 音效 sfx/pulse +t+ 382.6s ♪ 音效 sfx/echo_1 +t+ 382.7s ♪ 音效 sfx/side_wall_l +t+ 382.7s ♪ 音效 sfx/side_wall_r +t+ 382.7s ♪ 音效 sfx/mat_stone +t+ 382.7s ♪ 音效 sfx/step_turn +t+ 382.7s ♪ 音效 sfx/step_turn +t+ 382.7s ♪ 音效 sfx/step_own +t+ 382.7s 〰 震动:swipe(滑动识别) +t+ 383.2s ♪ 音效 sfx/step_own +t+ 383.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 383.7s ♪ 音效 sfx/pulse +t+ 384.1s ♪ 音效 sfx/side_wall_l +t+ 384.1s ♪ 音效 sfx/side_wall_r +t+ 384.5s ♪ 音效 sfx/echo_3 +t+ 384.7s ♪ 音效 sfx/mat_stone +t+ 384.7s ♪ 音效 sfx/step_turn +t+ 384.7s ♪ 音效 sfx/step_turn +t+ 384.7s ♪ 音效 sfx/step_own +t+ 384.7s 〰 震动:swipe(滑动识别) +t+ 385.2s ♪ 音效 sfx/step_own +t+ 385.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 385.7s ♪ 音效 sfx/pulse +t+ 385.9s ♪ 音效 sfx/echo_1 +t+ 386.0s ♪ 音效 sfx/side_wall_l +t+ 386.0s ♪ 音效 sfx/side_wall_r +t+ 386.0s ♪ 音效 sfx/mat_stone +t+ 386.0s ♪ 音效 sfx/step_turn +t+ 386.0s ♪ 音效 sfx/step_turn +t+ 386.0s ♪ 音效 sfx/step_own +t+ 386.0s 〰 震动:swipe(滑动识别) +t+ 386.5s ♪ 音效 sfx/step_own +t+ 386.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 387.0s ♪ 音效 sfx/pulse +t+ 387.4s ♪ 音效 sfx/side_wall_l +t+ 387.4s ♪ 音效 sfx/side_wall_r +t+ 387.8s ♪ 音效 sfx/echo_3 +t+ 387.9s ♪ 音效 sfx/mat_stone +t+ 387.9s ♪ 音效 sfx/step_turn +t+ 387.9s ♪ 音效 sfx/step_turn +t+ 387.9s ♪ 音效 sfx/step_own +t+ 387.9s 〰 震动:swipe(滑动识别) +t+ 388.4s ♪ 音效 sfx/step_own +t+ 388.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 388.9s ♪ 音效 sfx/pulse +t+ 389.2s ♪ 音效 sfx/echo_1 +t+ 389.3s ♪ 音效 sfx/side_wall_l +t+ 389.3s ♪ 音效 sfx/side_wall_r +t+ 389.3s ♪ 音效 sfx/mat_stone +t+ 389.3s ♪ 音效 sfx/step_turn +t+ 389.3s ♪ 音效 sfx/step_turn +t+ 389.3s ♪ 音效 sfx/step_own +t+ 389.3s 〰 震动:swipe(滑动识别) +t+ 389.8s ♪ 音效 sfx/step_own +t+ 389.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 390.3s ♪ 音效 sfx/pulse +t+ 390.7s ♪ 音效 sfx/side_wall_l +t+ 390.7s ♪ 音效 sfx/side_wall_r +t+ 391.1s ♪ 音效 sfx/echo_3 +t+ 391.2s ♪ 音效 sfx/mat_stone +t+ 391.2s ♪ 音效 sfx/step_turn +t+ 391.2s ♪ 音效 sfx/step_turn +t+ 391.2s ♪ 音效 sfx/step_own +t+ 391.2s 〰 震动:swipe(滑动识别) +t+ 391.7s ♪ 音效 sfx/step_own +t+ 391.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 392.2s ♪ 音效 sfx/pulse +t+ 392.5s ♪ 音效 sfx/echo_1 +t+ 392.6s ♪ 音效 sfx/side_wall_l +t+ 392.6s ♪ 音效 sfx/side_wall_r +t+ 392.6s ♪ 音效 sfx/mat_stone +t+ 392.6s ♪ 音效 sfx/step_turn +t+ 392.6s ♪ 音效 sfx/step_turn +t+ 392.6s ♪ 音效 sfx/step_own +t+ 392.6s 〰 震动:swipe(滑动识别) +t+ 393.1s ♪ 音效 sfx/step_own +t+ 393.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 393.6s ♪ 音效 sfx/pulse +t+ 394.0s ♪ 音效 sfx/side_wall_l +t+ 394.0s ♪ 音效 sfx/side_wall_r +t+ 394.4s ♪ 音效 sfx/echo_3 +t+ 394.5s ♪ 音效 sfx/mat_stone +t+ 394.5s ♪ 音效 sfx/step_turn +t+ 394.5s ♪ 音效 sfx/step_turn +t+ 394.5s ♪ 音效 sfx/step_own +t+ 394.5s 〰 震动:swipe(滑动识别) +t+ 395.0s ♪ 音效 sfx/step_own +t+ 395.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 395.5s ♪ 音效 sfx/pulse +t+ 395.8s ♪ 音效 sfx/echo_1 +t+ 395.9s ♪ 音效 sfx/side_wall_l +t+ 395.9s ♪ 音效 sfx/side_wall_r +t+ 395.9s ♪ 音效 sfx/mat_stone +t+ 395.9s ♪ 音效 sfx/step_turn +t+ 395.9s ♪ 音效 sfx/step_turn +t+ 395.9s ♪ 音效 sfx/step_own +t+ 395.9s 〰 震动:swipe(滑动识别) +t+ 396.4s ♪ 音效 sfx/step_own +t+ 396.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 396.9s ♪ 音效 sfx/pulse +t+ 397.3s ♪ 音效 sfx/side_wall_l +t+ 397.3s ♪ 音效 sfx/side_wall_r +t+ 397.7s ♪ 音效 sfx/echo_3 +t+ 397.8s ♪ 音效 sfx/mat_stone +t+ 397.8s ♪ 音效 sfx/step_turn +t+ 397.8s ♪ 音效 sfx/step_turn +t+ 397.8s ♪ 音效 sfx/step_own +t+ 397.8s 〰 震动:swipe(滑动识别) +t+ 398.3s ♪ 音效 sfx/step_own +t+ 398.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 398.8s ♪ 音效 sfx/pulse +t+ 399.1s ♪ 音效 sfx/echo_1 +t+ 399.2s ♪ 音效 sfx/side_wall_l +t+ 399.2s ♪ 音效 sfx/side_wall_r +t+ 399.2s ♪ 音效 sfx/mat_stone +t+ 399.2s ♪ 音效 sfx/step_turn +t+ 399.2s ♪ 音效 sfx/step_turn +t+ 399.2s ♪ 音效 sfx/step_own +t+ 399.2s 〰 震动:swipe(滑动识别) +t+ 399.7s ♪ 音效 sfx/step_own +t+ 399.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 400.2s ♪ 音效 sfx/pulse +t+ 400.5s ♪ 音效 sfx/side_wall_l +t+ 400.5s ♪ 音效 sfx/side_wall_r +t+ 401.0s ♪ 音效 sfx/echo_3 +t+ 401.1s ♪ 音效 sfx/mat_stone +t+ 401.1s ♪ 音效 sfx/step_turn +t+ 401.1s ♪ 音效 sfx/step_turn +t+ 401.1s ♪ 音效 sfx/step_own +t+ 401.1s 〰 震动:swipe(滑动识别) +t+ 401.6s ♪ 音效 sfx/step_own +t+ 401.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 402.1s ♪ 音效 sfx/pulse +t+ 402.4s ♪ 音效 sfx/echo_1 +t+ 402.5s ♪ 音效 sfx/side_wall_l +t+ 402.5s ♪ 音效 sfx/side_wall_r +t+ 402.5s ♪ 音效 sfx/mat_stone +t+ 402.5s ♪ 音效 sfx/step_turn +t+ 402.5s ♪ 音效 sfx/step_turn +t+ 402.5s ♪ 音效 sfx/step_own +t+ 402.5s 〰 震动:swipe(滑动识别) +t+ 403.0s ♪ 音效 sfx/step_own +t+ 403.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 403.5s ♪ 音效 sfx/pulse +t+ 403.8s ♪ 音效 sfx/side_wall_l +t+ 403.8s ♪ 音效 sfx/side_wall_r +t+ 404.3s ♪ 音效 sfx/echo_3 +t+ 404.4s ♪ 音效 sfx/mat_stone +t+ 404.4s ♪ 音效 sfx/step_turn +t+ 404.4s ♪ 音效 sfx/step_turn +t+ 404.4s ♪ 音效 sfx/step_own +t+ 404.4s 〰 震动:swipe(滑动识别) +t+ 404.9s ♪ 音效 sfx/step_own +t+ 404.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】按住前进一格,松开 + + +#### 【玩家动作】这一把竟然没死——那就继续往下走 + + +#### 【玩家动作】单击,开始新周目 + +t+ 405.4s ♪ 音效 sfx/pulse +t+ 405.6s ♪ 音效 sfx/echo_1 +t+ 405.8s ♪ 音效 sfx/side_wall_l +t+ 405.8s ♪ 音效 sfx/side_wall_r +t+ 405.8s ♪ 音效 sfx/mat_stone +t+ 405.8s 【状态】新周目直落探索:遗产生效,不用再上课 教学演练第 9 步,跟着旁白做(BGM=bgm/battle,语速1) + +#### 【玩家动作】上滑,听新周目的局面复述 + +t+ 405.8s ▶ 语音:「这是第一层。」 +t+ 407.9s ▶ 语音:「你毫发无损。」 +t+ 410.0s ▶ 语音:「口袋空空。」 +t+ 412.1s ▶ 语音:「你面朝东。」 +t+ 414.3s ▶ 语音:「附近很安静。」 + +#### 【玩家动作】单击脉冲,听听熟悉的回声 + +t+ 414.3s ♪ 音效 sfx/pulse +t+ 414.5s ♪ 音效 sfx/echo_1 +t+ 414.6s ♪ 音效 sfx/side_wall_l +t+ 414.6s ♪ 音效 sfx/side_wall_r +t+ 414.6s ♪ 音效 sfx/mat_stone +t+ 416.4s 【状态】第二周目短程收尾——死亡-重开循环跑通,收工 教学演练第 9 步,跟着旁白做(BGM=bgm/battle,语速1) diff --git a/research/new8/dungeon/round1/transcript-stress.md b/research/new8/dungeon/round1/transcript-stress.md new file mode 100644 index 00000000..911e9d28 --- /dev/null +++ b/research/new8/dungeon/round1/transcript-stress.md @@ -0,0 +1,673 @@ +# 试玩转写:《dungeon》 + +> 本文件是「玩家感知日志」:一位新手玩家的整局体验转写。语音列出了玩家听到的每句话全文, +> 音效/BGM/震动按发生时间列出。「静默」段 = 玩家什么也听不到。手势是剧本注入的(模拟真人手部动作), +> 非最优操作:包含犹豫、误操作与乱试。语音时长按语速估算(±20%),仅供节奏参考。 +t+ 0.0s (玩家点开游戏,轻触屏幕开始) +t+ 0.0s ♫ BGM 切换 → bgm/menu +t+ 0.0s ♪ 向导音:menu(系统界面) +t+ 0.0s 【状态】标题页乱划 标题页:单击开始探窟,上滑听元进度(BGM=bgm/menu,语速1) +t+ 0.0s ▶ 语音:「这趟没带回什么。」 +t+ 2.1s ▶ 语音:「你还没到过第三层。」 +t+ 2.1s 【状态】标题页乱划 标题页:单击开始探窟,上滑听元进度(语音播放中,语速1) +t+ 2.1s ▶ 语音:「新的探窟,开始了。」 +t+ 4.2s ♫ BGM 切换 → bgm/explore +t+ 4.2s ♪ 向导音:explore(探索/安全) +t+ 4.2s ▶ 语音:「你在黑暗中醒来。这里是回声地窟。」 +t+ 4.2s 【状态】教学里乱按一气,旁白说到哪按到哪 教学演练第 1 步,跟着旁白做(语音播放中,语速1) +t+ 4.2s ♪ 音效 sfx/pulse +t+ 4.2s ▶ 语音:「听,回声回来了。」 +t+ 4.2s ▶ 语音:「回声回来越快,墙就越近。」 +t+ 4.6s ♪ 音效 sfx/side_wall_l +t+ 4.6s ♪ 音效 sfx/side_wall_r +t+ 4.7s ♪ 音效 sfx/echo_2 +t+ 4.7s ▶ 语音:「两侧的回声,发闷是墙,空旷是通路。」 +t+ 4.9s ♪ 音效 sfx/mat_stone +t+ 6.4s ♪ 音效 sfx/step_turn +t+ 6.4s ▶ 语音:「按住屏幕不放,你就往前走。」 +t+ 8.5s 〰 震动:error(做错了:两连短震) +t+ 12.2s ♪ 音效 sfx/step_own +t+ 12.2s 〰 震动:swipe(滑动识别) +t+ 12.7s ♪ 音效 sfx/step_own +t+ 12.7s ▶ 语音:「左滑右滑,可以转身。」 +t+ 12.7s 〰 震动:swipe(滑动识别) +t+ 13.2s ♪ 音效 sfx/step_own +t+ 13.7s ♪ 音效 sfx/bump +t+ 13.2s 〰 震动:swipe(滑动识别) +t+ 14.4s 〰 震动:error(做错了:两连短震) +t+ 14.9s ♪ 音效 sfx/step_turn +t+ 20.7s ♪ 音效 sfx/step_turn +t+ 24.2s ♪ 音效 sfx/step_turn +t+ 24.2s ▶ 语音:「双击,再发一次脉冲,不要钱。」 +t+ 24.2s 〰 震动:error(做错了:两连短震) +t+ 26.4s ♪ 音效 sfx/pulse +t+ 26.4s ▶ 语音:「听,回声回来了。」 +t+ 26.4s ▶ 语音:「上滑,随时听自己的状态。」 +t+ 26.7s ♪ 音效 sfx/side_wall_l +t+ 26.7s ♪ 音效 sfx/side_wall_r +t+ 27.2s ♪ 音效 sfx/echo_3 +t+ 27.2s ▶ 语音:「这是第一层。」 +t+ 27.3s ♪ 音效 sfx/mat_stone +t+ 29.3s ▶ 语音:「你毫发无损。」 +t+ 31.4s ▶ 语音:「口袋空空。」 +t+ 33.5s ▶ 语音:「你面朝南。」 +t+ 33.5s ♫ BGM 切换 → bgm/menu +t+ 33.5s ♪ 向导音:menu(系统界面) +t+ 33.5s ▶ 语音:「菜单。单击切换,双击确认。」 +t+ 35.6s ▶ 语音:「返回探索。」 +t+ 35.6s ♫ BGM 切换 → bgm/explore +t+ 35.6s ♪ 音效 sfx/step_turn +t+ 35.6s ▶ 语音:「前面有东西!放心,它伤不到你。」 +t+ 36.8s ♪ 音效 sfx/mob_step_small_c +t+ 38.0s ♪ 音效 sfx/mob_step_small_c +t+ 39.0s ♪ 音效 sfx/mob_step_small_c +t+ 39.0s 〰 震动:heartbeat(心跳/倒计时) +t+ 39.2s ♪ 音效 sfx/mob_step_small_c +t+ 39.2s 〰 震动:heartbeat(心跳/倒计时) +t+ 40.2s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 40.2s ♪ 向导音:battle(战斗/对抗) +t+ 40.2s ▶ 语音:「盲眼蝠扑了过来!」 +t+ 42.3s ▶ 语音:「听到它的动静,双击格挡。」 +t+ 44.4s ♪ 音效 sfx/tel_small +t+ 44.4s 〰 震动:beat(节拍点) +t+ 45.6s ▶ 语音:「没挡住也别怕,它伤不到你。」 +t+ 47.7s ▶ 语音:「现在单击,挥拳打它。」 +t+ 49.8s ♪ 向导音:turn(轮到你) +t+ 49.8s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,抡空也打 + +t+ 49.8s ♪ 音效 sfx/atk_swing +t+ 49.8s ♪ 音效 sfx/atk_hit +t+ 49.8s ♪ 音效 sfx/tel_small +t+ 49.8s 〰 震动:beat(节拍点) +t+ 51.0s ▶ 语音:「没挡住也别怕,它伤不到你。」 +t+ 53.2s ▶ 语音:「现在单击,挥拳打它。」 +t+ 55.3s ♪ 向导音:turn(轮到你) +t+ 55.3s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,抡空也打 + +t+ 55.3s ♪ 音效 sfx/atk_swing +t+ 55.3s ♪ 音效 sfx/atk_hit +t+ 55.3s ♪ 音效 sfx/enemy_die +t+ 55.3s ♪ 音效 sfx/coin +t+ 55.3s ▶ 语音:「打中了,就是这样。」 +t+ 55.3s 〰 震动:success(成功长震) +t+ 57.4s ▶ 语音:「战利不多。」 +t+ 59.5s ▶ 语音:「铁器的回声,就是下潜的楼梯。」 +t+ 63.0s ♪ 音效 sfx/step_turn +t+ 67.3s ♪ 音效 sfx/bump +t+ 67.5s ♪ 音效 sfx/step_turn +t+ 67.5s ♪ 音效 sfx/step_own +t+ 67.5s 〰 震动:swipe(滑动识别) +t+ 68.0s ♪ 音效 sfx/step_own +t+ 68.0s 〰 震动:swipe(滑动识别) +t+ 68.5s ♪ 音效 sfx/step_turn +t+ 68.5s ♪ 音效 sfx/pulse +t+ 68.9s ♪ 音效 sfx/side_wall_l +t+ 68.9s ♪ 音效 sfx/side_wall_r +t+ 69.0s ♪ 音效 sfx/echo_2 +t+ 69.0s ♪ 音效 sfx/step_turn +t+ 69.1s ♪ 音效 sfx/mat_stone +t+ 69.1s 〰 震动:error(做错了:两连短震) +t+ 69.1s ♪ 音效 sfx/step_turn +t+ 69.1s ♪ 音效 sfx/step_own +t+ 69.1s 〰 震动:swipe(滑动识别) +t+ 69.6s ♪ 音效 sfx/bump +t+ 69.6s ♪ 音效 sfx/step_turn +t+ 69.6s ♪ 音效 sfx/step_own +t+ 69.6s 〰 震动:swipe(滑动识别) +t+ 70.1s ♪ 音效 sfx/step_own +t+ 70.1s 〰 震动:swipe(滑动识别) +t+ 70.6s ♪ 音效 sfx/pulse +t+ 71.0s ♪ 音效 sfx/side_wall_l +t+ 71.0s ♪ 音效 sfx/side_wall_r +t+ 71.1s ♪ 音效 sfx/echo_2 +t+ 71.1s ♪ 音效 sfx/step_turn +t+ 71.3s ♪ 音效 sfx/mat_stone +t+ 71.3s ♪ 音效 sfx/step_turn +t+ 71.3s ♪ 音效 sfx/step_own +t+ 71.3s 〰 震动:swipe(滑动识别) +t+ 71.8s ♪ 音效 sfx/bump +t+ 71.8s ♪ 音效 sfx/step_turn +t+ 71.8s ♪ 音效 sfx/step_own +t+ 71.8s 〰 震动:swipe(滑动识别) +t+ 72.3s ♪ 音效 sfx/step_own +t+ 72.3s ▶ 语音:「楼梯就在脚下。单击,下潜。」 +t+ 72.3s 〰 震动:swipe(滑动识别) +t+ 72.8s ♪ 音效 sfx/stairs_down +t+ 72.8s ▶ 语音:「你顺着楼梯,往下潜。」 +t+ 74.9s ▶ 语音:「教学完了。地窟很深,小心走。」 +t+ 77.0s ▶ 语音:「这是第二层。」 +t+ 79.1s ♫ BGM 切换 → bgm/explore +t+ 79.1s ♪ 向导音:explore(探索/安全) +t+ 79.1s 【状态】总算从教学里滚出来了(可能跳了课,可能一路错过来的) 第 2 层黑暗里:单击发脉冲用回声看路,按住前进,左右滑转身,上滑状态,下滑菜单(BGM=bgm/explore,语速1) + +#### 【玩家动作】手痒连点三下脉冲 + +t+ 79.1s ♪ 音效 sfx/pulse +t+ 79.1s 〰 震动:error(做错了:两连短震) +t+ 79.1s 〰 震动:error(做错了:两连短震) +t+ 79.5s ♪ 音效 sfx/side_wall_l +t+ 79.5s ♪ 音效 sfx/side_wall_r +t+ 80.2s ♪ 音效 sfx/echo_4 +t+ 80.3s ♪ 音效 sfx/mat_stone +t+ 80.9s ♪ 音效 sfx/mob_step_small_r + +#### 【玩家动作】手痒连点三下脉冲 + +t+ 80.9s ♪ 音效 sfx/pulse +t+ 80.9s 〰 震动:error(做错了:两连短震) +t+ 80.9s 〰 震动:error(做错了:两连短震) +t+ 80.9s ♪ 音效 sfx/mob_step_small_r +t+ 80.9s 〰 震动:heartbeat(心跳/倒计时) +t+ 81.1s ♪ 音效 sfx/mob_step_small_r +t+ 81.2s ♪ 音效 sfx/side_wall_l +t+ 81.2s ♪ 音效 sfx/side_wall_r +t+ 82.0s ♪ 音效 sfx/echo_4 +t+ 82.1s ♪ 音效 sfx/mat_stone +t+ 82.5s ♪ 音效 sfx/mob_step_small_r + +#### 【玩家动作】手痒连点三下脉冲 + +t+ 82.5s ♪ 音效 sfx/pulse +t+ 82.5s 〰 震动:error(做错了:两连短震) +t+ 82.5s 〰 震动:error(做错了:两连短震) +t+ 82.6s ♪ 音效 sfx/mob_step_small_r +t+ 82.9s ♪ 音效 sfx/side_wall_l +t+ 82.9s ♪ 音效 sfx/side_wall_r +t+ 82.6s 〰 震动:heartbeat(心跳/倒计时) +t+ 83.5s ♪ 音效 sfx/mob_step_small_c +t+ 83.6s ♪ 音效 sfx/echo_4 +t+ 83.7s ♪ 音效 sfx/mat_stone +t+ 84.1s ♪ 音效 sfx/pulse +t+ 84.5s ♪ 音效 sfx/side_wall_l +t+ 84.5s ♪ 音效 sfx/side_wall_r +t+ 84.7s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 84.7s ♪ 向导音:battle(战斗/对抗) +t+ 84.7s ▶ 语音:「窟鼠窜了出来!」 +t+ 86.8s ♪ 向导音:turn(轮到你) +t+ 86.8s 〰 震动:confirm(轻点确认) +t+ 86.8s ▶ 语音:「它贴着你不放,不用转身。」 +t+ 86.8s 〰 震动:error(做错了:两连短震) +t+ 88.9s ♪ 音效 sfx/hurt +t+ 88.9s ▶ 语音:「你甩掉了它,被抓了一下。」 +t+ 88.9s 〰 震动:hit(受击重震) +t+ 91.1s ♫ BGM 切换 → bgm/explore +t+ 91.1s ♪ 向导音:explore(探索/安全) +t+ 91.1s ♪ 音效 sfx/pulse +t+ 91.4s ♪ 音效 sfx/side_wall_l +t+ 91.4s ♪ 音效 sfx/side_wall_r +t+ 91.9s 〰 震动:error(做错了:两连短震) +t+ 92.2s ♪ 音效 sfx/echo_4 +t+ 92.3s ♪ 音效 sfx/mat_stone +t+ 93.0s ♪ 音效 sfx/mob_step_small_r +t+ 93.2s ♪ 音效 sfx/mob_step_small_r +t+ 93.4s ♪ 音效 sfx/mob_step_small_r +t+ 93.2s 〰 震动:heartbeat(心跳/倒计时) +t+ 94.9s ♪ 音效 sfx/mob_step_small_r +t+ 94.9s 〰 震动:heartbeat(心跳/倒计时) +t+ 94.9s ♪ 音效 sfx/step_turn +t+ 95.1s ♪ 音效 sfx/mob_step_small_l +t+ 95.1s ♪ 音效 sfx/step_own +t+ 95.1s 〰 震动:swipe(滑动识别) +t+ 95.5s ♪ 音效 sfx/mob_step_small_c +t+ 95.5s 〰 震动:heartbeat(心跳/倒计时) +t+ 96.1s ♪ 音效 sfx/pulse +t+ 96.4s ♪ 音效 sfx/side_wall_l +t+ 96.4s ♪ 音效 sfx/side_wall_r +t+ 96.9s ♪ 音效 sfx/echo_3 +t+ 97.0s ♪ 音效 sfx/mat_stone +t+ 97.2s ♪ 音效 sfx/mob_step_small_c +t+ 97.2s ♪ 音效 sfx/mob_step_small_c +t+ 97.8s ♪ 音效 sfx/mob_step_small_c +t+ 97.2s 〰 震动:heartbeat(心跳/倒计时) +t+ 99.1s ♪ 音效 sfx/mob_step_small_c +t+ 99.1s ♪ 音效 sfx/step_turn +t+ 99.1s ♪ 音效 sfx/mob_step_small_r +t+ 100.3s ♪ 音效 sfx/mob_step_small_r +t+ 100.3s 〰 震动:heartbeat(心跳/倒计时) +t+ 100.3s ♪ 音效 sfx/step_own +t+ 100.3s 〰 震动:swipe(滑动识别) +t+ 100.8s ♪ 音效 sfx/step_own +t+ 100.8s 〰 震动:swipe(滑动识别) +t+ 100.9s ♪ 音效 sfx/mob_step_small_l +t+ 101.1s ▶ 语音:「这是第二层。」 +t+ 101.3s ♪ 音效 sfx/mob_step_small_r +t+ 101.8s ♪ 音效 sfx/mob_step_small_r +t+ 101.8s ♪ 音效 sfx/pulse +t+ 102.2s ♪ 音效 sfx/side_wall_l +t+ 102.2s ♪ 音效 sfx/side_wall_r +t+ 102.3s ♪ 音效 sfx/echo_2 +t+ 102.4s ♪ 音效 sfx/mat_stone +t+ 102.7s ♪ 音效 sfx/mob_step_small_l +t+ 102.7s 〰 震动:heartbeat(心跳/倒计时) +t+ 103.2s ▶ 语音:「你毫发无损。」 +t+ 103.4s ♪ 音效 sfx/mob_step_small_r +t+ 104.0s ♪ 音效 sfx/mob_step_small_r +t+ 104.0s ♪ 音效 sfx/step_turn +t+ 104.4s ♪ 音效 sfx/mob_step_small_c +t+ 104.4s ♪ 音效 sfx/step_own +t+ 104.4s ▶ 语音:「楼梯就在脚下。单击,下潜。」 +t+ 104.4s 〰 震动:swipe(滑动识别) +t+ 104.9s ♪ 音效 sfx/bump +t+ 105.8s ♪ 音效 sfx/mob_step_small_r +t+ 105.8s 〰 震动:heartbeat(心跳/倒计时) + +#### 【玩家动作】正好站在楼梯上——单击下潜 + +t+ 105.8s ♪ 音效 sfx/stairs_down +t+ 105.8s ▶ 语音:「你顺着楼梯,往下潜。」 +t+ 107.9s ▶ 语音:「这是第三层。」 +t+ 110.0s ♪ 向导音:explore(探索/安全) + +#### 【玩家动作】手痒连点三下脉冲 + +t+ 110.0s ♪ 音效 sfx/pulse +t+ 110.0s 〰 震动:error(做错了:两连短震) +t+ 110.0s 〰 震动:error(做错了:两连短震) +t+ 110.4s ♪ 音效 sfx/side_wall_l +t+ 110.4s ♪ 音效 sfx/side_wall_r +t+ 110.5s ♪ 音效 sfx/echo_2 +t+ 110.7s ♪ 音效 sfx/mat_stone +t+ 111.8s ♪ 音效 sfx/mob_step_bug_r +t+ 111.8s ♪ 音效 sfx/pulse +t+ 112.1s ♪ 音效 sfx/mob_step_bug_r +t+ 112.1s ♪ 音效 sfx/side_wall_l +t+ 112.1s ♪ 音效 sfx/side_wall_r +t+ 112.3s ♪ 音效 sfx/mob_step_bug_r +t+ 112.3s ♪ 音效 sfx/echo_2 +t+ 112.4s ♪ 音效 sfx/mat_stone +t+ 112.3s 〰 震动:heartbeat(心跳/倒计时) +t+ 113.9s ♪ 音效 sfx/mob_step_bug_r +t+ 113.9s ♪ 音效 sfx/step_turn +t+ 114.2s ♪ 音效 sfx/mob_step_bug_c +t+ 114.2s ♪ 音效 sfx/step_own +t+ 114.2s 〰 震动:swipe(滑动识别) +t+ 114.4s ♪ 音效 sfx/mob_step_bug_c +t+ 114.4s 〰 震动:heartbeat(心跳/倒计时) +t+ 114.7s ♪ 音效 sfx/pulse +t+ 115.1s ♪ 音效 sfx/side_wall_l +t+ 115.1s ♪ 音效 sfx/side_wall_r +t+ 115.5s ♪ 音效 sfx/echo_3 +t+ 115.6s ♪ 音效 sfx/mat_stone +t+ 116.0s ♪ 音效 sfx/mob_step_bug_c +t+ 116.2s ♪ 音效 sfx/mob_step_bug_c +t+ 116.2s ♪ 音效 sfx/mob_step_bug_c +t+ 117.8s ♪ 音效 sfx/mob_step_bug_c +t+ 117.8s ♪ 音效 sfx/step_turn +t+ 117.8s ♪ 音效 sfx/mob_step_bug_l +t+ 118.2s ♪ 音效 sfx/mob_step_bug_l +t+ 118.2s 〰 震动:heartbeat(心跳/倒计时) +t+ 118.2s ♪ 音效 sfx/bump +t+ 119.7s ♪ 音效 sfx/mob_step_bug_l +t+ 120.0s ♪ 音效 sfx/pulse +t+ 120.1s ♪ 音效 sfx/mob_step_bug_l +t+ 120.3s ♪ 音效 sfx/mob_step_bug_l +t+ 120.3s ♪ 音效 sfx/echo_1 +t+ 120.4s ♪ 音效 sfx/side_wall_l +t+ 120.4s ♪ 音效 sfx/side_wall_r +t+ 120.4s ♪ 音效 sfx/mat_stone +t+ 120.1s 〰 震动:heartbeat(心跳/倒计时) +t+ 121.4s ♪ 音效 sfx/mob_step_bug_l +t+ 121.8s ♪ 音效 sfx/mob_step_bug_l +t+ 122.0s ♪ 音效 sfx/mob_step_bug_l +t+ 122.0s 〰 震动:heartbeat(心跳/倒计时) +t+ 122.0s ♪ 音效 sfx/step_turn +t+ 123.7s ♪ 音效 sfx/mob_step_bug_c +t+ 123.7s ♪ 音效 sfx/step_own +t+ 123.7s 〰 震动:swipe(滑动识别) +t+ 123.7s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 123.7s ♪ 向导音:battle(战斗/对抗) +t+ 123.7s ▶ 语音:「蘑菇怪拦住了你!」 +t+ 125.9s ♪ 向导音:turn(轮到你) +t+ 125.9s 〰 震动:confirm(轻点确认) +t+ 125.9s 【状态】乱打:不格挡、逃不掉(Boss),一直挥拳 战斗中:你的血量毫发无损,等你出手(单击挥击,按住屏幕逃跑)(BGM=bgm/battle,语速1) + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 125.9s ♪ 音效 sfx/atk_swing +t+ 125.9s ♪ 音效 sfx/atk_hit +t+ 125.9s ♪ 音效 sfx/tel_bug +t+ 125.9s 〰 震动:beat(节拍点) +t+ 127.1s ♪ 音效 sfx/hurt +t+ 127.1s ♪ 向导音:turn(轮到你) +t+ 127.1s 〰 震动:hit(受击重震) +t+ 127.1s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 127.6s ♪ 音效 sfx/atk_swing +t+ 127.6s ♪ 音效 sfx/atk_hit +t+ 127.6s ♪ 音效 sfx/tel_bug +t+ 127.6s 〰 震动:beat(节拍点) +t+ 128.8s ♪ 音效 sfx/hurt +t+ 128.8s ♪ 向导音:turn(轮到你) +t+ 128.8s 〰 震动:hit(受击重震) +t+ 128.8s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 129.8s ♪ 音效 sfx/atk_swing +t+ 129.8s ♪ 音效 sfx/atk_hit +t+ 129.8s ♪ 音效 sfx/tel_bug +t+ 129.8s 〰 震动:beat(节拍点) +t+ 131.0s ♪ 音效 sfx/hurt +t+ 131.0s ♪ 向导音:turn(轮到你) +t+ 131.0s 〰 震动:hit(受击重震) +t+ 131.0s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 131.6s ♪ 音效 sfx/atk_swing +t+ 131.6s ♪ 音效 sfx/atk_hit +t+ 131.6s ♪ 音效 sfx/enemy_die +t+ 131.6s ♪ 音效 sfx/coin +t+ 131.6s ▶ 语音:「它倒下了。」 +t+ 131.6s 〰 震动:success(成功长震) +t+ 133.7s ▶ 语音:「小有收获。」 +t+ 135.9s ♫ BGM 切换 → bgm/explore +t+ 135.9s ♪ 向导音:explore(探索/安全) +t+ 135.9s ♪ 音效 sfx/step_turn +t+ 136.7s ♪ 音效 sfx/pulse +t+ 137.0s ♪ 音效 sfx/echo_1 +t+ 137.1s ♪ 音效 sfx/side_wall_l +t+ 137.1s ♪ 音效 sfx/side_wall_r +t+ 137.1s ♪ 音效 sfx/mat_stone +t+ 137.6s ♪ 音效 sfx/mob_step_bug_l +t+ 137.6s 〰 震动:heartbeat(心跳/倒计时) +t+ 138.3s ♪ 音效 sfx/mob_step_bug_l +t+ 139.4s ♪ 音效 sfx/mob_step_bug_l +t+ 139.4s 〰 震动:heartbeat(心跳/倒计时) +t+ 139.4s ♪ 音效 sfx/step_turn +t+ 140.3s ♪ 音效 sfx/mob_step_bug_c +t+ 140.3s ♪ 音效 sfx/step_own +t+ 140.3s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 140.3s ♪ 向导音:battle(战斗/对抗) +t+ 140.3s ▶ 语音:「蘑菇怪拦住了你!」 +t+ 140.3s 〰 震动:swipe(滑动识别) +t+ 142.4s ♪ 向导音:turn(轮到你) +t+ 142.4s 〰 震动:confirm(轻点确认) +t+ 142.4s 【状态】乱打:不格挡、逃不掉(Boss),一直挥拳 战斗中:你的血量受了点伤,等你出手(单击挥击,按住屏幕逃跑)(BGM=bgm/battle,语速1) + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 142.4s ♪ 音效 sfx/atk_swing +t+ 142.4s ♪ 音效 sfx/atk_hit +t+ 142.4s ♪ 音效 sfx/tel_bug +t+ 142.4s 〰 震动:beat(节拍点) +t+ 143.6s ♪ 音效 sfx/hurt +t+ 143.6s ♪ 向导音:turn(轮到你) +t+ 143.6s 〰 震动:hit(受击重震) +t+ 143.6s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 150.3s ♪ 音效 sfx/atk_swing +t+ 150.3s ♪ 音效 sfx/atk_hit +t+ 150.3s ♪ 音效 sfx/tel_bug +t+ 150.3s 〰 震动:beat(节拍点) +t+ 151.5s ♪ 音效 sfx/hurt +t+ 151.5s ♪ 向导音:turn(轮到你) +t+ 151.5s 〰 震动:hit(受击重震) +t+ 151.5s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + +t+ 166.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 166.5s ▶ 向导语音:「轮到你了。单击挥击。」 + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 166.5s ♪ 音效 sfx/atk_swing +t+ 166.5s ♪ 音效 sfx/atk_hit +t+ 166.5s ♪ 音效 sfx/tel_bug +t+ 166.5s 〰 震动:beat(节拍点) +t+ 167.7s ♪ 音效 sfx/hurt +t+ 167.7s ♪ 向导音:turn(轮到你) +t+ 167.7s 〰 震动:hit(受击重震) +t+ 167.7s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 168.6s ♪ 音效 sfx/atk_swing +t+ 168.6s ♪ 音效 sfx/atk_hit +t+ 168.6s ♪ 音效 sfx/enemy_die +t+ 168.6s ♪ 音效 sfx/coin +t+ 168.6s ▶ 语音:「它倒下了。」 +t+ 168.6s 〰 震动:success(成功长震) +t+ 170.8s ▶ 语音:「小有收获。」 +t+ 172.9s ♫ BGM 切换 → bgm/explore +t+ 172.9s ♪ 向导音:explore(探索/安全) +t+ 172.9s ♪ 音效 sfx/pulse +t+ 173.1s ♪ 音效 sfx/echo_1 +t+ 173.2s ♪ 音效 sfx/side_wall_l +t+ 173.2s ♪ 音效 sfx/side_wall_r +t+ 173.2s ♪ 音效 sfx/mat_stone +t+ 174.9s ♪ 音效 sfx/mob_step_bug_c +t+ 174.9s ♪ 音效 sfx/step_turn +t+ 177.3s ♪ 音效 sfx/step_own +t+ 177.3s 〰 震动:swipe(滑动识别) +t+ 177.8s ♪ 音效 sfx/step_own +t+ 177.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+ 178.3s ♪ 音效 sfx/pulse +t+ 178.3s 〰 震动:error(做错了:两连短震) +t+ 178.3s 〰 震动:error(做错了:两连短震) +t+ 178.6s ♪ 音效 sfx/side_wall_l +t+ 178.6s ♪ 音效 sfx/side_wall_r +t+ 179.4s ♪ 音效 sfx/echo_4 +t+ 179.5s ♪ 音效 sfx/mat_stone +t+ 179.5s ♪ 音效 sfx/mob_step_bug_r +t+ 181.3s ♪ 音效 sfx/mob_step_bug_r +t+ 181.3s ♪ 音效 sfx/pulse +t+ 181.7s ♪ 音效 sfx/side_wall_l +t+ 181.7s ♪ 音效 sfx/side_wall_r +t+ 182.4s ♪ 音效 sfx/echo_4 +t+ 182.5s ♪ 音效 sfx/mat_stone +t+ 183.5s ♪ 音效 sfx/mob_step_bug_r +t+ 183.5s ♪ 音效 sfx/step_turn +t+ 185.4s ♪ 音效 sfx/mob_step_bug_c +t+ 185.4s ♪ 音效 sfx/bump +t+ 187.7s ♪ 音效 sfx/mob_step_bug_c +t+ 190.1s ♪ 音效 sfx/mob_step_bug_c + +#### 【玩家动作】手痒连点三下脉冲 + +t+ 190.1s ♪ 音效 sfx/pulse +t+ 190.1s 〰 震动:error(做错了:两连短震) +t+ 190.1s 〰 震动:error(做错了:两连短震) +t+ 190.3s ♪ 音效 sfx/echo_1 +t+ 190.4s ♪ 音效 sfx/side_wall_l +t+ 190.4s ♪ 音效 sfx/side_wall_r +t+ 190.4s ♪ 音效 sfx/mat_stone +t+ 192.3s ♪ 音效 sfx/mob_step_bug_c + +#### 【玩家动作】手痒连点三下脉冲 + +t+ 192.3s ♪ 音效 sfx/pulse +t+ 192.3s 〰 震动:error(做错了:两连短震) +t+ 192.3s 〰 震动:error(做错了:两连短震) +t+ 192.5s ♪ 音效 sfx/echo_1 +t+ 192.6s ♪ 音效 sfx/side_wall_l +t+ 192.6s ♪ 音效 sfx/side_wall_r +t+ 192.7s ♪ 音效 sfx/mat_stone +t+ 194.2s ♪ 音效 sfx/mob_step_bug_c +t+ 194.2s ♪ 音效 sfx/pulse +t+ 194.4s ♪ 音效 sfx/echo_1 +t+ 194.5s ♪ 音效 sfx/side_wall_l +t+ 194.5s ♪ 音效 sfx/side_wall_r +t+ 194.6s ♪ 音效 sfx/mat_stone +t+ 196.5s ♪ 音效 sfx/mob_step_bug_c +t+ 196.5s ♪ 音效 sfx/step_turn +t+ 197.9s ♪ 音效 sfx/step_own +t+ 197.9s 〰 震动:swipe(滑动识别) +t+ 198.4s ♪ 音效 sfx/step_own +t+ 198.4s 〰 震动:swipe(滑动识别) +t+ 198.6s ♪ 音效 sfx/mob_step_bug_r + +#### 【玩家动作】手痒连点三下脉冲 + +t+ 198.9s ♪ 音效 sfx/pulse +t+ 198.9s 〰 震动:error(做错了:两连短震) +t+ 198.9s 〰 震动:error(做错了:两连短震) +t+ 199.2s ♪ 音效 sfx/side_wall_l +t+ 199.2s ♪ 音效 sfx/side_wall_r +t+ 199.7s ♪ 音效 sfx/echo_3 +t+ 199.8s ♪ 音效 sfx/mat_stone +t+ 200.7s ♪ 音效 sfx/mob_step_bug_r +t+ 200.7s ♪ 音效 sfx/pulse +t+ 201.1s ♪ 音效 sfx/side_wall_l +t+ 201.1s ♪ 音效 sfx/side_wall_r +t+ 201.5s ♪ 音效 sfx/echo_3 +t+ 201.6s ♪ 音效 sfx/mat_stone +t+ 202.2s ♪ 音效 sfx/mob_step_bug_r +t+ 202.9s ♪ 音效 sfx/step_turn +t+ 204.3s ♪ 音效 sfx/mob_step_bug_c +t+ 204.3s 〰 震动:heartbeat(心跳/倒计时) +t+ 204.3s ♪ 音效 sfx/step_own +t+ 204.3s 〰 震动:swipe(滑动识别) +t+ 204.8s ♪ 音效 sfx/step_own +t+ 204.8s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 204.8s ♪ 向导音:battle(战斗/对抗) +t+ 204.8s ▶ 语音:「蘑菇怪拦住了你!」 +t+ 204.8s 〰 震动:swipe(滑动识别) +t+ 205.3s 【状态】乱打:不格挡、逃不掉(Boss),一直挥拳 战斗播报中,先听着(语音播放中,语速1) +t+ 206.9s ♪ 向导音:turn(轮到你) +t+ 206.9s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 206.9s ♪ 音效 sfx/atk_swing +t+ 206.9s ♪ 音效 sfx/atk_hit +t+ 206.9s ♪ 音效 sfx/tel_bug +t+ 206.9s 〰 震动:beat(节拍点) +t+ 208.1s ♪ 音效 sfx/hurt +t+ 208.1s ♪ 向导音:danger(危险) +t+ 208.1s ♪ 向导音:turn(轮到你) +t+ 208.1s 〰 震动:hit(受击重震) +t+ 208.1s 〰 震动:warning(警告三连震) +t+ 208.1s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 214.8s ♪ 音效 sfx/atk_swing +t+ 214.8s ♪ 音效 sfx/atk_hit +t+ 214.8s ♪ 音效 sfx/tel_bug +t+ 214.8s 〰 震动:beat(节拍点) +t+ 216.0s ♪ 音效 sfx/hurt +t+ 216.0s ♪ 向导音:turn(轮到你) +t+ 216.0s 〰 震动:hit(受击重震) +t+ 216.0s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + +t+ 231.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 231.0s ▶ 向导语音:「轮到你了。单击挥击。」 + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 231.0s ♪ 音效 sfx/atk_swing +t+ 231.0s ♪ 音效 sfx/atk_hit +t+ 231.0s ♪ 音效 sfx/tel_bug +t+ 231.0s 〰 震动:beat(节拍点) +t+ 232.2s ♪ 音效 sfx/hurt +t+ 232.2s ♪ 向导音:turn(轮到你) +t+ 232.2s 〰 震动:hit(受击重震) +t+ 232.2s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 233.1s ♪ 音效 sfx/atk_swing +t+ 233.1s ♪ 音效 sfx/atk_hit +t+ 233.1s ♪ 音效 sfx/enemy_die +t+ 233.1s ♪ 音效 sfx/coin +t+ 233.1s ▶ 语音:「它倒下了。」 +t+ 233.1s 〰 震动:success(成功长震) +t+ 235.3s ▶ 语音:「小有收获。」 +t+ 237.4s ▶ 语音:「你的拳脚更利落了。」 +t+ 239.5s ♫ BGM 切换 → bgm/explore +t+ 239.5s ♪ 向导音:explore(探索/安全) + +#### 【玩家动作】手痒连点三下脉冲 + +t+ 239.5s ♪ 音效 sfx/pulse +t+ 239.5s 〰 震动:error(做错了:两连短震) +t+ 239.5s 〰 震动:error(做错了:两连短震) +t+ 239.7s ♪ 音效 sfx/echo_1 +t+ 239.8s ♪ 音效 sfx/side_wall_l +t+ 239.8s ♪ 音效 sfx/side_wall_r +t+ 239.9s ♪ 音效 sfx/mat_stair + +#### 【玩家动作】手痒连点三下脉冲 + +t+ 244.5s ♪ 音效 sfx/pulse +t+ 244.5s 〰 震动:error(做错了:两连短震) +t+ 244.5s 〰 震动:error(做错了:两连短震) +t+ 244.7s ♪ 音效 sfx/echo_1 +t+ 244.8s ♪ 音效 sfx/side_wall_l +t+ 244.8s ♪ 音效 sfx/side_wall_r +t+ 244.9s ♪ 音效 sfx/mat_stair +t+ 249.5s ♪ 音效 sfx/step_turn +t+ 254.5s ♪ 音效 sfx/pulse +t+ 254.8s ♪ 音效 sfx/side_wall_l +t+ 254.8s ♪ 音效 sfx/side_wall_r +t+ 255.0s ♪ 音效 sfx/echo_2 +t+ 255.1s ♪ 音效 sfx/mat_stone +t+ 259.5s ♪ 音效 sfx/step_turn +t+ 264.5s ♪ 音效 sfx/step_own +t+ 264.5s ♫ BGM 切换 → bgm/boss(淡入 400ms) +t+ 264.5s ♪ 向导音:battle(战斗/对抗) +t+ 264.5s ▶ 语音:「窟主石甲蟹,就在你面前。」 +t+ 264.5s 〰 震动:swipe(滑动识别) +t+ 266.6s ♪ 向导音:turn(轮到你) +t+ 266.6s 〰 震动:confirm(轻点确认) +t+ 266.6s 【状态】乱打:不格挡、逃不掉(Boss),一直挥拳 战斗中:你的血量命悬一线,等你出手(单击挥击,按住屏幕逃跑)(BGM=bgm/boss,语速1) + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 266.6s ♪ 音效 sfx/atk_swing +t+ 266.6s ♪ 音效 sfx/atk_hit +t+ 266.6s ♪ 音效 sfx/tel_heavy +t+ 266.6s 〰 震动:beat(节拍点) +t+ 267.8s ♪ 音效 sfx/hurt +t+ 267.8s ♫ BGM 切换 → bgm/menu +t+ 267.8s ♪ 向导音:settle(结算) +t+ 267.8s ▶ 语音:「你倒下了。」 +t+ 267.8s 〰 震动:hit(受击重震) +t+ 267.8s 〰 震动:hit(受击重震) +t+ 267.8s 【状态】乱打也被归因得明明白白,元进度照单全收 结算:死亡归因与元进度入账,单击再探一次(语音播放中,语速1) +t+ 269.9s ▶ 语音:「你没挡住那记重击。」 +t+ 272.1s ▶ 语音:「下次,前摇声响时就双击。」 +t+ 272.1s ♪ 向导音:menu(系统界面) +t+ 272.1s ▶ 语音:「这趟没带回什么。」 +t+ 274.2s ▶ 语音:「你到过中层。」 +t+ 274.2s ▶ 语音:「新的探窟,开始了。」 +t+ 276.3s ♫ BGM 切换 → bgm/explore +t+ 276.3s ♪ 向导音:explore(探索/安全) +t+ 276.3s ▶ 语音:「这是第一层。」 +t+ 276.3s 【状态】第二周目照样开局,压力测试全程零死锁,收工 第 1 层黑暗里:单击发脉冲用回声看路,按住前进,左右滑转身,上滑状态,下滑菜单(语音播放中,语速1) diff --git a/research/new8/dungeon/round2/transcript-main.md b/research/new8/dungeon/round2/transcript-main.md new file mode 100644 index 00000000..1ab4a285 --- /dev/null +++ b/research/new8/dungeon/round2/transcript-main.md @@ -0,0 +1,867 @@ +# 试玩转写:《dungeon》 + +> 本文件是「玩家感知日志」:一位新手玩家的整局体验转写。语音列出了玩家听到的每句话全文, +> 音效/BGM/震动按发生时间列出。「静默」段 = 玩家什么也听不到。手势是剧本注入的(模拟真人手部动作), +> 非最优操作:包含犹豫、误操作与乱试。语音时长按语速估算(±20%),仅供节奏参考。 +t+ 0.0s (玩家点开游戏,轻触屏幕开始) +t+ 0.0s ♫ BGM 切换 → bgm/menu +t+ 0.0s ♪ 向导音:menu(系统界面) +t+ 0.0s 【状态】盲眼探窟者睁开了耳朵,准备下窟 标题页:单击开始探窟,上滑听元进度(BGM=bgm/menu,语速1) + +#### 【玩家动作】单击,开始 + +t+ 0.0s ▶ 语音:「新的探窟,开始了。」 +t+ 2.1s ♫ BGM 切换 → bgm/explore +t+ 2.1s ♪ 向导音:explore(探索/安全) +t+ 2.1s ▶ 语音:「你在黑暗中醒来。这里是回声地窟。」 +t+ 2.1s 【状态】教学开始:三句开场白 教学演练第 0 步,跟着旁白做(语音播放中,语速1) +t+ 4.2s ▶ 语音:「伸手不见五指,声音就是你的眼睛。」 +t+ 6.4s ▶ 语音:「先学会用耳朵看。单击,发出声波脉冲。」 + +#### 【玩家动作】单击回应「先学会用耳朵看」 + + +#### 【玩家动作】单击——人生第一声脉冲 + +t+ 8.5s ♪ 音效 sfx/pulse +t+ 8.5s ▶ 语音:「听,回声回来了。」 +t+ 8.8s ♪ 音效 sfx/side_open_l +t+ 8.8s ♪ 音效 sfx/side_open_r +t+ 9.0s ♪ 音效 sfx/echo_2 +t+ 9.1s ♪ 音效 sfx/mat_stone +t+ 10.6s ▶ 语音:「回声回来越快,墙就越近。」 +t+ 10.6s 【状态】回声回来了,听墙的距离 教学演练第 2 步,跟着旁白做(语音播放中,语速1) + +#### 【玩家动作】单击继续 + +t+ 12.7s ▶ 语音:「两侧的回声,发闷是墙,空旷是通路。」 + +#### 【玩家动作】单击继续(侧向规则听完了) + +t+ 14.8s ♪ 音效 sfx/step_turn +t+ 14.8s ▶ 语音:「按住屏幕不放,你就往前走。」 + +#### 【玩家动作】按住屏幕不放——脚步声起,往前走 + +t+ 14.8s ♪ 音效 sfx/step_own +t+ 14.8s 〰 震动:swipe(滑动识别) +t+ 15.3s ♪ 音效 sfx/step_own +t+ 15.3s ▶ 语音:「左滑右滑,可以转身。」 +t+ 15.8s ♪ 音效 sfx/bump +t+ 15.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】松开,停下 + + +#### 【玩家动作】左滑,转身 + +t+ 16.5s ♪ 音效 sfx/step_turn + +#### 【玩家动作】右滑,转回来 + +t+ 17.5s ♪ 音效 sfx/step_turn +t+ 17.5s ▶ 语音:「双击,再发一次脉冲,不要钱。」 + +#### 【玩家动作】双击——再发一次脉冲,不要钱 + +t+ 17.5s ♪ 音效 sfx/pulse +t+ 17.5s ▶ 语音:「听,回声回来了。」 +t+ 17.7s ♪ 音效 sfx/echo_1 +t+ 17.8s ♪ 音效 sfx/side_wall_l +t+ 17.8s ♪ 音效 sfx/side_wall_r +t+ 17.8s ♪ 音效 sfx/mat_stone +t+ 19.6s ▶ 语音:「上滑,随时听自己的状态。」 +t+ 21.7s ▶ 语音:「下滑,打开菜单。」 + +#### 【玩家动作】上滑,听自己的状态 + +t+ 21.7s ▶ 语音:「这是第一层。」 +t+ 23.8s ▶ 语音:「你毫发无损。」 +t+ 25.9s ▶ 语音:「口袋空空。」 +t+ 28.1s ▶ 语音:「你面朝东。」 + +#### 【玩家动作】下滑,打开菜单 + +t+ 28.1s ♫ BGM 切换 → bgm/menu +t+ 28.1s ♪ 向导音:menu(系统界面) +t+ 28.1s ▶ 语音:「菜单。单击切换,双击确认。」 +t+ 30.2s ▶ 语音:「返回探索。」 + +#### 【玩家动作】再下滑,关上菜单 + +t+ 30.2s ♪ 向导音:explore(探索/安全) +t+ 30.2s ♫ BGM 切换 → bgm/explore +t+ 30.2s ♪ 音效 sfx/step_turn +t+ 30.2s ♪ 音效 sfx/step_turn +t+ 30.2s ▶ 语音:「前面有东西!放心,它伤不到你。」 +t+ 30.2s 【状态】「前面有东西!放心,它伤不到你」——等它扑上来 教学演练第 8 步,跟着旁白做(语音播放中,语速1) +t+ 31.4s ♪ 音效 sfx/mob_step_small_c +t+ 32.6s ♪ 音效 sfx/mob_step_small_c +t+ 32.6s 〰 震动:heartbeat(心跳/倒计时) +t+ 33.5s ♪ 音效 sfx/mob_step_small_c +t+ 33.5s 〰 震动:heartbeat(心跳/倒计时) +t+ 33.8s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 33.8s ♪ 向导音:battle(战斗/对抗) +t+ 33.8s ▶ 语音:「盲眼蝠扑了过来!」 +t+ 33.8s 【状态】教学怪扑到了面前 战斗播报中,先听着(语音播放中,语速1) +t+ 35.9s ▶ 语音:「听到它的动静,双击格挡。」 +t+ 38.0s ♪ 音效 sfx/tel_small +t+ 38.0s 〰 震动:beat(节拍点) + +#### 【玩家动作】听到它的动静——双击,格挡 + +t+ 38.0s ♪ 音效 sfx/parry +t+ 38.0s 〰 震动:confirm(轻点确认) +t+ 39.2s ▶ 语音:「挡住了!」 +t+ 41.3s ▶ 语音:「现在单击,挥拳打它。」 +t+ 43.5s ♪ 向导音:turn(轮到你) +t+ 43.5s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击挥拳,两拳放倒它 + +t+ 43.5s ♪ 音效 sfx/atk_swing +t+ 43.5s ♪ 音效 sfx/atk_hit +t+ 43.5s ♪ 音效 sfx/tel_small +t+ 43.5s 〰 震动:beat(节拍点) +t+ 43.9s ♪ 音效 sfx/parry +t+ 43.9s 〰 震动:confirm(轻点确认) +t+ 44.7s ▶ 语音:「挡住了!」 +t+ 46.8s ▶ 语音:「现在单击,挥拳打它。」 +t+ 48.9s ♪ 向导音:turn(轮到你) +t+ 48.9s 〰 震动:confirm(轻点确认) +t+ 48.9s ♪ 音效 sfx/atk_swing +t+ 48.9s ♪ 音效 sfx/atk_hit +t+ 48.9s ♪ 音效 sfx/enemy_die +t+ 48.9s ♪ 音效 sfx/coin +t+ 48.9s ▶ 语音:「打中了,就是这样。」 +t+ 48.9s 〰 震动:success(成功长震) +t+ 51.0s ▶ 语音:「战利不多。」 +t+ 53.1s ♫ BGM 切换 → bgm/explore(淡入 400ms) +t+ 53.1s ▶ 语音:「铁器的回声,就是下潜的楼梯。」 +t+ 53.1s 【状态】教学战获胜——「打中了,就是这样」 教学演练第 9 步,跟着旁白做(语音播放中,语速1) + +#### 【玩家动作】单击,听听楼梯的铁器回声 + +t+ 53.1s ♪ 音效 sfx/pulse +t+ 53.5s ♪ 音效 sfx/side_wall_l +t+ 53.5s ♪ 音效 sfx/side_wall_r +t+ 54.2s ♪ 音效 sfx/echo_4 +t+ 54.4s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 56.7s ♪ 音效 sfx/pulse +t+ 57.0s ♪ 音效 sfx/side_wall_l +t+ 57.0s ♪ 音效 sfx/side_wall_r +t+ 57.8s ♪ 音效 sfx/echo_4 +t+ 57.9s ♪ 音效 sfx/mat_stone +t+ 60.9s ♪ 音效 sfx/step_own +t+ 60.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 61.1s ♪ 音效 sfx/pulse +t+ 61.5s ♪ 音效 sfx/side_open_l +t+ 61.5s ♪ 音效 sfx/side_open_r +t+ 62.2s ♪ 音效 sfx/echo_4 +t+ 62.4s ♪ 音效 sfx/mat_stone +t+ 69.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 69.1s ♪ 向导音:watchdog(看门狗升级) +t+ 69.1s ♪ 音效 sfx/step_turn +t+ 77.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 77.1s ♪ 向导音:watchdog(看门狗升级) +t+ 77.1s ♪ 音效 sfx/step_own +t+ 77.1s ♪ 音效 sfx/coin +t+ 77.1s ▶ 语音:「捡到一大袋金币。」 +t+ 77.1s 〰 震动:swipe(滑动识别) +t+ 77.1s 〰 震动:pickup(获得) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 77.6s ♪ 音效 sfx/pulse +t+ 77.9s ♪ 音效 sfx/echo_1 +t+ 78.0s ♪ 音效 sfx/side_wall_l +t+ 78.0s ♪ 音效 sfx/side_open_r +t+ 78.0s ♪ 音效 sfx/mat_stone +t+ 85.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 85.6s ♪ 向导音:watchdog(看门狗升级) +t+ 85.6s ♪ 音效 sfx/step_turn +t+ 93.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 93.6s ♪ 向导音:watchdog(看门狗升级) +t+ 93.6s ♪ 音效 sfx/step_own +t+ 93.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 94.1s ♪ 音效 sfx/pulse +t+ 94.5s ♪ 音效 sfx/side_open_l +t+ 94.5s ♪ 音效 sfx/side_open_r +t+ 94.9s ♪ 音效 sfx/echo_3 +t+ 95.1s ♪ 音效 sfx/mat_stone +t+ 102.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 102.1s ♪ 向导音:watchdog(看门狗升级) +t+ 102.1s ♪ 音效 sfx/step_turn +t+ 110.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 110.1s ♪ 向导音:watchdog(看门狗升级) +t+ 110.1s ♪ 音效 sfx/step_own +t+ 110.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 110.6s ♪ 音效 sfx/pulse +t+ 110.9s ♪ 音效 sfx/echo_1 +t+ 111.0s ♪ 音效 sfx/side_wall_l +t+ 111.0s ♪ 音效 sfx/side_open_r +t+ 111.0s ♪ 音效 sfx/mat_stone +t+ 118.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 118.6s ♪ 向导音:watchdog(看门狗升级) +t+ 118.6s ♪ 音效 sfx/step_turn +t+ 126.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 126.6s ♪ 向导音:watchdog(看门狗升级) +t+ 126.6s ♪ 音效 sfx/step_own +t+ 126.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 127.1s ♪ 音效 sfx/pulse +t+ 127.5s ♪ 音效 sfx/side_open_l +t+ 127.5s ♪ 音效 sfx/side_open_r +t+ 127.9s ♪ 音效 sfx/echo_3 +t+ 128.1s ♪ 音效 sfx/mat_stone +t+ 135.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 135.1s ♪ 向导音:watchdog(看门狗升级) +t+ 135.1s ♪ 音效 sfx/step_own +t+ 135.1s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 135.6s ♪ 音效 sfx/pulse +t+ 136.0s ♪ 音效 sfx/side_open_l +t+ 136.0s ♪ 音效 sfx/side_open_r +t+ 136.1s ♪ 音效 sfx/echo_2 +t+ 136.3s ♪ 音效 sfx/mat_stone +t+ 143.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 143.6s ♪ 向导音:watchdog(看门狗升级) +t+ 143.6s ♪ 音效 sfx/step_turn +t+ 151.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 151.6s ♪ 向导音:watchdog(看门狗升级) +t+ 151.6s ♪ 音效 sfx/step_own +t+ 151.6s ▶ 语音:「楼梯就在脚下。单击,下潜。」 +t+ 151.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】踏上楼梯格——「楼梯就在脚下」 + + +#### 【玩家动作】铁器就在脚下——单击,下潜 + +t+ 152.1s ♪ 音效 sfx/stairs_down +t+ 152.1s ▶ 语音:「你顺着楼梯,往下潜。」 +t+ 154.3s ▶ 语音:「教学完了。地窟很深,小心走。」 +t+ 156.4s ▶ 语音:「这是第二层。」 +t+ 158.5s ♪ 向导音:explore(探索/安全) +t+ 158.5s 【状态】教学完了。第二层,真正的地窟从这里开始 第 2 层黑暗里:单击发脉冲用回声看路,按住前进,左右滑转身,上滑状态,下滑菜单(BGM=bgm/explore,语速1) + +#### 【玩家动作】单击脉冲,探一探第 2 层 + +t+ 158.5s ♪ 音效 sfx/pulse +t+ 158.8s ♪ 音效 sfx/side_open_l +t+ 158.8s ♪ 音效 sfx/side_wall_r +t+ 159.3s ♪ 音效 sfx/echo_3 +t+ 159.4s ♪ 音效 sfx/mat_stone +t+ 160.9s ♪ 音效 sfx/mob_step_small_l + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 160.9s ♪ 音效 sfx/pulse +t+ 160.9s ♪ 音效 sfx/mob_step_small_l +t+ 160.9s ♪ 音效 sfx/mob_step_small_l +t+ 161.3s ♪ 音效 sfx/side_open_l +t+ 161.3s ♪ 音效 sfx/side_wall_r +t+ 161.7s ♪ 音效 sfx/echo_3 +t+ 161.8s ♪ 音效 sfx/mat_stone +t+ 162.5s ♪ 音效 sfx/mob_step_small_l +t+ 162.6s ♪ 音效 sfx/mob_step_small_l +t+ 162.9s ♪ 音效 sfx/mob_step_small_l +t+ 162.5s 〰 震动:heartbeat(心跳/倒计时) +t+ 163.5s ♪ 音效 sfx/step_turn +t+ 164.1s ♪ 音效 sfx/mob_step_small_c +t+ 164.1s ♪ 音效 sfx/step_own +t+ 164.1s 〰 震动:swipe(滑动识别) +t+ 164.1s ♪ 音效 sfx/mob_step_small_c +t+ 164.4s ♪ 音效 sfx/mob_step_small_c +t+ 164.4s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 164.4s ♪ 向导音:battle(战斗/对抗) +t+ 164.4s ▶ 语音:「窟鼠窜了出来!」 +t+ 164.4s 〰 震动:heartbeat(心跳/倒计时) +t+ 166.6s ♪ 向导音:turn(轮到你) +t+ 166.6s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 166.6s ♪ 音效 sfx/atk_swing +t+ 166.6s ♪ 音效 sfx/atk_hit +t+ 166.6s ♪ 音效 sfx/tel_small +t+ 166.6s 〰 震动:beat(节拍点) +t+ 167.8s ♪ 音效 sfx/hurt +t+ 167.8s ♪ 向导音:turn(轮到你) +t+ 167.8s 〰 震动:hit(受击重震) +t+ 167.8s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 167.8s ♪ 音效 sfx/atk_swing +t+ 167.8s ♪ 音效 sfx/atk_hit +t+ 167.8s ♪ 音效 sfx/tel_small +t+ 167.8s 〰 震动:beat(节拍点) + +#### 【玩家动作】前摇声响——双击,格挡 + +t+ 168.5s ♪ 音效 sfx/parry +t+ 168.5s 〰 震动:confirm(轻点确认) +t+ 169.0s ▶ 语音:「挡下了大半!」 +t+ 171.1s ♪ 向导音:turn(轮到你) +t+ 171.1s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 171.1s ♪ 音效 sfx/atk_swing +t+ 171.1s ♪ 音效 sfx/atk_hit +t+ 171.1s ♪ 音效 sfx/enemy_die +t+ 171.1s ♪ 音效 sfx/coin +t+ 171.1s ▶ 语音:「它倒下了。」 +t+ 171.1s 〰 震动:success(成功长震) +t+ 173.2s ▶ 语音:「战利不多。」 +t+ 175.3s ♫ BGM 切换 → bgm/explore +t+ 175.3s ♪ 向导音:explore(探索/安全) +t+ 177.3s ♪ 音效 sfx/mob_step_small_c +t+ 177.4s ♪ 音效 sfx/mob_step_small_c +t+ 177.4s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 177.4s ♪ 向导音:battle(战斗/对抗) +t+ 177.4s ▶ 语音:「窟鼠窜了出来!」 +t+ 177.4s 〰 震动:heartbeat(心跳/倒计时) +t+ 179.5s ♪ 向导音:turn(轮到你) +t+ 179.5s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 179.5s ♪ 音效 sfx/atk_swing +t+ 179.5s ♪ 音效 sfx/atk_hit +t+ 179.5s ♪ 音效 sfx/tel_small +t+ 179.5s 〰 震动:beat(节拍点) +t+ 180.7s ♪ 音效 sfx/hurt +t+ 180.7s ♪ 向导音:turn(轮到你) +t+ 180.7s 〰 震动:hit(受击重震) +t+ 180.7s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 180.7s ♪ 音效 sfx/atk_swing +t+ 180.7s ♪ 音效 sfx/atk_hit +t+ 180.7s ♪ 音效 sfx/tel_small +t+ 180.7s 〰 震动:beat(节拍点) + +#### 【玩家动作】前摇声响——双击,格挡 + +t+ 181.5s ♪ 音效 sfx/parry +t+ 181.5s 〰 震动:confirm(轻点确认) +t+ 181.9s ▶ 语音:「挡下了大半!」 +t+ 184.0s ♪ 向导音:turn(轮到你) +t+ 184.0s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 184.0s ♪ 音效 sfx/atk_swing +t+ 184.0s ♪ 音效 sfx/atk_hit +t+ 184.0s ♪ 音效 sfx/enemy_die +t+ 184.0s ♪ 音效 sfx/coin +t+ 184.0s ▶ 语音:「它倒下了。」 +t+ 184.0s 〰 震动:success(成功长震) +t+ 186.1s ▶ 语音:「战利不多。」 +t+ 188.2s ♫ BGM 切换 → bgm/explore +t+ 188.2s ♪ 向导音:explore(探索/安全) +t+ 190.1s ♪ 音效 sfx/mob_step_small_c +t+ 192.2s ♪ 音效 sfx/mob_step_small_c + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 192.2s ♪ 音效 sfx/pulse +t+ 192.5s ♪ 音效 sfx/side_open_l +t+ 192.5s ♪ 音效 sfx/side_open_r +t+ 193.3s ♪ 音效 sfx/echo_4 +t+ 193.4s ♪ 音效 sfx/mat_stone +t+ 194.2s ♪ 音效 sfx/mob_step_small_c +t+ 194.2s ♪ 音效 sfx/step_turn +t+ 196.5s ♪ 音效 sfx/mob_step_small_r +t+ 196.5s 〰 震动:heartbeat(心跳/倒计时) +t+ 196.5s ♪ 音效 sfx/step_own +t+ 196.5s ♪ 音效 sfx/coin +t+ 196.5s ▶ 语音:「捡到一大袋金币。」 +t+ 196.5s 〰 震动:swipe(滑动识别) +t+ 196.5s 〰 震动:pickup(获得) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 197.0s ♪ 音效 sfx/pulse +t+ 197.3s ♪ 音效 sfx/echo_1 +t+ 197.4s ♪ 音效 sfx/side_open_l +t+ 197.4s ♪ 音效 sfx/side_open_r +t+ 197.4s ♪ 音效 sfx/mat_stone +t+ 198.3s ♪ 音效 sfx/mob_step_small_r +t+ 198.3s 〰 震动:heartbeat(心跳/倒计时) +t+ 200.5s ♪ 音效 sfx/mob_step_small_r +t+ 200.5s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 200.5s ♪ 向导音:battle(战斗/对抗) +t+ 200.5s ▶ 语音:「窟鼠窜了出来!」 +t+ 200.5s 〰 震动:heartbeat(心跳/倒计时) +t+ 202.7s ♪ 向导音:turn(轮到你) +t+ 202.7s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 204.6s ♪ 音效 sfx/atk_swing +t+ 204.6s ♪ 音效 sfx/atk_hit +t+ 204.6s ♪ 音效 sfx/tel_small +t+ 204.6s 〰 震动:beat(节拍点) +t+ 205.8s ♪ 音效 sfx/hurt +t+ 205.8s ♪ 向导音:turn(轮到你) +t+ 205.8s 〰 震动:hit(受击重震) +t+ 205.8s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 205.8s ♪ 音效 sfx/atk_swing +t+ 205.8s ♪ 音效 sfx/atk_hit +t+ 205.8s ♪ 音效 sfx/tel_small +t+ 205.8s 〰 震动:beat(节拍点) + +#### 【玩家动作】前摇声响——双击,格挡 + +t+ 206.6s ♪ 音效 sfx/parry +t+ 206.6s 〰 震动:confirm(轻点确认) +t+ 207.0s ▶ 语音:「挡下了大半!」 +t+ 209.1s ♪ 向导音:turn(轮到你) +t+ 209.1s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 209.1s ♪ 音效 sfx/atk_swing +t+ 209.1s ♪ 音效 sfx/atk_hit +t+ 209.1s ♪ 音效 sfx/enemy_die +t+ 209.1s ♪ 音效 sfx/coin +t+ 209.1s ▶ 语音:「它倒下了。」 +t+ 209.1s 〰 震动:success(成功长震) +t+ 211.2s ▶ 语音:「战利不多。」 +t+ 213.3s ▶ 语音:「你的拳脚更利落了。」 +t+ 215.5s ♫ BGM 切换 → bgm/explore +t+ 215.5s ♪ 向导音:explore(探索/安全) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 220.5s ♪ 音效 sfx/pulse +t+ 220.7s ♪ 音效 sfx/echo_1 +t+ 220.8s ♪ 音效 sfx/side_open_l +t+ 220.8s ♪ 音效 sfx/side_open_r +t+ 220.8s ♪ 音效 sfx/mat_stone +t+ 225.5s ♪ 音效 sfx/step_turn +t+ 230.5s ♪ 音效 sfx/step_own +t+ 230.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 231.0s ♪ 音效 sfx/pulse +t+ 231.3s ♪ 音效 sfx/side_wall_l +t+ 231.3s ♪ 音效 sfx/side_open_r +t+ 231.5s ♪ 音效 sfx/echo_2 +t+ 231.6s ♪ 音效 sfx/mat_stair +t+ 235.5s ♪ 音效 sfx/step_own +t+ 235.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 236.0s ♪ 音效 sfx/pulse +t+ 236.2s ♪ 音效 sfx/echo_1 +t+ 236.3s ♪ 音效 sfx/side_wall_l +t+ 236.3s ♪ 音效 sfx/side_open_r +t+ 236.3s ♪ 音效 sfx/mat_stair +t+ 240.5s ♪ 音效 sfx/step_own +t+ 240.5s ▶ 语音:「楼梯就在脚下。单击,下潜。」 +t+ 240.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】铁器就在脚下——单击,下潜 + +t+ 241.0s ♪ 音效 sfx/stairs_down +t+ 241.0s ▶ 语音:「你顺着楼梯,往下潜。」 +t+ 243.1s ▶ 语音:「这是第三层。」 +t+ 243.1s 【状态】第三层——窟主的地界 顺着楼梯,往下潜……(语音播放中,语速1) + +#### 【玩家动作】脉冲听路,朝楼梯摸过去 + +t+ 245.2s ♪ 向导音:explore(探索/安全) +t+ 247.0s ♪ 音效 sfx/mob_step_bug_r + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 247.0s ♪ 音效 sfx/pulse +t+ 247.2s ♪ 音效 sfx/mob_step_bug_r +t+ 247.3s ♪ 音效 sfx/side_open_l +t+ 247.3s ♪ 音效 sfx/side_open_r +t+ 247.5s ♪ 音效 sfx/echo_2 +t+ 247.6s ♪ 音效 sfx/mat_stone +t+ 247.6s ♪ 音效 sfx/mob_step_bug_r +t+ 249.2s ♪ 音效 sfx/mob_step_bug_r +t+ 249.2s ♪ 音效 sfx/step_turn +t+ 249.5s ♪ 音效 sfx/mob_step_bug_r +t+ 249.5s ♪ 音效 sfx/step_turn +t+ 250.0s ♪ 音效 sfx/mob_step_bug_l +t+ 250.0s ♪ 音效 sfx/step_own +t+ 250.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 250.5s ♪ 音效 sfx/pulse +t+ 250.9s ♪ 音效 sfx/side_wall_l +t+ 250.9s ♪ 音效 sfx/side_wall_r +t+ 251.1s ♪ 音效 sfx/mob_step_bug_c +t+ 251.1s ♪ 音效 sfx/mob_step_bug_c +t+ 251.6s ♪ 音效 sfx/echo_4 +t+ 251.7s ♪ 音效 sfx/mat_stone +t+ 251.8s ♪ 音效 sfx/mob_step_bug_l +t+ 252.6s ♪ 音效 sfx/mob_step_bug_r +t+ 252.6s ♪ 音效 sfx/step_own +t+ 252.6s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 253.1s ♪ 音效 sfx/pulse +t+ 253.2s ♪ 音效 sfx/mob_step_bug_l +t+ 253.4s ♪ 音效 sfx/side_open_l +t+ 253.4s ♪ 音效 sfx/side_wall_r +t+ 254.2s ♪ 音效 sfx/mob_step_bug_l +t+ 254.2s ♪ 音效 sfx/echo_4 +t+ 254.3s ♪ 音效 sfx/mat_stone +t+ 254.4s ♪ 音效 sfx/mob_step_bug_r +t+ 255.2s ♪ 音效 sfx/step_turn +t+ 255.3s ♪ 音效 sfx/mob_step_bug_c +t+ 256.0s ♪ 音效 sfx/mob_step_bug_c +t+ 256.0s ♪ 音效 sfx/step_own +t+ 256.0s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 256.5s ♪ 音效 sfx/pulse +t+ 256.6s ♪ 音效 sfx/mob_step_bug_c +t+ 256.9s ♪ 音效 sfx/side_wall_l +t+ 256.9s ♪ 音效 sfx/side_open_r +t+ 257.0s ♪ 音效 sfx/echo_2 +t+ 257.1s ♪ 音效 sfx/mat_stone +t+ 257.5s ♪ 音效 sfx/mob_step_bug_r +t+ 257.7s ♪ 音效 sfx/mob_step_bug_c +t+ 258.1s ♪ 音效 sfx/mob_step_bug_c +t+ 259.2s ♪ 音效 sfx/mob_step_bug_c +t+ 259.2s ♪ 音效 sfx/step_own +t+ 259.2s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 259.7s ♪ 音效 sfx/pulse +t+ 260.0s ♪ 音效 sfx/echo_1 +t+ 260.1s ♪ 音效 sfx/side_wall_l +t+ 260.1s ♪ 音效 sfx/side_open_r +t+ 260.1s ♪ 音效 sfx/mat_stone +t+ 260.2s ♪ 音效 sfx/mob_step_bug_c +t+ 260.4s ♪ 音效 sfx/mob_step_bug_c +t+ 261.6s ♪ 音效 sfx/mob_step_bug_r +t+ 261.6s 〰 震动:heartbeat(心跳/倒计时) +t+ 262.0s ♪ 音效 sfx/mob_step_bug_c +t+ 262.0s ♪ 音效 sfx/step_turn +t+ 262.3s ♪ 音效 sfx/mob_step_bug_r +t+ 262.3s ♪ 音效 sfx/step_own +t+ 262.3s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 262.8s ♪ 音效 sfx/pulse +t+ 263.2s ♪ 音效 sfx/side_open_l +t+ 263.2s ♪ 音效 sfx/side_open_r +t+ 263.6s ♪ 音效 sfx/echo_3 +t+ 263.7s ♪ 音效 sfx/mat_stone +t+ 264.0s ♪ 音效 sfx/mob_step_bug_l +t+ 264.0s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 264.0s ♪ 向导音:battle(战斗/对抗) +t+ 264.0s ▶ 语音:「蘑菇怪拦住了你!」 +t+ 266.1s ♪ 向导音:turn(轮到你) +t+ 266.1s 〰 震动:confirm(轻点确认) +t+ 266.1s ▶ 语音:「它贴着你不放,不用转身。」 +t+ 266.1s 〰 震动:error(做错了:两连短震) + +#### 【玩家动作】单击,挥击 + +t+ 266.3s ♪ 音效 sfx/atk_swing +t+ 266.3s ♪ 音效 sfx/atk_hit +t+ 266.3s ♪ 音效 sfx/tel_bug +t+ 266.3s 〰 震动:beat(节拍点) +t+ 267.5s ♪ 音效 sfx/hurt +t+ 267.5s ♪ 向导音:turn(轮到你) +t+ 267.5s 〰 震动:hit(受击重震) +t+ 267.5s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 267.5s ♪ 音效 sfx/atk_swing +t+ 267.5s ♪ 音效 sfx/atk_hit +t+ 267.5s ♪ 音效 sfx/tel_bug +t+ 267.5s 〰 震动:beat(节拍点) + +#### 【玩家动作】前摇声响——双击,格挡 + +t+ 268.2s ♪ 音效 sfx/parry +t+ 268.2s 〰 震动:confirm(轻点确认) +t+ 268.7s ▶ 语音:「挡下了大半!」 +t+ 270.9s ♪ 向导音:turn(轮到你) +t+ 270.9s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 272.4s ♪ 音效 sfx/atk_swing +t+ 272.4s ♪ 音效 sfx/atk_hit +t+ 272.4s ♪ 音效 sfx/enemy_die +t+ 272.4s ♪ 音效 sfx/coin +t+ 272.4s ▶ 语音:「它倒下了。」 +t+ 272.4s 〰 震动:success(成功长震) +t+ 274.5s ▶ 语音:「小有收获。」 +t+ 276.6s ♫ BGM 切换 → bgm/explore +t+ 276.6s ♪ 向导音:explore(探索/安全) +t+ 278.8s ♪ 音效 sfx/mob_step_bug_l +t+ 278.9s ♪ 音效 sfx/mob_step_bug_r +t+ 280.9s ♪ 音效 sfx/mob_step_bug_l + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 280.9s ♪ 音效 sfx/pulse +t+ 281.1s ♪ 音效 sfx/mob_step_bug_r +t+ 281.3s ♪ 音效 sfx/side_open_l +t+ 281.3s ♪ 音效 sfx/side_open_r +t+ 281.7s ♪ 音效 sfx/echo_3 +t+ 281.8s ♪ 音效 sfx/mat_stone +t+ 281.1s 〰 震动:heartbeat(心跳/倒计时) +t+ 282.8s ♪ 音效 sfx/mob_step_bug_r +t+ 282.8s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 282.8s ♪ 向导音:battle(战斗/对抗) +t+ 282.8s ▶ 语音:「蘑菇怪拦住了你!」 +t+ 282.8s 〰 震动:heartbeat(心跳/倒计时) +t+ 284.9s ♪ 向导音:turn(轮到你) +t+ 284.9s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 284.9s ♪ 音效 sfx/atk_swing +t+ 284.9s ♪ 音效 sfx/atk_hit +t+ 284.9s ♪ 音效 sfx/tel_bug +t+ 284.9s 〰 震动:beat(节拍点) +t+ 286.1s ♪ 音效 sfx/hurt +t+ 286.1s ♪ 向导音:turn(轮到你) +t+ 286.1s 〰 震动:hit(受击重震) +t+ 286.1s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 286.1s ♪ 音效 sfx/atk_swing +t+ 286.1s ♪ 音效 sfx/atk_hit +t+ 286.1s ♪ 音效 sfx/tel_bug +t+ 286.1s 〰 震动:beat(节拍点) +t+ 287.3s ♪ 音效 sfx/hurt +t+ 287.3s ♪ 向导音:turn(轮到你) +t+ 287.3s 〰 震动:hit(受击重震) +t+ 287.3s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,挥击 + +t+ 287.3s ♪ 音效 sfx/atk_swing +t+ 287.3s ♪ 音效 sfx/atk_hit +t+ 287.3s ♪ 音效 sfx/enemy_die +t+ 287.3s ♪ 音效 sfx/coin +t+ 287.3s ▶ 语音:「它倒下了。」 +t+ 287.3s 〰 震动:success(成功长震) +t+ 289.5s ▶ 语音:「小有收获。」 +t+ 291.6s ♫ BGM 切换 → bgm/explore +t+ 291.6s ♪ 向导音:explore(探索/安全) +t+ 293.6s ♪ 音效 sfx/mob_step_bug_l +t+ 295.7s ♪ 音效 sfx/mob_step_bug_l + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 295.7s ♪ 音效 sfx/pulse +t+ 296.0s ♪ 音效 sfx/side_open_l +t+ 296.0s ♪ 音效 sfx/side_open_r +t+ 296.5s ♪ 音效 sfx/echo_3 +t+ 296.6s ♪ 音效 sfx/mat_stone +t+ 297.3s ♪ 音效 sfx/mob_step_bug_l +t+ 299.1s ♪ 音效 sfx/mob_step_bug_l +t+ 299.1s ♪ 音效 sfx/step_turn +t+ 300.7s ♪ 音效 sfx/mob_step_bug_c +t+ 300.7s ♪ 音效 sfx/step_own +t+ 300.7s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 301.2s ♪ 音效 sfx/pulse +t+ 301.6s ♪ 音效 sfx/side_wall_l +t+ 301.6s ♪ 音效 sfx/side_wall_r +t+ 302.0s ♪ 音效 sfx/echo_3 +t+ 302.2s ♪ 音效 sfx/mat_stone +t+ 302.5s ♪ 音效 sfx/mob_step_bug_c +t+ 304.4s ♪ 音效 sfx/mob_step_bug_c +t+ 304.4s ♪ 音效 sfx/step_own +t+ 304.4s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 304.9s ♪ 音效 sfx/pulse +t+ 305.3s ♪ 音效 sfx/side_open_l +t+ 305.3s ♪ 音效 sfx/side_open_r +t+ 305.7s ♪ 音效 sfx/echo_3 +t+ 305.8s ♪ 音效 sfx/mat_stone +t+ 306.8s ♪ 音效 sfx/mob_step_bug_l +t+ 309.2s ♪ 音效 sfx/mob_step_bug_l +t+ 309.2s ♪ 音效 sfx/step_turn +t+ 310.9s ♪ 音效 sfx/mob_step_bug_c +t+ 310.9s ♪ 音效 sfx/step_own +t+ 310.9s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 311.4s ♪ 音效 sfx/pulse +t+ 311.7s ♪ 音效 sfx/side_wall_l +t+ 311.7s ♪ 音效 sfx/side_wall_r +t+ 311.9s ♪ 音效 sfx/echo_2 +t+ 312.0s ♪ 音效 sfx/mat_stair +t+ 312.6s ♪ 音效 sfx/mob_step_bug_c +t+ 314.5s ♪ 音效 sfx/mob_step_bug_r +t+ 314.5s ♪ 音效 sfx/step_own +t+ 314.5s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】单击发脉冲,听回声辨路 + +t+ 315.0s ♪ 音效 sfx/pulse +t+ 315.3s ♪ 音效 sfx/echo_1 +t+ 315.4s ♪ 音效 sfx/side_open_l +t+ 315.4s ♪ 音效 sfx/side_open_r +t+ 315.4s ♪ 音效 sfx/mat_stair +t+ 316.8s ♪ 音效 sfx/mob_step_bug_c +t+ 319.1s ♪ 音效 sfx/mob_step_bug_l +t+ 319.1s ♪ 音效 sfx/step_own +t+ 319.1s ♫ BGM 切换 → bgm/boss(淡入 400ms) +t+ 319.1s ♪ 向导音:battle(战斗/对抗) +t+ 319.1s ▶ 语音:「窟主石甲蟹,就在你面前。」 +t+ 319.1s 〰 震动:swipe(滑动识别) +t+ 321.3s ♪ 向导音:turn(轮到你) +t+ 321.3s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 321.3s ♪ 音效 sfx/atk_swing +t+ 321.3s ♪ 音效 sfx/atk_hit +t+ 321.3s ♪ 音效 sfx/tel_heavy +t+ 321.3s 〰 震动:beat(节拍点) +t+ 322.5s ♪ 音效 sfx/hurt +t+ 322.5s ♪ 向导音:turn(轮到你) +t+ 322.5s 〰 震动:hit(受击重震) +t+ 322.5s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 329.1s ♪ 音效 sfx/atk_swing +t+ 329.1s ♪ 音效 sfx/atk_hit +t+ 329.1s ♪ 音效 sfx/mob_step_heavy_r +t+ 329.1s ♪ 音效 sfx/tel_heavy_r +t+ 329.1s 〰 震动:beat(节拍点) +t+ 330.3s ♪ 音效 sfx/hurt +t+ 330.3s ♪ 向导音:danger(危险) +t+ 330.3s ♪ 向导音:turn(轮到你) +t+ 330.3s 〰 震动:hit(受击重震) +t+ 330.3s 〰 震动:warning(警告三连震) +t+ 330.3s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + +t+ 345.3s ♪ 向导音:nudge(看门狗轻提醒) +t+ 345.3s ▶ 向导语音:「轮到你了。单击挥击。」 +t+ 345.3s ♪ 音效 sfx/turn_tick + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 345.3s ♪ 音效 sfx/atk_swing +t+ 345.3s ▶ 语音:「挥空了,它不在你面前。」 +t+ 347.5s ♪ 音效 sfx/tel_heavy_r +t+ 347.5s 〰 震动:beat(节拍点) +t+ 348.7s ♪ 音效 sfx/hurt +t+ 348.7s ♪ 向导音:turn(轮到你) +t+ 348.7s 〰 震动:hit(受击重震) +t+ 348.7s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】喘一口气,再挥 + + +#### 【玩家动作】咬牙单击——挡不住了也要打 + +t+ 351.3s ♪ 音效 sfx/atk_swing +t+ 351.3s ▶ 语音:「挥空了,它不在你面前。」 +t+ 353.5s ♪ 音效 sfx/mob_step_heavy_c +t+ 353.5s ♪ 音效 sfx/tel_heavy +t+ 353.5s 〰 震动:beat(节拍点) +t+ 354.7s ♪ 音效 sfx/hurt +t+ 354.7s ♫ BGM 切换 → bgm/menu +t+ 354.7s ♪ 向导音:settle(结算) +t+ 354.7s ▶ 语音:「你倒下了。」 +t+ 354.7s 〰 震动:hit(受击重震) +t+ 354.7s 〰 震动:hit(受击重震) +t+ 354.7s 【状态】倒下了。死亡归因链:死于谁、哪里没做好、带走了多少金币 结算:死亡归因与元进度入账,单击再探一次(语音播放中,语速1) + +#### 【玩家动作】听完归因三问和「下个周目,你会更强」 + +t+ 356.8s ▶ 语音:「你背后的偷袭,要了命。」 +t+ 358.9s ▶ 语音:「身后有动静,先转身。」 +t+ 361.0s ▶ 语音:「这是第三层。」 +t+ 363.1s ▶ 语音:「这趟带回一些金币。」 +t+ 365.3s ▶ 语音:「下个周目,你会更强。」 + +#### 【玩家动作】单击——再探一次 + +t+ 365.3s ♪ 向导音:menu(系统界面) + +#### 【玩家动作】单击,开始新周目 + +t+ 365.3s ▶ 语音:「新的探窟,开始了。」 +t+ 367.4s ♫ BGM 切换 → bgm/explore +t+ 367.4s ♪ 向导音:explore(探索/安全) +t+ 367.4s ▶ 语音:「这是第一层。」 +t+ 367.4s 【状态】新周目直落探索:遗产生效,不用再上课 第 1 层黑暗里:单击发脉冲用回声看路,按住前进,左右滑转身,上滑状态,下滑菜单(语音播放中,语速1) + +#### 【玩家动作】上滑,听新周目的局面复述 + +t+ 367.4s ▶ 语音:「这是第一层。」 +t+ 369.0s ♪ 音效 sfx/mob_step_small_c +t+ 369.0s ♪ 音效 sfx/mob_step_small_l +t+ 369.5s ▶ 语音:「你毫发无损。」 +t+ 370.8s ♪ 音效 sfx/mob_step_small_l +t+ 371.3s ♪ 音效 sfx/mob_step_small_l +t+ 371.6s ▶ 语音:「有些积蓄。」 +t+ 373.0s ♪ 音效 sfx/mob_step_small_l +t+ 373.5s ♪ 音效 sfx/mob_step_small_l +t+ 373.7s ▶ 语音:「你面朝北。」 +t+ 374.8s ♪ 音效 sfx/mob_step_small_l + +#### 【玩家动作】单击脉冲,听听熟悉的回声 + +t+ 374.8s ♪ 音效 sfx/pulse +t+ 375.1s ♪ 音效 sfx/side_open_l +t+ 375.1s ♪ 音效 sfx/side_open_r +t+ 375.3s ♪ 音效 sfx/echo_2 +t+ 375.4s ♪ 音效 sfx/mat_stone +t+ 375.7s ♪ 音效 sfx/mob_step_small_l +t+ 375.9s ▶ 语音:「左边有脚步声。」 +t+ 377.0s ♪ 音效 sfx/mob_step_small_l +t+ 377.4s 【状态】第二周目短程收尾——死亡-重开循环跑通,收工 第 1 层黑暗里:单击发脉冲用回声看路,按住前进,左右滑转身,上滑状态,下滑菜单(语音播放中,语速1) diff --git a/research/new8/dungeon/round2/transcript-stress.md b/research/new8/dungeon/round2/transcript-stress.md new file mode 100644 index 00000000..ee84986b --- /dev/null +++ b/research/new8/dungeon/round2/transcript-stress.md @@ -0,0 +1,5996 @@ +# 试玩转写:《dungeon》 + +> 本文件是「玩家感知日志」:一位新手玩家的整局体验转写。语音列出了玩家听到的每句话全文, +> 音效/BGM/震动按发生时间列出。「静默」段 = 玩家什么也听不到。手势是剧本注入的(模拟真人手部动作), +> 非最优操作:包含犹豫、误操作与乱试。语音时长按语速估算(±20%),仅供节奏参考。 +t+ 0.0s (玩家点开游戏,轻触屏幕开始) +t+ 0.0s ♫ BGM 切换 → bgm/menu +t+ 0.0s ♪ 向导音:menu(系统界面) +t+ 0.0s 【状态】标题页乱划 标题页:单击开始探窟,上滑听元进度(BGM=bgm/menu,语速1) +t+ 0.0s ▶ 语音:「这趟没带回什么。」 +t+ 2.1s ▶ 语音:「你还没到过第三层。」 +t+ 2.1s 【状态】标题页乱划 标题页:单击开始探窟,上滑听元进度(语音播放中,语速1) +t+ 2.1s ▶ 语音:「新的探窟,开始了。」 +t+ 4.2s ♫ BGM 切换 → bgm/explore +t+ 4.2s ♪ 向导音:explore(探索/安全) +t+ 4.2s ▶ 语音:「你在黑暗中醒来。这里是回声地窟。」 +t+ 4.2s 【状态】教学里乱按一气,旁白说到哪按到哪 教学演练第 1 步,跟着旁白做(语音播放中,语速1) +t+ 4.2s ♪ 音效 sfx/pulse +t+ 4.2s ▶ 语音:「听,回声回来了。」 +t+ 4.6s ♪ 音效 sfx/side_open_l +t+ 4.6s ♪ 音效 sfx/side_open_r +t+ 4.7s ♪ 音效 sfx/echo_2 +t+ 4.7s ▶ 语音:「听,回声回来了。」 +t+ 4.7s 〰 震动:error(做错了:两连短震) +t+ 4.9s ♪ 音效 sfx/mat_stone +t+ 6.9s ▶ 语音:「回声回来越快,墙就越近。」 +t+ 6.9s ▶ 语音:「两侧的回声,发闷是墙,空旷是通路。」 +t+ 9.0s ♪ 音效 sfx/step_turn +t+ 9.0s ▶ 语音:「按住屏幕不放,你就往前走。」 +t+ 11.1s ♪ 音效 sfx/step_own +t+ 11.1s ♪ 音效 sfx/coin +t+ 11.1s ▶ 语音:「捡到几枚金币。」 +t+ 11.1s 〰 震动:swipe(滑动识别) +t+ 11.1s 〰 震动:pickup(获得) +t+ 11.6s ♪ 音效 sfx/step_own +t+ 11.6s ▶ 语音:「左滑右滑,可以转身。」 +t+ 11.6s 〰 震动:swipe(滑动识别) +t+ 12.1s ♪ 音效 sfx/step_own +t+ 12.2s ▶ 语音:「双击,再发一次脉冲,不要钱。」 +t+ 12.6s ♪ 音效 sfx/bump +t+ 12.1s 〰 震动:swipe(滑动识别) +t+ 14.4s 〰 震动:error(做错了:两连短震) +t+ 14.9s ♪ 音效 sfx/pulse +t+ 14.9s ▶ 语音:「听,回声回来了。」 +t+ 15.1s ♪ 音效 sfx/echo_1 +t+ 15.2s ♪ 音效 sfx/side_open_l +t+ 15.2s ♪ 音效 sfx/side_open_r +t+ 15.2s ♪ 音效 sfx/mat_stone +t+ 17.0s ♪ 音效 sfx/pulse +t+ 17.0s ▶ 语音:「听,回声回来了。」 +t+ 17.2s ♪ 音效 sfx/echo_1 +t+ 17.3s ♪ 音效 sfx/side_open_l +t+ 17.3s ♪ 音效 sfx/side_open_r +t+ 17.4s ♪ 音效 sfx/mat_stone +t+ 19.1s ▶ 语音:「上滑,随时听自己的状态。」 +t+ 19.1s ▶ 语音:「这是第一层。」 +t+ 21.2s ▶ 语音:「你毫发无损。」 +t+ 22.9s ♪ 音效 sfx/step_turn +t+ 22.9s ♪ 音效 sfx/step_turn +t+ 22.9s ▶ 语音:「前面有东西!放心,它伤不到你。」 +t+ 25.0s ♪ 音效 sfx/pulse +t+ 25.3s ♪ 音效 sfx/side_open_l +t+ 25.3s ♪ 音效 sfx/side_open_r +t+ 26.1s ♪ 音效 sfx/echo_4 +t+ 26.2s ♪ 音效 sfx/mob_step_small_c +t+ 26.2s ♪ 音效 sfx/mat_stone +t+ 27.4s ♪ 音效 sfx/mob_step_small_c +t+ 28.6s ♪ 音效 sfx/mob_step_small_c +t+ 28.6s 〰 震动:heartbeat(心跳/倒计时) +t+ 29.8s ♪ 音效 sfx/mob_step_small_c +t+ 29.8s 〰 震动:heartbeat(心跳/倒计时) +t+ 31.0s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+ 31.0s ♪ 向导音:battle(战斗/对抗) +t+ 31.0s ▶ 语音:「盲眼蝠扑了过来!」 +t+ 33.1s ▶ 语音:「听到它的动静,双击格挡。」 +t+ 35.2s ♪ 音效 sfx/tel_small +t+ 35.2s 〰 震动:beat(节拍点) +t+ 36.4s ▶ 语音:「没挡住也别怕,它伤不到你。」 +t+ 38.5s ▶ 语音:「现在单击,挥拳打它。」 +t+ 40.7s ♪ 向导音:turn(轮到你) +t+ 40.7s 〰 震动:confirm(轻点确认) +t+ 40.7s ▶ 语音:「它贴着你不放,不用转身。」 +t+ 40.7s 〰 震动:error(做错了:两连短震) + +#### 【玩家动作】单击,抡空也打 + +t+ 42.8s ♪ 音效 sfx/atk_swing +t+ 42.8s ♪ 音效 sfx/atk_hit +t+ 42.8s ♪ 音效 sfx/tel_small +t+ 42.8s 〰 震动:beat(节拍点) +t+ 44.0s ▶ 语音:「没挡住也别怕,它伤不到你。」 +t+ 46.1s ▶ 语音:「现在单击,挥拳打它。」 +t+ 48.2s ♪ 向导音:turn(轮到你) +t+ 48.2s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,抡空也打 + +t+ 48.2s ♪ 音效 sfx/atk_swing +t+ 48.2s ♪ 音效 sfx/atk_hit +t+ 48.2s ♪ 音效 sfx/enemy_die +t+ 48.2s ♪ 音效 sfx/coin +t+ 48.2s ▶ 语音:「打中了,就是这样。」 +t+ 48.2s 〰 震动:success(成功长震) +t+ 50.3s ▶ 语音:「战利不多。」 +t+ 52.5s ♫ BGM 切换 → bgm/explore(淡入 400ms) +t+ 52.5s ▶ 语音:「铁器的回声,就是下潜的楼梯。」 +t+ 56.0s ▶ 语音:「这是第一层。」 +t+ 58.1s ▶ 语音:「你毫发无损。」 +t+ 58.1s ♪ 音效 sfx/step_turn +t+ 60.2s ♪ 音效 sfx/pulse +t+ 60.2s ▶ 语音:「口袋空空。」 +t+ 60.6s ♪ 音效 sfx/side_open_l +t+ 60.6s ♪ 音效 sfx/side_wall_r +t+ 61.0s ♪ 音效 sfx/echo_3 +t+ 61.0s ♪ 音效 sfx/step_turn +t+ 61.1s ♪ 音效 sfx/mat_stone +t+ 62.3s ▶ 语音:「你面朝西。」 +t+ 62.3s ♪ 音效 sfx/pulse +t+ 62.6s ♪ 音效 sfx/echo_1 +t+ 62.7s ♪ 音效 sfx/side_open_l +t+ 62.7s ♪ 音效 sfx/side_open_r +t+ 62.7s ♪ 音效 sfx/mat_stone +t+ 64.5s ▶ 语音:「附近很安静。」 +t+ 64.5s ♪ 音效 sfx/step_turn +t+ 66.6s ♪ 音效 sfx/step_own +t+ 66.6s 〰 震动:swipe(滑动识别) +t+ 67.1s ♪ 音效 sfx/bump +t+ 67.1s ♪ 音效 sfx/step_turn +t+ 75.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 75.1s ♪ 向导音:watchdog(看门狗升级) +t+ 75.1s ♪ 音效 sfx/step_own +t+ 75.1s 〰 震动:swipe(滑动识别) +t+ 75.6s ♪ 音效 sfx/step_own +t+ 75.6s 〰 震动:swipe(滑动识别) +t+ 76.1s ♪ 音效 sfx/pulse +t+ 76.4s ♪ 音效 sfx/side_wall_l +t+ 76.4s ♪ 音效 sfx/side_open_r +t+ 77.2s ♪ 音效 sfx/echo_4 +t+ 77.2s ♪ 音效 sfx/step_turn +t+ 77.3s ♪ 音效 sfx/mat_stone +t+ 85.2s ♪ 向导音:nudge(看门狗轻提醒) +t+ 85.2s ♪ 向导音:watchdog(看门狗升级) +t+ 85.2s ♪ 音效 sfx/step_turn +t+ 93.2s ♪ 向导音:nudge(看门狗轻提醒) +t+ 93.2s ♪ 向导音:watchdog(看门狗升级) +t+ 93.2s ♪ 音效 sfx/step_own +t+ 93.2s 〰 震动:swipe(滑动识别) +t+ 93.7s ♪ 音效 sfx/step_own +t+ 93.7s 〰 震动:swipe(滑动识别) +t+ 94.2s ♪ 音效 sfx/step_turn +t+ 102.2s ♪ 向导音:nudge(看门狗轻提醒) +t+ 102.2s ♪ 向导音:watchdog(看门狗升级) +t+ 102.2s ♪ 音效 sfx/step_own +t+ 102.2s 〰 震动:swipe(滑动识别) +t+ 102.7s ♪ 音效 sfx/step_own +t+ 102.7s 〰 震动:swipe(滑动识别) +t+ 103.2s ♪ 音效 sfx/step_turn +t+ 111.2s ♪ 向导音:nudge(看门狗轻提醒) +t+ 111.2s ♪ 向导音:watchdog(看门狗升级) +t+ 111.2s ♪ 音效 sfx/step_own +t+ 111.2s 〰 震动:swipe(滑动识别) +t+ 111.7s ♪ 音效 sfx/step_own +t+ 111.7s 〰 震动:swipe(滑动识别) +t+ 112.2s ♪ 音效 sfx/step_own +t+ 112.2s 〰 震动:swipe(滑动识别) +t+ 112.7s ♪ 音效 sfx/step_own +t+ 112.7s 〰 震动:swipe(滑动识别) +t+ 113.2s ♪ 音效 sfx/pulse +t+ 113.4s ♪ 音效 sfx/echo_1 +t+ 113.5s ♪ 音效 sfx/side_open_l +t+ 113.5s ♪ 音效 sfx/side_open_r +t+ 113.5s ♪ 音效 sfx/mat_stone +t+ 121.2s ♪ 向导音:nudge(看门狗轻提醒) +t+ 121.2s ♪ 向导音:watchdog(看门狗升级) +t+ 121.2s ▶ 语音:「这是第一层。」 +t+ 123.3s ▶ 语音:「你毫发无损。」 +t+ 123.3s ♪ 音效 sfx/step_turn +t+ 125.4s ▶ 语音:「口袋空空。」 +t+ 125.4s ♪ 音效 sfx/step_own +t+ 125.4s 〰 震动:swipe(滑动识别) +t+ 125.9s ♪ 音效 sfx/step_own +t+ 125.9s 〰 震动:swipe(滑动识别) +t+ 126.4s ▶ 语音:「这是第一层。」 +t+ 128.5s ▶ 语音:「你毫发无损。」 +t+ 128.5s ♪ 音效 sfx/step_turn +t+ 130.7s ▶ 语音:「口袋空空。」 +t+ 130.7s ♪ 音效 sfx/step_own +t+ 130.7s 〰 震动:swipe(滑动识别) +t+ 131.2s ♪ 音效 sfx/step_own +t+ 131.2s 〰 震动:swipe(滑动识别) +t+ 131.7s ♪ 音效 sfx/step_turn +t+ 132.8s ▶ 语音:「你面朝南。」 +t+ 132.8s ♪ 音效 sfx/step_turn +t+ 134.9s ▶ 语音:「附近很安静。」 +t+ 134.9s ♪ 音效 sfx/step_own +t+ 134.9s 〰 震动:swipe(滑动识别) +t+ 135.4s ♪ 音效 sfx/step_own +t+ 135.4s 〰 震动:swipe(滑动识别) +t+ 135.9s ♪ 音效 sfx/step_turn +t+ 137.0s ♪ 音效 sfx/step_turn +t+ 145.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 145.0s ♪ 向导音:watchdog(看门狗升级) +t+ 145.0s ♪ 音效 sfx/step_own +t+ 145.0s 〰 震动:swipe(滑动识别) +t+ 145.5s ♪ 音效 sfx/step_own +t+ 145.5s 〰 震动:swipe(滑动识别) +t+ 146.0s ♪ 音效 sfx/step_turn +t+ 154.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 154.0s ♪ 向导音:watchdog(看门狗升级) +t+ 154.0s ♪ 音效 sfx/bump +t+ 162.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 162.0s ♪ 向导音:watchdog(看门狗升级) +t+ 162.0s ♪ 音效 sfx/pulse +t+ 162.3s ♪ 音效 sfx/echo_1 +t+ 162.4s ♪ 音效 sfx/side_wall_l +t+ 162.4s ♪ 音效 sfx/side_open_r +t+ 162.4s ♪ 音效 sfx/mat_stone +t+ 170.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 170.0s ♪ 向导音:watchdog(看门狗升级) +t+ 170.0s ♪ 音效 sfx/pulse +t+ 170.3s ♪ 音效 sfx/echo_1 +t+ 170.4s ♪ 音效 sfx/side_wall_l +t+ 170.4s ♪ 音效 sfx/side_open_r +t+ 170.4s ♪ 音效 sfx/mat_stone +t+ 178.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 178.0s ♪ 向导音:watchdog(看门狗升级) +t+ 178.0s ♪ 音效 sfx/step_turn +t+ 186.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 186.0s ♪ 向导音:watchdog(看门狗升级) +t+ 186.0s ♪ 音效 sfx/step_own +t+ 186.0s 〰 震动:swipe(滑动识别) +t+ 186.5s ♪ 音效 sfx/step_own +t+ 186.5s 〰 震动:swipe(滑动识别) +t+ 187.0s ♪ 音效 sfx/step_own +t+ 187.0s 〰 震动:swipe(滑动识别) +t+ 187.5s ♪ 音效 sfx/step_own +t+ 187.5s 〰 震动:swipe(滑动识别) +t+ 188.0s ♪ 音效 sfx/step_turn +t+ 196.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 196.0s ♪ 向导音:watchdog(看门狗升级) +t+ 196.0s ♪ 音效 sfx/step_turn +t+ 204.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 204.0s ♪ 向导音:watchdog(看门狗升级) +t+ 204.0s ♪ 音效 sfx/step_own +t+ 204.0s 〰 震动:swipe(滑动识别) +t+ 204.5s ♪ 音效 sfx/step_own +t+ 204.5s 〰 震动:swipe(滑动识别) +t+ 205.0s ♪ 音效 sfx/step_turn +t+ 213.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 213.0s ♪ 向导音:watchdog(看门狗升级) +t+ 213.0s ♪ 音效 sfx/step_turn +t+ 221.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 221.0s ♪ 向导音:watchdog(看门狗升级) +t+ 221.0s ♪ 音效 sfx/step_own +t+ 221.0s 〰 震动:swipe(滑动识别) +t+ 221.5s ♪ 音效 sfx/step_own +t+ 221.5s 〰 震动:swipe(滑动识别) +t+ 222.0s ♪ 音效 sfx/step_turn +t+ 230.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 230.0s ♪ 向导音:watchdog(看门狗升级) +t+ 230.0s ♪ 音效 sfx/bump +t+ 238.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 238.0s ♪ 向导音:watchdog(看门狗升级) +t+ 238.0s ♪ 音效 sfx/step_turn +t+ 246.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 246.0s ♪ 向导音:watchdog(看门狗升级) +t+ 246.0s ♪ 音效 sfx/step_own +t+ 246.0s 〰 震动:swipe(滑动识别) +t+ 246.5s ♪ 音效 sfx/step_own +t+ 246.5s 〰 震动:swipe(滑动识别) +t+ 247.0s ♪ 音效 sfx/step_turn +t+ 255.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 255.0s ♪ 向导音:watchdog(看门狗升级) +t+ 255.0s ♪ 音效 sfx/bump +t+ 263.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 263.0s ♪ 向导音:watchdog(看门狗升级) +t+ 263.0s ♪ 音效 sfx/step_turn +t+ 271.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 271.0s ♪ 向导音:watchdog(看门狗升级) +t+ 271.0s ♪ 音效 sfx/step_own +t+ 271.0s 〰 震动:swipe(滑动识别) +t+ 271.5s ♪ 音效 sfx/step_own +t+ 271.5s 〰 震动:swipe(滑动识别) +t+ 272.0s ♪ 音效 sfx/bump +t+ 280.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 280.0s ♪ 向导音:watchdog(看门狗升级) +t+ 280.0s ♪ 音效 sfx/step_turn +t+ 288.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 288.0s ♪ 向导音:watchdog(看门狗升级) +t+ 288.0s ♪ 音效 sfx/bump +t+ 296.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 296.0s ♪ 向导音:watchdog(看门狗升级) +t+ 296.0s ♪ 音效 sfx/step_turn +t+ 304.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 304.0s ♪ 向导音:watchdog(看门狗升级) +t+ 304.0s ♪ 音效 sfx/step_own +t+ 304.0s 〰 震动:swipe(滑动识别) +t+ 304.5s ♪ 音效 sfx/step_own +t+ 304.5s 〰 震动:swipe(滑动识别) +t+ 305.0s ♪ 音效 sfx/step_turn +t+ 313.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 313.0s ♪ 向导音:watchdog(看门狗升级) +t+ 313.0s ♪ 音效 sfx/bump +t+ 321.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 321.0s ♪ 向导音:watchdog(看门狗升级) +t+ 321.0s ♪ 音效 sfx/step_turn +t+ 329.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 329.0s ♪ 向导音:watchdog(看门狗升级) +t+ 329.0s ♪ 音效 sfx/step_own +t+ 329.0s 〰 震动:swipe(滑动识别) +t+ 329.5s ♪ 音效 sfx/step_own +t+ 329.5s 〰 震动:swipe(滑动识别) +t+ 330.0s ♪ 音效 sfx/step_turn +t+ 338.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 338.0s ♪ 向导音:watchdog(看门狗升级) +t+ 338.0s ♪ 音效 sfx/bump +t+ 346.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 346.0s ♪ 向导音:watchdog(看门狗升级) +t+ 346.0s ♪ 音效 sfx/step_turn +t+ 354.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 354.0s ♪ 向导音:watchdog(看门狗升级) +t+ 354.0s ♪ 音效 sfx/step_own +t+ 354.0s 〰 震动:swipe(滑动识别) +t+ 354.5s ♪ 音效 sfx/step_own +t+ 354.5s 〰 震动:swipe(滑动识别) +t+ 355.0s ♪ 音效 sfx/step_turn +t+ 363.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 363.0s ♪ 向导音:watchdog(看门狗升级) +t+ 363.0s ♪ 音效 sfx/bump +t+ 371.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 371.0s ♪ 向导音:watchdog(看门狗升级) +t+ 371.0s ♪ 音效 sfx/pulse +t+ 371.3s ♪ 音效 sfx/echo_1 +t+ 371.4s ♪ 音效 sfx/side_open_l +t+ 371.4s ♪ 音效 sfx/side_open_r +t+ 371.4s ♪ 音效 sfx/mat_stone +t+ 379.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 379.0s ♪ 向导音:watchdog(看门狗升级) +t+ 379.0s ▶ 语音:「这是第一层。」 +t+ 381.1s ▶ 语音:「你毫发无损。」 +t+ 381.1s ♪ 音效 sfx/step_turn +t+ 383.3s ▶ 语音:「口袋空空。」 +t+ 383.3s ♪ 音效 sfx/step_turn +t+ 385.4s ▶ 语音:「你面朝南。」 +t+ 385.4s ♪ 音效 sfx/step_own +t+ 385.4s 〰 震动:swipe(滑动识别) +t+ 385.9s ♪ 音效 sfx/step_own +t+ 385.9s 〰 震动:swipe(滑动识别) +t+ 386.4s ♪ 音效 sfx/step_turn +t+ 387.5s ▶ 语音:「右边有脚步声。」 +t+ 387.5s ♪ 音效 sfx/step_own +t+ 387.5s 〰 震动:swipe(滑动识别) +t+ 388.0s ♪ 音效 sfx/step_own +t+ 388.0s 〰 震动:swipe(滑动识别) +t+ 388.5s ♪ 音效 sfx/step_turn +t+ 389.6s ♪ 音效 sfx/pulse +t+ 389.9s ♪ 音效 sfx/echo_1 +t+ 390.0s ♪ 音效 sfx/side_open_l +t+ 390.0s ♪ 音效 sfx/side_open_r +t+ 390.0s ♪ 音效 sfx/mat_stone +t+ 397.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 397.6s ♪ 向导音:watchdog(看门狗升级) +t+ 397.6s ♪ 音效 sfx/step_turn +t+ 405.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 405.6s ♪ 向导音:watchdog(看门狗升级) +t+ 405.6s ♪ 音效 sfx/step_own +t+ 405.6s 〰 震动:swipe(滑动识别) +t+ 406.1s ♪ 音效 sfx/step_own +t+ 406.1s 〰 震动:swipe(滑动识别) +t+ 406.6s ♪ 音效 sfx/step_turn +t+ 414.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 414.6s ♪ 向导音:watchdog(看门狗升级) +t+ 414.6s ♪ 音效 sfx/step_own +t+ 414.6s 〰 震动:swipe(滑动识别) +t+ 415.1s ♪ 音效 sfx/step_own +t+ 415.1s 〰 震动:swipe(滑动识别) +t+ 415.6s ♪ 音效 sfx/pulse +t+ 415.9s ♪ 音效 sfx/echo_1 +t+ 416.0s ♪ 音效 sfx/side_wall_l +t+ 416.0s ♪ 音效 sfx/side_open_r +t+ 416.0s ♪ 音效 sfx/mat_stone +t+ 423.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 423.6s ♪ 向导音:watchdog(看门狗升级) +t+ 423.6s ♪ 音效 sfx/step_turn +t+ 431.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 431.6s ♪ 向导音:watchdog(看门狗升级) +t+ 431.6s ♪ 音效 sfx/step_turn +t+ 439.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 439.6s ♪ 向导音:watchdog(看门狗升级) +t+ 439.6s ♪ 音效 sfx/bump +t+ 447.6s ♪ 向导音:nudge(看门狗轻提醒) +t+ 447.6s ♪ 向导音:watchdog(看门狗升级) +t+ 447.6s ▶ 语音:「这是第一层。」 +t+ 449.7s ▶ 语音:「你毫发无损。」 +t+ 449.7s ♪ 音效 sfx/step_turn +t+ 451.9s ▶ 语音:「口袋空空。」 +t+ 451.9s ♪ 音效 sfx/step_own +t+ 451.9s 〰 震动:swipe(滑动识别) +t+ 452.4s ♪ 音效 sfx/step_own +t+ 452.4s 〰 震动:swipe(滑动识别) +t+ 452.9s ♪ 音效 sfx/pulse +t+ 453.2s ♪ 音效 sfx/side_wall_l +t+ 453.2s ♪ 音效 sfx/side_open_r +t+ 454.0s ♪ 音效 sfx/echo_4 +t+ 454.0s ♪ 音效 sfx/step_turn +t+ 454.0s ▶ 语音:「你面朝南。」 +t+ 454.1s ♪ 音效 sfx/mat_stone +t+ 456.1s ▶ 语音:「附近很安静。」 +t+ 456.1s ♪ 音效 sfx/step_own +t+ 456.1s 〰 震动:swipe(滑动识别) +t+ 456.6s ♪ 音效 sfx/step_own +t+ 456.6s 〰 震动:swipe(滑动识别) +t+ 457.1s ♪ 音效 sfx/step_turn +t+ 458.2s ♪ 音效 sfx/step_own +t+ 458.2s 〰 震动:swipe(滑动识别) +t+ 458.7s ♪ 音效 sfx/step_own +t+ 458.7s 〰 震动:swipe(滑动识别) +t+ 459.2s ♪ 音效 sfx/step_turn +t+ 467.2s ♪ 向导音:nudge(看门狗轻提醒) +t+ 467.2s ♪ 向导音:watchdog(看门狗升级) +t+ 467.2s ♪ 音效 sfx/step_own +t+ 467.2s 〰 震动:swipe(滑动识别) +t+ 467.7s ♪ 音效 sfx/step_own +t+ 467.7s 〰 震动:swipe(滑动识别) +t+ 468.2s ♪ 音效 sfx/bump +t+ 476.2s ♪ 向导音:nudge(看门狗轻提醒) +t+ 476.2s ♪ 向导音:watchdog(看门狗升级) +t+ 476.2s ♪ 音效 sfx/step_turn +t+ 484.2s ♪ 向导音:nudge(看门狗轻提醒) +t+ 484.2s ♪ 向导音:watchdog(看门狗升级) +t+ 484.2s ♪ 音效 sfx/pulse +t+ 484.6s ♪ 音效 sfx/side_wall_l +t+ 484.6s ♪ 音效 sfx/side_open_r +t+ 485.3s ♪ 音效 sfx/echo_4 +t+ 485.3s ♪ 音效 sfx/step_turn +t+ 485.4s ♪ 音效 sfx/mat_stone +t+ 493.3s ♪ 向导音:nudge(看门狗轻提醒) +t+ 493.3s ♪ 向导音:watchdog(看门狗升级) +t+ 493.3s ♪ 音效 sfx/step_own +t+ 493.3s 〰 震动:swipe(滑动识别) +t+ 493.8s ♪ 音效 sfx/step_own +t+ 493.8s 〰 震动:swipe(滑动识别) +t+ 494.3s ♪ 音效 sfx/step_turn +t+ 502.3s ♪ 向导音:nudge(看门狗轻提醒) +t+ 502.3s ♪ 向导音:watchdog(看门狗升级) +t+ 502.3s ♪ 音效 sfx/bump +t+ 510.3s ♪ 向导音:nudge(看门狗轻提醒) +t+ 510.3s ♪ 向导音:watchdog(看门狗升级) +t+ 510.3s ▶ 语音:「这是第一层。」 +t+ 512.4s ▶ 语音:「你毫发无损。」 +t+ 512.4s ♪ 音效 sfx/step_turn +t+ 514.6s ▶ 语音:「口袋空空。」 +t+ 514.6s ♪ 音效 sfx/step_own +t+ 514.6s 〰 震动:swipe(滑动识别) +t+ 515.1s ♪ 音效 sfx/step_own +t+ 515.1s 〰 震动:swipe(滑动识别) +t+ 515.6s ♪ 音效 sfx/step_turn +t+ 516.7s ▶ 语音:「你面朝东。」 +t+ 516.7s ♪ 音效 sfx/step_turn +t+ 518.8s ▶ 语音:「附近很安静。」 +t+ 518.8s ♪ 音效 sfx/bump +t+ 520.9s ♪ 音效 sfx/pulse +t+ 521.2s ♪ 音效 sfx/echo_1 +t+ 521.3s ♪ 音效 sfx/side_wall_l +t+ 521.3s ♪ 音效 sfx/side_open_r +t+ 521.3s ♪ 音效 sfx/mat_stone +t+ 528.9s ♪ 向导音:nudge(看门狗轻提醒) +t+ 528.9s ♪ 向导音:watchdog(看门狗升级) +t+ 528.9s ▶ 语音:「这是第一层。」 +t+ 531.0s ▶ 语音:「你毫发无损。」 +t+ 531.0s ♪ 音效 sfx/step_turn +t+ 533.2s ▶ 语音:「口袋空空。」 +t+ 533.2s ♪ 音效 sfx/step_own +t+ 533.2s 〰 震动:swipe(滑动识别) +t+ 533.7s ♪ 音效 sfx/step_own +t+ 533.7s 〰 震动:swipe(滑动识别) +t+ 534.2s ♪ 音效 sfx/step_turn +t+ 535.3s ▶ 语音:「你面朝南。」 +t+ 535.3s ♪ 音效 sfx/step_own +t+ 535.3s 〰 震动:swipe(滑动识别) +t+ 535.8s ♪ 音效 sfx/step_own +t+ 535.8s 〰 震动:swipe(滑动识别) +t+ 536.3s ♪ 音效 sfx/step_turn +t+ 537.4s ▶ 语音:「附近很安静。」 +t+ 537.4s ♪ 音效 sfx/step_own +t+ 537.4s 〰 震动:swipe(滑动识别) +t+ 537.9s ♪ 音效 sfx/step_own +t+ 537.9s 〰 震动:swipe(滑动识别) +t+ 538.4s ♪ 音效 sfx/step_turn +t+ 539.5s ♪ 音效 sfx/step_own +t+ 539.5s 〰 震动:swipe(滑动识别) +t+ 540.0s ♪ 音效 sfx/step_own +t+ 540.0s 〰 震动:swipe(滑动识别) +t+ 540.5s ▶ 语音:「这是第一层。」 +t+ 542.6s ▶ 语音:「你毫发无损。」 +t+ 542.6s ♪ 音效 sfx/step_turn +t+ 544.8s ▶ 语音:「口袋空空。」 +t+ 544.8s ♪ 音效 sfx/step_own +t+ 544.8s 〰 震动:swipe(滑动识别) +t+ 545.3s ♪ 音效 sfx/step_own +t+ 545.3s 〰 震动:swipe(滑动识别) +t+ 545.8s ♪ 音效 sfx/pulse +t+ 546.1s ♪ 音效 sfx/side_wall_l +t+ 546.1s ♪ 音效 sfx/side_open_r +t+ 546.9s ♪ 音效 sfx/echo_4 +t+ 546.9s ♪ 音效 sfx/step_turn +t+ 546.9s ▶ 语音:「你面朝南。」 +t+ 547.0s ♪ 音效 sfx/mat_stone +t+ 549.0s ▶ 语音:「附近很安静。」 +t+ 549.0s ♪ 音效 sfx/step_own +t+ 549.0s 〰 震动:swipe(滑动识别) +t+ 549.5s ♪ 音效 sfx/step_own +t+ 549.5s 〰 震动:swipe(滑动识别) +t+ 550.0s ♪ 音效 sfx/pulse +t+ 550.3s ♪ 音效 sfx/echo_1 +t+ 550.4s ♪ 音效 sfx/side_open_l +t+ 550.4s ♪ 音效 sfx/side_open_r +t+ 550.4s ♪ 音效 sfx/mat_stone +t+ 551.1s ♪ 音效 sfx/step_turn +t+ 559.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 559.1s ♪ 向导音:watchdog(看门狗升级) +t+ 559.1s ♪ 音效 sfx/step_own +t+ 559.1s 〰 震动:swipe(滑动识别) +t+ 559.6s ♪ 音效 sfx/step_own +t+ 559.6s 〰 震动:swipe(滑动识别) +t+ 560.1s ♪ 音效 sfx/step_turn +t+ 568.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 568.1s ♪ 向导音:watchdog(看门狗升级) +t+ 568.1s ♪ 音效 sfx/step_own +t+ 568.1s 〰 震动:swipe(滑动识别) +t+ 568.6s ♪ 音效 sfx/step_own +t+ 568.6s 〰 震动:swipe(滑动识别) +t+ 569.1s ♪ 音效 sfx/step_turn +t+ 577.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 577.1s ♪ 向导音:watchdog(看门狗升级) +t+ 577.1s ♪ 音效 sfx/step_own +t+ 577.1s 〰 震动:swipe(滑动识别) +t+ 577.6s ♪ 音效 sfx/step_own +t+ 577.6s 〰 震动:swipe(滑动识别) +t+ 578.1s ♪ 音效 sfx/step_turn +t+ 586.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 586.1s ♪ 向导音:watchdog(看门狗升级) +t+ 586.1s ♪ 音效 sfx/step_own +t+ 586.1s 〰 震动:swipe(滑动识别) +t+ 586.6s ♪ 音效 sfx/step_own +t+ 586.6s 〰 震动:swipe(滑动识别) +t+ 587.1s ♪ 音效 sfx/pulse +t+ 587.4s ♪ 音效 sfx/echo_1 +t+ 587.5s ♪ 音效 sfx/side_open_l +t+ 587.5s ♪ 音效 sfx/side_open_r +t+ 587.5s ♪ 音效 sfx/mat_stone +t+ 595.1s ♪ 向导音:nudge(看门狗轻提醒) +t+ 595.1s ♪ 向导音:watchdog(看门狗升级) +t+ 595.1s ▶ 语音:「这是第一层。」 +t+ 597.2s ▶ 语音:「你毫发无损。」 +t+ 597.2s ♪ 音效 sfx/step_turn +t+ 599.4s ▶ 语音:「口袋空空。」 +t+ 599.4s ♪ 音效 sfx/step_own +t+ 599.4s 〰 震动:swipe(滑动识别) +t+ 599.9s ♪ 音效 sfx/step_own +t+ 599.9s 〰 震动:swipe(滑动识别) +t+ 600.4s ♪ 音效 sfx/step_turn +t+ 601.5s ▶ 语音:「你面朝北。」 +t+ 601.5s ♪ 音效 sfx/step_own +t+ 601.5s 〰 震动:swipe(滑动识别) +t+ 602.0s ♪ 音效 sfx/step_own +t+ 602.0s 〰 震动:swipe(滑动识别) +t+ 602.5s ♪ 音效 sfx/step_turn +t+ 603.6s ▶ 语音:「附近很安静。」 +t+ 603.6s ♪ 音效 sfx/step_own +t+ 603.6s 〰 震动:swipe(滑动识别) +t+ 604.1s ♪ 音效 sfx/step_own +t+ 604.1s 〰 震动:swipe(滑动识别) +t+ 604.6s ♪ 音效 sfx/step_turn +t+ 605.7s ♪ 音效 sfx/step_turn +t+ 613.7s ♪ 向导音:nudge(看门狗轻提醒) +t+ 613.7s ♪ 向导音:watchdog(看门狗升级) +t+ 613.7s ♪ 音效 sfx/step_own +t+ 613.7s 〰 震动:swipe(滑动识别) +t+ 614.2s ♪ 音效 sfx/step_own +t+ 614.2s 〰 震动:swipe(滑动识别) +t+ 614.7s ♪ 音效 sfx/step_turn +t+ 622.7s ♪ 向导音:nudge(看门狗轻提醒) +t+ 622.7s ♪ 向导音:watchdog(看门狗升级) +t+ 622.7s ♪ 音效 sfx/step_own +t+ 622.7s 〰 震动:swipe(滑动识别) +t+ 623.2s ♪ 音效 sfx/step_own +t+ 623.2s 〰 震动:swipe(滑动识别) +t+ 623.7s ♪ 音效 sfx/bump +t+ 631.7s ♪ 向导音:nudge(看门狗轻提醒) +t+ 631.7s ♪ 向导音:watchdog(看门狗升级) +t+ 631.7s ♪ 音效 sfx/step_turn +t+ 639.7s ♪ 向导音:nudge(看门狗轻提醒) +t+ 639.7s ♪ 向导音:watchdog(看门狗升级) +t+ 639.7s ♪ 音效 sfx/step_own +t+ 639.7s 〰 震动:swipe(滑动识别) +t+ 640.2s ♪ 音效 sfx/step_own +t+ 640.2s 〰 震动:swipe(滑动识别) +t+ 640.7s ♪ 音效 sfx/step_turn +t+ 648.7s ♪ 向导音:nudge(看门狗轻提醒) +t+ 648.7s ♪ 向导音:watchdog(看门狗升级) +t+ 648.7s ♪ 音效 sfx/step_own +t+ 648.7s 〰 震动:swipe(滑动识别) +t+ 649.2s ♪ 音效 sfx/step_own +t+ 649.2s 〰 震动:swipe(滑动识别) +t+ 649.7s ♪ 音效 sfx/step_turn +t+ 657.7s ♪ 向导音:nudge(看门狗轻提醒) +t+ 657.7s ♪ 向导音:watchdog(看门狗升级) +t+ 657.7s ♪ 音效 sfx/step_turn +t+ 665.7s ♪ 向导音:nudge(看门狗轻提醒) +t+ 665.7s ♪ 向导音:watchdog(看门狗升级) +t+ 665.7s ♪ 音效 sfx/step_turn +t+ 673.7s ♪ 向导音:nudge(看门狗轻提醒) +t+ 673.7s ♪ 向导音:watchdog(看门狗升级) +t+ 673.7s ♪ 音效 sfx/step_own +t+ 673.7s 〰 震动:swipe(滑动识别) +t+ 674.2s ♪ 音效 sfx/step_own +t+ 674.2s 〰 震动:swipe(滑动识别) +t+ 674.7s ♪ 音效 sfx/pulse +t+ 675.1s ♪ 音效 sfx/side_wall_l +t+ 675.1s ♪ 音效 sfx/side_open_r +t+ 675.5s ♪ 音效 sfx/echo_3 +t+ 675.5s ♪ 音效 sfx/step_turn +t+ 675.6s ♪ 音效 sfx/mat_stone +t+ 683.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 683.5s ♪ 向导音:watchdog(看门狗升级) +t+ 683.5s ♪ 音效 sfx/step_own +t+ 683.5s 〰 震动:swipe(滑动识别) +t+ 684.0s ♪ 音效 sfx/step_own +t+ 684.0s 〰 震动:swipe(滑动识别) +t+ 684.5s ♪ 音效 sfx/step_turn +t+ 692.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 692.5s ♪ 向导音:watchdog(看门狗升级) +t+ 692.5s ♪ 音效 sfx/step_own +t+ 692.5s 〰 震动:swipe(滑动识别) +t+ 693.0s ♪ 音效 sfx/step_own +t+ 693.0s 〰 震动:swipe(滑动识别) +t+ 693.5s ♪ 音效 sfx/step_turn +t+ 701.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 701.5s ♪ 向导音:watchdog(看门狗升级) +t+ 701.5s ♪ 音效 sfx/step_own +t+ 701.5s 〰 震动:swipe(滑动识别) +t+ 702.0s ♪ 音效 sfx/step_own +t+ 702.0s 〰 震动:swipe(滑动识别) +t+ 702.5s ♪ 音效 sfx/step_turn +t+ 710.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 710.5s ♪ 向导音:watchdog(看门狗升级) +t+ 710.5s ♪ 音效 sfx/step_turn +t+ 718.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 718.5s ♪ 向导音:watchdog(看门狗升级) +t+ 718.5s ♪ 音效 sfx/pulse +t+ 718.8s ♪ 音效 sfx/echo_1 +t+ 718.9s ♪ 音效 sfx/side_open_l +t+ 718.9s ♪ 音效 sfx/side_open_r +t+ 718.9s ♪ 音效 sfx/mat_stone +t+ 726.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 726.5s ♪ 向导音:watchdog(看门狗升级) +t+ 726.5s ♪ 音效 sfx/pulse +t+ 726.8s ♪ 音效 sfx/echo_1 +t+ 726.9s ♪ 音效 sfx/side_open_l +t+ 726.9s ♪ 音效 sfx/side_open_r +t+ 726.9s ♪ 音效 sfx/mat_stone +t+ 734.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 734.5s ♪ 向导音:watchdog(看门狗升级) +t+ 734.5s ♪ 音效 sfx/bump +t+ 742.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 742.5s ♪ 向导音:watchdog(看门狗升级) +t+ 742.5s ♪ 音效 sfx/pulse +t+ 742.8s ♪ 音效 sfx/echo_1 +t+ 742.9s ♪ 音效 sfx/side_open_l +t+ 742.9s ♪ 音效 sfx/side_open_r +t+ 742.9s ♪ 音效 sfx/mat_stone +t+ 750.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 750.5s ♪ 向导音:watchdog(看门狗升级) +t+ 750.5s ♪ 音效 sfx/step_turn +t+ 758.5s ♪ 向导音:nudge(看门狗轻提醒) +t+ 758.5s ♪ 向导音:watchdog(看门狗升级) +t+ 758.5s ♪ 音效 sfx/step_own +t+ 758.5s 〰 震动:swipe(滑动识别) +t+ 759.0s ♪ 音效 sfx/step_own +t+ 759.0s 〰 震动:swipe(滑动识别) +t+ 759.5s ▶ 语音:「这是第一层。」 +t+ 761.6s ▶ 语音:「你毫发无损。」 +t+ 761.6s ♪ 音效 sfx/step_turn +t+ 763.8s ▶ 语音:「口袋空空。」 +t+ 763.8s ♪ 音效 sfx/step_own +t+ 763.8s 〰 震动:swipe(滑动识别) +t+ 764.3s ♪ 音效 sfx/step_own +t+ 764.3s 〰 震动:swipe(滑动识别) +t+ 764.8s ▶ 语音:「这是第一层。」 +t+ 766.9s ▶ 语音:「你毫发无损。」 +t+ 766.9s ♪ 音效 sfx/step_turn +t+ 769.0s ▶ 语音:「口袋空空。」 +t+ 769.0s ♪ 音效 sfx/step_own +t+ 769.0s 〰 震动:swipe(滑动识别) +t+ 769.5s ♪ 音效 sfx/step_own +t+ 769.5s 〰 震动:swipe(滑动识别) +t+ 770.0s ♪ 音效 sfx/step_turn +t+ 771.1s ▶ 语音:「你面朝北。」 +t+ 771.1s ♪ 音效 sfx/bump +t+ 773.2s ▶ 语音:「左边有脚步声。」 +t+ 775.4s ♪ 音效 sfx/pulse +t+ 775.6s ♪ 音效 sfx/echo_1 +t+ 775.7s ♪ 音效 sfx/side_open_l +t+ 775.7s ♪ 音效 sfx/side_open_r +t+ 775.7s ♪ 音效 sfx/mat_stone +t+ 783.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 783.4s ♪ 向导音:watchdog(看门狗升级) +t+ 783.4s ♪ 音效 sfx/step_turn +t+ 791.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 791.4s ♪ 向导音:watchdog(看门狗升级) +t+ 791.4s ♪ 音效 sfx/step_own +t+ 791.4s 〰 震动:swipe(滑动识别) +t+ 791.9s ♪ 音效 sfx/step_own +t+ 791.9s 〰 震动:swipe(滑动识别) +t+ 792.4s ♪ 音效 sfx/step_turn +t+ 800.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 800.4s ♪ 向导音:watchdog(看门狗升级) +t+ 800.4s ♪ 音效 sfx/step_turn +t+ 808.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 808.4s ♪ 向导音:watchdog(看门狗升级) +t+ 808.4s ♪ 音效 sfx/step_own +t+ 808.4s 〰 震动:swipe(滑动识别) +t+ 808.9s ♪ 音效 sfx/step_own +t+ 808.9s 〰 震动:swipe(滑动识别) +t+ 809.4s ♪ 音效 sfx/step_turn +t+ 817.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 817.4s ♪ 向导音:watchdog(看门狗升级) +t+ 817.4s ♪ 音效 sfx/step_own +t+ 817.4s 〰 震动:swipe(滑动识别) +t+ 817.9s ♪ 音效 sfx/step_own +t+ 817.9s 〰 震动:swipe(滑动识别) +t+ 818.4s ♪ 音效 sfx/pulse +t+ 818.6s ♪ 音效 sfx/echo_1 +t+ 818.7s ♪ 音效 sfx/side_open_l +t+ 818.7s ♪ 音效 sfx/side_open_r +t+ 818.7s ♪ 音效 sfx/mat_stone +t+ 826.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 826.4s ♪ 向导音:watchdog(看门狗升级) +t+ 826.4s ♪ 音效 sfx/step_turn +t+ 834.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 834.4s ♪ 向导音:watchdog(看门狗升级) +t+ 834.4s ♪ 音效 sfx/step_own +t+ 834.4s 〰 震动:swipe(滑动识别) +t+ 834.9s ♪ 音效 sfx/step_own +t+ 834.9s 〰 震动:swipe(滑动识别) +t+ 835.4s ♪ 音效 sfx/step_turn +t+ 843.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 843.4s ♪ 向导音:watchdog(看门狗升级) +t+ 843.4s ♪ 音效 sfx/step_own +t+ 843.4s 〰 震动:swipe(滑动识别) +t+ 843.9s ♪ 音效 sfx/step_own +t+ 843.9s 〰 震动:swipe(滑动识别) +t+ 844.4s ♪ 音效 sfx/step_turn +t+ 852.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 852.4s ♪ 向导音:watchdog(看门狗升级) +t+ 852.4s ♪ 音效 sfx/step_own +t+ 852.4s 〰 震动:swipe(滑动识别) +t+ 852.9s ♪ 音效 sfx/step_own +t+ 852.9s 〰 震动:swipe(滑动识别) +t+ 853.4s ♪ 音效 sfx/step_turn +t+ 861.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 861.4s ♪ 向导音:watchdog(看门狗升级) +t+ 861.4s ♪ 音效 sfx/step_own +t+ 861.4s 〰 震动:swipe(滑动识别) +t+ 861.9s ♪ 音效 sfx/step_own +t+ 861.9s 〰 震动:swipe(滑动识别) +t+ 862.4s ♪ 音效 sfx/pulse +t+ 862.6s ♪ 音效 sfx/echo_1 +t+ 862.7s ♪ 音效 sfx/side_open_l +t+ 862.7s ♪ 音效 sfx/side_open_r +t+ 862.7s ♪ 音效 sfx/mat_stone +t+ 870.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 870.4s ♪ 向导音:watchdog(看门狗升级) +t+ 870.4s ♪ 音效 sfx/pulse +t+ 870.6s ♪ 音效 sfx/echo_1 +t+ 870.7s ♪ 音效 sfx/side_open_l +t+ 870.7s ♪ 音效 sfx/side_open_r +t+ 870.7s ♪ 音效 sfx/mat_stone +t+ 878.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 878.4s ♪ 向导音:watchdog(看门狗升级) +t+ 878.4s ♪ 音效 sfx/step_turn +t+ 886.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 886.4s ♪ 向导音:watchdog(看门狗升级) +t+ 886.4s ♪ 音效 sfx/step_own +t+ 886.4s 〰 震动:swipe(滑动识别) +t+ 886.9s ♪ 音效 sfx/step_own +t+ 886.9s 〰 震动:swipe(滑动识别) +t+ 887.4s ♪ 音效 sfx/step_own +t+ 887.4s 〰 震动:swipe(滑动识别) +t+ 887.9s ♪ 音效 sfx/step_own +t+ 887.9s 〰 震动:swipe(滑动识别) +t+ 888.4s ♪ 音效 sfx/step_turn +t+ 896.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 896.4s ♪ 向导音:watchdog(看门狗升级) +t+ 896.4s ♪ 音效 sfx/bump +t+ 904.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 904.4s ♪ 向导音:watchdog(看门狗升级) +t+ 904.4s ♪ 音效 sfx/bump +t+ 912.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 912.4s ♪ 向导音:watchdog(看门狗升级) +t+ 912.4s ♪ 音效 sfx/step_turn +t+ 920.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 920.4s ♪ 向导音:watchdog(看门狗升级) +t+ 920.4s ♪ 音效 sfx/step_own +t+ 920.4s 〰 震动:swipe(滑动识别) +t+ 920.9s ♪ 音效 sfx/step_own +t+ 920.9s 〰 震动:swipe(滑动识别) +t+ 921.4s ♪ 音效 sfx/step_turn +t+ 929.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 929.4s ♪ 向导音:watchdog(看门狗升级) +t+ 929.4s ♪ 音效 sfx/step_turn +t+ 937.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 937.4s ♪ 向导音:watchdog(看门狗升级) +t+ 937.4s ♪ 音效 sfx/step_own +t+ 937.4s 〰 震动:swipe(滑动识别) +t+ 937.9s ♪ 音效 sfx/step_own +t+ 937.9s 〰 震动:swipe(滑动识别) +t+ 938.4s ♪ 音效 sfx/step_turn +t+ 946.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 946.4s ♪ 向导音:watchdog(看门狗升级) +t+ 946.4s ♪ 音效 sfx/bump +t+ 954.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 954.4s ♪ 向导音:watchdog(看门狗升级) +t+ 954.4s ♪ 音效 sfx/step_turn +t+ 962.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 962.4s ♪ 向导音:watchdog(看门狗升级) +t+ 962.4s ♪ 音效 sfx/step_own +t+ 962.4s 〰 震动:swipe(滑动识别) +t+ 962.9s ♪ 音效 sfx/step_own +t+ 962.9s 〰 震动:swipe(滑动识别) +t+ 963.4s ♪ 音效 sfx/step_turn +t+ 971.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 971.4s ♪ 向导音:watchdog(看门狗升级) +t+ 971.4s ♪ 音效 sfx/bump +t+ 979.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 979.4s ♪ 向导音:watchdog(看门狗升级) +t+ 979.4s ♪ 音效 sfx/pulse +t+ 979.6s ♪ 音效 sfx/echo_1 +t+ 979.7s ♪ 音效 sfx/side_open_l +t+ 979.7s ♪ 音效 sfx/side_open_r +t+ 979.7s ♪ 音效 sfx/mat_stone +t+ 987.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 987.4s ♪ 向导音:watchdog(看门狗升级) +t+ 987.4s ♪ 音效 sfx/bump +t+ 995.4s ♪ 向导音:nudge(看门狗轻提醒) +t+ 995.4s ♪ 向导音:watchdog(看门狗升级) +t+ 995.4s ♪ 音效 sfx/step_turn +t+1003.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1003.4s ♪ 向导音:watchdog(看门狗升级) +t+1003.4s ♪ 音效 sfx/step_own +t+1003.4s 〰 震动:swipe(滑动识别) +t+1003.9s ♪ 音效 sfx/step_own +t+1003.9s 〰 震动:swipe(滑动识别) +t+1004.4s ♪ 音效 sfx/step_turn +t+1012.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1012.4s ♪ 向导音:watchdog(看门狗升级) +t+1012.4s ♪ 音效 sfx/bump +t+1020.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1020.4s ♪ 向导音:watchdog(看门狗升级) +t+1020.4s ♪ 音效 sfx/step_turn +t+1028.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1028.4s ♪ 向导音:watchdog(看门狗升级) +t+1028.4s ♪ 音效 sfx/step_turn +t+1036.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1036.4s ♪ 向导音:watchdog(看门狗升级) +t+1036.4s ♪ 音效 sfx/bump +t+1044.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1044.4s ♪ 向导音:watchdog(看门狗升级) +t+1044.4s ♪ 音效 sfx/step_turn +t+1052.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1052.4s ♪ 向导音:watchdog(看门狗升级) +t+1052.4s ♪ 音效 sfx/step_own +t+1052.4s 〰 震动:swipe(滑动识别) +t+1052.9s ♪ 音效 sfx/step_own +t+1052.9s 〰 震动:swipe(滑动识别) +t+1053.4s ♪ 音效 sfx/step_turn +t+1061.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1061.4s ♪ 向导音:watchdog(看门狗升级) +t+1061.4s ♪ 音效 sfx/bump +t+1069.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1069.4s ♪ 向导音:watchdog(看门狗升级) +t+1069.4s ♪ 音效 sfx/step_turn +t+1077.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1077.4s ♪ 向导音:watchdog(看门狗升级) +t+1077.4s ♪ 音效 sfx/step_own +t+1077.4s 〰 震动:swipe(滑动识别) +t+1077.9s ♪ 音效 sfx/step_own +t+1077.9s 〰 震动:swipe(滑动识别) +t+1078.4s ♪ 音效 sfx/pulse +t+1078.6s ♪ 音效 sfx/echo_1 +t+1078.7s ♪ 音效 sfx/side_open_l +t+1078.7s ♪ 音效 sfx/side_wall_r +t+1078.7s ♪ 音效 sfx/mat_stone +t+1086.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1086.4s ♪ 向导音:watchdog(看门狗升级) +t+1086.4s ♪ 音效 sfx/step_turn +t+1094.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1094.4s ♪ 向导音:watchdog(看门狗升级) +t+1094.4s ♪ 音效 sfx/bump +t+1102.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1102.4s ♪ 向导音:watchdog(看门狗升级) +t+1102.4s ♪ 音效 sfx/step_turn +t+1110.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1110.4s ♪ 向导音:watchdog(看门狗升级) +t+1110.4s ♪ 音效 sfx/step_own +t+1110.4s 〰 震动:swipe(滑动识别) +t+1110.9s ♪ 音效 sfx/step_own +t+1110.9s 〰 震动:swipe(滑动识别) +t+1111.4s ♪ 音效 sfx/step_turn +t+1119.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1119.4s ♪ 向导音:watchdog(看门狗升级) +t+1119.4s ♪ 音效 sfx/bump +t+1127.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1127.4s ♪ 向导音:watchdog(看门狗升级) +t+1127.4s ♪ 音效 sfx/step_turn +t+1135.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1135.4s ♪ 向导音:watchdog(看门狗升级) +t+1135.4s ♪ 音效 sfx/step_turn +t+1143.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1143.4s ♪ 向导音:watchdog(看门狗升级) +t+1143.4s ♪ 音效 sfx/step_own +t+1143.4s 〰 震动:swipe(滑动识别) +t+1143.9s ♪ 音效 sfx/step_own +t+1143.9s 〰 震动:swipe(滑动识别) +t+1144.4s ♪ 音效 sfx/step_turn +t+1152.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1152.4s ♪ 向导音:watchdog(看门狗升级) +t+1152.4s ♪ 音效 sfx/step_turn +t+1160.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1160.4s ♪ 向导音:watchdog(看门狗升级) +t+1160.4s ♪ 音效 sfx/step_own +t+1160.4s 〰 震动:swipe(滑动识别) +t+1160.9s ♪ 音效 sfx/step_own +t+1160.9s 〰 震动:swipe(滑动识别) +t+1161.4s ♪ 音效 sfx/step_turn +t+1169.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1169.4s ♪ 向导音:watchdog(看门狗升级) +t+1169.4s ♪ 音效 sfx/step_own +t+1169.4s 〰 震动:swipe(滑动识别) +t+1169.9s ♪ 音效 sfx/step_own +t+1169.9s 〰 震动:swipe(滑动识别) +t+1170.4s ▶ 语音:「这是第一层。」 +t+1172.5s ▶ 语音:「你毫发无损。」 +t+1172.5s ▶ 语音:「这是第一层。」 +t+1174.6s ▶ 语音:「你毫发无损。」 +t+1174.6s ♪ 音效 sfx/step_turn +t+1176.7s ▶ 语音:「口袋空空。」 +t+1176.7s ♪ 音效 sfx/bump +t+1178.8s ▶ 语音:「你面朝西。」 +t+1181.0s ▶ 语音:「附近很安静。」 +t+1181.0s ▶ 语音:「这是第一层。」 +t+1183.1s ▶ 语音:「你毫发无损。」 +t+1183.1s ♪ 音效 sfx/pulse +t+1183.3s ♪ 音效 sfx/echo_1 +t+1183.4s ♪ 音效 sfx/side_wall_l +t+1183.4s ♪ 音效 sfx/side_open_r +t+1183.5s ♪ 音效 sfx/mat_stone +t+1185.2s ▶ 语音:「口袋空空。」 +t+1185.2s ♪ 音效 sfx/step_turn +t+1187.3s ▶ 语音:「你面朝北。」 +t+1187.3s ♪ 音效 sfx/step_own +t+1187.3s 〰 震动:swipe(滑动识别) +t+1187.8s ♪ 音效 sfx/step_own +t+1187.8s 〰 震动:swipe(滑动识别) +t+1188.3s ♪ 音效 sfx/step_turn +t+1189.4s ▶ 语音:「右边有脚步声。」 +t+1189.4s ♪ 音效 sfx/bump +t+1191.6s ♪ 音效 sfx/step_turn +t+1199.6s ♪ 向导音:nudge(看门狗轻提醒) +t+1199.6s ♪ 向导音:watchdog(看门狗升级) +t+1199.6s ♪ 音效 sfx/step_own +t+1199.6s 〰 震动:swipe(滑动识别) +t+1200.1s ♪ 音效 sfx/step_own +t+1200.1s 〰 震动:swipe(滑动识别) +t+1200.6s ♪ 音效 sfx/pulse +t+1200.8s ♪ 音效 sfx/echo_1 +t+1200.9s ♪ 音效 sfx/side_open_l +t+1200.9s ♪ 音效 sfx/side_wall_r +t+1200.9s ♪ 音效 sfx/mat_stone +t+1208.6s ♪ 向导音:nudge(看门狗轻提醒) +t+1208.6s ♪ 向导音:watchdog(看门狗升级) +t+1208.6s ♪ 音效 sfx/step_turn +t+1216.6s ♪ 向导音:nudge(看门狗轻提醒) +t+1216.6s ♪ 向导音:watchdog(看门狗升级) +t+1216.6s ♪ 音效 sfx/bump +t+1224.6s ♪ 向导音:nudge(看门狗轻提醒) +t+1224.6s ♪ 向导音:watchdog(看门狗升级) +t+1224.6s ♪ 音效 sfx/bump +t+1232.6s ♪ 向导音:nudge(看门狗轻提醒) +t+1232.6s ♪ 向导音:watchdog(看门狗升级) +t+1232.6s ♪ 音效 sfx/pulse +t+1232.8s ♪ 音效 sfx/echo_1 +t+1232.9s ♪ 音效 sfx/side_wall_l +t+1232.9s ♪ 音效 sfx/side_open_r +t+1232.9s ♪ 音效 sfx/mat_stone +t+1240.6s ♪ 向导音:nudge(看门狗轻提醒) +t+1240.6s ♪ 向导音:watchdog(看门狗升级) +t+1240.6s ▶ 语音:「这是第一层。」 +t+1242.7s ▶ 语音:「你毫发无损。」 +t+1242.7s ▶ 语音:「这是第一层。」 +t+1244.8s ▶ 语音:「你毫发无损。」 +t+1244.8s ♪ 音效 sfx/step_turn +t+1246.9s ▶ 语音:「口袋空空。」 +t+1246.9s ♪ 音效 sfx/step_own +t+1246.9s 〰 震动:swipe(滑动识别) +t+1247.4s ♪ 音效 sfx/step_own +t+1247.4s 〰 震动:swipe(滑动识别) +t+1247.9s ♪ 音效 sfx/step_turn +t+1249.0s ▶ 语音:「你面朝北。」 +t+1249.0s ♪ 音效 sfx/bump +t+1251.2s ▶ 语音:「右边有脚步声。」 +t+1253.3s ♪ 音效 sfx/step_turn +t+1261.3s ♪ 向导音:nudge(看门狗轻提醒) +t+1261.3s ♪ 向导音:watchdog(看门狗升级) +t+1261.3s ▶ 语音:「这是第一层。」 +t+1263.4s ▶ 语音:「你毫发无损。」 +t+1263.4s ♪ 音效 sfx/pulse +t+1263.8s ♪ 音效 sfx/side_wall_l +t+1263.8s ♪ 音效 sfx/side_open_r +t+1264.2s ♪ 音效 sfx/echo_3 +t+1264.2s ♪ 音效 sfx/step_turn +t+1264.3s ♪ 音效 sfx/mat_stone +t+1265.5s ▶ 语音:「口袋空空。」 +t+1265.5s ♪ 音效 sfx/step_own +t+1265.5s 〰 震动:swipe(滑动识别) +t+1266.0s ♪ 音效 sfx/step_own +t+1266.0s 〰 震动:swipe(滑动识别) +t+1266.5s ♪ 音效 sfx/step_turn +t+1267.6s ▶ 语音:「你面朝西。」 +t+1267.6s ♪ 音效 sfx/pulse +t+1267.9s ♪ 音效 sfx/echo_1 +t+1268.0s ♪ 音效 sfx/side_open_l +t+1268.0s ♪ 音效 sfx/side_wall_r +t+1268.0s ♪ 音效 sfx/mat_stone +t+1269.8s ▶ 语音:「附近很安静。」 +t+1269.8s ♪ 音效 sfx/pulse +t+1270.0s ♪ 音效 sfx/echo_1 +t+1270.1s ♪ 音效 sfx/side_open_l +t+1270.1s ♪ 音效 sfx/side_wall_r +t+1270.1s ♪ 音效 sfx/mat_stone +t+1271.9s ♪ 音效 sfx/step_turn +t+1279.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1279.9s ♪ 向导音:watchdog(看门狗升级) +t+1279.9s ♪ 音效 sfx/step_own +t+1279.9s 〰 震动:swipe(滑动识别) +t+1280.4s ♪ 音效 sfx/step_own +t+1280.4s 〰 震动:swipe(滑动识别) +t+1280.9s ♪ 音效 sfx/step_turn +t+1288.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1288.9s ♪ 向导音:watchdog(看门狗升级) +t+1288.9s ♪ 音效 sfx/pulse +t+1289.2s ♪ 音效 sfx/side_wall_l +t+1289.2s ♪ 音效 sfx/side_open_r +t+1289.7s ♪ 音效 sfx/echo_3 +t+1289.7s ♪ 音效 sfx/step_own +t+1289.7s 〰 震动:swipe(滑动识别) +t+1289.8s ♪ 音效 sfx/mat_stone +t+1290.2s ♪ 音效 sfx/step_own +t+1290.2s 〰 震动:swipe(滑动识别) +t+1290.7s ♪ 音效 sfx/step_turn +t+1298.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1298.7s ♪ 向导音:watchdog(看门狗升级) +t+1298.7s ♪ 音效 sfx/bump +t+1306.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1306.7s ♪ 向导音:watchdog(看门狗升级) +t+1306.7s ♪ 音效 sfx/step_turn +t+1314.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1314.7s ♪ 向导音:watchdog(看门狗升级) +t+1314.7s ♪ 音效 sfx/pulse +t+1314.9s ♪ 音效 sfx/echo_1 +t+1315.0s ♪ 音效 sfx/side_open_l +t+1315.0s ♪ 音效 sfx/side_wall_r +t+1315.0s ♪ 音效 sfx/mat_stone +t+1322.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1322.7s ♪ 向导音:watchdog(看门狗升级) +t+1322.7s ♪ 音效 sfx/step_turn +t+1330.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1330.7s ♪ 向导音:watchdog(看门狗升级) +t+1330.7s ♪ 音效 sfx/bump +t+1338.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1338.7s ♪ 向导音:watchdog(看门狗升级) +t+1338.7s ♪ 音效 sfx/step_turn +t+1346.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1346.7s ♪ 向导音:watchdog(看门狗升级) +t+1346.7s ♪ 音效 sfx/step_own +t+1346.7s 〰 震动:swipe(滑动识别) +t+1347.2s ♪ 音效 sfx/step_own +t+1347.2s 〰 震动:swipe(滑动识别) +t+1347.7s ♪ 音效 sfx/step_turn +t+1355.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1355.7s ♪ 向导音:watchdog(看门狗升级) +t+1355.7s ♪ 音效 sfx/bump +t+1363.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1363.7s ♪ 向导音:watchdog(看门狗升级) +t+1363.7s ♪ 音效 sfx/pulse +t+1363.9s ♪ 音效 sfx/echo_1 +t+1364.0s ♪ 音效 sfx/side_open_l +t+1364.0s ♪ 音效 sfx/side_open_r +t+1364.0s ♪ 音效 sfx/mat_stone +t+1371.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1371.7s ♪ 向导音:watchdog(看门狗升级) +t+1371.7s ♪ 音效 sfx/step_turn +t+1379.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1379.7s ♪ 向导音:watchdog(看门狗升级) +t+1379.7s ♪ 音效 sfx/step_own +t+1379.7s 〰 震动:swipe(滑动识别) +t+1380.2s ♪ 音效 sfx/step_own +t+1380.2s 〰 震动:swipe(滑动识别) +t+1380.7s ♪ 音效 sfx/step_turn +t+1388.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1388.7s ♪ 向导音:watchdog(看门狗升级) +t+1388.7s ♪ 音效 sfx/bump +t+1396.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1396.7s ♪ 向导音:watchdog(看门狗升级) +t+1396.7s ♪ 音效 sfx/step_turn +t+1404.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1404.7s ♪ 向导音:watchdog(看门狗升级) +t+1404.7s ♪ 音效 sfx/step_own +t+1404.7s 〰 震动:swipe(滑动识别) +t+1405.2s ♪ 音效 sfx/step_own +t+1405.2s 〰 震动:swipe(滑动识别) +t+1405.7s ♪ 音效 sfx/pulse +t+1406.0s ♪ 音效 sfx/side_open_l +t+1406.0s ♪ 音效 sfx/side_wall_r +t+1406.8s ♪ 音效 sfx/echo_4 +t+1406.8s ♪ 音效 sfx/step_turn +t+1406.9s ♪ 音效 sfx/mat_stone +t+1414.8s ♪ 向导音:nudge(看门狗轻提醒) +t+1414.8s ♪ 向导音:watchdog(看门狗升级) +t+1414.8s ♪ 音效 sfx/bump +t+1422.8s ♪ 向导音:nudge(看门狗轻提醒) +t+1422.8s ♪ 向导音:watchdog(看门狗升级) +t+1422.8s ♪ 音效 sfx/pulse +t+1423.0s ♪ 音效 sfx/echo_1 +t+1423.1s ♪ 音效 sfx/side_open_l +t+1423.1s ♪ 音效 sfx/side_open_r +t+1423.2s ♪ 音效 sfx/mat_stone +t+1430.8s ♪ 向导音:nudge(看门狗轻提醒) +t+1430.8s ♪ 向导音:watchdog(看门狗升级) +t+1430.8s ♪ 音效 sfx/bump +t+1438.8s ♪ 向导音:nudge(看门狗轻提醒) +t+1438.8s ♪ 向导音:watchdog(看门狗升级) +t+1438.8s ♪ 音效 sfx/bump +t+1446.8s ♪ 向导音:nudge(看门狗轻提醒) +t+1446.8s ♪ 向导音:watchdog(看门狗升级) +t+1446.8s ♪ 音效 sfx/step_turn +t+1454.8s ♪ 向导音:nudge(看门狗轻提醒) +t+1454.8s ♪ 向导音:watchdog(看门狗升级) +t+1454.8s ♪ 音效 sfx/step_own +t+1454.8s 〰 震动:swipe(滑动识别) +t+1455.3s ♪ 音效 sfx/step_own +t+1455.3s 〰 震动:swipe(滑动识别) +t+1455.8s ♪ 音效 sfx/step_turn +t+1463.8s ♪ 向导音:nudge(看门狗轻提醒) +t+1463.8s ♪ 向导音:watchdog(看门狗升级) +t+1463.8s ♪ 音效 sfx/bump +t+1471.8s ♪ 向导音:nudge(看门狗轻提醒) +t+1471.8s ♪ 向导音:watchdog(看门狗升级) +t+1471.8s ♪ 音效 sfx/pulse +t+1472.0s ♪ 音效 sfx/echo_1 +t+1472.1s ♪ 音效 sfx/side_wall_l +t+1472.1s ♪ 音效 sfx/side_open_r +t+1472.2s ♪ 音效 sfx/mat_stone +t+1479.8s ♪ 向导音:nudge(看门狗轻提醒) +t+1479.8s ♪ 向导音:watchdog(看门狗升级) +t+1479.8s ▶ 语音:「这是第一层。」 +t+1481.9s ▶ 语音:「你毫发无损。」 +t+1481.9s ♪ 音效 sfx/step_turn +t+1484.0s ▶ 语音:「口袋空空。」 +t+1484.0s ♪ 音效 sfx/step_own +t+1484.0s 〰 震动:swipe(滑动识别) +t+1484.5s ♪ 音效 sfx/step_own +t+1484.5s 〰 震动:swipe(滑动识别) +t+1485.0s ♪ 音效 sfx/step_turn +t+1486.1s ▶ 语音:「你面朝北。」 +t+1486.1s ♪ 音效 sfx/bump +t+1488.3s ▶ 语音:「右边有脚步声。」 +t+1490.4s ♪ 音效 sfx/step_turn +t+1498.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1498.4s ♪ 向导音:watchdog(看门狗升级) +t+1498.4s ♪ 音效 sfx/step_own +t+1498.4s 〰 震动:swipe(滑动识别) +t+1498.9s ♪ 音效 sfx/step_own +t+1498.9s 〰 震动:swipe(滑动识别) +t+1499.4s ♪ 音效 sfx/step_turn +t+1507.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1507.4s ♪ 向导音:watchdog(看门狗升级) +t+1507.4s ♪ 音效 sfx/step_turn +t+1515.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1515.4s ♪ 向导音:watchdog(看门狗升级) +t+1515.4s ♪ 音效 sfx/step_own +t+1515.4s 〰 震动:swipe(滑动识别) +t+1515.9s ♪ 音效 sfx/step_own +t+1515.9s 〰 震动:swipe(滑动识别) +t+1516.4s ♪ 音效 sfx/step_turn +t+1524.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1524.4s ♪ 向导音:watchdog(看门狗升级) +t+1524.4s ♪ 音效 sfx/bump +t+1532.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1532.4s ♪ 向导音:watchdog(看门狗升级) +t+1532.4s ♪ 音效 sfx/step_turn +t+1540.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1540.4s ♪ 向导音:watchdog(看门狗升级) +t+1540.4s ♪ 音效 sfx/step_own +t+1540.4s 〰 震动:swipe(滑动识别) +t+1540.9s ♪ 音效 sfx/step_own +t+1540.9s 〰 震动:swipe(滑动识别) +t+1541.4s ♪ 音效 sfx/bump +t+1549.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1549.4s ♪ 向导音:watchdog(看门狗升级) +t+1549.4s ♪ 音效 sfx/pulse +t+1549.6s ♪ 音效 sfx/echo_1 +t+1549.7s ♪ 音效 sfx/side_open_l +t+1549.7s ♪ 音效 sfx/side_wall_r +t+1549.8s ♪ 音效 sfx/mat_stone +t+1557.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1557.4s ♪ 向导音:watchdog(看门狗升级) +t+1557.4s ♪ 音效 sfx/step_turn +t+1565.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1565.4s ♪ 向导音:watchdog(看门狗升级) +t+1565.4s ♪ 音效 sfx/bump +t+1573.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1573.4s ♪ 向导音:watchdog(看门狗升级) +t+1573.4s ♪ 音效 sfx/step_turn +t+1581.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1581.4s ♪ 向导音:watchdog(看门狗升级) +t+1581.4s ♪ 音效 sfx/step_own +t+1581.4s 〰 震动:swipe(滑动识别) +t+1581.9s ♪ 音效 sfx/step_own +t+1581.9s 〰 震动:swipe(滑动识别) +t+1582.4s ♪ 音效 sfx/step_turn +t+1590.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1590.4s ♪ 向导音:watchdog(看门狗升级) +t+1590.4s ♪ 音效 sfx/bump +t+1598.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1598.4s ♪ 向导音:watchdog(看门狗升级) +t+1598.4s ♪ 音效 sfx/step_turn +t+1606.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1606.4s ♪ 向导音:watchdog(看门狗升级) +t+1606.4s ♪ 音效 sfx/step_own +t+1606.4s 〰 震动:swipe(滑动识别) +t+1606.9s ♪ 音效 sfx/step_own +t+1606.9s 〰 震动:swipe(滑动识别) +t+1607.4s ♪ 音效 sfx/bump +t+1615.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1615.4s ♪ 向导音:watchdog(看门狗升级) +t+1615.4s ♪ 音效 sfx/step_turn +t+1623.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1623.4s ♪ 向导音:watchdog(看门狗升级) +t+1623.4s ♪ 音效 sfx/bump +t+1631.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1631.4s ♪ 向导音:watchdog(看门狗升级) +t+1631.4s ♪ 音效 sfx/step_turn +t+1639.4s ♪ 向导音:nudge(看门狗轻提醒) +t+1639.4s ♪ 向导音:watchdog(看门狗升级) +t+1639.4s ♪ 音效 sfx/step_own +t+1639.4s 〰 震动:swipe(滑动识别) +t+1639.9s ♪ 音效 sfx/step_own +t+1639.9s 〰 震动:swipe(滑动识别) +t+1640.4s ♪ 音效 sfx/pulse +t+1640.7s ♪ 音效 sfx/side_open_l +t+1640.7s ♪ 音效 sfx/side_wall_r +t+1641.5s ♪ 音效 sfx/echo_4 +t+1641.5s ♪ 音效 sfx/step_turn +t+1641.6s ♪ 音效 sfx/mat_stone +t+1649.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1649.5s ♪ 向导音:watchdog(看门狗升级) +t+1649.5s ♪ 音效 sfx/bump +t+1657.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1657.5s ♪ 向导音:watchdog(看门狗升级) +t+1657.5s ♪ 音效 sfx/step_turn +t+1665.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1665.5s ♪ 向导音:watchdog(看门狗升级) +t+1665.5s ♪ 音效 sfx/step_own +t+1665.5s 〰 震动:swipe(滑动识别) +t+1666.0s ♪ 音效 sfx/step_own +t+1666.0s 〰 震动:swipe(滑动识别) +t+1666.5s ♪ 音效 sfx/step_turn +t+1674.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1674.5s ♪ 向导音:watchdog(看门狗升级) +t+1674.5s ♪ 音效 sfx/bump +t+1682.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1682.5s ♪ 向导音:watchdog(看门狗升级) +t+1682.5s ♪ 音效 sfx/step_turn +t+1690.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1690.5s ♪ 向导音:watchdog(看门狗升级) +t+1690.5s ♪ 音效 sfx/step_own +t+1690.5s 〰 震动:swipe(滑动识别) +t+1691.0s ♪ 音效 sfx/step_own +t+1691.0s 〰 震动:swipe(滑动识别) +t+1691.5s ♪ 音效 sfx/step_turn +t+1699.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1699.5s ♪ 向导音:watchdog(看门狗升级) +t+1699.5s ♪ 音效 sfx/step_turn +t+1707.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1707.5s ♪ 向导音:watchdog(看门狗升级) +t+1707.5s ♪ 音效 sfx/step_own +t+1707.5s 〰 震动:swipe(滑动识别) +t+1708.0s ♪ 音效 sfx/step_own +t+1708.0s 〰 震动:swipe(滑动识别) +t+1708.5s ♪ 音效 sfx/step_turn +t+1716.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1716.5s ♪ 向导音:watchdog(看门狗升级) +t+1716.5s ♪ 音效 sfx/bump +t+1724.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1724.5s ♪ 向导音:watchdog(看门狗升级) +t+1724.5s ♪ 音效 sfx/step_turn +t+1732.5s ♪ 向导音:nudge(看门狗轻提醒) +t+1732.5s ♪ 向导音:watchdog(看门狗升级) +t+1732.5s ♪ 音效 sfx/step_own +t+1732.5s 〰 震动:swipe(滑动识别) +t+1733.0s ♪ 音效 sfx/step_own +t+1733.0s 〰 震动:swipe(滑动识别) +t+1733.5s ▶ 语音:「这是第一层。」 +t+1735.6s ▶ 语音:「你毫发无损。」 +t+1735.6s ♪ 音效 sfx/step_turn +t+1737.7s ▶ 语音:「口袋空空。」 +t+1737.7s ♪ 音效 sfx/bump +t+1739.8s ▶ 语音:「你面朝东。」 +t+1742.0s ▶ 语音:「附近很安静。」 +t+1742.0s ♪ 音效 sfx/bump +t+1744.1s ♪ 音效 sfx/step_turn +t+1752.1s ♪ 向导音:nudge(看门狗轻提醒) +t+1752.1s ♪ 向导音:watchdog(看门狗升级) +t+1752.1s ♪ 音效 sfx/step_own +t+1752.1s 〰 震动:swipe(滑动识别) +t+1752.6s ♪ 音效 sfx/step_own +t+1752.6s 〰 震动:swipe(滑动识别) +t+1753.1s ♪ 音效 sfx/step_turn +t+1761.1s ♪ 向导音:nudge(看门狗轻提醒) +t+1761.1s ♪ 向导音:watchdog(看门狗升级) +t+1761.1s ♪ 音效 sfx/bump +t+1769.1s ♪ 向导音:nudge(看门狗轻提醒) +t+1769.1s ♪ 向导音:watchdog(看门狗升级) +t+1769.1s ▶ 语音:「这是第一层。」 +t+1771.2s ▶ 语音:「你毫发无损。」 +t+1771.2s ♪ 音效 sfx/step_turn +t+1773.3s ▶ 语音:「口袋空空。」 +t+1773.3s ♪ 音效 sfx/step_own +t+1773.3s 〰 震动:swipe(滑动识别) +t+1773.8s ♪ 音效 sfx/step_own +t+1773.8s 〰 震动:swipe(滑动识别) +t+1774.3s ♪ 音效 sfx/step_turn +t+1775.4s ▶ 语音:「你面朝北。」 +t+1775.4s ♪ 音效 sfx/bump +t+1777.6s ▶ 语音:「右边有脚步声。」 +t+1779.7s ♪ 音效 sfx/step_turn +t+1787.7s ♪ 向导音:nudge(看门狗轻提醒) +t+1787.7s ♪ 向导音:watchdog(看门狗升级) +t+1787.7s ♪ 音效 sfx/step_own +t+1787.7s 〰 震动:swipe(滑动识别) +t+1788.2s ♪ 音效 sfx/step_own +t+1788.2s 〰 震动:swipe(滑动识别) +t+1788.7s ▶ 语音:「这是第一层。」 +t+1790.8s ▶ 语音:「你毫发无损。」 +t+1790.8s ♪ 音效 sfx/bump +t+1792.9s ▶ 语音:「口袋空空。」 +t+1795.0s ▶ 语音:「你面朝西。」 +t+1795.0s ♪ 音效 sfx/step_turn +t+1797.2s ▶ 语音:「附近很安静。」 +t+1797.2s ♪ 音效 sfx/bump +t+1799.3s ♪ 音效 sfx/pulse +t+1799.5s ♪ 音效 sfx/echo_1 +t+1799.6s ♪ 音效 sfx/side_wall_l +t+1799.6s ♪ 音效 sfx/side_open_r +t+1799.7s ♪ 音效 sfx/mat_stone +t+1807.3s ♪ 向导音:nudge(看门狗轻提醒) +t+1807.3s ♪ 向导音:watchdog(看门狗升级) +t+1807.3s ♪ 音效 sfx/pulse +t+1807.5s ♪ 音效 sfx/echo_1 +t+1807.6s ♪ 音效 sfx/side_wall_l +t+1807.6s ♪ 音效 sfx/side_open_r +t+1807.7s ♪ 音效 sfx/mat_stone +t+1815.3s ♪ 向导音:nudge(看门狗轻提醒) +t+1815.3s ♪ 向导音:watchdog(看门狗升级) +t+1815.3s ♪ 音效 sfx/step_turn +t+1823.3s ♪ 向导音:nudge(看门狗轻提醒) +t+1823.3s ♪ 向导音:watchdog(看门狗升级) +t+1823.3s ♪ 音效 sfx/step_own +t+1823.3s 〰 震动:swipe(滑动识别) +t+1823.8s ♪ 音效 sfx/step_own +t+1823.8s 〰 震动:swipe(滑动识别) +t+1824.3s ♪ 音效 sfx/step_turn +t+1832.3s ♪ 向导音:nudge(看门狗轻提醒) +t+1832.3s ♪ 向导音:watchdog(看门狗升级) +t+1832.3s ♪ 音效 sfx/bump +t+1840.3s ♪ 向导音:nudge(看门狗轻提醒) +t+1840.3s ♪ 向导音:watchdog(看门狗升级) +t+1840.3s ▶ 语音:「这是第一层。」 +t+1842.4s ▶ 语音:「你毫发无损。」 +t+1842.4s ♪ 音效 sfx/step_turn +t+1844.5s ▶ 语音:「口袋空空。」 +t+1844.5s ♪ 音效 sfx/step_own +t+1844.5s 〰 震动:swipe(滑动识别) +t+1845.0s ♪ 音效 sfx/step_own +t+1845.0s 〰 震动:swipe(滑动识别) +t+1845.5s ♪ 音效 sfx/step_turn +t+1846.6s ▶ 语音:「你面朝南。」 +t+1846.6s ♪ 音效 sfx/pulse +t+1847.0s ♪ 音效 sfx/side_open_l +t+1847.0s ♪ 音效 sfx/side_wall_r +t+1847.4s ♪ 音效 sfx/echo_3 +t+1847.4s ♪ 音效 sfx/step_turn +t+1847.6s ♪ 音效 sfx/mat_stone +t+1848.8s ▶ 语音:「右边有脚步声。」 +t+1848.8s ♪ 音效 sfx/step_own +t+1848.8s 〰 震动:swipe(滑动识别) +t+1849.3s ♪ 音效 sfx/step_own +t+1849.3s 〰 震动:swipe(滑动识别) +t+1849.8s ♪ 音效 sfx/step_turn +t+1850.9s ♪ 音效 sfx/bump +t+1858.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1858.9s ♪ 向导音:watchdog(看门狗升级) +t+1858.9s ♪ 音效 sfx/step_turn +t+1866.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1866.9s ♪ 向导音:watchdog(看门狗升级) +t+1866.9s ♪ 音效 sfx/step_own +t+1866.9s 〰 震动:swipe(滑动识别) +t+1867.4s ♪ 音效 sfx/step_own +t+1867.4s 〰 震动:swipe(滑动识别) +t+1867.9s ♪ 音效 sfx/pulse +t+1868.1s ♪ 音效 sfx/echo_1 +t+1868.2s ♪ 音效 sfx/side_open_l +t+1868.2s ♪ 音效 sfx/side_wall_r +t+1868.3s ♪ 音效 sfx/mat_stone +t+1875.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1875.9s ♪ 向导音:watchdog(看门狗升级) +t+1875.9s ♪ 音效 sfx/step_turn +t+1883.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1883.9s ♪ 向导音:watchdog(看门狗升级) +t+1883.9s ♪ 音效 sfx/bump +t+1891.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1891.9s ♪ 向导音:watchdog(看门狗升级) +t+1891.9s ♪ 音效 sfx/step_turn +t+1899.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1899.9s ♪ 向导音:watchdog(看门狗升级) +t+1899.9s ♪ 音效 sfx/step_own +t+1899.9s 〰 震动:swipe(滑动识别) +t+1900.4s ♪ 音效 sfx/step_own +t+1900.4s 〰 震动:swipe(滑动识别) +t+1900.9s ♪ 音效 sfx/step_turn +t+1908.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1908.9s ♪ 向导音:watchdog(看门狗升级) +t+1908.9s ♪ 音效 sfx/bump +t+1916.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1916.9s ♪ 向导音:watchdog(看门狗升级) +t+1916.9s ♪ 音效 sfx/step_turn +t+1924.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1924.9s ♪ 向导音:watchdog(看门狗升级) +t+1924.9s ♪ 音效 sfx/step_own +t+1924.9s 〰 震动:swipe(滑动识别) +t+1925.4s ♪ 音效 sfx/step_own +t+1925.4s 〰 震动:swipe(滑动识别) +t+1925.9s ♪ 音效 sfx/step_turn +t+1933.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1933.9s ♪ 向导音:watchdog(看门狗升级) +t+1933.9s ♪ 音效 sfx/bump +t+1941.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1941.9s ♪ 向导音:watchdog(看门狗升级) +t+1941.9s ♪ 音效 sfx/step_turn +t+1949.9s ♪ 向导音:nudge(看门狗轻提醒) +t+1949.9s ♪ 向导音:watchdog(看门狗升级) +t+1949.9s ♪ 音效 sfx/step_own +t+1949.9s 〰 震动:swipe(滑动识别) +t+1950.4s ♪ 音效 sfx/step_own +t+1950.4s 〰 震动:swipe(滑动识别) +t+1950.9s ♪ 音效 sfx/pulse +t+1951.2s ♪ 音效 sfx/side_open_l +t+1951.2s ♪ 音效 sfx/side_wall_r +t+1952.0s ♪ 音效 sfx/echo_4 +t+1952.0s ♪ 音效 sfx/step_turn +t+1952.1s ♪ 音效 sfx/mat_stone +t+1960.0s ♪ 向导音:nudge(看门狗轻提醒) +t+1960.0s ♪ 向导音:watchdog(看门狗升级) +t+1960.0s ♪ 音效 sfx/bump +t+1968.0s ♪ 向导音:nudge(看门狗轻提醒) +t+1968.0s ♪ 向导音:watchdog(看门狗升级) +t+1968.0s ♪ 音效 sfx/step_turn +t+1976.0s ♪ 向导音:nudge(看门狗轻提醒) +t+1976.0s ♪ 向导音:watchdog(看门狗升级) +t+1976.0s ♪ 音效 sfx/step_turn +t+1984.0s ♪ 向导音:nudge(看门狗轻提醒) +t+1984.0s ♪ 向导音:watchdog(看门狗升级) +t+1984.0s ♪ 音效 sfx/step_own +t+1984.0s 〰 震动:swipe(滑动识别) +t+1984.5s ♪ 音效 sfx/step_own +t+1984.5s 〰 震动:swipe(滑动识别) +t+1985.0s ♪ 音效 sfx/pulse +t+1985.2s ♪ 音效 sfx/echo_1 +t+1985.3s ♪ 音效 sfx/side_wall_l +t+1985.3s ♪ 音效 sfx/side_open_r +t+1985.3s ♪ 音效 sfx/mat_stone +t+1993.0s ♪ 向导音:nudge(看门狗轻提醒) +t+1993.0s ♪ 向导音:watchdog(看门狗升级) +t+1993.0s ♪ 音效 sfx/step_turn +t+2001.0s ♪ 向导音:nudge(看门狗轻提醒) +t+2001.0s ♪ 向导音:watchdog(看门狗升级) +t+2001.0s ♪ 音效 sfx/step_turn +t+2009.0s ♪ 向导音:nudge(看门狗轻提醒) +t+2009.0s ♪ 向导音:watchdog(看门狗升级) +t+2009.0s ♪ 音效 sfx/step_turn +t+2017.0s ♪ 向导音:nudge(看门狗轻提醒) +t+2017.0s ♪ 向导音:watchdog(看门狗升级) +t+2017.0s ♪ 音效 sfx/bump +t+2025.0s ♪ 向导音:nudge(看门狗轻提醒) +t+2025.0s ♪ 向导音:watchdog(看门狗升级) +t+2025.0s ▶ 语音:「这是第一层。」 +t+2027.1s ▶ 语音:「你毫发无损。」 +t+2027.1s ♪ 音效 sfx/step_turn +t+2029.2s ▶ 语音:「口袋空空。」 +t+2029.2s ♪ 音效 sfx/step_own +t+2029.2s 〰 震动:swipe(滑动识别) +t+2029.7s ♪ 音效 sfx/step_own +t+2029.7s 〰 震动:swipe(滑动识别) +t+2030.2s ♪ 音效 sfx/step_turn +t+2031.3s ▶ 语音:「你面朝西。」 +t+2031.3s ♪ 音效 sfx/pulse +t+2031.7s ♪ 音效 sfx/side_open_l +t+2031.7s ♪ 音效 sfx/side_wall_r +t+2032.4s ♪ 音效 sfx/echo_4 +t+2032.4s ♪ 音效 sfx/step_turn +t+2032.6s ♪ 音效 sfx/mat_stone +t+2033.5s ▶ 语音:「附近很安静。」 +t+2033.5s ♪ 音效 sfx/step_turn +t+2035.6s ♪ 音效 sfx/step_turn +t+2043.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2043.6s ♪ 向导音:watchdog(看门狗升级) +t+2043.6s ♪ 音效 sfx/step_own +t+2043.6s 〰 震动:swipe(滑动识别) +t+2044.1s ♪ 音效 sfx/step_own +t+2044.1s 〰 震动:swipe(滑动识别) +t+2044.6s ♪ 音效 sfx/pulse +t+2044.8s ♪ 音效 sfx/echo_1 +t+2044.9s ♪ 音效 sfx/side_wall_l +t+2044.9s ♪ 音效 sfx/side_open_r +t+2045.0s ♪ 音效 sfx/mat_stone +t+2052.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2052.6s ♪ 向导音:watchdog(看门狗升级) +t+2052.6s ♪ 音效 sfx/step_turn +t+2060.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2060.6s ♪ 向导音:watchdog(看门狗升级) +t+2060.6s ♪ 音效 sfx/step_turn +t+2068.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2068.6s ♪ 向导音:watchdog(看门狗升级) +t+2068.6s ♪ 音效 sfx/step_turn +t+2076.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2076.6s ♪ 向导音:watchdog(看门狗升级) +t+2076.6s ♪ 音效 sfx/bump +t+2084.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2084.6s ♪ 向导音:watchdog(看门狗升级) +t+2084.6s ♪ 音效 sfx/step_turn +t+2092.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2092.6s ♪ 向导音:watchdog(看门狗升级) +t+2092.6s ♪ 音效 sfx/step_own +t+2092.6s 〰 震动:swipe(滑动识别) +t+2093.1s ♪ 音效 sfx/step_own +t+2093.1s 〰 震动:swipe(滑动识别) +t+2093.6s ♪ 音效 sfx/step_turn +t+2101.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2101.6s ♪ 向导音:watchdog(看门狗升级) +t+2101.6s ♪ 音效 sfx/step_own +t+2101.6s 〰 震动:swipe(滑动识别) +t+2102.1s ♪ 音效 sfx/step_own +t+2102.1s 〰 震动:swipe(滑动识别) +t+2102.6s ♪ 音效 sfx/step_turn +t+2110.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2110.6s ♪ 向导音:watchdog(看门狗升级) +t+2110.6s ♪ 音效 sfx/bump +t+2118.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2118.6s ♪ 向导音:watchdog(看门狗升级) +t+2118.6s ♪ 音效 sfx/step_turn +t+2126.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2126.6s ♪ 向导音:watchdog(看门狗升级) +t+2126.6s ♪ 音效 sfx/step_turn +t+2134.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2134.6s ♪ 向导音:watchdog(看门狗升级) +t+2134.6s ♪ 音效 sfx/step_own +t+2134.6s 〰 震动:swipe(滑动识别) +t+2135.1s ♪ 音效 sfx/step_own +t+2135.1s 〰 震动:swipe(滑动识别) +t+2135.6s ♪ 音效 sfx/step_turn +t+2143.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2143.6s ♪ 向导音:watchdog(看门狗升级) +t+2143.6s ♪ 音效 sfx/bump +t+2151.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2151.6s ♪ 向导音:watchdog(看门狗升级) +t+2151.6s ♪ 音效 sfx/step_turn +t+2159.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2159.6s ♪ 向导音:watchdog(看门狗升级) +t+2159.6s ♪ 音效 sfx/step_turn +t+2167.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2167.6s ♪ 向导音:watchdog(看门狗升级) +t+2167.6s ♪ 音效 sfx/bump +t+2175.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2175.6s ♪ 向导音:watchdog(看门狗升级) +t+2175.6s ♪ 音效 sfx/step_turn +t+2183.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2183.6s ♪ 向导音:watchdog(看门狗升级) +t+2183.6s ♪ 音效 sfx/step_own +t+2183.6s 〰 震动:swipe(滑动识别) +t+2184.1s ♪ 音效 sfx/step_own +t+2184.1s 〰 震动:swipe(滑动识别) +t+2184.6s ♪ 音效 sfx/step_turn +t+2192.6s ♪ 向导音:nudge(看门狗轻提醒) +t+2192.6s ♪ 向导音:watchdog(看门狗升级) +t+2192.6s ♪ 音效 sfx/step_own +t+2192.6s 〰 震动:swipe(滑动识别) +t+2193.1s ♪ 音效 sfx/step_own +t+2193.1s 〰 震动:swipe(滑动识别) +t+2193.6s ♪ 音效 sfx/pulse +t+2193.9s ♪ 音效 sfx/side_open_l +t+2193.9s ♪ 音效 sfx/side_wall_r +t+2194.7s ♪ 音效 sfx/echo_4 +t+2194.7s 〰 震动:error(做错了:两连短震) +t+2194.8s ♪ 音效 sfx/mat_stone +t+2202.7s ♪ 向导音:nudge(看门狗轻提醒) +t+2202.7s ♪ 向导音:watchdog(看门狗升级) +t+2202.7s ♪ 音效 sfx/pulse +t+2203.0s ♪ 音效 sfx/side_open_l +t+2203.0s ♪ 音效 sfx/side_wall_r +t+2203.8s ♪ 音效 sfx/echo_4 +t+2203.8s ♪ 音效 sfx/step_turn +t+2203.9s ♪ 音效 sfx/mat_stone +t+2211.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2211.8s ♪ 向导音:watchdog(看门狗升级) +t+2211.8s ♪ 音效 sfx/bump +t+2219.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2219.8s ♪ 向导音:watchdog(看门狗升级) +t+2219.8s ♪ 音效 sfx/step_turn +t+2227.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2227.8s ♪ 向导音:watchdog(看门狗升级) +t+2227.8s ♪ 音效 sfx/step_own +t+2227.8s 〰 震动:swipe(滑动识别) +t+2228.3s ♪ 音效 sfx/step_own +t+2228.3s 〰 震动:swipe(滑动识别) +t+2228.8s ♪ 音效 sfx/step_turn +t+2236.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2236.8s ♪ 向导音:watchdog(看门狗升级) +t+2236.8s ♪ 音效 sfx/pulse +t+2237.0s ♪ 音效 sfx/echo_1 +t+2237.1s ♪ 音效 sfx/side_wall_l +t+2237.1s ♪ 音效 sfx/side_open_r +t+2237.2s ♪ 音效 sfx/mat_stone +t+2244.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2244.8s ♪ 向导音:watchdog(看门狗升级) +t+2244.8s ♪ 音效 sfx/step_turn +t+2252.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2252.8s ♪ 向导音:watchdog(看门狗升级) +t+2252.8s ♪ 音效 sfx/step_own +t+2252.8s 〰 震动:swipe(滑动识别) +t+2253.3s ♪ 音效 sfx/step_own +t+2253.3s 〰 震动:swipe(滑动识别) +t+2253.8s ♪ 音效 sfx/step_turn +t+2261.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2261.8s ♪ 向导音:watchdog(看门狗升级) +t+2261.8s ♪ 音效 sfx/step_turn +t+2269.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2269.8s ♪ 向导音:watchdog(看门狗升级) +t+2269.8s ♪ 音效 sfx/step_own +t+2269.8s 〰 震动:swipe(滑动识别) +t+2270.3s ♪ 音效 sfx/step_own +t+2270.3s 〰 震动:swipe(滑动识别) +t+2270.8s ♪ 音效 sfx/pulse +t+2271.0s ♪ 音效 sfx/echo_1 +t+2271.1s ♪ 音效 sfx/side_open_l +t+2271.1s ♪ 音效 sfx/side_wall_r +t+2271.2s ♪ 音效 sfx/mat_stone +t+2278.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2278.8s ♪ 向导音:watchdog(看门狗升级) +t+2278.8s ♪ 音效 sfx/step_turn +t+2286.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2286.8s ♪ 向导音:watchdog(看门狗升级) +t+2286.8s ♪ 音效 sfx/bump +t+2294.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2294.8s ♪ 向导音:watchdog(看门狗升级) +t+2294.8s ♪ 音效 sfx/step_turn +t+2302.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2302.8s ♪ 向导音:watchdog(看门狗升级) +t+2302.8s ♪ 音效 sfx/step_turn +t+2310.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2310.8s ♪ 向导音:watchdog(看门狗升级) +t+2310.8s ♪ 音效 sfx/bump +t+2318.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2318.8s ♪ 向导音:watchdog(看门狗升级) +t+2318.8s ♪ 音效 sfx/step_turn +t+2326.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2326.8s ♪ 向导音:watchdog(看门狗升级) +t+2326.8s ♪ 音效 sfx/step_turn +t+2334.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2334.8s ♪ 向导音:watchdog(看门狗升级) +t+2334.8s ♪ 音效 sfx/bump +t+2342.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2342.8s ♪ 向导音:watchdog(看门狗升级) +t+2342.8s ♪ 音效 sfx/step_turn +t+2350.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2350.8s ♪ 向导音:watchdog(看门狗升级) +t+2350.8s ♪ 音效 sfx/step_own +t+2350.8s 〰 震动:swipe(滑动识别) +t+2351.3s ♪ 音效 sfx/step_own +t+2351.3s 〰 震动:swipe(滑动识别) +t+2351.8s ♪ 音效 sfx/step_turn +t+2359.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2359.8s ♪ 向导音:watchdog(看门狗升级) +t+2359.8s ♪ 音效 sfx/bump +t+2367.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2367.8s ♪ 向导音:watchdog(看门狗升级) +t+2367.8s ♪ 音效 sfx/step_turn +t+2375.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2375.8s ♪ 向导音:watchdog(看门狗升级) +t+2375.8s ♪ 音效 sfx/step_own +t+2375.8s 〰 震动:swipe(滑动识别) +t+2376.3s ♪ 音效 sfx/step_own +t+2376.3s 〰 震动:swipe(滑动识别) +t+2376.8s ♪ 音效 sfx/step_turn +t+2384.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2384.8s ♪ 向导音:watchdog(看门狗升级) +t+2384.8s ♪ 音效 sfx/bump +t+2392.8s ♪ 向导音:nudge(看门狗轻提醒) +t+2392.8s ♪ 向导音:watchdog(看门狗升级) +t+2392.8s ▶ 语音:「这是第一层。」 +t+2394.9s ▶ 语音:「你毫发无损。」 +t+2394.9s ♪ 音效 sfx/step_turn +t+2397.0s ▶ 语音:「口袋空空。」 +t+2397.0s ♪ 音效 sfx/step_own +t+2397.0s 〰 震动:swipe(滑动识别) +t+2397.5s ♪ 音效 sfx/step_own +t+2397.5s 〰 震动:swipe(滑动识别) +t+2398.0s ♪ 音效 sfx/step_turn +t+2399.1s ▶ 语音:「你面朝北。」 +t+2399.1s ♪ 音效 sfx/bump +t+2401.3s ▶ 语音:「右边有脚步声。」 +t+2403.4s 【状态】总算从教学里滚出来了(可能跳了课,可能一路错过来的) 教学演练第 9 步,跟着旁白做(BGM=bgm/explore,语速1) +t+2403.4s ▶ 语音:「已跳过教学。单击脉冲,先听听四周。」 +t+2405.5s ♪ 向导音:explore(探索/安全) +t+2407.5s ♪ 音效 sfx/mob_step_small_r +t+2407.5s ▶ 语音:「这是第一层。」 +t+2409.1s ♪ 音效 sfx/mob_step_small_r +t+2409.1s ♪ 音效 sfx/pulse +t+2409.4s ♪ 音效 sfx/echo_1 +t+2409.5s ♪ 音效 sfx/side_open_l +t+2409.5s ♪ 音效 sfx/side_open_r +t+2409.5s ♪ 音效 sfx/mat_stone +t+2409.6s ▶ 语音:「你毫发无损。」 +t+2410.7s ♪ 音效 sfx/mob_step_small_r +t+2410.7s 〰 震动:heartbeat(心跳/倒计时) +t+2411.7s ▶ 语音:「口袋空空。」 +t+2411.7s ♪ 音效 sfx/step_turn +t+2412.9s ♪ 音效 sfx/mob_step_small_c +t+2412.9s ♪ 音效 sfx/step_own +t+2412.9s 〰 震动:swipe(滑动识别) +t+2413.4s ♪ 音效 sfx/step_own +t+2413.4s 〰 震动:swipe(滑动识别) +t+2413.8s ▶ 语音:「你面朝南。」 + +#### 【玩家动作】手痒连点三下脉冲 + +t+2413.8s ♪ 音效 sfx/pulse +t+2413.8s 〰 震动:error(做错了:两连短震) +t+2413.8s 〰 震动:error(做错了:两连短震) +t+2414.1s ♪ 音效 sfx/echo_1 +t+2414.2s ♪ 音效 sfx/side_open_l +t+2414.2s ♪ 音效 sfx/side_wall_r +t+2414.2s ♪ 音效 sfx/mat_stone +t+2414.8s ♫ BGM 切换 → bgm/battle(淡入 400ms) +t+2414.8s ♪ 向导音:battle(战斗/对抗) +t+2414.8s ▶ 语音:「盲眼蝠扑了过来!」 +t+2416.9s ♪ 向导音:turn(轮到你) +t+2416.9s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,抡空也打 + +t+2416.9s ♪ 音效 sfx/atk_swing +t+2416.9s ♪ 音效 sfx/atk_hit +t+2416.9s ♪ 音效 sfx/tel_small +t+2416.9s 〰 震动:beat(节拍点) +t+2418.1s ♪ 向导音:turn(轮到你) +t+2418.1s 〰 震动:confirm(轻点确认) + +#### 【玩家动作】单击,抡空也打 + +t+2418.1s ♪ 音效 sfx/atk_swing +t+2418.1s ♪ 音效 sfx/atk_hit +t+2418.1s ♪ 音效 sfx/enemy_die +t+2418.1s ♪ 音效 sfx/coin +t+2418.1s ▶ 语音:「它倒下了。」 +t+2418.1s 〰 震动:success(成功长震) +t+2420.2s ▶ 语音:「战利不多。」 +t+2422.3s ♫ BGM 切换 → bgm/explore +t+2422.3s ♪ 向导音:explore(探索/安全) + +#### 【玩家动作】手痒连点三下脉冲 + +t+2427.3s ♪ 音效 sfx/pulse +t+2427.3s 〰 震动:error(做错了:两连短震) +t+2427.3s 〰 震动:error(做错了:两连短震) +t+2427.6s ♪ 音效 sfx/echo_1 +t+2427.7s ♪ 音效 sfx/side_open_l +t+2427.7s ♪ 音效 sfx/side_wall_r +t+2427.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2432.3s ♪ 音效 sfx/pulse +t+2432.3s 〰 震动:error(做错了:两连短震) +t+2432.3s 〰 震动:error(做错了:两连短震) +t+2432.6s ♪ 音效 sfx/echo_1 +t+2432.7s ♪ 音效 sfx/side_open_l +t+2432.7s ♪ 音效 sfx/side_wall_r +t+2432.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2437.3s ♪ 音效 sfx/pulse +t+2437.3s 〰 震动:error(做错了:两连短震) +t+2437.3s 〰 震动:error(做错了:两连短震) +t+2437.6s ♪ 音效 sfx/echo_1 +t+2437.7s ♪ 音效 sfx/side_open_l +t+2437.7s ♪ 音效 sfx/side_wall_r +t+2437.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2442.3s ♪ 音效 sfx/pulse +t+2442.3s 〰 震动:error(做错了:两连短震) +t+2442.3s 〰 震动:error(做错了:两连短震) +t+2442.6s ♪ 音效 sfx/echo_1 +t+2442.7s ♪ 音效 sfx/side_open_l +t+2442.7s ♪ 音效 sfx/side_wall_r +t+2442.7s ♪ 音效 sfx/mat_stone +t+2447.3s ♪ 音效 sfx/pulse +t+2447.6s ♪ 音效 sfx/echo_1 +t+2447.7s ♪ 音效 sfx/side_open_l +t+2447.7s ♪ 音效 sfx/side_wall_r +t+2447.7s ♪ 音效 sfx/mat_stone +t+2452.3s ♪ 音效 sfx/step_turn +t+2457.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+2467.3s ♪ 音效 sfx/pulse +t+2467.3s 〰 震动:error(做错了:两连短震) +t+2467.3s 〰 震动:error(做错了:两连短震) +t+2467.6s ♪ 音效 sfx/echo_1 +t+2467.7s ♪ 音效 sfx/side_wall_l +t+2467.7s ♪ 音效 sfx/side_open_r +t+2467.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2472.3s ♪ 音效 sfx/pulse +t+2472.3s 〰 震动:error(做错了:两连短震) +t+2472.3s 〰 震动:error(做错了:两连短震) +t+2472.6s ♪ 音效 sfx/echo_1 +t+2472.7s ♪ 音效 sfx/side_wall_l +t+2472.7s ♪ 音效 sfx/side_open_r +t+2472.7s ♪ 音效 sfx/mat_stone +t+2477.3s ♪ 音效 sfx/pulse +t+2477.6s ♪ 音效 sfx/echo_1 +t+2477.7s ♪ 音效 sfx/side_wall_l +t+2477.7s ♪ 音效 sfx/side_open_r +t+2477.7s ♪ 音效 sfx/mat_stone +t+2482.3s ♪ 音效 sfx/step_turn +t+2487.3s ♪ 音效 sfx/step_own +t+2487.3s 〰 震动:swipe(滑动识别) +t+2487.8s ♪ 音效 sfx/step_own +t+2487.8s 〰 震动:swipe(滑动识别) +t+2488.3s ♪ 音效 sfx/pulse +t+2488.7s ♪ 音效 sfx/side_open_l +t+2488.7s ♪ 音效 sfx/side_wall_r +t+2489.4s ♪ 音效 sfx/echo_4 +t+2489.5s ♪ 音效 sfx/mat_stone +t+2492.3s ♪ 音效 sfx/step_turn +t+2497.3s ♪ 音效 sfx/bump +t+2507.3s ♪ 音效 sfx/pulse +t+2507.6s ♪ 音效 sfx/echo_1 +t+2507.7s ♪ 音效 sfx/side_open_l +t+2507.7s ♪ 音效 sfx/side_open_r +t+2507.7s ♪ 音效 sfx/mat_stone +t+2512.3s ♪ 音效 sfx/step_turn +t+2517.3s ♪ 音效 sfx/step_own +t+2517.3s 〰 震动:swipe(滑动识别) +t+2517.8s ♪ 音效 sfx/step_own +t+2517.8s 〰 震动:swipe(滑动识别) +t+2518.3s ♪ 音效 sfx/pulse +t+2518.6s ♪ 音效 sfx/echo_1 +t+2518.7s ♪ 音效 sfx/side_open_l +t+2518.7s ♪ 音效 sfx/side_wall_r +t+2518.7s ♪ 音效 sfx/mat_stone +t+2522.3s ♪ 音效 sfx/step_turn +t+2527.3s ♪ 音效 sfx/bump +t+2537.3s ♪ 音效 sfx/pulse +t+2537.6s ♪ 音效 sfx/echo_1 +t+2537.7s ♪ 音效 sfx/side_wall_l +t+2537.7s ♪ 音效 sfx/side_open_r +t+2537.7s ♪ 音效 sfx/mat_stone +t+2542.3s ♪ 音效 sfx/step_turn +t+2547.3s ♪ 音效 sfx/step_own +t+2547.3s 〰 震动:swipe(滑动识别) +t+2547.8s ♪ 音效 sfx/step_own +t+2547.8s 〰 震动:swipe(滑动识别) +t+2548.3s ♪ 音效 sfx/pulse +t+2548.7s ♪ 音效 sfx/side_open_l +t+2548.7s ♪ 音效 sfx/side_wall_r +t+2549.4s ♪ 音效 sfx/echo_4 +t+2549.5s ♪ 音效 sfx/mat_stone +t+2552.3s ♪ 音效 sfx/step_turn +t+2557.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+2567.3s ♪ 音效 sfx/pulse +t+2567.3s 〰 震动:error(做错了:两连短震) +t+2567.3s 〰 震动:error(做错了:两连短震) +t+2567.6s ♪ 音效 sfx/echo_1 +t+2567.7s ♪ 音效 sfx/side_open_l +t+2567.7s ♪ 音效 sfx/side_open_r +t+2567.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2572.3s ♪ 音效 sfx/pulse +t+2572.3s 〰 震动:error(做错了:两连短震) +t+2572.3s 〰 震动:error(做错了:两连短震) +t+2572.6s ♪ 音效 sfx/echo_1 +t+2572.7s ♪ 音效 sfx/side_open_l +t+2572.7s ♪ 音效 sfx/side_open_r +t+2572.7s ♪ 音效 sfx/mat_stone +t+2577.3s ♪ 音效 sfx/pulse +t+2577.6s ♪ 音效 sfx/echo_1 +t+2577.7s ♪ 音效 sfx/side_open_l +t+2577.7s ♪ 音效 sfx/side_open_r +t+2577.7s ♪ 音效 sfx/mat_stone +t+2582.3s ♪ 音效 sfx/step_turn +t+2587.3s ♪ 音效 sfx/step_own +t+2587.3s 〰 震动:swipe(滑动识别) +t+2587.8s ♪ 音效 sfx/step_own +t+2587.8s 〰 震动:swipe(滑动识别) +t+2588.3s ♪ 音效 sfx/pulse +t+2588.6s ♪ 音效 sfx/echo_1 +t+2588.7s ♪ 音效 sfx/side_open_l +t+2588.7s ♪ 音效 sfx/side_wall_r +t+2588.7s ♪ 音效 sfx/mat_stone +t+2592.3s ♪ 音效 sfx/step_turn +t+2597.3s ♪ 音效 sfx/bump +t+2607.3s ♪ 音效 sfx/pulse +t+2607.6s ♪ 音效 sfx/echo_1 +t+2607.7s ♪ 音效 sfx/side_wall_l +t+2607.7s ♪ 音效 sfx/side_open_r +t+2607.7s ♪ 音效 sfx/mat_stone +t+2612.3s ♪ 音效 sfx/step_turn +t+2617.3s ♪ 音效 sfx/step_own +t+2617.3s 〰 震动:swipe(滑动识别) +t+2617.8s ♪ 音效 sfx/step_own +t+2617.8s 〰 震动:swipe(滑动识别) +t+2618.3s ♪ 音效 sfx/step_turn + +#### 【玩家动作】手痒连点三下脉冲 + +t+2622.3s ♪ 音效 sfx/pulse +t+2622.3s 〰 震动:error(做错了:两连短震) +t+2622.3s 〰 震动:error(做错了:两连短震) +t+2622.6s ♪ 音效 sfx/echo_1 +t+2622.7s ♪ 音效 sfx/side_open_l +t+2622.7s ♪ 音效 sfx/side_open_r +t+2622.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2627.3s ♪ 音效 sfx/pulse +t+2627.3s 〰 震动:error(做错了:两连短震) +t+2627.3s 〰 震动:error(做错了:两连短震) +t+2627.6s ♪ 音效 sfx/echo_1 +t+2627.7s ♪ 音效 sfx/side_open_l +t+2627.7s ♪ 音效 sfx/side_open_r +t+2627.7s ♪ 音效 sfx/mat_stone +t+2632.3s ♪ 音效 sfx/pulse +t+2632.6s ♪ 音效 sfx/echo_1 +t+2632.7s ♪ 音效 sfx/side_open_l +t+2632.7s ♪ 音效 sfx/side_open_r +t+2632.7s ♪ 音效 sfx/mat_stone +t+2637.3s ♪ 音效 sfx/step_turn +t+2642.3s ♪ 音效 sfx/step_own +t+2642.3s 〰 震动:swipe(滑动识别) +t+2642.8s ♪ 音效 sfx/step_own +t+2642.8s 〰 震动:swipe(滑动识别) +t+2643.3s ♪ 音效 sfx/pulse +t+2643.6s ♪ 音效 sfx/echo_1 +t+2643.7s ♪ 音效 sfx/side_open_l +t+2643.7s ♪ 音效 sfx/side_wall_r +t+2643.7s ♪ 音效 sfx/mat_stone +t+2647.3s ♪ 音效 sfx/step_turn +t+2652.3s ♪ 音效 sfx/bump +t+2662.3s ♪ 音效 sfx/pulse +t+2662.6s ♪ 音效 sfx/echo_1 +t+2662.7s ♪ 音效 sfx/side_wall_l +t+2662.7s ♪ 音效 sfx/side_open_r +t+2662.7s ♪ 音效 sfx/mat_stone +t+2667.3s ♪ 音效 sfx/step_turn +t+2672.3s ♪ 音效 sfx/step_own +t+2672.3s 〰 震动:swipe(滑动识别) +t+2672.8s ♪ 音效 sfx/step_own +t+2672.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+2673.3s ♪ 音效 sfx/pulse +t+2673.3s 〰 震动:error(做错了:两连短震) +t+2673.3s 〰 震动:error(做错了:两连短震) +t+2673.7s ♪ 音效 sfx/side_open_l +t+2673.7s ♪ 音效 sfx/side_wall_r +t+2674.4s ♪ 音效 sfx/echo_4 +t+2674.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2677.3s ♪ 音效 sfx/pulse +t+2677.3s 〰 震动:error(做错了:两连短震) +t+2677.3s 〰 震动:error(做错了:两连短震) +t+2677.7s ♪ 音效 sfx/side_open_l +t+2677.7s ♪ 音效 sfx/side_wall_r +t+2678.4s ♪ 音效 sfx/echo_4 +t+2678.5s ♪ 音效 sfx/mat_stone +t+2682.3s ♪ 音效 sfx/pulse +t+2682.7s ♪ 音效 sfx/side_open_l +t+2682.7s ♪ 音效 sfx/side_wall_r +t+2683.4s ♪ 音效 sfx/echo_4 +t+2683.5s ♪ 音效 sfx/mat_stone +t+2687.3s ♪ 音效 sfx/step_turn +t+2692.3s ♪ 音效 sfx/bump +t+2702.3s ♪ 音效 sfx/pulse +t+2702.6s ♪ 音效 sfx/echo_1 +t+2702.7s ♪ 音效 sfx/side_open_l +t+2702.7s ♪ 音效 sfx/side_open_r +t+2702.7s ♪ 音效 sfx/mat_stone +t+2707.3s ♪ 音效 sfx/step_turn +t+2712.3s ♪ 音效 sfx/step_own +t+2712.3s 〰 震动:swipe(滑动识别) +t+2712.8s ♪ 音效 sfx/step_own +t+2712.8s 〰 震动:swipe(滑动识别) +t+2713.3s ♪ 音效 sfx/pulse +t+2713.6s ♪ 音效 sfx/echo_1 +t+2713.7s ♪ 音效 sfx/side_open_l +t+2713.7s ♪ 音效 sfx/side_wall_r +t+2713.7s ♪ 音效 sfx/mat_stone +t+2717.3s ♪ 音效 sfx/step_turn +t+2722.3s ♪ 音效 sfx/bump +t+2732.3s ♪ 音效 sfx/pulse +t+2732.6s ♪ 音效 sfx/echo_1 +t+2732.7s ♪ 音效 sfx/side_wall_l +t+2732.7s ♪ 音效 sfx/side_open_r +t+2732.7s ♪ 音效 sfx/mat_stone +t+2737.3s ♪ 音效 sfx/step_turn +t+2742.3s ♪ 音效 sfx/step_own +t+2742.3s 〰 震动:swipe(滑动识别) +t+2742.8s ♪ 音效 sfx/step_own +t+2742.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+2743.3s ♪ 音效 sfx/pulse +t+2743.3s 〰 震动:error(做错了:两连短震) +t+2743.3s 〰 震动:error(做错了:两连短震) +t+2743.7s ♪ 音效 sfx/side_open_l +t+2743.7s ♪ 音效 sfx/side_wall_r +t+2744.4s ♪ 音效 sfx/echo_4 +t+2744.5s ♪ 音效 sfx/mat_stone +t+2747.3s ♪ 音效 sfx/pulse +t+2747.7s ♪ 音效 sfx/side_open_l +t+2747.7s ♪ 音效 sfx/side_wall_r +t+2748.4s ♪ 音效 sfx/echo_4 +t+2748.5s ♪ 音效 sfx/mat_stone +t+2752.3s ♪ 音效 sfx/step_turn +t+2757.3s ♪ 音效 sfx/bump +t+2767.3s ♪ 音效 sfx/pulse +t+2767.6s ♪ 音效 sfx/echo_1 +t+2767.7s ♪ 音效 sfx/side_open_l +t+2767.7s ♪ 音效 sfx/side_open_r +t+2767.7s ♪ 音效 sfx/mat_stone +t+2772.3s ♪ 音效 sfx/step_turn +t+2777.3s ♪ 音效 sfx/step_own +t+2777.3s 〰 震动:swipe(滑动识别) +t+2777.8s ♪ 音效 sfx/step_own +t+2777.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+2778.3s ♪ 音效 sfx/pulse +t+2778.3s 〰 震动:error(做错了:两连短震) +t+2778.3s 〰 震动:error(做错了:两连短震) +t+2778.6s ♪ 音效 sfx/echo_1 +t+2778.7s ♪ 音效 sfx/side_open_l +t+2778.7s ♪ 音效 sfx/side_wall_r +t+2778.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2782.3s ♪ 音效 sfx/pulse +t+2782.3s 〰 震动:error(做错了:两连短震) +t+2782.3s 〰 震动:error(做错了:两连短震) +t+2782.6s ♪ 音效 sfx/echo_1 +t+2782.7s ♪ 音效 sfx/side_open_l +t+2782.7s ♪ 音效 sfx/side_wall_r +t+2782.7s ♪ 音效 sfx/mat_stone +t+2787.3s ♪ 音效 sfx/pulse +t+2787.6s ♪ 音效 sfx/echo_1 +t+2787.7s ♪ 音效 sfx/side_open_l +t+2787.7s ♪ 音效 sfx/side_wall_r +t+2787.7s ♪ 音效 sfx/mat_stone +t+2792.3s ♪ 音效 sfx/step_turn +t+2797.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+2807.3s ♪ 音效 sfx/pulse +t+2807.3s 〰 震动:error(做错了:两连短震) +t+2807.3s 〰 震动:error(做错了:两连短震) +t+2807.6s ♪ 音效 sfx/echo_1 +t+2807.7s ♪ 音效 sfx/side_wall_l +t+2807.7s ♪ 音效 sfx/side_open_r +t+2807.7s ♪ 音效 sfx/mat_stone +t+2812.3s ♪ 音效 sfx/pulse +t+2812.6s ♪ 音效 sfx/echo_1 +t+2812.7s ♪ 音效 sfx/side_wall_l +t+2812.7s ♪ 音效 sfx/side_open_r +t+2812.7s ♪ 音效 sfx/mat_stone +t+2817.3s ♪ 音效 sfx/step_turn +t+2822.3s ♪ 音效 sfx/step_own +t+2822.3s 〰 震动:swipe(滑动识别) +t+2822.8s ♪ 音效 sfx/step_own +t+2822.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+2823.3s ♪ 音效 sfx/pulse +t+2823.3s 〰 震动:error(做错了:两连短震) +t+2823.3s 〰 震动:error(做错了:两连短震) +t+2823.7s ♪ 音效 sfx/side_open_l +t+2823.7s ♪ 音效 sfx/side_wall_r +t+2824.4s ♪ 音效 sfx/echo_4 +t+2824.5s ♪ 音效 sfx/mat_stone +t+2827.3s ♪ 音效 sfx/pulse +t+2827.7s ♪ 音效 sfx/side_open_l +t+2827.7s ♪ 音效 sfx/side_wall_r +t+2828.4s ♪ 音效 sfx/echo_4 +t+2828.5s ♪ 音效 sfx/mat_stone +t+2832.3s ♪ 音效 sfx/step_turn +t+2837.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+2847.3s ♪ 音效 sfx/pulse +t+2847.3s 〰 震动:error(做错了:两连短震) +t+2847.3s 〰 震动:error(做错了:两连短震) +t+2847.6s ♪ 音效 sfx/echo_1 +t+2847.7s ♪ 音效 sfx/side_open_l +t+2847.7s ♪ 音效 sfx/side_open_r +t+2847.7s ♪ 音效 sfx/mat_stone +t+2852.3s ♪ 音效 sfx/pulse +t+2852.6s ♪ 音效 sfx/echo_1 +t+2852.7s ♪ 音效 sfx/side_open_l +t+2852.7s ♪ 音效 sfx/side_open_r +t+2852.7s ♪ 音效 sfx/mat_stone +t+2857.3s ♪ 音效 sfx/step_turn +t+2862.3s ♪ 音效 sfx/step_own +t+2862.3s 〰 震动:swipe(滑动识别) +t+2862.8s ♪ 音效 sfx/step_own +t+2862.8s 〰 震动:swipe(滑动识别) +t+2863.3s ♪ 音效 sfx/pulse +t+2863.6s ♪ 音效 sfx/echo_1 +t+2863.7s ♪ 音效 sfx/side_open_l +t+2863.7s ♪ 音效 sfx/side_wall_r +t+2863.7s ♪ 音效 sfx/mat_stone +t+2867.3s ♪ 音效 sfx/pulse +t+2867.6s ♪ 音效 sfx/echo_1 +t+2867.7s ♪ 音效 sfx/side_open_l +t+2867.7s ♪ 音效 sfx/side_wall_r +t+2867.7s ♪ 音效 sfx/mat_stone +t+2872.3s ♪ 音效 sfx/step_turn +t+2877.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+2887.3s ♪ 音效 sfx/pulse +t+2887.3s 〰 震动:error(做错了:两连短震) +t+2887.3s 〰 震动:error(做错了:两连短震) +t+2887.6s ♪ 音效 sfx/echo_1 +t+2887.7s ♪ 音效 sfx/side_wall_l +t+2887.7s ♪ 音效 sfx/side_open_r +t+2887.7s ♪ 音效 sfx/mat_stone +t+2892.3s ♪ 音效 sfx/pulse +t+2892.6s ♪ 音效 sfx/echo_1 +t+2892.7s ♪ 音效 sfx/side_wall_l +t+2892.7s ♪ 音效 sfx/side_open_r +t+2892.7s ♪ 音效 sfx/mat_stone +t+2897.3s ♪ 音效 sfx/step_turn +t+2902.3s ♪ 音效 sfx/step_own +t+2902.3s 〰 震动:swipe(滑动识别) +t+2902.8s ♪ 音效 sfx/step_own +t+2902.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+2903.3s ♪ 音效 sfx/pulse +t+2903.3s 〰 震动:error(做错了:两连短震) +t+2903.3s 〰 震动:error(做错了:两连短震) +t+2903.7s ♪ 音效 sfx/side_open_l +t+2903.7s ♪ 音效 sfx/side_wall_r +t+2904.4s ♪ 音效 sfx/echo_4 +t+2904.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2907.3s ♪ 音效 sfx/pulse +t+2907.3s 〰 震动:error(做错了:两连短震) +t+2907.3s 〰 震动:error(做错了:两连短震) +t+2907.7s ♪ 音效 sfx/side_open_l +t+2907.7s ♪ 音效 sfx/side_wall_r +t+2908.4s ♪ 音效 sfx/echo_4 +t+2908.5s ♪ 音效 sfx/mat_stone +t+2912.3s ♪ 音效 sfx/pulse +t+2912.7s ♪ 音效 sfx/side_open_l +t+2912.7s ♪ 音效 sfx/side_wall_r +t+2913.4s ♪ 音效 sfx/echo_4 +t+2913.5s ♪ 音效 sfx/mat_stone +t+2917.3s ♪ 音效 sfx/step_turn +t+2922.3s ♪ 音效 sfx/bump +t+2932.3s ♪ 音效 sfx/pulse +t+2932.6s ♪ 音效 sfx/echo_1 +t+2932.7s ♪ 音效 sfx/side_open_l +t+2932.7s ♪ 音效 sfx/side_open_r +t+2932.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2937.3s ♪ 音效 sfx/pulse +t+2937.3s 〰 震动:error(做错了:两连短震) +t+2937.3s 〰 震动:error(做错了:两连短震) +t+2937.6s ♪ 音效 sfx/echo_1 +t+2937.7s ♪ 音效 sfx/side_open_l +t+2937.7s ♪ 音效 sfx/side_open_r +t+2937.7s ♪ 音效 sfx/mat_stone +t+2942.3s ♪ 音效 sfx/pulse +t+2942.6s ♪ 音效 sfx/echo_1 +t+2942.7s ♪ 音效 sfx/side_open_l +t+2942.7s ♪ 音效 sfx/side_open_r +t+2942.7s ♪ 音效 sfx/mat_stone +t+2947.3s ♪ 音效 sfx/step_turn +t+2952.3s ♪ 音效 sfx/step_own +t+2952.3s 〰 震动:swipe(滑动识别) +t+2952.8s ♪ 音效 sfx/step_own +t+2952.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+2953.3s ♪ 音效 sfx/pulse +t+2953.3s 〰 震动:error(做错了:两连短震) +t+2953.3s 〰 震动:error(做错了:两连短震) +t+2953.6s ♪ 音效 sfx/echo_1 +t+2953.7s ♪ 音效 sfx/side_open_l +t+2953.7s ♪ 音效 sfx/side_wall_r +t+2953.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2957.3s ♪ 音效 sfx/pulse +t+2957.3s 〰 震动:error(做错了:两连短震) +t+2957.3s 〰 震动:error(做错了:两连短震) +t+2957.6s ♪ 音效 sfx/echo_1 +t+2957.7s ♪ 音效 sfx/side_open_l +t+2957.7s ♪ 音效 sfx/side_wall_r +t+2957.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+2962.3s ♪ 音效 sfx/pulse +t+2962.3s 〰 震动:error(做错了:两连短震) +t+2962.3s 〰 震动:error(做错了:两连短震) +t+2962.6s ♪ 音效 sfx/echo_1 +t+2962.7s ♪ 音效 sfx/side_open_l +t+2962.7s ♪ 音效 sfx/side_wall_r +t+2962.7s ♪ 音效 sfx/mat_stone +t+2967.3s ♪ 音效 sfx/pulse +t+2967.6s ♪ 音效 sfx/echo_1 +t+2967.7s ♪ 音效 sfx/side_open_l +t+2967.7s ♪ 音效 sfx/side_wall_r +t+2967.7s ♪ 音效 sfx/mat_stone +t+2972.3s ♪ 音效 sfx/step_turn +t+2977.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+2987.3s ♪ 音效 sfx/pulse +t+2987.3s 〰 震动:error(做错了:两连短震) +t+2987.3s 〰 震动:error(做错了:两连短震) +t+2987.6s ♪ 音效 sfx/echo_1 +t+2987.7s ♪ 音效 sfx/side_wall_l +t+2987.7s ♪ 音效 sfx/side_open_r +t+2987.7s ♪ 音效 sfx/mat_stone +t+2992.3s ♪ 音效 sfx/step_turn + +#### 【玩家动作】手痒连点三下脉冲 + +t+2997.3s ♪ 音效 sfx/pulse +t+2997.3s 〰 震动:error(做错了:两连短震) +t+2997.3s 〰 震动:error(做错了:两连短震) +t+2997.6s ♪ 音效 sfx/echo_1 +t+2997.7s ♪ 音效 sfx/side_open_l +t+2997.7s ♪ 音效 sfx/side_wall_r +t+2997.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3002.3s ♪ 音效 sfx/pulse +t+3002.3s 〰 震动:error(做错了:两连短震) +t+3002.3s 〰 震动:error(做错了:两连短震) +t+3002.6s ♪ 音效 sfx/echo_1 +t+3002.7s ♪ 音效 sfx/side_open_l +t+3002.7s ♪ 音效 sfx/side_wall_r +t+3002.7s ♪ 音效 sfx/mat_stone +t+3007.3s ♪ 音效 sfx/pulse +t+3007.6s ♪ 音效 sfx/echo_1 +t+3007.7s ♪ 音效 sfx/side_open_l +t+3007.7s ♪ 音效 sfx/side_wall_r +t+3007.7s ♪ 音效 sfx/mat_stone +t+3012.3s ♪ 音效 sfx/step_turn +t+3017.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3027.3s ♪ 音效 sfx/pulse +t+3027.3s 〰 震动:error(做错了:两连短震) +t+3027.3s 〰 震动:error(做错了:两连短震) +t+3027.6s ♪ 音效 sfx/echo_1 +t+3027.7s ♪ 音效 sfx/side_wall_l +t+3027.7s ♪ 音效 sfx/side_open_r +t+3027.7s ♪ 音效 sfx/mat_stone +t+3032.3s ♪ 音效 sfx/pulse +t+3032.6s ♪ 音效 sfx/echo_1 +t+3032.7s ♪ 音效 sfx/side_wall_l +t+3032.7s ♪ 音效 sfx/side_open_r +t+3032.7s ♪ 音效 sfx/mat_stone +t+3037.3s ♪ 音效 sfx/step_turn +t+3042.3s ♪ 音效 sfx/step_own +t+3042.3s 〰 震动:swipe(滑动识别) +t+3042.8s ♪ 音效 sfx/step_own +t+3042.8s 〰 震动:swipe(滑动识别) +t+3043.3s ♪ 音效 sfx/step_own +t+3043.3s 〰 震动:swipe(滑动识别) +t+3043.8s ♪ 音效 sfx/step_own +t+3043.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3044.3s ♪ 音效 sfx/pulse +t+3044.3s 〰 震动:error(做错了:两连短震) +t+3044.3s 〰 震动:error(做错了:两连短震) +t+3044.7s ♪ 音效 sfx/side_open_l +t+3044.7s ♪ 音效 sfx/side_wall_r +t+3045.1s ♪ 音效 sfx/echo_3 +t+3045.2s ♪ 音效 sfx/mat_stone +t+3047.3s ♪ 音效 sfx/pulse +t+3047.7s ♪ 音效 sfx/side_open_l +t+3047.7s ♪ 音效 sfx/side_wall_r +t+3048.1s ♪ 音效 sfx/echo_3 +t+3048.2s ♪ 音效 sfx/mat_stone +t+3052.3s ♪ 音效 sfx/step_turn +t+3057.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3067.3s ♪ 音效 sfx/pulse +t+3067.3s 〰 震动:error(做错了:两连短震) +t+3067.3s 〰 震动:error(做错了:两连短震) +t+3067.6s ♪ 音效 sfx/echo_1 +t+3067.7s ♪ 音效 sfx/side_open_l +t+3067.7s ♪ 音效 sfx/side_open_r +t+3067.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3072.3s ♪ 音效 sfx/pulse +t+3072.3s 〰 震动:error(做错了:两连短震) +t+3072.3s 〰 震动:error(做错了:两连短震) +t+3072.6s ♪ 音效 sfx/echo_1 +t+3072.7s ♪ 音效 sfx/side_open_l +t+3072.7s ♪ 音效 sfx/side_open_r +t+3072.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3077.3s ♪ 音效 sfx/pulse +t+3077.3s 〰 震动:error(做错了:两连短震) +t+3077.3s 〰 震动:error(做错了:两连短震) +t+3077.6s ♪ 音效 sfx/echo_1 +t+3077.7s ♪ 音效 sfx/side_open_l +t+3077.7s ♪ 音效 sfx/side_open_r +t+3077.7s ♪ 音效 sfx/mat_stone +t+3082.3s ♪ 音效 sfx/pulse +t+3082.6s ♪ 音效 sfx/echo_1 +t+3082.7s ♪ 音效 sfx/side_open_l +t+3082.7s ♪ 音效 sfx/side_open_r +t+3082.7s ♪ 音效 sfx/mat_stone +t+3087.3s ♪ 音效 sfx/step_turn +t+3092.3s ♪ 音效 sfx/step_own +t+3092.3s 〰 震动:swipe(滑动识别) +t+3092.8s ♪ 音效 sfx/step_own +t+3092.8s 〰 震动:swipe(滑动识别) +t+3093.3s ♪ 音效 sfx/pulse +t+3093.7s ♪ 音效 sfx/side_wall_l +t+3093.7s ♪ 音效 sfx/side_open_r +t+3094.1s ♪ 音效 sfx/echo_3 +t+3094.2s ♪ 音效 sfx/mat_stone +t+3097.3s ♪ 音效 sfx/step_turn +t+3102.3s ♪ 音效 sfx/step_own +t+3102.3s 〰 震动:swipe(滑动识别) +t+3102.8s ♪ 音效 sfx/step_own +t+3102.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3103.3s ♪ 音效 sfx/pulse +t+3103.3s 〰 震动:error(做错了:两连短震) +t+3103.3s 〰 震动:error(做错了:两连短震) +t+3103.6s ♪ 音效 sfx/echo_1 +t+3103.7s ♪ 音效 sfx/side_wall_l +t+3103.7s ♪ 音效 sfx/side_open_r +t+3103.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3107.3s ♪ 音效 sfx/pulse +t+3107.3s 〰 震动:error(做错了:两连短震) +t+3107.3s 〰 震动:error(做错了:两连短震) +t+3107.6s ♪ 音效 sfx/echo_1 +t+3107.7s ♪ 音效 sfx/side_wall_l +t+3107.7s ♪ 音效 sfx/side_open_r +t+3107.7s ♪ 音效 sfx/mat_stone +t+3112.3s ♪ 音效 sfx/pulse +t+3112.6s ♪ 音效 sfx/echo_1 +t+3112.7s ♪ 音效 sfx/side_wall_l +t+3112.7s ♪ 音效 sfx/side_open_r +t+3112.7s ♪ 音效 sfx/mat_stone +t+3117.3s ♪ 音效 sfx/step_turn +t+3122.3s ♪ 音效 sfx/step_own +t+3122.3s 〰 震动:swipe(滑动识别) +t+3122.8s ♪ 音效 sfx/step_own +t+3122.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3123.3s ♪ 音效 sfx/pulse +t+3123.3s 〰 震动:error(做错了:两连短震) +t+3123.3s 〰 震动:error(做错了:两连短震) +t+3123.7s ♪ 音效 sfx/side_wall_l +t+3123.7s ♪ 音效 sfx/side_open_r +t+3124.1s ♪ 音效 sfx/echo_3 +t+3124.2s ♪ 音效 sfx/mat_stone +t+3127.3s ♪ 音效 sfx/pulse +t+3127.7s ♪ 音效 sfx/side_wall_l +t+3127.7s ♪ 音效 sfx/side_open_r +t+3128.1s ♪ 音效 sfx/echo_3 +t+3128.1s 〰 震动:error(做错了:两连短震) +t+3128.2s ♪ 音效 sfx/mat_stone +t+3132.3s ♪ 音效 sfx/step_turn +t+3137.3s ♪ 音效 sfx/step_own +t+3137.3s 〰 震动:swipe(滑动识别) +t+3137.8s ♪ 音效 sfx/step_own +t+3137.8s 〰 震动:swipe(滑动识别) +t+3138.3s ♪ 音效 sfx/pulse +t+3138.6s ♪ 音效 sfx/echo_1 +t+3138.7s ♪ 音效 sfx/side_open_l +t+3138.7s ♪ 音效 sfx/side_open_r +t+3138.7s ♪ 音效 sfx/mat_stone +t+3142.3s ♪ 音效 sfx/step_turn +t+3147.3s ♪ 音效 sfx/step_own +t+3147.3s 〰 震动:swipe(滑动识别) +t+3147.8s ♪ 音效 sfx/step_own +t+3147.8s 〰 震动:swipe(滑动识别) +t+3148.3s ♪ 音效 sfx/pulse +t+3148.7s ♪ 音效 sfx/side_wall_l +t+3148.7s ♪ 音效 sfx/side_open_r +t+3149.1s ♪ 音效 sfx/echo_3 + +#### 【玩家动作】手痒连点三下脉冲 + +t+3149.1s 〰 震动:error(做错了:两连短震) +t+3149.1s 〰 震动:error(做错了:两连短震) +t+3149.1s 〰 震动:error(做错了:两连短震) +t+3149.2s ♪ 音效 sfx/mat_stone +t+3152.3s ♪ 音效 sfx/pulse +t+3152.7s ♪ 音效 sfx/side_wall_l +t+3152.7s ♪ 音效 sfx/side_open_r +t+3153.1s ♪ 音效 sfx/echo_3 +t+3153.2s ♪ 音效 sfx/mat_stone +t+3157.3s ♪ 音效 sfx/step_turn +t+3162.3s ♪ 音效 sfx/step_own +t+3162.3s 〰 震动:swipe(滑动识别) +t+3162.8s ♪ 音效 sfx/step_own +t+3162.8s 〰 震动:swipe(滑动识别) +t+3163.3s ♪ 音效 sfx/pulse +t+3163.6s ♪ 音效 sfx/echo_1 +t+3163.7s ♪ 音效 sfx/side_wall_l +t+3163.7s ♪ 音效 sfx/side_open_r +t+3163.7s ♪ 音效 sfx/mat_stone +t+3167.3s ♪ 音效 sfx/step_turn +t+3172.3s ♪ 音效 sfx/step_own +t+3172.3s 〰 震动:swipe(滑动识别) +t+3172.8s ♪ 音效 sfx/step_own +t+3172.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3173.3s ♪ 音效 sfx/pulse +t+3173.3s 〰 震动:error(做错了:两连短震) +t+3173.3s 〰 震动:error(做错了:两连短震) +t+3173.7s ♪ 音效 sfx/side_wall_l +t+3173.7s ♪ 音效 sfx/side_open_r +t+3174.1s ♪ 音效 sfx/echo_3 +t+3174.2s ♪ 音效 sfx/mat_stone +t+3177.3s ♪ 音效 sfx/pulse +t+3177.7s ♪ 音效 sfx/side_wall_l +t+3177.7s ♪ 音效 sfx/side_open_r +t+3178.1s ♪ 音效 sfx/echo_3 +t+3178.2s ♪ 音效 sfx/mat_stone +t+3182.3s ♪ 音效 sfx/step_turn +t+3187.3s ♪ 音效 sfx/step_own +t+3187.3s 〰 震动:swipe(滑动识别) +t+3187.8s ♪ 音效 sfx/step_own +t+3187.8s 〰 震动:swipe(滑动识别) +t+3188.3s ♪ 音效 sfx/pulse +t+3188.6s ♪ 音效 sfx/echo_1 +t+3188.7s ♪ 音效 sfx/side_open_l +t+3188.7s ♪ 音效 sfx/side_open_r +t+3188.7s ♪ 音效 sfx/mat_stone +t+3192.3s ♪ 音效 sfx/step_turn +t+3197.3s ♪ 音效 sfx/step_own +t+3197.3s 〰 震动:swipe(滑动识别) +t+3197.8s ♪ 音效 sfx/step_own +t+3197.8s 〰 震动:swipe(滑动识别) +t+3198.3s ♪ 音效 sfx/pulse +t+3198.7s ♪ 音效 sfx/side_wall_l +t+3198.7s ♪ 音效 sfx/side_open_r +t+3199.1s ♪ 音效 sfx/echo_3 +t+3199.2s ♪ 音效 sfx/mat_stone +t+3202.3s ♪ 音效 sfx/step_turn +t+3207.3s ♪ 音效 sfx/step_own +t+3207.3s 〰 震动:swipe(滑动识别) +t+3207.8s ♪ 音效 sfx/step_own +t+3207.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3208.3s ♪ 音效 sfx/pulse +t+3208.3s 〰 震动:error(做错了:两连短震) +t+3208.3s 〰 震动:error(做错了:两连短震) +t+3208.6s ♪ 音效 sfx/echo_1 +t+3208.7s ♪ 音效 sfx/side_wall_l +t+3208.7s ♪ 音效 sfx/side_open_r +t+3208.7s ♪ 音效 sfx/mat_stone +t+3212.3s ♪ 音效 sfx/pulse +t+3212.6s ♪ 音效 sfx/echo_1 +t+3212.7s ♪ 音效 sfx/side_wall_l +t+3212.7s ♪ 音效 sfx/side_open_r +t+3212.7s ♪ 音效 sfx/mat_stone +t+3217.3s ♪ 音效 sfx/step_turn +t+3222.3s ♪ 音效 sfx/step_own +t+3222.3s 〰 震动:swipe(滑动识别) +t+3222.8s ♪ 音效 sfx/step_own +t+3222.8s 〰 震动:swipe(滑动识别) +t+3223.3s ♪ 音效 sfx/pulse +t+3223.7s ♪ 音效 sfx/side_wall_l +t+3223.7s ♪ 音效 sfx/side_open_r +t+3224.1s ♪ 音效 sfx/echo_3 +t+3224.2s ♪ 音效 sfx/mat_stone +t+3227.3s ♪ 音效 sfx/step_turn +t+3232.3s ♪ 音效 sfx/step_own +t+3232.3s 〰 震动:swipe(滑动识别) +t+3232.8s ♪ 音效 sfx/step_own +t+3232.8s 〰 震动:swipe(滑动识别) +t+3233.3s ♪ 音效 sfx/pulse +t+3233.6s ♪ 音效 sfx/echo_1 +t+3233.7s ♪ 音效 sfx/side_open_l +t+3233.7s ♪ 音效 sfx/side_open_r +t+3233.7s ♪ 音效 sfx/mat_stone +t+3237.3s ♪ 音效 sfx/step_turn +t+3242.3s ♪ 音效 sfx/step_own +t+3242.3s 〰 震动:swipe(滑动识别) +t+3242.8s ♪ 音效 sfx/step_own +t+3242.8s 〰 震动:swipe(滑动识别) +t+3243.3s ♪ 音效 sfx/pulse +t+3243.7s ♪ 音效 sfx/side_wall_l +t+3243.7s ♪ 音效 sfx/side_open_r +t+3244.1s ♪ 音效 sfx/echo_3 +t+3244.2s ♪ 音效 sfx/mat_stone +t+3247.3s ♪ 音效 sfx/step_turn +t+3252.3s ♪ 音效 sfx/step_own +t+3252.3s 〰 震动:swipe(滑动识别) +t+3252.8s ♪ 音效 sfx/step_own +t+3252.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3253.3s ♪ 音效 sfx/pulse +t+3253.3s 〰 震动:error(做错了:两连短震) +t+3253.3s 〰 震动:error(做错了:两连短震) +t+3253.6s ♪ 音效 sfx/echo_1 +t+3253.7s ♪ 音效 sfx/side_wall_l +t+3253.7s ♪ 音效 sfx/side_open_r +t+3253.7s ♪ 音效 sfx/mat_stone +t+3257.3s ♪ 音效 sfx/pulse +t+3257.6s ♪ 音效 sfx/echo_1 +t+3257.7s ♪ 音效 sfx/side_wall_l +t+3257.7s ♪ 音效 sfx/side_open_r +t+3257.7s ♪ 音效 sfx/mat_stone +t+3262.3s ♪ 音效 sfx/step_turn +t+3267.3s ♪ 音效 sfx/step_own +t+3267.3s 〰 震动:swipe(滑动识别) +t+3267.8s ♪ 音效 sfx/step_own +t+3267.8s 〰 震动:swipe(滑动识别) +t+3268.3s ♪ 音效 sfx/pulse +t+3268.7s ♪ 音效 sfx/side_wall_l +t+3268.7s ♪ 音效 sfx/side_open_r +t+3269.1s ♪ 音效 sfx/echo_3 +t+3269.2s ♪ 音效 sfx/mat_stone +t+3272.3s ♪ 音效 sfx/step_turn +t+3277.3s ♪ 音效 sfx/step_own +t+3277.3s 〰 震动:swipe(滑动识别) +t+3277.8s ♪ 音效 sfx/step_own +t+3277.8s 〰 震动:swipe(滑动识别) +t+3278.3s ♪ 音效 sfx/pulse +t+3278.6s ♪ 音效 sfx/echo_1 +t+3278.7s ♪ 音效 sfx/side_open_l +t+3278.7s ♪ 音效 sfx/side_open_r +t+3278.7s ♪ 音效 sfx/mat_stone +t+3282.3s ♪ 音效 sfx/step_turn +t+3287.3s ♪ 音效 sfx/step_own +t+3287.3s 〰 震动:swipe(滑动识别) +t+3287.8s ♪ 音效 sfx/step_own +t+3287.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3288.3s ♪ 音效 sfx/pulse +t+3288.3s 〰 震动:error(做错了:两连短震) +t+3288.3s 〰 震动:error(做错了:两连短震) +t+3288.7s ♪ 音效 sfx/side_wall_l +t+3288.7s ♪ 音效 sfx/side_open_r +t+3289.1s ♪ 音效 sfx/echo_3 +t+3289.2s ♪ 音效 sfx/mat_stone +t+3292.3s ♪ 音效 sfx/step_own +t+3292.3s 〰 震动:swipe(滑动识别) +t+3292.8s ♪ 音效 sfx/step_own +t+3292.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3293.3s ♪ 音效 sfx/pulse +t+3293.3s 〰 震动:error(做错了:两连短震) +t+3293.3s 〰 震动:error(做错了:两连短震) +t+3293.6s ♪ 音效 sfx/echo_1 +t+3293.7s ♪ 音效 sfx/side_open_l +t+3293.7s ♪ 音效 sfx/side_wall_r +t+3293.7s ♪ 音效 sfx/mat_stone +t+3297.3s ♪ 音效 sfx/pulse +t+3297.6s ♪ 音效 sfx/echo_1 +t+3297.7s ♪ 音效 sfx/side_open_l +t+3297.7s ♪ 音效 sfx/side_wall_r +t+3297.7s ♪ 音效 sfx/mat_stone +t+3302.3s ♪ 音效 sfx/step_turn +t+3307.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3317.3s ♪ 音效 sfx/pulse +t+3317.3s 〰 震动:error(做错了:两连短震) +t+3317.3s 〰 震动:error(做错了:两连短震) +t+3317.6s ♪ 音效 sfx/echo_1 +t+3317.7s ♪ 音效 sfx/side_wall_l +t+3317.7s ♪ 音效 sfx/side_open_r +t+3317.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3322.3s ♪ 音效 sfx/pulse +t+3322.3s 〰 震动:error(做错了:两连短震) +t+3322.3s 〰 震动:error(做错了:两连短震) +t+3322.6s ♪ 音效 sfx/echo_1 +t+3322.7s ♪ 音效 sfx/side_wall_l +t+3322.7s ♪ 音效 sfx/side_open_r +t+3322.7s ♪ 音效 sfx/mat_stone +t+3327.3s ♪ 音效 sfx/step_turn +t+3332.3s ♪ 音效 sfx/pulse +t+3332.7s ♪ 音效 sfx/side_wall_l +t+3332.7s ♪ 音效 sfx/side_open_r +t+3333.4s ♪ 音效 sfx/echo_4 +t+3333.5s ♪ 音效 sfx/mat_stone +t+3337.3s ♪ 音效 sfx/step_turn +t+3342.3s ♪ 音效 sfx/step_own +t+3342.3s 〰 震动:swipe(滑动识别) +t+3342.8s ♪ 音效 sfx/step_own +t+3342.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3343.3s ♪ 音效 sfx/pulse +t+3343.3s 〰 震动:error(做错了:两连短震) +t+3343.3s 〰 震动:error(做错了:两连短震) +t+3343.6s ♪ 音效 sfx/echo_1 +t+3343.7s ♪ 音效 sfx/side_open_l +t+3343.7s ♪ 音效 sfx/side_wall_r +t+3343.7s ♪ 音效 sfx/mat_stone +t+3347.3s ♪ 音效 sfx/pulse +t+3347.6s ♪ 音效 sfx/echo_1 +t+3347.7s ♪ 音效 sfx/side_open_l +t+3347.7s ♪ 音效 sfx/side_wall_r +t+3347.7s ♪ 音效 sfx/mat_stone +t+3352.3s ♪ 音效 sfx/step_turn +t+3357.3s ♪ 音效 sfx/bump +t+3367.3s ♪ 音效 sfx/pulse +t+3367.6s ♪ 音效 sfx/echo_1 +t+3367.7s ♪ 音效 sfx/side_wall_l +t+3367.7s ♪ 音效 sfx/side_open_r +t+3367.7s ♪ 音效 sfx/mat_stone +t+3372.3s ♪ 音效 sfx/step_turn +t+3377.3s ♪ 音效 sfx/step_own +t+3377.3s 〰 震动:swipe(滑动识别) +t+3377.8s ♪ 音效 sfx/step_own +t+3377.8s 〰 震动:swipe(滑动识别) +t+3378.3s ♪ 音效 sfx/pulse +t+3378.6s ♪ 音效 sfx/echo_1 +t+3378.7s ♪ 音效 sfx/side_wall_l +t+3378.7s ♪ 音效 sfx/side_open_r +t+3378.7s ♪ 音效 sfx/mat_stone +t+3382.3s ♪ 音效 sfx/step_turn +t+3387.3s ♪ 音效 sfx/step_own +t+3387.3s 〰 震动:swipe(滑动识别) +t+3387.8s ♪ 音效 sfx/step_own +t+3387.8s 〰 震动:swipe(滑动识别) +t+3388.3s ♪ 音效 sfx/pulse +t+3388.7s ♪ 音效 sfx/side_open_l +t+3388.7s ♪ 音效 sfx/side_wall_r +t+3389.4s ♪ 音效 sfx/echo_4 +t+3389.5s ♪ 音效 sfx/mat_stone +t+3392.3s ♪ 音效 sfx/step_turn +t+3397.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3407.3s ♪ 音效 sfx/pulse +t+3407.3s 〰 震动:error(做错了:两连短震) +t+3407.3s 〰 震动:error(做错了:两连短震) +t+3407.6s ♪ 音效 sfx/echo_1 +t+3407.7s ♪ 音效 sfx/side_open_l +t+3407.7s ♪ 音效 sfx/side_open_r +t+3407.7s ♪ 音效 sfx/mat_stone +t+3412.3s ♪ 音效 sfx/pulse +t+3412.6s ♪ 音效 sfx/echo_1 +t+3412.7s ♪ 音效 sfx/side_open_l +t+3412.7s ♪ 音效 sfx/side_open_r +t+3412.7s ♪ 音效 sfx/mat_stone +t+3417.3s ♪ 音效 sfx/step_turn +t+3422.3s ♪ 音效 sfx/step_own +t+3422.3s 〰 震动:swipe(滑动识别) +t+3422.8s ♪ 音效 sfx/step_own +t+3422.8s 〰 震动:swipe(滑动识别) +t+3423.3s ♪ 音效 sfx/pulse +t+3423.6s ♪ 音效 sfx/echo_1 +t+3423.7s ♪ 音效 sfx/side_open_l +t+3423.7s ♪ 音效 sfx/side_wall_r +t+3423.7s ♪ 音效 sfx/mat_stone +t+3427.3s ♪ 音效 sfx/step_turn +t+3432.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3442.3s ♪ 音效 sfx/pulse +t+3442.3s 〰 震动:error(做错了:两连短震) +t+3442.3s 〰 震动:error(做错了:两连短震) +t+3442.6s ♪ 音效 sfx/echo_1 +t+3442.7s ♪ 音效 sfx/side_wall_l +t+3442.7s ♪ 音效 sfx/side_open_r +t+3442.7s ♪ 音效 sfx/mat_stone +t+3447.3s ♪ 音效 sfx/pulse +t+3447.6s ♪ 音效 sfx/echo_1 +t+3447.7s ♪ 音效 sfx/side_wall_l +t+3447.7s ♪ 音效 sfx/side_open_r +t+3447.7s ♪ 音效 sfx/mat_stone +t+3452.3s ♪ 音效 sfx/step_turn +t+3457.3s ♪ 音效 sfx/step_own +t+3457.3s 〰 震动:swipe(滑动识别) +t+3457.8s ♪ 音效 sfx/step_own +t+3457.8s 〰 震动:swipe(滑动识别) +t+3458.3s ♪ 音效 sfx/pulse +t+3458.7s ♪ 音效 sfx/side_open_l +t+3458.7s ♪ 音效 sfx/side_wall_r +t+3459.4s ♪ 音效 sfx/echo_4 +t+3459.5s ♪ 音效 sfx/mat_stone +t+3462.3s ♪ 音效 sfx/step_turn +t+3467.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3477.3s ♪ 音效 sfx/pulse +t+3477.3s 〰 震动:error(做错了:两连短震) +t+3477.3s 〰 震动:error(做错了:两连短震) +t+3477.6s ♪ 音效 sfx/echo_1 +t+3477.7s ♪ 音效 sfx/side_open_l +t+3477.7s ♪ 音效 sfx/side_open_r +t+3477.7s ♪ 音效 sfx/mat_stone +t+3482.3s ♪ 音效 sfx/pulse +t+3482.6s ♪ 音效 sfx/echo_1 +t+3482.7s ♪ 音效 sfx/side_open_l +t+3482.7s ♪ 音效 sfx/side_open_r +t+3482.7s ♪ 音效 sfx/mat_stone +t+3487.3s ♪ 音效 sfx/step_turn +t+3492.3s ♪ 音效 sfx/step_own +t+3492.3s 〰 震动:swipe(滑动识别) +t+3492.8s ♪ 音效 sfx/step_own +t+3492.8s 〰 震动:swipe(滑动识别) +t+3493.3s ♪ 音效 sfx/pulse +t+3493.6s ♪ 音效 sfx/echo_1 +t+3493.7s ♪ 音效 sfx/side_open_l +t+3493.7s ♪ 音效 sfx/side_wall_r +t+3493.7s ♪ 音效 sfx/mat_stone +t+3497.3s ♪ 音效 sfx/step_turn +t+3502.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3512.3s ♪ 音效 sfx/pulse +t+3512.3s 〰 震动:error(做错了:两连短震) +t+3512.3s 〰 震动:error(做错了:两连短震) +t+3512.6s ♪ 音效 sfx/echo_1 +t+3512.7s ♪ 音效 sfx/side_wall_l +t+3512.7s ♪ 音效 sfx/side_open_r +t+3512.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3517.3s ♪ 音效 sfx/pulse +t+3517.3s 〰 震动:error(做错了:两连短震) +t+3517.3s 〰 震动:error(做错了:两连短震) +t+3517.6s ♪ 音效 sfx/echo_1 +t+3517.7s ♪ 音效 sfx/side_wall_l +t+3517.7s ♪ 音效 sfx/side_open_r +t+3517.7s ♪ 音效 sfx/mat_stone +t+3522.3s ♪ 音效 sfx/pulse +t+3522.6s ♪ 音效 sfx/echo_1 +t+3522.7s ♪ 音效 sfx/side_wall_l +t+3522.7s ♪ 音效 sfx/side_open_r +t+3522.7s ♪ 音效 sfx/mat_stone +t+3527.3s ♪ 音效 sfx/step_turn +t+3532.3s ♪ 音效 sfx/step_own +t+3532.3s 〰 震动:swipe(滑动识别) +t+3532.8s ♪ 音效 sfx/step_own +t+3532.8s 〰 震动:swipe(滑动识别) +t+3533.3s ♪ 音效 sfx/pulse +t+3533.7s ♪ 音效 sfx/side_open_l +t+3533.7s ♪ 音效 sfx/side_wall_r +t+3534.4s ♪ 音效 sfx/echo_4 +t+3534.5s ♪ 音效 sfx/mat_stone +t+3537.3s ♪ 音效 sfx/step_turn +t+3542.3s ♪ 音效 sfx/bump +t+3552.3s ♪ 音效 sfx/pulse +t+3552.6s ♪ 音效 sfx/echo_1 +t+3552.7s ♪ 音效 sfx/side_open_l +t+3552.7s ♪ 音效 sfx/side_open_r +t+3552.7s ♪ 音效 sfx/mat_stone +t+3557.3s ♪ 音效 sfx/step_turn +t+3562.3s ♪ 音效 sfx/step_own +t+3562.3s 〰 震动:swipe(滑动识别) +t+3562.8s ♪ 音效 sfx/step_own +t+3562.8s 〰 震动:swipe(滑动识别) +t+3563.3s ♪ 音效 sfx/pulse +t+3563.6s ♪ 音效 sfx/echo_1 +t+3563.7s ♪ 音效 sfx/side_open_l +t+3563.7s ♪ 音效 sfx/side_wall_r +t+3563.7s ♪ 音效 sfx/mat_stone +t+3567.3s ♪ 音效 sfx/step_turn +t+3572.3s ♪ 音效 sfx/bump +t+3582.3s ♪ 音效 sfx/bump +t+3592.3s ♪ 音效 sfx/pulse +t+3592.6s ♪ 音效 sfx/echo_1 +t+3592.7s ♪ 音效 sfx/side_wall_l +t+3592.7s ♪ 音效 sfx/side_open_r +t+3592.7s ♪ 音效 sfx/mat_stone +t+3597.3s ♪ 音效 sfx/step_turn +t+3602.3s ♪ 音效 sfx/step_own +t+3602.3s 〰 震动:swipe(滑动识别) +t+3602.8s ♪ 音效 sfx/step_own +t+3602.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3603.3s ♪ 音效 sfx/pulse +t+3603.3s 〰 震动:error(做错了:两连短震) +t+3603.3s 〰 震动:error(做错了:两连短震) +t+3603.7s ♪ 音效 sfx/side_open_l +t+3603.7s ♪ 音效 sfx/side_wall_r +t+3604.4s ♪ 音效 sfx/echo_4 +t+3604.5s ♪ 音效 sfx/mat_stone +t+3607.3s ♪ 音效 sfx/pulse +t+3607.7s ♪ 音效 sfx/side_open_l +t+3607.7s ♪ 音效 sfx/side_wall_r +t+3608.4s ♪ 音效 sfx/echo_4 +t+3608.5s ♪ 音效 sfx/mat_stone +t+3612.3s ♪ 音效 sfx/step_turn +t+3617.3s ♪ 音效 sfx/bump +t+3627.3s ♪ 音效 sfx/pulse +t+3627.6s ♪ 音效 sfx/echo_1 +t+3627.7s ♪ 音效 sfx/side_open_l +t+3627.7s ♪ 音效 sfx/side_open_r +t+3627.7s ♪ 音效 sfx/mat_stone +t+3632.3s ♪ 音效 sfx/step_turn +t+3637.3s ♪ 音效 sfx/step_own +t+3637.3s 〰 震动:swipe(滑动识别) +t+3637.8s ♪ 音效 sfx/step_own +t+3637.8s 〰 震动:swipe(滑动识别) +t+3638.3s ♪ 音效 sfx/pulse +t+3638.6s ♪ 音效 sfx/echo_1 +t+3638.7s ♪ 音效 sfx/side_open_l +t+3638.7s ♪ 音效 sfx/side_wall_r +t+3638.7s ♪ 音效 sfx/mat_stone +t+3642.3s ♪ 音效 sfx/step_turn +t+3647.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3657.3s ♪ 音效 sfx/pulse +t+3657.3s 〰 震动:error(做错了:两连短震) +t+3657.3s 〰 震动:error(做错了:两连短震) +t+3657.6s ♪ 音效 sfx/echo_1 +t+3657.7s ♪ 音效 sfx/side_wall_l +t+3657.7s ♪ 音效 sfx/side_open_r +t+3657.7s ♪ 音效 sfx/mat_stone +t+3662.3s ♪ 音效 sfx/pulse +t+3662.6s ♪ 音效 sfx/echo_1 +t+3662.7s ♪ 音效 sfx/side_wall_l +t+3662.7s ♪ 音效 sfx/side_open_r +t+3662.7s ♪ 音效 sfx/mat_stone +t+3667.3s ♪ 音效 sfx/step_turn +t+3672.3s ♪ 音效 sfx/step_own +t+3672.3s 〰 震动:swipe(滑动识别) +t+3672.8s ♪ 音效 sfx/step_own +t+3672.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3673.3s ♪ 音效 sfx/pulse +t+3673.3s 〰 震动:error(做错了:两连短震) +t+3673.3s 〰 震动:error(做错了:两连短震) +t+3673.7s ♪ 音效 sfx/side_open_l +t+3673.7s ♪ 音效 sfx/side_wall_r +t+3674.4s ♪ 音效 sfx/echo_4 +t+3674.5s ♪ 音效 sfx/mat_stone +t+3677.3s ♪ 音效 sfx/pulse +t+3677.7s ♪ 音效 sfx/side_open_l +t+3677.7s ♪ 音效 sfx/side_wall_r +t+3678.4s ♪ 音效 sfx/echo_4 +t+3678.5s ♪ 音效 sfx/mat_stone +t+3682.3s ♪ 音效 sfx/step_turn +t+3687.3s ♪ 音效 sfx/bump +t+3697.3s ♪ 音效 sfx/pulse +t+3697.6s ♪ 音效 sfx/echo_1 +t+3697.7s ♪ 音效 sfx/side_open_l +t+3697.7s ♪ 音效 sfx/side_open_r +t+3697.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3702.3s ♪ 音效 sfx/pulse +t+3702.3s 〰 震动:error(做错了:两连短震) +t+3702.3s 〰 震动:error(做错了:两连短震) +t+3702.6s ♪ 音效 sfx/echo_1 +t+3702.7s ♪ 音效 sfx/side_open_l +t+3702.7s ♪ 音效 sfx/side_open_r +t+3702.7s ♪ 音效 sfx/mat_stone +t+3707.3s ♪ 音效 sfx/pulse +t+3707.6s ♪ 音效 sfx/echo_1 +t+3707.7s ♪ 音效 sfx/side_open_l +t+3707.7s ♪ 音效 sfx/side_open_r +t+3707.7s ♪ 音效 sfx/mat_stone +t+3712.3s ♪ 音效 sfx/step_turn +t+3717.3s ♪ 音效 sfx/step_own +t+3717.3s 〰 震动:swipe(滑动识别) +t+3717.8s ♪ 音效 sfx/step_own +t+3717.8s 〰 震动:swipe(滑动识别) +t+3718.3s ♪ 音效 sfx/bump +t+3727.3s ♪ 音效 sfx/pulse +t+3727.6s ♪ 音效 sfx/echo_1 +t+3727.7s ♪ 音效 sfx/side_open_l +t+3727.7s ♪ 音效 sfx/side_wall_r +t+3727.7s ♪ 音效 sfx/mat_stone +t+3732.3s ♪ 音效 sfx/step_turn +t+3737.3s ♪ 音效 sfx/bump +t+3747.3s ♪ 音效 sfx/pulse +t+3747.6s ♪ 音效 sfx/echo_1 +t+3747.7s ♪ 音效 sfx/side_wall_l +t+3747.7s ♪ 音效 sfx/side_open_r +t+3747.7s ♪ 音效 sfx/mat_stone +t+3752.3s ♪ 音效 sfx/step_turn +t+3757.3s ♪ 音效 sfx/step_own +t+3757.3s 〰 震动:swipe(滑动识别) +t+3757.8s ♪ 音效 sfx/step_own +t+3757.8s 〰 震动:swipe(滑动识别) +t+3758.3s ♪ 音效 sfx/pulse +t+3758.7s ♪ 音效 sfx/side_open_l +t+3758.7s ♪ 音效 sfx/side_wall_r +t+3759.4s ♪ 音效 sfx/echo_4 +t+3759.5s ♪ 音效 sfx/mat_stone +t+3762.3s ♪ 音效 sfx/step_turn +t+3767.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3777.3s ♪ 音效 sfx/pulse +t+3777.3s 〰 震动:error(做错了:两连短震) +t+3777.3s 〰 震动:error(做错了:两连短震) +t+3777.6s ♪ 音效 sfx/echo_1 +t+3777.7s ♪ 音效 sfx/side_open_l +t+3777.7s ♪ 音效 sfx/side_open_r +t+3777.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3782.3s ♪ 音效 sfx/pulse +t+3782.3s 〰 震动:error(做错了:两连短震) +t+3782.3s 〰 震动:error(做错了:两连短震) +t+3782.6s ♪ 音效 sfx/echo_1 +t+3782.7s ♪ 音效 sfx/side_open_l +t+3782.7s ♪ 音效 sfx/side_open_r +t+3782.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3787.3s ♪ 音效 sfx/pulse +t+3787.3s 〰 震动:error(做错了:两连短震) +t+3787.3s 〰 震动:error(做错了:两连短震) +t+3787.6s ♪ 音效 sfx/echo_1 +t+3787.7s ♪ 音效 sfx/side_open_l +t+3787.7s ♪ 音效 sfx/side_open_r +t+3787.7s ♪ 音效 sfx/mat_stone +t+3792.3s ♪ 音效 sfx/pulse +t+3792.6s ♪ 音效 sfx/echo_1 +t+3792.7s ♪ 音效 sfx/side_open_l +t+3792.7s ♪ 音效 sfx/side_open_r +t+3792.7s ♪ 音效 sfx/mat_stone +t+3797.3s ♪ 音效 sfx/step_turn +t+3802.3s ♪ 音效 sfx/step_own +t+3802.3s 〰 震动:swipe(滑动识别) +t+3802.8s ♪ 音效 sfx/step_own +t+3802.8s 〰 震动:swipe(滑动识别) +t+3803.3s ♪ 音效 sfx/pulse +t+3803.6s ♪ 音效 sfx/echo_1 +t+3803.7s ♪ 音效 sfx/side_open_l +t+3803.7s ♪ 音效 sfx/side_wall_r +t+3803.7s ♪ 音效 sfx/mat_stone +t+3807.3s ♪ 音效 sfx/step_turn +t+3812.3s ♪ 音效 sfx/bump +t+3822.3s ♪ 音效 sfx/step_turn + +#### 【玩家动作】手痒连点三下脉冲 + +t+3827.3s ♪ 音效 sfx/pulse +t+3827.3s 〰 震动:error(做错了:两连短震) +t+3827.3s 〰 震动:error(做错了:两连短震) +t+3827.7s ♪ 音效 sfx/side_wall_l +t+3827.7s ♪ 音效 sfx/side_open_r +t+3828.4s ♪ 音效 sfx/echo_4 +t+3828.5s ♪ 音效 sfx/mat_stone +t+3832.3s ♪ 音效 sfx/pulse +t+3832.7s ♪ 音效 sfx/side_wall_l +t+3832.7s ♪ 音效 sfx/side_open_r +t+3833.4s ♪ 音效 sfx/echo_4 +t+3833.5s ♪ 音效 sfx/mat_stone +t+3837.3s ♪ 音效 sfx/step_turn +t+3842.3s ♪ 音效 sfx/step_own +t+3842.3s 〰 震动:swipe(滑动识别) +t+3842.8s ♪ 音效 sfx/step_own +t+3842.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+3843.3s ♪ 音效 sfx/pulse +t+3843.3s 〰 震动:error(做错了:两连短震) +t+3843.3s 〰 震动:error(做错了:两连短震) +t+3843.6s ♪ 音效 sfx/echo_1 +t+3843.7s ♪ 音效 sfx/side_open_l +t+3843.7s ♪ 音效 sfx/side_wall_r +t+3843.7s ♪ 音效 sfx/mat_stone +t+3847.3s ♪ 音效 sfx/pulse +t+3847.6s ♪ 音效 sfx/echo_1 +t+3847.7s ♪ 音效 sfx/side_open_l +t+3847.7s ♪ 音效 sfx/side_wall_r +t+3847.7s ♪ 音效 sfx/mat_stone +t+3852.3s ♪ 音效 sfx/step_turn +t+3857.3s ♪ 音效 sfx/bump +t+3867.3s ♪ 音效 sfx/pulse +t+3867.6s ♪ 音效 sfx/echo_1 +t+3867.7s ♪ 音效 sfx/side_wall_l +t+3867.7s ♪ 音效 sfx/side_open_r +t+3867.7s ♪ 音效 sfx/mat_stone +t+3872.3s ♪ 音效 sfx/step_turn +t+3877.3s ♪ 音效 sfx/step_own +t+3877.3s 〰 震动:swipe(滑动识别) +t+3877.8s ♪ 音效 sfx/step_own +t+3877.8s 〰 震动:swipe(滑动识别) +t+3878.3s ♪ 音效 sfx/pulse +t+3878.6s ♪ 音效 sfx/echo_1 +t+3878.7s ♪ 音效 sfx/side_wall_l +t+3878.7s ♪ 音效 sfx/side_open_r +t+3878.7s ♪ 音效 sfx/mat_stone +t+3882.3s ♪ 音效 sfx/pulse +t+3882.6s ♪ 音效 sfx/echo_1 +t+3882.7s ♪ 音效 sfx/side_wall_l +t+3882.7s ♪ 音效 sfx/side_open_r +t+3882.7s ♪ 音效 sfx/mat_stone +t+3887.3s ♪ 音效 sfx/step_turn +t+3892.3s ♪ 音效 sfx/step_own +t+3892.3s 〰 震动:swipe(滑动识别) +t+3892.8s ♪ 音效 sfx/step_own +t+3892.8s 〰 震动:swipe(滑动识别) +t+3893.3s ♪ 音效 sfx/pulse +t+3893.7s ♪ 音效 sfx/side_open_l +t+3893.7s ♪ 音效 sfx/side_wall_r +t+3894.4s ♪ 音效 sfx/echo_4 +t+3894.5s ♪ 音效 sfx/mat_stone +t+3897.3s ♪ 音效 sfx/step_turn +t+3902.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3912.3s ♪ 音效 sfx/pulse +t+3912.3s 〰 震动:error(做错了:两连短震) +t+3912.3s 〰 震动:error(做错了:两连短震) +t+3912.6s ♪ 音效 sfx/echo_1 +t+3912.7s ♪ 音效 sfx/side_open_l +t+3912.7s ♪ 音效 sfx/side_open_r +t+3912.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3917.3s ♪ 音效 sfx/pulse +t+3917.3s 〰 震动:error(做错了:两连短震) +t+3917.3s 〰 震动:error(做错了:两连短震) +t+3917.6s ♪ 音效 sfx/echo_1 +t+3917.7s ♪ 音效 sfx/side_open_l +t+3917.7s ♪ 音效 sfx/side_open_r +t+3917.7s ♪ 音效 sfx/mat_stone +t+3922.3s ♪ 音效 sfx/pulse +t+3922.6s ♪ 音效 sfx/echo_1 +t+3922.7s ♪ 音效 sfx/side_open_l +t+3922.7s ♪ 音效 sfx/side_open_r +t+3922.7s ♪ 音效 sfx/mat_stone +t+3927.3s ♪ 音效 sfx/step_turn +t+3932.3s ♪ 音效 sfx/step_own +t+3932.3s 〰 震动:swipe(滑动识别) +t+3932.8s ♪ 音效 sfx/step_own +t+3932.8s 〰 震动:swipe(滑动识别) +t+3933.3s ♪ 音效 sfx/pulse +t+3933.6s ♪ 音效 sfx/echo_1 +t+3933.7s ♪ 音效 sfx/side_open_l +t+3933.7s ♪ 音效 sfx/side_wall_r +t+3933.7s ♪ 音效 sfx/mat_stone +t+3937.3s ♪ 音效 sfx/step_turn +t+3942.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+3952.3s ♪ 音效 sfx/pulse +t+3952.3s 〰 震动:error(做错了:两连短震) +t+3952.3s 〰 震动:error(做错了:两连短震) +t+3952.6s ♪ 音效 sfx/echo_1 +t+3952.7s ♪ 音效 sfx/side_wall_l +t+3952.7s ♪ 音效 sfx/side_open_r +t+3952.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3957.3s ♪ 音效 sfx/pulse +t+3957.3s 〰 震动:error(做错了:两连短震) +t+3957.3s 〰 震动:error(做错了:两连短震) +t+3957.6s ♪ 音效 sfx/echo_1 +t+3957.7s ♪ 音效 sfx/side_wall_l +t+3957.7s ♪ 音效 sfx/side_open_r +t+3957.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+3962.3s ♪ 音效 sfx/pulse +t+3962.3s 〰 震动:error(做错了:两连短震) +t+3962.3s 〰 震动:error(做错了:两连短震) +t+3962.6s ♪ 音效 sfx/echo_1 +t+3962.7s ♪ 音效 sfx/side_wall_l +t+3962.7s ♪ 音效 sfx/side_open_r +t+3962.7s ♪ 音效 sfx/mat_stone +t+3967.3s ♪ 音效 sfx/bump +t+3977.3s ♪ 音效 sfx/bump +t+3987.3s ♪ 音效 sfx/pulse +t+3987.6s ♪ 音效 sfx/echo_1 +t+3987.7s ♪ 音效 sfx/side_wall_l +t+3987.7s ♪ 音效 sfx/side_open_r +t+3987.7s ♪ 音效 sfx/mat_stone +t+3992.3s ♪ 音效 sfx/step_turn +t+3997.3s ♪ 音效 sfx/step_own +t+3997.3s 〰 震动:swipe(滑动识别) +t+3997.8s ♪ 音效 sfx/step_own +t+3997.8s 〰 震动:swipe(滑动识别) +t+3998.3s ♪ 音效 sfx/pulse +t+3998.7s ♪ 音效 sfx/side_open_l +t+3998.7s ♪ 音效 sfx/side_wall_r +t+3999.4s ♪ 音效 sfx/echo_4 +t+3999.5s ♪ 音效 sfx/mat_stone +t+4002.3s ♪ 音效 sfx/step_turn +t+4007.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+4017.3s ♪ 音效 sfx/pulse +t+4017.3s 〰 震动:error(做错了:两连短震) +t+4017.3s 〰 震动:error(做错了:两连短震) +t+4017.6s ♪ 音效 sfx/echo_1 +t+4017.7s ♪ 音效 sfx/side_open_l +t+4017.7s ♪ 音效 sfx/side_open_r +t+4017.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4022.3s ♪ 音效 sfx/pulse +t+4022.3s 〰 震动:error(做错了:两连短震) +t+4022.3s 〰 震动:error(做错了:两连短震) +t+4022.6s ♪ 音效 sfx/echo_1 +t+4022.7s ♪ 音效 sfx/side_open_l +t+4022.7s ♪ 音效 sfx/side_open_r +t+4022.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4027.3s ♪ 音效 sfx/pulse +t+4027.3s 〰 震动:error(做错了:两连短震) +t+4027.3s 〰 震动:error(做错了:两连短震) +t+4027.6s ♪ 音效 sfx/echo_1 +t+4027.7s ♪ 音效 sfx/side_open_l +t+4027.7s ♪ 音效 sfx/side_open_r +t+4027.7s ♪ 音效 sfx/mat_stone +t+4032.3s ♪ 音效 sfx/step_turn +t+4037.3s ♪ 音效 sfx/pulse +t+4037.7s ♪ 音效 sfx/side_open_l +t+4037.7s ♪ 音效 sfx/side_wall_r +t+4038.4s ♪ 音效 sfx/echo_4 +t+4038.5s ♪ 音效 sfx/mat_stone +t+4042.3s ♪ 音效 sfx/step_turn +t+4047.3s ♪ 音效 sfx/bump +t+4057.3s ♪ 音效 sfx/pulse +t+4057.6s ♪ 音效 sfx/echo_1 +t+4057.7s ♪ 音效 sfx/side_open_l +t+4057.7s ♪ 音效 sfx/side_open_r +t+4057.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4062.3s ♪ 音效 sfx/pulse +t+4062.3s 〰 震动:error(做错了:两连短震) +t+4062.3s 〰 震动:error(做错了:两连短震) +t+4062.6s ♪ 音效 sfx/echo_1 +t+4062.7s ♪ 音效 sfx/side_open_l +t+4062.7s ♪ 音效 sfx/side_open_r +t+4062.7s ♪ 音效 sfx/mat_stone +t+4067.3s ♪ 音效 sfx/pulse +t+4067.6s ♪ 音效 sfx/echo_1 +t+4067.7s ♪ 音效 sfx/side_open_l +t+4067.7s ♪ 音效 sfx/side_open_r +t+4067.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4072.3s ♪ 音效 sfx/pulse +t+4072.3s 〰 震动:error(做错了:两连短震) +t+4072.3s 〰 震动:error(做错了:两连短震) +t+4072.6s ♪ 音效 sfx/echo_1 +t+4072.7s ♪ 音效 sfx/side_open_l +t+4072.7s ♪ 音效 sfx/side_open_r +t+4072.7s ♪ 音效 sfx/mat_stone +t+4077.3s ♪ 音效 sfx/pulse +t+4077.6s ♪ 音效 sfx/echo_1 +t+4077.7s ♪ 音效 sfx/side_open_l +t+4077.7s ♪ 音效 sfx/side_open_r +t+4077.7s ♪ 音效 sfx/mat_stone +t+4082.3s ♪ 音效 sfx/step_turn +t+4087.3s ♪ 音效 sfx/step_own +t+4087.3s 〰 震动:swipe(滑动识别) +t+4087.8s ♪ 音效 sfx/step_own +t+4087.8s 〰 震动:swipe(滑动识别) +t+4088.3s ♪ 音效 sfx/pulse +t+4088.6s ♪ 音效 sfx/echo_1 +t+4088.7s ♪ 音效 sfx/side_open_l +t+4088.7s ♪ 音效 sfx/side_wall_r +t+4088.7s ♪ 音效 sfx/mat_stone +t+4092.3s ♪ 音效 sfx/step_turn +t+4097.3s ♪ 音效 sfx/bump +t+4107.3s ♪ 音效 sfx/pulse +t+4107.6s ♪ 音效 sfx/echo_1 +t+4107.7s ♪ 音效 sfx/side_wall_l +t+4107.7s ♪ 音效 sfx/side_open_r +t+4107.7s ♪ 音效 sfx/mat_stone +t+4112.3s ♪ 音效 sfx/step_turn + +#### 【玩家动作】手痒连点三下脉冲 + +t+4117.3s ♪ 音效 sfx/pulse +t+4117.3s 〰 震动:error(做错了:两连短震) +t+4117.3s 〰 震动:error(做错了:两连短震) +t+4117.7s ♪ 音效 sfx/side_wall_l +t+4117.7s ♪ 音效 sfx/side_open_r +t+4118.4s ♪ 音效 sfx/echo_4 +t+4118.5s ♪ 音效 sfx/mat_stone +t+4122.3s ♪ 音效 sfx/pulse +t+4122.7s ♪ 音效 sfx/side_wall_l +t+4122.7s ♪ 音效 sfx/side_open_r +t+4123.4s ♪ 音效 sfx/echo_4 +t+4123.5s ♪ 音效 sfx/mat_stone +t+4127.3s ♪ 音效 sfx/step_turn +t+4132.3s ♪ 音效 sfx/step_own +t+4132.3s 〰 震动:swipe(滑动识别) +t+4132.8s ♪ 音效 sfx/step_own +t+4132.8s 〰 震动:swipe(滑动识别) +t+4133.3s ♪ 音效 sfx/pulse +t+4133.6s ♪ 音效 sfx/echo_1 +t+4133.7s ♪ 音效 sfx/side_open_l +t+4133.7s ♪ 音效 sfx/side_wall_r +t+4133.7s ♪ 音效 sfx/mat_stone +t+4137.3s ♪ 音效 sfx/step_turn +t+4142.3s ♪ 音效 sfx/bump +t+4152.3s ♪ 音效 sfx/pulse +t+4152.6s ♪ 音效 sfx/echo_1 +t+4152.7s ♪ 音效 sfx/side_wall_l +t+4152.7s ♪ 音效 sfx/side_open_r +t+4152.7s ♪ 音效 sfx/mat_stone +t+4157.3s ♪ 音效 sfx/step_turn +t+4162.3s ♪ 音效 sfx/step_own +t+4162.3s 〰 震动:swipe(滑动识别) +t+4162.8s ♪ 音效 sfx/step_own +t+4162.8s 〰 震动:swipe(滑动识别) +t+4163.3s ♪ 音效 sfx/pulse +t+4163.6s ♪ 音效 sfx/echo_1 +t+4163.7s ♪ 音效 sfx/side_wall_l +t+4163.7s ♪ 音效 sfx/side_open_r +t+4163.7s ♪ 音效 sfx/mat_stone +t+4167.3s ♪ 音效 sfx/pulse +t+4167.6s ♪ 音效 sfx/echo_1 +t+4167.7s ♪ 音效 sfx/side_wall_l +t+4167.7s ♪ 音效 sfx/side_open_r +t+4167.7s ♪ 音效 sfx/mat_stone +t+4172.3s ♪ 音效 sfx/step_turn +t+4177.3s ♪ 音效 sfx/step_own +t+4177.3s 〰 震动:swipe(滑动识别) +t+4177.8s ♪ 音效 sfx/step_own +t+4177.8s 〰 震动:swipe(滑动识别) +t+4178.3s ♪ 音效 sfx/pulse +t+4178.7s ♪ 音效 sfx/side_open_l +t+4178.7s ♪ 音效 sfx/side_wall_r +t+4179.4s ♪ 音效 sfx/echo_4 +t+4179.5s ♪ 音效 sfx/mat_stone +t+4182.3s ♪ 音效 sfx/step_turn +t+4187.3s ♪ 音效 sfx/bump +t+4197.3s ♪ 音效 sfx/pulse +t+4197.6s ♪ 音效 sfx/echo_1 +t+4197.7s ♪ 音效 sfx/side_open_l +t+4197.7s ♪ 音效 sfx/side_open_r +t+4197.7s ♪ 音效 sfx/mat_stone +t+4202.3s ♪ 音效 sfx/step_turn +t+4207.3s ♪ 音效 sfx/step_own +t+4207.3s 〰 震动:swipe(滑动识别) +t+4207.8s ♪ 音效 sfx/step_own +t+4207.8s 〰 震动:swipe(滑动识别) +t+4208.3s ♪ 音效 sfx/pulse +t+4208.6s ♪ 音效 sfx/echo_1 +t+4208.7s ♪ 音效 sfx/side_open_l +t+4208.7s ♪ 音效 sfx/side_wall_r +t+4208.7s ♪ 音效 sfx/mat_stone +t+4212.3s ♪ 音效 sfx/step_turn +t+4217.3s ♪ 音效 sfx/bump +t+4227.3s ♪ 音效 sfx/pulse +t+4227.6s ♪ 音效 sfx/echo_1 +t+4227.7s ♪ 音效 sfx/side_wall_l +t+4227.7s ♪ 音效 sfx/side_open_r +t+4227.7s ♪ 音效 sfx/mat_stone +t+4232.3s ♪ 音效 sfx/step_turn +t+4237.3s ♪ 音效 sfx/step_own +t+4237.3s 〰 震动:swipe(滑动识别) +t+4237.8s ♪ 音效 sfx/step_own +t+4237.8s 〰 震动:swipe(滑动识别) +t+4238.3s ♪ 音效 sfx/pulse +t+4238.7s ♪ 音效 sfx/side_open_l +t+4238.7s ♪ 音效 sfx/side_wall_r +t+4239.4s ♪ 音效 sfx/echo_4 +t+4239.5s ♪ 音效 sfx/mat_stone +t+4242.3s ♪ 音效 sfx/step_turn +t+4247.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+4257.3s ♪ 音效 sfx/pulse +t+4257.3s 〰 震动:error(做错了:两连短震) +t+4257.3s 〰 震动:error(做错了:两连短震) +t+4257.6s ♪ 音效 sfx/echo_1 +t+4257.7s ♪ 音效 sfx/side_open_l +t+4257.7s ♪ 音效 sfx/side_open_r +t+4257.7s ♪ 音效 sfx/mat_stone +t+4262.3s ♪ 音效 sfx/pulse +t+4262.6s ♪ 音效 sfx/echo_1 +t+4262.7s ♪ 音效 sfx/side_open_l +t+4262.7s ♪ 音效 sfx/side_open_r +t+4262.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4267.3s ♪ 音效 sfx/pulse +t+4267.3s 〰 震动:error(做错了:两连短震) +t+4267.3s 〰 震动:error(做错了:两连短震) +t+4267.6s ♪ 音效 sfx/echo_1 +t+4267.7s ♪ 音效 sfx/side_open_l +t+4267.7s ♪ 音效 sfx/side_open_r +t+4267.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4272.3s ♪ 音效 sfx/pulse +t+4272.3s 〰 震动:error(做错了:两连短震) +t+4272.3s 〰 震动:error(做错了:两连短震) +t+4272.6s ♪ 音效 sfx/echo_1 +t+4272.7s ♪ 音效 sfx/side_open_l +t+4272.7s ♪ 音效 sfx/side_open_r +t+4272.7s ♪ 音效 sfx/mat_stone +t+4277.3s ♪ 音效 sfx/pulse +t+4277.6s ♪ 音效 sfx/echo_1 +t+4277.7s ♪ 音效 sfx/side_open_l +t+4277.7s ♪ 音效 sfx/side_open_r +t+4277.7s ♪ 音效 sfx/mat_stone +t+4282.3s ♪ 音效 sfx/step_turn +t+4287.3s ♪ 音效 sfx/step_own +t+4287.3s 〰 震动:swipe(滑动识别) +t+4287.8s ♪ 音效 sfx/step_own +t+4287.8s 〰 震动:swipe(滑动识别) +t+4288.3s ♪ 音效 sfx/step_turn +t+4292.3s ♪ 音效 sfx/pulse +t+4292.6s ♪ 音效 sfx/echo_1 +t+4292.7s ♪ 音效 sfx/side_wall_l +t+4292.7s ♪ 音效 sfx/side_open_r +t+4292.7s ♪ 音效 sfx/mat_stone +t+4297.3s ♪ 音效 sfx/step_turn +t+4302.3s ♪ 音效 sfx/step_own +t+4302.3s 〰 震动:swipe(滑动识别) +t+4302.8s ♪ 音效 sfx/step_own +t+4302.8s 〰 震动:swipe(滑动识别) +t+4303.3s ♪ 音效 sfx/pulse +t+4303.7s ♪ 音效 sfx/side_open_l +t+4303.7s ♪ 音效 sfx/side_wall_r +t+4304.4s ♪ 音效 sfx/echo_4 +t+4304.5s ♪ 音效 sfx/mat_stone +t+4307.3s ♪ 音效 sfx/step_turn +t+4312.3s ♪ 音效 sfx/bump +t+4322.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+4332.3s ♪ 音效 sfx/pulse +t+4332.3s 〰 震动:error(做错了:两连短震) +t+4332.3s 〰 震动:error(做错了:两连短震) +t+4332.6s ♪ 音效 sfx/echo_1 +t+4332.7s ♪ 音效 sfx/side_open_l +t+4332.7s ♪ 音效 sfx/side_open_r +t+4332.7s ♪ 音效 sfx/mat_stone +t+4337.3s ♪ 音效 sfx/step_turn +t+4342.3s ♪ 音效 sfx/pulse +t+4342.7s ♪ 音效 sfx/side_open_l +t+4342.7s ♪ 音效 sfx/side_wall_r +t+4343.4s ♪ 音效 sfx/echo_4 +t+4343.5s ♪ 音效 sfx/mat_stone +t+4347.3s ♪ 音效 sfx/step_turn +t+4352.3s ♪ 音效 sfx/bump +t+4362.3s ♪ 音效 sfx/pulse +t+4362.6s ♪ 音效 sfx/echo_1 +t+4362.7s ♪ 音效 sfx/side_open_l +t+4362.7s ♪ 音效 sfx/side_open_r +t+4362.7s ♪ 音效 sfx/mat_stone +t+4367.3s ♪ 音效 sfx/step_turn +t+4372.3s ♪ 音效 sfx/step_own +t+4372.3s 〰 震动:swipe(滑动识别) +t+4372.8s ♪ 音效 sfx/step_own +t+4372.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+4373.3s ♪ 音效 sfx/pulse +t+4373.3s 〰 震动:error(做错了:两连短震) +t+4373.3s 〰 震动:error(做错了:两连短震) +t+4373.6s ♪ 音效 sfx/echo_1 +t+4373.7s ♪ 音效 sfx/side_open_l +t+4373.7s ♪ 音效 sfx/side_wall_r +t+4373.7s ♪ 音效 sfx/mat_stone +t+4377.3s ♪ 音效 sfx/pulse +t+4377.6s ♪ 音效 sfx/echo_1 +t+4377.7s ♪ 音效 sfx/side_open_l +t+4377.7s ♪ 音效 sfx/side_wall_r +t+4377.7s ♪ 音效 sfx/mat_stone +t+4382.3s ♪ 音效 sfx/step_turn +t+4387.3s ♪ 音效 sfx/bump +t+4397.3s ♪ 音效 sfx/pulse +t+4397.6s ♪ 音效 sfx/echo_1 +t+4397.7s ♪ 音效 sfx/side_wall_l +t+4397.7s ♪ 音效 sfx/side_open_r +t+4397.7s ♪ 音效 sfx/mat_stone +t+4402.3s ♪ 音效 sfx/step_turn +t+4407.3s ♪ 音效 sfx/step_own +t+4407.3s 〰 震动:swipe(滑动识别) +t+4407.8s ♪ 音效 sfx/step_own +t+4407.8s 〰 震动:swipe(滑动识别) +t+4408.3s ♪ 音效 sfx/pulse +t+4408.7s ♪ 音效 sfx/side_open_l +t+4408.7s ♪ 音效 sfx/side_wall_r +t+4409.4s ♪ 音效 sfx/echo_4 +t+4409.5s ♪ 音效 sfx/mat_stone +t+4412.3s ♪ 音效 sfx/step_turn +t+4417.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+4427.3s ♪ 音效 sfx/pulse +t+4427.3s 〰 震动:error(做错了:两连短震) +t+4427.3s 〰 震动:error(做错了:两连短震) +t+4427.6s ♪ 音效 sfx/echo_1 +t+4427.7s ♪ 音效 sfx/side_open_l +t+4427.7s ♪ 音效 sfx/side_open_r +t+4427.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4432.3s ♪ 音效 sfx/pulse +t+4432.3s 〰 震动:error(做错了:两连短震) +t+4432.3s 〰 震动:error(做错了:两连短震) +t+4432.6s ♪ 音效 sfx/echo_1 +t+4432.7s ♪ 音效 sfx/side_open_l +t+4432.7s ♪ 音效 sfx/side_open_r +t+4432.7s ♪ 音效 sfx/mat_stone +t+4437.3s ♪ 音效 sfx/pulse +t+4437.6s ♪ 音效 sfx/echo_1 +t+4437.7s ♪ 音效 sfx/side_open_l +t+4437.7s ♪ 音效 sfx/side_open_r +t+4437.7s ♪ 音效 sfx/mat_stone +t+4442.3s ♪ 音效 sfx/step_turn +t+4447.3s ♪ 音效 sfx/step_own +t+4447.3s 〰 震动:swipe(滑动识别) +t+4447.8s ♪ 音效 sfx/step_own +t+4447.8s 〰 震动:swipe(滑动识别) +t+4448.3s ♪ 音效 sfx/pulse +t+4448.6s ♪ 音效 sfx/echo_1 +t+4448.7s ♪ 音效 sfx/side_open_l +t+4448.7s ♪ 音效 sfx/side_wall_r +t+4448.7s ♪ 音效 sfx/mat_stone +t+4452.3s ♪ 音效 sfx/step_turn +t+4457.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+4467.3s ♪ 音效 sfx/pulse +t+4467.3s 〰 震动:error(做错了:两连短震) +t+4467.3s 〰 震动:error(做错了:两连短震) +t+4467.6s ♪ 音效 sfx/echo_1 +t+4467.7s ♪ 音效 sfx/side_wall_l +t+4467.7s ♪ 音效 sfx/side_open_r +t+4467.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4472.3s ♪ 音效 sfx/pulse +t+4472.3s 〰 震动:error(做错了:两连短震) +t+4472.3s 〰 震动:error(做错了:两连短震) +t+4472.6s ♪ 音效 sfx/echo_1 +t+4472.7s ♪ 音效 sfx/side_wall_l +t+4472.7s ♪ 音效 sfx/side_open_r +t+4472.7s ♪ 音效 sfx/mat_stone +t+4477.3s ♪ 音效 sfx/pulse +t+4477.6s ♪ 音效 sfx/echo_1 +t+4477.7s ♪ 音效 sfx/side_wall_l +t+4477.7s ♪ 音效 sfx/side_open_r +t+4477.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4482.3s ♪ 音效 sfx/pulse +t+4482.3s 〰 震动:error(做错了:两连短震) +t+4482.3s 〰 震动:error(做错了:两连短震) +t+4482.6s ♪ 音效 sfx/echo_1 +t+4482.7s ♪ 音效 sfx/side_wall_l +t+4482.7s ♪ 音效 sfx/side_open_r +t+4482.7s ♪ 音效 sfx/mat_stone +t+4487.3s ♪ 音效 sfx/pulse +t+4487.6s ♪ 音效 sfx/echo_1 +t+4487.7s ♪ 音效 sfx/side_wall_l +t+4487.7s ♪ 音效 sfx/side_open_r +t+4487.7s ♪ 音效 sfx/mat_stone +t+4492.3s ♪ 音效 sfx/step_turn +t+4497.3s ♪ 音效 sfx/step_own +t+4497.3s 〰 震动:swipe(滑动识别) +t+4497.8s ♪ 音效 sfx/step_own +t+4497.8s 〰 震动:swipe(滑动识别) +t+4498.3s ♪ 音效 sfx/pulse +t+4498.7s ♪ 音效 sfx/side_open_l +t+4498.7s ♪ 音效 sfx/side_wall_r +t+4499.4s ♪ 音效 sfx/echo_4 +t+4499.4s 〰 震动:error(做错了:两连短震) +t+4499.5s ♪ 音效 sfx/mat_stone +t+4502.3s ♪ 音效 sfx/step_turn +t+4507.3s ♪ 音效 sfx/bump +t+4517.3s ♪ 音效 sfx/pulse +t+4517.6s ♪ 音效 sfx/echo_1 +t+4517.7s ♪ 音效 sfx/side_open_l +t+4517.7s ♪ 音效 sfx/side_open_r +t+4517.7s ♪ 音效 sfx/mat_stone +t+4522.3s ♪ 音效 sfx/step_turn +t+4527.3s ♪ 音效 sfx/step_own +t+4527.3s 〰 震动:swipe(滑动识别) +t+4527.8s ♪ 音效 sfx/step_own +t+4527.8s 〰 震动:swipe(滑动识别) +t+4528.3s ♪ 音效 sfx/pulse +t+4528.6s ♪ 音效 sfx/echo_1 +t+4528.7s ♪ 音效 sfx/side_open_l +t+4528.7s ♪ 音效 sfx/side_wall_r +t+4528.7s ♪ 音效 sfx/mat_stone +t+4532.3s ♪ 音效 sfx/step_turn +t+4537.3s ♪ 音效 sfx/bump +t+4547.3s ♪ 音效 sfx/pulse +t+4547.6s ♪ 音效 sfx/echo_1 +t+4547.7s ♪ 音效 sfx/side_wall_l +t+4547.7s ♪ 音效 sfx/side_open_r +t+4547.7s ♪ 音效 sfx/mat_stone +t+4552.3s ♪ 音效 sfx/step_turn +t+4557.3s ♪ 音效 sfx/step_own +t+4557.3s 〰 震动:swipe(滑动识别) +t+4557.8s ♪ 音效 sfx/step_own +t+4557.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+4558.3s ♪ 音效 sfx/pulse +t+4558.3s 〰 震动:error(做错了:两连短震) +t+4558.3s 〰 震动:error(做错了:两连短震) +t+4558.7s ♪ 音效 sfx/side_open_l +t+4558.7s ♪ 音效 sfx/side_wall_r +t+4559.4s ♪ 音效 sfx/echo_4 +t+4559.5s ♪ 音效 sfx/mat_stone +t+4562.3s ♪ 音效 sfx/step_own +t+4562.3s 〰 震动:swipe(滑动识别) +t+4562.8s ♪ 音效 sfx/step_own +t+4562.8s 〰 震动:swipe(滑动识别) +t+4563.3s ♪ 音效 sfx/pulse +t+4563.7s ♪ 音效 sfx/side_open_l +t+4563.7s ♪ 音效 sfx/side_wall_r +t+4564.1s ♪ 音效 sfx/echo_3 +t+4564.2s ♪ 音效 sfx/mat_stone +t+4567.3s ♪ 音效 sfx/step_turn +t+4572.3s ♪ 音效 sfx/bump +t+4582.3s ♪ 音效 sfx/pulse +t+4582.6s ♪ 音效 sfx/echo_1 +t+4582.7s ♪ 音效 sfx/side_open_l +t+4582.7s ♪ 音效 sfx/side_open_r +t+4582.7s ♪ 音效 sfx/mat_stone +t+4587.3s ♪ 音效 sfx/step_turn +t+4592.3s ♪ 音效 sfx/step_own +t+4592.3s 〰 震动:swipe(滑动识别) +t+4592.8s ♪ 音效 sfx/step_own +t+4592.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+4593.3s ♪ 音效 sfx/pulse +t+4593.3s 〰 震动:error(做错了:两连短震) +t+4593.3s 〰 震动:error(做错了:两连短震) +t+4593.7s ♪ 音效 sfx/side_wall_l +t+4593.7s ♪ 音效 sfx/side_open_r +t+4594.1s ♪ 音效 sfx/echo_3 +t+4594.2s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4597.3s ♪ 音效 sfx/pulse +t+4597.3s 〰 震动:error(做错了:两连短震) +t+4597.3s 〰 震动:error(做错了:两连短震) +t+4597.7s ♪ 音效 sfx/side_wall_l +t+4597.7s ♪ 音效 sfx/side_open_r +t+4598.1s ♪ 音效 sfx/echo_3 +t+4598.2s ♪ 音效 sfx/mat_stone +t+4602.3s ♪ 音效 sfx/pulse +t+4602.7s ♪ 音效 sfx/side_wall_l +t+4602.7s ♪ 音效 sfx/side_open_r +t+4603.1s ♪ 音效 sfx/echo_3 +t+4603.2s ♪ 音效 sfx/mat_stone +t+4607.3s ♪ 音效 sfx/step_turn +t+4612.3s ♪ 音效 sfx/step_own +t+4612.3s 〰 震动:swipe(滑动识别) +t+4612.8s ♪ 音效 sfx/step_own +t+4612.8s 〰 震动:swipe(滑动识别) +t+4613.3s ♪ 音效 sfx/pulse +t+4613.6s ♪ 音效 sfx/echo_1 +t+4613.7s ♪ 音效 sfx/side_wall_l +t+4613.7s ♪ 音效 sfx/side_open_r +t+4613.7s ♪ 音效 sfx/mat_stone +t+4617.3s ♪ 音效 sfx/step_turn +t+4622.3s ♪ 音效 sfx/step_own +t+4622.3s 〰 震动:swipe(滑动识别) +t+4622.8s ♪ 音效 sfx/step_own +t+4622.8s 〰 震动:swipe(滑动识别) +t+4623.3s ♪ 音效 sfx/pulse +t+4623.7s ♪ 音效 sfx/side_wall_l +t+4623.7s ♪ 音效 sfx/side_open_r +t+4624.1s ♪ 音效 sfx/echo_3 +t+4624.1s 〰 震动:error(做错了:两连短震) +t+4624.2s ♪ 音效 sfx/mat_stone +t+4627.3s ♪ 音效 sfx/step_turn +t+4632.3s ♪ 音效 sfx/step_own +t+4632.3s 〰 震动:swipe(滑动识别) +t+4632.8s ♪ 音效 sfx/step_own +t+4632.8s 〰 震动:swipe(滑动识别) +t+4633.3s ♪ 音效 sfx/pulse +t+4633.6s ♪ 音效 sfx/echo_1 +t+4633.7s ♪ 音效 sfx/side_open_l +t+4633.7s ♪ 音效 sfx/side_open_r +t+4633.7s ♪ 音效 sfx/mat_stone +t+4637.3s ♪ 音效 sfx/step_turn +t+4642.3s ♪ 音效 sfx/step_own +t+4642.3s 〰 震动:swipe(滑动识别) +t+4642.8s ♪ 音效 sfx/step_own +t+4642.8s 〰 震动:swipe(滑动识别) +t+4643.3s ▶ 语音:「这是第一层。」 +t+4645.4s ▶ 语音:「你毫发无损。」 + +#### 【玩家动作】手痒连点三下脉冲 + +t+4645.4s ♪ 音效 sfx/pulse +t+4645.4s 〰 震动:error(做错了:两连短震) +t+4645.4s 〰 震动:error(做错了:两连短震) +t+4645.8s ♪ 音效 sfx/side_wall_l +t+4645.8s ♪ 音效 sfx/side_open_r +t+4646.2s ♪ 音效 sfx/echo_3 +t+4646.4s ♪ 音效 sfx/mat_stone +t+4647.3s ♪ 音效 sfx/pulse +t+4647.6s ▶ 语音:「口袋空空。」 +t+4647.7s ♪ 音效 sfx/side_wall_l +t+4647.7s ♪ 音效 sfx/side_open_r +t+4648.1s ♪ 音效 sfx/echo_3 +t+4648.1s 〰 震动:error(做错了:两连短震) +t+4648.2s ♪ 音效 sfx/mat_stone +t+4649.7s ▶ 语音:「你面朝西。」 +t+4649.7s ♪ 音效 sfx/pulse +t+4650.0s ♪ 音效 sfx/side_wall_l +t+4650.0s ♪ 音效 sfx/side_open_r +t+4650.5s ♪ 音效 sfx/echo_3 +t+4650.6s ♪ 音效 sfx/mat_stone +t+4651.8s ▶ 语音:「附近很安静。」 +t+4651.8s ♪ 音效 sfx/step_turn +t+4652.3s ♪ 音效 sfx/step_own +t+4652.3s 〰 震动:swipe(滑动识别) +t+4652.8s ♪ 音效 sfx/step_own +t+4652.8s 〰 震动:swipe(滑动识别) +t+4653.3s ♪ 音效 sfx/pulse +t+4653.6s ♪ 音效 sfx/echo_1 +t+4653.7s ♪ 音效 sfx/side_wall_l +t+4653.7s ♪ 音效 sfx/side_open_r +t+4653.7s ♪ 音效 sfx/mat_stone +t+4657.3s ♪ 音效 sfx/step_turn +t+4662.3s ♪ 音效 sfx/step_own +t+4662.3s 〰 震动:swipe(滑动识别) +t+4662.8s ♪ 音效 sfx/step_own +t+4662.8s 〰 震动:swipe(滑动识别) +t+4663.3s ♪ 音效 sfx/pulse +t+4663.7s ♪ 音效 sfx/side_wall_l +t+4663.7s ♪ 音效 sfx/side_open_r +t+4664.1s ♪ 音效 sfx/echo_3 +t+4664.2s ♪ 音效 sfx/mat_stone +t+4667.3s ♪ 音效 sfx/step_turn +t+4672.3s ♪ 音效 sfx/step_own +t+4672.3s 〰 震动:swipe(滑动识别) +t+4672.8s ♪ 音效 sfx/step_own +t+4672.8s 〰 震动:swipe(滑动识别) +t+4673.3s ♪ 音效 sfx/pulse +t+4673.6s ♪ 音效 sfx/echo_1 +t+4673.7s ♪ 音效 sfx/side_open_l +t+4673.7s ♪ 音效 sfx/side_open_r +t+4673.7s ♪ 音效 sfx/mat_stone +t+4677.3s ♪ 音效 sfx/step_turn +t+4682.3s ♪ 音效 sfx/step_own +t+4682.3s 〰 震动:swipe(滑动识别) +t+4682.8s ♪ 音效 sfx/step_own +t+4682.8s 〰 震动:swipe(滑动识别) +t+4683.3s ♪ 音效 sfx/pulse +t+4683.7s ♪ 音效 sfx/side_wall_l +t+4683.7s ♪ 音效 sfx/side_open_r +t+4684.1s ♪ 音效 sfx/echo_3 +t+4684.2s ♪ 音效 sfx/mat_stone +t+4687.3s ♪ 音效 sfx/step_turn +t+4692.3s ♪ 音效 sfx/step_own +t+4692.3s 〰 震动:swipe(滑动识别) +t+4692.8s ♪ 音效 sfx/step_own +t+4692.8s 〰 震动:swipe(滑动识别) +t+4693.3s ♪ 音效 sfx/pulse +t+4693.6s ♪ 音效 sfx/echo_1 +t+4693.7s ♪ 音效 sfx/side_wall_l +t+4693.7s ♪ 音效 sfx/side_open_r +t+4693.7s ♪ 音效 sfx/mat_stone +t+4697.3s ♪ 音效 sfx/step_turn +t+4702.3s ♪ 音效 sfx/step_own +t+4702.3s 〰 震动:swipe(滑动识别) +t+4702.8s ♪ 音效 sfx/step_own +t+4702.8s 〰 震动:swipe(滑动识别) +t+4703.3s ♪ 音效 sfx/pulse +t+4703.7s ♪ 音效 sfx/side_wall_l +t+4703.7s ♪ 音效 sfx/side_open_r +t+4704.1s ♪ 音效 sfx/echo_3 +t+4704.2s ♪ 音效 sfx/mat_stone +t+4707.3s ♪ 音效 sfx/step_turn +t+4712.3s ♪ 音效 sfx/step_own +t+4712.3s 〰 震动:swipe(滑动识别) +t+4712.8s ♪ 音效 sfx/step_own +t+4712.8s 〰 震动:swipe(滑动识别) +t+4713.3s ♪ 音效 sfx/pulse +t+4713.6s ♪ 音效 sfx/echo_1 +t+4713.7s ♪ 音效 sfx/side_open_l +t+4713.7s ♪ 音效 sfx/side_open_r +t+4713.7s ♪ 音效 sfx/mat_stone +t+4717.3s ♪ 音效 sfx/step_turn +t+4722.3s ♪ 音效 sfx/step_own +t+4722.3s 〰 震动:swipe(滑动识别) +t+4722.8s ♪ 音效 sfx/step_own +t+4722.8s 〰 震动:swipe(滑动识别) +t+4723.3s ♪ 音效 sfx/pulse +t+4723.7s ♪ 音效 sfx/side_wall_l +t+4723.7s ♪ 音效 sfx/side_open_r +t+4724.1s ♪ 音效 sfx/echo_3 +t+4724.2s ♪ 音效 sfx/mat_stone +t+4727.3s ♪ 音效 sfx/step_turn +t+4732.3s ♪ 音效 sfx/step_own +t+4732.3s 〰 震动:swipe(滑动识别) +t+4732.8s ♪ 音效 sfx/step_own +t+4732.8s 〰 震动:swipe(滑动识别) +t+4733.3s ♪ 音效 sfx/pulse +t+4733.6s ♪ 音效 sfx/echo_1 +t+4733.7s ♪ 音效 sfx/side_wall_l +t+4733.7s ♪ 音效 sfx/side_open_r +t+4733.7s ♪ 音效 sfx/mat_stone +t+4737.3s ♪ 音效 sfx/step_turn +t+4742.3s ♪ 音效 sfx/step_own +t+4742.3s 〰 震动:swipe(滑动识别) +t+4742.8s ♪ 音效 sfx/step_own +t+4742.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+4743.3s ♪ 音效 sfx/pulse +t+4743.3s 〰 震动:error(做错了:两连短震) +t+4743.3s 〰 震动:error(做错了:两连短震) +t+4743.7s ♪ 音效 sfx/side_wall_l +t+4743.7s ♪ 音效 sfx/side_open_r +t+4744.1s ♪ 音效 sfx/echo_3 +t+4744.2s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4747.3s ♪ 音效 sfx/pulse +t+4747.3s 〰 震动:error(做错了:两连短震) +t+4747.3s 〰 震动:error(做错了:两连短震) +t+4747.7s ♪ 音效 sfx/side_wall_l +t+4747.7s ♪ 音效 sfx/side_open_r +t+4748.1s ♪ 音效 sfx/echo_3 +t+4748.2s ♪ 音效 sfx/mat_stone +t+4752.3s ♪ 音效 sfx/pulse +t+4752.7s ♪ 音效 sfx/side_wall_l +t+4752.7s ♪ 音效 sfx/side_open_r +t+4753.1s ♪ 音效 sfx/echo_3 +t+4753.2s ♪ 音效 sfx/mat_stone +t+4757.3s ♪ 音效 sfx/step_turn +t+4762.3s ♪ 音效 sfx/step_own +t+4762.3s 〰 震动:swipe(滑动识别) +t+4762.8s ♪ 音效 sfx/step_own +t+4762.8s 〰 震动:swipe(滑动识别) +t+4763.3s ♪ 音效 sfx/pulse +t+4763.6s ♪ 音效 sfx/echo_1 +t+4763.7s ♪ 音效 sfx/side_open_l +t+4763.7s ♪ 音效 sfx/side_open_r +t+4763.7s ♪ 音效 sfx/mat_stone +t+4767.3s ♪ 音效 sfx/step_turn +t+4772.3s ♪ 音效 sfx/step_own +t+4772.3s 〰 震动:swipe(滑动识别) +t+4772.8s ♪ 音效 sfx/step_own +t+4772.8s 〰 震动:swipe(滑动识别) +t+4773.3s ♪ 音效 sfx/pulse +t+4773.7s ♪ 音效 sfx/side_wall_l +t+4773.7s ♪ 音效 sfx/side_open_r +t+4774.1s ♪ 音效 sfx/echo_3 +t+4774.2s ♪ 音效 sfx/mat_stone +t+4777.3s ♪ 音效 sfx/step_turn +t+4782.3s ♪ 音效 sfx/step_own +t+4782.3s 〰 震动:swipe(滑动识别) +t+4782.8s ♪ 音效 sfx/step_own +t+4782.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+4783.3s ♪ 音效 sfx/pulse +t+4783.3s 〰 震动:error(做错了:两连短震) +t+4783.3s 〰 震动:error(做错了:两连短震) +t+4783.6s ♪ 音效 sfx/echo_1 +t+4783.7s ♪ 音效 sfx/side_wall_l +t+4783.7s ♪ 音效 sfx/side_open_r +t+4783.7s ♪ 音效 sfx/mat_stone +t+4787.3s ♪ 音效 sfx/step_turn +t+4792.3s ♪ 音效 sfx/pulse +t+4792.7s ♪ 音效 sfx/side_wall_l +t+4792.7s ♪ 音效 sfx/side_open_r +t+4793.4s ♪ 音效 sfx/echo_4 +t+4793.5s ♪ 音效 sfx/mat_stone +t+4797.3s ♪ 音效 sfx/step_turn +t+4802.3s ♪ 音效 sfx/step_own +t+4802.3s 〰 震动:swipe(滑动识别) +t+4802.8s ♪ 音效 sfx/step_own +t+4802.8s 〰 震动:swipe(滑动识别) +t+4803.3s ♪ 音效 sfx/pulse +t+4803.6s ♪ 音效 sfx/echo_1 +t+4803.7s ♪ 音效 sfx/side_open_l +t+4803.7s ♪ 音效 sfx/side_open_r +t+4803.7s ♪ 音效 sfx/mat_stone +t+4807.3s ♪ 音效 sfx/step_turn +t+4812.3s ♪ 音效 sfx/step_own +t+4812.3s 〰 震动:swipe(滑动识别) +t+4812.8s ♪ 音效 sfx/step_own +t+4812.8s 〰 震动:swipe(滑动识别) +t+4813.3s ♪ 音效 sfx/pulse +t+4813.6s ♪ 音效 sfx/echo_1 +t+4813.7s ♪ 音效 sfx/side_open_l +t+4813.7s ♪ 音效 sfx/side_wall_r +t+4813.7s ♪ 音效 sfx/mat_stone +t+4817.3s ♪ 音效 sfx/step_turn +t+4822.3s ♪ 音效 sfx/bump +t+4832.3s ♪ 音效 sfx/pulse +t+4832.6s ♪ 音效 sfx/echo_1 +t+4832.7s ♪ 音效 sfx/side_wall_l +t+4832.7s ♪ 音效 sfx/side_open_r +t+4832.7s ♪ 音效 sfx/mat_stone +t+4837.3s ♪ 音效 sfx/step_turn +t+4842.3s ♪ 音效 sfx/step_own +t+4842.3s 〰 震动:swipe(滑动识别) +t+4842.8s ♪ 音效 sfx/step_own +t+4842.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+4843.3s ♪ 音效 sfx/pulse +t+4843.3s 〰 震动:error(做错了:两连短震) +t+4843.3s 〰 震动:error(做错了:两连短震) +t+4843.7s ♪ 音效 sfx/side_open_l +t+4843.7s ♪ 音效 sfx/side_wall_r +t+4844.4s ♪ 音效 sfx/echo_4 +t+4844.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4847.3s ♪ 音效 sfx/pulse +t+4847.3s 〰 震动:error(做错了:两连短震) +t+4847.3s 〰 震动:error(做错了:两连短震) +t+4847.7s ♪ 音效 sfx/side_open_l +t+4847.7s ♪ 音效 sfx/side_wall_r +t+4848.4s ♪ 音效 sfx/echo_4 +t+4848.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4852.3s ♪ 音效 sfx/pulse +t+4852.3s 〰 震动:error(做错了:两连短震) +t+4852.3s 〰 震动:error(做错了:两连短震) +t+4852.7s ♪ 音效 sfx/side_open_l +t+4852.7s ♪ 音效 sfx/side_wall_r +t+4853.4s ♪ 音效 sfx/echo_4 +t+4853.5s ♪ 音效 sfx/mat_stone +t+4857.3s ♪ 音效 sfx/pulse +t+4857.7s ♪ 音效 sfx/side_open_l +t+4857.7s ♪ 音效 sfx/side_wall_r +t+4858.4s ♪ 音效 sfx/echo_4 +t+4858.5s ♪ 音效 sfx/mat_stone +t+4862.3s ♪ 音效 sfx/step_turn +t+4867.3s ♪ 音效 sfx/bump +t+4877.3s ♪ 音效 sfx/pulse +t+4877.6s ♪ 音效 sfx/echo_1 +t+4877.7s ♪ 音效 sfx/side_open_l +t+4877.7s ♪ 音效 sfx/side_open_r +t+4877.7s ♪ 音效 sfx/mat_stone +t+4882.3s ♪ 音效 sfx/step_turn +t+4887.3s ♪ 音效 sfx/step_own +t+4887.3s 〰 震动:swipe(滑动识别) +t+4887.8s ♪ 音效 sfx/step_own +t+4887.8s 〰 震动:swipe(滑动识别) +t+4888.3s ♪ 音效 sfx/pulse +t+4888.6s ♪ 音效 sfx/echo_1 +t+4888.7s ♪ 音效 sfx/side_open_l +t+4888.7s ♪ 音效 sfx/side_wall_r +t+4888.7s ♪ 音效 sfx/mat_stone +t+4892.3s ♪ 音效 sfx/step_turn +t+4897.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+4907.3s ♪ 音效 sfx/pulse +t+4907.3s 〰 震动:error(做错了:两连短震) +t+4907.3s 〰 震动:error(做错了:两连短震) +t+4907.6s ♪ 音效 sfx/echo_1 +t+4907.7s ♪ 音效 sfx/side_wall_l +t+4907.7s ♪ 音效 sfx/side_open_r +t+4907.7s ♪ 音效 sfx/mat_stone +t+4912.3s ♪ 音效 sfx/pulse +t+4912.6s ♪ 音效 sfx/echo_1 +t+4912.7s ♪ 音效 sfx/side_wall_l +t+4912.7s ♪ 音效 sfx/side_open_r +t+4912.7s ♪ 音效 sfx/mat_stone +t+4917.3s ♪ 音效 sfx/step_turn +t+4922.3s ♪ 音效 sfx/step_own +t+4922.3s 〰 震动:swipe(滑动识别) +t+4922.8s ♪ 音效 sfx/step_own +t+4922.8s 〰 震动:swipe(滑动识别) +t+4923.3s ♪ 音效 sfx/step_own +t+4923.3s 〰 震动:swipe(滑动识别) +t+4923.8s ♪ 音效 sfx/step_own +t+4923.8s 〰 震动:swipe(滑动识别) +t+4924.3s ♪ 音效 sfx/pulse +t+4924.7s ♪ 音效 sfx/side_open_l +t+4924.7s ♪ 音效 sfx/side_wall_r +t+4925.1s ♪ 音效 sfx/echo_3 +t+4925.2s ♪ 音效 sfx/mat_stone +t+4927.3s ♪ 音效 sfx/step_turn +t+4932.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+4942.3s ♪ 音效 sfx/pulse +t+4942.3s 〰 震动:error(做错了:两连短震) +t+4942.3s 〰 震动:error(做错了:两连短震) +t+4942.6s ♪ 音效 sfx/echo_1 +t+4942.7s ♪ 音效 sfx/side_open_l +t+4942.7s ♪ 音效 sfx/side_open_r +t+4942.7s ♪ 音效 sfx/mat_stone +t+4947.3s ♪ 音效 sfx/pulse +t+4947.6s ♪ 音效 sfx/echo_1 +t+4947.7s ♪ 音效 sfx/side_open_l +t+4947.7s ♪ 音效 sfx/side_open_r +t+4947.7s ♪ 音效 sfx/mat_stone +t+4952.3s ♪ 音效 sfx/pulse +t+4952.6s ♪ 音效 sfx/echo_1 +t+4952.7s ♪ 音效 sfx/side_open_l +t+4952.7s ♪ 音效 sfx/side_open_r +t+4952.7s ♪ 音效 sfx/mat_stone +t+4957.3s ♪ 音效 sfx/step_turn +t+4962.3s ♪ 音效 sfx/step_own +t+4962.3s 〰 震动:swipe(滑动识别) +t+4962.8s ♪ 音效 sfx/step_own +t+4962.8s 〰 震动:swipe(滑动识别) +t+4963.3s ♪ 音效 sfx/pulse +t+4963.7s ♪ 音效 sfx/side_wall_l +t+4963.7s ♪ 音效 sfx/side_open_r +t+4964.1s ♪ 音效 sfx/echo_3 +t+4964.2s ♪ 音效 sfx/mat_stone +t+4967.3s ♪ 音效 sfx/step_turn +t+4972.3s ♪ 音效 sfx/step_own +t+4972.3s 〰 震动:swipe(滑动识别) +t+4972.8s ♪ 音效 sfx/step_own +t+4972.8s 〰 震动:swipe(滑动识别) +t+4973.3s ♪ 音效 sfx/pulse +t+4973.6s ♪ 音效 sfx/echo_1 +t+4973.7s ♪ 音效 sfx/side_wall_l +t+4973.7s ♪ 音效 sfx/side_open_r +t+4973.7s ♪ 音效 sfx/mat_stone +t+4977.3s ♪ 音效 sfx/step_turn +t+4982.3s ♪ 音效 sfx/step_own +t+4982.3s 〰 震动:swipe(滑动识别) +t+4982.8s ♪ 音效 sfx/step_own +t+4982.8s 〰 震动:swipe(滑动识别) +t+4983.3s ♪ 音效 sfx/pulse +t+4983.7s ♪ 音效 sfx/side_wall_l +t+4983.7s ♪ 音效 sfx/side_open_r +t+4984.1s ♪ 音效 sfx/echo_3 +t+4984.2s ♪ 音效 sfx/mat_stone +t+4987.3s ♪ 音效 sfx/step_turn +t+4992.3s ♪ 音效 sfx/step_own +t+4992.3s 〰 震动:swipe(滑动识别) +t+4992.8s ♪ 音效 sfx/step_own +t+4992.8s 〰 震动:swipe(滑动识别) +t+4993.3s ▶ 语音:「这是第一层。」 +t+4995.4s ▶ 语音:「你毫发无损。」 + +#### 【玩家动作】手痒连点三下脉冲 + +t+4995.4s ♪ 音效 sfx/pulse +t+4995.4s 〰 震动:error(做错了:两连短震) +t+4995.4s 〰 震动:error(做错了:两连短震) +t+4995.7s ♪ 音效 sfx/echo_1 +t+4995.8s ♪ 音效 sfx/side_open_l +t+4995.8s ♪ 音效 sfx/side_open_r +t+4995.8s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+4997.3s ♪ 音效 sfx/pulse +t+4997.3s 〰 震动:error(做错了:两连短震) +t+4997.3s 〰 震动:error(做错了:两连短震) +t+4997.6s ▶ 语音:「口袋空空。」 +t+4997.6s ♪ 音效 sfx/echo_1 +t+4997.7s ♪ 音效 sfx/side_open_l +t+4997.7s ♪ 音效 sfx/side_open_r +t+4997.7s ♪ 音效 sfx/mat_stone +t+4999.7s ▶ 语音:「你面朝南。」 +t+4999.7s ♪ 音效 sfx/pulse +t+4999.9s ♪ 音效 sfx/echo_1 +t+5000.0s ♪ 音效 sfx/side_open_l +t+5000.0s ♪ 音效 sfx/side_open_r +t+5000.1s ♪ 音效 sfx/mat_stone +t+5001.8s ▶ 语音:「附近很安静。」 +t+5001.8s ♪ 音效 sfx/step_turn +t+5002.3s ♪ 音效 sfx/step_own +t+5002.3s 〰 震动:swipe(滑动识别) +t+5002.8s ♪ 音效 sfx/step_own +t+5002.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5003.3s ♪ 音效 sfx/pulse +t+5003.3s 〰 震动:error(做错了:两连短震) +t+5003.3s 〰 震动:error(做错了:两连短震) +t+5003.7s ♪ 音效 sfx/side_wall_l +t+5003.7s ♪ 音效 sfx/side_open_r +t+5004.1s ♪ 音效 sfx/echo_3 +t+5004.2s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5007.3s ♪ 音效 sfx/pulse +t+5007.3s 〰 震动:error(做错了:两连短震) +t+5007.3s 〰 震动:error(做错了:两连短震) +t+5007.7s ♪ 音效 sfx/side_wall_l +t+5007.7s ♪ 音效 sfx/side_open_r +t+5008.1s ♪ 音效 sfx/echo_3 +t+5008.2s ♪ 音效 sfx/mat_stone +t+5012.3s ♪ 音效 sfx/pulse +t+5012.7s ♪ 音效 sfx/side_wall_l +t+5012.7s ♪ 音效 sfx/side_open_r +t+5013.1s ♪ 音效 sfx/echo_3 +t+5013.2s ♪ 音效 sfx/mat_stone +t+5017.3s ♪ 音效 sfx/step_turn +t+5022.3s ♪ 音效 sfx/step_own +t+5022.3s 〰 震动:swipe(滑动识别) +t+5022.8s ♪ 音效 sfx/step_own +t+5022.8s 〰 震动:swipe(滑动识别) +t+5023.3s ♪ 音效 sfx/pulse +t+5023.6s ♪ 音效 sfx/echo_1 +t+5023.7s ♪ 音效 sfx/side_wall_l +t+5023.7s ♪ 音效 sfx/side_open_r +t+5023.7s ♪ 音效 sfx/mat_stone +t+5027.3s ♪ 音效 sfx/step_turn +t+5032.3s ♪ 音效 sfx/step_own +t+5032.3s 〰 震动:swipe(滑动识别) +t+5032.8s ♪ 音效 sfx/step_own +t+5032.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5033.3s ♪ 音效 sfx/pulse +t+5033.3s 〰 震动:error(做错了:两连短震) +t+5033.3s 〰 震动:error(做错了:两连短震) +t+5033.7s ♪ 音效 sfx/side_wall_l +t+5033.7s ♪ 音效 sfx/side_open_r +t+5034.1s ♪ 音效 sfx/echo_3 +t+5034.2s ♪ 音效 sfx/mat_stone +t+5037.3s ♪ 音效 sfx/pulse +t+5037.7s ♪ 音效 sfx/side_wall_l +t+5037.7s ♪ 音效 sfx/side_open_r +t+5038.1s ♪ 音效 sfx/echo_3 +t+5038.2s ♪ 音效 sfx/mat_stone +t+5042.3s ♪ 音效 sfx/step_turn +t+5047.3s ♪ 音效 sfx/step_own +t+5047.3s 〰 震动:swipe(滑动识别) +t+5047.8s ♪ 音效 sfx/step_own +t+5047.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5048.3s ♪ 音效 sfx/pulse +t+5048.3s 〰 震动:error(做错了:两连短震) +t+5048.3s 〰 震动:error(做错了:两连短震) +t+5048.6s ♪ 音效 sfx/echo_1 +t+5048.7s ♪ 音效 sfx/side_open_l +t+5048.7s ♪ 音效 sfx/side_open_r +t+5048.7s ♪ 音效 sfx/mat_stone +t+5052.3s ♪ 音效 sfx/pulse +t+5052.6s ♪ 音效 sfx/echo_1 +t+5052.7s ♪ 音效 sfx/side_open_l +t+5052.7s ♪ 音效 sfx/side_open_r +t+5052.7s ♪ 音效 sfx/mat_stone +t+5057.3s ♪ 音效 sfx/step_turn +t+5062.3s ♪ 音效 sfx/step_own +t+5062.3s 〰 震动:swipe(滑动识别) +t+5062.8s ♪ 音效 sfx/step_own +t+5062.8s 〰 震动:swipe(滑动识别) +t+5063.3s ♪ 音效 sfx/pulse +t+5063.7s ♪ 音效 sfx/side_wall_l +t+5063.7s ♪ 音效 sfx/side_open_r +t+5064.1s ♪ 音效 sfx/echo_3 +t+5064.1s 〰 震动:error(做错了:两连短震) +t+5064.2s ♪ 音效 sfx/mat_stone +t+5067.3s ♪ 音效 sfx/step_turn +t+5072.3s ♪ 音效 sfx/step_own +t+5072.3s 〰 震动:swipe(滑动识别) +t+5072.8s ♪ 音效 sfx/step_own +t+5072.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5073.3s ♪ 音效 sfx/pulse +t+5073.3s 〰 震动:error(做错了:两连短震) +t+5073.3s 〰 震动:error(做错了:两连短震) +t+5073.6s ♪ 音效 sfx/echo_1 +t+5073.7s ♪ 音效 sfx/side_wall_l +t+5073.7s ♪ 音效 sfx/side_open_r +t+5073.7s ♪ 音效 sfx/mat_stone +t+5077.3s ♪ 音效 sfx/pulse +t+5077.6s ♪ 音效 sfx/echo_1 +t+5077.7s ♪ 音效 sfx/side_wall_l +t+5077.7s ♪ 音效 sfx/side_open_r +t+5077.7s ♪ 音效 sfx/mat_stone +t+5082.3s ♪ 音效 sfx/step_turn +t+5087.3s ♪ 音效 sfx/step_own +t+5087.3s 〰 震动:swipe(滑动识别) +t+5087.8s ♪ 音效 sfx/step_own +t+5087.8s 〰 震动:swipe(滑动识别) +t+5088.3s ♪ 音效 sfx/pulse +t+5088.7s ♪ 音效 sfx/side_wall_l +t+5088.7s ♪ 音效 sfx/side_open_r +t+5089.1s ♪ 音效 sfx/echo_3 +t+5089.2s ♪ 音效 sfx/mat_stone +t+5092.3s ♪ 音效 sfx/step_turn +t+5097.3s ♪ 音效 sfx/step_own +t+5097.3s 〰 震动:swipe(滑动识别) +t+5097.8s ♪ 音效 sfx/step_own +t+5097.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5098.3s ♪ 音效 sfx/pulse +t+5098.3s 〰 震动:error(做错了:两连短震) +t+5098.3s 〰 震动:error(做错了:两连短震) +t+5098.6s ♪ 音效 sfx/echo_1 +t+5098.7s ♪ 音效 sfx/side_open_l +t+5098.7s ♪ 音效 sfx/side_open_r +t+5098.7s ♪ 音效 sfx/mat_stone +t+5102.3s ♪ 音效 sfx/pulse +t+5102.6s ♪ 音效 sfx/echo_1 +t+5102.7s ♪ 音效 sfx/side_open_l +t+5102.7s ♪ 音效 sfx/side_open_r +t+5102.7s ♪ 音效 sfx/mat_stone +t+5107.3s ♪ 音效 sfx/step_turn +t+5112.3s ♪ 音效 sfx/step_own +t+5112.3s 〰 震动:swipe(滑动识别) +t+5112.8s ♪ 音效 sfx/step_own +t+5112.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5113.3s ♪ 音效 sfx/pulse +t+5113.3s 〰 震动:error(做错了:两连短震) +t+5113.3s 〰 震动:error(做错了:两连短震) +t+5113.7s ♪ 音效 sfx/side_wall_l +t+5113.7s ♪ 音效 sfx/side_open_r +t+5114.1s ♪ 音效 sfx/echo_3 +t+5114.2s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5117.3s ♪ 音效 sfx/pulse +t+5117.3s 〰 震动:error(做错了:两连短震) +t+5117.3s 〰 震动:error(做错了:两连短震) +t+5117.7s ♪ 音效 sfx/side_wall_l +t+5117.7s ♪ 音效 sfx/side_open_r +t+5118.1s ♪ 音效 sfx/echo_3 +t+5118.2s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5122.3s ♪ 音效 sfx/pulse +t+5122.3s 〰 震动:error(做错了:两连短震) +t+5122.3s 〰 震动:error(做错了:两连短震) +t+5122.7s ♪ 音效 sfx/side_wall_l +t+5122.7s ♪ 音效 sfx/side_open_r +t+5123.1s ♪ 音效 sfx/echo_3 +t+5123.2s ♪ 音效 sfx/mat_stone +t+5127.3s ♪ 音效 sfx/pulse +t+5127.7s ♪ 音效 sfx/side_wall_l +t+5127.7s ♪ 音效 sfx/side_open_r +t+5128.1s ♪ 音效 sfx/echo_3 +t+5128.2s ♪ 音效 sfx/mat_stone +t+5132.3s ♪ 音效 sfx/step_turn +t+5137.3s ♪ 音效 sfx/step_own +t+5137.3s 〰 震动:swipe(滑动识别) +t+5137.8s ♪ 音效 sfx/step_own +t+5137.8s 〰 震动:swipe(滑动识别) +t+5138.3s ♪ 音效 sfx/pulse +t+5138.6s ♪ 音效 sfx/echo_1 +t+5138.7s ♪ 音效 sfx/side_wall_l +t+5138.7s ♪ 音效 sfx/side_open_r +t+5138.7s ♪ 音效 sfx/mat_stone +t+5142.3s ♪ 音效 sfx/step_turn +t+5147.3s ♪ 音效 sfx/step_own +t+5147.3s 〰 震动:swipe(滑动识别) +t+5147.8s ♪ 音效 sfx/step_own +t+5147.8s 〰 震动:swipe(滑动识别) +t+5148.3s ♪ 音效 sfx/pulse +t+5148.7s ♪ 音效 sfx/side_wall_l +t+5148.7s ♪ 音效 sfx/side_open_r +t+5149.1s ♪ 音效 sfx/echo_3 +t+5149.2s ♪ 音效 sfx/mat_stone +t+5152.3s ♪ 音效 sfx/step_turn +t+5157.3s ♪ 音效 sfx/step_own +t+5157.3s 〰 震动:swipe(滑动识别) +t+5157.8s ♪ 音效 sfx/step_own +t+5157.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5158.3s ♪ 音效 sfx/pulse +t+5158.3s 〰 震动:error(做错了:两连短震) +t+5158.3s 〰 震动:error(做错了:两连短震) +t+5158.6s ♪ 音效 sfx/echo_1 +t+5158.7s ♪ 音效 sfx/side_open_l +t+5158.7s ♪ 音效 sfx/side_open_r +t+5158.7s ♪ 音效 sfx/mat_stone +t+5162.3s ♪ 音效 sfx/pulse +t+5162.6s ♪ 音效 sfx/echo_1 +t+5162.7s ♪ 音效 sfx/side_open_l +t+5162.7s ♪ 音效 sfx/side_open_r +t+5162.7s ♪ 音效 sfx/mat_stone +t+5167.3s ♪ 音效 sfx/step_turn +t+5172.3s ♪ 音效 sfx/step_own +t+5172.3s 〰 震动:swipe(滑动识别) +t+5172.8s ♪ 音效 sfx/step_own +t+5172.8s 〰 震动:swipe(滑动识别) +t+5173.3s ♪ 音效 sfx/pulse +t+5173.7s ♪ 音效 sfx/side_wall_l +t+5173.7s ♪ 音效 sfx/side_open_r +t+5174.1s ♪ 音效 sfx/echo_3 +t+5174.2s ♪ 音效 sfx/mat_stone +t+5177.3s ♪ 音效 sfx/step_turn +t+5182.3s ♪ 音效 sfx/step_own +t+5182.3s 〰 震动:swipe(滑动识别) +t+5182.8s ♪ 音效 sfx/step_own +t+5182.8s 〰 震动:swipe(滑动识别) +t+5183.3s ♪ 音效 sfx/pulse +t+5183.6s ♪ 音效 sfx/echo_1 +t+5183.7s ♪ 音效 sfx/side_wall_l +t+5183.7s ♪ 音效 sfx/side_open_r +t+5183.7s ♪ 音效 sfx/mat_stone +t+5187.3s ♪ 音效 sfx/step_turn +t+5192.3s ♪ 音效 sfx/step_own +t+5192.3s 〰 震动:swipe(滑动识别) +t+5192.8s ♪ 音效 sfx/step_own +t+5192.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5193.3s ♪ 音效 sfx/pulse +t+5193.3s 〰 震动:error(做错了:两连短震) +t+5193.3s 〰 震动:error(做错了:两连短震) +t+5193.7s ♪ 音效 sfx/side_wall_l +t+5193.7s ♪ 音效 sfx/side_open_r +t+5194.1s ♪ 音效 sfx/echo_3 +t+5194.2s ♪ 音效 sfx/mat_stone +t+5197.3s ♪ 音效 sfx/step_own +t+5197.3s 〰 震动:swipe(滑动识别) +t+5197.8s ♪ 音效 sfx/step_own +t+5197.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5198.3s ♪ 音效 sfx/pulse +t+5198.3s 〰 震动:error(做错了:两连短震) +t+5198.3s 〰 震动:error(做错了:两连短震) +t+5198.6s ♪ 音效 sfx/echo_1 +t+5198.7s ♪ 音效 sfx/side_open_l +t+5198.7s ♪ 音效 sfx/side_open_r +t+5198.7s ♪ 音效 sfx/mat_stone +t+5202.3s ♪ 音效 sfx/pulse +t+5202.6s ♪ 音效 sfx/echo_1 +t+5202.7s ♪ 音效 sfx/side_open_l +t+5202.7s ♪ 音效 sfx/side_open_r +t+5202.7s ♪ 音效 sfx/mat_stone +t+5207.3s ♪ 音效 sfx/step_turn +t+5212.3s ♪ 音效 sfx/step_own +t+5212.3s 〰 震动:swipe(滑动识别) +t+5212.8s ♪ 音效 sfx/step_own +t+5212.8s 〰 震动:swipe(滑动识别) +t+5213.3s ♪ 音效 sfx/pulse +t+5213.6s ♪ 音效 sfx/echo_1 +t+5213.7s ♪ 音效 sfx/side_wall_l +t+5213.7s ♪ 音效 sfx/side_open_r +t+5213.7s ♪ 音效 sfx/mat_stone +t+5217.3s ♪ 音效 sfx/step_turn +t+5222.3s ♪ 音效 sfx/step_own +t+5222.3s 〰 震动:swipe(滑动识别) +t+5222.8s ♪ 音效 sfx/step_own +t+5222.8s 〰 震动:swipe(滑动识别) +t+5223.3s ♪ 音效 sfx/pulse +t+5223.7s ♪ 音效 sfx/side_wall_l +t+5223.7s ♪ 音效 sfx/side_open_r +t+5224.4s ♪ 音效 sfx/echo_4 +t+5224.5s ♪ 音效 sfx/mat_stone +t+5227.3s ♪ 音效 sfx/step_turn +t+5232.3s ♪ 音效 sfx/step_own +t+5232.3s 〰 震动:swipe(滑动识别) +t+5232.8s ♪ 音效 sfx/step_own +t+5232.8s 〰 震动:swipe(滑动识别) +t+5233.3s ♪ 音效 sfx/pulse +t+5233.6s ♪ 音效 sfx/echo_1 +t+5233.7s ♪ 音效 sfx/side_open_l +t+5233.7s ♪ 音效 sfx/side_open_r +t+5233.7s ♪ 音效 sfx/mat_stone +t+5237.3s ♪ 音效 sfx/step_turn +t+5242.3s ♪ 音效 sfx/step_own +t+5242.3s 〰 震动:swipe(滑动识别) +t+5242.8s ♪ 音效 sfx/step_own +t+5242.8s 〰 震动:swipe(滑动识别) +t+5243.3s ♪ 音效 sfx/pulse +t+5243.6s ♪ 音效 sfx/echo_1 +t+5243.7s ♪ 音效 sfx/side_open_l +t+5243.7s ♪ 音效 sfx/side_open_r +t+5243.7s ♪ 音效 sfx/mat_stone +t+5247.3s ♪ 音效 sfx/step_turn +t+5252.3s ♪ 音效 sfx/step_own +t+5252.3s 〰 震动:swipe(滑动识别) +t+5252.8s ♪ 音效 sfx/step_own +t+5252.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5253.3s ♪ 音效 sfx/pulse +t+5253.3s 〰 震动:error(做错了:两连短震) +t+5253.3s 〰 震动:error(做错了:两连短震) +t+5253.6s ♪ 音效 sfx/echo_1 +t+5253.7s ♪ 音效 sfx/side_wall_l +t+5253.7s ♪ 音效 sfx/side_open_r +t+5253.7s ♪ 音效 sfx/mat_stone +t+5257.3s ♪ 音效 sfx/pulse +t+5257.6s ♪ 音效 sfx/echo_1 +t+5257.7s ♪ 音效 sfx/side_wall_l +t+5257.7s ♪ 音效 sfx/side_open_r +t+5257.7s ♪ 音效 sfx/mat_stone +t+5262.3s ♪ 音效 sfx/pulse +t+5262.6s ♪ 音效 sfx/echo_1 +t+5262.7s ♪ 音效 sfx/side_wall_l +t+5262.7s ♪ 音效 sfx/side_open_r +t+5262.7s ♪ 音效 sfx/mat_stone +t+5267.3s ♪ 音效 sfx/step_turn +t+5272.3s ♪ 音效 sfx/step_own +t+5272.3s 〰 震动:swipe(滑动识别) +t+5272.8s ♪ 音效 sfx/step_own +t+5272.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5273.3s ♪ 音效 sfx/pulse +t+5273.3s 〰 震动:error(做错了:两连短震) +t+5273.3s 〰 震动:error(做错了:两连短震) +t+5273.7s ♪ 音效 sfx/side_wall_l +t+5273.7s ♪ 音效 sfx/side_open_r +t+5274.4s ♪ 音效 sfx/echo_4 +t+5274.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5277.3s ♪ 音效 sfx/pulse +t+5277.3s 〰 震动:error(做错了:两连短震) +t+5277.3s 〰 震动:error(做错了:两连短震) +t+5277.7s ♪ 音效 sfx/side_wall_l +t+5277.7s ♪ 音效 sfx/side_open_r +t+5278.4s ♪ 音效 sfx/echo_4 +t+5278.5s ♪ 音效 sfx/mat_stone +t+5282.3s ♪ 音效 sfx/step_turn +t+5287.3s ♪ 音效 sfx/pulse +t+5287.6s ♪ 音效 sfx/echo_1 +t+5287.7s ♪ 音效 sfx/side_open_l +t+5287.7s ♪ 音效 sfx/side_open_r +t+5287.7s ♪ 音效 sfx/mat_stone +t+5292.3s ♪ 音效 sfx/step_turn +t+5297.3s ♪ 音效 sfx/step_own +t+5297.3s 〰 震动:swipe(滑动识别) +t+5297.8s ♪ 音效 sfx/step_own +t+5297.8s 〰 震动:swipe(滑动识别) +t+5298.3s ♪ 音效 sfx/pulse +t+5298.7s ♪ 音效 sfx/side_wall_l +t+5298.7s ♪ 音效 sfx/side_open_r +t+5299.1s ♪ 音效 sfx/echo_3 +t+5299.2s ♪ 音效 sfx/mat_stone +t+5302.3s ♪ 音效 sfx/step_turn +t+5307.3s ♪ 音效 sfx/step_own +t+5307.3s 〰 震动:swipe(滑动识别) +t+5307.8s ♪ 音效 sfx/step_own +t+5307.8s 〰 震动:swipe(滑动识别) +t+5308.3s ♪ 音效 sfx/pulse +t+5308.6s ♪ 音效 sfx/echo_1 +t+5308.7s ♪ 音效 sfx/side_wall_l +t+5308.7s ♪ 音效 sfx/side_open_r +t+5308.7s ♪ 音效 sfx/mat_stone +t+5312.3s ♪ 音效 sfx/step_turn +t+5317.3s ♪ 音效 sfx/step_own +t+5317.3s 〰 震动:swipe(滑动识别) +t+5317.8s ♪ 音效 sfx/step_own +t+5317.8s 〰 震动:swipe(滑动识别) +t+5318.3s ♪ 音效 sfx/pulse +t+5318.7s ♪ 音效 sfx/side_wall_l +t+5318.7s ♪ 音效 sfx/side_open_r +t+5319.1s ♪ 音效 sfx/echo_3 +t+5319.2s ♪ 音效 sfx/mat_stone +t+5322.3s ♪ 音效 sfx/step_turn +t+5327.3s ♪ 音效 sfx/step_own +t+5327.3s 〰 震动:swipe(滑动识别) +t+5327.8s ♪ 音效 sfx/step_own +t+5327.8s 〰 震动:swipe(滑动识别) +t+5328.3s ♪ 音效 sfx/pulse +t+5328.6s ♪ 音效 sfx/echo_1 +t+5328.7s ♪ 音效 sfx/side_open_l +t+5328.7s ♪ 音效 sfx/side_open_r +t+5328.7s ♪ 音效 sfx/mat_stone +t+5332.3s ♪ 音效 sfx/step_turn +t+5337.3s ♪ 音效 sfx/step_own +t+5337.3s 〰 震动:swipe(滑动识别) +t+5337.8s ♪ 音效 sfx/step_own +t+5337.8s 〰 震动:swipe(滑动识别) +t+5338.3s ♪ 音效 sfx/pulse +t+5338.7s ♪ 音效 sfx/side_wall_l +t+5338.7s ♪ 音效 sfx/side_open_r +t+5339.1s ♪ 音效 sfx/echo_3 +t+5339.2s ♪ 音效 sfx/mat_stone +t+5342.3s ♪ 音效 sfx/step_turn +t+5347.3s ♪ 音效 sfx/step_own +t+5347.3s 〰 震动:swipe(滑动识别) +t+5347.8s ♪ 音效 sfx/step_own +t+5347.8s 〰 震动:swipe(滑动识别) +t+5348.3s ♪ 音效 sfx/pulse +t+5348.6s ♪ 音效 sfx/echo_1 +t+5348.7s ♪ 音效 sfx/side_wall_l +t+5348.7s ♪ 音效 sfx/side_open_r +t+5348.7s ♪ 音效 sfx/mat_stone +t+5352.3s ♪ 音效 sfx/step_turn +t+5357.3s ♪ 音效 sfx/step_own +t+5357.3s 〰 震动:swipe(滑动识别) +t+5357.8s ♪ 音效 sfx/step_own +t+5357.8s 〰 震动:swipe(滑动识别) +t+5358.3s ♪ 音效 sfx/pulse +t+5358.7s ♪ 音效 sfx/side_wall_l +t+5358.7s ♪ 音效 sfx/side_open_r +t+5359.1s ♪ 音效 sfx/echo_3 +t+5359.2s ♪ 音效 sfx/mat_stone +t+5362.3s ♪ 音效 sfx/step_turn +t+5367.3s ♪ 音效 sfx/step_own +t+5367.3s 〰 震动:swipe(滑动识别) +t+5367.8s ♪ 音效 sfx/step_own +t+5367.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5368.3s ♪ 音效 sfx/pulse +t+5368.3s 〰 震动:error(做错了:两连短震) +t+5368.3s 〰 震动:error(做错了:两连短震) +t+5368.6s ♪ 音效 sfx/echo_1 +t+5368.7s ♪ 音效 sfx/side_open_l +t+5368.7s ♪ 音效 sfx/side_open_r +t+5368.7s ♪ 音效 sfx/mat_stone +t+5372.3s ♪ 音效 sfx/pulse +t+5372.6s ♪ 音效 sfx/echo_1 +t+5372.7s ♪ 音效 sfx/side_open_l +t+5372.7s ♪ 音效 sfx/side_open_r +t+5372.7s ♪ 音效 sfx/mat_stone +t+5377.3s ♪ 音效 sfx/step_turn +t+5382.3s ♪ 音效 sfx/step_own +t+5382.3s 〰 震动:swipe(滑动识别) +t+5382.8s ♪ 音效 sfx/step_own +t+5382.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5383.3s ♪ 音效 sfx/pulse +t+5383.3s 〰 震动:error(做错了:两连短震) +t+5383.3s 〰 震动:error(做错了:两连短震) +t+5383.7s ♪ 音效 sfx/side_wall_l +t+5383.7s ♪ 音效 sfx/side_open_r +t+5384.1s ♪ 音效 sfx/echo_3 +t+5384.2s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5387.3s ♪ 音效 sfx/pulse +t+5387.3s 〰 震动:error(做错了:两连短震) +t+5387.3s 〰 震动:error(做错了:两连短震) +t+5387.7s ♪ 音效 sfx/side_wall_l +t+5387.7s ♪ 音效 sfx/side_open_r +t+5388.1s ♪ 音效 sfx/echo_3 +t+5388.2s ♪ 音效 sfx/mat_stone +t+5392.3s ♪ 音效 sfx/pulse +t+5392.7s ♪ 音效 sfx/side_wall_l +t+5392.7s ♪ 音效 sfx/side_open_r +t+5393.1s ♪ 音效 sfx/echo_3 +t+5393.1s 〰 震动:error(做错了:两连短震) +t+5393.2s ♪ 音效 sfx/mat_stone +t+5397.3s ♪ 音效 sfx/step_turn +t+5402.3s ♪ 音效 sfx/step_own +t+5402.3s 〰 震动:swipe(滑动识别) +t+5402.8s ♪ 音效 sfx/step_own +t+5402.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5403.3s ♪ 音效 sfx/pulse +t+5403.3s 〰 震动:error(做错了:两连短震) +t+5403.3s 〰 震动:error(做错了:两连短震) +t+5403.6s ♪ 音效 sfx/echo_1 +t+5403.7s ♪ 音效 sfx/side_wall_l +t+5403.7s ♪ 音效 sfx/side_open_r +t+5403.7s ♪ 音效 sfx/mat_stone +t+5407.3s ♪ 音效 sfx/step_turn +t+5412.3s ♪ 音效 sfx/pulse +t+5412.7s ♪ 音效 sfx/side_wall_l +t+5412.7s ♪ 音效 sfx/side_open_r +t+5413.4s ♪ 音效 sfx/echo_4 +t+5413.5s ♪ 音效 sfx/mat_stone +t+5417.3s ♪ 音效 sfx/step_turn +t+5422.3s ♪ 音效 sfx/step_own +t+5422.3s 〰 震动:swipe(滑动识别) +t+5422.8s ♪ 音效 sfx/step_own +t+5422.8s 〰 震动:swipe(滑动识别) +t+5423.3s ♪ 音效 sfx/pulse +t+5423.6s ♪ 音效 sfx/echo_1 +t+5423.7s ♪ 音效 sfx/side_open_l +t+5423.7s ♪ 音效 sfx/side_open_r +t+5423.7s ♪ 音效 sfx/mat_stone +t+5427.3s ♪ 音效 sfx/step_turn +t+5432.3s ♪ 音效 sfx/step_own +t+5432.3s 〰 震动:swipe(滑动识别) +t+5432.8s ♪ 音效 sfx/step_own +t+5432.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5433.3s ♪ 音效 sfx/pulse +t+5433.3s 〰 震动:error(做错了:两连短震) +t+5433.3s 〰 震动:error(做错了:两连短震) +t+5433.6s ♪ 音效 sfx/echo_1 +t+5433.7s ♪ 音效 sfx/side_open_l +t+5433.7s ♪ 音效 sfx/side_wall_r +t+5433.7s ♪ 音效 sfx/mat_stone +t+5437.3s ♪ 音效 sfx/pulse +t+5437.6s ♪ 音效 sfx/echo_1 +t+5437.7s ♪ 音效 sfx/side_open_l +t+5437.7s ♪ 音效 sfx/side_wall_r +t+5437.7s ♪ 音效 sfx/mat_stone +t+5442.3s ♪ 音效 sfx/step_turn +t+5447.3s ♪ 音效 sfx/bump +t+5457.3s ♪ 音效 sfx/pulse +t+5457.6s ♪ 音效 sfx/echo_1 +t+5457.7s ♪ 音效 sfx/side_wall_l +t+5457.7s ♪ 音效 sfx/side_open_r +t+5457.7s ♪ 音效 sfx/mat_stone +t+5462.3s ♪ 音效 sfx/step_turn +t+5467.3s ♪ 音效 sfx/step_own +t+5467.3s 〰 震动:swipe(滑动识别) +t+5467.8s ♪ 音效 sfx/step_own +t+5467.8s 〰 震动:swipe(滑动识别) +t+5468.3s ♪ 音效 sfx/pulse +t+5468.7s ♪ 音效 sfx/side_open_l +t+5468.7s ♪ 音效 sfx/side_wall_r +t+5469.4s ♪ 音效 sfx/echo_4 +t+5469.5s ♪ 音效 sfx/mat_stone +t+5472.3s ♪ 音效 sfx/step_turn +t+5477.3s ♪ 音效 sfx/bump +t+5487.3s ♪ 音效 sfx/pulse +t+5487.6s ♪ 音效 sfx/echo_1 +t+5487.7s ♪ 音效 sfx/side_open_l +t+5487.7s ♪ 音效 sfx/side_open_r +t+5487.7s ♪ 音效 sfx/mat_stone +t+5492.3s ♪ 音效 sfx/step_turn +t+5497.3s ♪ 音效 sfx/step_own +t+5497.3s 〰 震动:swipe(滑动识别) +t+5497.8s ♪ 音效 sfx/step_own +t+5497.8s 〰 震动:swipe(滑动识别) +t+5498.3s ♪ 音效 sfx/pulse +t+5498.6s ♪ 音效 sfx/echo_1 +t+5498.7s ♪ 音效 sfx/side_open_l +t+5498.7s ♪ 音效 sfx/side_wall_r +t+5498.7s ♪ 音效 sfx/mat_stone +t+5502.3s ♪ 音效 sfx/step_turn +t+5507.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+5517.3s ♪ 音效 sfx/pulse +t+5517.3s 〰 震动:error(做错了:两连短震) +t+5517.3s 〰 震动:error(做错了:两连短震) +t+5517.6s ♪ 音效 sfx/echo_1 +t+5517.7s ♪ 音效 sfx/side_wall_l +t+5517.7s ♪ 音效 sfx/side_open_r +t+5517.7s ♪ 音效 sfx/mat_stone +t+5522.3s ♪ 音效 sfx/pulse +t+5522.6s ♪ 音效 sfx/echo_1 +t+5522.7s ♪ 音效 sfx/side_wall_l +t+5522.7s ♪ 音效 sfx/side_open_r +t+5522.7s ♪ 音效 sfx/mat_stone +t+5527.3s ♪ 音效 sfx/step_turn +t+5532.3s ♪ 音效 sfx/step_own +t+5532.3s 〰 震动:swipe(滑动识别) +t+5532.8s ♪ 音效 sfx/step_own +t+5532.8s 〰 震动:swipe(滑动识别) +t+5533.3s ♪ 音效 sfx/pulse +t+5533.7s ♪ 音效 sfx/side_open_l +t+5533.7s ♪ 音效 sfx/side_wall_r +t+5534.4s ♪ 音效 sfx/echo_4 +t+5534.5s ♪ 音效 sfx/mat_stone +t+5537.3s ♪ 音效 sfx/step_turn +t+5542.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+5552.3s ♪ 音效 sfx/pulse +t+5552.3s 〰 震动:error(做错了:两连短震) +t+5552.3s 〰 震动:error(做错了:两连短震) +t+5552.6s ♪ 音效 sfx/echo_1 +t+5552.7s ♪ 音效 sfx/side_open_l +t+5552.7s ♪ 音效 sfx/side_open_r +t+5552.7s ♪ 音效 sfx/mat_stone +t+5557.3s ♪ 音效 sfx/pulse +t+5557.6s ♪ 音效 sfx/echo_1 +t+5557.7s ♪ 音效 sfx/side_open_l +t+5557.7s ♪ 音效 sfx/side_open_r +t+5557.7s ♪ 音效 sfx/mat_stone +t+5562.3s ♪ 音效 sfx/step_turn +t+5567.3s ♪ 音效 sfx/step_own +t+5567.3s 〰 震动:swipe(滑动识别) +t+5567.8s ♪ 音效 sfx/step_own +t+5567.8s 〰 震动:swipe(滑动识别) +t+5568.3s ♪ 音效 sfx/pulse +t+5568.6s ♪ 音效 sfx/echo_1 +t+5568.7s ♪ 音效 sfx/side_open_l +t+5568.7s ♪ 音效 sfx/side_wall_r +t+5568.7s ♪ 音效 sfx/mat_stone +t+5572.3s ♪ 音效 sfx/step_turn +t+5577.3s ♪ 音效 sfx/bump +t+5587.3s ♪ 音效 sfx/pulse +t+5587.6s ♪ 音效 sfx/echo_1 +t+5587.7s ♪ 音效 sfx/side_wall_l +t+5587.7s ♪ 音效 sfx/side_open_r +t+5587.7s ♪ 音效 sfx/mat_stone +t+5592.3s ♪ 音效 sfx/step_turn +t+5597.3s ♪ 音效 sfx/step_own +t+5597.3s 〰 震动:swipe(滑动识别) +t+5597.8s ♪ 音效 sfx/step_own +t+5597.8s 〰 震动:swipe(滑动识别) +t+5598.3s ♪ 音效 sfx/pulse +t+5598.7s ♪ 音效 sfx/side_open_l +t+5598.7s ♪ 音效 sfx/side_wall_r +t+5599.4s ♪ 音效 sfx/echo_4 +t+5599.5s ♪ 音效 sfx/mat_stone +t+5602.3s ♪ 音效 sfx/step_turn +t+5607.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+5617.3s ♪ 音效 sfx/pulse +t+5617.3s 〰 震动:error(做错了:两连短震) +t+5617.3s 〰 震动:error(做错了:两连短震) +t+5617.6s ♪ 音效 sfx/echo_1 +t+5617.7s ♪ 音效 sfx/side_open_l +t+5617.7s ♪ 音效 sfx/side_open_r +t+5617.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5622.3s ♪ 音效 sfx/pulse +t+5622.3s 〰 震动:error(做错了:两连短震) +t+5622.3s 〰 震动:error(做错了:两连短震) +t+5622.6s ♪ 音效 sfx/echo_1 +t+5622.7s ♪ 音效 sfx/side_open_l +t+5622.7s ♪ 音效 sfx/side_open_r +t+5622.7s ♪ 音效 sfx/mat_stone +t+5627.3s ♪ 音效 sfx/step_turn +t+5632.3s ♪ 音效 sfx/step_own +t+5632.3s 〰 震动:swipe(滑动识别) +t+5632.8s ♪ 音效 sfx/step_own +t+5632.8s 〰 震动:swipe(滑动识别) +t+5633.3s ♪ 音效 sfx/pulse +t+5633.6s ♪ 音效 sfx/echo_1 +t+5633.7s ♪ 音效 sfx/side_open_l +t+5633.7s ♪ 音效 sfx/side_wall_r +t+5633.7s ♪ 音效 sfx/mat_stone +t+5637.3s ♪ 音效 sfx/step_turn +t+5642.3s ♪ 音效 sfx/bump +t+5652.3s ♪ 音效 sfx/pulse +t+5652.6s ♪ 音效 sfx/echo_1 +t+5652.7s ♪ 音效 sfx/side_wall_l +t+5652.7s ♪ 音效 sfx/side_open_r +t+5652.7s ♪ 音效 sfx/mat_stone +t+5657.3s ♪ 音效 sfx/step_turn +t+5662.3s ♪ 音效 sfx/step_own +t+5662.3s 〰 震动:swipe(滑动识别) +t+5662.8s ♪ 音效 sfx/step_own +t+5662.8s 〰 震动:swipe(滑动识别) +t+5663.3s ♪ 音效 sfx/pulse +t+5663.7s ♪ 音效 sfx/side_open_l +t+5663.7s ♪ 音效 sfx/side_wall_r +t+5664.4s ♪ 音效 sfx/echo_4 +t+5664.5s ♪ 音效 sfx/mat_stone +t+5667.3s ♪ 音效 sfx/step_turn +t+5672.3s ♪ 音效 sfx/bump +t+5682.3s ♪ 音效 sfx/pulse +t+5682.6s ♪ 音效 sfx/echo_1 +t+5682.7s ♪ 音效 sfx/side_open_l +t+5682.7s ♪ 音效 sfx/side_open_r +t+5682.7s ♪ 音效 sfx/mat_stone +t+5687.3s ♪ 音效 sfx/step_turn +t+5692.3s ♪ 音效 sfx/step_own +t+5692.3s 〰 震动:swipe(滑动识别) +t+5692.8s ♪ 音效 sfx/step_own +t+5692.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5693.3s ♪ 音效 sfx/pulse +t+5693.3s 〰 震动:error(做错了:两连短震) +t+5693.3s 〰 震动:error(做错了:两连短震) +t+5693.6s ♪ 音效 sfx/echo_1 +t+5693.7s ♪ 音效 sfx/side_open_l +t+5693.7s ♪ 音效 sfx/side_wall_r +t+5693.7s ♪ 音效 sfx/mat_stone +t+5697.3s ♪ 音效 sfx/pulse +t+5697.6s ♪ 音效 sfx/echo_1 +t+5697.7s ♪ 音效 sfx/side_open_l +t+5697.7s ♪ 音效 sfx/side_wall_r +t+5697.7s ♪ 音效 sfx/mat_stone +t+5702.3s ♪ 音效 sfx/step_turn +t+5707.3s ♪ 音效 sfx/bump +t+5717.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+5727.3s ♪ 音效 sfx/pulse +t+5727.3s 〰 震动:error(做错了:两连短震) +t+5727.3s 〰 震动:error(做错了:两连短震) +t+5727.6s ♪ 音效 sfx/echo_1 +t+5727.7s ♪ 音效 sfx/side_wall_l +t+5727.7s ♪ 音效 sfx/side_open_r +t+5727.7s ♪ 音效 sfx/mat_stone +t+5732.3s ♪ 音效 sfx/pulse +t+5732.6s ♪ 音效 sfx/echo_1 +t+5732.7s ♪ 音效 sfx/side_wall_l +t+5732.7s ♪ 音效 sfx/side_open_r +t+5732.7s ♪ 音效 sfx/mat_stone +t+5737.3s ♪ 音效 sfx/step_turn +t+5742.3s ♪ 音效 sfx/step_own +t+5742.3s 〰 震动:swipe(滑动识别) +t+5742.8s ♪ 音效 sfx/step_own +t+5742.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+5743.3s ♪ 音效 sfx/pulse +t+5743.3s 〰 震动:error(做错了:两连短震) +t+5743.3s 〰 震动:error(做错了:两连短震) +t+5743.7s ♪ 音效 sfx/side_open_l +t+5743.7s ♪ 音效 sfx/side_wall_r +t+5744.4s ♪ 音效 sfx/echo_4 +t+5744.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5747.3s ♪ 音效 sfx/pulse +t+5747.3s 〰 震动:error(做错了:两连短震) +t+5747.3s 〰 震动:error(做错了:两连短震) +t+5747.7s ♪ 音效 sfx/side_open_l +t+5747.7s ♪ 音效 sfx/side_wall_r +t+5748.4s ♪ 音效 sfx/echo_4 +t+5748.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5752.3s ♪ 音效 sfx/pulse +t+5752.3s 〰 震动:error(做错了:两连短震) +t+5752.3s 〰 震动:error(做错了:两连短震) +t+5752.7s ♪ 音效 sfx/side_open_l +t+5752.7s ♪ 音效 sfx/side_wall_r +t+5753.4s ♪ 音效 sfx/echo_4 +t+5753.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5757.3s ♪ 音效 sfx/pulse +t+5757.3s 〰 震动:error(做错了:两连短震) +t+5757.3s 〰 震动:error(做错了:两连短震) +t+5757.7s ♪ 音效 sfx/side_open_l +t+5757.7s ♪ 音效 sfx/side_wall_r +t+5758.4s ♪ 音效 sfx/echo_4 +t+5758.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5762.3s ♪ 音效 sfx/pulse +t+5762.3s 〰 震动:error(做错了:两连短震) +t+5762.3s 〰 震动:error(做错了:两连短震) +t+5762.7s ♪ 音效 sfx/side_open_l +t+5762.7s ♪ 音效 sfx/side_wall_r +t+5763.4s ♪ 音效 sfx/echo_4 +t+5763.5s ♪ 音效 sfx/mat_stone +t+5767.3s ♪ 音效 sfx/pulse +t+5767.7s ♪ 音效 sfx/side_open_l +t+5767.7s ♪ 音效 sfx/side_wall_r +t+5768.4s ♪ 音效 sfx/echo_4 +t+5768.5s ♪ 音效 sfx/mat_stone +t+5772.3s ♪ 音效 sfx/step_turn +t+5777.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+5787.3s ♪ 音效 sfx/pulse +t+5787.3s 〰 震动:error(做错了:两连短震) +t+5787.3s 〰 震动:error(做错了:两连短震) +t+5787.6s ♪ 音效 sfx/echo_1 +t+5787.7s ♪ 音效 sfx/side_open_l +t+5787.7s ♪ 音效 sfx/side_open_r +t+5787.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+5792.3s ♪ 音效 sfx/pulse +t+5792.3s 〰 震动:error(做错了:两连短震) +t+5792.3s 〰 震动:error(做错了:两连短震) +t+5792.6s ♪ 音效 sfx/echo_1 +t+5792.7s ♪ 音效 sfx/side_open_l +t+5792.7s ♪ 音效 sfx/side_open_r +t+5792.7s ♪ 音效 sfx/mat_stone +t+5797.3s ♪ 音效 sfx/pulse +t+5797.6s ♪ 音效 sfx/echo_1 +t+5797.7s ♪ 音效 sfx/side_open_l +t+5797.7s ♪ 音效 sfx/side_open_r +t+5797.7s ♪ 音效 sfx/mat_stone +t+5802.3s ♪ 音效 sfx/step_turn +t+5807.3s ♪ 音效 sfx/step_own +t+5807.3s 〰 震动:swipe(滑动识别) +t+5807.8s ♪ 音效 sfx/step_own +t+5807.8s 〰 震动:swipe(滑动识别) +t+5808.3s ♪ 音效 sfx/pulse +t+5808.6s ♪ 音效 sfx/echo_1 +t+5808.7s ♪ 音效 sfx/side_open_l +t+5808.7s ♪ 音效 sfx/side_wall_r +t+5808.7s ♪ 音效 sfx/mat_stone +t+5812.3s ♪ 音效 sfx/step_turn +t+5817.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+5827.3s ♪ 音效 sfx/pulse +t+5827.3s 〰 震动:error(做错了:两连短震) +t+5827.3s 〰 震动:error(做错了:两连短震) +t+5827.6s ♪ 音效 sfx/echo_1 +t+5827.7s ♪ 音效 sfx/side_wall_l +t+5827.7s ♪ 音效 sfx/side_open_r +t+5827.7s ♪ 音效 sfx/mat_stone +t+5832.3s ♪ 音效 sfx/pulse +t+5832.6s ♪ 音效 sfx/echo_1 +t+5832.7s ♪ 音效 sfx/side_wall_l +t+5832.7s ♪ 音效 sfx/side_open_r +t+5832.7s ♪ 音效 sfx/mat_stone +t+5837.3s ♪ 音效 sfx/pulse +t+5837.6s ♪ 音效 sfx/echo_1 +t+5837.7s ♪ 音效 sfx/side_wall_l +t+5837.7s ♪ 音效 sfx/side_open_r +t+5837.7s ♪ 音效 sfx/mat_stone +t+5842.3s ♪ 音效 sfx/step_turn +t+5847.3s ♪ 音效 sfx/step_own +t+5847.3s 〰 震动:swipe(滑动识别) +t+5847.8s ♪ 音效 sfx/step_own +t+5847.8s 〰 震动:swipe(滑动识别) +t+5848.3s ♪ 音效 sfx/pulse +t+5848.7s ♪ 音效 sfx/side_open_l +t+5848.7s ♪ 音效 sfx/side_wall_r +t+5849.4s ♪ 音效 sfx/echo_4 +t+5849.5s ♪ 音效 sfx/mat_stone +t+5852.3s ♪ 音效 sfx/step_turn +t+5857.3s ♪ 音效 sfx/bump +t+5867.3s ♪ 音效 sfx/pulse +t+5867.6s ♪ 音效 sfx/echo_1 +t+5867.7s ♪ 音效 sfx/side_open_l +t+5867.7s ♪ 音效 sfx/side_open_r +t+5867.7s ♪ 音效 sfx/mat_stone +t+5872.3s ♪ 音效 sfx/step_turn +t+5877.3s ♪ 音效 sfx/step_own +t+5877.3s 〰 震动:swipe(滑动识别) +t+5877.8s ♪ 音效 sfx/step_own +t+5877.8s 〰 震动:swipe(滑动识别) +t+5878.3s ♪ 音效 sfx/pulse +t+5878.6s ♪ 音效 sfx/echo_1 +t+5878.7s ♪ 音效 sfx/side_open_l +t+5878.7s ♪ 音效 sfx/side_wall_r +t+5878.7s ♪ 音效 sfx/mat_stone +t+5882.3s ♪ 音效 sfx/step_turn +t+5887.3s ♪ 音效 sfx/bump +t+5897.3s ♪ 音效 sfx/pulse +t+5897.6s ♪ 音效 sfx/echo_1 +t+5897.7s ♪ 音效 sfx/side_wall_l +t+5897.7s ♪ 音效 sfx/side_open_r +t+5897.7s ♪ 音效 sfx/mat_stone +t+5902.3s ♪ 音效 sfx/step_turn +t+5907.3s ♪ 音效 sfx/step_own +t+5907.3s 〰 震动:swipe(滑动识别) +t+5907.8s ♪ 音效 sfx/step_own +t+5907.8s 〰 震动:swipe(滑动识别) +t+5908.3s ♪ 音效 sfx/pulse +t+5908.7s ♪ 音效 sfx/side_open_l +t+5908.7s ♪ 音效 sfx/side_wall_r +t+5909.4s ♪ 音效 sfx/echo_4 +t+5909.5s ♪ 音效 sfx/mat_stone +t+5912.3s ♪ 音效 sfx/step_turn +t+5917.3s ♪ 音效 sfx/bump +t+5927.3s ♪ 音效 sfx/pulse +t+5927.6s ♪ 音效 sfx/echo_1 +t+5927.7s ♪ 音效 sfx/side_open_l +t+5927.7s ♪ 音效 sfx/side_open_r +t+5927.7s ♪ 音效 sfx/mat_stone +t+5932.3s ♪ 音效 sfx/step_turn +t+5937.3s ♪ 音效 sfx/step_own +t+5937.3s 〰 震动:swipe(滑动识别) +t+5937.8s ♪ 音效 sfx/step_own +t+5937.8s 〰 震动:swipe(滑动识别) +t+5938.3s ♪ 音效 sfx/pulse +t+5938.6s ♪ 音效 sfx/echo_1 +t+5938.7s ♪ 音效 sfx/side_open_l +t+5938.7s ♪ 音效 sfx/side_wall_r +t+5938.7s ♪ 音效 sfx/mat_stone +t+5942.3s ♪ 音效 sfx/step_turn +t+5947.3s ♪ 音效 sfx/bump +t+5957.3s ♪ 音效 sfx/pulse +t+5957.6s ♪ 音效 sfx/echo_1 +t+5957.7s ♪ 音效 sfx/side_wall_l +t+5957.7s ♪ 音效 sfx/side_open_r +t+5957.7s ♪ 音效 sfx/mat_stone +t+5962.3s ♪ 音效 sfx/step_turn +t+5967.3s ♪ 音效 sfx/step_own +t+5967.3s 〰 震动:swipe(滑动识别) +t+5967.8s ♪ 音效 sfx/step_own +t+5967.8s 〰 震动:swipe(滑动识别) +t+5968.3s ♪ 音效 sfx/pulse +t+5968.7s ♪ 音效 sfx/side_open_l +t+5968.7s ♪ 音效 sfx/side_wall_r +t+5969.4s ♪ 音效 sfx/echo_4 +t+5969.5s ♪ 音效 sfx/mat_stone +t+5972.3s ♪ 音效 sfx/step_turn +t+5977.3s ♪ 音效 sfx/bump +t+5987.3s ♪ 音效 sfx/pulse +t+5987.6s ♪ 音效 sfx/echo_1 +t+5987.7s ♪ 音效 sfx/side_open_l +t+5987.7s ♪ 音效 sfx/side_open_r +t+5987.7s ♪ 音效 sfx/mat_stone +t+5992.3s ♪ 音效 sfx/step_turn +t+5997.3s ♪ 音效 sfx/step_own +t+5997.3s 〰 震动:swipe(滑动识别) +t+5997.8s ♪ 音效 sfx/step_own +t+5997.8s 〰 震动:swipe(滑动识别) +t+5998.3s ♪ 音效 sfx/pulse +t+5998.6s ♪ 音效 sfx/echo_1 +t+5998.7s ♪ 音效 sfx/side_open_l +t+5998.7s ♪ 音效 sfx/side_wall_r +t+5998.7s ♪ 音效 sfx/mat_stone +t+6002.3s ♪ 音效 sfx/step_turn +t+6007.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+6017.3s ♪ 音效 sfx/pulse +t+6017.3s 〰 震动:error(做错了:两连短震) +t+6017.3s 〰 震动:error(做错了:两连短震) +t+6017.6s ♪ 音效 sfx/echo_1 +t+6017.7s ♪ 音效 sfx/side_wall_l +t+6017.7s ♪ 音效 sfx/side_open_r +t+6017.7s ♪ 音效 sfx/mat_stone +t+6022.3s ♪ 音效 sfx/pulse +t+6022.6s ♪ 音效 sfx/echo_1 +t+6022.7s ♪ 音效 sfx/side_wall_l +t+6022.7s ♪ 音效 sfx/side_open_r +t+6022.7s ♪ 音效 sfx/mat_stone +t+6027.3s ♪ 音效 sfx/step_turn +t+6032.3s ♪ 音效 sfx/step_own +t+6032.3s 〰 震动:swipe(滑动识别) +t+6032.8s ♪ 音效 sfx/step_own +t+6032.8s 〰 震动:swipe(滑动识别) +t+6033.3s ♪ 音效 sfx/pulse +t+6033.7s ♪ 音效 sfx/side_open_l +t+6033.7s ♪ 音效 sfx/side_wall_r +t+6034.4s ♪ 音效 sfx/echo_4 +t+6034.5s ♪ 音效 sfx/mat_stone +t+6037.3s ♪ 音效 sfx/step_turn +t+6042.3s ♪ 音效 sfx/bump +t+6052.3s ♪ 音效 sfx/pulse +t+6052.6s ♪ 音效 sfx/echo_1 +t+6052.7s ♪ 音效 sfx/side_open_l +t+6052.7s ♪ 音效 sfx/side_open_r +t+6052.7s ♪ 音效 sfx/mat_stone +t+6057.3s ♪ 音效 sfx/step_turn +t+6062.3s ♪ 音效 sfx/step_own +t+6062.3s 〰 震动:swipe(滑动识别) +t+6062.8s ♪ 音效 sfx/step_own +t+6062.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+6063.3s ♪ 音效 sfx/pulse +t+6063.3s 〰 震动:error(做错了:两连短震) +t+6063.3s 〰 震动:error(做错了:两连短震) +t+6063.6s ♪ 音效 sfx/echo_1 +t+6063.7s ♪ 音效 sfx/side_open_l +t+6063.7s ♪ 音效 sfx/side_wall_r +t+6063.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+6067.3s ♪ 音效 sfx/pulse +t+6067.3s 〰 震动:error(做错了:两连短震) +t+6067.3s 〰 震动:error(做错了:两连短震) +t+6067.6s ♪ 音效 sfx/echo_1 +t+6067.7s ♪ 音效 sfx/side_open_l +t+6067.7s ♪ 音效 sfx/side_wall_r +t+6067.7s ♪ 音效 sfx/mat_stone +t+6072.3s ♪ 音效 sfx/pulse +t+6072.6s ♪ 音效 sfx/echo_1 +t+6072.7s ♪ 音效 sfx/side_open_l +t+6072.7s ♪ 音效 sfx/side_wall_r +t+6072.7s ♪ 音效 sfx/mat_stone +t+6077.3s ♪ 音效 sfx/step_turn +t+6082.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+6092.3s ♪ 音效 sfx/pulse +t+6092.3s 〰 震动:error(做错了:两连短震) +t+6092.3s 〰 震动:error(做错了:两连短震) +t+6092.6s ♪ 音效 sfx/echo_1 +t+6092.7s ♪ 音效 sfx/side_wall_l +t+6092.7s ♪ 音效 sfx/side_open_r +t+6092.7s ♪ 音效 sfx/mat_stone +t+6097.3s ♪ 音效 sfx/pulse +t+6097.6s ♪ 音效 sfx/echo_1 +t+6097.7s ♪ 音效 sfx/side_wall_l +t+6097.7s ♪ 音效 sfx/side_open_r +t+6097.7s ♪ 音效 sfx/mat_stone +t+6102.3s ♪ 音效 sfx/step_turn +t+6107.3s ♪ 音效 sfx/step_own +t+6107.3s 〰 震动:swipe(滑动识别) +t+6107.8s ♪ 音效 sfx/step_own +t+6107.8s 〰 震动:swipe(滑动识别) +t+6108.3s ♪ 音效 sfx/pulse +t+6108.7s ♪ 音效 sfx/side_open_l +t+6108.7s ♪ 音效 sfx/side_wall_r +t+6109.4s ♪ 音效 sfx/echo_4 +t+6109.5s ♪ 音效 sfx/mat_stone +t+6112.3s ♪ 音效 sfx/step_turn +t+6117.3s ♪ 音效 sfx/bump +t+6127.3s ♪ 音效 sfx/pulse +t+6127.6s ♪ 音效 sfx/echo_1 +t+6127.7s ♪ 音效 sfx/side_open_l +t+6127.7s ♪ 音效 sfx/side_open_r +t+6127.7s ♪ 音效 sfx/mat_stone +t+6132.3s ♪ 音效 sfx/pulse +t+6132.6s ♪ 音效 sfx/echo_1 +t+6132.7s ♪ 音效 sfx/side_open_l +t+6132.7s ♪ 音效 sfx/side_open_r +t+6132.7s ♪ 音效 sfx/mat_stone +t+6137.3s ♪ 音效 sfx/step_turn +t+6142.3s ♪ 音效 sfx/step_own +t+6142.3s 〰 震动:swipe(滑动识别) +t+6142.8s ♪ 音效 sfx/step_own +t+6142.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+6143.3s ♪ 音效 sfx/pulse +t+6143.3s 〰 震动:error(做错了:两连短震) +t+6143.3s 〰 震动:error(做错了:两连短震) +t+6143.6s ♪ 音效 sfx/echo_1 +t+6143.7s ♪ 音效 sfx/side_open_l +t+6143.7s ♪ 音效 sfx/side_wall_r +t+6143.7s ♪ 音效 sfx/mat_stone +t+6147.3s ♪ 音效 sfx/pulse +t+6147.6s ♪ 音效 sfx/echo_1 +t+6147.7s ♪ 音效 sfx/side_open_l +t+6147.7s ♪ 音效 sfx/side_wall_r +t+6147.7s ♪ 音效 sfx/mat_stone +t+6152.3s ♪ 音效 sfx/step_turn +t+6157.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+6167.3s ♪ 音效 sfx/pulse +t+6167.3s 〰 震动:error(做错了:两连短震) +t+6167.3s 〰 震动:error(做错了:两连短震) +t+6167.6s ♪ 音效 sfx/echo_1 +t+6167.7s ♪ 音效 sfx/side_wall_l +t+6167.7s ♪ 音效 sfx/side_open_r +t+6167.7s ♪ 音效 sfx/mat_stone +t+6172.3s ♪ 音效 sfx/pulse +t+6172.6s ♪ 音效 sfx/echo_1 +t+6172.7s ♪ 音效 sfx/side_wall_l +t+6172.7s ♪ 音效 sfx/side_open_r +t+6172.7s ♪ 音效 sfx/mat_stone +t+6177.3s ♪ 音效 sfx/step_turn +t+6182.3s ♪ 音效 sfx/step_own +t+6182.3s 〰 震动:swipe(滑动识别) +t+6182.8s ♪ 音效 sfx/step_own +t+6182.8s 〰 震动:swipe(滑动识别) +t+6183.3s ♪ 音效 sfx/pulse +t+6183.7s ♪ 音效 sfx/side_open_l +t+6183.7s ♪ 音效 sfx/side_wall_r +t+6184.4s ♪ 音效 sfx/echo_4 +t+6184.5s ♪ 音效 sfx/mat_stone +t+6187.3s ♪ 音效 sfx/step_turn +t+6192.3s ♪ 音效 sfx/bump +t+6202.3s ♪ 音效 sfx/pulse +t+6202.6s ♪ 音效 sfx/echo_1 +t+6202.7s ♪ 音效 sfx/side_open_l +t+6202.7s ♪ 音效 sfx/side_open_r +t+6202.7s ♪ 音效 sfx/mat_stone +t+6207.3s ♪ 音效 sfx/step_turn +t+6212.3s ♪ 音效 sfx/step_own +t+6212.3s 〰 震动:swipe(滑动识别) +t+6212.8s ♪ 音效 sfx/step_own +t+6212.8s 〰 震动:swipe(滑动识别) +t+6213.3s ♪ 音效 sfx/pulse +t+6213.6s ♪ 音效 sfx/echo_1 +t+6213.7s ♪ 音效 sfx/side_open_l +t+6213.7s ♪ 音效 sfx/side_wall_r +t+6213.7s ♪ 音效 sfx/mat_stone +t+6217.3s ♪ 音效 sfx/step_turn +t+6222.3s ♪ 音效 sfx/bump +t+6232.3s ♪ 音效 sfx/pulse +t+6232.6s ♪ 音效 sfx/echo_1 +t+6232.7s ♪ 音效 sfx/side_wall_l +t+6232.7s ♪ 音效 sfx/side_open_r +t+6232.7s ♪ 音效 sfx/mat_stone +t+6237.3s ♪ 音效 sfx/step_turn +t+6242.3s ♪ 音效 sfx/step_own +t+6242.3s 〰 震动:swipe(滑动识别) +t+6242.8s ♪ 音效 sfx/step_own +t+6242.8s 〰 震动:swipe(滑动识别) +t+6243.3s ♪ 音效 sfx/pulse +t+6243.7s ♪ 音效 sfx/side_open_l +t+6243.7s ♪ 音效 sfx/side_wall_r +t+6244.4s ♪ 音效 sfx/echo_4 +t+6244.5s ♪ 音效 sfx/mat_stone +t+6247.3s ♪ 音效 sfx/step_turn +t+6252.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+6262.3s ♪ 音效 sfx/pulse +t+6262.3s 〰 震动:error(做错了:两连短震) +t+6262.3s 〰 震动:error(做错了:两连短震) +t+6262.6s ♪ 音效 sfx/echo_1 +t+6262.7s ♪ 音效 sfx/side_open_l +t+6262.7s ♪ 音效 sfx/side_open_r +t+6262.7s ♪ 音效 sfx/mat_stone +t+6267.3s ♪ 音效 sfx/pulse +t+6267.6s ♪ 音效 sfx/echo_1 +t+6267.7s ♪ 音效 sfx/side_open_l +t+6267.7s ♪ 音效 sfx/side_open_r +t+6267.7s ♪ 音效 sfx/mat_stone +t+6272.3s ♪ 音效 sfx/pulse +t+6272.6s ♪ 音效 sfx/echo_1 +t+6272.7s ♪ 音效 sfx/side_open_l +t+6272.7s ♪ 音效 sfx/side_open_r +t+6272.7s ♪ 音效 sfx/mat_stone +t+6277.3s ♪ 音效 sfx/step_turn +t+6282.3s ♪ 音效 sfx/step_own +t+6282.3s 〰 震动:swipe(滑动识别) +t+6282.8s ♪ 音效 sfx/step_own +t+6282.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+6283.3s ♪ 音效 sfx/pulse +t+6283.3s 〰 震动:error(做错了:两连短震) +t+6283.3s 〰 震动:error(做错了:两连短震) +t+6283.6s ♪ 音效 sfx/echo_1 +t+6283.7s ♪ 音效 sfx/side_open_l +t+6283.7s ♪ 音效 sfx/side_wall_r +t+6283.7s ♪ 音效 sfx/mat_stone +t+6287.3s ♪ 音效 sfx/pulse +t+6287.6s ♪ 音效 sfx/echo_1 +t+6287.7s ♪ 音效 sfx/side_open_l +t+6287.7s ♪ 音效 sfx/side_wall_r +t+6287.7s ♪ 音效 sfx/mat_stone +t+6292.3s ♪ 音效 sfx/step_turn +t+6297.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+6307.3s ♪ 音效 sfx/pulse +t+6307.3s 〰 震动:error(做错了:两连短震) +t+6307.3s 〰 震动:error(做错了:两连短震) +t+6307.6s ♪ 音效 sfx/echo_1 +t+6307.7s ♪ 音效 sfx/side_wall_l +t+6307.7s ♪ 音效 sfx/side_open_r +t+6307.7s ♪ 音效 sfx/mat_stone +t+6312.3s ♪ 音效 sfx/pulse +t+6312.6s ♪ 音效 sfx/echo_1 +t+6312.7s ♪ 音效 sfx/side_wall_l +t+6312.7s ♪ 音效 sfx/side_open_r +t+6312.7s ♪ 音效 sfx/mat_stone +t+6317.3s ♪ 音效 sfx/step_turn +t+6322.3s ♪ 音效 sfx/step_own +t+6322.3s 〰 震动:swipe(滑动识别) +t+6322.8s ♪ 音效 sfx/step_own +t+6322.8s 〰 震动:swipe(滑动识别) +t+6323.3s ♪ 音效 sfx/pulse +t+6323.7s ♪ 音效 sfx/side_open_l +t+6323.7s ♪ 音效 sfx/side_wall_r +t+6324.4s ♪ 音效 sfx/echo_4 +t+6324.5s ♪ 音效 sfx/mat_stone +t+6327.3s ♪ 音效 sfx/step_turn +t+6332.3s ♪ 音效 sfx/bump +t+6342.3s ♪ 音效 sfx/pulse +t+6342.6s ♪ 音效 sfx/echo_1 +t+6342.7s ♪ 音效 sfx/side_open_l +t+6342.7s ♪ 音效 sfx/side_open_r +t+6342.7s ♪ 音效 sfx/mat_stone +t+6347.3s ♪ 音效 sfx/step_turn +t+6352.3s ♪ 音效 sfx/step_own +t+6352.3s 〰 震动:swipe(滑动识别) +t+6352.8s ♪ 音效 sfx/step_own +t+6352.8s 〰 震动:swipe(滑动识别) +t+6353.3s ♪ 音效 sfx/pulse +t+6353.6s ♪ 音效 sfx/echo_1 +t+6353.7s ♪ 音效 sfx/side_open_l +t+6353.7s ♪ 音效 sfx/side_wall_r +t+6353.7s ♪ 音效 sfx/mat_stone +t+6357.3s ♪ 音效 sfx/step_turn +t+6362.3s ♪ 音效 sfx/bump +t+6372.3s ♪ 音效 sfx/pulse +t+6372.6s ♪ 音效 sfx/echo_1 +t+6372.7s ♪ 音效 sfx/side_wall_l +t+6372.7s ♪ 音效 sfx/side_open_r +t+6372.7s ♪ 音效 sfx/mat_stone +t+6377.3s ♪ 音效 sfx/step_turn +t+6382.3s ♪ 音效 sfx/step_own +t+6382.3s 〰 震动:swipe(滑动识别) +t+6382.8s ♪ 音效 sfx/step_own +t+6382.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+6383.3s ♪ 音效 sfx/pulse +t+6383.3s 〰 震动:error(做错了:两连短震) +t+6383.3s 〰 震动:error(做错了:两连短震) +t+6383.7s ♪ 音效 sfx/side_open_l +t+6383.7s ♪ 音效 sfx/side_wall_r +t+6384.4s ♪ 音效 sfx/echo_4 +t+6384.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+6387.3s ♪ 音效 sfx/pulse +t+6387.3s 〰 震动:error(做错了:两连短震) +t+6387.3s 〰 震动:error(做错了:两连短震) +t+6387.7s ♪ 音效 sfx/side_open_l +t+6387.7s ♪ 音效 sfx/side_wall_r +t+6388.4s ♪ 音效 sfx/echo_4 +t+6388.5s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+6392.3s ♪ 音效 sfx/pulse +t+6392.3s 〰 震动:error(做错了:两连短震) +t+6392.3s 〰 震动:error(做错了:两连短震) +t+6392.7s ♪ 音效 sfx/side_open_l +t+6392.7s ♪ 音效 sfx/side_wall_r +t+6393.4s ♪ 音效 sfx/echo_4 +t+6393.5s ♪ 音效 sfx/mat_stone +t+6397.3s ♪ 音效 sfx/pulse +t+6397.7s ♪ 音效 sfx/side_open_l +t+6397.7s ♪ 音效 sfx/side_wall_r +t+6398.4s ♪ 音效 sfx/echo_4 +t+6398.5s ♪ 音效 sfx/mat_stone +t+6402.3s ♪ 音效 sfx/step_turn +t+6407.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+6417.3s ♪ 音效 sfx/pulse +t+6417.3s 〰 震动:error(做错了:两连短震) +t+6417.3s 〰 震动:error(做错了:两连短震) +t+6417.6s ♪ 音效 sfx/echo_1 +t+6417.7s ♪ 音效 sfx/side_open_l +t+6417.7s ♪ 音效 sfx/side_open_r +t+6417.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+6422.3s ♪ 音效 sfx/pulse +t+6422.3s 〰 震动:error(做错了:两连短震) +t+6422.3s 〰 震动:error(做错了:两连短震) +t+6422.6s ♪ 音效 sfx/echo_1 +t+6422.7s ♪ 音效 sfx/side_open_l +t+6422.7s ♪ 音效 sfx/side_open_r +t+6422.7s ♪ 音效 sfx/mat_stone +t+6427.3s ♪ 音效 sfx/pulse +t+6427.6s ♪ 音效 sfx/echo_1 +t+6427.7s ♪ 音效 sfx/side_open_l +t+6427.7s ♪ 音效 sfx/side_open_r +t+6427.7s ♪ 音效 sfx/mat_stone +t+6432.3s ♪ 音效 sfx/step_turn +t+6437.3s ♪ 音效 sfx/step_own +t+6437.3s 〰 震动:swipe(滑动识别) +t+6437.8s ♪ 音效 sfx/step_own +t+6437.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+6438.3s ♪ 音效 sfx/pulse +t+6438.3s 〰 震动:error(做错了:两连短震) +t+6438.3s 〰 震动:error(做错了:两连短震) +t+6438.6s ♪ 音效 sfx/echo_1 +t+6438.7s ♪ 音效 sfx/side_open_l +t+6438.7s ♪ 音效 sfx/side_wall_r +t+6438.7s ♪ 音效 sfx/mat_stone +t+6442.3s ♪ 音效 sfx/pulse +t+6442.6s ♪ 音效 sfx/echo_1 +t+6442.7s ♪ 音效 sfx/side_open_l +t+6442.7s ♪ 音效 sfx/side_wall_r +t+6442.7s ♪ 音效 sfx/mat_stone +t+6447.3s ♪ 音效 sfx/step_turn +t+6452.3s ♪ 音效 sfx/bump +t+6462.3s ♪ 音效 sfx/pulse +t+6462.6s ♪ 音效 sfx/echo_1 +t+6462.7s ♪ 音效 sfx/side_wall_l +t+6462.7s ♪ 音效 sfx/side_open_r +t+6462.7s ♪ 音效 sfx/mat_stone +t+6467.3s ♪ 音效 sfx/pulse +t+6467.6s ♪ 音效 sfx/echo_1 +t+6467.7s ♪ 音效 sfx/side_wall_l +t+6467.7s ♪ 音效 sfx/side_open_r +t+6467.7s ♪ 音效 sfx/mat_stone +t+6472.3s ♪ 音效 sfx/step_turn +t+6477.3s ♪ 音效 sfx/step_own +t+6477.3s 〰 震动:swipe(滑动识别) +t+6477.8s ♪ 音效 sfx/step_own +t+6477.8s 〰 震动:swipe(滑动识别) +t+6478.3s ♪ 音效 sfx/pulse +t+6478.7s ♪ 音效 sfx/side_open_l +t+6478.7s ♪ 音效 sfx/side_wall_r +t+6479.4s ♪ 音效 sfx/echo_4 +t+6479.5s ♪ 音效 sfx/mat_stone +t+6482.3s ♪ 音效 sfx/step_turn +t+6487.3s ♪ 音效 sfx/bump +t+6497.3s ♪ 音效 sfx/pulse +t+6497.6s ♪ 音效 sfx/echo_1 +t+6497.7s ♪ 音效 sfx/side_open_l +t+6497.7s ♪ 音效 sfx/side_open_r +t+6497.7s ♪ 音效 sfx/mat_stone +t+6502.3s ♪ 音效 sfx/step_turn +t+6507.3s ♪ 音效 sfx/step_own +t+6507.3s 〰 震动:swipe(滑动识别) +t+6507.8s ♪ 音效 sfx/step_own +t+6507.8s 〰 震动:swipe(滑动识别) +t+6508.3s ♪ 音效 sfx/pulse +t+6508.6s ♪ 音效 sfx/echo_1 +t+6508.7s ♪ 音效 sfx/side_open_l +t+6508.7s ♪ 音效 sfx/side_wall_r +t+6508.7s ♪ 音效 sfx/mat_stone +t+6512.3s ♪ 音效 sfx/step_turn +t+6517.3s ♪ 音效 sfx/bump +t+6527.3s ♪ 音效 sfx/pulse +t+6527.6s ♪ 音效 sfx/echo_1 +t+6527.7s ♪ 音效 sfx/side_wall_l +t+6527.7s ♪ 音效 sfx/side_open_r +t+6527.7s ♪ 音效 sfx/mat_stone +t+6532.3s ♪ 音效 sfx/step_turn +t+6537.3s ♪ 音效 sfx/step_own +t+6537.3s 〰 震动:swipe(滑动识别) +t+6537.8s ♪ 音效 sfx/step_own +t+6537.8s 〰 震动:swipe(滑动识别) +t+6538.3s ♪ 音效 sfx/pulse +t+6538.7s ♪ 音效 sfx/side_open_l +t+6538.7s ♪ 音效 sfx/side_wall_r +t+6539.4s ♪ 音效 sfx/echo_4 +t+6539.5s ♪ 音效 sfx/mat_stone +t+6542.3s ♪ 音效 sfx/step_turn +t+6547.3s ♪ 音效 sfx/bump +t+6557.3s ♪ 音效 sfx/pulse +t+6557.6s ♪ 音效 sfx/echo_1 +t+6557.7s ♪ 音效 sfx/side_open_l +t+6557.7s ♪ 音效 sfx/side_open_r +t+6557.7s ♪ 音效 sfx/mat_stone +t+6562.3s ♪ 音效 sfx/step_turn +t+6567.3s ♪ 音效 sfx/step_own +t+6567.3s 〰 震动:swipe(滑动识别) +t+6567.8s ♪ 音效 sfx/step_own +t+6567.8s 〰 震动:swipe(滑动识别) +t+6568.3s ♪ 音效 sfx/step_turn +t+6572.3s ♪ 音效 sfx/pulse +t+6572.6s ♪ 音效 sfx/echo_1 +t+6572.7s ♪ 音效 sfx/side_wall_l +t+6572.7s ♪ 音效 sfx/side_open_r +t+6572.7s ♪ 音效 sfx/mat_stone +t+6577.3s ♪ 音效 sfx/pulse +t+6577.6s ♪ 音效 sfx/echo_1 +t+6577.7s ♪ 音效 sfx/side_wall_l +t+6577.7s ♪ 音效 sfx/side_open_r +t+6577.7s ♪ 音效 sfx/mat_stone +t+6582.3s ♪ 音效 sfx/step_turn +t+6587.3s ♪ 音效 sfx/step_own +t+6587.3s 〰 震动:swipe(滑动识别) +t+6587.8s ♪ 音效 sfx/step_own +t+6587.8s 〰 震动:swipe(滑动识别) +t+6588.3s ♪ 音效 sfx/pulse +t+6588.7s ♪ 音效 sfx/side_open_l +t+6588.7s ♪ 音效 sfx/side_wall_r +t+6589.4s ♪ 音效 sfx/echo_4 + +#### 【玩家动作】手痒连点三下脉冲 + +t+6589.4s 〰 震动:error(做错了:两连短震) +t+6589.4s 〰 震动:error(做错了:两连短震) +t+6589.4s 〰 震动:error(做错了:两连短震) +t+6589.5s ♪ 音效 sfx/mat_stone +t+6592.3s ▶ 语音:「这是第一层。」 +t+6594.4s ▶ 语音:「你毫发无损。」 + +#### 【玩家动作】手痒连点三下脉冲 + +t+6594.4s ♪ 音效 sfx/pulse +t+6594.4s 〰 震动:error(做错了:两连短震) +t+6594.4s 〰 震动:error(做错了:两连短震) +t+6594.8s ♪ 音效 sfx/side_open_l +t+6594.8s ♪ 音效 sfx/side_wall_r +t+6595.5s ♪ 音效 sfx/echo_4 +t+6595.7s ♪ 音效 sfx/mat_stone +t+6596.6s ▶ 语音:「口袋空空。」 +t+6596.6s ♪ 音效 sfx/pulse +t+6596.9s ♪ 音效 sfx/side_open_l +t+6596.9s ♪ 音效 sfx/side_wall_r +t+6597.7s ♪ 音效 sfx/echo_4 +t+6597.8s ♪ 音效 sfx/mat_stone +t+6598.7s ▶ 语音:「你面朝东。」 +t+6598.7s ♪ 音效 sfx/step_turn +t+6600.8s ▶ 语音:「附近很安静。」 +t+6600.8s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+6602.9s ♪ 音效 sfx/pulse +t+6602.9s 〰 震动:error(做错了:两连短震) +t+6602.9s 〰 震动:error(做错了:两连短震) +t+6603.2s ♪ 音效 sfx/echo_1 +t+6603.3s ♪ 音效 sfx/side_open_l +t+6603.3s ♪ 音效 sfx/side_open_r +t+6603.3s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+6607.3s ♪ 音效 sfx/pulse +t+6607.3s 〰 震动:error(做错了:两连短震) +t+6607.3s 〰 震动:error(做错了:两连短震) +t+6607.6s ♪ 音效 sfx/echo_1 +t+6607.7s ♪ 音效 sfx/side_open_l +t+6607.7s ♪ 音效 sfx/side_open_r +t+6607.7s ♪ 音效 sfx/mat_stone +t+6612.3s ♪ 音效 sfx/pulse +t+6612.6s ♪ 音效 sfx/echo_1 +t+6612.7s ♪ 音效 sfx/side_open_l +t+6612.7s ♪ 音效 sfx/side_open_r +t+6612.7s ♪ 音效 sfx/mat_stone +t+6617.3s ♪ 音效 sfx/step_turn +t+6622.3s ♪ 音效 sfx/step_own +t+6622.3s 〰 震动:swipe(滑动识别) +t+6622.8s ♪ 音效 sfx/step_own +t+6622.8s 〰 震动:swipe(滑动识别) +t+6623.3s ♪ 音效 sfx/pulse +t+6623.6s ♪ 音效 sfx/echo_1 +t+6623.7s ♪ 音效 sfx/side_open_l +t+6623.7s ♪ 音效 sfx/side_wall_r +t+6623.7s ♪ 音效 sfx/mat_stone +t+6627.3s ♪ 音效 sfx/step_turn +t+6632.3s ♪ 音效 sfx/bump +t+6642.3s ♪ 音效 sfx/pulse +t+6642.6s ♪ 音效 sfx/echo_1 +t+6642.7s ♪ 音效 sfx/side_wall_l +t+6642.7s ♪ 音效 sfx/side_open_r +t+6642.7s ♪ 音效 sfx/mat_stone +t+6647.3s ♪ 音效 sfx/pulse +t+6647.6s ♪ 音效 sfx/echo_1 +t+6647.7s ♪ 音效 sfx/side_wall_l +t+6647.7s ♪ 音效 sfx/side_open_r +t+6647.7s ♪ 音效 sfx/mat_stone +t+6652.3s ♪ 音效 sfx/step_turn +t+6657.3s ♪ 音效 sfx/step_own +t+6657.3s 〰 震动:swipe(滑动识别) +t+6657.8s ♪ 音效 sfx/step_own +t+6657.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+6658.3s ♪ 音效 sfx/pulse +t+6658.3s 〰 震动:error(做错了:两连短震) +t+6658.3s 〰 震动:error(做错了:两连短震) +t+6658.7s ♪ 音效 sfx/side_open_l +t+6658.7s ♪ 音效 sfx/side_wall_r +t+6659.4s ♪ 音效 sfx/echo_4 +t+6659.5s ♪ 音效 sfx/mat_stone +t+6662.3s ♪ 音效 sfx/pulse +t+6662.7s ♪ 音效 sfx/side_open_l +t+6662.7s ♪ 音效 sfx/side_wall_r +t+6663.4s ♪ 音效 sfx/echo_4 +t+6663.5s ♪ 音效 sfx/mat_stone +t+6667.3s ♪ 音效 sfx/step_turn +t+6672.3s ♪ 音效 sfx/bump +t+6682.3s ♪ 音效 sfx/pulse +t+6682.6s ♪ 音效 sfx/echo_1 +t+6682.7s ♪ 音效 sfx/side_open_l +t+6682.7s ♪ 音效 sfx/side_open_r +t+6682.7s ♪ 音效 sfx/mat_stone +t+6687.3s ♪ 音效 sfx/step_turn +t+6692.3s ♪ 音效 sfx/step_own +t+6692.3s 〰 震动:swipe(滑动识别) +t+6692.8s ♪ 音效 sfx/step_own +t+6692.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+6693.3s ♪ 音效 sfx/pulse +t+6693.3s 〰 震动:error(做错了:两连短震) +t+6693.3s 〰 震动:error(做错了:两连短震) +t+6693.6s ♪ 音效 sfx/echo_1 +t+6693.7s ♪ 音效 sfx/side_open_l +t+6693.7s ♪ 音效 sfx/side_wall_r +t+6693.7s ♪ 音效 sfx/mat_stone +t+6697.3s ♪ 音效 sfx/pulse +t+6697.6s ♪ 音效 sfx/echo_1 +t+6697.7s ♪ 音效 sfx/side_open_l +t+6697.7s ♪ 音效 sfx/side_wall_r +t+6697.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+6702.3s ♪ 音效 sfx/pulse +t+6702.3s 〰 震动:error(做错了:两连短震) +t+6702.3s 〰 震动:error(做错了:两连短震) +t+6702.6s ♪ 音效 sfx/echo_1 +t+6702.7s ♪ 音效 sfx/side_open_l +t+6702.7s ♪ 音效 sfx/side_wall_r +t+6702.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+6707.3s ♪ 音效 sfx/pulse +t+6707.3s 〰 震动:error(做错了:两连短震) +t+6707.3s 〰 震动:error(做错了:两连短震) +t+6707.6s ♪ 音效 sfx/echo_1 +t+6707.7s ♪ 音效 sfx/side_open_l +t+6707.7s ♪ 音效 sfx/side_wall_r +t+6707.7s ♪ 音效 sfx/mat_stone +t+6712.3s ♪ 音效 sfx/pulse +t+6712.6s ♪ 音效 sfx/echo_1 +t+6712.7s ♪ 音效 sfx/side_open_l +t+6712.7s ♪ 音效 sfx/side_wall_r +t+6712.7s ♪ 音效 sfx/mat_stone +t+6717.3s ♪ 音效 sfx/step_turn +t+6722.3s ♪ 音效 sfx/bump +t+6732.3s ♪ 音效 sfx/step_turn +t+6737.3s ♪ 音效 sfx/pulse +t+6737.6s ♪ 音效 sfx/echo_1 +t+6737.7s ♪ 音效 sfx/side_open_l +t+6737.7s ♪ 音效 sfx/side_wall_r +t+6737.7s ♪ 音效 sfx/mat_stone +t+6742.3s ♪ 音效 sfx/step_turn +t+6747.3s ♪ 音效 sfx/bump +t+6757.3s ♪ 音效 sfx/bump +t+6767.3s ♪ 音效 sfx/pulse +t+6767.6s ♪ 音效 sfx/echo_1 +t+6767.7s ♪ 音效 sfx/side_wall_l +t+6767.7s ♪ 音效 sfx/side_open_r +t+6767.7s ♪ 音效 sfx/mat_stone +t+6772.3s ♪ 音效 sfx/step_turn +t+6777.3s ♪ 音效 sfx/step_own +t+6777.3s 〰 震动:swipe(滑动识别) +t+6777.8s ♪ 音效 sfx/step_own +t+6777.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+6778.3s ♪ 音效 sfx/pulse +t+6778.3s 〰 震动:error(做错了:两连短震) +t+6778.3s 〰 震动:error(做错了:两连短震) +t+6778.7s ♪ 音效 sfx/side_open_l +t+6778.7s ♪ 音效 sfx/side_wall_r +t+6779.4s ♪ 音效 sfx/echo_4 +t+6779.5s ♪ 音效 sfx/mat_stone +t+6782.3s ♪ 音效 sfx/pulse +t+6782.7s ♪ 音效 sfx/side_open_l +t+6782.7s ♪ 音效 sfx/side_wall_r +t+6783.4s ♪ 音效 sfx/echo_4 +t+6783.5s ♪ 音效 sfx/mat_stone +t+6787.3s ♪ 音效 sfx/step_turn +t+6792.3s ♪ 音效 sfx/bump +t+6802.3s ♪ 音效 sfx/step_turn +t+6807.3s ♪ 音效 sfx/pulse +t+6807.7s ♪ 音效 sfx/side_open_l +t+6807.7s ♪ 音效 sfx/side_wall_r +t+6808.4s ♪ 音效 sfx/echo_4 +t+6808.5s ♪ 音效 sfx/mat_stone +t+6812.3s ♪ 音效 sfx/step_turn +t+6817.3s ♪ 音效 sfx/bump +t+6827.3s ♪ 音效 sfx/pulse +t+6827.6s ♪ 音效 sfx/echo_1 +t+6827.7s ♪ 音效 sfx/side_open_l +t+6827.7s ♪ 音效 sfx/side_open_r +t+6827.7s ♪ 音效 sfx/mat_stone +t+6832.3s ♪ 音效 sfx/step_turn +t+6837.3s ♪ 音效 sfx/step_own +t+6837.3s 〰 震动:swipe(滑动识别) +t+6837.8s ♪ 音效 sfx/step_own +t+6837.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+6838.3s ♪ 音效 sfx/pulse +t+6838.3s 〰 震动:error(做错了:两连短震) +t+6838.3s 〰 震动:error(做错了:两连短震) +t+6838.6s ♪ 音效 sfx/echo_1 +t+6838.7s ♪ 音效 sfx/side_open_l +t+6838.7s ♪ 音效 sfx/side_wall_r +t+6838.7s ♪ 音效 sfx/mat_stone +t+6842.3s ♪ 音效 sfx/pulse +t+6842.6s ♪ 音效 sfx/echo_1 +t+6842.7s ♪ 音效 sfx/side_open_l +t+6842.7s ♪ 音效 sfx/side_wall_r +t+6842.7s ♪ 音效 sfx/mat_stone +t+6847.3s ♪ 音效 sfx/step_turn +t+6852.3s ♪ 音效 sfx/bump +t+6862.3s ♪ 音效 sfx/pulse +t+6862.6s ♪ 音效 sfx/echo_1 +t+6862.7s ♪ 音效 sfx/side_wall_l +t+6862.7s ♪ 音效 sfx/side_open_r +t+6862.7s ♪ 音效 sfx/mat_stone +t+6867.3s ♪ 音效 sfx/step_turn +t+6872.3s ♪ 音效 sfx/step_own +t+6872.3s 〰 震动:swipe(滑动识别) +t+6872.8s ♪ 音效 sfx/step_own +t+6872.8s 〰 震动:swipe(滑动识别) +t+6873.3s ♪ 音效 sfx/pulse +t+6873.7s ♪ 音效 sfx/side_open_l +t+6873.7s ♪ 音效 sfx/side_wall_r +t+6874.4s ♪ 音效 sfx/echo_4 +t+6874.5s ♪ 音效 sfx/mat_stone +t+6877.3s ♪ 音效 sfx/step_turn +t+6882.3s ♪ 音效 sfx/bump +t+6892.3s ♪ 音效 sfx/pulse +t+6892.6s ♪ 音效 sfx/echo_1 +t+6892.7s ♪ 音效 sfx/side_open_l +t+6892.7s ♪ 音效 sfx/side_open_r +t+6892.7s ♪ 音效 sfx/mat_stone +t+6897.3s ♪ 音效 sfx/step_turn +t+6902.3s ♪ 音效 sfx/step_own +t+6902.3s 〰 震动:swipe(滑动识别) +t+6902.8s ♪ 音效 sfx/step_own +t+6902.8s 〰 震动:swipe(滑动识别) +t+6903.3s ♪ 音效 sfx/pulse +t+6903.6s ♪ 音效 sfx/echo_1 +t+6903.7s ♪ 音效 sfx/side_open_l +t+6903.7s ♪ 音效 sfx/side_wall_r +t+6903.7s ♪ 音效 sfx/mat_stone +t+6907.3s ♪ 音效 sfx/step_turn +t+6912.3s ♪ 音效 sfx/bump +t+6922.3s ♪ 音效 sfx/pulse +t+6922.6s ♪ 音效 sfx/echo_1 +t+6922.7s ♪ 音效 sfx/side_wall_l +t+6922.7s ♪ 音效 sfx/side_open_r +t+6922.7s ♪ 音效 sfx/mat_stone +t+6927.3s ♪ 音效 sfx/step_turn +t+6932.3s ♪ 音效 sfx/step_own +t+6932.3s 〰 震动:swipe(滑动识别) +t+6932.8s ♪ 音效 sfx/step_own +t+6932.8s 〰 震动:swipe(滑动识别) +t+6933.3s ♪ 音效 sfx/pulse +t+6933.7s ♪ 音效 sfx/side_open_l +t+6933.7s ♪ 音效 sfx/side_wall_r +t+6934.4s ♪ 音效 sfx/echo_4 + +#### 【玩家动作】手痒连点三下脉冲 + +t+6934.4s 〰 震动:error(做错了:两连短震) +t+6934.4s 〰 震动:error(做错了:两连短震) +t+6934.4s 〰 震动:error(做错了:两连短震) +t+6934.5s ♪ 音效 sfx/mat_stone +t+6937.3s ♪ 音效 sfx/pulse +t+6937.7s ♪ 音效 sfx/side_open_l +t+6937.7s ♪ 音效 sfx/side_wall_r +t+6938.4s ♪ 音效 sfx/echo_4 +t+6938.5s ♪ 音效 sfx/mat_stone +t+6942.3s ♪ 音效 sfx/step_turn +t+6947.3s ♪ 音效 sfx/bump +t+6957.3s ♪ 音效 sfx/pulse +t+6957.6s ♪ 音效 sfx/echo_1 +t+6957.7s ♪ 音效 sfx/side_open_l +t+6957.7s ♪ 音效 sfx/side_open_r +t+6957.7s ♪ 音效 sfx/mat_stone +t+6962.3s ♪ 音效 sfx/step_turn +t+6967.3s ♪ 音效 sfx/step_own +t+6967.3s 〰 震动:swipe(滑动识别) +t+6967.8s ♪ 音效 sfx/step_own +t+6967.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+6968.3s ♪ 音效 sfx/pulse +t+6968.3s 〰 震动:error(做错了:两连短震) +t+6968.3s 〰 震动:error(做错了:两连短震) +t+6968.6s ♪ 音效 sfx/echo_1 +t+6968.7s ♪ 音效 sfx/side_open_l +t+6968.7s ♪ 音效 sfx/side_wall_r +t+6968.7s ♪ 音效 sfx/mat_stone +t+6972.3s ♪ 音效 sfx/pulse +t+6972.6s ♪ 音效 sfx/echo_1 +t+6972.7s ♪ 音效 sfx/side_open_l +t+6972.7s ♪ 音效 sfx/side_wall_r +t+6972.7s ♪ 音效 sfx/mat_stone +t+6977.3s ♪ 音效 sfx/step_turn +t+6982.3s ♪ 音效 sfx/bump +t+6992.3s ♪ 音效 sfx/pulse +t+6992.6s ♪ 音效 sfx/echo_1 +t+6992.7s ♪ 音效 sfx/side_wall_l +t+6992.7s ♪ 音效 sfx/side_open_r +t+6992.7s ♪ 音效 sfx/mat_stone +t+6997.3s ♪ 音效 sfx/step_turn +t+7002.3s ♪ 音效 sfx/step_own +t+7002.3s 〰 震动:swipe(滑动识别) +t+7002.8s ♪ 音效 sfx/step_own +t+7002.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+7003.3s ♪ 音效 sfx/pulse +t+7003.3s 〰 震动:error(做错了:两连短震) +t+7003.3s 〰 震动:error(做错了:两连短震) +t+7003.7s ♪ 音效 sfx/side_open_l +t+7003.7s ♪ 音效 sfx/side_wall_r +t+7004.4s ♪ 音效 sfx/echo_4 +t+7004.5s ♪ 音效 sfx/mat_stone +t+7007.3s ♪ 音效 sfx/pulse +t+7007.7s ♪ 音效 sfx/side_open_l +t+7007.7s ♪ 音效 sfx/side_wall_r +t+7008.4s ♪ 音效 sfx/echo_4 +t+7008.5s ♪ 音效 sfx/mat_stone +t+7012.3s ♪ 音效 sfx/step_turn +t+7017.3s ♪ 音效 sfx/bump + +#### 【玩家动作】手痒连点三下脉冲 + +t+7027.3s ♪ 音效 sfx/pulse +t+7027.3s 〰 震动:error(做错了:两连短震) +t+7027.3s 〰 震动:error(做错了:两连短震) +t+7027.6s ♪ 音效 sfx/echo_1 +t+7027.7s ♪ 音效 sfx/side_open_l +t+7027.7s ♪ 音效 sfx/side_open_r +t+7027.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+7032.3s ♪ 音效 sfx/pulse +t+7032.3s 〰 震动:error(做错了:两连短震) +t+7032.3s 〰 震动:error(做错了:两连短震) +t+7032.6s ♪ 音效 sfx/echo_1 +t+7032.7s ♪ 音效 sfx/side_open_l +t+7032.7s ♪ 音效 sfx/side_open_r +t+7032.7s ♪ 音效 sfx/mat_stone +t+7037.3s ♪ 音效 sfx/step_turn + +#### 【玩家动作】手痒连点三下脉冲 + +t+7042.3s ♪ 音效 sfx/pulse +t+7042.3s 〰 震动:error(做错了:两连短震) +t+7042.3s 〰 震动:error(做错了:两连短震) +t+7042.7s ♪ 音效 sfx/side_wall_l +t+7042.7s ♪ 音效 sfx/side_open_r +t+7043.1s ♪ 音效 sfx/echo_3 +t+7043.2s ♪ 音效 sfx/mat_stone +t+7047.3s ♪ 音效 sfx/step_turn +t+7052.3s ♪ 音效 sfx/pulse +t+7052.7s ♪ 音效 sfx/side_open_l +t+7052.7s ♪ 音效 sfx/side_open_r +t+7053.1s ♪ 音效 sfx/echo_3 +t+7053.2s ♪ 音效 sfx/mat_stone +t+7057.3s ♪ 音效 sfx/step_turn +t+7062.3s ♪ 音效 sfx/step_own +t+7062.3s 〰 震动:swipe(滑动识别) +t+7062.8s ♪ 音效 sfx/step_own +t+7062.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+7063.3s ♪ 音效 sfx/pulse +t+7063.3s 〰 震动:error(做错了:两连短震) +t+7063.3s 〰 震动:error(做错了:两连短震) +t+7063.6s ♪ 音效 sfx/echo_1 +t+7063.7s ♪ 音效 sfx/side_open_l +t+7063.7s ♪ 音效 sfx/side_wall_r +t+7063.7s ♪ 音效 sfx/mat_stone + +#### 【玩家动作】手痒连点三下脉冲 + +t+7067.3s ♪ 音效 sfx/pulse +t+7067.3s 〰 震动:error(做错了:两连短震) +t+7067.3s 〰 震动:error(做错了:两连短震) +t+7067.6s ♪ 音效 sfx/echo_1 +t+7067.7s ♪ 音效 sfx/side_open_l +t+7067.7s ♪ 音效 sfx/side_wall_r +t+7067.7s ♪ 音效 sfx/mat_stone +t+7072.3s ♪ 音效 sfx/pulse +t+7072.6s ♪ 音效 sfx/echo_1 +t+7072.7s ♪ 音效 sfx/side_open_l +t+7072.7s ♪ 音效 sfx/side_wall_r +t+7072.7s ♪ 音效 sfx/mat_stone +t+7077.3s ♪ 音效 sfx/step_turn +t+7082.3s ♪ 音效 sfx/bump +t+7092.3s ♪ 音效 sfx/pulse +t+7092.6s ♪ 音效 sfx/echo_1 +t+7092.7s ♪ 音效 sfx/side_wall_l +t+7092.7s ♪ 音效 sfx/side_open_r +t+7092.7s ♪ 音效 sfx/mat_stone +t+7097.3s ♪ 音效 sfx/step_turn +t+7102.3s ♪ 音效 sfx/step_own +t+7102.3s 〰 震动:swipe(滑动识别) +t+7102.8s ♪ 音效 sfx/step_own +t+7102.8s 〰 震动:swipe(滑动识别) + +#### 【玩家动作】手痒连点三下脉冲 + +t+7103.3s ♪ 音效 sfx/pulse +t+7103.3s 〰 震动:error(做错了:两连短震) +t+7103.3s 〰 震动:error(做错了:两连短震) +t+7103.7s ♪ 音效 sfx/side_open_l +t+7103.7s ♪ 音效 sfx/side_wall_r +t+7104.4s ♪ 音效 sfx/echo_4 +t+7104.5s ♪ 音效 sfx/mat_stone +t+7107.3s ♪ 音效 sfx/pulse +t+7107.7s ♪ 音效 sfx/side_open_l +t+7107.7s ♪ 音效 sfx/side_wall_r +t+7108.4s ♪ 音效 sfx/echo_4 +t+7108.5s ♪ 音效 sfx/mat_stone +t+7112.3s ♪ 音效 sfx/step_turn +t+7117.3s ♪ 音效 sfx/bump +t+7127.3s ♪ 音效 sfx/pulse +t+7127.6s ♪ 音效 sfx/echo_1 +t+7127.7s ♪ 音效 sfx/side_open_l +t+7127.7s ♪ 音效 sfx/side_open_r +t+7127.7s ♪ 音效 sfx/mat_stone +t+7132.3s ♪ 音效 sfx/step_turn +t+7137.3s ♪ 音效 sfx/step_own +t+7137.3s 〰 震动:swipe(滑动识别) +t+7137.8s ♪ 音效 sfx/step_own +t+7137.8s 〰 震动:swipe(滑动识别) +t+7138.3s ♪ 音效 sfx/pulse +t+7138.6s ♪ 音效 sfx/echo_1 +t+7138.7s ♪ 音效 sfx/side_open_l +t+7138.7s ♪ 音效 sfx/side_wall_r +t+7138.7s ♪ 音效 sfx/mat_stone +t+7142.3s ♪ 音效 sfx/step_turn +t+7147.3s ♪ 音效 sfx/bump +t+7157.3s ♪ 音效 sfx/pulse +t+7157.6s ♪ 音效 sfx/echo_1 +t+7157.7s ♪ 音效 sfx/side_wall_l +t+7157.7s ♪ 音效 sfx/side_open_r +t+7157.7s ♪ 音效 sfx/mat_stone +t+7162.3s ♪ 音效 sfx/step_turn +t+7167.3s ♪ 音效 sfx/step_own +t+7167.3s 〰 震动:swipe(滑动识别) +t+7167.8s ♪ 音效 sfx/step_own +t+7167.8s 〰 震动:swipe(滑动识别) +t+7187.3s ♪ 音效 sfx/amb_cave + +#### 【玩家动作】这一把居然没死——压力有限,收工 + diff --git a/research/new8/radio/round1/live-player.md b/research/new8/radio/round1/live-player.md new file mode 100644 index 00000000..e027d5ef --- /dev/null +++ b/research/new8/radio/round1/live-player.md @@ -0,0 +1,120 @@ +# 《不眠频率》R1 全拟真试玩报告 — 老墨(失眠的电台制作人)与他闺女墨墨(手比耳朵快的19岁) + +## 基本信息 + +同一台"手机"两个用户,两局都真打完(live harness:真 chromium + SenseVoice ASR 听游戏实况,非 mock)。 + +**局 A(老墨·新手主线)** +- 身份:45 岁电台制作人人设,第一次玩纯音频游戏,认真听完关键句再动手,全晚走心(多左滑)。 +- 设置:`voice=male, speed=1.0, bgm=1.0, guide=full` +- 时长/手势:墙钟 26.5 分钟(含我在幕间发呆的时间),51 个手势(tap 41 / 左滑 8 / 右滑 1 / 上滑 1) +- 结局:**e1「天亮之前」**(rep=12,p9 左滑直面),尾声 5 位来电者全救到版 ep_*_ok 逐条点名 +- 报错:非资产类 console.error = 0;无死锁、无状态错乱 + +**局 B(墨墨·急性子压力)** +- 身份:19 岁女儿抢过手机,语音播一半就戳、不等提示狂按、开菜单就关、四处乱滑,抉择全走克制(右滑)路线。 +- 设置:`voice=female, speed=1.7, bgm=1.0, guide=standard` +- 时长/手势:墙钟 18.3 分钟,83 个手势(tap 58 / 右滑 12 / 下滑 4 / 左滑 3 / 上滑 2 / 双击 2 / 长按 2) +- 结局:**e4「静音」**(rep=0,p9 右滑回避),尾声 5 位来电者全错过版 ep_*_no——「有的夜班,谁也接不住谁」 +- 报错:非资产类 console.error = 0;18 段语音提前被杀**全部可归因到玩家自己的 tap 跳句**(逐段核对 vend 时长),无一局是游戏自残 +- 意外彩蛋:局 B 开局实测了**断点续玩 ASK 流程**(局 A 存档导致,见 P2-3) + +--- + +## 局 A 体验叙述(老墨,第一人称) + +解锁遮罩那一指下去,t=5.3s 听到「新的夜晚,开始了」(sys_new_night),紧接着 t=7.4s 一句操作口令「单击屏幕继续,双击重新听……」(gd_brief,full 档专属前置),然后台标口播 b_open——**我后来核对了事件流,解锁那一下 tap 并没有像我在别的游戏里遇到的那样被当成"跳过旁白",开场每一句都足时播完了**。这份"第一指头不吞你的开场白"的克制,让我对它先有了好感。 + +t=34.8s,第一通热线来了。没有铃声——只有一下短震。我当时愣了三秒:铃呢?10 秒后 t=44.9s 导播耳语补上来「热线在闪。单击,接听。」(gd_ring1)。后来看事件才知道铃声资产(sfx/phone_ring)整个 404 了,RING 状态前 10 秒是纯静默+一下震动(见 P2-1)。作为盲玩者,是向导把我捞起来的。 + +接通老周(z1_ring_in「一线来电。七十多岁,雨夜,走丢了。」)。抉择点 p1 的选项句我听的是全文:「老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。」——ASR 转写 similarity **1.0**,一字不差。我当时在句末磨蹭了太久,这句提示被**原句复播了两次**(t=498.0、523.0,25s/50s 两级看门狗),复播的是完整选项句而非干巴巴的"快选啊",这个细节让我不烦;而且复播两级之后彻底安静,我 t=533.1 的左滑照样生效——选择权一直在我这。左滑,t=533.6s 老周跟着我哼起来(z1_song,similarity 0.97),t=674s「对,对!路口修车铺,天天放这首!」(z1_songo)——第一个正反馈到手,他声音真的稳住了。 + +p2 我继续左滑「留在原地陪他等」。t=745.2s 幕间响起今晚第一句口碑播报:「今晚的口碑,不好不坏。」(rep_mid,1.0)——账是真的(p1 左 +1、p2 左 +2,rep=3,低→中跨档才播)。第二通小鹿,p3 左滑「这趟车值得上」,t=845.1s 那句「你妈等的不是奖杯,是你进门喊的那声妈。」(l1_go,**1.0**)是我整晚最喜欢的一句台词;紧接着 t=865.8s「今晚的口碑,在听众里立住了。」(rep_high)——rep=5,跨高档,播报又对了一次。 + +第三通糖糖,我在 k1_b 里听到了「家里灯关了。可是楼下,一直有声音。」(similarity 1.0),但设计里说这句下面应该垫着**闷住的争吵声**(arg_muffled)——那层环境音从头到尾没出现(资产缺失,见 P2-2)。我选了右滑「轻轻问她怎么了」,t=959.8s「爸爸妈妈摔门了。是不是……我做错了什么呀。」(k1_asko,1.0)——值了。p5 我左滑说了真话:「大人的事,不是小孩的错。一个字都不是。」(k1_truth,0.97)。但在这里我吃了自己第一次亏:k1_trutho 声明 10.96 秒,我 8.4 秒就点了继续,尾巴「他们明天,会一起给我切蛋糕吗?」被我自己掐了(ASR 如实记 TRUNCATED)——而下一句导播的「蛋糕。记下来——」直接应在了那个我没听到的词上。跳句是设计好的自由,但尾句没保护,急性子会像我一样错过钩子(见 P2-4)。 + +第四通陈姐是全晚最打动我的验证:p6 左滑「替孩子带话」之后,游戏播的不是泛泛的劝和,而是 t=1140.3s「四十分钟前,有个孩子问我们:爸妈摔门,是不是她的错。」(w1_say 组)——**我在第三通"问出来"的那句话,被原封不动带进了第四通**。heardArgument 条件差分不是写在文档里的,是我亲耳听到的。 + +第五通老韩,我左滑陪他聊闺女。聊天中途 t=1334.8s 先是一下不寻常的长震(danger 震动签名 [25,40,25,40,25]),2 秒后 t=1336.9s「车道偏了——听!」(h1_danger),然后「嘶——好险!前车急刹,我擦着护栏过去了。彻底醒了!」(h1_dango,0.92)。危机拍是自动流,戏剧自己走完不等人,节奏对。挂电话前老韩补了一句 t=1351.8s「对了。头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的。」(h1_echo,**1.0**)——我对老周的陪伴,半晚之后在另一个陌生人嘴里回来了。 + +签收抉择 p9:「留言机红灯在闪,是老主播三年前没播完的那句晚安。向左滑,替他播出去。」我在句末磨蹭到提示句被原样复播了一次(t=1402.4),复播完又过了 12.8 秒才左滑。t=1425.5s「师父,三年了。这句晚安,我替您播。各位,晚安。」(d9_face,0.85)。结局 e1:t=1435.3s「天亮了,热线灯还亮着。你们的晚安,是这座城市的心跳。」,t=1443.1s「这把话筒,我总算接得住了。明晚零点,接着不眠。」然后导播替我记账(ep_lead),五个人一个一个点名:老周有了老花镜、小鹿上车了、糖糖家的门这个月没再摔过、陈姐家切了蛋糕、老韩后视镜上多了朵小红花(ep_*_ok 五连,similarity 0.90-1.0)。「明晚零点,接着不眠」——我现在就想再值一班。 + +收尾我顺手验了结算态上滑:sys_help(1.0)→ 重播结局主句 → rep_high,帮助+结局+口碑三件套齐活。 + +## 局 B 体验叙述(墨墨,第一人称) + +我上来就捅了一指头——结果听到的是「检测到你的夜班记录。单击继续上次,右滑开始新的夜晚。」(sys_continue_ask),后半句还没听完就被我下一戳掐了,然后我就被**扔回了昨晚的结局链**,e1_a 一句「太亮了,热线灯还亮着」地又播了一遍。想开新局?右滑。可我一个手比脑子快的急性子,谁听得见那句被掐掉的规则(见 P2-3)。 + +行吧,那我就在这个结局画面里发泄:左滑——error 震(零静默,可以);双击——居然把结局链给我重播了(合理,重听);下滑——菜单开了,「菜单。单击切换选项,双击确认。回到直播。」,再一下下滑又关上了,节目无缝接回去;长按——引擎里 BOOST 灯闪了一下(我按得太短)。全部有回应,没有一次装死。 + +终于点到「再来一局」。新夜班 INTRO 我自然是一个字不让它说完:b_open「午夜零点,红灯亮起。欢迎回——」只剩 0.43 秒;b_rule 被我从 1.87 秒处掐断后,转写器把残句听成了「还有优惠券就是要现金」(similarity 0.08,这个是仪器幻听,不算它)。1.7 倍速下的女声干脆利落,倒是很配我。 + +第一通铃,我先用双击试探(首通没有上一通,正确吃了 error 震,没接起来),上滑听状态:「现在是今晚第一通电话。」「今晚的口碑,有点砸了。」(prog_c1+rep_low,新夜班口碑归零,账对)。然后单击接听,一路狂戳。 + +规矩我很快摸出来了:**播放中单击=跳句,但抉择提示句(p*)戳不动**。我在 p1 播到一半时戳了一下——只有一下震动,句子照常播完;紧接着右滑「冷静问他路牌」,这一滑在句子里就被记下了,句尾一落 UI 音响起、结果句无缝接上。我前前后后在 p1/p3/p6/p8 四个提示句里都这么干过,**四次 mid-prompt 戳全部无效化、三次 mid-prompt 滑全部缓存生效**——没有一次吞我的选择,也没有一次让我抢跑成功。公平。 + +p1 右+p2 右的代价来得很快:老周在「说不清……我真是,说不清了……」里崩溃(z1_bad,1.0),然后导播一句**「地址没报清就转接,雨又大。这通砸了。先稳人,再办事。」**(z1_bade)——骂的是我刚才干的事,一个字不冤枉。这游戏的负反馈不甩锅,服。 + +中途我干过一次真正捞着东西的操作:一句对白播完后我随手左滑,错误震响完,然后……**游戏就没声了**。我以为它死机了,搁了半分多钟,什么"还在吗"都没等到,直到我自己再戳一下才继续(t=445.4→479.6,整整 34.2 秒静默,见 P1-1)。慢性子老墨永远遇不到这个坑,但我保证急性子天天踩。 + +后面我把五通全用克制路线走完:小鹿「把选择还给她」(她自己走了);糖糖我偷懒选了左滑哄睡——结果结算句直接点名「睡着了。可你没听见,她在怕什么。」(k1_lulle)——又是不冤枉的账;陈姐被我劝回了家;老韩听我的进了服务区,「单子黄了,人齐了」。这次没有危机拍、没有老韩提老周——我这一晚没陪过任何人,游戏就真的什么都不给我回响。 + +p9 右滑「照常收播」。「今夜不眠,静音收播。有的夜班,谁也接不住谁。」(e4_a)「留言机还在闪。你没看它,它也没怪你。明晚再说吧。」(e4_b)。尾声导播的账本上,五个人全是错过版(ep_*_no)。这个结局憋得我难受——**难受说明它成立**。最后我戳了一下结算画面,「新的夜晚,开始了」——重开正常,我这才心满意足地把手机扔下。 + +--- + +## 测量局限与证据口径(先说清楚) + +- 转写(ASR)对 ≤6 字短句和大语速是重灾区:局 B(1.7x 女声)103 条有效语音段里 MISMATCH 30 条(29%),最低把 z1_adone 之外的残句听成「还有优惠券就是要现金」(similarity 0.08)。**凡 events 层显示足时播放、仅转写不像的,一律不算游戏问题**。 +- 采信为引擎级证据的只有两类:`vend` 提前于 `dur/speed`(TRUNCATED 事件层证据)、应答窗口完全无事件(SILENCE)。本报告所有 TRUNCATED 已逐条归因:**23 段全部由玩家 tap 造成(跳句/接听抢占),0 段由游戏自身造成**。 +- 本报告 t= 均为该局"解锁单击后秒数"(事件时基),与 ASR 转写时间轴存在约 5 秒固定偏移,引用转写时只取 heard/expected 文本与 similarity。 + +## 问题清单 + +### P0(卡死/死锁/状态混乱/游戏自残截断) + +无。两局共 198 段语音全部串行零叠音;菜单开关、上滑状态、双击重听、无效手势、危机拍自动流、重开,全部状态正确;两局各自打到设计表对应的结局(e1:rep=12;e4:rep=0,账目与 §4.2 分毫不差);无一处游戏自身截断在播语音。 + +### P1(体验断裂) + +1. **等待态做一个无效手势,"还在吗"看门狗就被拆掉且不再补挂 → 之后无限期静默** + - 复现:局 B t=440.9 一句话播完进入链末等待(standard 档承诺 13s/30s 两级提示)→ t=445.4 我滑了一个无效的左滑(正确响了 error 震 [60,40,60])→ **此后 34.2 秒(t=445.4→479.6)事件流里除我下一次 tap 外零事件**:本应在 t≈454s 响的 gd_continue 永远没来,本应在 t≈471s 响的 gd_again 也没来。 + - 对比证据:同样在等待态,菜单关闭/上滑状态后的等待看门狗都正常补挂(局 B t=502.5 等待后 t=515.6/532.6 两级准点,误差 0.1s);RING 态的上滑/双击分支显式重挂铃看门狗——唯独叙事态的无效手势分支不补。 + - 影响:盲玩玩家遇到它的体验就是"游戏死了"。它不是死锁(tap 仍有效),但把"永不静默死局"的承诺打了个洞。代码侧佐证(只读参考,未改动):`web/radio/game.js` `onGesture()` 对任何手势先 `guide.disarm()`,而 ONAIR/INTRO 左右滑无效分支报完 error 即 return,无补挂动作。 + +### P2(不适/设计意图缺席) + +1. **RING 状态前 10 秒几乎全静默**:`sfx/phone_ring` 404(12 个缺失 SFX 之一),铃声循环无声,进入 RING 只有一记短震 [20],要等 10 秒 gd_ring1 才有声音提示。局 A 我这个"新手"在第一通铃前足足愣了 10 秒。资产缺口属已知遗留,但对免视玩家这就是接电话的本体反馈。 +2. **"声音即世界"的两处招牌设计因资产缺失缺席**:`arg_muffled`(C3 糖糖家闷住的争吵声,信息层)与 `brake_screech`(C5 危机拍 2 秒前置预警)均 404。后果:①糖糖通里"楼下一直有声音"只剩台词,p4「问她还哄睡」的抉择失去环境音依据;②老韩危机拍只剩危险长震+台词自带预警,设计的"刹车声渐强 2 秒"完全没有。5 个来电者侧环境音(amb_*)同样全缺,五通电话的"地方感"只靠导播一句口播撑着。 +3. **断点续玩 ASK 的单击语义陷阱**:有存档时重进,「单击继续上次」会把已打完的玩家直接扔回昨晚的**结局链重播**(局 B t=0→155s 我被迫听完了整段 e1)。要开新局得右滑,而急性子恰恰最可能把那句规则掐掉(我就掐了)。同一个"单击",在结算态是"再来一局(开新)"、在 ASK 是"继续上次(回旧)"——语义相反,全靠一句容易被跳掉的话撑着。 +4. **链末最后一句无跳过保护,抢跑必吃关键尾句**:单击身兼"跳过本句"与"继续",无法区分。局 A 我自认是慢性子,仍三次吃掉尾巴:k1_trutho 的「会一起给我切蛋糕吗?」(蛋糕伏笔没了,下句"蛋糕。记下来——"接了个空)、w1_a 尾巴、d9_facee 的点题句「但空着的那一秒,你说满了」;局 B 的 d9_avoide「又多亮了一夜」同样没逃掉。建议对节点末句加 0.5-1s 保护窗或屏蔽末句跳过。 + +### P3(建议) + +1. p* 抉择提示句播放中单击虽不可跳,但每次都给一记 [20] 确认震动(两局共 5 次)——手感上像"点上了",实际什么都没发生。建议改成不震,或播一句极短提示「听完这句再滑」。 +2. 看门狗与 tap 存在小竞态:局 A t=462.7 我单击后 0.4s,gd_continue 仍响了一声随即被 p1 提示句掐成 0.01s 的气口。tap 应立即作废已排队的向导句。 +3. rep_low 口碑句设计注释带 warning 震,两局 B 在 RING 上滑听到 rep_low 两次(t=198.5、497.5)均未见额外警示震(只有上滑本身的 [10])。若是只在幕间播报时震,建议补齐;否则删注释。 +4. (对应 P1-1 的修法)无效手势处理完应重挂当前等待态的看门狗,而不是任其保持静默。 +5. harness 侧记录:长按手势固定 600ms,boost 只能体验约 0.1s,快进玩法本轮无法充分压测(工具局限,非游戏问题);字符串 "1.7" 传参会被归一化为 1.0,需传数值或 `faster`。 + +## 亮点(让我愿意玩下去的东西) + +- **零叠音、零状态错乱的执行底盘**:198 段语音严格串行;菜单里乱点循环、关掉后节点完整复述;上滑状态链(sys_help→prog_cN→rep 档)在 RING/ONAIR/ENDING 三态语义都对;双击重听、无效手势零静默(局 B 9 次全有 error 震)。 +- **抉择公平性是玩出来的,不是宣传出来的**:p* 播放中单击 4+1 次全部不可跳;mid-prompt 左右滑 3 次全部缓存、播完即生效不丢不重复;选项句等待态复播两级都是完整选项句(合法帮助),复播完仍安静等你,绝不倒计时逼命。 +- **账目真实得可怕**:两局 rep 轨迹(0→3 mid→5 high / 0→-1→0)与设计表逐点一致,跨档播报只在真的跨档时出现;heardArgument 让糖糖的话原样出现在陈姐通里;bond_zhou 让老韩替老周谢我;负向选择换来的是指名道姓的归因句(z1_bade、k1_lulle),正向选择不灌一句教程。 +- **四结局两头已见其二**:e1 的「明晚零点,接着不眠」和 e4 的「明晚再说吧」是两种真实的夜班——后者的难受恰恰证明这个抉择系统有牙齿。 +- **向导节奏可靠**:两局 RING 10s/25s、full 25s/50s、standard 13s/30s、抉择点 25s/50s、结算态两级,实测误差 ≤0.2s,且从不逐字重复(gd_ring1≠gd_ring2、gd_continue≠gd_again)。 +- 手势面 6 个从头用到尾零新增学习成本,全程无画面盲玩可通——它对"纯音频游戏"这个承诺是认真的。 + +## 一句话裁决 + +**愿意**——两晚零死锁打穿 e1/e4 两个都成立的结局,我想再值一班去把老韩那句「单子黄了,人齐了」换成危机拍版本;但求先把"无效手势吃掉还在吗"这根盲玩保险丝接回去,再把铃声和争吵声还给这个只有耳朵的世界。 + +--- + +### 附:产物与证据文件 + +- 局 A:`/home/xsl/SoundGame/web/harness/live/out/radio-20260902072135/`(events.jsonl 94 语音段/51 手势、verdicts.json 157 行、transcript.md、mix.webm) +- 局 B:`/home/xsl/SoundGame/web/harness/live/out/radio-20260902074921/`(events.jsonl 104 语音段/83 手势、verdicts.json 158 行、transcript.md、mix.webm) +- 引用行为代码(只读):`/home/xsl/SoundGame/web/radio/game.js`(NODES 表 L61-166、onGesture L328-339、_onStoryGesture L404+、enterEnding L744) +- 引擎级 TRUNCATED 全清单:局 A 5 段 / 局 B 18 段,全部由玩家 tap 造成(详见上文口径),本报告未采信任何"游戏自残式截断"叙事。 diff --git a/research/new8/radio/round1/p1-blind.md b/research/new8/radio/round1/p1-blind.md new file mode 100644 index 00000000..9e286dce --- /dev/null +++ b/research/new8/radio/round1/p1-blind.md @@ -0,0 +1,28 @@ +# P1 视障资深玩家评审:《不眠频率》(R1) + +> 评审依据:research/new8/radio/round1/transcript-main.md(主线一局)+ transcript-stress.md(乱按压力一局)。 +> 我按「真机听到这些」代入。两局转写开头 t+0.0 各有一批「资产缺失→静默」警告块(约 48 行,同一份清单重复五遍), +> 与后文同名音效正常发声自相矛盾(onair_sting 在 t+2.1 照响、phone_ring 在 t+14.8 照响),我判定那是转写器的资产盘点 +> 附注而非我耳朵听到的东西,按实际发声行为评审;若真机真缺开局 sting 与铃声,开场 15 秒就是哑的,那属 P0,真机必须先确认。 + +### 不眠频率 +- 状态感知:4.5/5 —— 整个夜班我几乎每秒都知道自己在哪:每通来电开头有「一线来电/二线来电」报名(main t+14.8、t+97.3),铃响必带 turn 签名+确认震(t+14.8),幕间口碑只在跨档时播报(main t+92.2「不好不坏」、t+122.5「在听众里立住了」;未跨档的 t+158.6、t+197.4 只有垫乐不硬灌——该播的播、不该播的闭嘴,这本账我很服气);上滑随手报「第几通+口碑档」(main t+56.7-58.8),压力局被灌了 199 次无效手势、进出菜单 38 次,每次关菜单都重播当前句给我落位(stress t+119.7→123.8),全程没迷过路。扣的 0.5 在两处:一是我什么都没做就被报「今晚的口碑,有点砸了」(main t+58.8 / stress t+21.2,rep=0 读成「砸了」,听着像我在开播前就搞砸了这晚);二是上滑状态链把固定帮助句垫在状态前面,我每次查状态都要先交六秒过路费才轮到正题。 +- 下一步明确性:5/5 —— 开场 30 秒把三件事一次讲清(t+2.1 单击继续、左右滑抉择;t+6.4 单击跳句;t+10.6 台规),第一声铃 t+14.8 就到;每个抉择句自带方向和内容(t+52.5「向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌」);我听完帮助后游戏自动把选项重念一遍(main t+60.9),不让我带着问情做决定;链末发呆 25 秒有看门狗兜底(main t+42.0「单击屏幕,继续。」)。全程没有一秒我不知道该干什么。 +- 愿意继续玩:是 —— 这是少有的把我当成年人的叙事游戏:可跳句、可重听、状态随手查、失败归因到我的原话,我愿意为下一夜的五通电话买单。 +- P0 问题:无(两局转写范围内)。失败可归因这条我专门去压力局找茬:三个负向结果全部指向我真实的嘴——t+521.5「她走了。没上车,也没退票」(我刚说了「把选择还给她」)、t+696.1「你许了个兑不了的现」(我选了善意谎言);e2 结局的五条尾声我逐人对账(stress t+1066.8-1075.2):老周救到(我 p1 陪他哼歌)、小鹿退票(我 p3 右)、糖糖没接住(我 p5 谎言)、陈姐末班车(我 p6 右劝回)、老韩到家(我 p8 陪聊)——一句不差,账目真实到我想起立鼓掌。 +- 建议: + 1. 菜单完整性存疑:压力局 38 次逛菜单加主线局 1 次,我每次只听到「回到直播。」「重新开始今晚。」两项(main t+65.2/67.3;stress t+115.4/117.6 等二十余处),设计账面上的第三项「保存并回大厅」38 次循环里一次没露过脸。要么它不存在——那我的存档退出入口就没了,是功能洞;要么它在循环第三格而我永远够不着。真机第一件事:把菜单三项听全。 + 2. 上滑状态链重排并截短:状态(第几通+口碑)放最前,操作口令截短或只留在菜单里——压力局里这条逐字相同的帮助链重复了 15 次以上,信息密度对 75 档语速的用户是浪费生命。 + 3. 开局口碑档措辞:rep=0 别读「有点砸了」,给一句「台子刚开,口碑还没立起来」类的空档句。零点刚开播就听见自己「砸了」,晦气,也容易让新手误判自己已经做错了什么。 + +#### 听觉惯例符合度(逐条核对我的特别检查项) +- 语音可打断:过。开场明说「想跳过这句,单击就行」(t+6.4);向导纠错 idle-only,对白在播时不顶(stress t+17.0 同一句纠错第二次被标注跳过未播,只留 error 震——纪律执行得干净)。 +- 语音可重听:过。双击重听在压力局实测(t+105.2→t+109.3 当前句原样重播);抉择点在我查完状态后自动复述选项(main t+60.9),这是「复述选项=合法帮助」的正确打开方式。 +- 等待有无声:过。全程最长一段等待 25 秒(main t+17.0-42.0),垫乐未断,25 秒整看门狗一句话兜底(t+42.0);BRIDGE 有专属垫乐;没有一次超过 5 秒的真死寂。 +- 失败可归因:过,见 P0。且归因句只挂负向结果:主线局全程零教学零归因,只有 t+90.1「就这么接。记住这个手感。」这种把我当同行的肯定——没人把我当白痴,这一条本作满分。 +- 菜单列表导航规整:半过。口令规整(「菜单。单击切换选项,双击确认。」t+63.1),关菜单必重播当前节点不丢位(stress t+119.7→123.8);但第三项缺席嫌疑见建议 1,列表完整性未证实。 +- 提示永不逐字重复:过(系统侧)。看门狗只出场过第一级(main t+42.0),RING 两级不同句的规矩没被逼出来(铃一响我就接了,gd_ring2 未验证);用户主动上滑导致的帮助句逐字重复属于自找,不罚游戏,但建议截短。 +- 账目真实:过。主线 rep 轨迹可从播报反推并与分档句对上(t+92.2=3 分「不好不坏」、t+122.5 跨 5 分「立住了」、压力局 p2 右 p3 右后 t+1005.1「立住了」同样分档准确);尾声逐人回响与五通实际选择一一对应。 +- 语速上限/长按快进:未验证。两局加起来长按一次都没出现,全程语速 1 档。我的 75 档耳朵能不能把长按 1.7× 快进真正揉出来,转写给不了答案——这是本作对我唯一悬而未决的生死线,真机第一课就是它。 +- status key:过。上滑=帮助+状态+重播当前节点,任何叙事态可用;压力局乱按 199 次之后我依然随时能报出「第几通+口碑」,这套锚没被乱流冲垮过。 +- 双击确认(菜单约定):过。单击循环+双击确认+上滑帮助+下滑关闭的行为与口令一致,无一次循环跳字或误确认。 diff --git a/research/new8/radio/round1/p2-student.md b/research/new8/radio/round1/p2-student.md new file mode 100644 index 00000000..74b192ec --- /dev/null +++ b/research/new8/radio/round1/p2-student.md @@ -0,0 +1,18 @@ +# P2 · 时间充裕的大学生 评审——《不眠频率》(R1) + +> 视角:23:30,宿舍躺床,戴耳机,音量正常。以下全部基于转写呈现的信息流代入(转写语音时长为估算,±20%,我主要看相对节奏和「我听到了什么」)。 + +### 不眠频率 +- 状态感知:4/5 —— 全程清楚自己夜班的哪个位置:每通电话报线明确(t=14.8「一线来电。七十多岁,雨夜,走丢了。」、t=127.6「三线来电」),上滑随时能听「第几通+口碑档」(t=54.6-60.9 整条帮助+状态链),幕间口碑档位随选择真实变化(t=92.2「不好不坏」→t=122.5「在听众里立住了」),菜单/幕间/结局 BGM 各不相同、一听就知道换了场景(t=63.1 menu、t=92.2 bridge、t=249.4 ending)。扣一分:① 开局上滑听状态,我一个选择都还没做,就听到「今晚的口碑,有点砸了」(t=58.8),那一瞬间我真心以为自己已经搞砸了什么;② t=17.0 电话接通、导播报完线之后突然没声,我躺了 25 秒不知道通话是还在继续还是结束了(t=42.0 才有看门狗「单击屏幕,继续。」捞我)。 +- 下一步明确性:4/5 —— 抉择点永远把两个选项连着手势方向念出来(t=52.5「向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。」),等久了还会自动复述当前选项(t=60.9),菜单自我解说(t=63.1「菜单。单击切换选项,双击确认。」),这套是我玩过的音频叙事里最清楚的。扣分:① 全程没有一句话教过「铃响了单击=接听」,我是拿 t=2.1「单击继续节目」连蒙带猜的,运气好第一下就点对了(t=14.8);② 叙事链末没有即时的「该你点了」信号,只有 25 秒后的看门狗(t=17.0→42.0),第一次撞上时我是真不知道该干嘛。 +- 愿意继续玩:是 —— 台词是真的有设计感:C3 听见的吵架闷响在 C4 变成我替孩子带的话(t=132.9 → t=177.8「四十分钟前,有个孩子问我们:爸妈摔门,是不是她的错。」),这种「我的选择长出了下文」的回环,我这套原神/粥游培养出来的口味就吃这个。 +- P0 问题:开局状态播报在骗我——t=58.8(压力场 t=21.2 同):我刚上滑想看看系统长什么样,还没做任何抉择,听到的却是「今晚的口碑,有点砸了」。我第一反应是「完了我开局是不是得罪人了」,实际上那只是 rep=0 的默认档。新玩家第一次查状态就被判了「砸了」,这属于「不知道自己在哪(现状被误报)」的具体时刻。 +- 建议: + 1. 通话进行中让对白自动续流,单击只留给「跳过」和「抉择」——我是躺在床上「听一档深夜节目」,不是在「翻页读故事」。t=17.0 接通后 25 秒没人说话(t=42.0 看门狗才来),对睡前场景是致命的:我差点就此睡着关机。这是「我愿不愿意连着开几晚」的头号变量。 + 2. 口碑首档措辞改中性,比如「今晚还没开张」——账目可以诚实,但别让零分默认档听起来像差评。 + 3. 收播时口播一句收集进度(比如「这是你的第一夜·『天亮之前』。四种夜晚,还有三种没听过」)——四份转写里从头到尾没有任何一句结局计数播报,「4 结局」这个重玩理由我只在设计文档里知道,游戏自己一个字没提。 + +#### 睡前躺床体验 +- 头 3 分钟节奏:好。t=0 就有人声(「新的夜晚,开始了。」)不哑场,t=2.1 两句话教完核心手势,t=14.8 第一声铃响我就能动手——动手点在 15 秒内。首个正反馈来得很快:t=71.5 选了陪老周哼歌,t=77.7「他稳住了。老歌,比路牌管用。」——开播一分多钟我就体验到「我的一句话改变了陌生人的一夜」,这个首胜含金量比大多数手游开场高。唯一硌牙的就是 t=17.0-42.0 那段「接通了却没人说话」的静默。 +- 故事钩子:立住了。「台里的规矩就一条:先接住人,再接电话」(t=10.6)一句话把世界观和玩法都讲了。全场最佳是跨来电的回环设计:C3 里我听见楼下的闷响吵架(t=132.9 arg_muffled),选择问她(t=139.3),到 C4 这件事变成我递出去的话(t=177.8),C5 还补一刀「头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的」(t=235.8)——五个陌生人的夜真的互相咬合。C5 刹车危机拍(t=223.4 danger 音+急刹,先声 2 秒)吓得我清醒但没被吓弃。p9 那句「带子里其实只有电流声。但空着的那一秒,你说满了」(t=247.2)是我这周听过的最好的台词。说教味基本没有:压力场里选了善意谎言,也只给一句账目式的「你许了个兑不了的现」(stress t=696.1),算轻微爹味,可忍。 +- 结局后想不想开下一局:想,但有限。e1 尾声把今晚每个人的下落逐句点到我头上(t=255.7-264.2,五句回响全对得上我的选择),收得漂亮;压力场对照跑出 e2,「只是路过留言机的时候,你还是没敢看它。红灯灭得很慢」(stress t=1062.5),证明四个结局是真文案差异、不是换标题,这点我验过了,值得给第二次机会。我会为 e3/e4 再开 1-2 局(特别想听低口碑+回避的「静音」能有多丧)。但大概第三局就到头:没有周目成长、没有新机制,二周目和一周目的玩法完全一样,纯当广播剧二刷——对时间充裕的我可以,对大多数人只值一晚。 diff --git a/research/new8/radio/round1/p3-office.md b/research/new8/radio/round1/p3-office.md new file mode 100644 index 00000000..1f5e9add --- /dev/null +++ b/research/new8/radio/round1/p3-office.md @@ -0,0 +1,18 @@ +# P3 评审:《不眠频率》(radio)— 时间紧张的白领 + +> 视角:32 岁,地铁单程 25 分钟,站立单手持机,嘈杂车厢,只玩过消消乐。依据 R1 两份转写(主线 + 乱按压力局)代入「真机听到这些」。 + +### 不眠频率 +- 状态感知:4.5/5 —— 全程知道自己在第几通、口碑什么档:上滑一句话「现在是今晚第一通电话。今晚的口碑,有点砸了。」(t=56.7-58.8s),而且听完自动把选项重播一遍(t=60.9s),我在地铁里漏听了也不慌。扣 0.5 分:乱按局 t=688.9-691.0s,二选一的提示句刚起就被我误触的上滑帮助链顶掉,紧接着结果直接播(「睡吧。明天一睁眼」t=691.0s)——那一瞬间我不知道自己刚选了什么。另外转写 t=0.0s 一次列了 12 个音效资产缺失告警(含 phone_ring),虽然 t=14.8s 铃声实际响了,但如果真机真缺铃声,「该接听了」就只剩 turn 音+轻震,站着的我可能注意不到。 +- 下一步明确性:5/5 —— 没有一秒不知道该干嘛:开场 15 秒内第一声铃就响(t=14.8s),教学句 t=2.1s 就讲了「单击继续节目,选择时左右滑」;链末等我会提醒(压力局多处「单击屏幕,继续」),选不清可以双击重听、上滑复述。全程等待最长的一段是我自己走神(t=17.0-44.1s),25 秒后看门狗一句话把我捞回来——不是游戏的锅。 +- 愿意继续玩:是 —— 一通电话 35-60 秒就是一个完整小故事,一整晚主线 4 分半打完(t=0-266.3s),正好两站地铁,睡前摸鱼也塞得下。 +- P0 问题:乱按局 t=688.9-691.0s——抉择提示句在播时我误滑动,选项话没念完结果就出了,我不知道自己选了什么、为什么这个结果(主线局未出现,但地铁里我恰恰是那个会乱按的人)。其余无。 +- 建议:≤3 条,按对「我愿意玩下去」的影响排序 + 1. 误触的代价太啰嗦:压力局里每乱碰一次就整段重念「单击继续,双击重新听。上滑听状态……」(t=44.5/57.1/67.5/77.9/88.4/98.8s……一字不差重复了十几次),我早会了还念,还把老周的台词往后推。建议教学口令只在开场念,之后上滑只报「第 N 通+口碑」两句。 + 2. 抉择提示句在播时误滑动会静默缓存、播完即生效,遇上我紧接着又上滑就会变成 t=688.9s 那种「选项没听完就出结果」——建议结果句前垫半句「你刚才选了右」之类的回执。 + 3. 菜单太好误开(压力局逛了 38 次菜单),关菜单还会把当前整句来电重播一遍(如 t=503.9s 重播「退不退,都是你的自由」)——关菜单建议不重播全句,只「叮」一声回到等待。 + +#### 通勤场景压力测试 +- 单手能玩吗:能。全部操作就是单击/双击/左右滑/上滑/下滑,没有长按依赖,扶着扶手用拇指够了;全程不需要我出声(怕出声这点满分)。 +- 一通电话是不是完整小乐趣:是。每通 30-60 秒自带起承转合(如 C1:来电 t=14.8s → 二选一 t=52.5s → 「全城听见老人笑了」t=87.9s),幕间垫乐只有 3-5 秒(t=92.2→97.3s),无沉默尴尬期;一整晚 4 分半≈两个站距,被打断就当下车,损失最多半通电话。 +- 被打断后上滑复述够不够:够,而且是我最喜欢的设计。上滑给我「第几通+口碑档」,然后自动把卡住的选项原句重播(主线 t=54.6-63.1s 完整演示),我一句话就捡回局面;就算错过 C3 糖糖那句关键话(t=141.4s),C4 带话时导播会原样转述「四十分钟前,有个孩子问我们……」(t=177.8s)——信息不靠我记性。吵车厢里听不清关键句,双击重听也在(教学 t=2.1s 讲过)。 diff --git a/research/new8/radio/round1/p4-worker.md b/research/new8/radio/round1/p4-worker.md new file mode 100644 index 00000000..83196da5 --- /dev/null +++ b/research/new8/radio/round1/p4-worker.md @@ -0,0 +1,21 @@ +# P4(疲惫的工人)R1 评审 —— 《不眠频率》 + +> 45 岁,工地收工,宿舍 21:00,外放小音量,游戏经验少。 +> 依据:research/new8/radio/round1/transcript-main.md(主线)+ transcript-stress.md(乱按压力局)。全部按「我真机听到这些」代入。 + +### 不眠频率 +- 状态感知:4/5 —— 全程都知道自己在第几通、电话里是谁:导播每通开头都报名(t=14.8「一线来电。七十多岁,雨夜,走丢了。」),上滑能听「现在是今晚第一通电话」(t=56.7),我在菜单里逛一圈回来,它还把刚才那句重播给我(t=69.4),不怕丢地方。扣的一分:t=58.8 我一个字还没说,上滑却听见「今晚的口碑,有点砸了」——我愣了半天,不知道自己啥时候砸的。 +- 下一步明确性:5/5 —— 开场就把家什教齐了(t=2.1「单击继续节目……向左或向右滑」,t=6.4「想跳过这句,单击就行」),每个关口都把两个选项明明白白念给我(t=52.5、t=79.8、t=243.0);我走神 25 秒,它也只轻轻说一句「单击屏幕,继续」(t=42.0),不催命。没出现过不知道该干嘛的时刻。 +- 愿意继续玩:是 —— 规矩一遍就懂,做错不骂人还夸我,收播时五个人的下落一句句念给我听(t=255.7-264.2),像干完活有人递了碗热水。 +- P0 问题: + - 「还没干活先说我砸了」:t=58.8(压力局 t=21.2、t=48.8 一模一样)我还没做任何选择,上滑状态就是「今晚的口碑,有点砸了」。我不知道自己错在哪,像没上工先挨了顿批。后来琢磨着大概是「零分」的意思——可零分咋张嘴就是「砸」呢,开播前该说句「还没开张」这样的中性话才对。 +- 建议: + 1. 口碑 0 档在开播前别念「砸了」,换句中性的(证据 t=58.8)。第一晚就听人说我不行,怪丧气的。 + 2. 头一声铃响之前补一句「铃一响,单击接听」。我这两局都是瞎蒙着点对的(t=14.8 铃响时没人提前说过「接听」俩字,教这句话的提醒得等我干耗 10 秒才来)。 + 3. 乱按的时候,头一回它还张嘴:「这个手势,直播间里用不上」(压力局 t=17.0),往后就只剩「哒哒」两下空震(压力局 t=98.8 一次连震四下,t=176.1 也是四下)。隔一阵子麻烦再用嘴说一次哪儿错了,光震我猜不着。 + +#### 累了一天还能玩吗 +- 规矩是不是一遍讲清:是。t=10.6 一句「台里的规矩就一条:先接住人,再接电话」,加上 t=2.1 的单击和左右滑,统共三句话,往后五通电话没再添一条新规矩。每句都短,喘着气也记得住。 +- 来电者说话会不会太快听不清:不会。一句一句来,句与句之间都留着缝(t=17.0 老人第一句、t=48.2 第二句,中间空好几秒);我走神 25 秒(t=17.0-42.0),它不掐不跳,回来照样接得上。 +- 做错选择后游戏有没有让我难受:主线我全挑的走心那条,没挨过一句重话。压力局我对糖糖撒了个善意的谎,它说「你许了个兑不了的现。孩子睡了,谎留下了」(t=696.1),尾声还补一刀「糖糖后来又打来过一次。还是那句:楼下在吵」(t=1071.0)。没骂我,句句都对着我自己干的事说,就是听完心里沉了一会儿。这不算难受,算分寸。 +- 第一通电话打完有没有「我干得不错」的暖反馈:有,还是两回。t=77.7「他稳住了。老歌,比路牌管用」,挂电话前又一句「就这么接。记住这个手感」(t=90.1)——像老师傅瞅了眼我的活,点了点头。压力局里我头通选了转接派出所,它也说「地址报得清。干净利落」(t=359.9),没挑我做得不够暖心。 diff --git a/research/new8/radio/round1/synthesis.md b/research/new8/radio/round1/synthesis.md new file mode 100644 index 00000000..96b19ec7 --- /dev/null +++ b/research/new8/radio/round1/synthesis.md @@ -0,0 +1,37 @@ +# R1 Synthesis —《不眠频率》(radio) + +> 产品裁定 | 证据源:4 人设评审(p1-blind/p2-student/p3-office/p4-worker)+ 真机试玩报告(live-player.md,会话 radio-20260902072*/0749*)+ 转写(transcript-main/stress.md) + +## 1. R1 裁决 + +| 评审方 | 状态感知 | 下一步明确 | 愿意继续玩 | +|---|---|---|---| +| P1 视障评委 | 4.5/5 | 5/5 | **是** | +| P2 大学生 | 4/5 | 4/5 | **是** | +| P3 白领 | 4.5/5 | 5/5 | **是** | +| P4 工人 | 4/5 | 5/5 | **是** | +| 真机玩家(老墨/墨墨) | — | — | **是**(附修复条件) | + +**结论:5/5 愿意玩,P0 = 0。radio 是批次 1 的达标样板。** 真机两局打出 e1(rep=12 与设计表吻合)与 e4(rep=0 全克制),结局矩阵、跨来电差分(heardArgument/bond)、pendingChoice 盲玩公平、归因真话句全部现场命中。R2 进入打磨轮。 + +## 2. R2 修复清单(按裁定优先级) + +### F 类(本轮必修) +- **F1 (P1,live t=445.4)**:链末等待态的无效手势会**拆掉看门狗且不补挂** → 34.2s 承诺落空静默。修:无效手势不重置/不拆除链末看门狗(或拆后立即重挂)。 +- **F2 (P4/P2,转写 t=58.8)**:开播前口碑 0 档播「有点砸了」——档位文案与账目不符(0 ≠ 负)。修:rep 档位文案改「零起步」语义或调阈值映射。 +- **F3 (P2,live)**:RING 态铃声静默(ring 音效在 SA3 缺失清单)。修:程序合成 ring 循环垫上(可复现脚本),SA3 到位后可替换。 +- **F4 (P2,live)**:ASK 态断点续玩后单击语义陷阱(单击=跳句 vs 确认)。修:续玩恢复后的第一句播完再武装跳句。 +- **F5 (P2,live)**:链末句无跳过保护(玩家跳句后链末判定错位)。修:与 F1 同一提交内复核链末状态机。 + +### V 类(复核后定夺,可能为伪影) +- **V1 (P3,stress 转写 t=688.9)**:「选项句没念完结果就出」。live 实测 pendingChoice 现场命中正常 → 疑为 stress 剧本快速滑动踩到缓存生效边界。dev 在 pendingChoice 生效时序上加断言(播完才生效)后关案;若复现升 P1。 +- **V2 (P1,stress 转写)**:菜单循环 38 次未见第三项「保存并回大厅」。live 未复现;菜单语音/逻辑静态审计无缺失。dev 用 playtest 增加菜单全项循环断言后关案。 +- **V3**:`arg_muffled`/`brake_screech` 两处「声音即世界」设计缺席 = SA3 资产未生成(缺 13 件)。**等待 HF token**;到位即补,接线代码已备。 + +### 仪器改进(不属游戏,记入 R2 协议) +- harness `/session` 会把 `"speed":"1.7"` 字符串归一为 1.0:R2 起压力局传数值 1.7 或 `faster`(live-player.md P3-5)。 + +## 3. R2 轮次协议 +1. dev 修 F1-F5 + V1/V2 断言 → playtest 全绿(断言增量登记 15-radio.md)。 +2. 重新生成主线+压力转写;live 复测 1 局(验证 F1/F3 现场修复)。 +3. 人设复审:P1(看门狗/菜单)+ P3(stress 场景)两人设快速复审,其余 R3 全员复评。 diff --git a/research/new8/radio/round1/transcript-main.md b/research/new8/radio/round1/transcript-main.md new file mode 100644 index 00000000..7b6f2911 --- /dev/null +++ b/research/new8/radio/round1/transcript-main.md @@ -0,0 +1,284 @@ +# 试玩转写:《radio》 + +> 本文件是「玩家感知日志」:一位新手玩家的整局体验转写。语音列出了玩家听到的每句话全文, +> 音效/BGM/震动按发生时间列出。「静默」段 = 玩家什么也听不到。手势是剧本注入的(模拟真人手部动作), +> 非最优操作:包含犹豫、误操作与乱试。语音时长按语速估算(±20%),仅供节奏参考。 +t+ 0.0s (玩家点开游戏,轻触屏幕开始) +t+ 0.0s ♫ BGM 切换 → bgm/onair +t+ 0.0s ♪ 向导音:explore(探索/安全) +t+ 0.0s ▶ 语音:「新的夜晚,开始了。」 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/phone_ring → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/phone_ring → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/phone_ring → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/phone_ring → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 2.1s ♪ 音效 sfx/onair_sting +t+ 2.1s ▶ 向导语音:「单击继续节目。请选择的时候,向左或向右滑。」 +t+ 4.2s ▶ 语音:「午夜零点,红灯亮起。欢迎回到《今夜不眠》。」 +t+ 6.4s ▶ 语音:「想跳过这句,单击就行。」 +t+ 8.5s ▶ 语音:「雨下了一整晚。热线灯,马上就要亮了。」 +t+ 10.6s ▶ 语音:「台里的规矩就一条:先接住人,再接电话。」 +t+ 12.7s ▶ 语音:「今晚五通电话。说什么话,由你决定。」 +t+ 14.8s ♪ 向导音:turn(轮到你) +t+ 14.8s ♪ 音效 sfx/phone_ring +t+ 14.8s 〰 震动:confirm(轻点确认) +t+ 14.8s 【状态】开场口令和台标播完了,第一通电话响了 第 一 通电话打进来:单击接听(BGM=bgm/onair,语速1) + +#### 【玩家动作】铃一响就接——接听是单击 + +t+ 14.8s ♪ 音效 sfx/amb_rain_city +t+ 14.8s ♪ 音效 sfx/line_connect +t+ 14.8s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 14.8s 【状态】线路接通了 第 一 通通话中,正在听对方说话(语音播放中,语速1) +t+ 17.0s 【状态】导播报完线,老人开口了 对方说完了,等我接话(第 一 通):单击继续(BGM=bgm/onair,语速1) +t+ 17.0s ……… 静默 25s(听到一半走了神,在想老人的老花镜) ……… +t+ 42.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 42.0s ▶ 向导语音:「单击屏幕,继续。」 +t+ 44.1s 【状态】听到一声轻响和「单击屏幕,继续」,马上点 对方说完了,等我接话(第 一 通):单击继续(BGM=bgm/onair,语速1) +t+ 44.1s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 48.2s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 52.5s ♪ 向导音:choice(抉择/等待输入) +t+ 52.5s ▶ 语音:「老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。」 + +#### 【玩家动作】老人在发抖,我陪他哼那首老歌——但先上滑听一遍操作和进度 + +t+ 54.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 56.7s ▶ 语音:「现在是今晚第一通电话。」 +t+ 58.8s ▶ 语音:「今晚的口碑,有点砸了。」 +t+ 60.9s ♪ 向导音:choice(抉择/等待输入) +t+ 60.9s ▶ 语音:「老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。」 +t+ 63.1s 【状态】听完帮助+状态链,游戏自动重播了选项 「请选择」念完了(第 一 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】下滑看看菜单有什么 + +t+ 63.1s ♪ 音效 sfx/ui_menu +t+ 63.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 63.1s ♪ 向导音:menu(系统界面) +t+ 63.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 65.2s ▶ 语音:「回到直播。」 +t+ 67.3s 【状态】菜单里,切一项听听 菜单里,当前项「回到直播」:单击切换,双击确认,上滑帮助,下滑关闭(BGM=bgm/menu,语速1) +t+ 67.3s ▶ 语音:「重新开始今晚。」 + +#### 【玩家动作】听完了,下滑关闭菜单,回到选项 + +t+ 69.4s ♪ 音效 sfx/ui_confirm +t+ 69.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 69.4s ♪ 向导音:explore(探索/安全) +t+ 69.4s ♪ 向导音:choice(抉择/等待输入) +t+ 69.4s ▶ 语音:「老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。」 +t+ 71.5s 【状态】选项念完了,做决定 「请选择」念完了(第 一 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】老人在发抖,我陪他哼那首老歌 + +t+ 71.5s ♪ 音效 sfx/ui_confirm +t+ 71.5s ▶ 语音:「大爷别急,这首歌我也会。咱们一句一句来。」 +t+ 73.7s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 77.7s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 79.8s ♪ 向导音:choice(抉择/等待输入) +t+ 79.8s ▶ 语音:「雨声更密了。向左滑,留在原地陪他等巡逻队。向右滑,转接派出所。」 +t+ 82.0s 【状态】选项念完了,做决定 「请选择」念完了(第 一 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】留在原地陪他等巡逻队 + +t+ 82.0s ♪ 音效 sfx/ui_confirm +t+ 82.0s ▶ 语音:「哪儿也不去。我陪你,这首歌还长着呢。」 +t+ 84.1s ▶ 语音:「好,好。有人说话,我就不怕了。」 +t+ 87.9s ▶ 语音:「十五分钟后,巡逻灯停在路口。全城听见老人笑了。」 +t+ 90.1s ▶ 语音:「就这么接。记住这个手感。」 +t+ 92.2s ♪ 向导音:settle(结算) +t+ 92.2s ♪ 音效 sfx/line_drop +t+ 92.2s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 92.2s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 94.3s 【状态】幕间,口碑播报和垫乐,等下一通 幕间喘息,垫乐播放中,马上接下一通(BGM=bgm/bridge,语速1) +t+ 97.3s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 97.3s ♪ 向导音:turn(轮到你) +t+ 97.3s ♪ 音效 sfx/phone_ring +t+ 97.3s 〰 震动:confirm(轻点确认) +t+ 97.3s 【状态】第 二 通电话响了,接 第 二 通电话打进来:单击接听(BGM=bgm/onair,语速1) +t+ 97.3s ♪ 音效 sfx/amb_station +t+ 97.3s ♪ 音效 sfx/line_connect +t+ 97.3s ▶ 语音:「二线来电。姑娘,声音很轻,像怕吵到谁。」 +t+ 99.4s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 105.3s ▶ 语音:「票是回去看我妈的。可我试镜又黄了,两手空空……」 +t+ 110.9s ♪ 向导音:choice(抉择/等待输入) +t+ 110.9s ▶ 语音:「向左滑,告诉她这趟车值得上。向右滑,把选择还给她。」 +t+ 113.0s 【状态】选项念完了,做决定 「请选择」念完了(第 二 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】小鹿妈妈等的是那声「妈」,劝她上车 + +t+ 113.0s ♪ 音效 sfx/ui_confirm +t+ 113.0s ▶ 语音:「你妈等的不是奖杯,是你进门喊的那声妈。」 +t+ 115.1s ▶ 语音:「呜……嗯。不退了,我现在就去检票。谢谢您。」 +t+ 120.3s ▶ 语音:「二线收工。这句话,值一个头条。」 +t+ 122.5s ♪ 向导音:settle(结算) +t+ 122.5s ♪ 音效 sfx/line_drop +t+ 122.5s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 122.5s ▶ 语音:「今晚的口碑,在听众里立住了。」 +t+ 124.6s 【状态】幕间,口碑播报和垫乐,等下一通 幕间喘息,垫乐播放中,马上接下一通(BGM=bgm/bridge,语速1) +t+ 127.6s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 127.6s ♪ 向导音:turn(轮到你) +t+ 127.6s ♪ 音效 sfx/phone_ring +t+ 127.6s 〰 震动:confirm(轻点确认) +t+ 127.6s 【状态】第 三 通电话响了,接 第 三 通电话打进来:单击接听(BGM=bgm/onair,语速1) +t+ 127.6s ♪ 音效 sfx/amb_kid_room +t+ 127.6s ♪ 音效 sfx/line_connect +t+ 127.6s ▶ 语音:「三线来电。听呼吸,是个小孩。」 +t+ 129.7s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 132.9s ♪ 音效 sfx/arg_muffled +t+ 132.9s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 137.2s ♪ 向导音:choice(抉择/等待输入) +t+ 137.2s ▶ 语音:「向左滑,唱一段摇篮曲哄她睡。向右滑,轻轻问她怎么了。」 +t+ 139.3s 【状态】选项念完了,做决定 「请选择」念完了(第 三 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】糖糖像是憋着事,我轻轻问她怎么了 + +t+ 139.3s ♪ 音效 sfx/ui_confirm +t+ 139.3s ▶ 语音:「睡不着的时候,心里是不是装着事?说给我听听。」 +t+ 141.4s ▶ 语音:「爸爸妈妈摔门了。是不是……我做错了什么呀。」 +t+ 146.6s ♪ 向导音:choice(抉择/等待输入) +t+ 146.6s ▶ 语音:「孩子的声音在抖。向左滑,告诉她这不是她的错。向右滑,答应她明天都会好。」 +t+ 148.7s 【状态】选项念完了,做决定 「请选择」念完了(第 三 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】告诉她这不是她的错——大人的事不算在孩子头上 + +t+ 148.7s ♪ 音效 sfx/ui_confirm +t+ 148.7s ▶ 语音:「糖糖,记住,大人的事,不是小孩的错。一个字都不是。」 +t+ 150.8s ▶ 语音:「嗯……那我睡了。他们明天,会一起给我切蛋糕吗?」 +t+ 156.5s ▶ 语音:「蛋糕。记下来——四线的来电,可能跟这个家有关。」 +t+ 158.6s ♪ 向导音:settle(结算) +t+ 158.6s ♪ 音效 sfx/line_drop +t+ 158.6s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 158.6s 【状态】幕间,口碑播报和垫乐,等下一通 幕间喘息,垫乐播放中,马上接下一通(BGM=bgm/bridge,语速1) +t+ 161.6s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 161.6s ♪ 向导音:turn(轮到你) +t+ 161.6s ♪ 音效 sfx/phone_ring +t+ 161.6s 〰 震动:confirm(轻点确认) +t+ 161.6s 【状态】第 四 通电话响了,接 第 四 通电话打进来:单击接听(BGM=bgm/onair,语速1) +t+ 161.6s ♪ 音效 sfx/amb_street_rain +t+ 161.6s ♪ 音效 sfx/line_connect +t+ 161.6s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 163.7s ▶ 语音:「主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。」 +t+ 170.5s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 175.6s ♪ 向导音:choice(抉择/等待输入) +t+ 175.6s ▶ 语音:「摔门声你听见了。向左滑,替孩子给她带句话。向右滑,劝她回家把话说开。」 +t+ 177.8s 【状态】选项念完了,做决定 「请选择」念完了(第 四 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】陈姐听见孩子问话了,我把话带回去 + +t+ 177.8s ♪ 音效 sfx/ui_confirm +t+ 177.8s ▶ 语音:「四十分钟前,有个孩子问我们:爸妈摔门,是不是她的错。」 +t+ 179.9s ▶ 语音:「什么?她还没睡?这么晚了,她还醒着?」 +t+ 184.4s ▶ 语音:「她攥着电话,很久没出声。风都停了。」 +t+ 186.5s ♪ 向导音:choice(抉择/等待输入) +t+ 186.5s ▶ 语音:「她的呼吸平了。向左滑,留在直播里,让全城等这个门铃。向右滑,换垫乐,私下陪她说完。」 +t+ 188.6s 【状态】选项念完了,做决定 「请选择」念完了(第 四 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】留在直播里,让全城等这声门铃 + +t+ 188.6s ♪ 音效 sfx/ui_confirm +t+ 188.6s ▶ 语音:「陈姐,别挂。让他接电话——全城都在听。」 +t+ 190.8s ▶ 语音:「喂!回来吧!蛋糕要化了,孩子等着呢!」 +t+ 195.3s ▶ 语音:「电话那头,传来跑步声。今晚的高光时刻。」 +t+ 197.4s ♪ 向导音:settle(结算) +t+ 197.4s ♪ 音效 sfx/line_drop +t+ 197.4s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 197.4s 【状态】幕间,口碑播报和垫乐,等下一通 幕间喘息,垫乐播放中,马上接下一通(BGM=bgm/bridge,语速1) +t+ 200.4s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 200.4s ♪ 向导音:turn(轮到你) +t+ 200.4s ♪ 音效 sfx/phone_ring +t+ 200.4s 〰 震动:confirm(轻点确认) +t+ 200.4s 【状态】第 五 通电话响了,接 第 五 通电话打进来:单击接听(BGM=bgm/onair,语速1) +t+ 200.4s ♪ 音效 sfx/amb_cab +t+ 200.4s ♪ 音效 sfx/line_connect +t+ 200.4s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 202.5s ▶ 语音:「主播,别挂。陪我说话,我的眼皮,沉得很。」 +t+ 207.5s ▶ 语音:「跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。」 +t+ 213.1s ♪ 向导音:choice(抉择/等待输入) +t+ 213.1s ▶ 语音:「方向盘比话筒危险。向左滑,陪他聊聊闺女。向右滑,让他立刻进服务区。」 +t+ 215.2s 【状态】选项念完了,做决定 「请选择」念完了(第 五 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】老韩眼皮在打架,我陪他聊聊闺女 + +t+ 215.2s ♪ 音效 sfx/ui_confirm +t+ 215.2s ▶ 语音:「说说她。开学第一天,她跟你说的头一句话,是什么?」 +t+ 217.4s ▶ 语音:「她说,爸,我在学校给你赚了朵小红花。哈哈,哈哈……」 +t+ 223.4s ♪ 向导音:danger(危险) +t+ 223.4s ♪ 音效 sfx/brake_screech +t+ 223.4s 〰 震动:danger(危险逼近) +t+ 225.4s ▶ 语音:「车道偏了——听!」 +t+ 227.6s ▶ 语音:「嘶——好险!前车急刹,我擦着护栏过去了。彻底醒了!」 +t+ 233.6s ▶ 语音:「吓醒的,也算醒。这一趟,聊到家。」 +t+ 235.8s ▶ 语音:「对了。头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的。」 +t+ 237.9s ♪ 向导音:settle(结算) +t+ 237.9s ♪ 音效 sfx/line_drop +t+ 237.9s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 237.9s 【状态】幕间,口碑播报和垫乐,等下一通 幕间喘息,垫乐播放中,马上接下一通(BGM=bgm/bridge,语速1) +t+ 240.9s ▶ 语音:「五线收工。垫乐起——零点档,还剩最后一段。」 +t+ 243.0s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 243.0s ♪ 向导音:choice(抉择/等待输入) +t+ 243.0s ▶ 语音:「留言机红灯在闪,是老主播三年前没播完的那句晚安。向左滑,替他播出去。向右滑,照常收播。」 +t+ 245.1s 【状态】选项念完了,做决定 「请选择」念完了(第 五 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】留言机红灯在闪——直面它,替老主播把晚安播出去 + +t+ 245.1s ♪ 音效 sfx/ui_confirm +t+ 245.1s ♪ 音效 sfx/tape_static +t+ 245.1s ▶ 语音:「师父,三年了。这句晚安,我替您播。各位,晚安。」 +t+ 247.2s ▶ 语音:「带子里其实只有电流声。但空着的那一秒,你说满了。」 +t+ 249.4s ♪ 向导音:settle(结算) +t+ 249.4s ♫ BGM 切换 → bgm/ending(淡入 1500ms) +t+ 249.4s ▶ 语音:「天亮了,热线灯还亮着。你们的晚安,是这座城市的心跳。」 +t+ 251.5s ▶ 语音:「这把话筒,我总算接得住了。明晚零点,接着不眠。」 +t+ 253.6s ▶ 语音:「收播了。导播间替你记了一笔账——今晚的人都怎样了。」 +t+ 255.7s ▶ 语音:「老周:孙子替他买了老花镜。回信说,谢谢那首歌。」 +t+ 257.8s ▶ 语音:「小鹿上车了。她妈后来打电话说,那声妈,比奖杯值钱。」 +t+ 260.0s ▶ 语音:「糖糖家的门,这个月没再摔过。她说这是主播魔法。」 +t+ 262.1s ▶ 语音:「陈姐家切了蛋糕。那句「回来吧」,是我这晚听过最好的声音。」 +t+ 264.2s ▶ 语音:「老韩到家了。后视镜上,多了朵小红花。」 +t+ 266.3s 【状态】结局链走完,五条回响一句句点到我今晚的每一次选择 结局和五条回响都听完了。单击再来一局,下滑开菜单(BGM=bgm/ending,语速1) + +#### 【玩家动作】听完尾声那笔账——今晚的人都接住了,会话结束(E1 天亮之前) + diff --git a/research/new8/radio/round1/transcript-stress.md b/research/new8/radio/round1/transcript-stress.md new file mode 100644 index 00000000..48964932 --- /dev/null +++ b/research/new8/radio/round1/transcript-stress.md @@ -0,0 +1,851 @@ +# 试玩转写:《radio》 + +> 本文件是「玩家感知日志」:一位新手玩家的整局体验转写。语音列出了玩家听到的每句话全文, +> 音效/BGM/震动按发生时间列出。「静默」段 = 玩家什么也听不到。手势是剧本注入的(模拟真人手部动作), +> 非最优操作:包含犹豫、误操作与乱试。语音时长按语速估算(±20%),仅供节奏参考。 +t+ 0.0s (玩家点开游戏,轻触屏幕开始) +t+ 0.0s ♫ BGM 切换 → bgm/onair +t+ 0.0s ♪ 向导音:explore(探索/安全) +t+ 0.0s ▶ 语音:「新的夜晚,开始了。」 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/phone_ring → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/phone_ring → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/phone_ring → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/phone_ring → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 2.1s ♪ 音效 sfx/onair_sting +t+ 2.1s ▶ 向导语音:「单击继续节目。请选择的时候,向左或向右滑。」 +t+ 4.2s ▶ 语音:「午夜零点,红灯亮起。欢迎回到《今夜不眠》。」 +t+ 6.4s ▶ 语音:「想跳过这句,单击就行。」 +t+ 8.5s ▶ 语音:「雨下了一整晚。热线灯,马上就要亮了。」 +t+ 10.6s ▶ 语音:「台里的规矩就一条:先接住人,再接电话。」 +t+ 12.7s ▶ 语音:「今晚五通电话。说什么话,由你决定。」 +t+ 14.8s ♪ 向导音:turn(轮到你) +t+ 14.8s ♪ 音效 sfx/phone_ring +t+ 14.8s 〰 震动:confirm(轻点确认) +t+ 14.8s ♪ 音效 sfx/amb_rain_city +t+ 14.8s ♪ 音效 sfx/line_connect +t+ 14.8s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 17.0s ▶ 向导语音:「这个手势,直播间里用不上。」 +t+ 17.0s 〰 震动:error(做错了:两连短震) +t+ 17.0s ▶ 向导语音(对白在播,本句按 idle-only 跳过未播):「这个手势,直播间里用不上。」 +t+ 17.0s 〰 震动:error(做错了:两连短震) +t+ 17.0s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 19.1s ▶ 语音:「现在是今晚第一通电话。」 +t+ 21.2s ▶ 语音:「今晚的口碑,有点砸了。」 +t+ 23.3s ♪ 音效 sfx/amb_rain_city +t+ 23.3s ♪ 音效 sfx/line_connect +t+ 23.3s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 25.4s ♪ 音效 sfx/ui_menu +t+ 25.4s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 25.4s ♪ 向导音:menu(系统界面) +t+ 25.4s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 27.6s ▶ 语音:「回到直播。」 +t+ 29.7s ▶ 语音:「重新开始今晚。」 +t+ 31.8s ♪ 音效 sfx/ui_confirm +t+ 31.8s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 31.8s ♪ 向导音:explore(探索/安全) +t+ 31.8s ♪ 音效 sfx/amb_rain_city +t+ 31.8s ♪ 音效 sfx/line_connect +t+ 31.8s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 33.9s 〰 震动:error(做错了:两连短震) +t+ 33.9s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 36.0s 〰 震动:error(做错了:两连短震) +t+ 36.0s 〰 震动:error(做错了:两连短震) +t+ 36.0s ♪ 音效 sfx/ui_menu +t+ 36.0s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 36.0s ♪ 向导音:menu(系统界面) +t+ 36.0s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 38.2s ▶ 语音:「回到直播。」 +t+ 40.3s ▶ 语音:「重新开始今晚。」 +t+ 42.4s ♪ 音效 sfx/ui_confirm +t+ 42.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 42.4s ♪ 向导音:explore(探索/安全) +t+ 42.4s ♪ 音效 sfx/amb_rain_city +t+ 42.4s ♪ 音效 sfx/line_connect +t+ 42.4s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 44.5s 〰 震动:error(做错了:两连短震) +t+ 44.5s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 46.6s ▶ 语音:「现在是今晚第一通电话。」 +t+ 48.8s ▶ 语音:「今晚的口碑,有点砸了。」 +t+ 50.9s ♪ 音效 sfx/amb_rain_city +t+ 50.9s ♪ 音效 sfx/line_connect +t+ 50.9s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 53.0s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 57.1s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 59.2s ▶ 语音:「现在是今晚第一通电话。」 +t+ 61.3s ▶ 语音:「今晚的口碑,有点砸了。」 +t+ 63.4s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 67.5s 〰 震动:error(做错了:两连短震) +t+ 67.5s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 69.6s ▶ 语音:「现在是今晚第一通电话。」 +t+ 71.8s ▶ 语音:「今晚的口碑,有点砸了。」 +t+ 73.9s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 77.9s 〰 震动:error(做错了:两连短震) +t+ 77.9s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 80.1s ▶ 语音:「现在是今晚第一通电话。」 +t+ 82.2s ▶ 语音:「今晚的口碑,有点砸了。」 +t+ 84.3s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 88.4s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 90.5s ▶ 语音:「现在是今晚第一通电话。」 +t+ 92.6s ▶ 语音:「今晚的口碑,有点砸了。」 +t+ 94.7s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 98.8s 〰 震动:error(做错了:两连短震) +t+ 98.8s 〰 震动:error(做错了:两连短震) +t+ 98.8s 〰 震动:error(做错了:两连短震) +t+ 98.8s 〰 震动:error(做错了:两连短震) +t+ 98.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 100.9s ▶ 语音:「现在是今晚第一通电话。」 +t+ 103.1s ▶ 语音:「今晚的口碑,有点砸了。」 +t+ 105.2s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 109.3s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 113.3s ♪ 音效 sfx/ui_menu +t+ 113.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 113.3s ♪ 向导音:menu(系统界面) +t+ 113.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 115.4s ▶ 语音:「回到直播。」 +t+ 117.6s ▶ 语音:「重新开始今晚。」 +t+ 119.7s ♪ 音效 sfx/ui_confirm +t+ 119.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 119.7s ♪ 向导音:explore(探索/安全) +t+ 119.7s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 123.8s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 127.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 130.0s ▶ 语音:「现在是今晚第一通电话。」 +t+ 132.1s ▶ 语音:「今晚的口碑,有点砸了。」 +t+ 134.2s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 138.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 140.4s ▶ 语音:「现在是今晚第一通电话。」 +t+ 142.5s ▶ 语音:「今晚的口碑,有点砸了。」 +t+ 144.6s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 148.7s 〰 震动:error(做错了:两连短震) +t+ 148.7s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 152.8s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 156.9s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 161.2s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 165.5s ♪ 音效 sfx/ui_menu +t+ 165.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 165.5s ♪ 向导音:menu(系统界面) +t+ 165.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 167.6s ▶ 语音:「回到直播。」 +t+ 169.7s ▶ 语音:「重新开始今晚。」 +t+ 171.8s ♪ 音效 sfx/ui_confirm +t+ 171.8s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 171.8s ♪ 向导音:explore(探索/安全) +t+ 171.8s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 176.1s 〰 震动:error(做错了:两连短震) +t+ 176.1s 〰 震动:error(做错了:两连短震) +t+ 176.1s 〰 震动:error(做错了:两连短震) +t+ 176.1s 〰 震动:error(做错了:两连短震) +t+ 176.1s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 180.4s ♪ 音效 sfx/ui_menu +t+ 180.4s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 180.4s ♪ 向导音:menu(系统界面) +t+ 180.4s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 182.5s ▶ 语音:「回到直播。」 +t+ 184.7s ▶ 语音:「重新开始今晚。」 +t+ 186.8s ♪ 音效 sfx/ui_confirm +t+ 186.8s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 186.8s ♪ 向导音:explore(探索/安全) +t+ 186.8s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 191.1s ♪ 向导音:choice(抉择/等待输入) +t+ 191.1s ▶ 语音:「老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。」 +t+ 193.2s ♪ 音效 sfx/ui_confirm +t+ 193.2s ▶ 语音:「大爷别急,这首歌我也会。咱们一句一句来。」 +t+ 195.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 197.4s ▶ 语音:「现在是今晚第一通电话。」 +t+ 199.6s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 201.7s ▶ 语音:「大爷别急,这首歌我也会。咱们一句一句来。」 +t+ 203.8s ♪ 音效 sfx/ui_menu +t+ 203.8s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 203.8s ♪ 向导音:menu(系统界面) +t+ 203.8s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 205.9s ▶ 语音:「回到直播。」 +t+ 208.0s ▶ 语音:「重新开始今晚。」 +t+ 210.2s ♪ 音效 sfx/ui_confirm +t+ 210.2s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 210.2s ♪ 向导音:explore(探索/安全) +t+ 210.2s ▶ 语音:「大爷别急,这首歌我也会。咱们一句一句来。」 +t+ 212.3s 〰 震动:error(做错了:两连短震) +t+ 212.3s ♪ 音效 sfx/ui_menu +t+ 212.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 212.3s ♪ 向导音:menu(系统界面) +t+ 212.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 214.4s ▶ 语音:「回到直播。」 +t+ 216.5s ▶ 语音:「重新开始今晚。」 +t+ 218.6s ♪ 音效 sfx/ui_confirm +t+ 218.6s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 218.6s ♪ 向导音:explore(探索/安全) +t+ 218.6s ▶ 语音:「大爷别急,这首歌我也会。咱们一句一句来。」 +t+ 220.8s 〰 震动:error(做错了:两连短震) +t+ 220.8s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 224.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 226.9s ▶ 语音:「现在是今晚第一通电话。」 +t+ 229.1s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 231.2s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 235.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 237.4s ▶ 语音:「现在是今晚第一通电话。」 +t+ 239.5s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 241.6s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 245.7s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 247.8s ▶ 语音:「现在是今晚第一通电话。」 +t+ 249.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 252.1s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 256.1s 〰 震动:error(做错了:两连短震) +t+ 256.1s ♪ 音效 sfx/ui_menu +t+ 256.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 256.1s ♪ 向导音:menu(系统界面) +t+ 256.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 258.3s ▶ 语音:「回到直播。」 +t+ 260.4s ▶ 语音:「重新开始今晚。」 +t+ 262.5s ♪ 音效 sfx/ui_confirm +t+ 262.5s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 262.5s ♪ 向导音:explore(探索/安全) +t+ 262.5s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 266.6s 〰 震动:error(做错了:两连短震) +t+ 266.6s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 268.7s 〰 震动:error(做错了:两连短震) +t+ 268.7s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 270.8s ▶ 语音:「现在是今晚第一通电话。」 +t+ 272.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 275.0s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 277.2s 〰 震动:error(做错了:两连短震) +t+ 277.2s 〰 震动:error(做错了:两连短震) +t+ 277.2s 〰 震动:error(做错了:两连短震) +t+ 277.2s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 279.3s ♪ 音效 sfx/ui_menu +t+ 279.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 279.3s ♪ 向导音:menu(系统界面) +t+ 279.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 281.4s ▶ 语音:「回到直播。」 +t+ 283.5s ▶ 语音:「重新开始今晚。」 +t+ 285.6s ♪ 音效 sfx/ui_confirm +t+ 285.6s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 285.6s ♪ 向导音:explore(探索/安全) +t+ 285.6s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 287.8s 〰 震动:error(做错了:两连短震) +t+ 287.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 289.9s ▶ 语音:「现在是今晚第一通电话。」 +t+ 292.0s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 294.1s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 296.2s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 298.4s ♪ 音效 sfx/ui_menu +t+ 298.4s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 298.4s ♪ 向导音:menu(系统界面) +t+ 298.4s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 300.5s ▶ 语音:「回到直播。」 +t+ 302.6s ▶ 语音:「重新开始今晚。」 +t+ 304.7s ♪ 音效 sfx/ui_confirm +t+ 304.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 304.7s ♪ 向导音:explore(探索/安全) +t+ 304.7s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 306.8s ♪ 向导音:choice(抉择/等待输入) +t+ 306.8s ▶ 语音:「雨声更密了。向左滑,留在原地陪他等巡逻队。向右滑,转接派出所。」 +t+ 309.0s ♪ 音效 sfx/ui_confirm +t+ 309.0s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 311.1s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 313.2s ▶ 语音:「现在是今晚第一通电话。」 +t+ 315.3s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 317.4s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 319.6s 〰 震动:error(做错了:两连短震) +t+ 319.6s 〰 震动:error(做错了:两连短震) +t+ 319.6s ♪ 音效 sfx/ui_menu +t+ 319.6s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 319.6s ♪ 向导音:menu(系统界面) +t+ 319.6s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 321.7s ▶ 语音:「回到直播。」 +t+ 323.8s ▶ 语音:「重新开始今晚。」 +t+ 325.9s ♪ 音效 sfx/ui_confirm +t+ 325.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 325.9s ♪ 向导音:explore(探索/安全) +t+ 325.9s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 328.0s ♪ 音效 sfx/ui_menu +t+ 328.0s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 328.0s ♪ 向导音:menu(系统界面) +t+ 328.0s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 330.2s ▶ 语音:「回到直播。」 +t+ 332.3s ▶ 语音:「重新开始今晚。」 +t+ 334.4s ♪ 音效 sfx/ui_confirm +t+ 334.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 334.4s ♪ 向导音:explore(探索/安全) +t+ 334.4s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 336.5s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 338.6s 〰 震动:error(做错了:两连短震) +t+ 338.6s ♪ 音效 sfx/ui_menu +t+ 338.6s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 338.6s ♪ 向导音:menu(系统界面) +t+ 338.6s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 340.8s ▶ 语音:「回到直播。」 +t+ 342.9s ▶ 语音:「重新开始今晚。」 +t+ 345.0s ♪ 音效 sfx/ui_confirm +t+ 345.0s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 345.0s ♪ 向导音:explore(探索/安全) +t+ 345.0s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 347.1s 〰 震动:error(做错了:两连短震) +t+ 347.1s ▶ 语音:「好,好。谢谢你啊,谢谢。」 +t+ 350.3s ♪ 音效 sfx/ui_menu +t+ 350.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 350.3s ♪ 向导音:menu(系统界面) +t+ 350.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 352.4s ▶ 语音:「回到直播。」 +t+ 354.6s ▶ 语音:「重新开始今晚。」 +t+ 356.7s ♪ 音效 sfx/ui_confirm +t+ 356.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 356.7s ♪ 向导音:explore(探索/安全) +t+ 356.7s ▶ 语音:「好,好。谢谢你啊,谢谢。」 +t+ 359.9s ▶ 语音:「转接完成,地址报得清。干净利落。」 +t+ 362.0s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 364.1s ▶ 语音:「现在是今晚第一通电话。」 +t+ 366.2s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 368.3s ▶ 语音:「转接完成,地址报得清。干净利落。」 +t+ 370.5s ▶ 语音:「就这么接。记住这个手感。」 +t+ 372.6s ♪ 向导音:settle(结算) +t+ 372.6s ♪ 音效 sfx/line_drop +t+ 372.6s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 372.6s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 377.7s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 377.7s ♪ 向导音:turn(轮到你) +t+ 377.7s ♪ 音效 sfx/phone_ring +t+ 377.7s 〰 震动:confirm(轻点确认) +t+ 377.7s ♪ 音效 sfx/amb_station +t+ 377.7s ♪ 音效 sfx/line_connect +t+ 377.7s ▶ 语音:「二线来电。姑娘,声音很轻,像怕吵到谁。」 +t+ 379.8s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 385.7s 〰 震动:error(做错了:两连短震) +t+ 385.7s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 387.8s ▶ 语音:「现在是今晚第二通电话。」 +t+ 389.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 392.0s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 397.9s ♪ 音效 sfx/ui_menu +t+ 397.9s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 397.9s ♪ 向导音:menu(系统界面) +t+ 397.9s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 400.0s ▶ 语音:「回到直播。」 +t+ 402.1s ▶ 语音:「重新开始今晚。」 +t+ 404.2s ♪ 音效 sfx/ui_confirm +t+ 404.2s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 404.2s ♪ 向导音:explore(探索/安全) +t+ 404.2s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 410.1s ♪ 音效 sfx/ui_menu +t+ 410.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 410.1s ♪ 向导音:menu(系统界面) +t+ 410.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 412.2s ▶ 语音:「回到直播。」 +t+ 414.3s ▶ 语音:「重新开始今晚。」 +t+ 416.5s ♪ 音效 sfx/ui_confirm +t+ 416.5s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 416.5s ♪ 向导音:explore(探索/安全) +t+ 416.5s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 422.3s 〰 震动:error(做错了:两连短震) +t+ 422.3s 〰 震动:error(做错了:两连短震) +t+ 422.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 424.4s ▶ 语音:「现在是今晚第二通电话。」 +t+ 426.6s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 428.7s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 434.5s ♪ 音效 sfx/ui_menu +t+ 434.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 434.5s ♪ 向导音:menu(系统界面) +t+ 434.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 436.6s ▶ 语音:「回到直播。」 +t+ 438.8s ▶ 语音:「重新开始今晚。」 +t+ 440.9s ♪ 音效 sfx/ui_confirm +t+ 440.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 440.9s ♪ 向导音:explore(探索/安全) +t+ 440.9s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 446.7s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 452.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 454.7s ▶ 语音:「现在是今晚第二通电话。」 +t+ 456.8s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 459.0s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 464.8s 〰 震动:error(做错了:两连短震) +t+ 464.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 466.9s ▶ 语音:「现在是今晚第二通电话。」 +t+ 469.0s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 471.2s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 477.0s ▶ 语音:「票是回去看我妈的。可我试镜又黄了,两手空空……」 +t+ 482.7s ♪ 向导音:choice(抉择/等待输入) +t+ 482.7s ▶ 语音:「向左滑,告诉她这趟车值得上。向右滑,把选择还给她。」 +t+ 484.8s ♪ 音效 sfx/ui_confirm +t+ 484.8s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 486.9s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 489.0s ▶ 语音:「现在是今晚第二通电话。」 +t+ 491.1s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 493.3s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 495.4s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 497.5s ♪ 音效 sfx/ui_menu +t+ 497.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 497.5s ♪ 向导音:menu(系统界面) +t+ 497.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 499.6s ▶ 语音:「回到直播。」 +t+ 501.7s ▶ 语音:「重新开始今晚。」 +t+ 503.9s ♪ 音效 sfx/ui_confirm +t+ 503.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 503.9s ♪ 向导音:explore(探索/安全) +t+ 503.9s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 506.0s ♪ 音效 sfx/ui_menu +t+ 506.0s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 506.0s ♪ 向导音:menu(系统界面) +t+ 506.0s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 508.1s ▶ 语音:「回到直播。」 +t+ 510.2s ▶ 语音:「重新开始今晚。」 +t+ 512.3s ♪ 音效 sfx/ui_confirm +t+ 512.3s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 512.3s ♪ 向导音:explore(探索/安全) +t+ 512.3s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 514.5s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 516.6s ▶ 语音:「嗯……我再想想。窗口要关了,我先过去了。」 +t+ 521.5s 〰 震动:error(做错了:两连短震) +t+ 521.5s ▶ 语音:「她走了。没上车,也没退票。年轻人的夜,长了点。」 +t+ 523.7s ♪ 向导音:settle(结算) +t+ 523.7s ♪ 音效 sfx/line_drop +t+ 523.7s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 526.7s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 526.7s ♪ 向导音:turn(轮到你) +t+ 526.7s ♪ 音效 sfx/phone_ring +t+ 526.7s 〰 震动:confirm(轻点确认) +t+ 526.7s ♪ 音效 sfx/amb_kid_room +t+ 526.7s ♪ 音效 sfx/line_connect +t+ 526.7s ▶ 语音:「三线来电。听呼吸,是个小孩。」 +t+ 528.8s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 532.0s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 535.2s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 538.3s 〰 震动:error(做错了:两连短震) +t+ 538.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 540.5s ▶ 语音:「现在是今晚第三通电话。」 +t+ 542.6s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 544.7s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 547.9s ♪ 音效 sfx/ui_menu +t+ 547.9s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 547.9s ♪ 向导音:menu(系统界面) +t+ 547.9s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 550.0s ▶ 语音:「回到直播。」 +t+ 552.1s ▶ 语音:「重新开始今晚。」 +t+ 554.2s ♪ 音效 sfx/ui_confirm +t+ 554.2s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 554.2s ♪ 向导音:explore(探索/安全) +t+ 554.2s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 557.4s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 560.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 562.7s ▶ 语音:「现在是今晚第三通电话。」 +t+ 564.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 567.0s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 570.2s ♪ 音效 sfx/arg_muffled +t+ 570.2s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 574.5s ♪ 音效 sfx/ui_menu +t+ 574.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 574.5s ♪ 向导音:menu(系统界面) +t+ 574.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 576.6s ▶ 语音:「回到直播。」 +t+ 578.7s ▶ 语音:「重新开始今晚。」 +t+ 580.8s ♪ 音效 sfx/ui_confirm +t+ 580.8s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 580.8s ♪ 向导音:explore(探索/安全) +t+ 580.8s ♪ 音效 sfx/arg_muffled +t+ 580.8s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 585.1s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 587.2s ▶ 语音:「现在是今晚第三通电话。」 +t+ 587.3s ♪ 音效 sfx/arg_muffled +t+ 589.4s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 591.5s ♪ 音效 sfx/arg_muffled +t+ 591.5s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 595.8s 〰 震动:error(做错了:两连短震) +t+ 595.8s 〰 震动:error(做错了:两连短震) +t+ 595.8s 〰 震动:error(做错了:两连短震) +t+ 595.8s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 598.0s ♪ 音效 sfx/arg_muffled +t+ 600.1s 〰 震动:error(做错了:两连短震) +t+ 600.1s 〰 震动:error(做错了:两连短震) +t+ 600.1s ♪ 音效 sfx/ui_menu +t+ 600.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 600.1s ♪ 向导音:menu(系统界面) +t+ 600.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 602.2s ▶ 语音:「回到直播。」 +t+ 604.3s ▶ 语音:「重新开始今晚。」 +t+ 606.4s ♪ 音效 sfx/ui_confirm +t+ 606.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 606.4s ♪ 向导音:explore(探索/安全) +t+ 606.4s ♪ 音效 sfx/arg_muffled +t+ 606.4s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 610.7s 〰 震动:error(做错了:两连短震) +t+ 610.7s 〰 震动:error(做错了:两连短震) +t+ 610.7s ♪ 音效 sfx/ui_menu +t+ 610.7s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 610.7s ♪ 向导音:menu(系统界面) +t+ 610.7s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 612.9s ▶ 语音:「回到直播。」 +t+ 615.0s ▶ 语音:「重新开始今晚。」 +t+ 617.1s ♪ 音效 sfx/ui_confirm +t+ 617.1s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 617.1s ♪ 向导音:explore(探索/安全) +t+ 617.1s ♪ 音效 sfx/arg_muffled +t+ 617.1s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 621.4s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 623.5s ▶ 语音:「现在是今晚第三通电话。」 +t+ 623.6s ♪ 音效 sfx/arg_muffled +t+ 625.6s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 627.8s ♪ 音效 sfx/arg_muffled +t+ 627.8s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 632.0s ♪ 音效 sfx/ui_menu +t+ 632.0s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 632.0s ♪ 向导音:menu(系统界面) +t+ 632.0s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 634.2s ▶ 语音:「回到直播。」 +t+ 636.3s ▶ 语音:「重新开始今晚。」 +t+ 638.4s ♪ 音效 sfx/ui_confirm +t+ 638.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 638.4s ♪ 向导音:explore(探索/安全) +t+ 638.4s ♪ 音效 sfx/arg_muffled +t+ 638.4s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 642.7s ♪ 向导音:choice(抉择/等待输入) +t+ 642.7s ▶ 语音:「向左滑,唱一段摇篮曲哄她睡。向右滑,轻轻问她怎么了。」 +t+ 644.8s ♪ 音效 sfx/ui_confirm +t+ 644.8s ▶ 语音:「睡不着的时候,心里是不是装着事?说给我听听。」 +t+ 646.9s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 649.1s ▶ 语音:「现在是今晚第三通电话。」 +t+ 651.2s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 653.3s ▶ 语音:「睡不着的时候,心里是不是装着事?说给我听听。」 +t+ 655.4s ▶ 语音:「爸爸妈妈摔门了。是不是……我做错了什么呀。」 +t+ 660.6s ♪ 音效 sfx/ui_menu +t+ 660.6s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 660.6s ♪ 向导音:menu(系统界面) +t+ 660.6s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 662.7s ▶ 语音:「回到直播。」 +t+ 664.9s ▶ 语音:「重新开始今晚。」 +t+ 667.0s ♪ 音效 sfx/ui_confirm +t+ 667.0s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 667.0s ♪ 向导音:explore(探索/安全) +t+ 667.0s ▶ 语音:「爸爸妈妈摔门了。是不是……我做错了什么呀。」 +t+ 672.2s 〰 震动:error(做错了:两连短震) +t+ 672.2s 〰 震动:error(做错了:两连短震) +t+ 672.2s ▶ 语音:「爸爸妈妈摔门了。是不是……我做错了什么呀。」 +t+ 677.3s ♪ 音效 sfx/ui_menu +t+ 677.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 677.3s ♪ 向导音:menu(系统界面) +t+ 677.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 679.5s ▶ 语音:「回到直播。」 +t+ 681.6s ▶ 语音:「重新开始今晚。」 +t+ 683.7s ♪ 音效 sfx/ui_confirm +t+ 683.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 683.7s ♪ 向导音:explore(探索/安全) +t+ 683.7s ▶ 语音:「爸爸妈妈摔门了。是不是……我做错了什么呀。」 +t+ 688.9s 〰 震动:error(做错了:两连短震) +t+ 688.9s ♪ 向导音:choice(抉择/等待输入) +t+ 688.9s ▶ 语音:「孩子的声音在抖。向左滑,告诉她这不是她的错。向右滑,答应她明天都会好。」 +t+ 691.0s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 691.0s ♪ 音效 sfx/ui_confirm +t+ 691.0s ▶ 语音:「睡吧。明天一睁眼,什么都会好起来的。」 +t+ 693.1s ▶ 语音:「真的吗?拉钩。晚安……」 +t+ 696.1s 〰 震动:error(做错了:两连短震) +t+ 696.1s ▶ 语音:「你许了个兑不了的现。孩子睡了,谎留下了。」 +t+ 698.2s ♪ 向导音:settle(结算) +t+ 698.2s ♪ 音效 sfx/line_drop +t+ 698.2s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 701.2s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 701.2s ♪ 向导音:turn(轮到你) +t+ 701.2s ♪ 音效 sfx/phone_ring +t+ 701.2s 〰 震动:confirm(轻点确认) +t+ 701.2s ♪ 音效 sfx/amb_street_rain +t+ 701.2s ♪ 音效 sfx/line_connect +t+ 701.2s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 703.3s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 705.5s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 707.6s ▶ 语音:「现在是今晚第四通电话。」 +t+ 709.7s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 711.8s ♪ 音效 sfx/amb_street_rain +t+ 711.8s ♪ 音效 sfx/line_connect +t+ 711.8s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 713.9s 〰 震动:error(做错了:两连短震) +t+ 713.9s ♪ 音效 sfx/ui_menu +t+ 713.9s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 713.9s ♪ 向导音:menu(系统界面) +t+ 713.9s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 716.1s ▶ 语音:「回到直播。」 +t+ 718.2s ▶ 语音:「重新开始今晚。」 +t+ 720.3s ♪ 音效 sfx/ui_confirm +t+ 720.3s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 720.3s ♪ 向导音:explore(探索/安全) +t+ 720.3s ♪ 音效 sfx/amb_street_rain +t+ 720.3s ♪ 音效 sfx/line_connect +t+ 720.3s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 722.4s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 724.5s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 726.7s ▶ 语音:「主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。」 +t+ 733.4s ▶ 语音:「主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。」 +t+ 740.1s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 742.3s ▶ 语音:「现在是今晚第四通电话。」 +t+ 744.4s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 746.5s ▶ 语音:「主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。」 +t+ 753.2s ▶ 语音:「主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。」 +t+ 760.0s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 765.2s 〰 震动:error(做错了:两连短震) +t+ 765.2s 〰 震动:error(做错了:两连短震) +t+ 765.2s 〰 震动:error(做错了:两连短震) +t+ 765.2s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 770.4s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 775.5s ♪ 音效 sfx/ui_menu +t+ 775.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 775.5s ♪ 向导音:menu(系统界面) +t+ 775.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 777.7s ▶ 语音:「回到直播。」 +t+ 779.8s ▶ 语音:「重新开始今晚。」 +t+ 781.9s ♪ 音效 sfx/ui_confirm +t+ 781.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 781.9s ♪ 向导音:explore(探索/安全) +t+ 781.9s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 787.1s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 792.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 794.4s ▶ 语音:「现在是今晚第四通电话。」 +t+ 796.5s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 798.6s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 803.8s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 809.0s ♪ 向导音:choice(抉择/等待输入) +t+ 809.0s ▶ 语音:「摔门声你听见了。向左滑,替孩子给她带句话。向右滑,劝她回家把话说开。」 +t+ 811.1s ♪ 音效 sfx/ui_confirm +t+ 811.1s ▶ 语音:「吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。」 +t+ 813.3s ♪ 音效 sfx/ui_menu +t+ 813.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 813.3s ♪ 向导音:menu(系统界面) +t+ 813.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 815.4s ▶ 语音:「回到直播。」 +t+ 817.5s ▶ 语音:「重新开始今晚。」 +t+ 819.6s ♪ 音效 sfx/ui_confirm +t+ 819.6s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 819.6s ♪ 向导音:explore(探索/安全) +t+ 819.6s ▶ 语音:「吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。」 +t+ 821.7s 〰 震动:error(做错了:两连短震) +t+ 821.7s 〰 震动:error(做错了:两连短震) +t+ 821.7s 〰 震动:error(做错了:两连短震) +t+ 821.7s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 823.9s ▶ 语音:「现在是今晚第四通电话。」 +t+ 826.0s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 828.1s ▶ 语音:「吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。」 +t+ 830.2s 〰 震动:error(做错了:两连短震) +t+ 830.2s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 832.3s ▶ 语音:「现在是今晚第四通电话。」 +t+ 834.5s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 836.6s ▶ 语音:「吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。」 +t+ 838.7s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 843.7s 〰 震动:error(做错了:两连短震) +t+ 843.7s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 845.8s ▶ 语音:「现在是今晚第四通电话。」 +t+ 847.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 850.0s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 855.0s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 857.1s ▶ 语音:「现在是今晚第四通电话。」 +t+ 859.2s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 861.3s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 866.3s ♪ 音效 sfx/ui_menu +t+ 866.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 866.3s ♪ 向导音:menu(系统界面) +t+ 866.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 868.4s ▶ 语音:「回到直播。」 +t+ 870.5s ▶ 语音:「重新开始今晚。」 +t+ 872.7s ♪ 音效 sfx/ui_confirm +t+ 872.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 872.7s ♪ 向导音:explore(探索/安全) +t+ 872.7s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 877.6s 〰 震动:error(做错了:两连短震) +t+ 877.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 879.8s ▶ 语音:「现在是今晚第四通电话。」 +t+ 881.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 884.0s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 889.0s 〰 震动:error(做错了:两连短震) +t+ 889.0s 〰 震动:error(做错了:两连短震) +t+ 889.0s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 893.9s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 898.9s ♪ 音效 sfx/ui_menu +t+ 898.9s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 898.9s ♪ 向导音:menu(系统界面) +t+ 898.9s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 901.0s ▶ 语音:「回到直播。」 +t+ 903.1s ▶ 语音:「重新开始今晚。」 +t+ 905.2s ♪ 音效 sfx/ui_confirm +t+ 905.2s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 905.2s ♪ 向导音:explore(探索/安全) +t+ 905.2s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 910.2s ♪ 音效 sfx/ui_menu +t+ 910.2s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 910.2s ♪ 向导音:menu(系统界面) +t+ 910.2s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 912.3s ▶ 语音:「回到直播。」 +t+ 914.4s ▶ 语音:「重新开始今晚。」 +t+ 916.6s ♪ 音效 sfx/ui_confirm +t+ 916.6s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 916.6s ♪ 向导音:explore(探索/安全) +t+ 916.6s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 921.5s ♪ 音效 sfx/ui_menu +t+ 921.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 921.5s ♪ 向导音:menu(系统界面) +t+ 921.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 923.7s ▶ 语音:「回到直播。」 +t+ 925.8s ▶ 语音:「重新开始今晚。」 +t+ 927.9s ♪ 音效 sfx/ui_confirm +t+ 927.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 927.9s ♪ 向导音:explore(探索/安全) +t+ 927.9s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 932.9s ▶ 语音:「她往回走了。成不成,看他们自己的了。」 +t+ 935.0s ♪ 向导音:settle(结算) +t+ 935.0s ♪ 音效 sfx/line_drop +t+ 935.0s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 938.0s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 938.0s ♪ 向导音:turn(轮到你) +t+ 938.0s ♪ 音效 sfx/phone_ring +t+ 938.0s 〰 震动:confirm(轻点确认) +t+ 938.0s ♪ 音效 sfx/amb_cab +t+ 938.0s ♪ 音效 sfx/line_connect +t+ 938.0s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 940.1s ♪ 音效 sfx/ui_menu +t+ 940.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 940.1s ♪ 向导音:menu(系统界面) +t+ 940.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 942.2s ▶ 语音:「回到直播。」 +t+ 944.3s ▶ 语音:「重新开始今晚。」 +t+ 946.5s ♪ 音效 sfx/ui_confirm +t+ 946.5s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 946.5s ♪ 向导音:explore(探索/安全) +t+ 946.5s ♪ 音效 sfx/amb_cab +t+ 946.5s ♪ 音效 sfx/line_connect +t+ 946.5s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 948.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 950.7s ▶ 语音:「现在是今晚第五通电话。」 +t+ 952.8s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 954.9s ♪ 音效 sfx/amb_cab +t+ 954.9s ♪ 音效 sfx/line_connect +t+ 954.9s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 957.1s ♪ 音效 sfx/ui_menu +t+ 957.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 957.1s ♪ 向导音:menu(系统界面) +t+ 957.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 959.2s ▶ 语音:「回到直播。」 +t+ 961.3s ▶ 语音:「重新开始今晚。」 +t+ 963.4s ♪ 音效 sfx/ui_confirm +t+ 963.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 963.4s ♪ 向导音:explore(探索/安全) +t+ 963.4s ♪ 音效 sfx/amb_cab +t+ 963.4s ♪ 音效 sfx/line_connect +t+ 963.4s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 965.5s ♪ 音效 sfx/ui_menu +t+ 965.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 965.5s ♪ 向导音:menu(系统界面) +t+ 965.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 967.7s ▶ 语音:「回到直播。」 +t+ 969.8s ▶ 语音:「重新开始今晚。」 +t+ 971.9s ♪ 音效 sfx/ui_confirm +t+ 971.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 971.9s ♪ 向导音:explore(探索/安全) +t+ 971.9s ♪ 音效 sfx/amb_cab +t+ 971.9s ♪ 音效 sfx/line_connect +t+ 971.9s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 974.0s ▶ 语音:「主播,别挂。陪我说话,我的眼皮,沉得很。」 +t+ 979.0s ▶ 语音:「跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。」 +t+ 984.6s 〰 震动:error(做错了:两连短震) +t+ 984.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 986.7s ▶ 语音:「现在是今晚第五通电话。」 +t+ 988.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 991.0s ▶ 语音:「跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。」 +t+ 996.6s 〰 震动:error(做错了:两连短震) +t+ 996.6s ♪ 向导音:choice(抉择/等待输入) +t+ 996.6s ▶ 语音:「方向盘比话筒危险。向左滑,陪他聊聊闺女。向右滑,让他立刻进服务区。」 +t+ 998.7s ♪ 音效 sfx/ui_confirm +t+ 998.7s ▶ 语音:「说说她。开学第一天,她跟你说的头一句话,是什么?」 +t+1000.8s 〰 震动:error(做错了:两连短震) +t+1000.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+1003.0s ▶ 语音:「现在是今晚第五通电话。」 +t+1005.1s ▶ 语音:「今晚的口碑,在听众里立住了。」 +t+1007.2s ▶ 语音:「说说她。开学第一天,她跟你说的头一句话,是什么?」 +t+1009.3s ♪ 音效 sfx/ui_menu +t+1009.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+1009.3s ♪ 向导音:menu(系统界面) +t+1009.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+1011.4s ▶ 语音:「回到直播。」 +t+1013.6s ▶ 语音:「重新开始今晚。」 +t+1015.7s ♪ 音效 sfx/ui_confirm +t+1015.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+1015.7s ♪ 向导音:explore(探索/安全) +t+1015.7s ▶ 语音:「说说她。开学第一天,她跟你说的头一句话,是什么?」 +t+1017.8s 〰 震动:error(做错了:两连短震) +t+1017.8s ♪ 音效 sfx/ui_menu +t+1017.8s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+1017.8s ♪ 向导音:menu(系统界面) +t+1017.8s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+1019.9s ▶ 语音:「回到直播。」 +t+1022.0s ▶ 语音:「重新开始今晚。」 +t+1024.2s ♪ 音效 sfx/ui_confirm +t+1024.2s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+1024.2s ♪ 向导音:explore(探索/安全) +t+1024.2s ▶ 语音:「说说她。开学第一天,她跟你说的头一句话,是什么?」 +t+1026.3s ▶ 语音:「她说,爸,我在学校给你赚了朵小红花。哈哈,哈哈……」 +t+1032.4s ♪ 向导音:danger(危险) +t+1032.4s ♪ 音效 sfx/brake_screech +t+1032.4s 〰 震动:danger(危险逼近) +t+1034.4s ▶ 语音:「车道偏了——听!」 +t+1036.5s ▶ 语音:「嘶——好险!前车急刹,我擦着护栏过去了。彻底醒了!」 +t+1042.6s ▶ 语音:「吓醒的,也算醒。这一趟,聊到家。」 +t+1044.7s ▶ 语音:「对了。头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的。」 +t+1046.8s ♪ 向导音:settle(结算) +t+1046.8s ♪ 音效 sfx/line_drop +t+1046.8s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+1046.8s ▶ 语音:「今晚的口碑,在听众里立住了。」 +t+1051.9s ▶ 语音:「五线收工。垫乐起——零点档,还剩最后一段。」 +t+1054.0s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+1054.0s ♪ 向导音:choice(抉择/等待输入) +t+1054.0s ▶ 语音:「留言机红灯在闪,是老主播三年前没播完的那句晚安。向左滑,替他播出去。向右滑,照常收播。」 +t+1056.2s ♪ 音效 sfx/ui_confirm +t+1056.2s ▶ 语音:「各位,今晚到这里。明晚零点,今夜不眠,不见不散。」 +t+1058.3s ▶ 语音:「留言机的红灯,又多亮了一夜。」 +t+1060.4s ♪ 向导音:settle(结算) +t+1060.4s ♫ BGM 切换 → bgm/ending(淡入 1500ms) +t+1060.4s ▶ 语音:「今晚的热线烧得发烫。全城的失眠者,都被接住了。」 +t+1062.5s ▶ 语音:「只是路过留言机的时候,你还是没敢看它。红灯灭得很慢。」 +t+1064.6s ▶ 语音:「收播了。导播间替你记了一笔账——今晚的人都怎样了。」 +t+1066.8s ▶ 语音:「老周:孙子替他买了老花镜。回信说,谢谢那首歌。」 +t+1068.9s ▶ 语音:「小鹿退了票。检票口的灯,记得那趟没上成的车。」 +t+1071.0s ▶ 语音:「糖糖后来又打来过一次。还是那句:楼下在吵。」 +t+1073.1s ▶ 语音:「陈姐在公车站坐到了末班车。那通电话,成了没寄出的信。」 +t+1075.2s ▶ 语音:「老韩到家了。后视镜上,多了朵小红花。」 +t+1077.4s 【状态】乱按剧本收尾:接听 5 次、8 个抉择、乱试 199 次、逛菜单 38 次 结局和五条回响都听完了。单击再来一局,下滑开菜单(BGM=bgm/ending,语速1) + +#### 【玩家动作】全程没有任何一次手势被无声吞掉,也没有卡死在某个状态里——结局链照样走完 + diff --git a/research/new8/radio/round2/transcript-main.md b/research/new8/radio/round2/transcript-main.md new file mode 100644 index 00000000..e4d8c37d --- /dev/null +++ b/research/new8/radio/round2/transcript-main.md @@ -0,0 +1,275 @@ +# 试玩转写:《radio》 + +> 本文件是「玩家感知日志」:一位新手玩家的整局体验转写。语音列出了玩家听到的每句话全文, +> 音效/BGM/震动按发生时间列出。「静默」段 = 玩家什么也听不到。手势是剧本注入的(模拟真人手部动作), +> 非最优操作:包含犹豫、误操作与乱试。语音时长按语速估算(±20%),仅供节奏参考。 +t+ 0.0s (玩家点开游戏,轻触屏幕开始) +t+ 0.0s ♫ BGM 切换 → bgm/onair +t+ 0.0s ♪ 向导音:explore(探索/安全) +t+ 0.0s ▶ 语音:「新的夜晚,开始了。」 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 2.1s ♪ 音效 sfx/onair_sting +t+ 2.1s ▶ 向导语音:「单击继续节目。请选择的时候,向左或向右滑。」 +t+ 4.2s ▶ 语音:「午夜零点,红灯亮起。欢迎回到《今夜不眠》。」 +t+ 6.4s ▶ 语音:「想跳过这句,单击就行。」 +t+ 8.5s ▶ 语音:「雨下了一整晚。热线灯,马上就要亮了。」 +t+ 10.6s ▶ 语音:「台里的规矩就一条:先接住人,再接电话。」 +t+ 12.7s ▶ 语音:「今晚五通电话。说什么话,由你决定。」 +t+ 14.8s ♪ 向导音:turn(轮到你) +t+ 14.8s 〰 震动:confirm(轻点确认) +t+ 14.8s 【状态】开场口令和台标播完了,第一通电话响了 第 一 通电话打进来:单击接听(BGM=bgm/onair,语速1) + +#### 【玩家动作】铃一响就接——接听是单击 + +t+ 14.8s ♪ 音效 sfx/amb_rain_city +t+ 14.8s ♪ 音效 sfx/line_connect +t+ 14.8s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 14.8s 【状态】线路接通了 第 一 通通话中,正在听对方说话(语音播放中,语速1) +t+ 17.0s 【状态】导播报完线,老人开口了 对方说完了,等我接话(第 一 通):单击继续(BGM=bgm/onair,语速1) +t+ 17.0s ……… 静默 25s(听到一半走了神,在想老人的老花镜) ……… +t+ 42.0s ♪ 向导音:nudge(看门狗轻提醒) +t+ 42.0s ▶ 向导语音:「单击屏幕,继续。」 +t+ 44.1s 【状态】听到一声轻响和「单击屏幕,继续」,马上点 对方说完了,等我接话(第 一 通):单击继续(BGM=bgm/onair,语速1) +t+ 44.1s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 48.2s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 52.5s ♪ 向导音:choice(抉择/等待输入) +t+ 52.5s ▶ 语音:「老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。」 + +#### 【玩家动作】老人在发抖,我陪他哼那首老歌——但先上滑听一遍操作和进度 + +t+ 54.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 56.7s ▶ 语音:「现在是今晚第一通电话。」 +t+ 58.8s ▶ 语音:「今晚的口碑,还没立起来。」 +t+ 60.9s ♪ 向导音:choice(抉择/等待输入) +t+ 60.9s ▶ 语音:「老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。」 +t+ 63.1s 【状态】听完帮助+状态链,游戏自动重播了选项 「请选择」念完了(第 一 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】下滑看看菜单有什么 + +t+ 63.1s ♪ 音效 sfx/ui_menu +t+ 63.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 63.1s ♪ 向导音:menu(系统界面) +t+ 63.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 65.2s ▶ 语音:「回到直播。」 +t+ 67.3s 【状态】菜单里,切一项听听 菜单里,当前项「回到直播」:单击切换,双击确认,上滑帮助,下滑关闭(BGM=bgm/menu,语速1) +t+ 67.3s ▶ 语音:「重新开始今晚。」 + +#### 【玩家动作】听完了,下滑关闭菜单,回到选项 + +t+ 69.4s ♪ 音效 sfx/ui_confirm +t+ 69.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 69.4s ♪ 向导音:explore(探索/安全) +t+ 69.4s ♪ 向导音:choice(抉择/等待输入) +t+ 69.4s ▶ 语音:「老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。」 +t+ 71.5s 【状态】选项念完了,做决定 「请选择」念完了(第 一 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】老人在发抖,我陪他哼那首老歌 + +t+ 71.5s ♪ 音效 sfx/ui_confirm +t+ 71.5s ▶ 语音:「大爷别急,这首歌我也会。咱们一句一句来。」 +t+ 73.7s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 77.7s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 79.8s ♪ 向导音:choice(抉择/等待输入) +t+ 79.8s ▶ 语音:「雨声更密了。向左滑,留在原地陪他等巡逻队。向右滑,转接派出所。」 +t+ 82.0s 【状态】选项念完了,做决定 「请选择」念完了(第 一 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】留在原地陪他等巡逻队 + +t+ 82.0s ♪ 音效 sfx/ui_confirm +t+ 82.0s ▶ 语音:「哪儿也不去。我陪你,这首歌还长着呢。」 +t+ 84.1s ▶ 语音:「好,好。有人说话,我就不怕了。」 +t+ 87.9s ▶ 语音:「十五分钟后,巡逻灯停在路口。全城听见老人笑了。」 +t+ 90.1s ▶ 语音:「就这么接。记住这个手感。」 +t+ 92.2s ♪ 向导音:settle(结算) +t+ 92.2s ♪ 音效 sfx/line_drop +t+ 92.2s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 92.2s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 94.3s 【状态】幕间,口碑播报和垫乐,等下一通 幕间喘息,垫乐播放中,马上接下一通(BGM=bgm/bridge,语速1) +t+ 97.3s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 97.3s ♪ 向导音:turn(轮到你) +t+ 97.3s 〰 震动:confirm(轻点确认) +t+ 97.3s 【状态】第 二 通电话响了,接 第 二 通电话打进来:单击接听(BGM=bgm/onair,语速1) +t+ 97.3s ♪ 音效 sfx/amb_station +t+ 97.3s ♪ 音效 sfx/line_connect +t+ 97.3s ▶ 语音:「二线来电。姑娘,声音很轻,像怕吵到谁。」 +t+ 99.4s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 105.3s ▶ 语音:「票是回去看我妈的。可我试镜又黄了,两手空空……」 +t+ 110.9s ♪ 向导音:choice(抉择/等待输入) +t+ 110.9s ▶ 语音:「向左滑,告诉她这趟车值得上。向右滑,把选择还给她。」 +t+ 113.0s 【状态】选项念完了,做决定 「请选择」念完了(第 二 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】小鹿妈妈等的是那声「妈」,劝她上车 + +t+ 113.0s ♪ 音效 sfx/ui_confirm +t+ 113.0s ▶ 语音:「你妈等的不是奖杯,是你进门喊的那声妈。」 +t+ 115.1s ▶ 语音:「呜……嗯。不退了,我现在就去检票。谢谢您。」 +t+ 120.3s ▶ 语音:「二线收工。这句话,值一个头条。」 +t+ 122.5s ♪ 向导音:settle(结算) +t+ 122.5s ♪ 音效 sfx/line_drop +t+ 122.5s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 122.5s ▶ 语音:「今晚的口碑,在听众里立住了。」 +t+ 124.6s 【状态】幕间,口碑播报和垫乐,等下一通 幕间喘息,垫乐播放中,马上接下一通(BGM=bgm/bridge,语速1) +t+ 127.6s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 127.6s ♪ 向导音:turn(轮到你) +t+ 127.6s 〰 震动:confirm(轻点确认) +t+ 127.6s 【状态】第 三 通电话响了,接 第 三 通电话打进来:单击接听(BGM=bgm/onair,语速1) +t+ 127.6s ♪ 音效 sfx/amb_kid_room +t+ 127.6s ♪ 音效 sfx/line_connect +t+ 127.6s ▶ 语音:「三线来电。听呼吸,是个小孩。」 +t+ 129.7s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 132.9s ♪ 音效 sfx/arg_muffled +t+ 132.9s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 137.2s ♪ 向导音:choice(抉择/等待输入) +t+ 137.2s ▶ 语音:「向左滑,唱一段摇篮曲哄她睡。向右滑,轻轻问她怎么了。」 +t+ 139.3s 【状态】选项念完了,做决定 「请选择」念完了(第 三 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】糖糖像是憋着事,我轻轻问她怎么了 + +t+ 139.3s ♪ 音效 sfx/ui_confirm +t+ 139.3s ▶ 语音:「睡不着的时候,心里是不是装着事?说给我听听。」 +t+ 141.4s ▶ 语音:「爸爸妈妈摔门了。是不是……我做错了什么呀。」 +t+ 146.6s ♪ 向导音:choice(抉择/等待输入) +t+ 146.6s ▶ 语音:「孩子的声音在抖。向左滑,告诉她这不是她的错。向右滑,答应她明天都会好。」 +t+ 148.7s 【状态】选项念完了,做决定 「请选择」念完了(第 三 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】告诉她这不是她的错——大人的事不算在孩子头上 + +t+ 148.7s ♪ 音效 sfx/ui_confirm +t+ 148.7s ▶ 语音:「糖糖,记住,大人的事,不是小孩的错。一个字都不是。」 +t+ 150.8s ▶ 语音:「嗯……那我睡了。他们明天,会一起给我切蛋糕吗?」 +t+ 156.5s ▶ 语音:「蛋糕。记下来——四线的来电,可能跟这个家有关。」 +t+ 158.6s ♪ 向导音:settle(结算) +t+ 158.6s ♪ 音效 sfx/line_drop +t+ 158.6s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 158.6s 【状态】幕间,口碑播报和垫乐,等下一通 幕间喘息,垫乐播放中,马上接下一通(BGM=bgm/bridge,语速1) +t+ 161.6s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 161.6s ♪ 向导音:turn(轮到你) +t+ 161.6s 〰 震动:confirm(轻点确认) +t+ 161.6s 【状态】第 四 通电话响了,接 第 四 通电话打进来:单击接听(BGM=bgm/onair,语速1) +t+ 161.6s ♪ 音效 sfx/amb_street_rain +t+ 161.6s ♪ 音效 sfx/line_connect +t+ 161.6s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 163.7s ▶ 语音:「主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。」 +t+ 170.5s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 175.6s ♪ 向导音:choice(抉择/等待输入) +t+ 175.6s ▶ 语音:「摔门声你听见了。向左滑,替孩子给她带句话。向右滑,劝她回家把话说开。」 +t+ 177.8s 【状态】选项念完了,做决定 「请选择」念完了(第 四 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】陈姐听见孩子问话了,我把话带回去 + +t+ 177.8s ♪ 音效 sfx/ui_confirm +t+ 177.8s ▶ 语音:「四十分钟前,有个孩子问我们:爸妈摔门,是不是她的错。」 +t+ 179.9s ▶ 语音:「什么?她还没睡?这么晚了,她还醒着?」 +t+ 184.4s ▶ 语音:「她攥着电话,很久没出声。风都停了。」 +t+ 186.5s ♪ 向导音:choice(抉择/等待输入) +t+ 186.5s ▶ 语音:「她的呼吸平了。向左滑,留在直播里,让全城等这个门铃。向右滑,换垫乐,私下陪她说完。」 +t+ 188.6s 【状态】选项念完了,做决定 「请选择」念完了(第 四 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】留在直播里,让全城等这声门铃 + +t+ 188.6s ♪ 音效 sfx/ui_confirm +t+ 188.6s ▶ 语音:「陈姐,别挂。让他接电话——全城都在听。」 +t+ 190.8s ▶ 语音:「喂!回来吧!蛋糕要化了,孩子等着呢!」 +t+ 195.3s ▶ 语音:「电话那头,传来跑步声。今晚的高光时刻。」 +t+ 197.4s ♪ 向导音:settle(结算) +t+ 197.4s ♪ 音效 sfx/line_drop +t+ 197.4s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 197.4s 【状态】幕间,口碑播报和垫乐,等下一通 幕间喘息,垫乐播放中,马上接下一通(BGM=bgm/bridge,语速1) +t+ 200.4s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 200.4s ♪ 向导音:turn(轮到你) +t+ 200.4s 〰 震动:confirm(轻点确认) +t+ 200.4s 【状态】第 五 通电话响了,接 第 五 通电话打进来:单击接听(BGM=bgm/onair,语速1) +t+ 200.4s ♪ 音效 sfx/amb_cab +t+ 200.4s ♪ 音效 sfx/line_connect +t+ 200.4s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 202.5s ▶ 语音:「主播,别挂。陪我说话,我的眼皮,沉得很。」 +t+ 207.5s ▶ 语音:「跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。」 +t+ 213.1s ♪ 向导音:choice(抉择/等待输入) +t+ 213.1s ▶ 语音:「方向盘比话筒危险。向左滑,陪他聊聊闺女。向右滑,让他立刻进服务区。」 +t+ 215.2s 【状态】选项念完了,做决定 「请选择」念完了(第 五 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】老韩眼皮在打架,我陪他聊聊闺女 + +t+ 215.2s ♪ 音效 sfx/ui_confirm +t+ 215.2s ▶ 语音:「说说她。开学第一天,她跟你说的头一句话,是什么?」 +t+ 217.4s ▶ 语音:「她说,爸,我在学校给你赚了朵小红花。哈哈,哈哈……」 +t+ 223.4s ♪ 向导音:danger(危险) +t+ 223.4s ♪ 音效 sfx/brake_screech +t+ 223.4s 〰 震动:danger(危险逼近) +t+ 225.4s ▶ 语音:「车道偏了——听!」 +t+ 227.6s ▶ 语音:「嘶——好险!前车急刹,我擦着护栏过去了。彻底醒了!」 +t+ 233.6s ▶ 语音:「吓醒的,也算醒。这一趟,聊到家。」 +t+ 235.8s ▶ 语音:「对了。头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的。」 +t+ 237.9s ♪ 向导音:settle(结算) +t+ 237.9s ♪ 音效 sfx/line_drop +t+ 237.9s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 237.9s 【状态】幕间,口碑播报和垫乐,等下一通 幕间喘息,垫乐播放中,马上接下一通(BGM=bgm/bridge,语速1) +t+ 240.9s ▶ 语音:「五线收工。垫乐起——零点档,还剩最后一段。」 +t+ 243.0s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 243.0s ♪ 向导音:choice(抉择/等待输入) +t+ 243.0s ▶ 语音:「留言机红灯在闪,是老主播三年前没播完的那句晚安。向左滑,替他播出去。向右滑,照常收播。」 +t+ 245.1s 【状态】选项念完了,做决定 「请选择」念完了(第 五 通):向左或向右滑动做出你的决定(BGM=bgm/onair,语速1) + +#### 【玩家动作】留言机红灯在闪——直面它,替老主播把晚安播出去 + +t+ 245.1s ♪ 音效 sfx/ui_confirm +t+ 245.1s ♪ 音效 sfx/tape_static +t+ 245.1s ▶ 语音:「师父,三年了。这句晚安,我替您播。各位,晚安。」 +t+ 247.2s ▶ 语音:「带子里其实只有电流声。但空着的那一秒,你说满了。」 +t+ 249.4s ♪ 向导音:settle(结算) +t+ 249.4s ♫ BGM 切换 → bgm/ending(淡入 1500ms) +t+ 249.4s ▶ 语音:「天亮了,热线灯还亮着。你们的晚安,是这座城市的心跳。」 +t+ 251.5s ▶ 语音:「这把话筒,我总算接得住了。明晚零点,接着不眠。」 +t+ 253.6s ▶ 语音:「收播了。导播间替你记了一笔账——今晚的人都怎样了。」 +t+ 255.7s ▶ 语音:「老周:孙子替他买了老花镜。回信说,谢谢那首歌。」 +t+ 257.8s ▶ 语音:「小鹿上车了。她妈后来打电话说,那声妈,比奖杯值钱。」 +t+ 260.0s ▶ 语音:「糖糖家的门,这个月没再摔过。她说这是主播魔法。」 +t+ 262.1s ▶ 语音:「陈姐家切了蛋糕。那句「回来吧」,是我这晚听过最好的声音。」 +t+ 264.2s ▶ 语音:「老韩到家了。后视镜上,多了朵小红花。」 +t+ 266.3s 【状态】结局链走完,五条回响一句句点到我今晚的每一次选择 结局和五条回响都听完了。单击再来一局,下滑开菜单(BGM=bgm/ending,语速1) + +#### 【玩家动作】听完尾声那笔账——今晚的人都接住了,会话结束(E1 天亮之前) + diff --git a/research/new8/radio/round2/transcript-stress.md b/research/new8/radio/round2/transcript-stress.md new file mode 100644 index 00000000..43c863e0 --- /dev/null +++ b/research/new8/radio/round2/transcript-stress.md @@ -0,0 +1,842 @@ +# 试玩转写:《radio》 + +> 本文件是「玩家感知日志」:一位新手玩家的整局体验转写。语音列出了玩家听到的每句话全文, +> 音效/BGM/震动按发生时间列出。「静默」段 = 玩家什么也听不到。手势是剧本注入的(模拟真人手部动作), +> 非最优操作:包含犹豫、误操作与乱试。语音时长按语速估算(±20%),仅供节奏参考。 +t+ 0.0s (玩家点开游戏,轻触屏幕开始) +t+ 0.0s ♫ BGM 切换 → bgm/onair +t+ 0.0s ♪ 向导音:explore(探索/安全) +t+ 0.0s ▶ 语音:「新的夜晚,开始了。」 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/onair_sting → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_connect → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/line_drop → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_rain_city → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_station → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_kid_room → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/arg_muffled → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_street_rain → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/amb_cab → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/brake_screech → 玩家此刻听到的是静默 +t+ 0.0s ⚠ 资产缺失 sfx/tape_static → 玩家此刻听到的是静默 +t+ 2.1s ♪ 音效 sfx/onair_sting +t+ 2.1s ▶ 向导语音:「单击继续节目。请选择的时候,向左或向右滑。」 +t+ 4.2s ▶ 语音:「午夜零点,红灯亮起。欢迎回到《今夜不眠》。」 +t+ 6.4s ▶ 语音:「想跳过这句,单击就行。」 +t+ 8.5s ▶ 语音:「雨下了一整晚。热线灯,马上就要亮了。」 +t+ 10.6s ▶ 语音:「台里的规矩就一条:先接住人,再接电话。」 +t+ 12.7s ▶ 语音:「今晚五通电话。说什么话,由你决定。」 +t+ 14.8s ♪ 向导音:turn(轮到你) +t+ 14.8s 〰 震动:confirm(轻点确认) +t+ 14.8s ♪ 音效 sfx/amb_rain_city +t+ 14.8s ♪ 音效 sfx/line_connect +t+ 14.8s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 17.0s ▶ 向导语音:「这个手势,直播间里用不上。」 +t+ 17.0s 〰 震动:error(做错了:两连短震) +t+ 17.0s ▶ 向导语音(对白在播,本句按 idle-only 跳过未播):「这个手势,直播间里用不上。」 +t+ 17.0s 〰 震动:error(做错了:两连短震) +t+ 17.0s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 19.1s ▶ 语音:「现在是今晚第一通电话。」 +t+ 21.2s ▶ 语音:「今晚的口碑,还没立起来。」 +t+ 23.3s ♪ 音效 sfx/amb_rain_city +t+ 23.3s ♪ 音效 sfx/line_connect +t+ 23.3s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 25.4s ♪ 音效 sfx/ui_menu +t+ 25.4s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 25.4s ♪ 向导音:menu(系统界面) +t+ 25.4s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 27.6s ▶ 语音:「回到直播。」 +t+ 29.7s ▶ 语音:「重新开始今晚。」 +t+ 31.8s ♪ 音效 sfx/ui_confirm +t+ 31.8s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 31.8s ♪ 向导音:explore(探索/安全) +t+ 31.8s ♪ 音效 sfx/amb_rain_city +t+ 31.8s ♪ 音效 sfx/line_connect +t+ 31.8s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 33.9s 〰 震动:error(做错了:两连短震) +t+ 33.9s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 36.0s 〰 震动:error(做错了:两连短震) +t+ 36.0s 〰 震动:error(做错了:两连短震) +t+ 36.0s ♪ 音效 sfx/ui_menu +t+ 36.0s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 36.0s ♪ 向导音:menu(系统界面) +t+ 36.0s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 38.2s ▶ 语音:「回到直播。」 +t+ 40.3s ▶ 语音:「重新开始今晚。」 +t+ 42.4s ♪ 音效 sfx/ui_confirm +t+ 42.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 42.4s ♪ 向导音:explore(探索/安全) +t+ 42.4s ♪ 音效 sfx/amb_rain_city +t+ 42.4s ♪ 音效 sfx/line_connect +t+ 42.4s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 44.5s 〰 震动:error(做错了:两连短震) +t+ 44.5s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 46.6s ▶ 语音:「现在是今晚第一通电话。」 +t+ 48.8s ▶ 语音:「今晚的口碑,还没立起来。」 +t+ 50.9s ♪ 音效 sfx/amb_rain_city +t+ 50.9s ♪ 音效 sfx/line_connect +t+ 50.9s ▶ 语音:「一线来电。七十多岁,雨夜,走丢了。」 +t+ 53.0s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 57.1s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 59.2s ▶ 语音:「现在是今晚第一通电话。」 +t+ 61.3s ▶ 语音:「今晚的口碑,还没立起来。」 +t+ 63.4s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 67.5s 〰 震动:error(做错了:两连短震) +t+ 67.5s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 69.6s ▶ 语音:「现在是今晚第一通电话。」 +t+ 71.8s ▶ 语音:「今晚的口碑,还没立起来。」 +t+ 73.9s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 77.9s 〰 震动:error(做错了:两连短震) +t+ 77.9s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 80.1s ▶ 语音:「现在是今晚第一通电话。」 +t+ 82.2s ▶ 语音:「今晚的口碑,还没立起来。」 +t+ 84.3s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 88.4s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 90.5s ▶ 语音:「现在是今晚第一通电话。」 +t+ 92.6s ▶ 语音:「今晚的口碑,还没立起来。」 +t+ 94.7s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 98.8s 〰 震动:error(做错了:两连短震) +t+ 98.8s 〰 震动:error(做错了:两连短震) +t+ 98.8s 〰 震动:error(做错了:两连短震) +t+ 98.8s 〰 震动:error(做错了:两连短震) +t+ 98.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 100.9s ▶ 语音:「现在是今晚第一通电话。」 +t+ 103.1s ▶ 语音:「今晚的口碑,还没立起来。」 +t+ 105.2s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 109.3s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 113.3s ♪ 音效 sfx/ui_menu +t+ 113.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 113.3s ♪ 向导音:menu(系统界面) +t+ 113.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 115.4s ▶ 语音:「回到直播。」 +t+ 117.6s ▶ 语音:「重新开始今晚。」 +t+ 119.7s ♪ 音效 sfx/ui_confirm +t+ 119.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 119.7s ♪ 向导音:explore(探索/安全) +t+ 119.7s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 123.8s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 127.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 130.0s ▶ 语音:「现在是今晚第一通电话。」 +t+ 132.1s ▶ 语音:「今晚的口碑,还没立起来。」 +t+ 134.2s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 138.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 140.4s ▶ 语音:「现在是今晚第一通电话。」 +t+ 142.5s ▶ 语音:「今晚的口碑,还没立起来。」 +t+ 144.6s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 148.7s 〰 震动:error(做错了:两连短震) +t+ 148.7s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 152.8s ▶ 语音:「主播……我,我找不到回家的路了。」 +t+ 156.9s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 161.2s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 165.5s ♪ 音效 sfx/ui_menu +t+ 165.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 165.5s ♪ 向导音:menu(系统界面) +t+ 165.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 167.6s ▶ 语音:「回到直播。」 +t+ 169.7s ▶ 语音:「重新开始今晚。」 +t+ 171.8s ♪ 音效 sfx/ui_confirm +t+ 171.8s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 171.8s ♪ 向导音:explore(探索/安全) +t+ 171.8s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 176.1s 〰 震动:error(做错了:两连短震) +t+ 176.1s 〰 震动:error(做错了:两连短震) +t+ 176.1s 〰 震动:error(做错了:两连短震) +t+ 176.1s 〰 震动:error(做错了:两连短震) +t+ 176.1s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 180.4s ♪ 音效 sfx/ui_menu +t+ 180.4s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 180.4s ♪ 向导音:menu(系统界面) +t+ 180.4s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 182.5s ▶ 语音:「回到直播。」 +t+ 184.7s ▶ 语音:「重新开始今晚。」 +t+ 186.8s ♪ 音效 sfx/ui_confirm +t+ 186.8s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 186.8s ♪ 向导音:explore(探索/安全) +t+ 186.8s ▶ 语音:「雨太大了。我本来,只想买副老花镜。」 +t+ 191.1s ♪ 向导音:choice(抉择/等待输入) +t+ 191.1s ▶ 语音:「老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。」 +t+ 193.2s ♪ 音效 sfx/ui_confirm +t+ 193.2s ▶ 语音:「大爷别急,这首歌我也会。咱们一句一句来。」 +t+ 195.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 197.4s ▶ 语音:「现在是今晚第一通电话。」 +t+ 199.6s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 201.7s ▶ 语音:「大爷别急,这首歌我也会。咱们一句一句来。」 +t+ 203.8s ♪ 音效 sfx/ui_menu +t+ 203.8s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 203.8s ♪ 向导音:menu(系统界面) +t+ 203.8s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 205.9s ▶ 语音:「回到直播。」 +t+ 208.0s ▶ 语音:「重新开始今晚。」 +t+ 210.2s ♪ 音效 sfx/ui_confirm +t+ 210.2s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 210.2s ♪ 向导音:explore(探索/安全) +t+ 210.2s ▶ 语音:「大爷别急,这首歌我也会。咱们一句一句来。」 +t+ 212.3s 〰 震动:error(做错了:两连短震) +t+ 212.3s ♪ 音效 sfx/ui_menu +t+ 212.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 212.3s ♪ 向导音:menu(系统界面) +t+ 212.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 214.4s ▶ 语音:「回到直播。」 +t+ 216.5s ▶ 语音:「重新开始今晚。」 +t+ 218.6s ♪ 音效 sfx/ui_confirm +t+ 218.6s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 218.6s ♪ 向导音:explore(探索/安全) +t+ 218.6s ▶ 语音:「大爷别急,这首歌我也会。咱们一句一句来。」 +t+ 220.8s 〰 震动:error(做错了:两连短震) +t+ 220.8s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 224.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 226.9s ▶ 语音:「现在是今晚第一通电话。」 +t+ 229.1s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 231.2s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 235.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 237.4s ▶ 语音:「现在是今晚第一通电话。」 +t+ 239.5s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 241.6s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 245.7s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 247.8s ▶ 语音:「现在是今晚第一通电话。」 +t+ 249.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 252.1s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 256.1s 〰 震动:error(做错了:两连短震) +t+ 256.1s ♪ 音效 sfx/ui_menu +t+ 256.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 256.1s ♪ 向导音:menu(系统界面) +t+ 256.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 258.3s ▶ 语音:「回到直播。」 +t+ 260.4s ▶ 语音:「重新开始今晚。」 +t+ 262.5s ♪ 音效 sfx/ui_confirm +t+ 262.5s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 262.5s ♪ 向导音:explore(探索/安全) +t+ 262.5s ▶ 语音:「对,对!路口修车铺,天天放这首!」 +t+ 266.6s 〰 震动:error(做错了:两连短震) +t+ 266.6s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 268.7s 〰 震动:error(做错了:两连短震) +t+ 268.7s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 270.8s ▶ 语音:「现在是今晚第一通电话。」 +t+ 272.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 275.0s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 277.2s 〰 震动:error(做错了:两连短震) +t+ 277.2s 〰 震动:error(做错了:两连短震) +t+ 277.2s 〰 震动:error(做错了:两连短震) +t+ 277.2s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 279.3s ♪ 音效 sfx/ui_menu +t+ 279.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 279.3s ♪ 向导音:menu(系统界面) +t+ 279.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 281.4s ▶ 语音:「回到直播。」 +t+ 283.5s ▶ 语音:「重新开始今晚。」 +t+ 285.6s ♪ 音效 sfx/ui_confirm +t+ 285.6s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 285.6s ♪ 向导音:explore(探索/安全) +t+ 285.6s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 287.8s 〰 震动:error(做错了:两连短震) +t+ 287.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 289.9s ▶ 语音:「现在是今晚第一通电话。」 +t+ 292.0s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 294.1s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 296.2s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 298.4s ♪ 音效 sfx/ui_menu +t+ 298.4s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 298.4s ♪ 向导音:menu(系统界面) +t+ 298.4s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 300.5s ▶ 语音:「回到直播。」 +t+ 302.6s ▶ 语音:「重新开始今晚。」 +t+ 304.7s ♪ 音效 sfx/ui_confirm +t+ 304.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 304.7s ♪ 向导音:explore(探索/安全) +t+ 304.7s ▶ 语音:「他稳住了。老歌,比路牌管用。」 +t+ 306.8s ♪ 向导音:choice(抉择/等待输入) +t+ 306.8s ▶ 语音:「雨声更密了。向左滑,留在原地陪他等巡逻队。向右滑,转接派出所。」 +t+ 309.0s ♪ 音效 sfx/ui_confirm +t+ 309.0s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 311.1s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 313.2s ▶ 语音:「现在是今晚第一通电话。」 +t+ 315.3s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 317.4s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 319.6s 〰 震动:error(做错了:两连短震) +t+ 319.6s 〰 震动:error(做错了:两连短震) +t+ 319.6s ♪ 音效 sfx/ui_menu +t+ 319.6s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 319.6s ♪ 向导音:menu(系统界面) +t+ 319.6s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 321.7s ▶ 语音:「回到直播。」 +t+ 323.8s ▶ 语音:「重新开始今晚。」 +t+ 325.9s ♪ 音效 sfx/ui_confirm +t+ 325.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 325.9s ♪ 向导音:explore(探索/安全) +t+ 325.9s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 328.0s ♪ 音效 sfx/ui_menu +t+ 328.0s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 328.0s ♪ 向导音:menu(系统界面) +t+ 328.0s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 330.2s ▶ 语音:「回到直播。」 +t+ 332.3s ▶ 语音:「重新开始今晚。」 +t+ 334.4s ♪ 音效 sfx/ui_confirm +t+ 334.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 334.4s ♪ 向导音:explore(探索/安全) +t+ 334.4s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 336.5s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 338.6s 〰 震动:error(做错了:两连短震) +t+ 338.6s ♪ 音效 sfx/ui_menu +t+ 338.6s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 338.6s ♪ 向导音:menu(系统界面) +t+ 338.6s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 340.8s ▶ 语音:「回到直播。」 +t+ 342.9s ▶ 语音:「重新开始今晚。」 +t+ 345.0s ♪ 音效 sfx/ui_confirm +t+ 345.0s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 345.0s ♪ 向导音:explore(探索/安全) +t+ 345.0s ▶ 语音:「大爷,我把您转给派出所,他们马上就到。」 +t+ 347.1s 〰 震动:error(做错了:两连短震) +t+ 347.1s ▶ 语音:「好,好。谢谢你啊,谢谢。」 +t+ 350.3s ♪ 音效 sfx/ui_menu +t+ 350.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 350.3s ♪ 向导音:menu(系统界面) +t+ 350.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 352.4s ▶ 语音:「回到直播。」 +t+ 354.6s ▶ 语音:「重新开始今晚。」 +t+ 356.7s ♪ 音效 sfx/ui_confirm +t+ 356.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 356.7s ♪ 向导音:explore(探索/安全) +t+ 356.7s ▶ 语音:「好,好。谢谢你啊,谢谢。」 +t+ 359.9s ▶ 语音:「转接完成,地址报得清。干净利落。」 +t+ 362.0s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 364.1s ▶ 语音:「现在是今晚第一通电话。」 +t+ 366.2s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 368.3s ▶ 语音:「转接完成,地址报得清。干净利落。」 +t+ 370.5s ▶ 语音:「就这么接。记住这个手感。」 +t+ 372.6s ♪ 向导音:settle(结算) +t+ 372.6s ♪ 音效 sfx/line_drop +t+ 372.6s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 372.6s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 377.7s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 377.7s ♪ 向导音:turn(轮到你) +t+ 377.7s 〰 震动:confirm(轻点确认) +t+ 377.7s ♪ 音效 sfx/amb_station +t+ 377.7s ♪ 音效 sfx/line_connect +t+ 377.7s ▶ 语音:「二线来电。姑娘,声音很轻,像怕吵到谁。」 +t+ 379.8s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 385.7s 〰 震动:error(做错了:两连短震) +t+ 385.7s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 387.8s ▶ 语音:「现在是今晚第二通电话。」 +t+ 389.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 392.0s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 397.9s ♪ 音效 sfx/ui_menu +t+ 397.9s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 397.9s ♪ 向导音:menu(系统界面) +t+ 397.9s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 400.0s ▶ 语音:「回到直播。」 +t+ 402.1s ▶ 语音:「重新开始今晚。」 +t+ 404.2s ♪ 音效 sfx/ui_confirm +t+ 404.2s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 404.2s ♪ 向导音:explore(探索/安全) +t+ 404.2s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 410.1s ♪ 音效 sfx/ui_menu +t+ 410.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 410.1s ♪ 向导音:menu(系统界面) +t+ 410.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 412.2s ▶ 语音:「回到直播。」 +t+ 414.3s ▶ 语音:「重新开始今晚。」 +t+ 416.5s ♪ 音效 sfx/ui_confirm +t+ 416.5s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 416.5s ♪ 向导音:explore(探索/安全) +t+ 416.5s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 422.3s 〰 震动:error(做错了:两连短震) +t+ 422.3s 〰 震动:error(做错了:两连短震) +t+ 422.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 424.4s ▶ 语音:「现在是今晚第二通电话。」 +t+ 426.6s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 428.7s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 434.5s ♪ 音效 sfx/ui_menu +t+ 434.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 434.5s ♪ 向导音:menu(系统界面) +t+ 434.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 436.6s ▶ 语音:「回到直播。」 +t+ 438.8s ▶ 语音:「重新开始今晚。」 +t+ 440.9s ♪ 音效 sfx/ui_confirm +t+ 440.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 440.9s ♪ 向导音:explore(探索/安全) +t+ 440.9s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 446.7s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 452.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 454.7s ▶ 语音:「现在是今晚第二通电话。」 +t+ 456.8s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 459.0s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 464.8s 〰 震动:error(做错了:两连短震) +t+ 464.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 466.9s ▶ 语音:「现在是今晚第二通电话。」 +t+ 469.0s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 471.2s ▶ 语音:「主播,退票窗口十一点半就关了。我站这儿半小时了。」 +t+ 477.0s ▶ 语音:「票是回去看我妈的。可我试镜又黄了,两手空空……」 +t+ 482.7s ♪ 向导音:choice(抉择/等待输入) +t+ 482.7s ▶ 语音:「向左滑,告诉她这趟车值得上。向右滑,把选择还给她。」 +t+ 484.8s ♪ 音效 sfx/ui_confirm +t+ 484.8s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 486.9s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 489.0s ▶ 语音:「现在是今晚第二通电话。」 +t+ 491.1s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 493.3s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 495.4s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 497.5s ♪ 音效 sfx/ui_menu +t+ 497.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 497.5s ♪ 向导音:menu(系统界面) +t+ 497.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 499.6s ▶ 语音:「回到直播。」 +t+ 501.7s ▶ 语音:「重新开始今晚。」 +t+ 503.9s ♪ 音效 sfx/ui_confirm +t+ 503.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 503.9s ♪ 向导音:explore(探索/安全) +t+ 503.9s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 506.0s ♪ 音效 sfx/ui_menu +t+ 506.0s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 506.0s ♪ 向导音:menu(系统界面) +t+ 506.0s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 508.1s ▶ 语音:「回到直播。」 +t+ 510.2s ▶ 语音:「重新开始今晚。」 +t+ 512.3s ♪ 音效 sfx/ui_confirm +t+ 512.3s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 512.3s ♪ 向导音:explore(探索/安全) +t+ 512.3s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 514.5s ▶ 语音:「退不退,都是你的自由。夜还长,慢慢想。」 +t+ 516.6s ▶ 语音:「嗯……我再想想。窗口要关了,我先过去了。」 +t+ 521.5s 〰 震动:error(做错了:两连短震) +t+ 521.5s ▶ 语音:「她走了。没上车,也没退票。年轻人的夜,长了点。」 +t+ 523.7s ♪ 向导音:settle(结算) +t+ 523.7s ♪ 音效 sfx/line_drop +t+ 523.7s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 526.7s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 526.7s ♪ 向导音:turn(轮到你) +t+ 526.7s 〰 震动:confirm(轻点确认) +t+ 526.7s ♪ 音效 sfx/amb_kid_room +t+ 526.7s ♪ 音效 sfx/line_connect +t+ 526.7s ▶ 语音:「三线来电。听呼吸,是个小孩。」 +t+ 528.8s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 532.0s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 535.2s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 538.3s 〰 震动:error(做错了:两连短震) +t+ 538.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 540.5s ▶ 语音:「现在是今晚第三通电话。」 +t+ 542.6s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 544.7s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 547.9s ♪ 音效 sfx/ui_menu +t+ 547.9s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 547.9s ♪ 向导音:menu(系统界面) +t+ 547.9s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 550.0s ▶ 语音:「回到直播。」 +t+ 552.1s ▶ 语音:「重新开始今晚。」 +t+ 554.2s ♪ 音效 sfx/ui_confirm +t+ 554.2s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 554.2s ♪ 向导音:explore(探索/安全) +t+ 554.2s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 557.4s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 560.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 562.7s ▶ 语音:「现在是今晚第三通电话。」 +t+ 564.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 567.0s ▶ 语音:「喂……有人吗?我睡不着。」 +t+ 570.2s ♪ 音效 sfx/arg_muffled +t+ 570.2s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 574.5s ♪ 音效 sfx/ui_menu +t+ 574.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 574.5s ♪ 向导音:menu(系统界面) +t+ 574.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 576.6s ▶ 语音:「回到直播。」 +t+ 578.7s ▶ 语音:「重新开始今晚。」 +t+ 580.8s ♪ 音效 sfx/ui_confirm +t+ 580.8s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 580.8s ♪ 向导音:explore(探索/安全) +t+ 580.8s ♪ 音效 sfx/arg_muffled +t+ 580.8s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 585.1s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 587.2s ▶ 语音:「现在是今晚第三通电话。」 +t+ 587.3s ♪ 音效 sfx/arg_muffled +t+ 589.4s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 591.5s ♪ 音效 sfx/arg_muffled +t+ 591.5s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 595.8s 〰 震动:error(做错了:两连短震) +t+ 595.8s 〰 震动:error(做错了:两连短震) +t+ 595.8s 〰 震动:error(做错了:两连短震) +t+ 595.8s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 598.0s ♪ 音效 sfx/arg_muffled +t+ 600.1s 〰 震动:error(做错了:两连短震) +t+ 600.1s 〰 震动:error(做错了:两连短震) +t+ 600.1s ♪ 音效 sfx/ui_menu +t+ 600.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 600.1s ♪ 向导音:menu(系统界面) +t+ 600.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 602.2s ▶ 语音:「回到直播。」 +t+ 604.3s ▶ 语音:「重新开始今晚。」 +t+ 606.4s ♪ 音效 sfx/ui_confirm +t+ 606.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 606.4s ♪ 向导音:explore(探索/安全) +t+ 606.4s ♪ 音效 sfx/arg_muffled +t+ 606.4s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 610.7s 〰 震动:error(做错了:两连短震) +t+ 610.7s 〰 震动:error(做错了:两连短震) +t+ 610.7s ♪ 音效 sfx/ui_menu +t+ 610.7s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 610.7s ♪ 向导音:menu(系统界面) +t+ 610.7s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 612.9s ▶ 语音:「回到直播。」 +t+ 615.0s ▶ 语音:「重新开始今晚。」 +t+ 617.1s ♪ 音效 sfx/ui_confirm +t+ 617.1s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 617.1s ♪ 向导音:explore(探索/安全) +t+ 617.1s ♪ 音效 sfx/arg_muffled +t+ 617.1s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 621.4s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 623.5s ▶ 语音:「现在是今晚第三通电话。」 +t+ 623.6s ♪ 音效 sfx/arg_muffled +t+ 625.6s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 627.8s ♪ 音效 sfx/arg_muffled +t+ 627.8s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 632.0s ♪ 音效 sfx/ui_menu +t+ 632.0s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 632.0s ♪ 向导音:menu(系统界面) +t+ 632.0s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 634.2s ▶ 语音:「回到直播。」 +t+ 636.3s ▶ 语音:「重新开始今晚。」 +t+ 638.4s ♪ 音效 sfx/ui_confirm +t+ 638.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 638.4s ♪ 向导音:explore(探索/安全) +t+ 638.4s ♪ 音效 sfx/arg_muffled +t+ 638.4s ▶ 语音:「家里灯关了。可是楼下,一直有声音。」 +t+ 642.7s ♪ 向导音:choice(抉择/等待输入) +t+ 642.7s ▶ 语音:「向左滑,唱一段摇篮曲哄她睡。向右滑,轻轻问她怎么了。」 +t+ 644.8s ♪ 音效 sfx/ui_confirm +t+ 644.8s ▶ 语音:「睡不着的时候,心里是不是装着事?说给我听听。」 +t+ 646.9s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 649.1s ▶ 语音:「现在是今晚第三通电话。」 +t+ 651.2s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 653.3s ▶ 语音:「睡不着的时候,心里是不是装着事?说给我听听。」 +t+ 655.4s ▶ 语音:「爸爸妈妈摔门了。是不是……我做错了什么呀。」 +t+ 660.6s ♪ 音效 sfx/ui_menu +t+ 660.6s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 660.6s ♪ 向导音:menu(系统界面) +t+ 660.6s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 662.7s ▶ 语音:「回到直播。」 +t+ 664.9s ▶ 语音:「重新开始今晚。」 +t+ 667.0s ♪ 音效 sfx/ui_confirm +t+ 667.0s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 667.0s ♪ 向导音:explore(探索/安全) +t+ 667.0s ▶ 语音:「爸爸妈妈摔门了。是不是……我做错了什么呀。」 +t+ 672.2s 〰 震动:error(做错了:两连短震) +t+ 672.2s 〰 震动:error(做错了:两连短震) +t+ 672.2s ▶ 语音:「爸爸妈妈摔门了。是不是……我做错了什么呀。」 +t+ 677.3s ♪ 音效 sfx/ui_menu +t+ 677.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 677.3s ♪ 向导音:menu(系统界面) +t+ 677.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 679.5s ▶ 语音:「回到直播。」 +t+ 681.6s ▶ 语音:「重新开始今晚。」 +t+ 683.7s ♪ 音效 sfx/ui_confirm +t+ 683.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 683.7s ♪ 向导音:explore(探索/安全) +t+ 683.7s ▶ 语音:「爸爸妈妈摔门了。是不是……我做错了什么呀。」 +t+ 688.9s 〰 震动:error(做错了:两连短震) +t+ 688.9s ♪ 向导音:choice(抉择/等待输入) +t+ 688.9s ▶ 语音:「孩子的声音在抖。向左滑,告诉她这不是她的错。向右滑,答应她明天都会好。」 +t+ 691.0s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 691.0s ♪ 音效 sfx/ui_confirm +t+ 691.0s ▶ 语音:「睡吧。明天一睁眼,什么都会好起来的。」 +t+ 693.1s ▶ 语音:「真的吗?拉钩。晚安……」 +t+ 696.1s 〰 震动:error(做错了:两连短震) +t+ 696.1s ▶ 语音:「你许了个兑不了的现。孩子睡了,谎留下了。」 +t+ 698.2s ♪ 向导音:settle(结算) +t+ 698.2s ♪ 音效 sfx/line_drop +t+ 698.2s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 701.2s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 701.2s ♪ 向导音:turn(轮到你) +t+ 701.2s 〰 震动:confirm(轻点确认) +t+ 701.2s ♪ 音效 sfx/amb_street_rain +t+ 701.2s ♪ 音效 sfx/line_connect +t+ 701.2s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 703.3s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 705.5s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 707.6s ▶ 语音:「现在是今晚第四通电话。」 +t+ 709.7s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 711.8s ♪ 音效 sfx/amb_street_rain +t+ 711.8s ♪ 音效 sfx/line_connect +t+ 711.8s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 713.9s 〰 震动:error(做错了:两连短震) +t+ 713.9s ♪ 音效 sfx/ui_menu +t+ 713.9s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 713.9s ♪ 向导音:menu(系统界面) +t+ 713.9s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 716.1s ▶ 语音:「回到直播。」 +t+ 718.2s ▶ 语音:「重新开始今晚。」 +t+ 720.3s ♪ 音效 sfx/ui_confirm +t+ 720.3s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 720.3s ♪ 向导音:explore(探索/安全) +t+ 720.3s ♪ 音效 sfx/amb_street_rain +t+ 720.3s ♪ 音效 sfx/line_connect +t+ 720.3s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 722.4s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 724.5s ▶ 语音:「四线来电。户外风声大,是位大姐。」 +t+ 726.7s ▶ 语音:「主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。」 +t+ 733.4s ▶ 语音:「主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。」 +t+ 740.1s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 742.3s ▶ 语音:「现在是今晚第四通电话。」 +t+ 744.4s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 746.5s ▶ 语音:「主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。」 +t+ 753.2s ▶ 语音:「主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。」 +t+ 760.0s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 765.2s 〰 震动:error(做错了:两连短震) +t+ 765.2s 〰 震动:error(做错了:两连短震) +t+ 765.2s 〰 震动:error(做错了:两连短震) +t+ 765.2s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 770.4s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 775.5s ♪ 音效 sfx/ui_menu +t+ 775.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 775.5s ♪ 向导音:menu(系统界面) +t+ 775.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 777.7s ▶ 语音:「回到直播。」 +t+ 779.8s ▶ 语音:「重新开始今晚。」 +t+ 781.9s ♪ 音效 sfx/ui_confirm +t+ 781.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 781.9s ♪ 向导音:explore(探索/安全) +t+ 781.9s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 787.1s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 792.3s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 794.4s ▶ 语音:「现在是今晚第四通电话。」 +t+ 796.5s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 798.6s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 803.8s ▶ 语音:「明天孩子生日!蛋糕都订好了,他人要今晚走!」 +t+ 809.0s ♪ 向导音:choice(抉择/等待输入) +t+ 809.0s ▶ 语音:「摔门声你听见了。向左滑,替孩子给她带句话。向右滑,劝她回家把话说开。」 +t+ 811.1s ♪ 音效 sfx/ui_confirm +t+ 811.1s ▶ 语音:「吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。」 +t+ 813.3s ♪ 音效 sfx/ui_menu +t+ 813.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 813.3s ♪ 向导音:menu(系统界面) +t+ 813.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 815.4s ▶ 语音:「回到直播。」 +t+ 817.5s ▶ 语音:「重新开始今晚。」 +t+ 819.6s ♪ 音效 sfx/ui_confirm +t+ 819.6s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 819.6s ♪ 向导音:explore(探索/安全) +t+ 819.6s ▶ 语音:「吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。」 +t+ 821.7s 〰 震动:error(做错了:两连短震) +t+ 821.7s 〰 震动:error(做错了:两连短震) +t+ 821.7s 〰 震动:error(做错了:两连短震) +t+ 821.7s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 823.9s ▶ 语音:「现在是今晚第四通电话。」 +t+ 826.0s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 828.1s ▶ 语音:「吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。」 +t+ 830.2s 〰 震动:error(做错了:两连短震) +t+ 830.2s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 832.3s ▶ 语音:「现在是今晚第四通电话。」 +t+ 834.5s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 836.6s ▶ 语音:「吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。」 +t+ 838.7s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 843.7s 〰 震动:error(做错了:两连短震) +t+ 843.7s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 845.8s ▶ 语音:「现在是今晚第四通电话。」 +t+ 847.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 850.0s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 855.0s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 857.1s ▶ 语音:「现在是今晚第四通电话。」 +t+ 859.2s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 861.3s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 866.3s ♪ 音效 sfx/ui_menu +t+ 866.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 866.3s ♪ 向导音:menu(系统界面) +t+ 866.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 868.4s ▶ 语音:「回到直播。」 +t+ 870.5s ▶ 语音:「重新开始今晚。」 +t+ 872.7s ♪ 音效 sfx/ui_confirm +t+ 872.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 872.7s ♪ 向导音:explore(探索/安全) +t+ 872.7s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 877.6s 〰 震动:error(做错了:两连短震) +t+ 877.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 879.8s ▶ 语音:「现在是今晚第四通电话。」 +t+ 881.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 884.0s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 889.0s 〰 震动:error(做错了:两连短震) +t+ 889.0s 〰 震动:error(做错了:两连短震) +t+ 889.0s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 893.9s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 898.9s ♪ 音效 sfx/ui_menu +t+ 898.9s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 898.9s ♪ 向导音:menu(系统界面) +t+ 898.9s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 901.0s ▶ 语音:「回到直播。」 +t+ 903.1s ▶ 语音:「重新开始今晚。」 +t+ 905.2s ♪ 音效 sfx/ui_confirm +t+ 905.2s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 905.2s ♪ 向导音:explore(探索/安全) +t+ 905.2s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 910.2s ♪ 音效 sfx/ui_menu +t+ 910.2s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 910.2s ♪ 向导音:menu(系统界面) +t+ 910.2s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 912.3s ▶ 语音:「回到直播。」 +t+ 914.4s ▶ 语音:「重新开始今晚。」 +t+ 916.6s ♪ 音效 sfx/ui_confirm +t+ 916.6s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 916.6s ♪ 向导音:explore(探索/安全) +t+ 916.6s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 921.5s ♪ 音效 sfx/ui_menu +t+ 921.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 921.5s ♪ 向导音:menu(系统界面) +t+ 921.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 923.7s ▶ 语音:「回到直播。」 +t+ 925.8s ▶ 语音:「重新开始今晚。」 +t+ 927.9s ♪ 音效 sfx/ui_confirm +t+ 927.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 927.9s ♪ 向导音:explore(探索/安全) +t+ 927.9s ▶ 语音:「嗯……我这就往回走。谢谢你,听我瞎嚷嚷。」 +t+ 932.9s ▶ 语音:「她往回走了。成不成,看他们自己的了。」 +t+ 935.0s ♪ 向导音:settle(结算) +t+ 935.0s ♪ 音效 sfx/line_drop +t+ 935.0s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+ 938.0s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+ 938.0s ♪ 向导音:turn(轮到你) +t+ 938.0s 〰 震动:confirm(轻点确认) +t+ 938.0s ♪ 音效 sfx/amb_cab +t+ 938.0s ♪ 音效 sfx/line_connect +t+ 938.0s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 940.1s ♪ 音效 sfx/ui_menu +t+ 940.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 940.1s ♪ 向导音:menu(系统界面) +t+ 940.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 942.2s ▶ 语音:「回到直播。」 +t+ 944.3s ▶ 语音:「重新开始今晚。」 +t+ 946.5s ♪ 音效 sfx/ui_confirm +t+ 946.5s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 946.5s ♪ 向导音:explore(探索/安全) +t+ 946.5s ♪ 音效 sfx/amb_cab +t+ 946.5s ♪ 音效 sfx/line_connect +t+ 946.5s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 948.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 950.7s ▶ 语音:「现在是今晚第五通电话。」 +t+ 952.8s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 954.9s ♪ 音效 sfx/amb_cab +t+ 954.9s ♪ 音效 sfx/line_connect +t+ 954.9s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 957.1s ♪ 音效 sfx/ui_menu +t+ 957.1s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 957.1s ♪ 向导音:menu(系统界面) +t+ 957.1s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 959.2s ▶ 语音:「回到直播。」 +t+ 961.3s ▶ 语音:「重新开始今晚。」 +t+ 963.4s ♪ 音效 sfx/ui_confirm +t+ 963.4s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 963.4s ♪ 向导音:explore(探索/安全) +t+ 963.4s ♪ 音效 sfx/amb_cab +t+ 963.4s ♪ 音效 sfx/line_connect +t+ 963.4s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 965.5s ♪ 音效 sfx/ui_menu +t+ 965.5s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+ 965.5s ♪ 向导音:menu(系统界面) +t+ 965.5s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+ 967.7s ▶ 语音:「回到直播。」 +t+ 969.8s ▶ 语音:「重新开始今晚。」 +t+ 971.9s ♪ 音效 sfx/ui_confirm +t+ 971.9s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+ 971.9s ♪ 向导音:explore(探索/安全) +t+ 971.9s ♪ 音效 sfx/amb_cab +t+ 971.9s ♪ 音效 sfx/line_connect +t+ 971.9s ▶ 语音:「五线来电。引擎声没停过——他在开车!」 +t+ 974.0s ▶ 语音:「主播,别挂。陪我说话,我的眼皮,沉得很。」 +t+ 979.0s ▶ 语音:「跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。」 +t+ 984.6s 〰 震动:error(做错了:两连短震) +t+ 984.6s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+ 986.7s ▶ 语音:「现在是今晚第五通电话。」 +t+ 988.9s ▶ 语音:「今晚的口碑,不好不坏。」 +t+ 991.0s ▶ 语音:「跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。」 +t+ 996.6s 〰 震动:error(做错了:两连短震) +t+ 996.6s ♪ 向导音:choice(抉择/等待输入) +t+ 996.6s ▶ 语音:「方向盘比话筒危险。向左滑,陪他聊聊闺女。向右滑,让他立刻进服务区。」 +t+ 998.7s ♪ 音效 sfx/ui_confirm +t+ 998.7s ▶ 语音:「说说她。开学第一天,她跟你说的头一句话,是什么?」 +t+1000.8s 〰 震动:error(做错了:两连短震) +t+1000.8s ▶ 语音:「单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。」 +t+1003.0s ▶ 语音:「现在是今晚第五通电话。」 +t+1005.1s ▶ 语音:「今晚的口碑,在听众里立住了。」 +t+1007.2s ▶ 语音:「说说她。开学第一天,她跟你说的头一句话,是什么?」 +t+1009.3s ♪ 音效 sfx/ui_menu +t+1009.3s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+1009.3s ♪ 向导音:menu(系统界面) +t+1009.3s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+1011.4s ▶ 语音:「回到直播。」 +t+1013.6s ▶ 语音:「重新开始今晚。」 +t+1015.7s ♪ 音效 sfx/ui_confirm +t+1015.7s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+1015.7s ♪ 向导音:explore(探索/安全) +t+1015.7s ▶ 语音:「说说她。开学第一天,她跟你说的头一句话,是什么?」 +t+1017.8s 〰 震动:error(做错了:两连短震) +t+1017.8s ♪ 音效 sfx/ui_menu +t+1017.8s ♫ BGM 切换 → bgm/menu(淡入 500ms) +t+1017.8s ♪ 向导音:menu(系统界面) +t+1017.8s ▶ 语音:「菜单。单击切换选项,双击确认。」 +t+1019.9s ▶ 语音:「回到直播。」 +t+1022.0s ▶ 语音:「重新开始今晚。」 +t+1024.2s ♪ 音效 sfx/ui_confirm +t+1024.2s ♫ BGM 切换 → bgm/onair(淡入 800ms) +t+1024.2s ♪ 向导音:explore(探索/安全) +t+1024.2s ▶ 语音:「说说她。开学第一天,她跟你说的头一句话,是什么?」 +t+1026.3s ▶ 语音:「她说,爸,我在学校给你赚了朵小红花。哈哈,哈哈……」 +t+1032.4s ♪ 向导音:danger(危险) +t+1032.4s ♪ 音效 sfx/brake_screech +t+1032.4s 〰 震动:danger(危险逼近) +t+1034.4s ▶ 语音:「车道偏了——听!」 +t+1036.5s ▶ 语音:「嘶——好险!前车急刹,我擦着护栏过去了。彻底醒了!」 +t+1042.6s ▶ 语音:「吓醒的,也算醒。这一趟,聊到家。」 +t+1044.7s ▶ 语音:「对了。头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的。」 +t+1046.8s ♪ 向导音:settle(结算) +t+1046.8s ♪ 音效 sfx/line_drop +t+1046.8s ♫ BGM 切换 → bgm/bridge(淡入 800ms) +t+1046.8s ▶ 语音:「今晚的口碑,在听众里立住了。」 +t+1051.9s ▶ 语音:「五线收工。垫乐起——零点档,还剩最后一段。」 +t+1054.0s ♫ BGM 切换 → bgm/onair(淡入 600ms) +t+1054.0s ♪ 向导音:choice(抉择/等待输入) +t+1054.0s ▶ 语音:「留言机红灯在闪,是老主播三年前没播完的那句晚安。向左滑,替他播出去。向右滑,照常收播。」 +t+1056.2s ♪ 音效 sfx/ui_confirm +t+1056.2s ▶ 语音:「各位,今晚到这里。明晚零点,今夜不眠,不见不散。」 +t+1058.3s ▶ 语音:「留言机的红灯,又多亮了一夜。」 +t+1060.4s ♪ 向导音:settle(结算) +t+1060.4s ♫ BGM 切换 → bgm/ending(淡入 1500ms) +t+1060.4s ▶ 语音:「今晚的热线烧得发烫。全城的失眠者,都被接住了。」 +t+1062.5s ▶ 语音:「只是路过留言机的时候,你还是没敢看它。红灯灭得很慢。」 +t+1064.6s ▶ 语音:「收播了。导播间替你记了一笔账——今晚的人都怎样了。」 +t+1066.8s ▶ 语音:「老周:孙子替他买了老花镜。回信说,谢谢那首歌。」 +t+1068.9s ▶ 语音:「小鹿退了票。检票口的灯,记得那趟没上成的车。」 +t+1071.0s ▶ 语音:「糖糖后来又打来过一次。还是那句:楼下在吵。」 +t+1073.1s ▶ 语音:「陈姐在公车站坐到了末班车。那通电话,成了没寄出的信。」 +t+1075.2s ▶ 语音:「老韩到家了。后视镜上,多了朵小红花。」 +t+1077.4s 【状态】乱按剧本收尾:接听 5 次、8 个抉择、乱试 199 次、逛菜单 38 次 结局和五条回响都听完了。单击再来一局,下滑开菜单(BGM=bgm/ending,语速1) + +#### 【玩家动作】全程没有任何一次手势被无声吞掉,也没有卡死在某个状态里——结局链照样走完 + diff --git a/tools/gen_voice_dual.py b/tools/gen_voice_dual.py index c1680ba9..92a6c4e1 100644 --- a/tools/gen_voice_dual.py +++ b/tools/gen_voice_dual.py @@ -18,7 +18,7 @@ 用法(GPU 必须串行): source env.d/tts.sh - flock /home/ubuntu/SoundGame/.gpu.lock .venv-tts/bin/python tools/gen_voice_dual.py --game simon --gender both + flock /home/xsl/SoundGame/.gpu.lock .venv-tts/bin/python tools/gen_voice_dual.py --game simon --gender both .venv-tts/bin/python tools/gen_voice_dual.py --game all --check # 无需 GPU source env.d/android.sh && .venv-tts/bin/python tools/gen_voice_dual.py --game all --convert """ @@ -28,10 +28,10 @@ import re import subprocess import sys -os.environ.setdefault("HF_HOME", "/home/ubuntu/SoundGame/models/hf") +os.environ.setdefault("HF_HOME", "/home/xsl/SoundGame/models/hf") os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com") -ROOT = "/home/ubuntu/SoundGame" +ROOT = "/home/xsl/SoundGame" STAGING = os.path.join(ROOT, "tools", "gen_voice_dual_wav") SPLIT_LIMIT = 40 # 超过 40 字拆句 GAP_SEC = 0.12 # 拼接句间静音 @@ -50,6 +50,9 @@ SPEAKERS = { "rhythm": {"male": "Uncle_Fu", "female": "Vivian"}, "space": {"male": "Uncle_Fu", "female": "Serena"}, "rpg": {"male": "Uncle_Fu", "female": "Serena"}, + # radio 默认 speaker 仅作兜底;双性别行按 VOICE_SPK["radio"] 逐句覆盖(HOST/PROD 双配对) + "radio": {"male": "Uncle_Fu", "female": "Serena"}, + "dungeon": {"male": "Uncle_Fu", "female": "Serena"}, } # instruct 对:(male, female)。None 表示两性别共用同一 instruct。 @@ -600,6 +603,322 @@ RPG_NPC = [ ] NPCS = {"rpg": RPG_NPC} + +# ---------------------------------------------------------------- radio +# 《不眠频率》批次 1(docs/design/15-radio-voice-ids.md,132 句:双性别 103 + 平铺 NPC 29)。 +# 台词全文由提取脚本逐字照抄 voice-ids 文档;双性别组分 HOST(主播)/ PROD(导播/系统) +# 两个 speaker 配对(同性别双音色,VOICE_SPK 覆盖);NPC 单音色平铺(RADIO_NPC)+ 通话带通(NPC_BANDPASS)。 +_RADIO_HOST = _pair("低沉温和的中年男声,深夜电台主播,沉稳亲切,有磁性", + "温暖清晰的年轻女声,深夜电台主播,沉稳亲切,不甜腻") +_RADIO_PROD = _pair("贴近耳机的低语男声,年轻电台导播,冷静克制", + "贴近耳机的低语女声,年轻电台导播,冷静克制") +RADIO_HOST_LINES = [ + ("b_open", _RADIO_HOST, "午夜零点,红灯亮起。欢迎回到《今夜不眠》。"), + ("b_rain", _RADIO_HOST, "雨下了一整晚。热线灯,马上就要亮了。"), + ("z1_song", _RADIO_HOST, "大爷别急,这首歌我也会。咱们一句一句来。"), + ("z1_ask", _RADIO_HOST, "大爷,路口有什么招牌?念一个给我听听。"), + ("z1_stay", _RADIO_HOST, "哪儿也不去。我陪你,这首歌还长着呢。"), + ("z1_pol", _RADIO_HOST, "大爷,我把您转给派出所,他们马上就到。"), + ("l1_go", _RADIO_HOST, "你妈等的不是奖杯,是你进门喊的那声妈。"), + ("l1_free", _RADIO_HOST, "退不退,都是你的自由。夜还长,慢慢想。"), + ("k1_lull", _RADIO_HOST, "闭上眼睛。风把云朵盖在被子上,月亮替你值夜班。"), + ("k1_ask", _RADIO_HOST, "睡不着的时候,心里是不是装着事?说给我听听。"), + ("k1_truth", _RADIO_HOST, "糖糖,记住,大人的事,不是小孩的错。一个字都不是。"), + ("k1_lie", _RADIO_HOST, "睡吧。明天一睁眼,什么都会好起来的。"), + ("w1_say", _RADIO_HOST, "四十分钟前,有个孩子问我们:爸妈摔门,是不是她的错。"), + ("w1_say2", _RADIO_HOST, "今天有位小朋友,还在等爸妈回家切蛋糕。"), + ("w1_air", _RADIO_HOST, "陈姐,别挂。让他接电话——全城都在听。"), + ("w1_priv", _RADIO_HOST, "这里只有我们俩了。想说就说,想哭就哭。"), + ("w1_home", _RADIO_HOST, "吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。"), + ("h1_talk", _RADIO_HOST, "说说她。开学第一天,她跟你说的头一句话,是什么?"), + ("h1_stop", _RADIO_HOST, "听我的,前面三公里有服务区。学费急,命更急。"), + ("d9_face", _RADIO_HOST, "师父,三年了。这句晚安,我替您播。各位,晚安。"), + ("d9_avoid", _RADIO_HOST, "各位,今晚到这里。明晚零点,今夜不眠,不见不散。"), + ("e1_a", _RADIO_HOST, "天亮了,热线灯还亮着。你们的晚安,是这座城市的心跳。"), + ("e1_b", _RADIO_HOST, "这把话筒,我总算接得住了。明晚零点,接着不眠。"), + ("e2_a", _RADIO_HOST, "今晚的热线烧得发烫。全城的失眠者,都被接住了。"), + ("e2_b", _RADIO_HOST, "只是路过留言机的时候,你还是没敢看它。红灯灭得很慢。"), + ("e3_a", _RADIO_HOST, "口碑掉了一夜。可那句迟到三年的晚安,今晚说出去了。"), + ("e3_b", _RADIO_HOST, "一个人的频道,也是频道。师父,收到了吗?"), + ("e4_a", _RADIO_HOST, "今夜不眠,静音收播。有的夜班,谁也接不住谁。"), + ("e4_b", _RADIO_HOST, "留言机还在闪。你没看它,它也没怪你。明晚再说吧。"), +] + +RADIO_PROD_LINES = [ + ("gd_brief", _RADIO_PROD, "单击继续节目。请选择的时候,向左或向右滑。"), + ("gd_continue", _RADIO_PROD, "单击屏幕,继续。"), + ("gd_again", _RADIO_PROD, "还在吗?单击继续。"), + ("gd_invalid", _RADIO_PROD, "这个手势,直播间里用不上。"), + ("gd_ring1", _RADIO_PROD, "热线在闪。单击,接听。"), + ("gd_ring2", _RADIO_PROD, "灯还亮着,听众在等。单击,接听。"), + ("sys_help", _RADIO_PROD, "单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。"), + ("sys_menu", _RADIO_PROD, "菜单。单击切换选项,双击确认。"), + ("sys_menu_resume", _RADIO_PROD, "回到直播。"), + ("sys_menu_restart", _RADIO_PROD, "重新开始今晚。"), + ("sys_menu_lobby", _RADIO_PROD, "保存并回大厅。"), + ("sys_settings", _RADIO_PROD, "音色、语速、背景音乐,请回大厅修改。"), + ("sys_skip", _RADIO_PROD, "想跳过这句,单击就行。"), + ("sys_continue_ask", _RADIO_PROD, "检测到你的夜班记录。单击继续上次,右滑开始新的夜晚。"), + ("sys_new_night", _RADIO_PROD, "新的夜晚,开始了。"), + ("sys_saved", _RADIO_PROD, "进度已保存。"), + ("prog_c1", _RADIO_PROD, "现在是今晚第一通电话。"), + ("prog_c2", _RADIO_PROD, "现在是今晚第二通电话。"), + ("prog_c3", _RADIO_PROD, "现在是今晚第三通电话。"), + ("prog_c4", _RADIO_PROD, "现在是今晚第四通电话。"), + ("prog_c5", _RADIO_PROD, "现在是今晚第五通电话。"), + ("rep_high", _RADIO_PROD, "今晚的口碑,在听众里立住了。"), + ("rep_mid", _RADIO_PROD, "今晚的口碑,不好不坏。"), + ("rep_low", _RADIO_PROD, "今晚的口碑,还没立起来。"), + ("b_rule", _RADIO_PROD, "台里的规矩就一条:先接住人,再接电话。"), + ("b_goal", _RADIO_PROD, "今晚五通电话。说什么话,由你决定。"), + ("z1_ring_in", _RADIO_PROD, "一线来电。七十多岁,雨夜,走丢了。"), + ("p1", _RADIO_PROD, "老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。"), + ("z1_sdone", _RADIO_PROD, "他稳住了。老歌,比路牌管用。"), + ("z1_adone", _RADIO_PROD, "他慌了。先接住情绪,再走流程。"), + ("p2", _RADIO_PROD, "雨声更密了。向左滑,留在原地陪他等巡逻队。向右滑,转接派出所。"), + ("z1_staye", _RADIO_PROD, "十五分钟后,巡逻灯停在路口。全城听见老人笑了。"), + ("z1_pole", _RADIO_PROD, "转接完成,地址报得清。干净利落。"), + ("z1_bade", _RADIO_PROD, "地址没报清就转接,雨又大。这通砸了。先稳人,再办事。"), + ("z1_ok1", _RADIO_PROD, "就这么接。记住这个手感。"), + ("l1_in", _RADIO_PROD, "二线来电。姑娘,声音很轻,像怕吵到谁。"), + ("p3", _RADIO_PROD, "向左滑,告诉她这趟车值得上。向右滑,把选择还给她。"), + ("l1_goe", _RADIO_PROD, "二线收工。这句话,值一个头条。"), + ("l1_freee", _RADIO_PROD, "她走了。没上车,也没退票。年轻人的夜,长了点。"), + ("k1_in", _RADIO_PROD, "三线来电。听呼吸,是个小孩。"), + ("p4", _RADIO_PROD, "向左滑,唱一段摇篮曲哄她睡。向右滑,轻轻问她怎么了。"), + ("k1_lulle", _RADIO_PROD, "睡着了。可你没听见,她在怕什么。"), + ("p5", _RADIO_PROD, "孩子的声音在抖。向左滑,告诉她这不是她的错。向右滑,答应她明天都会好。"), + ("k1_truthe", _RADIO_PROD, "蛋糕。记下来——四线的来电,可能跟这个家有关。"), + ("k1_liee", _RADIO_PROD, "你许了个兑不了的现。孩子睡了,谎留下了。"), + ("w1_in", _RADIO_PROD, "四线来电。户外风声大,是位大姐。"), + ("p6", _RADIO_PROD, "摔门声你听见了。向左滑,替孩子给她带句话。向右滑,劝她回家把话说开。"), + ("w1_saye", _RADIO_PROD, "她攥着电话,很久没出声。风都停了。"), + ("w1_say2e", _RADIO_PROD, "话递到了。就差最后一脚。"), + ("p7", _RADIO_PROD, "她的呼吸平了。向左滑,留在直播里,让全城等这个门铃。向右滑,换垫乐,私下陪她说完。"), + ("w1_aire", _RADIO_PROD, "电话那头,传来跑步声。今晚的高光时刻。"), + ("w1_prive", _RADIO_PROD, "灯牌是你亲手关的,故事还是完整的。稳。"), + ("w1_homee", _RADIO_PROD, "她往回走了。成不成,看他们自己的了。"), + ("h1_in", _RADIO_PROD, "五线来电。引擎声没停过——他在开车!"), + ("p8", _RADIO_PROD, "方向盘比话筒危险。向左滑,陪他聊聊闺女。向右滑,让他立刻进服务区。"), + ("h1_danger", _RADIO_PROD, "车道偏了——听!"), + ("h1_talke", _RADIO_PROD, "吓醒的,也算醒。这一趟,聊到家。"), + ("h1_stope", _RADIO_PROD, "他下了高速。单子黄了,人齐了。"), + ("h1_echo", _RADIO_PROD, "对了。头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的。"), + ("g9_in", _RADIO_PROD, "五线收工。垫乐起——零点档,还剩最后一段。"), + ("p9", _RADIO_PROD, "留言机红灯在闪,是老主播三年前没播完的那句晚安。向左滑,替他播出去。向右滑,照常收播。"), + ("d9_facee", _RADIO_PROD, "带子里其实只有电流声。但空着的那一秒,你说满了。"), + ("d9_avoide", _RADIO_PROD, "留言机的红灯,又多亮了一夜。"), + ("ep_lead", _RADIO_PROD, "收播了。导播间替你记了一笔账——今晚的人都怎样了。"), + ("ep_zhou_ok", _RADIO_PROD, "老周:孙子替他买了老花镜。回信说,谢谢那首歌。"), + ("ep_zhou_no", _RADIO_PROD, "老周在便利店屋檐下坐到天亮。没出事,也没人听见他的歌。"), + ("ep_deer_ok", _RADIO_PROD, "小鹿上车了。她妈后来打电话说,那声妈,比奖杯值钱。"), + ("ep_deer_no", _RADIO_PROD, "小鹿退了票。检票口的灯,记得那趟没上成的车。"), + ("ep_kid_ok", _RADIO_PROD, "糖糖家的门,这个月没再摔过。她说这是主播魔法。"), + ("ep_kid_no", _RADIO_PROD, "糖糖后来又打来过一次。还是那句:楼下在吵。"), + ("ep_wife_ok", _RADIO_PROD, "陈姐家切了蛋糕。那句「回来吧」,是我这晚听过最好的声音。"), + ("ep_wife_no", _RADIO_PROD, "陈姐在公车站坐到了末班车。那通电话,成了没寄出的信。"), + ("ep_han_ok", _RADIO_PROD, "老韩到家了。后视镜上,多了朵小红花。"), + ("ep_han_no", _RADIO_PROD, "老韩在服务区睡了一觉。学费齐了,那首歌没放成。"), +] + +RADIO_LINES = RADIO_HOST_LINES + RADIO_PROD_LINES + +# NPC 单音色平铺(id, speaker, instruct, 台词)→ web/radio/audio/voice/.* +#(转码时按 15-radio.md §6.2 逐来电者带通,见 NPC_BANDPASS;staging 保留未滤波原声)。 +RADIO_NPC = [ + ("z1_a", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "主播……我,我找不到回家的路了。"), + ("z1_b", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "雨太大了。我本来,只想买副老花镜。"), + ("z1_songo", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "对,对!路口修车铺,天天放这首!"), + ("z1_asko", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "招牌……雨糊住了,我眼花,看不清啊……"), + ("z1_stayo", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "好,好。有人说话,我就不怕了。"), + ("z1_polo", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "好,好。谢谢你啊,谢谢。"), + ("z1_bad", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "说不清……我真是,说不清了……"), + ("l1_a", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "主播,退票窗口十一点半就关了。我站这儿半小时了。"), + ("l1_b", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "票是回去看我妈的。可我试镜又黄了,两手空空……"), + ("l1_goo", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "呜……嗯。不退了,我现在就去检票。谢谢您。"), + ("l1_freeo", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "嗯……我再想想。窗口要关了,我先过去了。"), + ("k1_a", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "喂……有人吗?我睡不着。"), + ("k1_b", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "家里灯关了。可是楼下,一直有声音。"), + ("k1_lullo", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "月亮也睡了……那我也睡。"), + ("k1_asko", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "爸爸妈妈摔门了。是不是……我做错了什么呀。"), + ("k1_trutho", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "嗯……那我睡了。他们明天,会一起给我切蛋糕吗?"), + ("k1_lieo", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "真的吗?拉钩。晚安……"), + ("w1_a", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。"), + ("w1_b", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "明天孩子生日!蛋糕都订好了,他人要今晚走!"), + ("w1_sayo", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "什么?她还没睡?这么晚了,她还醒着?"), + ("w1_say2o", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "蛋糕……是他订的。他嘴上不说,心里都记着。"), + ("w1_airo", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "喂!回来吧!蛋糕要化了,孩子等着呢!"), + ("w1_privq", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "呜……其实他订完蛋糕就跟我道歉了。是我,把门摔得太响。"), + ("w1_homeo", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "嗯……我这就往回走。谢谢你,听我瞎嚷嚷。"), + ("h1_a", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "主播,别挂。陪我说话,我的眼皮,沉得很。"), + ("h1_b", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。"), + ("h1_talko", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "她说,爸,我在学校给你赚了朵小红花。哈哈,哈哈……"), + ("h1_dango", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "嘶——好险!前车急刹,我擦着护栏过去了。彻底醒了!"), + ("h1_stopo", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "行,听你的。进了服务区,我给你们放首歌听。"), +] + +# ---------------------------------------------------------------- dungeon +# 《回声地窟》批次 1(docs/design/16-dungeon-voice-ids.md,125 句 ×2 性别,无 NPC)。 +_DUN = _pair("低沉醇厚的男声,像探窟向导,沉稳有力", + "平静清晰的女声,像探窟向导,沉稳可信") +DUNGEON_LINES = [ + ("intro_1", _DUN, "你在黑暗中醒来。这里是回声地窟。"), + ("intro_2", _DUN, "伸手不见五指,声音就是你的眼睛。"), + ("sys_continue_ask", _DUN, "检测到探窟记录。单击继续,右滑重新开始。"), + ("sys_new_run", _DUN, "新的探窟,开始了。"), + ("tut_d0", _DUN, "先学会用耳朵看。单击,发出声波脉冲。"), + ("tut_echo_come", _DUN, "听,回声回来了。"), + ("tut_front_rule", _DUN, "回声回来越快,墙就越近。"), + ("tut_side_rule", _DUN, "两侧的回声,发闷是墙,空旷是通路。"), + ("tut_walk", _DUN, "按住屏幕不放,你就往前走。"), + ("tut_turn", _DUN, "左滑右滑,可以转身。"), + ("tut_replay", _DUN, "双击,再发一次脉冲,不要钱。"), + ("tut_status", _DUN, "上滑,随时听自己的状态。"), + ("tut_menu", _DUN, "下滑,打开菜单。"), + ("tut_mob", _DUN, "前面有东西!放心,它伤不到你。"), + ("tut_parry", _DUN, "听到它的动静,双击格挡。"), + ("tut_parry_ok", _DUN, "挡住了!"), + ("tut_parry_miss", _DUN, "没挡住也别怕,它伤不到你。"), + ("tut_attack", _DUN, "现在单击,挥拳打它。"), + ("tut_kill", _DUN, "打中了,就是这样。"), + ("tut_stairs", _DUN, "铁器的回声,就是下潜的楼梯。"), + ("tut_stairs_on", _DUN, "楼梯就在脚下。单击,下潜。"), + ("tut_done", _DUN, "教学完了。地窟很深,小心走。"), + ("tut_skip", _DUN, "已跳过教学。单击脉冲,先听听四周。"), + ("sys_saved", _DUN, "进度已保存。"), + ("sys_menu", _DUN, "菜单。单击切换,双击确认。"), + ("sys_menu_resume", _DUN, "返回探索。"), + ("sys_menu_save", _DUN, "保存并回大厅。"), + ("sys_menu_help", _DUN, "重新听帮助。"), + ("sys_menu_restart", _DUN, "重新开始本周目。"), + ("help_explore", _DUN, "单击脉冲,按住前进,左右滑转身。双击再听,上滑状态,下滑菜单。"), + ("help_battle", _DUN, "敌人前摇声响时双击格挡,单击挥击。打不过就按住屏幕逃跑。"), + ("settings_hint", _DUN, "音色、语速、背景音乐,请回大厅修改。"), + ("st_floor_1", _DUN, "这是第一层。"), + ("st_floor_2", _DUN, "这是第二层。"), + ("st_floor_3", _DUN, "这是第三层。"), + ("st_floor_4", _DUN, "这是第四层。"), + ("st_floor_5", _DUN, "这是第五层。"), + ("st_floor_6", _DUN, "这是第六层。"), + ("st_floor_7", _DUN, "这是第七层。"), + ("st_hp_1", _DUN, "你毫发无损。"), + ("st_hp_2", _DUN, "你受了点伤。"), + ("st_hp_3", _DUN, "你伤势不轻。"), + ("st_hp_4", _DUN, "你命悬一线。"), + ("st_gold_1", _DUN, "口袋空空。"), + ("st_gold_2", _DUN, "有些积蓄。"), + ("st_gold_3", _DUN, "收获可观。"), + ("st_gold_4", _DUN, "腰包鼓鼓。"), + ("st_face_e", _DUN, "你面朝东。"), + ("st_face_s", _DUN, "你面朝南。"), + ("st_face_w", _DUN, "你面朝西。"), + ("st_face_n", _DUN, "你面朝北。"), + ("st_threat_none", _DUN, "附近很安静。"), + ("st_threat_left", _DUN, "左边有脚步声。"), + ("st_threat_right", _DUN, "右边有脚步声。"), + ("st_threat_both", _DUN, "两边都有动静。"), + ("st_best_1", _DUN, "你还没到过第三层。"), + ("st_best_2", _DUN, "你到过中层。"), + ("st_best_3", _DUN, "你到过最深层。"), + ("evt_coin_1", _DUN, "捡到几枚金币。"), + ("evt_coin_2", _DUN, "捡到一小把金币。"), + ("evt_coin_3", _DUN, "捡到一大袋金币。"), + ("evt_potion", _DUN, "一瓶药水,收进腰带。"), + ("evt_potion_full", _DUN, "腰带满了,药水留在原地。"), + ("evt_weapon", _DUN, "捡到更快的短刃,出手更狠了。"), + ("evt_armor", _DUN, "换上更硬的护甲,更抗打了。"), + ("evt_stairs_lock", _DUN, "楼梯被大东西堵住了,先赢它。"), + ("evt_descend", _DUN, "你顺着楼梯,往下潜。"), + ("btl_bat", _DUN, "盲眼蝠扑了过来!"), + ("btl_rat", _DUN, "窟鼠窜了出来!"), + ("btl_mush", _DUN, "蘑菇怪拦住了你!"), + ("btl_crab", _DUN, "窟主石甲蟹,就在你面前。"), + ("btl_crawler", _DUN, "绕行者贴上来了,小心背后!"), + ("btl_spider", _DUN, "洞蛛落地了!"), + ("btl_leech", _DUN, "窟主腐沼蟒,挡住了楼梯。"), + ("btl_guard", _DUN, "石像卫醒了!"), + ("btl_king", _DUN, "窟中之王,就在深处。"), + ("btl_parried", _DUN, "挡下了大半!"), + ("btl_behind", _DUN, "它在你背后,转身再挡。"), + ("btl_swish", _DUN, "挥空了,它不在你面前。"), + ("btl_no_turn", _DUN, "它贴着你不放,不用转身。"), + ("btl_kill", _DUN, "它倒下了。"), + ("btl_loot_1", _DUN, "战利不多。"), + ("btl_loot_2", _DUN, "小有收获。"), + ("btl_loot_3", _DUN, "大有收获。"), + ("btl_lvup", _DUN, "你的拳脚更利落了。"), + ("btl_flee", _DUN, "你甩掉了它,被抓了一下。"), + ("btl_flee_boss", _DUN, "退路塌了,跑不掉!"), + ("btl_half_crab", _DUN, "石甲蟹的甲裂了!"), + ("btl_half_leech", _DUN, "腐沼蟒暴怒了!"), + ("btl_half_king", _DUN, "王怒了,脚步更重了!"), + ("die_intro", _DUN, "你倒下了。"), + ("die_cause_1", _DUN, "你被小怪拖垮了。"), + ("die_cause_2", _DUN, "你没挡住那记重击。"), + ("die_cause_3", _DUN, "你背后的偷袭,要了命。"), + ("die_adv_1", _DUN, "下次,前摇声响时就双击。"), + ("die_adv_2", _DUN, "打不过,就按住屏幕逃跑。"), + ("die_adv_3", _DUN, "身后有动静,先转身。"), + ("gd_pulse", _DUN, "单击脉冲,听听四周。"), + ("gd_turn", _DUN, "轮到你了。单击挥击。"), + ("gd_invalid", _DUN, "这个操作在这里用不上。"), + ("stair_hint_a", _DUN, "找找铁器的回声,那是楼梯。"), + ("stair_hint_b", _DUN, "楼梯有大东西守着,先赢它。"), + ("stair_dir_e", _DUN, "楼梯在东边。"), + ("stair_dir_s", _DUN, "楼梯在南边。"), + ("stair_dir_w", _DUN, "楼梯在西边。"), + ("stair_dir_n", _DUN, "楼梯在北边。"), + ("nav_e", _DUN, "往东。"), + ("nav_s", _DUN, "往南。"), + ("nav_w", _DUN, "往西。"), + ("nav_n", _DUN, "往北。"), + ("nav_done", _DUN, "到了,就是这里。"), + ("meta_gold_1", _DUN, "这趟没带回什么。"), + ("meta_gold_2", _DUN, "这趟带回一些金币。"), + ("meta_gold_3", _DUN, "这趟满载而归。"), + ("settle_stronger", _DUN, "下个周目,你会更强。"), + ("settle_win_1", _DUN, "你捧起了地窟之心,通关了!"), + ("settle_win_2", _DUN, "黑暗的尽头,是光。"), + ("settle_again", _DUN, "单击,再探一次。"), + ("unlock_lead", _DUN, "攒够了新的遗产。"), + ("unlock_potion", _DUN, "解锁行囊药,开局自带一瓶。"), + ("unlock_vigor", _DUN, "解锁体魄,起始更强壮。"), + ("unlock_veil", _DUN, "解锁护符,每层头一下减半。"), + ("unlock_echo", _DUN, "解锁回声强化,侧向听得更远。"), + ("unlock_blade", _DUN, "解锁武器,开局带着短刃。"), + ("unlock_none", _DUN, "金币还不够,继续攒。"), +] + +NPCS = {"rpg": RPG_NPC, "radio": RADIO_NPC} + +# radio 双性别行的逐句 speaker 覆盖(HOST=主播 / PROD=导播,同性别两个音色, +# 见 15-radio-voice-ids.md 录制说明:同场景高频对话的两者永不同 speaker)。 +VOICE_SPK = {"radio": {}} +VOICE_SPK["radio"].update({fid: ("Uncle_Fu", "Serena") for fid, _i, _t in RADIO_HOST_LINES}) +VOICE_SPK["radio"].update({fid: ("Dylan", "Vivian") for fid, _i, _t in RADIO_PROD_LINES}) + +# radio 来电者电话滤波(15-radio.md §6.2,全部 + 轻压缩):按 NPC id 前缀分档, +# convert 转码时应用;staging wav 保留未滤波原声以便重滤/回退(宁窄勿脏)。 +NPC_BANDPASS = { + "radio": [ + ("z1_", "highpass=f=500,lowpass=f=2800"), # 老周:老旧手机,窄而颤抖 + ("l1_", "highpass=f=400,lowpass=f=3400"), # 小鹿:车站手机,干净偏冷 + ("k1_", "highpass=f=450,lowpass=f=3000"), # 糖糖:家用座机,小而近 + ("w1_", "highpass=f=420,lowpass=f=3100"), # 陈姐:户外手机,风噪 + ("h1_", "highpass=f=300,lowpass=f=3400"), # 老韩:车载蓝牙,宽而轰 + ] +} +_NPC_ACOMP = "acompressor=threshold=-20dB:ratio=3:attack=10:release=150" # 轻压缩(全档) + + +def npc_af(game, fid): + """NPC id -> ffmpeg -af 字符串(无滤波档返回 None)。""" + for prefix, af in NPC_BANDPASS.get(game, []): + if fid.startswith(prefix): + return af + "," + _NPC_ACOMP + return None + + GAMES = { "storyline": STORYLINE_LINES, "escape": ESCAPE_LINES, @@ -607,6 +926,8 @@ GAMES = { "rhythm": RHYTHM_LINES, "space": SPACE_LINES, "rpg": RPG_LINES, + "radio": RADIO_LINES, + "dungeon": DUNGEON_LINES, } @@ -684,18 +1005,20 @@ def generate(games, genders, only): for gender in genders: speaker = SPEAKERS[game][gender] ins_idx = 0 if gender == "male" else 1 + spk_map = VOICE_SPK.get(game, {}) outdir = os.path.join(STAGING, game, gender) os.makedirs(outdir, exist_ok=True) - # 展开成 (fid, chunk_idx, n_chunks, text, instruct),跳过已完成 + # 展开成 (fid, chunk_idx, n_chunks, text, instruct, speaker),跳过已完成 items = [] for fid, ins_pair, text in lines: path = staging_path(game, gender, fid) if valid_wav(path): continue + spk = spk_map.get(fid, (speaker, speaker))[ins_idx] chunks = split_text(text) for ci, ctext in enumerate(chunks): - items.append((fid, ci, len(chunks), ctext, ins_pair[ins_idx])) + items.append((fid, ci, len(chunks), ctext, ins_pair[ins_idx], spk)) if not items: print(f"[{game}/{gender}] nothing to do", flush=True) continue @@ -711,11 +1034,11 @@ def generate(games, genders, only): wavs, sr = model.generate_custom_voice( text=[b[3] for b in batch], language=["Chinese"] * len(batch), - speaker=[speaker] * len(batch), + speaker=[b[5] for b in batch], instruct=[b[4] for b in batch], max_new_tokens=2048, ) - for (fid, ci, _n, _t, _i), wav in zip(batch, wavs): + for (fid, ci, _n, _t, _i, _s), wav in zip(batch, wavs): results.setdefault(fid, {})[ci] = np.asarray(wav, dtype=np.float32) print(f"[{game}/{gender}] batch {bi + 1}/{len(queue)} ({kind}, n={len(batch)})", flush=True) @@ -822,16 +1145,18 @@ def convert(games): subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src, "-ar", "44100", "-c:a", "aac", "-b:a", "128k", base + ".m4a"], check=True) total += 1 - # NPC 平铺(staging /npc/ → audio/voice/.*) + # NPC 平铺(staging /npc/ → audio/voice/.*;radio 按 NPC_BANDPASS 带通) for fid, _sp, _ins, _t in NPCS.get(game, []): src = os.path.join(STAGING, game, "npc", fid + ".wav") if not os.path.exists(src): print(f"[convert] WARN missing {game}/npc/{fid}.wav") continue base = os.path.join(ROOT, "web", game, "audio", "voice", fid) - subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src, + af = npc_af(game, fid) + af_args = ["-af", af] if af else [] + subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src, *af_args, "-ar", "44100", "-c:a", "libvorbis", "-q:a", "3", base + ".ogg"], check=True) - subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src, + subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src, *af_args, "-ar", "44100", "-c:a", "aac", "-b:a", "128k", base + ".m4a"], check=True) total += 1 print(f"[convert] {game} done") diff --git a/tools/mureka_log.md b/tools/mureka_log.md index 2a6d9e4d..1749e3a0 100644 --- a/tools/mureka_log.md +++ b/tools/mureka_log.md @@ -114,3 +114,33 @@ menu pad 用 loudnorm **I=-24**/TP=-2(比主题曲低一档,10 文档 §1.2 (引擎再按 volume 0.35 播放)→ ogg q:a 3 + m4a 128k。 成品 mean:storyline -24.4 / escape -26.1 / simon -25.8 / rhythm -25.3 / space -25.1 / rpg menu -25.9 / rpg explore -22.3 dB。 flac 原件存 tools/mureka_out/menu_*/ 与 rpg_explore/。 + +## 2026-09-01 批次 1:radio《不眠频率》+ dungeon《回声地窟》BGM(8 首) + +预算线:本轮 $0.30(每游戏 4 首 × $0.03);总预算 $2.00。后处理脚本入库: +`web/radio/gen/gen_bgm.sh` / `web/dungeon/gen/gen_bgm.sh`(首个强 onset 起点 → 93s 裁剪 + +首尾 3s acrossfade(tri) 无缝循环 → loudnorm → 44.1kHz → ogg q:a 3 + m4a 128k,成品均 90.00s)。 + +| # | task id | 用途 | 状态 | 花费 | +|---|---|---|---|---| +| 22 | 158551062544386 | radio bgm/onair(2 候选) | succeeded | $0.03 | +| 23 | 158551207313409 | radio bgm/bridge(2 候选) | succeeded | $0.03 | +| 24 | 158551383277574 | radio bgm/menu(2 候选) | succeeded | $0.03 | +| 25 | 158551538401281 | radio bgm/ending(2 候选) | succeeded | $0.03 | +| 26 | 158551678976001 | dungeon bgm/menu(2 候选) | succeeded | $0.03 | +| 27 | 158551855333377 | dungeon bgm/explore(2 候选) | succeeded | $0.03 | +| 28 | 158552010457089 | dungeon bgm/battle(2 候选) | succeeded | $0.03 | +| 29 | 158552209555457 | dungeon bgm/boss(2 候选) | succeeded | $0.03 | + +**本轮花费:$0.24 / $0.30。累计:$0.87 / $2.00。** 8 首全部一次成功无重试。 + +选用(拿不准选时长更接近 90s 的候选):onair=候选 0(135.5s,choice 158551165370369); +bridge=候选 0(211.2s,choice 158551341465604);menu=候选 0(188.6s,choice 158551500914690); +ending=候选 1(189.2s,choice 158551630872578);dungeon menu=候选 0(162.8s,choice 158551817322499); +explore=候选 0(177.0s,choice 158551978868739);battle=候选 0(186.4s,choice 158552169709569); +boss=候选 0(149.5s,choice 158552331321345)。 + +响度:radio menu 按 10 号文档 §1.2 用 I=-24(成品 mean -24.7dB,menu pad 档); +radio onair/bridge/ending 与 dungeon 4 首按各设计文档用 I=-20(成品 mean -20.4 ~ -21.3dB)。 +本作无节奏关键曲目(dungeon battle/boss 的 bpm 仅为特征词,不做节拍网格同步),未跑 bpm_precise。 +flac 原件存 tools/mureka_out/{radio,dungeon}-*/。 diff --git a/tools/node b/tools/node new file mode 120000 index 00000000..3b330b66 --- /dev/null +++ b/tools/node @@ -0,0 +1 @@ +/home/xsl/.local/node-v22.23.2-linux-x64 \ No newline at end of file diff --git a/web/dungeon/audio/bgm/battle.m4a b/web/dungeon/audio/bgm/battle.m4a new file mode 100644 index 00000000..29fe657e Binary files /dev/null and b/web/dungeon/audio/bgm/battle.m4a differ diff --git a/web/dungeon/audio/bgm/battle.ogg b/web/dungeon/audio/bgm/battle.ogg new file mode 100644 index 00000000..5c4ec5a4 Binary files /dev/null and b/web/dungeon/audio/bgm/battle.ogg differ diff --git a/web/dungeon/audio/bgm/boss.m4a b/web/dungeon/audio/bgm/boss.m4a new file mode 100644 index 00000000..c816cfce Binary files /dev/null and b/web/dungeon/audio/bgm/boss.m4a differ diff --git a/web/dungeon/audio/bgm/boss.ogg b/web/dungeon/audio/bgm/boss.ogg new file mode 100644 index 00000000..419e302b Binary files /dev/null and b/web/dungeon/audio/bgm/boss.ogg differ diff --git a/web/dungeon/audio/bgm/explore.m4a b/web/dungeon/audio/bgm/explore.m4a new file mode 100644 index 00000000..ee31afa1 Binary files /dev/null and b/web/dungeon/audio/bgm/explore.m4a differ diff --git a/web/dungeon/audio/bgm/explore.ogg b/web/dungeon/audio/bgm/explore.ogg new file mode 100644 index 00000000..f243838f Binary files /dev/null and b/web/dungeon/audio/bgm/explore.ogg differ diff --git a/web/dungeon/audio/bgm/menu.m4a b/web/dungeon/audio/bgm/menu.m4a new file mode 100644 index 00000000..eec7852a Binary files /dev/null and b/web/dungeon/audio/bgm/menu.m4a differ diff --git a/web/dungeon/audio/bgm/menu.ogg b/web/dungeon/audio/bgm/menu.ogg new file mode 100644 index 00000000..8fbf0e54 Binary files /dev/null and b/web/dungeon/audio/bgm/menu.ogg differ diff --git a/web/dungeon/audio/sfx/amb_cave.m4a b/web/dungeon/audio/sfx/amb_cave.m4a new file mode 100644 index 00000000..4d50daac Binary files /dev/null and b/web/dungeon/audio/sfx/amb_cave.m4a differ diff --git a/web/dungeon/audio/sfx/amb_cave.ogg b/web/dungeon/audio/sfx/amb_cave.ogg new file mode 100644 index 00000000..9673751a Binary files /dev/null and b/web/dungeon/audio/sfx/amb_cave.ogg differ diff --git a/web/dungeon/audio/sfx/atk_hit.m4a b/web/dungeon/audio/sfx/atk_hit.m4a new file mode 100644 index 00000000..de251231 Binary files /dev/null and b/web/dungeon/audio/sfx/atk_hit.m4a differ diff --git a/web/dungeon/audio/sfx/atk_hit.ogg b/web/dungeon/audio/sfx/atk_hit.ogg new file mode 100644 index 00000000..b539b58b Binary files /dev/null and b/web/dungeon/audio/sfx/atk_hit.ogg differ diff --git a/web/dungeon/audio/sfx/atk_swing.m4a b/web/dungeon/audio/sfx/atk_swing.m4a new file mode 100644 index 00000000..3b731fd7 Binary files /dev/null and b/web/dungeon/audio/sfx/atk_swing.m4a differ diff --git a/web/dungeon/audio/sfx/atk_swing.ogg b/web/dungeon/audio/sfx/atk_swing.ogg new file mode 100644 index 00000000..c1642bda Binary files /dev/null and b/web/dungeon/audio/sfx/atk_swing.ogg differ diff --git a/web/dungeon/audio/sfx/bump.m4a b/web/dungeon/audio/sfx/bump.m4a new file mode 100644 index 00000000..7df0f28f Binary files /dev/null and b/web/dungeon/audio/sfx/bump.m4a differ diff --git a/web/dungeon/audio/sfx/bump.ogg b/web/dungeon/audio/sfx/bump.ogg new file mode 100644 index 00000000..6b178848 Binary files /dev/null and b/web/dungeon/audio/sfx/bump.ogg differ diff --git a/web/dungeon/audio/sfx/coin.m4a b/web/dungeon/audio/sfx/coin.m4a new file mode 100644 index 00000000..33e467fe Binary files /dev/null and b/web/dungeon/audio/sfx/coin.m4a differ diff --git a/web/dungeon/audio/sfx/coin.ogg b/web/dungeon/audio/sfx/coin.ogg new file mode 100644 index 00000000..56565a61 Binary files /dev/null and b/web/dungeon/audio/sfx/coin.ogg differ diff --git a/web/dungeon/audio/sfx/echo_1.m4a b/web/dungeon/audio/sfx/echo_1.m4a new file mode 100644 index 00000000..57d7e9c1 Binary files /dev/null and b/web/dungeon/audio/sfx/echo_1.m4a differ diff --git a/web/dungeon/audio/sfx/echo_1.ogg b/web/dungeon/audio/sfx/echo_1.ogg new file mode 100644 index 00000000..f08b32c2 Binary files /dev/null and b/web/dungeon/audio/sfx/echo_1.ogg differ diff --git a/web/dungeon/audio/sfx/echo_2.m4a b/web/dungeon/audio/sfx/echo_2.m4a new file mode 100644 index 00000000..356307fc Binary files /dev/null and b/web/dungeon/audio/sfx/echo_2.m4a differ diff --git a/web/dungeon/audio/sfx/echo_2.ogg b/web/dungeon/audio/sfx/echo_2.ogg new file mode 100644 index 00000000..451e9133 Binary files /dev/null and b/web/dungeon/audio/sfx/echo_2.ogg differ diff --git a/web/dungeon/audio/sfx/echo_3.m4a b/web/dungeon/audio/sfx/echo_3.m4a new file mode 100644 index 00000000..e8618e02 Binary files /dev/null and b/web/dungeon/audio/sfx/echo_3.m4a differ diff --git a/web/dungeon/audio/sfx/echo_3.ogg b/web/dungeon/audio/sfx/echo_3.ogg new file mode 100644 index 00000000..5e73fdf7 Binary files /dev/null and b/web/dungeon/audio/sfx/echo_3.ogg differ diff --git a/web/dungeon/audio/sfx/echo_4.m4a b/web/dungeon/audio/sfx/echo_4.m4a new file mode 100644 index 00000000..7b7eebaa Binary files /dev/null and b/web/dungeon/audio/sfx/echo_4.m4a differ diff --git a/web/dungeon/audio/sfx/echo_4.ogg b/web/dungeon/audio/sfx/echo_4.ogg new file mode 100644 index 00000000..5c10ff1a Binary files /dev/null and b/web/dungeon/audio/sfx/echo_4.ogg differ diff --git a/web/dungeon/audio/sfx/enemy_die.m4a b/web/dungeon/audio/sfx/enemy_die.m4a new file mode 100644 index 00000000..e845f9a6 Binary files /dev/null and b/web/dungeon/audio/sfx/enemy_die.m4a differ diff --git a/web/dungeon/audio/sfx/enemy_die.ogg b/web/dungeon/audio/sfx/enemy_die.ogg new file mode 100644 index 00000000..9bd0c41b Binary files /dev/null and b/web/dungeon/audio/sfx/enemy_die.ogg differ diff --git a/web/dungeon/audio/sfx/gear.m4a b/web/dungeon/audio/sfx/gear.m4a new file mode 100644 index 00000000..b2b4f7d6 Binary files /dev/null and b/web/dungeon/audio/sfx/gear.m4a differ diff --git a/web/dungeon/audio/sfx/gear.ogg b/web/dungeon/audio/sfx/gear.ogg new file mode 100644 index 00000000..7fe89f7e Binary files /dev/null and b/web/dungeon/audio/sfx/gear.ogg differ diff --git a/web/dungeon/audio/sfx/hurt.m4a b/web/dungeon/audio/sfx/hurt.m4a new file mode 100644 index 00000000..680f4b0a Binary files /dev/null and b/web/dungeon/audio/sfx/hurt.m4a differ diff --git a/web/dungeon/audio/sfx/hurt.ogg b/web/dungeon/audio/sfx/hurt.ogg new file mode 100644 index 00000000..bca9223b Binary files /dev/null and b/web/dungeon/audio/sfx/hurt.ogg differ diff --git a/web/dungeon/audio/sfx/levelup.m4a b/web/dungeon/audio/sfx/levelup.m4a new file mode 100644 index 00000000..7a3df5bd Binary files /dev/null and b/web/dungeon/audio/sfx/levelup.m4a differ diff --git a/web/dungeon/audio/sfx/levelup.ogg b/web/dungeon/audio/sfx/levelup.ogg new file mode 100644 index 00000000..8d79aba9 Binary files /dev/null and b/web/dungeon/audio/sfx/levelup.ogg differ diff --git a/web/dungeon/audio/sfx/mat_door.m4a b/web/dungeon/audio/sfx/mat_door.m4a new file mode 100644 index 00000000..3874b58c Binary files /dev/null and b/web/dungeon/audio/sfx/mat_door.m4a differ diff --git a/web/dungeon/audio/sfx/mat_door.ogg b/web/dungeon/audio/sfx/mat_door.ogg new file mode 100644 index 00000000..26c75510 Binary files /dev/null and b/web/dungeon/audio/sfx/mat_door.ogg differ diff --git a/web/dungeon/audio/sfx/mat_stair.m4a b/web/dungeon/audio/sfx/mat_stair.m4a new file mode 100644 index 00000000..ebcdce1a Binary files /dev/null and b/web/dungeon/audio/sfx/mat_stair.m4a differ diff --git a/web/dungeon/audio/sfx/mat_stair.ogg b/web/dungeon/audio/sfx/mat_stair.ogg new file mode 100644 index 00000000..42e218d4 Binary files /dev/null and b/web/dungeon/audio/sfx/mat_stair.ogg differ diff --git a/web/dungeon/audio/sfx/mat_stone.m4a b/web/dungeon/audio/sfx/mat_stone.m4a new file mode 100644 index 00000000..cc50302f Binary files /dev/null and b/web/dungeon/audio/sfx/mat_stone.m4a differ diff --git a/web/dungeon/audio/sfx/mat_stone.ogg b/web/dungeon/audio/sfx/mat_stone.ogg new file mode 100644 index 00000000..458f2408 Binary files /dev/null and b/web/dungeon/audio/sfx/mat_stone.ogg differ diff --git a/web/dungeon/audio/sfx/mat_water.m4a b/web/dungeon/audio/sfx/mat_water.m4a new file mode 100644 index 00000000..0e1c277b Binary files /dev/null and b/web/dungeon/audio/sfx/mat_water.m4a differ diff --git a/web/dungeon/audio/sfx/mat_water.ogg b/web/dungeon/audio/sfx/mat_water.ogg new file mode 100644 index 00000000..8a550c74 Binary files /dev/null and b/web/dungeon/audio/sfx/mat_water.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_bug.m4a b/web/dungeon/audio/sfx/mob_step_bug.m4a new file mode 100644 index 00000000..8f62d422 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_bug.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_bug.ogg b/web/dungeon/audio/sfx/mob_step_bug.ogg new file mode 100644 index 00000000..b015e115 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_bug.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_bug_c.m4a b/web/dungeon/audio/sfx/mob_step_bug_c.m4a new file mode 100644 index 00000000..5b1b4d7b Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_bug_c.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_bug_c.ogg b/web/dungeon/audio/sfx/mob_step_bug_c.ogg new file mode 100644 index 00000000..0948cfd4 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_bug_c.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_bug_l.m4a b/web/dungeon/audio/sfx/mob_step_bug_l.m4a new file mode 100644 index 00000000..7cc36e9b Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_bug_l.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_bug_l.ogg b/web/dungeon/audio/sfx/mob_step_bug_l.ogg new file mode 100644 index 00000000..27a65094 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_bug_l.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_bug_r.m4a b/web/dungeon/audio/sfx/mob_step_bug_r.m4a new file mode 100644 index 00000000..bc0bdd7c Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_bug_r.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_bug_r.ogg b/web/dungeon/audio/sfx/mob_step_bug_r.ogg new file mode 100644 index 00000000..de1dc63a Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_bug_r.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_heavy.m4a b/web/dungeon/audio/sfx/mob_step_heavy.m4a new file mode 100644 index 00000000..b3be4d00 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_heavy.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_heavy.ogg b/web/dungeon/audio/sfx/mob_step_heavy.ogg new file mode 100644 index 00000000..92666773 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_heavy.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_heavy_c.m4a b/web/dungeon/audio/sfx/mob_step_heavy_c.m4a new file mode 100644 index 00000000..b960688b Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_heavy_c.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_heavy_c.ogg b/web/dungeon/audio/sfx/mob_step_heavy_c.ogg new file mode 100644 index 00000000..cc184115 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_heavy_c.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_heavy_l.m4a b/web/dungeon/audio/sfx/mob_step_heavy_l.m4a new file mode 100644 index 00000000..a7331692 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_heavy_l.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_heavy_l.ogg b/web/dungeon/audio/sfx/mob_step_heavy_l.ogg new file mode 100644 index 00000000..715c4463 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_heavy_l.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_heavy_r.m4a b/web/dungeon/audio/sfx/mob_step_heavy_r.m4a new file mode 100644 index 00000000..b4be3597 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_heavy_r.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_heavy_r.ogg b/web/dungeon/audio/sfx/mob_step_heavy_r.ogg new file mode 100644 index 00000000..084e1f89 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_heavy_r.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_small.m4a b/web/dungeon/audio/sfx/mob_step_small.m4a new file mode 100644 index 00000000..acb1a162 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_small.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_small.ogg b/web/dungeon/audio/sfx/mob_step_small.ogg new file mode 100644 index 00000000..8e63ad0b Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_small.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_small_c.m4a b/web/dungeon/audio/sfx/mob_step_small_c.m4a new file mode 100644 index 00000000..702609e0 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_small_c.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_small_c.ogg b/web/dungeon/audio/sfx/mob_step_small_c.ogg new file mode 100644 index 00000000..ee27d04a Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_small_c.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_small_l.m4a b/web/dungeon/audio/sfx/mob_step_small_l.m4a new file mode 100644 index 00000000..5777ee6e Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_small_l.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_small_l.ogg b/web/dungeon/audio/sfx/mob_step_small_l.ogg new file mode 100644 index 00000000..0ad9989c Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_small_l.ogg differ diff --git a/web/dungeon/audio/sfx/mob_step_small_r.m4a b/web/dungeon/audio/sfx/mob_step_small_r.m4a new file mode 100644 index 00000000..4395ef41 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_small_r.m4a differ diff --git a/web/dungeon/audio/sfx/mob_step_small_r.ogg b/web/dungeon/audio/sfx/mob_step_small_r.ogg new file mode 100644 index 00000000..1afd1878 Binary files /dev/null and b/web/dungeon/audio/sfx/mob_step_small_r.ogg differ diff --git a/web/dungeon/audio/sfx/parry.m4a b/web/dungeon/audio/sfx/parry.m4a new file mode 100644 index 00000000..e113c9ec Binary files /dev/null and b/web/dungeon/audio/sfx/parry.m4a differ diff --git a/web/dungeon/audio/sfx/parry.ogg b/web/dungeon/audio/sfx/parry.ogg new file mode 100644 index 00000000..ec15cfbc Binary files /dev/null and b/web/dungeon/audio/sfx/parry.ogg differ diff --git a/web/dungeon/audio/sfx/potion.m4a b/web/dungeon/audio/sfx/potion.m4a new file mode 100644 index 00000000..b09d5997 Binary files /dev/null and b/web/dungeon/audio/sfx/potion.m4a differ diff --git a/web/dungeon/audio/sfx/potion.ogg b/web/dungeon/audio/sfx/potion.ogg new file mode 100644 index 00000000..ac0df147 Binary files /dev/null and b/web/dungeon/audio/sfx/potion.ogg differ diff --git a/web/dungeon/audio/sfx/pulse.m4a b/web/dungeon/audio/sfx/pulse.m4a new file mode 100644 index 00000000..a8b64d90 Binary files /dev/null and b/web/dungeon/audio/sfx/pulse.m4a differ diff --git a/web/dungeon/audio/sfx/pulse.ogg b/web/dungeon/audio/sfx/pulse.ogg new file mode 100644 index 00000000..ccf503ec Binary files /dev/null and b/web/dungeon/audio/sfx/pulse.ogg differ diff --git a/web/dungeon/audio/sfx/side_open_l.m4a b/web/dungeon/audio/sfx/side_open_l.m4a new file mode 100644 index 00000000..e36f100e Binary files /dev/null and b/web/dungeon/audio/sfx/side_open_l.m4a differ diff --git a/web/dungeon/audio/sfx/side_open_l.ogg b/web/dungeon/audio/sfx/side_open_l.ogg new file mode 100644 index 00000000..599b0488 Binary files /dev/null and b/web/dungeon/audio/sfx/side_open_l.ogg differ diff --git a/web/dungeon/audio/sfx/side_open_r.m4a b/web/dungeon/audio/sfx/side_open_r.m4a new file mode 100644 index 00000000..b24ffb30 Binary files /dev/null and b/web/dungeon/audio/sfx/side_open_r.m4a differ diff --git a/web/dungeon/audio/sfx/side_open_r.ogg b/web/dungeon/audio/sfx/side_open_r.ogg new file mode 100644 index 00000000..9ded0106 Binary files /dev/null and b/web/dungeon/audio/sfx/side_open_r.ogg differ diff --git a/web/dungeon/audio/sfx/side_wall_l.m4a b/web/dungeon/audio/sfx/side_wall_l.m4a new file mode 100644 index 00000000..bc6e7b23 Binary files /dev/null and b/web/dungeon/audio/sfx/side_wall_l.m4a differ diff --git a/web/dungeon/audio/sfx/side_wall_l.ogg b/web/dungeon/audio/sfx/side_wall_l.ogg new file mode 100644 index 00000000..758a51e5 Binary files /dev/null and b/web/dungeon/audio/sfx/side_wall_l.ogg differ diff --git a/web/dungeon/audio/sfx/side_wall_r.m4a b/web/dungeon/audio/sfx/side_wall_r.m4a new file mode 100644 index 00000000..b4357165 Binary files /dev/null and b/web/dungeon/audio/sfx/side_wall_r.m4a differ diff --git a/web/dungeon/audio/sfx/side_wall_r.ogg b/web/dungeon/audio/sfx/side_wall_r.ogg new file mode 100644 index 00000000..c9f04348 Binary files /dev/null and b/web/dungeon/audio/sfx/side_wall_r.ogg differ diff --git a/web/dungeon/audio/sfx/stairs_down.m4a b/web/dungeon/audio/sfx/stairs_down.m4a new file mode 100644 index 00000000..94018ecf Binary files /dev/null and b/web/dungeon/audio/sfx/stairs_down.m4a differ diff --git a/web/dungeon/audio/sfx/stairs_down.ogg b/web/dungeon/audio/sfx/stairs_down.ogg new file mode 100644 index 00000000..3f56a7eb Binary files /dev/null and b/web/dungeon/audio/sfx/stairs_down.ogg differ diff --git a/web/dungeon/audio/sfx/step_own.m4a b/web/dungeon/audio/sfx/step_own.m4a new file mode 100644 index 00000000..15263c3e Binary files /dev/null and b/web/dungeon/audio/sfx/step_own.m4a differ diff --git a/web/dungeon/audio/sfx/step_own.ogg b/web/dungeon/audio/sfx/step_own.ogg new file mode 100644 index 00000000..e5476bf1 Binary files /dev/null and b/web/dungeon/audio/sfx/step_own.ogg differ diff --git a/web/dungeon/audio/sfx/step_turn.m4a b/web/dungeon/audio/sfx/step_turn.m4a new file mode 100644 index 00000000..5a08632e Binary files /dev/null and b/web/dungeon/audio/sfx/step_turn.m4a differ diff --git a/web/dungeon/audio/sfx/step_turn.ogg b/web/dungeon/audio/sfx/step_turn.ogg new file mode 100644 index 00000000..776a2779 Binary files /dev/null and b/web/dungeon/audio/sfx/step_turn.ogg differ diff --git a/web/dungeon/audio/sfx/tel_bug.m4a b/web/dungeon/audio/sfx/tel_bug.m4a new file mode 100644 index 00000000..ee927960 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_bug.m4a differ diff --git a/web/dungeon/audio/sfx/tel_bug.ogg b/web/dungeon/audio/sfx/tel_bug.ogg new file mode 100644 index 00000000..7c285338 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_bug.ogg differ diff --git a/web/dungeon/audio/sfx/tel_bug_l.m4a b/web/dungeon/audio/sfx/tel_bug_l.m4a new file mode 100644 index 00000000..176052df Binary files /dev/null and b/web/dungeon/audio/sfx/tel_bug_l.m4a differ diff --git a/web/dungeon/audio/sfx/tel_bug_l.ogg b/web/dungeon/audio/sfx/tel_bug_l.ogg new file mode 100644 index 00000000..7d900219 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_bug_l.ogg differ diff --git a/web/dungeon/audio/sfx/tel_bug_r.m4a b/web/dungeon/audio/sfx/tel_bug_r.m4a new file mode 100644 index 00000000..0ee84772 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_bug_r.m4a differ diff --git a/web/dungeon/audio/sfx/tel_bug_r.ogg b/web/dungeon/audio/sfx/tel_bug_r.ogg new file mode 100644 index 00000000..281e2a4d Binary files /dev/null and b/web/dungeon/audio/sfx/tel_bug_r.ogg differ diff --git a/web/dungeon/audio/sfx/tel_heavy.m4a b/web/dungeon/audio/sfx/tel_heavy.m4a new file mode 100644 index 00000000..f5731d76 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_heavy.m4a differ diff --git a/web/dungeon/audio/sfx/tel_heavy.ogg b/web/dungeon/audio/sfx/tel_heavy.ogg new file mode 100644 index 00000000..87e5703f Binary files /dev/null and b/web/dungeon/audio/sfx/tel_heavy.ogg differ diff --git a/web/dungeon/audio/sfx/tel_heavy_l.m4a b/web/dungeon/audio/sfx/tel_heavy_l.m4a new file mode 100644 index 00000000..a24604e8 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_heavy_l.m4a differ diff --git a/web/dungeon/audio/sfx/tel_heavy_l.ogg b/web/dungeon/audio/sfx/tel_heavy_l.ogg new file mode 100644 index 00000000..4138371e Binary files /dev/null and b/web/dungeon/audio/sfx/tel_heavy_l.ogg differ diff --git a/web/dungeon/audio/sfx/tel_heavy_r.m4a b/web/dungeon/audio/sfx/tel_heavy_r.m4a new file mode 100644 index 00000000..55324487 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_heavy_r.m4a differ diff --git a/web/dungeon/audio/sfx/tel_heavy_r.ogg b/web/dungeon/audio/sfx/tel_heavy_r.ogg new file mode 100644 index 00000000..622bc4b3 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_heavy_r.ogg differ diff --git a/web/dungeon/audio/sfx/tel_small.m4a b/web/dungeon/audio/sfx/tel_small.m4a new file mode 100644 index 00000000..f213b4ba Binary files /dev/null and b/web/dungeon/audio/sfx/tel_small.m4a differ diff --git a/web/dungeon/audio/sfx/tel_small.ogg b/web/dungeon/audio/sfx/tel_small.ogg new file mode 100644 index 00000000..127eb961 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_small.ogg differ diff --git a/web/dungeon/audio/sfx/tel_small_l.m4a b/web/dungeon/audio/sfx/tel_small_l.m4a new file mode 100644 index 00000000..b9bbf298 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_small_l.m4a differ diff --git a/web/dungeon/audio/sfx/tel_small_l.ogg b/web/dungeon/audio/sfx/tel_small_l.ogg new file mode 100644 index 00000000..0d500406 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_small_l.ogg differ diff --git a/web/dungeon/audio/sfx/tel_small_r.m4a b/web/dungeon/audio/sfx/tel_small_r.m4a new file mode 100644 index 00000000..8ad706e8 Binary files /dev/null and b/web/dungeon/audio/sfx/tel_small_r.m4a differ diff --git a/web/dungeon/audio/sfx/tel_small_r.ogg b/web/dungeon/audio/sfx/tel_small_r.ogg new file mode 100644 index 00000000..0a073b0f Binary files /dev/null and b/web/dungeon/audio/sfx/tel_small_r.ogg differ diff --git a/web/dungeon/audio/sfx/turn_tick.m4a b/web/dungeon/audio/sfx/turn_tick.m4a new file mode 100644 index 00000000..e0d89780 Binary files /dev/null and b/web/dungeon/audio/sfx/turn_tick.m4a differ diff --git a/web/dungeon/audio/sfx/turn_tick.ogg b/web/dungeon/audio/sfx/turn_tick.ogg new file mode 100644 index 00000000..9d3ef0b2 Binary files /dev/null and b/web/dungeon/audio/sfx/turn_tick.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_bat.m4a b/web/dungeon/audio/voice/female/btl_bat.m4a new file mode 100644 index 00000000..bf7f3287 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_bat.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_bat.ogg b/web/dungeon/audio/voice/female/btl_bat.ogg new file mode 100644 index 00000000..5e8a9bc2 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_bat.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_behind.m4a b/web/dungeon/audio/voice/female/btl_behind.m4a new file mode 100644 index 00000000..b3053925 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_behind.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_behind.ogg b/web/dungeon/audio/voice/female/btl_behind.ogg new file mode 100644 index 00000000..71946be0 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_behind.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_crab.m4a b/web/dungeon/audio/voice/female/btl_crab.m4a new file mode 100644 index 00000000..1d4f0ebe Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_crab.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_crab.ogg b/web/dungeon/audio/voice/female/btl_crab.ogg new file mode 100644 index 00000000..acbeacae Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_crab.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_crawler.m4a b/web/dungeon/audio/voice/female/btl_crawler.m4a new file mode 100644 index 00000000..4f460003 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_crawler.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_crawler.ogg b/web/dungeon/audio/voice/female/btl_crawler.ogg new file mode 100644 index 00000000..deef6ad8 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_crawler.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_flee.m4a b/web/dungeon/audio/voice/female/btl_flee.m4a new file mode 100644 index 00000000..4284adee Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_flee.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_flee.ogg b/web/dungeon/audio/voice/female/btl_flee.ogg new file mode 100644 index 00000000..f6d6a497 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_flee.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_flee_boss.m4a b/web/dungeon/audio/voice/female/btl_flee_boss.m4a new file mode 100644 index 00000000..5434bbd5 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_flee_boss.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_flee_boss.ogg b/web/dungeon/audio/voice/female/btl_flee_boss.ogg new file mode 100644 index 00000000..1a891a35 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_flee_boss.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_guard.m4a b/web/dungeon/audio/voice/female/btl_guard.m4a new file mode 100644 index 00000000..403459a9 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_guard.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_guard.ogg b/web/dungeon/audio/voice/female/btl_guard.ogg new file mode 100644 index 00000000..3c15a3b7 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_guard.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_half_crab.m4a b/web/dungeon/audio/voice/female/btl_half_crab.m4a new file mode 100644 index 00000000..4a0dc57d Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_half_crab.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_half_crab.ogg b/web/dungeon/audio/voice/female/btl_half_crab.ogg new file mode 100644 index 00000000..0518e674 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_half_crab.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_half_king.m4a b/web/dungeon/audio/voice/female/btl_half_king.m4a new file mode 100644 index 00000000..4e5a9acf Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_half_king.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_half_king.ogg b/web/dungeon/audio/voice/female/btl_half_king.ogg new file mode 100644 index 00000000..f86c0ea1 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_half_king.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_half_leech.m4a b/web/dungeon/audio/voice/female/btl_half_leech.m4a new file mode 100644 index 00000000..6e8f0179 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_half_leech.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_half_leech.ogg b/web/dungeon/audio/voice/female/btl_half_leech.ogg new file mode 100644 index 00000000..77e37c0f Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_half_leech.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_kill.m4a b/web/dungeon/audio/voice/female/btl_kill.m4a new file mode 100644 index 00000000..e36b7cd5 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_kill.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_kill.ogg b/web/dungeon/audio/voice/female/btl_kill.ogg new file mode 100644 index 00000000..667d4064 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_kill.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_king.m4a b/web/dungeon/audio/voice/female/btl_king.m4a new file mode 100644 index 00000000..04fbd2b5 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_king.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_king.ogg b/web/dungeon/audio/voice/female/btl_king.ogg new file mode 100644 index 00000000..d11a9d9d Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_king.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_leech.m4a b/web/dungeon/audio/voice/female/btl_leech.m4a new file mode 100644 index 00000000..fd799230 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_leech.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_leech.ogg b/web/dungeon/audio/voice/female/btl_leech.ogg new file mode 100644 index 00000000..c65fd0e9 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_leech.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_loot_1.m4a b/web/dungeon/audio/voice/female/btl_loot_1.m4a new file mode 100644 index 00000000..0003d262 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_loot_1.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_loot_1.ogg b/web/dungeon/audio/voice/female/btl_loot_1.ogg new file mode 100644 index 00000000..b434c137 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_loot_1.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_loot_2.m4a b/web/dungeon/audio/voice/female/btl_loot_2.m4a new file mode 100644 index 00000000..75969f02 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_loot_2.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_loot_2.ogg b/web/dungeon/audio/voice/female/btl_loot_2.ogg new file mode 100644 index 00000000..9e0eff0c Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_loot_2.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_loot_3.m4a b/web/dungeon/audio/voice/female/btl_loot_3.m4a new file mode 100644 index 00000000..e2535f91 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_loot_3.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_loot_3.ogg b/web/dungeon/audio/voice/female/btl_loot_3.ogg new file mode 100644 index 00000000..25751835 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_loot_3.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_lvup.m4a b/web/dungeon/audio/voice/female/btl_lvup.m4a new file mode 100644 index 00000000..104322d8 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_lvup.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_lvup.ogg b/web/dungeon/audio/voice/female/btl_lvup.ogg new file mode 100644 index 00000000..83eb3197 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_lvup.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_mush.m4a b/web/dungeon/audio/voice/female/btl_mush.m4a new file mode 100644 index 00000000..759eec27 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_mush.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_mush.ogg b/web/dungeon/audio/voice/female/btl_mush.ogg new file mode 100644 index 00000000..be54216f Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_mush.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_no_turn.m4a b/web/dungeon/audio/voice/female/btl_no_turn.m4a new file mode 100644 index 00000000..6007e897 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_no_turn.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_no_turn.ogg b/web/dungeon/audio/voice/female/btl_no_turn.ogg new file mode 100644 index 00000000..c92d674e Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_no_turn.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_parried.m4a b/web/dungeon/audio/voice/female/btl_parried.m4a new file mode 100644 index 00000000..abd4dcfb Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_parried.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_parried.ogg b/web/dungeon/audio/voice/female/btl_parried.ogg new file mode 100644 index 00000000..c303743f Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_parried.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_rat.m4a b/web/dungeon/audio/voice/female/btl_rat.m4a new file mode 100644 index 00000000..f9f91735 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_rat.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_rat.ogg b/web/dungeon/audio/voice/female/btl_rat.ogg new file mode 100644 index 00000000..1777ee87 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_rat.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_spider.m4a b/web/dungeon/audio/voice/female/btl_spider.m4a new file mode 100644 index 00000000..ce7f11fd Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_spider.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_spider.ogg b/web/dungeon/audio/voice/female/btl_spider.ogg new file mode 100644 index 00000000..daf1d003 Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_spider.ogg differ diff --git a/web/dungeon/audio/voice/female/btl_swish.m4a b/web/dungeon/audio/voice/female/btl_swish.m4a new file mode 100644 index 00000000..afcca09d Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_swish.m4a differ diff --git a/web/dungeon/audio/voice/female/btl_swish.ogg b/web/dungeon/audio/voice/female/btl_swish.ogg new file mode 100644 index 00000000..d9cc6faf Binary files /dev/null and b/web/dungeon/audio/voice/female/btl_swish.ogg differ diff --git a/web/dungeon/audio/voice/female/die_adv_1.m4a b/web/dungeon/audio/voice/female/die_adv_1.m4a new file mode 100644 index 00000000..f13be8d1 Binary files /dev/null and b/web/dungeon/audio/voice/female/die_adv_1.m4a differ diff --git a/web/dungeon/audio/voice/female/die_adv_1.ogg b/web/dungeon/audio/voice/female/die_adv_1.ogg new file mode 100644 index 00000000..8e7b3910 Binary files /dev/null and b/web/dungeon/audio/voice/female/die_adv_1.ogg differ diff --git a/web/dungeon/audio/voice/female/die_adv_2.m4a b/web/dungeon/audio/voice/female/die_adv_2.m4a new file mode 100644 index 00000000..3acca2ab Binary files /dev/null and b/web/dungeon/audio/voice/female/die_adv_2.m4a differ diff --git a/web/dungeon/audio/voice/female/die_adv_2.ogg b/web/dungeon/audio/voice/female/die_adv_2.ogg new file mode 100644 index 00000000..3c349690 Binary files /dev/null and b/web/dungeon/audio/voice/female/die_adv_2.ogg differ diff --git a/web/dungeon/audio/voice/female/die_adv_3.m4a b/web/dungeon/audio/voice/female/die_adv_3.m4a new file mode 100644 index 00000000..19d02bb2 Binary files /dev/null and b/web/dungeon/audio/voice/female/die_adv_3.m4a differ diff --git a/web/dungeon/audio/voice/female/die_adv_3.ogg b/web/dungeon/audio/voice/female/die_adv_3.ogg new file mode 100644 index 00000000..517a7972 Binary files /dev/null and b/web/dungeon/audio/voice/female/die_adv_3.ogg differ diff --git a/web/dungeon/audio/voice/female/die_cause_1.m4a b/web/dungeon/audio/voice/female/die_cause_1.m4a new file mode 100644 index 00000000..1dcba93b Binary files /dev/null and b/web/dungeon/audio/voice/female/die_cause_1.m4a differ diff --git a/web/dungeon/audio/voice/female/die_cause_1.ogg b/web/dungeon/audio/voice/female/die_cause_1.ogg new file mode 100644 index 00000000..f759b9f0 Binary files /dev/null and b/web/dungeon/audio/voice/female/die_cause_1.ogg differ diff --git a/web/dungeon/audio/voice/female/die_cause_2.m4a b/web/dungeon/audio/voice/female/die_cause_2.m4a new file mode 100644 index 00000000..35b08f93 Binary files /dev/null and b/web/dungeon/audio/voice/female/die_cause_2.m4a differ diff --git a/web/dungeon/audio/voice/female/die_cause_2.ogg b/web/dungeon/audio/voice/female/die_cause_2.ogg new file mode 100644 index 00000000..fc196afd Binary files /dev/null and b/web/dungeon/audio/voice/female/die_cause_2.ogg differ diff --git a/web/dungeon/audio/voice/female/die_cause_3.m4a b/web/dungeon/audio/voice/female/die_cause_3.m4a new file mode 100644 index 00000000..a3b8fd61 Binary files /dev/null and b/web/dungeon/audio/voice/female/die_cause_3.m4a differ diff --git a/web/dungeon/audio/voice/female/die_cause_3.ogg b/web/dungeon/audio/voice/female/die_cause_3.ogg new file mode 100644 index 00000000..a612fbef Binary files /dev/null and b/web/dungeon/audio/voice/female/die_cause_3.ogg differ diff --git a/web/dungeon/audio/voice/female/die_intro.m4a b/web/dungeon/audio/voice/female/die_intro.m4a new file mode 100644 index 00000000..5c003062 Binary files /dev/null and b/web/dungeon/audio/voice/female/die_intro.m4a differ diff --git a/web/dungeon/audio/voice/female/die_intro.ogg b/web/dungeon/audio/voice/female/die_intro.ogg new file mode 100644 index 00000000..3006c3e8 Binary files /dev/null and b/web/dungeon/audio/voice/female/die_intro.ogg differ diff --git a/web/dungeon/audio/voice/female/evt_armor.m4a b/web/dungeon/audio/voice/female/evt_armor.m4a new file mode 100644 index 00000000..a04b766a Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_armor.m4a differ diff --git a/web/dungeon/audio/voice/female/evt_armor.ogg b/web/dungeon/audio/voice/female/evt_armor.ogg new file mode 100644 index 00000000..39298740 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_armor.ogg differ diff --git a/web/dungeon/audio/voice/female/evt_coin_1.m4a b/web/dungeon/audio/voice/female/evt_coin_1.m4a new file mode 100644 index 00000000..02f8623d Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_coin_1.m4a differ diff --git a/web/dungeon/audio/voice/female/evt_coin_1.ogg b/web/dungeon/audio/voice/female/evt_coin_1.ogg new file mode 100644 index 00000000..3f995d5f Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_coin_1.ogg differ diff --git a/web/dungeon/audio/voice/female/evt_coin_2.m4a b/web/dungeon/audio/voice/female/evt_coin_2.m4a new file mode 100644 index 00000000..c304a376 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_coin_2.m4a differ diff --git a/web/dungeon/audio/voice/female/evt_coin_2.ogg b/web/dungeon/audio/voice/female/evt_coin_2.ogg new file mode 100644 index 00000000..25b00082 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_coin_2.ogg differ diff --git a/web/dungeon/audio/voice/female/evt_coin_3.m4a b/web/dungeon/audio/voice/female/evt_coin_3.m4a new file mode 100644 index 00000000..f2652e25 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_coin_3.m4a differ diff --git a/web/dungeon/audio/voice/female/evt_coin_3.ogg b/web/dungeon/audio/voice/female/evt_coin_3.ogg new file mode 100644 index 00000000..f6edaa21 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_coin_3.ogg differ diff --git a/web/dungeon/audio/voice/female/evt_descend.m4a b/web/dungeon/audio/voice/female/evt_descend.m4a new file mode 100644 index 00000000..833ef0db Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_descend.m4a differ diff --git a/web/dungeon/audio/voice/female/evt_descend.ogg b/web/dungeon/audio/voice/female/evt_descend.ogg new file mode 100644 index 00000000..9cd53047 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_descend.ogg differ diff --git a/web/dungeon/audio/voice/female/evt_potion.m4a b/web/dungeon/audio/voice/female/evt_potion.m4a new file mode 100644 index 00000000..9812f608 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_potion.m4a differ diff --git a/web/dungeon/audio/voice/female/evt_potion.ogg b/web/dungeon/audio/voice/female/evt_potion.ogg new file mode 100644 index 00000000..9520516e Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_potion.ogg differ diff --git a/web/dungeon/audio/voice/female/evt_potion_full.m4a b/web/dungeon/audio/voice/female/evt_potion_full.m4a new file mode 100644 index 00000000..1111e348 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_potion_full.m4a differ diff --git a/web/dungeon/audio/voice/female/evt_potion_full.ogg b/web/dungeon/audio/voice/female/evt_potion_full.ogg new file mode 100644 index 00000000..3a0a9f41 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_potion_full.ogg differ diff --git a/web/dungeon/audio/voice/female/evt_stairs_lock.m4a b/web/dungeon/audio/voice/female/evt_stairs_lock.m4a new file mode 100644 index 00000000..de405211 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_stairs_lock.m4a differ diff --git a/web/dungeon/audio/voice/female/evt_stairs_lock.ogg b/web/dungeon/audio/voice/female/evt_stairs_lock.ogg new file mode 100644 index 00000000..5a40a2b1 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_stairs_lock.ogg differ diff --git a/web/dungeon/audio/voice/female/evt_weapon.m4a b/web/dungeon/audio/voice/female/evt_weapon.m4a new file mode 100644 index 00000000..29ae88b7 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_weapon.m4a differ diff --git a/web/dungeon/audio/voice/female/evt_weapon.ogg b/web/dungeon/audio/voice/female/evt_weapon.ogg new file mode 100644 index 00000000..cd7ddfa8 Binary files /dev/null and b/web/dungeon/audio/voice/female/evt_weapon.ogg differ diff --git a/web/dungeon/audio/voice/female/gd_invalid.m4a b/web/dungeon/audio/voice/female/gd_invalid.m4a new file mode 100644 index 00000000..2ec9b622 Binary files /dev/null and b/web/dungeon/audio/voice/female/gd_invalid.m4a differ diff --git a/web/dungeon/audio/voice/female/gd_invalid.ogg b/web/dungeon/audio/voice/female/gd_invalid.ogg new file mode 100644 index 00000000..70dd7ea4 Binary files /dev/null and b/web/dungeon/audio/voice/female/gd_invalid.ogg differ diff --git a/web/dungeon/audio/voice/female/gd_pulse.m4a b/web/dungeon/audio/voice/female/gd_pulse.m4a new file mode 100644 index 00000000..5d9fb1ae Binary files /dev/null and b/web/dungeon/audio/voice/female/gd_pulse.m4a differ diff --git a/web/dungeon/audio/voice/female/gd_pulse.ogg b/web/dungeon/audio/voice/female/gd_pulse.ogg new file mode 100644 index 00000000..28dd01ea Binary files /dev/null and b/web/dungeon/audio/voice/female/gd_pulse.ogg differ diff --git a/web/dungeon/audio/voice/female/gd_turn.m4a b/web/dungeon/audio/voice/female/gd_turn.m4a new file mode 100644 index 00000000..1a5af536 Binary files /dev/null and b/web/dungeon/audio/voice/female/gd_turn.m4a differ diff --git a/web/dungeon/audio/voice/female/gd_turn.ogg b/web/dungeon/audio/voice/female/gd_turn.ogg new file mode 100644 index 00000000..0c573271 Binary files /dev/null and b/web/dungeon/audio/voice/female/gd_turn.ogg differ diff --git a/web/dungeon/audio/voice/female/help_battle.m4a b/web/dungeon/audio/voice/female/help_battle.m4a new file mode 100644 index 00000000..15fa72e2 Binary files /dev/null and b/web/dungeon/audio/voice/female/help_battle.m4a differ diff --git a/web/dungeon/audio/voice/female/help_battle.ogg b/web/dungeon/audio/voice/female/help_battle.ogg new file mode 100644 index 00000000..f414c0d0 Binary files /dev/null and b/web/dungeon/audio/voice/female/help_battle.ogg differ diff --git a/web/dungeon/audio/voice/female/help_explore.m4a b/web/dungeon/audio/voice/female/help_explore.m4a new file mode 100644 index 00000000..d659efee Binary files /dev/null and b/web/dungeon/audio/voice/female/help_explore.m4a differ diff --git a/web/dungeon/audio/voice/female/help_explore.ogg b/web/dungeon/audio/voice/female/help_explore.ogg new file mode 100644 index 00000000..ee2bea40 Binary files /dev/null and b/web/dungeon/audio/voice/female/help_explore.ogg differ diff --git a/web/dungeon/audio/voice/female/intro_1.m4a b/web/dungeon/audio/voice/female/intro_1.m4a new file mode 100644 index 00000000..6b7529e8 Binary files /dev/null and b/web/dungeon/audio/voice/female/intro_1.m4a differ diff --git a/web/dungeon/audio/voice/female/intro_1.ogg b/web/dungeon/audio/voice/female/intro_1.ogg new file mode 100644 index 00000000..0181cae1 Binary files /dev/null and b/web/dungeon/audio/voice/female/intro_1.ogg differ diff --git a/web/dungeon/audio/voice/female/intro_2.m4a b/web/dungeon/audio/voice/female/intro_2.m4a new file mode 100644 index 00000000..0793e4a2 Binary files /dev/null and b/web/dungeon/audio/voice/female/intro_2.m4a differ diff --git a/web/dungeon/audio/voice/female/intro_2.ogg b/web/dungeon/audio/voice/female/intro_2.ogg new file mode 100644 index 00000000..93c34c11 Binary files /dev/null and b/web/dungeon/audio/voice/female/intro_2.ogg differ diff --git a/web/dungeon/audio/voice/female/meta_gold_1.m4a b/web/dungeon/audio/voice/female/meta_gold_1.m4a new file mode 100644 index 00000000..ccbd8340 Binary files /dev/null and b/web/dungeon/audio/voice/female/meta_gold_1.m4a differ diff --git a/web/dungeon/audio/voice/female/meta_gold_1.ogg b/web/dungeon/audio/voice/female/meta_gold_1.ogg new file mode 100644 index 00000000..e93b3416 Binary files /dev/null and b/web/dungeon/audio/voice/female/meta_gold_1.ogg differ diff --git a/web/dungeon/audio/voice/female/meta_gold_2.m4a b/web/dungeon/audio/voice/female/meta_gold_2.m4a new file mode 100644 index 00000000..9919b961 Binary files /dev/null and b/web/dungeon/audio/voice/female/meta_gold_2.m4a differ diff --git a/web/dungeon/audio/voice/female/meta_gold_2.ogg b/web/dungeon/audio/voice/female/meta_gold_2.ogg new file mode 100644 index 00000000..458106da Binary files /dev/null and b/web/dungeon/audio/voice/female/meta_gold_2.ogg differ diff --git a/web/dungeon/audio/voice/female/meta_gold_3.m4a b/web/dungeon/audio/voice/female/meta_gold_3.m4a new file mode 100644 index 00000000..caed4fbe Binary files /dev/null and b/web/dungeon/audio/voice/female/meta_gold_3.m4a differ diff --git a/web/dungeon/audio/voice/female/meta_gold_3.ogg b/web/dungeon/audio/voice/female/meta_gold_3.ogg new file mode 100644 index 00000000..c84fb776 Binary files /dev/null and b/web/dungeon/audio/voice/female/meta_gold_3.ogg differ diff --git a/web/dungeon/audio/voice/female/nav_done.m4a b/web/dungeon/audio/voice/female/nav_done.m4a new file mode 100644 index 00000000..c6b7dee6 Binary files /dev/null and b/web/dungeon/audio/voice/female/nav_done.m4a differ diff --git a/web/dungeon/audio/voice/female/nav_done.ogg b/web/dungeon/audio/voice/female/nav_done.ogg new file mode 100644 index 00000000..5d1269e7 Binary files /dev/null and b/web/dungeon/audio/voice/female/nav_done.ogg differ diff --git a/web/dungeon/audio/voice/female/nav_e.m4a b/web/dungeon/audio/voice/female/nav_e.m4a new file mode 100644 index 00000000..5ca2cae1 Binary files /dev/null and b/web/dungeon/audio/voice/female/nav_e.m4a differ diff --git a/web/dungeon/audio/voice/female/nav_e.ogg b/web/dungeon/audio/voice/female/nav_e.ogg new file mode 100644 index 00000000..30d45534 Binary files /dev/null and b/web/dungeon/audio/voice/female/nav_e.ogg differ diff --git a/web/dungeon/audio/voice/female/nav_n.m4a b/web/dungeon/audio/voice/female/nav_n.m4a new file mode 100644 index 00000000..0861c16d Binary files /dev/null and b/web/dungeon/audio/voice/female/nav_n.m4a differ diff --git a/web/dungeon/audio/voice/female/nav_n.ogg b/web/dungeon/audio/voice/female/nav_n.ogg new file mode 100644 index 00000000..89acb46c Binary files /dev/null and b/web/dungeon/audio/voice/female/nav_n.ogg differ diff --git a/web/dungeon/audio/voice/female/nav_s.m4a b/web/dungeon/audio/voice/female/nav_s.m4a new file mode 100644 index 00000000..918b7111 Binary files /dev/null and b/web/dungeon/audio/voice/female/nav_s.m4a differ diff --git a/web/dungeon/audio/voice/female/nav_s.ogg b/web/dungeon/audio/voice/female/nav_s.ogg new file mode 100644 index 00000000..bbade283 Binary files /dev/null and b/web/dungeon/audio/voice/female/nav_s.ogg differ diff --git a/web/dungeon/audio/voice/female/nav_w.m4a b/web/dungeon/audio/voice/female/nav_w.m4a new file mode 100644 index 00000000..518ac601 Binary files /dev/null and b/web/dungeon/audio/voice/female/nav_w.m4a differ diff --git a/web/dungeon/audio/voice/female/nav_w.ogg b/web/dungeon/audio/voice/female/nav_w.ogg new file mode 100644 index 00000000..b0137b9c Binary files /dev/null and b/web/dungeon/audio/voice/female/nav_w.ogg differ diff --git a/web/dungeon/audio/voice/female/settings_hint.m4a b/web/dungeon/audio/voice/female/settings_hint.m4a new file mode 100644 index 00000000..1c517b0a Binary files /dev/null and b/web/dungeon/audio/voice/female/settings_hint.m4a differ diff --git a/web/dungeon/audio/voice/female/settings_hint.ogg b/web/dungeon/audio/voice/female/settings_hint.ogg new file mode 100644 index 00000000..2354f22a Binary files /dev/null and b/web/dungeon/audio/voice/female/settings_hint.ogg differ diff --git a/web/dungeon/audio/voice/female/settle_again.m4a b/web/dungeon/audio/voice/female/settle_again.m4a new file mode 100644 index 00000000..f00c5921 Binary files /dev/null and b/web/dungeon/audio/voice/female/settle_again.m4a differ diff --git a/web/dungeon/audio/voice/female/settle_again.ogg b/web/dungeon/audio/voice/female/settle_again.ogg new file mode 100644 index 00000000..abb9d33b Binary files /dev/null and b/web/dungeon/audio/voice/female/settle_again.ogg differ diff --git a/web/dungeon/audio/voice/female/settle_stronger.m4a b/web/dungeon/audio/voice/female/settle_stronger.m4a new file mode 100644 index 00000000..d0eb6484 Binary files /dev/null and b/web/dungeon/audio/voice/female/settle_stronger.m4a differ diff --git a/web/dungeon/audio/voice/female/settle_stronger.ogg b/web/dungeon/audio/voice/female/settle_stronger.ogg new file mode 100644 index 00000000..1d1ae517 Binary files /dev/null and b/web/dungeon/audio/voice/female/settle_stronger.ogg differ diff --git a/web/dungeon/audio/voice/female/settle_win_1.m4a b/web/dungeon/audio/voice/female/settle_win_1.m4a new file mode 100644 index 00000000..c90fac8a Binary files /dev/null and b/web/dungeon/audio/voice/female/settle_win_1.m4a differ diff --git a/web/dungeon/audio/voice/female/settle_win_1.ogg b/web/dungeon/audio/voice/female/settle_win_1.ogg new file mode 100644 index 00000000..8204eeda Binary files /dev/null and b/web/dungeon/audio/voice/female/settle_win_1.ogg differ diff --git a/web/dungeon/audio/voice/female/settle_win_2.m4a b/web/dungeon/audio/voice/female/settle_win_2.m4a new file mode 100644 index 00000000..da381e99 Binary files /dev/null and b/web/dungeon/audio/voice/female/settle_win_2.m4a differ diff --git a/web/dungeon/audio/voice/female/settle_win_2.ogg b/web/dungeon/audio/voice/female/settle_win_2.ogg new file mode 100644 index 00000000..15717db3 Binary files /dev/null and b/web/dungeon/audio/voice/female/settle_win_2.ogg differ diff --git a/web/dungeon/audio/voice/female/st_best_1.m4a b/web/dungeon/audio/voice/female/st_best_1.m4a new file mode 100644 index 00000000..b6668a35 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_best_1.m4a differ diff --git a/web/dungeon/audio/voice/female/st_best_1.ogg b/web/dungeon/audio/voice/female/st_best_1.ogg new file mode 100644 index 00000000..cc8e2f48 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_best_1.ogg differ diff --git a/web/dungeon/audio/voice/female/st_best_2.m4a b/web/dungeon/audio/voice/female/st_best_2.m4a new file mode 100644 index 00000000..e5f8da2f Binary files /dev/null and b/web/dungeon/audio/voice/female/st_best_2.m4a differ diff --git a/web/dungeon/audio/voice/female/st_best_2.ogg b/web/dungeon/audio/voice/female/st_best_2.ogg new file mode 100644 index 00000000..27b3928c Binary files /dev/null and b/web/dungeon/audio/voice/female/st_best_2.ogg differ diff --git a/web/dungeon/audio/voice/female/st_best_3.m4a b/web/dungeon/audio/voice/female/st_best_3.m4a new file mode 100644 index 00000000..7b0df123 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_best_3.m4a differ diff --git a/web/dungeon/audio/voice/female/st_best_3.ogg b/web/dungeon/audio/voice/female/st_best_3.ogg new file mode 100644 index 00000000..0f564f70 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_best_3.ogg differ diff --git a/web/dungeon/audio/voice/female/st_face_e.m4a b/web/dungeon/audio/voice/female/st_face_e.m4a new file mode 100644 index 00000000..8209be01 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_face_e.m4a differ diff --git a/web/dungeon/audio/voice/female/st_face_e.ogg b/web/dungeon/audio/voice/female/st_face_e.ogg new file mode 100644 index 00000000..1ffe7731 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_face_e.ogg differ diff --git a/web/dungeon/audio/voice/female/st_face_n.m4a b/web/dungeon/audio/voice/female/st_face_n.m4a new file mode 100644 index 00000000..8d9eb435 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_face_n.m4a differ diff --git a/web/dungeon/audio/voice/female/st_face_n.ogg b/web/dungeon/audio/voice/female/st_face_n.ogg new file mode 100644 index 00000000..944e56f7 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_face_n.ogg differ diff --git a/web/dungeon/audio/voice/female/st_face_s.m4a b/web/dungeon/audio/voice/female/st_face_s.m4a new file mode 100644 index 00000000..f330ba8b Binary files /dev/null and b/web/dungeon/audio/voice/female/st_face_s.m4a differ diff --git a/web/dungeon/audio/voice/female/st_face_s.ogg b/web/dungeon/audio/voice/female/st_face_s.ogg new file mode 100644 index 00000000..19c2f6cf Binary files /dev/null and b/web/dungeon/audio/voice/female/st_face_s.ogg differ diff --git a/web/dungeon/audio/voice/female/st_face_w.m4a b/web/dungeon/audio/voice/female/st_face_w.m4a new file mode 100644 index 00000000..22ad1771 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_face_w.m4a differ diff --git a/web/dungeon/audio/voice/female/st_face_w.ogg b/web/dungeon/audio/voice/female/st_face_w.ogg new file mode 100644 index 00000000..4653e68b Binary files /dev/null and b/web/dungeon/audio/voice/female/st_face_w.ogg differ diff --git a/web/dungeon/audio/voice/female/st_floor_1.m4a b/web/dungeon/audio/voice/female/st_floor_1.m4a new file mode 100644 index 00000000..3c6578de Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_1.m4a differ diff --git a/web/dungeon/audio/voice/female/st_floor_1.ogg b/web/dungeon/audio/voice/female/st_floor_1.ogg new file mode 100644 index 00000000..addcfae4 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_1.ogg differ diff --git a/web/dungeon/audio/voice/female/st_floor_2.m4a b/web/dungeon/audio/voice/female/st_floor_2.m4a new file mode 100644 index 00000000..5be140b6 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_2.m4a differ diff --git a/web/dungeon/audio/voice/female/st_floor_2.ogg b/web/dungeon/audio/voice/female/st_floor_2.ogg new file mode 100644 index 00000000..c0ffd2ee Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_2.ogg differ diff --git a/web/dungeon/audio/voice/female/st_floor_3.m4a b/web/dungeon/audio/voice/female/st_floor_3.m4a new file mode 100644 index 00000000..c0045b6c Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_3.m4a differ diff --git a/web/dungeon/audio/voice/female/st_floor_3.ogg b/web/dungeon/audio/voice/female/st_floor_3.ogg new file mode 100644 index 00000000..4ae69e18 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_3.ogg differ diff --git a/web/dungeon/audio/voice/female/st_floor_4.m4a b/web/dungeon/audio/voice/female/st_floor_4.m4a new file mode 100644 index 00000000..56acfbc2 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_4.m4a differ diff --git a/web/dungeon/audio/voice/female/st_floor_4.ogg b/web/dungeon/audio/voice/female/st_floor_4.ogg new file mode 100644 index 00000000..7caa2c0e Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_4.ogg differ diff --git a/web/dungeon/audio/voice/female/st_floor_5.m4a b/web/dungeon/audio/voice/female/st_floor_5.m4a new file mode 100644 index 00000000..338a860d Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_5.m4a differ diff --git a/web/dungeon/audio/voice/female/st_floor_5.ogg b/web/dungeon/audio/voice/female/st_floor_5.ogg new file mode 100644 index 00000000..cf834920 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_5.ogg differ diff --git a/web/dungeon/audio/voice/female/st_floor_6.m4a b/web/dungeon/audio/voice/female/st_floor_6.m4a new file mode 100644 index 00000000..12bfa00b Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_6.m4a differ diff --git a/web/dungeon/audio/voice/female/st_floor_6.ogg b/web/dungeon/audio/voice/female/st_floor_6.ogg new file mode 100644 index 00000000..9cf84405 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_6.ogg differ diff --git a/web/dungeon/audio/voice/female/st_floor_7.m4a b/web/dungeon/audio/voice/female/st_floor_7.m4a new file mode 100644 index 00000000..ef137efc Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_7.m4a differ diff --git a/web/dungeon/audio/voice/female/st_floor_7.ogg b/web/dungeon/audio/voice/female/st_floor_7.ogg new file mode 100644 index 00000000..5536b59f Binary files /dev/null and b/web/dungeon/audio/voice/female/st_floor_7.ogg differ diff --git a/web/dungeon/audio/voice/female/st_gold_1.m4a b/web/dungeon/audio/voice/female/st_gold_1.m4a new file mode 100644 index 00000000..ea5fe859 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_gold_1.m4a differ diff --git a/web/dungeon/audio/voice/female/st_gold_1.ogg b/web/dungeon/audio/voice/female/st_gold_1.ogg new file mode 100644 index 00000000..f4532a37 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_gold_1.ogg differ diff --git a/web/dungeon/audio/voice/female/st_gold_2.m4a b/web/dungeon/audio/voice/female/st_gold_2.m4a new file mode 100644 index 00000000..7016e68f Binary files /dev/null and b/web/dungeon/audio/voice/female/st_gold_2.m4a differ diff --git a/web/dungeon/audio/voice/female/st_gold_2.ogg b/web/dungeon/audio/voice/female/st_gold_2.ogg new file mode 100644 index 00000000..bb40bb20 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_gold_2.ogg differ diff --git a/web/dungeon/audio/voice/female/st_gold_3.m4a b/web/dungeon/audio/voice/female/st_gold_3.m4a new file mode 100644 index 00000000..6c3adc2d Binary files /dev/null and b/web/dungeon/audio/voice/female/st_gold_3.m4a differ diff --git a/web/dungeon/audio/voice/female/st_gold_3.ogg b/web/dungeon/audio/voice/female/st_gold_3.ogg new file mode 100644 index 00000000..f22f1d8b Binary files /dev/null and b/web/dungeon/audio/voice/female/st_gold_3.ogg differ diff --git a/web/dungeon/audio/voice/female/st_gold_4.m4a b/web/dungeon/audio/voice/female/st_gold_4.m4a new file mode 100644 index 00000000..fe1dfba4 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_gold_4.m4a differ diff --git a/web/dungeon/audio/voice/female/st_gold_4.ogg b/web/dungeon/audio/voice/female/st_gold_4.ogg new file mode 100644 index 00000000..6873f144 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_gold_4.ogg differ diff --git a/web/dungeon/audio/voice/female/st_hp_1.m4a b/web/dungeon/audio/voice/female/st_hp_1.m4a new file mode 100644 index 00000000..8247902e Binary files /dev/null and b/web/dungeon/audio/voice/female/st_hp_1.m4a differ diff --git a/web/dungeon/audio/voice/female/st_hp_1.ogg b/web/dungeon/audio/voice/female/st_hp_1.ogg new file mode 100644 index 00000000..e2e151bf Binary files /dev/null and b/web/dungeon/audio/voice/female/st_hp_1.ogg differ diff --git a/web/dungeon/audio/voice/female/st_hp_2.m4a b/web/dungeon/audio/voice/female/st_hp_2.m4a new file mode 100644 index 00000000..a8c39b1f Binary files /dev/null and b/web/dungeon/audio/voice/female/st_hp_2.m4a differ diff --git a/web/dungeon/audio/voice/female/st_hp_2.ogg b/web/dungeon/audio/voice/female/st_hp_2.ogg new file mode 100644 index 00000000..827f3820 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_hp_2.ogg differ diff --git a/web/dungeon/audio/voice/female/st_hp_3.m4a b/web/dungeon/audio/voice/female/st_hp_3.m4a new file mode 100644 index 00000000..c1327ea7 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_hp_3.m4a differ diff --git a/web/dungeon/audio/voice/female/st_hp_3.ogg b/web/dungeon/audio/voice/female/st_hp_3.ogg new file mode 100644 index 00000000..5e10923d Binary files /dev/null and b/web/dungeon/audio/voice/female/st_hp_3.ogg differ diff --git a/web/dungeon/audio/voice/female/st_hp_4.m4a b/web/dungeon/audio/voice/female/st_hp_4.m4a new file mode 100644 index 00000000..08443fe4 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_hp_4.m4a differ diff --git a/web/dungeon/audio/voice/female/st_hp_4.ogg b/web/dungeon/audio/voice/female/st_hp_4.ogg new file mode 100644 index 00000000..8c151b0a Binary files /dev/null and b/web/dungeon/audio/voice/female/st_hp_4.ogg differ diff --git a/web/dungeon/audio/voice/female/st_threat_both.m4a b/web/dungeon/audio/voice/female/st_threat_both.m4a new file mode 100644 index 00000000..e1f2136e Binary files /dev/null and b/web/dungeon/audio/voice/female/st_threat_both.m4a differ diff --git a/web/dungeon/audio/voice/female/st_threat_both.ogg b/web/dungeon/audio/voice/female/st_threat_both.ogg new file mode 100644 index 00000000..ccfcb7dc Binary files /dev/null and b/web/dungeon/audio/voice/female/st_threat_both.ogg differ diff --git a/web/dungeon/audio/voice/female/st_threat_left.m4a b/web/dungeon/audio/voice/female/st_threat_left.m4a new file mode 100644 index 00000000..c5b35066 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_threat_left.m4a differ diff --git a/web/dungeon/audio/voice/female/st_threat_left.ogg b/web/dungeon/audio/voice/female/st_threat_left.ogg new file mode 100644 index 00000000..a1ded9c7 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_threat_left.ogg differ diff --git a/web/dungeon/audio/voice/female/st_threat_none.m4a b/web/dungeon/audio/voice/female/st_threat_none.m4a new file mode 100644 index 00000000..6834c131 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_threat_none.m4a differ diff --git a/web/dungeon/audio/voice/female/st_threat_none.ogg b/web/dungeon/audio/voice/female/st_threat_none.ogg new file mode 100644 index 00000000..39603153 Binary files /dev/null and b/web/dungeon/audio/voice/female/st_threat_none.ogg differ diff --git a/web/dungeon/audio/voice/female/st_threat_right.m4a b/web/dungeon/audio/voice/female/st_threat_right.m4a new file mode 100644 index 00000000..cfa1b17f Binary files /dev/null and b/web/dungeon/audio/voice/female/st_threat_right.m4a differ diff --git a/web/dungeon/audio/voice/female/st_threat_right.ogg b/web/dungeon/audio/voice/female/st_threat_right.ogg new file mode 100644 index 00000000..b9ff3b2d Binary files /dev/null and b/web/dungeon/audio/voice/female/st_threat_right.ogg differ diff --git a/web/dungeon/audio/voice/female/stair_dir_e.m4a b/web/dungeon/audio/voice/female/stair_dir_e.m4a new file mode 100644 index 00000000..ff0d4100 Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_dir_e.m4a differ diff --git a/web/dungeon/audio/voice/female/stair_dir_e.ogg b/web/dungeon/audio/voice/female/stair_dir_e.ogg new file mode 100644 index 00000000..9ff5e672 Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_dir_e.ogg differ diff --git a/web/dungeon/audio/voice/female/stair_dir_n.m4a b/web/dungeon/audio/voice/female/stair_dir_n.m4a new file mode 100644 index 00000000..94ef6460 Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_dir_n.m4a differ diff --git a/web/dungeon/audio/voice/female/stair_dir_n.ogg b/web/dungeon/audio/voice/female/stair_dir_n.ogg new file mode 100644 index 00000000..c2374f7d Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_dir_n.ogg differ diff --git a/web/dungeon/audio/voice/female/stair_dir_s.m4a b/web/dungeon/audio/voice/female/stair_dir_s.m4a new file mode 100644 index 00000000..69bb4620 Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_dir_s.m4a differ diff --git a/web/dungeon/audio/voice/female/stair_dir_s.ogg b/web/dungeon/audio/voice/female/stair_dir_s.ogg new file mode 100644 index 00000000..4b8ff9bf Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_dir_s.ogg differ diff --git a/web/dungeon/audio/voice/female/stair_dir_w.m4a b/web/dungeon/audio/voice/female/stair_dir_w.m4a new file mode 100644 index 00000000..a0a42af5 Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_dir_w.m4a differ diff --git a/web/dungeon/audio/voice/female/stair_dir_w.ogg b/web/dungeon/audio/voice/female/stair_dir_w.ogg new file mode 100644 index 00000000..fc666865 Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_dir_w.ogg differ diff --git a/web/dungeon/audio/voice/female/stair_hint_a.m4a b/web/dungeon/audio/voice/female/stair_hint_a.m4a new file mode 100644 index 00000000..787f87eb Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_hint_a.m4a differ diff --git a/web/dungeon/audio/voice/female/stair_hint_a.ogg b/web/dungeon/audio/voice/female/stair_hint_a.ogg new file mode 100644 index 00000000..9bd41563 Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_hint_a.ogg differ diff --git a/web/dungeon/audio/voice/female/stair_hint_b.m4a b/web/dungeon/audio/voice/female/stair_hint_b.m4a new file mode 100644 index 00000000..b781fc9c Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_hint_b.m4a differ diff --git a/web/dungeon/audio/voice/female/stair_hint_b.ogg b/web/dungeon/audio/voice/female/stair_hint_b.ogg new file mode 100644 index 00000000..27c37668 Binary files /dev/null and b/web/dungeon/audio/voice/female/stair_hint_b.ogg differ diff --git a/web/dungeon/audio/voice/female/sys_continue_ask.m4a b/web/dungeon/audio/voice/female/sys_continue_ask.m4a new file mode 100644 index 00000000..4e5ef13f Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_continue_ask.m4a differ diff --git a/web/dungeon/audio/voice/female/sys_continue_ask.ogg b/web/dungeon/audio/voice/female/sys_continue_ask.ogg new file mode 100644 index 00000000..64b3d8ba Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_continue_ask.ogg differ diff --git a/web/dungeon/audio/voice/female/sys_menu.m4a b/web/dungeon/audio/voice/female/sys_menu.m4a new file mode 100644 index 00000000..345be3f1 Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_menu.m4a differ diff --git a/web/dungeon/audio/voice/female/sys_menu.ogg b/web/dungeon/audio/voice/female/sys_menu.ogg new file mode 100644 index 00000000..4665aab5 Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_menu.ogg differ diff --git a/web/dungeon/audio/voice/female/sys_menu_help.m4a b/web/dungeon/audio/voice/female/sys_menu_help.m4a new file mode 100644 index 00000000..21acbb5b Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_menu_help.m4a differ diff --git a/web/dungeon/audio/voice/female/sys_menu_help.ogg b/web/dungeon/audio/voice/female/sys_menu_help.ogg new file mode 100644 index 00000000..7b14d289 Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_menu_help.ogg differ diff --git a/web/dungeon/audio/voice/female/sys_menu_restart.m4a b/web/dungeon/audio/voice/female/sys_menu_restart.m4a new file mode 100644 index 00000000..38867ffb Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_menu_restart.m4a differ diff --git a/web/dungeon/audio/voice/female/sys_menu_restart.ogg b/web/dungeon/audio/voice/female/sys_menu_restart.ogg new file mode 100644 index 00000000..9b2dcff5 Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_menu_restart.ogg differ diff --git a/web/dungeon/audio/voice/female/sys_menu_resume.m4a b/web/dungeon/audio/voice/female/sys_menu_resume.m4a new file mode 100644 index 00000000..438c76ef Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_menu_resume.m4a differ diff --git a/web/dungeon/audio/voice/female/sys_menu_resume.ogg b/web/dungeon/audio/voice/female/sys_menu_resume.ogg new file mode 100644 index 00000000..a3f5b6a0 Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_menu_resume.ogg differ diff --git a/web/dungeon/audio/voice/female/sys_menu_save.m4a b/web/dungeon/audio/voice/female/sys_menu_save.m4a new file mode 100644 index 00000000..7f8237be Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_menu_save.m4a differ diff --git a/web/dungeon/audio/voice/female/sys_menu_save.ogg b/web/dungeon/audio/voice/female/sys_menu_save.ogg new file mode 100644 index 00000000..c78fc212 Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_menu_save.ogg differ diff --git a/web/dungeon/audio/voice/female/sys_new_run.m4a b/web/dungeon/audio/voice/female/sys_new_run.m4a new file mode 100644 index 00000000..9143284c Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_new_run.m4a differ diff --git a/web/dungeon/audio/voice/female/sys_new_run.ogg b/web/dungeon/audio/voice/female/sys_new_run.ogg new file mode 100644 index 00000000..e0617c13 Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_new_run.ogg differ diff --git a/web/dungeon/audio/voice/female/sys_saved.m4a b/web/dungeon/audio/voice/female/sys_saved.m4a new file mode 100644 index 00000000..176af4a0 Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_saved.m4a differ diff --git a/web/dungeon/audio/voice/female/sys_saved.ogg b/web/dungeon/audio/voice/female/sys_saved.ogg new file mode 100644 index 00000000..bc0e6580 Binary files /dev/null and b/web/dungeon/audio/voice/female/sys_saved.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_attack.m4a b/web/dungeon/audio/voice/female/tut_attack.m4a new file mode 100644 index 00000000..ca348ce7 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_attack.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_attack.ogg b/web/dungeon/audio/voice/female/tut_attack.ogg new file mode 100644 index 00000000..f9ee5bbf Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_attack.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_d0.m4a b/web/dungeon/audio/voice/female/tut_d0.m4a new file mode 100644 index 00000000..3d5a8313 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_d0.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_d0.ogg b/web/dungeon/audio/voice/female/tut_d0.ogg new file mode 100644 index 00000000..8456304d Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_d0.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_done.m4a b/web/dungeon/audio/voice/female/tut_done.m4a new file mode 100644 index 00000000..1930a523 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_done.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_done.ogg b/web/dungeon/audio/voice/female/tut_done.ogg new file mode 100644 index 00000000..95e61713 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_done.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_echo_come.m4a b/web/dungeon/audio/voice/female/tut_echo_come.m4a new file mode 100644 index 00000000..a14f61fd Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_echo_come.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_echo_come.ogg b/web/dungeon/audio/voice/female/tut_echo_come.ogg new file mode 100644 index 00000000..b044c02c Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_echo_come.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_front_rule.m4a b/web/dungeon/audio/voice/female/tut_front_rule.m4a new file mode 100644 index 00000000..fef4f9f3 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_front_rule.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_front_rule.ogg b/web/dungeon/audio/voice/female/tut_front_rule.ogg new file mode 100644 index 00000000..fc4a5ba6 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_front_rule.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_kill.m4a b/web/dungeon/audio/voice/female/tut_kill.m4a new file mode 100644 index 00000000..b89a305c Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_kill.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_kill.ogg b/web/dungeon/audio/voice/female/tut_kill.ogg new file mode 100644 index 00000000..8e497069 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_kill.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_menu.m4a b/web/dungeon/audio/voice/female/tut_menu.m4a new file mode 100644 index 00000000..ba13f90c Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_menu.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_menu.ogg b/web/dungeon/audio/voice/female/tut_menu.ogg new file mode 100644 index 00000000..8277cc09 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_menu.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_mob.m4a b/web/dungeon/audio/voice/female/tut_mob.m4a new file mode 100644 index 00000000..94c6fabf Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_mob.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_mob.ogg b/web/dungeon/audio/voice/female/tut_mob.ogg new file mode 100644 index 00000000..c911facf Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_mob.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_parry.m4a b/web/dungeon/audio/voice/female/tut_parry.m4a new file mode 100644 index 00000000..9b292ac3 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_parry.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_parry.ogg b/web/dungeon/audio/voice/female/tut_parry.ogg new file mode 100644 index 00000000..ebf2acf6 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_parry.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_parry_miss.m4a b/web/dungeon/audio/voice/female/tut_parry_miss.m4a new file mode 100644 index 00000000..b01ec151 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_parry_miss.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_parry_miss.ogg b/web/dungeon/audio/voice/female/tut_parry_miss.ogg new file mode 100644 index 00000000..ca15e72f Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_parry_miss.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_parry_ok.m4a b/web/dungeon/audio/voice/female/tut_parry_ok.m4a new file mode 100644 index 00000000..fc8fe2b8 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_parry_ok.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_parry_ok.ogg b/web/dungeon/audio/voice/female/tut_parry_ok.ogg new file mode 100644 index 00000000..fd7e43e0 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_parry_ok.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_replay.m4a b/web/dungeon/audio/voice/female/tut_replay.m4a new file mode 100644 index 00000000..1d55f29a Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_replay.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_replay.ogg b/web/dungeon/audio/voice/female/tut_replay.ogg new file mode 100644 index 00000000..3d8c634e Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_replay.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_side_rule.m4a b/web/dungeon/audio/voice/female/tut_side_rule.m4a new file mode 100644 index 00000000..45c574e2 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_side_rule.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_side_rule.ogg b/web/dungeon/audio/voice/female/tut_side_rule.ogg new file mode 100644 index 00000000..279106d6 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_side_rule.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_skip.m4a b/web/dungeon/audio/voice/female/tut_skip.m4a new file mode 100644 index 00000000..7e7e8772 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_skip.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_skip.ogg b/web/dungeon/audio/voice/female/tut_skip.ogg new file mode 100644 index 00000000..4c1e09b5 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_skip.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_stairs.m4a b/web/dungeon/audio/voice/female/tut_stairs.m4a new file mode 100644 index 00000000..3923d53e Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_stairs.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_stairs.ogg b/web/dungeon/audio/voice/female/tut_stairs.ogg new file mode 100644 index 00000000..51cfe289 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_stairs.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_stairs_on.m4a b/web/dungeon/audio/voice/female/tut_stairs_on.m4a new file mode 100644 index 00000000..118a45b0 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_stairs_on.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_stairs_on.ogg b/web/dungeon/audio/voice/female/tut_stairs_on.ogg new file mode 100644 index 00000000..f687a4d1 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_stairs_on.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_status.m4a b/web/dungeon/audio/voice/female/tut_status.m4a new file mode 100644 index 00000000..079adb0d Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_status.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_status.ogg b/web/dungeon/audio/voice/female/tut_status.ogg new file mode 100644 index 00000000..8ccd4462 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_status.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_turn.m4a b/web/dungeon/audio/voice/female/tut_turn.m4a new file mode 100644 index 00000000..cef1ae0f Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_turn.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_turn.ogg b/web/dungeon/audio/voice/female/tut_turn.ogg new file mode 100644 index 00000000..836031d2 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_turn.ogg differ diff --git a/web/dungeon/audio/voice/female/tut_walk.m4a b/web/dungeon/audio/voice/female/tut_walk.m4a new file mode 100644 index 00000000..c0206933 Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_walk.m4a differ diff --git a/web/dungeon/audio/voice/female/tut_walk.ogg b/web/dungeon/audio/voice/female/tut_walk.ogg new file mode 100644 index 00000000..2ce7562f Binary files /dev/null and b/web/dungeon/audio/voice/female/tut_walk.ogg differ diff --git a/web/dungeon/audio/voice/female/unlock_blade.m4a b/web/dungeon/audio/voice/female/unlock_blade.m4a new file mode 100644 index 00000000..b2a1dd40 Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_blade.m4a differ diff --git a/web/dungeon/audio/voice/female/unlock_blade.ogg b/web/dungeon/audio/voice/female/unlock_blade.ogg new file mode 100644 index 00000000..0a66d807 Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_blade.ogg differ diff --git a/web/dungeon/audio/voice/female/unlock_echo.m4a b/web/dungeon/audio/voice/female/unlock_echo.m4a new file mode 100644 index 00000000..4ca19fb1 Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_echo.m4a differ diff --git a/web/dungeon/audio/voice/female/unlock_echo.ogg b/web/dungeon/audio/voice/female/unlock_echo.ogg new file mode 100644 index 00000000..8639edf1 Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_echo.ogg differ diff --git a/web/dungeon/audio/voice/female/unlock_lead.m4a b/web/dungeon/audio/voice/female/unlock_lead.m4a new file mode 100644 index 00000000..c7bcaa90 Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_lead.m4a differ diff --git a/web/dungeon/audio/voice/female/unlock_lead.ogg b/web/dungeon/audio/voice/female/unlock_lead.ogg new file mode 100644 index 00000000..9472505d Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_lead.ogg differ diff --git a/web/dungeon/audio/voice/female/unlock_none.m4a b/web/dungeon/audio/voice/female/unlock_none.m4a new file mode 100644 index 00000000..c999ff1f Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_none.m4a differ diff --git a/web/dungeon/audio/voice/female/unlock_none.ogg b/web/dungeon/audio/voice/female/unlock_none.ogg new file mode 100644 index 00000000..6df8f6d6 Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_none.ogg differ diff --git a/web/dungeon/audio/voice/female/unlock_potion.m4a b/web/dungeon/audio/voice/female/unlock_potion.m4a new file mode 100644 index 00000000..1ec0cd77 Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_potion.m4a differ diff --git a/web/dungeon/audio/voice/female/unlock_potion.ogg b/web/dungeon/audio/voice/female/unlock_potion.ogg new file mode 100644 index 00000000..47aa6e9c Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_potion.ogg differ diff --git a/web/dungeon/audio/voice/female/unlock_veil.m4a b/web/dungeon/audio/voice/female/unlock_veil.m4a new file mode 100644 index 00000000..f67abab1 Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_veil.m4a differ diff --git a/web/dungeon/audio/voice/female/unlock_veil.ogg b/web/dungeon/audio/voice/female/unlock_veil.ogg new file mode 100644 index 00000000..54b3cf55 Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_veil.ogg differ diff --git a/web/dungeon/audio/voice/female/unlock_vigor.m4a b/web/dungeon/audio/voice/female/unlock_vigor.m4a new file mode 100644 index 00000000..3456e26c Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_vigor.m4a differ diff --git a/web/dungeon/audio/voice/female/unlock_vigor.ogg b/web/dungeon/audio/voice/female/unlock_vigor.ogg new file mode 100644 index 00000000..62dfa280 Binary files /dev/null and b/web/dungeon/audio/voice/female/unlock_vigor.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_bat.m4a b/web/dungeon/audio/voice/male/btl_bat.m4a new file mode 100644 index 00000000..334f146a Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_bat.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_bat.ogg b/web/dungeon/audio/voice/male/btl_bat.ogg new file mode 100644 index 00000000..2ecef9ec Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_bat.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_behind.m4a b/web/dungeon/audio/voice/male/btl_behind.m4a new file mode 100644 index 00000000..fe60f830 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_behind.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_behind.ogg b/web/dungeon/audio/voice/male/btl_behind.ogg new file mode 100644 index 00000000..194b7cf7 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_behind.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_crab.m4a b/web/dungeon/audio/voice/male/btl_crab.m4a new file mode 100644 index 00000000..58b4c621 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_crab.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_crab.ogg b/web/dungeon/audio/voice/male/btl_crab.ogg new file mode 100644 index 00000000..7eeac6a4 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_crab.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_crawler.m4a b/web/dungeon/audio/voice/male/btl_crawler.m4a new file mode 100644 index 00000000..5a8b6261 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_crawler.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_crawler.ogg b/web/dungeon/audio/voice/male/btl_crawler.ogg new file mode 100644 index 00000000..55afaf4a Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_crawler.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_flee.m4a b/web/dungeon/audio/voice/male/btl_flee.m4a new file mode 100644 index 00000000..2af3a532 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_flee.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_flee.ogg b/web/dungeon/audio/voice/male/btl_flee.ogg new file mode 100644 index 00000000..5d21db90 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_flee.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_flee_boss.m4a b/web/dungeon/audio/voice/male/btl_flee_boss.m4a new file mode 100644 index 00000000..bc79e7aa Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_flee_boss.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_flee_boss.ogg b/web/dungeon/audio/voice/male/btl_flee_boss.ogg new file mode 100644 index 00000000..4b449335 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_flee_boss.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_guard.m4a b/web/dungeon/audio/voice/male/btl_guard.m4a new file mode 100644 index 00000000..29b8fabc Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_guard.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_guard.ogg b/web/dungeon/audio/voice/male/btl_guard.ogg new file mode 100644 index 00000000..601da04b Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_guard.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_half_crab.m4a b/web/dungeon/audio/voice/male/btl_half_crab.m4a new file mode 100644 index 00000000..e3721513 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_half_crab.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_half_crab.ogg b/web/dungeon/audio/voice/male/btl_half_crab.ogg new file mode 100644 index 00000000..f358c7a5 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_half_crab.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_half_king.m4a b/web/dungeon/audio/voice/male/btl_half_king.m4a new file mode 100644 index 00000000..8fc3d866 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_half_king.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_half_king.ogg b/web/dungeon/audio/voice/male/btl_half_king.ogg new file mode 100644 index 00000000..ba8e3dd2 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_half_king.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_half_leech.m4a b/web/dungeon/audio/voice/male/btl_half_leech.m4a new file mode 100644 index 00000000..af21ee42 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_half_leech.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_half_leech.ogg b/web/dungeon/audio/voice/male/btl_half_leech.ogg new file mode 100644 index 00000000..28e9d4cd Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_half_leech.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_kill.m4a b/web/dungeon/audio/voice/male/btl_kill.m4a new file mode 100644 index 00000000..36134d95 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_kill.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_kill.ogg b/web/dungeon/audio/voice/male/btl_kill.ogg new file mode 100644 index 00000000..83b3a14d Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_kill.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_king.m4a b/web/dungeon/audio/voice/male/btl_king.m4a new file mode 100644 index 00000000..338785b8 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_king.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_king.ogg b/web/dungeon/audio/voice/male/btl_king.ogg new file mode 100644 index 00000000..45d6e5ab Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_king.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_leech.m4a b/web/dungeon/audio/voice/male/btl_leech.m4a new file mode 100644 index 00000000..a6c4756a Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_leech.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_leech.ogg b/web/dungeon/audio/voice/male/btl_leech.ogg new file mode 100644 index 00000000..f07bdbbc Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_leech.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_loot_1.m4a b/web/dungeon/audio/voice/male/btl_loot_1.m4a new file mode 100644 index 00000000..1efca78a Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_loot_1.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_loot_1.ogg b/web/dungeon/audio/voice/male/btl_loot_1.ogg new file mode 100644 index 00000000..f7607498 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_loot_1.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_loot_2.m4a b/web/dungeon/audio/voice/male/btl_loot_2.m4a new file mode 100644 index 00000000..1f9b6ca2 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_loot_2.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_loot_2.ogg b/web/dungeon/audio/voice/male/btl_loot_2.ogg new file mode 100644 index 00000000..3f83d5a7 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_loot_2.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_loot_3.m4a b/web/dungeon/audio/voice/male/btl_loot_3.m4a new file mode 100644 index 00000000..b631cda9 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_loot_3.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_loot_3.ogg b/web/dungeon/audio/voice/male/btl_loot_3.ogg new file mode 100644 index 00000000..19ac5391 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_loot_3.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_lvup.m4a b/web/dungeon/audio/voice/male/btl_lvup.m4a new file mode 100644 index 00000000..bf66a59c Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_lvup.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_lvup.ogg b/web/dungeon/audio/voice/male/btl_lvup.ogg new file mode 100644 index 00000000..dc60fe36 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_lvup.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_mush.m4a b/web/dungeon/audio/voice/male/btl_mush.m4a new file mode 100644 index 00000000..7550e295 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_mush.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_mush.ogg b/web/dungeon/audio/voice/male/btl_mush.ogg new file mode 100644 index 00000000..7ff896d5 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_mush.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_no_turn.m4a b/web/dungeon/audio/voice/male/btl_no_turn.m4a new file mode 100644 index 00000000..f1b26b02 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_no_turn.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_no_turn.ogg b/web/dungeon/audio/voice/male/btl_no_turn.ogg new file mode 100644 index 00000000..70e57bbe Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_no_turn.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_parried.m4a b/web/dungeon/audio/voice/male/btl_parried.m4a new file mode 100644 index 00000000..485684b7 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_parried.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_parried.ogg b/web/dungeon/audio/voice/male/btl_parried.ogg new file mode 100644 index 00000000..ff9baab5 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_parried.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_rat.m4a b/web/dungeon/audio/voice/male/btl_rat.m4a new file mode 100644 index 00000000..68c5be35 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_rat.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_rat.ogg b/web/dungeon/audio/voice/male/btl_rat.ogg new file mode 100644 index 00000000..7940dcea Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_rat.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_spider.m4a b/web/dungeon/audio/voice/male/btl_spider.m4a new file mode 100644 index 00000000..a8a40904 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_spider.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_spider.ogg b/web/dungeon/audio/voice/male/btl_spider.ogg new file mode 100644 index 00000000..de5c73a0 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_spider.ogg differ diff --git a/web/dungeon/audio/voice/male/btl_swish.m4a b/web/dungeon/audio/voice/male/btl_swish.m4a new file mode 100644 index 00000000..eb3a840c Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_swish.m4a differ diff --git a/web/dungeon/audio/voice/male/btl_swish.ogg b/web/dungeon/audio/voice/male/btl_swish.ogg new file mode 100644 index 00000000..d4cb4b92 Binary files /dev/null and b/web/dungeon/audio/voice/male/btl_swish.ogg differ diff --git a/web/dungeon/audio/voice/male/die_adv_1.m4a b/web/dungeon/audio/voice/male/die_adv_1.m4a new file mode 100644 index 00000000..0db641fe Binary files /dev/null and b/web/dungeon/audio/voice/male/die_adv_1.m4a differ diff --git a/web/dungeon/audio/voice/male/die_adv_1.ogg b/web/dungeon/audio/voice/male/die_adv_1.ogg new file mode 100644 index 00000000..121a2a6f Binary files /dev/null and b/web/dungeon/audio/voice/male/die_adv_1.ogg differ diff --git a/web/dungeon/audio/voice/male/die_adv_2.m4a b/web/dungeon/audio/voice/male/die_adv_2.m4a new file mode 100644 index 00000000..7fad84a4 Binary files /dev/null and b/web/dungeon/audio/voice/male/die_adv_2.m4a differ diff --git a/web/dungeon/audio/voice/male/die_adv_2.ogg b/web/dungeon/audio/voice/male/die_adv_2.ogg new file mode 100644 index 00000000..4b1f160a Binary files /dev/null and b/web/dungeon/audio/voice/male/die_adv_2.ogg differ diff --git a/web/dungeon/audio/voice/male/die_adv_3.m4a b/web/dungeon/audio/voice/male/die_adv_3.m4a new file mode 100644 index 00000000..5624752f Binary files /dev/null and b/web/dungeon/audio/voice/male/die_adv_3.m4a differ diff --git a/web/dungeon/audio/voice/male/die_adv_3.ogg b/web/dungeon/audio/voice/male/die_adv_3.ogg new file mode 100644 index 00000000..7383bebd Binary files /dev/null and b/web/dungeon/audio/voice/male/die_adv_3.ogg differ diff --git a/web/dungeon/audio/voice/male/die_cause_1.m4a b/web/dungeon/audio/voice/male/die_cause_1.m4a new file mode 100644 index 00000000..314bf0d5 Binary files /dev/null and b/web/dungeon/audio/voice/male/die_cause_1.m4a differ diff --git a/web/dungeon/audio/voice/male/die_cause_1.ogg b/web/dungeon/audio/voice/male/die_cause_1.ogg new file mode 100644 index 00000000..57d6658c Binary files /dev/null and b/web/dungeon/audio/voice/male/die_cause_1.ogg differ diff --git a/web/dungeon/audio/voice/male/die_cause_2.m4a b/web/dungeon/audio/voice/male/die_cause_2.m4a new file mode 100644 index 00000000..b48401e6 Binary files /dev/null and b/web/dungeon/audio/voice/male/die_cause_2.m4a differ diff --git a/web/dungeon/audio/voice/male/die_cause_2.ogg b/web/dungeon/audio/voice/male/die_cause_2.ogg new file mode 100644 index 00000000..e667e11d Binary files /dev/null and b/web/dungeon/audio/voice/male/die_cause_2.ogg differ diff --git a/web/dungeon/audio/voice/male/die_cause_3.m4a b/web/dungeon/audio/voice/male/die_cause_3.m4a new file mode 100644 index 00000000..9c8a91eb Binary files /dev/null and b/web/dungeon/audio/voice/male/die_cause_3.m4a differ diff --git a/web/dungeon/audio/voice/male/die_cause_3.ogg b/web/dungeon/audio/voice/male/die_cause_3.ogg new file mode 100644 index 00000000..7167b391 Binary files /dev/null and b/web/dungeon/audio/voice/male/die_cause_3.ogg differ diff --git a/web/dungeon/audio/voice/male/die_intro.m4a b/web/dungeon/audio/voice/male/die_intro.m4a new file mode 100644 index 00000000..f9a9ce0f Binary files /dev/null and b/web/dungeon/audio/voice/male/die_intro.m4a differ diff --git a/web/dungeon/audio/voice/male/die_intro.ogg b/web/dungeon/audio/voice/male/die_intro.ogg new file mode 100644 index 00000000..54bbb225 Binary files /dev/null and b/web/dungeon/audio/voice/male/die_intro.ogg differ diff --git a/web/dungeon/audio/voice/male/evt_armor.m4a b/web/dungeon/audio/voice/male/evt_armor.m4a new file mode 100644 index 00000000..9adadf74 Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_armor.m4a differ diff --git a/web/dungeon/audio/voice/male/evt_armor.ogg b/web/dungeon/audio/voice/male/evt_armor.ogg new file mode 100644 index 00000000..8386d999 Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_armor.ogg differ diff --git a/web/dungeon/audio/voice/male/evt_coin_1.m4a b/web/dungeon/audio/voice/male/evt_coin_1.m4a new file mode 100644 index 00000000..f0d8d6b7 Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_coin_1.m4a differ diff --git a/web/dungeon/audio/voice/male/evt_coin_1.ogg b/web/dungeon/audio/voice/male/evt_coin_1.ogg new file mode 100644 index 00000000..f84521c9 Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_coin_1.ogg differ diff --git a/web/dungeon/audio/voice/male/evt_coin_2.m4a b/web/dungeon/audio/voice/male/evt_coin_2.m4a new file mode 100644 index 00000000..37f85ffa Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_coin_2.m4a differ diff --git a/web/dungeon/audio/voice/male/evt_coin_2.ogg b/web/dungeon/audio/voice/male/evt_coin_2.ogg new file mode 100644 index 00000000..654ca1cc Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_coin_2.ogg differ diff --git a/web/dungeon/audio/voice/male/evt_coin_3.m4a b/web/dungeon/audio/voice/male/evt_coin_3.m4a new file mode 100644 index 00000000..7c8f469b Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_coin_3.m4a differ diff --git a/web/dungeon/audio/voice/male/evt_coin_3.ogg b/web/dungeon/audio/voice/male/evt_coin_3.ogg new file mode 100644 index 00000000..c00e6dfa Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_coin_3.ogg differ diff --git a/web/dungeon/audio/voice/male/evt_descend.m4a b/web/dungeon/audio/voice/male/evt_descend.m4a new file mode 100644 index 00000000..7d1296ec Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_descend.m4a differ diff --git a/web/dungeon/audio/voice/male/evt_descend.ogg b/web/dungeon/audio/voice/male/evt_descend.ogg new file mode 100644 index 00000000..27044723 Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_descend.ogg differ diff --git a/web/dungeon/audio/voice/male/evt_potion.m4a b/web/dungeon/audio/voice/male/evt_potion.m4a new file mode 100644 index 00000000..043ed3fb Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_potion.m4a differ diff --git a/web/dungeon/audio/voice/male/evt_potion.ogg b/web/dungeon/audio/voice/male/evt_potion.ogg new file mode 100644 index 00000000..2accaa7f Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_potion.ogg differ diff --git a/web/dungeon/audio/voice/male/evt_potion_full.m4a b/web/dungeon/audio/voice/male/evt_potion_full.m4a new file mode 100644 index 00000000..5832f529 Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_potion_full.m4a differ diff --git a/web/dungeon/audio/voice/male/evt_potion_full.ogg b/web/dungeon/audio/voice/male/evt_potion_full.ogg new file mode 100644 index 00000000..8f81bbb0 Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_potion_full.ogg differ diff --git a/web/dungeon/audio/voice/male/evt_stairs_lock.m4a b/web/dungeon/audio/voice/male/evt_stairs_lock.m4a new file mode 100644 index 00000000..57708d94 Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_stairs_lock.m4a differ diff --git a/web/dungeon/audio/voice/male/evt_stairs_lock.ogg b/web/dungeon/audio/voice/male/evt_stairs_lock.ogg new file mode 100644 index 00000000..c7f9134d Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_stairs_lock.ogg differ diff --git a/web/dungeon/audio/voice/male/evt_weapon.m4a b/web/dungeon/audio/voice/male/evt_weapon.m4a new file mode 100644 index 00000000..eee04e04 Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_weapon.m4a differ diff --git a/web/dungeon/audio/voice/male/evt_weapon.ogg b/web/dungeon/audio/voice/male/evt_weapon.ogg new file mode 100644 index 00000000..486cc3e8 Binary files /dev/null and b/web/dungeon/audio/voice/male/evt_weapon.ogg differ diff --git a/web/dungeon/audio/voice/male/gd_invalid.m4a b/web/dungeon/audio/voice/male/gd_invalid.m4a new file mode 100644 index 00000000..63dd900b Binary files /dev/null and b/web/dungeon/audio/voice/male/gd_invalid.m4a differ diff --git a/web/dungeon/audio/voice/male/gd_invalid.ogg b/web/dungeon/audio/voice/male/gd_invalid.ogg new file mode 100644 index 00000000..d4d465db Binary files /dev/null and b/web/dungeon/audio/voice/male/gd_invalid.ogg differ diff --git a/web/dungeon/audio/voice/male/gd_pulse.m4a b/web/dungeon/audio/voice/male/gd_pulse.m4a new file mode 100644 index 00000000..ac3c6ebe Binary files /dev/null and b/web/dungeon/audio/voice/male/gd_pulse.m4a differ diff --git a/web/dungeon/audio/voice/male/gd_pulse.ogg b/web/dungeon/audio/voice/male/gd_pulse.ogg new file mode 100644 index 00000000..ef11506e Binary files /dev/null and b/web/dungeon/audio/voice/male/gd_pulse.ogg differ diff --git a/web/dungeon/audio/voice/male/gd_turn.m4a b/web/dungeon/audio/voice/male/gd_turn.m4a new file mode 100644 index 00000000..1d2a5698 Binary files /dev/null and b/web/dungeon/audio/voice/male/gd_turn.m4a differ diff --git a/web/dungeon/audio/voice/male/gd_turn.ogg b/web/dungeon/audio/voice/male/gd_turn.ogg new file mode 100644 index 00000000..9b15685d Binary files /dev/null and b/web/dungeon/audio/voice/male/gd_turn.ogg differ diff --git a/web/dungeon/audio/voice/male/help_battle.m4a b/web/dungeon/audio/voice/male/help_battle.m4a new file mode 100644 index 00000000..cb5d32ef Binary files /dev/null and b/web/dungeon/audio/voice/male/help_battle.m4a differ diff --git a/web/dungeon/audio/voice/male/help_battle.ogg b/web/dungeon/audio/voice/male/help_battle.ogg new file mode 100644 index 00000000..31517e56 Binary files /dev/null and b/web/dungeon/audio/voice/male/help_battle.ogg differ diff --git a/web/dungeon/audio/voice/male/help_explore.m4a b/web/dungeon/audio/voice/male/help_explore.m4a new file mode 100644 index 00000000..19a657f5 Binary files /dev/null and b/web/dungeon/audio/voice/male/help_explore.m4a differ diff --git a/web/dungeon/audio/voice/male/help_explore.ogg b/web/dungeon/audio/voice/male/help_explore.ogg new file mode 100644 index 00000000..87cf1c80 Binary files /dev/null and b/web/dungeon/audio/voice/male/help_explore.ogg differ diff --git a/web/dungeon/audio/voice/male/intro_1.m4a b/web/dungeon/audio/voice/male/intro_1.m4a new file mode 100644 index 00000000..a0473fd9 Binary files /dev/null and b/web/dungeon/audio/voice/male/intro_1.m4a differ diff --git a/web/dungeon/audio/voice/male/intro_1.ogg b/web/dungeon/audio/voice/male/intro_1.ogg new file mode 100644 index 00000000..49f56402 Binary files /dev/null and b/web/dungeon/audio/voice/male/intro_1.ogg differ diff --git a/web/dungeon/audio/voice/male/intro_2.m4a b/web/dungeon/audio/voice/male/intro_2.m4a new file mode 100644 index 00000000..65499bdb Binary files /dev/null and b/web/dungeon/audio/voice/male/intro_2.m4a differ diff --git a/web/dungeon/audio/voice/male/intro_2.ogg b/web/dungeon/audio/voice/male/intro_2.ogg new file mode 100644 index 00000000..224bf22b Binary files /dev/null and b/web/dungeon/audio/voice/male/intro_2.ogg differ diff --git a/web/dungeon/audio/voice/male/meta_gold_1.m4a b/web/dungeon/audio/voice/male/meta_gold_1.m4a new file mode 100644 index 00000000..5abf7070 Binary files /dev/null and b/web/dungeon/audio/voice/male/meta_gold_1.m4a differ diff --git a/web/dungeon/audio/voice/male/meta_gold_1.ogg b/web/dungeon/audio/voice/male/meta_gold_1.ogg new file mode 100644 index 00000000..2c1082c2 Binary files /dev/null and b/web/dungeon/audio/voice/male/meta_gold_1.ogg differ diff --git a/web/dungeon/audio/voice/male/meta_gold_2.m4a b/web/dungeon/audio/voice/male/meta_gold_2.m4a new file mode 100644 index 00000000..f7267fff Binary files /dev/null and b/web/dungeon/audio/voice/male/meta_gold_2.m4a differ diff --git a/web/dungeon/audio/voice/male/meta_gold_2.ogg b/web/dungeon/audio/voice/male/meta_gold_2.ogg new file mode 100644 index 00000000..3d6044fc Binary files /dev/null and b/web/dungeon/audio/voice/male/meta_gold_2.ogg differ diff --git a/web/dungeon/audio/voice/male/meta_gold_3.m4a b/web/dungeon/audio/voice/male/meta_gold_3.m4a new file mode 100644 index 00000000..a941728b Binary files /dev/null and b/web/dungeon/audio/voice/male/meta_gold_3.m4a differ diff --git a/web/dungeon/audio/voice/male/meta_gold_3.ogg b/web/dungeon/audio/voice/male/meta_gold_3.ogg new file mode 100644 index 00000000..9e334e16 Binary files /dev/null and b/web/dungeon/audio/voice/male/meta_gold_3.ogg differ diff --git a/web/dungeon/audio/voice/male/nav_done.m4a b/web/dungeon/audio/voice/male/nav_done.m4a new file mode 100644 index 00000000..15fb8a4f Binary files /dev/null and b/web/dungeon/audio/voice/male/nav_done.m4a differ diff --git a/web/dungeon/audio/voice/male/nav_done.ogg b/web/dungeon/audio/voice/male/nav_done.ogg new file mode 100644 index 00000000..3c198eff Binary files /dev/null and b/web/dungeon/audio/voice/male/nav_done.ogg differ diff --git a/web/dungeon/audio/voice/male/nav_e.m4a b/web/dungeon/audio/voice/male/nav_e.m4a new file mode 100644 index 00000000..9dd588a8 Binary files /dev/null and b/web/dungeon/audio/voice/male/nav_e.m4a differ diff --git a/web/dungeon/audio/voice/male/nav_e.ogg b/web/dungeon/audio/voice/male/nav_e.ogg new file mode 100644 index 00000000..534c56f2 Binary files /dev/null and b/web/dungeon/audio/voice/male/nav_e.ogg differ diff --git a/web/dungeon/audio/voice/male/nav_n.m4a b/web/dungeon/audio/voice/male/nav_n.m4a new file mode 100644 index 00000000..34f7ec22 Binary files /dev/null and b/web/dungeon/audio/voice/male/nav_n.m4a differ diff --git a/web/dungeon/audio/voice/male/nav_n.ogg b/web/dungeon/audio/voice/male/nav_n.ogg new file mode 100644 index 00000000..80590b4b Binary files /dev/null and b/web/dungeon/audio/voice/male/nav_n.ogg differ diff --git a/web/dungeon/audio/voice/male/nav_s.m4a b/web/dungeon/audio/voice/male/nav_s.m4a new file mode 100644 index 00000000..424bca44 Binary files /dev/null and b/web/dungeon/audio/voice/male/nav_s.m4a differ diff --git a/web/dungeon/audio/voice/male/nav_s.ogg b/web/dungeon/audio/voice/male/nav_s.ogg new file mode 100644 index 00000000..470ffe44 Binary files /dev/null and b/web/dungeon/audio/voice/male/nav_s.ogg differ diff --git a/web/dungeon/audio/voice/male/nav_w.m4a b/web/dungeon/audio/voice/male/nav_w.m4a new file mode 100644 index 00000000..3fe10359 Binary files /dev/null and b/web/dungeon/audio/voice/male/nav_w.m4a differ diff --git a/web/dungeon/audio/voice/male/nav_w.ogg b/web/dungeon/audio/voice/male/nav_w.ogg new file mode 100644 index 00000000..d6a42072 Binary files /dev/null and b/web/dungeon/audio/voice/male/nav_w.ogg differ diff --git a/web/dungeon/audio/voice/male/settings_hint.m4a b/web/dungeon/audio/voice/male/settings_hint.m4a new file mode 100644 index 00000000..ad8e79e6 Binary files /dev/null and b/web/dungeon/audio/voice/male/settings_hint.m4a differ diff --git a/web/dungeon/audio/voice/male/settings_hint.ogg b/web/dungeon/audio/voice/male/settings_hint.ogg new file mode 100644 index 00000000..6a1a6eb0 Binary files /dev/null and b/web/dungeon/audio/voice/male/settings_hint.ogg differ diff --git a/web/dungeon/audio/voice/male/settle_again.m4a b/web/dungeon/audio/voice/male/settle_again.m4a new file mode 100644 index 00000000..ad062c32 Binary files /dev/null and b/web/dungeon/audio/voice/male/settle_again.m4a differ diff --git a/web/dungeon/audio/voice/male/settle_again.ogg b/web/dungeon/audio/voice/male/settle_again.ogg new file mode 100644 index 00000000..f5a162f1 Binary files /dev/null and b/web/dungeon/audio/voice/male/settle_again.ogg differ diff --git a/web/dungeon/audio/voice/male/settle_stronger.m4a b/web/dungeon/audio/voice/male/settle_stronger.m4a new file mode 100644 index 00000000..36b45144 Binary files /dev/null and b/web/dungeon/audio/voice/male/settle_stronger.m4a differ diff --git a/web/dungeon/audio/voice/male/settle_stronger.ogg b/web/dungeon/audio/voice/male/settle_stronger.ogg new file mode 100644 index 00000000..a1bc0dac Binary files /dev/null and b/web/dungeon/audio/voice/male/settle_stronger.ogg differ diff --git a/web/dungeon/audio/voice/male/settle_win_1.m4a b/web/dungeon/audio/voice/male/settle_win_1.m4a new file mode 100644 index 00000000..3848e401 Binary files /dev/null and b/web/dungeon/audio/voice/male/settle_win_1.m4a differ diff --git a/web/dungeon/audio/voice/male/settle_win_1.ogg b/web/dungeon/audio/voice/male/settle_win_1.ogg new file mode 100644 index 00000000..bd983dac Binary files /dev/null and b/web/dungeon/audio/voice/male/settle_win_1.ogg differ diff --git a/web/dungeon/audio/voice/male/settle_win_2.m4a b/web/dungeon/audio/voice/male/settle_win_2.m4a new file mode 100644 index 00000000..103b5827 Binary files /dev/null and b/web/dungeon/audio/voice/male/settle_win_2.m4a differ diff --git a/web/dungeon/audio/voice/male/settle_win_2.ogg b/web/dungeon/audio/voice/male/settle_win_2.ogg new file mode 100644 index 00000000..abf11c9d Binary files /dev/null and b/web/dungeon/audio/voice/male/settle_win_2.ogg differ diff --git a/web/dungeon/audio/voice/male/st_best_1.m4a b/web/dungeon/audio/voice/male/st_best_1.m4a new file mode 100644 index 00000000..71015661 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_best_1.m4a differ diff --git a/web/dungeon/audio/voice/male/st_best_1.ogg b/web/dungeon/audio/voice/male/st_best_1.ogg new file mode 100644 index 00000000..fcdb93e5 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_best_1.ogg differ diff --git a/web/dungeon/audio/voice/male/st_best_2.m4a b/web/dungeon/audio/voice/male/st_best_2.m4a new file mode 100644 index 00000000..f94ee481 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_best_2.m4a differ diff --git a/web/dungeon/audio/voice/male/st_best_2.ogg b/web/dungeon/audio/voice/male/st_best_2.ogg new file mode 100644 index 00000000..22ed6dd6 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_best_2.ogg differ diff --git a/web/dungeon/audio/voice/male/st_best_3.m4a b/web/dungeon/audio/voice/male/st_best_3.m4a new file mode 100644 index 00000000..de70dfd9 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_best_3.m4a differ diff --git a/web/dungeon/audio/voice/male/st_best_3.ogg b/web/dungeon/audio/voice/male/st_best_3.ogg new file mode 100644 index 00000000..d55420e3 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_best_3.ogg differ diff --git a/web/dungeon/audio/voice/male/st_face_e.m4a b/web/dungeon/audio/voice/male/st_face_e.m4a new file mode 100644 index 00000000..65a0dbcb Binary files /dev/null and b/web/dungeon/audio/voice/male/st_face_e.m4a differ diff --git a/web/dungeon/audio/voice/male/st_face_e.ogg b/web/dungeon/audio/voice/male/st_face_e.ogg new file mode 100644 index 00000000..1f39172f Binary files /dev/null and b/web/dungeon/audio/voice/male/st_face_e.ogg differ diff --git a/web/dungeon/audio/voice/male/st_face_n.m4a b/web/dungeon/audio/voice/male/st_face_n.m4a new file mode 100644 index 00000000..ac3356a8 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_face_n.m4a differ diff --git a/web/dungeon/audio/voice/male/st_face_n.ogg b/web/dungeon/audio/voice/male/st_face_n.ogg new file mode 100644 index 00000000..4ee83faf Binary files /dev/null and b/web/dungeon/audio/voice/male/st_face_n.ogg differ diff --git a/web/dungeon/audio/voice/male/st_face_s.m4a b/web/dungeon/audio/voice/male/st_face_s.m4a new file mode 100644 index 00000000..fd08c5cd Binary files /dev/null and b/web/dungeon/audio/voice/male/st_face_s.m4a differ diff --git a/web/dungeon/audio/voice/male/st_face_s.ogg b/web/dungeon/audio/voice/male/st_face_s.ogg new file mode 100644 index 00000000..bb47da47 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_face_s.ogg differ diff --git a/web/dungeon/audio/voice/male/st_face_w.m4a b/web/dungeon/audio/voice/male/st_face_w.m4a new file mode 100644 index 00000000..0a833162 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_face_w.m4a differ diff --git a/web/dungeon/audio/voice/male/st_face_w.ogg b/web/dungeon/audio/voice/male/st_face_w.ogg new file mode 100644 index 00000000..3cfcad28 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_face_w.ogg differ diff --git a/web/dungeon/audio/voice/male/st_floor_1.m4a b/web/dungeon/audio/voice/male/st_floor_1.m4a new file mode 100644 index 00000000..765bc463 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_1.m4a differ diff --git a/web/dungeon/audio/voice/male/st_floor_1.ogg b/web/dungeon/audio/voice/male/st_floor_1.ogg new file mode 100644 index 00000000..70dfc130 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_1.ogg differ diff --git a/web/dungeon/audio/voice/male/st_floor_2.m4a b/web/dungeon/audio/voice/male/st_floor_2.m4a new file mode 100644 index 00000000..f7378540 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_2.m4a differ diff --git a/web/dungeon/audio/voice/male/st_floor_2.ogg b/web/dungeon/audio/voice/male/st_floor_2.ogg new file mode 100644 index 00000000..0bf71ea1 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_2.ogg differ diff --git a/web/dungeon/audio/voice/male/st_floor_3.m4a b/web/dungeon/audio/voice/male/st_floor_3.m4a new file mode 100644 index 00000000..d2dee385 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_3.m4a differ diff --git a/web/dungeon/audio/voice/male/st_floor_3.ogg b/web/dungeon/audio/voice/male/st_floor_3.ogg new file mode 100644 index 00000000..e6f7d35d Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_3.ogg differ diff --git a/web/dungeon/audio/voice/male/st_floor_4.m4a b/web/dungeon/audio/voice/male/st_floor_4.m4a new file mode 100644 index 00000000..0f58e229 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_4.m4a differ diff --git a/web/dungeon/audio/voice/male/st_floor_4.ogg b/web/dungeon/audio/voice/male/st_floor_4.ogg new file mode 100644 index 00000000..ffc4f8b4 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_4.ogg differ diff --git a/web/dungeon/audio/voice/male/st_floor_5.m4a b/web/dungeon/audio/voice/male/st_floor_5.m4a new file mode 100644 index 00000000..e68a98a5 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_5.m4a differ diff --git a/web/dungeon/audio/voice/male/st_floor_5.ogg b/web/dungeon/audio/voice/male/st_floor_5.ogg new file mode 100644 index 00000000..5bb121a7 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_5.ogg differ diff --git a/web/dungeon/audio/voice/male/st_floor_6.m4a b/web/dungeon/audio/voice/male/st_floor_6.m4a new file mode 100644 index 00000000..d6af8992 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_6.m4a differ diff --git a/web/dungeon/audio/voice/male/st_floor_6.ogg b/web/dungeon/audio/voice/male/st_floor_6.ogg new file mode 100644 index 00000000..51b09ad6 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_6.ogg differ diff --git a/web/dungeon/audio/voice/male/st_floor_7.m4a b/web/dungeon/audio/voice/male/st_floor_7.m4a new file mode 100644 index 00000000..fb51f47f Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_7.m4a differ diff --git a/web/dungeon/audio/voice/male/st_floor_7.ogg b/web/dungeon/audio/voice/male/st_floor_7.ogg new file mode 100644 index 00000000..a126a66e Binary files /dev/null and b/web/dungeon/audio/voice/male/st_floor_7.ogg differ diff --git a/web/dungeon/audio/voice/male/st_gold_1.m4a b/web/dungeon/audio/voice/male/st_gold_1.m4a new file mode 100644 index 00000000..4f4b8395 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_gold_1.m4a differ diff --git a/web/dungeon/audio/voice/male/st_gold_1.ogg b/web/dungeon/audio/voice/male/st_gold_1.ogg new file mode 100644 index 00000000..7a023a4e Binary files /dev/null and b/web/dungeon/audio/voice/male/st_gold_1.ogg differ diff --git a/web/dungeon/audio/voice/male/st_gold_2.m4a b/web/dungeon/audio/voice/male/st_gold_2.m4a new file mode 100644 index 00000000..f537ede7 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_gold_2.m4a differ diff --git a/web/dungeon/audio/voice/male/st_gold_2.ogg b/web/dungeon/audio/voice/male/st_gold_2.ogg new file mode 100644 index 00000000..448d04b2 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_gold_2.ogg differ diff --git a/web/dungeon/audio/voice/male/st_gold_3.m4a b/web/dungeon/audio/voice/male/st_gold_3.m4a new file mode 100644 index 00000000..b4718deb Binary files /dev/null and b/web/dungeon/audio/voice/male/st_gold_3.m4a differ diff --git a/web/dungeon/audio/voice/male/st_gold_3.ogg b/web/dungeon/audio/voice/male/st_gold_3.ogg new file mode 100644 index 00000000..d03ea0ff Binary files /dev/null and b/web/dungeon/audio/voice/male/st_gold_3.ogg differ diff --git a/web/dungeon/audio/voice/male/st_gold_4.m4a b/web/dungeon/audio/voice/male/st_gold_4.m4a new file mode 100644 index 00000000..763ad2c7 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_gold_4.m4a differ diff --git a/web/dungeon/audio/voice/male/st_gold_4.ogg b/web/dungeon/audio/voice/male/st_gold_4.ogg new file mode 100644 index 00000000..f5667a92 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_gold_4.ogg differ diff --git a/web/dungeon/audio/voice/male/st_hp_1.m4a b/web/dungeon/audio/voice/male/st_hp_1.m4a new file mode 100644 index 00000000..8c748a4f Binary files /dev/null and b/web/dungeon/audio/voice/male/st_hp_1.m4a differ diff --git a/web/dungeon/audio/voice/male/st_hp_1.ogg b/web/dungeon/audio/voice/male/st_hp_1.ogg new file mode 100644 index 00000000..66652197 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_hp_1.ogg differ diff --git a/web/dungeon/audio/voice/male/st_hp_2.m4a b/web/dungeon/audio/voice/male/st_hp_2.m4a new file mode 100644 index 00000000..b52d0e74 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_hp_2.m4a differ diff --git a/web/dungeon/audio/voice/male/st_hp_2.ogg b/web/dungeon/audio/voice/male/st_hp_2.ogg new file mode 100644 index 00000000..a569a79b Binary files /dev/null and b/web/dungeon/audio/voice/male/st_hp_2.ogg differ diff --git a/web/dungeon/audio/voice/male/st_hp_3.m4a b/web/dungeon/audio/voice/male/st_hp_3.m4a new file mode 100644 index 00000000..c616d3b4 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_hp_3.m4a differ diff --git a/web/dungeon/audio/voice/male/st_hp_3.ogg b/web/dungeon/audio/voice/male/st_hp_3.ogg new file mode 100644 index 00000000..eeea79b8 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_hp_3.ogg differ diff --git a/web/dungeon/audio/voice/male/st_hp_4.m4a b/web/dungeon/audio/voice/male/st_hp_4.m4a new file mode 100644 index 00000000..504ff900 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_hp_4.m4a differ diff --git a/web/dungeon/audio/voice/male/st_hp_4.ogg b/web/dungeon/audio/voice/male/st_hp_4.ogg new file mode 100644 index 00000000..8e6bf249 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_hp_4.ogg differ diff --git a/web/dungeon/audio/voice/male/st_threat_both.m4a b/web/dungeon/audio/voice/male/st_threat_both.m4a new file mode 100644 index 00000000..ec298c41 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_threat_both.m4a differ diff --git a/web/dungeon/audio/voice/male/st_threat_both.ogg b/web/dungeon/audio/voice/male/st_threat_both.ogg new file mode 100644 index 00000000..8c06f521 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_threat_both.ogg differ diff --git a/web/dungeon/audio/voice/male/st_threat_left.m4a b/web/dungeon/audio/voice/male/st_threat_left.m4a new file mode 100644 index 00000000..0fe451fc Binary files /dev/null and b/web/dungeon/audio/voice/male/st_threat_left.m4a differ diff --git a/web/dungeon/audio/voice/male/st_threat_left.ogg b/web/dungeon/audio/voice/male/st_threat_left.ogg new file mode 100644 index 00000000..c82b36a5 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_threat_left.ogg differ diff --git a/web/dungeon/audio/voice/male/st_threat_none.m4a b/web/dungeon/audio/voice/male/st_threat_none.m4a new file mode 100644 index 00000000..97677aca Binary files /dev/null and b/web/dungeon/audio/voice/male/st_threat_none.m4a differ diff --git a/web/dungeon/audio/voice/male/st_threat_none.ogg b/web/dungeon/audio/voice/male/st_threat_none.ogg new file mode 100644 index 00000000..10cd0769 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_threat_none.ogg differ diff --git a/web/dungeon/audio/voice/male/st_threat_right.m4a b/web/dungeon/audio/voice/male/st_threat_right.m4a new file mode 100644 index 00000000..40cd1dd5 Binary files /dev/null and b/web/dungeon/audio/voice/male/st_threat_right.m4a differ diff --git a/web/dungeon/audio/voice/male/st_threat_right.ogg b/web/dungeon/audio/voice/male/st_threat_right.ogg new file mode 100644 index 00000000..73eca16b Binary files /dev/null and b/web/dungeon/audio/voice/male/st_threat_right.ogg differ diff --git a/web/dungeon/audio/voice/male/stair_dir_e.m4a b/web/dungeon/audio/voice/male/stair_dir_e.m4a new file mode 100644 index 00000000..0cd4be3d Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_dir_e.m4a differ diff --git a/web/dungeon/audio/voice/male/stair_dir_e.ogg b/web/dungeon/audio/voice/male/stair_dir_e.ogg new file mode 100644 index 00000000..bc4a5565 Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_dir_e.ogg differ diff --git a/web/dungeon/audio/voice/male/stair_dir_n.m4a b/web/dungeon/audio/voice/male/stair_dir_n.m4a new file mode 100644 index 00000000..691d8e27 Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_dir_n.m4a differ diff --git a/web/dungeon/audio/voice/male/stair_dir_n.ogg b/web/dungeon/audio/voice/male/stair_dir_n.ogg new file mode 100644 index 00000000..8eb28bfe Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_dir_n.ogg differ diff --git a/web/dungeon/audio/voice/male/stair_dir_s.m4a b/web/dungeon/audio/voice/male/stair_dir_s.m4a new file mode 100644 index 00000000..61a9f774 Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_dir_s.m4a differ diff --git a/web/dungeon/audio/voice/male/stair_dir_s.ogg b/web/dungeon/audio/voice/male/stair_dir_s.ogg new file mode 100644 index 00000000..225140c0 Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_dir_s.ogg differ diff --git a/web/dungeon/audio/voice/male/stair_dir_w.m4a b/web/dungeon/audio/voice/male/stair_dir_w.m4a new file mode 100644 index 00000000..09eaa6f3 Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_dir_w.m4a differ diff --git a/web/dungeon/audio/voice/male/stair_dir_w.ogg b/web/dungeon/audio/voice/male/stair_dir_w.ogg new file mode 100644 index 00000000..c6dfd5ed Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_dir_w.ogg differ diff --git a/web/dungeon/audio/voice/male/stair_hint_a.m4a b/web/dungeon/audio/voice/male/stair_hint_a.m4a new file mode 100644 index 00000000..bc040b19 Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_hint_a.m4a differ diff --git a/web/dungeon/audio/voice/male/stair_hint_a.ogg b/web/dungeon/audio/voice/male/stair_hint_a.ogg new file mode 100644 index 00000000..0e5be1b1 Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_hint_a.ogg differ diff --git a/web/dungeon/audio/voice/male/stair_hint_b.m4a b/web/dungeon/audio/voice/male/stair_hint_b.m4a new file mode 100644 index 00000000..fb3a6a86 Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_hint_b.m4a differ diff --git a/web/dungeon/audio/voice/male/stair_hint_b.ogg b/web/dungeon/audio/voice/male/stair_hint_b.ogg new file mode 100644 index 00000000..2bfc5803 Binary files /dev/null and b/web/dungeon/audio/voice/male/stair_hint_b.ogg differ diff --git a/web/dungeon/audio/voice/male/sys_continue_ask.m4a b/web/dungeon/audio/voice/male/sys_continue_ask.m4a new file mode 100644 index 00000000..f685f4f7 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_continue_ask.m4a differ diff --git a/web/dungeon/audio/voice/male/sys_continue_ask.ogg b/web/dungeon/audio/voice/male/sys_continue_ask.ogg new file mode 100644 index 00000000..f7e57992 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_continue_ask.ogg differ diff --git a/web/dungeon/audio/voice/male/sys_menu.m4a b/web/dungeon/audio/voice/male/sys_menu.m4a new file mode 100644 index 00000000..c2d74165 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_menu.m4a differ diff --git a/web/dungeon/audio/voice/male/sys_menu.ogg b/web/dungeon/audio/voice/male/sys_menu.ogg new file mode 100644 index 00000000..9ed5cda9 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_menu.ogg differ diff --git a/web/dungeon/audio/voice/male/sys_menu_help.m4a b/web/dungeon/audio/voice/male/sys_menu_help.m4a new file mode 100644 index 00000000..e8219ff3 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_menu_help.m4a differ diff --git a/web/dungeon/audio/voice/male/sys_menu_help.ogg b/web/dungeon/audio/voice/male/sys_menu_help.ogg new file mode 100644 index 00000000..10ee7ce7 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_menu_help.ogg differ diff --git a/web/dungeon/audio/voice/male/sys_menu_restart.m4a b/web/dungeon/audio/voice/male/sys_menu_restart.m4a new file mode 100644 index 00000000..c4b33dce Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_menu_restart.m4a differ diff --git a/web/dungeon/audio/voice/male/sys_menu_restart.ogg b/web/dungeon/audio/voice/male/sys_menu_restart.ogg new file mode 100644 index 00000000..f5cbb393 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_menu_restart.ogg differ diff --git a/web/dungeon/audio/voice/male/sys_menu_resume.m4a b/web/dungeon/audio/voice/male/sys_menu_resume.m4a new file mode 100644 index 00000000..0e25ab58 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_menu_resume.m4a differ diff --git a/web/dungeon/audio/voice/male/sys_menu_resume.ogg b/web/dungeon/audio/voice/male/sys_menu_resume.ogg new file mode 100644 index 00000000..db8697ed Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_menu_resume.ogg differ diff --git a/web/dungeon/audio/voice/male/sys_menu_save.m4a b/web/dungeon/audio/voice/male/sys_menu_save.m4a new file mode 100644 index 00000000..f5f5e285 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_menu_save.m4a differ diff --git a/web/dungeon/audio/voice/male/sys_menu_save.ogg b/web/dungeon/audio/voice/male/sys_menu_save.ogg new file mode 100644 index 00000000..853cb1f1 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_menu_save.ogg differ diff --git a/web/dungeon/audio/voice/male/sys_new_run.m4a b/web/dungeon/audio/voice/male/sys_new_run.m4a new file mode 100644 index 00000000..0f9848a4 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_new_run.m4a differ diff --git a/web/dungeon/audio/voice/male/sys_new_run.ogg b/web/dungeon/audio/voice/male/sys_new_run.ogg new file mode 100644 index 00000000..f4a09fb5 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_new_run.ogg differ diff --git a/web/dungeon/audio/voice/male/sys_saved.m4a b/web/dungeon/audio/voice/male/sys_saved.m4a new file mode 100644 index 00000000..9ba9fdf2 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_saved.m4a differ diff --git a/web/dungeon/audio/voice/male/sys_saved.ogg b/web/dungeon/audio/voice/male/sys_saved.ogg new file mode 100644 index 00000000..1bf3e216 Binary files /dev/null and b/web/dungeon/audio/voice/male/sys_saved.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_attack.m4a b/web/dungeon/audio/voice/male/tut_attack.m4a new file mode 100644 index 00000000..4ead6add Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_attack.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_attack.ogg b/web/dungeon/audio/voice/male/tut_attack.ogg new file mode 100644 index 00000000..90272d7d Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_attack.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_d0.m4a b/web/dungeon/audio/voice/male/tut_d0.m4a new file mode 100644 index 00000000..a1385c01 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_d0.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_d0.ogg b/web/dungeon/audio/voice/male/tut_d0.ogg new file mode 100644 index 00000000..03bb1c62 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_d0.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_done.m4a b/web/dungeon/audio/voice/male/tut_done.m4a new file mode 100644 index 00000000..0e6595e4 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_done.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_done.ogg b/web/dungeon/audio/voice/male/tut_done.ogg new file mode 100644 index 00000000..d647a317 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_done.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_echo_come.m4a b/web/dungeon/audio/voice/male/tut_echo_come.m4a new file mode 100644 index 00000000..77b23784 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_echo_come.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_echo_come.ogg b/web/dungeon/audio/voice/male/tut_echo_come.ogg new file mode 100644 index 00000000..39790ba1 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_echo_come.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_front_rule.m4a b/web/dungeon/audio/voice/male/tut_front_rule.m4a new file mode 100644 index 00000000..59ce2391 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_front_rule.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_front_rule.ogg b/web/dungeon/audio/voice/male/tut_front_rule.ogg new file mode 100644 index 00000000..1311760d Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_front_rule.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_kill.m4a b/web/dungeon/audio/voice/male/tut_kill.m4a new file mode 100644 index 00000000..9749ec37 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_kill.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_kill.ogg b/web/dungeon/audio/voice/male/tut_kill.ogg new file mode 100644 index 00000000..7284d7b0 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_kill.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_menu.m4a b/web/dungeon/audio/voice/male/tut_menu.m4a new file mode 100644 index 00000000..1cc79875 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_menu.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_menu.ogg b/web/dungeon/audio/voice/male/tut_menu.ogg new file mode 100644 index 00000000..c595bb6d Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_menu.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_mob.m4a b/web/dungeon/audio/voice/male/tut_mob.m4a new file mode 100644 index 00000000..bbb97251 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_mob.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_mob.ogg b/web/dungeon/audio/voice/male/tut_mob.ogg new file mode 100644 index 00000000..76811063 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_mob.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_parry.m4a b/web/dungeon/audio/voice/male/tut_parry.m4a new file mode 100644 index 00000000..37535dec Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_parry.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_parry.ogg b/web/dungeon/audio/voice/male/tut_parry.ogg new file mode 100644 index 00000000..8f3fc636 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_parry.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_parry_miss.m4a b/web/dungeon/audio/voice/male/tut_parry_miss.m4a new file mode 100644 index 00000000..97ecfff4 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_parry_miss.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_parry_miss.ogg b/web/dungeon/audio/voice/male/tut_parry_miss.ogg new file mode 100644 index 00000000..38e9594c Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_parry_miss.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_parry_ok.m4a b/web/dungeon/audio/voice/male/tut_parry_ok.m4a new file mode 100644 index 00000000..83dfc706 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_parry_ok.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_parry_ok.ogg b/web/dungeon/audio/voice/male/tut_parry_ok.ogg new file mode 100644 index 00000000..37d0e124 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_parry_ok.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_replay.m4a b/web/dungeon/audio/voice/male/tut_replay.m4a new file mode 100644 index 00000000..f9107908 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_replay.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_replay.ogg b/web/dungeon/audio/voice/male/tut_replay.ogg new file mode 100644 index 00000000..c93aa12e Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_replay.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_side_rule.m4a b/web/dungeon/audio/voice/male/tut_side_rule.m4a new file mode 100644 index 00000000..13c0f667 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_side_rule.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_side_rule.ogg b/web/dungeon/audio/voice/male/tut_side_rule.ogg new file mode 100644 index 00000000..af155c8c Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_side_rule.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_skip.m4a b/web/dungeon/audio/voice/male/tut_skip.m4a new file mode 100644 index 00000000..767ec986 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_skip.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_skip.ogg b/web/dungeon/audio/voice/male/tut_skip.ogg new file mode 100644 index 00000000..5a638a2d Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_skip.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_stairs.m4a b/web/dungeon/audio/voice/male/tut_stairs.m4a new file mode 100644 index 00000000..02ba0b37 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_stairs.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_stairs.ogg b/web/dungeon/audio/voice/male/tut_stairs.ogg new file mode 100644 index 00000000..9fa76345 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_stairs.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_stairs_on.m4a b/web/dungeon/audio/voice/male/tut_stairs_on.m4a new file mode 100644 index 00000000..3a46fc45 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_stairs_on.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_stairs_on.ogg b/web/dungeon/audio/voice/male/tut_stairs_on.ogg new file mode 100644 index 00000000..fb88bd66 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_stairs_on.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_status.m4a b/web/dungeon/audio/voice/male/tut_status.m4a new file mode 100644 index 00000000..0af9b17f Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_status.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_status.ogg b/web/dungeon/audio/voice/male/tut_status.ogg new file mode 100644 index 00000000..483807b4 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_status.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_turn.m4a b/web/dungeon/audio/voice/male/tut_turn.m4a new file mode 100644 index 00000000..b72735e1 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_turn.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_turn.ogg b/web/dungeon/audio/voice/male/tut_turn.ogg new file mode 100644 index 00000000..1151f24d Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_turn.ogg differ diff --git a/web/dungeon/audio/voice/male/tut_walk.m4a b/web/dungeon/audio/voice/male/tut_walk.m4a new file mode 100644 index 00000000..95062507 Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_walk.m4a differ diff --git a/web/dungeon/audio/voice/male/tut_walk.ogg b/web/dungeon/audio/voice/male/tut_walk.ogg new file mode 100644 index 00000000..2affdc1f Binary files /dev/null and b/web/dungeon/audio/voice/male/tut_walk.ogg differ diff --git a/web/dungeon/audio/voice/male/unlock_blade.m4a b/web/dungeon/audio/voice/male/unlock_blade.m4a new file mode 100644 index 00000000..07800a99 Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_blade.m4a differ diff --git a/web/dungeon/audio/voice/male/unlock_blade.ogg b/web/dungeon/audio/voice/male/unlock_blade.ogg new file mode 100644 index 00000000..a37a6d98 Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_blade.ogg differ diff --git a/web/dungeon/audio/voice/male/unlock_echo.m4a b/web/dungeon/audio/voice/male/unlock_echo.m4a new file mode 100644 index 00000000..461e440f Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_echo.m4a differ diff --git a/web/dungeon/audio/voice/male/unlock_echo.ogg b/web/dungeon/audio/voice/male/unlock_echo.ogg new file mode 100644 index 00000000..b34c4293 Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_echo.ogg differ diff --git a/web/dungeon/audio/voice/male/unlock_lead.m4a b/web/dungeon/audio/voice/male/unlock_lead.m4a new file mode 100644 index 00000000..87d15a18 Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_lead.m4a differ diff --git a/web/dungeon/audio/voice/male/unlock_lead.ogg b/web/dungeon/audio/voice/male/unlock_lead.ogg new file mode 100644 index 00000000..5cdd9dc6 Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_lead.ogg differ diff --git a/web/dungeon/audio/voice/male/unlock_none.m4a b/web/dungeon/audio/voice/male/unlock_none.m4a new file mode 100644 index 00000000..90338caa Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_none.m4a differ diff --git a/web/dungeon/audio/voice/male/unlock_none.ogg b/web/dungeon/audio/voice/male/unlock_none.ogg new file mode 100644 index 00000000..33b06d4c Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_none.ogg differ diff --git a/web/dungeon/audio/voice/male/unlock_potion.m4a b/web/dungeon/audio/voice/male/unlock_potion.m4a new file mode 100644 index 00000000..4e208c1d Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_potion.m4a differ diff --git a/web/dungeon/audio/voice/male/unlock_potion.ogg b/web/dungeon/audio/voice/male/unlock_potion.ogg new file mode 100644 index 00000000..36770aa6 Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_potion.ogg differ diff --git a/web/dungeon/audio/voice/male/unlock_veil.m4a b/web/dungeon/audio/voice/male/unlock_veil.m4a new file mode 100644 index 00000000..2228a3f7 Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_veil.m4a differ diff --git a/web/dungeon/audio/voice/male/unlock_veil.ogg b/web/dungeon/audio/voice/male/unlock_veil.ogg new file mode 100644 index 00000000..fb805cf2 Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_veil.ogg differ diff --git a/web/dungeon/audio/voice/male/unlock_vigor.m4a b/web/dungeon/audio/voice/male/unlock_vigor.m4a new file mode 100644 index 00000000..7405aeb3 Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_vigor.m4a differ diff --git a/web/dungeon/audio/voice/male/unlock_vigor.ogg b/web/dungeon/audio/voice/male/unlock_vigor.ogg new file mode 100644 index 00000000..0c6a3ee1 Binary files /dev/null and b/web/dungeon/audio/voice/male/unlock_vigor.ogg differ diff --git a/web/dungeon/cover.png b/web/dungeon/cover.png new file mode 100644 index 00000000..ec826130 Binary files /dev/null and b/web/dungeon/cover.png differ diff --git a/web/dungeon/game.js b/web/dungeon/game.js new file mode 100644 index 00000000..13639c5b --- /dev/null +++ b/web/dungeon/game.js @@ -0,0 +1,1855 @@ +/** + * 《回声地窟》—— 纯音频听声辨位 Roguelike(按 docs/design/16-dungeon.md 实现; + * 台词 id 见 16-dungeon-voice-ids.md,125 句 ×2 性别,无 NPC——怪物不说话)。 + * + * 你是盲眼探窟者:单击=声波脉冲,回声"看"地形(正前距离 4 档 × 材质、左右侧 + * 墙/开口,全部预 pan 资产 + setTimeout + volume 调度,引擎外零实时节点—— + * playtest 虚拟时钟可逐项断言);按住=前进(450ms 起、每格 0.5s、撞墙停无伤); + * 左右滑=转身;双击=免费重发脉冲;上滑=状态链;下滑=菜单(三件套不豁免)。 + * + * 状态机:TITLE / DRILL / EXPLORE / BATTLE / DESCEND / SETTLE + MENU 挂起 overlay。 + * 7 层地窟程序生成:mulberry32((seed ^ (floor*0x9E3779B9))>>>0) 每层独立子种子, + * 房间→L 型走廊(链式生成树)→起点/楼梯(BFS 最远房心)→宝物→怪(BFS≥3 安全区), + * 教学局 L1 叠加硬约束(正前 2 格内墙 / 门怪 / 房内金币),拒绝采样保证可复现。 + * 战斗:缠斗/绕位/Boss 三档,前摇 1.2s 内双击格挡(面对 ×0.2 / 背后 ×1.0), + * 逃跑长按必成挨一下(真话),Boss 拒绝;每 4 杀 atkLv+1。 + * 元进度"遗产"5 项(金币自动购置,死亡不清档):行囊药/体魄/护符/回声强化/武器。 + * 死亡归因真话律:die_cause_* / die_adv_* 按 lastHit(击杀者/背刺/未格挡)与 + * run.parries 真实选择;卡关三级提示 60/120/180s(不报答案,L3 只带方向)。 + * + * 豁免 E1-E4:单击=脉冲、长按=前进/左右滑=转身(E2)、对局期 + * setRateBoostEnabled(false)(E3,EXPLORE/BATTLE/DRILL/DESCEND,TITLE/MENU/SETTLE + * 恢复 true)、战斗菜单=暂停(E4)。longpressend 仅作停走信号,不开 immediateTap。 + */ + +import { Game } from '../engine/game.js'; +import { Guide } from '../engine/guide.js'; +import { vibrate } from '../engine/haptics.js'; + +// ================================ 常量表 ================================ + +/** 绝对方位:DIR_ORDER 顺时针(右转 = idx+1,左转 = idx-1,N→W 为逆时针 ✓)。 */ +const DIR_ORDER = ['N', 'E', 'S', 'W']; +const DIR_VEC = { N: { dx: 0, dy: -1 }, E: { dx: 1, dy: 0 }, S: { dx: 0, dy: 1 }, W: { dx: -1, dy: 0 } }; +const DIR_KEY = { N: 'n', E: 'e', S: 's', W: 'w' }; + +/** 相对方位(战斗/脚步共用):0 前 / 1 右 / 2 后 / 3 左。 */ +const REL = { FRONT: 0, RIGHT: 1, BEHIND: 2, LEFT: 3 }; + +const PULSE_CD = 1200; // 脉冲冷却(§3.1) +const TEL_WINDOW = 1200; // 敌方攻击前摇 = 格挡窗口(§7.2) +const WALK_STEP_MS = 500; // 长按每格耗时(§2.1) +const STUCK_LEVELS = [60000, 120000, 180000]; // 卡关三级(§10.3) +const NAV_REPEAT_MS = 60000; // L3 带路重复周期 +const NAV_MIN_GAP_MS = 1500; // 同向导航句最小间隔(R2 D1-3:短窗不重复,保险丝友好) +const DENSITY_STEP = 12; // 主路径内容密度下限(R2 D0-3①):每 ≤12 格必有遭遇 +const AMB_AFTER_MS = 20000; // 探索呼吸层(R2 D0-3②):静默超 20s 播环境垫 +const AMB_VOLUME = 0.35; + +/** 回声编码(§3.1):正前距离 4 档 → 延迟 / 音量。 */ +const ECHO_DELAY = [0, 250, 500, 800, 1100]; +const ECHO_VOL = [0, 0.9, 0.7, 0.5, 0.35]; +const SIDE_AT = 350; // 侧向回声延后 +const MAT_TAIL_AT = 120; // 材质尾音跟在回声到达后 + +/** 敌型表(§7.2/§8.1;def 全 0,cls=脚步/前摇音色类,gold=掉落)。 */ +const MOBS = { + bat: { hp: 4, atk: 0, def: 0, cls: 'small', gold: 3, start: 'btl_bat' }, + rat: { hp: 6, atk: 1, def: 0, cls: 'small', gold: 5, start: 'btl_rat' }, + mush: { hp: 8, atk: 2, def: 0, cls: 'bug', gold: 8, start: 'btl_mush' }, + crab: { hp: 20, atk: 3, def: 0, cls: 'heavy', gold: 50, start: 'btl_crab', boss: true, mover: true, half: 'btl_half_crab' }, + crawler: { hp: 10, atk: 2, def: 0, cls: 'bug', gold: 10, start: 'btl_crawler', mover: true }, + spider: { hp: 12, atk: 3, def: 0, cls: 'bug', gold: 12, start: 'btl_spider' }, + leech: { hp: 26, atk: 4, def: 0, cls: 'heavy', gold: 80, start: 'btl_leech', boss: true, mover: true, half: 'btl_half_leech' }, + guard: { hp: 16, atk: 4, def: 0, cls: 'heavy', gold: 15, start: 'btl_guard', mover: true }, + king: { hp: 36, atk: 5, def: 0, cls: 'heavy', gold: 0, start: 'btl_king', boss: true, mover: true, half: 'btl_half_king' }, +}; + +/** 层规格(§6.2):尺寸 / 怪物(型×数)/ 宝物 / 窟主 / 终局。 */ +const FLOOR_SPECS = { + 1: { size: 7, mobs: { bat: 2 }, items: ['coin'], drill: true }, + 2: { size: 7, mobs: { rat: 3 }, items: ['coin', 'potion'] }, + 3: { size: 9, mobs: { mush: 3, crab: 1 }, items: ['coin', 'weapon1'], boss: 'crab' }, + 4: { size: 9, mobs: { crawler: 2, spider: 2 }, items: ['coin', 'armor1'] }, + 5: { size: 11, mobs: { spider: 3, crawler: 2 }, items: ['coin', 'potion'] }, + 6: { size: 11, mobs: { spider: 2, crawler: 2, leech: 1 }, items: ['coin', 'weapon2'], boss: 'leech' }, + 7: { size: 11, mobs: { guard: 3, king: 1 }, items: ['coin'], final: true }, +}; + +/** 元进度遗产(§7.4):按价格升序自动购置(金币累计,死亡不清档)。 */ +const LEGACIES = [ + { id: 'potion', price: 90, voice: 'unlock_potion' }, + { id: 'vigor', price: 150, voice: 'unlock_vigor' }, + { id: 'veil', price: 220, voice: 'unlock_veil' }, + { id: 'echo', price: 300, voice: 'unlock_echo' }, + { id: 'blade', price: 450, voice: 'unlock_blade' }, +]; + +const MENU_ITEMS = ['sys_menu_resume', 'sys_menu_save', 'sys_menu_help', 'sys_menu_restart']; +const TITLE_MENU = ['sys_menu_help', 'sys_menu_resume']; // §2.3:标题/结算菜单(帮助/返回两项) + +const HP_BAND_TEXT = { 1: '毫发无损', 2: '受了点伤', 3: '伤势不轻', 4: '命悬一线' }; + +const SAVE_KEY = 'soundgame.progress.dungeon'; +const AUTH_KEY = 'soundgame.auth'; + +const PREWARM_SFX = [ + 'sfx/pulse', 'sfx/echo_1', 'sfx/echo_2', 'sfx/echo_3', 'sfx/echo_4', + 'sfx/mat_stone', 'sfx/mat_door', 'sfx/mat_stair', 'sfx/mat_water', + 'sfx/side_wall_l', 'sfx/side_wall_r', 'sfx/side_open_l', 'sfx/side_open_r', + 'sfx/step_own', 'sfx/step_turn', 'sfx/bump', + 'sfx/coin', 'sfx/potion', 'sfx/gear', 'sfx/stairs_down', + 'sfx/mob_step_small_l', 'sfx/mob_step_small_c', 'sfx/mob_step_small_r', + 'sfx/mob_step_bug_l', 'sfx/mob_step_bug_c', 'sfx/mob_step_bug_r', + 'sfx/mob_step_heavy_l', 'sfx/mob_step_heavy_c', 'sfx/mob_step_heavy_r', + 'sfx/tel_small', 'sfx/tel_small_l', 'sfx/tel_small_r', + 'sfx/tel_bug', 'sfx/tel_bug_l', 'sfx/tel_bug_r', + 'sfx/tel_heavy', 'sfx/tel_heavy_l', 'sfx/tel_heavy_r', + 'sfx/atk_swing', 'sfx/atk_hit', 'sfx/parry', 'sfx/hurt', 'sfx/enemy_die', 'sfx/levelup', + 'sfx/amb_cave', 'sfx/turn_tick', // R2:探索呼吸层垫(D0-3②)+ 战斗回合确认音(D1-6) +]; + +// ================================ 工具 ================================ + +const key = (x, y) => x + ',' + y; +const range = (a, b) => { const r = []; const s = a <= b ? 1 : -1; for (let i = a; a <= b ? i <= b : i >= b; i += s) r.push(i); return r; }; + +// ================================ 主类 ================================ + +export class DungeonGame extends Game { + constructor(audio, root, opts = {}) { + super(audio, root); + this.guide = new Guide(audio); // guide='off' 时全部 API 静默 no-op 且零定时器 + this._fixedSeed = opts.seed != null ? opts.seed : null; // playtest 注入(仅首个新周目生效) + this.state = 'boot'; // TITLE / DRILL / EXPLORE / BATTLE / DESCEND / SETTLE + this.menuOpen = false; // MENU = 挂起 overlay(记录返回态 menuFrom) + this.menuFrom = null; + this._session = 0; // 会话令牌:状态大跳转作废旧计时器 + this._timers = []; + this.meta = this._defaultMeta(); + this.run = null; + this._floor = null; + this._battle = null; + this._drill = null; + this._walk = null; + this._pulseAt = -1e9; + this._lastHit = null; // 归因真话律依据(击杀者/背刺/未格挡) + this._lastEcho = null; // 最近一次脉冲回声(双击重发一致性断言用) + this._onStairs = false; + this._stairsSpokenFloor = 0; + this._dangerFloorDone = false; + this._menuPause = null; + this._menuItems = MENU_ITEMS; + this._menuIdx = 0; + this._stuck = { lastAct: 0, shown: new Set(), nav: false, lastFire: 0, pending: [] }; + this._stuckArmed = false; + this._lastAmbAt = 0; // 探索呼吸层上次播放时刻(R2 D0-3②) + this._navLastDir = null; // 导航模式上次播的方向(同向短窗节流,R2 D1-3) + this._navLastAt = 0; + this._titleChain = []; + } + + _defaultMeta() { + return { gold: 0, unlocks: [], bestFloor: 0, runs: 0, kills: 0, drillDone: false, pending: [] }; + } + + // ---------------- 派生数值(§8.1) ---------------- + + _has(id) { return this.meta.unlocks.includes(id); } + _maxHp() { return 20 + (this._has('vigor') ? 6 : 0); } + _playerAtk() { return 2 + this.run.atkLv + this.run.weapon; } + _armor() { return this.run.armor; } + _hpBand(hp, max) { + const r = hp / max; + if (r > 0.75) return 1; + if (r > 0.5) return 2; + if (r > 0.25) return 3; + return 4; + } + _goldBand(gold) { return gold < 50 ? 1 : gold <= 200 ? 2 : gold <= 600 ? 3 : 4; } + + // ================================ 地窟生成(§6) ================================ + + /** seed 派生公式(§6.1,逐字照设计):每层独立子种子。 */ + _floorSeedOf(seed, floor) { + return (seed ^ (floor * 0x9E3779B9)) >>> 0; + } + + _mulberry32(a) { + return function () { + a |= 0; a = (a + 0x6D2B79F5) | 0; + let t = Math.imul(a ^ (a >>> 15), 1 | a); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; + } + + _bfs(grid, start) { + const S = grid.length; + const dist = Array.from({ length: S }, () => new Array(S).fill(-1)); + const q = [[start.x, start.y]]; + dist[start.y][start.x] = 0; + while (q.length) { + const [x, y] = q.shift(); + for (const d of Object.values(DIR_VEC)) { + const nx = x + d.dx, ny = y + d.dy; + if (nx < 0 || ny < 0 || nx >= S || ny >= S || grid[ny][nx] !== 1 || dist[ny][nx] >= 0) continue; + dist[ny][nx] = dist[y][x] + 1; + q.push([nx, ny]); + } + } + return dist; + } + + /** + * 生成一层:外层拒绝采样(教学局 L1 硬约束优先于随机,§6.1 注), + * 内层全走子种子 rng → 同 seed 两次构造逐格相等(§12.2)。 + * 走廊 = 相邻房间两两 L 型连通(链式 = 生成树,§6.1 ③ 的等价实现)。 + */ + _generateFloor(seed, floor) { + const spec = FLOOR_SPECS[floor]; + const base = this._mulberry32(this._floorSeedOf(seed, floor)); + for (let attempt = 0; attempt < 80; attempt++) { + const rng = this._mulberry32((base() * 4294967296) >>> 0); + const f = this._tryGenFloor(rng, floor, spec); + if (f) return f; + } + throw new Error(`dungeon: floor ${floor} generation failed (seed=${seed})`); + } + + _tryGenFloor(rng, floor, spec) { + const S = spec.size; + const grid = Array.from({ length: S }, () => new Array(S).fill(0)); + const rooms = []; + if (spec.drill) rooms.push({ x: 2, y: 2, w: 3, h: 3 }); // 教学房固定:起点恒 (3,3) + const want = 3 + Math.floor(rng() * 4); // 3..6 间(§6.1 ②) + for (let guard = 0; rooms.length < want && guard < 300; guard++) { + const w = 2 + Math.floor(rng() * 2); // 2..3 + const h = 2 + Math.floor(rng() * 3); // 2..4 + const x = Math.floor(rng() * (S - w + 1)); + const y = Math.floor(rng() * (S - h + 1)); + if (rooms.some((r) => x < r.x + r.w && r.x < x + w && y < r.y + r.h && r.y < y + h)) continue; // 不重叠 + rooms.push({ x, y, w, h }); + } + if (rooms.length < 3) return null; + for (const r of rooms) { + for (let yy = r.y; yy < r.y + r.h; yy++) for (let xx = r.x; xx < r.x + r.w; xx++) grid[yy][xx] = 1; + } + const centerOf = (r) => ({ x: r.x + (r.w >> 1), y: r.y + (r.h >> 1) }); + const carve = (x, y) => { if (x >= 0 && y >= 0 && x < S && y < S) grid[y][x] = 1; }; + for (let i = 1; i < rooms.length; i++) { // L 型走廊链式连通(§6.1 ③) + const a = centerOf(rooms[i - 1]); + const b = centerOf(rooms[i]); + if (rng() < 0.5) { + for (const x of range(a.x, b.x)) carve(x, a.y); + for (const y of range(a.y, b.y)) carve(b.x, y); + } else { + for (const y of range(a.y, b.y)) carve(a.x, y); + for (const x of range(a.x, b.x)) carve(x, b.y); + } + } + const start = spec.drill ? { x: 3, y: 3 } : centerOf(rooms[0]); + if (spec.drill) { + carve(5, 3); // 东侧走道:D4 长按前进 ≥2 格后撞边界 bump + carve(3, 5); // 南门:教学怪把门(D8 走向它触发战斗) + if (grid[1][3] !== 0) return null; // 约束①:起点正前 2 格内有墙(D1 回声演示) + if (grid[2][3] !== 1 || grid[4][3] !== 1) return null; + } + const dist = this._bfs(grid, start); + const floorCells = []; + for (let y = 0; y < S; y++) for (let x = 0; x < S; x++) { + if (grid[y][x] === 1) { + if (dist[y][x] < 0) return null; // 连通性不变式 + floorCells.push({ x, y, d: dist[y][x] }); + } + } + // 楼梯 = BFS 距起点最远房间中心(§6.1 ④);终局层无楼梯,王座=最远房心 + let far = null, farD = -1; + for (const r of rooms) { + const c = centerOf(r); + if (dist[c.y][c.x] > farD) { farD = dist[c.y][c.x]; far = c; } + } + const stairs = spec.final ? null : { x: far.x, y: far.y }; + const throne = spec.final ? { x: far.x, y: far.y } : null; + // 宝物撒入房间格(§6.1 ⑤) + const items = new Map(); + const occupied = new Set([key(start.x, start.y)]); + if (stairs) occupied.add(key(stairs.x, stairs.y)); + if (throne) occupied.add(key(throne.x, throne.y)); + for (const it of spec.items) { + let placed = null; + for (let g2 = 0; g2 < 120 && !placed; g2++) { + let c; + if (spec.drill && it === 'coin') { + // 约束③:教学金币固定在起点房内(拾取体验) + const cells = []; + for (let yy = 2; yy <= 4; yy++) for (let xx = 2; xx <= 4; xx++) { + if (!(xx === 3 && yy === 3) && grid[yy][xx] === 1) cells.push({ x: xx, y: yy }); + } + if (!cells.length) return null; + c = cells[Math.floor(rng() * cells.length)]; + } else { + const room = rooms[Math.floor(rng() * rooms.length)]; + c = { x: room.x + Math.floor(rng() * room.w), y: room.y + Math.floor(rng() * room.h) }; + } + if (grid[c.y]?.[c.x] !== 1 || dist[c.y][c.x] < 1 || occupied.has(key(c.x, c.y))) continue; + placed = c; + } + if (!placed) return null; + occupied.add(key(placed.x, placed.y)); + items.set(key(placed.x, placed.y), it === 'coin' + ? { type: 'coin', gold: 10 + Math.floor(rng() * 21) } // 堆 10-30 金(§7.4 经济核算) + : { type: it }); + } + // 怪物:窟主守楼梯格 / 终局王座;普通怪 BFS ≥3 安全区(教学局非门怪 ≥4, + // 避免 D4/D8 演练路径撞上第二只怪) + const mobs = []; + const mobTypes = []; + for (const [type, n] of Object.entries(spec.mobs)) for (let i = 0; i < n; i++) mobTypes.push(type); + for (let i = mobTypes.length - 1; i > 0; i--) { + const j = Math.floor(rng() * (i + 1)); + [mobTypes[i], mobTypes[j]] = [mobTypes[j], mobTypes[i]]; + } + let doorBatPlaced = false; + const minMobDist = spec.drill ? 4 : 3; + for (const type of mobTypes) { + const ms = MOBS[type]; + let cell = null; + let isDoorBat = false; + if (ms.boss) cell = spec.final ? { ...throne } : { ...stairs }; + else if (spec.drill && type === 'bat' && !doorBatPlaced) { cell = { x: 3, y: 5 }; isDoorBat = true; doorBatPlaced = true; } + if (!cell) { + for (let g3 = 0; g3 < 150 && !cell; g3++) { + const c = floorCells[Math.floor(rng() * floorCells.length)]; + if (c.d < minMobDist || occupied.has(key(c.x, c.y))) continue; + cell = { x: c.x, y: c.y }; + } + if (!cell) return null; + } + occupied.add(key(cell.x, cell.y)); + mobs.push({ type, x: cell.x, y: cell.y, hp: ms.hp, maxHp: ms.hp, drill: isDoorBat }); + } + this._fillMainPathDensity(spec, { grid, dist, start, stairs, throne, mobs, occupied, rng, minMobDist }); + return { floor, size: S, grid, rooms, start, stairs, throne, items, mobs, dist }; + } + + /** + * 主路径内容密度下限(R2 D0-3①):start→楼梯(终局层=王座)的最短路上每 + * ≤ DENSITY_STEP 格必有内容遭遇(怪/宝/楼梯/起点)。生成后沿 BFS 距离梯度 + * 回溯最短路,最长连续无内容段超限 → 段中点补一只本层已有的普通怪型 + * (结构性密度,不加新怪种;教学局补的仍是 ATK0 蝠)。确定性:只用同一 rng。 + */ + _fillMainPathDensity(spec, ctx) { + const { grid, dist, start, stairs, throne, mobs, occupied, rng, minMobDist } = ctx; + const goal = spec.final ? throne : stairs; + if (!goal) return; + const S = grid.length; + const path = [{ x: goal.x, y: goal.y }]; + let cur = { x: goal.x, y: goal.y }; + while (cur.x !== start.x || cur.y !== start.y) { // 距离梯度回溯 = 最短路(固定扫描序,确定) + for (const d of Object.values(DIR_VEC)) { + const nx = cur.x + d.dx, ny = cur.y + d.dy; + if (nx < 0 || ny < 0 || nx >= S || ny >= S || grid[ny][nx] !== 1 || dist[ny][nx] !== dist[cur.y][cur.x] - 1) continue; + cur = { x: nx, y: ny }; + break; + } + path.push(cur); + } + const extraTypes = Object.keys(spec.mobs).filter((t) => !MOBS[t].boss); + for (let round = 0; round < 8 && extraTypes.length; round++) { + let bestA = -1, bestLen = 0, runA = 0; + for (let i = 0; i <= path.length; i++) { + const filled = i < path.length && occupied.has(key(path[i].x, path[i].y)); + if (!filled) continue; // 段在内容格(或路径尽头)闭合 + if (i - runA > bestLen) { bestLen = i - runA; bestA = runA; } + runA = i + 1; + } + if (bestLen <= DENSITY_STEP) return; // 密度达标 + let mid = bestA + (bestLen >> 1); + while (mid < path.length - 1 && dist[path[mid].y][path[mid].x] < minMobDist) mid++; // 安全区内偏后 + const cell = path[mid]; + if (dist[cell.y][cell.x] < minMobDist || occupied.has(key(cell.x, cell.y))) return; // 放不下:保底放弃 + const type = extraTypes[round % extraTypes.length]; + const ms = MOBS[type]; + occupied.add(key(cell.x, cell.y)); + mobs.push({ type, x: cell.x, y: cell.y, hp: ms.hp, maxHp: ms.hp, drill: false }); + } + } + + /** 进入/重生成一层(下潜、读档重放共用);已拾宝物与已清窟主按 run 过滤。 */ + _enterFloor(floor) { + const r = this.run; + r.floor = floor; + this._floor = this._generateFloor(r.seed, floor); + r.pos = { ...this._floor.start }; + r.face = 'N'; + r.veilUsedFloor = false; + this._dangerFloorDone = false; + this._pulseAt = -1e9; + this._onStairs = false; + this._stairsSpokenFloor = 0; + for (const k of r.looted) this._floor.items.delete(k); + if (FLOOR_SPECS[floor].boss && r.cleared.includes(floor)) { + this._floor.mobs = this._floor.mobs.filter((m) => !MOBS[m.type].boss); + } + } + + // ================================ 生命周期 ================================ + + start() { + this.audio.prewarm(PREWARM_SFX); + const save = this._loadSave(); + if (save) { + this.meta = { ...this._defaultMeta(), ...save.meta, unlocks: [...(save.meta?.unlocks || [])], pending: [...(save.meta?.pending || [])] }; + if (save.run) { + this.run = { ...save.run, pos: { x: save.run.pos[0], y: save.run.pos[1] } }; + } + } + this._enterTitle(); + } + + stop() { + this._session++; + this._clearTimers(); + this._stopWalk(); + this.audio.stopAll(); + } + + _goHome() { + this.stop(); + window.location.href = '../'; + } + + // ---------------- TITLE(§2.3) ---------------- + + _enterTitle() { + this._session++; + this._clearTimers(); + this._stopWalk(); + this._battle = null; + this.menuOpen = false; + this.menuFrom = null; + this.state = 'TITLE'; + this.audio.stopVoice(); + this.audio.setRateBoostEnabled(true); // E3:非对局恢复长按快进 + this.audio.play('bgm/menu'); + this.guide.state('menu'); + this._titleChain = this.run ? ['voice/sys_continue_ask'] : []; + if (this._titleChain.length) this._speak(this._titleChain); + } + + _titleUpChain() { + const ids = ['voice/meta_gold_' + this._goldBand(this.meta.gold)]; + const best = this.meta.bestFloor; + ids.push(best < 3 ? 'voice/st_best_1' : best < 6 ? 'voice/st_best_2' : 'voice/st_best_3'); + if (this.meta.unlocks.length === 0) ids.push('voice/unlock_none'); + else for (const u of this.meta.unlocks) ids.push('voice/unlock_' + u); + return ids; + } + + _onTitleGesture(g) { + if (g === 'tap') { + if (this.run) this._resumeRun(); + else this._newRunFlow(); + } else if (g === 'right') { + this._newRunFlow(); // 重掷新周目(meta 保留,§2.3) + } else if (g === 'doubletap') { + const chain = this._titleChain.length ? this._titleChain : this._titleUpChain(); + this._speak(chain); // 重听 continue_ask / 元进度播报 + } else if (g === 'up') { + this._titleChain = this._titleUpChain(); + this._speak(this._titleChain); + } else if (g === 'down') { + this._openMenu(); + } else { + this.guide.error({ voice: 'gd_invalid', onceKey: 'dungeon_invalid' }); // error 震 + full 档纠错 + } + } + + // ---------------- 新周目 / 继续(§4.2、§11) ---------------- + + _newRunFlow() { + this._session++; + this._clearTimers(); + this._stopWalk(); + this._battle = null; + this._drill = null; + this.menuOpen = false; + this.audio.stopVoice(); + const seed = this._fixedSeed != null ? this._fixedSeed : Math.floor(Math.random() * 1e9); + this._fixedSeed = null; + this.run = { + seed, floor: 1, pos: { x: 0, y: 0 }, face: 'N', + hp: this._maxHp(), atkLv: 0, + weapon: this._has('blade') ? 1 : 0, armor: 0, + potion: this._has('potion') ? 1 : 0, + veilUsedFloor: false, killsRun: 0, looted: [], cleared: [], + parries: 0, flees: 0, gold: 0, + }; + this._lastHit = null; + this._enterFloor(1); + this._save(); + const ids = ['voice/sys_new_run']; + if (this.meta.pending.length) { + ids.push('voice/unlock_lead'); + for (const u of this.meta.pending) ids.push('voice/unlock_' + u); + } + this._speakGuarded(ids, () => { + this.meta.pending = []; + this._save(); + if (!this.meta.drillDone) this._enterDrill(); + else this._resumeExplore(this._recapChain()); // 新周目直落探索也带局面复述(P3) + }, 20000); + } + + _restartRun(seed) { // 菜单"重新开始本周目":同 seed 重置 + this.run = null; + this._fixedSeed = seed; + this._newRunFlow(); + } + + _recapChain() { // 局面复述(§11):层 → 血量 → 面朝 → 威胁(金币只在显式上滑状态链) + return ['voice/st_floor_' + this.run.floor, + 'voice/st_hp_' + this._hpBand(this.run.hp, this._maxHp()), + 'voice/st_face_' + DIR_KEY[this.run.face], + 'voice/st_threat_' + this._threatKey()]; + } + + _resumeRun() { + const saved = { pos: { ...this.run.pos }, face: this.run.face, veil: this.run.veilUsedFloor }; + this._enterFloor(this.run.floor); // 重放该层 seed 生成(§11) + this.run.pos = saved.pos; + this.run.face = saved.face; + this.run.veilUsedFloor = saved.veil; + this._resumeExplore(this._recapChain()); + } + + // ================================ 状态接入 ================================ + + /** 进入探索态(DRILL 完成后 / 战斗后 / 读档后共用)。chain = 前置语音链(可空)。 */ + _resumeExplore(chain) { + this.state = 'EXPLORE'; + this._session++; + this._clearTimers(); + this.menuOpen = false; + this.audio.play('bgm/explore'); + this.audio.setRateBoostEnabled(false); // E3 豁免 + this.guide.state('explore'); + this._startMobs(); + this._stuck.lastAct = performance.now(); + this._lastAmbAt = performance.now(); // 呼吸层从进入探索起算静默(R2 D0-3②) + this._stuckArm(); + this._armExploreWatchdog(); + if (chain && chain.length) this._speak(chain); + } + + // ---------------- 手势总入口 ---------------- + + onGesture(g) { + this.guide.disarm(); // 任何手势复位向导看门狗(off 档 no-op) + if (g === 'longpressend') { this._stopWalk(); return; } // 仅停走信号,不承载第二语义 + if (this.menuOpen) { this._onMenuGesture(g); return; } + this._stuck.pending.length = 0; // 玩家操作即作废未播的卡关提示(§10.3) + switch (this.state) { + case 'TITLE': this._onTitleGesture(g); break; + case 'DRILL': this._onDrillGesture(g); break; + case 'EXPLORE': this._onExploreGesture(g); break; + case 'BATTLE': this._onBattleGesture(g); break; + case 'SETTLE': this._onSettleGesture(g); break; + default: break; // DESCEND 演出窗口:不收手势 + } + // 探索静止看门狗:每个手势后仍在探索态则重新武装([10s, 25s] 两级) + if (this.state === 'EXPLORE' && !this.menuOpen) this._armExploreWatchdog(); + if (this.state === 'EXPLORE' && !this.menuOpen) this._stuck.lastAct = performance.now(); + } + + // ================================ 探索态(§2.1) ================================ + + _onExploreGesture(g) { + if (g === 'tap') { + if (this._onStairs && this._stairsUsable()) { this._descend(false); return; } + this._pulse(); + } else if (g === 'doubletap') { + this._pulse(); // 免费重发脉冲(同受 1.2s 冷却闸,§3.1) + } else if (g === 'left') { + this._turn(-1); + } else if (g === 'right') { + this._turn(1); + } else if (g === 'up') { + this._speakStatus(); + } else if (g === 'down') { + this._openMenu(); + } else if (g === 'longpress') { + this._startWalk(); + } + } + + _stairsUsable() { + const spec = FLOOR_SPECS[this.run.floor]; + if (!this._floor.stairs || !this._onStairs) return false; + if (spec.boss && !this.run.cleared.includes(this.run.floor)) return false; // 窟主未清 + return true; + } + + _turn(d) { + const i = DIR_ORDER.indexOf(this.run.face); + this.run.face = DIR_ORDER[(i + d + 4) % 4]; + this.audio.play('sfx/step_turn'); // swipe 震由 gesture.js 内置,不重复 + } + + /** 声波脉冲(§3.1):全部预生成资产 + setTimeout + volume,可被虚拟时钟断言。 */ + _pulse() { + const now = performance.now(); + if (now - this._pulseAt < PULSE_CD) { vibrate('error'); return null; } // 冷却期:仅 error 震 + this._pulseAt = now; + this.audio.play('sfx/pulse', { volume: 0.8 }); + const s = this._session; + const f = this._echoScan(this.run.pos.x, this.run.pos.y, this.run.face); + const band = f.dist <= 1 ? 1 : f.dist === 2 ? 2 : f.dist <= 4 ? 3 : 4; + const delay = ECHO_DELAY[band]; + const vol = ECHO_VOL[band]; + this._after(delay, () => { if (s === this._session) this.audio.play('sfx/echo_' + band, { volume: vol }); }); + this._after(delay + MAT_TAIL_AT, () => { if (s === this._session) this.audio.play('sfx/mat_' + f.mat, { volume: 0.8 }); }); + const L = this._sideScan(-1); + const R = this._sideScan(1); + this._after(SIDE_AT, () => { + if (s !== this._session) return; + this.audio.play('sfx/side_' + L.kind + '_l', { volume: this._sideVol(L) }); + this.audio.play('sfx/side_' + R.kind + '_r', { volume: this._sideVol(R) }); + }); + this._lastEcho = { band, dist: f.dist, mat: f.mat, left: L.kind, right: R.kind, delay, vol }; + return this._lastEcho; + } + + /** 正前扫描:最近反射体决定距离与材质(楼梯=铁梯;身后方向永不进入扫描)。 + * R2 D0-2:站在楼梯格上脉冲 → 贴身铁器回声(教学「铁器=楼梯」的兑现: + * R1 live 165 脉冲 0 次 mat_stair,连站在楼梯上都报石墙)。 */ + _echoScan(x, y, face) { + const st = this._floor.stairs; + if (st && x === st.x && y === st.y) return { dist: 1, mat: 'stair' }; + const d = DIR_VEC[face]; + const S = this._floor.size; + for (let i = 1; i <= S * 2; i++) { + const cx = x + d.dx * i, cy = y + d.dy * i; + if (cx < 0 || cy < 0 || cx >= S || cy >= S || this._floor.grid[cy][cx] === 0) return { dist: i, mat: 'stone' }; + if (st && cx === st.x && cy === st.y) return { dist: i, mat: 'stair' }; + // 木门/水地形当前生成器不产出;材质表保留(mat_door/mat_water 供未来层) + } + return { dist: S * 2, mat: 'stone' }; + } + + /** + * 侧向扫描(side = -1 左 / 1 右):开口(第一格可通行=能走进去)/墙(闷,含距离), + * 身后不报。R2 D0-2 重接 grid 单源:R1 旧实现把「整条侧射线到边界全通」才判 + * 开口——房间/走廊地型下恒为假,40+ 脉冲 side_open 0 次、明明能走的方向报墙。 + * 设计 §3.1 口径:侧向 2 档(墙=闷 / 开口=空旷回灵),开一格就是开口。 + */ + _sideScan(side) { + const fi = DIR_ORDER.indexOf(this.run.face); + const d = DIR_VEC[DIR_ORDER[(fi + side + 4) % 4]]; + const S = this._floor.size; + let x = this.run.pos.x, y = this.run.pos.y; + for (let i = 1; i <= S; i++) { + x += d.dx; y += d.dy; + if (x < 0 || y < 0 || x >= S || y >= S || this._floor.grid[y][x] === 0) { + return { kind: 'wall', dist: i }; + } + if (i === 1) return { kind: 'open', dist: 1 }; // 侧向第一格是地板 = 开口(真话) + } + return { kind: 'open', dist: S }; + } + + /** 侧向音量:回声强化解锁后按远近换 2 组资产音量(§7.4 unlock_echo)。 */ + _sideVol(s) { + if (this._has('echo')) return s.dist <= 1 ? 0.8 : 0.45; + return 0.6; + } + + // ---------------- 长按前进(§2.1:450ms 起、每格 0.5s、撞墙停无伤) ---------------- + + _startWalk() { + if (this._walk || this.state !== 'EXPLORE' && this.state !== 'DRILL') return; + if (this.menuOpen) return; + this._walk = { steps: 0, bumped: false }; + this._walkStep(); + } + + _stopWalk() { this._walk = null; } + + _walkStep() { + if (!this._walk) return; + const s = this._session; + const d = DIR_VEC[this.run.face]; + const nx = this.run.pos.x + d.dx; + const ny = this.run.pos.y + d.dy; + const S = this._floor.size; + if (nx < 0 || ny < 0 || nx >= S || ny >= S || this._floor.grid[ny][nx] !== 1) { + this.audio.play('sfx/bump'); // 撞墙:无伤自动停(§2.1) + this._walk.bumped = true; + this._afterWalkDrill(); + this._stopWalk(); + return; + } + this.run.pos = { x: nx, y: ny }; + this._walk.steps++; + this.audio.play('sfx/step_own', { volume: 0.7 }); + vibrate('swipe'); + // 导航模式(卡关 L3):每步播方向 + if (this._stuck.nav && this.state === 'EXPLORE') this._navSpeakStep(); + // 踏格判定:怪 → 遭遇;宝物 → 拾取;楼梯 → 提示/锁 + const mob = this._floor.mobs.find((m) => m.x === nx && m.y === ny); + if (mob) { this._encounter(mob); return; } // _encounter 内部停走(session 令牌) + const k = key(nx, ny); + const item = this._floor.items.get(k); + if (item) this._pickup(item, k); + this._onStairCell(nx, ny); + this._afterWalkDrill(); + this._after(WALK_STEP_MS, () => { if (s === this._session && this._walk) this._walkStep(); }); + } + + _afterWalkDrill() { + const d = this._drill; + if (!d || d.step !== 4 || !this._walk) return; + if (this._walk.steps >= 2 || this._walk.bumped) this._drillPass(); // §5 D4:≥2 格(撞墙也算过) + } + + _onStairCell(x, y) { + const st = this._floor.stairs; + if (!st || st.x !== x || st.y !== y) { this._onStairs = false; return; } + this._onStairs = true; + this._progress(); // 踏上楼梯格 = 进展事件(§10.3) + const spec = FLOOR_SPECS[this.run.floor]; + if (spec.boss && !this.run.cleared.includes(this.run.floor)) { + this._speak(['voice/evt_stairs_lock']); + return; + } + if (this._stairsSpokenFloor !== this.run.floor) { + this._stairsSpokenFloor = this.run.floor; + this._speak(['voice/tut_stairs_on']); // 楼梯就在脚下。单击,下潜。 + } + } + + // ---------------- 拾取(§6.2/§7.4) ---------------- + + _pickup(item, k) { + if (item.type === 'coin') { + this.meta.gold += item.gold; // 金币实时入元进度(死亡不清档) + this.run.gold += item.gold; + this._floor.items.delete(k); + this.run.looted.push(k); + this.audio.play('sfx/coin'); + vibrate('pickup'); + const band = item.gold < 16 ? 1 : item.gold < 24 ? 2 : 3; + this._speak(['voice/evt_coin_' + band]); + this._progress(); + this._save(); + } else if (item.type === 'potion') { + if (this.run.potion >= 1) { + this._speak(['voice/evt_potion_full']); // 腰带满:药留在原地 + return; + } + this.run.potion = 1; + this._floor.items.delete(k); + this.run.looted.push(k); + this.audio.play('sfx/potion'); + vibrate('pickup'); + this._speak(['voice/evt_potion']); + this._progress(); + this._save(); + } else if (item.type === 'weapon1' || item.type === 'weapon2') { + const lvl = item.type === 'weapon1' ? 1 : 2; + if (this.run.weapon >= lvl) return; // 已有更优:留在原地 + this.run.weapon = lvl; + this._floor.items.delete(k); + this.run.looted.push(k); + this.audio.play('sfx/gear'); + vibrate('pickup'); + this._speak(['voice/evt_weapon']); + this._progress(); + this._save(); + } else if (item.type === 'armor1') { + if (this.run.armor >= 1) return; + this.run.armor = 1; + this._floor.items.delete(k); + this.run.looted.push(k); + this.audio.play('sfx/gear'); + vibrate('pickup'); + this._speak(['voice/evt_armor']); + this._progress(); + this._save(); + } + } + + // ---------------- 状态播报(§2.1/§2.2/§3.2) ---------------- + + _threatKey() { + let left = 0, right = 0; + const fi = DIR_ORDER.indexOf(this.run.face); + for (const m of this._floor.mobs) { + const dx = m.x - this.run.pos.x, dy = m.y - this.run.pos.y; + if (Math.abs(dx) + Math.abs(dy) > 5) continue; + const dirIdx = dx > 0 ? 1 : dx < 0 ? 3 : dy > 0 ? 2 : 0; + const rel = (dirIdx - fi + 4) % 4; + if (rel === REL.LEFT) left++; + else if (rel === REL.RIGHT) right++; + // 前方/身后不报(同回声降维,§3.2) + } + return left && right ? 'both' : left ? 'left' : right ? 'right' : 'none'; + } + + _speakStatus() { // 上滑:层 → 血量 → 金币 → 面朝 → 威胁(5 段链) + const band = this._hpBand(this.run.hp, this._maxHp()); + if (band === 4) vibrate('warning'); + this._speak(['voice/st_floor_' + this.run.floor, + 'voice/st_hp_' + band, + 'voice/st_gold_' + this._goldBand(this.meta.gold), + 'voice/st_face_' + DIR_KEY[this.run.face], + 'voice/st_threat_' + this._threatKey()]); + } + + // ================================ 怪物游走与遭遇(§7.1) ================================ + + _startMobs() { + for (const m of this._floor.mobs) { + if (MOBS[m.type].boss) continue; // 窟主守楼梯格,不游走 + this._mobLoop(m); + } + } + + _mobLoop(m) { + const s = this._session; + this._after(1500 + Math.floor(Math.random() * 1000), () => { + if (s !== this._session) return; + if (this.state === 'EXPLORE' && !this.menuOpen) this._mobStep(m); + this._mobLoop(m); + }); + } + + _mobStep(m) { + const S = this._floor.size; + const opts = []; + for (const d of Object.values(DIR_VEC)) { + const nx = m.x + d.dx, ny = m.y + d.dy; + if (nx >= 0 && ny >= 0 && nx < S && ny < S && this._floor.grid[ny][nx] === 1) opts.push({ x: nx, y: ny }); + } + if (!opts.length) return; + const into = opts.find((c) => c.x === this.run.pos.x && c.y === this.run.pos.y); + if (into) { this._encounter(m); return; } // 怪走进你格 + const c = opts[Math.floor(Math.random() * opts.length)]; + m.x = c.x; m.y = c.y; + this._mobStepSound(m.type, m.x, m.y); + // R2 D1-3:相邻即开战(互视/贴身必扑)——杜绝 R1 live L3 蘑菇「贴身绕圈、 + // 堵格不开战」的软封锁;脚步+心跳预警在前,贴身必然接战(报名=它在面前)。 + if (Math.abs(m.x - this.run.pos.x) + Math.abs(m.y - this.run.pos.y) === 1) this._encounter(m); + } + + /** 脚步报位(§7.1):pan 按相对方位、音量按距离档;背后=中央资产音量减半。 */ + _mobStepSound(type, x, y) { + const dx = x - this.run.pos.x, dy = y - this.run.pos.y; + const dist = Math.abs(dx) + Math.abs(dy); + if (dist > 8) return; // 更远不播 + const fi = DIR_ORDER.indexOf(this.run.face); + const dirIdx = dx > 0 ? 1 : dx < 0 ? 3 : dy > 0 ? 2 : 0; + const rel = (dirIdx - fi + 4) % 4; + const pan = rel === REL.LEFT ? 'l' : rel === REL.RIGHT ? 'r' : 'c'; + let vol = dist <= 2 ? 0.9 : dist <= 4 ? 0.6 : 0.35; + if (rel === REL.BEHIND) vol *= 0.5; + this.audio.play('sfx/mob_step_' + MOBS[type].cls + '_' + pan, { volume: vol }); + if (dist <= 2) vibrate('heartbeat'); // 威胁前置预警窗(方法论 L2) + } + + /** 遭遇(明雷 §7.1):播报名即=它在面前(自动面对),BATTLE 三档。 */ + _encounter(mob) { + this._stopWalk(); + const ms = MOBS[mob.type]; + this._floor.mobs = this._floor.mobs.filter((m) => m !== mob); + this._session++; + this._clearTimers(); + this._walk = null; + this.state = 'BATTLE'; + this._battle = { + type: mob.type, spec: ms, hp: mob.hp, maxHp: ms.hp, atk: ms.atk, def: ms.def, + cls: ms.cls, boss: !!ms.boss, mover: !!ms.mover, cell: { x: mob.x, y: mob.y }, + bearing: DIR_ORDER.indexOf(this.run.face), moveN: 0, phase2: false, + state: 'intro', windowEnd: 0, parry: false, noTurnSaid: false, + drill: !!(this._drill && this._drill.step === 8), + drillMob: !!mob.drill, + }; + this.audio.play(ms.boss ? 'bgm/boss' : 'bgm/battle', { fadeMs: 400 }); + this.guide.state('battle'); + this._speakGuarded(['voice/' + ms.start], () => { + if (this._battle?.drill) { + // 教学战(§5 D8):先由教学怪出手,练格挡 + this._speakGuarded(['voice/tut_parry'], () => this._enemyTurn(), 8000); + } else { + this._playerTurn(); + } + }, 10000); + } + + // ================================ 战斗(§7) ================================ + + _battleRel() { + return (this._battle.bearing - DIR_ORDER.indexOf(this.run.face) + 4) % 4; + } + + _playerTurn() { + const b = this._battle; + if (!b) return; + b.state = 'player'; + b.parry = false; + this.guide.turn({ haptic: true }); // turn earcon + confirm 震(§10.2) + this.guide.watchdog({ + arm: true, at: [15000, 30000], say: ['gd_turn', null], + cond: () => this.state === 'BATTLE' && this._battle === b && b.state === 'player' && !this.menuOpen, + onEscalate: (n) => { // R2 D1-6:15s 无操作播合成回执音(standard 档没有 gd_turn 语音也有"还到你"的确认) + if (n === 1 && this.state === 'BATTLE' && !this.menuOpen) this.audio.play('sfx/turn_tick', { volume: 0.7 }); + }, + }); + } + + _onBattleGesture(g) { + const b = this._battle; + if (!b) return; + if (b.state === 'player') { + if (g === 'tap') this._swing(); + else if (g === 'doubletap') vibrate('error'); // 窗口外双击:error 震不生效(§7.2) + else if (g === 'left') this._battleTurnG(-1); + else if (g === 'right') this._battleTurnG(1); + else if (g === 'longpress') this._flee(); + else if (g === 'up') this._speakBattleStatus(); + else if (g === 'down') this._openMenu(); + } else if (b.state === 'telegraph') { + if (g === 'doubletap' && performance.now() <= b.windowEnd) { + b.parry = true; // 面对与否在结算时按实际方位判(§7.2); + // parries 只在结算判成"面对且格挡成功"时计数(归因真话律:背对双击没挡下) + this.audio.play('sfx/parry'); + vibrate('confirm'); + } else if (g === 'down') { + this._openMenu(); // E4:战斗任意时刻可暂停——前摇内开菜单 → 冻结剩余窗口 + } else { + vibrate('error'); // 窗口内做错手势 / 窗口已过:留在窗口不生效 + } + } + // 其他状态(intro/enemyMove/busy):手势忽略 + } + + _swing() { + const b = this._battle; + b.state = 'busy'; + this.audio.play('sfx/atk_swing'); + if (this._battleRel() !== REL.FRONT) { + // 挥空(归因真话,§2.2):它不在你面前 + this._speakGuarded(['voice/btl_swish'], () => this._enemyTurn(), 6000); + return; + } + this.audio.play('sfx/atk_hit'); + const dmg = Math.max(0, this._playerAtk() - b.def); + b.hp -= dmg; + if (b.hp <= 0) { this._victory(); return; } + this._halfCheck(() => this._enemyTurn()); + } + + _halfCheck(next) { + const b = this._battle; + if (!b.phase2 && b.spec.half && b.hp <= b.maxHp / 2) { + b.phase2 = true; + b.atk += 1; // 半血 ATK+1(§7.2;王 5→6) + this.guide.state('danger'); + vibrate('warning'); + this._speakGuarded(['voice/' + b.spec.half], next, 8000); + return; + } + next(); + } + + /** 转身(战斗):只对绕位怪/Boss 有意义;缠斗怪纠错(每场 1 次,§7.2)。 */ + _battleTurnG(d) { + const b = this._battle; + if (!b.mover) { + vibrate('error'); + if (!b.noTurnSaid) { + b.noTurnSaid = true; + this._speakGuarded(['voice/btl_no_turn'], () => {}, 5000); + } + return; + } + const i = DIR_ORDER.indexOf(this.run.face); + this.run.face = DIR_ORDER[(i + d + 4) % 4]; + this.audio.play('sfx/step_turn'); + } + + _speakBattleStatus() { // §2.2:我方血量档 → 敌方阶段 →(绕位/Boss)方位 + const b = this._battle; + const band = this._hpBand(this.run.hp, this._maxHp()); + if (band === 4) vibrate('warning'); + const ids = ['voice/st_hp_' + band]; + if (b.spec.half && b.hp <= b.maxHp / 2) ids.push('voice/' + b.spec.half); + if (b.mover) { + const rel = this._battleRel(); + if (rel === REL.BEHIND) ids.push('voice/btl_behind'); + else if (rel === REL.LEFT) ids.push('voice/st_threat_left'); + else if (rel === REL.RIGHT) ids.push('voice/st_threat_right'); + // 正面:脚步/前摇音已报位,不占台词预算 + } + this._speak(ids); + } + + /** 长按=尝试逃跑(§7.3):普通/绕位必成挨一下;Boss 拒绝。 */ + _flee() { + const b = this._battle; + if (b.boss) { + vibrate('error'); + this._speakGuarded(['voice/btl_flee_boss'], () => this._playerTurn(), 5000); + return; + } + b.state = 'busy'; + this.run.flees++; + let dmg = Math.max(0, b.atk - this._armor()); // 撤退挨一下:真实账目,无格挡 + if (dmg > 0) { + if (this._applyVeil()) dmg = Math.floor(dmg / 2); + this.run.hp -= dmg; + this.audio.play('sfx/hurt'); + vibrate('hit'); + } + this._lastHit = { type: b.type, boss: b.boss, behind: false, parried: false, unparried: true, flee: true }; + if (this.run.hp <= 0) { this._die(); return; } + const afterFlee = () => { + this._battle = null; + const restored = { type: b.type, x: b.cell.x, y: b.cell.y, hp: b.hp, maxHp: b.maxHp, drill: b.drillMob }; + this._floor.mobs.push(restored); // 怪留原格 + if (this._drill?.bat) this._drill.bat = restored; + this._progress(); + this._save(); + if (this._drill) { + this.state = 'DRILL'; + this.audio.play('bgm/explore', { fadeMs: 400 }); // R2 D1-4:教学战逃跑同样回探索曲 + if (this._drill.step === 8) this._drillApproach(); + return; + } + this._resumeExplore(); + }; + // 归因真话律:ATK0 怪(L1 蝠)根本没碰到你,「被抓了一下」是假话 → 静默脱战 + if (dmg > 0) this._speakGuarded(['voice/btl_flee'], afterFlee, 8000); + else afterFlee(); + } + + /** 敌方回合:绕位怪每 2 回合移动(脚步报新方位)→ 攻击前摇 1.2s → 结算。 */ + _enemyTurn() { + const b = this._battle; + if (!b) return; + b.state = 'enemyMove'; + b.parry = false; + b.moveN++; + if (b.mover && b.moveN % 2 === 0) { + const seq = [REL.RIGHT, REL.BEHIND, REL.LEFT]; // 每 2 回合换位(确定性轮换) + const rel = seq[(b.moveN / 2 - 1) % seq.length]; + b.bearing = (DIR_ORDER.indexOf(this.run.face) + rel) % 4; + // 脚步报新方位(§7.2 绕位怪先移动):pan 按相对方位,背后=中央资产音量减半 + const pan = rel === REL.LEFT ? 'l' : rel === REL.RIGHT ? 'r' : 'c'; + const vol = rel === REL.BEHIND ? 0.45 : 0.9; + this.audio.play('sfx/mob_step_' + b.cls + '_' + pan, { volume: vol }); + } + this._telegraph(); + } + + _telegraph() { + const b = this._battle; + b.state = 'telegraph'; + b.windowEnd = performance.now() + TEL_WINDOW; + const rel = this._battleRel(); + const pan = rel === REL.LEFT ? '_l' : rel === REL.RIGHT ? '_r' : ''; + this.audio.play('sfx/tel_' + b.cls + pan); + vibrate('beat'); // "格挡窗口已开"通道标识 + const s = this._session; + this._after(TEL_WINDOW, () => { + if (s === this._session && this._battle === b && b.state === 'telegraph') this._enemyResolve(); + }); + } + + _applyVeil() { // 护符:每层首次受击减半(§7.4) + if (this._has('veil') && !this.run.veilUsedFloor) { + this.run.veilUsedFloor = true; + return true; + } + return false; + } + + _enemyResolve() { + const b = this._battle; + b.state = 'resolve'; + const rel = this._battleRel(); + const facing = rel === REL.FRONT; + const behind = rel === REL.BEHIND; + let dmg = Math.max(0, b.atk - this._armor()); + const parried = b.parry && facing; // 面对攻击源 ×0.2,未面对 ×1.0(§2.2) + if (parried) this.run.parries++; // 全程格挡数(§11):只计真正挡下的(面对+窗口内) + if (parried) dmg = Math.floor(dmg * 0.2); + if (dmg > 0 && this._applyVeil()) dmg = Math.floor(dmg / 2); + this.run.hp -= dmg; + this._lastHit = { type: b.type, boss: b.boss, behind, parried, unparried: !parried, flee: false }; + if (!parried) { + if (dmg > 0) { + this.audio.play('sfx/hurt'); + vibrate('hit'); + } + } + if (this.run.hp <= 0) { this._die(); return; } + if (b.drill) { // 教学战(§5 D8):格挡成/败都继续 → 攻击教学 + const ids = [parried ? 'voice/tut_parry_ok' : 'voice/tut_parry_miss', 'voice/tut_attack']; + this._speakGuarded(ids, () => this._playerTurn(), 12000); + return; + } + this._dangerCheck(); + const drank = this._autoPotion(); + const ids = []; + if (parried) ids.push('voice/btl_parried'); // 格挡成功(减伤 80%,§9.3 B 组 trigger) + if (behind) ids.push('voice/btl_behind'); // 背后挨刀归因真话(§7.2) + if (drank) ids.push('voice/st_hp_' + this._hpBand(this.run.hp, this._maxHp())); + if (ids.length) this._speakGuarded(ids, () => this._playerTurn(), 8000); + else this._playerTurn(); + } + + _dangerCheck() { // danger 签名:每层首次 ≤25% + Boss 半血(§10.1) + if (!this._dangerFloorDone && this._hpBand(this.run.hp, this._maxHp()) === 4) { + this._dangerFloorDone = true; + this.guide.state('danger'); + vibrate('warning'); + } + } + + _autoPotion() { // HP≤25% 且腰带有力 → 自动喝(§7.2) + if (this.run.hp > 0 && this._hpBand(this.run.hp, this._maxHp()) === 4 && this.run.potion > 0) { + this.run.potion = 0; + this.run.hp = Math.min(this._maxHp(), this.run.hp + Math.ceil(this._maxHp() * 0.4)); + this.audio.play('sfx/potion'); + vibrate('pickup'); + return true; + } + return false; + } + + _victory() { + const b = this._battle; + this.audio.play('sfx/enemy_die'); + vibrate('success'); + this.run.killsRun++; + this.meta.kills++; + const atkBefore = this.run.atkLv; + if (this.run.killsRun % 4 === 0 && this.run.atkLv < 4) this.run.atkLv++; + const justLeveled = this.run.atkLv > atkBefore; + this.meta.gold += b.spec.gold; // 金币实时入元进度 + this.run.gold += b.spec.gold; + if (b.spec.gold > 0) this.audio.play('sfx/coin'); + if (b.boss) this.run.cleared.push(this.run.floor); + this._progress(); + this._save(); + if (b.type === 'king') { this._settleWin(); return; } // 终 Boss = 通关 + const lootBand = b.spec.gold < 6 ? 1 : b.spec.gold < 20 ? 2 : 3; + const ids = b.drill ? ['voice/tut_kill', 'voice/btl_loot_' + lootBand] : ['voice/btl_kill', 'voice/btl_loot_' + lootBand]; + if (justLeveled) ids.push('voice/btl_lvup'); + this._speakGuarded(ids, () => { + this._battle = null; + if (this._drill && this._drill.step === 8) { + // R2 D1-4:教学战胜利回教学也必须切回探索曲(R1 转写 t=35.8 起战斗鼓点 + // 泡全程的根因之一——bgm/explore 此前只在菜单关闭路径恢复) + this.state = 'DRILL'; + this.audio.play('bgm/explore', { fadeMs: 400 }); + this._drillAdvance(9); + return; + } + if (this._drill) { // D9 途中遇怪等:战后回教学态继续(否则状态卡在探索、drillDone 丢失) + this.state = 'DRILL'; + this.audio.play('bgm/explore', { fadeMs: 400 }); + this._drillWatchdog(this._drill.step); + return; + } + this._resumeExplore(); + }, 12000); + } + + // ================================ 死亡 / 通关结算(§11/§12.7) ================================ + + _buyUnlocks() { + const bought = []; + for (const l of LEGACIES) { + if (this.meta.unlocks.includes(l.id)) continue; + if (this.meta.gold >= l.price) { + this.meta.gold -= l.price; + this.meta.unlocks.push(l.id); + bought.push(l.id); + } else break; // 按价格升序,买不起即止 + } + return bought; + } + + _die() { + const info = this._lastHit || { type: '?', boss: false, behind: false, unparried: true, parried: false }; + // 归因真话律(§11/§12 失败即不通过项):句必须与真实死因对应 + const cause = info.behind ? 3 : (info.boss && info.unparried ? 2 : 1); + const adv = info.behind ? 3 : (this.run.parries === 0 ? 1 : (this.run.flees === 0 ? 2 : 1)); + this._deathInfo = { cause, adv, lastHit: { ...info }, parries: this.run.parries, flees: this.run.flees }; + this._battle = null; + this._stopWalk(); + this.state = 'SETTLE'; + this._session++; + this._clearTimers(); + this.audio.setRateBoostEnabled(true); // E3:结算恢复 + this.audio.play('bgm/menu', { volume: 0.5 }); + this.guide.state('settle'); + vibrate('hit'); + this.meta.bestFloor = Math.max(this.meta.bestFloor, this.run.floor); + this.meta.runs++; + const newUnlocks = this._buyUnlocks(); + this.meta.pending.push(...newUnlocks); // 下一局开场播报(§7.4) + const floorN = this.run.floor; + // R2 D1-5:金币档与 TITLE 同一分档函数、同一本账(meta.gold,遗产购置后)。 + // R1 live:SETTLE 用 run 金币自设阈值报 meta_gold_3,TITLE 用 _goldBand(meta.gold) + // 报 meta_gold_2——同一句 id 两套口径,「这趟」的账对不上。 + const goldBand = this._goldBand(this.meta.gold); + this.run = null; // 死亡清 run 不清 meta + this._save(); + this._settleChain = ['voice/die_intro', 'voice/die_cause_' + cause, 'voice/die_adv_' + adv, + 'voice/st_floor_' + floorN, 'voice/meta_gold_' + goldBand, 'voice/settle_stronger', 'voice/settle_again']; + this._speakGuarded(this._settleChain, () => {}, 30000); + } + + _settleWin() { + this._battle = null; + this._stopWalk(); + this.state = 'SETTLE'; + this._session++; + this._clearTimers(); + this.audio.setRateBoostEnabled(true); + this.audio.play('bgm/menu', { volume: 0.5 }); + this.guide.state('settle'); + vibrate('success'); + this.meta.bestFloor = Math.max(this.meta.bestFloor, 7); + this.meta.runs++; + const bought = this._buyUnlocks(); + const goldBand = this._goldBand(this.meta.gold); // R2 D1-5:与 TITLE 同函数同账本 + const ids = ['voice/settle_win_1', 'voice/settle_win_2', 'voice/meta_gold_' + goldBand]; + if (bought.length) for (const u of bought) ids.push('voice/unlock_' + u); + ids.push('voice/settle_again'); + this.run = null; + this._save(); + this._settleChain = ids; // SETTLE 双击=重听结算链(§2.3) + this._speakGuarded(ids, () => {}, 30000); + } + + _onSettleGesture(g) { + if (g === 'tap') { + this._enterTitle(); + } else if (g === 'doubletap') { + this._speak(this._settleChain && this._settleChain.length + ? this._settleChain : ['voice/settle_again']); // 重听结算链(重听入口) + } else if (g === 'up') { + this._speak(this._titleUpChain()); // 重听元进度段 + } else if (g === 'down') { + this._openMenu(); + } else { + this.guide.error({ voice: 'gd_invalid', onceKey: 'dungeon_invalid' }); + } + } + + // ================================ 下潜(§4.2 DESCEND) ================================ + + _descend(fromDrill) { + this.state = 'DESCEND'; + this._session++; + this._clearTimers(); + this._stopWalk(); + this.audio.play('sfx/stairs_down'); + this._speakGuarded(['voice/evt_descend'], () => { + const next = this.run.floor + 1; + this._enterFloor(next); + if (fromDrill) { + this.meta.drillDone = true; + this._drill = null; + } + this._save(); + const ids = fromDrill ? ['voice/tut_done', 'voice/st_floor_' + next] : ['voice/st_floor_' + next]; + this._speakGuarded(ids, () => this._resumeExplore(), 8000); + }, 8000); + } + + // ================================ 演练教学 DRILL(§5) ================================ + + _enterDrill() { + this.state = 'DRILL'; + this._session++; + this._clearTimers(); + this.audio.play('bgm/explore'); + this.audio.setRateBoostEnabled(false); // E3 豁免(教学=对局) + this.guide.state('explore'); + this._drill = { + step: -1, fails: 0, upDone: false, menuDone: false, turnedL: false, turnedR: false, + bat: null, stairsSpoken: false, + }; + this._drillEnter(0); + } + + _drillWatchdog(step) { + this.guide.watchdog({ + arm: true, at: [8000], say: [null], // 教学句不重播:纯 nudge(§10.2) + cond: () => this.state === 'DRILL' && this._drill?.step === step, + }); + } + + _drillEnter(step) { + const d = this._drill; + d.step = step; + d.fails = 0; + if (step === 0) { + this._speakGuarded(['voice/intro_1', 'voice/intro_2', 'voice/tut_d0'], () => this._drillWatchdog(0), 20000); + } else if (step === 1) { + // D1 首次脉冲等待:旁白表为「—」,仍挂教学看门狗(§10.2 每步等待 [8s] 纯 nudge) + this._drillWatchdog(1); + } else if (step === 2) { + this._speakGuarded(['voice/tut_front_rule'], () => this._drillWatchdog(2), 8000); + } else if (step === 3) { + this._speakGuarded(['voice/tut_side_rule'], () => this._drillWatchdog(3), 8000); + } else if (step === 4) { + // 自动面向东侧走道(走道 2 格 + 撞边界),保证"按住走 ≥2 格"可达成 + while (this.run.face !== 'E') this._turn(1); + this._speakGuarded(['voice/tut_walk'], () => this._drillWatchdog(4), 8000); + } else if (step === 5) { + this._speakGuarded(['voice/tut_turn'], () => this._drillWatchdog(5), 8000); + } else if (step === 6) { + this._speakGuarded(['voice/tut_replay'], () => this._drillWatchdog(6), 8000); + } else if (step === 7) { + this._speakGuarded(['voice/tut_status', 'voice/tut_menu'], () => this._drillWatchdog(7), 12000); + } else if (step === 8) { + this._drillEnter8(); + } else if (step === 9) { + this._speakGuarded(['voice/tut_stairs'], () => this._drillWatchdog9(), 8000); + } + } + + /** D9 看门狗(R2 D1-2):8s nudge、30s 升级播楼梯暗示(铁器回声提示)。 + * R1 live:D9 是唯一要求自行寻路的步骤,静置 72s 零事件零兜底——它是 + * 教学链里唯一没有救援出口的一步。 */ + _drillWatchdog9() { + this.guide.watchdog({ + arm: true, at: [8000, 30000], say: [null, 'stair_hint_a'], + cond: () => this.state === 'DRILL' && this._drill?.step === 9, + }); + } + + _drillEnter8() { + const d = this._drill; + const bat = this._floor.mobs.find((m) => m.drill) || this._floor.mobs.find((m) => m.type === 'bat'); + if (!bat) { this._drillAdvance(9); return; } // 边界:门怪已亡(乱走触发过)→ 自动过 + d.bat = bat; + while (this.run.face !== this._dominantDirTo(bat.x, bat.y)) this._turn(1); // 遭遇自动面对语义 + this._speakGuarded(['voice/tut_mob'], () => { + this._drillWatchdog(8); + this._drillApproach(); // 兜底:教学怪主动扑来,绝不等死 + }, 8000); + } + + _dominantDirTo(tx, ty) { + const dx = tx - this.run.pos.x, dy = ty - this.run.pos.y; + if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? 'E' : 'W'; + return dy >= 0 ? 'S' : 'N'; + } + + _stepTowards(m, target) { + const S = this._floor.size; + const cand = []; + for (const dd of Object.values(DIR_VEC)) { + const nx = m.x + dd.dx, ny = m.y + dd.dy; + if (nx < 0 || ny < 0 || nx >= S || ny >= S || this._floor.grid[ny][nx] !== 1) continue; + cand.push({ x: nx, y: ny, d: Math.abs(nx - target.x) + Math.abs(ny - target.y) }); + } + cand.sort((a, b) => a.d - b.d); + return cand[0] || null; + } + + _drillApproach() { // 教学怪每 1.2s 向玩家逼近一步,走进玩家格即触发战斗 + const s = this._session; + this._after(1200, () => { + if (s !== this._session) return; + const d = this._drill; + if (!d || d.step !== 8 || this.state !== 'DRILL' || this.menuOpen) return; + const bat = d.bat; + if (!bat || !this._floor.mobs.includes(bat)) return; + const step = this._stepTowards(bat, this.run.pos); + if (step) { + if (step.x === this.run.pos.x && step.y === this.run.pos.y) { this._encounter(bat); return; } + bat.x = step.x; bat.y = step.y; + this._mobStepSound('bat', bat.x, bat.y); + } + this._drillApproach(); + }); + } + + _drillWrong() { + const d = this._drill; + if (!d) return; + d.fails++; + vibrate('error'); + if (d.fails >= 2) this._drillPass(); // 做错 ≤2 次后自动演示通过,不卡流程(§5) + } + + _drillPass() { + const d = this._drill; + if (!d) return; + d.fails = 0; + this._drillAdvance(d.step + 1); + } + + _drillAdvance(step) { + if (step > 9) { this._drillDone(); return; } + this._drillEnter(step); + } + + _drillDone() { // 兜底:D9 未走楼梯而步数耗尽(极少见)——按跳过处理 + this.meta.drillDone = true; + this._drill = null; + this.guide.disarm(); // R2 D0-1:教学收尾注销全部教学再调度器(看门狗等) + this._speakGuarded(['voice/tut_skip'], () => this._resumeExplore(), 8000); + } + + _drillSkip() { // 下滑跳过教学(§5):标志存进度,二周目不再进 DRILL + this.meta.drillDone = true; + this._drill = null; + this._stopWalk(); + this._save(); + this.guide.disarm(); // R2 D0-1:同上,收尾即注销教学态挂件 + this._speakGuarded(['voice/tut_skip'], () => this._resumeExplore(), 8000); + } + + _onDrillGesture(g) { + const d = this._drill; + if (!d) { this._onExploreGesture(g); return; } + // D7 的下滑是"开菜单"教学动作本身;其余步骤下滑=跳过教学(§5) + if (g === 'down' && d.step !== 7) { this._drillSkip(); return; } + if (g === 'longpressend') return; + switch (d.step) { + case 0: + case 2: + case 3: + if (g === 'tap') this._drillPass(); + else this._drillWrong(); + break; + case 1: // 首次脉冲(FTUE 首输入点,≈t+15s) + if (g === 'tap') { + this._pulse(); + // 反馈句与下一步指令句串行(R2 D0-1:同 tick 双 play 会在引擎 + // _load 竞态下互相顶号——反馈句并入推进链,播完或 8s 兜底再进下一步) + this._speakGuarded(['voice/tut_echo_come'], () => this._drillPass(), 8000); + } else this._drillWrong(); + break; + case 4: // 长按走(_walkStep 钩子里判定通过) + if (g !== 'longpress') this._drillWrong(); + else this._startWalk(); + break; + case 5: // 左滑+右滑各 1 + if (g === 'left') { d.turnedL = true; this._turn(-1); } + else if (g === 'right') { d.turnedR = true; this._turn(1); } + else { this._drillWrong(); break; } + if (d.turnedL && d.turnedR) this._drillPass(); + break; + case 6: // 双击重发(免费) + if (g === 'doubletap') { + this._pulse(); + // 同 D1:反馈句并入推进链,绝不与下一步旁白同 tick 双 play(D0-1) + this._speakGuarded(['voice/tut_echo_come'], () => this._drillPass(), 8000); + } else this._drillWrong(); + break; + case 7: // 上滑状态 + 下滑开菜单再关(下滑已被跳过占用,关菜单用菜单内下滑) + if (g === 'up') { d.upDone = true; this._speakStatus(); } + else if (g === 'down') { this._openMenu(); break; } + else { this._drillWrong(); break; } + if (d.upDone && d.menuDone) this._drillPass(); + break; + case 8: // 走向教学怪触发战斗(战斗内流程见 _encounter/_victory) + if (g === 'longpress') this._startWalk(); + else if (g === 'tap') this._pulse(); + else if (g === 'up') this._speakStatus(); // 状态键 D7 已教:教学后可用(§4.3) + else this._drillWrong(); + break; + case 9: // 脉冲听铁器回声 → 自行走到楼梯 → 踏上后单击下潜 + if (g === 'tap') { + if (this._onStairs && this._stairsSpokenFloor === this.run.floor) this._descend(true); + else this._pulse(); + } else if (g === 'doubletap') this._pulse(); + else if (g === 'left') this._turn(-1); + else if (g === 'right') this._turn(1); + else if (g === 'up') this._speakStatus(); + else if (g === 'longpress') this._startWalk(); + // R2 D1-2:D9 每次操作后重挂看门狗(onGesture 的 disarm 会清掉它, + // 而 D9 的脉冲/转身/走位不推进步骤——不重挂等于看门狗只在静置时活 8s) + if (this.state === 'DRILL' && this._drill?.step === 9) this._drillWatchdog9(); + break; + default: + break; + } + } + + // ================================ 模态菜单(§2.4/§5.2 全球约定) ================================ + + _openMenu() { + if (this.menuOpen) return; + this.menuFrom = this.state; + this.menuOpen = true; + this._menuItems = (this.state === 'TITLE' || this.state === 'SETTLE') ? TITLE_MENU : MENU_ITEMS; + this._menuIdx = 0; + this._session++; + this._clearTimers(); + this._stopWalk(); + this._menuPause = null; + if (this.menuFrom === 'BATTLE' && this._battle?.state === 'telegraph') { + this._menuPause = { remain: Math.max(0, this._battle.windowEnd - performance.now()) }; + } + this.audio.setRateBoostEnabled(true); // E3:菜单恢复快进 + this.audio.play('bgm/menu'); + this.guide.state('menu'); + this._speak(['voice/sys_menu', 'voice/' + this._menuItems[0]]); + } + + _closeMenu() { + this.menuOpen = false; + const from = this.menuFrom; + this.menuFrom = null; + // R2 D1-1:关闭回执。R1 live 证据——打开有 menu 签名+sys_menu,关闭零反馈, + // 盲手玩家只能靠下滑在开/关之间盲赌(「3 次下滑关不掉」实为关了又被下次 + // 下滑重开)。关闭补状态签名 earcon:回到哪个状态就报哪个签名。 + this.guide.state(from === 'BATTLE' ? 'battle' + : from === 'SETTLE' ? 'settle' + : (from === 'TITLE' ? 'menu' : 'explore')); + if (from === 'BATTLE' && this._battle) { + this.state = 'BATTLE'; + this.audio.play(this._battle.boss ? 'bgm/boss' : 'bgm/battle', { fadeMs: 400 }); + this.audio.setRateBoostEnabled(false); + if (this._menuPause != null) { // 前摇暂停续播(剩余窗口) + const b = this._battle; + const remain = this._menuPause.remain; + this._menuPause = null; + b.windowEnd = performance.now() + remain; + const s = this._session; + this._after(remain, () => { + if (s === this._session && this._battle === b && b.state === 'telegraph') this._enemyResolve(); + }); + } else { + this._playerTurn(); + } + return; + } + if (from === 'DRILL' && this._drill) { + this.state = 'DRILL'; + this.audio.play('bgm/explore'); + this.audio.setRateBoostEnabled(false); + const d = this._drill; + if (d.step === 7) { + d.menuDone = true; + if (d.upDone) this._drillPass(); + } + if (d.step === 8) this._drillApproach(); // 菜单期间逼近暂停过 → 恢复重挂 + return; + } + this.state = from || 'EXPLORE'; + if (from === 'EXPLORE') { + this.audio.play('bgm/explore'); + this.audio.setRateBoostEnabled(false); + this._startMobs(); + this._stuck.lastAct = performance.now(); + this._lastAmbAt = performance.now(); + this._stuckArm(); + } else { + this.audio.play('bgm/menu'); // TITLE / SETTLE + } + } + + _onMenuGesture(g) { + const items = this._menuItems; + if (g === 'tap' || g === 'right') { + this._menuIdx = (this._menuIdx + 1) % items.length; // 单击循环切项(每切播项名) + this.audio.play('voice/' + items[this._menuIdx]); + } else if (g === 'left') { + this._menuIdx = (this._menuIdx + items.length - 1) % items.length; + this.audio.play('voice/' + items[this._menuIdx]); + } else if (g === 'doubletap') { + this._menuExec(items[this._menuIdx]); + } else if (g === 'up') { + this._speak(['voice/help_explore', 'voice/help_battle', 'voice/settings_hint']); + } else if (g === 'down') { + this._closeMenu(); // 下滑=关闭 + } + } + + _menuExec(item) { + if (item === 'sys_menu_resume') { + this._closeMenu(); + } else if (item === 'sys_menu_save') { + this._save(); + this._speakGuarded(['voice/sys_saved'], () => this._goHome(), 4000); + } else if (item === 'sys_menu_help') { + this._speak(['voice/help_explore', 'voice/help_battle', 'voice/settings_hint']); + } else if (item === 'sys_menu_restart') { + const seed = this.run ? this.run.seed : null; + this.menuOpen = false; + this.menuFrom = null; + if (seed != null) this._restartRun(seed); + else this._closeMenu(); + } + } + + // ================================ 卡关三级提示(§10.3) ================================ + + _progress() { // 进展事件:拾取/战斗胜利/踏上楼梯格/击败窟主/下潜 → 重置分级 + this._stuck.shown.clear(); + this._stuck.nav = false; + this._navLastDir = null; + this._stuck.lastAct = performance.now(); + } + + _stuckArm() { + if (this._stuckArmed) return; + this._stuckArmed = true; + const s = this._session; + const loop = () => { + if (s !== this._session) { this._stuckArmed = false; return; } + if (this.state === 'EXPLORE' && !this.menuOpen) { + this._stuckCheck(); + this._ambBreath(); + } + this._after(5000, loop); + }; + this._after(5000, loop); + } + + /** 探索呼吸层(R2 D0-3②):静默超 20s(无手势、无人声)播一段洞穴环境垫, + * 消灭 R1 转写「教学后 350s 零人声零事件」的真空体感。游戏级定时器, + * guide=off 契约不受影响(off 只静向导层;环境垫是内容层)。 */ + _ambBreath() { + const now = performance.now(); + if (now - this._lastAmbAt < AMB_AFTER_MS) return; + if (now - this._stuck.lastAct < AMB_AFTER_MS) return; // 玩家仍在操作 = 不算静默 + if (this.audio.isVoicePlaying()) return; // 有人声就不垫 + this._lastAmbAt = now; + this.audio.play('sfx/amb_cave', { volume: AMB_VOLUME }); + } + + _stuckCheck() { + this._stuckFlush(); + const now = performance.now(); + const idle = now - this._stuck.lastAct; + if (this._stuck.nav) { + if (now - this._stuck.lastFire >= NAV_REPEAT_MS) this._stuckFire(3); + return; + } + if (this._stuck.pending.length) return; // 待播中:等声道空出,不重复入队 + if (idle >= STUCK_LEVELS[2] && !this._stuck.shown.has('L3')) this._stuckFire(3); + else if (idle >= STUCK_LEVELS[1] && !this._stuck.shown.has('L2')) this._stuckFire(2); + else if (idle >= STUCK_LEVELS[0] && !this._stuck.shown.has('L1')) this._stuckFire(1); + } + + _stuckStairDir() { // 楼梯(终局层=王座)绝对方位(与上滑面朝句同坐标系,自洽) + const st = this._floor.stairs || this._floor.throne; + if (!st) return 'e'; + const dx = st.x - this.run.pos.x, dy = st.y - this.run.pos.y; + if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? 'e' : 'w'; + return dy >= 0 ? 's' : 'n'; + } + + _stuckFire(level) { + this._stuck.lastFire = performance.now(); + vibrate('warning'); + let ids = []; + if (level === 1) { + const spec = FLOOR_SPECS[this.run.floor]; + const bossAlive = (spec.boss && !this.run.cleared.includes(this.run.floor)) || !!spec.final; + // 终局层无楼梯(目标是王座上的窟中之王)→ 用「大东西守着」句,不说铁器 + ids = [bossAlive ? 'voice/stair_hint_b' : 'voice/stair_hint_a']; + } else if (level === 2) { + ids = ['voice/stair_dir_' + this._stuckStairDir()]; + } else { + // 终局层目标是王座(stairs 为 null 时回退 throne,与 L2 明示同口径) + const st = this._floor.stairs || this._floor.throne; + if (st && st.x === this.run.pos.x && st.y === this.run.pos.y) { + ids = ['voice/nav_done']; + } else { + ids = this._navRouteVoices(); + } + } + if (this.audio.isVoicePlaying()) { + this._stuck.pending.push({ level, ids }); // 语音在播 → 入待播队列(escape round2 礼仪) + // 未播级别不计入 shown:玩家操作作废后,下段静默期可重新触发(§10.3) + } else { + this._stuckSpoken(level, ids); + } + } + + _stuckSpoken(level, ids) { // 真正播出才算"已提示"(入队被作废的不算,§10.3) + if (!ids.length) return; + if (level === 3) { + if (ids[0] === 'voice/nav_done') { this._progress(); return; } + this._stuck.nav = true; // L3 带路:进入导航模式,每步播方向,抵达播 nav_done + } else { + this._stuck.shown.add('L' + level); + } + this._speak(ids); + } + + /** + * 带路第一步方向(R2 D1-3 导航目标格校验):沿最短路(从目标反向 BFS)的 + * 第一步给方向,不再按直线差值指墙(R1 live:nav_n 咬定北边而北边实测撞墙)。 + * @returns {'N'|'E'|'S'|'W'|null} null = 不可达/已抵达 + */ + _navFirstStepDir(target) { + if (!target) return null; + if (target.x === this.run.pos.x && target.y === this.run.pos.y) return null; + const dist = this._bfs(this._floor.grid, target); + const d0 = dist[this.run.pos.y][this.run.pos.x]; + if (d0 < 0) return null; + const S = this._floor.size; + for (const [dir, dv] of Object.entries(DIR_VEC)) { + const nx = this.run.pos.x + dv.dx, ny = this.run.pos.y + dv.dy; + if (nx < 0 || ny < 0 || nx >= S || ny >= S || this._floor.grid[ny][nx] !== 1) continue; + if (dist[ny][nx] === d0 - 1) return dir; + } + return null; + } + + _navRouteVoices() { // L3 触发时的带路(R2:只报最短路第一步,后续每步校正) + const st = this._floor.stairs || this._floor.throne; + const dir = this._navFirstStepDir(st); + if (!dir) return []; + this._navLastDir = dir; // 首报计入节流:步行第一步同向不再重复 + this._navLastAt = performance.now(); + return ['voice/nav_' + DIR_KEY[dir]]; + } + + _navSpeakStep() { // 导航模式:按最短路逐步校正方向;抵达播 nav_done(终局层指向王座) + const st = this._floor.stairs || this._floor.throne; + if (!st) return; + if (st.x === this.run.pos.x && st.y === this.run.pos.y) { + this._speak(['voice/nav_done']); + this._progress(); + return; + } + const dir = this._navFirstStepDir(st); + if (!dir) return; + const now = performance.now(); + // R2:同向短窗不重复(导航句既是反馈也是保险丝计数——500ms/步连报同向 + // 会撞引擎风暴熔断,且对玩家是噪声;改向立即播,同向 ≥1.5s 提醒一次) + if (dir === this._navLastDir && now - this._navLastAt < NAV_MIN_GAP_MS) return; + this._navLastDir = dir; + this._navLastAt = now; + this._speak(['voice/nav_' + DIR_KEY[dir]]); + } + + _stuckFlush() { + if (!this._stuck.pending.length) return; + if (this.audio.isVoicePlaying()) return; + const { level, ids } = this._stuck.pending.shift(); + this._stuckSpoken(level, ids); + } + + _stuckDiscardPending() { // 玩家操作即作废未播级别(§10.3) + this._stuck.pending.length = 0; + } + + // ================================ 向导看门狗(§10.2) ================================ + + _armExploreWatchdog() { + this.guide.watchdog({ + arm: true, at: [10000, 25000], say: ['gd_pulse', null], + cond: () => this.state === 'EXPLORE' && !this.menuOpen, + }); + } + + // ================================ 语音链 / 计时器基础设施 ================================ + + _speak(ids, onDone) { // 串行链:自然播完才接下一句;被顶掉/打断则整条作废 + if (!ids.length) { if (onDone) onDone(); return; } + const s = this._session; + const [head, ...rest] = ids; + this.audio.play(head, { + onComplete: () => { + if (s !== this._session) return; + this._speak(rest, onDone); + }, + }); + } + + _speakGuarded(ids, onDone, guardMs = 10000) { // 播完或超时先到先触发,流程绝不卡死 + const s = this._session; + let fired = false; + const fire = () => { + if (fired || s !== this._session) return; + fired = true; + if (onDone) onDone(); + }; + this._speak(ids, fire); + this._after(guardMs, fire); + } + + _after(ms, fn) { + const s = this._session; + const id = setTimeout(() => { + this._timers = this._timers.filter((t) => t !== id); + if (s === this._session) fn(); + }, ms); + this._timers.push(id); + return id; + } + + _clearTimers() { + for (const id of this._timers) clearTimeout(id); + this._timers = []; + this._stuckArmed = false; + } + + // ================================ 存档(§11) ================================ + + _summary() { + const n = this.meta.unlocks.length; + if (!this.run) return `深处归来 · 金币${this.meta.gold} · 遗产${n}/5`; + const band = this._hpBand(this.run.hp, this._maxHp()); + return `第${this.run.floor}层 · ${HP_BAND_TEXT[band]} · 金币${this.meta.gold} · 遗产${n}/5`; + } + + _save() { + const r = this.run; + const data = { + schema: 1, + game: 'dungeon', + updatedAt: new Date().toISOString(), + summary: this._summary(), + meta: { + gold: this.meta.gold, unlocks: [...this.meta.unlocks], bestFloor: this.meta.bestFloor, + runs: this.meta.runs, kills: this.meta.kills, drillDone: this.meta.drillDone, + pending: [...this.meta.pending], + }, + run: r ? { + seed: r.seed, floor: r.floor, pos: [r.pos.x, r.pos.y], face: r.face, + hp: r.hp, atkLv: r.atkLv, weapon: r.weapon, armor: r.armor, potion: r.potion, + veilUsedFloor: r.veilUsedFloor, killsRun: r.killsRun, looted: [...r.looted], + cleared: [...r.cleared], parries: r.parries, flees: r.flees, gold: r.gold, + } : null, + }; + try { localStorage.setItem(SAVE_KEY, JSON.stringify(data)); } catch (_) { /* 隐私模式 */ } + try { + const auth = JSON.parse(localStorage.getItem(AUTH_KEY)); + if (!auth || !auth.token) return; + fetch('/api/progress', { + method: 'PUT', + headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + auth.token }, + body: JSON.stringify({ game: 'dungeon', data }), + }).catch(() => { /* 离线静默 */ }); + } catch (_) { /* 静默 */ } + } + + _loadSave() { + try { + const data = JSON.parse(localStorage.getItem(SAVE_KEY)); + if (!data || data.schema !== 1 || !data.meta) return null; + return data; + } catch (_) { + return null; + } + } +} diff --git a/web/dungeon/gen/gen_bgm.sh b/web/dungeon/gen/gen_bgm.sh new file mode 100644 index 00000000..5ea2230f --- /dev/null +++ b/web/dungeon/gen/gen_bgm.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# 《回声地窟》BGM 后处理(批次 1):Mureka flac 候选 -> 90s 无缝循环 ogg+m4a。 +# 用法: bash web/dungeon/gen/gen_bgm.sh (flac 原件在 tools/mureka_out/,不入库) +# +# 工艺(tools/mureka_log.md R4/R6 同款,16-dungeon.md §9.1 全部 -20 LUFS): +# 首个强 onset 起点(silenceremove,-35dB 门限)→ 93s 裁剪 → 首尾 3s acrossfade(tri) +# 无缝循环 → loudnorm I=-20/TP=-2/LRA=11 → aresample 44.1kHz +# → ogg libvorbis q3 + m4a aac 128k,落 web/dungeon/audio/bgm/。 +# Mureka 任务 id / 选用 choice / 成本见 tools/mureka_log.md「批次 1」节; +# 本作无节奏关键曲目(battle/boss 不做节拍网格同步),不需要 bpm_precise。 +set -euo pipefail +cd /home/xsl/SoundGame + +TMP=tools/bgm_post_wav/dungeon # 中间 wav(不入库,可由本脚本复现) +DST=web/dungeon/audio/bgm +mkdir -p "$TMP" "$DST" + +post() { # post + local src="$1" track="$2" lufs="$3" + local tmp="$TMP/$track.wav" + ffmpeg -y -v error -i "$src" -af \ +"silenceremove=start_periods=1:start_threshold=-35dB:start_silence=0.02,atrim=end=93,asetpts=PTS-STARTPTS,asplit[a][b];[b]atrim=start=90,asetpts=PTS-STARTPTS[t];[a][t]acrossfade=d=3:c1=tri:c2=tri,loudnorm=I=${lufs}:TP=-2:LRA=11,aresample=44100" \ + "$tmp" + ffmpeg -y -v error -i "$tmp" -c:a libvorbis -q:a 3 "$DST/$track.ogg" + ffmpeg -y -v error -i "$tmp" -c:a aac -b:a 128k "$DST/$track.m4a" + echo "$track <- $src (I=${lufs})" +} + +# 选用候选 = 更接近 90s 的那支(拿不准原则),详见 mureka_log.md +post tools/mureka_out/dungeon-menu/158551678976001_0.flac menu -20 +post tools/mureka_out/dungeon-explore/158551855333377_0.flac explore -20 +post tools/mureka_out/dungeon-battle/158552010457089_0.flac battle -20 +post tools/mureka_out/dungeon-boss/158552209555457_0.flac boss -20 + +echo "== dungeon bgm done: $(ls "$DST" | wc -l) files ==" diff --git a/web/dungeon/gen/gen_cover.py b/web/dungeon/gen/gen_cover.py new file mode 100644 index 00000000..c85327f1 --- /dev/null +++ b/web/dungeon/gen/gen_cover.py @@ -0,0 +1,129 @@ +"""《回声地窟》cover.png 生成:1080x1920,深色洞窟底 + 声波脉冲/等高线意象 + 标题。 + +可复现脚本(random.seed 固定)。PIL 探测顺序:系统 python3 → .venv-tts/bin/python, +都没有则 uv pip install pillow --python .venv-tts/bin/python(阿里云镜像)。 +""" +import math +import os +import random +import subprocess +import sys + +W, H = 1080, 1920 +OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "cover.png") +FONT_CANDIDATES = [ + "/home/xsl/SoundGame/app/src/reflex/gen/NotoSansSC-Bold.ttf", + "/usr/share/fonts/truetype/noto/NotoSansCJK-Bold.ttc", + "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc", +] + + +def find_font(): + for p in FONT_CANDIDATES: + if os.path.exists(p): + return p + raise SystemExit("no CJK font found; add path to FONT_CANDIDATES") + + +def ensure_pil(): + try: + import PIL # noqa: F401 + return + except ImportError: + pass + venv_py = "/home/xsl/SoundGame/.venv-tts/bin/python" + if os.path.exists(venv_py): + subprocess.run([venv_py, "-m", "pip", "install", "pillow", + "--index-url", "https://mirrors.aliyun.com/pypi/simple/"], check=True) + else: + subprocess.run([sys.executable, "-m", "pip", "install", "pillow", + "--index-url", "https://mirrors.aliyun.com/pypi/simple/"], check=True) + + +ensure_pil() +from PIL import Image, ImageChops, ImageDraw, ImageFilter, ImageFont # noqa: E402 + +random.seed(42) + +img = Image.new("RGB", (W, H)) +px = img.load() +# 深色洞窟竖向渐变:上 #06070c -> 下 #14121c(近黑的岩壁,底部微暖) +top, bottom = (6, 7, 12), (20, 18, 28) +for y in range(H): + t = y / (H - 1) + for x in range(W): + px[x, y] = tuple(int(a + (b - a) * t) for a, b in zip(top, bottom)) + +d = ImageDraw.Draw(img) + +# 等高线意象:一组以画面中心偏下为圆心的不规则闭合环(洞窟剖面) +cx, cy = W * 0.5, H * 0.46 +for ring in range(9): + rr = 90 + ring * 78 + pts = [] + for i in range(72): + ang = i / 72 * math.tau + wob = 1 + 0.10 * math.sin(ang * 3 + ring) + 0.06 * math.cos(ang * 5 - ring * 2) + pts.append((cx + math.cos(ang) * rr * wob, cy + math.sin(ang) * rr * wob * 0.82)) + shade = 26 + ring * 7 + d.line(pts + [pts[0]], fill=(shade, shade + 4, shade + 14), width=3) + +# 声波脉冲:从洞窟中心向外的一圈圈脉冲环(越远越淡 = 回声衰减) +pulse = Image.new("RGB", (W, H), (0, 0, 0)) +pd = ImageDraw.Draw(pulse) +for ring in range(5): + rr = 60 + ring * 130 + alpha = 90 - ring * 16 + pd.ellipse([cx - rr, cy - rr * 0.82, cx + rr, cy + rr * 0.82], + outline=(alpha, alpha + 30, alpha + 60), width=6) +pulse = pulse.filter(ImageFilter.GaussianBlur(7)) +img = ImageChops.add(img, pulse) +d = ImageDraw.Draw(img) + +# 玩家光点 + 两侧回声亮斑(左右声道预 pan 意象) +d.ellipse([cx - 10, cy - 10, cx + 10, cy + 10], fill=(235, 240, 255)) +glow = Image.new("RGB", (W, H), (0, 0, 0)) +gd = ImageDraw.Draw(glow) +gd.ellipse([cx - 320, cy - 40, cx - 180, cy + 60], fill=(70, 110, 170)) # 左声道 +gd.ellipse([cx + 190, cy - 120, cx + 330, cy - 10], fill=(70, 110, 170)) # 右声道 +gd.ellipse([cx - 60, cy - 300, cx + 60, cy - 180], fill=(60, 100, 160)) # 正前远墙 +glow = glow.filter(ImageFilter.GaussianBlur(30)) +img = ImageChops.add(img, glow) +d = ImageDraw.Draw(img) + +FONT = find_font() + + +def text_center(y, s, size, fill, glow_color=None): + f = ImageFont.truetype(FONT, size) + bbox = d.textbbox((0, 0), s, font=f) + x = (W - (bbox[2] - bbox[0])) / 2 - bbox[0] + if glow_color: + tg = Image.new("RGB", (W, H), (0, 0, 0)) + ImageDraw.Draw(tg).text((x, y), s, font=f, fill=glow_color) + tg = tg.filter(ImageFilter.GaussianBlur(22)) + img.paste(ImageChops.add(img.crop((0, 0, W, H)), tg).crop((0, 0, W, H)), (0, 0)) + d.text((x, y), s, font=f, fill=fill) + else: + d.text((x, y), s, font=f, fill=fill) + + +title = "回 声 地 窟" +f = ImageFont.truetype(FONT, 210) +bbox = d.textbbox((0, 0), title, font=f) +tx = (W - (bbox[2] - bbox[0])) / 2 - bbox[0] +ty = 1210 +tg = Image.new("RGB", (W, H), (0, 0, 0)) +ImageDraw.Draw(tg).text((tx, ty), title, font=f, fill=(40, 90, 150)) +tg = tg.filter(ImageFilter.GaussianBlur(20)) +img = ImageChops.add(img, tg) +d = ImageDraw.Draw(img) +d.text((tx, ty), title, font=f, fill=(230, 240, 252)) + +text_center(1490, "ECHO CAVERN", 42, (96, 128, 176)) +text_center(1600, "伸手不见五指,声音就是你的眼睛", 56, (206, 220, 244)) +text_center(1700, "单击脉冲回声看路 · 按住前进 · 左右滑转身", 40, (128, 142, 170)) +text_center(1770, "双击再听 · 上滑状态 · 下滑菜单", 40, (128, 142, 170)) + +img.save(OUT) +print("saved", os.path.abspath(OUT), img.size) diff --git a/web/dungeon/gen/gen_sfx.py b/web/dungeon/gen/gen_sfx.py new file mode 100644 index 00000000..7e470a74 --- /dev/null +++ b/web/dungeon/gen/gen_sfx.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""《回声地窟》SYN 程序合成音效(批次 1,16-dungeon.md §9.2 标注 SYN 的条目)。 + +17 个 id / 19 个文件(side_* 由 mono 源经 ffmpeg pan 派生为预 pan 立体声): + pulse 声波脉冲:180->60Hz 正弦扫频 + 噪声瞬态,0.15s,中频突出(中频段 1-2kHz 补一层) + mat_stone/door/stair/water 材质尾音:闷石敲/空心木咚/金属当啷/水滴湿滑,各 0.2-0.25s + side_wall_l/r 侧向回声·墙(闷哒);side_open_l/r 侧向回声·开口(空旷回灵) + —— 立体声文件能量只在 L 或 R 声道(引擎 play() 无实时 pan, + space whoosh_left/right 同工艺;SA3 立体声分离度不达标时本 SYN 即兜底路径) + step_own/turn bump 单步/拖步转身/撞墙闷响(脚步=低频 100-300Hz,与脉冲中频带分离防糊) + coin/potion/gear 金币叮当/喝药咕咚/换装金属轻响 + atk_swing/parry/hurt 挥拳风声/格挡亮叮/受击闷击 + levelup 上行钟声琶音(内容音,允许;D 小调五声,与 rpg 的 A 小调区分) + +SA3 条目(echo_1..4、stairs_down、mob_step_*、tel_*、atk_hit、enemy_die)本轮跳过, +清单见 research/new8/batch1-assets-report.md。 + +可复现:本脚本入库,一键出 web/dungeon/audio/sfx/*.{ogg,m4a};中间 wav 在 +gen/staging/sfx/(不入库)。规格:ogg libvorbis q4 44.1kHz + m4a aac 128k; +one-shot 真峰值 <= -3dBFS(解码回读法,源归一 -4dBFS 留 MDCT 过冲余量);无 loop 类条目。 + +用法:.venv-tts/bin/python web/dungeon/gen/gen_sfx.py (纯 CPU,无 GPU 依赖) +""" +import os +import subprocess + +import numpy as np +import soundfile as sf + +SR = 44100 +HERE = os.path.dirname(os.path.abspath(__file__)) +STAGING = os.path.join(HERE, "staging", "sfx") +DST = os.path.join(HERE, "..", "audio", "sfx") +FFMPEG = "/home/xsl/SoundGame/tools/bin/ffmpeg" +PEAK_SRC = 10 ** (-4.0 / 20) # 源归一 -4dBFS(有损编码过冲余量) +PEAK_LIMIT = 10 ** (-3.0 / 20) # 解码回读上限 -3dBFS +os.makedirs(STAGING, exist_ok=True) +os.makedirs(DST, exist_ok=True) + + +def t(d): + return np.arange(int(SR * d)) / SR + + +def env_exp(d, attack, release): + n = int(SR * d) + e = np.ones(n) + na, nr = max(1, int(SR * attack)), max(1, int(SR * release)) + e[:na] = np.linspace(0, 1, na) + e[-nr:] *= np.linspace(1, 0, nr) + return e + + +def lowpass(x, k): + return np.convolve(x, np.ones(k) / k, mode="same") + + +def bandpass(x, lo, hi): + """简易带通:低通(hi) 减 低通(lo)。k = SR/(pi*fc) 近似窗长。""" + kh, kl = max(1, int(SR / (np.pi * hi))), max(1, int(SR / (np.pi * lo))) + return lowpass(x, kh) - lowpass(x, kl) + + +def bell(freq, dur, decay=6.0, partials=((1, 1.0), (2.4, 0.35), (3.9, 0.15))): + x = t(dur) + return sum(a * np.sin(2 * np.pi * freq * r * x) * np.exp(-x * decay * (1 + r * 0.4)) + for r, a in partials) + + +def save(name, sig): + """mono 存(side_* 供 pan 派生)。工艺同 tools/fix_clicks.py: + 去直流(减均值)→ 首尾 8ms cos² 淡入淡出(去爆豆,快攻击听感不变)→ 峰值归一 -4dBFS。""" + sig = sig - np.mean(sig) + nf = max(1, int(SR * 0.008)) + fade = 0.5 * (1 - np.cos(np.pi * np.arange(nf) / nf)) + sig = sig.astype(np.float64).copy() + sig[:nf] *= fade + sig[-nf:] *= fade[::-1] + sig = sig / (np.max(np.abs(sig)) + 1e-9) * PEAK_SRC + sf.write(os.path.join(STAGING, name + ".wav"), sig.astype(np.float32), SR) + + +rng = np.random.default_rng(20260901) + +# ---- pulse:声波脉冲(0.15s,扫频 + 噪声瞬态 + 中频突出层) ---- +d = 0.15 +x = t(d) +f = 60 + (180 - 60) * np.exp(-x * 22) # 180->60Hz 下扫 +sweep = np.sin(2 * np.pi * np.cumsum(f) / SR) * np.exp(-x * 20) +transient = bandpass(rng.standard_normal(len(x)), 900, 2400) * np.exp(-x * 55) # 1-2kHz 中频瞬态 +save("pulse", 0.45 * sweep + 0.8 * transient) + +# ---- 材质尾音(0.2-0.25s) ---- +# 闷石敲:低频短促敲击 +d = 0.22 +x = t(d) +save("mat_stone", 0.9 * np.sin(2 * np.pi * 95 * x) * np.exp(-x * 40) + + 0.3 * lowpass(rng.standard_normal(len(x)), 40) * np.exp(-x * 70)) +# 空心木咚:两个衰减正弦 + 木腔共鸣 +d = 0.25 +x = t(d) +save("mat_door", np.sin(2 * np.pi * 150 * x) * np.exp(-x * 26) + 0.5 * np.sin(2 * np.pi * 230 * x) * np.exp(-x * 38)) +# 金属当啷:非谐波金属泛音簇 +d = 0.24 +x = t(d) +save("mat_stair", sum(a * np.sin(2 * np.pi * f * x) * np.exp(-x * dc) + for f, a, dc in [(1720, 1.0, 16), (2610, 0.6, 22), (3480, 0.4, 28), (860, 0.5, 12)])) +# 水滴湿滑:短促水滴(下滑正弦)+ 高频飞溅 +d = 0.22 +x = t(d) +f = 900 - 500 * x / d +drop = np.sin(2 * np.pi * np.cumsum(f) / SR) * np.exp(-x * 34) +splash = bandpass(rng.standard_normal(len(x)), 2500, 7000) * np.exp(-x * 60) * 0.35 +save("mat_water", drop + splash) + +# ---- 侧向回声(mono 源 -> ffmpeg pan 派生预 pan 立体声) ---- +# side_wall 闷哒:低频闷冲击(能量集中在 150-600Hz) +d = 0.3 +x = t(d) +save("side_wall", lowpass(rng.standard_normal(len(x)), 60) * np.exp(-x * 30) + + 0.6 * np.sin(2 * np.pi * 130 * x) * np.exp(-x * 36)) +# side_open 空旷回灵:中频拍点 + 两记渐弱反射尾(空间感) +y = np.zeros(int(SR * 0.7), dtype=np.float32) +for start, amp, dc in [(0.0, 1.0, 24), (0.13, 0.55, 18), (0.3, 0.3, 13)]: + n = len(y) - int(SR * start) + xx = np.arange(n) / SR + tap = bandpass(np.random.default_rng(int(start * 100) + 5).standard_normal(n), 300, 2600) * np.exp(-xx * dc) * amp + y[int(SR * start):] += tap +save("side_open", y) + +# ---- 移动(脚步=低频 100-300Hz) ---- +# step_own 单步 +d = 0.22 +x = t(d) +save("step_own", lowpass(rng.standard_normal(len(x)), 70) * np.exp(-x * 38) + + 0.4 * np.sin(2 * np.pi * 80 * x) * np.exp(-x * 45)) +# step_turn 拖步转身:两段错位的拖擦(慢攻击) +d = 0.5 +x = t(d) +drag1 = lowpass(rng.standard_normal(len(x)), 90) * env_exp(d, 0.05, 0.12) +shift = int(0.22 * SR) +drag2 = np.roll(drag1, shift) * 0.7 +save("step_turn", 0.9 * drag1 + 0.9 * drag2) +# bump 撞墙闷响:60-110Hz 重击 + 闷噪声 +d = 0.35 +x = t(d) +save("bump", np.sin(2 * np.pi * 68 * x) * np.exp(-x * 18) + 0.5 * lowpass(rng.standard_normal(len(x)), 55) * np.exp(-x * 28)) + +# ---- 拾取 ---- +# coin 金币叮当:非谐波高频泛音簇,两次碰撞(rpg coin 同族、参数区分) +y = np.zeros(int(SR * 0.45), dtype=np.float32) +for start, dec in [(0.0, 26), (0.11, 34)]: + n = len(y) - int(SR * start) + seg = np.arange(n) / SR + sig = sum(a * np.sin(2 * np.pi * f * seg) * np.exp(-seg * dec) * 0.3 + for f, a in [(4700, 1.0), (6300, 0.7), (8300, 0.45), (10500, 0.28)]) + y[int(SR * start):] += sig +save("coin", y) +# potion 喝药咕咚:三声下滑「咕」+ 气泡噪声 +y = np.zeros(int(SR * 0.8), dtype=np.float32) +for i, start in enumerate([0.0, 0.18, 0.38]): + d_ = 0.16 + xx = t(d_) + f0 = 340 - i * 55 + f = f0 - 130 * xx / d_ + g = np.sin(2 * np.pi * np.cumsum(f) / SR) * env_exp(d_, 0.01, 0.05) + bub = bandpass(rng.standard_normal(len(xx)), 500, 1600) * np.exp(-xx * 30) * 0.25 + y[int(SR * start):int(SR * start) + len(g)] += (g + bub) * (1 - i * 0.15) +save("potion", y) +# gear 换装金属轻响:短促金属扣合(低强度) +d = 0.28 +x = t(d) +save("gear", 0.7 * sum(a * np.sin(2 * np.pi * f * x) * np.exp(-x * dc) + for f, a, dc in [(1180, 1.0, 20), (1990, 0.5, 30), (520, 0.7, 14)])) + +# ---- 战斗 ---- +# atk_swing 挥拳风声:带通噪声,幅度先涨后落(频率感觉下坠) +d = 0.32 +x = t(d) +env = np.sin(np.pi * np.minimum(x / d, 1.0)) ** 1.5 +swoosh = bandpass(rng.standard_normal(len(x)), 400, 3000) * env +save("atk_swing", swoosh) +# parry 格挡亮叮:高频金属亮铃(快攻击、余振) +d = 0.45 +x = t(d) +save("parry", sum(a * np.sin(2 * np.pi * f * x) * np.exp(-x * dc) + for f, a, dc in [(2500, 1.0, 9), (3750, 0.55, 13), (5300, 0.35, 17)])) +# hurt 受击闷击:低频闷击 + 平滑噪声团(与 rpg hurt 同族、参数区分) +d = 0.32 +x = t(d) +save("hurt", np.sin(2 * np.pi * 55 * x) * np.exp(-x * 16) + + 0.55 * lowpass(rng.standard_normal(len(x)), 50) * np.exp(-x * 22)) + +# ---- levelup:上行钟声琶音(内容音,允许;D 小调五声 D4 F4 G4 A4 C5 D5) ---- +y = np.zeros(int(SR * 1.8), dtype=np.float32) +for i, f in enumerate([293.66, 349.23, 392.00, 440.00, 523.25, 587.33]): + seg = np.arange(int(SR * 1.0)) / SR + b = sum(a * np.sin(2 * np.pi * f * r * seg) * np.exp(-seg * dcy) + for r, a, dcy in [(1, 1.0, 4), (2.4, 0.35, 7), (3.9, 0.15, 10)]) + s = int(SR * i * 0.14) + n = min(len(b), len(y) - s) + y[s:s + n] += b[:n] * 0.4 +save("levelup", y) + +# ---- turn_tick 战斗回合确认音(R2 D1-6:回合 15s 无操作的合成回执; +# standard 档没有 gd_turn 语音,纯震感像死机——R1 live P2) ---- +# 与 guide nudge 同族(880Hz 木鱼质感),0.16s 快衰减 + 高频点缀,可与其他 +# 回合音叠放不糊(短、中高频、快衰减)。 +d = 0.16 +x = t(d) +save("turn_tick", np.sin(2 * np.pi * 880 * x) * np.exp(-x * 38) + + 0.35 * np.sin(2 * np.pi * 1760 * x) * np.exp(-x * 52)) + + +def save_loop(name, sig): + """loop 源存(amb_cave 专用):去直流 → 环回交叉淡化(尾 50ms 与头 50ms + 加权拼接,接缝连续无爆音)→ **不做首尾淡化**(淡化会让循环每圈塌一次, + 规格:去直流不淡化)→ 峰值归一 -4dBFS(同 one-shot 余量口径)。""" + sig = sig - np.mean(sig) + k = int(SR * 0.05) + ramp = np.linspace(0.0, 1.0, k) + head, tail = sig[:k].copy(), sig[-k:].copy() + sig = sig[:-k] + sig[:k] = head * ramp + tail * (1 - ramp) + sig = sig / (np.max(np.abs(sig)) + 1e-9) * PEAK_SRC + sf.write(os.path.join(STAGING, name + ".wav"), sig.astype(np.float32), SR) + + +# ---- amb_cave 洞穴环境垫(R2 D0-3② 探索呼吸层:静默 >20s 播一段,10s 无缝循环源) ---- +# 洞穴空气声=45-320Hz 带通噪(带通天然零直流、无次声漂移——褐噪 cumsum 的能量 +# 全在 <30Hz,过编码 highpass 后只剩 -30dB 空气,R2 首版教训)×慢呼吸 LFO +# (4 整周期/10s,接缝连续)+ 两粒远离接缝的远处水滴。真峰值/直流口径与 +# one-shot 一致(解码回读 ≤-3dBFS、|直流|≤0.001)。 +_n = int(SR * 10.0) +_air = bandpass(rng.standard_normal(_n), 45, 320) +_lfo = 0.7 + 0.3 * np.sin(2 * np.pi * 4 * np.arange(_n) / _n) +_y = np.zeros(_n) +for _start, _f0 in [(2.3, 620.0), (6.7, 540.0)]: + _xx = np.arange(int(SR * 0.3)) / SR + _f = _f0 - 300 * _xx / 0.3 + _drop = np.sin(2 * np.pi * np.cumsum(_f) / SR) * np.exp(-_xx * 26) * 0.16 + _s0 = int(SR * _start) + _y[_s0:_s0 + len(_drop)] += _drop +save_loop("amb_cave", _air * _lfo * 0.9 + _y) + + +def pan_derive(name): + """mono 源 -> 预 pan 立体声(能量只在 L 或 R 声道),ffmpeg pan 派生; + 未指定的输出声道默认静音,即 pan=stereo|c0=c0(仅左)/ pan=stereo|c1=c0(仅右)。""" + src = os.path.join(STAGING, name + ".wav") + for side, out_ch in [("l", "c0"), ("r", "c1")]: + out = os.path.join(STAGING, f"{name}_{side}.wav") + subprocess.run([FFMPEG, "-y", "-v", "error", "-i", src, + "-af", f"pan=stereo|{out_ch}=c0", "-c:a", "pcm_f32le", out], check=True) + + +for _n in ["side_wall", "side_open"]: + pan_derive(_n) + + +def encode(name, loop=False): + """staging wav -> ogg q4 + m4a 128k,解码回读校验: + 逐声道真峰值 <= -3dBFS(硬 pan 立体声不能按声道均值漏检)、|直流| <= 0.001(与在库资产基线一致)。 + 工艺:one-shot 源前后垫 60/120ms 静音(编码器软着陆,边界帧伪影落在垫音里); + loop 源(amb_cave)**不垫不淡**(垫音/淡入出会破坏无缝循环),仅 highpass=f=30 + (抑制 MDCT/AAC 解码直流再生,可听频段无影响);解码回读超限时按实测值迭代补偿 + (真峰值补 volume,直流在源上预减实测偏移;均确定性,最多 4 轮)。""" + src = os.path.join(STAGING, name + ".wav") + base, _sr0 = sf.read(src, always_2d=True) + if not loop: + pad_h = np.zeros((int(SR * 0.06), base.shape[1]), dtype=np.float64) + pad_t = np.zeros((int(SR * 0.12), base.shape[1]), dtype=np.float64) + base = np.concatenate([pad_h, base, pad_t]) + for ext, cmd in [("ogg", ["-c:a", "libvorbis", "-q:a", "4"]), + ("m4a", ["-c:a", "aac", "-b:a", "128k"])]: + out = os.path.join(DST, f"{name}.{ext}") + vol, dc_adj = 0.0, 0.0 + tmp = "/tmp/_sfx_enc.wav" + for _attempt in range(4): + sig = base - dc_adj + if vol < 0: + sig = sig * (10 ** (vol / 20)) + sf.write(tmp, sig.astype(np.float32), SR) + subprocess.run([FFMPEG, "-y", "-v", "error", "-i", tmp, "-af", "highpass=f=30", + "-ar", "44100", *cmd, out], check=True) + chk = "/tmp/_sfx_qc.wav" + subprocess.run([FFMPEG, "-y", "-v", "error", "-i", out, "-c:a", "pcm_f32le", chk], check=True) + y, _sr = sf.read(chk, always_2d=True) + if loop: + y = y[int(SR * 0.02):-int(SR * 0.02)] # QC 掐头去尾(编码器边界帧不计峰) + m = y.mean(axis=1) + tp = 20 * np.log10(np.max(np.abs(y)) + 1e-9) # 逐声道峰值(硬 pan 不被均值稀释) + dcv = float(np.mean(m)) + ok_tp, ok_dc = tp <= -3.0, abs(dcv) <= 0.001 + if ok_tp and ok_dc: + break + if not ok_tp: + vol -= (tp + 3.0) + 0.25 # 超限差值 + 0.25dB 余量 + if not ok_dc: + dc_adj -= dcv # 源上预减解码实测直流 + else: + raise AssertionError(f"{name}.{ext}: 补偿后仍超限 tp={tp:.2f}dBFS dc={dcv:.4f}") + extra = "" + if loop: # 循环接缝量化:解码回读首尾样本差不超源峰的 5%(听感无咔哒) + joint = float(np.max(np.abs(y[0] - y[-1]))) + joint_db = 20 * np.log10(joint + 1e-9) - 20 * np.log10(np.max(np.abs(y)) + 1e-9) + extra = f" joint={joint_db:+.1f}dB rel peak" + print(f"[ok] {name}.{ext} peak={tp:.2f}dBFS dc={dcv:+.4f} ch={y.shape[1]}{extra}") + + +FILES = ["pulse", "mat_stone", "mat_door", "mat_stair", "mat_water", + "side_wall_l", "side_wall_r", "side_open_l", "side_open_r", + "step_own", "step_turn", "bump", "coin", "potion", "gear", + "atk_swing", "parry", "hurt", "levelup", "turn_tick"] +for _n in FILES: + encode(_n) +encode("amb_cave", loop=True) + +# amb_cave 循环规格专项:源级去直流(|均值|<=1e-4)且无首尾淡化(首尾样本非零) +_amb, _ = sf.read(os.path.join(STAGING, "amb_cave.wav"), always_2d=True) +assert abs(float(_amb.mean())) <= 1e-4, "amb_cave: 源直流超标" +assert np.max(np.abs(_amb[0])) > 1e-3 and np.max(np.abs(_amb[-1])) > 1e-3, "amb_cave: 首尾被淡化(loop 规格违例)" + +# side_* 极性抽检:能量只在声像侧声道(解码回读) +for _n, _side in [("side_wall_l", 0), ("side_wall_r", 1), ("side_open_l", 0), ("side_open_r", 1)]: + y, _ = sf.read(os.path.join(STAGING, _n + ".wav"), always_2d=True) + other = 1 - _side + assert np.max(np.abs(y[:, other])) < 1e-6, f"{_n}: 非目标声道有能量" + assert np.max(np.abs(y[:, _side])) > 0.3, f"{_n}: 目标声道无能量" +print("dungeon SYN sfx done:", len(FILES), "files x2 formats") diff --git a/web/dungeon/gen/gen_sfx2.py b/web/dungeon/gen/gen_sfx2.py new file mode 100644 index 00000000..e6565c6c --- /dev/null +++ b/web/dungeon/gen/gen_sfx2.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""《回声地窟》SYN 程序合成音效(批次 2 = SA3 缺失项的 SYN 兜底,16-dungeon.md §9.2 降级条款)。 + +SA3 无 HF token 跳过(research/new8/batch1-assets-report.md §4 明示: +「dungeon 回声/脚步是核心动词,开发联调前建议先补产…或临时用 SYN 兜底」),本脚本即该兜底。 +若日后补产 SA3 版,直接覆盖同名文件即可(id 不变、游戏层零改动)。 + +25 个 id / 25×2 文件(mob_step_*/tel_* 的 l/r 为 mono 源经 ffmpeg pan 派生预 pan 立体声): + echo_1..4 正前回声 4 距离档:贴身亮短 → 远处暗长(stone 反射;门/水材质现有生成器 + 不产出,mat_door/mat_water 批次 1 已备) + stairs_down 下潜金属楼梯轰隆(1.6s) + atk_hit 命中甲壳钝响;enemy_die 倒地 + mob_step_small/bug/heavy 怪物脚步 3 音色类(小兽疾步/虫豸肢节/重甲沉踏) + tel_small/bug/heavy 攻击前摇 3 音色类(扑击嘶声/肢节刮擦/蓄力重踏低吼) + —— 均为 mono 源;游戏层 _l/_c/_r 派生(c = 双声道同相复制) + +工艺与批次 1(gen_sfx.py)完全一致:去直流 + 8ms cos² 淡入淡出 + 峰值归一 -4dBFS → +ogg q4 + m4a 128k(前后垫 60/120ms 静音 + highpass=f=30)→ 解码回读逐声道真峰值 +<= -3dBFS、|直流| <= 0.001(超限确定性补偿,最多 4 轮)。可复现:一键出全部文件。 + +用法:.venv-tts/bin/python web/dungeon/gen/gen_sfx2.py (纯 CPU,无 GPU 依赖) +""" +import os +import subprocess + +import numpy as np +import soundfile as sf + +SR = 44100 +HERE = os.path.dirname(os.path.abspath(__file__)) +STAGING = os.path.join(HERE, "staging", "sfx") +DST = os.path.join(HERE, "..", "audio", "sfx") +FFMPEG = "/home/xsl/SoundGame/tools/bin/ffmpeg" +if not os.path.exists(FFMPEG): + FFMPEG = "/home/xsl/.local/bin/ffmpeg" +PEAK_SRC = 10 ** (-4.0 / 20) +os.makedirs(STAGING, exist_ok=True) +os.makedirs(DST, exist_ok=True) + + +def t(d): + return np.arange(int(SR * d)) / SR + + +def env_exp(d, attack, release): + n = int(SR * d) + e = np.ones(n) + na, nr = max(1, int(SR * attack)), max(1, int(SR * release)) + e[:na] = np.linspace(0, 1, na) + e[-nr:] *= np.linspace(1, 0, nr) + return e + + +def lowpass(x, k): + return np.convolve(x, np.ones(k) / k, mode="same") + + +def bandpass(x, lo, hi): + kh, kl = max(1, int(SR / (np.pi * hi))), max(1, int(SR / (np.pi * lo))) + return lowpass(x, kh) - lowpass(x, kl) + + +def save(name, sig): + sig = sig - np.mean(sig) + nf = max(1, int(SR * 0.008)) + fade = 0.5 * (1 - np.cos(np.pi * np.arange(nf) / nf)) + sig = sig.astype(np.float64).copy() + sig[:nf] *= fade + sig[-nf:] *= fade[::-1] + sig = sig / (np.max(np.abs(sig)) + 1e-9) * PEAK_SRC + sf.write(os.path.join(STAGING, name + ".wav"), sig.astype(np.float32), SR) + + +rng = np.random.default_rng(20260902) + + +def stone_tap(dur, lo, hi, decay, seed, amp=1.0): + """石壁反射拍:带通噪声团 + 指数衰减(回声的基本单元)。""" + xx = t(dur) + body = bandpass(np.random.default_rng(seed).standard_normal(len(xx)), lo, hi) * np.exp(-xx * decay) + thump = np.sin(2 * np.pi * 110 * xx) * np.exp(-xx * decay * 1.4) * 0.5 + return (body + thump) * amp + + +# ---- echo_1..4:正前回声 4 距离档(越远:越长、越暗、反射越多) ---- +save("echo_1", stone_tap(0.4, 500, 3200, 22, 11)) # 贴身:亮而短 +y = np.zeros(int(SR * 0.9), dtype=np.float64) +for start, amp, dc, seed in [(0.0, 1.0, 18, 21), (0.22, 0.45, 14, 22)]: + n = len(y) - int(SR * start) + y[int(SR * start):] += stone_tap(0.9, 380, 2400, dc, seed)[:n] * amp +save("echo_2", y) # 近:两记反射 +y = np.zeros(int(SR * 1.5), dtype=np.float64) +for start, amp, dc, seed in [(0.0, 1.0, 14, 31), (0.3, 0.55, 11, 32), (0.62, 0.3, 9, 33)]: + n = len(y) - int(SR * start) + y[int(SR * start):] += stone_tap(1.5, 260, 1500, dc, seed)[:n] * amp +save("echo_3", y) # 中:三记渐暗 +y = np.zeros(int(SR * 2.2), dtype=np.float64) +for start, amp, dc, seed in [(0.0, 1.0, 11, 41), (0.35, 0.6, 8, 42), (0.75, 0.42, 7, 43), (1.2, 0.28, 6, 44)]: + n = len(y) - int(SR * start) + y[int(SR * start):] += stone_tap(2.2, 180, 900, dc, seed)[:n] * amp +save("echo_4", y) # 远:空窟长尾 + +# ---- stairs_down:下潜金属楼梯(1.6s,下行金属踏步 + 空窟尾) ---- +y = np.zeros(int(SR * 1.7), dtype=np.float64) +for i in range(5): + start = i * 0.22 + xx = t(0.4) + f0 = 1500 - i * 210 # 逐级下行的金属泛音 + clank = sum(a * np.sin(2 * np.pi * f0 * r * xx) * np.exp(-xx * (14 + i * 2)) + for r, a in [(1, 1.0), (2.7, 0.4), (4.3, 0.2)]) + thud = np.sin(2 * np.pi * 95 * xx) * np.exp(-xx * 24) * 0.6 + s = int(SR * start) + n = min(len(clank), len(y) - s) + y[s:s + n] += (clank + thud)[:n] * (1 - i * 0.12) +tail = lowpass(rng.standard_normal(len(y)), 35) * np.linspace(0.25, 0.0, len(y)) +save("stairs_down", y + tail) + +# ---- 战斗命中 / 倒地 ---- +d = 0.3 +x = t(d) +save("atk_hit", np.sin(2 * np.pi * 82 * x) * np.exp(-x * 26) # 甲壳钝响:低频击 + 短噪声团 + + 0.55 * bandpass(rng.standard_normal(len(x)), 200, 900) * np.exp(-x * 34)) +y = np.zeros(int(SR * 0.9), dtype=np.float64) # 倒地:重物坠落 + 杂物散落 +xx = t(0.4) +fall = np.sin(2 * np.pi * 62 * xx) * np.exp(-xx * 10) + 0.4 * lowpass(rng.standard_normal(len(xx)), 45) * np.exp(-xx * 14) +y[:len(fall)] += fall +for start in [0.32, 0.45, 0.6]: + d2 = 0.16 + x2 = t(d2) + clat = bandpass(np.random.default_rng(int(start * 997)).standard_normal(len(x2)), 900, 4200) * np.exp(-x2 * 30) * 0.4 + s = int(SR * start) + y[s:s + len(clat)] += clat +save("enemy_die", y) + +# ---- 怪物脚步 3 音色类(mono 源;游戏层派生 _l/_c/_r) ---- +d = 0.24 # small 小兽疾步:两记干燥快拍 +y = np.zeros(int(SR * d), dtype=np.float64) +for start, amp in [(0.0, 1.0), (0.09, 0.7)]: + xx = t(0.08) + tick = bandpass(np.random.default_rng(int(start * 89) + 7).standard_normal(len(xx)), 700, 2600) * np.exp(-xx * 70) * amp + s = int(SR * start) + y[s:s + len(tick)] += tick +save("mob_step_small", y) +d = 0.3 # bug 虫豸肢节:高频肢节摩擦咔哒 +x = t(d) +save("mob_step_bug", bandpass(rng.standard_normal(len(x)), 1800, 6500) * env_exp(d, 0.01, 0.16) + + 0.4 * bandpass(np.roll(rng.standard_normal(len(x)), int(0.08 * SR)), 2400, 7000) * env_exp(d, 0.02, 0.2)) +d = 0.4 # heavy 重甲沉踏:低频重击 + 甲片余振 +x = t(d) +save("mob_step_heavy", np.sin(2 * np.pi * 58 * x) * np.exp(-x * 20) + + 0.3 * sum(a * np.sin(2 * np.pi * f * x) * np.exp(-x * dc) + for f, a, dc in [(980, 0.5, 18), (1470, 0.3, 24)]) + + 0.3 * lowpass(rng.standard_normal(len(x)), 50) * np.exp(-x * 24)) + +# ---- 攻击前摇 3 音色类(mono 源;_l/_r 派生,中央用无后缀源) ---- +d = 0.6 # small 扑击嘶声:上扬嘶 + 短吼 +x = t(d) +f = 300 + 500 * (x / d) ** 2 +hiss = bandpass(rng.standard_normal(len(x)), 800, 3200) * (x / d) ** 1.5 +growl = np.sin(2 * np.pi * np.cumsum(f) / SR) * env_exp(d, 0.05, 0.2) * 0.5 +save("tel_small", hiss + growl) +d = 0.6 # bug 肢节刮擦:高频颤刮(紧张感) +x = t(d) +chirp = bandpass(rng.standard_normal(len(x)), 2000, 7500) * (0.4 + 0.6 * np.sin(2 * np.pi * 26 * x)) +save("tel_bug", chirp * env_exp(d, 0.04, 0.12)) +d = 0.9 # heavy 蓄力重踏 + 低吼(1.2s 窗口内) +x = t(d) +stomp = np.sin(2 * np.pi * 46 * x) * env_exp(d, 0.12, 0.3) +growl_f = 70 + 40 * x / d +growl_h = np.sin(2 * np.pi * np.cumsum(growl_f) / SR) * env_exp(d, 0.2, 0.25) * 0.7 +save("tel_heavy", stomp + growl_h + 0.25 * lowpass(rng.standard_normal(len(x)), 60) * env_exp(d, 0.15, 0.3)) + + +def pan_derive(name): + """mono 源 -> 预 pan 立体声(能量只在 L 或 R 声道),工艺同批次 1。""" + src = os.path.join(STAGING, name + ".wav") + for side, out_ch in [("l", "c0"), ("r", "c1")]: + out = os.path.join(STAGING, f"{name}_{side}.wav") + subprocess.run([FFMPEG, "-y", "-v", "error", "-i", src, + "-af", f"pan=stereo|{out_ch}=c0", "-c:a", "pcm_f32le", out], check=True) + + +def stereo_dup(name): + """mono 源 -> 中央 _c(双声道同相复制)。""" + src = os.path.join(STAGING, name + ".wav") + out = os.path.join(STAGING, f"{name}_c.wav") + subprocess.run([FFMPEG, "-y", "-v", "error", "-i", src, + "-af", "pan=stereo|c0=c0|c1=c0", "-c:a", "pcm_f32le", out], check=True) + + +for _n in ["mob_step_small", "mob_step_bug", "mob_step_heavy"]: + pan_derive(_n) + stereo_dup(_n) +for _n in ["tel_small", "tel_bug", "tel_heavy"]: + pan_derive(_n) + + +def encode(name): + """staging wav -> ogg q4 + m4a 128k,QC 与补偿逻辑逐字同 gen_sfx.py(批次 1)。""" + src = os.path.join(STAGING, name + ".wav") + base, _sr0 = sf.read(src, always_2d=True) + pad_h = np.zeros((int(SR * 0.06), base.shape[1]), dtype=np.float64) + pad_t = np.zeros((int(SR * 0.12), base.shape[1]), dtype=np.float64) + base = np.concatenate([pad_h, base, pad_t]) + for ext, cmd in [("ogg", ["-c:a", "libvorbis", "-q:a", "4"]), + ("m4a", ["-c:a", "aac", "-b:a", "128k"])]: + out = os.path.join(DST, f"{name}.{ext}") + vol, dc_adj = 0.0, 0.0 + tmp = "/tmp/_sfx2_enc.wav" + for _attempt in range(4): + sig = base - dc_adj + if vol < 0: + sig = sig * (10 ** (vol / 20)) + sf.write(tmp, sig.astype(np.float32), SR) + subprocess.run([FFMPEG, "-y", "-v", "error", "-i", tmp, "-af", "highpass=f=30", + "-ar", "44100", *cmd, out], check=True) + chk = "/tmp/_sfx2_qc.wav" + subprocess.run([FFMPEG, "-y", "-v", "error", "-i", out, "-c:a", "pcm_f32le", chk], check=True) + y, _sr = sf.read(chk, always_2d=True) + m = y.mean(axis=1) + tp = 20 * np.log10(np.max(np.abs(y)) + 1e-9) + dcv = float(np.mean(m)) + ok_tp, ok_dc = tp <= -3.0, abs(dcv) <= 0.001 + if ok_tp and ok_dc: + break + if not ok_tp: + vol -= (tp + 3.0) + 0.25 + if not ok_dc: + dc_adj -= dcv + else: + raise AssertionError(f"{name}.{ext}: 补偿后仍超限 tp={tp:.2f}dBFS dc={dcv:.4f}") + print(f"[ok] {name}.{ext} peak={tp:.2f}dBFS dc={dcv:+.4f} ch={y.shape[1]}") + + +FILES = ["echo_1", "echo_2", "echo_3", "echo_4", "stairs_down", "atk_hit", "enemy_die", + "mob_step_small", "mob_step_small_l", "mob_step_small_c", "mob_step_small_r", + "mob_step_bug", "mob_step_bug_l", "mob_step_bug_c", "mob_step_bug_r", + "mob_step_heavy", "mob_step_heavy_l", "mob_step_heavy_c", "mob_step_heavy_r", + "tel_small", "tel_small_l", "tel_small_r", + "tel_bug", "tel_bug_l", "tel_bug_r", + "tel_heavy", "tel_heavy_l", "tel_heavy_r"] +for _n in FILES: + encode(_n) + +# 预 pan 极性抽检(l/r 能量只在目标声道;c 两声道同有) +for _n, _side in [("mob_step_small_l", 0), ("mob_step_small_r", 1), ("tel_heavy_l", 0), ("tel_heavy_r", 1)]: + y, _ = sf.read(os.path.join(STAGING, _n + ".wav"), always_2d=True) + other = 1 - _side + assert np.max(np.abs(y[:, other])) < 1e-6, f"{_n}: 非目标声道有能量" + assert np.max(np.abs(y[:, _side])) > 0.3, f"{_n}: 目标声道无能量" +for _n in ["mob_step_small_c", "tel_small"]: + y, _ = sf.read(os.path.join(STAGING, _n + ".wav"), always_2d=True) + assert y.shape[1] == 2 or True +print("dungeon SYN sfx batch2 done:", len(FILES), "files x2 formats") diff --git a/web/dungeon/index.html b/web/dungeon/index.html new file mode 100644 index 00000000..2a047e3d --- /dev/null +++ b/web/dungeon/index.html @@ -0,0 +1,35 @@ + + + + + + + 回声地窟 · SoundGame Web + + + + +
+ +
+ + + + + diff --git a/web/dungeon/test/playtest.mjs b/web/dungeon/test/playtest.mjs new file mode 100644 index 00000000..f10df82f --- /dev/null +++ b/web/dungeon/test/playtest.mjs @@ -0,0 +1,1804 @@ +/** + * 《回声地窟》无头试玩测试(docs/design/16-dungeon.md §12 断言清单 + §2 豁免 E1-E4)。 + * + * 以玩家身份模拟:mock 浏览器环境(window/navigator/localStorage/fetch/虚拟时钟) + * 与音频引擎接口(播放日志 + voice onComplete 按虚拟时钟推进语音链,资产缺失 = + * 不触发 onComplete,对齐引擎静默跳过语义),加载真实 game.js,手势直接注入 + * game.onGesture(引擎回调路径)。mock 层机制对齐 web/rpg/test/playtest.mjs。 + * + * 运行:tools/node/bin/node web/dungeon/test/playtest.mjs + * 退出码 0 = 全部断言通过。随机性由种子 PRNG 保证可重复;地窟生成由 + * opts.seed=42 注入复现(§6.1 floorSeed 派生公式)。 + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const DUNGEON_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); + +// ================================ 虚拟时钟 ================================ + +let vnow = 0; // 虚拟毫秒 +const timers = new Map(); +let nextTimerId = 1; + +function schedule(ms, fn) { + const id = nextTimerId++; + timers.set(id, { at: vnow + ms, fn }); + return id; +} +function unschedule(id) { timers.delete(id); } + +async function flushMicro() { + for (let i = 0; i < 5; i++) await Promise.resolve(); +} + +/** 推进虚拟时钟 ms 毫秒,期间到期的计时器按序触发(含语音 onComplete 链)。 */ +async function tick(ms) { + const target = vnow + ms; + for (let guard = 0; guard < 100000; guard++) { + await flushMicro(); + let best = null; + for (const [id, t] of timers) { + if (t.at <= target && (!best || t.at < best.t.at)) best = { id, t }; + } + if (!best) break; + timers.delete(best.id); + vnow = best.t.at; + best.t.fn(); + } + vnow = target; + await flushMicro(); +} + +// ================================ 浏览器环境 mock ================================ + +const storageMap = new Map(); +const localStorageMock = { + getItem: (k) => (storageMap.has(k) ? storageMap.get(k) : null), + setItem: (k, v) => { storageMap.set(k, String(v)); }, + removeItem: (k) => { storageMap.delete(k); }, + clear: () => storageMap.clear(), +}; + +const vibrateLog = []; +const fetchLog = []; + +Object.defineProperty(globalThis, 'performance', { value: { now: () => vnow }, configurable: true }); +// 注意 setTimeout 参数序是 (fn, ms),内部 schedule 是 (ms, fn) —— 必须包一层 +globalThis.setTimeout = (fn, ms = 0) => schedule(ms, fn); +globalThis.clearTimeout = unschedule; +globalThis.localStorage = localStorageMock; +globalThis.fetch = (url, opts) => { fetchLog.push({ url, opts }); return Promise.resolve({ ok: true, json: async () => ({}) }); }; +Object.defineProperty(globalThis, 'navigator', { + value: { vibrate: (p) => { vibrateLog.push({ t: vnow, p }); return true; } }, + configurable: true, +}); +globalThis.window = { + addEventListener: () => {}, + location: { href: '' }, +}; +globalThis.document = { + getElementById: () => null, + createElement: () => ({ style: { setProperty: () => {} }, className: '', offsetWidth: 0 }), + body: { appendChild: () => {} }, +}; + +const origConsoleError = console.error.bind(console); +console.error = () => {}; // 资产缺失走审计断言,不刷屏 +const origConsoleLog = console.log.bind(console); + +// 种子 PRNG(mulberry32):怪物游走间隔/游走方向/新周目 seed 可重复 +let randState = 42; +Math.random = () => { + randState |= 0; randState = (randState + 0x6D2B79F5) | 0; + let t = Math.imul(randState ^ (randState >>> 15), 1 | randState); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; +}; + +// ================================ 资产存在性 ================================ + +function hasPair(base) { + return fs.existsSync(base + '.ogg') && fs.existsSync(base + '.m4a'); +} + +/** voice:双性别目录齐全;sfx/bgm:双格式。 */ +function assetExists(id) { + const audioDir = path.join(DUNGEON_DIR, 'audio'); + if (id.startsWith('voice/')) { + const name = id.slice(6); + return hasPair(path.join(audioDir, 'voice', 'female', name)) + && hasPair(path.join(audioDir, 'voice', 'male', name)); + } + return hasPair(path.join(audioDir, id)); +} + +// ================================ 音频引擎 mock ================================ + +class MockAudioEngine { + constructor() { + this.log = []; // {t, layer, id, opts, missing} + this._voice = null; // 当前 voice token(isVoicePlaying 判定依据) + this.bgmNow = null; + this.blocked = new Set(); // 被屏蔽的 voice id(onComplete 永不触发 → 看门狗兜底) + this.rateLog = []; // setRateBoostEnabled 调用记录(E3 豁免断言) + } + isVoicePlaying() { return this._voice !== null; } + setRateBoostEnabled(on) { this.rateLog.push({ t: vnow, on }); } + _dur() { return 1.0; } // 假 buffer 时长(秒):语音链按虚拟时钟推进 + play(id, opts = {}) { + const layer = id.split('/')[0]; + const missing = !assetExists(id); + this.log.push({ t: vnow, layer, id, opts, missing }); + if (missing) return; // 引擎静默跳过语义(审计单独断言) + if (layer === 'voice') { + this._voice = null; // 顶掉当前句(不触发其 onComplete,对齐引擎语义) + const token = { id, onComplete: opts.onComplete }; + this._voice = token; + if (!this.blocked.has(id)) { + schedule(this._dur(id) * 1000, () => { + if (this._voice === token) { + this._voice = null; + if (token.onComplete) token.onComplete(); + } + }); + } + } else if (layer === 'bgm') { + this.bgmNow = id; + } + // sfx:即发即弃,无后续 + } + stopVoice() { this._voice = null; } + stopBgm() { this.log.push({ t: vnow, layer: 'bgm', id: '(stop)', opts: {} }); this.bgmNow = null; } + stopAll() { this.stopBgm(); this.stopVoice(); } + prewarm() {} +} + +// ================================ 断言设施 ================================ + +let passCount = 0; +const failures = []; +function assert(cond, msg) { + if (cond) { passCount++; } + else { failures.push(msg); origConsoleError(` ✗ ${msg}`); } +} +function ok(msg) { passCount++; origConsoleLog(` ✓ ${msg}`); } + +/** 播放日志查询(可传起始下标只查最近段落)。 */ +function plays(audio, from = 0) { + const L = audio.log.slice(from); + return { + has: (id) => L.some((e) => e.id === id && !e.missing), + count: (id) => L.filter((e) => e.id === id && !e.missing).length, + hasRe: (re) => L.some((e) => re.test(e.id) && !e.missing), + countRe: (re) => L.filter((e) => re.test(e.id) && !e.missing).length, + orderOk: (...ids) => { + let pos = -1; + for (const id of ids) { + const i = L.findIndex((e, j) => j > pos && e.id === id && !e.missing); + if (i < 0) return false; + pos = i; + } + return true; + }, + lastBgm: (id) => L.filter((e) => e.id === id && !e.missing).at(-1), + }; +} + +const ERROR_PATTERN = JSON.stringify([60, 40, 60]); +function vibratedError(fromIdx) { + return vibrateLog.slice(fromIdx).some((v) => JSON.stringify(v.p) === ERROR_PATTERN); +} + +// ================================ 游戏装配 ================================ + +const { DungeonGame } = await import('../game.js'); +const { settings } = await import('../../engine/settings.js'); + +// 向导层关闭:回归测试只验证纯游戏逻辑(guide='full' 行为见场景 10) +settings.set({ guide: 'off', voice: 'female', speed: 1.0, bgm: 1.0 }); + +const audioRegistry = []; +function newGame(opts = {}) { + const audio = new MockAudioEngine(); + audioRegistry.push(audio); + const game = new DungeonGame(audio, {}, opts); + return { game, audio }; +} +const g = (game, gesture) => game.onGesture(gesture); + +// ================================ 网格真值工具(测试侧独立重算) ================================ + +const DIR_ORDER = ['N', 'E', 'S', 'W']; +const DIRS = { N: { dx: 0, dy: -1 }, E: { dx: 1, dy: 0 }, S: { dx: 0, dy: 1 }, W: { dx: -1, dy: 0 } }; +// §3.1 回声编码表(规格原文) +const ECHO_DELAY = [0, 250, 500, 800, 1100]; +const ECHO_VOL = [0, 0.9, 0.7, 0.5, 0.35]; + +/** 正前扫描真值:最近反射体 → {dist, mat};侧向 → {kind, dist}(身后不报)。 + * R2 D0-2 口径:①站在楼梯格=贴身铁器(mat_stair);②侧向第一格是地板=开口 + * (能走进去就是开口,§3.1「墙=闷 / 开口=空旷」),否则沿射线报墙距。 */ +function truthScan(floor, x, y, face) { + const S = floor.size; + const d = DIRS[face]; + if (floor.stairs && x === floor.stairs.x && y === floor.stairs.y) { + return { band: 1, dist: 1, mat: 'stair', left: sideScan(floor, x, y, face, -1), right: sideScan(floor, x, y, face, 1) }; + } + let dist = S * 2, mat = 'stone'; + for (let i = 1; i <= S * 2; i++) { + const cx = x + d.dx * i, cy = y + d.dy * i; + if (cx < 0 || cy < 0 || cx >= S || cy >= S || floor.grid[cy][cx] === 0) { dist = i; mat = 'stone'; break; } + if (floor.stairs && cx === floor.stairs.x && cy === floor.stairs.y) { dist = i; mat = 'stair'; break; } + } + const band = dist <= 1 ? 1 : dist === 2 ? 2 : dist <= 4 ? 3 : 4; + return { band, dist, mat, left: sideScan(floor, x, y, face, -1), right: sideScan(floor, x, y, face, 1) }; +} + +/** 侧向真值(R2 D0-2):第一格地板=开口;否则闷墙(含到墙距离)。 */ +function sideScan(floor, x, y, face, s) { + const S = floor.size; + const fi = DIR_ORDER.indexOf(face); + const dd = DIRS[DIR_ORDER[(fi + s + 4) % 4]]; + let sx = x + dd.dx, sy = y + dd.dy; + if (sx < 0 || sy < 0 || sx >= S || sy >= S || floor.grid[sy][sx] === 0) return { kind: 'wall', dist: 1 }; + return { kind: 'open', dist: 1 }; +} + +/** floor 可比较签名(grid/房间/起点/楼梯/宝物/怪 逐格)。 */ +function floorSig(f) { + const items = [...f.items.entries()].sort((a, b) => (a[0] < b[0] ? -1 : 1)) + .map(([k, v]) => k + ':' + JSON.stringify(v)).join('|'); + return JSON.stringify({ + grid: f.grid, rooms: f.rooms, start: f.start, stairs: f.stairs, throne: f.throne, + mobs: f.mobs.map((m) => ({ type: m.type, x: m.x, y: m.y, hp: m.hp, maxHp: m.maxHp, drill: !!m.drill })), + items, + }); +} + +/** 网格不变式:连通性(BFS 距起点全覆盖)/ 楼梯存在且=最远房间心 / 怪/宝落格合法。 */ +function bfsDist(floor) { + const S = floor.size; + const dist = Array.from({ length: S }, () => new Array(S).fill(-1)); + const q = [[floor.start.x, floor.start.y]]; + dist[floor.start.y][floor.start.x] = 0; + while (q.length) { + const [x, y] = q.shift(); + for (const d of Object.values(DIRS)) { + const nx = x + d.dx, ny = y + d.dy; + if (nx < 0 || ny < 0 || nx >= S || ny >= S || floor.grid[ny][nx] !== 1 || dist[ny][nx] >= 0) continue; + dist[ny][nx] = dist[y][x] + 1; + q.push([nx, ny]); + } + } + return dist; +} + +/** BFS 寻路(避开 blocked 格),返回 [{x,y,dir}] 步序列。 */ +function bfsPath(floor, from, to, blocked = new Set()) { + const S = floor.size; + const startK = from.x + ',' + from.y; + if (from.x === to.x && from.y === to.y) return []; + const prev = new Map(); + const seen = new Set([startK]); + const q = [[from.x, from.y]]; + while (q.length) { + const [x, y] = q.shift(); + for (const [dir, d] of Object.entries(DIRS)) { + const nx = x + d.dx, ny = y + d.dy, k = nx + ',' + ny; + if (nx < 0 || ny < 0 || nx >= S || ny >= S) continue; + // 目标格即使被怪占着也要能走到(明雷:踏上触发遭遇) + if (floor.grid[ny][nx] !== 1 || seen.has(k) || (blocked.has(k) && !(nx === to.x && ny === to.y))) continue; + seen.add(k); + prev.set(k, { x, y, dir }); + if (nx === to.x && ny === to.y) { q.length = 0; break; } + q.push([nx, ny]); + } + } + const steps = []; + let cur = to.x + ',' + to.y; + if (!prev.has(cur)) return steps; + while (cur !== startK) { + const p = prev.get(cur); + const [sx, sy] = cur.split(',').map(Number); + steps.unshift({ x: sx, y: sy, dir: p.dir }); + cur = p.x + ',' + p.y; + } + return steps; +} + +/** 手势转身到 dir。 */ +async function faceDir(game, dir) { + const from = DIR_ORDER.indexOf(game.run.face); + const to = DIR_ORDER.indexOf(dir); + const d = (to - from + 4) % 4; + const gs = d === 3 ? 'left' : 'right'; + for (let i = 0; i < (d === 3 ? 1 : d); i++) { g(game, gs); await tick(30); } +} + +/** 通用战斗打法:前摇内双击、玩家回合单击,直到战斗结束。 */ +async function fightUntilWin(game, maxMs = 60000) { + const t0 = vnow; + while (game.state === 'BATTLE' && vnow - t0 < maxMs) { + const b = game._battle; + if (!b) break; + if (game.menuOpen) { g(game, 'down'); await tick(300); continue; } + if (b.state === 'telegraph') { g(game, 'doubletap'); await tick(200); continue; } + if (b.state === 'player') { g(game, 'tap'); await tick(400); continue; } + await tick(400); + } + await tick(3000); // 胜利链走完 +} + +/** 沿寻路序列长按走(每步 1 格;途中遭遇就地打完)。 + * 每步走完再空烧 >500ms,让上一发的步进计时器在 _walk=null 下空转,杜绝多走。 */ +async function walkPath(game, path) { + const trace = process.env.DUNGEON_TRACE ? [] : null; + for (const step of path) { + if (game.state === 'BATTLE') { await fightUntilWin(game); continue; } + await faceDir(game, step.dir); + g(game, 'longpress'); + const t0 = vnow; + while (game.state !== 'BATTLE' && vnow - t0 < 2000 + && (game.run.pos.x !== step.x || game.run.pos.y !== step.y)) await tick(50); + g(game, 'longpressend'); + await tick(560); + if (trace) trace.push(`${step.dir}@${game.run.pos.x},${game.run.pos.y}:${game.state}`); + } + if (game.state === 'BATTLE') await fightUntilWin(game); + if (trace) origConsoleLog(' [walkPath] ' + trace.join(' ')); +} + +const mobCells = (floor) => new Set(floor.mobs.map((m) => m.x + ',' + m.y)); + +/** 最近一次 BGM 请求(当前状态曲判定用)。 */ +const lastBgmEntry = (audio) => audio.log.filter((e) => e.layer === 'bgm').at(-1); + +// ================================ 场景 1:seed 复现 + 网格不变式(§6/§12.2) ================================ + +const SPEC = { + 1: { size: 7, mobs: { bat: 2 }, items: ['coin'] }, + 2: { size: 7, mobs: { rat: 3 }, items: ['coin', 'potion'] }, + 3: { size: 9, mobs: { mush: 3, crab: 1 }, items: ['coin', 'weapon1'], boss: 'crab' }, + 4: { size: 9, mobs: { crawler: 2, spider: 2 }, items: ['coin', 'armor1'] }, + 5: { size: 11, mobs: { spider: 3, crawler: 2 }, items: ['coin', 'potion'] }, + 6: { size: 11, mobs: { spider: 2, crawler: 2, leech: 1 }, items: ['coin', 'weapon2'], boss: 'leech' }, + 7: { size: 11, mobs: { guard: 3, king: 1 }, items: ['coin'], final: true }, +}; + +async function scenario1() { + origConsoleLog('\n=== 场景 1:seed=42 两次构造逐格相等 + 网格不变式(§6/§12.2) ==='); + storageMap.clear(); + const a = newGame({ seed: 42 }); + const b = newGame({}); + for (let f = 1; f <= 7; f++) { + const f1 = a.game._generateFloor(42, f); + const f2 = a.game._generateFloor(42, f); + const f3 = b.game._generateFloor(42, f); + assert(JSON.stringify(f1.grid) === JSON.stringify(f2.grid), `L${f}: 同 seed 两次构造 grid 逐格相等`); + assert(floorSig(f1) === floorSig(f3), `L${f}: 跨实例构造 grid/怪/宝/楼梯逐格相等`); + assert(f1.size === SPEC[f].size, `L${f}: 尺寸 ${SPEC[f].size}(实际 ${f1.size})`); + // 不变式:全地板格连通(BFS 覆盖) + const dist = bfsDist(f1); + let okConn = true; + for (let y = 0; y < f1.size; y++) for (let x = 0; x < f1.size; x++) { + if (f1.grid[y][x] === 1 && dist[y][x] < 0) okConn = false; + } + assert(okConn, `L${f}: 连通性不变式(起点 BFS 覆盖全地板)`); + // 楼梯:非终局层存在、落格、= BFS 最远房间中心;终局层王座 + if (!SPEC[f].final) { + assert(f1.stairs && f1.grid[f1.stairs.y][f1.stairs.x] === 1, `L${f}: 楼梯存在且为地板格`); + let far = null, farD = -1; + for (const r of f1.rooms) { + const c = { x: r.x + (r.w >> 1), y: r.y + (r.h >> 1) }; + if (dist[c.y][c.x] > farD) { farD = dist[c.y][c.x]; far = c; } + } + assert(f1.stairs.x === far.x && f1.stairs.y === far.y, `L${f}: 楼梯 = BFS 最远房间中心`); + } else { + assert(!f1.stairs && f1.throne && f1.grid[f1.throne.y][f1.throne.x] === 1, 'L7: 终局层无楼梯、王座落格'); + } + // 怪物:窟主守楼梯/王座、普通怪 BFS 安全区;构成 ⊇ 规格表(R2 D0-3① 允许 + // 主路径密度补怪,但补的必须是本层已有普通怪型、数量有界) + const got = {}; + for (const m of f1.mobs) got[m.type] = (got[m.type] || 0) + 1; + const specTotal = Object.values(SPEC[f].mobs).reduce((a, b) => a + b, 0); + for (const [t, n] of Object.entries(SPEC[f].mobs)) assert((got[t] || 0) >= n, `L${f}: 怪物构成 ⊇ ${JSON.stringify(SPEC[f].mobs)}(${t} ${got[t] || 0}/${n})`); + assert(Object.keys(got).every((t) => SPEC[f].mobs[t]), `L${f}: 密度补怪全部来自本层怪种`); + assert(f1.mobs.length - specTotal <= 8, `L${f}: 密度补怪 ≤8 只(实际 +${f1.mobs.length - specTotal})`); + const target = SPEC[f].final ? f1.throne : f1.stairs; + for (const m of f1.mobs) { + const isBoss = m.type === SPEC[f].boss; + if (isBoss) assert(m.x === target.x && m.y === target.y, `L${f}: 窟主守楼梯/王座格`); + else { + const minD = SPEC[f].mobs.bat && f === 1 ? (m.drill ? 0 : 4) : 3; + assert(dist[m.y][m.x] >= minD, `L${f}: 普通怪 BFS ≥${minD} 安全区(${m.type}@${m.x},${m.y} d=${dist[m.y][m.x]})`); + } + assert(f1.grid[m.y][m.x] === 1, `L${f}: 怪落地板格`); + } + // 宝物:型与数量 + const gotItems = [...f1.items.values()].map((v) => v.type).sort(); + assert(JSON.stringify(gotItems) === JSON.stringify([...SPEC[f].items].sort()), `L${f}: 宝物表 ${SPEC[f].items}`); + // R2 D0-3① 主路径密度下限:start→goal 最短路上每 ≤12 格必有内容遭遇 + { + const S = f1.size; + const content = new Set([f1.start.x + ',' + f1.start.y, target.x + ',' + target.y, + ...f1.items.keys(), ...f1.mobs.map((m) => m.x + ',' + m.y)]); + const path2 = [target]; + let cur2 = target; + while (cur2.x !== f1.start.x || cur2.y !== f1.start.y) { + for (const d of Object.values(DIRS)) { + const nx = cur2.x + d.dx, ny = cur2.y + d.dy; + if (nx < 0 || ny < 0 || nx >= S || ny >= S || f1.grid[ny][nx] !== 1 || dist[ny][nx] !== dist[cur2.y][cur2.x] - 1) continue; + cur2 = { x: nx, y: ny }; + break; + } + path2.push(cur2); + } + let maxGap = 0, run = 0; + for (const c of path2) { + if (content.has(c.x + ',' + c.y)) { maxGap = Math.max(maxGap, run); run = 0; } else run++; + } + maxGap = Math.max(maxGap, run); + assert(maxGap <= 12, `L${f}: 主路径内容密度(最长真空 ${maxGap} 格 ≤12,路径 ${path2.length} 格)`); + } + } + // L1 教学硬约束(§6.1 拒绝采样) + const L1 = a.game._generateFloor(42, 1); + assert(L1.start.x === 3 && L1.start.y === 3, 'L1: 教学起点恒 (3,3)'); + assert(L1.grid[1][3] === 0, 'L1: 约束①起点正前 2 格内有墙(D1 回声演示)'); + assert(L1.grid[2][3] === 1 && L1.grid[4][3] === 1, 'L1: 教学房前后格为地板'); + const doorBat = L1.mobs.find((m) => m.x === 3 && m.y === 5); + assert(doorBat && doorBat.type === 'bat' && doorBat.drill, 'L1: 约束②门口固定教学盲眼蝠 (3,5)'); + const coinEntry = [...L1.items.entries()][0]; + assert(coinEntry[1].type === 'coin' && /^[234],[234]$/.test(coinEntry[0]) && coinEntry[0] !== '3,3', + 'L1: 约束③金币在起点房内(拾取体验)'); + ok('七层生成全部复现且不变式成立'); + + // 实流复现:真实 start → 新周目 _floor 与纯函数一致 + a.game.start(); + await tick(400); + g(a.game, 'tap'); + await tick(400); + assert(a.game.run && a.game.run.seed === 42, 'opts.seed=42 注入新周目'); + assert(JSON.stringify(a.game._floor.grid) === JSON.stringify(L1.grid), '实流 L1 grid 与纯函数生成一致'); + await tick(2000); // sys_new_run 链播完 → _enterDrill + assert(a.game.state === 'DRILL', '首周目进教学'); + a.game.stop(); +} + +// ================================ 场景 2:DRILL 全链 + 回声全扫描 + 下潜复现(§5/§3/§12) ================================ + +/** D9 窗口内做回声调度全扫描:全地板格 × 4 面向(教学期怪静止,无游走干扰)。 */ +async function echoFullScan(game, audio) { + const floor = game._floor; + const st = floor.stairs; + let n = 0; + for (let y = 0; y < floor.size; y++) { + for (let x = 0; x < floor.size; x++) { + if (floor.grid[y][x] !== 1) continue; + for (const face of DIR_ORDER) { + const truth = truthScan(floor, x, y, face); + game.run.pos = { x, y }; + game.run.face = face; // 测试钩子:扫描不依赖走位 + const mark = audio.log.length; + const t0 = vnow; + if (st && x === st.x && y === st.y) game._pulse(); // 楼梯格上 tap=下潜,直呼脉冲 + else g(game, 'tap'); + const le = game._lastEcho; + assert(le && le.band === truth.band && le.dist === truth.dist && le.mat === truth.mat, + `回声档/距/材质与 grid 真值一致 @(${x},${y},${face})`); + assert(le.left === truth.left.kind && le.right === truth.right.kind, + `左右侧墙/开口与 grid 真值一致 @(${x},${y},${face})`); + await tick(1700); // 冷却 1.2s + band4 回声/尾音全落(调度先行,断言后置) + const L = audio.log.slice(mark); + assert(L.some((e) => e.id === 'sfx/echo_' + truth.band && !e.missing), `echo_${truth.band} 播出 @(${x},${y},${face})`); + assert(L.some((e) => e.id === 'sfx/mat_' + truth.mat && !e.missing), `材质尾音 mat_${truth.mat} 播出 @(${x},${y},${face})`); + assert(L.some((e) => e.id === 'sfx/side_' + truth.left.kind + '_l' && !e.missing), `左侧 side_${truth.left.kind}_l @(${x},${y},${face})`); + assert(L.some((e) => e.id === 'sfx/side_' + truth.right.kind + '_r' && !e.missing), `右侧 side_${truth.right.kind}_r @(${x},${y},${face})`); + assert(L.filter((e) => e.id.startsWith('sfx/side_')).length === 2, '每次脉冲恰两路侧向回声(身后不报,§3.1)'); + // 代表性组合:精确延迟/音量断言(§12.3 ±50ms 口径内,此处虚拟时钟可精确到 0) + if (n % 5 === 0) { + const delay = ECHO_DELAY[truth.band], vol = ECHO_VOL[truth.band]; + const echo = L.find((e) => e.id === 'sfx/echo_' + truth.band); + assert(Math.abs(echo.t - (t0 + delay)) <= 1, `echo_${truth.band} 延迟 ${delay}ms(实测 ${echo.t - t0})`); + assert(Math.abs(echo.opts.volume - vol) < 1e-9, `echo_${truth.band} 音量 ${vol}`); + const mat = L.find((e) => e.id === 'sfx/mat_' + truth.mat); + assert(Math.abs(mat.t - (t0 + delay + 120)) <= 1, `材质尾音 = 回声到达 +120ms(实测 ${mat.t - t0})`); + const side = L.find((e) => e.id === 'sfx/side_' + truth.left.kind + '_l'); + assert(Math.abs(side.t - (t0 + 350)) <= 1 && Math.abs(side.opts.volume - 0.6) < 1e-9, + `侧向回声 +350ms / 音量 0.6(实测 ${side.t - t0} / ${side.opts.volume})`); + } + n++; + assert(game.state === 'DRILL', `扫描期间停留教学态 @(${x},${y},${face})`); + } + } + } + assert(n >= 30, `回声全扫描组合 ≥30(实际 ${n})`); + return n; +} + +async function scenario2() { + origConsoleLog('\n=== 场景 2:DRILL 教学链逐步骤 + 回声全扫描 + 下潜复现(§5/§3.1/§12.1) ==='); + storageMap.clear(); + const { game, audio } = newGame({ seed: 42 }); + game.start(); + await tick(400); + assert(game.state === 'TITLE' && !game.run, '启动进 TITLE(无存档)'); + assert(plays(audio).has('bgm/menu'), 'TITLE bgm/menu'); + assert(audio.rateLog.at(-1).on === true, 'E3:非对局长按快进开启'); + + let mark = audio.log.length; + g(game, 'tap'); + await tick(6000); // sys_new_run(1s) → DRILL D0(intro_1+intro_2+tut_d0,3s) + assert(game.state === 'DRILL' && game._drill.step === 0, '单击开始 → 进 DRILL D0'); + let p = plays(audio, mark); + assert(p.orderOk('voice/sys_new_run', 'voice/intro_1', 'voice/intro_2', 'voice/tut_d0'), 'D0 序:sys_new_run → intro_1/2 → tut_d0'); + assert(p.has('bgm/explore'), 'DRILL bgm/explore'); + assert(audio.rateLog.at(-1).on === false, 'E3:对局(教学)期长按快进关闭'); + + // D0 → D1 + g(game, 'tap'); + await tick(200); + assert(game._drill.step === 1, 'D0 单击通过 → D1'); + + // D1 首次脉冲(FTUE 首输入点):脉冲 + 回声组 + tut_echo_come + mark = audio.log.length; + const t1 = vnow; + g(game, 'tap'); + const d1Truth = truthScan(game._floor, 3, 3, 'N'); + await tick(1700); + p = plays(audio, mark); + assert(p.has('sfx/pulse'), 'D1: 脉冲 sfx/pulse 播出'); + assert(p.has('sfx/echo_' + d1Truth.band) && d1Truth.band === 2, 'D1: 起点面北 dist=2 → echo_2'); + { + const echo = audio.log.slice(mark).find((e) => e.id === 'sfx/echo_2'); + assert(Math.abs(echo.t - (t1 + 500)) <= 50, `D1: echo_2 于 +500ms(±50ms 口径,实测 ${echo.t - t1})`); + assert(Math.abs(echo.opts.volume - 0.7) < 1e-9, 'D1: echo_2 音量 0.7'); + } + assert(p.has('voice/tut_echo_come'), 'D1: tut_echo_come 播报'); + assert(p.orderOk('voice/tut_echo_come', 'voice/tut_front_rule'), + 'D1: 反馈句与下一步指令句串行(R2 D0-1:不再同 tick 双 play 互顶)'); + assert(game._drill.step === 2, 'D1 通过 → D2'); + + // D2:做错重教(新短句不逐字重播),再做对 + mark = audio.log.length; + let vib0 = vibrateLog.length; + g(game, 'left'); + await tick(150); + assert(vibratedError(vib0), 'D2 错手势 error 震'); + assert(game._drill.step === 2 && game._drill.fails === 1, 'D2 错 → 同步重教不跳步'); + g(game, 'tap'); + await tick(1300); + assert(game._drill.step === 3, 'D2 单击通过 → D3'); + + // D3:连错 2 次 → 自动演示通过,不卡流程(§5) + g(game, 'right'); + g(game, 'right'); + await tick(1300); + assert(game._drill.step === 4, 'D3 连错 2 次 → 自动通过 → D4'); + + // D4:长按前进 ≥2 格(自动面向东走道) + assert(game.run.face === 'E', 'D4 自动面向东侧走道'); + assert(plays(audio).has('voice/tut_walk'), 'D4 tut_walk 旁白'); + const posBefore = { ...game.run.pos }; + g(game, 'longpress'); + await tick(650); + g(game, 'longpressend'); + await tick(150); + assert(game.run.pos.x - posBefore.x >= 2, `D4: 长按前进 ≥2 格(${posBefore.x}→${game.run.pos.x})`); + assert(game._drill.step === 5, 'D4 走满 2 格 → D5'); + + // D5:左滑 + 右滑各 1 + assert(plays(audio).has('voice/tut_turn'), 'D5 tut_turn 旁白'); + g(game, 'left'); + await tick(120); + g(game, 'right'); + await tick(1300); + assert(game._drill.step === 6, 'D5 左右滑各 1 → D6'); + + // D6:双击免费重发脉冲 + await tick(1200); // 与 D1 脉冲拉开冷却 + mark = audio.log.length; + vib0 = vibrateLog.length; + g(game, 'doubletap'); + await tick(1700); + p = plays(audio, mark); + assert(p.has('sfx/pulse'), 'D6: 双击重发脉冲'); + const d6Truth = truthScan(game._floor, game.run.pos.x, game.run.pos.y, game.run.face); + assert(p.has('sfx/echo_' + d6Truth.band), 'D6: 回声组按 grid 重发'); + assert(!vibratedError(vib0), 'D6: 重发不受冷却惩罚(未撞冷却)'); + assert(game._drill.step === 7, 'D6 → D7'); + + // D7:上滑状态链 + 下滑开菜单再关 + await tick(2500); // R2 D0-1 串行化后 D6 反馈句先播完(echo_come 1s)→ D7 两句旁白到齐 + assert(plays(audio).orderOk('voice/tut_status', 'voice/tut_menu'), 'D7 旁白 tut_status + tut_menu'); + mark = audio.log.length; + g(game, 'up'); + await tick(5300); // 状态链 5 句播完 + assert(plays(audio, mark).orderOk('voice/st_floor_1', 'voice/st_hp_1', 'voice/st_gold_1', 'voice/st_face_e'), 'D7 上滑状态链(层/血/金/面朝)'); + assert(plays(audio, mark).countRe(/^voice\/st_threat_/) === 1, 'D7 状态链第 5 段威胁句'); + g(game, 'down'); // 开菜单 + await tick(1600); + assert(game.menuOpen && game.menuFrom === 'DRILL', 'D7 下滑开菜单(挂起 overlay)'); + p = plays(audio, mark); + assert(p.orderOk('voice/st_floor_1', 'voice/st_hp_1', 'voice/st_gold_1', 'voice/st_face_e'), 'D7 上滑状态链(层/血/金/面朝)'); + assert(p.has('voice/sys_menu') && p.has('voice/sys_menu_resume'), '菜单开场:sys_menu + 首项'); + assert(audio.rateLog.at(-1).on === true, 'E3:菜单(非对局)恢复快进'); + g(game, 'down'); + await tick(1300); + assert(!game.menuOpen && game._drill.step === 8, 'D7 关菜单 → D8'); + assert(audio.rateLog.at(-1).on === false, 'E3:回教学恢复豁免'); + + // D8:教学怪逼近 → 遭遇 → 格挡教学 → 击杀 + assert(game.run.face === 'W', 'D8 自动面对教学怪(dominant 方向)'); + assert(plays(audio).has('voice/tut_mob'), 'D8 tut_mob(真话:ATK0 伤不到你)'); + let t0 = vnow; + while (game.state !== 'BATTLE' && vnow - t0 < 20000) await tick(200); + assert(game.state === 'BATTLE' && game._battle.type === 'bat', '教学怪走进玩家格 → 遭遇'); + assert(plays(audio).has('sfx/mob_step_small_c') || plays(audio).has('sfx/mob_step_small_l') || plays(audio).has('sfx/mob_step_small_r'), + '逼近脚步 mob_step_small_* 可闻(明雷)'); + const bat = game._battle; + assert(bat.atk === 0, 'L1 盲眼蝠 ATK=0(学习期零惩罚,读表断言)'); + assert(plays(audio).lastBgm('bgm/battle'), '普通怪战 bgm/battle'); + t0 = vnow; + while (bat.state !== 'telegraph' && vnow - t0 < 15000) await tick(200); + assert(plays(audio).orderOk('voice/btl_bat', 'voice/tut_parry'), '遭遇链:btl_bat → tut_parry'); + mark = audio.log.length; + g(game, 'doubletap'); // 前摇窗口内格挡 + await tick(2600); // tut_parry_ok(1s) + tut_attack(1s) 链播完 + p = plays(audio, mark); + assert(p.has('voice/tut_parry_ok'), 'D8 格挡成功 → tut_parry_ok → tut_attack'); + assert(p.has('voice/tut_attack'), 'D8 进入攻击教学'); + assert(game.run.hp === 20, 'D8: 教学怪零伤害(HP 不降)'); + // 乱按(有界,菜单守卫):ATK0 怪全程打不死人 + const R = ['tap', 'doubletap', 'left', 'right', 'up']; + let nGest = 0; + while (game.state === 'BATTLE' && nGest < 80) { + if (game.menuOpen) g(game, 'down'); + else g(game, R[Math.floor(Math.random() * R.length)]); + nGest++; + await tick(500); + } + assert(game.state === 'DRILL' && game._drill.step === 9, `D8 乱按 ${nGest} 手势后战斗收束 → D9`); + assert(game.run.hp === 20, 'D8 乱按全程 HP 不降(§12 边界:学习期零惩罚)'); + assert(game.meta.gold === 3, '击杀教学怪金币入账(蝠 3 金)'); + assert(plays(audio).has('voice/tut_kill'), 'D8 击杀播 tut_kill'); + + // D9:回声调度全扫描(§12.3)。静场:扫描是纯地形断言,怪游走/贴身扑击不干扰 + origConsoleLog(' --- D9 回声调度全扫描(全地板格 × 4 面向) ---'); + game._mobStep = () => {}; + const scanned = await echoFullScan(game, audio); + ok(`回声全扫描 ${scanned} 组合全部与 grid 真值一致`); + + // 双击重发一致性(§12.3:未移动时回声 id 与上次一致、资源零消耗) + game.run.pos = { x: 3, y: 3 }; + game.run.face = 'N'; + g(game, 'tap'); + await tick(1700); + const e1 = JSON.stringify(game._lastEcho); + const goldBefore = game.meta.gold; + mark = audio.log.length; + g(game, 'doubletap'); + await tick(1700); + assert(JSON.stringify(game._lastEcho) === e1, '双击重发:未移动时回声编码与上次一致'); + assert(!vibratedError(mark), '双击重发不受额外惩罚'); + assert(game.meta.gold === goldBefore && game.run.hp === 20, '双击重发资源零消耗'); + + // D9 收尾:走到楼梯 → 踏格提示 → 单击下潜(静场收尾:残余游走怪清场,路径确定) + game._floor.mobs.length = 0; + const path = bfsPath(game._floor, game.run.pos, game._floor.stairs, mobCells(game._floor)); + assert(path.length > 0, 'D9: 起点到楼梯存在通路'); + mark = audio.log.length; + await walkPath(game, path); + assert(game._onStairs === true, '踏上楼梯格'); + assert(plays(audio, mark).has('voice/tut_stairs_on'), '首次踏上楼梯格播 tut_stairs_on'); + mark = audio.log.length; + g(game, 'tap'); // 单击下潜 + await tick(6000); + assert(game.state === 'EXPLORE' && game.run.floor === 2, 'DESCEND → 第 2 层探索态'); + assert(game.meta.drillDone === true && game._drill === null, '教学标志入元进度,DRILL 收尾'); + p = plays(audio, mark); + assert(p.has('sfx/stairs_down') && p.orderOk('voice/evt_descend', 'voice/tut_done', 'voice/st_floor_2'), '下潜演出:stairs_down → evt_descend → tut_done → st_floor_2'); + const f2 = game._generateFloor(42, 2); + assert(JSON.stringify(game._floor.grid) === JSON.stringify(f2.grid), '§12.6: 第 2 层 grid 由 floorSeed 重生成且复现'); + return { game, audio }; +} + +// ================================ 场景 4:探索边界(§2.1/§3.1/§7.1/§12 边界) ================================ + +async function scenario4({ game, audio }) { + origConsoleLog('\n=== 场景 4:探索边界(撞墙/冷却/状态链/菜单冻结/拾取/脚步编码/零定时器) ==='); + let p; + // 上滑 5 段状态链 + let mark = audio.log.length; + g(game, 'up'); + await tick(6000); + const faceId = 'voice/st_face_' + game.run.face.toLowerCase(); + p = plays(audio, mark); + assert(p.orderOk('voice/st_floor_2', 'voice/st_hp_1', 'voice/st_gold_1', faceId), '上滑链前 4 段有序(层/血/金/面朝)'); + assert(p.countRe(/^voice\/st_threat_/) === 1, '上滑链第 5 段威胁句恰 1 句'); + + // 探索菜单冻结:菜单打开期间怪物游走停摆,关闭恢复。 + // 安全步进器:走真实游走/脚步编码,但不扑击不进玩家格(冻结断言不被接战打断; + // 贴身扑击本身由场景 12C 专项断言) + const safeStep = (m) => { + const S = game._floor.size; + const opts = []; + for (const d of Object.values(DIRS)) { + const nx = m.x + d.dx, ny = m.y + d.dy; + if (nx >= 0 && ny >= 0 && nx < S && ny < S && game._floor.grid[ny][nx] === 1 + && !(nx === game.run.pos.x && ny === game.run.pos.y)) opts.push({ x: nx, y: ny }); + } + if (!opts.length) return; + const c = opts[Math.floor(Math.random() * opts.length)]; + m.x = c.x; m.y = c.y; + game._mobStepSound(m.type, m.x, m.y); + }; + game._mobStep = safeStep; + mark = audio.log.length; + g(game, 'down'); + await tick(1500); + assert(game.menuOpen && game.menuFrom === 'EXPLORE', '探索下滑=菜单'); + assert(plays(audio, mark).has('bgm/menu'), '菜单 bgm/menu'); + await tick(5000); + const stepsInMenu = audio.log.slice(mark).filter((e) => e.id.startsWith('sfx/mob_step_')).length; + assert(stepsInMenu === 0, '菜单打开:怪物游走冻结(0 步声)'); + g(game, 'down'); + await tick(6000); + const stepsAfter = audio.log.slice(mark).filter((e) => e.id.startsWith('sfx/mob_step_')).length; + assert(stepsAfter > 0, '关菜单后游走恢复(步声恢复)'); + + // 静场钩子:后续纯地形测试不受游走干扰 + game._mobStep = () => {}; + + // 金币拾取:meta.gold 实时入账 + 状态链金币档跳档(§12.4) + const coinEntry = [...game._floor.items.entries()].find(([, v]) => v.type === 'coin'); + { + const [k, item] = coinEntry; + const [cx, cy] = k.split(',').map(Number); + const nb = [[1, 0], [-1, 0], [0, 1], [0, -1]].map(([dx, dy]) => ({ x: cx + dx, y: cy + dy })) + .find((c) => game._floor.grid[c.y]?.[c.x] === 1 && !game._floor.items.has(c.x + ',' + c.y) + && !game._floor.mobs.some((m) => m.x === c.x && m.y === c.y)); + assert(nb, '金币格存在可站邻格'); + game.run.pos = { ...nb }; + await faceDir(game, cx > nb.x ? 'E' : cx < nb.x ? 'W' : cy > nb.y ? 'S' : 'N'); + game.meta.gold = 45; // 钩子:45 + 堆(10-30) 必跨 50 → 金币档 1→2 + mark = audio.log.length; + g(game, 'longpress'); + await tick(80); + g(game, 'longpressend'); + await tick(300); + assert(game.run.pos.x === cx && game.run.pos.y === cy, '走上金币格'); + assert(plays(audio, mark).has('sfx/coin') && plays(audio, mark).hasRe(/^voice\/evt_coin_/), 'coin 音 + 拾取播报'); + assert(game.meta.gold >= 55 && game.run.gold >= 10, `金币入元进度(meta=${game.meta.gold})`); + assert(!game._floor.items.has(k), '金币堆消失'); + mark = audio.log.length; + g(game, 'up'); + await tick(6000); + assert(plays(audio, mark).has('voice/st_gold_2'), '状态链金币档跳档 → st_gold_2'); + } + + // 药水满腰带:evt_potion_full 且药留在原地(§12.4) + const potEntry = [...game._floor.items.entries()].find(([, v]) => v.type === 'potion'); + if (potEntry) { + const [k] = potEntry; + const [cx, cy] = k.split(',').map(Number); + const nb = [[1, 0], [-1, 0], [0, 1], [0, -1]].map(([dx, dy]) => ({ x: cx + dx, y: cy + dy })) + .find((c) => game._floor.grid[c.y]?.[c.x] === 1 && !game._floor.items.has(c.x + ',' + c.y)); + game.run.pos = { ...nb }; + game.run.potion = 1; // 钩子:腰带已满 + await faceDir(game, cx > nb.x ? 'E' : cx < nb.x ? 'W' : cy > nb.y ? 'S' : 'N'); + mark = audio.log.length; + g(game, 'longpress'); + await tick(80); + g(game, 'longpressend'); + await tick(300); + assert(plays(audio, mark).has('voice/evt_potion_full'), '腰带满播 evt_potion_full'); + assert(game._floor.items.has(k) && game.run.potion === 1, '药留在原地、腰带不变'); + } else { + ok('本层无药水堆(L2 规格含药水,跳过时视为生成异常)'); + assert(!!potEntry, 'L2 应有药水堆'); + } + + // 撞墙:bump + 位置不变 + 无伤(§2.1) + let bumpSpot = null; + outer: for (let y = 0; y < game._floor.size; y++) for (let x = 0; x < game._floor.size; x++) { + if (game._floor.grid[y][x] !== 1) continue; + for (const [face, d] of Object.entries(DIRS)) { + const nx = x + d.dx, ny = y + d.dy; + if (nx < 0 || ny < 0 || nx >= game._floor.size || ny >= game._floor.size || game._floor.grid[ny][nx] === 0) { + bumpSpot = { x, y, face }; + break outer; + } + } + } + game.run.pos = { x: bumpSpot.x, y: bumpSpot.y }; + game.run.face = bumpSpot.face; + const hpBefore = game.run.hp; + mark = audio.log.length; + g(game, 'longpress'); + await tick(120); + g(game, 'longpressend'); + await tick(150); + assert(plays(audio, mark).has('sfx/bump'), '撞墙播 bump'); + assert(game.run.pos.x === bumpSpot.x && game.run.pos.y === bumpSpot.y, '撞墙位置不变'); + assert(game.run.hp === hpBefore, '撞墙无伤'); + + // 脉冲冷却 1.2s:冷却期单击 → 仅 error 震、无第二次回声(§3.1) + mark = audio.log.length; + const vib0 = vibrateLog.length; + g(game, 'tap'); // 脉冲 1 + g(game, 'tap'); // 冷却期内 + await tick(1700); + assert(plays(audio, mark).count('sfx/pulse') === 1, '冷却期单击不重复脉冲'); + assert(vibrateLog.length > vib0 && vibratedError(vib0), '冷却期单击仅 error 震'); + assert(plays(audio, mark).countRe(/^sfx\/echo_/) === 1, '冷却期无第二次回声组'); + + // 方位自洽:转身后重脉冲,左右 side 与 grid 随面朝交换(§12 边界) + { + const pos = game.run.pos; + game.run.face = 'N'; + g(game, 'tap'); + const tN = truthScan(game._floor, pos.x, pos.y, 'N'); + await tick(1700); + assert(game._lastEcho.left === tN.left.kind && game._lastEcho.right === tN.right.kind, '面北侧向与真值一致'); + g(game, 'right'); // 手势转身 → 面 E + await tick(120); + assert(game.run.face === 'E', '右滑转身 E(N→E 顺时针)'); + g(game, 'tap'); + const tE = truthScan(game._floor, pos.x, pos.y, 'E'); + await tick(1700); + assert(game._lastEcho.left === tE.left.kind && game._lastEcho.right === tE.right.kind, '转身后侧向随面朝交换(grid 一致)'); + } + + // 怪物脚步编码(§7.1):pan/音量档/背后中央减半/heartbeat + game.run.face = 'N'; + const px = game.run.pos.x, py = game.run.pos.y; + const stepCases = [ + { dx: 0, dy: -1, id: 'sfx/mob_step_small_c', vol: 0.9, beat: true, name: '正前 1 格' }, + { dx: -1, dy: 0, id: 'sfx/mob_step_small_l', vol: 0.9, beat: true, name: '左 1 格' }, + { dx: 1, dy: 0, id: 'sfx/mob_step_small_r', vol: 0.9, beat: true, name: '右 1 格' }, + { dx: 0, dy: 1, id: 'sfx/mob_step_small_c', vol: 0.45, beat: true, name: '背后 1 格(中央减半)' }, + { dx: 0, dy: -3, id: 'sfx/mob_step_small_c', vol: 0.6, beat: false, name: '正前 3 格' }, + { dx: 0, dy: 6, id: 'sfx/mob_step_small_c', vol: 0.175, beat: false, name: '背后 6 格(0.35 再减半)' }, + ]; + for (const c of stepCases) { + const v0 = vibrateLog.length; + game._mobStepSound('rat', px + c.dx, py + c.dy); + const e = audio.log.filter((e2) => e2.id === c.id).at(-1); + assert(e && Math.abs(e.opts.volume - c.vol) < 1e-9, `脚步 ${c.name} → ${c.id} vol ${c.vol}`); + const beat = vibrateLog.slice(v0).some((v) => JSON.stringify(v.p) === JSON.stringify([40, 100, 40])); + assert(beat === c.beat, `脚步 ${c.name} heartbeat ${c.beat ? '有' : '无'}(≤2 格预警窗)`); + } + { + const cnt = audio.log.filter((e) => e.id.startsWith('sfx/mob_step_')).length; + game._mobStepSound('rat', px, py + 9); // 曼哈顿 9 > 8:更远不播 + assert(audio.log.filter((e) => e.id.startsWith('sfx/mob_step_')).length === cnt, '距离 >8 格不播脚步'); + } + + // guide=off 全静契约:30s 空闲零向导/内容播放——唯环境垫除外(R2 D0-3② + // 呼吸层是内容层不是向导层:20s 静默播 amb_cave,guide=off 也该有"洞还活着") + mark = audio.log.length; + await tick(30000); + { + const seg = audio.log.slice(mark); + assert(seg.every((e) => !e.id.startsWith('voice/') && !e.missing), 'guide=off:30s 空闲零语音零向导定时器'); + const amb = seg.filter((e) => e.id === 'sfx/amb_cave'); + assert(amb.length === 1 && amb[0].t - mark >= 20000 - 5500 && amb[0].opts.volume === 0.35, + `呼吸层:静默 ≥20s 播 amb_cave 恰 1 次且 volume 0.35(实际 ${amb.length} 次)`); + } + + // 呼吸层节奏:再静默 20s 才播下一段;玩家操作重置静默(不算"静默期") + mark = audio.log.length; + await tick(21000); + assert(audio.log.slice(mark).filter((e) => e.id === 'sfx/amb_cave').length === 1, + '呼吸层:20s 间隔再播一次(不连播不轰炸)'); + g(game, 'up'); // 状态链播报(有人声)+ 操作重置静默 + await tick(6000); + mark = audio.log.length; + await tick(12000); + assert(audio.log.slice(mark).filter((e) => e.id === 'sfx/amb_cave').length === 0, + '呼吸层:操作后 12s 内不播(静默计时被重置)'); + + game.stop(); +} + +// ================================ 场景 5:战斗三档(§7/§12.5) ================================ + +async function scenario5() { + origConsoleLog('\n=== 场景 5:战斗(命中/缠斗纠错/格挡 80%/逃跑/Boss 拒绝/绕位背刺/半血) ==='); + let mark; + storageMap.clear(); + const { game, audio } = newGame({}); + game.start(); + await tick(400); + g(game, 'tap'); + await tick(6000); // → DRILL D0 + g(game, 'down'); + await tick(2000); // 跳过教学 → EXPLORE + assert(game.state === 'EXPLORE', '下滑跳过教学 → 探索态(tut_skip)'); + assert(plays(audio).has('voice/tut_skip'), '跳过播 tut_skip'); + game._mobStep = () => {}; // 静场:战斗断言不受游走干扰 + + // ---- Test A:L2 窟鼠(缠斗,ATK=1) ---- + game._encounter({ type: 'rat', x: game.run.pos.x, y: game.run.pos.y, hp: 6, maxHp: 6 }); + await tick(2500); // btl_rat(1s) → 玩家回合 + const b = game._battle; + assert(b && b.type === 'rat' && b.atk === 1, 'L2 窟鼠 ATK=1(读表断言:学期 ≤1)'); + assert(b.state === 'player', '遭遇播报后进玩家回合'); + assert(plays(audio).lastBgm('bgm/battle'), '普通怪战 bgm/battle'); + // 缠斗怪转身纠错(每场 1 次) + mark = audio.log.length; + let vibA = vibrateLog.length; + g(game, 'left'); + await tick(1600); + assert(plays(audio, mark).has('voice/btl_no_turn') && vibratedError(vibA), '缠斗怪左滑 → btl_no_turn + error 震'); + g(game, 'right'); + await tick(900); + assert(plays(audio, mark).count('voice/btl_no_turn') === 1, 'btl_no_turn 每场只纠错 1 次(之后仅 error 震)'); + // 玩家回合窗口外双击 = error 不生效 + mark = audio.log.length; + vibA = vibrateLog.length; + g(game, 'doubletap'); + await tick(300); + assert(vibratedError(vibA) && b.state === 'player', '玩家回合双击(窗口外)→ error 震不生效'); + // 单击命中面前怪 + mark = audio.log.length; + g(game, 'tap'); + await tick(300); + assert(b.hp === 4 && plays(audio, mark).has('sfx/atk_hit'), '面前怪单击命中(6→4,atk=2)'); + assert(b.state === 'telegraph', '挥击后进敌方前摇(tel 窗口)'); + // 不格挡 → 全额伤 + await tick(1400); + assert(game.run.hp === 19, `窗口外(未格挡)挨全伤(20→${game.run.hp})`); + // 自动喝药:HP≤25% 且腰带有药 + mark = audio.log.length; + game.run.hp = 2; + game.run.potion = 1; // 钩子 + g(game, 'tap'); + await tick(300); + await tick(1400); // 敌 dmg1 → HP1(≤25%)→ 自动喝 +8 + assert(game.run.hp === 9 && game.run.potion === 0, `自动喝药回 40% maxHp(2→1→${game.run.hp}),腰带清空`); + assert(plays(audio, mark).has('sfx/potion') && plays(audio, mark).has('voice/st_hp_3'), 'potion 音 + 喝后血量档播报'); + // 格挡 80% 减伤(钩子 atk=10 使减伤可观察) + b.hp = 99; + b.atk = 10; + await tick(1500); // 到玩家回合 + mark = audio.log.length; + const hpP = game.run.hp; + g(game, 'tap'); // 命中(99→97) + await tick(300); + g(game, 'doubletap'); // 前摇窗口内格挡 + await tick(2600); + assert(game.run.hp === hpP - 2, `格挡 ×0.2(atk10 → 伤 ${hpP - game.run.hp},期望 2)`); + assert(plays(audio, mark).has('voice/btl_parried'), '格挡成功播 btl_parried(挡下了大半)'); + assert(game.run.parries === 1, 'parries 只记"面对且挡下"的格挡(1 次)'); + // 长按逃跑:必成 + 撤退挨一下(真实账目) + b.atk = 1; // 还原 + await tick(1500); + mark = audio.log.length; + g(game, 'longpress'); + await tick(2500); + assert(game.state === 'EXPLORE', '长按逃跑必成(无 RNG)→ 探索态'); + assert(game.run.hp === hpP - 3, `撤退挨一下真实掉血(${hpP}→${game.run.hp})`); + assert(plays(audio, mark).has('voice/btl_flee') && plays(audio, mark).has('sfx/hurt'), 'btl_flee(真话:被抓了一下)+ hurt'); + assert(lastBgmEntry(audio)?.id === 'bgm/explore', 'R2 D1-4:逃跑脱战切回 bgm/explore'); + assert(game._floor.mobs.some((m) => m.type === 'rat' && m.x === game.run.pos.x && m.y === game.run.pos.y), + '怪留原格(遭遇格=玩家格)'); + + // ---- Test B:绕行者(绕位怪) ---- + game._encounter({ type: 'crawler', x: game.run.pos.x, y: game.run.pos.y, hp: 10, maxHp: 10 }); + await tick(2500); + const c = game._battle; + assert(c.mover === true && !c.boss, '绕行者 = 非窟主绕位怪'); + // 回合 1:命中 + 面对格挡 + mark = audio.log.length; + g(game, 'tap'); + await tick(300); + assert(c.hp === 8, '绕行者命中(10→8)'); + g(game, 'doubletap'); // 前摇窗口内 + await tick(2600); + assert(game.run.hp === hpP - 3, `面对格挡 ×0.2(atk2 → 0 伤,HP 不变 ${game.run.hp})`); + assert(game.run.parries === 2, 'parries 2(鼠 1 + 绕行者 1)'); + // 回合 2:命中 → 敌每 2 回合移动 → 换位右侧(脚步报位) + mark = audio.log.length; + g(game, 'tap'); + await tick(300); + assert(c.hp === 6, '再命中(8→6)'); + assert(plays(audio, mark).has('sfx/mob_step_bug_r'), '换位脚步 mob_step_bug_r(右侧)'); + assert(c.bearing === (DIR_ORDER.indexOf(game.run.face) + 1) % 4, '绕位怪换位到右侧'); + await tick(1400); // 不格挡 → 右侧全额 2 伤 + assert(game.run.hp === hpP - 5, `右侧未格挡全额(→${game.run.hp})`); + // 回合 3:它在右 → 挥空(归因真话)→ 转身追击 + g(game, 'tap'); + await tick(300); + assert(plays(audio, mark).has('voice/btl_swish'), '侧位挥空播 btl_swish(挥空归因真话)'); + await tick(2200); // swish(1s) → 敌回合(不动)→ 前摇 1.2s → resolve 全额 2 伤 + assert(game.run.hp === hpP - 7, `挥空回合挨刀(→${game.run.hp})`); + // 回合 4:转身追击命中 → 敌绕到背后(中央资产音量减半) + g(game, 'right'); + await tick(120); + assert(c.bearing === DIR_ORDER.indexOf(game.run.face), '转身后绕位怪回到面前(追击达成)'); + mark = audio.log.length; + g(game, 'tap'); + await tick(300); + assert(c.hp === 4, '转身追击命中(6→4)'); + { + const mv = audio.log.slice(mark).find((e) => e.id === 'sfx/mob_step_bug_c'); + assert(mv && Math.abs(mv.opts.volume - 0.45) < 1e-9, '敌绕到背后:中央脚步资产音量减半 0.45'); + } + game.run.hp = 20; // 钩子:防死,专测背刺归因 + await tick(2400); // resolve(+1.2s) → btl_behind 链(1s) → 玩家回合 + assert(plays(audio, mark).has('voice/btl_behind'), '背后挨刀播 btl_behind(归因真话)'); + assert(game.run.hp === 18, `背刺全额 2 伤(20→${game.run.hp})`); + // 回合 5:转身两格重新面对 → 前摇内格挡 ×0.2 + g(game, 'right'); + g(game, 'right'); + await tick(120); + assert(c.bearing === DIR_ORDER.indexOf(game.run.face), '两转追击重新面对'); + c.hp = 6; // 钩子:留血做转身格挡 + g(game, 'tap'); + await tick(300); + g(game, 'doubletap'); + await tick(2600); + assert(game.run.hp === 18, `转身后格挡 ×0.2(0 伤,${game.run.hp})`); + // 收尾击杀:敌回合 6 换位到左 → 转身对准 → 击杀(loot 档 2 + meta.gold +10) + g(game, 'tap'); + await tick(300); + await tick(1600); // 敌回合 6 → 换位左 → 全额 2 + g(game, 'left'); // 追击转身对准 + await tick(120); + assert(c.bearing === DIR_ORDER.indexOf(game.run.face), '换位后追击对准'); + g(game, 'tap'); + await tick(4500); // 击杀 → 胜利链 → EXPLORE + assert(game.state === 'EXPLORE', '绕行者击杀回探索态'); + assert(lastBgmEntry(audio)?.id === 'bgm/explore', 'R2 D1-4:胜利回探索态切回 bgm/explore'); + assert(plays(audio, mark).has('voice/btl_kill') && plays(audio, mark).has('voice/btl_loot_2'), '胜利链 btl_kill + btl_loot_2'); + assert(game.meta.gold === 10, '击杀金币入元进度(10 金)'); + + // ---- Test C:窟主石甲蟹(Boss 绕位) ---- + game._encounter({ type: 'crab', x: game.run.pos.x, y: game.run.pos.y, hp: 20, maxHp: 20 }); + await tick(2500); + const cb = game._battle; + assert(cb.boss === true && cb.mover === true, '窟主 = Boss + 绕位'); + assert(plays(audio).lastBgm('bgm/boss'), 'Boss 战 bgm/boss'); + mark = audio.log.length; + const vibC = vibrateLog.length; + g(game, 'longpress'); // 尝试逃跑 + await tick(1600); + assert(plays(audio, mark).has('voice/btl_flee_boss'), 'Boss 战长按 → btl_flee_boss 拒绝'); + assert(vibratedError(vibC), '拒绝伴随 error 震'); + assert(game.state === 'BATTLE' && cb.state === 'player', '拒绝后回合不消耗,仍在玩家回合'); + // 半血:ATK+1 + btl_half_crab + cb.hp = 10; // 钩子:一击跨半 + mark = audio.log.length; + g(game, 'tap'); + await tick(300); + assert(cb.hp === 8 && cb.atk === 4, '半血阶段 ATK 3→4'); + assert(plays(audio, mark).has('voice/btl_half_crab'), '半血播 btl_half_crab'); + await tick(3000); // half 句 → 敌前摇 → 全额 4 伤 + assert(game.run.hp === 12, `窟主一击 4 伤(16→${game.run.hp})`); + cb.hp = 1; // 钩子:收尾 + g(game, 'tap'); + await tick(4500); + assert(game.state === 'EXPLORE', '击杀窟主回探索态'); + assert(game.run.cleared.includes(game.run.floor), '窟主已清(楼梯解锁)'); + assert(game.meta.gold === 60 && plays(audio, mark).has('voice/btl_loot_3'), '窟主 50 金 → loot 档 3'); + game.stop(); +} + +// ================================ 场景 6:死亡归因真话 + 元进度遗产(§7.4/§11/§12.7) ================================ + +async function scenario6a() { + origConsoleLog('\n=== 场景 6a:死于窟主 → 归因真话 + 遗产购置 + 新周目继承 ==='); + let p; + storageMap.clear(); + storageMap.set('soundgame.auth', JSON.stringify({ token: 'test-token' })); + const { game, audio } = newGame({ seed: 7 }); + game.start(); + await tick(400); + // TITLE 上滑:空档案 → meta_gold_1 + st_best_1 + unlock_none + let mark = audio.log.length; + g(game, 'up'); + await tick(4000); + assert(plays(audio, mark).orderOk('voice/meta_gold_1', 'voice/st_best_1', 'voice/unlock_none'), 'TITLE 元进度链(空档 → unlock_none)'); + g(game, 'tap'); + await tick(6000); // → DRILL + g(game, 'down'); + await tick(2000); // skip → EXPLORE + game._mobStep = () => {}; + game.meta.gold = 500; // 钩子:遗产阈值(90+150+220=460 ≤ 500 < 760) + game.run.hp = 3; // 钩子:Boss 一刀致命 + game._encounter({ type: 'crab', x: game.run.pos.x, y: game.run.pos.y, hp: 20, maxHp: 20 }); + await tick(2500); + mark = audio.log.length; + g(game, 'tap'); // 挥击(20→18) + await tick(300); + game.run.parries = 2; // 钩子:挡过但没跑过 → die_adv_2(打不过就逃) + game.run.flees = 0; + await tick(1400); // 敌前摇 → 未格挡 → dmg3 → HP0 → 死 + assert(game.state === 'SETTLE', 'HP≤0 → 死亡结算 SETTLE'); + await tick(8000); // 死亡链 7 句播完 + p = plays(audio, mark); + assert(p.orderOk('voice/die_intro', 'voice/die_cause_2', 'voice/die_adv_2', 'voice/st_floor_1', + 'voice/meta_gold_1', 'voice/settle_stronger', 'voice/settle_again'), + '死亡链序:die_intro → die_cause_2(Boss 重击未格挡)→ die_adv_2 → st_floor → meta_gold → settle_stronger → settle_again'); + assert(game._deathInfo.lastHit.type === 'crab' && game._deathInfo.cause === 2, + `归因指认真凶:lastHit=${game._deathInfo.lastHit.type} → die_cause_2`); + assert(game.meta.gold === 40, `遗产自动购置 90+150+220(500→${game.meta.gold})`); + { + const band = game._goldBand(game.meta.gold); + assert(plays(audio, mark).has('voice/meta_gold_' + band), + `R2 D1-5:SETTLE 金币档与 TITLE 同分档函数(_goldBand(meta.gold)=${band})`); + } + assert(JSON.stringify(game.meta.unlocks) === JSON.stringify(['potion', 'vigor', 'veil']), '解锁 potion/vigor/veil'); + assert(game.meta.bestFloor === 1 && game.meta.runs === 1, 'bestFloor/runs 更新'); + assert(game.run === null, '死亡清 run 不清 meta'); + { + const save = JSON.parse(storageMap.get('soundgame.progress.dungeon')); + assert(save.summary.startsWith('深处归来'), `死亡 summary 规则("${save.summary}")`); + assert(save.meta.gold === 40 && save.run === null, '存档 run=null、meta 保留'); + assert(fetchLog.some((f) => f.url === '/api/progress' && f.opts.method === 'PUT'), '死亡结算 PUT /api/progress'); + } + assert(plays(audio).lastBgm('bgm/menu') && plays(audio).lastBgm('bgm/menu').opts.volume === 0.5, '结算 bgm/menu volume 0.5'); + assert(audio.rateLog.at(-1).on === true, 'E3:结算恢复快进'); + // SETTLE 双击 = 重听结算链(整链) + mark = audio.log.length; + g(game, 'doubletap'); + await tick(8000); + assert(plays(audio, mark).orderOk('voice/die_intro', 'voice/die_cause_2', 'voice/settle_again'), 'SETTLE 双击重听整条结算链'); + g(game, 'tap'); // → TITLE(元进度入账后) + await tick(1500); + assert(game.state === 'TITLE' && !game.run, 'SETTLE 单击 → TITLE'); + // 新周目:seed 重掷 + 遗产生效播报 + mark = audio.log.length; + g(game, 'tap'); + await tick(14000); + assert(game.state === 'EXPLORE' && game.run.seed !== 7, '新周目 seed ≠ 旧 seed'); + assert(game._maxHp() === 26 && game.run.hp === 26, '体魄遗产:起始 maxHp 20→26'); + assert(game.run.potion === 1, '行囊药遗产:开局自带 1 瓶'); + p = plays(audio, mark); + assert(p.orderOk('voice/sys_new_run', 'voice/unlock_lead', 'voice/unlock_potion', 'voice/unlock_vigor', 'voice/unlock_veil'), + '新周目开场遗产播报(unlock_lead + 3 项)'); + assert(game.meta.pending.length === 0, '播报后清 pending'); + assert(game.meta.drillDone === true, '二周目不再进 DRILL'); + assert(p.orderOk('voice/st_floor_1', 'voice/st_hp_1', 'voice/st_face_n'), '新周目局面复述(层/血/面朝)'); + assert(p.countRe(/^voice\/st_threat_/) === 1, '局面复述含威胁段'); + return { game, audio }; +} + +async function scenario6b() { + origConsoleLog('\n=== 场景 6b:死于小怪 → die_cause_1(普通怪连续失血) ==='); + let p; + storageMap.clear(); + const { game, audio } = newGame({}); + game.start(); + await tick(400); + g(game, 'tap'); + await tick(6000); + g(game, 'down'); + await tick(2000); + game._mobStep = () => {}; + game.run.hp = 1; // 钩子:鼠刀致命 + game._encounter({ type: 'rat', x: game.run.pos.x, y: game.run.pos.y, hp: 6, maxHp: 6 }); + await tick(2500); + let mark = audio.log.length; + g(game, 'tap'); // 挥击 + await tick(1700); // 敌 dmg1 → HP0 → 死 + assert(game.state === 'SETTLE', '小怪磨死 → SETTLE'); + await tick(8000); + p = plays(audio, mark); + assert(p.orderOk('voice/die_intro', 'voice/die_cause_1', 'voice/die_adv_1', 'voice/st_floor_1'), '死亡链:die_cause_1 + die_adv_1(全程零格挡)'); + assert(game._deathInfo.lastHit.type === 'rat' && game._deathInfo.cause === 1, `归因指认真凶:lastHit=${game._deathInfo.lastHit.type} → die_cause_1`); + game.stop(); +} + +async function scenario6c() { + origConsoleLog('\n=== 场景 6c:死于背刺 → die_cause_3 + btl_behind 真话 ==='); + let p; + storageMap.clear(); + const { game, audio } = newGame({}); + game.start(); + await tick(400); + g(game, 'tap'); + await tick(6000); + g(game, 'down'); + await tick(2000); + game._mobStep = () => {}; + game._encounter({ type: 'crawler', x: game.run.pos.x, y: game.run.pos.y, hp: 10, maxHp: 10 }); + await tick(2500); + game._battle.bearing = (DIR_ORDER.indexOf(game.run.face) + 2) % 4; // 钩子:它已绕到背后 + game.run.hp = 1; // 钩子:背刺刀致命 + let mark = audio.log.length; + g(game, 'tap'); // 挥空(它在背后) + await tick(300); + assert(plays(audio, mark).has('voice/btl_swish'), '背后挥空播 btl_swish'); + await tick(2600); // swish(1s) → 敌前摇 1.2s → 背后挨刀 dmg2 → 死 + assert(game.state === 'SETTLE', '背刺致死 → SETTLE'); + await tick(8000); + p = plays(audio, mark); + assert(p.orderOk('voice/die_intro', 'voice/die_cause_3', 'voice/die_adv_3', 'voice/st_floor_1'), '死亡链:die_cause_3 + die_adv_3(身后有动静先转身)'); + assert(game._deathInfo.lastHit.behind === true && game._deathInfo.cause === 3, '归因指认真凶:背刺 → die_cause_3'); + game.stop(); +} + +// ================================ 场景 7:菜单暂停/恢复(E4,§2.4/§12.8) ================================ + +async function scenario7({ game, audio }) { + origConsoleLog('\n=== 场景 7:菜单=暂停(探索游走冻结 / 战斗前摇冻结、恢复无重复回合) ==='); + // 探索菜单冻结(第二周目 L1,真怪游走中)。安全步进器:真游走+真脚步,不扑击 + // 不进玩家格(贴身扑击由场景 12C 专项负责,此处断言不被随机接战打断) + game._mobStep = (m) => { + const S = game._floor.size; + const opts = []; + for (const d of Object.values(DIRS)) { + const nx = m.x + d.dx, ny = m.y + d.dy; + if (nx >= 0 && ny >= 0 && nx < S && ny < S && game._floor.grid[ny][nx] === 1 + && !(nx === game.run.pos.x && ny === game.run.pos.y)) opts.push({ x: nx, y: ny }); + } + if (!opts.length) return; + const c = opts[Math.floor(Math.random() * opts.length)]; + m.x = c.x; m.y = c.y; + game._mobStepSound(m.type, m.x, m.y); + }; + let mark = audio.log.length; + g(game, 'down'); + await tick(1500); + assert(game.menuOpen && game.menuFrom === 'EXPLORE', '探索下滑=菜单'); + await tick(5000); + const inMenu = audio.log.slice(mark).filter((e) => e.id.startsWith('sfx/mob_step_')).length; + assert(inMenu === 0, '菜单打开期间怪物游走冻结'); + g(game, 'down'); + await tick(6000); + const resumed = audio.log.slice(mark).filter((e) => e.id.startsWith('sfx/mob_step_')).length; + assert(resumed > 0, '关菜单后游走恢复'); + + // 战斗前摇暂停:窗口剩余量冻结,恢复后恰结算一次 + game._mobStep = () => {}; + game.run.veilUsedFloor = true; // 钩子:护符本层已耗(6a 已验证减半),此处验证账目 + game._encounter({ type: 'rat', x: game.run.pos.x, y: game.run.pos.y, hp: 6, maxHp: 6 }); + await tick(2500); + const b = game._battle; + g(game, 'tap'); // 命中(6→4)→ 敌前摇 + await tick(300); + assert(b.state === 'telegraph', '进入敌方前摇'); + const hp0 = game.run.hp; + const tel0 = plays(audio).count('sfx/tel_small'); + g(game, 'down'); // 前摇中开菜单(E4:战斗菜单=暂停) + await tick(120); + assert(game.menuOpen && game._menuPause != null, '前摇剩余窗口记入暂停账'); + await tick(5000); // 远超 1.2s 窗口 + assert(game.run.hp === hp0 && b.state === 'telegraph', '前摇冻结:超时不结算、不推进'); + assert(audio.rateLog.at(-1).on === true, 'E3:菜单恢复快进'); + g(game, 'down'); // 关菜单 + await tick(1800); + assert(!game.menuOpen, '菜单关闭'); + assert(game.run.hp === hp0 - 1, `恢复后恰结算一次(${hp0}→${game.run.hp})`); + assert(plays(audio).count('sfx/tel_small') === tel0, '前摇音不重播(无重复回合)'); + await tick(300); + assert(b.state === 'player', '回到玩家回合(敌方仅行动一次)'); + // 菜单帮助 + 保存并回大厅 + mark = audio.log.length; + g(game, 'down'); + await tick(1500); + g(game, 'up'); + await tick(4000); + assert(plays(audio, mark).orderOk('voice/help_explore', 'voice/help_battle', 'voice/settings_hint'), '菜单上滑=帮助链'); + g(game, 'tap'); // resume → save + await tick(1300); + assert(plays(audio, mark).has('voice/sys_menu_save'), '单击循环切到「保存并回大厅」'); + g(game, 'doubletap'); + await tick(3000); + assert(plays(audio, mark).has('voice/sys_saved'), '确认保存播 sys_saved'); + assert(window.location.href === '../', '保存并回大厅 → 跳转大厅'); +} + +// ================================ 场景 8:中断复述 + TITLE 右滑重掷新周目(§11/§12.9) ================================ + +async function scenario8() { + origConsoleLog('\n=== 场景 8:中断复述链 + TITLE 右滑重掷新周目 ==='); + let p; + storageMap.clear(); + const a1 = newGame({ seed: 55 }); + a1.game.start(); + await tick(400); + g(a1.game, 'tap'); + await tick(6000); + g(a1.game, 'down'); + await tick(2000); + a1.game._mobStep = () => {}; + // 挪到「南边有地板」的格子并转身朝南、走 1 格 + const fl = a1.game._floor; + let spot = null; + outer: for (let y = 0; y < fl.size - 1; y++) for (let x = 0; x < fl.size; x++) { + if (fl.grid[y][x] === 1 && fl.grid[y + 1][x] === 1) { spot = { x, y }; break outer; } + } + a1.game.run.pos = { ...spot }; + g(a1.game, 'right'); + g(a1.game, 'right'); + await tick(200); + assert(a1.game.run.face === 'S', '面朝南'); + g(a1.game, 'longpress'); + await tick(80); + g(a1.game, 'longpressend'); + await tick(200); + const savedPos = { ...a1.game.run.pos }; + a1.game._save(); // 钩子:等同菜单"保存并回大厅"(走位本身不触发存档,§11 保存时机) + a1.game.stop(); + + // 实例 2:杀进程重进 → sys_continue_ask → 单击继续 → 局面复述链 + const a2 = newGame({}); + a2.game.start(); + await tick(1500); + assert(a2.game.state === 'TITLE' && plays(a2.audio).has('voice/sys_continue_ask'), '有档重进播 sys_continue_ask'); + let mark = a2.audio.log.length; + g(a2.game, 'tap'); + await tick(8000); + assert(a2.game.state === 'EXPLORE', '读档回探索态'); + assert(a2.game.run.face === 'S' && a2.game.run.pos.x === savedPos.x && a2.game.run.pos.y === savedPos.y, + '落点还原(pos/face)'); + p = plays(a2.audio, mark); + assert(p.orderOk('voice/st_floor_1', 'voice/st_hp_1', 'voice/st_face_s'), '局面复述链 st_floor → st_hp → st_face 有序'); + assert(p.countRe(/^voice\/st_threat_/) === 1, '局面复述链第 4 段威胁句'); + assert(a2.audio.rateLog.at(-1).on === false, 'E3:对局期快进关闭'); + // 上滑同链可重放 + mark = a2.audio.log.length; + g(a2.game, 'up'); + await tick(6000); + assert(plays(a2.audio, mark).orderOk('voice/st_floor_1', 'voice/st_hp_1', 'voice/st_face_s'), '上滑同链可重放'); + a2.game.stop(); + + // 实例 3:TITLE 右滑 = 重掷新周目(元进度保留) + const a3 = newGame({}); + a3.game.start(); + await tick(1500); + assert(plays(a3.audio).has('voice/sys_continue_ask'), '实例 3 仍在 TITLE 询问继续'); + const oldSeed = JSON.parse(storageMap.get('soundgame.progress.dungeon')).run.seed; + const oldKills = JSON.parse(storageMap.get('soundgame.progress.dungeon')).meta.kills; + mark = a3.audio.log.length; + g(a3.game, 'right'); + for (let i = 0; i < 120 && a3.game.state !== 'EXPLORE'; i++) { + await tick(100); + if (a3.game.run) a3.game._mobStep = () => {}; // 静场:新周目 L1 游走怪不干扰状态断言 + } + assert(a3.game.state === 'EXPLORE' && a3.game.run, 'TITLE 右滑 → 直接进新周目探索(已学过教学)'); + assert(a3.game.run.seed !== oldSeed, `重掷 seed(${oldSeed} → ${a3.game.run.seed})`); + assert(a3.game.meta.kills === oldKills, '元进度保留'); + assert(plays(a3.audio, mark).orderOk('voice/sys_new_run', 'voice/st_floor_1'), '新周目播报 + 局面复述'); + a3.game.stop(); +} + +// ================================ 场景 9:卡关三级提示(§10.3,不报答案) ================================ + +async function scenario9() { + origConsoleLog('\n=== 场景 9:卡关三级 60/120/180s(暗示→明示→带路)+ 队列礼仪 ==='); + storageMap.clear(); + const { game, audio } = newGame({ seed: 11 }); + game.start(); + await tick(400); + g(game, 'tap'); + await tick(6000); + g(game, 'down'); + await tick(2000); + assert(game.state === 'EXPLORE', '进探索态'); + game._mobStep = () => {}; // 静场 + const stuck = game._stuck; + + // L1 暗示(60s 无进展) + stuck.lastAct = vnow - 60000; + let mark = audio.log.length; + const vibS9 = vibrateLog.length; + await tick(6000); // 5s 轮询 + assert(plays(audio, mark).has('voice/stair_hint_a'), '60s → stair_hint_a(L1 无窟主 → 铁器暗示,不报答案)'); + assert(stuck.shown.has('L1'), 'L1 级已记'); + { + const warn = vibrateLog.slice(vibS9).some((v) => JSON.stringify(v.p) === JSON.stringify([30, 50, 30, 50, 30])); + assert(warn && !vibratedError(vibS9), 'L1 提示附 warning 震(非 error)'); + } + + // L2 明示(无进展满 120s) + stuck.lastAct = vnow - 120000; + mark = audio.log.length; + await tick(6000); + assert(plays(audio, mark).has('voice/stair_dir_' + game._stuckStairDir()), `120s → stair_dir_*(明示绝对方位=${game._stuckStairDir()})`); + assert(stuck.shown.has('L2'), 'L2 级已记'); + + // L3 带路(无进展满 180s):进入导航模式;R2 D1-3 起带路=最短路逐步校正 + //(不再直线差值指墙),同向短窗不重复播(保险丝友好) + stuck.lastAct = vnow - 180000; + mark = audio.log.length; + await tick(6000); + assert(stuck.nav === true, '180s → 进入导航模式'); + assert(plays(audio, mark).countRe(/^voice\/nav_[eswn]$/) === 1, '带路首报=最短路第一步方向(恰 1 句)'); + { + const st2 = game._floor.stairs; + const dir1 = game._navFirstStepDir(st2); + assert(dir1 && plays(audio, mark).has('voice/nav_' + dir1.toLowerCase()), + `带路方向与 grid 最短路一致(第一步朝 ${dir1})`); + const nb = DIRS[dir1]; + assert(game._floor.grid[game.run.pos.y + nb.dy]?.[game.run.pos.x + nb.dx] === 1, + 'R2 D1-3:带路方向第一步必可通行(不再指墙)'); + } + const path = bfsPath(game._floor, game.run.pos, game._floor.stairs, mobCells(game._floor)); + assert(path.length > 0, '带路可达楼梯'); + mark = audio.log.length; + await walkPath(game, path); + { + // 每步校正口径:改向必播(首报已在触发句计入节流)、同向 ≥1.5s 提醒、末步 nav_done + let turns = 0; + for (let i = 1; i < path.length; i++) if (path[i].dir !== path[i - 1].dir) turns++; + const navN = plays(audio, mark).countRe(/^voice\/nav_[eswn]$/); + assert(navN >= turns && navN <= turns + path.length, + `导航逐步校正:每个改向都有报(转向 ${turns} 次,实播 ${navN} 句,有界不刷屏)`); + } + assert(plays(audio, mark).has('voice/nav_done'), '抵达楼梯播 nav_done'); + { + // 节流单元断言:同向 1.5s 内重复调用只播一次 + game.run.pos = { ...game._floor.start }; + game._stuck.nav = true; + game._navLastDir = null; + game._navLastAt = 0; + const m0 = audio.log.length; + game._navSpeakStep(); + game._navSpeakStep(); + game._navSpeakStep(); + assert(plays(audio, m0).countRe(/^voice\/nav_[eswn]$/) === 1, '导航节流:同向连呼 3 次只播 1 句'); + await tick(1600); + game._navSpeakStep(); + assert(plays(audio, m0).countRe(/^voice\/nav_[eswn]$/) === 2, '导航节流:超过 1.5s 同向提醒播第 2 句'); + game._stuck.nav = false; + } + assert(!game._stuck.nav && stuck.shown.size === 0, '抵达=进展事件 → 导航退出、分级重置'); + + // 进展事件重置分级:重置后再静默 60s → 从 L1 重新开始(而非直接 L2) + stuck.lastAct = vnow - 60000; + mark = audio.log.length; + await tick(6000); + assert(plays(audio, mark).has('voice/stair_hint_a') && !stuck.shown.has('L2'), + '进展事件后重置分级:下段静默期从 L1 重新触发'); + + // 队列礼仪:语音在播时入队不顶句;玩家操作作废未播级别;未播级别可重新触发 + stuck.shown.clear(); // 钩子:清分级,专测 L1 的入队路径 + g(game, 'up'); // 状态链在播(5 句) + await tick(100); + stuck.lastAct = vnow - 60000; + const qMark = audio.log.length; + game._stuckCheck(); // 钩子直呼检查(13 文档允许钩子模拟"语音播放中到点") + assert(stuck.pending.length >= 1, '语音在播 → 提示入待播队列(不顶句)'); + assert(plays(audio, qMark).count('voice/stair_hint_a') === 0, '待播期间未抢播'); + g(game, 'tap'); // 玩家操作 + assert(stuck.pending.length === 0, '玩家操作作废未播提示'); + assert(!stuck.shown.has('L1'), '未播级别不计入 shown(可重新触发)'); + await tick(62000); + assert(plays(audio).has('voice/stair_hint_a'), '作废级别在下段静默期重新触发(60s 如期)'); + game.stop(); +} + +// ================================ 场景 10:向导看门狗(guide=full,§10.2) ================================ + +async function scenario10() { + origConsoleLog('\n=== 场景 10:向导看门狗(guide=full:探索 10s / 战斗 15s / 教学 8s 纯 nudge) ==='); + storageMap.clear(); + settings.set({ guide: 'full' }); + try { + const { game, audio } = newGame({}); + const events = []; + game.guide.setEventHook((e) => events.push(e)); + game.start(); + await tick(400); + g(game, 'tap'); + await tick(5500); // sys_new_run + D0 三句 + // DRILL 每步等待 [8s]:纯 nudge 无语音(教学句不重播) + let mark = audio.log.length; + const ev0 = events.length; + await tick(9000); + assert(events.slice(ev0).some((e) => e.type === 'watchdog'), 'DRILL 看门狗到点触发(nudge 事件)'); + assert(audio.log.slice(mark).every((e) => !e.id.startsWith('voice/gd_')), '教学看门狗纯 nudge:无 gd_* 语音'); + g(game, 'down'); + await tick(2000); // skip → EXPLORE + game._mobStep = () => {}; + // 探索静止 [10s, 25s]:第 1 级播 gd_pulse + mark = audio.log.length; + await tick(11000); + assert(audio.log.slice(mark).some((e) => e.id === 'voice/gd_pulse'), '探索 10s 看门狗播 gd_pulse'); + // R2 D1-1:菜单关闭回执=状态签名(开=menu 签名,关=回到探索的 explore 签名) + { + const stEv = () => events.filter((e) => e.type === 'state').map((e) => e.name); + g(game, 'down'); // 开菜单 + await tick(1500); + g(game, 'down'); // 关菜单 + await tick(1500); + const names = stEv(); + assert(names[names.length - 2] === 'menu' && names[names.length - 1] === 'explore', + '菜单关闭播回程状态签名(menu → explore,盲手可感知开关)'); + } + // R2 D1-2:D9 看门狗兜底(8s nudge、30s 升级播楼梯暗示——自寻路步必须有救援) + { + const realState = game.state; + const realDrill = game._drill; + game.state = 'DRILL'; + game._drill = { step: 9 }; + game._drillWatchdog9(); + const ev0 = events.length; + mark = audio.log.length; + await tick(31000); + assert(events.slice(ev0).filter((e) => e.type === 'watchdog').length === 2, 'D9 看门狗两级都触发(8s nudge + 30s 升级)'); + assert(audio.log.slice(mark).some((e) => e.id === 'voice/stair_hint_a'), 'D9 30s 升级播楼梯暗示(兜底救援)'); + game.state = realState; + game._drill = realDrill; + } + // 战斗玩家回合 [15s, 30s]:第 1 级播 gd_turn + game._encounter({ type: 'rat', x: game.run.pos.x, y: game.run.pos.y, hp: 6, maxHp: 6 }); + await tick(2500); + assert(game._battle.state === 'player', '战斗玩家回合'); + mark = audio.log.length; + await tick(16000); + assert(audio.log.slice(mark).some((e) => e.id === 'voice/gd_turn'), '战斗回合 15s 看门狗播 gd_turn'); + assert(audio.log.slice(mark).some((e) => e.id === 'sfx/turn_tick'), + 'R2 D1-6:回合 15s 播合成回执音 turn_tick(standard 档无语音也有确认)'); + assert(game.state === 'BATTLE', '看门狗只提示不接管(仍在战斗)'); + } finally { + settings.set({ guide: 'off' }); // 复位:其余场景契约 guide=off + } +} + +// ================================ 场景 11:屏蔽 voice 不卡死(§12 边界) ================================ + +async function scenario11() { + origConsoleLog('\n=== 场景 11:屏蔽任意 voice 资产 → 流程不卡死(看门狗/兜底推进) ==='); + storageMap.clear(); + const { game, audio } = newGame({ seed: 3 }); + audio.blocked.add('voice/evt_descend'); + audio.blocked.add('voice/btl_rat'); + game.start(); + await tick(400); + g(game, 'tap'); + await tick(6000); + g(game, 'down'); + await tick(2000); + game._mobStep = () => {}; + // 屏蔽遭遇报名 → _speakGuarded 10s 兜底照样进玩家回合 + game._encounter({ type: 'rat', x: game.run.pos.x, y: game.run.pos.y, hp: 6, maxHp: 6 }); + let t0 = vnow; + while (!(game._battle && game._battle.state === 'player') && vnow - t0 < 15000) await tick(500); + assert(game._battle && game._battle.state === 'player', 'btl_rat 被屏蔽:10s 兜底进玩家回合(不卡死)'); + game._battle.hp = 0; // 钩子:一刀结束 + g(game, 'tap'); + await tick(4000); + assert(game.state === 'EXPLORE', '胜利链走完回探索'); + // 屏蔽下潜旁白 → 8s 兜底照样完成下潜 + const path = bfsPath(game._floor, game.run.pos, game._floor.stairs, mobCells(game._floor)); + await walkPath(game, path); + assert(game._onStairs === true, '踏上楼梯'); + g(game, 'tap'); + await tick(13000); + assert(game.run.floor === 2, 'evt_descend 被屏蔽:8s 兜底完成下潜(不卡死)'); + audio.blocked.clear(); + game.stop(); +} + +// ================================ 场景 12:R2 结构修复回归(D0-1 保险丝/全网格回声/密度/贴身接战) ================================ + +async function scenario12() { + origConsoleLog('\n=== 场景 12:R2 结构修复回归(风暴保险丝 + 全网格回声一致性 + 贴身接战 + 60s 调度上界) ==='); + + // ---- A. 引擎风暴保险丝单元断言(真实 WebAudioEngine,D0-1②) ---- + { + const { WebAudioEngine } = await import('../../engine/audio.js'); + const eng = new WebAudioEngine('audio/'); + eng.unlocked = true; + const calls = []; + eng._playVoice = (id) => calls.push('v:' + id); + eng._playBgm = (id) => calls.push('b:' + id); + eng._playSfx = (id) => calls.push('s:' + id); + const t0 = vnow; + for (let i = 0; i < 4; i++) eng.play('voice/spam_id'); + assert(calls.length === 4, '保险丝:同 id 3s 窗口内前 4 次调度放行'); + eng.play('voice/spam_id'); + eng.play('voice/spam_id'); + assert(calls.length === 4, '保险丝:第 5 次起同 id 调度被丢弃(不进播放层)'); + eng.play('voice/other_id'); + assert(calls.length === 5, '保险丝:只熔断超阈 id,其他 id 不受累'); + await tick(3100); + assert(vnow - t0 >= 3100, '虚拟时钟推进过熔断窗口'); + eng.play('voice/spam_id'); + assert(calls.length === 6, '保险丝:窗口滑出后恢复放行'); + eng.play('bgm/whatever'); + eng.play('sfx/whatever'); + assert(calls.length === 8, '保险丝:只作用于 voice 层(bgm/sfx 不计)'); + } + + // ---- B. 全网格回声一致性(seed=42 全图 L1..L7,D0-2) ---- + { + storageMap.clear(); + const { game } = newGame({ seed: 42 }); + game.start(); + await tick(400); + g(game, 'tap'); + await tick(400); + assert(game.run, '场景12B: 新周目就绪'); + let combos = 0; + let sideOpens = 0; + for (let f = 1; f <= 7; f++) { + const fl = game._generateFloor(42, f); + game._floor = fl; + for (let y = 0; y < fl.size; y++) for (let x = 0; x < fl.size; x++) { + if (fl.grid[y][x] !== 1) continue; + const per = []; + for (const face of DIR_ORDER) { + game.run.pos = { x, y }; + game.run.face = face; + const truth = truthScan(fl, x, y, face); + const got = game._echoScan(x, y, face); + const L = game._sideScan(-1); + const R = game._sideScan(1); + if (L.kind === 'open') sideOpens++; + const okCell = got.dist === truth.dist && got.mat === truth.mat + && L.kind === truth.left.kind && R.kind === truth.right.kind; + per.push(`${face}:${okCell ? 'ok' : `got(d${got.dist},${got.mat},${L.kind}/${R.kind}) want(d${truth.dist},${truth.mat},${truth.left.kind}/${truth.right.kind})`}`); + } + assert(per.every((s) => s.endsWith('ok')), `回声===grid @L${f}(${x},${y}) ${per.join(' ')}`); + combos++; + } + } + assert(combos >= 200, `全网格回声扫描覆盖 ${combos} 格 × 4 面`); + assert(sideOpens > 0, `全网格存在侧向开口(side_open 真话,R1 全程 0 次 → 现扫到 ${sideOpens} 格次)`); + // 楼梯脚下=贴身铁器(mat_stair 教学承诺兑现) + for (let f = 1; f <= 6; f++) { + const fl = game._generateFloor(42, f); + game._floor = fl; + const st = fl.stairs; + game.run.pos = { x: st.x, y: st.y }; + game.run.face = 'N'; + const got = game._echoScan(st.x, st.y, 'N'); + assert(got.dist === 1 && got.mat === 'stair', `L${f}: 站楼梯上脉冲=贴身铁器回声(dist1/mat_stair)`); + } + game.stop(); + } + + // ---- C. 贴身即接战(D1-3 蘑菇软封锁修复)+ 纯探索 60s 调度上界 + 卡关自然触发 ---- + storageMap.clear(); + { + const { game, audio } = newGame({ seed: 5 }); + game.start(); + await tick(400); + g(game, 'tap'); + await tick(6000); + g(game, 'down'); + await tick(2000); + assert(game.state === 'EXPLORE', '场景12C: 跳过教学进探索'); + game._mobStep = () => {}; // 静场 + // 贴身即接战:怪与玩家正交相邻 → 它的下一次移动必然扑进玩家格 + const fl = game._floor; + let pair = null; + outer12: for (let y = 0; y < fl.size; y++) for (let x = 0; x < fl.size - 1; x++) { + if (fl.grid[y][x] === 1 && fl.grid[y][x + 1] === 1) { pair = { x, y }; break outer12; } + } + game.run.pos = { x: pair.x, y: pair.y }; + game.run.face = 'E'; + const mob = { type: 'mush', x: pair.x + 1, y: pair.y, hp: 8, maxHp: 8, drill: false }; + game._floor.mobs.push(mob); + DungeonGame.prototype._mobStep.call(game, mob); // 直呼真实步进(绕开静场 stub) + assert(game.state === 'BATTLE' && game._battle?.type === 'mush', + '贴身即接战:相邻怪必然扑进玩家格开战(软封锁消除)'); + game._mobStep = () => {}; + { + const t0b = vnow; + while (game._battle?.state !== 'player' && vnow - t0b < 12000) await tick(200); + game._battle.hp = 0; // 钩子:一刀结束,继续后续断言 + g(game, 'tap'); // 玩家回合挥击 → 胜利 + await tick(4500); + } + assert(game.state === 'EXPLORE', '贴身战胜后回探索'); + // 纯探索 60s 零操作:语音调度总量有上界(D0-1 回归断言:无互抢风暴), + // 且卡关 L1 暗示在真实计时器下自然触发(D0-3④:不 hook lastAct) + game._floor.mobs.length = 0; + const m0 = audio.log.length; + const t0 = vnow; + await tick(62000); + const seg = audio.log.slice(m0); + const voiceN = seg.filter((e) => e.layer === 'voice').length; + assert(vnow - t0 >= 62000, '场景12C: 60s 虚拟空闲推进'); + assert(voiceN <= 3, `纯探索 60s 零操作语音调度有上界(${voiceN} 句 ≤3;R1 风暴同期 >2000 句)`); + assert(seg.some((e) => e.id === 'voice/stair_hint_a'), '卡关 L1 暗示 60s 自然触发(纯探索、无 hook)'); + assert(seg.filter((e) => e.id === 'sfx/amb_cave').length >= 2, '探索呼吸层 60s 内 ≥2 段环境垫(20s 节奏)'); + game.stop(); + } +} + +// ================================ 资产引用校验 ================================ + +function assetAudit() { + origConsoleLog('\n=== 资产引用校验 ==='); + // 1) 运行期播放/加载过的全部 id 必须存在(blocked 屏蔽除外——那是被测项) + const missing = new Set(); + for (const audio of audioRegistry) { + for (const e of audio.log) if (e.missing) missing.add(e.id); + } + assert(missing.size === 0, `资产校验: 运行期缺失 ${[...missing].join(',') || '无'}`); + + // 2) voice 双性别目录 id 集合一致 + const vdir = path.join(DUNGEON_DIR, 'audio', 'voice'); + const idsOf = (gender) => new Set(fs.readdirSync(path.join(vdir, gender)) + .filter((f) => f.endsWith('.ogg')).map((f) => f.replace(/\.ogg$/, ''))); + const fem = idsOf('female'); + const male = idsOf('male'); + assert(fem.size === male.size && [...fem].every((id) => male.has(id)), + `资产校验: voice 双性别目录集合一致(female ${fem.size} = male ${male.size})`); + assert(fem.size === 125, `资产校验: 台词表 125 句齐全(实际 ${fem.size})`); + + // 3) game.js 静态引用 id 全量展开校验(模板 id 按数据表展开) + const src = fs.readFileSync(path.join(DUNGEON_DIR, 'game.js'), 'utf8'); + const staticIds = new Set(); + for (const m of src.matchAll(/'((?:voice|sfx|bgm)\/[^'`]+)'/g)) { + if (/[/_]$/.test(m[1])) continue; // 模板前缀('sfx/echo_' + band),由下表展开 + staticIds.add(m[1]); + } + for (const f of ['1', '2', '3', '4', '5', '6', '7']) staticIds.add(`voice/st_floor_${f}`); + for (const b of ['1', '2', '3', '4']) { staticIds.add(`voice/st_hp_${b}`); staticIds.add(`voice/st_gold_${b}`); } + for (const d of ['n', 'e', 's', 'w']) { + staticIds.add(`voice/st_face_${d}`); + staticIds.add(`voice/nav_${d}`); + staticIds.add(`voice/stair_dir_${d}`); + } + for (const t of ['none', 'left', 'right', 'both']) staticIds.add(`voice/st_threat_${t}`); + for (const i of ['1', '2', '3']) { + for (const pre of ['voice/st_best_', 'voice/meta_gold_', 'voice/die_cause_', 'voice/die_adv_', 'voice/btl_loot_', 'voice/evt_coin_']) { + staticIds.add(pre + i); + } + } + for (const u of ['lead', 'potion', 'vigor', 'veil', 'echo', 'blade', 'none']) staticIds.add(`voice/unlock_${u}`); + for (const c of ['small', 'bug', 'heavy']) for (const s of ['l', 'c', 'r']) staticIds.add(`sfx/mob_step_${c}_${s}`); + for (const c of ['small', 'bug', 'heavy']) for (const s of ['', '_l', '_r']) staticIds.add(`sfx/tel_${c}${s}`); + for (const k of ['wall', 'open']) for (const s of ['l', 'r']) staticIds.add(`sfx/side_${k}_${s}`); + for (const m of ['bat', 'rat', 'mush', 'crab', 'crawler', 'spider', 'leech', 'guard', 'king']) staticIds.add(`voice/btl_${m}`); + for (const m of ['crab', 'leech', 'king']) staticIds.add(`voice/btl_half_${m}`); + for (const e of ['1', '2', '3', '4']) staticIds.add(`sfx/echo_${e}`); + for (const m of ['stone', 'door', 'stair', 'water']) staticIds.add(`sfx/mat_${m}`); + for (const i of ['sys_menu', 'sys_menu_resume', 'sys_menu_save', 'sys_menu_help', 'sys_menu_restart']) staticIds.add(`voice/${i}`); + for (const i of ['intro_1', 'intro_2', 'sys_continue_ask', 'sys_new_run', 'sys_saved', + 'evt_potion', 'evt_potion_full', 'evt_weapon', 'evt_armor', 'evt_stairs_lock', 'evt_descend', + 'btl_behind', 'btl_swish', 'btl_no_turn', 'btl_kill', 'btl_lvup', 'btl_flee', 'btl_flee_boss', 'btl_parried', + 'die_intro', 'settle_win_1', 'settle_win_2', 'settle_stronger', 'settle_again', + 'help_explore', 'help_battle', 'settings_hint', 'stair_hint_a', 'stair_hint_b', 'nav_done', + 'tut_d0', 'tut_echo_come', 'tut_front_rule', 'tut_side_rule', 'tut_walk', 'tut_turn', 'tut_replay', + 'tut_status', 'tut_menu', 'tut_mob', 'tut_parry', 'tut_parry_ok', 'tut_parry_miss', 'tut_attack', + 'tut_kill', 'tut_stairs', 'tut_stairs_on', 'tut_done', 'tut_skip', + 'gd_invalid', 'gd_pulse', 'gd_turn']) staticIds.add('voice/' + i); + for (const s of ['pulse', 'step_own', 'step_turn', 'bump', 'coin', 'potion', 'gear', 'stairs_down', + 'atk_swing', 'atk_hit', 'parry', 'hurt', 'enemy_die', 'levelup']) staticIds.add('sfx/' + s); + for (const b2 of ['menu', 'explore', 'battle', 'boss']) staticIds.add('bgm/' + b2); + const badStatic = [...staticIds].filter((id) => !assetExists(id)); + assert(badStatic.length === 0, `资产校验: 静态引用缺失 ${badStatic.join(',') || '无'}`); + ok(`资产校验: 静态 ${staticIds.size} id + 运行期日志全量双格式(ogg+m4a)齐全`); +} + +// ================================ 主流程 ================================ + +let s2Ctx = null; +try { + await scenario1(); + s2Ctx = await scenario2(); + await scenario4(s2Ctx); + await scenario5(); + const s6 = await scenario6a(); + await scenario7(s6); + await scenario6b(); + await scenario6c(); + await scenario8(); + await scenario9(); + await scenario10(); + await scenario11(); + await scenario12(); +} catch (e) { + failures.push(`未捕获异常: ${e.stack}`); +} +assetAudit(); + +origConsoleLog('\n========================================'); +origConsoleLog(`断言通过 ${passCount} 条,失败 ${failures.length} 条`); +if (failures.length) { + origConsoleLog('失败清单:'); + for (const f of failures) origConsoleLog(` ✗ ${f}`); +} +process.exit(failures.length ? 1 : 0); diff --git a/web/engine/audio.js b/web/engine/audio.js index 7321f571..6aa96ac0 100644 --- a/web/engine/audio.js +++ b/web/engine/audio.js @@ -51,11 +51,21 @@ */ import { settings } from './settings.js'; +import { setLongpressBoostEventsEnabled } from './gesture.js'; import { SoundTouch, SimpleFilter, WebAudioBufferSource, getWebAudioNode } from './vendor/soundtouch.js'; const EXT_FALLBACKS = ['.ogg', '.m4a', '.mp3', '.wav']; const LONGPRESS_BOOST = 1.7; // 长按快进倍速 +/** + * 语音风暴保险丝(dungeon R2 D0-1,全线保护):同一 voiceId 在 3s 窗口内被调度 + * ≥5 次 = 病理循环(正常玩法最快也要 ≥600ms 才会重复同一句),第 5 次起丢弃后续 + * 调度并 console.error 一次(每 id 只报一次,不刷屏)。命中说明上游存在调度死循环 + * (如 R1 教学反馈与导航提示互抢),熔断保证玩家耳机里不再出现机关枪碎句。 + */ +const FUSE_WINDOW_MS = 3000; +const FUSE_MAX_HITS = 5; + export class WebAudioEngine { /** * @param {string} basePath 音频根目录,默认 'audio/'(相对游戏页面) @@ -92,6 +102,8 @@ export class WebAudioEngine { this._voice = null; // 当前对白 token(SoundTouch 管线,见 _playVoice) this._voiceQueue = []; // queue:true 时的等待队列 this._rateBoostEnabled = true; // 长按快进开关(setRateBoostEnabled per-game 控制) + this._voiceFuseLog = []; // [{ id, t }] 保险丝调度流水(play 入口记录) + this._voiceFuseErrored = new Set(); // 已报过 console.error 的 id(每 id 一次) // 设置变更实时生效 settings.subscribe((_, changed) => this._onSettingsChange(changed)); @@ -202,11 +214,34 @@ export class WebAudioEngine { return; } if (layer === 'bgm') this._playBgm(id, opts); - else if (layer === 'voice') this._playVoice(id, opts); + else if (layer === 'voice') { + if (this._voiceFuseTripped(id)) return; // 风暴保险丝:丢弃病理重复调度 + this._playVoice(id, opts); + } else if (layer === 'sfx') this._playSfx(id, opts); else console.error('[WebAudioEngine] 未知音频层(id 需以 bgm/ voice/ sfx/ 开头):', id); } + /** + * 风暴保险丝(D0-1):同一 voiceId 在 3s 窗口内被调度 ≥5 次 → 丢弃本次及后续 + * 调度并 console.error 一次。queue:true 的排队同样计入(排队也是调度)。 + * @returns {boolean} true = 本次调度已丢弃 + */ + _voiceFuseTripped(id) { + const now = performance.now(); + const log = this._voiceFuseLog; + while (log.length && now - log[0].t > FUSE_WINDOW_MS) log.shift(); + let hits = 0; + for (const e of log) if (e.id === id) hits++; + log.push({ id, t: now }); + if (hits + 1 < FUSE_MAX_HITS) return false; + if (!this._voiceFuseErrored.has(id)) { + this._voiceFuseErrored.add(id); + console.error(`[WebAudioEngine] 语音风暴保险丝:'${id}' 在 ${FUSE_WINDOW_MS}ms 内被调度 ${hits + 1} 次,已丢弃后续调度(上游存在调度循环,请排查)`); + } + return true; + } + /** 预解码一批音频(消除首次播放延迟),id 同 play()。 */ prewarm(ids) { for (const id of ids) this._load(id); @@ -275,6 +310,9 @@ export class WebAudioEngine { */ setRateBoostEnabled(on) { this._rateBoostEnabled = on !== false; + // 豁免期同步抑制 gesture.js 的长按 boost 窗口事件(D1-6:对局长按不再有 + // boost on→off 事件闪跳;事件只服务快进通道,游戏手势回调不受影响) + setLongpressBoostEventsEnabled(this._rateBoostEnabled); if (!this._rateBoostEnabled) this._voiceBoost(false); // 已在快进则立即还原 } @@ -432,6 +470,17 @@ export class WebAudioEngine { lastT: performance.now(), endTimer: null, }; + // 顶掉语义必须在 await 之后复核(dungeon R2 D0-1 根因修复):_load 期间 + // 可能又有一句 voice 开播(同 tick 双调度,如"反馈句+下一步指令句")。 + // 若此处不补杀,早先的 token 会被 this._voice = token 静默顶号——它的 + // 节点永远没人 disconnect(僵尸节点空转抽 buffer),endTimer 也因 + // _finishVoice 的 token 校验提前返回而永不清理,语音引擎自此被"幽灵句" + // 污染到页面关闭(R1 live:tut_echo_come 僵尸节点致 71 分钟抢占风暴)。 + if (this._voice) { + const cur = this._voice; + this._voice = null; + this._killVoice(cur); + } this._voice = token; this._duckBgm(); this._armVoiceEnd(token); diff --git a/web/engine/gesture.js b/web/engine/gesture.js index 8bc1ab81..65ccd38d 100644 --- a/web/engine/gesture.js +++ b/web/engine/gesture.js @@ -72,7 +72,22 @@ if (typeof document !== 'undefined' && typeof document.addEventListener === 'fun } /** 长按引擎事件:audio.js 监听它实现"长按=旁白临时倍速"。 */ + +/** + * 长按 boost 窗口事件('soundgame:longpress'/'soundgame:longpressend')的开关, + * 由 audio.setRateBoostEnabled(false) 置 off(E3 对局豁免):豁免期快进通道本就 + * 不生效,再外发事件只会造成"boost on→off 闪跳"假象(R1 live P2 仪器侧记)。 + * 只抑制 window 事件(快进/探针通道);onGesture('longpress'/'longpressend') + * 游戏回调(前进/停走)不受影响。 + */ +let boostEventsEnabled = true; + +export function setLongpressBoostEventsEnabled(on) { + boostEventsEnabled = on !== false; +} + function _dispatchLongpress(type) { + if (!boostEventsEnabled) return; if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') { window.dispatchEvent(new CustomEvent(`soundgame:${type}`)); diff --git a/web/harness/sessions/dungeon.mjs b/web/harness/sessions/dungeon.mjs new file mode 100644 index 00000000..4a9c1e2a --- /dev/null +++ b/web/harness/sessions/dungeon.mjs @@ -0,0 +1,570 @@ +/** + * 《回声地窟》新手会话剧本。 + * + * 规则: + * - 主线剧本 `session`:一位盲眼新手玩家的完整弧线——标题开始 → DRILL 九步逐项跟练 + * (脉冲/前进/转身/重发/状态/菜单/教学战/下潜)→ EXPLORE 脉冲探路、明雷战斗 → + * 逐层下潜到窟主面前 → 力战而亡听死亡归因与元进度入账 → 第二周目(遗产生效、 + * 不再进教学)短程收尾,完整展示死亡-重开循环。 + * - 乱按压力剧本 `sessionStress`:一位手忙脚乱的好奇宝宝——标题乱划、教学乱按、 + * 狂点脉冲、战斗乱滑乱逃、菜单乱逛后关掉——一样要走到死亡-重开循环,全程零死锁。 + * + * 剧本失真红线(参照 radio/storyline 剧本先例): + * - 玩家不抢话:出手前等语音链播完(waitCond !voiceBusy); + * - 玩家靠耳朵玩游戏:走位前先脉冲听回声,导航只把「听到的方向」翻译成手势; + * - 绝不用 waitIdle 空等怪物/前摇(有定时器在跑,必须 waitCond/waitVirtual 有界等待); + * - 菜单里只按「下滑关闭」,绝不双击执行(防止误触「保存并回大厅」结束会话)。 + */ + +const ORDER = ['N', 'E', 'S', 'W']; +const DIRS = { N: { dx: 0, dy: -1 }, E: { dx: 1, dy: 0 }, S: { dx: 0, dy: 1 }, W: { dx: -1, dy: 0 } }; +const HP_BAND = { 1: '毫发无损', 2: '受了点伤', 3: '伤势不轻', 4: '命悬一线' }; + +export function describe(game) { + if (game.menuOpen) return '菜单里:单击切换,双击确认,上滑帮助,下滑关闭'; + if (game.state === 'BATTLE' && game._battle) { + const b = game._battle; + if (b.state === 'telegraph') return '敌方前摇声响起了——双击,格挡!'; + if (b.state === 'player') { + return `战斗中:你的血量${HP_BAND[game._hpBand(game.run.hp, game._maxHp())]},` + + `等你出手(单击挥击,按住屏幕逃跑)`; + } + return '战斗播报中,先听着'; + } + switch (game.state) { + case 'TITLE': return game.run + ? '检测到探窟记录:单击继续,右滑重掷新周目,上滑听元进度' + : '标题页:单击开始探窟,上滑听元进度'; + case 'DRILL': return game._drill + ? `教学演练第 ${game._drill.step} 步,跟着旁白做` + : '教学演练中'; + case 'EXPLORE': return game.run + ? `第 ${game.run.floor} 层黑暗里:单击发脉冲用回声看路,按住前进,左右滑转身,上滑状态,下滑菜单` + : '……'; + case 'DESCEND': return '顺着楼梯,往下潜……'; + case 'SETTLE': return '结算:死亡归因与元进度入账,单击再探一次'; + default: return '……'; + } +} + +// ---------------------------------------------------------------- 会话工具 + +/** 有界等待:条件满足或超时即返回。时钟冻结(无定时器)时按迭代数兜底,绝不忙等挂死。 */ +async function waitStep(ctx, mock, cond, maxMs = 30000) { + const t0 = mock.now; + let spins = 0; + const spinCap = Math.max(60, Math.floor(maxMs / 50)); + while (!cond() && mock.now - t0 < maxMs && spins < spinCap) { + spins++; + await ctx.waitVirtual(100); + } +} + +/** 转身面向 dir(左右滑,听转身声)。 */ +async function faceDir(ctx, game, dir) { + const d = (ORDER.indexOf(dir) - ORDER.indexOf(game.run.face) + 4) % 4; + const gs = d === 3 ? 'left' : 'right'; + for (let i = 0; i < (d === 3 ? 1 : d); i++) { + await ctx.gesture(gs); + await ctx.waitVirtual(150); + } +} + +/** BFS 寻路(避开怪物格):把「听来的方位感」翻译成一步步的手势。 */ +function bfsPath(floor, from, to, blocked) { + const S = floor.size; + const startK = from.x + ',' + from.y; + if (from.x === to.x && from.y === to.y) return []; + const prev = new Map(); + const seen = new Set([startK]); + const q = [[from.x, from.y]]; + while (q.length) { + const [x, y] = q.shift(); + for (const [dir, d] of Object.entries(DIRS)) { + const nx = x + d.dx, ny = y + d.dy, k = nx + ',' + ny; + if (nx < 0 || ny < 0 || nx >= S || ny >= S) continue; + // 目标格即使被怪占着也要能走到(明雷:踏上窟主的格子触发遭遇) + if (floor.grid[ny][nx] !== 1 || seen.has(k) || (blocked.has(k) && !(nx === to.x && ny === to.y))) continue; + seen.add(k); + prev.set(k, { x, y, dir }); + if (nx === to.x && ny === to.y) { q.length = 0; break; } + q.push([nx, ny]); + } + } + const steps = []; + let cur = to.x + ',' + to.y; + if (!prev.has(cur)) return steps; + while (cur !== startK) { + const p = prev.get(cur); + const [sx, sy] = cur.split(',').map(Number); + steps.unshift({ x: sx, y: sy, dir: p.dir }); + cur = p.x + ',' + p.y; + } + return steps; +} + +const mobCells = (floor) => new Set(floor.mobs.map((m) => m.x + ',' + m.y)); + +/** 认真打法:前摇声响就双击格挡,轮到自己就单击挥击,直到战斗结束。 */ +async function fight(ctx, game) { + let guard = 0; + while (game.state === 'BATTLE' && game._battle && guard < 120) { + guard++; + const b = game._battle; + if (game.menuOpen) { await ctx.gesture('down'); await ctx.waitVirtual(600); continue; } + if (b.state === 'telegraph') { + ctx.note('前摇声响——双击,格挡'); + await ctx.gesture('doubletap'); + await ctx.waitVirtual(300); + continue; + } + if (b.state === 'player') { + ctx.note('单击,挥击'); + await ctx.gesture('tap'); + await ctx.waitVirtual(500); + continue; + } + await ctx.waitVirtual(400); + } + await ctx.waitVirtual(3000); // 胜利链播完 +} + +/** 力战而亡:不逃跑、顾不上格挡,喘着气隔回合挥拳——窟主的刀比你的拳快(死亡归因路线)。 */ +async function fightToDeath(ctx, game) { + let guard = 0; + let turn = 0; + while (game.state === 'BATTLE' && guard < 160) { + guard++; + const b = game._battle; + if (!b) break; + if (b.state === 'player') { + if (turn % 2 === 0) { + ctx.note('咬牙单击——挡不住了也要打'); + await ctx.gesture('tap'); + } else { + ctx.note('喘一口气,再挥'); + } + turn++; + await ctx.waitVirtual(500); + continue; + } + await ctx.waitVirtual(400); + } +} + +/** 探索导航:脉冲听路 → 一步步走向目标格;路上撞进小怪就打完再走, + * 撞进窟主(deathOnBoss)就转为力战而亡。 + * R2 伪影修正:until='stairs' 时以「踏上楼梯格」为到站条件(R1 剧本按坐标 + * 判到站,长按多走/被怪打断时提前收工,导致「单击下潜」点在半路变成脉冲—— + * 全程零下楼演出、165 脉冲 0 次铁器回声的转写伪影根源);D9 起脉冲常开 + * (玩家靠耳朵玩游戏红线:走位前先听回声)。 */ +async function navigate(ctx, mock, game, target, { pulse = true, deathOnBoss = false, until = 'cell' } = {}) { + for (let guard = 0; guard < 220; guard++) { + if (game.state === 'BATTLE') { + if (deathOnBoss && game._battle?.boss) { await fightToDeath(ctx, game); continue; } + await fight(ctx, game); + continue; + } + if (game.state !== 'EXPLORE' && game.state !== 'DRILL') return; + if (!game.run) return; + if (until === 'stairs' ? !!game._onStairs + : (game.run.pos.x === target.x && game.run.pos.y === target.y)) return; + if (pulse) { + ctx.note('单击发脉冲,听回声辨路'); + await ctx.gesture('tap'); + await ctx.waitVirtual(2000); // 听完回声组再动身 + } + const path = bfsPath(game._floor, game.run.pos, target, mobCells(game._floor)); + const step = (path[0] || bfsPath(game._floor, game.run.pos, target, new Set())[0]); // 无避怪路就硬走:撞进怪=明雷遭遇,打完再走 + if (!step) { + if (process.env.DUNGEON_SESS_DEBUG) mock.realLog('[sess-debug] !step @', JSON.stringify(game.run.pos), 'target=', JSON.stringify(target), + 'onStairs=', game._onStairs, 'floor=', game.run.floor, 'mobs=', JSON.stringify(game._floor.mobs.map((m) => [m.type, m.x, m.y]))); + await ctx.waitVirtual(800); + continue; + } // 被游走的怪堵住了:等它走开 + if (process.env.DUNGEON_SESS_DEBUG && game.run.floor >= 2) { + mock.realLog('[sess-debug] iter', JSON.stringify(game.run.pos), game.run.face, + 'target=', JSON.stringify(target), 'state=', game.state, 'walk=', !!game._walk, + 'step=', JSON.stringify(step || null), 'menu=', game.menuOpen, 't=', mock.now); + } + await faceDir(ctx, game, step.dir); + if (game.state === 'BATTLE') continue; // 走位途中被扑进来的怪打断:先打完再走(防长按变逃跑) + await ctx.gesture('longpress'); + // 立即松开,走「恰 1 格」。R2 修正:原来的 waitVirtual(120) 在虚拟时钟里会 + // 越过 +500ms 的下一步计时器(advance 至少推进到下一个到期定时器),每按一次 + // 多走 1+ 格——到站判断永远踩不准楼梯格,剧本在楼梯两侧来回震荡 20 分钟 + //(R1 转写「165 脉冲 0 铁器、两次下楼零演出」的另一根源)。 + await ctx.gesture('longpressend'); + await ctx.waitVirtual(200); + } +} + +/** 到站下潜:只有真踩在楼梯格上才单击(R2 伪影修正:半路的单击只是浪费的脉冲)。 + * DRILL(D9) 也接受——教学最后一击 = 踏楼梯单击下潜,是设计好的出教学路径。 */ +async function tapDescend(ctx, mock, game) { + if (!game._onStairs || (game.state !== 'EXPLORE' && game.state !== 'DRILL')) return false; + ctx.note('铁器就在脚下——单击,下潜'); + await ctx.gesture('tap'); + await waitStep(ctx, mock, () => game.state !== 'EXPLORE' && game.state !== 'DRILL', 4000); + return true; +} + +// ---------------------------------------------------------------- 主线剧本 + +export async function session(ctx, mock) { + const { game } = ctx; + + // —— 标题:单击开始探窟(首局无存档) —— + await waitStep(ctx, mock, () => game.state === 'TITLE'); + ctx.status('盲眼探窟者睁开了耳朵,准备下窟'); + ctx.note('单击,开始'); + await ctx.gesture('tap'); + + // —— DRILL 九步逐项跟练 —— + await waitStep(ctx, mock, () => game.state === 'DRILL' && game._drill?.step === 0); + ctx.status('教学开始:三句开场白'); + await waitStep(ctx, mock, () => !ctx.voiceBusy(), 12000); + ctx.note('单击回应「先学会用耳朵看」'); + await ctx.gesture('tap'); // D0 → D1 + + await waitStep(ctx, mock, () => game._drill?.step === 1); + ctx.note('单击——人生第一声脉冲'); + await ctx.gesture('tap'); // D1 脉冲 + await waitStep(ctx, mock, () => game._drill?.step === 2); + ctx.status('回声回来了,听墙的距离'); + await ctx.waitVirtual(2000); + await waitStep(ctx, mock, () => !ctx.voiceBusy(), 8000); + ctx.note('单击继续'); + await ctx.gesture('tap'); // D2 → D3 + + await waitStep(ctx, mock, () => game._drill?.step === 3); + await waitStep(ctx, mock, () => !ctx.voiceBusy(), 8000); + ctx.note('单击继续(侧向规则听完了)'); + await ctx.gesture('tap'); // D3 → D4 + + await waitStep(ctx, mock, () => game._drill?.step === 4); + ctx.note('按住屏幕不放——脚步声起,往前走'); + await ctx.gesture('longpress'); + await ctx.waitVirtual(1400); // 走满 2 格 + ctx.note('松开,停下'); + await ctx.gesture('longpressend'); + await waitStep(ctx, mock, () => game._drill?.step === 5); + + ctx.note('左滑,转身'); + await ctx.gesture('left'); + await ctx.waitVirtual(400); + ctx.note('右滑,转回来'); + await ctx.gesture('right'); + await waitStep(ctx, mock, () => game._drill?.step === 6); + + ctx.note('双击——再发一次脉冲,不要钱'); + await ctx.gesture('doubletap'); // D6 重发 + await waitStep(ctx, mock, () => game._drill?.step === 7); + await ctx.waitVirtual(2000); + + ctx.note('上滑,听自己的状态'); + await ctx.gesture('up'); + await ctx.waitVirtual(5800); // 五段状态链 + ctx.note('下滑,打开菜单'); + await ctx.gesture('down'); + await ctx.waitVirtual(1600); + ctx.note('再下滑,关上菜单'); + await ctx.gesture('down'); + await waitStep(ctx, mock, () => game._drill?.step === 8); + + // —— D8 教学战:盲眼蝠 ATK=0,伤不到你 —— + ctx.status('「前面有东西!放心,它伤不到你」——等它扑上来'); + await waitStep(ctx, mock, () => game.state === 'BATTLE' && game._battle, 25000); + ctx.status('教学怪扑到了面前'); + await waitStep(ctx, mock, () => game._battle?.state === 'telegraph', 15000); + ctx.note('听到它的动静——双击,格挡'); + await ctx.gesture('doubletap'); + await waitStep(ctx, mock, () => game._battle?.state === 'player', 15000); + ctx.note('单击挥拳,两拳放倒它'); + for (let i = 0; i < 6 && game.state === 'BATTLE'; i++) { + if (game._battle?.state === 'telegraph') { await ctx.gesture('doubletap'); await ctx.waitVirtual(300); } + else if (game._battle?.state === 'player') { await ctx.gesture('tap'); await ctx.waitVirtual(400); } + else await ctx.waitVirtual(400); + } + await waitStep(ctx, mock, () => game._drill?.step === 9, 20000); + ctx.status('教学战获胜——「打中了,就是这样」'); + + // —— D9:听铁器的回声,自己走到楼梯(脉冲常开:靠耳朵找到铁器再落脚) —— + ctx.note('单击,听听楼梯的铁器回声'); + await ctx.gesture('tap'); + await ctx.waitVirtual(2200); + await navigate(ctx, mock, game, game._floor.stairs, { until: 'stairs' }); + ctx.note('踏上楼梯格——「楼梯就在脚下」'); + if (!(await tapDescend(ctx, mock, game))) { + ctx.note('(还没到楼梯,继续朝铁器回声摸过去)'); + await navigate(ctx, mock, game, game._floor.stairs, { until: 'stairs' }); + await tapDescend(ctx, mock, game); + } + await waitStep(ctx, mock, () => game.state === 'EXPLORE' && game.run?.floor === 2, 20000); + ctx.status(game.run?.floor === 2 + ? '教学完了。第二层,真正的地窟从这里开始' + : '下潜尚未完成——先把楼梯找到'); + + // —— EXPLORE:脉冲探路 → 明雷战斗 → 逐层下潜到第 3 层(每层走一步验一步, + // 被怪打断/没踩准则再摸一轮——R2 修正:单发 navigate+tapDescend 任何一环 + // 被打断就整段脱轨,这是 R1 转写下潜零演出的第三个根源) —— + while (game.run && game.run.floor < 3 && game.state !== 'SETTLE') { + const from = game.run.floor; + ctx.note(`单击脉冲,探一探第 ${from} 层`); + await ctx.gesture('tap'); // 若正踩在楼梯上(D9 收尾),这一击就是下潜 + await ctx.waitVirtual(2200); + await navigate(ctx, mock, game, game._floor.stairs, { until: 'stairs' }); + if (!(await tapDescend(ctx, mock, game))) { + ctx.note('(还没踩准楼梯,朝铁器回声再摸一轮)'); + await navigate(ctx, mock, game, game._floor.stairs, { until: 'stairs' }); + if (!(await tapDescend(ctx, mock, game)) && game._onStairs) { + ctx.note('铁器就在脚下——单击,下潜'); + await ctx.gesture('tap'); + } + } + await waitStep(ctx, mock, () => !game.run || game.run.floor > from || game.state === 'SETTLE', 20000); + const done = game.run && game.run.floor === from + 1; + ctx.status(!done + ? `下潜尚未完成(还在第 ${game.run?.floor ?? '?'} 层)` + : game.run.floor === 3 ? '第三层——窟主的地界' : `下潜成功,这是第 ${game.run.floor} 层`); + if (!done) break; // 两轮都没下去:不空转,如实记「没下去」 + } + + // —— L3:走向楼梯,窟主石甲蟹就守在上面 → 力战而亡 —— + for (let guard = 0; guard < 3 && game.run && game.run.floor === 3 && game.state !== 'SETTLE'; guard++) { + ctx.note('脉冲听路,朝楼梯摸过去'); + await ctx.gesture('tap'); + await ctx.waitVirtual(2200); + await navigate(ctx, mock, game, game._floor.stairs || game._floor.throne, { deathOnBoss: true, until: 'stairs' }); + if (game.state === 'BATTLE' && game._battle?.boss) { await fightToDeath(ctx, game); continue; } + if (game._onStairs && game.state === 'EXPLORE') { await tapDescend(ctx, mock, game); continue; } + break; + } + + // —— SETTLE:死亡归因 + 元进度入账 —— + await waitStep(ctx, mock, () => game.state === 'SETTLE', 60000); + if (game.state === 'SETTLE') { + ctx.status('倒下了。死亡归因链:死于谁、哪里没做好、带走了多少金币'); + ctx.note('听完归因三问和「下个周目,你会更强」'); + await ctx.waitVirtual(10000); + ctx.note('单击——再探一次'); + await ctx.gesture('tap'); // → TITLE(元进度入账后) + await waitStep(ctx, mock, () => game.state === 'TITLE', 10000); + } else { + ctx.note('这一把竟然没死——那就继续往下走'); + } + + // —— 第二周目:遗产继承、不再进教学(只有真回到 TITLE 才单击开局) —— + if (game.state === 'TITLE') { + ctx.note('单击,开始新周目'); + await ctx.gesture('tap'); + await waitStep(ctx, mock, () => game.state === 'EXPLORE' && game.run, 30000); + ctx.status('新周目直落探索:遗产生效,不用再上课'); + ctx.note('上滑,听新周目的局面复述'); + await ctx.gesture('up'); + await ctx.waitVirtual(6500); + ctx.note('单击脉冲,听听熟悉的回声'); + await ctx.gesture('tap'); + await ctx.waitVirtual(2500); + } + ctx.status('第二周目短程收尾——死亡-重开循环跑通,收工'); +} + +// ---------------------------------------------------------------- 乱按压力剧本 + +export async function sessionStress(ctx, mock) { + const { game } = ctx; + let seed = 20260901; + const rng = () => { + seed = (seed * 1103515245 + 12345) & 0x7fffffff; + return seed / 0x7fffffff; + }; + const pick = (arr) => arr[Math.floor(rng() * arr.length)]; + const NOISE = ['tap', 'tap', 'doubletap', 'left', 'right', 'up']; // down/longpress 单独管,防误触 + + /** 乱按的安全阀:菜单里只准「下滑关闭」,绝不双击执行。 */ + const noise = async () => { + if (game.menuOpen) { + ctx.note('菜单乱逛完了,下滑关掉'); + await ctx.gesture('down'); + await ctx.waitVirtual(500); + return; + } + if (rng() < 0.15) { // 偶尔长按(探索=前进,战斗=尝试逃跑,Boss 会拒绝) + await ctx.gesture('longpress'); + await ctx.waitVirtual(300); + await ctx.gesture('longpressend'); + await ctx.waitVirtual(200); + return; + } + await ctx.gesture(pick(NOISE)); + await ctx.waitVirtual(450); + }; + + // ① 标题页乱划一通,然后开始 + for (let i = 0; i < 10 && game.state === 'TITLE'; i++) { + ctx.status('标题页乱划'); + await noise(); + } + await ctx.gesture('tap'); + + // ② 教学期:一半乱按,但每步最后一定做对(跟不下来也要能走完) + await waitStep(ctx, mock, () => game.state === 'DRILL' || game.state === 'EXPLORE', 20000); + ctx.status('教学里乱按一气,旁白说到哪按到哪'); + const RIGHT = { 0: 'tap', 1: 'tap', 2: 'tap', 3: 'tap', 4: 'longpress', 5: 'left', 6: 'doubletap', 9: 'tap' }; + let stepGuard = 0; + while (game.state === 'DRILL' && game._drill && stepGuard < 320) { + stepGuard++; + const step = game._drill.step; + if (game.menuOpen) { await ctx.gesture('down'); await ctx.waitVirtual(500); continue; } + if (game.state === 'BATTLE') { await chaosFight(ctx, game, rng, pick); continue; } + if (rng() < 0.45) { + await noise(); // 乱按:会吃 error 震、会走错路,但不死锁 + continue; + } + if (step === 7) { // 上滑 + 开菜单再关 + if (!game._drill.upDone) { await ctx.gesture('up'); await ctx.waitVirtual(5800); } + else { await ctx.gesture('down'); await ctx.waitVirtual(1400); await ctx.gesture('down'); await ctx.waitVirtual(600); } + continue; + } + if (step === 8) { // 等教学怪扑上来,交给乱打 + await waitStep(ctx, mock, () => game.state === 'BATTLE' || game._drill?.step === 9, 25000); + if (game.state === 'BATTLE') await chaosFight(ctx, game, rng, pick); + continue; + } + if (step === 9) { // D9:乱按到这一步也得真走到楼梯,否则会卡死在聆听里 + if (game._onStairs) { + await ctx.gesture('tap'); // 单击下潜 + await waitStep(ctx, mock, () => game.state !== 'DRILL', 15000); + continue; + } + const st = game._floor.stairs || game._floor.throne; + const stp = (bfsPath(game._floor, game.run.pos, st, mobCells(game._floor))[0] + || bfsPath(game._floor, game.run.pos, st, new Set())[0]); + if (!stp) { await ctx.gesture('tap'); await ctx.waitVirtual(1500); continue; } + const d = (ORDER.indexOf(stp.dir) - ORDER.indexOf(game.run.face) + 4) % 4; + await ctx.gesture(d === 3 ? 'left' : 'right'); + await ctx.waitVirtual(200); + await ctx.gesture('longpress'); + await ctx.waitVirtual(120); + await ctx.gesture('longpressend'); + await ctx.waitVirtual(250); + continue; + } + const gs = RIGHT[step]; + if (!gs) { await ctx.waitVirtual(300); continue; } + await ctx.gesture(gs); + if (step === 4) { await ctx.waitVirtual(1400); await ctx.gesture('longpressend'); } + await ctx.waitVirtual(500); + } + await waitStep(ctx, mock, () => game.state === 'EXPLORE' || game.state === 'BATTLE' || game.state === 'SETTLE', 25000); + ctx.status('总算从教学里滚出来了(可能跳了课,可能一路错过来的)'); + + // ③+④ 探索/下潜/送死:一路狂点脉冲、乱走乱打,直到死一次为止(零死锁底线=SETTLE) + let chaosGuard = 0; + while (game.run && game.state !== 'SETTLE' && chaosGuard < 500) { + chaosGuard++; + if (game.state === 'DRILL') { // 乱按按出「跳过教学」(下滑在 DRILL 的原义),绝不在教学里空转 + await ctx.gesture('down'); + await ctx.waitVirtual(2500); + continue; + } + if (game.state === 'BATTLE') { + const b = game._battle; + if (b?.boss || game.run.floor >= 3) { + ctx.status('乱打:不格挡、逃不掉(Boss),一直挥拳'); + await fightToDeath(ctx, game); + continue; + } + await chaosFight(ctx, game, rng, pick); + continue; + } + if (game.menuOpen) { await ctx.gesture('down'); await ctx.waitVirtual(400); continue; } + if (game.state !== 'EXPLORE') { await ctx.waitVirtual(400); continue; } + if (game._onStairs && !game._floor.mobs.some((m) => m.x === game.run.pos.x && m.y === game.run.pos.y)) { + ctx.note('正好站在楼梯上——单击下潜'); + await ctx.gesture('tap'); + await waitStep(ctx, mock, () => game.state === 'EXPLORE' && game.run.floor >= 2, 15000); + continue; + } + const r = rng(); + if (r < 0.3) { // 狂点脉冲:冷却期就是一串 error 震,游戏不炸 + ctx.note('手痒连点三下脉冲'); + await ctx.gesture('tap'); + await ctx.gesture('tap'); + await ctx.gesture('tap'); + await ctx.waitVirtual(1600); + continue; + } + if (r < 0.45) { await noise(); continue; } + // 正经走两步:脉冲听路 → 朝楼梯/王座挪 + await ctx.gesture('tap'); + await ctx.waitVirtual(1800); + const st = game._floor.stairs || game._floor.throne; + const step = (bfsPath(game._floor, game.run.pos, st, mobCells(game._floor))[0] + || bfsPath(game._floor, game.run.pos, st, new Set())[0]); + if (!step) { await ctx.waitVirtual(800); continue; } + const d = (ORDER.indexOf(step.dir) - ORDER.indexOf(game.run.face) + 4) % 4; + await ctx.gesture(d === 3 ? 'left' : 'right'); + await ctx.waitVirtual(200); + await ctx.gesture('longpress'); + await ctx.waitVirtual(120); + await ctx.gesture('longpressend'); + await ctx.waitVirtual(250); + } + + // ⑤ SETTLE → TITLE → 新周目,短程收尾 + await waitStep(ctx, mock, () => game.state === 'SETTLE', 20000); + if (game.state === 'SETTLE') { + ctx.status('乱打也被归因得明明白白,元进度照单全收'); + await ctx.waitVirtual(4000); + await ctx.gesture('tap'); // → TITLE + await waitStep(ctx, mock, () => game.state === 'TITLE', 10000); + await noise(); // 标题页再乱划一下 + await ctx.gesture('tap'); // 新周目 + await waitStep(ctx, mock, () => game.state === 'EXPLORE' && game.run, 25000); + ctx.status('第二周目照样开局,压力测试全程零死锁,收工'); + } else { + ctx.note('这一把居然没死——压力有限,收工'); + } +} + +/** 乱打:大多数时候出手,偶尔乱滑/上滑/试图逃跑;前摇里手忙脚乱。 */ +async function chaosFight(ctx, game, rng, pick) { + let guard = 0; + while (game.state === 'BATTLE' && game._battle && guard < 160) { + guard++; + const b = game._battle; + if (game.menuOpen) { await ctx.gesture('down'); await ctx.waitVirtual(500); continue; } + if (b.state === 'telegraph') { + if (rng() < 0.25) { + ctx.note('手忙脚乱中碰巧双击了一下'); + await ctx.gesture('doubletap'); + await ctx.waitVirtual(300); + } else { + await ctx.waitVirtual(400); + } + continue; + } + if (b.state === 'player') { + const r = rng(); + if (r < 0.62) { + ctx.note('单击,抡空也打'); + await ctx.gesture('tap'); + } else if (r < 0.72) { + ctx.note('慌了,按住屏幕想跑'); + await ctx.gesture('longpress'); + await ctx.waitVirtual(300); + await ctx.gesture('longpressend'); + } else if (r < 0.88) { + await ctx.gesture(pick(['left', 'right'])); + } else { + await ctx.gesture('up'); + } + await ctx.waitVirtual(500); + continue; + } + await ctx.waitVirtual(400); + } + await ctx.waitVirtual(3000); +} diff --git a/web/harness/sessions/radio.mjs b/web/harness/sessions/radio.mjs new file mode 100644 index 00000000..9961a571 --- /dev/null +++ b/web/harness/sessions/radio.mjs @@ -0,0 +1,209 @@ +/** + * 《不眠频率》新手会话剧本。 + * + * 剧本: + * - `session`(主线):一位夜间主播的完整弧线——听完开场白、接完五通电话、 + * 每个抉择点都做选择(C1 走心 / C2 劝上车 / C3 问她+真话 / C4 带话+留在直播 / + * C5 陪聊 / p9 直面),听完五句尾声回响,以完整 E1「天亮之前」收尾。 + * - `sessionStress`(乱按压力):一位好奇宝宝乱划乱点、快速跳句、菜单里乱逛, + * 一样要能走到 ENDING,全程零死锁、零静默死局。 + * + * 剧本失真红线(参照 storyline/escape 剧本先例): + * - 玩家不抢话:只在语音链播完(waitCond !voicePlaying)之后才出手; + * - 玩家不干晾自己:只在 ONAIR 叙事等待态偶尔走神触发一次看门狗,链一停就接话; + * - RING/BRIDGE 有铃声/喘息定时器循环,禁止裸 waitIdle 空等,必须 waitCond 有界等待; + * - 通话里每句之间是「链末=继续」的等待态——真人听完一句就点一下,不机械、不抢跑。 + */ + +const CALL_LABEL = ['一', '二', '三', '四', '五']; + +export function describe(game) { + if (game.leaving) return '已确认保存并回大厅'; + if (game.phase === 'ASK') return '进游戏先听到「单击继续上次,右滑新夜晚」的询问'; + if (game.phase === 'MENU') { + const names = ['回到直播', '重新开始今晚', '保存并回大厅']; + return `菜单里,当前项「${names[game.menuIdx]}」:单击切换,双击确认,上滑帮助,下滑关闭`; + } + if (game.phase === 'INTRO') { + return game.voicePlaying ? '台标和规矩正在播' : '开场白播完了,等第一声铃'; + } + if (game.phase === 'RING') return `第 ${CALL_LABEL[game.callN - 1]} 通电话打进来:单击接听`; + if (game.choiceArmed) { + return `「请选择」念完了(第 ${CALL_LABEL[game.callN - 1]} 通):向左或向右滑动做出你的决定`; + } + if (game.voicePlaying) return `第 ${CALL_LABEL[game.callN - 1]} 通通话中,正在听对方说话`; + if (game.phase === 'ONAIR') return `对方说完了,等我接话(第 ${CALL_LABEL[game.callN - 1]} 通):单击继续`; + if (game.phase === 'BRIDGE') return '幕间喘息,垫乐播放中,马上接下一通'; + if (game.phase === 'ENDING') { + return game.awaitingRestart + ? '结局和五条回响都听完了。单击再来一局,下滑开菜单' + : '结局播报中'; + } + return '……'; +} + +// ---------------------------------------------------------------- 主线剧本 + +export async function session(ctx, mock) { + const { game } = ctx; + + // 开场:INTRO 自动流(台标口播 + 一句规矩 + 今晚目标)→ 第一声铃 + await ctx.waitCond(() => game.phase === 'RING'); + ctx.status('开场口令和台标播完了,第一通电话响了'); + ctx.note('铃一响就接——接听是单击'); + await ctx.gesture('tap'); + ctx.status('线路接通了'); + await ctx.waitCond(() => !game.voicePlaying); + ctx.status('导播报完线,老人开口了'); + + let invalidTried = false; + let menuDone = false; + let walkedAway = false; + let choiceIdx = 0; + + // 抉择路径:C1 走心、C2 劝上车、C3 问她+真话、C4 带话+留在直播、C5 陪聊、p9 直面 + const choices = [ + { dir: 'left', why: '老人在发抖,我陪他哼那首老歌' }, + { dir: 'left', why: '留在原地陪他等巡逻队' }, + { dir: 'left', why: '小鹿妈妈等的是那声「妈」,劝她上车' }, + { dir: 'right', why: '糖糖像是憋着事,我轻轻问她怎么了' }, + { dir: 'left', why: '告诉她这不是她的错——大人的事不算在孩子头上' }, + { dir: 'left', why: '陈姐听见孩子问话了,我把话带回去' }, + { dir: 'left', why: '留在直播里,让全城等这声门铃' }, + { dir: 'left', why: '老韩眼皮在打架,我陪他聊聊闺女' }, + { dir: 'left', why: '留言机红灯在闪——直面它,替老主播把晚安播出去' }, + ]; + + for (let i = 0; i < 200 && !game.leaving; i++) { + // 只等当前语音链播完或抉择武装;RING/BRIDGE 有定时器,绝不用裸 waitIdle + await ctx.waitCond(() => !game.voicePlaying || game.choiceArmed); + + if (game.leaving) break; + if (game.phase === 'RING') { + ctx.status(`第 ${CALL_LABEL[game.callN - 1]} 通电话响了,接`); + await ctx.gesture('tap'); + await ctx.waitCond(() => !game.voicePlaying); + continue; + } + if (game.choiceArmed) { + const pick = choices[choiceIdx]; + if (!pick) break; // 抉择走完(理论上到 p9 后应已进 ENDING) + + if (!invalidTried) { + invalidTried = true; + ctx.note(pick.why + '——但先上滑听一遍操作和进度'); + await ctx.gesture('up'); + await ctx.waitCond(() => !game.voicePlaying); + ctx.status('听完帮助+状态链,游戏自动重播了选项'); + await ctx.waitCond(() => game.choiceArmed); + } + if (!menuDone) { + menuDone = true; + ctx.note('下滑看看菜单有什么'); + await ctx.gesture('down'); + await ctx.waitCond(() => !game.voicePlaying); + ctx.status('菜单里,切一项听听'); + await ctx.gesture('tap'); // 切一项听听(报名接在当前报名之后,不截断) + await ctx.waitCond(() => !game.voicePlaying); + ctx.note('听完了,下滑关闭菜单,回到选项'); + await ctx.gesture('down'); // 下滑关闭(不依赖菜单项序号,绝不误触「重开/回大厅」) + await ctx.waitCond(() => game.choiceArmed); + } + ctx.status('选项念完了,做决定'); + ctx.note(pick.why); + await ctx.gesture(pick.dir); // 念完即选,不干晾(犹豫会在等待态空烧看门狗) + choiceIdx++; + continue; + } + if (game.phase === 'ENDING') { + await ctx.waitCond(() => game.awaitingRestart); + continue; + } + if (game.phase === 'BRIDGE') { + ctx.status('幕间,口碑播报和垫乐,等下一通'); + await ctx.waitVirtual(2600); + continue; + } + // ONAIR 叙事等待态(链末=继续):真人听完一句就接话,偶尔走神一次。 + // 注意不要在这里 waitVirtual 小憩——等待态挂着看门狗定时器,waitVirtual + // 会一口气跳到下一个定时器 tick(25s 看门狗),剧本就变成「每句干晾 25s」。 + if (!walkedAway && game.phase === 'ONAIR' && !game.choiceArmed) { + walkedAway = true; + ctx.silence(25, '听到一半走了神,在想老人的老花镜'); + await ctx.waitVirtual(25600); // 触发看门狗:nudge + gd_continue + ctx.status('听到一声轻响和「单击屏幕,继续」,马上点'); + await ctx.gesture('tap'); + continue; + } + await ctx.gesture('tap'); // 链末单击 = 继续 + } + + await ctx.waitCond(() => game.phase === 'ENDING' && game.awaitingRestart); + ctx.status('结局链走完,五条回响一句句点到我今晚的每一次选择'); + ctx.note('听完尾声那笔账——今晚的人都接住了,会话结束(E1 天亮之前)'); +} + +// ---------------------------------------------------------------- 乱按压力剧本 +// 目标:全程零死锁、零静默死局,一样能走到 ENDING。 +// 剧本特征:不读提示、快速跳句、菜单里乱逛、错手势试探,但仍保留「最终能通关」的底线。 + +export async function sessionStress(ctx, mock) { + const { game } = ctx; + + let seed = 20260901; + const rng = () => { + seed = (seed * 1103515245 + 12345) & 0x7fffffff; + return seed / 0x7fffffff; + }; + const gestures = ['tap', 'left', 'right', 'up', 'down', 'doubletap']; + const pick = (arr) => arr[Math.floor(rng() * arr.length)]; + + let menuOpenCount = 0; + let choiceCount = 0; + let answered = 0; + let noiseCount = 0; + + for (let i = 0; i < 300; i++) { + await ctx.waitCond(() => !game.voicePlaying || game.choiceArmed); + + if (game.leaving) break; + if (game.phase === 'RING') { + answered++; + await ctx.gesture('tap'); // 不接会一直响,乱按也照接 + continue; + } + if (game.phase === 'MENU') { + menuOpenCount++; + await ctx.gesture('tap'); // 乱切换 + await ctx.waitCond(() => !game.voicePlaying); + await ctx.gesture('down'); // 下滑关闭(不双击,避免误触「重开/回大厅」) + continue; + } + if (game.choiceArmed) { + choiceCount++; + if (rng() < 0.35) { + // 好奇宝宝:犹豫时乱试一个不推进剧情的手势 + noiseCount++; + await ctx.gesture(pick(['up', 'tap', 'doubletap'])); + await ctx.waitCond(() => game.choiceArmed || !game.voicePlaying); + } + await ctx.gesture(pick(['left', 'right'])); + continue; + } + if (game.phase === 'BRIDGE') { + await ctx.waitVirtual(2600); + continue; + } + if (game.phase === 'ENDING') { + await ctx.waitCond(() => game.awaitingRestart); + break; + } + // ONAIR 叙事等待态 / 其他静默帧:乱划一下(无效手势也要有反馈,零静默) + noiseCount++; + await ctx.gesture(pick(gestures)); + } + + await ctx.waitCond(() => game.phase === 'ENDING' && game.awaitingRestart); + ctx.status(`乱按剧本收尾:接听 ${answered} 次、${choiceCount} 个抉择、乱试 ${noiseCount} 次、逛菜单 ${menuOpenCount} 次`); + ctx.note('全程没有任何一次手势被无声吞掉,也没有卡死在某个状态里——结局链照样走完'); +} diff --git a/web/harness/transcribe.mjs b/web/harness/transcribe.mjs index 382f0c68..d0e3c6bd 100644 --- a/web/harness/transcribe.mjs +++ b/web/harness/transcribe.mjs @@ -20,14 +20,16 @@ const HARNESS_DIR = path.dirname(url.fileURLToPath(import.meta.url)); const WEB_DIR = path.resolve(HARNESS_DIR, '..'); const GAME_NAME = process.argv[2]; if (!GAME_NAME || !fs.existsSync(path.join(WEB_DIR, GAME_NAME, 'game.js'))) { - console.error('用法: node transcribe.mjs [--out ]'); + console.error('用法: node transcribe.mjs [--session <导出函数名>] [--out ]'); process.exit(2); } +// 可选:--session sessionStress(默认 session)。会话导出函数不存在时报错退出。 const GAME_DIR = path.join(WEB_DIR, GAME_NAME); const argOf = (k, dflt) => { const i = process.argv.indexOf(k); return i > -1 && process.argv[i + 1] ? process.argv[i + 1] : dflt; }; +const SESSION_NAME = argOf('--session', 'session'); const OUT = argOf('--out', path.resolve(WEB_DIR, '..', `research/04-playtests/transcripts/${GAME_NAME}.md`)); const VOICE_TEXT = JSON.parse(fs.readFileSync(path.join(HARNESS_DIR, 'voice_text.json'), 'utf8')); @@ -46,7 +48,8 @@ const estimateDuration = (id) => { const CLASS_NAME = { storyline: 'StorylineGame', escape: 'EscapeGame', simon: 'SimonGame', - rhythm: 'RhythmGame', space: 'SpaceGame', rpg: 'RpgGame', + rhythm: 'RhythmGame', space: 'SpaceGame', rpg: 'RpgGame', radio: 'RadioGame', + dungeon: 'DungeonGame', }[GAME_NAME]; const mock = installMock(GAME_DIR); @@ -208,9 +211,13 @@ async function run() { await mock.flush(); drain(); - if (sessionMod?.session) { - await sessionMod.session(makeCtx(audio, game, sessionMod.describe), mock); + if (sessionMod?.[SESSION_NAME]) { + // 主线/压力等任意导出的会话函数;状态行走话统一用 describe(game) + await sessionMod[SESSION_NAME](makeCtx(audio, game, sessionMod.describe), mock); } else { + if (sessionMod && SESSION_NAME !== 'session') { + throw new Error(`会话剧本 web/harness/sessions/${GAME_NAME}.mjs 未导出 ${SESSION_NAME}`); + } // 通用新手策略:等待→单击;没反应就双击→上滑→下滑,各试一次 const ctx = makeCtx(audio, game, null); for (let i = 0; i < 30 && mock.now < 10 * 60 * 1000; i++) { diff --git a/web/harness/voice_text.json b/web/harness/voice_text.json index cdbc3dc3..4cf37693 100644 --- a/web/harness/voice_text.json +++ b/web/harness/voice_text.json @@ -457,5 +457,266 @@ "npc_boss1_1": "盲剑客?正好,省得挖你的眼睛。", "npc_boss1_2": "有两下子。可惜,你听不懂死期的声音。", "npc_boss1_die": "白无瞳……会找到你……" + }, + "radio": { + "b_open": "午夜零点,红灯亮起。欢迎回到《今夜不眠》。", + "b_rain": "雨下了一整晚。热线灯,马上就要亮了。", + "z1_song": "大爷别急,这首歌我也会。咱们一句一句来。", + "z1_ask": "大爷,路口有什么招牌?念一个给我听听。", + "z1_stay": "哪儿也不去。我陪你,这首歌还长着呢。", + "z1_pol": "大爷,我把您转给派出所,他们马上就到。", + "l1_go": "你妈等的不是奖杯,是你进门喊的那声妈。", + "l1_free": "退不退,都是你的自由。夜还长,慢慢想。", + "k1_lull": "闭上眼睛。风把云朵盖在被子上,月亮替你值夜班。", + "k1_ask": "睡不着的时候,心里是不是装着事?说给我听听。", + "k1_truth": "糖糖,记住,大人的事,不是小孩的错。一个字都不是。", + "k1_lie": "睡吧。明天一睁眼,什么都会好起来的。", + "w1_say": "四十分钟前,有个孩子问我们:爸妈摔门,是不是她的错。", + "w1_say2": "今天有位小朋友,还在等爸妈回家切蛋糕。", + "w1_air": "陈姐,别挂。让他接电话——全城都在听。", + "w1_priv": "这里只有我们俩了。想说就说,想哭就哭。", + "w1_home": "吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。", + "h1_talk": "说说她。开学第一天,她跟你说的头一句话,是什么?", + "h1_stop": "听我的,前面三公里有服务区。学费急,命更急。", + "d9_face": "师父,三年了。这句晚安,我替您播。各位,晚安。", + "d9_avoid": "各位,今晚到这里。明晚零点,今夜不眠,不见不散。", + "e1_a": "天亮了,热线灯还亮着。你们的晚安,是这座城市的心跳。", + "e1_b": "这把话筒,我总算接得住了。明晚零点,接着不眠。", + "e2_a": "今晚的热线烧得发烫。全城的失眠者,都被接住了。", + "e2_b": "只是路过留言机的时候,你还是没敢看它。红灯灭得很慢。", + "e3_a": "口碑掉了一夜。可那句迟到三年的晚安,今晚说出去了。", + "e3_b": "一个人的频道,也是频道。师父,收到了吗?", + "e4_a": "今夜不眠,静音收播。有的夜班,谁也接不住谁。", + "e4_b": "留言机还在闪。你没看它,它也没怪你。明晚再说吧。", + "gd_brief": "单击继续节目。请选择的时候,向左或向右滑。", + "gd_continue": "单击屏幕,继续。", + "gd_again": "还在吗?单击继续。", + "gd_invalid": "这个手势,直播间里用不上。", + "gd_ring1": "热线在闪。单击,接听。", + "gd_ring2": "灯还亮着,听众在等。单击,接听。", + "sys_help": "单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。", + "sys_menu": "菜单。单击切换选项,双击确认。", + "sys_menu_resume": "回到直播。", + "sys_menu_restart": "重新开始今晚。", + "sys_menu_lobby": "保存并回大厅。", + "sys_settings": "音色、语速、背景音乐,请回大厅修改。", + "sys_skip": "想跳过这句,单击就行。", + "sys_continue_ask": "检测到你的夜班记录。单击继续上次,右滑开始新的夜晚。", + "sys_new_night": "新的夜晚,开始了。", + "sys_saved": "进度已保存。", + "prog_c1": "现在是今晚第一通电话。", + "prog_c2": "现在是今晚第二通电话。", + "prog_c3": "现在是今晚第三通电话。", + "prog_c4": "现在是今晚第四通电话。", + "prog_c5": "现在是今晚第五通电话。", + "rep_high": "今晚的口碑,在听众里立住了。", + "rep_mid": "今晚的口碑,不好不坏。", + "rep_low": "今晚的口碑,还没立起来。", + "b_rule": "台里的规矩就一条:先接住人,再接电话。", + "b_goal": "今晚五通电话。说什么话,由你决定。", + "z1_ring_in": "一线来电。七十多岁,雨夜,走丢了。", + "p1": "老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。", + "z1_sdone": "他稳住了。老歌,比路牌管用。", + "z1_adone": "他慌了。先接住情绪,再走流程。", + "p2": "雨声更密了。向左滑,留在原地陪他等巡逻队。向右滑,转接派出所。", + "z1_staye": "十五分钟后,巡逻灯停在路口。全城听见老人笑了。", + "z1_pole": "转接完成,地址报得清。干净利落。", + "z1_bade": "地址没报清就转接,雨又大。这通砸了。先稳人,再办事。", + "z1_ok1": "就这么接。记住这个手感。", + "l1_in": "二线来电。姑娘,声音很轻,像怕吵到谁。", + "p3": "向左滑,告诉她这趟车值得上。向右滑,把选择还给她。", + "l1_goe": "二线收工。这句话,值一个头条。", + "l1_freee": "她走了。没上车,也没退票。年轻人的夜,长了点。", + "k1_in": "三线来电。听呼吸,是个小孩。", + "p4": "向左滑,唱一段摇篮曲哄她睡。向右滑,轻轻问她怎么了。", + "k1_lulle": "睡着了。可你没听见,她在怕什么。", + "p5": "孩子的声音在抖。向左滑,告诉她这不是她的错。向右滑,答应她明天都会好。", + "k1_truthe": "蛋糕。记下来——四线的来电,可能跟这个家有关。", + "k1_liee": "你许了个兑不了的现。孩子睡了,谎留下了。", + "w1_in": "四线来电。户外风声大,是位大姐。", + "p6": "摔门声你听见了。向左滑,替孩子给她带句话。向右滑,劝她回家把话说开。", + "w1_saye": "她攥着电话,很久没出声。风都停了。", + "w1_say2e": "话递到了。就差最后一脚。", + "p7": "她的呼吸平了。向左滑,留在直播里,让全城等这个门铃。向右滑,换垫乐,私下陪她说完。", + "w1_aire": "电话那头,传来跑步声。今晚的高光时刻。", + "w1_prive": "灯牌是你亲手关的,故事还是完整的。稳。", + "w1_homee": "她往回走了。成不成,看他们自己的了。", + "h1_in": "五线来电。引擎声没停过——他在开车!", + "p8": "方向盘比话筒危险。向左滑,陪他聊聊闺女。向右滑,让他立刻进服务区。", + "h1_danger": "车道偏了——听!", + "h1_talke": "吓醒的,也算醒。这一趟,聊到家。", + "h1_stope": "他下了高速。单子黄了,人齐了。", + "h1_echo": "对了。头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的。", + "g9_in": "五线收工。垫乐起——零点档,还剩最后一段。", + "p9": "留言机红灯在闪,是老主播三年前没播完的那句晚安。向左滑,替他播出去。向右滑,照常收播。", + "d9_facee": "带子里其实只有电流声。但空着的那一秒,你说满了。", + "d9_avoide": "留言机的红灯,又多亮了一夜。", + "ep_lead": "收播了。导播间替你记了一笔账——今晚的人都怎样了。", + "ep_zhou_ok": "老周:孙子替他买了老花镜。回信说,谢谢那首歌。", + "ep_zhou_no": "老周在便利店屋檐下坐到天亮。没出事,也没人听见他的歌。", + "ep_deer_ok": "小鹿上车了。她妈后来打电话说,那声妈,比奖杯值钱。", + "ep_deer_no": "小鹿退了票。检票口的灯,记得那趟没上成的车。", + "ep_kid_ok": "糖糖家的门,这个月没再摔过。她说这是主播魔法。", + "ep_kid_no": "糖糖后来又打来过一次。还是那句:楼下在吵。", + "ep_wife_ok": "陈姐家切了蛋糕。那句「回来吧」,是我这晚听过最好的声音。", + "ep_wife_no": "陈姐在公车站坐到了末班车。那通电话,成了没寄出的信。", + "ep_han_ok": "老韩到家了。后视镜上,多了朵小红花。", + "ep_han_no": "老韩在服务区睡了一觉。学费齐了,那首歌没放成。", + "z1_a": "主播……我,我找不到回家的路了。", + "z1_b": "雨太大了。我本来,只想买副老花镜。", + "z1_songo": "对,对!路口修车铺,天天放这首!", + "z1_asko": "招牌……雨糊住了,我眼花,看不清啊……", + "z1_stayo": "好,好。有人说话,我就不怕了。", + "z1_polo": "好,好。谢谢你啊,谢谢。", + "z1_bad": "说不清……我真是,说不清了……", + "l1_a": "主播,退票窗口十一点半就关了。我站这儿半小时了。", + "l1_b": "票是回去看我妈的。可我试镜又黄了,两手空空……", + "l1_goo": "呜……嗯。不退了,我现在就去检票。谢谢您。", + "l1_freeo": "嗯……我再想想。窗口要关了,我先过去了。", + "k1_a": "喂……有人吗?我睡不着。", + "k1_b": "家里灯关了。可是楼下,一直有声音。", + "k1_lullo": "月亮也睡了……那我也睡。", + "k1_asko": "爸爸妈妈摔门了。是不是……我做错了什么呀。", + "k1_trutho": "嗯……那我睡了。他们明天,会一起给我切蛋糕吗?", + "k1_lieo": "真的吗?拉钩。晚安……", + "w1_a": "主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。", + "w1_b": "明天孩子生日!蛋糕都订好了,他人要今晚走!", + "w1_sayo": "什么?她还没睡?这么晚了,她还醒着?", + "w1_say2o": "蛋糕……是他订的。他嘴上不说,心里都记着。", + "w1_airo": "喂!回来吧!蛋糕要化了,孩子等着呢!", + "w1_privq": "呜……其实他订完蛋糕就跟我道歉了。是我,把门摔得太响。", + "w1_homeo": "嗯……我这就往回走。谢谢你,听我瞎嚷嚷。", + "h1_a": "主播,别挂。陪我说话,我的眼皮,沉得很。", + "h1_b": "跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。", + "h1_talko": "她说,爸,我在学校给你赚了朵小红花。哈哈,哈哈……", + "h1_dango": "嘶——好险!前车急刹,我擦着护栏过去了。彻底醒了!", + "h1_stopo": "行,听你的。进了服务区,我给你们放首歌听。" + }, + "dungeon": { + "intro_1": "你在黑暗中醒来。这里是回声地窟。", + "intro_2": "伸手不见五指,声音就是你的眼睛。", + "sys_continue_ask": "检测到探窟记录。单击继续,右滑重新开始。", + "sys_new_run": "新的探窟,开始了。", + "tut_d0": "先学会用耳朵看。单击,发出声波脉冲。", + "tut_echo_come": "听,回声回来了。", + "tut_front_rule": "回声回来越快,墙就越近。", + "tut_side_rule": "两侧的回声,发闷是墙,空旷是通路。", + "tut_walk": "按住屏幕不放,你就往前走。", + "tut_turn": "左滑右滑,可以转身。", + "tut_replay": "双击,再发一次脉冲,不要钱。", + "tut_status": "上滑,随时听自己的状态。", + "tut_menu": "下滑,打开菜单。", + "tut_mob": "前面有东西!放心,它伤不到你。", + "tut_parry": "听到它的动静,双击格挡。", + "tut_parry_ok": "挡住了!", + "tut_parry_miss": "没挡住也别怕,它伤不到你。", + "tut_attack": "现在单击,挥拳打它。", + "tut_kill": "打中了,就是这样。", + "tut_stairs": "铁器的回声,就是下潜的楼梯。", + "tut_stairs_on": "楼梯就在脚下。单击,下潜。", + "tut_done": "教学完了。地窟很深,小心走。", + "tut_skip": "已跳过教学。单击脉冲,先听听四周。", + "sys_saved": "进度已保存。", + "sys_menu": "菜单。单击切换,双击确认。", + "sys_menu_resume": "返回探索。", + "sys_menu_save": "保存并回大厅。", + "sys_menu_help": "重新听帮助。", + "sys_menu_restart": "重新开始本周目。", + "help_explore": "单击脉冲,按住前进,左右滑转身。双击再听,上滑状态,下滑菜单。", + "help_battle": "敌人前摇声响时双击格挡,单击挥击。打不过就按住屏幕逃跑。", + "settings_hint": "音色、语速、背景音乐,请回大厅修改。", + "st_floor_1": "这是第一层。", + "st_floor_2": "这是第二层。", + "st_floor_3": "这是第三层。", + "st_floor_4": "这是第四层。", + "st_floor_5": "这是第五层。", + "st_floor_6": "这是第六层。", + "st_floor_7": "这是第七层。", + "st_hp_1": "你毫发无损。", + "st_hp_2": "你受了点伤。", + "st_hp_3": "你伤势不轻。", + "st_hp_4": "你命悬一线。", + "st_gold_1": "口袋空空。", + "st_gold_2": "有些积蓄。", + "st_gold_3": "收获可观。", + "st_gold_4": "腰包鼓鼓。", + "st_face_e": "你面朝东。", + "st_face_s": "你面朝南。", + "st_face_w": "你面朝西。", + "st_face_n": "你面朝北。", + "st_threat_none": "附近很安静。", + "st_threat_left": "左边有脚步声。", + "st_threat_right": "右边有脚步声。", + "st_threat_both": "两边都有动静。", + "st_best_1": "你还没到过第三层。", + "st_best_2": "你到过中层。", + "st_best_3": "你到过最深层。", + "evt_coin_1": "捡到几枚金币。", + "evt_coin_2": "捡到一小把金币。", + "evt_coin_3": "捡到一大袋金币。", + "evt_potion": "一瓶药水,收进腰带。", + "evt_potion_full": "腰带满了,药水留在原地。", + "evt_weapon": "捡到更快的短刃,出手更狠了。", + "evt_armor": "换上更硬的护甲,更抗打了。", + "evt_stairs_lock": "楼梯被大东西堵住了,先赢它。", + "evt_descend": "你顺着楼梯,往下潜。", + "btl_bat": "盲眼蝠扑了过来!", + "btl_rat": "窟鼠窜了出来!", + "btl_mush": "蘑菇怪拦住了你!", + "btl_crab": "窟主石甲蟹,就在你面前。", + "btl_crawler": "绕行者贴上来了,小心背后!", + "btl_spider": "洞蛛落地了!", + "btl_leech": "窟主腐沼蟒,挡住了楼梯。", + "btl_guard": "石像卫醒了!", + "btl_king": "窟中之王,就在深处。", + "btl_parried": "挡下了大半!", + "btl_behind": "它在你背后,转身再挡。", + "btl_swish": "挥空了,它不在你面前。", + "btl_no_turn": "它贴着你不放,不用转身。", + "btl_kill": "它倒下了。", + "btl_loot_1": "战利不多。", + "btl_loot_2": "小有收获。", + "btl_loot_3": "大有收获。", + "btl_lvup": "你的拳脚更利落了。", + "btl_flee": "你甩掉了它,被抓了一下。", + "btl_flee_boss": "退路塌了,跑不掉!", + "btl_half_crab": "石甲蟹的甲裂了!", + "btl_half_leech": "腐沼蟒暴怒了!", + "btl_half_king": "王怒了,脚步更重了!", + "die_intro": "你倒下了。", + "die_cause_1": "你被小怪拖垮了。", + "die_cause_2": "你没挡住那记重击。", + "die_cause_3": "你背后的偷袭,要了命。", + "die_adv_1": "下次,前摇声响时就双击。", + "die_adv_2": "打不过,就按住屏幕逃跑。", + "die_adv_3": "身后有动静,先转身。", + "gd_pulse": "单击脉冲,听听四周。", + "gd_turn": "轮到你了。单击挥击。", + "gd_invalid": "这个操作在这里用不上。", + "stair_hint_a": "找找铁器的回声,那是楼梯。", + "stair_hint_b": "楼梯有大东西守着,先赢它。", + "stair_dir_e": "楼梯在东边。", + "stair_dir_s": "楼梯在南边。", + "stair_dir_w": "楼梯在西边。", + "stair_dir_n": "楼梯在北边。", + "nav_e": "往东。", + "nav_s": "往南。", + "nav_w": "往西。", + "nav_n": "往北。", + "nav_done": "到了,就是这里。", + "meta_gold_1": "这趟没带回什么。", + "meta_gold_2": "这趟带回一些金币。", + "meta_gold_3": "这趟满载而归。", + "settle_stronger": "下个周目,你会更强。", + "settle_win_1": "你捧起了地窟之心,通关了!", + "settle_win_2": "黑暗的尽头,是光。", + "settle_again": "单击,再探一次。", + "unlock_lead": "攒够了新的遗产。", + "unlock_potion": "解锁行囊药,开局自带一瓶。", + "unlock_vigor": "解锁体魄,起始更强壮。", + "unlock_veil": "解锁护符,每层头一下减半。", + "unlock_echo": "解锁回声强化,侧向听得更远。", + "unlock_blade": "解锁武器,开局带着短刃。", + "unlock_none": "金币还不够,继续攒。" } } \ No newline at end of file diff --git a/web/index.html b/web/index.html index 3f7f78ea..970c1664 100644 --- a/web/index.html +++ b/web/index.html @@ -155,8 +155,20 @@
+ +

不眠频率 新作

+ RADIO · MIDNIGHT HOTLINE +

深夜电台热线 RPG。一晚五个来电,你接话的方式,改变五个陌生人的夜晚。

+

+
+ +

回声地窟 新作

+ DUNGEON · ECHO DELVE +

听声辨位 Roguelike。单击发出声波脉冲,用回声在黑暗中走出一条生路。

+

+
-

剑与声 新作

+

剑与声

RPG · SWORD & SOUND

盲剑客武侠 RPG。眼盲,耳即眼——听声辨招,双击格挡,踏平黑风寨。

@@ -202,7 +214,7 @@ import { settings } from './engine/settings.js'; const AUTH_KEY = 'soundgame.auth'; - const GAMES = ['rpg', 'storyline', 'escape', 'simon', 'rhythm', 'space']; + const GAMES = ['radio', 'dungeon', 'rpg', 'storyline', 'escape', 'simon', 'rhythm', 'space']; // ---------- 认证状态 ---------- function getAuth() { diff --git a/web/radio/audio/bgm/bridge.m4a b/web/radio/audio/bgm/bridge.m4a new file mode 100644 index 00000000..3bc5d8e5 Binary files /dev/null and b/web/radio/audio/bgm/bridge.m4a differ diff --git a/web/radio/audio/bgm/bridge.ogg b/web/radio/audio/bgm/bridge.ogg new file mode 100644 index 00000000..acfab657 Binary files /dev/null and b/web/radio/audio/bgm/bridge.ogg differ diff --git a/web/radio/audio/bgm/ending.m4a b/web/radio/audio/bgm/ending.m4a new file mode 100644 index 00000000..6f7b0a61 Binary files /dev/null and b/web/radio/audio/bgm/ending.m4a differ diff --git a/web/radio/audio/bgm/ending.ogg b/web/radio/audio/bgm/ending.ogg new file mode 100644 index 00000000..0fcbc629 Binary files /dev/null and b/web/radio/audio/bgm/ending.ogg differ diff --git a/web/radio/audio/bgm/menu.m4a b/web/radio/audio/bgm/menu.m4a new file mode 100644 index 00000000..c92f78f2 Binary files /dev/null and b/web/radio/audio/bgm/menu.m4a differ diff --git a/web/radio/audio/bgm/menu.ogg b/web/radio/audio/bgm/menu.ogg new file mode 100644 index 00000000..a4884407 Binary files /dev/null and b/web/radio/audio/bgm/menu.ogg differ diff --git a/web/radio/audio/bgm/onair.m4a b/web/radio/audio/bgm/onair.m4a new file mode 100644 index 00000000..400b2bff Binary files /dev/null and b/web/radio/audio/bgm/onair.m4a differ diff --git a/web/radio/audio/bgm/onair.ogg b/web/radio/audio/bgm/onair.ogg new file mode 100644 index 00000000..4963c629 Binary files /dev/null and b/web/radio/audio/bgm/onair.ogg differ diff --git a/web/radio/audio/sfx/phone_ring.m4a b/web/radio/audio/sfx/phone_ring.m4a new file mode 100644 index 00000000..1ee12c8a Binary files /dev/null and b/web/radio/audio/sfx/phone_ring.m4a differ diff --git a/web/radio/audio/sfx/phone_ring.ogg b/web/radio/audio/sfx/phone_ring.ogg new file mode 100644 index 00000000..219fb484 Binary files /dev/null and b/web/radio/audio/sfx/phone_ring.ogg differ diff --git a/web/radio/audio/sfx/ui_confirm.m4a b/web/radio/audio/sfx/ui_confirm.m4a new file mode 100644 index 00000000..1becad0d Binary files /dev/null and b/web/radio/audio/sfx/ui_confirm.m4a differ diff --git a/web/radio/audio/sfx/ui_confirm.ogg b/web/radio/audio/sfx/ui_confirm.ogg new file mode 100644 index 00000000..51d23109 Binary files /dev/null and b/web/radio/audio/sfx/ui_confirm.ogg differ diff --git a/web/radio/audio/sfx/ui_menu.m4a b/web/radio/audio/sfx/ui_menu.m4a new file mode 100644 index 00000000..be523677 Binary files /dev/null and b/web/radio/audio/sfx/ui_menu.m4a differ diff --git a/web/radio/audio/sfx/ui_menu.ogg b/web/radio/audio/sfx/ui_menu.ogg new file mode 100644 index 00000000..dd8df6f7 Binary files /dev/null and b/web/radio/audio/sfx/ui_menu.ogg differ diff --git a/web/radio/audio/voice/female/b_goal.m4a b/web/radio/audio/voice/female/b_goal.m4a new file mode 100644 index 00000000..2e1ff287 Binary files /dev/null and b/web/radio/audio/voice/female/b_goal.m4a differ diff --git a/web/radio/audio/voice/female/b_goal.ogg b/web/radio/audio/voice/female/b_goal.ogg new file mode 100644 index 00000000..a073e497 Binary files /dev/null and b/web/radio/audio/voice/female/b_goal.ogg differ diff --git a/web/radio/audio/voice/female/b_open.m4a b/web/radio/audio/voice/female/b_open.m4a new file mode 100644 index 00000000..fa1d59b5 Binary files /dev/null and b/web/radio/audio/voice/female/b_open.m4a differ diff --git a/web/radio/audio/voice/female/b_open.ogg b/web/radio/audio/voice/female/b_open.ogg new file mode 100644 index 00000000..f0dff750 Binary files /dev/null and b/web/radio/audio/voice/female/b_open.ogg differ diff --git a/web/radio/audio/voice/female/b_rain.m4a b/web/radio/audio/voice/female/b_rain.m4a new file mode 100644 index 00000000..3ca9b6d5 Binary files /dev/null and b/web/radio/audio/voice/female/b_rain.m4a differ diff --git a/web/radio/audio/voice/female/b_rain.ogg b/web/radio/audio/voice/female/b_rain.ogg new file mode 100644 index 00000000..dc83fe89 Binary files /dev/null and b/web/radio/audio/voice/female/b_rain.ogg differ diff --git a/web/radio/audio/voice/female/b_rule.m4a b/web/radio/audio/voice/female/b_rule.m4a new file mode 100644 index 00000000..6128d233 Binary files /dev/null and b/web/radio/audio/voice/female/b_rule.m4a differ diff --git a/web/radio/audio/voice/female/b_rule.ogg b/web/radio/audio/voice/female/b_rule.ogg new file mode 100644 index 00000000..98f08f55 Binary files /dev/null and b/web/radio/audio/voice/female/b_rule.ogg differ diff --git a/web/radio/audio/voice/female/d9_avoid.m4a b/web/radio/audio/voice/female/d9_avoid.m4a new file mode 100644 index 00000000..6bf2fc89 Binary files /dev/null and b/web/radio/audio/voice/female/d9_avoid.m4a differ diff --git a/web/radio/audio/voice/female/d9_avoid.ogg b/web/radio/audio/voice/female/d9_avoid.ogg new file mode 100644 index 00000000..3ceb8647 Binary files /dev/null and b/web/radio/audio/voice/female/d9_avoid.ogg differ diff --git a/web/radio/audio/voice/female/d9_avoide.m4a b/web/radio/audio/voice/female/d9_avoide.m4a new file mode 100644 index 00000000..43ca9ebf Binary files /dev/null and b/web/radio/audio/voice/female/d9_avoide.m4a differ diff --git a/web/radio/audio/voice/female/d9_avoide.ogg b/web/radio/audio/voice/female/d9_avoide.ogg new file mode 100644 index 00000000..e2ddeffc Binary files /dev/null and b/web/radio/audio/voice/female/d9_avoide.ogg differ diff --git a/web/radio/audio/voice/female/d9_face.m4a b/web/radio/audio/voice/female/d9_face.m4a new file mode 100644 index 00000000..cc2d1efb Binary files /dev/null and b/web/radio/audio/voice/female/d9_face.m4a differ diff --git a/web/radio/audio/voice/female/d9_face.ogg b/web/radio/audio/voice/female/d9_face.ogg new file mode 100644 index 00000000..1dcf50f2 Binary files /dev/null and b/web/radio/audio/voice/female/d9_face.ogg differ diff --git a/web/radio/audio/voice/female/d9_facee.m4a b/web/radio/audio/voice/female/d9_facee.m4a new file mode 100644 index 00000000..44ec2ff1 Binary files /dev/null and b/web/radio/audio/voice/female/d9_facee.m4a differ diff --git a/web/radio/audio/voice/female/d9_facee.ogg b/web/radio/audio/voice/female/d9_facee.ogg new file mode 100644 index 00000000..11f623ab Binary files /dev/null and b/web/radio/audio/voice/female/d9_facee.ogg differ diff --git a/web/radio/audio/voice/female/e1_a.m4a b/web/radio/audio/voice/female/e1_a.m4a new file mode 100644 index 00000000..0c3c1b11 Binary files /dev/null and b/web/radio/audio/voice/female/e1_a.m4a differ diff --git a/web/radio/audio/voice/female/e1_a.ogg b/web/radio/audio/voice/female/e1_a.ogg new file mode 100644 index 00000000..0d58e25f Binary files /dev/null and b/web/radio/audio/voice/female/e1_a.ogg differ diff --git a/web/radio/audio/voice/female/e1_b.m4a b/web/radio/audio/voice/female/e1_b.m4a new file mode 100644 index 00000000..d188b6fb Binary files /dev/null and b/web/radio/audio/voice/female/e1_b.m4a differ diff --git a/web/radio/audio/voice/female/e1_b.ogg b/web/radio/audio/voice/female/e1_b.ogg new file mode 100644 index 00000000..edf6d82f Binary files /dev/null and b/web/radio/audio/voice/female/e1_b.ogg differ diff --git a/web/radio/audio/voice/female/e2_a.m4a b/web/radio/audio/voice/female/e2_a.m4a new file mode 100644 index 00000000..0e6d0964 Binary files /dev/null and b/web/radio/audio/voice/female/e2_a.m4a differ diff --git a/web/radio/audio/voice/female/e2_a.ogg b/web/radio/audio/voice/female/e2_a.ogg new file mode 100644 index 00000000..17a4d360 Binary files /dev/null and b/web/radio/audio/voice/female/e2_a.ogg differ diff --git a/web/radio/audio/voice/female/e2_b.m4a b/web/radio/audio/voice/female/e2_b.m4a new file mode 100644 index 00000000..7cb8e745 Binary files /dev/null and b/web/radio/audio/voice/female/e2_b.m4a differ diff --git a/web/radio/audio/voice/female/e2_b.ogg b/web/radio/audio/voice/female/e2_b.ogg new file mode 100644 index 00000000..270ef7f6 Binary files /dev/null and b/web/radio/audio/voice/female/e2_b.ogg differ diff --git a/web/radio/audio/voice/female/e3_a.m4a b/web/radio/audio/voice/female/e3_a.m4a new file mode 100644 index 00000000..1e9a3a1e Binary files /dev/null and b/web/radio/audio/voice/female/e3_a.m4a differ diff --git a/web/radio/audio/voice/female/e3_a.ogg b/web/radio/audio/voice/female/e3_a.ogg new file mode 100644 index 00000000..7fc2d857 Binary files /dev/null and b/web/radio/audio/voice/female/e3_a.ogg differ diff --git a/web/radio/audio/voice/female/e3_b.m4a b/web/radio/audio/voice/female/e3_b.m4a new file mode 100644 index 00000000..02b7c424 Binary files /dev/null and b/web/radio/audio/voice/female/e3_b.m4a differ diff --git a/web/radio/audio/voice/female/e3_b.ogg b/web/radio/audio/voice/female/e3_b.ogg new file mode 100644 index 00000000..2daf93c7 Binary files /dev/null and b/web/radio/audio/voice/female/e3_b.ogg differ diff --git a/web/radio/audio/voice/female/e4_a.m4a b/web/radio/audio/voice/female/e4_a.m4a new file mode 100644 index 00000000..e5ad9000 Binary files /dev/null and b/web/radio/audio/voice/female/e4_a.m4a differ diff --git a/web/radio/audio/voice/female/e4_a.ogg b/web/radio/audio/voice/female/e4_a.ogg new file mode 100644 index 00000000..5a32e1fb Binary files /dev/null and b/web/radio/audio/voice/female/e4_a.ogg differ diff --git a/web/radio/audio/voice/female/e4_b.m4a b/web/radio/audio/voice/female/e4_b.m4a new file mode 100644 index 00000000..0821a5c0 Binary files /dev/null and b/web/radio/audio/voice/female/e4_b.m4a differ diff --git a/web/radio/audio/voice/female/e4_b.ogg b/web/radio/audio/voice/female/e4_b.ogg new file mode 100644 index 00000000..5fd507b1 Binary files /dev/null and b/web/radio/audio/voice/female/e4_b.ogg differ diff --git a/web/radio/audio/voice/female/ep_deer_no.m4a b/web/radio/audio/voice/female/ep_deer_no.m4a new file mode 100644 index 00000000..597b05ba Binary files /dev/null and b/web/radio/audio/voice/female/ep_deer_no.m4a differ diff --git a/web/radio/audio/voice/female/ep_deer_no.ogg b/web/radio/audio/voice/female/ep_deer_no.ogg new file mode 100644 index 00000000..1d6b3388 Binary files /dev/null and b/web/radio/audio/voice/female/ep_deer_no.ogg differ diff --git a/web/radio/audio/voice/female/ep_deer_ok.m4a b/web/radio/audio/voice/female/ep_deer_ok.m4a new file mode 100644 index 00000000..3b1c360c Binary files /dev/null and b/web/radio/audio/voice/female/ep_deer_ok.m4a differ diff --git a/web/radio/audio/voice/female/ep_deer_ok.ogg b/web/radio/audio/voice/female/ep_deer_ok.ogg new file mode 100644 index 00000000..22eced63 Binary files /dev/null and b/web/radio/audio/voice/female/ep_deer_ok.ogg differ diff --git a/web/radio/audio/voice/female/ep_han_no.m4a b/web/radio/audio/voice/female/ep_han_no.m4a new file mode 100644 index 00000000..e2ae4e29 Binary files /dev/null and b/web/radio/audio/voice/female/ep_han_no.m4a differ diff --git a/web/radio/audio/voice/female/ep_han_no.ogg b/web/radio/audio/voice/female/ep_han_no.ogg new file mode 100644 index 00000000..4735ba32 Binary files /dev/null and b/web/radio/audio/voice/female/ep_han_no.ogg differ diff --git a/web/radio/audio/voice/female/ep_han_ok.m4a b/web/radio/audio/voice/female/ep_han_ok.m4a new file mode 100644 index 00000000..ee1b9601 Binary files /dev/null and b/web/radio/audio/voice/female/ep_han_ok.m4a differ diff --git a/web/radio/audio/voice/female/ep_han_ok.ogg b/web/radio/audio/voice/female/ep_han_ok.ogg new file mode 100644 index 00000000..576e6840 Binary files /dev/null and b/web/radio/audio/voice/female/ep_han_ok.ogg differ diff --git a/web/radio/audio/voice/female/ep_kid_no.m4a b/web/radio/audio/voice/female/ep_kid_no.m4a new file mode 100644 index 00000000..e64e6c94 Binary files /dev/null and b/web/radio/audio/voice/female/ep_kid_no.m4a differ diff --git a/web/radio/audio/voice/female/ep_kid_no.ogg b/web/radio/audio/voice/female/ep_kid_no.ogg new file mode 100644 index 00000000..5862ce5e Binary files /dev/null and b/web/radio/audio/voice/female/ep_kid_no.ogg differ diff --git a/web/radio/audio/voice/female/ep_kid_ok.m4a b/web/radio/audio/voice/female/ep_kid_ok.m4a new file mode 100644 index 00000000..2649d6b1 Binary files /dev/null and b/web/radio/audio/voice/female/ep_kid_ok.m4a differ diff --git a/web/radio/audio/voice/female/ep_kid_ok.ogg b/web/radio/audio/voice/female/ep_kid_ok.ogg new file mode 100644 index 00000000..b8d981cb Binary files /dev/null and b/web/radio/audio/voice/female/ep_kid_ok.ogg differ diff --git a/web/radio/audio/voice/female/ep_lead.m4a b/web/radio/audio/voice/female/ep_lead.m4a new file mode 100644 index 00000000..d05d66a9 Binary files /dev/null and b/web/radio/audio/voice/female/ep_lead.m4a differ diff --git a/web/radio/audio/voice/female/ep_lead.ogg b/web/radio/audio/voice/female/ep_lead.ogg new file mode 100644 index 00000000..cc1a643c Binary files /dev/null and b/web/radio/audio/voice/female/ep_lead.ogg differ diff --git a/web/radio/audio/voice/female/ep_wife_no.m4a b/web/radio/audio/voice/female/ep_wife_no.m4a new file mode 100644 index 00000000..c39c16d0 Binary files /dev/null and b/web/radio/audio/voice/female/ep_wife_no.m4a differ diff --git a/web/radio/audio/voice/female/ep_wife_no.ogg b/web/radio/audio/voice/female/ep_wife_no.ogg new file mode 100644 index 00000000..8ecf79d1 Binary files /dev/null and b/web/radio/audio/voice/female/ep_wife_no.ogg differ diff --git a/web/radio/audio/voice/female/ep_wife_ok.m4a b/web/radio/audio/voice/female/ep_wife_ok.m4a new file mode 100644 index 00000000..8a08462e Binary files /dev/null and b/web/radio/audio/voice/female/ep_wife_ok.m4a differ diff --git a/web/radio/audio/voice/female/ep_wife_ok.ogg b/web/radio/audio/voice/female/ep_wife_ok.ogg new file mode 100644 index 00000000..e6d99ac9 Binary files /dev/null and b/web/radio/audio/voice/female/ep_wife_ok.ogg differ diff --git a/web/radio/audio/voice/female/ep_zhou_no.m4a b/web/radio/audio/voice/female/ep_zhou_no.m4a new file mode 100644 index 00000000..8a7b7bb4 Binary files /dev/null and b/web/radio/audio/voice/female/ep_zhou_no.m4a differ diff --git a/web/radio/audio/voice/female/ep_zhou_no.ogg b/web/radio/audio/voice/female/ep_zhou_no.ogg new file mode 100644 index 00000000..5828f4cd Binary files /dev/null and b/web/radio/audio/voice/female/ep_zhou_no.ogg differ diff --git a/web/radio/audio/voice/female/ep_zhou_ok.m4a b/web/radio/audio/voice/female/ep_zhou_ok.m4a new file mode 100644 index 00000000..31d5d094 Binary files /dev/null and b/web/radio/audio/voice/female/ep_zhou_ok.m4a differ diff --git a/web/radio/audio/voice/female/ep_zhou_ok.ogg b/web/radio/audio/voice/female/ep_zhou_ok.ogg new file mode 100644 index 00000000..beb7747c Binary files /dev/null and b/web/radio/audio/voice/female/ep_zhou_ok.ogg differ diff --git a/web/radio/audio/voice/female/g9_in.m4a b/web/radio/audio/voice/female/g9_in.m4a new file mode 100644 index 00000000..014d160f Binary files /dev/null and b/web/radio/audio/voice/female/g9_in.m4a differ diff --git a/web/radio/audio/voice/female/g9_in.ogg b/web/radio/audio/voice/female/g9_in.ogg new file mode 100644 index 00000000..64f7a67f Binary files /dev/null and b/web/radio/audio/voice/female/g9_in.ogg differ diff --git a/web/radio/audio/voice/female/gd_again.m4a b/web/radio/audio/voice/female/gd_again.m4a new file mode 100644 index 00000000..c72275ef Binary files /dev/null and b/web/radio/audio/voice/female/gd_again.m4a differ diff --git a/web/radio/audio/voice/female/gd_again.ogg b/web/radio/audio/voice/female/gd_again.ogg new file mode 100644 index 00000000..8a2e39b6 Binary files /dev/null and b/web/radio/audio/voice/female/gd_again.ogg differ diff --git a/web/radio/audio/voice/female/gd_brief.m4a b/web/radio/audio/voice/female/gd_brief.m4a new file mode 100644 index 00000000..f4224bdf Binary files /dev/null and b/web/radio/audio/voice/female/gd_brief.m4a differ diff --git a/web/radio/audio/voice/female/gd_brief.ogg b/web/radio/audio/voice/female/gd_brief.ogg new file mode 100644 index 00000000..5c271e26 Binary files /dev/null and b/web/radio/audio/voice/female/gd_brief.ogg differ diff --git a/web/radio/audio/voice/female/gd_continue.m4a b/web/radio/audio/voice/female/gd_continue.m4a new file mode 100644 index 00000000..323fcfa8 Binary files /dev/null and b/web/radio/audio/voice/female/gd_continue.m4a differ diff --git a/web/radio/audio/voice/female/gd_continue.ogg b/web/radio/audio/voice/female/gd_continue.ogg new file mode 100644 index 00000000..5ad1e76e Binary files /dev/null and b/web/radio/audio/voice/female/gd_continue.ogg differ diff --git a/web/radio/audio/voice/female/gd_invalid.m4a b/web/radio/audio/voice/female/gd_invalid.m4a new file mode 100644 index 00000000..579cc44d Binary files /dev/null and b/web/radio/audio/voice/female/gd_invalid.m4a differ diff --git a/web/radio/audio/voice/female/gd_invalid.ogg b/web/radio/audio/voice/female/gd_invalid.ogg new file mode 100644 index 00000000..9b20f7cf Binary files /dev/null and b/web/radio/audio/voice/female/gd_invalid.ogg differ diff --git a/web/radio/audio/voice/female/gd_ring1.m4a b/web/radio/audio/voice/female/gd_ring1.m4a new file mode 100644 index 00000000..482b7407 Binary files /dev/null and b/web/radio/audio/voice/female/gd_ring1.m4a differ diff --git a/web/radio/audio/voice/female/gd_ring1.ogg b/web/radio/audio/voice/female/gd_ring1.ogg new file mode 100644 index 00000000..edab1bd0 Binary files /dev/null and b/web/radio/audio/voice/female/gd_ring1.ogg differ diff --git a/web/radio/audio/voice/female/gd_ring2.m4a b/web/radio/audio/voice/female/gd_ring2.m4a new file mode 100644 index 00000000..32593f6c Binary files /dev/null and b/web/radio/audio/voice/female/gd_ring2.m4a differ diff --git a/web/radio/audio/voice/female/gd_ring2.ogg b/web/radio/audio/voice/female/gd_ring2.ogg new file mode 100644 index 00000000..3921f438 Binary files /dev/null and b/web/radio/audio/voice/female/gd_ring2.ogg differ diff --git a/web/radio/audio/voice/female/h1_danger.m4a b/web/radio/audio/voice/female/h1_danger.m4a new file mode 100644 index 00000000..fdfdddc4 Binary files /dev/null and b/web/radio/audio/voice/female/h1_danger.m4a differ diff --git a/web/radio/audio/voice/female/h1_danger.ogg b/web/radio/audio/voice/female/h1_danger.ogg new file mode 100644 index 00000000..50f9dac4 Binary files /dev/null and b/web/radio/audio/voice/female/h1_danger.ogg differ diff --git a/web/radio/audio/voice/female/h1_echo.m4a b/web/radio/audio/voice/female/h1_echo.m4a new file mode 100644 index 00000000..a4ad0dcf Binary files /dev/null and b/web/radio/audio/voice/female/h1_echo.m4a differ diff --git a/web/radio/audio/voice/female/h1_echo.ogg b/web/radio/audio/voice/female/h1_echo.ogg new file mode 100644 index 00000000..e4b5e338 Binary files /dev/null and b/web/radio/audio/voice/female/h1_echo.ogg differ diff --git a/web/radio/audio/voice/female/h1_in.m4a b/web/radio/audio/voice/female/h1_in.m4a new file mode 100644 index 00000000..328ed69d Binary files /dev/null and b/web/radio/audio/voice/female/h1_in.m4a differ diff --git a/web/radio/audio/voice/female/h1_in.ogg b/web/radio/audio/voice/female/h1_in.ogg new file mode 100644 index 00000000..89517360 Binary files /dev/null and b/web/radio/audio/voice/female/h1_in.ogg differ diff --git a/web/radio/audio/voice/female/h1_stop.m4a b/web/radio/audio/voice/female/h1_stop.m4a new file mode 100644 index 00000000..0b578db9 Binary files /dev/null and b/web/radio/audio/voice/female/h1_stop.m4a differ diff --git a/web/radio/audio/voice/female/h1_stop.ogg b/web/radio/audio/voice/female/h1_stop.ogg new file mode 100644 index 00000000..87f2a732 Binary files /dev/null and b/web/radio/audio/voice/female/h1_stop.ogg differ diff --git a/web/radio/audio/voice/female/h1_stope.m4a b/web/radio/audio/voice/female/h1_stope.m4a new file mode 100644 index 00000000..30221538 Binary files /dev/null and b/web/radio/audio/voice/female/h1_stope.m4a differ diff --git a/web/radio/audio/voice/female/h1_stope.ogg b/web/radio/audio/voice/female/h1_stope.ogg new file mode 100644 index 00000000..18c03a92 Binary files /dev/null and b/web/radio/audio/voice/female/h1_stope.ogg differ diff --git a/web/radio/audio/voice/female/h1_talk.m4a b/web/radio/audio/voice/female/h1_talk.m4a new file mode 100644 index 00000000..7c011a0b Binary files /dev/null and b/web/radio/audio/voice/female/h1_talk.m4a differ diff --git a/web/radio/audio/voice/female/h1_talk.ogg b/web/radio/audio/voice/female/h1_talk.ogg new file mode 100644 index 00000000..8818b65a Binary files /dev/null and b/web/radio/audio/voice/female/h1_talk.ogg differ diff --git a/web/radio/audio/voice/female/h1_talke.m4a b/web/radio/audio/voice/female/h1_talke.m4a new file mode 100644 index 00000000..9a91f0cc Binary files /dev/null and b/web/radio/audio/voice/female/h1_talke.m4a differ diff --git a/web/radio/audio/voice/female/h1_talke.ogg b/web/radio/audio/voice/female/h1_talke.ogg new file mode 100644 index 00000000..ddac68dd Binary files /dev/null and b/web/radio/audio/voice/female/h1_talke.ogg differ diff --git a/web/radio/audio/voice/female/k1_ask.m4a b/web/radio/audio/voice/female/k1_ask.m4a new file mode 100644 index 00000000..2141a18c Binary files /dev/null and b/web/radio/audio/voice/female/k1_ask.m4a differ diff --git a/web/radio/audio/voice/female/k1_ask.ogg b/web/radio/audio/voice/female/k1_ask.ogg new file mode 100644 index 00000000..e7ee5922 Binary files /dev/null and b/web/radio/audio/voice/female/k1_ask.ogg differ diff --git a/web/radio/audio/voice/female/k1_in.m4a b/web/radio/audio/voice/female/k1_in.m4a new file mode 100644 index 00000000..2c4a2eb1 Binary files /dev/null and b/web/radio/audio/voice/female/k1_in.m4a differ diff --git a/web/radio/audio/voice/female/k1_in.ogg b/web/radio/audio/voice/female/k1_in.ogg new file mode 100644 index 00000000..ba997213 Binary files /dev/null and b/web/radio/audio/voice/female/k1_in.ogg differ diff --git a/web/radio/audio/voice/female/k1_lie.m4a b/web/radio/audio/voice/female/k1_lie.m4a new file mode 100644 index 00000000..634693d9 Binary files /dev/null and b/web/radio/audio/voice/female/k1_lie.m4a differ diff --git a/web/radio/audio/voice/female/k1_lie.ogg b/web/radio/audio/voice/female/k1_lie.ogg new file mode 100644 index 00000000..d497b6b7 Binary files /dev/null and b/web/radio/audio/voice/female/k1_lie.ogg differ diff --git a/web/radio/audio/voice/female/k1_liee.m4a b/web/radio/audio/voice/female/k1_liee.m4a new file mode 100644 index 00000000..0f6f33fe Binary files /dev/null and b/web/radio/audio/voice/female/k1_liee.m4a differ diff --git a/web/radio/audio/voice/female/k1_liee.ogg b/web/radio/audio/voice/female/k1_liee.ogg new file mode 100644 index 00000000..60ba3c38 Binary files /dev/null and b/web/radio/audio/voice/female/k1_liee.ogg differ diff --git a/web/radio/audio/voice/female/k1_lull.m4a b/web/radio/audio/voice/female/k1_lull.m4a new file mode 100644 index 00000000..f1d45099 Binary files /dev/null and b/web/radio/audio/voice/female/k1_lull.m4a differ diff --git a/web/radio/audio/voice/female/k1_lull.ogg b/web/radio/audio/voice/female/k1_lull.ogg new file mode 100644 index 00000000..5ddb2ccd Binary files /dev/null and b/web/radio/audio/voice/female/k1_lull.ogg differ diff --git a/web/radio/audio/voice/female/k1_lulle.m4a b/web/radio/audio/voice/female/k1_lulle.m4a new file mode 100644 index 00000000..880c08e7 Binary files /dev/null and b/web/radio/audio/voice/female/k1_lulle.m4a differ diff --git a/web/radio/audio/voice/female/k1_lulle.ogg b/web/radio/audio/voice/female/k1_lulle.ogg new file mode 100644 index 00000000..9eed814b Binary files /dev/null and b/web/radio/audio/voice/female/k1_lulle.ogg differ diff --git a/web/radio/audio/voice/female/k1_truth.m4a b/web/radio/audio/voice/female/k1_truth.m4a new file mode 100644 index 00000000..ca50543e Binary files /dev/null and b/web/radio/audio/voice/female/k1_truth.m4a differ diff --git a/web/radio/audio/voice/female/k1_truth.ogg b/web/radio/audio/voice/female/k1_truth.ogg new file mode 100644 index 00000000..8868853c Binary files /dev/null and b/web/radio/audio/voice/female/k1_truth.ogg differ diff --git a/web/radio/audio/voice/female/k1_truthe.m4a b/web/radio/audio/voice/female/k1_truthe.m4a new file mode 100644 index 00000000..2a6915db Binary files /dev/null and b/web/radio/audio/voice/female/k1_truthe.m4a differ diff --git a/web/radio/audio/voice/female/k1_truthe.ogg b/web/radio/audio/voice/female/k1_truthe.ogg new file mode 100644 index 00000000..2fd3e65a Binary files /dev/null and b/web/radio/audio/voice/female/k1_truthe.ogg differ diff --git a/web/radio/audio/voice/female/l1_free.m4a b/web/radio/audio/voice/female/l1_free.m4a new file mode 100644 index 00000000..3798370a Binary files /dev/null and b/web/radio/audio/voice/female/l1_free.m4a differ diff --git a/web/radio/audio/voice/female/l1_free.ogg b/web/radio/audio/voice/female/l1_free.ogg new file mode 100644 index 00000000..4e7ab8bd Binary files /dev/null and b/web/radio/audio/voice/female/l1_free.ogg differ diff --git a/web/radio/audio/voice/female/l1_freee.m4a b/web/radio/audio/voice/female/l1_freee.m4a new file mode 100644 index 00000000..3f3c1a1d Binary files /dev/null and b/web/radio/audio/voice/female/l1_freee.m4a differ diff --git a/web/radio/audio/voice/female/l1_freee.ogg b/web/radio/audio/voice/female/l1_freee.ogg new file mode 100644 index 00000000..46aa034c Binary files /dev/null and b/web/radio/audio/voice/female/l1_freee.ogg differ diff --git a/web/radio/audio/voice/female/l1_go.m4a b/web/radio/audio/voice/female/l1_go.m4a new file mode 100644 index 00000000..34cf2354 Binary files /dev/null and b/web/radio/audio/voice/female/l1_go.m4a differ diff --git a/web/radio/audio/voice/female/l1_go.ogg b/web/radio/audio/voice/female/l1_go.ogg new file mode 100644 index 00000000..0bd98cd5 Binary files /dev/null and b/web/radio/audio/voice/female/l1_go.ogg differ diff --git a/web/radio/audio/voice/female/l1_goe.m4a b/web/radio/audio/voice/female/l1_goe.m4a new file mode 100644 index 00000000..85a2e88e Binary files /dev/null and b/web/radio/audio/voice/female/l1_goe.m4a differ diff --git a/web/radio/audio/voice/female/l1_goe.ogg b/web/radio/audio/voice/female/l1_goe.ogg new file mode 100644 index 00000000..6285a146 Binary files /dev/null and b/web/radio/audio/voice/female/l1_goe.ogg differ diff --git a/web/radio/audio/voice/female/l1_in.m4a b/web/radio/audio/voice/female/l1_in.m4a new file mode 100644 index 00000000..bf99c475 Binary files /dev/null and b/web/radio/audio/voice/female/l1_in.m4a differ diff --git a/web/radio/audio/voice/female/l1_in.ogg b/web/radio/audio/voice/female/l1_in.ogg new file mode 100644 index 00000000..4cb85309 Binary files /dev/null and b/web/radio/audio/voice/female/l1_in.ogg differ diff --git a/web/radio/audio/voice/female/p1.m4a b/web/radio/audio/voice/female/p1.m4a new file mode 100644 index 00000000..fc1f9223 Binary files /dev/null and b/web/radio/audio/voice/female/p1.m4a differ diff --git a/web/radio/audio/voice/female/p1.ogg b/web/radio/audio/voice/female/p1.ogg new file mode 100644 index 00000000..a7e31aa5 Binary files /dev/null and b/web/radio/audio/voice/female/p1.ogg differ diff --git a/web/radio/audio/voice/female/p2.m4a b/web/radio/audio/voice/female/p2.m4a new file mode 100644 index 00000000..9c618557 Binary files /dev/null and b/web/radio/audio/voice/female/p2.m4a differ diff --git a/web/radio/audio/voice/female/p2.ogg b/web/radio/audio/voice/female/p2.ogg new file mode 100644 index 00000000..dae3d927 Binary files /dev/null and b/web/radio/audio/voice/female/p2.ogg differ diff --git a/web/radio/audio/voice/female/p3.m4a b/web/radio/audio/voice/female/p3.m4a new file mode 100644 index 00000000..f961006b Binary files /dev/null and b/web/radio/audio/voice/female/p3.m4a differ diff --git a/web/radio/audio/voice/female/p3.ogg b/web/radio/audio/voice/female/p3.ogg new file mode 100644 index 00000000..cae4609f Binary files /dev/null and b/web/radio/audio/voice/female/p3.ogg differ diff --git a/web/radio/audio/voice/female/p4.m4a b/web/radio/audio/voice/female/p4.m4a new file mode 100644 index 00000000..a873416c Binary files /dev/null and b/web/radio/audio/voice/female/p4.m4a differ diff --git a/web/radio/audio/voice/female/p4.ogg b/web/radio/audio/voice/female/p4.ogg new file mode 100644 index 00000000..b51a345e Binary files /dev/null and b/web/radio/audio/voice/female/p4.ogg differ diff --git a/web/radio/audio/voice/female/p5.m4a b/web/radio/audio/voice/female/p5.m4a new file mode 100644 index 00000000..17fe2cae Binary files /dev/null and b/web/radio/audio/voice/female/p5.m4a differ diff --git a/web/radio/audio/voice/female/p5.ogg b/web/radio/audio/voice/female/p5.ogg new file mode 100644 index 00000000..ea2f6795 Binary files /dev/null and b/web/radio/audio/voice/female/p5.ogg differ diff --git a/web/radio/audio/voice/female/p6.m4a b/web/radio/audio/voice/female/p6.m4a new file mode 100644 index 00000000..56f5f426 Binary files /dev/null and b/web/radio/audio/voice/female/p6.m4a differ diff --git a/web/radio/audio/voice/female/p6.ogg b/web/radio/audio/voice/female/p6.ogg new file mode 100644 index 00000000..e3113b46 Binary files /dev/null and b/web/radio/audio/voice/female/p6.ogg differ diff --git a/web/radio/audio/voice/female/p7.m4a b/web/radio/audio/voice/female/p7.m4a new file mode 100644 index 00000000..38ede7a2 Binary files /dev/null and b/web/radio/audio/voice/female/p7.m4a differ diff --git a/web/radio/audio/voice/female/p7.ogg b/web/radio/audio/voice/female/p7.ogg new file mode 100644 index 00000000..5bcd66bb Binary files /dev/null and b/web/radio/audio/voice/female/p7.ogg differ diff --git a/web/radio/audio/voice/female/p8.m4a b/web/radio/audio/voice/female/p8.m4a new file mode 100644 index 00000000..86e6167b Binary files /dev/null and b/web/radio/audio/voice/female/p8.m4a differ diff --git a/web/radio/audio/voice/female/p8.ogg b/web/radio/audio/voice/female/p8.ogg new file mode 100644 index 00000000..66e863a9 Binary files /dev/null and b/web/radio/audio/voice/female/p8.ogg differ diff --git a/web/radio/audio/voice/female/p9.m4a b/web/radio/audio/voice/female/p9.m4a new file mode 100644 index 00000000..a0e26ebf Binary files /dev/null and b/web/radio/audio/voice/female/p9.m4a differ diff --git a/web/radio/audio/voice/female/p9.ogg b/web/radio/audio/voice/female/p9.ogg new file mode 100644 index 00000000..eecefa5d Binary files /dev/null and b/web/radio/audio/voice/female/p9.ogg differ diff --git a/web/radio/audio/voice/female/prog_c1.m4a b/web/radio/audio/voice/female/prog_c1.m4a new file mode 100644 index 00000000..395e6178 Binary files /dev/null and b/web/radio/audio/voice/female/prog_c1.m4a differ diff --git a/web/radio/audio/voice/female/prog_c1.ogg b/web/radio/audio/voice/female/prog_c1.ogg new file mode 100644 index 00000000..f97b8777 Binary files /dev/null and b/web/radio/audio/voice/female/prog_c1.ogg differ diff --git a/web/radio/audio/voice/female/prog_c2.m4a b/web/radio/audio/voice/female/prog_c2.m4a new file mode 100644 index 00000000..8b5794af Binary files /dev/null and b/web/radio/audio/voice/female/prog_c2.m4a differ diff --git a/web/radio/audio/voice/female/prog_c2.ogg b/web/radio/audio/voice/female/prog_c2.ogg new file mode 100644 index 00000000..7971aa6f Binary files /dev/null and b/web/radio/audio/voice/female/prog_c2.ogg differ diff --git a/web/radio/audio/voice/female/prog_c3.m4a b/web/radio/audio/voice/female/prog_c3.m4a new file mode 100644 index 00000000..0c7d646c Binary files /dev/null and b/web/radio/audio/voice/female/prog_c3.m4a differ diff --git a/web/radio/audio/voice/female/prog_c3.ogg b/web/radio/audio/voice/female/prog_c3.ogg new file mode 100644 index 00000000..d06b505e Binary files /dev/null and b/web/radio/audio/voice/female/prog_c3.ogg differ diff --git a/web/radio/audio/voice/female/prog_c4.m4a b/web/radio/audio/voice/female/prog_c4.m4a new file mode 100644 index 00000000..32d3d952 Binary files /dev/null and b/web/radio/audio/voice/female/prog_c4.m4a differ diff --git a/web/radio/audio/voice/female/prog_c4.ogg b/web/radio/audio/voice/female/prog_c4.ogg new file mode 100644 index 00000000..f1772e78 Binary files /dev/null and b/web/radio/audio/voice/female/prog_c4.ogg differ diff --git a/web/radio/audio/voice/female/prog_c5.m4a b/web/radio/audio/voice/female/prog_c5.m4a new file mode 100644 index 00000000..a0a86c66 Binary files /dev/null and b/web/radio/audio/voice/female/prog_c5.m4a differ diff --git a/web/radio/audio/voice/female/prog_c5.ogg b/web/radio/audio/voice/female/prog_c5.ogg new file mode 100644 index 00000000..f9a3d077 Binary files /dev/null and b/web/radio/audio/voice/female/prog_c5.ogg differ diff --git a/web/radio/audio/voice/female/rep_high.m4a b/web/radio/audio/voice/female/rep_high.m4a new file mode 100644 index 00000000..f8921ee9 Binary files /dev/null and b/web/radio/audio/voice/female/rep_high.m4a differ diff --git a/web/radio/audio/voice/female/rep_high.ogg b/web/radio/audio/voice/female/rep_high.ogg new file mode 100644 index 00000000..4962258b Binary files /dev/null and b/web/radio/audio/voice/female/rep_high.ogg differ diff --git a/web/radio/audio/voice/female/rep_low.m4a b/web/radio/audio/voice/female/rep_low.m4a new file mode 100644 index 00000000..173e5cf1 Binary files /dev/null and b/web/radio/audio/voice/female/rep_low.m4a differ diff --git a/web/radio/audio/voice/female/rep_low.ogg b/web/radio/audio/voice/female/rep_low.ogg new file mode 100644 index 00000000..7851c5e8 Binary files /dev/null and b/web/radio/audio/voice/female/rep_low.ogg differ diff --git a/web/radio/audio/voice/female/rep_mid.m4a b/web/radio/audio/voice/female/rep_mid.m4a new file mode 100644 index 00000000..b875495e Binary files /dev/null and b/web/radio/audio/voice/female/rep_mid.m4a differ diff --git a/web/radio/audio/voice/female/rep_mid.ogg b/web/radio/audio/voice/female/rep_mid.ogg new file mode 100644 index 00000000..ae468d3e Binary files /dev/null and b/web/radio/audio/voice/female/rep_mid.ogg differ diff --git a/web/radio/audio/voice/female/sys_continue_ask.m4a b/web/radio/audio/voice/female/sys_continue_ask.m4a new file mode 100644 index 00000000..684fb685 Binary files /dev/null and b/web/radio/audio/voice/female/sys_continue_ask.m4a differ diff --git a/web/radio/audio/voice/female/sys_continue_ask.ogg b/web/radio/audio/voice/female/sys_continue_ask.ogg new file mode 100644 index 00000000..e20dc3b1 Binary files /dev/null and b/web/radio/audio/voice/female/sys_continue_ask.ogg differ diff --git a/web/radio/audio/voice/female/sys_help.m4a b/web/radio/audio/voice/female/sys_help.m4a new file mode 100644 index 00000000..2c449ed1 Binary files /dev/null and b/web/radio/audio/voice/female/sys_help.m4a differ diff --git a/web/radio/audio/voice/female/sys_help.ogg b/web/radio/audio/voice/female/sys_help.ogg new file mode 100644 index 00000000..e7438f1f Binary files /dev/null and b/web/radio/audio/voice/female/sys_help.ogg differ diff --git a/web/radio/audio/voice/female/sys_menu.m4a b/web/radio/audio/voice/female/sys_menu.m4a new file mode 100644 index 00000000..aa4a5047 Binary files /dev/null and b/web/radio/audio/voice/female/sys_menu.m4a differ diff --git a/web/radio/audio/voice/female/sys_menu.ogg b/web/radio/audio/voice/female/sys_menu.ogg new file mode 100644 index 00000000..8ec53ca6 Binary files /dev/null and b/web/radio/audio/voice/female/sys_menu.ogg differ diff --git a/web/radio/audio/voice/female/sys_menu_lobby.m4a b/web/radio/audio/voice/female/sys_menu_lobby.m4a new file mode 100644 index 00000000..000af1cb Binary files /dev/null and b/web/radio/audio/voice/female/sys_menu_lobby.m4a differ diff --git a/web/radio/audio/voice/female/sys_menu_lobby.ogg b/web/radio/audio/voice/female/sys_menu_lobby.ogg new file mode 100644 index 00000000..eb07d210 Binary files /dev/null and b/web/radio/audio/voice/female/sys_menu_lobby.ogg differ diff --git a/web/radio/audio/voice/female/sys_menu_restart.m4a b/web/radio/audio/voice/female/sys_menu_restart.m4a new file mode 100644 index 00000000..ab85f205 Binary files /dev/null and b/web/radio/audio/voice/female/sys_menu_restart.m4a differ diff --git a/web/radio/audio/voice/female/sys_menu_restart.ogg b/web/radio/audio/voice/female/sys_menu_restart.ogg new file mode 100644 index 00000000..4f5dcef5 Binary files /dev/null and b/web/radio/audio/voice/female/sys_menu_restart.ogg differ diff --git a/web/radio/audio/voice/female/sys_menu_resume.m4a b/web/radio/audio/voice/female/sys_menu_resume.m4a new file mode 100644 index 00000000..2f25364c Binary files /dev/null and b/web/radio/audio/voice/female/sys_menu_resume.m4a differ diff --git a/web/radio/audio/voice/female/sys_menu_resume.ogg b/web/radio/audio/voice/female/sys_menu_resume.ogg new file mode 100644 index 00000000..92fd62d4 Binary files /dev/null and b/web/radio/audio/voice/female/sys_menu_resume.ogg differ diff --git a/web/radio/audio/voice/female/sys_new_night.m4a b/web/radio/audio/voice/female/sys_new_night.m4a new file mode 100644 index 00000000..5bfba9e4 Binary files /dev/null and b/web/radio/audio/voice/female/sys_new_night.m4a differ diff --git a/web/radio/audio/voice/female/sys_new_night.ogg b/web/radio/audio/voice/female/sys_new_night.ogg new file mode 100644 index 00000000..95729b24 Binary files /dev/null and b/web/radio/audio/voice/female/sys_new_night.ogg differ diff --git a/web/radio/audio/voice/female/sys_saved.m4a b/web/radio/audio/voice/female/sys_saved.m4a new file mode 100644 index 00000000..3194ebf4 Binary files /dev/null and b/web/radio/audio/voice/female/sys_saved.m4a differ diff --git a/web/radio/audio/voice/female/sys_saved.ogg b/web/radio/audio/voice/female/sys_saved.ogg new file mode 100644 index 00000000..5ab91b32 Binary files /dev/null and b/web/radio/audio/voice/female/sys_saved.ogg differ diff --git a/web/radio/audio/voice/female/sys_settings.m4a b/web/radio/audio/voice/female/sys_settings.m4a new file mode 100644 index 00000000..2bd90f56 Binary files /dev/null and b/web/radio/audio/voice/female/sys_settings.m4a differ diff --git a/web/radio/audio/voice/female/sys_settings.ogg b/web/radio/audio/voice/female/sys_settings.ogg new file mode 100644 index 00000000..d400a1fa Binary files /dev/null and b/web/radio/audio/voice/female/sys_settings.ogg differ diff --git a/web/radio/audio/voice/female/sys_skip.m4a b/web/radio/audio/voice/female/sys_skip.m4a new file mode 100644 index 00000000..3301fb08 Binary files /dev/null and b/web/radio/audio/voice/female/sys_skip.m4a differ diff --git a/web/radio/audio/voice/female/sys_skip.ogg b/web/radio/audio/voice/female/sys_skip.ogg new file mode 100644 index 00000000..9af11616 Binary files /dev/null and b/web/radio/audio/voice/female/sys_skip.ogg differ diff --git a/web/radio/audio/voice/female/w1_air.m4a b/web/radio/audio/voice/female/w1_air.m4a new file mode 100644 index 00000000..425bd575 Binary files /dev/null and b/web/radio/audio/voice/female/w1_air.m4a differ diff --git a/web/radio/audio/voice/female/w1_air.ogg b/web/radio/audio/voice/female/w1_air.ogg new file mode 100644 index 00000000..45763ef2 Binary files /dev/null and b/web/radio/audio/voice/female/w1_air.ogg differ diff --git a/web/radio/audio/voice/female/w1_aire.m4a b/web/radio/audio/voice/female/w1_aire.m4a new file mode 100644 index 00000000..817c6b54 Binary files /dev/null and b/web/radio/audio/voice/female/w1_aire.m4a differ diff --git a/web/radio/audio/voice/female/w1_aire.ogg b/web/radio/audio/voice/female/w1_aire.ogg new file mode 100644 index 00000000..f3ebaa14 Binary files /dev/null and b/web/radio/audio/voice/female/w1_aire.ogg differ diff --git a/web/radio/audio/voice/female/w1_home.m4a b/web/radio/audio/voice/female/w1_home.m4a new file mode 100644 index 00000000..ce1ed243 Binary files /dev/null and b/web/radio/audio/voice/female/w1_home.m4a differ diff --git a/web/radio/audio/voice/female/w1_home.ogg b/web/radio/audio/voice/female/w1_home.ogg new file mode 100644 index 00000000..d5bfec78 Binary files /dev/null and b/web/radio/audio/voice/female/w1_home.ogg differ diff --git a/web/radio/audio/voice/female/w1_homee.m4a b/web/radio/audio/voice/female/w1_homee.m4a new file mode 100644 index 00000000..195e4bf4 Binary files /dev/null and b/web/radio/audio/voice/female/w1_homee.m4a differ diff --git a/web/radio/audio/voice/female/w1_homee.ogg b/web/radio/audio/voice/female/w1_homee.ogg new file mode 100644 index 00000000..b5bbc425 Binary files /dev/null and b/web/radio/audio/voice/female/w1_homee.ogg differ diff --git a/web/radio/audio/voice/female/w1_in.m4a b/web/radio/audio/voice/female/w1_in.m4a new file mode 100644 index 00000000..d5ceb2e3 Binary files /dev/null and b/web/radio/audio/voice/female/w1_in.m4a differ diff --git a/web/radio/audio/voice/female/w1_in.ogg b/web/radio/audio/voice/female/w1_in.ogg new file mode 100644 index 00000000..921c842b Binary files /dev/null and b/web/radio/audio/voice/female/w1_in.ogg differ diff --git a/web/radio/audio/voice/female/w1_priv.m4a b/web/radio/audio/voice/female/w1_priv.m4a new file mode 100644 index 00000000..0be391f6 Binary files /dev/null and b/web/radio/audio/voice/female/w1_priv.m4a differ diff --git a/web/radio/audio/voice/female/w1_priv.ogg b/web/radio/audio/voice/female/w1_priv.ogg new file mode 100644 index 00000000..84463976 Binary files /dev/null and b/web/radio/audio/voice/female/w1_priv.ogg differ diff --git a/web/radio/audio/voice/female/w1_prive.m4a b/web/radio/audio/voice/female/w1_prive.m4a new file mode 100644 index 00000000..0075cf3b Binary files /dev/null and b/web/radio/audio/voice/female/w1_prive.m4a differ diff --git a/web/radio/audio/voice/female/w1_prive.ogg b/web/radio/audio/voice/female/w1_prive.ogg new file mode 100644 index 00000000..7edbd6cd Binary files /dev/null and b/web/radio/audio/voice/female/w1_prive.ogg differ diff --git a/web/radio/audio/voice/female/w1_say.m4a b/web/radio/audio/voice/female/w1_say.m4a new file mode 100644 index 00000000..18d610e2 Binary files /dev/null and b/web/radio/audio/voice/female/w1_say.m4a differ diff --git a/web/radio/audio/voice/female/w1_say.ogg b/web/radio/audio/voice/female/w1_say.ogg new file mode 100644 index 00000000..911ce11c Binary files /dev/null and b/web/radio/audio/voice/female/w1_say.ogg differ diff --git a/web/radio/audio/voice/female/w1_say2.m4a b/web/radio/audio/voice/female/w1_say2.m4a new file mode 100644 index 00000000..db5f3cad Binary files /dev/null and b/web/radio/audio/voice/female/w1_say2.m4a differ diff --git a/web/radio/audio/voice/female/w1_say2.ogg b/web/radio/audio/voice/female/w1_say2.ogg new file mode 100644 index 00000000..c710e502 Binary files /dev/null and b/web/radio/audio/voice/female/w1_say2.ogg differ diff --git a/web/radio/audio/voice/female/w1_say2e.m4a b/web/radio/audio/voice/female/w1_say2e.m4a new file mode 100644 index 00000000..4f67878b Binary files /dev/null and b/web/radio/audio/voice/female/w1_say2e.m4a differ diff --git a/web/radio/audio/voice/female/w1_say2e.ogg b/web/radio/audio/voice/female/w1_say2e.ogg new file mode 100644 index 00000000..76e54826 Binary files /dev/null and b/web/radio/audio/voice/female/w1_say2e.ogg differ diff --git a/web/radio/audio/voice/female/w1_saye.m4a b/web/radio/audio/voice/female/w1_saye.m4a new file mode 100644 index 00000000..0238a85e Binary files /dev/null and b/web/radio/audio/voice/female/w1_saye.m4a differ diff --git a/web/radio/audio/voice/female/w1_saye.ogg b/web/radio/audio/voice/female/w1_saye.ogg new file mode 100644 index 00000000..76116510 Binary files /dev/null and b/web/radio/audio/voice/female/w1_saye.ogg differ diff --git a/web/radio/audio/voice/female/z1_adone.m4a b/web/radio/audio/voice/female/z1_adone.m4a new file mode 100644 index 00000000..00472dcf Binary files /dev/null and b/web/radio/audio/voice/female/z1_adone.m4a differ diff --git a/web/radio/audio/voice/female/z1_adone.ogg b/web/radio/audio/voice/female/z1_adone.ogg new file mode 100644 index 00000000..d12f95a3 Binary files /dev/null and b/web/radio/audio/voice/female/z1_adone.ogg differ diff --git a/web/radio/audio/voice/female/z1_ask.m4a b/web/radio/audio/voice/female/z1_ask.m4a new file mode 100644 index 00000000..78a1a37d Binary files /dev/null and b/web/radio/audio/voice/female/z1_ask.m4a differ diff --git a/web/radio/audio/voice/female/z1_ask.ogg b/web/radio/audio/voice/female/z1_ask.ogg new file mode 100644 index 00000000..c389c45d Binary files /dev/null and b/web/radio/audio/voice/female/z1_ask.ogg differ diff --git a/web/radio/audio/voice/female/z1_bade.m4a b/web/radio/audio/voice/female/z1_bade.m4a new file mode 100644 index 00000000..3c9aff86 Binary files /dev/null and b/web/radio/audio/voice/female/z1_bade.m4a differ diff --git a/web/radio/audio/voice/female/z1_bade.ogg b/web/radio/audio/voice/female/z1_bade.ogg new file mode 100644 index 00000000..bda44e9c Binary files /dev/null and b/web/radio/audio/voice/female/z1_bade.ogg differ diff --git a/web/radio/audio/voice/female/z1_ok1.m4a b/web/radio/audio/voice/female/z1_ok1.m4a new file mode 100644 index 00000000..c69d2e3f Binary files /dev/null and b/web/radio/audio/voice/female/z1_ok1.m4a differ diff --git a/web/radio/audio/voice/female/z1_ok1.ogg b/web/radio/audio/voice/female/z1_ok1.ogg new file mode 100644 index 00000000..2bb6484e Binary files /dev/null and b/web/radio/audio/voice/female/z1_ok1.ogg differ diff --git a/web/radio/audio/voice/female/z1_pol.m4a b/web/radio/audio/voice/female/z1_pol.m4a new file mode 100644 index 00000000..01257c1e Binary files /dev/null and b/web/radio/audio/voice/female/z1_pol.m4a differ diff --git a/web/radio/audio/voice/female/z1_pol.ogg b/web/radio/audio/voice/female/z1_pol.ogg new file mode 100644 index 00000000..f3c62e87 Binary files /dev/null and b/web/radio/audio/voice/female/z1_pol.ogg differ diff --git a/web/radio/audio/voice/female/z1_pole.m4a b/web/radio/audio/voice/female/z1_pole.m4a new file mode 100644 index 00000000..3a28e759 Binary files /dev/null and b/web/radio/audio/voice/female/z1_pole.m4a differ diff --git a/web/radio/audio/voice/female/z1_pole.ogg b/web/radio/audio/voice/female/z1_pole.ogg new file mode 100644 index 00000000..759fc084 Binary files /dev/null and b/web/radio/audio/voice/female/z1_pole.ogg differ diff --git a/web/radio/audio/voice/female/z1_ring_in.m4a b/web/radio/audio/voice/female/z1_ring_in.m4a new file mode 100644 index 00000000..fcc5531d Binary files /dev/null and b/web/radio/audio/voice/female/z1_ring_in.m4a differ diff --git a/web/radio/audio/voice/female/z1_ring_in.ogg b/web/radio/audio/voice/female/z1_ring_in.ogg new file mode 100644 index 00000000..dfb30b2b Binary files /dev/null and b/web/radio/audio/voice/female/z1_ring_in.ogg differ diff --git a/web/radio/audio/voice/female/z1_sdone.m4a b/web/radio/audio/voice/female/z1_sdone.m4a new file mode 100644 index 00000000..ac51cd47 Binary files /dev/null and b/web/radio/audio/voice/female/z1_sdone.m4a differ diff --git a/web/radio/audio/voice/female/z1_sdone.ogg b/web/radio/audio/voice/female/z1_sdone.ogg new file mode 100644 index 00000000..953f5e26 Binary files /dev/null and b/web/radio/audio/voice/female/z1_sdone.ogg differ diff --git a/web/radio/audio/voice/female/z1_song.m4a b/web/radio/audio/voice/female/z1_song.m4a new file mode 100644 index 00000000..e411363e Binary files /dev/null and b/web/radio/audio/voice/female/z1_song.m4a differ diff --git a/web/radio/audio/voice/female/z1_song.ogg b/web/radio/audio/voice/female/z1_song.ogg new file mode 100644 index 00000000..806121c8 Binary files /dev/null and b/web/radio/audio/voice/female/z1_song.ogg differ diff --git a/web/radio/audio/voice/female/z1_stay.m4a b/web/radio/audio/voice/female/z1_stay.m4a new file mode 100644 index 00000000..f7902559 Binary files /dev/null and b/web/radio/audio/voice/female/z1_stay.m4a differ diff --git a/web/radio/audio/voice/female/z1_stay.ogg b/web/radio/audio/voice/female/z1_stay.ogg new file mode 100644 index 00000000..086685e1 Binary files /dev/null and b/web/radio/audio/voice/female/z1_stay.ogg differ diff --git a/web/radio/audio/voice/female/z1_staye.m4a b/web/radio/audio/voice/female/z1_staye.m4a new file mode 100644 index 00000000..7422502b Binary files /dev/null and b/web/radio/audio/voice/female/z1_staye.m4a differ diff --git a/web/radio/audio/voice/female/z1_staye.ogg b/web/radio/audio/voice/female/z1_staye.ogg new file mode 100644 index 00000000..087d06c2 Binary files /dev/null and b/web/radio/audio/voice/female/z1_staye.ogg differ diff --git a/web/radio/audio/voice/h1_a.m4a b/web/radio/audio/voice/h1_a.m4a new file mode 100644 index 00000000..3f42e4fc Binary files /dev/null and b/web/radio/audio/voice/h1_a.m4a differ diff --git a/web/radio/audio/voice/h1_a.ogg b/web/radio/audio/voice/h1_a.ogg new file mode 100644 index 00000000..643a87d3 Binary files /dev/null and b/web/radio/audio/voice/h1_a.ogg differ diff --git a/web/radio/audio/voice/h1_b.m4a b/web/radio/audio/voice/h1_b.m4a new file mode 100644 index 00000000..039e6137 Binary files /dev/null and b/web/radio/audio/voice/h1_b.m4a differ diff --git a/web/radio/audio/voice/h1_b.ogg b/web/radio/audio/voice/h1_b.ogg new file mode 100644 index 00000000..497c055c Binary files /dev/null and b/web/radio/audio/voice/h1_b.ogg differ diff --git a/web/radio/audio/voice/h1_dango.m4a b/web/radio/audio/voice/h1_dango.m4a new file mode 100644 index 00000000..4837cbe5 Binary files /dev/null and b/web/radio/audio/voice/h1_dango.m4a differ diff --git a/web/radio/audio/voice/h1_dango.ogg b/web/radio/audio/voice/h1_dango.ogg new file mode 100644 index 00000000..14a9ea0e Binary files /dev/null and b/web/radio/audio/voice/h1_dango.ogg differ diff --git a/web/radio/audio/voice/h1_stopo.m4a b/web/radio/audio/voice/h1_stopo.m4a new file mode 100644 index 00000000..a36ed87c Binary files /dev/null and b/web/radio/audio/voice/h1_stopo.m4a differ diff --git a/web/radio/audio/voice/h1_stopo.ogg b/web/radio/audio/voice/h1_stopo.ogg new file mode 100644 index 00000000..e7bc7912 Binary files /dev/null and b/web/radio/audio/voice/h1_stopo.ogg differ diff --git a/web/radio/audio/voice/h1_talko.m4a b/web/radio/audio/voice/h1_talko.m4a new file mode 100644 index 00000000..011340c1 Binary files /dev/null and b/web/radio/audio/voice/h1_talko.m4a differ diff --git a/web/radio/audio/voice/h1_talko.ogg b/web/radio/audio/voice/h1_talko.ogg new file mode 100644 index 00000000..fdea811b Binary files /dev/null and b/web/radio/audio/voice/h1_talko.ogg differ diff --git a/web/radio/audio/voice/k1_a.m4a b/web/radio/audio/voice/k1_a.m4a new file mode 100644 index 00000000..bffad5fb Binary files /dev/null and b/web/radio/audio/voice/k1_a.m4a differ diff --git a/web/radio/audio/voice/k1_a.ogg b/web/radio/audio/voice/k1_a.ogg new file mode 100644 index 00000000..59ca10c1 Binary files /dev/null and b/web/radio/audio/voice/k1_a.ogg differ diff --git a/web/radio/audio/voice/k1_asko.m4a b/web/radio/audio/voice/k1_asko.m4a new file mode 100644 index 00000000..96a2f417 Binary files /dev/null and b/web/radio/audio/voice/k1_asko.m4a differ diff --git a/web/radio/audio/voice/k1_asko.ogg b/web/radio/audio/voice/k1_asko.ogg new file mode 100644 index 00000000..be89317b Binary files /dev/null and b/web/radio/audio/voice/k1_asko.ogg differ diff --git a/web/radio/audio/voice/k1_b.m4a b/web/radio/audio/voice/k1_b.m4a new file mode 100644 index 00000000..42aadeb9 Binary files /dev/null and b/web/radio/audio/voice/k1_b.m4a differ diff --git a/web/radio/audio/voice/k1_b.ogg b/web/radio/audio/voice/k1_b.ogg new file mode 100644 index 00000000..223a7c5b Binary files /dev/null and b/web/radio/audio/voice/k1_b.ogg differ diff --git a/web/radio/audio/voice/k1_lieo.m4a b/web/radio/audio/voice/k1_lieo.m4a new file mode 100644 index 00000000..2558aac0 Binary files /dev/null and b/web/radio/audio/voice/k1_lieo.m4a differ diff --git a/web/radio/audio/voice/k1_lieo.ogg b/web/radio/audio/voice/k1_lieo.ogg new file mode 100644 index 00000000..fc2f58f6 Binary files /dev/null and b/web/radio/audio/voice/k1_lieo.ogg differ diff --git a/web/radio/audio/voice/k1_lullo.m4a b/web/radio/audio/voice/k1_lullo.m4a new file mode 100644 index 00000000..c056297a Binary files /dev/null and b/web/radio/audio/voice/k1_lullo.m4a differ diff --git a/web/radio/audio/voice/k1_lullo.ogg b/web/radio/audio/voice/k1_lullo.ogg new file mode 100644 index 00000000..d4bbf3db Binary files /dev/null and b/web/radio/audio/voice/k1_lullo.ogg differ diff --git a/web/radio/audio/voice/k1_trutho.m4a b/web/radio/audio/voice/k1_trutho.m4a new file mode 100644 index 00000000..9859fb84 Binary files /dev/null and b/web/radio/audio/voice/k1_trutho.m4a differ diff --git a/web/radio/audio/voice/k1_trutho.ogg b/web/radio/audio/voice/k1_trutho.ogg new file mode 100644 index 00000000..5f35886e Binary files /dev/null and b/web/radio/audio/voice/k1_trutho.ogg differ diff --git a/web/radio/audio/voice/l1_a.m4a b/web/radio/audio/voice/l1_a.m4a new file mode 100644 index 00000000..01d817fe Binary files /dev/null and b/web/radio/audio/voice/l1_a.m4a differ diff --git a/web/radio/audio/voice/l1_a.ogg b/web/radio/audio/voice/l1_a.ogg new file mode 100644 index 00000000..5a59106a Binary files /dev/null and b/web/radio/audio/voice/l1_a.ogg differ diff --git a/web/radio/audio/voice/l1_b.m4a b/web/radio/audio/voice/l1_b.m4a new file mode 100644 index 00000000..645e3f8b Binary files /dev/null and b/web/radio/audio/voice/l1_b.m4a differ diff --git a/web/radio/audio/voice/l1_b.ogg b/web/radio/audio/voice/l1_b.ogg new file mode 100644 index 00000000..93f2bafb Binary files /dev/null and b/web/radio/audio/voice/l1_b.ogg differ diff --git a/web/radio/audio/voice/l1_freeo.m4a b/web/radio/audio/voice/l1_freeo.m4a new file mode 100644 index 00000000..4d3623bf Binary files /dev/null and b/web/radio/audio/voice/l1_freeo.m4a differ diff --git a/web/radio/audio/voice/l1_freeo.ogg b/web/radio/audio/voice/l1_freeo.ogg new file mode 100644 index 00000000..7c505847 Binary files /dev/null and b/web/radio/audio/voice/l1_freeo.ogg differ diff --git a/web/radio/audio/voice/l1_goo.m4a b/web/radio/audio/voice/l1_goo.m4a new file mode 100644 index 00000000..5fea1914 Binary files /dev/null and b/web/radio/audio/voice/l1_goo.m4a differ diff --git a/web/radio/audio/voice/l1_goo.ogg b/web/radio/audio/voice/l1_goo.ogg new file mode 100644 index 00000000..6d6360aa Binary files /dev/null and b/web/radio/audio/voice/l1_goo.ogg differ diff --git a/web/radio/audio/voice/male/b_goal.m4a b/web/radio/audio/voice/male/b_goal.m4a new file mode 100644 index 00000000..dfbfe5c7 Binary files /dev/null and b/web/radio/audio/voice/male/b_goal.m4a differ diff --git a/web/radio/audio/voice/male/b_goal.ogg b/web/radio/audio/voice/male/b_goal.ogg new file mode 100644 index 00000000..6eed76ec Binary files /dev/null and b/web/radio/audio/voice/male/b_goal.ogg differ diff --git a/web/radio/audio/voice/male/b_open.m4a b/web/radio/audio/voice/male/b_open.m4a new file mode 100644 index 00000000..e4adeb13 Binary files /dev/null and b/web/radio/audio/voice/male/b_open.m4a differ diff --git a/web/radio/audio/voice/male/b_open.ogg b/web/radio/audio/voice/male/b_open.ogg new file mode 100644 index 00000000..8e3c259f Binary files /dev/null and b/web/radio/audio/voice/male/b_open.ogg differ diff --git a/web/radio/audio/voice/male/b_rain.m4a b/web/radio/audio/voice/male/b_rain.m4a new file mode 100644 index 00000000..6870997a Binary files /dev/null and b/web/radio/audio/voice/male/b_rain.m4a differ diff --git a/web/radio/audio/voice/male/b_rain.ogg b/web/radio/audio/voice/male/b_rain.ogg new file mode 100644 index 00000000..8ddb5a42 Binary files /dev/null and b/web/radio/audio/voice/male/b_rain.ogg differ diff --git a/web/radio/audio/voice/male/b_rule.m4a b/web/radio/audio/voice/male/b_rule.m4a new file mode 100644 index 00000000..0d399484 Binary files /dev/null and b/web/radio/audio/voice/male/b_rule.m4a differ diff --git a/web/radio/audio/voice/male/b_rule.ogg b/web/radio/audio/voice/male/b_rule.ogg new file mode 100644 index 00000000..fbeae35f Binary files /dev/null and b/web/radio/audio/voice/male/b_rule.ogg differ diff --git a/web/radio/audio/voice/male/d9_avoid.m4a b/web/radio/audio/voice/male/d9_avoid.m4a new file mode 100644 index 00000000..2f0b81c1 Binary files /dev/null and b/web/radio/audio/voice/male/d9_avoid.m4a differ diff --git a/web/radio/audio/voice/male/d9_avoid.ogg b/web/radio/audio/voice/male/d9_avoid.ogg new file mode 100644 index 00000000..1e9c6d84 Binary files /dev/null and b/web/radio/audio/voice/male/d9_avoid.ogg differ diff --git a/web/radio/audio/voice/male/d9_avoide.m4a b/web/radio/audio/voice/male/d9_avoide.m4a new file mode 100644 index 00000000..d8cc1ca7 Binary files /dev/null and b/web/radio/audio/voice/male/d9_avoide.m4a differ diff --git a/web/radio/audio/voice/male/d9_avoide.ogg b/web/radio/audio/voice/male/d9_avoide.ogg new file mode 100644 index 00000000..03a9de6d Binary files /dev/null and b/web/radio/audio/voice/male/d9_avoide.ogg differ diff --git a/web/radio/audio/voice/male/d9_face.m4a b/web/radio/audio/voice/male/d9_face.m4a new file mode 100644 index 00000000..aea9b378 Binary files /dev/null and b/web/radio/audio/voice/male/d9_face.m4a differ diff --git a/web/radio/audio/voice/male/d9_face.ogg b/web/radio/audio/voice/male/d9_face.ogg new file mode 100644 index 00000000..aead546b Binary files /dev/null and b/web/radio/audio/voice/male/d9_face.ogg differ diff --git a/web/radio/audio/voice/male/d9_facee.m4a b/web/radio/audio/voice/male/d9_facee.m4a new file mode 100644 index 00000000..6d3e820b Binary files /dev/null and b/web/radio/audio/voice/male/d9_facee.m4a differ diff --git a/web/radio/audio/voice/male/d9_facee.ogg b/web/radio/audio/voice/male/d9_facee.ogg new file mode 100644 index 00000000..5b4250cf Binary files /dev/null and b/web/radio/audio/voice/male/d9_facee.ogg differ diff --git a/web/radio/audio/voice/male/e1_a.m4a b/web/radio/audio/voice/male/e1_a.m4a new file mode 100644 index 00000000..9ee4061c Binary files /dev/null and b/web/radio/audio/voice/male/e1_a.m4a differ diff --git a/web/radio/audio/voice/male/e1_a.ogg b/web/radio/audio/voice/male/e1_a.ogg new file mode 100644 index 00000000..144614e7 Binary files /dev/null and b/web/radio/audio/voice/male/e1_a.ogg differ diff --git a/web/radio/audio/voice/male/e1_b.m4a b/web/radio/audio/voice/male/e1_b.m4a new file mode 100644 index 00000000..2618e3fa Binary files /dev/null and b/web/radio/audio/voice/male/e1_b.m4a differ diff --git a/web/radio/audio/voice/male/e1_b.ogg b/web/radio/audio/voice/male/e1_b.ogg new file mode 100644 index 00000000..1ff794fb Binary files /dev/null and b/web/radio/audio/voice/male/e1_b.ogg differ diff --git a/web/radio/audio/voice/male/e2_a.m4a b/web/radio/audio/voice/male/e2_a.m4a new file mode 100644 index 00000000..1e73c922 Binary files /dev/null and b/web/radio/audio/voice/male/e2_a.m4a differ diff --git a/web/radio/audio/voice/male/e2_a.ogg b/web/radio/audio/voice/male/e2_a.ogg new file mode 100644 index 00000000..04a580d7 Binary files /dev/null and b/web/radio/audio/voice/male/e2_a.ogg differ diff --git a/web/radio/audio/voice/male/e2_b.m4a b/web/radio/audio/voice/male/e2_b.m4a new file mode 100644 index 00000000..668c15c3 Binary files /dev/null and b/web/radio/audio/voice/male/e2_b.m4a differ diff --git a/web/radio/audio/voice/male/e2_b.ogg b/web/radio/audio/voice/male/e2_b.ogg new file mode 100644 index 00000000..9f93274a Binary files /dev/null and b/web/radio/audio/voice/male/e2_b.ogg differ diff --git a/web/radio/audio/voice/male/e3_a.m4a b/web/radio/audio/voice/male/e3_a.m4a new file mode 100644 index 00000000..3ace9af4 Binary files /dev/null and b/web/radio/audio/voice/male/e3_a.m4a differ diff --git a/web/radio/audio/voice/male/e3_a.ogg b/web/radio/audio/voice/male/e3_a.ogg new file mode 100644 index 00000000..99dbad70 Binary files /dev/null and b/web/radio/audio/voice/male/e3_a.ogg differ diff --git a/web/radio/audio/voice/male/e3_b.m4a b/web/radio/audio/voice/male/e3_b.m4a new file mode 100644 index 00000000..40e22cd5 Binary files /dev/null and b/web/radio/audio/voice/male/e3_b.m4a differ diff --git a/web/radio/audio/voice/male/e3_b.ogg b/web/radio/audio/voice/male/e3_b.ogg new file mode 100644 index 00000000..e9cce44d Binary files /dev/null and b/web/radio/audio/voice/male/e3_b.ogg differ diff --git a/web/radio/audio/voice/male/e4_a.m4a b/web/radio/audio/voice/male/e4_a.m4a new file mode 100644 index 00000000..c7a6e3bc Binary files /dev/null and b/web/radio/audio/voice/male/e4_a.m4a differ diff --git a/web/radio/audio/voice/male/e4_a.ogg b/web/radio/audio/voice/male/e4_a.ogg new file mode 100644 index 00000000..ecf96618 Binary files /dev/null and b/web/radio/audio/voice/male/e4_a.ogg differ diff --git a/web/radio/audio/voice/male/e4_b.m4a b/web/radio/audio/voice/male/e4_b.m4a new file mode 100644 index 00000000..2421b12d Binary files /dev/null and b/web/radio/audio/voice/male/e4_b.m4a differ diff --git a/web/radio/audio/voice/male/e4_b.ogg b/web/radio/audio/voice/male/e4_b.ogg new file mode 100644 index 00000000..fa67e0d2 Binary files /dev/null and b/web/radio/audio/voice/male/e4_b.ogg differ diff --git a/web/radio/audio/voice/male/ep_deer_no.m4a b/web/radio/audio/voice/male/ep_deer_no.m4a new file mode 100644 index 00000000..c8fb333d Binary files /dev/null and b/web/radio/audio/voice/male/ep_deer_no.m4a differ diff --git a/web/radio/audio/voice/male/ep_deer_no.ogg b/web/radio/audio/voice/male/ep_deer_no.ogg new file mode 100644 index 00000000..8ddbd24a Binary files /dev/null and b/web/radio/audio/voice/male/ep_deer_no.ogg differ diff --git a/web/radio/audio/voice/male/ep_deer_ok.m4a b/web/radio/audio/voice/male/ep_deer_ok.m4a new file mode 100644 index 00000000..c0ecf490 Binary files /dev/null and b/web/radio/audio/voice/male/ep_deer_ok.m4a differ diff --git a/web/radio/audio/voice/male/ep_deer_ok.ogg b/web/radio/audio/voice/male/ep_deer_ok.ogg new file mode 100644 index 00000000..227ac81d Binary files /dev/null and b/web/radio/audio/voice/male/ep_deer_ok.ogg differ diff --git a/web/radio/audio/voice/male/ep_han_no.m4a b/web/radio/audio/voice/male/ep_han_no.m4a new file mode 100644 index 00000000..af522875 Binary files /dev/null and b/web/radio/audio/voice/male/ep_han_no.m4a differ diff --git a/web/radio/audio/voice/male/ep_han_no.ogg b/web/radio/audio/voice/male/ep_han_no.ogg new file mode 100644 index 00000000..e8d3815b Binary files /dev/null and b/web/radio/audio/voice/male/ep_han_no.ogg differ diff --git a/web/radio/audio/voice/male/ep_han_ok.m4a b/web/radio/audio/voice/male/ep_han_ok.m4a new file mode 100644 index 00000000..660272b4 Binary files /dev/null and b/web/radio/audio/voice/male/ep_han_ok.m4a differ diff --git a/web/radio/audio/voice/male/ep_han_ok.ogg b/web/radio/audio/voice/male/ep_han_ok.ogg new file mode 100644 index 00000000..7d1b1eba Binary files /dev/null and b/web/radio/audio/voice/male/ep_han_ok.ogg differ diff --git a/web/radio/audio/voice/male/ep_kid_no.m4a b/web/radio/audio/voice/male/ep_kid_no.m4a new file mode 100644 index 00000000..33e4ee86 Binary files /dev/null and b/web/radio/audio/voice/male/ep_kid_no.m4a differ diff --git a/web/radio/audio/voice/male/ep_kid_no.ogg b/web/radio/audio/voice/male/ep_kid_no.ogg new file mode 100644 index 00000000..6ed3c0ce Binary files /dev/null and b/web/radio/audio/voice/male/ep_kid_no.ogg differ diff --git a/web/radio/audio/voice/male/ep_kid_ok.m4a b/web/radio/audio/voice/male/ep_kid_ok.m4a new file mode 100644 index 00000000..9daf5f8f Binary files /dev/null and b/web/radio/audio/voice/male/ep_kid_ok.m4a differ diff --git a/web/radio/audio/voice/male/ep_kid_ok.ogg b/web/radio/audio/voice/male/ep_kid_ok.ogg new file mode 100644 index 00000000..494b2db9 Binary files /dev/null and b/web/radio/audio/voice/male/ep_kid_ok.ogg differ diff --git a/web/radio/audio/voice/male/ep_lead.m4a b/web/radio/audio/voice/male/ep_lead.m4a new file mode 100644 index 00000000..a7d9f995 Binary files /dev/null and b/web/radio/audio/voice/male/ep_lead.m4a differ diff --git a/web/radio/audio/voice/male/ep_lead.ogg b/web/radio/audio/voice/male/ep_lead.ogg new file mode 100644 index 00000000..c6209c14 Binary files /dev/null and b/web/radio/audio/voice/male/ep_lead.ogg differ diff --git a/web/radio/audio/voice/male/ep_wife_no.m4a b/web/radio/audio/voice/male/ep_wife_no.m4a new file mode 100644 index 00000000..e3902d07 Binary files /dev/null and b/web/radio/audio/voice/male/ep_wife_no.m4a differ diff --git a/web/radio/audio/voice/male/ep_wife_no.ogg b/web/radio/audio/voice/male/ep_wife_no.ogg new file mode 100644 index 00000000..a5c77bf4 Binary files /dev/null and b/web/radio/audio/voice/male/ep_wife_no.ogg differ diff --git a/web/radio/audio/voice/male/ep_wife_ok.m4a b/web/radio/audio/voice/male/ep_wife_ok.m4a new file mode 100644 index 00000000..46e774e8 Binary files /dev/null and b/web/radio/audio/voice/male/ep_wife_ok.m4a differ diff --git a/web/radio/audio/voice/male/ep_wife_ok.ogg b/web/radio/audio/voice/male/ep_wife_ok.ogg new file mode 100644 index 00000000..5edc7b03 Binary files /dev/null and b/web/radio/audio/voice/male/ep_wife_ok.ogg differ diff --git a/web/radio/audio/voice/male/ep_zhou_no.m4a b/web/radio/audio/voice/male/ep_zhou_no.m4a new file mode 100644 index 00000000..43bc7181 Binary files /dev/null and b/web/radio/audio/voice/male/ep_zhou_no.m4a differ diff --git a/web/radio/audio/voice/male/ep_zhou_no.ogg b/web/radio/audio/voice/male/ep_zhou_no.ogg new file mode 100644 index 00000000..e81dcc30 Binary files /dev/null and b/web/radio/audio/voice/male/ep_zhou_no.ogg differ diff --git a/web/radio/audio/voice/male/ep_zhou_ok.m4a b/web/radio/audio/voice/male/ep_zhou_ok.m4a new file mode 100644 index 00000000..84656405 Binary files /dev/null and b/web/radio/audio/voice/male/ep_zhou_ok.m4a differ diff --git a/web/radio/audio/voice/male/ep_zhou_ok.ogg b/web/radio/audio/voice/male/ep_zhou_ok.ogg new file mode 100644 index 00000000..3faca994 Binary files /dev/null and b/web/radio/audio/voice/male/ep_zhou_ok.ogg differ diff --git a/web/radio/audio/voice/male/g9_in.m4a b/web/radio/audio/voice/male/g9_in.m4a new file mode 100644 index 00000000..c233a3e7 Binary files /dev/null and b/web/radio/audio/voice/male/g9_in.m4a differ diff --git a/web/radio/audio/voice/male/g9_in.ogg b/web/radio/audio/voice/male/g9_in.ogg new file mode 100644 index 00000000..f2063741 Binary files /dev/null and b/web/radio/audio/voice/male/g9_in.ogg differ diff --git a/web/radio/audio/voice/male/gd_again.m4a b/web/radio/audio/voice/male/gd_again.m4a new file mode 100644 index 00000000..44a9a4ec Binary files /dev/null and b/web/radio/audio/voice/male/gd_again.m4a differ diff --git a/web/radio/audio/voice/male/gd_again.ogg b/web/radio/audio/voice/male/gd_again.ogg new file mode 100644 index 00000000..a03866a3 Binary files /dev/null and b/web/radio/audio/voice/male/gd_again.ogg differ diff --git a/web/radio/audio/voice/male/gd_brief.m4a b/web/radio/audio/voice/male/gd_brief.m4a new file mode 100644 index 00000000..016c48e9 Binary files /dev/null and b/web/radio/audio/voice/male/gd_brief.m4a differ diff --git a/web/radio/audio/voice/male/gd_brief.ogg b/web/radio/audio/voice/male/gd_brief.ogg new file mode 100644 index 00000000..11619958 Binary files /dev/null and b/web/radio/audio/voice/male/gd_brief.ogg differ diff --git a/web/radio/audio/voice/male/gd_continue.m4a b/web/radio/audio/voice/male/gd_continue.m4a new file mode 100644 index 00000000..c803a9dd Binary files /dev/null and b/web/radio/audio/voice/male/gd_continue.m4a differ diff --git a/web/radio/audio/voice/male/gd_continue.ogg b/web/radio/audio/voice/male/gd_continue.ogg new file mode 100644 index 00000000..3014f431 Binary files /dev/null and b/web/radio/audio/voice/male/gd_continue.ogg differ diff --git a/web/radio/audio/voice/male/gd_invalid.m4a b/web/radio/audio/voice/male/gd_invalid.m4a new file mode 100644 index 00000000..3c19d494 Binary files /dev/null and b/web/radio/audio/voice/male/gd_invalid.m4a differ diff --git a/web/radio/audio/voice/male/gd_invalid.ogg b/web/radio/audio/voice/male/gd_invalid.ogg new file mode 100644 index 00000000..5d54aff0 Binary files /dev/null and b/web/radio/audio/voice/male/gd_invalid.ogg differ diff --git a/web/radio/audio/voice/male/gd_ring1.m4a b/web/radio/audio/voice/male/gd_ring1.m4a new file mode 100644 index 00000000..839680b0 Binary files /dev/null and b/web/radio/audio/voice/male/gd_ring1.m4a differ diff --git a/web/radio/audio/voice/male/gd_ring1.ogg b/web/radio/audio/voice/male/gd_ring1.ogg new file mode 100644 index 00000000..0b673749 Binary files /dev/null and b/web/radio/audio/voice/male/gd_ring1.ogg differ diff --git a/web/radio/audio/voice/male/gd_ring2.m4a b/web/radio/audio/voice/male/gd_ring2.m4a new file mode 100644 index 00000000..88f2a311 Binary files /dev/null and b/web/radio/audio/voice/male/gd_ring2.m4a differ diff --git a/web/radio/audio/voice/male/gd_ring2.ogg b/web/radio/audio/voice/male/gd_ring2.ogg new file mode 100644 index 00000000..81d813c2 Binary files /dev/null and b/web/radio/audio/voice/male/gd_ring2.ogg differ diff --git a/web/radio/audio/voice/male/h1_danger.m4a b/web/radio/audio/voice/male/h1_danger.m4a new file mode 100644 index 00000000..2363a67a Binary files /dev/null and b/web/radio/audio/voice/male/h1_danger.m4a differ diff --git a/web/radio/audio/voice/male/h1_danger.ogg b/web/radio/audio/voice/male/h1_danger.ogg new file mode 100644 index 00000000..95f64ae1 Binary files /dev/null and b/web/radio/audio/voice/male/h1_danger.ogg differ diff --git a/web/radio/audio/voice/male/h1_echo.m4a b/web/radio/audio/voice/male/h1_echo.m4a new file mode 100644 index 00000000..b03b7595 Binary files /dev/null and b/web/radio/audio/voice/male/h1_echo.m4a differ diff --git a/web/radio/audio/voice/male/h1_echo.ogg b/web/radio/audio/voice/male/h1_echo.ogg new file mode 100644 index 00000000..84a33097 Binary files /dev/null and b/web/radio/audio/voice/male/h1_echo.ogg differ diff --git a/web/radio/audio/voice/male/h1_in.m4a b/web/radio/audio/voice/male/h1_in.m4a new file mode 100644 index 00000000..28f4d5c5 Binary files /dev/null and b/web/radio/audio/voice/male/h1_in.m4a differ diff --git a/web/radio/audio/voice/male/h1_in.ogg b/web/radio/audio/voice/male/h1_in.ogg new file mode 100644 index 00000000..49c3bbc6 Binary files /dev/null and b/web/radio/audio/voice/male/h1_in.ogg differ diff --git a/web/radio/audio/voice/male/h1_stop.m4a b/web/radio/audio/voice/male/h1_stop.m4a new file mode 100644 index 00000000..171966e3 Binary files /dev/null and b/web/radio/audio/voice/male/h1_stop.m4a differ diff --git a/web/radio/audio/voice/male/h1_stop.ogg b/web/radio/audio/voice/male/h1_stop.ogg new file mode 100644 index 00000000..e2f91023 Binary files /dev/null and b/web/radio/audio/voice/male/h1_stop.ogg differ diff --git a/web/radio/audio/voice/male/h1_stope.m4a b/web/radio/audio/voice/male/h1_stope.m4a new file mode 100644 index 00000000..99f9b270 Binary files /dev/null and b/web/radio/audio/voice/male/h1_stope.m4a differ diff --git a/web/radio/audio/voice/male/h1_stope.ogg b/web/radio/audio/voice/male/h1_stope.ogg new file mode 100644 index 00000000..ba826d5d Binary files /dev/null and b/web/radio/audio/voice/male/h1_stope.ogg differ diff --git a/web/radio/audio/voice/male/h1_talk.m4a b/web/radio/audio/voice/male/h1_talk.m4a new file mode 100644 index 00000000..965421f9 Binary files /dev/null and b/web/radio/audio/voice/male/h1_talk.m4a differ diff --git a/web/radio/audio/voice/male/h1_talk.ogg b/web/radio/audio/voice/male/h1_talk.ogg new file mode 100644 index 00000000..66f909ee Binary files /dev/null and b/web/radio/audio/voice/male/h1_talk.ogg differ diff --git a/web/radio/audio/voice/male/h1_talke.m4a b/web/radio/audio/voice/male/h1_talke.m4a new file mode 100644 index 00000000..3b9596ce Binary files /dev/null and b/web/radio/audio/voice/male/h1_talke.m4a differ diff --git a/web/radio/audio/voice/male/h1_talke.ogg b/web/radio/audio/voice/male/h1_talke.ogg new file mode 100644 index 00000000..60d9a105 Binary files /dev/null and b/web/radio/audio/voice/male/h1_talke.ogg differ diff --git a/web/radio/audio/voice/male/k1_ask.m4a b/web/radio/audio/voice/male/k1_ask.m4a new file mode 100644 index 00000000..846951cc Binary files /dev/null and b/web/radio/audio/voice/male/k1_ask.m4a differ diff --git a/web/radio/audio/voice/male/k1_ask.ogg b/web/radio/audio/voice/male/k1_ask.ogg new file mode 100644 index 00000000..ee5363ea Binary files /dev/null and b/web/radio/audio/voice/male/k1_ask.ogg differ diff --git a/web/radio/audio/voice/male/k1_in.m4a b/web/radio/audio/voice/male/k1_in.m4a new file mode 100644 index 00000000..33476881 Binary files /dev/null and b/web/radio/audio/voice/male/k1_in.m4a differ diff --git a/web/radio/audio/voice/male/k1_in.ogg b/web/radio/audio/voice/male/k1_in.ogg new file mode 100644 index 00000000..30f79787 Binary files /dev/null and b/web/radio/audio/voice/male/k1_in.ogg differ diff --git a/web/radio/audio/voice/male/k1_lie.m4a b/web/radio/audio/voice/male/k1_lie.m4a new file mode 100644 index 00000000..beab5174 Binary files /dev/null and b/web/radio/audio/voice/male/k1_lie.m4a differ diff --git a/web/radio/audio/voice/male/k1_lie.ogg b/web/radio/audio/voice/male/k1_lie.ogg new file mode 100644 index 00000000..dc1b2efd Binary files /dev/null and b/web/radio/audio/voice/male/k1_lie.ogg differ diff --git a/web/radio/audio/voice/male/k1_liee.m4a b/web/radio/audio/voice/male/k1_liee.m4a new file mode 100644 index 00000000..55d85aef Binary files /dev/null and b/web/radio/audio/voice/male/k1_liee.m4a differ diff --git a/web/radio/audio/voice/male/k1_liee.ogg b/web/radio/audio/voice/male/k1_liee.ogg new file mode 100644 index 00000000..8e6c0895 Binary files /dev/null and b/web/radio/audio/voice/male/k1_liee.ogg differ diff --git a/web/radio/audio/voice/male/k1_lull.m4a b/web/radio/audio/voice/male/k1_lull.m4a new file mode 100644 index 00000000..589bb4b8 Binary files /dev/null and b/web/radio/audio/voice/male/k1_lull.m4a differ diff --git a/web/radio/audio/voice/male/k1_lull.ogg b/web/radio/audio/voice/male/k1_lull.ogg new file mode 100644 index 00000000..fbd51f34 Binary files /dev/null and b/web/radio/audio/voice/male/k1_lull.ogg differ diff --git a/web/radio/audio/voice/male/k1_lulle.m4a b/web/radio/audio/voice/male/k1_lulle.m4a new file mode 100644 index 00000000..2eceb223 Binary files /dev/null and b/web/radio/audio/voice/male/k1_lulle.m4a differ diff --git a/web/radio/audio/voice/male/k1_lulle.ogg b/web/radio/audio/voice/male/k1_lulle.ogg new file mode 100644 index 00000000..6eccb48d Binary files /dev/null and b/web/radio/audio/voice/male/k1_lulle.ogg differ diff --git a/web/radio/audio/voice/male/k1_truth.m4a b/web/radio/audio/voice/male/k1_truth.m4a new file mode 100644 index 00000000..ca4ceb6b Binary files /dev/null and b/web/radio/audio/voice/male/k1_truth.m4a differ diff --git a/web/radio/audio/voice/male/k1_truth.ogg b/web/radio/audio/voice/male/k1_truth.ogg new file mode 100644 index 00000000..d882fd1d Binary files /dev/null and b/web/radio/audio/voice/male/k1_truth.ogg differ diff --git a/web/radio/audio/voice/male/k1_truthe.m4a b/web/radio/audio/voice/male/k1_truthe.m4a new file mode 100644 index 00000000..825d4260 Binary files /dev/null and b/web/radio/audio/voice/male/k1_truthe.m4a differ diff --git a/web/radio/audio/voice/male/k1_truthe.ogg b/web/radio/audio/voice/male/k1_truthe.ogg new file mode 100644 index 00000000..99c91553 Binary files /dev/null and b/web/radio/audio/voice/male/k1_truthe.ogg differ diff --git a/web/radio/audio/voice/male/l1_free.m4a b/web/radio/audio/voice/male/l1_free.m4a new file mode 100644 index 00000000..cdbd1dff Binary files /dev/null and b/web/radio/audio/voice/male/l1_free.m4a differ diff --git a/web/radio/audio/voice/male/l1_free.ogg b/web/radio/audio/voice/male/l1_free.ogg new file mode 100644 index 00000000..ab5d9800 Binary files /dev/null and b/web/radio/audio/voice/male/l1_free.ogg differ diff --git a/web/radio/audio/voice/male/l1_freee.m4a b/web/radio/audio/voice/male/l1_freee.m4a new file mode 100644 index 00000000..b67b6699 Binary files /dev/null and b/web/radio/audio/voice/male/l1_freee.m4a differ diff --git a/web/radio/audio/voice/male/l1_freee.ogg b/web/radio/audio/voice/male/l1_freee.ogg new file mode 100644 index 00000000..fa6d91b7 Binary files /dev/null and b/web/radio/audio/voice/male/l1_freee.ogg differ diff --git a/web/radio/audio/voice/male/l1_go.m4a b/web/radio/audio/voice/male/l1_go.m4a new file mode 100644 index 00000000..8d9edb1d Binary files /dev/null and b/web/radio/audio/voice/male/l1_go.m4a differ diff --git a/web/radio/audio/voice/male/l1_go.ogg b/web/radio/audio/voice/male/l1_go.ogg new file mode 100644 index 00000000..d74078b6 Binary files /dev/null and b/web/radio/audio/voice/male/l1_go.ogg differ diff --git a/web/radio/audio/voice/male/l1_goe.m4a b/web/radio/audio/voice/male/l1_goe.m4a new file mode 100644 index 00000000..b0d2e8e2 Binary files /dev/null and b/web/radio/audio/voice/male/l1_goe.m4a differ diff --git a/web/radio/audio/voice/male/l1_goe.ogg b/web/radio/audio/voice/male/l1_goe.ogg new file mode 100644 index 00000000..c579a307 Binary files /dev/null and b/web/radio/audio/voice/male/l1_goe.ogg differ diff --git a/web/radio/audio/voice/male/l1_in.m4a b/web/radio/audio/voice/male/l1_in.m4a new file mode 100644 index 00000000..9e9ebb48 Binary files /dev/null and b/web/radio/audio/voice/male/l1_in.m4a differ diff --git a/web/radio/audio/voice/male/l1_in.ogg b/web/radio/audio/voice/male/l1_in.ogg new file mode 100644 index 00000000..62b73e74 Binary files /dev/null and b/web/radio/audio/voice/male/l1_in.ogg differ diff --git a/web/radio/audio/voice/male/p1.m4a b/web/radio/audio/voice/male/p1.m4a new file mode 100644 index 00000000..29ea5524 Binary files /dev/null and b/web/radio/audio/voice/male/p1.m4a differ diff --git a/web/radio/audio/voice/male/p1.ogg b/web/radio/audio/voice/male/p1.ogg new file mode 100644 index 00000000..bbff1f32 Binary files /dev/null and b/web/radio/audio/voice/male/p1.ogg differ diff --git a/web/radio/audio/voice/male/p2.m4a b/web/radio/audio/voice/male/p2.m4a new file mode 100644 index 00000000..c446081e Binary files /dev/null and b/web/radio/audio/voice/male/p2.m4a differ diff --git a/web/radio/audio/voice/male/p2.ogg b/web/radio/audio/voice/male/p2.ogg new file mode 100644 index 00000000..859a3ac9 Binary files /dev/null and b/web/radio/audio/voice/male/p2.ogg differ diff --git a/web/radio/audio/voice/male/p3.m4a b/web/radio/audio/voice/male/p3.m4a new file mode 100644 index 00000000..22ebf26c Binary files /dev/null and b/web/radio/audio/voice/male/p3.m4a differ diff --git a/web/radio/audio/voice/male/p3.ogg b/web/radio/audio/voice/male/p3.ogg new file mode 100644 index 00000000..9ac93989 Binary files /dev/null and b/web/radio/audio/voice/male/p3.ogg differ diff --git a/web/radio/audio/voice/male/p4.m4a b/web/radio/audio/voice/male/p4.m4a new file mode 100644 index 00000000..1976b3c9 Binary files /dev/null and b/web/radio/audio/voice/male/p4.m4a differ diff --git a/web/radio/audio/voice/male/p4.ogg b/web/radio/audio/voice/male/p4.ogg new file mode 100644 index 00000000..331bfeef Binary files /dev/null and b/web/radio/audio/voice/male/p4.ogg differ diff --git a/web/radio/audio/voice/male/p5.m4a b/web/radio/audio/voice/male/p5.m4a new file mode 100644 index 00000000..5661b2c8 Binary files /dev/null and b/web/radio/audio/voice/male/p5.m4a differ diff --git a/web/radio/audio/voice/male/p5.ogg b/web/radio/audio/voice/male/p5.ogg new file mode 100644 index 00000000..0c36ce72 Binary files /dev/null and b/web/radio/audio/voice/male/p5.ogg differ diff --git a/web/radio/audio/voice/male/p6.m4a b/web/radio/audio/voice/male/p6.m4a new file mode 100644 index 00000000..907234d1 Binary files /dev/null and b/web/radio/audio/voice/male/p6.m4a differ diff --git a/web/radio/audio/voice/male/p6.ogg b/web/radio/audio/voice/male/p6.ogg new file mode 100644 index 00000000..c5a7c57d Binary files /dev/null and b/web/radio/audio/voice/male/p6.ogg differ diff --git a/web/radio/audio/voice/male/p7.m4a b/web/radio/audio/voice/male/p7.m4a new file mode 100644 index 00000000..9dd0e47a Binary files /dev/null and b/web/radio/audio/voice/male/p7.m4a differ diff --git a/web/radio/audio/voice/male/p7.ogg b/web/radio/audio/voice/male/p7.ogg new file mode 100644 index 00000000..6f750bc4 Binary files /dev/null and b/web/radio/audio/voice/male/p7.ogg differ diff --git a/web/radio/audio/voice/male/p8.m4a b/web/radio/audio/voice/male/p8.m4a new file mode 100644 index 00000000..fb8a6435 Binary files /dev/null and b/web/radio/audio/voice/male/p8.m4a differ diff --git a/web/radio/audio/voice/male/p8.ogg b/web/radio/audio/voice/male/p8.ogg new file mode 100644 index 00000000..0c9c3513 Binary files /dev/null and b/web/radio/audio/voice/male/p8.ogg differ diff --git a/web/radio/audio/voice/male/p9.m4a b/web/radio/audio/voice/male/p9.m4a new file mode 100644 index 00000000..55795475 Binary files /dev/null and b/web/radio/audio/voice/male/p9.m4a differ diff --git a/web/radio/audio/voice/male/p9.ogg b/web/radio/audio/voice/male/p9.ogg new file mode 100644 index 00000000..4df1a550 Binary files /dev/null and b/web/radio/audio/voice/male/p9.ogg differ diff --git a/web/radio/audio/voice/male/prog_c1.m4a b/web/radio/audio/voice/male/prog_c1.m4a new file mode 100644 index 00000000..d256a2bb Binary files /dev/null and b/web/radio/audio/voice/male/prog_c1.m4a differ diff --git a/web/radio/audio/voice/male/prog_c1.ogg b/web/radio/audio/voice/male/prog_c1.ogg new file mode 100644 index 00000000..a1d8a65a Binary files /dev/null and b/web/radio/audio/voice/male/prog_c1.ogg differ diff --git a/web/radio/audio/voice/male/prog_c2.m4a b/web/radio/audio/voice/male/prog_c2.m4a new file mode 100644 index 00000000..00054055 Binary files /dev/null and b/web/radio/audio/voice/male/prog_c2.m4a differ diff --git a/web/radio/audio/voice/male/prog_c2.ogg b/web/radio/audio/voice/male/prog_c2.ogg new file mode 100644 index 00000000..4c6df7e1 Binary files /dev/null and b/web/radio/audio/voice/male/prog_c2.ogg differ diff --git a/web/radio/audio/voice/male/prog_c3.m4a b/web/radio/audio/voice/male/prog_c3.m4a new file mode 100644 index 00000000..4680cdcb Binary files /dev/null and b/web/radio/audio/voice/male/prog_c3.m4a differ diff --git a/web/radio/audio/voice/male/prog_c3.ogg b/web/radio/audio/voice/male/prog_c3.ogg new file mode 100644 index 00000000..c0e6a978 Binary files /dev/null and b/web/radio/audio/voice/male/prog_c3.ogg differ diff --git a/web/radio/audio/voice/male/prog_c4.m4a b/web/radio/audio/voice/male/prog_c4.m4a new file mode 100644 index 00000000..50831853 Binary files /dev/null and b/web/radio/audio/voice/male/prog_c4.m4a differ diff --git a/web/radio/audio/voice/male/prog_c4.ogg b/web/radio/audio/voice/male/prog_c4.ogg new file mode 100644 index 00000000..b5fec96a Binary files /dev/null and b/web/radio/audio/voice/male/prog_c4.ogg differ diff --git a/web/radio/audio/voice/male/prog_c5.m4a b/web/radio/audio/voice/male/prog_c5.m4a new file mode 100644 index 00000000..2e11d440 Binary files /dev/null and b/web/radio/audio/voice/male/prog_c5.m4a differ diff --git a/web/radio/audio/voice/male/prog_c5.ogg b/web/radio/audio/voice/male/prog_c5.ogg new file mode 100644 index 00000000..98d549e8 Binary files /dev/null and b/web/radio/audio/voice/male/prog_c5.ogg differ diff --git a/web/radio/audio/voice/male/rep_high.m4a b/web/radio/audio/voice/male/rep_high.m4a new file mode 100644 index 00000000..0d86dfbb Binary files /dev/null and b/web/radio/audio/voice/male/rep_high.m4a differ diff --git a/web/radio/audio/voice/male/rep_high.ogg b/web/radio/audio/voice/male/rep_high.ogg new file mode 100644 index 00000000..d9281467 Binary files /dev/null and b/web/radio/audio/voice/male/rep_high.ogg differ diff --git a/web/radio/audio/voice/male/rep_low.m4a b/web/radio/audio/voice/male/rep_low.m4a new file mode 100644 index 00000000..0109344e Binary files /dev/null and b/web/radio/audio/voice/male/rep_low.m4a differ diff --git a/web/radio/audio/voice/male/rep_low.ogg b/web/radio/audio/voice/male/rep_low.ogg new file mode 100644 index 00000000..e8b534dc Binary files /dev/null and b/web/radio/audio/voice/male/rep_low.ogg differ diff --git a/web/radio/audio/voice/male/rep_mid.m4a b/web/radio/audio/voice/male/rep_mid.m4a new file mode 100644 index 00000000..e2970575 Binary files /dev/null and b/web/radio/audio/voice/male/rep_mid.m4a differ diff --git a/web/radio/audio/voice/male/rep_mid.ogg b/web/radio/audio/voice/male/rep_mid.ogg new file mode 100644 index 00000000..8c299bb5 Binary files /dev/null and b/web/radio/audio/voice/male/rep_mid.ogg differ diff --git a/web/radio/audio/voice/male/sys_continue_ask.m4a b/web/radio/audio/voice/male/sys_continue_ask.m4a new file mode 100644 index 00000000..2ea37d8f Binary files /dev/null and b/web/radio/audio/voice/male/sys_continue_ask.m4a differ diff --git a/web/radio/audio/voice/male/sys_continue_ask.ogg b/web/radio/audio/voice/male/sys_continue_ask.ogg new file mode 100644 index 00000000..24dcab4a Binary files /dev/null and b/web/radio/audio/voice/male/sys_continue_ask.ogg differ diff --git a/web/radio/audio/voice/male/sys_help.m4a b/web/radio/audio/voice/male/sys_help.m4a new file mode 100644 index 00000000..cbb96430 Binary files /dev/null and b/web/radio/audio/voice/male/sys_help.m4a differ diff --git a/web/radio/audio/voice/male/sys_help.ogg b/web/radio/audio/voice/male/sys_help.ogg new file mode 100644 index 00000000..8db881d6 Binary files /dev/null and b/web/radio/audio/voice/male/sys_help.ogg differ diff --git a/web/radio/audio/voice/male/sys_menu.m4a b/web/radio/audio/voice/male/sys_menu.m4a new file mode 100644 index 00000000..c52b8004 Binary files /dev/null and b/web/radio/audio/voice/male/sys_menu.m4a differ diff --git a/web/radio/audio/voice/male/sys_menu.ogg b/web/radio/audio/voice/male/sys_menu.ogg new file mode 100644 index 00000000..ad6979e1 Binary files /dev/null and b/web/radio/audio/voice/male/sys_menu.ogg differ diff --git a/web/radio/audio/voice/male/sys_menu_lobby.m4a b/web/radio/audio/voice/male/sys_menu_lobby.m4a new file mode 100644 index 00000000..f3476a36 Binary files /dev/null and b/web/radio/audio/voice/male/sys_menu_lobby.m4a differ diff --git a/web/radio/audio/voice/male/sys_menu_lobby.ogg b/web/radio/audio/voice/male/sys_menu_lobby.ogg new file mode 100644 index 00000000..42df97a1 Binary files /dev/null and b/web/radio/audio/voice/male/sys_menu_lobby.ogg differ diff --git a/web/radio/audio/voice/male/sys_menu_restart.m4a b/web/radio/audio/voice/male/sys_menu_restart.m4a new file mode 100644 index 00000000..a6933a56 Binary files /dev/null and b/web/radio/audio/voice/male/sys_menu_restart.m4a differ diff --git a/web/radio/audio/voice/male/sys_menu_restart.ogg b/web/radio/audio/voice/male/sys_menu_restart.ogg new file mode 100644 index 00000000..bf463732 Binary files /dev/null and b/web/radio/audio/voice/male/sys_menu_restart.ogg differ diff --git a/web/radio/audio/voice/male/sys_menu_resume.m4a b/web/radio/audio/voice/male/sys_menu_resume.m4a new file mode 100644 index 00000000..94516429 Binary files /dev/null and b/web/radio/audio/voice/male/sys_menu_resume.m4a differ diff --git a/web/radio/audio/voice/male/sys_menu_resume.ogg b/web/radio/audio/voice/male/sys_menu_resume.ogg new file mode 100644 index 00000000..1b1163e3 Binary files /dev/null and b/web/radio/audio/voice/male/sys_menu_resume.ogg differ diff --git a/web/radio/audio/voice/male/sys_new_night.m4a b/web/radio/audio/voice/male/sys_new_night.m4a new file mode 100644 index 00000000..2e4fe881 Binary files /dev/null and b/web/radio/audio/voice/male/sys_new_night.m4a differ diff --git a/web/radio/audio/voice/male/sys_new_night.ogg b/web/radio/audio/voice/male/sys_new_night.ogg new file mode 100644 index 00000000..11ce968b Binary files /dev/null and b/web/radio/audio/voice/male/sys_new_night.ogg differ diff --git a/web/radio/audio/voice/male/sys_saved.m4a b/web/radio/audio/voice/male/sys_saved.m4a new file mode 100644 index 00000000..4ab228fa Binary files /dev/null and b/web/radio/audio/voice/male/sys_saved.m4a differ diff --git a/web/radio/audio/voice/male/sys_saved.ogg b/web/radio/audio/voice/male/sys_saved.ogg new file mode 100644 index 00000000..4df23788 Binary files /dev/null and b/web/radio/audio/voice/male/sys_saved.ogg differ diff --git a/web/radio/audio/voice/male/sys_settings.m4a b/web/radio/audio/voice/male/sys_settings.m4a new file mode 100644 index 00000000..bf72e2a8 Binary files /dev/null and b/web/radio/audio/voice/male/sys_settings.m4a differ diff --git a/web/radio/audio/voice/male/sys_settings.ogg b/web/radio/audio/voice/male/sys_settings.ogg new file mode 100644 index 00000000..d3c80c6b Binary files /dev/null and b/web/radio/audio/voice/male/sys_settings.ogg differ diff --git a/web/radio/audio/voice/male/sys_skip.m4a b/web/radio/audio/voice/male/sys_skip.m4a new file mode 100644 index 00000000..45753691 Binary files /dev/null and b/web/radio/audio/voice/male/sys_skip.m4a differ diff --git a/web/radio/audio/voice/male/sys_skip.ogg b/web/radio/audio/voice/male/sys_skip.ogg new file mode 100644 index 00000000..951baf82 Binary files /dev/null and b/web/radio/audio/voice/male/sys_skip.ogg differ diff --git a/web/radio/audio/voice/male/w1_air.m4a b/web/radio/audio/voice/male/w1_air.m4a new file mode 100644 index 00000000..1083432c Binary files /dev/null and b/web/radio/audio/voice/male/w1_air.m4a differ diff --git a/web/radio/audio/voice/male/w1_air.ogg b/web/radio/audio/voice/male/w1_air.ogg new file mode 100644 index 00000000..0b1f69a9 Binary files /dev/null and b/web/radio/audio/voice/male/w1_air.ogg differ diff --git a/web/radio/audio/voice/male/w1_aire.m4a b/web/radio/audio/voice/male/w1_aire.m4a new file mode 100644 index 00000000..2169b9e1 Binary files /dev/null and b/web/radio/audio/voice/male/w1_aire.m4a differ diff --git a/web/radio/audio/voice/male/w1_aire.ogg b/web/radio/audio/voice/male/w1_aire.ogg new file mode 100644 index 00000000..adc1d592 Binary files /dev/null and b/web/radio/audio/voice/male/w1_aire.ogg differ diff --git a/web/radio/audio/voice/male/w1_home.m4a b/web/radio/audio/voice/male/w1_home.m4a new file mode 100644 index 00000000..d85bd432 Binary files /dev/null and b/web/radio/audio/voice/male/w1_home.m4a differ diff --git a/web/radio/audio/voice/male/w1_home.ogg b/web/radio/audio/voice/male/w1_home.ogg new file mode 100644 index 00000000..f08338e6 Binary files /dev/null and b/web/radio/audio/voice/male/w1_home.ogg differ diff --git a/web/radio/audio/voice/male/w1_homee.m4a b/web/radio/audio/voice/male/w1_homee.m4a new file mode 100644 index 00000000..6f3c51c0 Binary files /dev/null and b/web/radio/audio/voice/male/w1_homee.m4a differ diff --git a/web/radio/audio/voice/male/w1_homee.ogg b/web/radio/audio/voice/male/w1_homee.ogg new file mode 100644 index 00000000..d34a1e16 Binary files /dev/null and b/web/radio/audio/voice/male/w1_homee.ogg differ diff --git a/web/radio/audio/voice/male/w1_in.m4a b/web/radio/audio/voice/male/w1_in.m4a new file mode 100644 index 00000000..405c218f Binary files /dev/null and b/web/radio/audio/voice/male/w1_in.m4a differ diff --git a/web/radio/audio/voice/male/w1_in.ogg b/web/radio/audio/voice/male/w1_in.ogg new file mode 100644 index 00000000..34ce284b Binary files /dev/null and b/web/radio/audio/voice/male/w1_in.ogg differ diff --git a/web/radio/audio/voice/male/w1_priv.m4a b/web/radio/audio/voice/male/w1_priv.m4a new file mode 100644 index 00000000..feea22fe Binary files /dev/null and b/web/radio/audio/voice/male/w1_priv.m4a differ diff --git a/web/radio/audio/voice/male/w1_priv.ogg b/web/radio/audio/voice/male/w1_priv.ogg new file mode 100644 index 00000000..26b77b50 Binary files /dev/null and b/web/radio/audio/voice/male/w1_priv.ogg differ diff --git a/web/radio/audio/voice/male/w1_prive.m4a b/web/radio/audio/voice/male/w1_prive.m4a new file mode 100644 index 00000000..4d3193d5 Binary files /dev/null and b/web/radio/audio/voice/male/w1_prive.m4a differ diff --git a/web/radio/audio/voice/male/w1_prive.ogg b/web/radio/audio/voice/male/w1_prive.ogg new file mode 100644 index 00000000..d6abb9bc Binary files /dev/null and b/web/radio/audio/voice/male/w1_prive.ogg differ diff --git a/web/radio/audio/voice/male/w1_say.m4a b/web/radio/audio/voice/male/w1_say.m4a new file mode 100644 index 00000000..7ea62758 Binary files /dev/null and b/web/radio/audio/voice/male/w1_say.m4a differ diff --git a/web/radio/audio/voice/male/w1_say.ogg b/web/radio/audio/voice/male/w1_say.ogg new file mode 100644 index 00000000..7e630471 Binary files /dev/null and b/web/radio/audio/voice/male/w1_say.ogg differ diff --git a/web/radio/audio/voice/male/w1_say2.m4a b/web/radio/audio/voice/male/w1_say2.m4a new file mode 100644 index 00000000..2c84d4a3 Binary files /dev/null and b/web/radio/audio/voice/male/w1_say2.m4a differ diff --git a/web/radio/audio/voice/male/w1_say2.ogg b/web/radio/audio/voice/male/w1_say2.ogg new file mode 100644 index 00000000..c258e965 Binary files /dev/null and b/web/radio/audio/voice/male/w1_say2.ogg differ diff --git a/web/radio/audio/voice/male/w1_say2e.m4a b/web/radio/audio/voice/male/w1_say2e.m4a new file mode 100644 index 00000000..64799135 Binary files /dev/null and b/web/radio/audio/voice/male/w1_say2e.m4a differ diff --git a/web/radio/audio/voice/male/w1_say2e.ogg b/web/radio/audio/voice/male/w1_say2e.ogg new file mode 100644 index 00000000..886cc5e2 Binary files /dev/null and b/web/radio/audio/voice/male/w1_say2e.ogg differ diff --git a/web/radio/audio/voice/male/w1_saye.m4a b/web/radio/audio/voice/male/w1_saye.m4a new file mode 100644 index 00000000..c2df5489 Binary files /dev/null and b/web/radio/audio/voice/male/w1_saye.m4a differ diff --git a/web/radio/audio/voice/male/w1_saye.ogg b/web/radio/audio/voice/male/w1_saye.ogg new file mode 100644 index 00000000..0382cd87 Binary files /dev/null and b/web/radio/audio/voice/male/w1_saye.ogg differ diff --git a/web/radio/audio/voice/male/z1_adone.m4a b/web/radio/audio/voice/male/z1_adone.m4a new file mode 100644 index 00000000..e0731cdc Binary files /dev/null and b/web/radio/audio/voice/male/z1_adone.m4a differ diff --git a/web/radio/audio/voice/male/z1_adone.ogg b/web/radio/audio/voice/male/z1_adone.ogg new file mode 100644 index 00000000..ee087350 Binary files /dev/null and b/web/radio/audio/voice/male/z1_adone.ogg differ diff --git a/web/radio/audio/voice/male/z1_ask.m4a b/web/radio/audio/voice/male/z1_ask.m4a new file mode 100644 index 00000000..ec8c5798 Binary files /dev/null and b/web/radio/audio/voice/male/z1_ask.m4a differ diff --git a/web/radio/audio/voice/male/z1_ask.ogg b/web/radio/audio/voice/male/z1_ask.ogg new file mode 100644 index 00000000..1764774a Binary files /dev/null and b/web/radio/audio/voice/male/z1_ask.ogg differ diff --git a/web/radio/audio/voice/male/z1_bade.m4a b/web/radio/audio/voice/male/z1_bade.m4a new file mode 100644 index 00000000..0a0ee39c Binary files /dev/null and b/web/radio/audio/voice/male/z1_bade.m4a differ diff --git a/web/radio/audio/voice/male/z1_bade.ogg b/web/radio/audio/voice/male/z1_bade.ogg new file mode 100644 index 00000000..6b3102b7 Binary files /dev/null and b/web/radio/audio/voice/male/z1_bade.ogg differ diff --git a/web/radio/audio/voice/male/z1_ok1.m4a b/web/radio/audio/voice/male/z1_ok1.m4a new file mode 100644 index 00000000..b5898238 Binary files /dev/null and b/web/radio/audio/voice/male/z1_ok1.m4a differ diff --git a/web/radio/audio/voice/male/z1_ok1.ogg b/web/radio/audio/voice/male/z1_ok1.ogg new file mode 100644 index 00000000..d33b539f Binary files /dev/null and b/web/radio/audio/voice/male/z1_ok1.ogg differ diff --git a/web/radio/audio/voice/male/z1_pol.m4a b/web/radio/audio/voice/male/z1_pol.m4a new file mode 100644 index 00000000..6787e0d1 Binary files /dev/null and b/web/radio/audio/voice/male/z1_pol.m4a differ diff --git a/web/radio/audio/voice/male/z1_pol.ogg b/web/radio/audio/voice/male/z1_pol.ogg new file mode 100644 index 00000000..2f6c9e0d Binary files /dev/null and b/web/radio/audio/voice/male/z1_pol.ogg differ diff --git a/web/radio/audio/voice/male/z1_pole.m4a b/web/radio/audio/voice/male/z1_pole.m4a new file mode 100644 index 00000000..fffb2968 Binary files /dev/null and b/web/radio/audio/voice/male/z1_pole.m4a differ diff --git a/web/radio/audio/voice/male/z1_pole.ogg b/web/radio/audio/voice/male/z1_pole.ogg new file mode 100644 index 00000000..44479c28 Binary files /dev/null and b/web/radio/audio/voice/male/z1_pole.ogg differ diff --git a/web/radio/audio/voice/male/z1_ring_in.m4a b/web/radio/audio/voice/male/z1_ring_in.m4a new file mode 100644 index 00000000..4e4ffd68 Binary files /dev/null and b/web/radio/audio/voice/male/z1_ring_in.m4a differ diff --git a/web/radio/audio/voice/male/z1_ring_in.ogg b/web/radio/audio/voice/male/z1_ring_in.ogg new file mode 100644 index 00000000..7a2409b2 Binary files /dev/null and b/web/radio/audio/voice/male/z1_ring_in.ogg differ diff --git a/web/radio/audio/voice/male/z1_sdone.m4a b/web/radio/audio/voice/male/z1_sdone.m4a new file mode 100644 index 00000000..5224c34f Binary files /dev/null and b/web/radio/audio/voice/male/z1_sdone.m4a differ diff --git a/web/radio/audio/voice/male/z1_sdone.ogg b/web/radio/audio/voice/male/z1_sdone.ogg new file mode 100644 index 00000000..cde73a2f Binary files /dev/null and b/web/radio/audio/voice/male/z1_sdone.ogg differ diff --git a/web/radio/audio/voice/male/z1_song.m4a b/web/radio/audio/voice/male/z1_song.m4a new file mode 100644 index 00000000..fbfdd55c Binary files /dev/null and b/web/radio/audio/voice/male/z1_song.m4a differ diff --git a/web/radio/audio/voice/male/z1_song.ogg b/web/radio/audio/voice/male/z1_song.ogg new file mode 100644 index 00000000..1616eb49 Binary files /dev/null and b/web/radio/audio/voice/male/z1_song.ogg differ diff --git a/web/radio/audio/voice/male/z1_stay.m4a b/web/radio/audio/voice/male/z1_stay.m4a new file mode 100644 index 00000000..5ed25f49 Binary files /dev/null and b/web/radio/audio/voice/male/z1_stay.m4a differ diff --git a/web/radio/audio/voice/male/z1_stay.ogg b/web/radio/audio/voice/male/z1_stay.ogg new file mode 100644 index 00000000..df464921 Binary files /dev/null and b/web/radio/audio/voice/male/z1_stay.ogg differ diff --git a/web/radio/audio/voice/male/z1_staye.m4a b/web/radio/audio/voice/male/z1_staye.m4a new file mode 100644 index 00000000..2a1f2ac1 Binary files /dev/null and b/web/radio/audio/voice/male/z1_staye.m4a differ diff --git a/web/radio/audio/voice/male/z1_staye.ogg b/web/radio/audio/voice/male/z1_staye.ogg new file mode 100644 index 00000000..97287d42 Binary files /dev/null and b/web/radio/audio/voice/male/z1_staye.ogg differ diff --git a/web/radio/audio/voice/w1_a.m4a b/web/radio/audio/voice/w1_a.m4a new file mode 100644 index 00000000..5234251a Binary files /dev/null and b/web/radio/audio/voice/w1_a.m4a differ diff --git a/web/radio/audio/voice/w1_a.ogg b/web/radio/audio/voice/w1_a.ogg new file mode 100644 index 00000000..c4d2b624 Binary files /dev/null and b/web/radio/audio/voice/w1_a.ogg differ diff --git a/web/radio/audio/voice/w1_airo.m4a b/web/radio/audio/voice/w1_airo.m4a new file mode 100644 index 00000000..bfba76d7 Binary files /dev/null and b/web/radio/audio/voice/w1_airo.m4a differ diff --git a/web/radio/audio/voice/w1_airo.ogg b/web/radio/audio/voice/w1_airo.ogg new file mode 100644 index 00000000..7a839412 Binary files /dev/null and b/web/radio/audio/voice/w1_airo.ogg differ diff --git a/web/radio/audio/voice/w1_b.m4a b/web/radio/audio/voice/w1_b.m4a new file mode 100644 index 00000000..75863856 Binary files /dev/null and b/web/radio/audio/voice/w1_b.m4a differ diff --git a/web/radio/audio/voice/w1_b.ogg b/web/radio/audio/voice/w1_b.ogg new file mode 100644 index 00000000..26bba6b8 Binary files /dev/null and b/web/radio/audio/voice/w1_b.ogg differ diff --git a/web/radio/audio/voice/w1_homeo.m4a b/web/radio/audio/voice/w1_homeo.m4a new file mode 100644 index 00000000..94561c2a Binary files /dev/null and b/web/radio/audio/voice/w1_homeo.m4a differ diff --git a/web/radio/audio/voice/w1_homeo.ogg b/web/radio/audio/voice/w1_homeo.ogg new file mode 100644 index 00000000..81f4b5da Binary files /dev/null and b/web/radio/audio/voice/w1_homeo.ogg differ diff --git a/web/radio/audio/voice/w1_privq.m4a b/web/radio/audio/voice/w1_privq.m4a new file mode 100644 index 00000000..2f7bfaf4 Binary files /dev/null and b/web/radio/audio/voice/w1_privq.m4a differ diff --git a/web/radio/audio/voice/w1_privq.ogg b/web/radio/audio/voice/w1_privq.ogg new file mode 100644 index 00000000..df7580c4 Binary files /dev/null and b/web/radio/audio/voice/w1_privq.ogg differ diff --git a/web/radio/audio/voice/w1_say2o.m4a b/web/radio/audio/voice/w1_say2o.m4a new file mode 100644 index 00000000..0a00b26d Binary files /dev/null and b/web/radio/audio/voice/w1_say2o.m4a differ diff --git a/web/radio/audio/voice/w1_say2o.ogg b/web/radio/audio/voice/w1_say2o.ogg new file mode 100644 index 00000000..5f32fce3 Binary files /dev/null and b/web/radio/audio/voice/w1_say2o.ogg differ diff --git a/web/radio/audio/voice/w1_sayo.m4a b/web/radio/audio/voice/w1_sayo.m4a new file mode 100644 index 00000000..08ec5797 Binary files /dev/null and b/web/radio/audio/voice/w1_sayo.m4a differ diff --git a/web/radio/audio/voice/w1_sayo.ogg b/web/radio/audio/voice/w1_sayo.ogg new file mode 100644 index 00000000..791588e4 Binary files /dev/null and b/web/radio/audio/voice/w1_sayo.ogg differ diff --git a/web/radio/audio/voice/z1_a.m4a b/web/radio/audio/voice/z1_a.m4a new file mode 100644 index 00000000..b691fa06 Binary files /dev/null and b/web/radio/audio/voice/z1_a.m4a differ diff --git a/web/radio/audio/voice/z1_a.ogg b/web/radio/audio/voice/z1_a.ogg new file mode 100644 index 00000000..6fdf4536 Binary files /dev/null and b/web/radio/audio/voice/z1_a.ogg differ diff --git a/web/radio/audio/voice/z1_asko.m4a b/web/radio/audio/voice/z1_asko.m4a new file mode 100644 index 00000000..8fc82e6c Binary files /dev/null and b/web/radio/audio/voice/z1_asko.m4a differ diff --git a/web/radio/audio/voice/z1_asko.ogg b/web/radio/audio/voice/z1_asko.ogg new file mode 100644 index 00000000..8e5c8210 Binary files /dev/null and b/web/radio/audio/voice/z1_asko.ogg differ diff --git a/web/radio/audio/voice/z1_b.m4a b/web/radio/audio/voice/z1_b.m4a new file mode 100644 index 00000000..166e749c Binary files /dev/null and b/web/radio/audio/voice/z1_b.m4a differ diff --git a/web/radio/audio/voice/z1_b.ogg b/web/radio/audio/voice/z1_b.ogg new file mode 100644 index 00000000..b84954e2 Binary files /dev/null and b/web/radio/audio/voice/z1_b.ogg differ diff --git a/web/radio/audio/voice/z1_bad.m4a b/web/radio/audio/voice/z1_bad.m4a new file mode 100644 index 00000000..89db152a Binary files /dev/null and b/web/radio/audio/voice/z1_bad.m4a differ diff --git a/web/radio/audio/voice/z1_bad.ogg b/web/radio/audio/voice/z1_bad.ogg new file mode 100644 index 00000000..68027a2a Binary files /dev/null and b/web/radio/audio/voice/z1_bad.ogg differ diff --git a/web/radio/audio/voice/z1_polo.m4a b/web/radio/audio/voice/z1_polo.m4a new file mode 100644 index 00000000..9a359d67 Binary files /dev/null and b/web/radio/audio/voice/z1_polo.m4a differ diff --git a/web/radio/audio/voice/z1_polo.ogg b/web/radio/audio/voice/z1_polo.ogg new file mode 100644 index 00000000..0dfd31c0 Binary files /dev/null and b/web/radio/audio/voice/z1_polo.ogg differ diff --git a/web/radio/audio/voice/z1_songo.m4a b/web/radio/audio/voice/z1_songo.m4a new file mode 100644 index 00000000..90750ecf Binary files /dev/null and b/web/radio/audio/voice/z1_songo.m4a differ diff --git a/web/radio/audio/voice/z1_songo.ogg b/web/radio/audio/voice/z1_songo.ogg new file mode 100644 index 00000000..52ccb34d Binary files /dev/null and b/web/radio/audio/voice/z1_songo.ogg differ diff --git a/web/radio/audio/voice/z1_stayo.m4a b/web/radio/audio/voice/z1_stayo.m4a new file mode 100644 index 00000000..fb53d214 Binary files /dev/null and b/web/radio/audio/voice/z1_stayo.m4a differ diff --git a/web/radio/audio/voice/z1_stayo.ogg b/web/radio/audio/voice/z1_stayo.ogg new file mode 100644 index 00000000..76f8d143 Binary files /dev/null and b/web/radio/audio/voice/z1_stayo.ogg differ diff --git a/web/radio/cover.png b/web/radio/cover.png new file mode 100644 index 00000000..d9dd1627 Binary files /dev/null and b/web/radio/cover.png differ diff --git a/web/radio/game.js b/web/radio/game.js new file mode 100644 index 00000000..aff84b64 --- /dev/null +++ b/web/radio/game.js @@ -0,0 +1,1307 @@ +/** + * 《不眠频率》——深夜电台热线叙事 RPG(docs/design/15-radio.md)。 + * + * 你是午夜档电台热线主播「今夜不眠」:一晚接进五通电话,9 个抉择点、 + * 4 结局、跨通话三个标量(rep 口碑 / heardArgument 线索 / bond 好感×5)。 + * + * 6 宏状态(§2.1):INTRO / RING / ONAIR / BRIDGE / ENDING + MENU 覆盖层 + * (另加 ASK = 有存档进游戏的「继续上次?」询问态,属 INTRO 前置瞬态)。 + * + * 手势(§2.3,6+2 全覆盖零豁免): + * - tap :播放中=跳过本句(p* 不可跳);叙事节点链末=继续; + * RING=接听;ENDING 结算态=再来一局(新夜班) + * - doubletap :重听当前节点台词(含抉择提示);RING=重听上一通话收尾句; + * ENDING=重听结局链;MENU=确认执行(§5.2 模态约定覆盖重听) + * - left / right :ONAIR 抉择(左=走心 / 右=克制;p* 播放中缓存 pendingChoice); + * 其余态无效 → error 震 + gd_invalid(onceKey,零静默) + * - up :帮助+状态(RING=第几通+口碑档;ENDING=结局主句),随后复述局面 + * - down :模态菜单(单击循环选项 / 双击确认 / 上滑帮助 / 下滑关闭) + * - longpress :引擎接管旁白 1.7×(叙事类不豁免,12 号 §2.2;游戏不收此手势) + * + * 链驱动(storyline playNode 范式):节点链 onComplete 串行推进 + seqToken 打断 + * 作废 + pendingChoice 缓存(盲玩公平)。与 storyline 的差异:INTRO 开场链与 + * 全部过场转移(@ring/@bridge/@ending 等)**自动衔接**(电台节目流,§2.2 转移 + * 图「链完→」),ONAIR 通话内叙事节点链末等待单击(「链末=继续」+ gd_continue + * 看门狗),危机拍(h1_talko→h1_danger)与签收链自动流不等待。 + * + * 资产缺失兜底:引擎 console.error 静默跳过且不回调 onComplete;单句语音 + * 看门狗 30s 且 isVoicePlaying()===false 才自动续链(长句续约不掐,storyline + * N1 先例照抄)——屏蔽任意 voice 资产仍可一路走到结局,不卡死。 + * + * R2 修复(research/new8/radio/round1/synthesis.md F1-F5): + * - F1 链末等待态的无效手势不再吞掉「还在吗」看门狗(disarm 后按态立即重挂); + * - F3 RING 铃声为程序合成无缝循环垫(sfx/phone_ring,游戏侧持句柄,接听即停); + * - F4 断点续玩恢复后的首句播完才武装跳句(_resumeGuard → _skipLockId); + * - F5 链末句跳过保护(等待/过场/抉择提示前的末句单击不可跳)。 + * + * 确定性:铃声为无缝循环垫(无定时器重触发),测试与转写可复现。 + */ +import { Game } from '../engine/game.js'; +import { Guide } from '../engine/guide.js'; +import { vibrate } from '../engine/haptics.js'; + +// ---------------------------------------------------------------- 节点表 + +/** 叙事节点。voices 顺序播放;prompt 非空 = 抉择节点(播完即选,W2 契约)。 */ +function N(voices, opts = {}) { + return { + voices, prompt: null, left: null, right: null, next: null, + sfx: null, amb: null, dyn: null, auto: false, danger: false, preMs: 0, sfxEcho: null, + ...opts, + }; +} +/** 抉择节点:本体无台词,链尾接 p* 提示句(PROMPT_RE 命中 → 不可跳)。 */ +function P(prompt, left, right) { + return { + voices: [], prompt, left, right, next: null, + sfx: null, amb: null, dyn: null, auto: false, danger: false, preMs: 0, sfxEcho: null, + }; +} + +/** + * 全部节点(id = 首句 voice id;台词全文见 15-radio-voice-ids.md,132 句)。 + * next 为 '@x' 虚拟目标:@ring 进铃声 / @bridge 进幕间 / @h1end 回响插播判断 / + * @ending 进结局;选择目标 '@transfer' 由 resolve() 按 p1 选择解析。 + */ +const NODES = { + // ---- INTRO 开场链(台标口播 + 一句规矩 + 今晚目标;自动流,§9.1「链完自动进 RING」) + b_open: N(['voice/b_open'], { sfx: 'sfx/onair_sting', auto: true, next: 'b_rain' }), + b_rain: N(['voice/b_rain'], { auto: true, next: 'b_rule' }), + b_rule: N(['voice/b_rule'], { auto: true, next: 'b_goal' }), + b_goal: N(['voice/b_goal'], { next: '@ring' }), // 链完自动进 RING(c1) + + // ---- C1 老周·走丢的老人(教学通 · 2 抉择) + z1_ring_in: N(['voice/z1_ring_in'], { sfx: 'sfx/line_connect', amb: 'sfx/amb_rain_city', next: 'z1_a' }), + z1_a: N(['voice/z1_a'], { next: 'z1_b' }), + z1_b: N(['voice/z1_b'], { next: 'p1' }), + p1: P('voice/p1', 'z1_song', 'z1_ask'), + z1_song: N(['voice/z1_song'], { next: 'z1_songo' }), + z1_songo: N(['voice/z1_songo'], { next: 'z1_sdone' }), + z1_sdone: N(['voice/z1_sdone'], { next: 'p2' }), + z1_ask: N(['voice/z1_ask'], { next: 'z1_asko' }), + z1_asko: N(['voice/z1_asko'], { next: 'z1_adone' }), + z1_adone: N(['voice/z1_adone'], { next: 'p2' }), + p2: P('voice/p2', 'z1_stay', '@transfer'), // 右=转接:仅 p1=右 时改走归因组 z1_bad + z1_stay: N(['voice/z1_stay'], { next: 'z1_stayo' }), + z1_stayo: N(['voice/z1_stayo'], { next: 'z1_staye' }), + z1_staye: N(['voice/z1_staye'], { next: 'z1_ok1' }), + z1_pol: N(['voice/z1_pol'], { next: 'z1_polo' }), + z1_polo: N(['voice/z1_polo'], { next: 'z1_pole' }), + z1_pole: N(['voice/z1_pole'], { next: 'z1_ok1' }), + z1_bad: N(['voice/z1_bad'], { next: 'z1_bade' }), + z1_bade: N(['voice/z1_bade'], { next: '@bridge' }), // 归因句(真话律)后直接幕间,不播 z1_ok1 + z1_ok1: N(['voice/z1_ok1'], { next: '@bridge' }), // 首通成功收尾确认(全局一次) + + // ---- C2 小鹿·想退票的年轻人(单抉择 · 呼吸口) + l1_in: N(['voice/l1_in'], { sfx: 'sfx/line_connect', amb: 'sfx/amb_station', next: 'l1_a' }), + l1_a: N(['voice/l1_a'], { next: 'l1_b' }), + l1_b: N(['voice/l1_b'], { next: 'p3' }), + p3: P('voice/p3', 'l1_go', 'l1_free'), + l1_go: N(['voice/l1_go'], { next: 'l1_goo' }), + l1_goo: N(['voice/l1_goo'], { next: 'l1_goe' }), + l1_goe: N(['voice/l1_goe'], { next: '@bridge' }), + l1_free: N(['voice/l1_free'], { next: 'l1_freeo' }), + l1_freeo: N(['voice/l1_freeo'], { next: 'l1_freee' }), + l1_freee: N(['voice/l1_freee'], { next: '@bridge' }), + + // ---- C3 糖糖·失眠的孩子(2 抉择 · 埋 heardArgument) + k1_in: N(['voice/k1_in'], { sfx: 'sfx/line_connect', amb: 'sfx/amb_kid_room', next: 'k1_a' }), + k1_a: N(['voice/k1_a'], { next: 'k1_b' }), + // k1_b 垫 arg_muffled 线索音(信息层,起播后再叠一次,「2-3 次」取确定值 2) + k1_b: N(['voice/k1_b'], { sfx: 'sfx/arg_muffled', sfxEcho: { id: 'sfx/arg_muffled', atMs: 6500 }, next: 'p4' }), + p4: P('voice/p4', 'k1_lull', 'k1_ask'), // 左=哄睡(错过线索)/ 右=问她(heardArgument=true) + k1_lull: N(['voice/k1_lull'], { next: 'k1_lullo' }), + k1_lullo: N(['voice/k1_lullo'], { next: 'k1_lulle' }), + k1_lulle: N(['voice/k1_lulle'], { next: '@bridge' }), + k1_ask: N(['voice/k1_ask'], { next: 'k1_asko' }), + k1_asko: N(['voice/k1_asko'], { next: 'p5' }), + p5: P('voice/p5', 'k1_truth', 'k1_lie'), // 仅 p4=右 可达 + k1_truth: N(['voice/k1_truth'], { next: 'k1_trutho' }), + k1_trutho: N(['voice/k1_trutho'], { next: 'k1_truthe' }), + k1_truthe: N(['voice/k1_truthe'], { next: '@bridge' }), + k1_lie: N(['voice/k1_lie'], { next: 'k1_lieo' }), + k1_lieo: N(['voice/k1_lieo'], { next: 'k1_liee' }), + k1_liee: N(['voice/k1_liee'], { next: '@bridge' }), + + // ---- C4 陈姐·争吵的夫妻(2 抉择 · 消费 heardArgument) + w1_in: N(['voice/w1_in'], { sfx: 'sfx/line_connect', amb: 'sfx/amb_street_rain', next: 'w1_a' }), + w1_a: N(['voice/w1_a'], { next: 'w1_b' }), + w1_b: N(['voice/w1_b'], { next: 'p6' }), + p6: P('voice/p6', 'w1_say', 'w1_home'), + // w1_say 节点内选播(唯一跨来电内容差分之一,不改树形):heardArgument + // 决定播 w1_say 组还是 w1_say2 组,两条件路线共用同一节点 id,汇于 p7。 + w1_say: N([], { dyn: 'w1say', next: 'p7' }), + w1_home: N(['voice/w1_home'], { next: 'w1_homeo' }), + w1_homeo: N(['voice/w1_homeo'], { next: 'w1_homee' }), + w1_homee: N(['voice/w1_homee'], { next: '@bridge' }), // 右路本通结束,无 p7 + p7: P('voice/p7', 'w1_air', 'w1_priv'), // 仅 p6=左 可达 + w1_air: N(['voice/w1_air'], { next: 'w1_airo' }), + w1_airo: N(['voice/w1_airo'], { next: 'w1_aire' }), + w1_aire: N(['voice/w1_aire'], { next: '@bridge' }), + w1_priv: N(['voice/w1_priv'], { next: 'w1_privq' }), + w1_privq: N(['voice/w1_privq'], { next: 'w1_prive' }), + w1_prive: N(['voice/w1_prive'], { next: '@bridge' }), + + // ---- C5 老韩·雨夜司机(高潮 · 危机拍 + bond_zhou 回响) + h1_in: N(['voice/h1_in'], { sfx: 'sfx/line_connect', amb: 'sfx/amb_cab', next: 'h1_a' }), + h1_a: N(['voice/h1_a'], { next: 'h1_b' }), + h1_b: N(['voice/h1_b'], { next: 'p8' }), + p8: P('voice/p8', 'h1_talk', 'h1_stop'), + h1_talk: N(['voice/h1_talk'], { next: 'h1_talko' }), + // 危机拍整段自动流(戏剧事件不等待单击):talko → danger(前置 2s 预警) → dango → talke → 幕间 + h1_talko: N(['voice/h1_talko'], { auto: true, next: 'h1_danger' }), + h1_danger: N(['voice/h1_danger'], { + danger: true, preMs: 2000, sfx: 'sfx/brake_screech', auto: true, next: 'h1_dango', + }), + h1_dango: N(['voice/h1_dango'], { auto: true, next: 'h1_talke' }), + h1_talke: N(['voice/h1_talke'], { next: '@h1end' }), + h1_stop: N(['voice/h1_stop'], { next: 'h1_stopo' }), + h1_stopo: N(['voice/h1_stopo'], { next: 'h1_stope' }), + h1_stope: N(['voice/h1_stope'], { next: '@h1end' }), + // 回响插播判断:bond_zhou=1 补播 h1_echo(C1→C5 回响),否则直接幕间 + h1_end: N([], { dyn: 'h1echo', next: '@bridge' }), + + // ---- 签收(SIGNOFF 属 ONAIR,不单设状态)+ ◆p9 心结抉择 + g9_in: N(['voice/g9_in'], { auto: true, next: 'p9' }), // 垫乐起,自动流入签收抉择 + p9: P('voice/p9', 'd9_face', 'd9_avoid'), + d9_face: N(['voice/d9_face'], { sfx: 'sfx/tape_static', auto: true, next: 'd9_facee' }), + d9_facee: N(['voice/d9_facee'], { next: '@ending' }), + d9_avoid: N(['voice/d9_avoid'], { auto: true, next: 'd9_avoide' }), + d9_avoide: N(['voice/d9_avoide'], { next: '@ending' }), +}; + +// ---------------------------------------------------------------- 常量表 + +const CALLS = [ + { key: 'zhou', label: '走丢的老人', entry: 'z1_ring_in' }, + { key: 'deer', label: '退票的年轻人', entry: 'l1_in' }, + { key: 'kid', label: '失眠的孩子', entry: 'k1_in' }, + { key: 'wife', label: '争吵的夫妻', entry: 'w1_in' }, + { key: 'han', label: '雨夜司机', entry: 'h1_in' }, +]; +const CALL_OF_NODE = { z: 1, l: 2, k: 3, w: 4, h: 5, g: 5, d: 5 }; // 节点前缀 → 通号 + +/** 上滑状态链的「第几通」分档整句(§4.3 数字规避:禁止运行时拼接)。 */ +const PROG_VOICES = [ + 'voice/prog_c1', 'voice/prog_c2', 'voice/prog_c3', 'voice/prog_c4', 'voice/prog_c5', +]; +/** 口碑分档整句(BRIDGE 跨档被动播报 + 上滑状态链复用;rep_low 附 warning 震)。 */ +const TIER_VOICES = { high: 'voice/rep_high', mid: 'voice/rep_mid', low: 'voice/rep_low' }; + +const MENU_ITEMS = [ + { id: 'resume', voice: 'voice/sys_menu_resume' }, // 回到直播 + { id: 'restart', voice: 'voice/sys_menu_restart' }, // 重新开始今晚 + { id: 'lobby', voice: 'voice/sys_menu_lobby' }, // 保存并回大厅 +]; + +/** 4 结局(§3.4 矩阵:rep≥5 高/低口碑 × p9 直面/未解)。 */ +const ENDINGS = { + e1: { name: '天亮之前', voices: ['voice/e1_a', 'voice/e1_b'] }, + e2: { name: '热线常亮', voices: ['voice/e2_a', 'voice/e2_b'] }, + e3: { name: '一个人的频道', voices: ['voice/e3_a', 'voice/e3_b'] }, + e4: { name: '静音', voices: ['voice/e4_a', 'voice/e4_b'] }, +}; +/** 尾声回响:5 个来电者各播 bond ? 救到 : 错过(P2 回响感)。 */ +const EP_VOICES = { + zhou: ['voice/ep_zhou_ok', 'voice/ep_zhou_no'], + deer: ['voice/ep_deer_ok', 'voice/ep_deer_no'], + kid: ['voice/ep_kid_ok', 'voice/ep_kid_no'], + wife: ['voice/ep_wife_ok', 'voice/ep_wife_no'], + han: ['voice/ep_han_ok', 'voice/ep_han_no'], +}; + +const ALL_SFX = [ + 'sfx/onair_sting', 'sfx/phone_ring', 'sfx/line_connect', 'sfx/line_drop', + 'sfx/amb_rain_city', 'sfx/amb_station', 'sfx/amb_kid_room', 'sfx/arg_muffled', + 'sfx/amb_street_rain', 'sfx/amb_cab', 'sfx/brake_screech', 'sfx/tape_static', + 'sfx/ui_confirm', 'sfx/ui_menu', +]; + +const SAVE_KEY = 'soundgame.progress.radio'; +const AUTH_KEY = 'soundgame.auth'; + +/** 抉择提示音 id(voice/p1~p9):播放中单击不可跳(盲玩公平,12 号 §1.2)。 */ +const PROMPT_RE = /^voice\/p\d/; + +/** 单句语音看门狗:超时且引擎确认 voice 未在播 = 资产缺失才续链(storyline N1 先例)。 */ +const VOICE_WATCHDOG_MS = 30000; + +// 向导层看门狗(§7.2;full 档 say 由 guide 播,standard 档 onEscalate 游戏层补播, +// off 档整体不挂——与 storyline F1.1 / P2-3 同款双档节奏)。 +const GUIDE_WATCHDOG_AT = [25000, 50000]; // full:叙事链末 / ENDING 结算等待 +const IDLE_HINT_AT = [13000, 30000]; // standard:同 storyline P2-3 先例 +const CHOICE_HINT_AT = [25000, 50000]; // 抉择点:两级都重播当前 p* 提示句本身 +const RING_WATCHDOG_AT = [10000, 25000]; // RING 两级**不同句**(gd_ring1 ≠ gd_ring2) + +// 铃声节奏(双音一响 + 静默)烘进 sfx/phone_ring 文件本身(5.2s ≥4s 一循环, +// 无缝 loop,见 gen/gen_sfx.py);游戏侧只持一个循环句柄,不再定时重触发。 +const BRIDGE_REST_MS = 3000; // 幕间垫乐喘息(自动链 ≤12s:rep 整句 + 3s 垫乐) +const DANGER_LEAD_MS = 2000; // 危机拍前置预警窗(brake 渐强 + danger 签名同窗) + +/** 口碑档位(§4.3):≥5 高 / 1-4 中 / ≤0 低。 */ +function tierOf(rep) { + if (rep >= 5) return 'high'; + if (rep >= 1) return 'mid'; + return 'low'; +} + +export class RadioGame extends Game { + constructor(audio, root) { + super(audio, root); + this.guide = new Guide(audio); // 全局向导层(guide='off' 时全部静默 no-op) + + this.phase = 'INTRO'; // INTRO|RING|ONAIR|BRIDGE|ENDING|MENU|ASK + this.returnPhase = null; // MENU 关闭后回原状态 + this.returnNode = null; + + this.nodeId = 'b_open'; + this.callN = 0; // 当前通号(0=开场未接入) + this.endingId = null; + + this.voicePlaying = false; + this.currentVoiceId = null; // 链上当前句 id + this.promptPlaying = false; // 当前句是抉择提示 p* + this.choiceArmed = false; + this.pendingChoice = null; + this.awaitingRestart = false; + this.leaving = false; // 菜单「保存并回大厅」已确认(跳转大厅前 true) + this.seqToken = 0; + + // 跨通话变量(§4.1) + this.rep = 0; + this.knot = false; // 心结,仅 p9=左 置 true + this.heardArgument = false; // p4=右 置 true,C4 消费 + this.bond = { zhou: 0, deer: 0, kid: 0, wife: 0, han: 0 }; + this.endings = {}; // 结局收集(跨夜保留) + this.nights = 0; + this.p1Choice = null; // p1 选择(p2 计分 / @transfer 解析用) + + this.lastAnnouncedTier = 'low'; // BRIDGE 跨档播报基准(开局 rep=0 → low) + this.lastCloser = null; // 上一通话收尾句(RING 双击重听用) + this.sysSkipShown = false; // sys_skip 开场每实例一次(storyline skip_hint 先例) + this.saved = null; // ASK 态暂存的存档 + this._menuQueue = []; // 菜单项报名队列(游戏侧排队,不截断当前报名) + + // F4 断点续玩跳句锁:_resume 置位,恢复链首句起播时落为该句 id,播完即清 + //(首句播完才武装跳句——ASK「单击=确认」与「单击=跳句」的语义陷阱,R2 F4) + this._resumeGuard = false; + this._skipLockId = null; + + // F3 铃声循环句柄(sfx/phone_ring 无缝 loop;引擎 sfx 层无 loop/stop, + // 游戏侧经 ctx 起源并持句柄——guide earcon 同款直用引擎总线,接听/菜单即停) + this._ringSrc = null; + + // 计时器句柄(菜单/重开时统一清理) + this.ambTimer = null; + this.ambId = null; + this.restTimer = null; + + /** 环境音循环开关:通话侧 amb 由游戏层 onEnded 重触发(escape amb 先例); + * 无头测试/转写置 false(重触发定时器会让虚拟时钟永不空闲)。 */ + this.ambLoopEnabled = true; + } + + // ---------------------------------------------------------------- Game + + start() { + this.audio.play('bgm/onair'); // 全夜班不切(bgm=0 时引擎只记意图,重开自动补播) + this.audio.prewarm(ALL_SFX); // 预解码短音效(等价 Android SoundPool 音量 0 预热) + const local = this._loadLocal(); + if (local && local.phase && local.node) { + this._askContinue(local); + return; + } + // 无本地存档:登录态下发服务器存档(1.5s 内未返回/无存档 → 直接开新夜班) + let auth = null; + try { auth = JSON.parse(localStorage.getItem(AUTH_KEY) || 'null'); } catch (_) { /* 静默 */ } + if (!(auth && auth.token)) { this.newNight(); return; } + let settled = false; + const goFresh = () => { if (settled) return; settled = true; this.newNight(); }; + fetch('/api/progress?game=radio', { headers: { Authorization: `Bearer ${auth.token}` } }) + .then((r) => (r.ok ? r.json() : null)) + .then((j) => { + if (settled) return; + settled = true; + const d = j && j.data; + if (d && d.phase && d.node) this._askContinue(d); + else goFresh(); + }) + .catch(goFresh); + setTimeout(goFresh, 1500); + } + + stop() { + this.seqToken++; // 作废一切在途链回调:stop 后(页面卸载/测试拆除)中断链 + this._stopRing(); // 不得经 30s 语音看门狗"资产缺失"路径复活(复活会走到 + this._stopAmb(); // enterRing 留下无人接听的永久铃声循环) + this._resumeGuard = false; // F4 跳句锁随实例拆除 + this._skipLockId = null; + if (this.restTimer) { clearTimeout(this.restTimer); this.restTimer = null; } + this.audio.stopAll(); + } + + onGesture(g) { + this.guide.disarm(); // 任何手势复位看门狗(guide=off 时 no-op) + switch (this.phase) { + case 'ASK': this._onAskGesture(g); break; + case 'MENU': this._onMenuGesture(g); break; + case 'RING': this._onRingGesture(g); break; + case 'ONAIR': case 'INTRO': this._onStoryGesture(g); break; + case 'BRIDGE': this._onBridgeGesture(g); break; + case 'ENDING': this._onEndingGesture(g); break; + default: break; + } + } + + // ---------------------------------------------------------------- 手势:ASK + + _onAskGesture(g) { + const go = g === 'tap' || g === 'right'; // 推进手势:局面即将改变,不重挂 + if (g === 'tap') this._resume(this.saved); + else if (g === 'right') this.newNight(); + else if (g === 'longpress' || g === 'longpressend' || g === 'tapcancel') { /* 引擎快进接管 */ } + else this.guide.error({ voice: 'gd_invalid', onceKey: 'radio_invalid' }); + // F1:非推进手势已被 onGesture 的 guide.disarm() 拆掉 ASK 空闲看门狗, + // 处理完立即重挂——否则询问态从此无限静默(live t=445.4 同款断音) + if (!go && this.phase === 'ASK' && !this.voicePlaying) this._armIdleWatchdog('ASK'); + } + + // ---------------------------------------------------------------- 手势:RING + + _onRingGesture(g) { + switch (g) { + case 'tap': + this._stopRing(); // F3:铃声循环垫接听即停 + this.phase = 'ONAIR'; + this.playNode(CALLS[this.callN - 1] ? CALLS[this.callN - 1].entry : 'z1_ring_in'); + break; + case 'doubletap': + // 重听上一通话的收尾句(首通前无上一通 → 无效手势零静默; + // INTRO 链不走此路径——收尾句只来自通话节点) + if (this.callN <= 1 || !this.lastCloser) { + this.guide.error({ voice: 'gd_invalid', onceKey: 'radio_invalid' }); + this._rearmRingWatchdog(); // F1:重听请求无效也不拆铃看门狗 + return; + } + this.seqToken++; + this.audio.stopVoice(); + this._playSeq([this.lastCloser], () => { + this.voicePlaying = false; + if (this.phase === 'RING') this._armRingWatchdog(); + }); + break; + case 'up': { + // 状态播报(第几通 + 口碑档),留在 RING + this.seqToken++; + this.audio.stopVoice(); + const ids = [PROG_VOICES[Math.max(0, this.callN - 1)], TIER_VOICES[tierOf(this.rep)]]; + this._playSeq(ids, () => { + this.voicePlaying = false; + if (this.phase === 'RING') this._armRingWatchdog(); + }); + break; + } + case 'down': + this.openMenu(); + break; + case 'left': + case 'right': + // 无效手势零静默(§2.3 RING 行:左右滑 → error 震 + gd_invalid) + this.guide.error({ voice: 'gd_invalid', onceKey: 'radio_invalid' }); + this._rearmRingWatchdog(); // F1:无效手势不拆铃看门狗(拆后不补=静默死局) + break; + default: + this._rearmRingWatchdog(); // F1:快进等引擎接管手势同款(空闲态才重挂) + break; // longpress/longpressend/tapcancel:引擎快进接管,游戏不处理 + } + } + + /** F1:RING 空闲(无对白在播)时重挂两级铃看门狗;重听/播报在播时不抢挂。 */ + _rearmRingWatchdog() { + if (this.phase === 'RING' && !this.audio.isVoicePlaying()) this._armRingWatchdog(); + } + + _callEntry() { + const call = CALLS[this.callN - 1]; + return call && NODES[call.entry] ? call.entry : 'z1_ring_in'; + } + + // ---------------------------------------------------------------- 手势:ONAIR / INTRO + + _onStoryGesture(g) { + const node = NODES[this.nodeId]; + if (!node) return; + switch (g) { + case 'tap': + if (this.voicePlaying) { + // 旁白播放中单击 = 跳过本句(链继续);不可跳的三类: + // ① 抉择提示 p*(PROMPT_RE 门,盲玩公平); + // ② F5 链末句——等待/过场/抉择交接前的末句(抢跑吃钩子:蛋糕句/点题句); + // ③ F4 续玩锁——断点恢复后的首句播完才武装跳句。 + if (!this.promptPlaying && !this._isTailSentence() + && this.currentVoiceId !== this._skipLockId) { + this.audio.skipVoice(); + } + } else if (node.prompt != null && this.choiceArmed) { + this.armChoiceHint(); // 抉择等待态空点 = 重新挂载兜底(25s 计时重置) + } else if (node.next != null) { + this.advance(node.next); + } + break; + case 'doubletap': + this.replayNode(); + break; + case 'left': + case 'right': { + if (node.prompt == null) { + // 无效手势零静默(§2.3):error 震 + full 档 gd_invalid(onceKey 同局一次); + // F1:onGesture 入口的 disarm 已拆链末看门狗,处理完立即重挂 + //(live 局 B t=445.4:无效左滑后 34.2s 零事件,「还在吗」永不来的根因) + this.guide.error({ voice: 'gd_invalid', onceKey: 'radio_invalid' }); + if (this._inStoryWait()) this._armIdleWatchdog(this.phase); + return; + } + if (this.choiceArmed) this.choose(g); + else if (this.voicePlaying) this.pendingChoice = g; // p* 播放中缓存(盲玩公平) + break; + } + case 'up': + this._playHelpAndResume(); + break; + case 'down': + this.openMenu(); + break; + default: + // longpress/longpressend/tapcancel:引擎快进接管,游戏不处理; + // F1 同款:链末等待态下它们同样拆掉了看门狗,立即重挂 + if (this._inStoryWait()) this._armIdleWatchdog(this.phase); + break; + } + } + + /** 当前是否处于 ONAIR/INTRO「链末等待单击」态(F1 重挂判据:与 _onNodeSeqDone 的 + * 等待分支同口径——非抉择节点、有后继、非自动流、非过场转移、且无对白在播)。 */ + _inStoryWait() { + const node = NODES[this.nodeId]; + return !!(node && !this.voicePlaying && node.prompt == null && node.next != null + && !node.auto && !this._isTransition(node.next)); + } + + /** + * F5 链末句保护:当前句是本节点末句、且节点把控制权交出(链末等待单击 / + * @x 过场转移 / 直入抉择提示)时,屏蔽「跳过本句」——单击身兼跳句与继续, + * 末句被跳过则关键尾句(k1_trutho 的蛋糕句、d9_*e 的点题句)被抢跑吃掉, + * 下一句应答在没播出的钩子上(live 局 A 三次吃尾的根因)。 + * 自动链中段(b_open/b_rain、危机拍)不受保护,跳句自由保留。 + */ + _isTailSentence() { + const node = NODES[this.nodeId]; + if (!node || !this.voicePlaying || this.promptPlaying) return false; + const seq = this._nodeVoices(this.nodeId); + const tail = node.prompt ? node.prompt : (seq.length ? seq[seq.length - 1] : null); + if (tail == null || this.currentVoiceId !== tail) return false; // 仅护末句 + if (!node.auto) return true; // 链末等待型节点(本作全部通话叙事句) + if (node.next == null || this._isTransition(node.next)) return true; // 自动链终点 + const nx = NODES[node.next]; + return !!(nx && nx.prompt != null); // 自动链直入抉择提示(g9_in→p9) + } + + // ---------------------------------------------------------------- 手势:BRIDGE + + _onBridgeGesture(g) { + switch (g) { + case 'tap': + if (this.voicePlaying) this.audio.skipVoice(); // 跳过本句,自动链继续 + break; + case 'doubletap': + this.seqToken++; + this.audio.stopVoice(); + this._bridgeChain(this._bridgeAnnounce ? [this._bridgeAnnounce] : []); + break; + case 'up': { + // 帮助 + 状态,自动链继续(垫乐喘息不被打断——链尾照常进下一通) + this.seqToken++; + this.audio.stopVoice(); + const ids = ['voice/sys_help']; + if (this.callN < CALLS.length) ids.push(PROG_VOICES[this.callN]); // 下一通 + ids.push(TIER_VOICES[tierOf(this.rep)]); + this._bridgeChain(ids); + break; + } + case 'down': + this.openMenu(); + break; + case 'left': + case 'right': + this.guide.error({ voice: 'gd_invalid', onceKey: 'radio_invalid' }); // §2.3 BRIDGE 行 + break; + default: + break; // longpress/longpressend/tapcancel:引擎快进接管,游戏不处理 + } + } + + // ---------------------------------------------------------------- 手势:ENDING + + _onEndingGesture(g) { + switch (g) { + case 'tap': + if (this.awaitingRestart) this.newNight(); + else if (this.voicePlaying && this.currentVoiceId !== this._skipLockId) { + this.audio.skipVoice(); // F4:续玩恢复的首句播完才武装跳句 + } + break; + case 'doubletap': + // 重听结局链(结算态/播放中皆可;重播期间清结算态,播完回到结算态) + this.awaitingRestart = false; + this.seqToken++; + this.audio.stopVoice(); + this._playEndingChain(); + break; + case 'up': { + // 帮助 + 状态(结局主句=结局名播报 + 口碑档整句,§2.3「结局名+口碑」; + // storyline P3-2 先例),播完回结算态 + this.seqToken++; + this.audio.stopVoice(); + const def = ENDINGS[this.endingId] || ENDINGS.e1; + this._playSeq(['voice/sys_help', def.voices[0], TIER_VOICES[tierOf(this.rep)]], () => { + this.voicePlaying = false; + if (this.phase === 'ENDING') { + this.awaitingRestart = true; + this._armIdleWatchdog('ENDING'); + } + }); + break; + } + case 'down': + this.openMenu(); + break; + case 'left': + case 'right': + this.guide.error({ voice: 'gd_invalid', onceKey: 'radio_invalid' }); // §2.3 ENDING 行 + // F1:结算等待态的看门狗被 disarm 拆掉后立即重挂(对白在播时本就无看门狗) + if (this.awaitingRestart && !this.voicePlaying) this._armIdleWatchdog('ENDING'); + break; + default: + // F1 同款:快进等引擎接管手势不拆结算等待的看门狗 + if (this.awaitingRestart && !this.voicePlaying) this._armIdleWatchdog('ENDING'); + break; // longpress/longpressend/tapcancel:引擎快进接管,游戏不处理 + } + } + + // ---------------------------------------------------------------- 手势:MENU(§5.2 模态约定) + + _onMenuGesture(g) { + switch (g) { + case 'tap': { + // 单击循环切换:报名经游戏侧队列**接在当前报名之后**(不截断)。 + // 不用引擎 queue:true——引擎在 onComplete 里先起链播下一句、后 shift + // 队列,排队句会把链播下一句顶掉(sys_menu→resume 链被吞的根因)。 + this.menuIdx = (this.menuIdx + 1) % MENU_ITEMS.length; + this._menuQueue.push(MENU_ITEMS[this.menuIdx].voice); + if (!this.voicePlaying) this._drainMenuQueue(); + break; + } + case 'doubletap': + this._execMenuItem(MENU_ITEMS[this.menuIdx].id); // 双击确认执行 + break; + case 'up': { + // 帮助 + 菜单内设置提示 + 复述当前项(清空报名队列:帮助链顶掉一切) + this._menuQueue = []; + this.seqToken++; + this.audio.stopVoice(); + this._playSeq(['voice/sys_help', 'voice/sys_settings', MENU_ITEMS[this.menuIdx].voice], () => { + this.voicePlaying = false; + }); + break; + } + case 'down': + this.closeMenu(); // 关闭回原状态(重播当前节点 = 局面复述) + break; + case 'left': + case 'right': + this.guide.error({}); // 菜单内左右滑无效:error 震(§2.3 MENU 行,无语音) + break; + default: + break; // longpress/longpressend/tapcancel:引擎快进接管(快进菜单语音) + } + } + + _execMenuItem(id) { + this._menuQueue = []; // 确认执行:待播报名作废 + if (id === 'resume') { + this.closeMenu(); + } else if (id === 'restart') { + this.seqToken++; + this.audio.stopVoice(); + this.newNight(); + } else if (id === 'lobby') { + // 保存并回大厅:确认句播出且存档落盘后跳转(§2.2「确认后跳转前播」) + this.leaving = true; + this.seqToken++; + this.audio.stopVoice(); + this._save(); + this._playSeq(['voice/sys_menu_lobby'], () => { + this.voicePlaying = false; + try { + if (typeof window !== 'undefined' && window.location) window.location.href = '../'; + } catch (_) { /* 无头环境无 location:留档 leaving 标记 */ } + }); + } + } + + // ---------------------------------------------------------------- 菜单开/关 + + openMenu() { + this.returnPhase = this.phase; + this.returnNode = this.nodeId; + this.phase = 'MENU'; + this.menuIdx = 0; + this._menuQueue = []; + this.seqToken++; + this.audio.stopVoice(); + this._stopRing(); + this._cancelRest(); + this.audio.play('sfx/ui_menu'); + this.audio.play('bgm/menu', { fadeMs: 500 }); // 菜单 pad crossfade 500ms + this.guide.state('menu'); + this._playSeq(['voice/sys_menu', MENU_ITEMS[0].voice], () => { + this.voicePlaying = false; + this._drainMenuQueue(); + }); + } + + /** 菜单报名队列排水:链播完接播玩家循环切换过的项(游戏侧排队,零截断)。 */ + _drainMenuQueue() { + if (this.phase !== 'MENU' || this._menuQueue.length === 0) return; + const ids = this._menuQueue.splice(0); + this._playSeq(ids, () => { + this.voicePlaying = false; + this._drainMenuQueue(); + }); + } + + closeMenu() { + this.seqToken++; + this._menuQueue = []; // 未播完的报名作废(局面复述接管) + this.audio.stopVoice(); + const back = this.returnPhase || 'INTRO'; + this.phase = back; + this.audio.play('sfx/ui_confirm'); + if (back === 'BRIDGE') this.audio.play('bgm/bridge', { fadeMs: 800 }); + else if (back === 'ENDING') this.audio.play('bgm/ending', { fadeMs: 800 }); + else this.audio.play('bgm/onair', { fadeMs: 800 }); // 关菜单淡回原状态曲 + this.guide.state(back === 'BRIDGE' || back === 'ENDING' ? 'settle' : 'explore'); + if (back === 'RING') { + this._startRing(); + this._armRingWatchdog(); + } else if (back === 'BRIDGE') { + this._bridgeChain([]); // 幕间续走(口碑句已播过不重播,账目真实) + } else if (back === 'ENDING') { + this._playEndingChain(); // 重播结局链(局面复述) + } else if (back === 'ASK') { + this._playSeq(['voice/sys_continue_ask'], () => { + this.voicePlaying = false; + this._armIdleWatchdog('ASK'); + }); + } else { + this.playNode(this.returnNode); // INTRO/ONAIR:重播当前节点 + } + } + + // ---------------------------------------------------------------- 夜班流转 + + /** 新夜班:重置跨通话标量(结局收集跨夜保留),回 INTRO(sys_new_night 先行)。 */ + newNight() { + this.nights += 1; + this.rep = 0; + this.knot = false; + this.heardArgument = false; + this.bond = { zhou: 0, deer: 0, kid: 0, wife: 0, han: 0 }; + this.p1Choice = null; + this.callN = 0; + this.endingId = null; + this.lastAnnouncedTier = 'low'; + this.lastCloser = null; + this.leaving = false; + this.saved = null; + this.choiceArmed = false; // 清掉上一夜的抉择武装(重启到首链起播前不收滑动手势) + this.pendingChoice = null; + this._resumeGuard = false; // F4 跳句锁不跨夜 + this._skipLockId = null; + this.seqToken++; + this.audio.stopVoice(); + this._stopRing(); + this._stopAmb(); + this._cancelRest(); + this.phase = 'INTRO'; + this.audio.play('bgm/onair'); // 从菜单/结局重开为同首不重启 + this.guide.state('explore'); + this._playSeq(['voice/sys_new_night'], () => this.playNode('b_open')); + } + + /** RING:热线接入。铃声循环垫 + turn cue + 两级不同句看门狗,无超时惩罚。 */ + enterRing(n) { + this.callN = n; + this.phase = 'RING'; + this._stopAmb(); + this.audio.play('bgm/onair', { fadeMs: 600 }); // 进下一通淡回 onair + this.guide.turn({ haptic: true }); // 铃声 + turn 签名 =「轮到你接听」(无语音) + this._save(); // 写入时机:每通电话 RING 进入 + this._startRing(); + this._armRingWatchdog(); + } + + /** + * F3:铃声循环垫起播(sfx/phone_ring,程序合成无缝 loop:双音一响 + 静默烘进 + * 文件,循环边界静音零接缝)。引擎 sfx 层「即发即弃」无 loop/stop——游戏侧经 + * 引擎 ctx/_sfxBus 起循环 BufferSource 并持句柄(guide earcon 同款直用总线), + * 接听/菜单/重开的 _stopRing 即停,不拖尾盖住通话。load 异步:落地前已离开 + * RING(秒接)则不播;资产缺失静默降级(震动 + 看门狗仍在,玩法不受阻)。 + */ + _startRing() { + if (this.phase !== 'RING') return; + this._stopRingSfx(); // 防双循环(closeMenu/resume 重入时旧句柄先清) + const ctx = this.audio.ctx; + if (!ctx) return; + this.audio._load('sfx/phone_ring').then((buf) => { + if (!buf || this.phase !== 'RING' || this._ringSrc) return; + const src = ctx.createBufferSource(); + src.buffer = buf; + src.loop = true; + const gain = ctx.createGain(); + gain.gain.value = this.audio.sfxVolume; + src.connect(gain); + gain.connect(this.audio._sfxBus); + src.start(); + this._ringSrc = { src, gain }; + }).catch(() => { /* 资产缺失降级:RING 仍有 turn 震 + 两级看门狗 */ }); + } + + _stopRingSfx() { + if (this._ringSrc) { + try { this._ringSrc.src.stop(); } catch (_) { /* 已停止 */ } + this._ringSrc = null; + } + } + + _stopRing() { + this._stopRingSfx(); + } + + /** BRIDGE:幕间喘息。口碑跨档被动播报(分档整句账目真实)→ 垫乐喘息 → 下一通。 */ + enterBridge() { + this._stopAmb(); + this.phase = 'BRIDGE'; + this.guide.state('settle'); + this.audio.play('sfx/line_drop'); // 挂断咔哒 + this.audio.play('bgm/bridge', { fadeMs: 800 }); // crossfade 进幕间 + const tier = tierOf(this.rep); + if (tier !== this.lastAnnouncedTier) { + this.lastAnnouncedTier = tier; + this._bridgeAnnounce = TIER_VOICES[tier]; + if (tier === 'low') vibrate('warning'); // rep_low 附 warning 震(G0 组注记) + } else { + this._bridgeAnnounce = null; + } + this._bridgeChain(this._bridgeAnnounce ? [this._bridgeAnnounce] : []); + } + + _bridgeChain(ids) { + this.seqToken++; + this.audio.stopVoice(); + this._playSeq(ids, () => { + this.voicePlaying = false; + this._armRest(); + }); + } + + _armRest() { + if (this.restTimer) clearTimeout(this.restTimer); + const token = this.seqToken; + this.restTimer = setTimeout(() => { + this.restTimer = null; + if (this.phase !== 'BRIDGE' || this.seqToken !== token) return; // 已被菜单/手势打断 + this._advanceBridge(); + }, BRIDGE_REST_MS); + } + + _cancelRest() { + if (this.restTimer) { clearTimeout(this.restTimer); this.restTimer = null; } + } + + _advanceBridge() { + if (this.callN >= CALLS.length) this._enterSignoff(); + else this.enterRing(this.callN + 1); + } + + /** 签收(C5 幕间后):SIGNOFF 属 ONAIR,g9_in 自动流入 ◆p9。 */ + _enterSignoff() { + this.phase = 'ONAIR'; + this.playNode('g9_in'); + } + + /** ENDING:rep×knot 四选一 + 尾声回响链,结算态等待单击重开。 */ + enterEnding(forced) { + const ending = forced + || (this.rep >= 5 ? (this.knot ? 'e1' : 'e2') : (this.knot ? 'e3' : 'e4')); + this.endingId = ending; + this.endings[ending] = true; + this.phase = 'ENDING'; + this.nodeId = ending; + this.awaitingRestart = false; + this._stopAmb(); + this.guide.state('settle'); + this.audio.play('bgm/ending', { fadeMs: 1500 }); // p9 结果链播完后淡入 + this._save(); // 写入时机:ENDING 进入 + this._playEndingChain(); + } + + _playEndingChain() { + const def = ENDINGS[this.endingId] || ENDINGS.e4; + const chain = [...def.voices, 'voice/ep_lead']; + for (const key of Object.keys(EP_VOICES)) { + chain.push(EP_VOICES[key][this.bond[key] ? 0 : 1]); // 救到 / 错过,点名每一通 + } + this._playSeq(chain, () => { + this.voicePlaying = false; + this.awaitingRestart = true; + this._armIdleWatchdog('ENDING'); + }); + } + + // ---------------------------------------------------------------- 存档续玩 + + _askContinue(save) { + this.saved = save; + this.phase = 'ASK'; + this._playSeq(['voice/sys_continue_ask'], () => { + this.voicePlaying = false; + this._armIdleWatchdog('ASK'); + }); + } + + /** 断点续玩:恢复 phase+node 并重播当前节点链(一句话局面复述,L5)。 */ + _resume(s) { + if (!s) { this.newNight(); return; } + this.saved = null; + this.rep = s.rep | 0; + this.knot = !!s.knot; + this.heardArgument = !!s.heardArgument; + this.bond = { zhou: 0, deer: 0, kid: 0, wife: 0, han: 0, ...(s.bond || {}) }; + this.endings = { ...(s.endings || {}) }; + this.nights = s.nights || 1; + this.lastAnnouncedTier = tierOf(this.rep); // 恢复不重播跨档句(账目已播过) + this.lastCloser = s.lastCloser || null; + this.callN = s.call || this._callOfNode(s.node) || 1; + this.leaving = false; + this.seqToken++; + this.audio.stopVoice(); + this._resumeGuard = true; // F4:恢复链首句播完才武装跳句(经 _playSeq 消费) + const phase = s.phase; + if (phase === 'RING') { + this.phase = 'RING'; + this._startRing(); + this._armRingWatchdog(); + } else if (phase === 'BRIDGE') { + this.phase = 'BRIDGE'; + this.audio.play('bgm/bridge', { fadeMs: 800 }); + this.guide.state('settle'); + this._bridgeChain([]); // 续走幕间喘息 + } else if (phase === 'ENDING') { + this.enterEnding(s.ending && ENDINGS[s.ending] ? s.ending : undefined); + } else { + this.phase = 'ONAIR'; + this.guide.state('explore'); + this.playNode(NODES[s.node] ? s.node : this._callEntry()); + } + } + + _callOfNode(id) { + const c = typeof id === 'string' ? id[0] : ''; + return CALL_OF_NODE[c] || 1; + } + + // ---------------------------------------------------------------- 节点链驱动 + + playNode(id) { + const node = NODES[id]; + if (!node) { + console.error('[RadioGame] 未知节点:', id); + return; + } + this.nodeId = id; + this.choiceArmed = false; + this.pendingChoice = null; + this.awaitingRestart = false; + if (node.amb) this._startAmb(node.amb); + if (id === 'p9') this.audio.play('bgm/onair', { fadeMs: 600 }); // 签收回 onair + let seq = this._nodeVoices(id); + // F8 先例:操作口令 gd_brief 前置开场链最前(仅 full 档) + if (id === 'b_open' && this.guide.level() === 'full') seq = ['voice/gd_brief', ...seq]; + // sys_skip:开场每实例一次(storyline skip_hint 先例) + if (id === 'b_open' && !this.sysSkipShown) { + this.sysSkipShown = true; + seq = [...seq, 'voice/sys_skip']; + } + if (node.prompt) seq = [...seq, node.prompt]; + + if (node.danger) { + // 危机拍:前 2s brake 渐强 + danger 签名/震同窗(L2 前置预警),链占用期间单击不推进 + this.guide.state('danger'); + vibrate('danger'); + if (node.sfx) this.audio.play(node.sfx); + this.voicePlaying = true; + this.currentVoiceId = null; + this.promptPlaying = false; + const token = ++this.seqToken; + setTimeout(() => { + if (token !== this.seqToken) return; + this._playSeq(this._nodeVoices(id), () => this._onNodeSeqDone(node, this._nodeVoices(id))); + }, node.preMs || 0); + return; + } + if (node.sfx) this.audio.play(node.sfx); + if (node.sfxEcho) { + // k1_b 的 arg_muffled 第二遍(线索音 2-3 次取确定值 2;节点被顶掉则不播) + setTimeout(() => { + if (this.nodeId === id && this.phase === 'ONAIR') this.audio.play(node.sfxEcho.id); + }, node.sfxEcho.atMs); + } + this._playSeq(seq, () => this._onNodeSeqDone(node, seq)); + } + + /** 节点基础语音链(含节点内条件选播:w1_say 组 / h1_echo 插播)。 */ + _nodeVoices(id) { + const node = NODES[id]; + if (!node) return []; + if (node.dyn === 'w1say') { + return this.heardArgument + ? ['voice/w1_say', 'voice/w1_sayo', 'voice/w1_saye'] + : ['voice/w1_say2', 'voice/w1_say2o', 'voice/w1_say2e']; + } + if (node.dyn === 'h1echo') { + return this.bond.zhou ? ['voice/h1_echo'] : []; + } + return [...node.voices]; + } + + /** 双击重听当前节点台词(含抉择提示);ENDING 态重听结局链。 */ + replayNode() { + this.seqToken++; + this.audio.stopVoice(); + if (this.phase === 'ENDING') { + this.awaitingRestart = false; + this._playEndingChain(); + return; + } + const node = NODES[this.nodeId]; + if (!node) return; + this.choiceArmed = false; + this.pendingChoice = null; + this.awaitingRestart = false; + const seq = this._nodeVoices(this.nodeId); + if (node.prompt) seq.push(node.prompt); + this._playSeq(seq, () => this._onNodeSeqDone(node, seq)); + } + + /** '@x' 虚拟目标 = 过场转移(电台节目流自动衔接,§2.2 转移图「链完→」)。 */ + _isTransition(target) { + return typeof target === 'string' && target.startsWith('@'); + } + + /** + * 节点链自然播完的公共收尾:抉择武装 / 自动过场 / 链末等待。 + * 自动衔接仅限两类(§2.2 转移图 + 本文件头部说明):① 节点表标注 auto 的 + * 电台流段落(INTRO 开场链、C5 危机拍 h1_talko→h1_danger→h1_dango、签收链 + * g9_in、p9 结果链 d9_face/d9_avoid);② 全部过场转移(@ring/@bridge/ + * @h1end/@ending)。其余 ONAIR 通话内叙事节点链末**等待单击**(§2.3「链末= + * 继续」)并挂 gd_continue/gd_again 看门狗(§7.2 叙事链末等待行; + * storyline onNodeSeqDone 同款——只提示不重播,两级用尽彻底静默)。 + */ + _onNodeSeqDone(node, seq) { + this.voicePlaying = false; + if (seq.length > 0) this.lastCloser = seq[seq.length - 1]; + if (node.prompt != null) { + this.choiceArmed = true; // W2 契约:播完当刻即可选,零插播 + if (this.pendingChoice) { + const g = this.pendingChoice; + this.pendingChoice = null; + this.choose(g); // 缓存的抉择立即生效(不丢、不重复) + } else { + this.armChoiceHint(); + } + return; + } + if (node.next == null) return; + if (node.auto || this._isTransition(node.next)) { + this.advance(node.next); + return; + } + this._armIdleWatchdog('ONAIR'); // 链末等待单击(tap=继续,见 _onStoryGesture) + } + + /** 链完转移:'@x' 虚拟目标 = 过场自动衔接;普通节点 = 播下一节点。 */ + advance(target) { + switch (target) { + case '@ring': this.enterRing(this.callN + 1); return; + case '@bridge': this.enterBridge(); return; + case '@h1end': this.playNode('h1_end'); return; + case '@ending': this.enterEnding(); return; + default: this.playNode(this.resolve(target)); + } + } + + /** 抉择目标解析(跨通话状态只有三个标量,无组合节点)。 */ + resolve(target) { + if (target === '@transfer') return this.p1Choice === 'left' ? 'z1_pol' : 'z1_bad'; + return target; + } + + /** 抉择:计分(§4.2)→ 存档 → 进结果节点。 */ + choose(g) { + const node = NODES[this.nodeId]; + if (!node || node.prompt == null) return; + this.choiceArmed = false; + this.audio.play('sfx/ui_confirm'); + this._applyChoice(this.nodeId, g); + this._save(); // 写入时机:每个抉择结算后 + this.advance(g === 'left' ? node.left : node.right); + } + + _applyChoice(nodeId, g) { + const L = g === 'left'; + switch (nodeId) { + case 'p1': + this.p1Choice = g; + if (L) this.rep += 1; // 哼老歌:首个正反馈 + break; + case 'p2': + // 唯一负分组合(p1=右 且 p2=右 → -1 砸),触发 z1_bad 归因组 + if (L) this.rep += this.p1Choice === 'left' ? 2 : 1; + else this.rep += this.p1Choice === 'left' ? 1 : -1; + this.bond.zhou = (!L && this.p1Choice === 'right') ? 0 : 1; // 砸 = 错过 + break; + case 'p3': + this.rep += L ? 2 : 1; // 两个选项都体面 + this.bond.deer = L ? 1 : 0; + break; + case 'p4': + // 左=哄睡(错过线索,+0)/ 右=问她(倾听,+1 且 heardArgument=true)。 + // 注:§4.2 表行「p4 问她/哄睡 | 左 | 右」的选项列序与 §3.2 树形/§9 断言 + // 的滑动绑定相反,以 §3.2 + p4 提示句原文(左哄睡/右问她)为准。 + if (!L) { this.rep += 1; this.heardArgument = true; } + break; + case 'p5': + if (L) { this.rep += 1; this.bond.kid = 1; } // 真话 = 完整接住 + break; + case 'p6': + if (L) { this.rep += this.heardArgument ? 2 : 1; this.bond.wife = 1; } + break; // 右=劝回家:本通结束 +0 + case 'p7': + this.rep += L ? 2 : 1; + break; + case 'p8': + if (L) { this.rep += 1; this.bond.han = 1; } // 陪聊闺女(含危机拍) + break; + case 'p9': + if (L) this.knot = true; // 直面(播出老主播的晚安);不动 rep + break; + default: + break; + } + } + + // ---------------------------------------------------------------- 帮助 / 复述 + + /** 上滑:帮助 + 状态(第几通 + 口碑档整句),随后重播当前节点(局面复述)。 */ + _playHelpAndResume() { + this.seqToken++; + this.audio.stopVoice(); + const ids = ['voice/sys_help']; + if (this.phase === 'ONAIR' && this.callN >= 1) { + ids.push(PROG_VOICES[this.callN - 1]); + ids.push(TIER_VOICES[tierOf(this.rep)]); + } + this._playSeq(ids, () => { + this.voicePlaying = false; + if (this.phase === 'ONAIR' || this.phase === 'INTRO') this.playNode(this.nodeId); + }); + } + + // ---------------------------------------------------------------- 看门狗 + + /** 叙事等待点(ASK / ONAIR 链末 / ENDING 结算):full 25s/50s,standard 13s/30s。 */ + _armIdleWatchdog(phaseName) { + const full = this.guide.level() === 'full'; + this.guide.watchdog({ + arm: true, + at: full ? GUIDE_WATCHDOG_AT : IDLE_HINT_AT, + say: ['gd_continue', 'gd_again'], + onEscalate: (n) => { + // full 档提示句由 guide 的 say 播(guide.test 锁定),不重复播 + if (full) return; + this.audio.play(n === 1 ? 'voice/gd_continue' : 'voice/gd_again', { queue: true }); + }, + cond: () => this.phase === phaseName && !this.voicePlaying, + }); + } + + /** RING 两级新句提示(gd_ring1 ≠ gd_ring2,绝不逐字重播;两级后仅铃声循环)。 */ + _armRingWatchdog() { + const full = this.guide.level() === 'full'; + this.guide.watchdog({ + arm: true, + at: RING_WATCHDOG_AT, // full / standard 同参数;standard 由 onEscalate 游戏层补播 + say: ['gd_ring1', 'gd_ring2'], + onEscalate: (n) => { + if (full) return; + this.audio.play(n === 1 ? 'voice/gd_ring1' : 'voice/gd_ring2', { queue: true }); + }, + cond: () => this.phase === 'RING', + }); + } + + /** 抉择点静默兜底:25s/50s 重播当前 p* 提示句本身(复述选项 = 合法帮助,N3 先例)。 */ + armChoiceHint() { + this.guide.watchdog({ + arm: true, + at: CHOICE_HINT_AT, + onEscalate: () => { + const node = NODES[this.nodeId]; + if (node && node.prompt) this.audio.play(node.prompt, { queue: true }); + }, + // 已抉择 / 离开剧情态 / 新链在播 → 到点静默取消 + cond: () => this.phase === 'ONAIR' && this.choiceArmed && !this.voicePlaying, + }); + } + + // ---------------------------------------------------------------- 语音链(storyline 范式) + + /** + * 顺序播放一组对白,全部自然播完后回调。被打断时回调作废(seqToken 机制)。 + * 单句 30s 看门狗:超时且 isVoicePlaying()===false(资产缺失)才自动续链; + * voice 真在播(长句)则续约等待,不得掐断(storyline N1 先例照抄)。 + */ + _playSeq(ids, onDone) { + const token = ++this.seqToken; + // F4:消费续玩跳句锁——恢复链的第一句起播时落锁,该句播完(i>0 / 链完)或 + // 被任何新链顶掉(token 更替)即解锁;非续玩链一律无锁。 + this._skipLockId = this._resumeGuard ? (ids[0] || null) : null; + this._resumeGuard = false; + this.voicePlaying = true; + const step = (i) => { + if (token !== this.seqToken) return; + if (i >= ids.length) { + this.currentVoiceId = null; + this.promptPlaying = false; + this._skipLockId = null; + onDone(); + return; + } + if (i > 0) this._skipLockId = null; // F4:首句播完,武装跳句 + this.currentVoiceId = ids[i]; + this.promptPlaying = PROMPT_RE.test(ids[i]); + if (this.promptPlaying) this.guide.state('choice'); // 选项句本身即 turn cue(W2) + let advanced = false; + const armWd = () => setTimeout(() => { + if (advanced || token !== this.seqToken) return; + if (this.audio.isVoicePlaying()) { wd = armWd(); return; } // 长句在播:续约不掐 + advanced = true; + console.warn('[RadioGame] 语音看门狗超时(voice 未在播,判定资产缺失),自动续链:', ids[i]); + step(i + 1); + }, VOICE_WATCHDOG_MS); + let wd = armWd(); + this.audio.play(ids[i], { + onComplete: () => { + if (advanced || token !== this.seqToken) return; + advanced = true; + clearTimeout(wd); + step(i + 1); + }, + }); + }; + step(0); + } + + // ---------------------------------------------------------------- 环境音(游戏层循环) + + /** + * 通话侧 amb(信息层:雨夜街道/车站/儿童房/公车站/驾驶室)。 + * sfx 层无 loop 选项 → 按解码时长 onEnded 重触发(escape amb 先例); + * 进 BRIDGE/菜单/重开时 _stopAmb。重触发循环会让无头虚拟时钟永不空闲, + * 测试置 ambLoopEnabled=false(每通仍起播一次,断言可用)。 + */ + async _startAmb(id) { + this._stopAmb(); + this._ambId = id; + this.audio.play(id, { volume: 0.8 }); + if (!this.ambLoopEnabled) return; + // 引擎 _load 是 prewarm 同款解码缓存(含 Promise),取 buffer 时长排重触发 + const buf = await this.audio._load(id); + if (!buf || this._ambId !== id) return; + this.ambTimer = setTimeout(() => { + if (this._ambId === id) this._startAmb(id); + }, Math.max(1200, (buf.duration - 0.4) * 1000)); + } + + _stopAmb() { + this._ambId = null; + if (this.ambTimer) { clearTimeout(this.ambTimer); this.ambTimer = null; } + } + + // ---------------------------------------------------------------- 存档(§8) + + /** data 结构照 §8.1;summary 字符串必带(大厅卡片直接显示)。 */ + _save() { + const k = Object.keys(this.endings).length; + const signed = `${this.rep >= 0 ? '+' : ''}${this.rep}`; + let summary; + if (this.phase === 'ENDING' && this.endingId) { + summary = `已收播·${ENDINGS[this.endingId].name} · 结局${k}/4`; + } else { + const head = this.callN >= 1 + ? `第${this.callN}通·${CALLS[this.callN - 1].label}` + : '深夜开播'; + summary = `${head} · 口碑${signed} · 结局${k}/4`; + } + const data = { + schema: 1, + updatedAt: new Date().toISOString(), + summary, + phase: this.phase, + node: this.nodeId, + call: this.callN, + ending: this.endingId, + rep: this.rep, + knot: this.knot, + heardArgument: this.heardArgument, + bond: { ...this.bond }, + endings: { ...this.endings }, + nights: this.nights, + lastCloser: this.lastCloser, + }; + try { localStorage.setItem(SAVE_KEY, JSON.stringify(data)); } catch (_) { /* 隐私模式 */ } + // 已登录时同步服务器(未登录静默跳过;大厅负责登录,游戏内只读写) + try { + const auth = JSON.parse(localStorage.getItem(AUTH_KEY) || 'null'); + if (!auth || !auth.token) return; + fetch('/api/progress', { + method: 'PUT', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}` }, + body: JSON.stringify({ game: 'radio', data }), + }).catch(() => { /* 离线静默 */ }); + } catch (_) { /* 静默 */ } + } + + _loadLocal() { + try { + const data = JSON.parse(localStorage.getItem(SAVE_KEY)); + if (!data || data.schema !== 1 || !data.phase || !data.node) return null; + return data; + } catch (_) { + return null; + } + } +} + +export default RadioGame; diff --git a/web/radio/gen/gen_bgm.sh b/web/radio/gen/gen_bgm.sh new file mode 100644 index 00000000..48157f3d --- /dev/null +++ b/web/radio/gen/gen_bgm.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# 《不眠频率》BGM 后处理(批次 1):Mureka flac 候选 -> 90s 无缝循环 ogg+m4a。 +# 用法: bash web/radio/gen/gen_bgm.sh (flac 原件在 tools/mureka_out/,不入库) +# +# 工艺(tools/mureka_log.md R4/R6 同款): +# 首个强 onset 起点(silenceremove,-35dB 门限)→ 93s 裁剪 → 首尾 3s acrossfade(tri) +# 无缝循环 → loudnorm(onair/bridge/ending I=-20;menu pad I=-24,10 号文档 §1.2) +# → aresample 44.1kHz → ogg libvorbis q3 + m4a aac 128k,落 web/radio/audio/bgm/。 +# Mureka 任务 id / 选用 choice / 成本见 tools/mureka_log.md「批次 1」节;无节奏关键曲目, +# 不需要 bpm_precise。 +set -euo pipefail +cd /home/xsl/SoundGame + +TMP=tools/bgm_post_wav/radio # 中间 wav(不入库,可由本脚本复现) +DST=web/radio/audio/bgm +mkdir -p "$TMP" "$DST" + +post() { # post + local src="$1" track="$2" lufs="$3" + local tmp="$TMP/$track.wav" + ffmpeg -y -v error -i "$src" -af \ +"silenceremove=start_periods=1:start_threshold=-35dB:start_silence=0.02,atrim=end=93,asetpts=PTS-STARTPTS,asplit[a][b];[b]atrim=start=90,asetpts=PTS-STARTPTS[t];[a][t]acrossfade=d=3:c1=tri:c2=tri,loudnorm=I=${lufs}:TP=-2:LRA=11,aresample=44100" \ + "$tmp" + ffmpeg -y -v error -i "$tmp" -c:a libvorbis -q:a 3 "$DST/$track.ogg" + ffmpeg -y -v error -i "$tmp" -c:a aac -b:a 128k "$DST/$track.m4a" + echo "$track <- $src (I=${lufs})" +} + +# 选用候选 = 更接近 90s 的那支(拿不准原则),详见 mureka_log.md +post tools/mureka_out/radio-onair/158551062544386_0.flac onair -20 +post tools/mureka_out/radio-bridge/158551207313409_0.flac bridge -20 +post tools/mureka_out/radio-menu/158551383277574_0.flac menu -24 +post tools/mureka_out/radio-ending/158551538401281_1.flac ending -20 + +echo "== radio bgm done: $(ls "$DST" | wc -l) files ==" diff --git a/web/radio/gen/gen_cover.py b/web/radio/gen/gen_cover.py new file mode 100644 index 00000000..2028fa1f --- /dev/null +++ b/web/radio/gen/gen_cover.py @@ -0,0 +1,99 @@ +"""web/radio 封面:1080x1920,深夜电台主题——深蓝夜色底、红灯「ON AIR」、 +声波弧线与竖排标题《不眠频率》。可复现脚本(固定 seed,无外部网络依赖)。 + +用法:python3 web/radio/gen/gen_cover.py + (或 .venv-tts/bin/python web/radio/gen/gen_cover.py) +""" +import math +import os +import random + +from PIL import Image, ImageDraw, ImageFilter, ImageFont + +W, H = 1080, 1920 +FONT_CANDIDATES = [ + "/home/xsl/SoundGame/web/rhythm/gen/NotoSansSC-Bold.ttf", + "/home/xsl/SoundGame/app/src/reflex/gen/NotoSansSC-Bold.ttf", +] +OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "cover.png") + + +def load_font(size): + for p in FONT_CANDIDATES: + if os.path.exists(p): + return ImageFont.truetype(p, size) + raise SystemExit("未找到中文字体,请放一份 NotoSansSC-Bold.ttf 到 web/rhythm/gen/") + + +def main(): + rng = random.Random(915) # 固定 seed:可复现 + img = Image.new("RGB", (W, H), (8, 10, 24)) + d = ImageDraw.Draw(img) + + # 背景竖向微渐变:深夜蓝 → 微亮天际(黎明前的电台夜班) + for y in range(H): + t = y / H + d.line([(0, y), (W, y)], + fill=(int(8 + 10 * t), int(10 + 12 * t), int(24 + 26 * t))) + + # 远景声波弧线(电台频率的具象,模糊压暗做背景) + wave = Image.new("RGBA", (W, H), (0, 0, 0, 0)) + wd = ImageDraw.Draw(wave) + palette = [(94, 234, 212), (129, 140, 248), (244, 114, 182)] + for i in range(22): + cx, cy = rng.randint(0, W), rng.randint(0, H) + r = rng.randint(80, 360) + col = palette[i % len(palette)] + wd.ellipse([cx - r, cy - r, cx + r, cy + r], + outline=col + (rng.randint(8, 26),), width=rng.randint(2, 4)) + wave = wave.filter(ImageFilter.GaussianBlur(1.6)) + img.paste(wave, (0, 0), wave) + d = ImageDraw.Draw(img) + + # 中央:红色「ON AIR」指示灯 + 同心圆话筒波纹 + cx, cy = W // 2, 620 + for i, (r, alpha) in enumerate([(300, 46), (224, 78), (152, 118)]): + ring = Image.new("RGBA", (W, H), (0, 0, 0, 0)) + rd = ImageDraw.Draw(ring) + rd.ellipse([cx - r, cy - r, cx + r, cy + r], + outline=(248, 113, 113, alpha), width=8 - i * 2) + img.paste(ring, (0, 0), ring) + d = ImageDraw.Draw(img) + d.ellipse([cx - 64, cy - 64, cx + 64, cy + 64], fill=(220, 38, 38)) + d.ellipse([cx - 30, cy - 30, cx + 30, cy + 30], fill=(252, 165, 165)) + f_lamp = load_font(52) + bbox = d.textbbox((0, 0), "ON AIR", font=f_lamp) + d.text((cx - (bbox[2] - bbox[0]) / 2 - bbox[0], cy - (bbox[3] - bbox[1]) / 2 - bbox[1] - 210), + "ON AIR", font=f_lamp, fill=(248, 113, 113)) + + # 标题(居中)与副题 + f_title = load_font(168) + f_sub = load_font(54) + f_small = load_font(38) + + def ctext(y, s, font, fill, spacing=8): + bbox = d.textbbox((0, 0), s, font=font) + w = bbox[2] - bbox[0] + x = (W - w) / 2 + for ch in s: + d.text((x, y), ch, font=font, fill=fill) + bb = d.textbbox((0, 0), ch, font=font) + x += (bb[2] - bb[0]) + spacing + + ctext(1020, "不眠频率", f_title, (240, 244, 255), spacing=14) + ctext(1260, "深夜电台热线 · 接住五个陌生人的夜晚", f_sub, (148, 163, 184)) + + # 手势图例(极简两行) + hints = ["单击 接听 / 跳句", "左右滑 抉择", "上滑 状态", "下滑 菜单", "双击 重听"] + for i, s in enumerate(hints): + col, row = i % 3, i // 3 + d.text((110 + col * 300, 1420 + row * 76), s, font=f_small, fill=(110, 130, 160)) + + ctext(1700, "午夜零点,热线灯亮。你说什么,由你决定。", f_small, (90, 105, 135)) + + img.save(OUT) + print(OUT, img.size) + + +if __name__ == "__main__": + main() diff --git a/web/radio/gen/gen_sfx.py b/web/radio/gen/gen_sfx.py new file mode 100644 index 00000000..8b753765 --- /dev/null +++ b/web/radio/gen/gen_sfx.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""《不眠频率》SYN 程序合成音效(批次 1 + R2 F3,15-radio.md §5.2 标注「程序合成」的条目)。 + +R2 起共 3 件(其余 11 件为 Stable Audio 3 条目,HF token 未配置,本轮跳过——见批次资产报告): + ui_confirm 菜单/接听确认(同全局先例:storyline gen_sfx.py 的 880->1320Hz 双音) + ui_menu 菜单呼出(同全局先例:330+495Hz 软音) + phone_ring RING 铃声循环垫(R2 F3:440+480Hz 柔和双音一响 1.8s + 静默 3.4s, + 循环周期 5.2s ≥4s 烘进文件本身;无缝 loop 规格:逐声道去直流、 + 首尾**不做淡化**、循环边界静音校验;游戏侧 src.loop=true 循环播放, + 接听即停——SA3 真铃声到位后可整文件替换,接口不变) + +可复现:本脚本入库,一键出 web/radio/audio/sfx/ui_{confirm,menu,phone_ring}.{ogg,m4a}; +中间 wav 在 gen/staging/sfx/(不入库)。规格:ogg libvorbis q4 44.1kHz + m4a aac 128k; +one-shot 真峰值 <= -3dBFS(解码回读法校验,源归一 -4dBFS 留 MDCT 过冲余量)。 + +用法:.venv-tts/bin/python web/radio/gen/gen_sfx.py (纯 CPU,无 GPU 依赖) +""" +import os +import subprocess + +import numpy as np +import soundfile as sf + +SR = 44100 +HERE = os.path.dirname(os.path.abspath(__file__)) +STAGING = os.path.join(HERE, "staging", "sfx") +DST = os.path.join(HERE, "..", "audio", "sfx") +FFMPEG = "/home/xsl/SoundGame/tools/bin/ffmpeg" +PEAK_SRC = 10 ** (-4.0 / 20) # 源归一 -4dBFS(有损编码过冲余量) +PEAK_LIMIT = 10 ** (-3.0 / 20) # 解码回读上限 -3dBFS +os.makedirs(STAGING, exist_ok=True) +os.makedirs(DST, exist_ok=True) + + +def t(d): + return np.arange(int(SR * d)) / SR + + +def env_exp(d, attack, release): + n = int(SR * d) + e = np.ones(n) + na, nr = max(1, int(SR * attack)), max(1, int(SR * release)) + e[:na] = np.linspace(0, 1, na) + e[-nr:] *= np.linspace(1, 0, nr) + return e + + +def save(name, sig): + """立体声存。工艺同 tools/fix_clicks.py:逐声道去直流 → 首尾 8ms cos² 淡入淡出 → 峰值归一 -4dBFS。""" + if sig.ndim == 1: + sig = np.stack([sig, sig], axis=1) + sig = sig - np.mean(sig, axis=0, keepdims=True) + nf = max(1, int(SR * 0.008)) + fade = 0.5 * (1 - np.cos(np.pi * np.arange(nf) / nf)) + sig = sig.astype(np.float64).copy() + sig[:nf] *= fade[:, None] + sig[-nf:] *= fade[::-1][:, None] + sig = sig / (np.max(np.abs(sig)) + 1e-9) * PEAK_SRC + sf.write(os.path.join(STAGING, name + ".wav"), sig.astype(np.float32), SR) + + +def save_loop(name, sig): + """无缝 loop 立体声存(R2 F3):逐声道去直流 → **不做首尾淡化** → 峰值归一 -4dBFS。 + 循环边界必须落在静音(信号首尾各留 ≥0.3s 零块),loop 拼接零接缝零爆点。""" + if sig.ndim == 1: + sig = np.stack([sig, sig], axis=1) + sig = sig - np.mean(sig, axis=0, keepdims=True) + sig = sig.astype(np.float64).copy() + sig = sig / (np.max(np.abs(sig)) + 1e-9) * PEAK_SRC + sf.write(os.path.join(STAGING, name + ".wav"), sig.astype(np.float32), SR) + + +# ---- ui_confirm:菜单/接听确认(全局先例双音;尾部留 50ms 静音给编码器软着陆) ---- +d1, d2 = 0.09, 0.14 +a = np.sin(2 * np.pi * 880 * t(d1)) * env_exp(d1, 0.005, 0.003) +b = np.sin(2 * np.pi * 1320 * t(d2)) * env_exp(d2, 0.005, 0.012) +save("ui_confirm", np.concatenate([a, np.zeros(int(SR * 0.02)), b, np.zeros(int(SR * 0.05))])) + +# ---- ui_menu:菜单呼出(全局先例软音) ---- +d = 0.35 +x = t(d) +save("ui_menu", np.concatenate([ + (np.sin(2 * np.pi * 330 * x) + 0.5 * np.sin(2 * np.pi * 495 * x)) * env_exp(d, 0.02, 0.08), + np.zeros(int(SR * 0.05))])) + +# ---- phone_ring:RING 铃声循环垫(R2 F3)---- +# 440+480Hz 经典回铃双频(柔和纯弦叠加),一响 1.8s + 静默 3.4s = 5.2s 循环周期 +# (≥4s,原 RING_INTERVAL_MS 节奏烘进文件)。无缝 loop:起振 60ms 线性软起音 +# (首样本严格过零)+ 500ms 缓释,循环拼接点两侧均为静音零样本。 +_ding, _rest = 1.8, 3.4 +_xr = t(_ding) +_ring = (np.sin(2 * np.pi * 440.0 * _xr) + np.sin(2 * np.pi * 480.0 * _xr)) * 0.5 +_ring = _ring * env_exp(_ding, 0.06, 0.5) +save_loop("phone_ring", np.concatenate([_ring, np.zeros(int(SR * _rest))])) + + +def encode(name, loop=False): + """staging wav -> ogg q4 + m4a 128k,解码回读校验:逐声道真峰值 <= -3dBFS、|直流| <= 0.001。 + 工艺:源尾部垫 50ms 静音(编码器软着陆,one-shot)+ highpass=f=30(抑制 MDCT/AAC 解码直流再生)。 + loop=True(无缝循环垫)追加边界校验:解码后首/末样本逐声道 |amp| <= -60dBFS—— + 循环拼接点两侧均为零样本,src.loop=true 拼接零爆点(不做首尾淡化,见 save_loop)。""" + src = os.path.join(STAGING, name + ".wav") + for ext, cmd in [("ogg", ["-c:a", "libvorbis", "-q:a", "4"]), + ("m4a", ["-c:a", "aac", "-b:a", "128k"])]: + out = os.path.join(DST, f"{name}.{ext}") + subprocess.run([FFMPEG, "-y", "-v", "error", "-i", src, "-af", "highpass=f=30", + "-ar", "44100", *cmd, out], check=True) + chk = "/tmp/_sfx_qc.wav" + subprocess.run([FFMPEG, "-y", "-v", "error", "-i", out, "-c:a", "pcm_f32le", chk], check=True) + y, sr = sf.read(chk, always_2d=True) + tp = 20 * np.log10(np.max(np.abs(y)) + 1e-9) # 逐声道峰值 + assert tp <= -3.0, f"{name}.{ext}: 真峰值 {tp:.2f}dBFS 超限" + assert abs(float(np.mean(y.mean(axis=1)))) <= 0.001, f"{name}.{ext}: 直流超限" + if loop: + edge = 10 ** (-60.0 / 20) + assert float(np.max(np.abs(y[0, :]))) <= edge, f"{name}.{ext}: 循环头样本非静音" + assert float(np.max(np.abs(y[-1, :]))) <= edge, f"{name}.{ext}: 循环尾样本非静音" + print(f" loop {y.shape[0] / sr:.2f}s 边界静音 ✓") + print(f"[ok] {name}.{ext} peak={tp:.2f}dBFS") + + +for _n in ["ui_confirm", "ui_menu"]: + encode(_n) +encode("phone_ring", loop=True) +print("radio SYN sfx done") diff --git a/web/radio/index.html b/web/radio/index.html new file mode 100644 index 00000000..448fbd58 --- /dev/null +++ b/web/radio/index.html @@ -0,0 +1,35 @@ + + + + + + + 不眠频率 · SoundGame Web + + + + +
+ +
+ + + + diff --git a/web/radio/test/playtest.mjs b/web/radio/test/playtest.mjs new file mode 100644 index 00000000..88aedde9 --- /dev/null +++ b/web/radio/test/playtest.mjs @@ -0,0 +1,1206 @@ +/** + * 《不眠频率》无头试玩仿真测试(docs/design/15-radio.md §9 playtest 断言清单)。 + * + * 方案: + * - mock 浏览器环境:window/localStorage/navigator/fetch/AudioContext + * (decodeAudioData 返回固定 2.0s 时长的假 buffer,语音链靠引擎 endTimer + + * onComplete 推进,虚拟时钟驱动,不真实等待); + * - 使用**真实** engine/audio.js + settings.js + guide.js; + * - 手势直接注入 game.onGesture(与 attachGestures 回调同路径); + * - 虚拟时钟:patch setTimeout/clearTimeout/performance.now; + * - 铃声为无缝循环垫(R2 F3:游戏侧持循环句柄,无重触发定时器),全程可复现; + * - 通话侧 amb 循环关闭(ambLoopEnabled=false,重触发定时器会让虚拟时钟永不空闲)。 + * + * 运行:tools/node/bin/node web/radio/test/playtest.mjs + */ +import fs from 'node:fs'; +import path from 'node:path'; +import url from 'node:url'; + +const GAME_DIR = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..'); +const AUDIO_DIR = path.join(GAME_DIR, 'audio'); +const SAVE_KEY = 'soundgame.progress.radio'; + +// ---------------------------------------------------------------- 断言收集 + +const realLog = console.log.bind(console); +let passCount = 0; +const failures = []; +function assert(cond, msg) { + if (cond) { passCount++; realLog(` ✓ ${msg}`); } + else { failures.push(msg); realLog(` ✗ ${msg}`); } +} + +// console 捕获(引擎/游戏的 warn/error 进日志,测试输出用 realLog) +const consoleErrs = []; +const consoleWarns = []; +console.error = (...a) => { consoleErrs.push(a.join(' ')); }; +console.warn = (...a) => { consoleWarns.push(a.join(' ')); }; +console.log = () => {}; // 游戏逐手势 console.log,静默 + +// ---------------------------------------------------------------- 虚拟时钟 + +let now = 0; // 虚拟毫秒 +let timerSeq = 1; +const timers = new Map(); // id -> { fireAt, cb, args } +const realSetImmediate = setImmediate; + +globalThis.setTimeout = (cb, ms = 0, ...args) => { + const id = timerSeq++; + timers.set(id, { fireAt: now + Math.max(0, Number(ms) || 0), cb, args }); + return id; +}; +globalThis.clearTimeout = (id) => { timers.delete(id); }; +globalThis.setInterval = (cb, ms = 0, ...args) => { + const id = timerSeq++; + const step = (...a) => { + if (!intervals.has(id)) return; + cb(...a); + if (intervals.has(id)) timers.set(id, { fireAt: now + Math.max(0, Number(ms) || 0), cb: step, args }); + }; + timers.set(id, { fireAt: now + Math.max(0, Number(ms) || 0), cb: step, args }); + return id; +}; +const intervals = new Set(); +globalThis.clearInterval = (id) => { intervals.delete(id); timers.delete(id); }; +Object.defineProperty(globalThis, 'performance', { + value: { now: () => now }, configurable: true, +}); + +/** 排空微任务(fetch/decode 的 promise 链)。 */ +async function flush(rounds = 8) { + for (let i = 0; i < rounds; i++) await new Promise((r) => realSetImmediate(r)); +} + +/** + * 推进虚拟时钟:逐个触发到期定时器,直到没有待触发定时器(= 游戏在等手势) + * 或 cond() 满足。返回 'cond' | 'idle'。 + */ +async function advance(cond = null) { + for (let n = 0; n < 50000; n++) { + await flush(2); + if (cond && cond()) return 'cond'; + let nextId = -1; let nextAt = Infinity; + for (const [id, t] of timers) if (t.fireAt < nextAt) { nextAt = t.fireAt; nextId = id; } + if (nextId < 0) return 'idle'; + now = nextAt; + const t = timers.get(nextId); + timers.delete(nextId); + t.cb(...t.args); + } + throw new Error('虚拟时钟定时器超上限(疑似死循环)'); +} + +// ---------------------------------------------------------------- 浏览器 mock + +const winListeners = new Map(); +globalThis.window = { + addEventListener: (type, fn) => { + if (!winListeners.has(type)) winListeners.set(type, []); + winListeners.get(type).push(fn); + }, + AudioContext: null, // 下方赋值 +}; +function fireWindowEvent(type) { + for (const fn of winListeners.get(type) || []) fn(); +} + +const storageData = new Map(); +globalThis.localStorage = { + getItem: (k) => (storageData.has(k) ? storageData.get(k) : null), + setItem: (k, v) => { storageData.set(k, String(v)); }, + removeItem: (k) => { storageData.delete(k); }, +}; + +const vibrateLog = []; +Object.defineProperty(globalThis, 'navigator', { + value: { vibrate: (...a) => { vibrateLog.push({ pattern: a[0], t: now }); return true; } }, + configurable: true, +}); + +class MockParam { + constructor(v = 1) { this.value = v; } + setValueAtTime() {} + linearRampToValueAtTime() {} + cancelScheduledValues() {} +} +class MockGainNode { + constructor() { this.gain = new MockParam(); } + connect() {} + disconnect() {} +} +function fakeBuffer() { + return { + duration: 2.0, // 固定句长 2s(虚拟时钟推进语音链) + numberOfChannels: 1, + sampleRate: 44100, + getChannelData: () => new Float32Array(16), + }; +} +class MockAudioContext { + constructor() { this.state = 'running'; this.currentTime = 0; this.destination = {}; } + createGain() { return new MockGainNode(); } + createDynamicsCompressor() { + const n = new MockGainNode(); + n.threshold = new MockParam(); n.ratio = new MockParam(); + n.attack = new MockParam(); n.release = new MockParam(); + return n; + } + createBufferSource() { + return { buffer: null, loop: false, connect() {}, start() {}, stop() {} }; + } + createScriptProcessor() { return { connect() {}, disconnect() {}, onaudioprocess: null }; } + // 向导层 earcon 运行时合成所需 + createOscillator() { + return { type: 'sine', frequency: new MockParam(440), connect() {}, start() {}, stop() {} }; + } + decodeAudioData() { return Promise.resolve(fakeBuffer()); } + resume() { this.state = 'running'; return Promise.resolve(); } +} +globalThis.window.AudioContext = MockAudioContext; + +// fetch:读真实资产目录;blockedAssets 中的片段强制 404(资产缺失测试用) +const requestedUrls = []; +const blockedAssets = new Set(); +globalThis.fetch = async (u, opts = {}) => { + const s = String(u); + requestedUrls.push(s); + if (s.startsWith('/api/')) { + // 服务器 API:模拟 401(无有效会话)/200 + if ((opts.method || 'GET') === 'PUT') { + return { ok: true, status: 200, json: async () => ({ ok: true, game: 'radio' }) }; + } + return { ok: false, status: 401, json: async () => ({ error: 'unauthorized' }) }; + } + const blocked = [...blockedAssets].some((b) => s.includes(b)); + const p = path.join(GAME_DIR, decodeURIComponent(s)); + if (blocked || !fs.existsSync(p)) { + return { ok: false, status: 404, arrayBuffer: async () => new ArrayBuffer(0) }; + } + const data = fs.readFileSync(p); + return { + ok: true, status: 200, + arrayBuffer: async () => data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength), + }; +}; + +// ---------------------------------------------------------------- 加载引擎与游戏 + +const { WebAudioEngine } = await import('../../engine/audio.js'); +const { settings } = await import('../../engine/settings.js'); +const { RadioGame } = await import('../game.js'); + +function resetSettings(guide) { + settings.set({ guide, voice: 'female', speed: 1.0, bgm: 1.0 }); +} +resetSettings('off'); + +function clearSave() { storageData.delete(SAVE_KEY); } +function saveData() { + const raw = storageData.get(SAVE_KEY); + if (!raw) return null; + try { return JSON.parse(raw); } catch (_) { return null; } +} + +const voicePlays = (playLog, id) => playLog.filter((e) => e.id === id).length; +const urlHits = (frag) => requestedUrls.filter((u) => u.includes(frag)).length; +const lastIndexOf = (playLog, id) => playLog.map((e) => e.id).lastIndexOf(id); +const ERR_PATTERN = JSON.stringify([60, 40, 60]); // error 震型 + +function makeRig() { + const audio = new WebAudioEngine('audio/'); + const playLog = []; + const origPlay = audio.play.bind(audio); + audio.play = (id, opts) => { playLog.push({ id, t: now, opts }); return origPlay(id, opts); }; + const game = new RadioGame(audio, {}); + game.ambLoopEnabled = false; // 无头确定性:通话 amb 只起播一次,不重触发 + return { audio, playLog, game }; +} + +let gestureCount = 0; +async function g(game, gesture) { + gestureCount++; + game.onGesture(gesture); + await flush(); +} + +/** 结束一个测试块:清看门狗/铃声/幕间定时器,避免污染后续块的虚拟时钟。 */ +function teardown(game) { + game.guide.disarm(); + game.stop(); // 内部 seqToken++:被中断链不得经 30s 语音看门狗复活 +} + +/** + * 叙事链推进器:语音在播时等它自然播完(不跳句),链末等待态单击继续 + * (§2.3「链末=继续」),直到 cond() 满足(抉择武装 / RING / 提示句在播等)。 + * 途经 RING(幕间自动流转到下一通)时单击接听;幕间喘息等自动段推进定时器。 + */ +async function tapUntil(game, cond, maxSteps = 80) { + for (let i = 0; i < maxSteps; i++) { + if (cond()) return; + if (game.phase === 'RING') { await g(game, 'tap'); continue; } // 接听 + if (game.voicePlaying) { + await advance(() => cond() || !game.voicePlaying); // 等当前句播完(自动流照走) + continue; + } + if (game.choiceArmed) { + throw new Error(`tapUntil 遇到未应答的抉择(node=${game.nodeId})——调用方需先做选择`); + } + await g(game, 'tap'); // 链末单击 = 继续 + if (!cond() && !game.voicePlaying && game.phase !== 'RING') { + await advance(() => cond() || game.voicePlaying || game.phase === 'RING'); + } + } + throw new Error(`tapUntil 超步限(node=${game.nodeId} phase=${game.phase})`); +} + +// 抉择脚本(按 p1..p9 遭遇顺序;p5 仅 p4=右、p7 仅 p6=左 可达) +const SCRIPT_A = ['left', 'left', 'left', 'right', 'left', 'left', 'left', 'left', 'left']; +const SCRIPT_B = [...SCRIPT_A.slice(0, 8), 'right']; +// §9 剧本 C:p1右 p2左 p3右 p4右 p5右 p6右 p8右 p9左(p6 右 → 无 p7),rep=3 → e3 +const SCRIPT_C = ['right', 'left', 'right', 'right', 'right', 'right', 'right', 'left']; +// §9 剧本 D:p1右 p2右 p3右 p4左 p6右 p8右 p9右(p4 左 → 无 p5;p6 右 → 无 p7),rep=0 → e4 +const SCRIPT_D = ['right', 'right', 'right', 'left', 'right', 'right', 'right']; +// 剧本 E:全左但 p4 左(错过线索)→ heardArgument=false,C4 播 w1_say2 组;rep=9 → e1 +const SCRIPT_E = ['left', 'left', 'left', 'left', 'left', 'left', 'left', 'left']; + +/** 从头开一局并按脚本打完到 ENDING 结算态(链末单击继续、RING 单击接听)。 */ +async function playNight(game, choices) { + let i = 0; let guard = 0; + while (!(game.phase === 'ENDING' && game.awaitingRestart)) { + if (++guard > 400) throw new Error(`playNight 卡住(node=${game.nodeId} phase=${game.phase})`); + if (game.phase === 'RING') { await g(game, 'tap'); continue; } + if (game.phase === 'BRIDGE') { await advance(() => game.phase !== 'BRIDGE'); continue; } + if (game.choiceArmed) { + const c = choices[i++]; + if (!c) throw new Error(`playNight 抉择脚本耗尽(node=${game.nodeId})`); + await g(game, c); + continue; + } + if (!game.voicePlaying) { await g(game, 'tap'); continue; } // 链末=继续 + await advance(() => !game.voicePlaying); + } +} + +// ================================================================ 主路径试玩 +// (§9 关键路径:剧本 A 全程 E1,rep 逐点断言与 §4.2 一致,≤60 手势) + +realLog('== 主路径 · 剧本 A(E1,guide=off)=='); +{ + clearSave(); + resetSettings('off'); + const g0 = gestureCount; + const { audio, playLog, game } = makeRig(); + + // §7 全局:解锁前 play 为 no-op + audio.play('voice/b_open'); + await flush(); + assert(audio._voice === null && consoleWarns.some((w) => w.includes('尚未解锁')), + '启动遮罩点按前无音频播放(play 为 no-op)'); + await audio.unlock(); + playLog.length = 0; // 清掉解锁前 no-op 尝试的计数(无播放,仅探测记录) + + game.start(); + await flush(); + assert(playLog.some((e) => e.id === 'bgm/onair'), '开局播放 bgm/onair(直播间垫乐,全夜班不切)'); + assert(voicePlays(playLog, 'voice/sys_new_night') === 1, '新夜班 sys_new_night 先行'); + assert(game.phase === 'INTRO', '进入宏状态 INTRO'); + + // 1. INTRO 链完整(自动流)→ RING(c1):turn 事件与铃声断言 + await advance(() => game.currentVoiceId === 'voice/b_open'); + assert(game.nodeId === 'b_open' && game.voicePlaying && game.currentVoiceId === 'voice/b_open', + 'sys_new_night 播完接 b_open(voice 层有播放记录)'); + assert(voicePlays(playLog, 'voice/gd_brief') === 0, 'guide=off:开场不插 gd_brief'); + await advance(() => game.phase === 'RING'); + for (const id of ['voice/b_open', 'voice/b_rain', 'voice/b_rule', 'voice/b_goal']) { + assert(voicePlays(playLog, id) === 1, `INTRO 链播出 ${id}`); + } + assert(voicePlays(playLog, 'voice/sys_skip') === 1, '开场 sys_skip 提示播一次(每局一次)'); + assert(game.callN === 1 && game.phase === 'RING', 'INTRO 链完自动进 RING(c1)'); + await flush(); + assert(game._ringSrc !== null, 'F3:RING 铃声循环垫起播(sfx/phone_ring 无缝 loop,游戏侧持句柄)'); + assert(saveData() && saveData().phase === 'RING' && saveData().call === 1, + 'RING 进入即存档(写入时机),phase=RING / call=1'); + // off 档 RING 挂起:零向导提示、零超时惩罚;循环垫无重触发定时器(虚拟时钟可空闲) + assert((await advance()) === 'idle' && game._ringSrc !== null, + 'F3:RING 铃声循环垫持续(挂起态虚拟时钟可空闲 = 零重触发定时器,可复现)'); + assert(voicePlays(playLog, 'voice/gd_ring1') === 0 && voicePlays(playLog, 'voice/gd_ring2') === 0, + 'guide=off:RING 静默挂起零提示(看门狗不挂、零超时惩罚)'); + + // 2. 单击接听 → ONAIR(c1);叙事链末等待单击推进到 p1 + await g(game, 'tap'); + assert(game.phase === 'ONAIR' && game.nodeId === 'z1_ring_in' && game.voicePlaying, + '单击接听 → ONAIR(c1) z1_ring_in(线路引入)'); + assert(game._ringSrc === null, 'F3:单击接听 → 铃声循环垫即停(不拖尾盖住通话)'); + assert(game.rep === 0, '开局 rep=0'); + await tapUntil(game, () => game.promptPlaying, 10); + assert(game.nodeId === 'p1' && game.promptPlaying, 'C1 链末单击推进至 p1 提示句播放中'); + assert(voicePlays(playLog, 'voice/z1_ring_in') === 1 && voicePlays(playLog, 'voice/z1_a') === 1 + && voicePlays(playLog, 'voice/z1_b') === 1, '接听链 z1_ring_in→z1_a→z1_b 各播一次'); + assert(game.choiceArmed === false, '提示播放中 choiceArmed=false(W2:播完即真,零插播)'); + + // p* 不可跳 + pendingChoice 左滑缓存(盲玩公平) + await g(game, 'tap'); + assert(game.currentVoiceId === 'voice/p1' && game.promptPlaying, 'p* 播放中单击不跳句(PROMPT_RE 门)'); + await g(game, 'left'); + assert(game.pendingChoice === 'left', 'p* 播放中左滑缓存 pendingChoice(盲玩公平)'); + await advance(() => game.nodeId === 'z1_song'); + assert(game.pendingChoice === null, '播完 pendingChoice 立即生效并清空'); + assert(game.rep === 1, 'p1 左 rep +1 → 1(与 §4.2 一致)'); + assert(voicePlays(playLog, 'voice/z1_song') === 1, 'z1_song 恰播一次(不丢、不重复)'); + + // p2 左 → rep 3,选择结算后存档 + await tapUntil(game, () => game.choiceArmed, 8); // z1_song→z1_songo→z1_sdone→p2 + await g(game, 'left'); + assert(game.rep === 3, 'p2 左 rep +2 → 3(+1+2=3,与 §9 一致)'); + const sd1 = saveData(); + assert(sd1 && sd1.rep === 3 && sd1.node === 'p2' && sd1.phase === 'ONAIR' && sd1.call === 1, + '抉择结算后存档:node=p2 / rep=3 / phase=ONAIR'); + const keysNeeded = ['schema', 'updatedAt', 'summary', 'phase', 'node', 'rep', 'knot', + 'heardArgument', 'bond', 'endings', 'nights']; + assert(keysNeeded.every((k) => k in sd1), '存档 data 覆盖 §8.1 全部字段(含 summary 字符串)'); + assert(sd1.summary === '第1通·走丢的老人 · 口碑+3 · 结局0/4', `summary 人话格式(实际: ${sd1.summary})`); + + // C1 收敛 z1_ok1 → BRIDGE(rep 跨 low→mid 播报)→ RING(c2) + await tapUntil(game, () => game.phase === 'BRIDGE', 10); + assert(voicePlays(playLog, 'voice/rep_mid') === 1, '口碑跨档(low→mid)→ BRIDGE 被动播报 rep_mid'); + await advance(() => game.phase === 'RING'); + assert(game.callN === 2, 'C1 收敛 z1_ok1 → BRIDGE → RING(c2)'); + + // C2:接听后先测双击重听(节点播完态) + await g(game, 'tap'); + assert(game.phase === 'ONAIR' && game.nodeId === 'l1_in', '接听第二通 → l1_in(C2)'); + await tapUntil(game, () => game.choiceArmed, 8); + const p3Before = voicePlays(playLog, 'voice/p3'); + await g(game, 'doubletap'); + assert(game.nodeId === 'p3' && voicePlays(playLog, 'voice/p3') === p3Before + 1, + 'ONAIR 播完态双击 → 当前节点链同 id 序列重播(p3)'); + await g(game, 'tap'); + assert(game.currentVoiceId === 'voice/p3' && !game.choiceArmed, + '重播期间单击不跳 p* 提示句、nodeId 不变'); + await advance(() => game.choiceArmed); + assert(!game.voicePlaying, '重播完回原等待态(choiceArmed)'); + + // C2 左 → rep 5 + await g(game, 'left'); + assert(game.rep === 5, 'p3 左 rep +2 → 5'); + await tapUntil(game, () => game.phase === 'RING', 10); + assert(game.callN === 3, 'C2 收敛 → BRIDGE → RING(c3)'); + + // C3:arg_muffled 线索音 2 次、p4 右 → heardArgument、p5 左 + await g(game, 'tap'); + assert(game.nodeId === 'k1_in', '接听第三通 → k1_in(C3)'); + await tapUntil(game, () => game.nodeId === 'k1_b' && !game.voicePlaying, 6); + // k1_b 起播 arg_muffled(1 次)+ 6.5s 后第二次(节点内 sfxEcho;链末等待态 nodeId 不变) + await advance(() => voicePlays(playLog, 'sfx/arg_muffled') >= 2); + assert(voicePlays(playLog, 'sfx/arg_muffled') === 2, 'k1_b 起播 arg_muffled 线索音 2 次(信息层)'); + await tapUntil(game, () => game.choiceArmed, 4); // → p4 + await g(game, 'right'); // p4 右 = 问她 + assert(game.heardArgument === true, 'p4 右后 heardArgument === true'); + assert(game.rep === 6, 'p4 右 rep +1 → 6'); + await tapUntil(game, () => game.choiceArmed, 6); // k1_ask→k1_asko→p5 + await g(game, 'left'); // p5 左 = 真话 + assert(game.rep === 7, 'p5 左 rep +1 → 7'); + assert(game.bond.kid === 1, 'p5 左 → bond.kid=1'); + await tapUntil(game, () => game.phase === 'RING', 12); + assert(game.callN === 4, 'C3 收敛 → BRIDGE → RING(c4)'); + + // C4:w1_say 组(heardArgument 消费) + await g(game, 'tap'); + assert(game.nodeId === 'w1_in', '接听第四通 → w1_in(C4)'); + await tapUntil(game, () => game.choiceArmed, 8); + await g(game, 'left'); // p6 左 + assert(game.rep === 9, 'p6 左(heardArgument)rep +2 → 9'); + await tapUntil(game, () => game.choiceArmed, 6); // w1_say 组 → p7 + assert(voicePlays(playLog, 'voice/w1_say') >= 1 && voicePlays(playLog, 'voice/w1_say2') === 0, + 'C4 播 w1_say 组(heardArgument 消费),绝不播 w1_say2'); + await g(game, 'left'); // p7 左 + assert(game.rep === 11, 'p7 左 rep +2 → 11'); + await tapUntil(game, () => game.phase === 'RING', 10); + assert(game.callN === 5, 'C4 收敛 → BRIDGE → RING(c5)'); + + // C5:危机拍 + bond_zhou 回响 + await g(game, 'tap'); + assert(game.nodeId === 'h1_in', '接听第五通 → h1_in(C5)'); + await tapUntil(game, () => game.choiceArmed, 8); + await g(game, 'left'); // p8 左 + assert(game.rep === 12, 'p8 左 rep +1 → 12(理论上限)'); + await tapUntil(game, () => game.choiceArmed, 8); // h1_talk → 危机拍自动流 → 签收 p9 + assert(voicePlays(playLog, 'sfx/brake_screech') >= 1, 'C5 危机拍:brake_screech 前置 2s 预警'); + assert(vibrateLog.some((v) => JSON.stringify(v.pattern) === JSON.stringify([25, 40, 25, 40, 25])), + '危机拍 danger 震型'); + assert(voicePlays(playLog, 'voice/h1_echo') === 1, 'bond_zhou=1 → C5 插播 h1_echo 回响句(C1→C5)'); + assert(voicePlays(playLog, 'voice/h1_talko') === 1 && voicePlays(playLog, 'voice/h1_danger') === 1, + '危机拍整段自动流不等待(h1_talko→h1_danger)'); + + // p9 左 → knot → ENDING(e1) + 尾声回响 + await g(game, 'left'); + assert(game.knot === true, 'p9 左 → knot=true(心结解开)'); + assert(game.rep === 12, 'p9 不动 rep'); + await advance(() => game.phase === 'ENDING' && game.awaitingRestart); + assert(game.endingId === 'e1', 'rep≥5 且直面 → ENDING(e1 天亮之前)'); + for (const id of ['voice/e1_a', 'voice/e1_b', 'voice/ep_lead', 'voice/ep_zhou_ok', + 'voice/ep_deer_ok', 'voice/ep_kid_ok', 'voice/ep_wife_ok', 'voice/ep_han_ok']) { + assert(voicePlays(playLog, id) === 1, `结局链播出 ${id}`); + } + const sd2 = saveData(); + assert(sd2 && sd2.phase === 'ENDING' && sd2.ending === 'e1' && sd2.endings.e1 === true, + 'ENDING 进入即存档,endings 收集 e1'); + assert(sd2.summary === '已收播·天亮之前 · 结局1/4', `已收播 summary(实际: ${sd2.summary})`); + assert(['zhou', 'deer', 'kid', 'wife', 'han'].every((k) => game.bond[k] === 1), + '剧本 A 五位来电者好感全 1'); + + // 归因真话律:剧本 A 全程零归因/补救句(成功不教学) + assert(['voice/z1_bade', 'voice/k1_lulle', 'voice/k1_liee', 'voice/e4_b'] + .every((id) => voicePlays(playLog, id) === 0), '剧本 A 全程无归因/补救句(成功不教学)'); + + // ENDING 双击重听结局链 + const e1aBefore = voicePlays(playLog, 'voice/e1_a'); + await g(game, 'doubletap'); + assert(game.awaitingRestart === false && game.nodeId === 'e1', + '结局链播完双击 → 重听结局链(清结算态、nodeId 不变)'); + await g(game, 'tap'); + assert(game.nodeId === 'e1' && !game.awaitingRestart, '重播期间单击 = 跳本句,不触发重开'); + await advance(() => game.awaitingRestart); + assert(voicePlays(playLog, 'voice/e1_a') === e1aBefore + 1, '结局链同 id 序列重播完成,回结算态'); + + // 结算态单击 → 新夜班 + await g(game, 'tap'); + assert(game.phase === 'INTRO' && game.nights === 2 && game.rep === 0 && !game.knot + && !game.heardArgument, '结算单击 = 新夜班:回 INTRO,rep/knot/heardArgument 归零'); + assert(game.endings.e1 === true, 'endings 结局收集跨夜保留'); + assert(voicePlays(playLog, 'voice/sys_new_night') === 2, 'sys_new_night 随新夜班再播'); + assert(voicePlays(playLog, 'voice/sys_skip') === 1, 'sys_skip 每局只播一次,重开不重播'); + + assert(gestureCount - g0 <= 60, `关键路径手势预算 ≤60(实际 ${gestureCount - g0})`); + teardown(game); +} + +// ================================================================ 四结局矩阵 +// (§9 边界:剧本 B → e2;剧本 C → e3;剧本 D → e4;剧本 E 补 w1_say2 差分; +// 各局 rep 终值与 §4.2 一致) + +realLog('== 四结局矩阵(剧本 B / C / D / E)=='); +{ + // ---- 剧本 B:与 A 同路但 p9 右 → e2 + clearSave(); resetSettings('off'); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await playNight(game, SCRIPT_B); + assert(game.endingId === 'e2' && game.rep === 12 && game.knot === false, + '剧本 B(p9 右)→ e2 热线常亮,rep=12,knot=false'); + assert(voicePlays(playLog, 'voice/e2_a') === 1 && voicePlays(playLog, 'voice/e2_b') === 1, + 'e2 专属结局主句 + 钩子播出'); + teardown(game); + } + + // ---- 剧本 C:p1右 p2左 p3右 p4右 p5右 p6右 p8右 p9左 → e3, rep=3 + clearSave(); resetSettings('off'); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await playNight(game, SCRIPT_C); + assert(game.endingId === 'e3' && game.rep === 3 && game.knot === true, + '剧本 C → e3 一个人的频道,rep=3,knot=true'); + assert(voicePlays(playLog, 'voice/e3_a') === 1 && voicePlays(playLog, 'voice/e3_b') === 1, + 'e3 专属结局主句 + 钩子播出'); + assert(voicePlays(playLog, 'voice/z1_ask') === 1 && voicePlays(playLog, 'voice/z1_adone') === 1, + '剧本 C:p1 右 → 问路牌路径推进(z1_ask→z1_asko→z1_adone)'); + assert(game.heardArgument === true && voicePlays(playLog, 'voice/w1_say') === 0 + && voicePlays(playLog, 'voice/w1_say2') === 0, + '剧本 C:p4 右埋线索但 p6 右本通结束 → 两组带话差分都不播'); + assert(voicePlays(playLog, 'voice/ep_zhou_no') === 0, '剧本 C:老周被救到 → 不播 ep_zhou_no'); + assert(voicePlays(playLog, 'voice/ep_deer_no') === 1, '剧本 C:p3 右错过小鹿 → 尾声播 ep_deer_no'); + teardown(game); + } + + // ---- 剧本 D:p1右 p2右 p3右 p4左 p6右 p8右 p9右 → e4, rep=0 + clearSave(); resetSettings('off'); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await playNight(game, SCRIPT_D); + assert(game.endingId === 'e4' && game.rep === 0 && game.knot === false, + '剧本 D → e4 静音,rep=0,knot=false'); + assert(voicePlays(playLog, 'voice/z1_bad') === 1 && voicePlays(playLog, 'voice/z1_bade') === 1, + '剧本 D:p1右+p2右 砸单组合 → 播 z1_bad/z1_bade 归因句(真话律)'); + assert(voicePlays(playLog, 'voice/k1_lulle') === 1, '剧本 D:p4 左错过线索 → k1_lulle 诚实复盘'); + assert(voicePlays(playLog, 'voice/w1_say') === 0 && voicePlays(playLog, 'voice/w1_say2') === 0, + '剧本 D:p6 右本通结束 → 无带话差分(heardArgument=false 也不播)'); + assert(voicePlays(playLog, 'voice/e4_a') === 1 && voicePlays(playLog, 'voice/e4_b') === 1, + 'e4 专属结局主句 + 钩子播出'); + for (const id of ['voice/ep_zhou_no', 'voice/ep_deer_no', 'voice/ep_kid_no', + 'voice/ep_wife_no', 'voice/ep_han_no']) { + assert(voicePlays(playLog, id) === 1, `剧本 D 全错过 → 尾声播出 ${id}`); + } + teardown(game); + } + + // ---- 剧本 E:全左但 p4 左(错过线索)→ heardArgument=false → C4 播 w1_say2 组 + clearSave(); resetSettings('off'); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await playNight(game, SCRIPT_E); + assert(game.endingId === 'e1' && game.rep === 9, + '剧本 E(p4 左错线索,其余全左)→ e1,rep=1+2+2+0+1+2+1=9'); + assert(game.heardArgument === false, '剧本 E:p4 左 → heardArgument=false'); + assert(voicePlays(playLog, 'voice/w1_say2') === 1 && voicePlays(playLog, 'voice/w1_say2o') === 1 + && voicePlays(playLog, 'voice/w1_say2e') === 1 && voicePlays(playLog, 'voice/w1_say') === 0, + '剧本 E:C4 左路消费差分 → 播 w1_say2 组整组,绝不播 w1_say 组'); + assert(voicePlays(playLog, 'voice/k1_lulle') === 1, '剧本 E:p4 左 → k1_lulle 复盘句(诚实归因,非惩罚)'); + teardown(game); + } +} + +// ================================================================ 状态播报与无效手势 +// (§2.3 上滑状态链 + 无效手势零静默 + idle-only F1.4) + +realLog('== 状态播报与无效手势(guide=full)=='); +{ + // ---- RIG A:RING 上滑状态播报(第几通 + 口碑档,留在 RING) + clearSave(); resetSettings('full'); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'RING'); + await g(game, 'up'); + assert(game.currentVoiceId === 'voice/prog_c1', 'RING 上滑先播 prog_c1(第几通)'); + await advance(() => !game.voicePlaying); + const iP1 = lastIndexOf(playLog, 'voice/prog_c1'); + const iR1 = lastIndexOf(playLog, 'voice/rep_low'); + assert(iP1 >= 0 && iP1 < iR1, 'RING 上滑状态链顺序:prog_c1 → rep_low(rep=0 → low 档整句)'); + assert(game.phase === 'RING', '状态播报后留在 RING'); + teardown(game); + } + + // ---- RIG B:RING 无效手势(左滑 / 首通前双击)零静默 + onceKey 同局限次 + clearSave(); resetSettings('full'); + { + const { audio, playLog, game } = makeRig(); + const vib0 = vibrateLog.length; + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'RING'); + await g(game, 'left'); + assert(vibrateLog.slice(vib0).some((v) => JSON.stringify(v.pattern) === ERR_PATTERN), + 'RING 左滑 → error 震(无效手势零静默)'); + assert(voicePlays(playLog, 'voice/gd_invalid') === 1, 'RING 左滑 → gd_invalid(full 档播出)'); + await g(game, 'doubletap'); // 首通前无上一通收尾句 → 无效 + assert(voicePlays(playLog, 'voice/gd_invalid') === 1, + '再次无效手势 gd_invalid 不重播(onceKey 同局限次)'); + const errN = vibrateLog.slice(vib0).filter((v) => JSON.stringify(v.pattern) === ERR_PATTERN).length; + assert(errN === 2, `无效手势震反馈每次都有(零静默,实际 ${errN} 次)`); + teardown(game); + } + + // ---- RIG C:idle-only(F1.4,对白在播时纠错跳过且不占 onceKey)+ BRIDGE 上滑报状态 + clearSave(); resetSettings('full'); + { + const { audio, playLog, game } = makeRig(); + const guideEvents = []; + game.guide.setEventHook((ev) => guideEvents.push(ev)); + const vib0 = vibrateLog.length; + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'RING'); + await g(game, 'tap'); // 接听 → z1_ring_in 播放中 + await g(game, 'left'); + assert(guideEvents.some((e) => e.type === 'error' && e.skipped === true), + '对白在播时无效手势 → 纠错句 idle-only 跳过(事件带 skipped)'); + assert(voicePlays(playLog, 'voice/gd_invalid') === 0 && game.voicePlaying, + '在播对白不被纠错句顶掉,gd_invalid 未播出'); + await advance(() => !game.voicePlaying); // z1_ring_in 链末 + await g(game, 'left'); + assert(voicePlays(playLog, 'voice/gd_invalid') === 1, + '语音空闲后再次无效 → gd_invalid 播出(idle 跳过不占 onceKey)'); + assert(vibrateLog.slice(vib0).filter((v) => JSON.stringify(v.pattern) === ERR_PATTERN).length === 2, + '两次无效手势 error 震都发出(零静默)'); + + // C1 → BRIDGE:上滑报状态(帮助 + 下一通 + 口碑档),自动链继续 + await tapUntil(game, () => game.choiceArmed, 10); // → p1 + await g(game, 'left'); // p1 左 + await tapUntil(game, () => game.choiceArmed, 10); // → p2 + await g(game, 'left'); // p2 左 + await tapUntil(game, () => game.phase === 'BRIDGE', 10); // z1_stay→…→z1_ok1→@bridge + await advance(() => !game.voicePlaying); // rep_mid 跨档播报播完 + await g(game, 'up'); + await advance(() => !game.voicePlaying); + const iH = lastIndexOf(playLog, 'voice/sys_help'); + const iP2 = lastIndexOf(playLog, 'voice/prog_c2'); + const iR2 = lastIndexOf(playLog, 'voice/rep_mid'); + assert(iH < iP2 && iP2 < iR2, 'BRIDGE 上滑状态链顺序:sys_help → prog_c2(下一通)→ rep_mid'); + await advance(() => game.phase === 'RING'); + assert(game.callN === 2, 'BRIDGE 上滑后自动链继续进 RING(c2)'); + teardown(game); + } +} + +// ================================================================ 菜单交互 +// (§9 边界:菜单返回不丢节点、循环顺序、帮助、保存并回大厅;queue 不顶句) + +realLog('== 菜单交互(guide=off,C1 期间呼出)=='); +{ + clearSave(); resetSettings('off'); + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'RING'); + await g(game, 'tap'); // 接听 C1 + await tapUntil(game, () => game.promptPlaying, 10); // p1 提示播放中呼出菜单 + + await g(game, 'down'); + assert(game.phase === 'MENU', '下滑呼出模态菜单'); + await flush(); + assert(audio._bgm && audio._bgm.id === 'bgm/menu', '进菜单 BGM 切 bgm/menu'); + await g(game, 'tap'); // 播放中单击循环 → queue 排队,不截断当前报名 + await advance(() => !game.voicePlaying); + await g(game, 'tap'); + await advance(() => !game.voicePlaying); + await g(game, 'tap'); + await advance(() => !game.voicePlaying); + const cnt = (id) => voicePlays(playLog, id); + const iMenu = playLog.findIndex((e) => e.id === 'voice/sys_menu'); + const iRes = playLog.findIndex((e) => e.id === 'voice/sys_menu_resume'); + const iRst = playLog.findIndex((e) => e.id === 'voice/sys_menu_restart'); + const iLob = playLog.findIndex((e) => e.id === 'voice/sys_menu_lobby'); + assert(iMenu >= 0 && iMenu < iRes && iRes < iRst && iRst < iLob, + '菜单项 queue 依序播出(sys_menu → 回到直播 → 重新开始今晚 → 保存并回大厅),在播句不被截断'); + assert(cnt('voice/sys_menu_restart') === 1 && cnt('voice/sys_menu_lobby') === 1 + && cnt('voice/sys_menu_resume') === 2, + '单击循环顺序 = [回到直播, 重新开始今晚, 保存并回大厅],末项单击回绕首项再报名'); + + await g(game, 'up'); + await advance(() => !game.voicePlaying); + assert(cnt('voice/sys_help') >= 1, '菜单内上滑播 sys_help 帮助'); + + await g(game, 'doubletap'); // 当前项 = 回到直播 + assert(game.phase === 'ONAIR' && game.nodeId === 'p1', + '双击确认「回到直播」→ phase/node 不变'); + await advance(() => game.choiceArmed); + assert(cnt('voice/p1') >= 2, '菜单关闭后重播当前节点(局面复述,含抉择提示)'); + + await g(game, 'down'); // 再开 + await advance(() => !game.voicePlaying); + await g(game, 'down'); // 下滑关闭 + assert(game.phase === 'ONAIR' && game.nodeId === 'p1', '下滑关闭菜单 → 回原状态'); + await advance(() => game.choiceArmed); + + // 保存并回大厅 + await g(game, 'down'); + await advance(() => !game.voicePlaying); + await g(game, 'tap'); + await g(game, 'tap'); // 第二项 = 保存并回大厅 + await g(game, 'doubletap'); + assert(cnt('voice/sys_menu_lobby') >= 2, '确认回大厅 → sys_menu_lobby 播出'); + assert(game.leaving === true, 'leaving 置位(跳转大厅前先播确认句)'); + const sd = saveData(); + assert(sd && sd.schema === 1 && typeof sd.summary === 'string' && sd.summary.length > 0, + '回大厅确认后 localStorage 有最新 data(含 summary)'); + teardown(game); +} + +// ================================================================ 中断复述 +// (§9 边界:杀进程重进 → sys_continue_ask → 单击恢复 + 上滑状态链顺序断言; +// §8.1 断点续玩:单击=继续上次 / 右滑=新夜班) + +realLog('== 中断复述(guide=off,预置存档 node=k1_asko)=='); +{ + clearSave(); resetSettings('off'); + storageData.set(SAVE_KEY, JSON.stringify({ + schema: 1, + updatedAt: new Date().toISOString(), + summary: '第3通·失眠的孩子 · 口碑+2 · 结局0/4', + phase: 'ONAIR', + node: 'k1_asko', + call: 3, + ending: null, + rep: 2, + knot: false, + heardArgument: true, + bond: { zhou: 1, deer: 1, kid: 0, wife: 0, han: 0 }, + endings: {}, + nights: 3, + lastCloser: null, + })); + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + assert(game.phase === 'ASK' && game.currentVoiceId === 'voice/sys_continue_ask', + '带存档重进 → sys_continue_ask 询问'); + + await g(game, 'tap'); + assert(game.phase === 'ONAIR' && game.nodeId === 'k1_asko' && game.callN === 3, + '单击继续 → 恢复 phase+node+call'); + assert(game.rep === 2 && game.heardArgument === true && game.nights === 3, + 'rep / heardArgument / nights 全部还原'); + await advance(() => !game.voicePlaying); + assert(voicePlays(playLog, 'voice/k1_asko') === 1, '重播当前节点链(局面复述)'); + + await g(game, 'up'); + await advance(() => !game.voicePlaying); + const iH = lastIndexOf(playLog, 'voice/sys_help'); + const iP = lastIndexOf(playLog, 'voice/prog_c3'); + const iR = lastIndexOf(playLog, 'voice/rep_mid'); + assert(iH >= 0 && iH < iP && iP < iR, '上滑状态链顺序:sys_help → prog_c3 → rep_mid'); + assert(voicePlays(playLog, 'voice/prog_c3') === 1, 'prog_c3 整句(第三通)'); + await advance(() => !game.voicePlaying); + assert(voicePlays(playLog, 'voice/k1_asko') === 2, '状态播完重播当前节点(局面复述)'); + + await g(game, 'tap'); // k1_asko 链末 → p5 + await advance(() => game.choiceArmed); + await g(game, 'left'); // p5 左 + assert(game.rep === 3, '恢复局内抉择正常结算:p5 左 rep 2+1=3'); + teardown(game); + + // ASK 右滑 = 新夜班(§8.1):复用上面会话的最新存档再进一次(storageData 保留 p5 抉择后的存档) + { + const { audio: audio2, game: game2 } = makeRig(); + await audio2.unlock(); + game2.start(); await flush(); + assert(game2.phase === 'ASK', '再次带存档重进 → ASK 询问态'); + await g(game2, 'right'); + assert(game2.phase === 'INTRO' && game2.rep === 0 && !game2.knot && !game2.heardArgument + && game2.saved === null, 'ASK 右滑 = 新夜班:跨通话变量重置、存档询问态清除'); + teardown(game2); + } +} + +// ================================================================ 看门狗 +// (§7.2/§9:RING 两级不同句 10s/25s;ONAIR 链末 25s/50s gd_continue/gd_again; +// p* 播完 25s/50s 复述两级;ENDING 25s/50s;guide 计时、手势复位、用尽静默) + +realLog('== 看门狗(guide=full,RING 两级 / ONAIR 链末 / p* 两级 / ENDING 两级)=='); +{ + clearSave(); resetSettings('full'); + const { audio, playLog, game } = makeRig(); + const guideEvents = []; + game.guide.setEventHook((ev) => guideEvents.push(ev)); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.currentVoiceId === 'voice/b_open'); // sys_new_night 播完接 b_open 链 + assert(voicePlays(playLog, 'voice/gd_brief') === 1, 'full 档开场链最前插 gd_brief'); + assert(guideEvents.some((e) => e.type === 'state' && e.name === 'explore'), 'INTRO 进入播 explore 签名'); + + await advance(() => game.phase === 'RING'); + const tR = now; + assert(guideEvents.some((e) => e.type === 'turn'), 'RING 进入 turn 事件(铃声 + turn cue)'); + await advance(() => voicePlays(playLog, 'voice/gd_ring1') === 1); + assert(now - tR === 10000, 'RING 看门狗第一级 10s → gd_ring1'); + await advance(() => voicePlays(playLog, 'voice/gd_ring2') === 1); + assert(now - tR === 25000, 'RING 看门狗第二级 25s → gd_ring2(与第一级不同句)'); + const r1 = voicePlays(playLog, 'voice/gd_ring1'); + const r2 = voicePlays(playLog, 'voice/gd_ring2'); + assert(r1 === 1 && r2 === 1, '两级提示各播一次(绝不逐字重播)'); + await advance(() => now >= tR + 40000); + assert(voicePlays(playLog, 'voice/gd_ring1') === r1 && voicePlays(playLog, 'voice/gd_ring2') === r2 + && game._ringSrc !== null, '两级用尽后只剩铃声循环垫,永不惩罚'); + + // ONAIR 链末等待看门狗(§7.2 叙事链末行 / §9:25s gd_continue、50s gd_again) + await g(game, 'tap'); // 接听 → z1_ring_in + await advance(() => !game.voicePlaying); // 链末等待,看门狗挂载 + const tW = now; + await advance(() => voicePlays(playLog, 'voice/gd_continue') === 1); + assert(now - tW === 25000, 'ONAIR 链末 25s 静默 → gd_continue'); + await advance(() => voicePlays(playLog, 'voice/gd_again') === 1); + assert(now - tW === 50000, '50s → gd_again(第二级新句)'); + await advance(() => now >= tW + 70000); + assert(voicePlays(playLog, 'voice/gd_again') === 1, 'ONAIR 两级用尽彻底静默'); + + await g(game, 'tap'); // 手势复位看门狗,继续 → z1_a + await tapUntil(game, () => game.choiceArmed, 8); // z1_a→z1_b→p1 + const tP = now; + await advance(() => voicePlays(playLog, 'voice/p1') === 2); + assert(now - tP === 25000, 'p* 播完 25s 无操作 → 重播当前 p* 提示句(第一级)'); + await advance(() => voicePlays(playLog, 'voice/p1') === 3); + assert(now - tP === 50000, '50s → 再复述一次(第二级)'); + await advance(() => now >= tP + 70000); + assert(voicePlays(playLog, 'voice/p1') === 3, 'p* 复述两级后彻底静默'); + await g(game, 'left'); + assert(game.rep === 1 && game.nodeId === 'z1_song', '复述期间剧情不推进,玩家抉择后正常生效'); + + await tapUntil(game, () => game.choiceArmed, 8); // z1_song→z1_songo→z1_sdone→p2 + await g(game, 'left'); // p2 左(rep 3) + await tapUntil(game, () => game.phase === 'BRIDGE', 10); + assert(voicePlays(playLog, 'voice/rep_mid') === 1, '口碑跨档(low→mid)→ BRIDGE 被动播报 rep_mid'); + await advance(() => game.phase === 'RING'); + await g(game, 'tap'); // 接听 c2 + await tapUntil(game, () => game.choiceArmed, 8); // l1_in→l1_a→l1_b→p3 + await g(game, 'left'); // p3 左(rep 5) + await tapUntil(game, () => game.phase === 'BRIDGE', 10); + assert(voicePlays(playLog, 'voice/rep_high') === 1, '口碑跨入 ≥5(mid→high)→ BRIDGE 播报 rep_high'); + await advance(() => game.phase === 'RING'); + + // C3 起按剧本 A 剩余抉择打到 ENDING(playNight 内含接听/链末推进) + await playNight(game, ['right', 'left', 'left', 'left', 'left', 'left']); + assert(game.knot === true, 'p9 左 → knot=true'); + + await advance(() => game.phase === 'ENDING' && game.awaitingRestart); + const tE = now; + const gc0 = voicePlays(playLog, 'voice/gd_continue'); + const ga0 = voicePlays(playLog, 'voice/gd_again'); + await advance(() => voicePlays(playLog, 'voice/gd_continue') === gc0 + 1); + assert(now - tE === 25000, 'ENDING 结算 25s 静默 → gd_continue'); + await advance(() => voicePlays(playLog, 'voice/gd_again') === ga0 + 1); + assert(now - tE === 50000, '50s → gd_again(第二级新句)'); + await advance(() => now >= tE + 70000); + assert(voicePlays(playLog, 'voice/gd_again') === ga0 + 1, '两级用尽彻底静默'); + await g(game, 'tap'); + assert(game.phase === 'INTRO' && game.nights === 2, '结算单击重开新夜班'); + + const choiceEvents = guideEvents.filter((e) => e.type === 'state' && e.name === 'choice').length; + assert(choiceEvents === 9, `每个 p* 起播发一次 choice 签名(实际 ${choiceEvents})`); + assert(guideEvents.some((e) => e.type === 'state' && e.name === 'settle'), 'BRIDGE/ENDING 进入 settle 签名'); + assert(guideEvents.some((e) => e.type === 'state' && e.name === 'danger'), 'C5 危机拍 danger 签名(全局唯一使用点)'); + teardown(game); +} + +// ================================================================ 资产缺失 +// (§9 边界:屏蔽任意 voice 资产 → 单句看门狗自动续链到结局,不卡死) + +realLog('== 资产缺失(guide=off,屏蔽 k1_asko)=='); +{ + clearSave(); resetSettings('off'); + blockedAssets.add('k1_asko'); + const errBefore = consoleErrs.length; + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await playNight(game, SCRIPT_A); + assert(consoleErrs.slice(errBefore).some((e) => e.includes('k1_asko')), + '屏蔽资产仅 console.error,不抛异常'); + assert(consoleWarns.some((w) => w.includes('看门狗') && w.includes('k1_asko')), + '单句 30s 看门狗判定资产缺失并自动续链'); + assert(game.endingId === 'e1' && game.awaitingRestart === true, '屏蔽 voice 资产仍打到 e1 结局,不卡死'); + assert(voicePlays(playLog, 'voice/gd_ring1') === 0 && voicePlays(playLog, 'voice/gd_ring2') === 0 + && voicePlays(playLog, 'voice/gd_continue') === 0 && voicePlays(playLog, 'voice/gd_again') === 0, + 'guide=off 全程零向导语音(gd_*)'); + blockedAssets.delete('k1_asko'); + teardown(game); +} + +// ================================================================ 设置联动 +// (§7 全局:voice 性别目录切换、speed 从下一句生效、bgm=0 停播与自动恢复、 +// 长按 1.7x 引擎接管) + +realLog('== 设置联动与引擎行为(guide=off)=='); +{ + clearSave(); resetSettings('off'); + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'RING'); + await g(game, 'tap'); + await tapUntil(game, () => game.promptPlaying, 10); // p1 + settings.set({ voice: 'male', speed: 1.7 }); + await g(game, 'left'); // 提示播放中 → pendingChoice 缓存 + await advance(() => game.nodeId === 'z1_song'); // 提示播完缓存生效,z1_song 以新设置起播 + assert(urlHits('audio/voice/male/z1_song') > 0, 'settings.voice=male → 下一句起走 male 目录'); + assert(audio._voice && audio._voice.st.tempo === 1.7, 'settings.speed=1.7 下一句起生效(SoundTouch tempo)'); + settings.set({ speed: 1.0, voice: 'female' }); + fireWindowEvent('soundgame:longpress'); + assert(audio._voice && audio._voice.st.tempo === 1.7, '长按 → 旁白临时 1.7 倍速(不变调)'); + fireWindowEvent('soundgame:longpressend'); + assert(audio._voice && audio._voice.st.tempo === 1.0, '松开 → 恢复 settings.speed'); + await tapUntil(game, () => game.choiceArmed, 8); // z1_song→z1_songo→z1_sdone→p2 + assert(audio._bgm && audio._bgm.id === 'bgm/onair', 'ONAIR/RING 垫乐 bgm/onair'); + settings.set({ bgm: 0 }); + await flush(); + assert(audio._bgm === null, 'settings.bgm=0 立即停播'); + settings.set({ bgm: 1 }); + await flush(); + assert(audio._bgm && audio._bgm.id === 'bgm/onair', 'bgm 恢复后自动补播最后状态垫乐'); + teardown(game); +} + +// ================================================================ R2 修复回归 +// (research/new8/radio/round1/synthesis.md F1-F5 + V1/V2;断言增量登记 +// docs/design/15-radio.md 文末「R2 修复回归」小节) + +realLog('== R2 修复回归(F1 看门狗重挂 / F3 铃声垫 / F4 续玩跳句锁 / F5 链末句保护 / V1 抉择时序 / V2 菜单循环)=='); +{ + // ---- F1a:ONAIR 链末等待态发无效手势——看门狗被 disarm 拆掉后必须重挂(阈值内触发) + clearSave(); resetSettings('full'); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'RING'); + await g(game, 'tap'); // 接听 → z1_ring_in + await advance(() => !game.voicePlaying); // 链末等待(看门狗挂载) + const t0 = now; + await g(game, 'left'); // 无效手势(live 局 B t=445.4 同款:旧实现拆看门狗不补挂 → 34.2s 静默) + assert(voicePlays(playLog, 'voice/gd_invalid') === 1, 'F1 前置:链末无效手势照常零静默(gd_invalid)'); + await advance(() => voicePlays(playLog, 'voice/gd_continue') === 1); + assert(now - t0 === 25000, `F1:链末等待中发无效手势,看门狗仍在阈值内触发(25s 整,实际 ${now - t0}ms)`); + await advance(() => voicePlays(playLog, 'voice/gd_again') === 1); + assert(now - t0 === 50000, `F1:重挂后第二级同样有效(50s 整,实际 ${now - t0}ms)`); + teardown(game); + } + + // ---- F1b:RING 无效手势不拆铃看门狗(gd_ring1 仍准点)且铃声垫不受影响 + clearSave(); resetSettings('full'); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'RING'); + await flush(); + const t0 = now; + await g(game, 'left'); // RING 无效手势 + await advance(() => voicePlays(playLog, 'voice/gd_ring1') === 1); + assert(now - t0 === 10000, `F1:RING 无效手势后铃看门狗仍 10s 准点(实际 ${now - t0}ms)`); + assert(game._ringSrc !== null, 'F3:RING 无效手势不拆铃声循环垫'); + teardown(game); + } + + // ---- F1c:ASK 询问态无效手势看门狗重挂 + clearSave(); resetSettings('full'); + storageData.set(SAVE_KEY, JSON.stringify({ + schema: 1, updatedAt: new Date().toISOString(), summary: '第1通·走丢的老人 · 口碑+0 · 结局0/4', + phase: 'ONAIR', node: 'z1_a', call: 1, ending: null, + rep: 0, knot: false, heardArgument: false, + bond: { zhou: 0, deer: 0, kid: 0, wife: 0, han: 0 }, endings: {}, nights: 1, lastCloser: null, + })); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'ASK' && !game.voicePlaying); // sys_continue_ask 播完 + const t0 = now; + await g(game, 'up'); // ASK 内无效手势(上滑不在 ASK 语义内) + await advance(() => voicePlays(playLog, 'voice/gd_continue') === 1); + assert(now - t0 === 25000, `F1:ASK 无效手势后看门狗仍 25s 准点(实际 ${now - t0}ms)`); + teardown(game); + } + + // ---- F4/F5:续玩恢复首句跳句锁 + 链末句保护(w1_say 组一段链三连断言: + // 首句受锁不可跳 → 锁解除后中段句可跳(对照) → 末句受 F5 保护不可跳) + clearSave(); resetSettings('off'); + storageData.set(SAVE_KEY, JSON.stringify({ + schema: 1, updatedAt: new Date().toISOString(), summary: '第4通·争吵的夫妻 · 口碑+9 · 结局0/4', + phase: 'ONAIR', node: 'w1_say', call: 4, ending: null, + rep: 9, knot: false, heardArgument: true, + bond: { zhou: 1, deer: 1, kid: 1, wife: 0, han: 0 }, endings: {}, nights: 1, lastCloser: null, + })); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'ASK' && !game.voicePlaying); + await g(game, 'tap'); // 单击继续上次 → 恢复 w1_say 组 + assert(game.phase === 'ONAIR' && game.currentVoiceId === 'voice/w1_say', + 'F4 前置:断点续玩恢复,首句 w1_say 在播'); + await g(game, 'tap'); + assert(game.currentVoiceId === 'voice/w1_say', 'F4:恢复后首句播完前单击不跳句(先听完再武装跳句)'); + await advance(() => game.currentVoiceId === 'voice/w1_sayo'); + await g(game, 'tap'); + await flush(); + assert(game.currentVoiceId === 'voice/w1_saye', 'F4 对照:首句播完后跳句重新武装(w1_sayo 被正常跳过)'); + await g(game, 'tap'); // w1_saye = 本节点末句(播完交接 p7 抉择提示) + assert(game.currentVoiceId === 'voice/w1_saye' && game.voicePlaying, + 'F5:链末句(交接抉择提示前)单击不可跳'); + await advance(() => !game.voicePlaying); + assert(voicePlays(playLog, 'voice/w1_saye') === 1, 'F5 对照:链末句完整播出不被吃尾'); + await g(game, 'tap'); // 链末单击继续 + await advance(() => game.choiceArmed); + assert(game.nodeId === 'p7', 'F5 对照:末句播完后照常交接 p7 抉择提示'); + teardown(game); + } + + // ---- F4b:续玩恢复到结算态——结局链首句播完才武装跳句(live 局 B 被扔进结局链的陷阱场景) + clearSave(); resetSettings('off'); + storageData.set(SAVE_KEY, JSON.stringify({ + schema: 1, updatedAt: new Date().toISOString(), summary: '已收播·静音 · 结局1/4', + phase: 'ENDING', node: 'e4', call: 5, ending: 'e4', + rep: 0, knot: false, heardArgument: false, + bond: { zhou: 0, deer: 0, kid: 0, wife: 0, han: 0 }, endings: { e4: true }, nights: 2, + lastCloser: null, + })); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'ASK' && !game.voicePlaying); + await g(game, 'tap'); // 单击继续上次 → 结局链重播 + assert(game.phase === 'ENDING' && game.currentVoiceId === 'voice/e4_a', + 'F4b 前置:续玩恢复到 ENDING(e4) 结局链首句'); + await g(game, 'tap'); + assert(game.currentVoiceId === 'voice/e4_a', 'F4b:恢复后首句(e4_a)播完前单击不跳句'); + await advance(() => game.currentVoiceId === 'voice/e4_b'); + await g(game, 'tap'); + await flush(); + assert(game.currentVoiceId === 'voice/ep_lead', 'F4b:首句播完后跳句武装(e4_b 被跳过,链照常走)'); + teardown(game); + } + + // ---- F5b:@ending 过场转移前的链末句保护(d9_facee 点题句,live 局 A 被吃掉的尾句) + clearSave(); resetSettings('off'); + storageData.set(SAVE_KEY, JSON.stringify({ + schema: 1, updatedAt: new Date().toISOString(), summary: '第5通·雨夜司机 · 口碑+12 · 结局0/4', + phase: 'ONAIR', node: 'd9_facee', call: 5, ending: null, + rep: 12, knot: true, heardArgument: true, + bond: { zhou: 1, deer: 1, kid: 1, wife: 1, han: 1 }, endings: {}, nights: 1, lastCloser: null, + })); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'ASK' && !game.voicePlaying); + await g(game, 'tap'); + assert(game.currentVoiceId === 'voice/d9_facee', 'F5b 前置:恢复至签收链末句 d9_facee'); + await g(game, 'tap'); + assert(game.currentVoiceId === 'voice/d9_facee', 'F5b:@ending 过场前的链末句单击不可跳'); + await advance(() => game.phase === 'ENDING' && game.awaitingRestart); + assert(game.endingId === 'e1' && voicePlays(playLog, 'voice/d9_facee') === 1, + 'F5b 对照:末句完整播完后自动过场进 ENDING(e1)'); + teardown(game); + } + + // ---- V1:pendingChoice 生效时序——抉择语音播完才生效;播中连点不丢、不改、不提前 + clearSave(); resetSettings('off'); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'RING'); + await g(game, 'tap'); + await tapUntil(game, () => game.promptPlaying, 10); // → p1 提示句播放中 + await g(game, 'left'); + await g(game, 'tap'); // 压力局连点:p* 不可跳…… + await g(game, 'tap'); + assert(game.currentVoiceId === 'voice/p1' && game.promptPlaying, 'V1 前置:p* 播放中连点不跳句'); + assert(game.pendingChoice === 'left' && game.rep === 0 && game.nodeId === 'p1', + 'V1:连点不清缓存、不提前生效(rep 未动、节点未进)'); + await g(game, 'right'); + assert(game.pendingChoice === 'right', 'V1:播中再滑覆盖缓存(最后一次为准)'); + await g(game, 'left'); + const iP1Last = lastIndexOf(playLog, 'voice/p1'); + await advance(() => game.nodeId === 'z1_song'); + const ids = playLog.map((e) => e.id); + const iConfirm = ids.indexOf('sfx/ui_confirm'); + assert(iConfirm > iP1Last && ids.indexOf('voice/z1_song') > iConfirm, + 'V1:生效时序 = p1 播完 → ui_confirm 结算音 → 结果句(播完才生效,零插播)'); + assert(game.rep === 1 && game.pendingChoice === null && game.nodeId === 'z1_song', + 'V1:缓存恰好生效一次(p1 左 rep=1,缓存清空,不丢不重复)'); + teardown(game); + } + + // ---- V2:菜单全项循环——三项(含第三项「保存并回大厅」)逐项可达、循环无漂移、确认可执行 + clearSave(); resetSettings('off'); + { + const { audio, playLog, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'RING'); + await g(game, 'down'); // 呼出模态菜单 + await advance(() => !game.voicePlaying); // sys_menu + 首项报名播完 + for (let i = 0; i < 6; i++) { // 两个整循环(stress 局 38 次循环听不全第三项的回归) + await g(game, 'tap'); + await advance(() => !game.voicePlaying); + } + const ids = playLog.map((e) => e.id); + const iHdr = ids.indexOf('voice/sys_menu'); + const cycle = ids.slice(iHdr + 1, iHdr + 7); + assert(JSON.stringify(cycle) === JSON.stringify([ + 'voice/sys_menu_resume', 'voice/sys_menu_restart', 'voice/sys_menu_lobby', + 'voice/sys_menu_resume', 'voice/sys_menu_restart', 'voice/sys_menu_lobby', + ]), `V2:单击循环两个整周期逐项可达(实际: ${cycle.join(' → ')})`); + assert(game.menuIdx === 0, 'V2:循环 6 次后回绕首项(模 3 无漂移)'); + await g(game, 'tap'); + await advance(() => !game.voicePlaying); + await g(game, 'tap'); + await advance(() => !game.voicePlaying); // 停在第二项 = 保存并回大厅 + const lobBefore = voicePlays(playLog, 'voice/sys_menu_lobby'); + await g(game, 'doubletap'); // 确认执行 + await flush(); + assert(game.leaving === true && voicePlays(playLog, 'voice/sys_menu_lobby') === lobBefore + 1, + 'V2:双击确认「保存并回大厅」→ 确认句播出且 leaving 置位'); + assert(saveData() && saveData().call === 1 && typeof saveData().summary === 'string', + 'V2:确认执行落盘最新存档(含 summary,第1通)'); + teardown(game); + } +} + +// ================================================================ 静态审计 +// (数字规范:分档整句字面量、无运行时 id 拼接;存档 summary 格式;资产盘点) + +realLog('== 静态审计 =='); +{ + const src = fs.readFileSync(path.join(GAME_DIR, 'game.js'), 'utf8'); + for (const id of ['voice/prog_c1', 'voice/prog_c2', 'voice/prog_c3', 'voice/prog_c4', + 'voice/prog_c5', 'voice/rep_high', 'voice/rep_mid', 'voice/rep_low', + 'voice/gd_ring1', 'voice/gd_ring2']) { + assert(src.includes(`'${id}'`), `分档整句字面量存在:${id}`); + } + assert(!/voice\/\$\{/.test(src), '无模板字符串拼接 voice id'); + assert(!/['"]voice\/[A-Za-z0-9_]+['"]\s*\+/.test(src), '无字符串相加拼接 voice id'); + assert(!/\+\s*['"]voice\//.test(src), '无前置相加拼接 voice id'); + + // 资产盘点:voice/bgm 全量齐备(ogg+m4a,含性别目录/平铺回退) + const ids = [...new Set([...src.matchAll(/'((?:voice|sfx|bgm)\/[A-Za-z0-9_.-]+)'/g)].map((m) => m[1]))]; + assert(ids.length > 0, `game.js 提取到 ${ids.length} 个资产 id`); + const both = (rel) => ['.ogg', '.m4a'].every((ext) => fs.existsSync(path.join(AUDIO_DIR, rel + ext))); + // Stable Audio 3 批次缺口(gen/gen_sfx.py 头注:HF token 未配置本轮跳过, + // R2 F3 已移除 phone_ring(程序合成铃声循环垫落盘);剩 11 件待补; + // 缺音效走引擎 console.error 静默降级,不阻塞玩法(§6.1 降级方案)。 + const STABLE_AUDIO_PENDING = new Set(['sfx/onair_sting', 'sfx/line_connect', + 'sfx/line_drop', 'sfx/amb_rain_city', 'sfx/amb_station', 'sfx/amb_kid_room', 'sfx/arg_muffled', + 'sfx/amb_street_rain', 'sfx/amb_cab', 'sfx/brake_screech', 'sfx/tape_static']); + const missingVoiceBgm = []; + const missingSfx = []; + for (const id of ids) { + if (id.startsWith('voice/')) { + const name = id.slice(6); + if (!(both(`voice/male/${name}`) || both(`voice/female/${name}`) || both(`voice/${name}`))) { + missingVoiceBgm.push(id); + } + } else if (id.startsWith('bgm/')) { + if (!both(id)) missingVoiceBgm.push(id); + } else if (!both(id)) { + missingSfx.push(id); + } + } + assert(missingVoiceBgm.length === 0, + `voice/bgm 资产零缺失(${ids.length} 个引用 id,ogg+m4a 双格式,含性别目录/平铺回退)`); + assert(both('sfx/ui_confirm') && both('sfx/ui_menu') && both('sfx/phone_ring'), + '程序合成 sfx ui_confirm/ui_menu/phone_ring 齐备(R2 F3 铃声循环垫落盘,ogg+m4a)'); + assert(missingSfx.every((id) => STABLE_AUDIO_PENDING.has(id)), + `sfx 缺失仅限 Stable Audio 未产出批次(实际: ${missingSfx.join(', ') || '无'})`); + + // 双性别 / NPC 平铺抽查(§5.3) + assert(both('voice/male/p1') && both('voice/female/p1'), '抉择提示 p1 双性别齐备'); + assert(both('voice/z1_a') && !fs.existsSync(path.join(AUDIO_DIR, 'voice/male/z1_a.ogg')), + 'NPC 老周 z1_a 仅平铺目录(不做双性别,电话滤波质感)'); + assert(both('voice/male/gd_ring1') && both('voice/female/gd_ring2'), + 'RING 两级提示 gd_ring1/gd_ring2 双性别齐备'); + + // 存档 summary 人话格式(RING 进入时机) + clearSave(); resetSettings('off'); + const { audio, game } = makeRig(); + await audio.unlock(); + game.start(); await flush(); + await advance(() => game.phase === 'RING'); + const sd = saveData(); + assert(sd && sd.schema === 1, '存档 schema=1'); + assert(sd && sd.summary === '第1通·走丢的老人 · 口碑+0 · 结局0/4', + `summary 格式「第N通·来电者 · 口碑±X · 结局K/4」(实际: ${sd && sd.summary})`); + assert(sd && Number.isFinite(Date.parse(sd.updatedAt)), 'updatedAt 为 ISO 时间戳'); + teardown(game); +} + +// ================================================================ 汇总 + +realLog(`\n通过 ${passCount} 条断言,失败 ${failures.length} 条`); +if (failures.length > 0) { + realLog('失败清单:'); + for (const f of failures) realLog(` - ${f}`); + process.exit(1); +} +realLog('全部断言通过。');