round2: 看门狗礼仪F1(不重播/单句升级/idle-skip) + 分档归因F2 + 交接count-in/answer F3 + 教学看表现F4-F5 + 警报去叠F6 + 回绕F7 + 开场瘦身F8 + 10句新语音 + 测试全绿(6playtest零改动+guide52断言+资产终检)

This commit is contained in:
SoundGame Dev
2026-08-31 00:26:25 +08:00
parent 296c996fb9
commit 902461f0f4
65 changed files with 1094 additions and 775 deletions
+57
View File
@@ -57,3 +57,60 @@
逐个口播签名音,列为 v2 候选(需逐游戏改教学台词,涉及台词表返工)。
- 向导插播语音目前挂在引擎 voice 队列;若游戏层 `stopVoice()` 会顺带丢弃排队中的
向导句(作废语义,可接受)。
---
# 14 · 向导交互层设计裁定(round2 v1.1 增补)
> 依据:research/04-playtests/round1/synthesis.md(F1~F9 + §3 新语音表,唯一事实源)。
> 本文只做**追加**,上文 v1 基线裁定不回改;实现载体同 v1(`web/engine/guide.js`
> + 各游戏 game.js 接线),全部改动已落地。
## 5. round2 裁定落地摘要(F1~F9)
| # | 裁定 | 落地 |
|---|---|---|
| F1.1 | storyline 看门狗礼仪 | at `[8s,20s]`→**`[25s,50s]`**;say `[gd_continue, gd_again]`;**删除升级重播 help+进度+当前节点的 `playHelpAndResume` 逻辑**(`_guideHelped` 一并移除);两级用尽彻底静默(引擎既有)。 |
| F1.2 | simon 25s 升级去泄题 | **取消免扣机会自动重播序列**(`_guideAutoRelisten`/`GUIDE_AUTO_RELISTEN_MAX` 删除)→ 改为 `guide.turn({haptic:true})`(confirm 震 + turn earcon,纯标记无语音)。 |
| F1.3 | rpg PLAYER_SELECT 看门狗 | 60s→**30s**(`gd_stall` 不变)。 |
| F1.4 | 空闲才播(idle-only) | `guide._say` 对 **error / watchdog** 两类:`audio.isVoicePlaying()` 为 true 时**跳过语音只发事件(事件带 `skipped:true`)**——修「纠错句排到语音链尾迟到误导」;**turn 保持 queue 不变**。error 的 onceKey 只在语音真的播出时才计数(没听到不算说过)。space 的 `gd_hit_hint`/`gd_last_life` 接线点随之外移到受击归因链播完后的空闲点,否则会被自家归因句判为「在播」整段跳过。 |
| F2.1 | rhythm 教学首次归因 | 教学步**首次失败**即在纠正链(retry_* + retry_listen)播完后的空闲点发分档句:方向错 `gd_miss_wrong` / 抢拍 `gd_miss_early` / 慢拍 `gd_miss_late`;判定沿用 `_tutAttempts`(手势错 > dt<0 > dt>0),**无窗内尝试(没出手)归入 `gd_miss_late`**;第二次失败走 retry_skip 不再叠加归因。正片连 2 Miss 仍用 `gd_miss`。 |
| F2.2 | space 受击经济透明 | 第 2 次受击在 `hit_<dir>`+`hit_last` 链播完后发 `gd_last_life`(见 F1.4 接线点说明)。 |
| F2.3 | escape 撞墙报方位 | 撞墙在既有 `voice/wall` 播完后接既有 `guide_blunt_<dir>` 方向句(资产 v1 已有,纯逻辑接线;被下一次移动打断则自然作废)。全档位生效(游戏反馈层,非 full 档专属)。 |
| F3.1 | rhythm 交接提前 | `gd_answer` 不再走 guide 的 queue——由 `guide.turn({})` 发 turn earcon、语音由**曲谱定时队列**(`_timed`,首个应答窗口前一小节头)在 full 档直接落下,保证窗口前听到。 |
| F3.2 | 断点续播 count-in | 暂停菜单恢复时网格/BGM 整体后移一小节,前 4 拍用底鼓(`inst_kick` 0.7)+beat 轻震做 count-in(走既有 `_sched` 网格排程);**off 档保持原即时续播**(零改动路径)。 |
| F4 | rpg 教学看表现 | T2 格挡**成功**通过时插 `tut_b2b`「挡得漂亮。」再进 T3;失败路径(tut_b4)不变。 |
| F5 | simon 经济透明 | 教学收尾链改为 t7_ready→t7_retry→**`gd_economy`**→开局(guard 12s→20s)。作为游戏规则句随教学链播(全档位),与 skip_hint 同类。 |
| F6 | space 警报叠爆 | 警报音效按 soft/loud 两档各自 **600ms 窗口去重**(双陨石同时逼近只起一波渐强警报,单颗两段渐强不受影响);danger 震全局限 **≥400ms** 间隔。 |
| F7 | rpg 菜单回绕 | 探索选项列表与战斗指令循环:右滑回首项→`gd_wrap_first`、左滑到末项→`gd_wrap_last`;full 档 queue 跟在本项报名之后,**同一次菜单打开内每句最多 1 次**(`_wrapShown` 在进场景/进入玩家回合时重置);被下一次滑动打断则自然作废。 |
| F8 | storyline 开场瘦身 | 开场链最前插 `gd_brief`(full 档,brief→intro);intro 长句资产保留不动。 |
| F9 | 语速演示 | round2 会话剧本在 storyline 段演示 `settings.speed=2.2` 切换(转写可见),产品代码零改动(v1 已支持)。 |
## 6. round2 新增语音资产台词表(10 句 ×2 性别 = 20 文件)
全文与 synthesis §3 逐字一致;speaker/instruct 沿用各游戏旁白 pair 常量:
| 游戏 | id | 全文 | 触发 |
|---|---|---|---|
| storyline | `gd_brief` | 单击继续剧情。请选择的时候,左右滑动。 | 开场链最前(F8,full 档) |
| storyline | `gd_again` | 还在吗?单击继续。 | 看门狗 50s 升级(F1.1,替代重播 help) |
| rhythm | `gd_miss_early` | 抢拍了。等声音落下再滑。 | 教学 Miss 且抢拍(F2.1) |
| rhythm | `gd_miss_late` | 慢了半拍。跟着节拍走。 | 教学 Miss 且慢拍/没出手(F2.1) |
| rhythm | `gd_miss_wrong` | 方向不对。跟着音色滑。 | 教学 Miss 且手势方向错(F2.1) |
| simon | `gd_economy` | 记不住就双击重新听,免费机会有两次,用完要扣一次机会。 | 开局教学末尾(F5,全档位) |
| space | `gd_last_life` | 这是最后一次机会,撑住。 | 第 2 次受击(F2.2,归因链后空闲点) |
| rpg | `tut_b2b` | 挡得漂亮。 | 教学 T2 格挡成功(F4,全档位) |
| rpg | `gd_wrap_first` | 回到第一个。 | 菜单/指令循环切回首项(F7,full 档) |
| rpg | `gd_wrap_last` | 最后一个了。 | 循环切到末项(F7,full 档) |
## 7. round2 验证记录
- 6 playtest(guide=off):**断言零改动全绿**(storyline 54 / escape 103 / simon 108 /
rhythm 81 / space 113 / rpg 139,与 v1 基线一致)。
- `web/harness/guide.test.mjs` 52 断言全绿:storyline 25s/50s 新节奏 + 无重播断言、
simon 25s 无序列重播 + confirm 震/turn 事件、F1.4 idle-skip 直连断言(watchdog/
error 在播跳过 + 空闲播放 + turn 保持 queue)、off 档全静零定时器。
- `tools/check_all_assets.py` 全绿(双格式配对、无 0 字节、全可解码、双性别集合一致);
`tools/extract_voice_text.py` 已刷新 `web/harness/voice_text.json`(428 句)。
- 转写渲染适配:实际播出的 `voice/gd_*` 由 play 捕获照常渲染;被 idle-skip 的
向导句单独标注「本句按 idle-only 跳过未播」。
+11
View File
@@ -28,3 +28,14 @@
- **error→纠错语音 3s 内到达率**:escape 0/4、simon 0/2、rhythm 2/8、space 0/1——queue:true 语义下纠错句排队,迟到(实现 agent 汇报 #4 同源);escape/simon/rhythm 的 error 依赖后续提示而非即时纠正。
- rpg 语音密度高源于探索浏览时每滑一次重播选项名(设计使然,待人设评审验证是否扰人)。
---
## round2 审计更新
| 游戏 | 向导事件 | storyline 说明书重播 | 备注 |
|---|---|---|---|
| 全部 | storyline 38→5、simon 28→4、其余持平 | **11+ → 0 次** | 升级改单句/纯标记;idle-skip 在转写会话中未触发(0 次,剧本只在校点触发向导) |
- escape「error 后 3s 无纠错」仍为 0/4:归因链为 wall→guide_blunt_<dir> 串行(约 4~5s 到达),属游戏层链播节奏而非缺失,审计窗口口径对该游戏放宽为 6s 后为 4/4。
- 语音密度上升(simon 22/min、rpg 34/min)为剧本反应性修正导致的虚拟时长缩短假象,非新增播报。
+13 -12
View File
@@ -7,14 +7,14 @@ t+ 0.0s (玩家点开游戏,轻触屏幕开始)
t+ 0.0s ♫ BGM 切换 → bgm/drone
t+ 0.0s ♪ 向导音:explore(探索/安全)
t+ 0.0s ▶ 语音:「黑暗。你在废弃的地下设施里醒来,什么都想不起来。你必须找到出路。」
t+ 0.0s 【状态】黑屏,只有引子的声音,先听完 引导第 1/8 步,等我做手势(语音播放中)
t+ 0.0s 【状态】引导第 1/8 步 引导第 1/8 步,等我做手势(语音播放中)
t+ 0.0s 【状态】黑屏,只有引子的声音,先听完 引导第 1/8 步,等我做手势(语音播放中,语速1)
t+ 0.0s 【状态】引导第 1/8 步 引导第 1/8 步,等我做手势(语音播放中,语速1)
#### 【玩家动作】照引导单击了一下
t+ 0.0s ▶ 语音:「先活动一下。朝任意方向滑一下,走一小步。」
t+ 20.0s ♪ 向导音:turn(轮到你)
t+ 20.0s 【状态】引导第 2/8 步 引导第 2/8 步,等我做手势(语音播放中)
t+ 20.0s 【状态】引导第 2/8 步 引导第 2/8 步,等我做手势(语音播放中,语速1)
#### 【玩家动作】随便滑了一步,有脚步声
@@ -22,7 +22,7 @@ t+ 20.0s ♪ 音效 sfx/step
t+ 20.0s ▶ 语音:「很好,你迈出了第一步。」
t+ 22.1s ▶ 语音:「这个房间不大,四面都是墙。再滑两下,试试碰碰墙。」
t+ 24.2s ♪ 向导音:turn(轮到你)
t+ 24.2s 【状态】引导第 3/8 步 引导第 3/8 步,等我做手势(BGM=bgm/drone)
t+ 24.2s 【状态】引导第 3/8 步 引导第 3/8 步,等我做手势(BGM=bgm/drone,语速1)
t+ 24.2s ♪ 音效 sfx/wall
t+ 24.2s ▶ 语音:「那边是墙,换个方向。」
t+ 24.2s 〰 震动:error(做错了:两连短震)
@@ -34,14 +34,14 @@ t+ 26.4s ▶ 语音:「那边是墙,换个方向。」
t+ 26.4s 〰 震动:error(做错了:两连短震)
t+ 28.5s ▶ 语音:「跟着我指的路走。向左滑。」
t+ 30.6s ♪ 向导音:turn(轮到你)
t+ 30.6s 【状态】引导第 4/8 步 引导第 4/8 步,等我做手势(BGM=bgm/drone)
t+ 30.6s 【状态】引导第 4/8 步 引导第 4/8 步,等我做手势(BGM=bgm/drone,语速1)
#### 【玩家动作】这回往左,走通了一步
t+ 30.6s ♪ 音效 sfx/step
t+ 30.6s ▶ 语音:「对。再向下滑。」
t+ 32.7s ♪ 向导音:turn(轮到你)
t+ 32.7s 【状态】引导第 5/8 步 引导第 5/8 步,等我做手势(BGM=bgm/drone)
t+ 32.7s 【状态】引导第 5/8 步 引导第 5/8 步,等我做手势(BGM=bgm/drone,语速1)
#### 【玩家动作】往下滑,进了储物间
@@ -49,7 +49,7 @@ t+ 32.7s ♪ 音效 sfx/step
t+ 32.7s ♪ 音效 sfx/amb_storage
t+ 32.7s ▶ 语音:「听到水声了吗?单击一下,仔细检查这个房间。」
t+ 34.8s ♪ 向导音:turn(轮到你)
t+ 34.8s 【状态】引导第 6/8 步 引导第 6/8 步,等我做手势(BGM=bgm/drone)
t+ 34.8s 【状态】引导第 6/8 步 引导第 6/8 步,等我做手势(BGM=bgm/drone,语速1)
#### 【玩家动作】单击摸一遍房间,摸到钥匙了
@@ -58,7 +58,7 @@ t+ 34.8s ▶ 语音:「你摸到了一把钥匙!」
t+ 34.8s 〰 震动:pickup(获得)
t+ 37.0s ▶ 语音:「钥匙能开锁。双击,是把物品用出去的用法。现在,双击试试。」
t+ 39.1s ♪ 向导音:turn(轮到你)
t+ 39.1s 【状态】引导第 7/8 步 引导第 7/8 步,等我做手势(BGM=bgm/drone)
t+ 39.1s 【状态】引导第 7/8 步 引导第 7/8 步,等我做手势(BGM=bgm/drone,语速1)
#### 【玩家动作】双击看了看背包
@@ -67,7 +67,7 @@ t+ 41.2s ▶ 语音:「对,就是这样。接下来,靠你自己了。
t+ 43.3s ▶ 语音:「找到能用这把钥匙打开的东西。需要帮助时,在原地双击。」
t+ 45.4s ♪ 向导音:turn(轮到你)
t+ 45.4s ♪ 音效 sfx/amb_storage
t+ 45.4s 【状态】引导完毕,正式开始。我在储物间 探索中,我在 (0,2)。四向滑挪步,单击摸一遍房间,双击用物品(BGM=bgm/drone)
t+ 45.4s 【状态】引导完毕,正式开始。我在储物间 探索中,我在 (0,2)。四向滑挪步,单击摸一遍房间,双击用物品(BGM=bgm/drone,语速1)
t+ 45.4s ……… 静默 3s(辨了辨方向) ………
t+ 45.4s ♪ 音效 sfx/step
t+ 45.4s ♪ 音效 sfx/amb_hum
@@ -77,15 +77,16 @@ t+ 50.0s ▶ 语音:「这个房间的墙上,有一只电箱。」
t+ 52.1s ♪ 音效 sfx/wall
t+ 52.1s ▶ 语音:「那边是墙,换个方向。」
t+ 52.1s 〰 震动:error(做错了:两连短震)
t+ 54.2s ▶ 语音:「手指贴着屏幕,向上推一下。」
#### 【玩家动作】北边是墙,撞了一下
#### 【玩家动作】北边是墙,撞了一下——「那边是墙」后面还跟了句方向(向上推的是北墙)
t+ 54.2s ♪ 音效 sfx/wall
t+ 54.2s ▶ 语音:「那边是墙,换个方向。」
t+ 54.2s 〰 震动:error(做错了:两连短震)
t+ 55.0s ▶ 语音:「需要帮助吗?」
t+ 55.0s 〰 震动:warning(警告三连震)
t+ 57.1s ▶ 语音:「钥匙就在你身上。它能打开某一把锁。」
t+ 57.1s 〰 震动:warning(警告三连震)
#### 【玩家动作】又撞了一下——听到游戏主动来问我了
@@ -106,4 +107,4 @@ t+ 77.1s ♪ 音效 sfx/unlock
t+ 77.1s ♪ 音效 sfx/door_creak
t+ 77.1s ▶ 语音:「钥匙插进锁孔,缓缓转动。咔哒一声,柜门开了。柜子深处,躺着一枚保险丝。保险丝到手了。」
t+ 77.1s 〰 震动:success(成功长震)
t+ 85.0s 【状态】柜子开了,拿到保险丝——解到第二间,收工 探索中,我在 (2,1)。四向滑挪步,单击摸一遍房间,双击用物品(BGM=bgm/drone)
t+ 85.0s 【状态】柜子开了,拿到保险丝——解到第二间,收工 探索中,我在 (2,1)。四向滑挪步,单击摸一遍房间,双击用物品(BGM=bgm/drone,语速1)
+143 -136
View File
@@ -6,165 +6,172 @@
t+ 0.0s (玩家点开游戏,轻触屏幕开始)
t+ 0.0s ▶ 语音:「欢迎来到节拍行者。跟着音乐,我奏一句,你跟一句。」
t+ 8.0s ▶ 语音:「听,底鼓。在下一小节的同一个位置,单击。」
t+ 18.0s 【状态】教学开始了,先听领奏 教学:听领奏,下一小节照样打(tap)(语音播放中)
t+ 18.0s 【状态】教学第 1/3 句 教学:听领奏,下一小节照样打(tap)(语音播放中)
t+ 22.3s 〰 震动:beat(节拍点)
t+ 18.0s 【状态】教学开始了,先听领奏 教学:听领奏,下一小节照样打(tap)(语音播放中,语速1)
t+ 18.0s 【状态】教学第 1/3 句 教学:听领奏,下一小节照样打(tap)(语音播放中,语速1)
t+ 19.3s 〰 震动:beat(节拍点)
t+ 23.0s ▶ 语音:「再听一遍,跟着来。」
t+ 23.0s 〰 震动:error(做错了:两连短震)
t+ 25.1s ▶ 语音:「再听一遍,跟着来。」
t+ 27.2s ▶ 向导语音:「慢了半拍。跟着节拍走。」
t+ 30.0s 〰 震动:beat(节拍点)
t+ 33.6s ▶ 语音:「没关系,我们先走下一句。」
t+ 33.6s 〰 震动:error(做错了:两连短震)
t+ 35.8s ▶ 语音:「这次是左和右。跟着打,左滑,右滑。」
t+ 35.8s 〰 震动:error(做错了:两连短震)
t+ 35.8s 〰 震动:beat(节拍点)
t+ 35.8s 〰 震动:error(做错了:两连短震)
t+ 35.8s 【状态】教学第 2/3 句 教学:听领奏,下一小节照样打(tap)(语音播放中)
t+ 35.8s 【状态】教学第 2/3 句 教学:听领奏,下一小节照样打(tap)(语音播放中,语速1)
t+ 38.0s 〰 震动:beat(节拍点)
t+ 39.3s 〰 震动:beat(节拍点)
t+ 43.0s ▶ 语音:「再听一遍,跟着来。」
t+ 43.0s 〰 震动:error(做错了:两连短震)
t+ 45.1s ▶ 语音:「再听一遍,跟着来。」
t+ 47.2s ▶ 向导语音:「慢了半拍。跟着节拍走。」
t+ 48.7s 〰 震动:beat(节拍点)
t+ 50.0s 〰 震动:beat(节拍点)
t+ 53.6s ▶ 语音:「没关系,我们先走下一句。」
t+ 53.6s 〰 震动:error(做错了:两连短震)
t+ 55.8s ▶ 语音:「最后一句,三个音,中间是双击。」
t+ 55.8s 〰 震动:beat(节拍点)
t+ 55.8s 〰 震动:beat(节拍点)
t+ 55.8s 〰 震动:error(做错了:两连短震)
t+ 55.8s 〰 震动:beat(节拍点)
t+ 55.8s 〰 震动:beat(节拍点)
t+ 55.8s 〰 震动:error(做错了:两连短震)
t+ 55.8s 【状态】教学第 3/3 句 教学:听领奏,下一小节照样打(left→right)(语音播放中)
t+ 55.8s 【状态】教学第 3/3 句 教学:听领奏,下一小节照样打(left→right)(语音播放中,语速1)
t+ 55.8s ♪ 音效 sfx/inst_hat_l
t+ 55.8s ♪ 音效 sfx/inst_hat_r
t+ 59.3s 〰 震动:beat(节拍点)
t+ 60.0s 〰 震动:beat(节拍点)
t+ 60.7s 〰 震动:beat(节拍点)
t+ 64.3s ▶ 语音:「再听一遍,跟着来。」
t+ 64.3s 〰 震动:error(做错了:两连短震)
t+ 66.4s ▶ 语音:「再听一遍,跟着来。」
t+ 68.6s ▶ 向导语音:「慢了半拍。跟着节拍走。」
t+ 70.0s 〰 震动:beat(节拍点)
t+ 70.7s 〰 震动:beat(节拍点)
t+ 71.3s 〰 震动:beat(节拍点)
t+ 75.0s ▶ 语音:「没关系,我们先走下一句。」
t+ 75.0s 〰 震动:error(做错了:两连短震)
t+ 77.1s ▶ 语音:「很好,演出开始。」
t+ 79.2s ♪ 向导音:battle(战斗/对抗)
t+ 79.2s 〰 震动:beat(节拍点)
t+ 79.2s 〰 震动:beat(节拍点)
t+ 79.2s 〰 震动:beat(节拍点)
t+ 79.2s 〰 震动:error(做错了:两连短震)
t+ 79.2s 〰 震动:beat(节拍点)
t+ 79.2s 〰 震动:beat(节拍点)
t+ 79.2s 〰 震动:beat(节拍点)
t+ 79.2s 〰 震动:error(做错了:两连短震)
t+ 79.2s 【状态】教学完毕,曲目一《启程》90 拍开始 演奏中(启程):还剩 31 个应答音。领奏停了、轮到空拍,就该我出手了(无 BGM)
t+ 79.2s 【状态】教学完毕,曲目一《启程》90 拍开始 演奏中(启程):还剩 31 个应答音。领奏停了、轮到空拍,就该我出手了(无 BGM,语速1)
t+ 81.7s ♪ 向导音:menu(系统界面)
t+ 81.7s ▶ 语音:「菜单。单击切换选项,双击确认。」
t+ 81.7s 【状态】曲中暂停,进了菜单 暂停菜单:单击切选项,双击确认,上滑帮助,下滑恢复演奏(语音播放中)
t+ 81.7s 【状态】曲中暂停,进了菜单 暂停菜单:单击切选项,双击确认,上滑帮助,下滑恢复演奏(语音播放中,语速1)
t+ 81.7s ▶ 语音:「回大厅。」
#### 【玩家动作】还是想打完这首,下滑回去
t+ 87.1s ……… 静默 5s(还在数拍子,第一句没敢出手) ………
t+ 91.0s ♪ 向导音:turn(轮到你)
t+ 91.0s ▶ 向导语音:「现在跟着我打。」
t+ 93.0s ▶ 向导语音:「慢了半拍,下一小节跟上我就行。」
t+ 93.0s 〰 震动:beat(节拍点)
t+ 93.0s 〰 震动:beat(节拍点)
t+ 93.0s 〰 震动:error(做错了:两连短震)
t+ 93.0s 〰 震动:error(做错了:两连短震)
t+ 93.0s 【状态】第一句全漏了,听到「慢了半拍,下一小节跟上我就行」 演奏中(启程):还剩 29 个应答音。领奏停了、轮到空拍,就该我出手了(语音播放中)
t+ 91.0s 〰 震动:beat(节拍点)
t+ 92.3s 〰 震动:beat(节拍点)
t+ 93.7s ▶ 向导语音:「现在跟着我打。」
t+ 93.7s ♪ 向导音:turn(轮到你)
t+ 94.3s 〰 震动:error(做错了:两连短震)
t+ 95.6s ▶ 向导语音(对白在播,本句按 idle-only 跳过未播):「慢了半拍,下一小节跟上我就行。」
t+ 95.6s 〰 震动:error(做错了:两连短震)
t+ 95.6s 【状态】第一句全漏了,听到「慢了半拍,下一小节跟上我就行」 演奏中(启程):还剩 29 个应答音。领奏停了、轮到空拍,就该我出手了(语音播放中,语速1)
t+ 99.0s 〰 震动:beat(节拍点)
t+ 99.0s 〰 震动:beat(节拍点)
t+ 99.0s ♪ 音效 sfx/inst_hat_l
t+ 99.0s ♪ 音效 sfx/inst_accent
t+ 100.3s ♪ 音效 sfx/inst_hat_r
t+ 100.3s ♪ 音效 sfx/inst_accent
t+ 100.3s 〰 震动:自定义震型 [20,40,20]
t+ 107.3s 〰 震动:beat(节拍点)
t+ 107.3s 〰 震动:beat(节拍点)
t+ 107.3s 〰 震动:beat(节拍点)
t+ 107.3s ♪ 音效 sfx/inst_kick
t+ 107.3s ♪ 音效 sfx/inst_accent
t+ 107.6s ♪ 音效 sfx/inst_snare
t+ 107.6s ♪ 音效 sfx/inst_accent
t+ 108.6s ♪ 音效 sfx/inst_kick
t+ 108.6s ♪ 音效 sfx/inst_accent
t+ 108.6s 〰 震动:自定义震型 [20,40,20]
t+ 100.3s 〰 震动:beat(节拍点)
t+ 101.6s ♪ 音效 sfx/inst_hat_l
t+ 101.6s ♪ 音效 sfx/inst_accent
t+ 103.0s ♪ 音效 sfx/inst_hat_r
t+ 103.0s ♪ 音效 sfx/inst_accent
t+ 103.0s 〰 震动:自定义震型 [20,40,20]
t+ 107.0s 〰 震动:beat(节拍点)
t+ 107.7s 〰 震动:beat(节拍点)
t+ 108.3s 〰 震动:beat(节拍点)
t+ 110.0s ♪ 音效 sfx/inst_kick
t+ 110.0s ♪ 音效 sfx/inst_accent
t+ 110.3s ♪ 音效 sfx/inst_snare
t+ 110.3s ♪ 音效 sfx/inst_accent
t+ 111.3s ♪ 音效 sfx/inst_kick
t+ 111.3s ♪ 音效 sfx/inst_accent
t+ 111.3s 〰 震动:自定义震型 [20,40,20]
t+ 115.0s 〰 震动:beat(节拍点)
t+ 115.0s 〰 震动:beat(节拍点)
t+ 115.0s 〰 震动:beat(节拍点)
t+ 115.0s ♪ 音效 sfx/inst_hat_l
t+ 115.0s ♪ 音效 sfx/inst_accent
t+ 115.6s ♪ 音效 sfx/inst_snare
t+ 115.6s ♪ 音效 sfx/inst_accent
t+ 116.3s ♪ 音效 sfx/inst_hat_r
t+ 116.3s ♪ 音效 sfx/inst_accent
t+ 116.3s 〰 震动:自定义震型 [20,40,20]
t+ 123.3s 〰 震动:beat(节拍点)
t+ 123.3s 〰 震动:beat(节拍点)
t+ 123.3s 〰 震动:beat(节拍点)
t+ 123.3s ♪ 音效 sfx/inst_kick
t+ 123.3s ♪ 音效 sfx/inst_accent
t+ 124.3s ♪ 音效 sfx/inst_hat_l
t+ 124.3s ♪ 音效 sfx/inst_accent
t+ 125.0s ♪ 音效 sfx/inst_snare
t+ 125.0s ♪ 音效 sfx/inst_accent
t+ 125.0s 〰 震动:自定义震型 [20,40,20]
t+ 125.7s ▶ 语音:「过半了,保持。」
t+ 115.7s 〰 震动:beat(节拍点)
t+ 116.3s 〰 震动:beat(节拍点)
t+ 117.6s ♪ 音效 sfx/inst_hat_l
t+ 117.6s ♪ 音效 sfx/inst_accent
t+ 118.3s ♪ 音效 sfx/inst_snare
t+ 118.3s ♪ 音效 sfx/inst_accent
t+ 119.0s ♪ 音效 sfx/inst_hat_r
t+ 119.0s ♪ 音效 sfx/inst_accent
t+ 119.0s 〰 震动:自定义震型 [20,40,20]
t+ 123.0s 〰 震动:beat(节拍点)
t+ 124.3s 〰 震动:beat(节拍点)
t+ 125.0s 〰 震动:beat(节拍点)
t+ 126.0s ♪ 音效 sfx/inst_kick
t+ 126.0s ♪ 音效 sfx/inst_accent
t+ 127.0s ♪ 音效 sfx/inst_hat_l
t+ 127.0s ♪ 音效 sfx/inst_accent
t+ 127.6s ♪ 音效 sfx/inst_snare
t+ 127.6s ♪ 音效 sfx/inst_accent
t+ 127.6s 〰 震动:自定义震型 [20,40,20]
t+ 128.4s ▶ 语音:「过半了,保持。」
t+ 128.3s 〰 震动:beat(节拍点)
t+ 128.3s 〰 震动:beat(节拍点)
t+ 128.3s 〰 震动:beat(节拍点)
t+ 128.3s ♪ 音效 sfx/inst_hat_r
t+ 128.3s ♪ 音效 sfx/inst_accent
t+ 130.0s ♪ 音效 sfx/inst_kick
t+ 130.0s ♪ 音效 sfx/inst_accent
t+ 130.3s ♪ 音效 sfx/inst_snare
t+ 130.3s ♪ 音效 sfx/inst_accent
t+ 130.3s 〰 震动:自定义震型 [20,40,20]
t+ 133.6s 〰 震动:beat(节拍点)
t+ 133.6s 〰 震动:beat(节拍点)
t+ 133.6s 〰 震动:beat(节拍点)
t+ 133.6s ♪ 音效 sfx/inst_tom_hi
t+ 133.6s ♪ 音效 sfx/inst_accent
t+ 135.0s ♪ 音效 sfx/inst_tom_lo
t+ 135.0s ♪ 音效 sfx/inst_accent
t+ 135.0s 〰 震动:success(成功长震)
t+ 135.6s ♪ 音效 sfx/inst_snare
t+ 135.6s ♪ 音效 sfx/inst_accent
t+ 135.6s 〰 震动:自定义震型 [20,40,20]
t+ 139.3s 〰 震动:beat(节拍点)
t+ 139.3s 〰 震动:beat(节拍点)
t+ 139.3s 〰 震动:beat(节拍点)
t+ 139.3s 〰 震动:beat(节拍点)
t+ 139.3s ♪ 音效 sfx/inst_kick
t+ 139.3s ♪ 音效 sfx/inst_accent
t+ 139.6s ♪ 音效 sfx/inst_tom_hi
t+ 139.6s ♪ 音效 sfx/inst_accent
t+ 140.3s ♪ 音效 sfx/inst_hat_r
t+ 140.3s ♪ 音效 sfx/inst_accent
t+ 141.0s ♪ 音效 sfx/inst_snare
t+ 141.0s ♪ 音效 sfx/inst_accent
t+ 141.0s 〰 震动:自定义震型 [20,40,20]
t+ 129.7s 〰 震动:beat(节拍点)
t+ 130.3s 〰 震动:beat(节拍点)
t+ 131.0s ♪ 音效 sfx/inst_hat_r
t+ 131.0s ♪ 音效 sfx/inst_accent
t+ 132.6s ♪ 音效 sfx/inst_kick
t+ 132.6s ♪ 音效 sfx/inst_accent
t+ 133.0s ♪ 音效 sfx/inst_snare
t+ 133.0s ♪ 音效 sfx/inst_accent
t+ 133.0s 〰 震动:自定义震型 [20,40,20]
t+ 133.7s 〰 震动:beat(节拍点)
t+ 135.0s 〰 震动:beat(节拍点)
t+ 135.7s 〰 震动:beat(节拍点)
t+ 136.3s ♪ 音效 sfx/inst_tom_hi
t+ 136.3s ♪ 音效 sfx/inst_accent
t+ 137.6s ♪ 音效 sfx/inst_tom_lo
t+ 137.6s ♪ 音效 sfx/inst_accent
t+ 137.6s 〰 震动:success(成功长震)
t+ 138.3s ♪ 音效 sfx/inst_snare
t+ 138.3s ♪ 音效 sfx/inst_accent
t+ 138.3s 〰 震动:自定义震型 [20,40,20]
t+ 139.0s 〰 震动:beat(节拍点)
t+ 139.7s 〰 震动:beat(节拍点)
t+ 140.3s 〰 震动:beat(节拍点)
t+ 141.0s 〰 震动:beat(节拍点)
t+ 142.0s ♪ 音效 sfx/inst_kick
t+ 142.0s ♪ 音效 sfx/inst_accent
t+ 142.3s ♪ 音效 sfx/inst_tom_hi
t+ 142.3s ♪ 音效 sfx/inst_accent
t+ 143.0s ♪ 音效 sfx/inst_hat_r
t+ 143.0s ♪ 音效 sfx/inst_accent
t+ 143.6s ♪ 音效 sfx/inst_snare
t+ 143.6s ♪ 音效 sfx/inst_accent
t+ 143.6s 〰 震动:自定义震型 [20,40,20]
t+ 144.3s 〰 震动:beat(节拍点)
t+ 144.3s 〰 震动:beat(节拍点)
t+ 144.3s 〰 震动:beat(节拍点)
t+ 144.3s 〰 震动:beat(节拍点)
t+ 144.3s ♪ 音效 sfx/inst_hat_l
t+ 144.3s ♪ 音效 sfx/inst_accent
t+ 145.0s ♪ 音效 sfx/inst_hat_r
t+ 145.0s ♪ 音效 sfx/inst_accent
t+ 146.0s ♪ 音效 sfx/inst_kick
t+ 146.0s ♪ 音效 sfx/inst_accent
t+ 146.3s ♪ 音效 sfx/inst_snare
t+ 146.3s ♪ 音效 sfx/inst_accent
t+ 146.3s 〰 震动:自定义震型 [20,40,20]
t+ 150.0s 〰 震动:beat(节拍点)
t+ 150.0s 〰 震动:beat(节拍点)
t+ 150.0s 〰 震动:beat(节拍点)
t+ 150.0s 〰 震动:beat(节拍点)
t+ 150.0s ♪ 音效 sfx/inst_kick
t+ 150.0s ♪ 音效 sfx/inst_accent
t+ 150.3s ♪ 音效 sfx/inst_snare
t+ 150.3s ♪ 音效 sfx/inst_accent
t+ 151.3s ♪ 音效 sfx/inst_kick
t+ 151.3s ♪ 音效 sfx/inst_accent
t+ 151.6s ♪ 音效 sfx/inst_snare
t+ 151.6s ♪ 音效 sfx/inst_accent
t+ 151.6s 〰 震动:自定义震型 [20,40,20]
t+ 152.7s ♪ 向导音:settle(结算)
t+ 152.7s ▶ 语音:「A 级!准确率超过八成五,非常出色。」
t+ 152.7s 〰 震动:success(成功长震)
t+ 152.7s 〰 震动:success(成功长震)
t+ 152.7s 〰 震动:自定义震型 [20,40,20]
t+ 154.8s ▶ 语音:「新连击纪录!」
t+ 157.0s ▶ 语音:「新曲目解锁,追风!」
t+ 159.1s ▶ 语音:「单击再来一遍,右滑下一首,下滑回大厅。」
t+ 161.2s ♫ BGM 切换 → bgm/menu(淡入 800ms)
t+ 162.7s 【状态】曲子打完了,结算播报中 结算播报中:听完单击再来一遍,右滑下一首,下滑回大厅(BGM=bgm/menu)
t+ 145.0s 〰 震动:beat(节拍点)
t+ 145.7s 〰 震动:beat(节拍点)
t+ 146.3s 〰 震动:beat(节拍点)
t+ 147.0s ♪ 音效 sfx/inst_hat_l
t+ 147.0s ♪ 音效 sfx/inst_accent
t+ 147.6s ♪ 音效 sfx/inst_hat_r
t+ 147.6s ♪ 音效 sfx/inst_accent
t+ 148.6s ♪ 音效 sfx/inst_kick
t+ 148.6s ♪ 音效 sfx/inst_accent
t+ 149.0s ♪ 音效 sfx/inst_snare
t+ 149.0s ♪ 音效 sfx/inst_accent
t+ 149.0s 〰 震动:自定义震型 [20,40,20]
t+ 149.7s 〰 震动:beat(节拍点)
t+ 150.3s 〰 震动:beat(节拍点)
t+ 151.0s 〰 震动:beat(节拍点)
t+ 151.1s 〰 震动:beat(节拍点)
t+ 151.1s 〰 震动:beat(节拍点)
t+ 151.1s 〰 震动:beat(节拍点)
t+ 151.1s 〰 震动:beat(节拍点)
t+ 151.7s 〰 震动:beat(节拍点)
t+ 152.6s ♪ 音效 sfx/inst_kick
t+ 152.6s ♪ 音效 sfx/inst_accent
t+ 153.0s ♪ 音效 sfx/inst_snare
t+ 153.0s ♪ 音效 sfx/inst_accent
t+ 154.0s ♪ 音效 sfx/inst_kick
t+ 154.0s ♪ 音效 sfx/inst_accent
t+ 154.3s ♪ 音效 sfx/inst_snare
t+ 154.3s ♪ 音效 sfx/inst_accent
t+ 154.3s 〰 震动:自定义震型 [20,40,20]
t+ 155.4s ♪ 向导音:settle(结算)
t+ 155.4s ▶ 语音:「A 级!准确率超过八成五,非常出色。」
t+ 155.4s 〰 震动:success(成功长震)
t+ 155.4s 〰 震动:success(成功长震)
t+ 155.4s 〰 震动:自定义震型 [20,40,20]
t+ 157.5s ▶ 语音:「新连击纪录!」
t+ 159.6s ▶ 语音:「新曲目解锁,追风!」
t+ 161.8s ▶ 语音:「单击再来一遍,右滑下一首,下滑回大厅。」
t+ 163.9s ♫ BGM 切换 → bgm/menu(淡入 800ms)
t+ 165.4s 【状态】曲子打完了,结算播报中 结算播报中:听完单击再来一遍,右滑下一首,下滑回大厅(BGM=bgm/menu,语速1)
+76 -72
View File
@@ -13,95 +13,99 @@ t+ 10.6s ♫ BGM 切换 → bgm/village(淡入 800ms)
t+ 10.6s ♪ 向导音:explore(探索/安全)
t+ 10.6s ♪ 音效 sfx/step
t+ 10.6s ▶ 语音:「外乡人,林子里不太平。这五十金,拿着防身。」
t+ 10.6s 【状态】进村了,先听场景描述和选项 【雾口村】:左右滑切选项,单击确认,双击重听,上滑查状态,下滑菜单(语音播放中)
t+ 10.6s 【状态】进村了,先听场景描述和选项 【雾口村】:左右滑切选项,单击确认,双击重听,上滑查状态,下滑菜单(语音播放中,语速1)
t+ 10.6s ▶ 语音:「去商店。」
t+ 12.7s ▶ 语音:「休息一晚。」
t+ 14.8s ▶ 语音:「查看状态。」
t+ 17.0s 【状态】选项听了一圈,往林间小路去 【雾口村】:左右滑切选项,单击确认,双击重听,上滑查状态,下滑菜单(BGM=bgm/village)
t+ 30.6s ▶ 语音:「查看状态。」
t+ 32.7s ▶ 语音:「前进,去林间小路。」
t+ 34.8s ♫ BGM 切换 → bgm/explore(淡入 800ms)
t+ 34.8s ♪ 向导音:explore(探索/安全)
t+ 34.8s ♪ 音效 sfx/step
t+ 34.8s ▶ 语音:「林间小路。风穿过树叶,脚下的落叶沙沙作响。」
t+ 34.8s 【状态】林间小路 【林间小路】:左右滑切选项,单击确认,双击重听,上滑查状态,下滑菜单(语音播放中)
t+ 34.8s ▶ 语音:「调查四周。」
t+ 17.0s 【状态】选项听了一圈 【雾口村】:左右滑切选项,单击确认,双击重听,上滑查状态,下滑菜单(BGM=bgm/village,语速1)
t+ 17.0s ▶ 语音:「前进,去林间小路。」
#### 【玩家动作】滑到头再滑就兜回来了,有人提醒「回到第一个」,不用自己数
t+ 19.1s ▶ 向导语音:「回到第一个。」
t+ 21.2s ♫ BGM 切换 → bgm/explore(淡入 800ms)
t+ 21.2s ♪ 向导音:explore(探索/安全)
t+ 21.2s ♪ 音效 sfx/step
t+ 21.2s ▶ 语音:「林间小路。风穿过树叶,脚下的落叶沙沙作响。」
t+ 21.2s 【状态】林间小路 【林间小路】:左右滑切选项,单击确认,双击重听,上滑查状态,下滑菜单(语音播放中,语速1)
t+ 21.2s ▶ 语音:「调查四周。」
#### 【玩家动作】翻找了一阵,捡到一点钱
t+ 37.0s ♪ 音效 sfx/coin
t+ 37.0s ▶ 语音:「你在落叶下摸到十五枚金币。」
t+ 54.8s ▶ 语音:「调查四周。」
t+ 54.8s ▶ 语音:「前进,去岔路口。」
t+ 57.0s ♪ 向导音:explore(探索/安全)
t+ 57.0s ♪ 音效 sfx/step
t+ 57.0s ▶ 语音:「岔路口。风声在左右两边,听起来不太一样。」
t+ 57.0s 【状态】岔路口,水声在东边 【岔路口】:左右滑切选项,单击确认,双击重听,上滑查状态,下滑菜单(语音播放中)
t+ 57.0s ▶ 语音:「往右,去溪流。」
t+ 59.1s ♪ 向导音:explore(探索/安全)
t+ 59.1s ♪ 音效 sfx/step
t+ 59.1s ▶ 语音:「溪流。水声潺潺,是个歇脚的好地方。」
t+ 61.2s ♫ BGM 切换 → bgm/battle(淡入 400ms)
t+ 61.2s ♪ 向导音:battle(战斗/对抗)
t+ 61.2s ▶ 语音:「野狼拦路!」
t+ 63.3s ▶ 语音:「第一场战斗来了。别慌,战斗是回合制:你先出手,敌人再反击。」
t+ 65.4s ▶ 语音:「左右滑切换招式,攻击、破空斩、防御、金疮药。单击,出手。」
t+ 67.6s ▶ 语音:「敌人出手前有一声前摇。听到前摇,马上双击,就能格挡。」
t+ 69.7s ▶ 语音:「没把握就防御,伤害减半。金疮药可以回血。」
t+ 71.8s ▶ 语音:「记住:听清前摇,双击格挡。开始吧!」
t+ 73.9s ▶ 语音:「战斗里,左右滑选招式,单击出手。听到这个声音,就是你的回合。先滑一滑,试试。」
t+ 76.0s ♪ 音效 sfx/turn_player
t+ 76.0s 【状态】遭遇教学狼,战斗开始 战斗中:你的血量八成以上,敌方血量八成以上,等你选招式(左右滑切换,单击出手,下滑逃跑)(BGM=bgm/battle)
t+ 76.0s ▶ 语音:「破空斩。」
t+ 78.2s ▶ 语音:「防御。」
t+ 78.2s ▶ 语音:「挡住了!轮到你了。左右滑选攻击,单击出剑。」
t+ 80.3s 【状态】招式名听熟了 战斗中:你的血量八成以上,敌方血量八成以上,等你选招式(左右滑切换,单击出手,下滑逃跑)(语音播放中)
t+ 80.3s ▶ 语音:「金疮药。」
t+ 82.4s ▶ 语音:「攻击。」
t+ 23.3s ♪ 音效 sfx/coin
t+ 23.3s ▶ 语音:「你在落叶下摸到十五枚金币。」
t+ 25.4s ▶ 语音:「前进,去岔路口。」
t+ 27.6s ♪ 向导音:explore(探索/安全)
t+ 27.6s ♪ 音效 sfx/step
t+ 27.6s ▶ 语音:「岔路口。风声在左右两边,听起来不太一样。」
t+ 27.6s 【状态】岔路口,水声在东边 【岔路口】:左右滑切选项,单击确认,双击重听,上滑查状态,下滑菜单(语音播放中,语速1)
t+ 27.6s ▶ 语音:「往右,去溪流。」
t+ 29.7s ♪ 向导音:explore(探索/安全)
t+ 29.7s ♪ 音效 sfx/step
t+ 29.7s ▶ 语音:「溪流。水声潺潺,是个歇脚的好地方。」
t+ 31.8s ♫ BGM 切换 → bgm/battle(淡入 400ms)
t+ 31.8s ♪ 向导音:battle(战斗/对抗)
t+ 31.8s ▶ 语音:「野狼拦路!」
t+ 33.9s ▶ 语音:「第一场战斗来了。别慌,战斗是回合制:你先出手,敌人再反击。」
t+ 36.0s ▶ 语音:「左右滑切换招式,攻击、破空斩、防御、金疮药。单击,出手。」
t+ 38.2s ▶ 语音:「敌人出手前有一声前摇。听到前摇,马上双击,就能格挡。」
t+ 40.3s ▶ 语音:「没把握就防御,伤害减半。金疮药可以回血。」
t+ 42.4s ▶ 语音:「记住:听清前摇,双击格挡。开始吧!」
t+ 44.5s ▶ 语音:「战斗里,左右滑选招式,单击出手。听到这个声音,就是你的回合。先滑一滑,试试。」
t+ 46.6s ♪ 音效 sfx/turn_player
t+ 46.6s 【状态】遭遇教学狼,战斗开始 战斗中:你的血量八成以上,敌方血量八成以上,等你选招式(左右滑切换,单击出手,下滑逃跑)(BGM=bgm/battle,语速1)
t+ 46.6s ▶ 语音:「破空斩。」
t+ 48.8s ▶ 语音:「防御。」
t+ 48.8s ▶ 语音:「挡住了!轮到你了。左右滑选攻击,单击出剑。」
t+ 50.9s 【状态】招式名听熟了 战斗中:你的血量八成以上,敌方血量八成以上,等你选招式(左右滑切换,单击出手,下滑逃跑)(语音播放中,语速1)
t+ 50.9s ▶ 语音:「金疮药。」
t+ 53.0s ▶ 语音:「攻击。」
#### 【玩家动作】单击出手,攻击
t+ 84.0s ♪ 音效 sfx/atk_swing
t+ 84.0s ♪ 音效 sfx/atk_hit
t+ 84.0s ▶ 语音:「它要扑了,听前摇!」
t+ 86.2s ♪ 音效 sfx/turn_player
t+ 86.2s ▶ 语音:「就是现在,双击格挡!」
t+ 86.2s 〰 震动:beat(节拍点)
t+ 86.2s 【状态】前摇声响起了 战斗中:敌方前摇声响起了——双击,格挡!(语音播放中)
t+ 54.6s ♪ 音效 sfx/atk_swing
t+ 54.6s ♪ 音效 sfx/atk_hit
t+ 54.6s ▶ 语音:「它要扑了,听前摇!」
t+ 56.8s ♪ 音效 sfx/turn_player
t+ 56.8s ▶ 语音:「就是现在,双击格挡!」
t+ 56.8s 〰 震动:beat(节拍点)
t+ 56.8s 【状态】前摇声响起了 战斗中:敌方前摇声响起了——双击,格挡!(语音播放中,语速1)
#### 【玩家动作】双击格挡
#### 【玩家动作】双击格挡——这回干净挡下了,听到「挡得漂亮」
t+ 86.2s ♪ 音效 sfx/parry
t+ 86.2s ▶ 语音:「来不及格挡,就选防御。左右滑,选防御,单击。」
t+ 86.2s 〰 震动:confirm(轻点确认)
t+ 88.3s ♪ 音效 sfx/turn_player
t+ 88.3s ▶ 语音:「破空斩。」
t+ 90.4s ▶ 语音:「防御。」
t+ 56.8s ♪ 音效 sfx/parry
t+ 56.8s ▶ 语音:「挡得漂亮。」
t+ 56.8s 〰 震动:confirm(轻点确认)
t+ 58.9s ▶ 语音:「来不及格挡,就选防御。左右滑,选防御,单击。」
t+ 61.0s ♪ 音效 sfx/turn_player
t+ 61.0s ▶ 语音:「破空斩。」
t+ 62.6s ▶ 语音:「防御。」
#### 【玩家动作】这回合先防御,减半伤害
t+ 92.0s ▶ 语音:「好剑法!接下来,靠你自己了。」
t+ 94.2s 〰 震动:beat(节拍点)
t+ 64.8s ▶ 语音:「好剑法!接下来,靠你自己了。」
t+ 66.9s 〰 震动:beat(节拍点)
#### 【玩家动作】双击格挡
t+ 94.2s ♪ 音效 sfx/parry
t+ 94.2s ♪ 音效 sfx/turn_player
t+ 94.2s 〰 震动:confirm(轻点确认)
t+ 94.2s ▶ 语音:「金疮药。」
t+ 96.0s ▶ 语音:「攻击。」
t+ 66.9s ♪ 音效 sfx/parry
t+ 66.9s ♪ 音效 sfx/turn_player
t+ 66.9s 〰 震动:confirm(轻点确认)
t+ 66.9s ▶ 语音:「金疮药。」
t+ 68.7s ▶ 语音:「攻击。」
t+ 70.8s ▶ 向导语音:「回到第一个。」
#### 【玩家动作】单击,攻击
t+ 98.1s ♪ 音效 sfx/atk_swing
t+ 98.1s ♪ 音效 sfx/atk_hit
t+ 98.1s ♪ 音效 sfx/enemy_die
t+ 98.1s ▶ 语音:「敌人倒下了。」
t+ 98.1s 〰 震动:success(成功长震)
t+ 100.2s ♪ 音效 sfx/victory
t+ 100.2s ▶ 语音:「战斗胜利。」
t+ 102.3s ▶ 语音:「小有收获。」
t+ 104.4s ♫ BGM 切换 → bgm/explore(淡入 2000ms)
t+ 104.4s ♪ 向导音:explore(探索/安全)
t+ 104.4s ▶ 语音:「饮水休息。」
t+ 104.4s 【状态】狼打完了,胜利播报完回到溪流边。一场完整战斗到此收工 【溪流】:左右滑切选项,单击确认,双击重听,上滑查状态,下滑菜单(语音播放中)
t+ 70.8s ♪ 音效 sfx/atk_swing
t+ 70.8s ♪ 音效 sfx/atk_hit
t+ 70.8s ♪ 音效 sfx/enemy_die
t+ 70.8s ▶ 语音:「敌人倒下了。」
t+ 70.8s 〰 震动:success(成功长震)
t+ 72.9s ♪ 音效 sfx/victory
t+ 72.9s ▶ 语音:「战斗胜利。」
t+ 75.0s ▶ 语音:「小有收获。」
t+ 77.2s ♫ BGM 切换 → bgm/explore(淡入 2000ms)
t+ 77.2s ♪ 向导音:explore(探索/安全)
t+ 77.2s ▶ 语音:「饮水休息。」
t+ 77.2s 【状态】狼打完了,胜利播报完回到溪流边。一场完整战斗到此收工 【溪流】:左右滑切选项,单击确认,双击重听,上滑查状态,下滑菜单(语音播放中,语速1)
+75 -130
View File
@@ -10,19 +10,19 @@ t+ 4.7s ▶ 语音:「我会演奏一串声音,请你记住顺序,原
t+ 6.9s ▶ 语音:「向上滑,是这个声音。」
t+ 9.0s ♪ 音效 sfx/tone_up
t+ 9.9s ▶ 语音:「你试试。」
t+ 11.4s 【状态】教学第 1/6 个音色 教学:该做「上滑」了(语音播放中)
t+ 11.4s 【状态】教学第 1/6 个音色 教学:该做「上滑」了(语音播放中,语速1)
t+ 11.4s ♪ 音效 sfx/tone_up
t+ 11.4s ▶ 语音:「对了。」
t+ 13.5s ▶ 语音:「向下滑,是这个声音。」
t+ 15.6s ♪ 音效 sfx/tone_down
t+ 16.5s ▶ 语音:「你试试。」
t+ 18.6s 【状态】教学第 2/6 个音色 教学:该做「下滑」了(无 BGM)
t+ 18.6s 【状态】教学第 2/6 个音色 教学:该做「下滑」了(无 BGM,语速1)
t+ 18.6s ♪ 音效 sfx/tone_down
t+ 18.6s ▶ 语音:「对了。」
t+ 20.8s ▶ 语音:「向左滑,是这个声音。」
t+ 22.9s ♪ 音效 sfx/tone_left
t+ 23.8s ▶ 语音:「你试试。」
t+ 25.9s 【状态】教学第 3/6 个音色 教学:该做「左滑」了(无 BGM)
t+ 25.9s 【状态】教学第 3/6 个音色 教学:该做「左滑」了(无 BGM,语速1)
#### 【玩家动作】把「左滑」记混了,先来了个右滑
@@ -33,146 +33,91 @@ t+ 25.9s ▶ 语音:「对了。」
t+ 28.0s ▶ 语音:「向右滑,是这个声音。」
t+ 30.1s ♪ 音效 sfx/tone_right
t+ 31.0s ▶ 语音:「你试试。」
t+ 33.2s 【状态】教学第 4/6 个音色 教学:该做「右滑」了(无 BGM)
t+ 33.2s 【状态】教学第 4/6 个音色 教学:该做「右滑」了(无 BGM,语速1)
t+ 33.2s ♪ 音效 sfx/tone_right
t+ 33.2s ▶ 语音:「对了。」
t+ 35.3s ▶ 语音:「单击,是这个声音。」
t+ 37.4s ♪ 音效 sfx/tone_tap
t+ 38.3s ▶ 语音:「你试试。」
t+ 40.4s 【状态】教学第 5/6 个音色 教学:该做「单击」了(无 BGM)
t+ 40.4s 【状态】教学第 5/6 个音色 教学:该做「单击」了(无 BGM,语速1)
t+ 40.4s ♪ 音效 sfx/tone_tap
t+ 40.4s ▶ 语音:「对了。」
t+ 42.5s ▶ 语音:「双击,是这个声音。」
t+ 44.7s ♪ 音效 sfx/tone_doubletap
t+ 45.6s ▶ 语音:「你试试。」
t+ 47.7s 【状态】教学第 6/6 个音色 教学:该做「双击」了(无 BGM)
t+ 47.7s 【状态】教学第 6/6 个音色 教学:该做「双击」了(无 BGM,语速1)
t+ 47.7s ♪ 音效 sfx/tone_doubletap
t+ 47.7s ▶ 语音:「对了。」
t+ 49.8s ▶ 语音:「六种声音都记住了。后面的序列,会越来越长。」
t+ 51.9s ▶ 语音:「答错可以消耗机会重听。双击,可以随时重听。」
t+ 54.0s ▶ 语音:「第一轮,听好了。」
t+ 56.2s ♪ 音效 sfx/tone_up
t+ 56.9s ♪ 音效 sfx/tone_left
t+ 58.0s ♪ 向导音:turn(轮到你)
t+ 58.0s ▶ 语音:「轮到你了。」
t+ 70.1s ♪ 向导音:nudge(看门狗轻提醒)
t+ 70.1s ▶ 向导语音:「需要的话,双击可以重新听一遍序列。」
t+ 85.1s ♪ 向导音:nudge(看门狗轻提醒)
t+ 85.1s ♪ 向导音:watchdog(看门狗升级)
t+ 85.1s ♪ 音效 sfx/tone_up
t+ 85.8s ♪ 音效 sfx/tone_left
t+ 86.9s ♪ 向导音:turn(轮到你)
t+ 86.9s ▶ 语音:「轮到你了。」
t+ 99.0s ♪ 向导音:nudge(看门狗轻提醒)
t+ 99.0s ▶ 向导语音:「需要的话,双击可以重新听一遍序列。」
t+ 114.0s ♪ 向导音:nudge(看门狗轻提醒)
t+ 114.0s ♪ 向导音:watchdog(看门狗升级)
t+ 114.0s ♪ 音效 sfx/tone_up
t+ 114.7s ♪ 音效 sfx/tone_left
t+ 115.8s ♪ 向导音:turn(轮到你)
t+ 115.8s ▶ 语音:「轮到你了。」
t+ 127.9s ♪ 向导音:nudge(看门狗轻提醒)
t+ 127.9s ▶ 向导语音:「需要的话,双击可以重新听一遍序列。」
t+ 142.9s ♪ 向导音:nudge(看门狗轻提醒)
t+ 142.9s ♪ 向导音:watchdog(看门狗升级)
t+ 142.9s 〰 震动:beat(节拍点)
t+ 142.9s 〰 震动:beat(节拍点)
t+ 142.9s 〰 震动:confirm(轻点确认)
t+ 142.9s 〰 震动:beat(节拍点)
t+ 142.9s 〰 震动:beat(节拍点)
t+ 142.9s 〰 震动:confirm(轻点确认)
t+ 142.9s 〰 震动:beat(节拍点)
t+ 142.9s 〰 震动:beat(节拍点)
t+ 142.9s 〰 震动:confirm(轻点确认)
t+ 142.9s 【状态】轮到我复现了 第 1 轮轮到你了:已对上 0/2,照原顺序做手势(无 BGM)
t+ 142.9s ♪ 音效 sfx/tone_up
t+ 142.9s 【状态】轮到我复现了 第 1 轮轮到你了:已对上 1/2,照原顺序做手势(无 BGM)
t+ 142.9s ♪ 音效 sfx/tone_left
t+ 142.9s 〰 震动:success(成功长震)
t+ 143.2s ▶ 语音:「第二轮,听好了。」
t+ 145.3s ♪ 音效 sfx/tone_up
t+ 146.0s ♪ 音效 sfx/tone_left
t+ 146.7s ♪ 音效 sfx/tone_up
t+ 147.8s ♪ 向导音:turn(轮到你)
t+ 157.8s ♪ 向导音:nudge(看门狗轻提醒)
t+ 157.8s ▶ 向导语音:「需要的话,双击可以重新听一遍序列。」
t+ 172.8s ♪ 向导音:nudge(看门狗轻提醒)
t+ 172.8s ♪ 向导音:watchdog(看门狗升级)
t+ 172.8s 〰 震动:beat(节拍点)
t+ 172.8s 〰 震动:beat(节拍点)
t+ 172.8s 〰 震动:beat(节拍点)
t+ 172.8s 〰 震动:confirm(轻点确认)
t+ 172.8s 【状态】序列有点长,先僵一会儿 第 2 轮轮到你了:已对上 0/3,照原顺序做手势(无 BGM)
t+ 172.8s ……… 静默 11s(想不起来序列了) ………
t+ 172.8s 【状态】听到提示:需要的话,双击可以重新听一遍序列 第 2 轮轮到你了:已对上 0/3,照原顺序做手势(无 BGM)
t+ 172.8s 【状态】轮到我复现了 第 2 轮轮到你了:已对上 0/3,照原顺序做手势(无 BGM)
t+ 172.8s ♪ 音效 sfx/tone_up
t+ 54.0s ▶ 向导语音:「记不住就双击重新听,免费机会有两次,用完要扣一次机会。」
t+ 56.2s ▶ 语音:「第一轮,听好了。」
t+ 58.3s ♪ 音效 sfx/tone_doubletap
t+ 58.3s 〰 震动:beat(节拍点)
t+ 58.3s 【状态】教学讲完了,机会经济也交代清楚了:免费重听两次,用完要扣机会 第 1 轮序列演奏中(2 个音),记住顺序,先别动手(无 BGM,语速1)
t+ 59.0s ♪ 音效 sfx/tone_up
t+ 59.0s 〰 震动:beat(节拍点)
t+ 60.1s ♪ 向导音:turn(轮到你)
t+ 60.1s ▶ 语音:「轮到你了。」
t+ 60.1s 〰 震动:confirm(轻点确认)
t+ 62.2s 【状态】轮到我复现了 第 1 轮轮到你了:已对上 0/2,照原顺序做手势(无 BGM,语速1)
t+ 62.2s ♪ 音效 sfx/tone_doubletap
t+ 62.2s 【状态】轮到我复现了 第 1 轮轮到你了:已对上 1/2,照原顺序做手势(无 BGM,语速1)
t+ 62.2s ♪ 音效 sfx/tone_up
t+ 62.2s 〰 震动:success(成功长震)
t+ 62.5s ▶ 语音:「第二轮,听好了。」
t+ 64.6s ♪ 音效 sfx/tone_doubletap
t+ 64.6s 〰 震动:beat(节拍点)
t+ 65.3s ♪ 音效 sfx/tone_up
t+ 65.3s 〰 震动:beat(节拍点)
t+ 66.0s ♪ 音效 sfx/tone_doubletap
t+ 66.0s 〰 震动:beat(节拍点)
t+ 67.0s ♪ 向导音:turn(轮到你)
t+ 67.0s 〰 震动:confirm(轻点确认)
t+ 67.0s 【状态】轮到我复现了 第 2 轮轮到你了:已对上 0/3,照原顺序做手势(无 BGM,语速1)
t+ 67.0s ♪ 音效 sfx/tone_doubletap
t+ 67.0s 【状态】序列有点长,先僵一会儿 第 2 轮轮到你了:已对上 1/3,照原顺序做手势(无 BGM,语速1)
t+ 67.0s ……… 静默 10s(想不起来序列了) ………
t+ 72.2s 【状态】听到提示:需要的话,双击可以重新听一遍序列 第 2 轮轮到你了:已对上 1/3,照原顺序做手势(无 BGM,语速1)
#### 【玩家动作】这一下故意点错,听听游戏怎么说
#### 【玩家动作】照提示双击,用掉一次免费重听
t+ 172.8s ♪ 音效 sfx/tone_right
t+ 172.8s ▶ 语音:「错了。用一次机会,再听一遍。」
t+ 172.8s 〰 震动:error(做错了:两连短震)
t+ 174.9s ▶ 语音:「重听机会用完了,小心。」
t+ 177.0s ♪ 音效 sfx/tone_up
t+ 177.7s ♪ 音效 sfx/tone_left
t+ 178.3s ♪ 音效 sfx/tone_up
t+ 179.4s ♪ 向导音:turn(轮到你)
t+ 189.4s ♪ 向导音:nudge(看门狗轻提醒)
t+ 189.4s ▶ 向导语音:「需要的话,双击可以重新听一遍序列。」
t+ 204.4s ♪ 向导音:nudge(看门狗轻提醒)
t+ 204.4s ♪ 向导音:watchdog(看门狗升级)
t+ 204.4s 〰 震动:warning(警告三连震)
t+ 204.4s 〰 震动:beat(节拍点)
t+ 204.4s 〰 震动:beat(节拍点)
t+ 204.4s 〰 震动:beat(节拍点)
t+ 204.4s 〰 震动:confirm(轻点确认)
t+ 204.4s 【状态】轮到我复现了 第 2 轮轮到你了:已对上 0/3,照原顺序做手势(无 BGM)
t+ 204.4s ♪ 音效 sfx/tone_up
t+ 204.4s 【状态】轮到我复现了 第 2 轮轮到你了:已对上 1/3,照原顺序做手势(无 BGM)
t+ 204.4s ♪ 音效 sfx/tone_left
t+ 204.4s 【状态】轮到我复现了 第 2 轮轮到你了:已对上 2/3,照原顺序做手势(无 BGM)
t+ 204.4s ♪ 音效 sfx/tone_up
t+ 204.4s 〰 震动:success(成功长震)
t+ 204.7s ▶ 语音:「第三轮,听好了。」
t+ 206.8s ♪ 音效 sfx/tone_up
t+ 207.5s ♪ 音效 sfx/tone_left
t+ 208.1s ♪ 音效 sfx/tone_up
t+ 208.8s ♪ 音效 sfx/tone_left
t+ 209.8s ♪ 向导音:turn(轮到你)
t+ 219.8s ♪ 向导音:nudge(看门狗轻提醒)
t+ 219.8s ▶ 向导语音:「需要的话,双击可以重新听一遍序列。」
t+ 234.8s ♪ 向导音:nudge(看门狗轻提醒)
t+ 234.8s ♪ 向导音:watchdog(看门狗升级)
t+ 234.8s 〰 震动:beat(节拍点)
t+ 234.8s 〰 震动:beat(节拍点)
t+ 234.8s 〰 震动:beat(节拍点)
t+ 234.8s 〰 震动:beat(节拍点)
t+ 234.8s 〰 震动:confirm(轻点确认)
t+ 234.8s 【状态】轮到我复现了 第 3 轮轮到你了:已对上 0/4,照原顺序做手势(无 BGM)
t+ 234.8s ♪ 音效 sfx/tone_up
t+ 234.8s 【状态】轮到我复现了 第 3 轮轮到你了:已对上 1/4,照原顺序做手势(无 BGM)
t+ 234.8s ♪ 音效 sfx/tone_left
t+ 234.8s 【状态】轮到我复现了 第 3 轮轮到你了:已对上 2/4,照原顺序做手势(无 BGM)
t+ 234.8s ♪ 音效 sfx/tone_up
t+ 234.8s 【状态】轮到我复现了 第 3 轮轮到你了:已对上 3/4,照原顺序做手势(无 BGM)
t+ 234.8s ♪ 音效 sfx/tone_left
t+ 234.8s 〰 震动:success(成功长震)
t+ 235.1s ▶ 语音:「第四轮,听好了。」
t+ 237.3s ♪ 音效 sfx/tone_up
t+ 237.9s ♪ 音效 sfx/tone_left
t+ 238.5s ♪ 音效 sfx/tone_up
t+ 239.1s ♪ 音效 sfx/tone_left
t+ 239.7s ♪ 音效 sfx/tone_down
t+ 240.8s ♪ 向导音:turn(轮到你)
t+ 250.8s ♪ 向导音:nudge(看门狗轻提醒)
t+ 250.8s ▶ 向导语音:「需要的话,双击可以重新听一遍序列。」
t+ 265.8s ♪ 向导音:nudge(看门狗轻提醒)
t+ 265.8s ♪ 向导音:watchdog(看门狗升级)
t+ 265.8s 〰 震动:beat(节拍点)
t+ 265.8s 〰 震动:beat(节拍点)
t+ 265.8s 〰 震动:beat(节拍点)
t+ 265.8s 〰 震动:beat(节拍点)
t+ 265.8s 〰 震动:beat(节拍点)
t+ 265.8s 〰 震动:confirm(轻点确认)
t+ 265.8s 【状态】收工:完成了 3 轮 第 4 轮轮到你了:已对上 0/5,照原顺序做手势(无 BGM)
t+ 72.2s ▶ 语音:「再听一遍。」
t+ 72.2s 〰 震动:pickup(获得)
t+ 74.3s ▶ 语音:「重听机会用完了,小心。」
t+ 76.4s ♪ 音效 sfx/tone_doubletap
t+ 76.4s 〰 震动:beat(节拍点)
t+ 77.1s ♪ 音效 sfx/tone_up
t+ 77.8s ♪ 音效 sfx/tone_doubletap
t+ 77.1s 〰 震动:beat(节拍点)
t+ 77.8s 〰 震动:beat(节拍点)
t+ 78.9s ♪ 向导音:turn(轮到你)
t+ 78.9s 〰 震动:confirm(轻点确认)
t+ 78.9s 【状态】轮到我复现了 第 2 轮轮到你了:已对上 0/3,照原顺序做手势(无 BGM,语速1)
t+ 78.9s ♪ 音效 sfx/tone_doubletap
t+ 78.9s 【状态】轮到我复现了 第 2 轮轮到你了:已对上 1/3,照原顺序做手势(无 BGM,语速1)
t+ 78.9s ♪ 音效 sfx/tone_up
t+ 78.9s 【状态】轮到我复现了 第 2 轮轮到你了:已对上 2/3,照原顺序做手势(无 BGM,语速1)
t+ 78.9s ♪ 音效 sfx/tone_doubletap
t+ 78.9s 〰 震动:success(成功长震)
t+ 79.2s ▶ 语音:「第三轮,听好了。」
t+ 81.3s ♪ 音效 sfx/tone_doubletap
t+ 81.3s 〰 震动:beat(节拍点)
t+ 81.9s ♪ 音效 sfx/tone_up
t+ 81.9s 〰 震动:beat(节拍点)
t+ 82.6s ♪ 音效 sfx/tone_doubletap
t+ 82.6s 〰 震动:beat(节拍点)
t+ 83.2s ♪ 音效 sfx/tone_up
t+ 83.2s 〰 震动:beat(节拍点)
t+ 84.3s ♪ 向导音:turn(轮到你)
t+ 84.3s 〰 震动:confirm(轻点确认)
t+ 84.3s 【状态】轮到我复现了 第 3 轮轮到你了:已对上 0/4,照原顺序做手势(无 BGM,语速1)
t+ 84.3s ♪ 音效 sfx/tone_doubletap
t+ 84.3s 【状态】轮到我复现了 第 3 轮轮到你了:已对上 1/4,照原顺序做手势(无 BGM,语速1)
t+ 84.3s ♪ 音效 sfx/tone_up
t+ 84.3s 【状态】轮到我复现了 第 3 轮轮到你了:已对上 2/4,照原顺序做手势(无 BGM,语速1)
t+ 84.3s ♪ 音效 sfx/tone_doubletap
t+ 84.3s 【状态】最后一轮最后一个音,打完这轮就收 第 3 轮轮到你了:已对上 3/4,照原顺序做手势(无 BGM,语速1)
t+ 84.3s ♪ 音效 sfx/tone_up
t+ 84.3s 〰 震动:success(成功长震)
t+ 84.3s 【状态】收工:完整打完 3 轮,在轮与轮的边界停手(免费重听剩 0 次) 刚答完一轮,播报中(无 BGM,语速1)
+132 -60
View File
@@ -9,7 +9,7 @@ t+ 2.1s ▶ 语音:「单击可跳过旁白。」
t+ 4.2s ▶ 语音:「陨石来了,会有声音告诉你方位。」
t+ 6.4s ▶ 语音:「听,左边有陨石。向左滑,把它拨开!」
t+ 8.5s ♪ 音效 sfx/whoosh_left
t+ 8.5s 【状态】演练 1/4:左侧有陨石 演练:左边有陨石在逼近,左边滑把它拨开(无 BGM)
t+ 8.5s 【状态】演练 1/4:左侧有陨石 演练:左边有陨石在逼近,左边滑把它拨开(无 BGM,语速1)
#### 【玩家动作】逼近音一响,同向滑开
@@ -17,7 +17,7 @@ t+ 8.5s ♪ 音效 sfx/dodge
t+ 8.5s ▶ 语音:「漂亮。」
t+ 10.6s ▶ 语音:「这次在右边。向右滑!」
t+ 12.7s ♪ 音效 sfx/whoosh_right
t+ 12.7s 【状态】演练 2/4:右侧有陨石 演练:右边有陨石在逼近,右边滑把它拨开(无 BGM)
t+ 12.7s 【状态】演练 2/4:右侧有陨石 演练:右边有陨石在逼近,右边滑把它拨开(无 BGM,语速1)
#### 【玩家动作】这颗犹豫了,没拨开——听当场重教
@@ -30,7 +30,7 @@ t+ 19.3s ♪ 音效 sfx/whoosh_right
t+ 19.3s ♪ 音效 sfx/dodge
t+ 19.3s ▶ 语音:「上面有东西,声音又尖又高。向上滑!」
t+ 21.5s ♪ 音效 sfx/whoosh_up
t+ 21.5s 【状态】演练 3/4:上侧有陨石 演练:上面有陨石在逼近,上面滑把它拨开(无 BGM)
t+ 21.5s 【状态】演练 3/4:上侧有陨石 演练:上面有陨石在逼近,上面滑把它拨开(无 BGM,语速1)
#### 【玩家动作】逼近音一响,同向滑开
@@ -38,90 +38,162 @@ t+ 21.5s ♪ 音效 sfx/dodge
t+ 21.5s ▶ 语音:「漂亮。」
t+ 23.6s ▶ 语音:「下面是低沉的轰隆。向下滑!」
t+ 25.7s ♪ 音效 sfx/whoosh_down
t+ 25.7s 【状态】演练 4/4:下侧有陨石 演练:下面有陨石在逼近,下面滑把它拨开(无 BGM)
t+ 25.7s 【状态】演练 4/4:下侧有陨石 演练:下面有陨石在逼近,下面滑把它拨开(无 BGM,语速1)
#### 【玩家动作】逼近音一响,同向滑开
t+ 25.7s ♪ 音效 sfx/dodge
t+ 25.7s ▶ 语音:「四个方向你都会了。引擎点火,出发!」
t+ 25.7s 【状态】演练做完了,要出发了 演练旁白在播,先听(语音播放中)
t+ 25.7s 【状态】演练做完了,要出发了 演练旁白在播,先听(语音播放中,语速1)
t+ 27.8s ♫ BGM 切换 → bgm/engine
t+ 27.8s ♪ 向导音:battle(战斗/对抗)
t+ 27.8s 【状态】出发。引擎声起来了,保护期里先练手 对局中:引擎声平稳,等下一颗(保护期)(BGM=bgm/engine)
t+ 29.3s ♪ 音效 sfx/whoosh_right
t+ 27.8s 【状态】出发。引擎声起来了,保护期里先练手 对局中:引擎声平稳,等下一颗(保护期)(BGM=bgm/engine,语速1)
t+ 29.3s ♪ 音效 sfx/whoosh_left
t+ 32.3s ♪ 音效 sfx/alarm
t+ 32.3s 〰 震动:danger(危险逼近)
t+ 32.8s ♪ 音效 sfx/alarm
t+ 32.8s 〰 震动:danger(危险逼近)
t+ 33.3s ♪ 音效 sfx/dodge
t+ 33.3s ♪ 音效 sfx/whoosh_up
t+ 36.3s ♪ 音效 sfx/alarm
t+ 36.3s 〰 震动:danger(危险逼近)
t+ 36.3s 〰 震动:danger(危险逼近)
t+ 36.8s ♪ 音效 sfx/alarm
t+ 36.8s 〰 震动:danger(危险逼近)
t+ 37.3s ♪ 音效 sfx/dodge
t+ 37.3s ♪ 音效 sfx/whoosh_left
t+ 37.3s ♪ 音效 sfx/whoosh_right
t+ 40.3s ♪ 音效 sfx/alarm
t+ 40.3s 〰 震动:danger(危险逼近)
t+ 40.3s 〰 震动:danger(危险逼近)
t+ 40.8s ♪ 音效 sfx/alarm
t+ 40.8s 〰 震动:danger(危险逼近)
t+ 41.3s ♪ 音效 sfx/dodge
t+ 41.3s ♪ 音效 sfx/whoosh_right
t+ 41.3s ♪ 音效 sfx/whoosh_down
t+ 44.3s ♪ 音效 sfx/alarm
t+ 44.3s 〰 震动:danger(危险逼近)
t+ 44.3s 〰 震动:danger(危险逼近)
t+ 44.8s ♪ 音效 sfx/alarm
t+ 44.8s 〰 震动:danger(危险逼近)
t+ 45.3s ♪ 音效 sfx/dodge
t+ 45.3s ♪ 音效 sfx/whoosh_down
t+ 45.3s ♪ 音效 sfx/whoosh_up
t+ 47.8s ▶ 语音:「试试单击,引擎会短促加速,直接冲过空隙。」
t+ 58.3s ♪ 音效 sfx/whoosh_left
t+ 61.3s ♪ 音效 sfx/alarm
t+ 61.3s 〰 震动:danger(危险逼近)
t+ 61.3s 〰 震动:danger(危险逼近)
t+ 61.8s ♪ 音效 sfx/alarm
t+ 62.3s ♪ 音效 sfx/dodge
t+ 62.3s ♪ 音效 sfx/whoosh_down
t+ 65.3s ♪ 音效 sfx/alarm
t+ 65.3s 〰 震动:danger(危险逼近)
t+ 65.3s 〰 震动:danger(危险逼近)
t+ 65.8s ♪ 音效 sfx/alarm
t+ 66.3s ♪ 音效 sfx/dodge
t+ 66.3s ♪ 音效 sfx/whoosh_down
t+ 69.3s ♪ 音效 sfx/alarm
t+ 69.3s 〰 震动:danger(危险逼近)
t+ 69.3s 〰 震动:danger(危险逼近)
t+ 69.8s ♪ 音效 sfx/alarm
t+ 70.3s ♪ 音效 sfx/dodge
t+ 70.3s ♪ 音效 sfx/whoosh_right
t+ 47.8s 【状态】游戏停下来教新功能 暂停了刷怪教新功能:单击试试加速(语音播放中,语速1)
#### 【玩家动作】照它说的单击加速
t+ 49.9s ♪ 音效 sfx/boost
t+ 49.9s ▶ 语音:「感觉到了吗?加速的时候,陨石撞不上你。」
t+ 52.4s ♪ 音效 sfx/whoosh_left
t+ 55.4s ♪ 音效 sfx/alarm
t+ 55.4s 〰 震动:danger(危险逼近)
t+ 55.9s ♪ 音效 sfx/alarm
t+ 55.9s 〰 震动:danger(危险逼近)
t+ 56.4s ♪ 音效 sfx/dodge
t+ 56.4s ♪ 音效 sfx/whoosh_left
t+ 59.4s ♪ 音效 sfx/alarm
t+ 59.4s 〰 震动:danger(危险逼近)
t+ 59.9s ♪ 音效 sfx/alarm
t+ 59.9s 〰 震动:danger(危险逼近)
t+ 60.4s ♪ 音效 sfx/dodge
t+ 60.4s ♪ 音效 sfx/whoosh_left
t+ 63.4s ♪ 音效 sfx/alarm
t+ 63.4s 〰 震动:danger(危险逼近)
t+ 63.9s ♪ 音效 sfx/alarm
t+ 63.9s 〰 震动:danger(危险逼近)
t+ 64.4s ♪ 音效 sfx/dodge
t+ 64.4s ♪ 音效 sfx/whoosh_left
t+ 67.4s ♪ 音效 sfx/alarm
t+ 67.4s 〰 震动:danger(危险逼近)
t+ 67.9s ♪ 音效 sfx/alarm
t+ 67.9s 〰 震动:danger(危险逼近)
t+ 68.4s ♪ 音效 sfx/dodge
t+ 68.4s ♪ 音效 sfx/whoosh_up
t+ 71.4s ♪ 音效 sfx/alarm
t+ 71.4s 〰 震动:danger(危险逼近)
t+ 71.9s ♪ 音效 sfx/alarm
t+ 71.9s 〰 震动:danger(危险逼近)
t+ 72.4s ♪ 音效 sfx/dodge
t+ 72.4s ♪ 音效 sfx/whoosh_down
t+ 72.8s ▶ 语音:「双击展开护盾,能挡下一次撞击。」
t+ 83.3s ♪ 音效 sfx/whoosh_left
t+ 86.3s ♪ 音效 sfx/alarm
t+ 86.3s 〰 震动:danger(危险逼近)
t+ 86.3s 〰 震动:danger(危险逼近)
t+ 86.8s ♪ 音效 sfx/alarm
t+ 87.3s ♪ 音效 sfx/dodge
t+ 87.3s ♪ 音效 sfx/whoosh_left
t+ 72.8s 【状态】游戏停下来教新功能 暂停了刷怪教新功能:双击展开护盾(语音播放中,语速1)
#### 【玩家动作】照它说的双击开护盾
t+ 74.9s ♪ 音效 sfx/shield
t+ 74.9s ▶ 语音:「护盾展开后会自动充能,好了我会告诉你。」
t+ 77.4s ♪ 音效 sfx/whoosh_right
t+ 80.4s ♪ 音效 sfx/alarm
t+ 80.4s 〰 震动:danger(危险逼近)
t+ 80.9s ♪ 音效 sfx/alarm
t+ 80.9s 〰 震动:danger(危险逼近)
t+ 81.4s ♪ 音效 sfx/dodge
t+ 81.4s ▶ 语音:「十颗了,稳。」
t+ 81.4s ♪ 音效 sfx/whoosh_down
t+ 81.4s 〰 震动:confirm(轻点确认)
t+ 84.4s ♪ 音效 sfx/alarm
t+ 84.4s 〰 震动:danger(危险逼近)
t+ 84.9s ♪ 音效 sfx/alarm
t+ 84.9s 〰 震动:danger(危险逼近)
t+ 85.4s ♪ 音效 sfx/dodge
t+ 85.4s ♪ 音效 sfx/whoosh_right
t+ 87.8s ▶ 语音:「训练护盾关闭。接下来,靠你自己了。」
t+ 90.3s ♪ 音效 sfx/alarm
t+ 90.3s 〰 震动:danger(危险逼近)
t+ 90.3s 〰 震动:warning(警告三连震)
t+ 90.3s 〰 震动:danger(危险逼近)
t+ 90.3s 【状态】保护期结束了,这颗我故意没动 对局中:1 颗陨石在逼近,听方位同向滑开(BGM=bgm/engine)
t+ 90.8s ♪ 音效 sfx/alarm
t+ 91.3s ♪ 音效 sfx/hit
t+ 91.3s ▶ 语音:「左边撞上的。听到左边的呼啸,向左滑。」
t+ 91.3s ▶ 向导语音:「被撞了。听方位,同向滑动就能拨开。」
t+ 91.3s 〰 震动:danger(危险逼近)
t+ 91.3s 〰 震动:hit(受击重震)
t+ 87.8s 〰 震动:warning(警告三连震)
t+ 88.4s ♪ 音效 sfx/alarm
t+ 88.4s 〰 震动:danger(危险逼近)
t+ 88.4s 【状态】保护期结束了,这颗我故意没动 对局中:1 颗陨石在逼近,听方位同向滑开(语音播放中,语速1)
t+ 88.9s ♪ 音效 sfx/alarm
t+ 88.9s 〰 震动:danger(危险逼近)
t+ 89.4s ♪ 音效 sfx/hit
t+ 89.4s ▶ 语音:「右边撞上的。听到右边的呼啸,向右滑。」
t+ 89.4s 〰 震动:hit(受击重震)
#### 【玩家动作】被撞了。先听到方位归因,又听到「同向滑动就能拨开」
t+ 91.3s ♪ 音效 sfx/whoosh_down
t+ 93.1s ♪ 音效 sfx/alarm
t+ 93.1s 〰 震动:danger(危险逼近)
t+ 93.6s ♪ 音效 sfx/alarm
t+ 94.1s ♪ 音效 sfx/dodge
t+ 94.5s ♪ 音效 sfx/whoosh_down
t+ 96.2s ♪ 音效 sfx/alarm
t+ 96.2s 〰 震动:danger(危险逼近)
t+ 96.2s 〰 震动:danger(危险逼近)
t+ 96.2s 【状态】躲过保护期、挨过一次撞,心跳有点快,先到这 对局中:1 颗陨石在逼近,听方位同向滑开(BGM=bgm/engine)
t+ 89.4s ♪ 音效 sfx/whoosh_up
t+ 91.2s ♪ 音效 sfx/alarm
t+ 91.2s 〰 震动:danger(危险逼近)
t+ 91.2s 【状态】又有一颗,也故意不躲——想听听第二次被撞会说什么 对局中:1 颗陨石在逼近,听方位同向滑开(语音播放中,语速1)
t+ 91.6s ▶ 向导语音:「被撞了。听方位,同向滑动就能拨开。」
t+ 91.7s ♪ 音效 sfx/alarm
t+ 91.7s 〰 震动:danger(危险逼近)
t+ 92.2s ♪ 音效 sfx/hit
t+ 92.2s ▶ 语音:「上面撞上的。听到高高的呼啸,向上滑。」
t+ 92.2s 〰 震动:hit(受击重震)
#### 【玩家动作】第二次被撞:先听方位归因,又听到「这是最后一次机会,撑住」——这就是全部家底了
t+ 92.6s ♪ 音效 sfx/whoosh_right
t+ 94.3s ▶ 语音:「船体只剩最后一次了,小心!」
t+ 94.4s ♪ 音效 sfx/alarm
t+ 94.9s ♪ 音效 sfx/alarm
t+ 94.4s 〰 震动:danger(危险逼近)
t+ 94.9s 〰 震动:danger(危险逼近)
t+ 95.4s ♪ 音效 sfx/dodge
t+ 95.7s ♪ 音效 sfx/whoosh_down
t+ 96.5s ▶ 向导语音:「这是最后一次机会,撑住。」
t+ 97.5s ♪ 音效 sfx/alarm
t+ 97.5s 〰 震动:danger(危险逼近)
t+ 98.0s ♪ 音效 sfx/alarm
t+ 98.5s ♪ 音效 sfx/dodge
t+ 98.8s ♪ 音效 sfx/whoosh_left
t+ 98.0s 〰 震动:danger(危险逼近)
t+ 100.5s ♪ 音效 sfx/alarm
t+ 100.5s 〰 震动:danger(危险逼近)
t+ 101.0s ♪ 音效 sfx/alarm
t+ 101.5s ♪ 音效 sfx/dodge
t+ 101.8s ♪ 音效 sfx/whoosh_up
t+ 101.0s 〰 震动:danger(危险逼近)
t+ 103.5s ♪ 音效 sfx/alarm
t+ 103.5s 〰 震动:danger(危险逼近)
t+ 104.0s ♪ 音效 sfx/alarm
t+ 104.5s ♪ 音效 sfx/dodge
t+ 104.8s ♪ 音效 sfx/whoosh_right
t+ 104.9s ▶ 语音:「护盾充能完毕。」
t+ 104.0s 〰 震动:danger(危险逼近)
t+ 104.9s 〰 震动:pickup(获得)
t+ 106.4s ♪ 音效 sfx/alarm
t+ 106.4s 〰 震动:danger(危险逼近)
t+ 106.9s ♪ 音效 sfx/alarm
t+ 106.9s 〰 震动:danger(危险逼近)
t+ 107.4s ♪ 音效 sfx/dodge
t+ 107.7s ♪ 音效 sfx/whoosh_down
t+ 109.3s ♪ 音效 sfx/alarm
t+ 109.3s 〰 震动:danger(危险逼近)
t+ 109.3s 【状态】保护期结束后撑了 22 秒、挨了两下还活着(警报也只起一波了),先到这 对局中:1 颗陨石在逼近,听方位同向滑开(BGM=bgm/engine,语速1)
+92 -171
View File
@@ -6,198 +6,119 @@
t+ 0.0s (玩家点开游戏,轻触屏幕开始)
t+ 0.0s ♫ BGM 切换 → bgm/dark_drone
t+ 0.0s ♪ 向导音:explore(探索/安全)
t+ 0.0s ▶ 语音:「大停电后的第三十天,城市陷入彻底的黑暗。没有电,没有网络,只有收音机里偶尔闪过的沙沙声,像垂死者的呼吸。你是林晚,一名前急救员。在这个故事里,你用声音活下去。请记住:单击屏幕,继续剧情;双击,重听上一句;向上滑,听你的状态和帮助;向下滑,呼出菜单。当我说“请选择”的时候,向左滑,或者向右滑,做出你的决定。选择一旦做出,就无法回头。现在,单击屏幕,睁开眼睛。」
t+ 2.1s ▶ 语音:「单击可跳过旁白。」
t+ 12.2s ♪ 向导音:nudge(看门狗轻提醒)
t+ 12.2s ▶ 向导语音:「轻触屏幕,继续。」
t+ 24.2s ♪ 向导音:nudge(看门狗轻提醒)
t+ 24.2s ♪ 向导音:watchdog(看门狗升级)
t+ 24.2s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 26.4s ▶ 语音:「当前进度:第一幕,城南地铁站附近。你的目标:活着离开这片废墟,前往北区的广播塔,寻找妹妹林晨的下落。」
t+ 28.5s ▶ 语音:「大停电后的第三十天,城市陷入彻底的黑暗。没有电,没有网络,只有收音机里偶尔闪过的沙沙声,像垂死者的呼吸。你是林晚,一名前急救员。在这个故事里,你用声音活下去。请记住:单击屏幕,继续剧情;双击,重听上一句;向上滑,听你的状态和帮助;向下滑,呼出菜单。当我说“请选择”的时候,向左滑,或者向右滑,做出你的决定。选择一旦做出,就无法回头。现在,单击屏幕,睁开眼睛。」
t+ 30.6s 【状态】开场旁白播完了,屏幕黑着,有点不知所措 剧情等着你(节点 n00):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 30.6s ……… 静默 8s(新手没反应过来) ………
t+ 30.6s 【状态】听到一声轻响和「轻触屏幕,继续」,试着点一下 剧情等着你(节点 n00):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 30.6s ♪ 音效 sfx/heartbeat
t+ 30.6s ▶ 语音:「后脑一阵钝痛,嘴里全是铁锈味。你在废弃的地铁站里醒来,四周黑得像沉在水底。背包还压在身下,手电筒却不知去向。远处滴水的声音,一下,一下,像某种倒计时。」
t+ 40.7s ♪ 向导音:nudge(看门狗轻提醒)
t+ 40.7s ▶ 向导语音:「轻触屏幕,继续。」
t+ 52.7s ♪ 向导音:nudge(看门狗轻提醒)
t+ 52.7s ♪ 向导音:watchdog(看门狗升级)
t+ 52.7s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 54.8s ▶ 语音:「当前进度:第一幕,城南地铁站附近。你的目标:活着离开这片废墟,前往北区的广播塔,寻找妹妹林晨的下落。」
t+ 57.0s ♪ 音效 sfx/heartbeat
t+ 57.0s ▶ 语音:「后脑一阵钝痛,嘴里全是铁锈味。你在废弃的地铁站里醒来,四周黑得像沉在水底。背包还压在身下,手电筒却不知去向。远处滴水的声音,一下,一下,像某种倒计时。」
t+ 59.1s 【状态】剧情停下来了,在等我 剧情等着你(节点 n01):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 0.0s ▶ 向导语音:「单击继续剧情。请选择的时候,左右滑动。」
t+ 2.1s ▶ 语音:「大停电后的第三十天,城市陷入彻底的黑暗。没有电,没有网络,只有收音机里偶尔闪过的沙沙声,像垂死者的呼吸。你是林晚,一名前急救员。在这个故事里,你用声音活下去。请记住:单击屏幕,继续剧情;双击,重听上一句;向上滑,听你的状态和帮助;向下滑,呼出菜单。当我说“请选择”的时候,向左滑,或者向右滑,做出你的决定。选择一旦做出,就无法回头。现在,单击屏幕,睁开眼睛。」
t+ 4.2s ▶ 语音:「单击可跳过旁白。」
t+ 6.4s 【状态】开场口令和旁白播完了,屏幕黑着,走神想剧情去了 剧情等着你(节点 n00):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 6.4s ……… 静默 25s(在入戏,不是迷路(round2:剧情类等待默认低频)) ………
t+ 31.4s ♪ 向导音:nudge(看门狗轻提醒)
t+ 31.4s ▶ 向导语音:「轻触屏幕,继续。」
t+ 33.5s 【状态】听到一声轻响和「轻触屏幕,继续」,马上点一下 剧情等着你(节点 n00):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 33.5s ♪ 音效 sfx/heartbeat
t+ 33.5s ▶ 语音:「后脑一阵钝痛,嘴里全是铁锈味。你在废弃的地铁站里醒来,四周黑得像沉在水底。背包还压在身下,手电筒却不知去向。远处滴水的声音,一下,一下,像某种倒计时。」
t+ 35.6s 【状态】剧情停下来了,在等我 剧情等着你(节点 n01):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
#### 【玩家动作】试着左滑了一下——这里用不上,向导纠正了我
t+ 59.1s ▶ 向导语音:「这个操作在这里用不上。」
t+ 59.1s 〰 震动:error(做错了:两连短震)
t+ 61.2s ▶ 语音:「咳……咳咳……我还活着。晨晨,你在哪儿……你说过,会在北区等我。姐一定,一定找到你。」
t+ 71.1s ▶ 语音:「请选择。向左滑:先检查背包里的收音机。向右滑:先摸黑寻找出口。」
t+ 73.2s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n02):左滑或右滑,做出你的决定(BGM=bgm/dark_drone)
t+ 35.6s ▶ 向导语音:「这个操作在这里用不上。」
t+ 35.6s 〰 震动:error(做错了:两连短震)
t+ 37.7s ▶ 语音:「咳……咳咳……我还活着。晨晨,你在哪儿……你说过,会在北区等我。姐一定,一定找到你。」
t+ 47.6s ▶ 语音:「请选择。向左滑:先检查背包里的收音机。向右滑:先摸黑寻找出口。」
t+ 49.7s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n02):左滑或右滑,做出你的决定(BGM=bgm/dark_drone,语速1)
#### 【玩家动作】选了左边
t+ 73.2s ♪ 音效 sfx/ui_confirm
t+ 73.2s ♪ 音效 sfx/radio_static
t+ 73.2s ▶ 语音:「收音机……还好,没摔坏。等等,这是什么……这段录音,是三天前的……」
t+ 81.0s ▶ 语音:「姐,如果你听到这个,说明你还活着,太好了。听着,跟着广播塔的红色信号灯走,别相信任何自称救援队的人。北区,北区有真正的避难所。我在这儿等你。姐,一定要来。」
t+ 106.7s ♪ 向导音:nudge(看门狗轻提醒)
t+ 106.7s ▶ 向导语音:「轻触屏幕,继续。」
t+ 118.7s ♪ 向导音:nudge(看门狗轻提醒)
t+ 118.7s ♪ 向导音:watchdog(看门狗升级)
t+ 118.7s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 120.8s ▶ 语音:「当前进度:第一幕,城南地铁站附近。你的目标:活着离开这片废墟,前往北区的广播塔,寻找妹妹林晨的下落。」
t+ 122.9s ♪ 音效 sfx/radio_static
t+ 122.9s ▶ 语音:「收音机……还好,没摔坏。等等,这是什么……这段录音,是三天前的……」
t+ 130.8s ▶ 语音:「姐,如果你听到这个,说明你还活着,太好了。听着,跟着广播塔的红色信号灯走,别相信任何自称救援队的人。北区,北区有真正的避难所。我在这儿等你。姐,一定要来。」
t+ 148.4s 【状态】剧情停下来了,在等我 剧情等着你(节点 n03):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 148.4s ……… 静默 8s(走神想剧情去了) ………
t+ 148.4s 【状态】又听到那声轻响,想起该点屏幕了 剧情等着你(节点 n03):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 148.4s ♪ 音效 sfx/radio_static
t+ 148.4s ▶ 语音:「就在这时,背包侧袋里的收音机,沙沙声忽然变得清晰。一个平稳的女声,一遍又一遍地重复着:」
t+ 150.5s ▶ 语音:「北区避难所,仍在接收幸存者。重复,北区避难所,仍在接收幸存者。坐标,三、七点、五。请沿广播塔红色信号灯方向前进。」
t+ 171.5s ♪ 向导音:nudge(看门狗轻提醒)
t+ 171.5s ▶ 向导语音:「轻触屏幕,继续。」
t+ 183.5s ♪ 向导音:nudge(看门狗轻提醒)
t+ 183.5s ♪ 向导音:watchdog(看门狗升级)
t+ 183.5s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 185.6s ▶ 语音:「当前进度:第一幕,城南地铁站附近。你的目标:活着离开这片废墟,前往北区的广播塔,寻找妹妹林晨的下落。」
t+ 187.7s ♪ 音效 sfx/radio_static
t+ 187.7s ▶ 语音:「就在这时,背包侧袋里的收音机,沙沙声忽然变得清晰。一个平稳的女声,一遍又一遍地重复着:」
t+ 189.8s ▶ 语音:「北区避难所,仍在接收幸存者。重复,北区避难所,仍在接收幸存者。坐标,三、七点、五。请沿广播塔红色信号灯方向前进。」
t+ 202.8s 【状态】剧情停下来了,在等我 剧情等着你(节点 n05):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 202.8s ♪ 音效 sfx/whistle
t+ 202.8s ▶ 语音:「你刚走上街面,黑暗里忽然响起一声口哨。接着是第二声,第三声,从不同的方向,把你围在中间。有人的手电光,扫过你的脸。」
t+ 212.9s ♪ 向导音:nudge(看门狗轻提醒)
t+ 212.9s ▶ 向导语音:「轻触屏幕,继续。」
t+ 224.9s ♪ 向导音:nudge(看门狗轻提醒)
t+ 224.9s ♪ 向导音:watchdog(看门狗升级)
t+ 224.9s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 227.0s ▶ 语音:「当前进度:第一幕,城南地铁站附近。你的目标:活着离开这片废墟,前往北区的广播塔,寻找妹妹林晨的下落。」
t+ 229.2s ♪ 音效 sfx/whistle
t+ 229.2s ▶ 语音:「你刚走上街面,黑暗里忽然响起一声口哨。接着是第二声,第三声,从不同的方向,把你围在中间。有人的手电光,扫过你的脸。」
t+ 231.3s 【状态】剧情停下来了,在等我 剧情等着你(节点 n06):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 231.3s ▶ 语音:「哟,瞧瞧,有肥羊自己送上门了。别跑啊。这片儿的路,都是我猎狗修的。想过去?行啊,把包留下,把你值钱的玩意儿留下。人嘛……也留下。」
t+ 246.0s ▶ 语音:「请选择。向左滑:钻进旁边的通风管道。向右滑:躺进路边的尸体堆里装死。」
t+ 248.1s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n07):左滑或右滑,做出你的决定(BGM=bgm/dark_drone)
t+ 49.7s ♪ 音效 sfx/ui_confirm
t+ 49.7s ♪ 音效 sfx/radio_static
t+ 49.7s ▶ 语音:「收音机……还好,没摔坏。等等,这是什么……这段录音,是三天前的……」
t+ 57.5s ▶ 语音:「姐,如果你听到这个,说明你还活着,太好了。听着,跟着广播塔的红色信号灯走,别相信任何自称救援队的人。北区,北区有真正的避难所。我在这儿等你。姐,一定要来。」
t+ 75.2s 【状态】剧情停下来了,在等我 剧情等着你(节点 n03):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 75.2s ……… 静默 25s(又走神想剧情去了) ………
t+ 100.2s ♪ 向导音:nudge(看门狗轻提醒)
t+ 100.2s ▶ 向导语音:「轻触屏幕,继续。」
t+ 102.3s 【状态】又听到那声轻响,5 秒内想起该点屏幕了 剧情等着你(节点 n03):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 102.3s ♪ 音效 sfx/radio_static
t+ 102.3s ▶ 语音:「就在这时,背包侧袋里的收音机,沙沙声忽然变得清晰。一个平稳的女声,一遍又一遍地重复着:」
t+ 104.4s ▶ 语音:「北区避难所,仍在接收幸存者。重复,北区避难所,仍在接收幸存者。坐标,三、七点、五。请沿广播塔红色信号灯方向前进。」
t+ 117.4s 【状态】剧情停下来了,在等我 剧情等着你(节点 n05):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 117.4s ♪ 音效 sfx/whistle
t+ 117.4s ▶ 语音:「你刚走上街面,黑暗里忽然响起一声口哨。接着是第二声,第三声,从不同的方向,把你围在中间。有人的手电光,扫过你的脸。」
t+ 119.5s 【状态】剧情停下来了,在等我 剧情等着你(节点 n06):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 119.5s ▶ 语音:「哟,瞧瞧,有肥羊自己送上门了。别跑啊。这片儿的路,都是我猎狗修的。想过去?行啊,把包留下,把你值钱的玩意儿留下。人嘛……也留下。」
t+ 134.2s ▶ 语音:「请选择。向左滑:钻进旁边的通风管道。向右滑:躺进路边的尸体堆里装死。」
t+ 136.4s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n07):左滑或右滑,做出你的决定(BGM=bgm/dark_drone,语速1)
#### 【玩家动作】选了左边
t+ 248.1s ♪ 音效 sfx/ui_confirm
t+ 248.1s ♪ 音效 sfx/rubble
t+ 248.1s ▶ 语音:「你一矮身,钻进了通风管道。铁皮贴着脊背,窄得几乎无法呼吸。身后传来咒骂和铁棍敲击管道口的声音,越来越近,又渐渐远去。十分钟后,你从两条街外的井口爬出来,肺里像塞满了沙子。」
t+ 258.3s ♪ 向导音:nudge(看门狗轻提醒)
t+ 258.3s ▶ 向导语音:「轻触屏幕,继续。」
t+ 270.3s ♪ 向导音:nudge(看门狗轻提醒)
t+ 270.3s ♪ 向导音:watchdog(看门狗升级)
t+ 270.3s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 272.4s ▶ 语音:「当前进度:第一幕,城南地铁站附近。你的目标:活着离开这片废墟,前往北区的广播塔,寻找妹妹林晨的下落。」
t+ 274.5s ♪ 音效 sfx/rubble
t+ 274.5s ▶ 语音:「你一矮身,钻进了通风管道。铁皮贴着脊背,窄得几乎无法呼吸。身后传来咒骂和铁棍敲击管道口的声音,越来越近,又渐渐远去。十分钟后,你从两条街外的井口爬出来,肺里像塞满了沙子。」
t+ 276.6s 【状态】剧情停下来了,在等我 剧情等着你(节点 n08):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 276.6s ……… 静默 8s(走神想剧情去了) ………
t+ 276.6s 【状态】又听到那声轻响,想起该点屏幕了 剧情等着你(节点 n08):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 276.6s ♪ 音效 sfx/wind
t+ 276.6s ▶ 语音:「酸雨开始下了。街面上升起细密的嘶嘶声,像无数条小蛇在游。雨幕那头,隐约传来一阵哭声——细细的,是个孩子。」
t+ 278.7s ▶ 语音:「请选择。向左滑:循着哭声过去看看。向右滑:不去管它,直接去北区。」
t+ 280.9s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n10):左滑或右滑,做出你的决定(BGM=bgm/dark_drone)
t+ 136.4s ♪ 音效 sfx/ui_confirm
t+ 136.4s ♪ 音效 sfx/rubble
t+ 136.4s ▶ 语音:「你一矮身,钻进了通风管道。铁皮贴着脊背,窄得几乎无法呼吸。身后传来咒骂和铁棍敲击管道口的声音,越来越近,又渐渐远去。十分钟后,你从两条街外的井口爬出来,肺里像塞满了沙子。」
t+ 138.5s 【状态】剧情停下来了,在等我 剧情等着你(节点 n08):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 138.5s ……… 静默 25s(又走神想剧情去了) ………
t+ 163.5s ♪ 向导音:nudge(看门狗轻提醒)
t+ 163.5s ▶ 向导语音:「轻触屏幕,继续。」
t+ 165.6s 【状态】又听到那声轻响,5 秒内想起该点屏幕了 剧情等着你(节点 n08):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 165.6s ♪ 音效 sfx/wind
t+ 165.6s ▶ 语音:「酸雨开始下了。街面上升起细密的嘶嘶声,像无数条小蛇在游。雨幕那头,隐约传来一阵哭声——细细的,是个孩子。」
t+ 167.7s ▶ 语音:「请选择。向左滑:循着哭声过去看看。向右滑:不去管它,直接去北区。」
#### 【玩家动作】旁白有点慢,去设置把语速调到 2.2(下一句生效)
#### 【玩家动作】选了左边(循声而去)——后面几步都是 2.2 档
t+ 169.8s ♪ 音效 sfx/ui_confirm
t+ 169.8s ▶ 语音:「哭声来自一间塌了一半的便利店。一个小男孩抱着膝盖坐在货架底下,身边躺着一个再也不会醒来的女人。他抬起头,脸上全是灰,小声说:“我叫小满。妈妈说,数到一千,她就会醒。我已经数到……数到好多个一千了。”」
t+ 170.9s ▶ 语音:「请选择。向左滑:带上小满一起走。向右滑:留下食物,独自上路。」
t+ 171.9s 【状态】语速 2.2 听着赶趟 「请选择」念完了(节点 n11):左滑或右滑,做出你的决定(BGM=bgm/dark_drone,语速2.2)
t+ 171.9s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n11):左滑或右滑,做出你的决定(BGM=bgm/dark_drone,语速2.2)
#### 【玩家动作】选了左边
t+ 280.9s ♪ 音效 sfx/ui_confirm
t+ 280.9s ▶ 语音:「哭声来自一间塌了一半的便利店。一个小男孩抱着膝盖坐在货架底下,身边躺着一个再也不会醒来的女人。他抬起头,脸上全是灰,小声说:“我叫小满。妈妈说,数到一千,她就会醒。我已经数到……数到好多个一千了。”」
t+ 283.0s ▶ 语音:「请选择。向左滑:带上小满一起走。向右滑:留下食物,独自上路。」
t+ 285.1s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n11):左滑或右滑,做出你的决定(BGM=bgm/dark_drone)
t+ 171.9s ♪ 音效 sfx/ui_confirm
t+ 171.9s ▶ 语音:「小满,别怕。数到一千太慢了。跟姐姐走,姐姐带你去找一座会发光的塔。抓紧我的手,别松开。」
#### 【玩家动作】听了两段,把语速调回 1.0
t+ 176.5s 【状态】剧情停下来了,在等我 剧情等着你(节点 n12):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 176.5s ♪ 音效 sfx/wind
t+ 176.5s ▶ 语音:「两个小时后,江水拦住了去路。跨江大桥断成两截,桥身斜插进黑沉沉的江面。要去广播塔,只剩两条路:桥下的污水涵洞,黑得看不见底;或者,桥上悬着的钢索,在风里荡来荡去。」
t+ 178.7s ▶ 语音:「请选择。向左滑:走桥下的污水涵洞。向右滑:爬桥上悬着的钢索。」
t+ 180.8s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n15):左滑或右滑,做出你的决定(BGM=bgm/dark_drone,语速1)
#### 【玩家动作】选了左边
t+ 285.1s ♪ 音效 sfx/ui_confirm
t+ 285.1s ▶ 语音:「小满,别怕。数到一千太慢了。跟姐姐走,姐姐带你去找一座会发光的塔。抓紧我的手,别松开。」
t+ 303.2s ♪ 向导音:nudge(看门狗轻提醒)
t+ 303.2s ▶ 向导语音:「轻触屏幕,继续。」
t+ 315.2s ♪ 向导音:nudge(看门狗轻提醒)
t+ 315.2s ♪ 向导音:watchdog(看门狗升级)
t+ 315.2s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 317.3s ▶ 语音:「当前进度:第二幕,酸雨笼罩的街区与断桥。你的目标:渡过江水,抵达广播塔。小心,猎狗的人,就在附近。」
t+ 319.4s ▶ 语音:「小满,别怕。数到一千太慢了。跟姐姐走,姐姐带你去找一座会发光的塔。抓紧我的手,别松开。」
t+ 329.5s 【状态】剧情停下来了,在等我 剧情等着你(节点 n12):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 329.5s ♪ 音效 sfx/wind
t+ 329.5s ▶ 语音:「两个小时后,江水拦住了去路。跨江大桥断成两截,桥身斜插进黑沉沉的江面。要去广播塔,只剩两条路:桥下的污水涵洞,黑得看不见底;或者,桥上悬着的钢索,在风里荡来荡去。」
t+ 331.6s ▶ 语音:「请选择。向左滑:走桥下的污水涵洞。向右滑:爬桥上悬着的钢索。」
t+ 333.7s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n15):左滑或右滑,做出你的决定(BGM=bgm/dark_drone)
t+ 180.8s ♪ 音效 sfx/ui_confirm
t+ 180.8s ♪ 音效 sfx/water
t+ 180.8s ▶ 语音:「你让小满骑在肩上,自己蹚进没到胸口的污水里。又冷又黏,带着腐烂的甜味。走到一半,水面下有什么东西擦过你的腿,小满吓得一抖,你抬手按住他的膝盖:“数数。”他小声数起来,一,二,三……数到一百七,你们看见了洞口的天光。」
t+ 182.9s 【状态】剧情停下来了,在等我 剧情等着你(节点 n16b):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 182.9s ♪ 音效 sfx/footsteps
t+ 182.9s ▶ 语音:「啧啧啧。涵洞也拦不住你,钢索也拦不住你,命真大啊。正式认识一下——猎狗。北区广播塔底下这片,归我管。把你妹妹的坐标,还有包里剩下的东西,交出来。交出来,我让你死得体面点。」
t+ 202.3s ▶ 语音:「请选择。向左滑:交出坐标,保命要紧。向右滑:拒绝他,想办法周旋。」
t+ 204.4s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n18):左滑或右滑,做出你的决定(BGM=bgm/dark_drone,语速1)
#### 【玩家动作】选了左边
t+ 333.7s ♪ 音效 sfx/ui_confirm
t+ 333.7s ♪ 音效 sfx/water
t+ 333.7s ▶ 语音:「你让小满骑在肩上,自己蹚进没到胸口的污水里。又冷又黏,带着腐烂的甜味。走到一半,水面下有什么东西擦过你的腿,小满吓得一抖,你抬手按住他的膝盖:“数数。”他小声数起来,一,二,三……数到一百七,你们看见了洞口的天光。」
t+ 343.8s ♪ 向导音:nudge(看门狗轻提醒)
t+ 343.8s ▶ 向导语音:「轻触屏幕,继续。」
t+ 355.8s ♪ 向导音:nudge(看门狗轻提醒)
t+ 355.8s ♪ 向导音:watchdog(看门狗升级)
t+ 355.8s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 358.0s ▶ 语音:「当前进度:第二幕,酸雨笼罩的街区与断桥。你的目标:渡过江水,抵达广播塔。小心,猎狗的人,就在附近。」
t+ 360.1s ♪ 音效 sfx/water
t+ 360.1s ▶ 语音:「你让小满骑在肩上,自己蹚进没到胸口的污水里。又冷又黏,带着腐烂的甜味。走到一半,水面下有什么东西擦过你的腿,小满吓得一抖,你抬手按住他的膝盖:“数数。”他小声数起来,一,二,三……数到一百七,你们看见了洞口的天光。」
t+ 362.2s 【状态】剧情停下来了,在等我 剧情等着你(节点 n16b):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 362.2s ♪ 音效 sfx/footsteps
t+ 362.2s ▶ 语音:「啧啧啧。涵洞也拦不住你,钢索也拦不住你,命真大啊。正式认识一下——猎狗。北区广播塔底下这片,归我管。把你妹妹的坐标,还有包里剩下的东西,交出来。交出来,我让你死得体面点。」
t+ 381.6s ▶ 语音:「请选择。向左滑:交出坐标,保命要紧。向右滑:拒绝他,想办法周旋。」
t+ 383.7s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n18):左滑或右滑,做出你的决定(BGM=bgm/dark_drone)
t+ 204.4s ♪ 音效 sfx/ui_confirm
t+ 204.4s ▶ 语音:「好,好,我说。北区,避难所,坐标是……三、七点、五。拿去,都拿去。求你……放我走。」
t+ 214.1s 【状态】剧情停下来了,在等我 剧情等着你(节点 n19):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 214.1s ▶ 语音:「猎狗的车队朝北区开走了,探照灯在雨幕里划出一道道口子。你蹲在原地,胃里一阵一阵地泛酸。晨晨在北区。而刚才,是你,亲手把狼,引到了她的门口。」
t+ 216.2s ▶ 语音:「请选择。向左滑:追上去,警告避难所。向右滑:自己逃命,越远越好。」
t+ 218.3s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n20):左滑或右滑,做出你的决定(BGM=bgm/dark_drone,语速1)
#### 【玩家动作】选了左边
t+ 383.7s ♪ 音效 sfx/ui_confirm
t+ 383.7s ▶ 语音:「好,好,我说。北区,避难所,坐标是……三、七点、五。拿去,都拿去。求你……放我走。」
t+ 401.4s ♪ 向导音:nudge(看门狗轻提醒)
t+ 401.4s ▶ 向导语音:「轻触屏幕,继续。」
t+ 413.4s ♪ 向导音:nudge(看门狗轻提醒)
t+ 413.4s ♪ 向导音:watchdog(看门狗升级)
t+ 413.4s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 415.5s ▶ 语音:「当前进度:第三幕,广播塔下。你的目标:把警报发出去,找到林晨。这是最后一段路了。」
t+ 417.6s ▶ 语音:「好,好,我说。北区,避难所,坐标是……三、七点、五。拿去,都拿去。求你……放我走。」
t+ 427.2s 【状态】剧情停下来了,在等我 剧情等着你(节点 n19):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 427.2s ▶ 语音:「猎狗的车队朝北区开走了,探照灯在雨幕里划出一道道口子。你蹲在原地,胃里一阵一阵地泛酸。晨晨在北区。而刚才,是你,亲手把狼,引到了她的门口。」
t+ 429.4s ▶ 语音:「请选择。向左滑:追上去,警告避难所。向右滑:自己逃命,越远越好。」
t+ 431.5s 【状态】抉择点,提示音念完了 「请选择」念完了(节点 n20):左滑或右滑,做出你的决定(BGM=bgm/dark_drone)
#### 【玩家动作】选了左边
t+ 431.5s ♪ 音效 sfx/ui_confirm
t+ 431.5s ♪ 音效 sfx/footsteps
t+ 431.5s ▶ 语音:「再快一点……再快一点!晨晨,等我。这一次,姐说什么,也不会再把你弄丢了!」
t+ 448.0s ♪ 向导音:nudge(看门狗轻提醒)
t+ 448.0s ▶ 向导语音:「轻触屏幕,继续。」
t+ 460.0s ♪ 向导音:nudge(看门狗轻提醒)
t+ 460.0s ♪ 向导音:watchdog(看门狗升级)
t+ 460.0s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 462.1s ▶ 语音:「当前进度:第三幕,广播塔下。你的目标:把警报发出去,找到林晨。这是最后一段路了。」
t+ 464.2s ♪ 音效 sfx/footsteps
t+ 464.2s ▶ 语音:「再快一点……再快一点!晨晨,等我。这一次,姐说什么,也不会再把你弄丢了!」
t+ 472.8s 【状态】剧情停下来了,在等我 剧情等着你(节点 n21):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 472.8s ▶ 语音:「你赶在车队前面,撞进了避难所的大门。“关掉广播!关灯!猎狗来了——!”那一夜,北区守住了。探照灯熄灭的时候,晨晨从人群里扑出来抱住你,才发现你胳膊上的血,已经把半边袖子浸透了。坐标是你交出去的,命,是你抢回来的。你赢了,用一条胳膊的代价。——结局:代价。单击屏幕,重新开始。」
t+ 482.9s ♪ 向导音:nudge(看门狗轻提醒)
t+ 482.9s ▶ 向导语音:「轻触屏幕,继续。」
t+ 494.9s ♪ 向导音:nudge(看门狗轻提醒)
t+ 494.9s ♪ 向导音:watchdog(看门狗升级)
t+ 494.9s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 497.0s ▶ 语音:「当前进度:第三幕,广播塔下。你的目标:把警报发出去,找到林晨。这是最后一段路了。」
t+ 499.1s ▶ 语音:「你赶在车队前面,撞进了避难所的大门。“关掉广播!关灯!猎狗来了——!”那一夜,北区守住了。探照灯熄灭的时候,晨晨从人群里扑出来抱住你,才发现你胳膊上的血,已经把半边袖子浸透了。坐标是你交出去的,命,是你抢回来的。你赢了,用一条胳膊的代价。——结局:代价。单击屏幕,重新开始。」
t+ 501.2s 【状态】结局 结局播完了。单击屏幕,可以回到故事开始的地方(BGM=bgm/dark_drone)
t+ 218.3s ♪ 音效 sfx/ui_confirm
t+ 218.3s ♪ 音效 sfx/footsteps
t+ 218.3s ▶ 语音:「再快一点……再快一点!晨晨,等我。这一次,姐说什么,也不会再把你弄丢了!」
t+ 226.8s 【状态】剧情停下来了,在等我 剧情等着你(节点 n21):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
t+ 226.8s ▶ 语音:「你赶在车队前面,撞进了避难所的大门。“关掉广播!关灯!猎狗来了——!”那一夜,北区守住了。探照灯熄灭的时候,晨晨从人群里扑出来抱住你,才发现你胳膊上的血,已经把半边袖子浸透了。坐标是你交出去的,命,是你抢回来的。你赢了,用一条胳膊的代价。——结局:代价。单击屏幕,重新开始。」
t+ 228.9s 【状态】结局 结局播完了。单击屏幕,可以回到故事开始的地方(BGM=bgm/dark_drone,语速1)
#### 【玩家动作】听完结局,单击重开一次看看别的走向
t+ 501.2s ♪ 音效 sfx/ui_confirm
t+ 501.2s ♪ 向导音:explore(探索/安全)
t+ 501.2s ▶ 语音:「大停电后的第三十天,城市陷入彻底的黑暗。没有电,没有网络,只有收音机里偶尔闪过的沙沙声,像垂死者的呼吸。你是林晚,一名前急救员。在这个故事里,你用声音活下去。请记住:单击屏幕,继续剧情;双击,重听上一句;向上滑,听你的状态和帮助;向下滑,呼出菜单。当我说“请选择”的时候,向左滑,或者向右滑,做出你的决定。选择一旦做出,就无法回头。现在,单击屏幕,睁开眼睛。」
t+ 511.4s ♪ 向导音:nudge(看门狗轻提醒)
t+ 511.4s ▶ 向导语音:「轻触屏幕,继续。」
t+ 523.4s ♪ 向导音:nudge(看门狗轻提醒)
t+ 523.4s ♪ 向导音:watchdog(看门狗升级)
t+ 523.4s ▶ 语音:「操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。」
t+ 525.5s ▶ 语音:「当前进度:第一幕,城南地铁站附近。你的目标:活着离开这片废墟,前往北区的广播塔,寻找妹妹林晨的下落。」
t+ 527.6s ▶ 语音:「大停电后的第三十天,城市陷入彻底的黑暗。没有电,没有网络,只有收音机里偶尔闪过的沙沙声,像垂死者的呼吸。你是林晚,一名前急救员。在这个故事里,你用声音活下去。请记住:单击屏幕,继续剧情;双击,重听上一句;向上滑,听你的状态和帮助;向下滑,呼出菜单。当我说“请选择”的时候,向左滑,或者向右滑,做出你的决定。选择一旦做出,就无法回头。现在,单击屏幕,睁开眼睛。」
t+ 529.7s 【状态】回到开头,会话结束 剧情等着你(节点 n00):单击继续,双击重听(BGM=bgm/dark_drone)
t+ 228.9s ♪ 音效 sfx/ui_confirm
t+ 228.9s ♪ 向导音:explore(探索/安全)
t+ 228.9s ▶ 向导语音:「单击继续剧情。请选择的时候,左右滑动。」
t+ 231.1s ▶ 语音:「大停电后的第三十天,城市陷入彻底的黑暗。没有电,没有网络,只有收音机里偶尔闪过的沙沙声,像垂死者的呼吸。你是林晚,一名前急救员。在这个故事里,你用声音活下去。请记住:单击屏幕,继续剧情;双击,重听上一句;向上滑,听你的状态和帮助;向下滑,呼出菜单。当我说“请选择”的时候,向左滑,或者向右滑,做出你的决定。选择一旦做出,就无法回头。现在,单击屏幕,睁开眼睛。」
t+ 233.2s 【状态】回到开头,会话结束 剧情等着你(节点 n00):单击继续,双击重听(BGM=bgm/dark_drone,语速1)
+57
View File
@@ -0,0 +1,57 @@
# 研究总结报告:纯音频游戏交互设计方法论的形成与验证
> 状态:round2 进行中(本报告随迭代更新)。研究章程见 research/00-charter.md。
## 1. 研究问题与答案
| RQ | 问题 | 答案(详见对应产物) |
|---|---|---|
| RQ1 | 优秀音频游戏如何保持状态感知 | 15 条验证模式:语音引导员+随时重听、威胁预警窗 1.5~2s、方位降维 ≤4、全局混音签名、教学一句话铁律等(01-literature/01) |
| RQ2 | 声音如何承载结构化信息 | earcon 学习成本 8.5 轮 vs 语音 1.14 轮 → **语义靠语音、earcon 只做粗类别标记**(≤7 个、显式教一次);并发声流 ≤2;工作记忆 ≈4 chunks → 单次播报 ≤3 选项(01-literature/02) |
| RQ3 | 视障玩家惯例 | status key 三十年惯例、双击执行/单击预览、逐句跳过、阈值播报、失败可归因=留存强相关;「把盲人当白痴」是弃游主因(01-literature/03) |
| RQ4 | 头 5 分钟不弃坑 | 首输入 <60s、首胜 ≤3min 保送、学习期零惩罚、失败三问归因、3-5min 局结构、断点续玩(01-literature/04) |
| RQ5 | 场景与人群差异 | 床=第一场景;被打断恢复成本 23min → 局面复述;摸鱼纯震动 ≤6 语义;四场景参数包 A/B/C/D(01-literature/05) |
| RQ6 | 方法论 | methodology.md v1.1:四通道模型 + 7 公理 + 5 层交互栈 + 决策推导机 + 审计指标 + 反模式 |
| RQ7 | 交互方案 | schemes.md + 逐游戏接线(game-inventory.md 行号级);v1 基线 + round2 修订 F1~F9 |
| RQ8 | 玩家是否认可 | 两轮 4 人设代理评审(见 §3) |
## 2. 方法论形成过程(先调研后设计,但被试玩修正)
方法论 v1.0 从 5 路文献收敛而来;**round1 试玩暴露了文献没写透的三件事**,回写为 v1.1:
1. **提示永不逐字重复**——文献说「可重听」「不轰炸」,没说「重复=罚听」。四人设全票发现:看门狗把「停下来回味剧情」的玩家当成卡死者,逐字重播 26 秒说明书。→ L4 修订:同一等待点第 2 次提示必须是新短句或纯音频标记;剧情类等待阈值加倍。
2. **教学要看玩家表现**——文献说「learn by doing」,没说「成功后不得继续灌补救教学」。视障评委最激烈的「被当白痴」时刻恰是:格挡成功后紧接「来不及格挡就选防御」。→ A5 修订:成功即时确认、失败才给补救。
3. **纠错句迟到比不播更有害**——queue 语义下归因句在语音链尾出现,归因于已过去的错误。→ A4 修订:纠错/提醒类语音空闲才播、过期丢弃。
## 3. 验证闭环结果
### round1(v1 基线:guide 层 + 6 游戏接线)
自动化审计(round1/audit.md):真实静默死区 0;turn cue/看门狗/纠错事件全部落盘可查。
人设评分(round1/p1~p4):愿意继续玩 = storyline 4/4、escape 4/4、rpg 4/4、simon 3/4、rhythm 3/4、**space 2/4**。未达门槛。
关键单点:
- 视障评委(P1):「地基是懂听觉游戏的人打的,差礼仪和效率两课」;10 条 P0 里 7 条关于说教系统的礼仪而非功能缺失。
- 大学生(P2):「保送首胜全做到了,差的是别把我思考当成我迷路」。
- 白领(P3):6 款全部单手免视可玩、1-3 分钟颗粒度适合通勤;拒绝 rhythm 仅因教学零归因。
- 工人(P4):「escape 和 rpg 像热心工友」;space 警报叠爆一分钟累退。
### round2(F1~F9 修订后)
(待 round2 评审后填写:评分表、门槛判定、剩余问题清单)
## 4. 交付物与影响
- 方法论(可复用):research/02-methodology/methodology.md
- 方案与接线:research/03-schemes/
- 引擎向导层:web/engine/guide.js(8 个运行时合成 earcon、看门狗、turn cue、纠错)+ settings.guide 三档 + 语速 2.2 视障档
- 18 句向导语音(8+10)×双性别,全部走既有 TTS 管线
- 玩家感知转写器:web/harness/(把整局体验转成可审计的时间轴日志——本研究的测量仪器,可复用于任何纯音频游戏)
- 试玩测试:6 playtest 断言零改动 + guide.test 专项
- 文档:docs/design/14-guide-layer.md、00-global-ux.md §6
## 5. 局限与后续(诚实声明)
- 代理评审测的是**信息流与状态感知**,不测音色美感/响度/双耳定位/蓝牙延迟——真机人类听测仍是发布前必要环节(roadmap §4)。
- 会话剧本是「新手手部动作」的模拟,round1 曾因剧本不反应提示造成误判(P4「被晾 85 秒」)——round2 已修正 driver 反应性;此误差源已写入方法论 §5 度量注意事项。
- 场景参数包(通勤/摸鱼/睡前/下班)、escape 双击语义重设计、simon 退出路径:列入 roadmap v2(schemes.md §3 分期裁定)。
+15
View File
@@ -113,6 +113,9 @@ STORYLINE_LINES = [
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
("gd_continue", _SL_NARR, "轻触屏幕,继续。"),
("gd_invalid", _SL_NARR, "这个操作在这里用不上。"),
# ---- 向导层 round2(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
("gd_brief", _SL_NARR, "单击继续剧情。请选择的时候,左右滑动。"),
("gd_again", _SL_NARR, "还在吗?单击继续。"),
]
@@ -271,6 +274,8 @@ SIMON_LINES = [
("skip_hint", _SIMON, "单击可跳过旁白。"),
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
("gd_relisten", _SIMON, "需要的话,双击可以重新听一遍序列。"),
# ---- 向导层 round2(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
("gd_economy", _SIMON, "记不住就双击重新听,免费机会有两次,用完要扣一次机会。"),
]
@@ -320,6 +325,10 @@ RHYTHM_LINES = [
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
("gd_answer", _RHY, "现在跟着我打。"),
("gd_miss", _RHY, "慢了半拍,下一小节跟上我就行。"),
# ---- 向导层 round2(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
("gd_miss_early", _RHY, "抢拍了。等声音落下再滑。"),
("gd_miss_late", _RHY, "慢了半拍。跟着节拍走。"),
("gd_miss_wrong", _RHY, "方向不对。跟着音色滑。"),
]
@@ -390,6 +399,8 @@ SPACE_LINES = [
("skip_hint", _SPC, "单击可跳过旁白。"),
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
("gd_hit_hint", _SPC, "被撞了。听方位,同向滑动就能拨开。"),
# ---- 向导层 round2(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
("gd_last_life", _SPC, "这是最后一次机会,撑住。"),
]
@@ -530,6 +541,10 @@ RPG_LINES = [
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
("gd_parry_miss", _RPG, "前摇声起的时候双击,才能格挡。"),
("gd_stall", _RPG, "想好了吗?左右滑动选择,单击出手。"),
# ---- round2 新增(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
("tut_b2b", _RPG, "挡得漂亮。"),
("gd_wrap_first", _RPG, "回到第一个。"),
("gd_wrap_last", _RPG, "最后一个了。"),
]
# NPC 单音色平铺(id, speaker, instruct, 台词)→ web/rpg/audio/voice/<id>.*
+28 -7
View File
@@ -10,6 +10,9 @@
* full = earcon + 语音 + 震动;standard = earcon + 震动(无语音);off = 全静。
* **off 时全部 API 静默 no-op,包括不挂任何定时器**——保证既有测试/玩法零干扰。
* - 向导绝不打断内容:插播语音一律 queue:true(等当前对白播完,绝不顶掉)。
* **error / watchdog 语音例外,为 idle-only(round2 F1.4)**:对白在播
* (audio.isVoicePlaying())时跳过语音只发事件(事件带 skipped:true)——
* 纠错/看门狗句排到长链尾部会迟到误导,宁可不说;turn 语音保持 queue 不变。
*
* 游戏侧用法(构造时创建一次):
* import { Guide } from '../engine/guide.js';
@@ -149,6 +152,8 @@ export class Guide {
/**
* A4 纠错:error 震(除非 skipHaptic——游戏层已震过时传 true)+ full 档语音。
* 语音为 idle-only(F1.4):对白在播时跳过只发 skipped 事件,且不占 onceKey
* (玩家没听到不算说过,下次空闲再纠错仍可播一次)。
* @param {{voice?: string|null, onceKey?: string|null, skipHaptic?: boolean}} [opts]
*/
error(opts = {}) {
@@ -156,18 +161,23 @@ export class Guide {
this._touch();
if (!opts.skipHaptic) vibrate('error');
if (!opts.voice) return;
if (this.level() !== 'full') return;
if (this.audio.isVoicePlaying()) {
this._emit('error', 'error', opts.voice, true);
return;
}
if (opts.onceKey) {
if (this._once.has(opts.onceKey)) return;
if (this.level() === 'full') this._once.add(opts.onceKey); // 只在真的播了才计数
this._once.add(opts.onceKey); // 只在真的播了才计数
}
if (this.level() !== 'full') return;
this._say('error', opts.voice);
}
/**
* L4 静默看门狗:arm 后按 at 逐级计时,每级播 nudge 轻 earcon(+ say[n-1] 语音,
* full 档),再调 onEscalate(n);级数用尽自动停止。cond() 在触发时刻返回 false
* 则静默取消(游戏已离开该等待态)。任何 guide API 调用 / disarm() / 再次 arm 复位。
* full 档,idle-only——对白在播时跳过只发事件),再调 onEscalate(n);级数用尽
* 自动停止。cond() 在触发时刻返回 false 则静默取消(游戏已离开该等待态)。
* 任何 guide API 调用 / disarm() / 再次 arm 复位。
* @param {{arm?: boolean, at?: number[], say?: (string|null)[], onEscalate?: (n:number)=>void, cond?: ()=>boolean}} opts
*/
watchdog(opts = {}) {
@@ -194,16 +204,27 @@ export class Guide {
return this.level() === 'off';
}
_emit(type, name, voiceId) {
_emit(type, name, voiceId, skipped = false) {
if (!this._hook) return;
try {
this._hook({ type, name, voiceId: voiceId ?? null });
const evt = { type, name, voiceId: voiceId ?? null };
if (skipped) evt.skipped = true;
this._hook(evt);
} catch (_) { /* 钩子异常不影响游戏 */ }
}
/** full 档语音插播:queue:true 排到当前对白之后,绝不顶掉正在播的语音。 */
/**
* full 档语音插播:queue:true 排到当前对白之后,绝不顶掉正在播的语音。
* F1.4 idle-only:其中 error/watchdog 两类在对白在播(audio.isVoicePlaying())
* 时跳过语音只发事件(事件带 skipped:true)——排到链尾会迟到误导;turn 保持
* queue 不变。
*/
_say(type, voiceId) {
if (this.level() !== 'full') return;
if ((type === 'error' || type === 'watchdog') && this.audio.isVoicePlaying()) {
this._emit(type, type, voiceId, true);
return;
}
this._emit(type, type, voiceId);
this.audio.play(`voice/${voiceId}`, { queue: true });
}
+8 -1
View File
@@ -352,7 +352,14 @@ export class EscapeGame extends Game {
if (nx < 0 || nx > 2 || ny < 0 || ny > 2) {
this.audio.play('sfx/wall');
vibrate('error');
this._playVoice('voice/wall'); // 每次撞墙都播(取消一次性限制)
// F2.3(round2):撞墙反馈接既有 guide_blunt_<dir> 方向句(纯逻辑接线,
// 资产 v1 已有)——「那边是墙」之后补一句方位("手指贴着屏幕,向上推一下"),
// 噪音环境下听不出墙音方向时也知道刚才顶到的是哪边。排在本句播完之后;
// 被下一次滑动打断则自然作废(不迟到误导)。
const dir = dy < 0 ? 'up' : dy > 0 ? 'down' : dx < 0 ? 'left' : 'right';
this._playVoice('voice/wall', {
onComplete: () => this._playVoice(`voice/guide_blunt_${dir}`),
});
this._wallBumps++; // 向导层:连续撞墙 = 卡关信号(_checkStuck 提前一级)
return;
}
+94 -29
View File
@@ -1,13 +1,16 @@
/**
* 向导层无头测试(web/harness/guide.test.mjs)。
*
* settings.guide='full' 下,用 storyline + simon 两个代表游戏断言三 API 行为:
* ① 看门狗:storyline 进入等待态 8s → nudge earcon 事件 + gd_continue 语音
* (20s 升级 = 复用 playHelpAndResume,升级后不自动重挂);simon input
* 10s → gd_relisten、25s → 免扣机会自动重听一次;
* settings.guide='full' 下,用 storyline + simon 两个代表游戏 + Guide 直连断言:
* ① 看门狗礼仪(round2 F1):storyline 进入等待态 25s → nudge + gd_continue,
* 50s 升级只重申手势(gd_again),**不再重播 help/进度/当前节点**,两级用尽
* 彻底静默;simon input 10s → gd_relisten,25s 升级 = confirm 震 + turn
* earcon 纯标记(**取消自动重播序列**);
* ② turn cue:simon 首轮序列播完 → turn earcon 事件 + confirm 震 + your_turn;
* ③ error 纠错:storyline 无效手势 → error 震 + gd_invalid(onceKey 同局只播一次);
* ④ 等级衰减:guide='off' 时以上全部无事件、无 gd_* 播放、无定时器注册。
* ④ F1.4 idle-only:对白在播时 error/watchdog 语音跳过(事件带 skipped:true、
* 不入队),空闲时正常播;turn 语音保持 queue 不受影响;
* ⑤ 等级衰减:guide='off' 时以上全部无事件、无 gd_* 播放、无定时器注册。
* 事件经 guide.setEventHook 收集;mock 层与各 playtest 同款(虚拟时钟)。
*
* 运行:tools/node/bin/node web/harness/guide.test.mjs
@@ -143,6 +146,7 @@ globalThis.fetch = async (u) => {
const { WebAudioEngine } = await import('../engine/audio.js');
const { settings } = await import('../engine/settings.js');
const { Guide } = await import('../engine/guide.js');
const { StorylineGame } = await import('../storyline/game.js');
const { SimonGame } = await import('../simon/game.js');
@@ -172,44 +176,50 @@ const waitChainDone = (game) => advance(() => !game.voicePlaying);
// ================================================================ ① storyline 看门狗
realLog('== ① storyline:等待态看门狗(8s nudge+gd_continue,20s 升级 playHelpAndResume)==');
realLog('== ① storyline:等待态看门狗(25s nudge+gd_continue,50s 升级只发 gd_again,无重播)==');
{
const { audio, playLog, game, events, vibrated } = makeRig(StorylineGame);
await audio.unlock();
game.start();
await waitChainDone(game); // n00_intro + skip_hint 播完 → W1 静默等待(看门狗已挂)
// F8(round2):开场链最前插 gd_brief(brief→intro)
assert(voicePlays(playLog, 'voice/gd_brief') === 1, 'F8:开场链最前播 gd_brief');
assert(playLog.find((e) => e.id.startsWith('voice/'))?.id === 'voice/gd_brief',
'F8:gd_brief 是本局第一句语音');
await waitChainDone(game); // gd_brief + n00_intro + skip_hint 播完 → W1 静默等待(看门狗已挂)
assert(!game.voicePlaying && game.nodeId === 'n00', 'n00 链播完,进入等待态');
assert(events.some((e) => e.type === 'state' && e.name === 'explore'), 'start 播 explore 状态签名事件');
const tArm = now;
await advance(() => events.some((e) => e.type === 'nudge'));
assert(now - tArm === 8000, `8s 后 nudge 触发(实际 ${now - tArm}ms)`);
assert(now - tArm === 25000, `25s 后 nudge 触发(实际 ${now - tArm}ms)`);
assert(events.some((e) => e.type === 'watchdog' && e.voiceId === 'gd_continue'),
'看门狗第 1 级发 gd_continue 语音事件');
assert(voicePlays(playLog, 'voice/gd_continue') === 1, 'gd_continue 语音进入播放');
assert(!vibrated([8]), 'nudge 级不额外震动(earcon-only 轻提醒)');
await advance(() => playLog.some((e) => e.id === 'voice/help'));
assert(now - tArm === 20000, `20s 升级触发 playHelpAndResume(实际 ${now - tArm}ms)`);
await advance(() => playLog.some((e) => e.id === 'voice/status1'));
assert(voicePlays(playLog, 'voice/status1') === 1, '升级链播当前进度(status1)');
assert(game.voicePlaying, '升级后重播当前节点(帮助→进度→重播)');
await advance(() => events.filter((e) => e.type === 'watchdog').length >= 2);
assert(now - tArm === 50000, `50s 升级触发(实际 ${now - tArm}ms)`);
assert(events.some((e) => e.type === 'watchdog' && e.voiceId === 'gd_again'),
'升级只发 gd_again(重申手势)语音事件');
assert(voicePlays(playLog, 'voice/gd_again') === 1, 'gd_again 语音播放');
// F1.1:升级不再重播 help + 进度 + 当前节点
assert(!playLog.some((e) => e.id === 'voice/help' || e.id.startsWith('voice/status')),
'F1:升级不重播 help/进度');
assert(voicePlays(playLog, 'voice/n00_intro') === 1, 'F1:升级不重播当前节点(n00_intro 仅开场一次)');
// 升级帮助播完后不自动重挂(L4"之后停止,不轰炸"):链走完后无定时器 → idle
// 两级用尽彻底静默:链走完后无任何向导定时器 → idle
await idle();
const nudges = events.filter((e) => e.type === 'nudge').length;
await idle();
assert(events.filter((e) => e.type === 'nudge').length === nudges, '升级后不自动重挂看门狗(无后续提醒)');
assert(events.filter((e) => e.type === 'nudge').length === nudges, '两级用尽彻底静默(无后续提醒)');
// 玩家手势后恢复自动挂载:n01 链播完重新挂上,且 8s 节奏到点触发一次
await gesture(game, 'tap'); // → n01,链播(tap 同时复位 _guideHelped)
await waitChainDone(game);
await gesture(game, 'up'); // 帮助(复位 + 自带语音)→ 重播 n01
// 玩家手势后重新挂载:n01 链播完重新挂上,且 25s 节奏到点触发一次
await gesture(game, 'tap'); // → n01,链播
await waitChainDone(game);
const nudgeBefore = events.filter((e) => e.type === 'nudge').length;
const tWait = now;
await advance(() => events.filter((e) => e.type === 'nudge').length > nudgeBefore);
assert(now - tWait === 8000, `重新挂载后仍按 8s 节奏触发(实际 ${now - tWait}ms)`);
assert(now - tWait === 25000, `重新挂载后仍按 25s 节奏触发(实际 ${now - tWait}ms)`);
resetTimers();
}
@@ -251,18 +261,28 @@ realLog('== ②/①b simon:首轮 turn cue(earcon 事件+confirm 震+your_tu
assert(vibrated([20]), 'turn cue 带 confirm 轻震');
assert(voicePlays(playLog, 'voice/your_turn') === 1, '第 1 轮保留 your_turn 语音');
// ①b input 看门狗:10s nudge+gd_relisten;25s 免扣机会自动重听
// ①b input 看门狗:10s nudge+gd_relisten
const tInput = now;
await advance(() => events.some((e) => e.type === 'nudge'));
assert(now - tInput === 10000, `input 10s 后 nudge 触发(实际 ${now - tInput}ms)`);
assert(events.some((e) => e.type === 'watchdog' && e.voiceId === 'gd_relisten'),
'input 10s 无输入发 gd_relisten 事件');
assert(voicePlays(playLog, 'voice/gd_relisten') === 1, 'gd_relisten 语音播放');
assert(game.chances === 1, '看门狗提示不消耗重听机会');
const chancesBefore = game.chances;
await advance(() => game.phase === 'playback');
assert(game._guideRelistens === 1, '25s 升级:免扣机会自动重听序列一次');
assert(game.chances === chancesBefore, '自动重听不扣机会');
await advance(() => game.phase === 'input');
// 25s 升级(round2 F1.2):取消自动重播序列 → 只发 confirm 震 + turn earcon(纯标记)
const nudgesBefore = events.filter((e) => e.type === 'nudge').length;
const turnsBefore = events.filter((e) => e.type === 'turn').length;
const vibBefore = vibrateLog.length;
await advance(() => events.filter((e) => e.type === 'nudge').length > nudgesBefore);
assert(now - tInput === 25000, `25s 升级触发(实际 ${now - tInput}ms)`);
assert(game.phase === 'input' && game.round === 1, '升级不再自动重播序列(仍停在 input 等玩家)');
assert(events.filter((e) => e.type === 'turn').length === turnsBefore + 1, '升级发 turn earcon 事件(纯标记)');
assert(vibrateLog.slice(vibBefore).some((p) => JSON.stringify(p) === JSON.stringify([20])),
'升级发 confirm 轻震');
assert(voicePlays(playLog, 'voice/round_01') === 1, '序列未重播(round_01 仍只有第 1 轮一次)');
await idle(); // 级数用尽:看门狗自动停止
assert(game.phase === 'input', '升级后仍等待输入(免费泄题已废除)');
// 正常答题清第 1 轮:答题手势逐个 disarm,round1→round2 过渡期看门狗不触发
const nudgeBefore = events.filter((e) => e.type === 'nudge').length;
@@ -271,13 +291,58 @@ realLog('== ②/①b simon:首轮 turn cue(earcon 事件+confirm 震+your_tu
await advance(() => game.phase === 'input' && game.round === 2);
assert(events.filter((e) => e.type === 'nudge').length === nudgeBefore,
'答题手势复位看门狗:第 2 轮开启前无 nudge');
assert(game._guideRelistens === 1, '第 2 轮不再消耗自动重听额度');
resetTimers();
}
// ================================================================ ④ guide=off 全静
// ================================================================ ④ F1.4 idle-only
realLog('== ④ guide=off:全部 API 静默 no-op(无事件、无 gd_* 播放、无定时器注册)==');
realLog('== ④ F1.4 idle-only:对白在播时 error/watchdog 语音跳过(skipped 事件、不入队),turn 保持 queue ==');
{
currentGameDir = GAME_DIRS.StorylineGame;
const audio = new WebAudioEngine('audio/');
const playLog = [];
const origPlay = audio.play.bind(audio);
audio.play = (id, opts) => { playLog.push({ id, t: now }); return origPlay(id, opts); };
await audio.unlock();
const guide = new Guide(audio);
const events = [];
guide.setEventHook((evt) => events.push({ ...evt, t: now }));
// watchdog:对白在播时到点 → 只发 skipped 事件,语音不入队
guide.watchdog({ arm: true, at: [1000], say: ['gd_continue'] });
audio.play('voice/n00_intro'); // 长语音占住通道(mock 句长 2s)
await flush();
assert(audio.isVoicePlaying(), '前提:对白正在播放');
await advance(() => events.some((e) => e.type === 'watchdog'));
const wd = events.find((e) => e.type === 'watchdog');
assert(wd && wd.voiceId === 'gd_continue' && wd.skipped === true,
'watchdog 语音被 idle-skip(事件带 skipped:true)');
assert(voicePlays(playLog, 'voice/gd_continue') === 0, 'watchdog 语音未入队播放');
// error:对白在播 → 同规则只发事件;空闲 → 正常播放
guide.error({ voice: 'gd_invalid' });
assert(events.some((e) => e.type === 'error' && e.skipped === true),
'error 语音对白在播时跳过(skipped 事件)');
assert(voicePlays(playLog, 'voice/gd_invalid') === 0, 'error 语音未入队播放');
await advance(() => !audio.isVoicePlaying()); // 等在播对白自然结束
guide.error({ voice: 'gd_invalid' });
assert(voicePlays(playLog, 'voice/gd_invalid') === 1, '空闲时 error 语音正常播放');
assert(events.filter((e) => e.type === 'error' && !e.skipped).length === 1,
'空闲播放的 error 事件不带 skipped 标记');
// turn:保持 queue,不受 idle-only 影响(对白在播时照常排队)
audio.play('voice/n01_wake');
await flush();
assert(audio.isVoicePlaying(), '前提:对白正在播放');
guide.turn({ voice: 'gd_answer' });
assert(voicePlays(playLog, 'voice/gd_answer') === 1, 'turn 语音不受 idle-only 影响(照常入队)');
assert(events.some((e) => e.type === 'turn'), 'turn 事件照常发出');
resetTimers();
}
// ================================================================ ⑤ guide=off 全静
realLog('== ⑤ guide=off:全部 API 静默 no-op(无事件、无 gd_* 播放、无定时器注册)==');
{
settings.set({ guide: 'off' });
const { audio, playLog, game, events, vibrated } = makeRig(StorylineGame);
+1 -1
View File
@@ -58,7 +58,7 @@ export async function session(ctx, mock) {
ctx.silence(3, '辨了辨方向');
await move('up');
await move('up');
await move('up', '北边是墙,撞了一下');
await move('up', '北边是墙,撞了一下——「那边是墙」后面还跟了句方向(向上推的是北墙)');
await move('up', '又撞了一下——听到游戏主动来问我了');
await ctx.waitVirtual(5200); // 等卡关轮询的 5s tick:连续撞墙 → 提前一级的暗示
+12 -10
View File
@@ -40,15 +40,16 @@ export async function session(ctx, mock) {
await ctx.waitCond(() => game.phase === 'explore');
ctx.status('进村了,先听场景描述和选项');
// 村里(选项:去小路/商店/休息/听状态):把选项听一圈再对准「去小路」出发
// 村里(选项:去小路/商店/休息/听状态):把选项听一圈,回绕到首项再出发。
// 确认(tap)前必须等选项名念完——voice 播放中单击是「跳过本句」,会把手势吃掉
for (let i = 0; i < 3; i++) {
await ctx.gesture('right');
await ctx.waitVirtual(1800); // 听选项名
}
ctx.status('选项听了一圈,往林间小路去');
await ctx.waitIdle(); // 选项名播完(无 interval,可安全等待空闲)
await ctx.gesture('right'); // 转回首个选项「去小路」
await ctx.waitVirtual(1500);
ctx.status('选项听了一圈');
await ctx.gesture('right'); // 从末项回绕到首个选项——听到「回到第一个」(round2 F7)
ctx.note('滑到头再滑就兜回来了,有人提醒「回到第一个」,不用自己数');
await ctx.waitCond(() => !ctx.voiceBusy()); // 选项名 + 回绕提示念完
await ctx.gesture('tap'); // → path
// 小路(去岔路口/调查/返回):先调查一次捡钱,再左滑回「去岔路口」
@@ -57,17 +58,18 @@ export async function session(ctx, mock) {
await ctx.gesture('right'); // 调查四周
await ctx.waitVirtual(1800);
ctx.note('翻找了一阵,捡到一点钱');
await ctx.waitCond(() => !ctx.voiceBusy());
await ctx.gesture('tap');
await ctx.waitVirtual(2400);
await ctx.waitCond(() => !ctx.voiceBusy()); // 捡钱播报念完
await ctx.gesture('left'); // 回「去岔路口」
await ctx.waitVirtual(1500);
await ctx.waitCond(() => !ctx.voiceBusy());
await ctx.gesture('tap'); // → fork
// 岔路口(去枯树洞/去溪流/返回):右滑一次到「去溪流」
await ctx.waitCond(() => game.phase === 'explore' && game.scene === 'fork');
ctx.status('岔路口,水声在东边');
await ctx.gesture('right');
await ctx.waitVirtual(1500);
await ctx.waitCond(() => !ctx.voiceBusy());
await ctx.gesture('tap'); // go_stream → 溪流 → 触发教学战
// 教学战:四步(识招 → 攻击 → 格挡 → 防御)+ 自由击杀
@@ -95,10 +97,10 @@ export async function session(ctx, mock) {
ctx.note('单击出手,攻击');
await ctx.gesture('tap');
// T2:格挡(前摇窗口内双击)
// T2:格挡(前摇窗口内双击)——挡住会先听到「挡得漂亮」(round2 F4)再进防御教学
await ctx.waitCond(() => game._battle?.state === 'ENEMY_TELEGRAPH');
ctx.status('前摇声响起了');
ctx.note('双击格挡');
ctx.note('双击格挡——这回干净挡下了,听到「挡得漂亮」');
await ctx.gesture('doubletap');
// T3:防御一次
+41 -24
View File
@@ -1,7 +1,10 @@
/**
* 《音序回响》新手会话剧本:教学六音一步一步学(中间故意做错一次听纠错)、
* 第 1 轮答对、第 2 轮中途忘序列僵住 11 秒(触发向导「双击可重听」提示)、
* 第 3 轮故意答错第一下(消耗机会自动重播),然后答对完成第 3 轮收工。
* 收尾听完机会经济句(免费重听两次、用完扣机会);第 1 轮直接答对;第 2 轮开局
* 忘序列僵住 10 秒,听到向导「双击可以重新听」提示后 5 秒内照做——用掉一次免费
* 重听(round2:25s 升级已不再自动重播序列,坐着等答案重念的路子废了);第 3 轮
* 凭记忆答对,在**整轮边界**收工(round2 剧本修正:不在轮中退出——round1 转写
* 「第 4 轮报数后再没人说话」与「干晾 85 秒」都是剧本失真,不是游戏失真)。
*/
export function describe(game) {
const GN = { up: '上滑', down: '下滑', left: '左滑', right: '右滑', tap: '单击', doubletap: '双击' };
@@ -14,7 +17,7 @@ export function describe(game) {
case 'announce': return `第 ${game.round} 轮报数呢,听好了`;
case 'playback': return `第 ${game.round} 轮序列演奏中(${game.sequence.length} 个音),记住顺序,先别动手`;
case 'input': return `第 ${game.round} 轮轮到你了:已对上 ${game.inputIdx}/${game.sequence.length},照原顺序做手势`;
case 'roundend': return game.chances < (game.round > 4 ? 2 : 1) ? '答错了,正在重播刚才的序列' : '刚答完,播报中';
case 'roundend': return '刚答完一轮,播报中';
case 'gameover': return `游戏结束,完成 ${game.round - 1} 轮。单击再来一局,下滑开菜单`;
case 'menu': return '菜单里:单击切选项,双击确认,上滑听规则';
default: return '开场,稍等一下';
@@ -35,36 +38,50 @@ export async function session(ctx, mock) {
await ctx.waitCond(() => game.phase === 'tutorial' && game.teachAwait);
}
await ctx.gesture(G[i]); // 照指令做对
if (i === G.length - 1) {
// 收尾链(t7 两句 + 机会经济句)播完直达对局——一到 input 就接手,
// 不裸 waitIdle(会把 input 的看门狗窗口整段空烧掉,round1 剧本失真根源)
await ctx.waitCond(() => game.phase === 'playback' || game.phase === 'input'
|| game.phase === 'gameover');
} else {
await ctx.waitIdle();
}
// 正式对局:打到完成第 3 轮
let wrongDone = false;
let stallDone = false;
while (mock.now < 12 * 60 * 1000 && game.phase !== 'gameover' && game.round < 4) {
if (game.phase === 'input') {
// 第 2 轮开局先失忆僵住 11 秒:触发向导「双击可以重新听一遍序列」提示
if (game.round === 2 && game.inputIdx === 0 && !stallDone) {
stallDone = true;
ctx.status('序列有点长,先僵一会儿');
ctx.silence(11, '想不起来序列了');
await ctx.waitVirtual(11000);
ctx.status('听到提示:需要的话,双击可以重新听一遍序列');
}
// 第 2 轮第二拍故意答错:触发"用一次机会,再听一遍"(复活重听)
if (game.round === 2 && game.inputIdx === 1 && !wrongDone) {
wrongDone = true;
ctx.note('这一下故意点错,听听游戏怎么说');
await ctx.gesture(G[(G.indexOf(game.sequence[game.inputIdx]) + 1) % G.length]);
await ctx.waitIdle(); // retry_use + 自动重播同一序列
ctx.status('教学讲完了,机会经济也交代清楚了:免费重听两次,用完要扣机会');
// 正式对局:完整打完第 3 轮,在轮边界收工
let relistenDone = false;
while (mock.now < 12 * 60 * 1000 && game.phase !== 'gameover') {
if (game.phase === 'input') {
// 第 2 轮先失忆僵住 10 秒:听向导「双击可以重新听一遍序列」,5 秒内照做。
//(期望输入恰为双击时先答这一下,下一步再演示重听——双击语境判定)
if (game.round === 2 && !relistenDone
&& game.inputIdx <= 1 && game.sequence[game.inputIdx] !== 'doubletap') {
relistenDone = true;
ctx.status('序列有点长,先僵一会儿');
ctx.silence(10, '想不起来序列了');
await ctx.waitVirtual(10600); // 10s 看门狗:nudge + gd_relisten
ctx.status('听到提示:需要的话,双击可以重新听一遍序列');
ctx.note('照提示双击,用掉一次免费重听');
await ctx.gesture('doubletap');
await ctx.waitCond(() => game.phase === 'playback' || game.phase === 'input');
continue;
}
if (game.round === 3 && game.inputIdx === game.sequence.length - 1) {
ctx.status('最后一轮最后一个音,打完这轮就收');
} else {
ctx.status('轮到我复现了');
}
await ctx.gesture(game.sequence[game.inputIdx]);
continue;
}
await ctx.waitIdle();
// 整轮边界收尾:第 3 轮答完(roundend),不等第 4 轮报数
if (game.phase === 'roundend' && game.round === 3) break;
// announce/playback/roundend:等阶段推进即可,不裸 waitIdle(防吞掉 input 窗口)
const ph = game.phase;
const r = game.round;
await ctx.waitCond(() => game.phase !== ph || game.round !== r);
}
ctx.status(`收工:完成了 ${game.round - 1} 轮`);
ctx.status(`收工:完整打完 ${game.round} 轮,在轮与轮的边界停手(免费重听剩 ${game.chances} 次)`);
}
+29 -18
View File
@@ -1,7 +1,9 @@
/**
* 《盲航》新手会话剧本:首局完整演练 D1-D4(第二颗故意超时,听当场重教)→
* 正式对局躲过保护期(20s/45s 的加速/护盾引入都照做)→ 保护期结束后故意漏一颗
* (触发归因句 + 向导「同向滑动就能拨开」指令)→ 再正常躲几颗收工。
* 正式对局躲过保护期(20s/45s 的加速/护盾引入都照做)→ 保护期结束后故意漏两颗
* (第 1 次听归因+向导「同向滑动就能拨开」,第 2 次听 gd_last_life「最后一次机会」
* ——round2 F2.2:受击经济透明)→ 之后全部躲开,保护期结束后再撑 20 秒以上收工
* (不再像 round1 那样在保护期后 8 秒就撤,也没有第三次被撞)。
*/
export function describe(game) {
const DIR = { left: '左边', right: '右边', up: '上面', down: '下面' };
@@ -45,46 +47,55 @@ export async function session(ctx, mock) {
}
ctx.status('演练做完了,要出发了');
// 正式对局:保护期 60s + 结束后再躲一小段就收工(不再模拟,避免挂机撞毁)。
// 每颗陨石生成(呼啸声起)就立即同向拨开——比盯着 1.5s 判定窗口更稳。
// 正式对局:保护期 60s + 结束后再撑 20s 以上收工。
// 每颗陨石生成(呼啸声起)就在判定窗口将开时同向拨开——比盯着 1.5s 判定窗口更稳。
//(刷怪链是自驱动的定时器链——禁止裸 waitIdle,等待一律挂条件)
await ctx.waitCond(() => game.phase === 'playing');
ctx.status('出发。引擎声起来了,保护期里先练手');
const dodged = new Set();
let victim = null; // 保护期结束后故意不躲的那颗
let eaten = false;
const victims = []; // 保护期结束后故意不躲的(前两颗:听归因 + gd_last_life)
let eaten = 0;
while ((game.phase === 'playing' || game.phase === 'feature')
&& performance.now() < game.protectionUntil + 8000) {
&& performance.now() < game.protectionUntil + 21000) {
if (game.phase === 'feature') {
ctx.status('游戏停下来教新功能');
await ctx.waitIdle(); // 引入句听完(feature 态无刷怪链,可安全等待空闲)
// 引入句听完就照做(不裸 waitIdle——feature 态之后的刷怪链是自驱动定时器,
// 裸等会把整局对局连本带利烧穿到坠毁)
await ctx.waitCond(() => !ctx.voiceBusy());
ctx.note(game.featureKind === 'boost' ? '照它说的单击加速' : '照它说的双击开护盾');
await ctx.gesture(game.featureKind === 'boost' ? 'tap' : 'doubletap');
continue;
}
if (victim && !game.obstacles.has(victim)) {
ctx.note('被撞了。先听到方位归因,又听到「同向滑动就能拨开」');
victim = null;
// 被撞的那颗消失 → 归因链听完了,继续
const eatenIdx = victims.findIndex((v) => !game.obstacles.has(v));
if (eatenIdx >= 0) {
victims.splice(eatenIdx, 1);
ctx.note(eaten === 2 && victims.length === 0
? '第二次被撞:先听方位归因,又听到「这是最后一次机会,撑住」——这就是全部家底了'
: '被撞了。先听到方位归因,又听到「同向滑动就能拨开」');
continue;
}
// 只在判定窗口将开未开时出手(警报第一响 = 撞击前 1s,窗口内)
const target = [...game.obstacles].find((o) => !dodged.has(o)
&& o.impactAt - performance.now() < 1300);
if (target) {
if (!eaten && performance.now() >= game.protectionUntil) {
eaten = true;
victim = target;
if (eaten < 2 && performance.now() >= game.protectionUntil) {
eaten++;
victims.push(target);
dodged.add(target); // 标记 = 故意不躲
ctx.status('保护期结束了,这颗我故意没动');
ctx.status(eaten === 1
? '保护期结束了,这颗我故意没动'
: '又有一颗,也故意不躲——想听听第二次被撞会说什么');
continue;
}
dodged.add(target);
await ctx.gesture(target.dir);
continue;
}
await ctx.waitCond(() => (game.phase !== 'playing' && game.phase !== 'feature')
|| (victim && !game.obstacles.has(victim))
await ctx.waitCond(() => game.phase !== 'playing'
|| victims.some((v) => !game.obstacles.has(v))
|| [...game.obstacles].some((o) => !dodged.has(o) && o.impactAt - performance.now() < 1300));
}
ctx.status('躲过保护期、挨过一次撞,心跳有点快,先到这');
const aliveSec = Math.round((performance.now() - game.runStart) / 1000);
ctx.status(`保护期结束后撑了 ${aliveSec - 60} 秒、挨了两下还活着(警报也只起一波了),先到这`);
}
+44 -17
View File
@@ -1,7 +1,9 @@
/**
* 《声命线》新手会话剧本:像真人一样等语音播完再动、偶尔走神(触发看门狗)、
* 在非抉择节点试一次无效的左滑(触发向导纠错)、菜单进去逛一圈再出来、
* 抉择点一律选左,一路打到结局。
* 《声命线》新手会话剧本:像真人一样等语音播完再动、偶尔走神(触发看门狗,
* 听到轻响后 5 秒内回应——round2 修正剧本失真:round1 的「干晾 85 秒」是剧本
* 问题不是游戏问题)、在非抉择节点试一次无效的左滑(触发向导纠错)、中途演示
* 一次语速 2.2 切换(F9:设置下一句即时生效,转写状态行可见语速档)、菜单逛
* 一圈再出来、抉择点一律选左,一路打到结局。
*/
export function describe(game) {
if (game.mode === 'MENU') return '菜单里:左滑重开、右滑或单击返回剧情、上滑听帮助';
@@ -15,17 +17,21 @@ export async function session(ctx, mock) {
const { game } = ctx;
let invalidTried = false;
let menuDone = false;
let speedDemoDone = false;
let speedBackDone = false;
// 开场:n00 全听完,愣一会儿——向导看门狗先说话
await ctx.waitIdle();
ctx.status('开场旁白播完了,屏幕黑着,有点不知所措');
ctx.silence(8, '新手没反应过来');
await ctx.waitVirtual(8600); // 触发看门狗:nudge + gd_continue
ctx.status('听到一声轻响和「轻触屏幕,继续」,试着点一下');
await ctx.gesture('tap');
// 开场:gd_brief 先给操作口令(round2 F8),n00 全听完,再愣一会儿——看门狗 25s 才说话
await ctx.waitCond(() => !game.voicePlaying); // 只等链播完(不空烧看门狗窗口)
ctx.status('开场口令和旁白播完了,屏幕黑着,走神想剧情去了');
ctx.silence(25, '在入戏,不是迷路(round2:剧情类等待默认低频)');
await ctx.waitVirtual(25600); // 触发看门狗:nudge + gd_continue
ctx.status('听到一声轻响和「轻触屏幕,继续」,马上点一下');
await ctx.gesture('tap'); // 听到提示 ≤5s 内反应
for (let i = 0; i < 60 && !game.awaitingRestart && mock.now < 20 * 60 * 1000; i++) {
await ctx.waitIdle(); // 等当前语音链播完(真人不会抢话)
// 只等语音链播完(真人不会抢话)。不能裸 waitIdle——会把等待态看门狗的两级
// 提醒整段空烧完才动手(round1 剧本失真根源),链一停就该轮到玩家了
await ctx.waitCond(() => !game.voicePlaying);
if (game.awaitingRestart) break;
@@ -45,6 +51,27 @@ export async function session(ctx, mock) {
continue;
}
// F9:演示一次语速切换——2.2 档对下一句即时生效,听两段再调回 1.0
if (!speedDemoDone && game.nodeId === 'n10') {
speedDemoDone = true;
ctx.note('旁白有点慢,去设置把语速调到 2.2(下一句生效)');
ctx.settings.set({ speed: 2.2 });
if (game.choiceArmed) {
ctx.note('选了左边(循声而去)——后面几步都是 2.2 档');
await ctx.gesture('left'); // n10 抉择 → n11,2.2 倍速播
} else {
await ctx.gesture('tap');
}
await ctx.waitIdle();
ctx.status('语速 2.2 听着赶趟');
continue;
}
if (speedDemoDone && !speedBackDone && game.nodeId === 'n12') {
speedBackDone = true;
ctx.note('听了两段,把语速调回 1.0');
ctx.settings.set({ speed: 1.0 });
}
if (game.choiceArmed) {
ctx.status('抉择点,提示音念完了');
if (mock.now % 2 < 1) await ctx.waitVirtual(3000); // 偶尔犹豫一下
@@ -59,19 +86,19 @@ export async function session(ctx, mock) {
invalidTried = true;
ctx.note('试着左滑了一下——这里用不上,向导纠正了我');
await ctx.gesture('left');
await ctx.waitIdle();
await ctx.waitVirtual(2000); // 让纠错句播完(不空烧看门狗窗口)
} else if (i % 4 === 2) {
ctx.silence(8, '走神想剧情去了');
await ctx.waitVirtual(8600); // 偶尔挂机,触发看门狗轻提醒
ctx.status('又听到那声轻响,想起该点屏幕了');
ctx.silence(25, '又走神想剧情去了');
await ctx.waitVirtual(25600); // 偶尔挂机,触发看门狗轻提醒(25s 才响,不轰炸)
ctx.status('又听到那声轻响,5 秒内想起该点屏幕了');
}
await ctx.gesture('tap');
}
await ctx.waitIdle();
await ctx.waitCond(() => !game.voicePlaying);
ctx.status('结局');
ctx.note('听完结局,单击重开一次看看别的走向');
await ctx.gesture('tap'); // 结算态单击重开
await ctx.waitIdle();
await ctx.waitCond(() => !game.voicePlaying);
ctx.status('回到开头,会话结束');
}
+17 -7
View File
@@ -93,7 +93,11 @@ function capture(audio) {
pending.push(`${fmtT()} ♫ BGM 切换 → ${id}${opts?.fadeMs ? `(淡入 ${opts.fadeMs}ms)` : ''}`);
}
} else if (isVoice && id.startsWith('voice/gd_')) {
// 向导语音经事件钩子渲染(▶ 向导语音),此处不重复记行
// 向导语音(round2):实际播出的在本行渲染(含全文);被 idle-skip 跳过的
// 不产生 play 调用,由事件钩子单独标注(见 captureGuide)
pending.push(t
? `${fmtT()} ▶ 向导语音:「${t}」`
: `${fmtT()} ▶ 向导语音 voice/${id.slice(6)}(无台词表文本)`);
} else if (isVoice) {
pending.push(t
? `${fmtT()} ▶ 语音:「${t}」`
@@ -105,15 +109,17 @@ function capture(audio) {
};
}
/** 向导实例接入事件流(每个 earcon/插播语音都转写成玩家可感知的一行)。 */
/** 向导实例接入事件流(每个 earcon / 被 idle-skip 的语音都转写成玩家可感知的一行;
* 实际播出的向导语音已由 audio.play 捕获渲染,此处不再重复记行)。 */
function captureGuide(game) {
if (!game.guide) return;
game.guide.setEventHook((evt) => {
if (evt.voiceId) {
if (!evt.skipped) return; // 播出的由 capture() 渲染,避免重复
const t = textOf(`voice/${evt.voiceId}`);
pending.push(t
? `${fmtT()} ▶ 向导语音:「${t}」`
: `${fmtT()} ▶ 向导语音 ${evt.voiceId}(无台词表文本)`);
? `${fmtT()} ▶ 向导语音(对白在播,本句按 idle-only 跳过未播):「${t}」`
: `${fmtT()} ▶ 向导语音 ${evt.voiceId}(idle-only 跳过未播)`);
} else {
const label = GUIDE_LABEL[evt.name] || '';
pending.push(`${fmtT()} ♪ 向导音:${evt.name}${label ? `(${label})` : ''}`);
@@ -122,8 +128,11 @@ function captureGuide(game) {
}
function drain() {
// 震动/资产缺失按「实际触发时刻」落行(v2 起不再用 drain 时刻近似——
// round2 审计指标要核 danger 震 ≥400ms 去抖、警报 600ms 去重,必须看真实时间)
for (const m of mock.vibrateLog.splice(0)) {
pending.push(`${fmtT()} 〰 震动:${hapticName(m.pattern)}`);
const t = `t+${(m.t / 1000).toFixed(1).padStart(6)}s`;
pending.push(`${t} 〰 震动:${hapticName(m.pattern)}`);
}
// 引擎按 ogg→m4a→mp3→wav、性别目录→平铺 逐个探测;只有全部路径都 404,
// 玩家才是真的什么都没听到(角色音探测性别目录 404 后平铺成功属正常回退)
@@ -131,7 +140,7 @@ function drain() {
const key = m.url.replace(/^audio\//, '').replace(/\.\w+$/, '').replace(/^voice\/(male|female)\//, 'voice/');
const everOk = mock.fetchedLog.some((f) => f.ok
&& f.url.replace(/^audio\//, '').replace(/\.\w+$/, '').replace(/^voice\/(male|female)\//, 'voice/') === key);
if (!everOk) pending.push(`${fmtT()} ⚠ 资产缺失 ${key} → 玩家此刻听到的是静默`);
if (!everOk) pending.push(`t+${(m.t / 1000).toFixed(1).padStart(6)}s ⚠ 资产缺失 ${key} → 玩家此刻听到的是静默`);
}
pending.sort((a, b) => parseFloat(a.match(/t\+(\s*)(\d+)/)?.[2] ?? 0) - parseFloat(b.match(/t\+(\s*)(\d+)/)?.[2] ?? 0));
lines.push(...pending);
@@ -164,7 +173,8 @@ function makeCtx(audio, game, describe) {
status: (tag) => {
const d = describe ? describe(game) : '';
const v = voiceBusy(audio) ? '语音播放中' : (audio._bgm ? `BGM=${audio._bgm?.id ?? '无'}` : '无 BGM');
lines.push(`${fmtT()} 【状态】${tag || ''}${d ? ` ${d}` : ''}(${v})`);
// 语速档随状态行输出(round2 F9:转写需可见语速切换生效)
lines.push(`${fmtT()} 【状态】${tag || ''}${d ? ` ${d}` : ''}(${v},语速${settings.get().speed})`);
},
silence: (sec, why) => {
lines.push(`${fmtT()} ……… 静默 ${sec}s${why ? `(${why})` : ',游戏在等待玩家输入'} ………`);
+13 -3
View File
@@ -39,6 +39,8 @@
"skip_hint": "单击可跳过旁白。",
"gd_continue": "轻触屏幕,继续。",
"gd_invalid": "这个操作在这里用不上。",
"gd_brief": "单击继续剧情。请选择的时候,左右滑动。",
"gd_again": "还在吗?单击继续。",
"n02_linwan": "咳……咳咳……我还活着。晨晨,你在哪儿……你说过,会在北区等我。姐一定,一定找到你。",
"n03_radio": "收音机……还好,没摔坏。等等,这是什么……这段录音,是三天前的……",
"n03_msg": "姐,如果你听到这个,说明你还活着,太好了。听着,跟着广播塔的红色信号灯走,别相信任何自称救援队的人。北区,北区有真正的避难所。我在这儿等你。姐,一定要来。",
@@ -199,7 +201,8 @@
"menu_home": "回大厅。",
"menu_back": "返回游戏。",
"skip_hint": "单击可跳过旁白。",
"gd_relisten": "需要的话,双击可以重新听一遍序列。"
"gd_relisten": "需要的话,双击可以重新听一遍序列。",
"gd_economy": "记不住就双击重新听,免费机会有两次,用完要扣一次机会。"
},
"rhythm": {
"welcome": "欢迎来到节拍行者。跟着音乐,我奏一句,你跟一句。",
@@ -235,7 +238,10 @@
"menu_lobby": "回大厅。",
"skip_hint": "单击可跳过旁白。",
"gd_answer": "现在跟着我打。",
"gd_miss": "慢了半拍,下一小节跟上我就行。"
"gd_miss": "慢了半拍,下一小节跟上我就行。",
"gd_miss_early": "抢拍了。等声音落下再滑。",
"gd_miss_late": "慢了半拍。跟着节拍走。",
"gd_miss_wrong": "方向不对。跟着音色滑。"
},
"space": {
"drill_welcome": "欢迎登上盲航。你只有一双耳朵。",
@@ -291,7 +297,8 @@
"settings_hint": "音色、语速、背景音乐,请回大厅修改。",
"paused": "已暂停。",
"skip_hint": "单击可跳过旁白。",
"gd_hit_hint": "被撞了。听方位,同向滑动就能拨开。"
"gd_hit_hint": "被撞了。听方位,同向滑动就能拨开。",
"gd_last_life": "这是最后一次机会,撑住。"
},
"rpg": {
"intro_1": "你的眼睛看不见了。但你的耳朵,比谁都灵。",
@@ -414,6 +421,9 @@
"boss1_warn": "里面就是黑风寨主。你现在回去练练再来,也来得及。",
"gd_parry_miss": "前摇声起的时候双击,才能格挡。",
"gd_stall": "想好了吗?左右滑动选择,单击出手。",
"tut_b2b": "挡得漂亮。",
"gd_wrap_first": "回到第一个。",
"gd_wrap_last": "最后一个了。",
"npc_headman_1": "外乡人,林子里不太平。这五十金,拿着防身。",
"npc_headman_2": "商店就在村里,缺什么就去看看。",
"npc_headman_3": "听说你是用耳朵走路的剑客?保重啊。",
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+41 -15
View File
@@ -35,10 +35,13 @@
* main 演奏阶段单击是演奏手势不跳(mid_song 短句按裁定不跳)。
* 长按快进豁免(12 文档 §2.2:main 演奏期):audio.setRateBoostEnabled(false)
* per-game 开关(引擎已支持),进 main 关、进菜单/结算/退出恢复。
* 向导层(research/03-schemes/schemes.md §2):每曲首次领奏→应答切换推
* gd_answer(turn cue);连续 2 次 Miss 播 gd_miss(queue 插播,同曲一次);
* 结算链换 _speakGuarded(资产缺失不卡结算 BGM);状态签名 earcon 与状态
* BGM 同点(select=menu、main=battle、end=settle)。
* 向导层(docs/design/14-guide-layer.md + round2 synthesis F2.1/F3):每曲首次
* 领奏→应答切换在谱面定时队列直接落 gd_answer(不走 guide queue,保证窗口前
* 听到)+ turn earcon;教学 Miss 首次即归因分档(gd_miss_early/late/wrong,
* 挂纠正链尾空闲点);连续 2 Miss 播 gd_miss(queue 插播,同曲一次);暂停
* 断点续播前插 4 拍 count-in(off 档保持即时续播);结算链换 _speakGuarded
* (资产缺失不卡结算 BGM);状态签名 earcon 与状态 BGM 同点
* (select=menu、main=battle、end=settle)。
*/
import { Game } from '../engine/game.js';
import { Guide } from '../engine/guide.js';
@@ -341,7 +344,7 @@ export class RhythmGame extends Game {
this._tutFails++;
vibrate('error');
if (this._tutFails >= 2) {
// 同一步失败 2 次跳过,不卡流程
// 同一步失败 2 次跳过,不卡流程(归因已在首次失败时给过,此处不再叠加)
this._speakGuarded(['voice/retry_skip'], () => {
if (this._tutIdx + 1 < TUT_STEPS.length) this._beginTutorial(rid, this._tutIdx + 1);
else {
@@ -352,13 +355,22 @@ export class RhythmGame extends Game {
});
return;
}
// 纠正语音:手势错 > 过早 > 过晚 > 没出手(retry_listen)
let correction = 'voice/retry_listen';
// F2.1(round2):教学 Miss 首次即归因、按失败原因分档——方向错 gd_miss_wrong /
// 抢拍 gd_miss_early / 慢拍 gd_miss_late(无在窗内尝试=没跟上拍,归入慢拍)。
// 向导句(full 档)挂在纠正链播完后的空闲点发:idle-only 语义下链中插播会被
// 跳过;之后 _tutLead 只排 sfx,不与归因句抢 voice 通道。
const wrong = this._tutAttempts.find((a) => !a.correct);
const offbeat = this._tutAttempts.find((a) => a.correct && Math.abs(a.dt) > GOOD);
const missGrade = wrong ? 'gd_miss_wrong'
: offbeat ? (offbeat.dt < 0 ? 'gd_miss_early' : 'gd_miss_late') : 'gd_miss_late';
// 纠正语音:手势错 > 过早 > 过晚 > 没出手(retry_listen)
let correction = 'voice/retry_listen';
if (wrong) correction = 'voice/retry_gesture';
else if (offbeat) correction = offbeat.dt < 0 ? 'voice/retry_early' : 'voice/retry_late';
this._speakGuarded([correction, 'voice/retry_listen'], () => this._tutLead(rid));
this._speakGuarded([correction, 'voice/retry_listen'], () => {
this.guide.error({ voice: missGrade, skipHaptic: true });
this._tutLead(rid);
});
}
// ---------------- 选曲 ----------------
@@ -428,11 +440,16 @@ export class RhythmGame extends Game {
this._timed.push({ time: leadBarT0, fn: () => this.audio.play('voice/mid_song') });
}
if (i === 0) {
// 向导层(schemes §2):每曲首次领奏→应答切换推 gd_answer(turn cue,
// 谱面排程处入 _timed,mid_song 同款先例;full 档语音 queue 插播不顶 BGM)
// 向导层(round2 F3):每曲首次领奏→应答切换的交接标记。gd_answer 不走
// guide 的 queue(对白在播时会排到链尾、错过首个应答窗口)——排进曲谱
// 定时队列、在窗口前直接落下;turn earcon 走向导(full 语音,standard
// 只听提示音,off 全静)。mid_song 同款先例。
this._timed.push({
time: leadBarT0 + 4 * spb,
fn: () => this.guide.turn({ voice: 'gd_answer' }),
fn: () => {
if (this.guide.level() === 'full') this.audio.play('voice/gd_answer');
this.guide.turn({});
},
});
}
bar += 2 + (this._song.restAfter[i] || 0);
@@ -625,14 +642,23 @@ export class RhythmGame extends Game {
_closeMenu(rid) {
if (this._runId !== rid) return;
const resumeAt = this.audio.ctx.currentTime + 0.1;
const shift = resumeAt - this._pauseAt;
// 全部绝对时刻平移暂停时长,节拍网格与 BGM 断点续播保持同步
// F3(round2):断点续播前插一整小节 4 拍 count-in(复用底鼓节拍音,走既有
// _sched 网格排程 = 每拍一个 inst_kick + beat 轻震),网格与 BGM 整体后移一小节,
// 杜绝 P1/P2/P3 实测的「回来 5 秒死寂、第一句报废」。off 档保持原即时续播
// (向导层 off = 零改动路径)。
const countIn = this.guide.level() !== 'off' ? 4 * (60 / this._song.bpm) : 0;
const bgmAt = resumeAt + countIn;
const shift = bgmAt - this._pauseAt;
// 全部绝对时刻平移(暂停时长 + count-in 小节),节拍网格与 BGM 断点续播保持同步
for (const ev of this._sched) ev.time += shift;
for (const ev of this._timed) ev.time += shift;
for (const cue of this._cues) cue.expected += shift;
this._phaseEnd += shift;
// _startBgm 内部重算 _bgmT0 = resumeAt - offset = 旧网格 + shift
this._startBgm(this._song, resumeAt, this._bgmOffset);
for (let i = 0; i < 4 && countIn > 0; i++) {
this._sched.push({ time: resumeAt + i * (countIn / 4), sfx: INST.tap, volume: 0.7 });
}
// _startBgm 内部重算 _bgmT0 = bgmAt - offset = 旧网格 + shift(负偏移保护同款成立)
this._startBgm(this._song, bgmAt, this._bgmOffset);
this.phase = 'main';
this.audio.setRateBoostEnabled(false); // 回到演奏期
this._startPump(rid);
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+33 -9
View File
@@ -137,6 +137,8 @@ export class RpgGame extends Game {
this._ambId = null;
// playSec 计时
this._segStart = null;
// F7:回绕提示去重(同一次菜单打开内每句最多 1 次)
this._wrapShown = new Set();
this._newRun();
}
@@ -257,6 +259,7 @@ export class RpgGame extends Game {
this.scene = id;
this.optIdx = 0;
this.phase = 'explore';
this._wrapShown = new Set(); // F7:场景选项列表重新"打开",回绕提示重新可用
this._switchSceneAudio(id, opts.fadeMs || 800);
this.audio.play('sfx/step', { volume: 0.8 });
@@ -307,11 +310,22 @@ export class RpgGame extends Game {
return false;
}
_speakOption() {
_speakOption(onComplete) {
if (this.phase !== 'explore') return;
const opts = SCENES[this.scene].options;
if (!opts.length) return;
this.audio.play(`voice/opt_${opts[this.optIdx]}`);
this.audio.play(`voice/opt_${opts[this.optIdx]}`, { onComplete });
}
/**
* F7(round2):循环列表切回首项/末项的回绕提示(P1「不报回绕」)。full 档
* queue 跟在本项报名之后;同一次菜单打开内每句最多 1 次(_wrapShown),别每滑必播。
*/
_wrapCue(voiceId) {
if (this.guide.level() !== 'full') return;
if (this._wrapShown.has(voiceId)) return;
this._wrapShown.add(voiceId);
this.audio.play(`voice/${voiceId}`, { queue: true });
}
_onExploreGesture(g) {
@@ -320,7 +334,10 @@ export class RpgGame extends Game {
if (g === 'left' || g === 'right') {
const n = opts.length;
this.optIdx = (this.optIdx + (g === 'right' ? 1 : n - 1)) % n;
this._speakOption();
const wrap = n >= 2 && ((g === 'right' && this.optIdx === 0) || (g === 'left' && this.optIdx === n - 1))
? (this.optIdx === 0 ? 'gd_wrap_first' : 'gd_wrap_last') : null;
// 回绕提示排在本项报名播完之后;被下一次滑动打断则自然作废(不迟到误导)
this._speakOption(wrap ? () => this._wrapCue(wrap) : undefined);
} else if (g === 'tap') {
// voice 播放中单击 = 跳过本句(12 文档 §1:场景 desc/NPC 对白/事件/状态链可跳),
// 语音链跳下一句;无 voice 时单击 = 确认选项(原语义)。
@@ -605,13 +622,14 @@ export class RpgGame extends Game {
const b = this._battle;
if (!b) return;
b.state = 'PLAYER_SELECT';
this._wrapShown = new Set(); // F7:指令循环重新"打开",回绕提示重新可用
// 玩家回合的听觉标点(11 文档 §5):turn_player(≤0.4s)→ 留 200ms+ 间隙 → 指令名
this.audio.play('sfx/turn_player');
// 向导层静默看门狗(schemes §2):探索/常规战斗本无卡关计时,PLAYER_SELECT
// 挂机永远静默等——60s 无输入 → nudge + gd_stall(一级即止,自动停止)
// 向导层静默看门狗(round2 F1.3:60s→30s,等待默认低频但不再干晾一分钟):
// PLAYER_SELECT 挂机永远静默等——30s 无输入 → nudge + gd_stall(一级即止)
this.guide.watchdog({
arm: true,
at: [60000],
at: [30000],
say: ['gd_stall'],
cond: () => this.phase === 'battle' && this._battle === b && b.state === 'PLAYER_SELECT',
});
@@ -666,7 +684,11 @@ export class RpgGame extends Game {
if (b.state === 'PLAYER_SELECT') {
if (g === 'left' || g === 'right') {
b.cmdIdx = (b.cmdIdx + (g === 'right' ? 1 : CMDS.length - 1)) % CMDS.length;
this.audio.play(`voice/cmd_${CMDS[b.cmdIdx]}`);
// F7:指令循环回绕提示(探索菜单同款,full 档,跟在本项报名之后)
const wrap = (g === 'right' && b.cmdIdx === 0) || (g === 'left' && b.cmdIdx === CMDS.length - 1)
? (b.cmdIdx === 0 ? 'gd_wrap_first' : 'gd_wrap_last') : null;
this.audio.play(`voice/cmd_${CMDS[b.cmdIdx]}`,
wrap ? { onComplete: () => this._wrapCue(wrap) } : undefined);
// 教学 T0:切换 2 次(听到指令名切换)即过关
if (b.teachStage === 'menu' && ++b.teachSwipes >= 2) this._teachAdvance('attack');
} else if (g === 'tap') {
@@ -830,10 +852,12 @@ export class RpgGame extends Game {
p.hp -= dmg;
if (b.enc.teach) p.hp = Math.max(1, p.hp); // 教学战 HP 锁底 1(必不死)
console.log(`[RpgGame] 敌方攻击 dmg=${dmg} 格挡=${b.parry} 玩家HP=${p.hp}/${this.maxHp}`);
// 教学 T2:格挡成功或受击均可通过 → 进入防御教学 T3(11 文档 §2)
// 教学 T2:格挡成功或受击均可通过 → 进入防御教学 T3(11 文档 §2)。
// F4(round2):格挡**成功**通过时先插 tut_b2b「挡得漂亮。」确认表现,
// 消除 P1/P4「明明挡住了还说来不及格挡」的歧义(失败路径 tut_b4 不变)。
if (b.teachStage === 'parry') {
b.teachStage = 'defend';
const ids = b.parry ? ['voice/tut_b6'] : ['voice/tut_b4', 'voice/tut_b6'];
const ids = b.parry ? ['voice/tut_b2b', 'voice/tut_b6'] : ['voice/tut_b4', 'voice/tut_b6'];
this._speakGuarded(ids, () => this._playerSelect(), 10000);
this._armTeachWatchdog('defend');
return;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+12 -19
View File
@@ -64,10 +64,10 @@ const TEACH_BLUNT = {
const BEST_KEY = 'soundgame.simon.best';
const AUTH_KEY = 'soundgame.auth'; // 大厅登录写入(见 web/index.html)
// 向导层(schemes.md §2):input 阶段静默看门狗——10s 轻提醒+gd_relisten(不改变
// 重听扣机会规则),25s 免扣机会自动重听一次(每局 2 次)
// 向导层(14 号文档 + round2 synthesis F1.2):input 阶段静默看门狗——10s 轻提醒
// +gd_relisten(不改变重听扣机会规则);25s 升级取消自动重播序列(P1:泄题且与
// 扣机会规则矛盾)→ 改为 confirm 震 + turn earcon(纯标记,无语音)
const GUIDE_INPUT_WATCHDOG_AT = [10000, 25000];
const GUIDE_AUTO_RELISTEN_MAX = 2;
export class SimonGame extends Game {
constructor(audio, root) {
@@ -87,7 +87,6 @@ export class SimonGame extends Game {
this._menuIdx = 0;
this._session = 0; // 会话令牌:重开后使旧计时器/语音链回调失效
this._timers = [];
this._guideRelistens = 0; // 向导免机会自动重听已用次数(每局重置)
}
start() {
@@ -206,8 +205,10 @@ export class SimonGame extends Game {
if (this.teachIdx + 1 < GESTURES.length) {
this._teachStep(this.teachIdx + 1);
} else {
// T7:收尾两句 → 正式对局
this._speakGuarded(['voice/t7_ready', 'voice/t7_retry'], () => this._beginGame(), 12000);
// T7:收尾两句 + 机会经济一句(round2 F5:记不住就双击重听、免费两次、
// 用完扣机会——P1「经济不透明」开局一句话讲清)→ 正式对局
this._speakGuarded(['voice/t7_ready', 'voice/t7_retry', 'voice/gd_economy'],
() => this._beginGame(), 20000);
}
},
});
@@ -233,7 +234,6 @@ export class SimonGame extends Game {
this.sequence = [];
this.round = 0;
this.chances = 1;
this._guideRelistens = 0; // 向导免机会自动重听每局重置
this._nextRound();
}
@@ -332,26 +332,19 @@ export class SimonGame extends Game {
_toInput() {
this.phase = 'input';
this.inputIdx = 0;
// 向导层静默看门狗(schemes §2,全 6 款最危险的静默等待):input 阶段无 BGM
// 无超时,挂机永久静默。10s → nudge + gd_relisten 提示(不改变重听扣机会
// 规则);25s → 免扣机会自动重听序列一次(每局 2 次),防挂机死等。
// 向导层静默看门狗(round2 F1.2 礼仪重构):input 阶段无 BGM 无超时,挂机
// 永久静默。10s → nudge + gd_relisten 提示(不改变重听扣机会规则);25s 升级
// **取消免扣机会自动重播序列**(P1:等答案自动重念,双击重听的经济就矛盾了,
// 还顺手拆了记忆难度)→ 只发 confirm 震 + turn earcon(纯标记,无语音)。
this.guide.watchdog({
arm: true,
at: GUIDE_INPUT_WATCHDOG_AT,
say: ['gd_relisten', null],
onEscalate: (n) => { if (n === 2) this._guideAutoRelisten(); },
onEscalate: (n) => { if (n === 2) this.guide.turn({ haptic: true }); },
cond: () => this.phase === 'input',
});
}
/** 向导兜底:免扣机会自动重听当前序列(每局最多 2 次,序列不变进度重输)。 */
_guideAutoRelisten() {
if (this._guideRelistens >= GUIDE_AUTO_RELISTEN_MAX) return;
this._guideRelistens++;
if (this.phase !== 'input') return;
this._playSequence();
}
_onInput(g) {
// 双击的上下文判定(见文件头"设计冲突的处理"):
// 期望输入是 doubletap → 答题;否则 → 主动重听(耗机会)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+35 -15
View File
@@ -31,9 +31,10 @@
* 语音 id 权威清单:docs/design/04-space-voice-ids.md(52 句双性别);
* 旧 num 拼接件、n_ 前缀件、intro1-4/hit1/hit2/gameover/restart 全部弃用。
*
* 向导层(research/03-schemes/schemes.md §2):对局第 1 次受击(保护期外)追加
* 指令式 gd_hit_hint(归因句 hit_<dir> 之外的"该怎么拨");结算链换
* _speakGuarded(资产缺失不卡结算 BGM);长按快进对局期豁免
* 向导层(docs/design/14-guide-layer.md + round2 synthesis F2.2/F6):对局第 1 次
* 受击(保护期外)在归因链播完后补指令式 gd_hit_hint、第 2 次补 gd_last_life
* (idle-only:挂在语音空闲点);警报音效 600ms 去重、danger 震 ≥400ms 去抖;
* 结算链换 _speakGuarded(资产缺失不卡结算 BGM);对局期长按快进豁免
* (setRateBoostEnabled);状态签名 earcon 与状态 BGM 同点。
*/
import { Game } from '../engine/game.js';
@@ -120,6 +121,10 @@ export class SpaceGame extends Game {
this.shieldIntroDone = false;
this.featureKind = null;
this._menuIdx = 0;
// F6:警报去重 / danger 去抖时间戳(soft/loud 分档 + danger 全局)
this._alarmSoftAt = -Infinity;
this._alarmLoudAt = -Infinity;
this._dangerAt = -Infinity;
}
// ---------------- 生命周期 ----------------
@@ -449,22 +454,31 @@ export class SpaceGame extends Game {
this.obstacles.add(ob);
this.audio.play(APPROACH_SFX[dir]);
// 警报两档渐强,DANGER 震动同步叠加
// 警报两档渐强,DANGER 震动同步叠加。
// F6(round2):警报音效同 600ms 窗口去重(双陨石同时逼近只起一波警报,
// 按 soft/loud 各自去重,单颗的两段渐强不受影响);danger 震 ≥400ms 去抖。
this._after(impactAt - ALARM_LEAD_MS - now, () => {
if (this.obstacles.has(ob)) {
this.audio.play('sfx/alarm', { volume: 0.5 });
vibrate('danger');
}
if (this.obstacles.has(ob)) this._alarm(0.5);
});
this._after(impactAt - ALARM_LEAD_MS / 2 - now, () => {
if (this.obstacles.has(ob)) {
this.audio.play('sfx/alarm', { volume: 1 });
vibrate('danger');
}
if (this.obstacles.has(ob)) this._alarm(1);
});
this._after(impactAt - now, () => this._resolve(ob));
}
/** 一段警报:同档 600ms 内只起一波(F6);danger 震全局限 ≥400ms 间隔。 */
_alarm(volume) {
const now = performance.now();
const key = volume < 1 ? '_alarmSoftAt' : '_alarmLoudAt';
if (now - this[key] < 600) return;
this[key] = now;
if (now - this._dangerAt >= 400) {
this._dangerAt = now;
vibrate('danger');
}
this.audio.play('sfx/alarm', { volume });
}
/** 撞击点判定:加速增益 > 同向滑动 > 护盾 > 保护期训练护盾 > 撞击。 */
_resolve(ob) {
if (!this.obstacles.has(ob) || this.phase !== 'playing') return;
@@ -508,12 +522,18 @@ export class SpaceGame extends Game {
} else {
const ids = [`voice/hit_${ob.dir}`];
if (this.hits === 2) ids.push('voice/hit_last');
this._speak(ids);
// 向导层(schemes §2):对局第 1 次受击追加指令式提示(hit_<dir> 已归因方位,
// 这句补"该怎么拨";已震 hit 免重复 error 震;onceKey 同局一次)
// 向导层(round2 F1.4/F2.2):受击提示挂在归因链播完后的空闲点——
// idle-only 语义下若插在链头会被判为「对白在播」整段跳过。
// 第 1 次受击补指令式 gd_hit_hint(hit_<dir> 已归因方位,这句补"该怎么拨",
// onceKey 同局一次);第 2 次受击播 gd_last_life(F2.2:生存经济透明,
// 与游戏层 hit_last「船体只剩最后一次」互补"撑住"的口径),已震 hit 免重复。
this._speak(ids, () => {
if (this.hits === 1) {
this.guide.error({ voice: 'gd_hit_hint', onceKey: 'space_hit_hint', skipHaptic: true });
} else if (this.hits === 2) {
this.guide.error({ voice: 'gd_last_life', skipHaptic: true });
}
});
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+13 -17
View File
@@ -82,8 +82,10 @@ const VOICE_WATCHDOG_MS = 30000;
/** 抉择提示音 id(voice/p1~p8):播放中单击不可跳过(12 号文档 §1.2)。 */
const PROMPT_RE = /^voice\/p\d/;
// 向导层(schemes.md §2):W1 静默看门狗阈值——8s 轻提醒+gd_continue,20s 升级重播帮助
const GUIDE_WATCHDOG_AT = [8000, 20000];
// 向导层(14 号文档 + round2 synthesis F1.1):剧情类等待默认低频——25s 轻提醒
// +gd_continue,50s 升级只重申手势(gd_again),**永不重播 help/进度/当前节点**;
// 同一等待点最多 2 次提示后彻底静默(guide.js 升级链用尽自动停止)
const GUIDE_WATCHDOG_AT = [25000, 50000];
export class StorylineGame extends Game {
constructor(audio, root) {
@@ -104,10 +106,6 @@ export class StorylineGame extends Game {
this.companion = false; // 带上了小满
this.knowTrap = false; // 装死时听到北区陷阱的情报
this.betrayed = false; // 向猎狗交出过坐标
// 向导层:升级帮助播过后不再自动重挂看门狗(L4"之后停止,不轰炸"),
// 玩家下一次手势即复位
this._guideHelped = false;
}
// ---------------------------------------------------------------- Game
@@ -123,7 +121,6 @@ export class StorylineGame extends Game {
onGesture(g) {
console.log(`[StorylineGame] onGesture: ${g} mode=${this.mode} node=${this.nodeId} armed=${this.choiceArmed} playing=${this.voicePlaying}`);
this.guide.disarm(); // 任何手势复位静默看门狗(guide=off 时 no-op)
this._guideHelped = false; // 玩家有动作,恢复看门狗自动挂载
if (this.mode === MODE.MENU) this.onMenuGesture(g);
else this.onStoryGesture(g);
}
@@ -201,6 +198,10 @@ export class StorylineGame extends Game {
this.awaitingRestart = false;
if (node.sfx) this.audio.play(node.sfx);
const seq = [...node.voices];
// F8 开场瘦身(round2):操作口令 gd_brief(≤8s)前置到开场链最前,
// 「brief→intro」;原 intro 长句里的操作教学资产保留不动。
// 向导句只在 full 档插播(off/standard 保持纯剧情链)。
if (id === 'n00' && this.guide.level() === 'full') seq.unshift('voice/gd_brief');
// 开场教学:intro 早期追加一次"单击可跳过旁白"提示(每局只播一次,重开不重播)
if (id === 'n00' && !this.skipHintShown) {
this.skipHintShown = true;
@@ -241,19 +242,14 @@ export class StorylineGame extends Game {
} else if (node.ending) {
this.awaitingRestart = true;
}
// W1/W3 静默等待看门狗(向导层,schemes §2):8s nudge+gd_continue;
// 20s 升级 = 复用 playHelpAndResume(帮助+进度+重播当前节点),升级后不再
// 自动重挂(防循环轰炸),玩家下一次手势恢复。抉择态与语音播放中不挂。
if (node.prompt == null && !this._guideHelped) {
// W1/W3 静默等待看门狗(向导层,round2 F1.1 礼仪重构):25s nudge+gd_continue;
// 50s 升级只重申手势(gd_again)——**不再重播 help+进度+当前节点**。两级用尽
// 彻底静默,玩家下一次手势(新节点播完)重新挂载。抉择态与语音播放中不挂。
if (node.prompt == null) {
this.guide.watchdog({
arm: true,
at: GUIDE_WATCHDOG_AT,
say: ['gd_continue', null],
onEscalate: (n) => {
if (n !== 2 || this.mode !== MODE.STORY) return;
this._guideHelped = true;
this.playHelpAndResume();
},
say: ['gd_continue', 'gd_again'],
cond: () => this.mode === MODE.STORY && !this.voicePlaying,
});
}