第二批反馈:引擎改造(限幅/SoundTouch不变调变速/长按快进/跳句/iOS闪光降级) + 6游戏状态BGM与试玩测试全绿

This commit is contained in:
SoundGame Dev
2026-08-30 16:45:42 +08:00
parent cc0f8c008d
commit 9a915d62c0
1163 changed files with 6346 additions and 208 deletions
+19 -12
View File
@@ -37,7 +37,7 @@
### storyline《声命线》
- voice 双性别版(male/ 与 female/ 各 36 条):旁白 + 系统音 = `help menu status1 status2 status3 p1–p8 n00_intro n01_wake n04_exit n05_signal n06_whistle n08_vent n09_dead n10_rain n11_child n13_leave n14_ignore n15_bridge n16_culvert n16b_culvert_c n17_cable n17b_cable_c n20_convoy n22_fight n23_tower e1_dawn e2_price e3_alone e4_static`
- voice 双性别版(male/ 与 female/ 各 37 条(R6 +`skip_hint`):旁白 + 系统音 = `help menu status1 status2 status3 p1–p8 n00_intro n01_wake n04_exit n05_signal n06_whistle n08_vent n09_dead n10_rain n11_child n13_leave n14_ignore n15_bridge n16_culvert n16b_culvert_c n17_cable n17b_cable_c n20_convoy n22_fight n23_tower e1_dawn e2_price e3_alone e4_static`
- voice 角色台词(**不做双性别**,沿用平铺原文件):
| 角色 | speaker | 文件 id |
@@ -50,45 +50,52 @@
约定:角色台词在两个性别目录下使用**同一份文件**(由开发复制/链接或引擎回退到平铺目录,见 round1-report)。
- sfx(12):`footsteps gunshot heartbeat radio_static rubble signal_beep struggle ui_confirm ui_menu water whistle wind`
- bgm(1):`dark_drone`
- bgm(2):`dark_drone`(剧情)+ R6 新增 `menu`(菜单 pad,-24 LUFS 档)
### escape《暗室》(R2 重设计版,最终清单见 `round2-voice-ids.md`)
- voice 双性别版(male/ 与 female/ 各 74 条):引导 `g0_intro g1_move g1_done g2_wall g3_left g4_down g5_inspect g5_key g6_doubletap g6_done g7_goal`、引导纠错 `guide_wrong_* guide_blunt_* guide_offpath guide_skip reopen_prompt`、三级提示 `stuck_ask stuck_{key,cabinet,box,gate}_{1,2,3}`、导航 `nav_{up,down,left,right}`、房间 `room_{storage,cabinet,power,gate}`、用时 `time_band1–4`、保留句 `wall start_room found_key inspect_* unlock_cabinet box_fixed fix_box escape use_fail inv_* replay again empty_1–3`、菜单 `help_ops menu_*`
- voice 双性别版(male/ 与 female/ 各 75 条(R6 +`skip_hint`):引导 `g0_intro g1_move g1_done g2_wall g3_left g4_down g5_inspect g5_key g6_doubletap g6_done g7_goal`、引导纠错 `guide_wrong_* guide_blunt_* guide_offpath guide_skip reopen_prompt`、三级提示 `stuck_ask stuck_{key,cabinet,box,gate}_{1,2,3}`、导航 `nav_{up,down,left,right}`、房间 `room_{storage,cabinet,power,gate}`、用时 `time_band1–4`、保留句 `wall start_room found_key inspect_* unlock_cabinet box_fixed fix_box escape use_fail inv_* replay again empty_1–3`、菜单 `help_ops menu_*`
- voice 已删除(R2):`n0–n9 nshi unit_min unit_sec time_prefix intro tutorial hint_key hint_cabinet hint_box hint_gate`(整句化替代);平铺 `voice/` 旧文件全部移除
- sfx(16):`amb_drip amb_hum amb_wind`(保留)+ R2 新增 `amb_storage amb_cabinet amb_power amb_gate amb_gate_live`(Stable Audio 3,6s,统一 -25dB 均值,storage/power 偏左、cabinet 偏右 pan)+ `door_creak gate_open pickup power_on spark step unlock wall win_chime`;已删除 `ui_deny`(反馈改震动)
- bgm(1):`drone`
- bgm(2):`drone`(探索)+ R6 新增 `menu`(菜单/结算 pad)
### simon《音序回响》(R2 重设计版,最终清单见 `round2-voice-ids.md`)
- voice 双性别版(male/ 与 female/ 各 68 条):交互教学 `t0_welcome t0_goal t1_up–t6_double t_try t_ok t_wrong_* t_blunt_* t7_ready t7_retry t_skip`、轮次 `round_01–round_20 round_more`(整句)、成绩 `score_band1–6`(整句)、流程 `your_turn chances_out retry_use relisten replay rules_prompt rules gameover`、反馈 `correct wrong chance_plus new_record`、菜单 `menu_*`
- voice 双性别版(male/ 与 female/ 各 69 条(R6 +`skip_hint`):交互教学 `t0_welcome t0_goal t1_up–t6_double t_try t_ok t_wrong_* t_blunt_* t7_ready t7_retry t_skip`、轮次 `round_01–round_20 round_more`(整句)、成绩 `score_band1–6`(整句)、流程 `your_turn chances_out retry_use relisten replay rules_prompt rules gameover`、反馈 `correct wrong chance_plus new_record`、菜单 `menu_*`
- voice 已删除(R2):`di lun num_shi num_0–num_9 score_pre best_pre`(拼接件)+ `intro teach_* listen`(旧连播教学);平铺 `voice/` 旧文件全部移除
- sfx(7):`gameover tone_doubletap tone_down tone_left tone_right tone_tap tone_up`;已删除 `correct wrong newrecord`(反馈改震动)
- bgm:**已于 R1 按用户要求移除**(原 `theme`,代码引用由开发删除)
- bgm:游玩**无 BGM**(用户要求);R6 新增 `menu`(菜单/结算极轻 pad,volume 0.3,10 文档 §4)
### rhythm《节拍行者》(R3 重构版,台词依据 `docs/design/03-rhythm-voice-ids.md`)
- voice 双性别版(male/ 与 female/ 各 31 条):开场流程 `welcome welcome_back tut_1 tut_2 tut_3 tut_done tut_skip mid_song`、教学纠正 `retry_early retry_late retry_gesture retry_listen retry_skip`、选曲解锁 `select_hint song1_name song2_name song3_name song_locked unlock_song2 unlock_song3`、结算 `grade_s grade_a grade_b grade_c new_best_combo restart`、菜单规则 `rules settings_hint menu_main menu_resume menu_lobby`
- voice 双性别版(male/ 与 female/ 各 32 条(R6 +`skip_hint`):开场流程 `welcome welcome_back tut_1 tut_2 tut_3 tut_done tut_skip mid_song`、教学纠正 `retry_early retry_late retry_gesture retry_listen retry_skip`、选曲解锁 `select_hint song1_name song2_name song3_name song_locked unlock_song2 unlock_song3`、结算 `grade_s grade_a grade_b grade_c new_best_combo restart`、菜单规则 `rules settings_hint menu_main menu_resume menu_lobby`
- voice 已删除(R3):`tut_start`(平铺旧 id 全部移除,其余旧 id 均在 R2 已替换)
- voice 修订(R5):`menu_main` 改词重录为「菜单。单击切换选项,双击确认。」(对齐 round2-review 裁决②全球菜单约定;选曲界面非模态菜单不受影响)
- sfx(7,R3 补齐):`inst_kick inst_snare inst_hat_l inst_hat_r inst_tom_hi inst_tom_lo inst_accent`(演奏乐器音,详见 round3-report §7;hat_l 闭合镲极左 / hat_r 开放镲极右,accent 为 A minor 合成亮音);已删除 `judge_* tick tick_accent`(R3)与 `cue_*`(R3 收尾,重构不再使用指令音)
- bgm(3,R3 重制):`track90`(90.01 BPM,85.33s)`track105`(104.99 BPM,73.14s)`track120`(120.00 BPM,72.00s),A minor 电子乐,整数小节无缝循环,loudnorm -20 LUFS;旧 `groove` 已删除
- bgm(3,R3 重制):`track90`(90.01 BPM,85.33s)`track105`(104.99 BPM,73.14s)`track120`(120.00 BPM,72.00s),A minor 电子乐,整数小节无缝循环,loudnorm -20 LUFS;旧 `groove` 已删除;R6 新增 `menu`(选曲/结算 pad)
### space《盲航》(R3 备产,台词依据 `docs/design/04-space-voice-ids.md`)
- voice 双性别版(male/ 与 female/ 各 52 条):演练 `drill_*`(14)、保护期 `protect_*`(5)、功能引入 `feat_* shield_ready shield_block`(6)、被撞归因 `hit_*`(5)、里程碑 `ms_10 ms_25 ms_50 ms_100`、结算 `time_1–4 dodge_1–4 new_record restart_hint`、菜单系统 `second_run_ask menu_* help_recap settings_hint paused`
- voice 双性别版(male/ 与 female/ 各 53 条(R6 +`skip_hint`):演练 `drill_*`(14)、保护期 `protect_*`(5)、功能引入 `feat_* shield_ready shield_block`(6)、被撞归因 `hit_*`(5)、里程碑 `ms_10 ms_25 ms_50 ms_100`、结算 `time_1–4 dodge_1–4 new_record restart_hint`、菜单系统 `second_run_ask menu_* help_recap settings_hint paused`
- voice 修订(R4):`menu_main` 改词重录为「菜单。单击切换选项,双击确认。」(对齐 round2-review 裁决②全球菜单约定)
- voice 已删除(R3):`num0–9 num_shi num_bai n_time n_sec n_dodge n_ge n_milestone intro1–4 hit1 hit2 gameover restart`(数字拼接与旧教学弃用);平铺旧文件全部移除
- sfx(12):`alarm boost crash dodge hit shield shield_block whoosh_down whoosh_left whoosh_right whoosh_up`
- bgm(1):`engine`
- bgm(2):`engine`(对局)+ R6 新增 `menu`(菜单/结算 pad)
### rpg《剑与声》(R4 新游戏第一章,台词依据 `docs/design/05-rpg-ch1-voice-ids.md` + `05-rpg-addendum.md`)
- voice 旁白双性别版(male/ 与 female/ 各 108 条,male=Uncle_Fu 说书人 / female=Serena 武侠故事):序章 `intro_1–3`、系统状态 `sys_* help_* settings_hint st_*`、场景 `desc_*`(16)、选项 `opt_*`(18)、事件 `evt_*`(6)、战斗 `btl_* cmd_* tut_*`(27,含 R5 补录 `btl_half_bandit`)、商店 `shop_*`(7)、敌方档位 `foe_hp_high/mid/low`(addendum §6.2 补录)
- voice 旁白双性别版(male/ 与 female/ 各 113 条,male=Uncle_Fu 说书人 / female=Serena 武侠故事):序章 `intro_1–3`、R6 新增首次战斗教学 `tut_battle_1–5`、系统状态 `sys_* help_* settings_hint st_*`、场景 `desc_*`(16)、选项 `opt_*`(18)、事件 `evt_*`(6)、战斗 `btl_* cmd_* tut_*`(27,含 R5 补录 `btl_half_bandit`)、商店 `shop_*`(7)、敌方档位 `foe_hp_high/mid/low`(addendum §6.2 补录)
- voice NPC 单音色平铺(14 条,落 `voice/` 根,引擎平铺回退):村长 `npc_headman_1–4`(Uncle_Fu·慈祥和缓)、老猎人 `npc_hunter_1–4`(Eric·沙哑豪爽)、寨丁头目 `npc_elite_1/half/die`(Dylan·凶狠)、黑风寨主 `npc_boss1_1/2/die`(Uncle_Fu·低沉阴狠,与村长同 speaker 不同 instruct)
- sfx(35):前摇 `tel_wolf tel_bandit tel_spider tel_elite tel_boss1 tel_croc tel_shaman tel_puppet tel_boss2`(1.0s 格挡窗口);战斗 `atk_swing atk_hit parry skill_slash enemy_die potion` + SYN `hurt flee`;探索 `chest drink_water rest door_heavy` + SYN `step coin levelup`;环境 `amb_village amb_path amb_fork amb_chest amb_stream amb_hunter amb_deepwood amb_gate amb_hall amb_swamp amb_peak`(5s 无缝循环,均值 -25dB);`ui_deny` 按设计不做
- bgm(4,Mureka):`village battle boss ending`(90s 无缝循环,loudnorm -20);探索期无音乐(addendum §3),`amb_*` 由游戏层手动 loop 接 sfx 总线
- bgm(6,Mureka):`village battle boss ending`(R4)+ R6 新增 `menu`(菜单 pad)与 `explore`(探索轻垫乐,10 文档 §7 恢复,volume 0.35);90s 无缝循环;`amb_*` 由游戏层手动 loop 接 sfx 总线
- 再生成方法见 `web/rpg/gen/README.md`
## 响度与爆音标准(R6 起)
- sfx one-shot:峰值归一 **-3dBFS**(真峰值经 ogg 解码回读校验,见 round6-report §1);首尾 8ms cos² 淡入淡出;去直流。
- loop(amb_*/bgm):首尾交叉淡化保证循环连续,不加淡入淡出;仅做直流/峰值检查。
- voice:沿用 R1 峰值 0.89 归一策略,不动。
- bgm:loudnorm -20 LUFS(主题曲)/ -24 LUFS(menu pad)。
## 发音质量
### 本轮已采取的规避措施
+10 -9
View File
@@ -2,12 +2,12 @@
| 游戏 | ogg | m4a | 0字节 | 解码失败 | voice male/female id 一致 |
|---|---|---|---|---|---|
| storyline | 133 | 133 | 0 | 0 | ✅ 各 36(平铺 48 个角色/NPC 句,豁免) |
| escape | 166 | 166 | 0 | 0 | ✅ 各 74 |
| simon | 143 | 143 | 0 | 0 | ✅ 各 68 |
| rhythm | 72 | 72 | 0 | 0 | ✅ 各 31 |
| space | 116 | 116 | 0 | 0 | ✅ 各 52 |
| rpg | 269 | 269 | 0 | 0 | ✅ 各 108(平铺 14 个角色/NPC 句,豁免) |
| storyline | 136 | 136 | 0 | 0 | ✅ 各 37(平铺 48 个角色/NPC 句,豁免) |
| escape | 169 | 169 | 0 | 0 | ✅ 各 75 |
| simon | 146 | 146 | 0 | 0 | ✅ 各 69 |
| rhythm | 75 | 75 | 0 | 0 | ✅ 各 32 |
| space | 119 | 119 | 0 | 0 | ✅ 各 53 |
| rpg | 283 | 283 | 0 | 0 | ✅ 各 114(平铺 14 个角色/NPC 句,豁免) |
## 结论
@@ -15,6 +15,7 @@
## 附注
- storyline 平铺 48 个 = 12 个角色台词(林晚/猎狗/小满/林晨/广播,单音色永久保留)+ 36 个 R1 旁白旧文件(与 male/ 内容重复的遗产,待引擎切换验证后清理,见 round1-report §2)。
- rpg 平铺 14 个 = NPC 句(设计如此,单音色)。
- 终检脚本:`tools/check_all_assets.py`(可重复执行)。
- storyline 平铺 = 角色台词(单音色永久保留)+ R1 旁白旧文件(遗产,待清理)。
- rpg 平铺 = NPC 句(设计如此,单音色)。
- R6 起 sfx 峰值标准为 -3dBFS(真峰值经解码回读校验);voice 目录响度策略不变(峰值 0.89)。
- 终检脚本:`tools/check_all_assets.py`;爆音扫描/修复:`tools/scan_clicks.py` / `tools/fix_clicks.py`。
+60
View File
@@ -0,0 +1,60 @@
# R6 音频轮次报告(2026-08-30,音乐音效总监)
本轮主题:爆音资产排查修复 + 状态 BGM 7 首 + RPG 战斗教学/全局跳过提示语音。Mureka 本轮 $0.21 / $1.00,**累计 $0.63 / $2.00**。
## 1. 爆音排查与修复
**工具**:`tools/scan_clicks.py`(扫描)/ `tools/fix_clicks.py`(修复),全程可复跑;原件备份在 `tools/asset_fix_backup/`(71 个文件)。
**扫描口径**:峰值 >-1dBFS / 首尾电平突变(首末 1ms 幅值 > 局部 50ms 峰值 15%,局部低于 -40dB 的静音区不判)/ 直流偏置 |DC|>0.001。voice 目录按任务要求**不动**(峰值 0.89 归一策略保留)。
**初扫结果(899 个 ogg 全量)**:
| 类别 | 数量 | 说明 |
|---|---|---|
| sfx/bgm 峰值过热 | 27 | 其中 rpg 15 个 SA3 one-shot 解码峰值高达 +0.9~+6.6dBFS(真·爆音源) |
| sfx/bgm 直流偏置 | 43 | 最大 0.076(rpg/atk_swing) |
| sfx/bgm 首尾突变 | 48 | 老资产无淡入淡出(storyline/escape/simon/space 为主) |
| voice 过热 | 340 | 不修(语音响度策略不动,见任务说明) |
**根因发现(重要)**:R4 的 rpg SA3 one-shot 源是近满幅高密度波形(rms 0.84 / 峰值 0dBFS,>15kHz 能量 22%),MDCT 有损编码(vorbis/aac/opus 全部)重建时过冲 +4~8dB——ffmpeg `volumedetect` 走 s16 中间格式把超幅削到 0.0dB 显示,**之前的 QC 完全没发现**。本轮修复 encode 环节改为「编码→解码回读真峰值→超 -3dB 则降增益 + 首轮 18kHz 低通→重编」(最多 3 次迭代)。
**修复结果**:71 个 sfx/bgm 文件重处理(storyline 10 / escape 10 / simon 7 / rhythm 7 / space 11 / rpg 26),统一标准:one-shot 去直流 + 首尾 8ms cos² 淡入淡出 + 峰值 -3dBFS(真峰值校验后);loop(amb_*/bgm)只去直流(交叉淡化循环不加首尾淡入淡出,避免破坏循环)。rpg one-shot 从 staging 无损 wav 重做,其余从备份初代源修复(避免多次有损转码)。
**复扫(终态)**:sfx/bgm 99 个 —— 过热 0 / 直流 0 / 首尾突变 0。**全绿。**
## 2. 状态 BGM(7 首,docs/design/10-feedback2-state-bgm.md)
| 资产 | task id | 响度 | 成品 |
|---|---|---|---|
| storyline `bgm/menu` | 158096439705601 | -24 LUFS(menu pad 档) | 90s 循环,mean -24.4dB |
| escape `bgm/menu` | 158096580149249 | -24 | mean -26.1dB |
| simon `bgm/menu` | 158096714629126 | -24(引擎再按 volume 0.3) | mean -25.8dB |
| rhythm `bgm/menu` | 158096850812929 | -24 | mean -25.3dB |
| space `bgm/menu` | 158097039425538 | -24 | mean -25.1dB |
| rpg `bgm/menu` | 158097182294017 | -24 | mean -25.9dB |
| rpg `bgm/explore`(恢复) | 158097360420865 | -20(引擎 volume 0.35) | mean -22.3dB |
全部一次成功选候选 0;强 onset 起点 + 90s + 3s crossfade 无缝循环 + ogg q3/m4a。既有主题曲全部不动(storyline dark_drone、escape drone、rhythm track90/105/120、space engine、rpg village/battle/boss/ending)。状态→BGM 映射与切换时机以 10 文档 §2-§7 为准。
## 3. 新语音(22 条,双性别)
| id | 游戏 | 条数 | 文案 |
|---|---|---|---|
| `tut_battle_1..5` | rpg | 5×2 | 首次战斗教学:回合制说明 / 左右滑选招式单击出手 / 听前摇双击格挡 / 防御减半与金疮药 / 开场收束 |
| `skip_hint` | 全 6 游戏 | 1×2×6 | 「单击可跳过旁白。」(各游戏既有 instruct 常量) |
均已合入 `tools/gen_voice_dual.py` 台词表并落盘 male/female 双格式。rpg 旁白现 113 id ×2(+NPC 14 平铺)。
## 4. 给游戏层的接线约定
1. **菜单/结算音乐**:所有游戏统一 `bgm/menu` id;切换时机与淡入时长按 10 文档逐游戏表(storyline 500ms、escape 400ms/结算 1500ms、simon 600ms/结算延后 1000ms、rhythm 800ms、space 300ms、rpg 战斗 400ms 快切)。
2. simon 游玩全程不得加载任何 bgm;`bgm/menu` 仅菜单与结算,volume 0.3。
3. rpg 双层:BGM 层报状态(menu/explore/village/battle/boss/ending),sfx 层 amb 报场景(手动 loop 接 `_sfxBus`,不变);胜利过渡 = battle 淡出 1500ms → explore 淡入 2000ms。
4. `tut_battle_1..5`:首场战斗(enc_teach_wolf)B_START 时按序链播,二周目起跳过;`skip_hint` 为备选通用句,接入与否由游戏层定。
5. `bgm=0` 时全部状态音乐不加载(含 menu pad 与 space engine——10 文档 §1.5 裁定)。
## 5. 复跑与终检
- `tools/check_all_assets.py` 终检已更新(`docs/audio/final-asset-check.md`):928 ogg + 928 m4a,双格式/0 字节/解码/性别集合一致性全绿。
- 遗留:voice 层 340 个文件峰值 >-1dBFS 属 R1 既定归一(0.89 峰值),非爆音(有 TTS 天然淡入);如需统一可考虑 R7 评估,但会破坏既有响度平衡,不建议。
+93
View File
@@ -0,0 +1,93 @@
# 10 · 全局"状态即音乐"规范(state-driven BGM,音频总监生产依据)
> **给音频总监的生产摘要**:本轮新增 7 条 BGM——每游戏一条 `bgm/menu`(6 条)+ RPG 恢复 `bgm/explore`(1 条)。各游戏 prompt 草稿见 §8。所有新 BGM:60-90s 无缝循环、-20 LUFS、ogg+m4a 双格式、归一化工艺沿用现有管线。
> **给开发的摘要**:状态→BGM 映射表见 §2-§7(逐游戏)。切换全部走引擎 `play('bgm/...')` 交叉淡入(默认 800ms),特例见各表"切换"列。`bgm=0` 设置时全部静默(含 menu pad)。RPG 的探索 amb 环境音保持在 sfx 层 loop(05-rpg-addendum §3 不变),**BGM 层专用于状态音乐**——两层叠加正是"听 BGM 辨状态、听 amb 辨场景"的设计意图。
---
## 1. 总则
1. **每个可感知状态都要有 BGM 身份**:菜单/结算 = `bgm/menu`(轻 pad);游玩 = 各游戏既有主题曲;战斗/Boss(RPG)= 专属曲。玩家闭上眼只听 2 秒音乐,就该知道自己在菜单里还是在游戏里。
2. **menu pad 的共性要求**:音量基底 -24 LUFS(比主题曲低一档)、无鼓点或极弱脉冲、和声静止(pad 性质)、长度 ≥60s 无缝循环。它是"系统界面"的声音,不能抢戏。
3. **simon 特例**:游玩/教学/序列播放**全程无 BGM**(用户明确要求,02 文档既定)。只在菜单与结算挂 `bgm/menu`,且以 volume 0.3 播放(比别的游戏更轻)。
4. 切换时机一律在**状态进入时**(不是退出时);语音 ducking 行为不变(voice 播放时 BGM duck 到 20%)。
5. `bgm=0`(设置关闭)时:所有状态音乐不加载不播放;RPG 场景 amb(sfx 层)不受影响;space 的 `bgm/engine` 属内容音?——**裁定:engine 是 BGM 层资产,bgm=0 时静默**,盲航的方位信息全部由 whoosh/alarm(sfx)承载,无信息损失。
## 2. storyline《声命线》
| 状态 | BGM | 切换 |
|---|---|---|
| 剧情播放(含抉择) | `bgm/dark_drone`(保留) | 开局即播,全剧不切 |
| 菜单(下滑呼出) | `bgm/menu` | 进菜单 crossfade 500ms;关菜单淡回 dark_drone 800ms |
| 结局结算 | 保持 `bgm/dark_drone` 不切 | 结局语音 duck 即可,氛围连贯 |
## 3. escape《暗室》
| 状态 | BGM | 切换 |
|---|---|---|
| 引导 + 自由探索 | `bgm/drone`(保留) | 不变 |
| 菜单(ENDING 下滑呼出) | `bgm/menu` | 进菜单 crossfade 400ms |
| 逃脱结算(ENDING) | `bgm/menu`(volume 0.5) | 现有 `stopBgm(2500)` 淡出 drone 后,escape 语音播完淡入 menu pad 1500ms——结算画面从"空洞的静"变成"松一口气的亮" |
## 4. simon《音序回响》(特例)
| 状态 | BGM | 切换 |
|---|---|---|
| 教学 / 对局全流程 | **无**(静默) | 不加载任何 bgm |
| 菜单(结算后下滑) | `bgm/menu`(volume 0.3) | 淡入 600ms |
| 结算(gameover) | `bgm/menu`(volume 0.3) | sfx/gameover 播完 1s 后淡入 1000ms |
| 单击重开 | 停 menu pad | `stopBgm(300)`,回静默 |
## 5. rhythm《节拍行者》
| 状态 | BGM | 切换 |
|---|---|---|
| 曲目游玩(含教学) | `bgm/track90/105/120`(不动) | 现有手动网格排程不变 |
| 选曲界面 | `bgm/menu` | 进选曲淡入 800ms;选定曲目后 stopBgm(400) 再按现有网格起曲目 BGM |
| 模态菜单(暂停) | 现有断点续播机制**保留**,menu pad **不叠**——暂停 = 音乐停(实现已如此,是最干净的"暂停感") | 不变 |
| 结算 | 曲目 BGM 现有 0.6s 淡出后,评级语音播完淡入 `bgm/menu` 800ms | 新增 |
## 6. space《盲航》
| 状态 | BGM | 切换 |
|---|---|---|
| 演练 + 对局 | `bgm/engine`(保留) | 不变 |
| 模态菜单(暂停) | `bgm/menu` | 进菜单 crossfade 300ms(暂停要快);回对局淡回 engine 500ms |
| 坠毁结算 | 现有 stopBgm(600) 后,结算语音链播完淡入 `bgm/menu` 800ms | 新增 |
## 7. rpg《剑与声》(重点:听觉状态语言)
用户要求"通过背景音乐判断当前处于菜单/地图/战斗状态"。RPG 的状态语言 = **BGM 层报状态 + sfx 层 amb 报场景**(双层各司其职):
| 状态 | BGM 层 | sfx 层 amb | 听感设计 |
|---|---|---|---|
| 菜单(模态) | `bgm/menu` | amb 继续(音量不变) | "游戏还在,界面开了" |
| 地图/探索 | `bgm/explore`(**恢复**,volume 0.35 轻垫) | `amb_<scene>` 主角 | 音乐是"底",环境音是"景" |
| 村庄(安全区特例) | `bgm/village`(保留) | amb_village(bgm=0 时的垫底) | 温暖,与探索的紧张对比 |
| 普通战斗 | `bgm/battle`(保留) | amb 停 | 进战斗 crossfade **400ms**(战斗要快切, urgency) |
| Boss 战 | `bgm/boss`(保留) | amb 停 | 同上 400ms |
| 战斗胜利 → 探索 | 关键过渡:`sfx/victory`(胜利标识音,见 11 文档 §5)→ battle/boss **淡出 1500ms** → `bgm/explore` **淡入 2000ms** + amb 恢复 | — | "从紧绷到松弛"是可听的,这是用户要的过渡 |
| 战败 → 章首 | `bgm/battle` stopBgm(2000) → `voice/btl_lose` → village:bgm/village 淡入 1500ms | — | 重生即安全区音乐 |
| 章结算/剧情(evt_ch*_end) | `bgm/ending`(保留) | amb 停 | 播完淡回 explore |
**裁定(修订 05-rpg-addendum §3)**:addendum 当时取消了 `bgm/explore`(探索期纯 amb);按用户本轮反馈恢复 explore 作为低音量状态垫乐(volume 0.35),amb 仍在 sfx 层不受影响。BGM 需求回到 5 首(village/explore/battle/boss/ending)+ menu。
## 8. 新 BGM 生产清单(Mureka prompt 草稿)
共性后缀:`", ambient pad, very sparse, no drums or minimal pulse, static harmony, calm, no vocals, seamless loop, 60-90 seconds"`。
| 资产 | prompt 草稿(英文) |
|---|---|
| storyline `bgm/menu` | "Dark post-apocalyptic menu pad, low warm drone with faint radio static texture, sibling mood to a dark ambient drone track" + 共性 |
| escape `bgm/menu` | "Underground facility menu pad, soft low hum with distant water drip feel, slightly hopeful resolution tone" + 共性 |
| simon `bgm/menu` | "Minimal memory-game menu pad, gentle mallet harmonics over soft sine pad, meditative, extremely light" + 共性 |
| rhythm `bgm/menu` | "Electronic music-menu pad, slowed-down synthwave chords at 90 bpm feel, airy, between-songs breather" + 共性 |
| space `bgm/menu` | "Space cockpit menu pad, soft engine-room hum with airy synth wash, weightless" + 共性 |
| rpg `bgm/menu` | "Wuxia menu pad, single sustained guzheng harmonic over warm drone, ink-painting stillness" + 共性 |
| rpg `bgm/explore` | "Wuxia exploration ambient music, sparse guzheng and dizi fragments over low forest drone, 60 bpm, mysterious calm, no vocals, seamless loop, sits quietly under ambience"(轻垫乐,混音时音量再降) |
## 9. 验收点
- 每游戏闭眼测试:菜单开/关、战斗进/出(RPG)仅靠 BGM 可辨。
- `bgm=0.3`(小声)与 `bgm=0`(关闭)两档在各状态切换处不破音、不残响(引擎 crossfade 与增益总线叠加正确)。
- menu pad 与主题曲同调性优先(storyline/escape/rpg 的 menu 与主题曲同 key;simon/space 无强约束)。
+98
View File
@@ -0,0 +1,98 @@
# 11 · 《剑与声》战斗重构规范(rpg battle rebalance)
> **给开发的实现要点摘要**
> 1. 首战教学化:教学战从"学格挡+学攻击"扩为**四步强制分步**(认识指令→格挡→攻击→防御),教学战敌人 ATK 降为 2 且玩家 HP 锁底 1(不死)。§2。
> 2. 新手难度保护:按**已完成战斗场数**分两档——第 1-3 场前摇 1.5s、敌方伤害 ×0.6;第 4-5 场过渡(1.25s、×0.8);第 6 场起恢复正常(1.0s、×1.0)。判定依据 `stats.battlesWon`,与等级无关(等级通过数值自然体现)。§3 数值表。
> 3. 逃跑新手保护:Lv<3 逃跑 100% 成功;Lv≥3 普通战斗逃跑 70%,失败代价 = 敌方一个正常回合(有前摇、可格挡,不白打)。Boss/剧情战维持不可逃。§4。
> 4. 战斗四状态声音标识:战斗开始 / 玩家回合 / 敌方前摇 / 胜利各有专属 sfx(新增 2 个:`turn_player`、`victory`)。§5。
> 5. 现状衔接:现版(web/rpg/game.js)前摇固定 1.0s(+听风剑 0.3s)、逃跑 flee 布尔、教学战两步。本规范是**参数化 + 步骤扩展**,不改状态机骨架(05-rpg-chapter1 §3 的 10 状态转换表不变)。
---
## 1. 现状问题(对应用户反馈)
- "第一次进战斗必须说明清楚交互方式":现教学战只有格挡(tut_b1/b2)+ 攻击(tut_b3)两步,**防御指令和指令切换本身没教**——玩家不知道左右滑能选招式、不知道有防御。
- "战斗太难、要循序渐进":现版所有战斗前摇恒 1.0s、敌人满伤害,第一场正式战斗(enc_wolf)和 Boss 战用同一反应标准;逃跑非 Boss 必成但没有"失败"概念,缺少梯度。
## 2. 首战教学化(enc_teach_wolf 重排为四步)
教学战流水(teachStage 状态扩为 4 段,每段旁白引导 → 玩家亲手执行 → 确认后进下一段;每段 8s 无操作给一次重复提示,绝不卡死):
| 步 | stage | 旁白(新增语音 id,台词表见 §6) | 玩家动作 | 通过条件 |
|---|---|---|---|---|
| T0 | `menu` | `tut_b0`:"战斗里,左右滑选招式,单击出手。先试试,滑一滑。" | 左右滑 ≥2 次(听到指令名切换) | 切换 2 次 |
| T1 | `attack` | `tut_b3`(现有):"轮到你了。左右滑选攻击,单击出剑。" | 选中攻击并单击 | 攻击命中一次 |
| T2 | `parry` | `tut_b1`+`tut_b2`(现有):"它要扑了,听前摇!""就是现在,双击格挡!" | 前摇内双击 | 格挡成功或受击均可通过(受击掉血,教学点已到) |
| T3 | `defend` | `tut_b6`(新增):"来不及格挡,就选防御。选防御,单击。" | 选中防御并单击 | 执行防御一次(敌方攻击减伤 50% 演示) |
| T4 | — | `tut_b5`(现有):"好剑法!接下来,靠你自己了。" | 自由击杀 | 敌亡 |
- 教学战参数:teach_wolf **ATK 3→2**、HP 6 不变、前摇 **1.8s**(比正常宽 80%)、玩家 HP 锁底 1(不死,现版已有 clamp)、不可逃(现有)。
- 顺序理由:先会"怎么选"(T0)再会"打"(T1)再会"保命"(T2/T3);格挡放在攻击后是让它成为"回合交换"的一部分而非孤立动作。
## 3. 难度曲线(新手保护分档)
保护档位由 `stats.battlesWon`(已完成战斗场数)决定,**每场战斗开始时取档**:
| 档 | battlesWon | 敌方前摇 | 敌方伤害系数 | 说明 |
|---|---|---|---|---|
| 教学 | 教学战 | 1.8s | ATK 2 特例 | §2 |
| 保护 | 0-2(前 3 场) | **1.5s** | **×0.6**(向下取整,min 1) | 反应窗口宽 50%,掉血痛感低 |
| 过渡 | 3-4(第 4-5 场) | 1.25s | ×0.8 | 渐回标准 |
| 标准 | ≥5(第 6 场起) | 1.0s(听风剑 +0.3s) | ×1.0 | 现版数值 |
### 第一章遭遇战数值表(最终版,含推荐等级)
| 遭遇战 | 敌人 | HP | ATK(标准) | 前摇 | 经验/金币 | 推荐等级 | 说明 |
|---|---|---|---|---|---|---|---|
| enc_teach_wolf | 教学野狼 | 6 | 2(教学特例) | 1.8s | 5 / 0 | Lv1 | 四步教学,锁血不死 |
| enc_wolf | 野狼 | 10 | 4(保护档实际 2) | 按档位 | 8 / 5 | Lv1 | 可逃;保护档内打它=2 伤/刀 |
| enc_bandits | 山贼 ×2 连战 | 16+16 | 6(过渡档实际 4) | 按档位 | 24 / 24 | Lv2 | 不可逃;连战不回血 |
| enc_elite | 寨丁头目 | 24 | 8 | 标准 | 20 / 25 | Lv2-3 | 强攻线;半血有狠话 |
| enc_sneak | 山贼+毒蛛 | 16+12 | 6/7 | 标准 | 24 / 20 | Lv2 | 绕行线;胜后进 Boss 前回满 |
| enc_boss1 | 黑风寨主 | 45 | 10(半血 12) | 标准 | 50 / 100 | **Lv3** | 不可逃;进 Boss 战前强制提示(§4) |
等级节奏校验:教学狼 5 + 野狼 8 + 山贼 24 + 头目/偷袭 20-24 + 沿途 ≈ 60 exp 到 Boss 前 → Lv3(20+40=60 正好升 3)。Lv3 玩家(HP36、铁剑 atk12、皮甲 def2):保护档已过,Boss 10-2=8~11 伤/刀,可吃 4 刀;格挡成功 2-3 伤。会格挡=稳过,不格挡=紧过,符合"技巧有收益"原则。
**Boss 保护例外**:enc_boss1 始终标准档(battlesWon 再多也不再放宽;battlesWon 不足 5 时打 Boss 也不享受 1.5s 前摇——Boss 是毕业考)。实现:`enc.boss` 已存在,取档时 `boss ? 标准 : 按 battlesWon`。
## 4. 逃跑规则
| 玩家等级 | 普通战斗 | Boss/剧情战(flee:false) |
|---|---|---|
| Lv < 3 | **100% 成功**(新手保护) | 不可逃(btl_noflee) |
| Lv ≥ 3 | 70% 成功;失败:播 `btl_flee_fail`(新增)→ 敌方直接进入正常前摇回合(**可格挡**,不白打) | 不可逃 |
实现:现版 `_fleeCheck` 是 flee 布尔直出;改为 `{ allowed, rate }`:`allowed=false` 播 btl_noflee;`rate = lv<3 ? 1.0 : 0.7`,失败走 ENEMY_TELEGRAPH。
**进 Boss 战提示**:首次进入 enc_boss1 且 Lv<3 时,门前插播 `boss1_warn`(新增):"里面就是黑风寨主。你现在回去练练再来,也来得及。"——给低等级玩家一个知情回头机会(gate 场景 back 选项本来就在)。
## 5. 战斗状态声音标识(听觉状态语言的战斗部分)
| 状态 | 声音标识 | 资产 |
|---|---|---|
| 战斗开始 | `bgm/battle` 400ms 切入(10 文档 §7)+ 现有 `btl_start_<enemy>` | 已有 |
| **玩家回合开始** | 新增 `sfx/turn_player`:一声轻亮的剑鸣/木鱼短音("轮到你"的听觉标点,每回合都出现,必须极短 ≤0.4s、不烦) | **新增(程序合成即可,上行双音)** |
| 敌方前摇 | 现有 `tel_<enemy>` + 窗口开启 `beat` 轻震(addendum §1) | 已有 |
| **胜利** | 新增 `sfx/victory`:3 音上行短号角(≤1.2s),随后 BGM 过渡(10 文档 §7 的 1.5s 出/2s 入) | **新增(SA3:"triumphant short brass fanfare, 3 rising notes, wuxia style, under 1.5 seconds")** |
| 战败 | 现有 `hurt` + `btl_lose` + battle BGM 2s 淡出 | 已有 |
`turn_player` 在每回合 PLAYER_SELECT 进入时播;教学战 T0 的"指令切换"教学正好以它为锚("听到这个声音,就是你的回合"——写进 `tut_b0` 台词)。
## 6. 新增语音(补录清单,×2 性别,交音频总监)
| id | 台词全文 | 用途 |
|---|---|---|
| `tut_b0` | 战斗里,左右滑选招式,单击出手。听到这个声音,就是你的回合。先滑一滑,试试。 | 教学 T0("这个声音"= turn_player,旁白落音后 0.5s 播 turn_player 示范) |
| `tut_b6` | 来不及格挡,就选防御。左右滑,选防御,单击。 | 教学 T3 |
| `btl_flee_fail` | 没甩掉!它追上来了。 | Lv≥3 逃跑失败 |
| `boss1_warn` | 里面就是黑风寨主。你现在回去练练再来,也来得及。 | Lv<3 首次到 boss1 |
instruct 沿用 ch1 台词表常量(female=Serena / male=Uncle_Fu,讲武侠故事/说书人)。
## 7. 验收点
- 首战:不做任何正确操作也不死(锁底 1);四步每步都能被看门狗兜底推进。
- 保护档实测:第 2 场战斗野狼每刀 ≤2 伤、前摇明显变慢(可听)。
- Lv2 逃跑必成 ×10 次;Lv3 后逃跑出现失败且失败后可格挡。
- Lv2 到 Boss 门前听到 `boss1_warn`;Lv3 到则不播。
- `turn_player` 每回合出现且不与前摇音/指令语音叠放(回合切换处留 200ms 间隙)。
+93
View File
@@ -0,0 +1,93 @@
# 12 · 跳过旁白 + 长按加速 · 全局 UX 规范(含男女声边界核查)
> **给开发的实现要点摘要**
> 1. 单击跳过当前句:§1 逐游戏裁定表——核心规则是"**voice 播放中单击=跳过本句;无 voice 时单击=原语义**",语音链衔接统一"跳下一句"(不是跳到节点末)。
> 2. 长按加速(引擎临时 1.7× voice 语速):§2 豁免表——rhythm 演奏阶段全局豁免,其余游戏默认允许。引擎层拦截 longpress,游戏不收此手势。
> 3. 首局提示时机:§3——不进语音资产,教学自然涵盖 + 大厅 footer 文案。
> 4. §4 男女声边界核查结论:storyline/rpg 实际行为**符合**"只有旁白跟随设置、角色音固定",无需修复;两处命名/计数勘误。
---
## 1. 单击跳过当前句
### 1.1 总则
- **语境判定**:`voice` 层有音频在播时,单击 = 跳过当前句(`stopVoice()` 截断当前句,链播自然进下一句——引擎语义"被打断不触发 onComplete",因此**实现上不是 stopVoice 而是"截断当前句但保住链"**:需引擎或游戏层支持 `skipVoice()`(截断当前句并触发 onComplete 续链)。若引擎暂无此 API,游戏层用等价实现:手动推进链。以引擎最终提供的 API 为准)。
- **无 voice 时单击 = 各游戏原语义**(检查/确认/继续),两态有明确的听觉区分(有语音在播 vs 没有),不混淆。
- **跳到节点末 vs 跳下一句**:统一**跳下一句**。跳到节点末会让玩家误过后续信息且实现要感知"节点"边界;"逐句跳"心智最简单:不想听就多点几下。
- 跳过不给震动(跳过是浏览操作不是确认;双击=重听的对偶操作,保持安静)。
### 1.2 逐游戏裁定表
| 游戏 | 阶段 | 单击=跳过本句? | 说明 |
|---|---|---|---|
| storyline | 剧情对白 | ✅ 可跳 | 节点最后一句被跳 = 进下一节点(现有"单击继续"语义自然延伸) |
| storyline | 抉择提示(prompt) | ❌ 不可跳 | 左右滑抉择前的选项说明必须听完(盲玩公平性) |
| escape | 引导 G0-G7 | ❌ 不可跳(下滑=跳整个引导,已有) | 引导句即操作指令,跳句会破坏步骤机 |
| escape | PLAY 检查描述/卡关提示/背包 | ✅ 可跳 | 熟练玩家快速刷检查 |
| escape | 关键获得(found_key/unlock_cabinet/fix_box/escape) | ❌ 不可跳 | 谜题链进展的叙事回报,总长 <10s |
| simon | 教学 T0-T7 | ❌ 不可跳(下滑=跳教学,已有) | 同 escape 引导 |
| simon | 轮次播报 round_*、成绩播报 | ✅ 可跳 | 高轮次玩家最需要 |
| simon | input 阶段 | — 无语音 | 无冲突 |
| rhythm | intro/tut/结算/选曲 | ✅ 可跳 | 演奏阶段(main)无语音,无冲突 |
| rhythm | mid_song | ❌ 不可跳(仅 2s,不值得跳) | 演奏中,手势是演奏输入 |
| space | 演练 D0-D5 | ❌ 不可跳(下滑=跳演练,已有) | 教学即玩法 |
| space | feat_boost/feat_shield 引入句 | ✅ 可跳 | 二周目熟练玩家 |
| space | 归因/保护期/结算播报 | ✅ 可跳 | |
| rpg | 场景 desc、NPC 对白、事件句 | ✅ 可跳 | 刷图玩家核心诉求 |
| rpg | 战斗指令名 cmd_*、教学战 tut_b* | ❌ 不可跳 | 指令名 <1s 且正在选指令,跳过会打乱选择节奏;教学句同引导逻辑 |
| rpg | 状态播报链(st_*) | ✅ 可跳 | 逐句跳过即"快进状态查询" |
### 1.3 与"单击 300ms 延迟"的相容性
跳过是非即时操作,300ms 延迟无影响。注意双击=重听与单击=跳过的对偶关系:引擎的双击窗口保证两者不误判(现有机制天然支持)。
## 2. 长按加速(引擎:按住 = voice 层临时 1.7×)
**核查注记**:本规范按用户声明的既定事实("引擎已实现长按=临时 1.7 倍速")撰写;当前 `web/engine/gesture.js` 输出仍为 6 手势、未见 longpress 实现——若引擎接口与本节假设不同,**以引擎实际 API 为准**,游戏侧豁免规则不变。
### 2.1 总则
- 长按由**引擎层拦截**:按住 ≥400ms 未位移 → 当前 voice 句 playbackRate 临时 1.7(松手或句终恢复 settings.speed),**不产生手势回调**(游戏永远收不到 longpress)。BGM/sfx 不受影响。
- 与设置语速的关系:长按倍率 = max(1.7, 当前 settings.speed) 之上的"再快一档"语义——settings.speed=1.7 时长按无效果(已是特快)。
- 加速中的 onComplete 时序自然提前(句长缩短),语音链不断。
### 2.2 豁免表
| 游戏 | 豁免 | 理由 |
|---|---|---|
| rhythm | **main(演奏)+ tut(教学跟打)阶段全局豁免**(游戏向引擎注册关闭 rate boost) | 演奏中手指可能长按屏幕边缘蓄力;且 mid_song 加速会破坏"语音提示与节拍网格对齐"的幻觉。教学跟打同理 |
| rhythm | select/end/菜单正常允许 | 结算播报快进是刚需 |
| simon | playback(序列播放)阶段豁免 | 序列是记忆对象,虽然 tone 是 sfx 不受 rate 影响,但该阶段无 voice,豁免无成本;input/announce/结算允许 |
| space | playing 阶段豁免 | 对局中无长语音(归因句 2-3s),豁免避免长按与连续滑动的手势边界模糊;演练/结算允许 |
| escape / storyline / rpg | 不豁免(全局允许) | 叙事类游戏正是长按加速的主战场 |
| RPG 战斗 | 允许但有边界:敌方前摇是 **sfx**(tel_*)不受 rate 影响,格挡窗口时长不变——加速 voice 不影响战斗判定,安全 | 裁定:允许 |
豁免实现:游戏在阶段切换时调引擎开关(如 `audio.setRateBoostEnabled(false)`,以引擎实际 API 为准);默认开启。
## 3. 首局提示时机(两个功能的发现性)
原则:**不新增语音资产、不打断首局沉浸**。
1. **大厅 footer 文案**(可视化,零成本):footer 已有"单击判定有约 300ms 延迟"一行,追加一行:"播放旁白时:单击=跳过本句,按住=加速播放"。
2. **各游戏教学内自然涵盖**:
- 有交互教学的游戏(simon/space/rhythm/rpg/escape)教学本身已教六手势;跳过/长按是**增强功能而非必需操作**,不教——玩家从大厅文案或摸索中发现即可(发现成本低:单击本来就常用)。
- storyline(无教学)在首个抉择点 prompt 播完后追加一次现有系统提示?——**不加**。storyline 的"单击继续"已是事实上的跳过来由,长按从大厅文案发现。
3. 若后续数据显示玩家长语音中途退出率高,再考虑在第二局长语音加一次性提示句(届时补录 `sys_skip_hint`,本规范预留 id)。
## 4. 附:男女声设置边界核查(用户反馈"可选男女声只是旁白,不是游戏内容")
核查方法:读 `web/storyline/game.js`、`web/rpg/game.js` 实际播放路径 + 资产目录结构 + `tools/gen_voice_dual.py` 台词表归属。
### 结论:符合规范,无需修复
| 检查项 | storyline | rpg |
|---|---|---|
| 旁白/系统音在 `voice/<gender>/` 双目录 | ✅ 36 id 双性别 | ✅ 旁白 id 双性别 |
| 角色/NPC 音平铺 `voice/`(固定音色,引擎平铺回退命中) | ✅ n02_linwan(Serena)、n07_dog(Dylan)、e1_child/n03_msg/n05_bcast(Vivian) 均只存在于平铺 | ✅ npc_* 14 id 只存在于平铺,female/male 目录均无 npc_ 文件 |
| 角色音被错误切性别的路径 | **未发现** | **未发现** |
### 两处勘误(非 bug)
1. **storyline `n11_child` 命名误导**:该 id 在两个性别目录都存在,初看像角色"小满"被切性别——实际它是**旁白句**(`gen_voice_dual.py:95`,speaker 组 `_SL_NARR`,文本是旁白叙述中嵌入小满的话)。行为正确,但文件名易误读;**维持不改**(改 id 要动代码+资产两端,无实际收益),此处留档说明。
2. **RPG NPC 台词计数勘误**:05-rpg-ch1-voice-ids.md 表头写"NPC 16 句",实际表内 14 id(村长 4 + 猎人 4 + 头目 3 + 寨主 3)。以 14 为准,音频资产已按 14 落盘,文档表头笔误不另改文件,记于此。
+110
View File
@@ -0,0 +1,110 @@
# 13 · 六游戏试玩通过标准(playtest criteria,无头测试 agent 断言检查单)
> **给测试 agent 的使用说明**:每游戏分三节——**关键路径**(从启动到通关/结算必须按序达成的事件序列,附最大手势/步数预算)、**边界断言**(必须显式验证的容错/保护行为)、**失败即不通过**。测试可用测试钩子(如直接读取 game 实例字段、快进计时器)模拟时间,但手势只能通过引擎回调注入(模拟真实判定路径)。语音缺失不应导致卡死(看门狗是各游戏的既定实现,属被测项)。
---
## 1. storyline《声命线》
**关键路径**(单结局线,≤40 次手势):
1. 启动遮罩点按 → 音频解锁 → `n00` 旁白开始(voice 层有播放记录)。
2. 沿任一抉择路径行进:每个抉择点断言 `pendingChoice/choiceArmed` 生效,左/右滑各至少使用一次。
3. 到达任一结局节点(4 结局之一)→ 结局语音链 → 结算态单击重开回 `n00`。
**边界断言**:
- 语音播放中双击 = 重听当前节点(同一 voice id 重播)。
- 下滑呼出菜单 → 返回剧情后节点不丢(mode 恢复,不重头)。
- 中途选择产生剧情标记:`n09` 右滑后 `knowTrap === true`、`n11` 左滑后 `companion === true`、`n18` 左滑后 `betrayed === true`(标记设置即断言;knowTrap 无下游消费是已知项,roadmap §2.2,不作为失败项)。
- 语音链看门狗:屏蔽某一 voice 资产时流程仍可推进到结局。
## 2. escape《暗室》
**关键路径**(引导 8 步 + 解谜链,≤80 次手势):
1. 启动 → GUIDE 进入(g0_intro 播放)→ 依次:单击(G0)→ 任意滑动(G1)→ 滑动 ×2(G2)→ 左滑(G3)→ 下滑(G4)→ 单击拾钥匙(G5,断言 `hasKey === true`)→ 双击(G6)→ 进入 PLAY(断言 `phase === 'PLAY'`,localStorage `soundgame.escape.guideDone === '1'`)。
2. PLAY 路径:(0,2)出发 → 右滑×2 到铁柜房 (2,1) → 双击开柜(`cabinetOpen === true && hasFuse === true`)→ 左滑×2+上滑到电箱房 (0,0) → 双修复电箱(`powerOn === true`)→ 右滑×2+下滑×2 到大门 (2,2) → 双击逃脱。
3. ENDING:断言 time_band 语音播出 + 单击重开回 PLAY。
**边界断言**:
- 引导期错手势:G3 右滑 → 播 guide_wrong_left;连错 3 次 → guide_blunt_left + warning 震动(监听 vibrate 调用)。
- 撞墙:PLAY 中 (0,0) 左滑 → wall 音效 + error 震动 + 位置不变。
- 卡关提示:计时器快进 60s/120s/180s → 依次播 stuck_*_1 / stuck_*_2 / stuck_*_3 + nav_*;任意进展事件后重置(`_stuckShown` 清空)。
- 单击检查 = 环境音先播、描述 800ms 后播(断言播放顺序)。
- 双击无目标兜底:播背包 + L2 明示,不崩。
## 3. simon《音序回响》
**关键路径**(教学 + 5 轮 + 复活耗尽,≤120 次手势):
1. 启动 → 0.5s 后教学 T0(t0_welcome 播放)→ T1-T6 每步:听到 tone → 做出对应手势(断言 `teachIdx` 递进)→ T7 → 进入第 1 轮。
2. 第 1-3 轮:playback 阶段手势被忽略(注入手势断言 sequence 不受影响)→ input 阶段按 `game.sequence` 原样复现 → `round` 递增。
3. 触发复活:第 4 轮故意答错 → 断言 `chances` 1→0、序列**不变**重播(`sequence` 引用值相等)→ 复现成功 → 第 4 轮完成时 chances 回 1(每 4 轮 +1)。
4. 主动重听:input 阶段且期望输入非 doubletap 时双击 → 断言 chances 减少、inputIdx 清零、序列重播。
5. 耗尽后再答错 → gameover → 断言 score_band 语音与 `round-1` 匹配 → 单击重开跳过教学直达第 1 轮。
**边界断言**:
- 无 BGM:全程无 bgm 层播放调用。
- 双击冲突(裁定①):期望输入恰为 doubletap 时双击 = 答题(inputIdx 前进),不触发重听。
- chances=0 时双击非期望手势:不判错(round 不变、inputIdx 不变)、播 chances_out。
- 序列约束:前 5 轮无三连同手势;全程无四连。
- 速度:第 1 轮 interval 700ms、第 12 轮起 420ms 下限。
## 4. rhythm《节拍行者》
**关键路径**(教学 + 曲目一通关 + 解锁,≤200 次手势):
1. 启动 → welcome → 教学 T1-T3:按 cues 的 expected 时刻(测试用 AudioContext 时钟对齐)做对应手势 → `tutorialDone === true` → 进曲目一 main。
2. 曲目一:按 `_cues` 全部应答(Perfect 精度注入)→ 结算 phase 'end' → 断言评级 S(全 Perfect)→ `grades.song1 === 's'`、`unlocked === 2`、unlock_song2 播报。
3. 结算单击 = 同曲再来一遍;右滑 = 下一首(已解锁 song2 进入 main)。
**边界断言**:
- tap 补偿:tap 音符以"回调时刻 -0.30s"判定(注入 tap 手势的实际回调延迟下仍判 Perfect)。
- Miss=无乐器音:窗口内做错手势 → 无 inst_* 播放 + error 震动 + combo 清零。
- 下滑折中:main 中无临近 down 音符时下滑 → 菜单(phase 'menu'、BGM 断点);有临近 down 应答音符时下滑 = 演奏判定,不开菜单。
- 菜单暂停恢复:关菜单后 cue.expected 平移、BGM 断点续播(断言 `_bgmT0` 平移量 = 暂停时长)。
- 教学失败 2 次 → retry_skip 跳过该步不卡死。
- **结算末位**:song3 结算页右滑 → 不播 song_locked(round3-4 裁定①的回归断言;修正落地前此断言预期失败,标记 known-issue)。
## 5. space《盲航》
**关键路径**(演练 + 保护期 + 坠毁,≤150 次手势):
1. 启动 → 演练 D1-D4:按 `DRILLS[d].dir` 同向滑 → `drillIdx` 递进 → D5 后进 playing(`drillDone === '1'`)。
2. 保护期(runStart 起 60s):等一颗陨石 → **不滑动任其撞** → 断言 `hits === 0`(训练护盾吸收)+ protect_<dir> 播报。
3. 第 20s:feature 阶段单击 → boost 生效(`boostUntil > now`);第 45s:双击 → 护盾(`shieldUpUntil > now`)。
4. 60s 后:保护解除播报;之后正常闪避 3 颗(同向滑,断言 dodges=3)→ 再任撞 3 次 → 坠毁 gameover。
5. 结算:time_/dodge_ 分档句与实际值匹配 → 单击重开。
**边界断言**:
- 演练做错:D1 右滑(应为左)→ drill_retry_left 重教 + 同一步重试;失败 2 次 → drill_moveon 带过。
- 保护期撞击方位归因:撞左来陨石播 protect_left(方向句与 ob.dir 一致)。
- 同向判定:撞击前 1.5s 内同向滑 = dodge(dodges+1);反向滑 ≠ dodge。
- 第 2 次被撞后追加 hit_last;hits===3 才 gameover。
- 下滑折中:下方有悬置陨石时下滑 = 闪避;无下方陨石时下滑 = 菜单(phase 'menu')。
- 护盾充能:doubletap 后 30s 内 shieldReadyAt 锁定,到时播 shield_ready。
- 里程碑:dodges=10 播 ms_10(测试可快进或直接设置 dodges 后触发一次 dodge)。
## 6. rpg《剑与声》(含 11 号文档重构项;重构未落地处标 known-issue)
**关键路径**(第一章通关,≤300 次手势):
1. 启动 → 无存档时序章 intro_1-3 → village;断言场景进入 = amb/desc 播报 + 选项可左右滑切换。
2. village:商店买铁剑(金币 50→断言不够 → 先调查/战斗攒钱或直接断言 shop_poor 播报路径);上滑状态播报链完整(st_lv→st_hp→st_gold→st_weapon→st_armor)。
3. path:investigate 一次性 +15 金(再次调查播 evt_nothing)。
4. stream:**教学战不败**——全程乱按/不动,断言玩家 HP 不低于 1(锁底);按 11 号文档重构后:四步教学(menu→attack→parry→defend)依次断言 teachStage 流转(重构落地前按现版两步 parry→attack 断言)。
5. hunter:talk_hunter → hunter_quest flag + potions+1 + shop_discount。
6. deepwood:山贼连战(两只依次上场,连战间不回血)→ enc_bandits_done。
7. gate:assault → elite 战 → boss1:黑风寨主(半血二阶段 ATK 12 + npc_boss1_2)→ 胜利 → evt_ch1_end → `chapters['2'] === 'open'`。
8. 存档断言:任一战斗结算后 PUT/localStorage 有最新 data(summary 含场景名与 Lv);杀进程重进 → sys_continue_ask → 单击继续落点正确。
**边界断言**:
- 格挡:前摇窗口内双击 → 伤害 ×0.3(`dmg` 断言或 HP 差值断言);窗口外双击 → error 震动不生效。
- 防御指令:当回合伤害 ×0.5。
- 逃跑:Lv<3 普通战 100% 成功(11 号文档;重构前现版为 flee 布尔,按现版断言 flee:true 必成);Boss 战下滑 → btl_noflee。
- 新手保护(11 号文档,known-issue 至重构落地):前 3 场前摇 1.5s、伤害 ×0.6;enc_boss1 恒标准档。
- 战败:HP≤0 → btl_lose → village 重生,等级/金币/flags 保留、非 Boss 遭遇重置。
- 菜单豁免:探索态下滑=菜单(左右滑+单击确认),战斗态下滑=逃跑。
- 看门狗:屏蔽任意 voice 资产,战斗与场景流转不卡死。
## 7. 全局通用断言(每游戏都跑)
1. 启动遮罩点按前无音频播放(引擎解锁语义)。
2. 全程无未捕获异常;资产缺失仅 console.error 不崩。
3. 设置边界:settings.voice 切换后下一句起用对应目录(voice 缓存键含性别);settings.bgm=0 时无 bgm 层加载;speed=1.7 时下一句 playbackRate 生效。
4. 双击=重听在其声明可用的阶段生效(escape 检查/simon 规则/rpg 场景 desc 等)。
5. 菜单约定(裁决②):适用游戏的模态菜单 = 单击循环 / 双击确认 / 上滑帮助 / 下滑关闭(RPG 豁免)。
+52 -12
View File
@@ -15,9 +15,10 @@ web/
│ ├── gesture.js # 6 手势识别(触摸+鼠标,内置基础震动反馈)
│ ├── audio.js # Web Audio 三层音频引擎 + 启动遮罩(自动应用用户设置)
│ ├── settings.js # 全局用户设置(voice/speed/bgm,localStorage 持久化)
│ ├── haptics.js # navigator.vibrate 震动封装(预定义震型)
│ ├── haptics.js # navigator.vibrate 震动封装(预定义震型 + iOS 视觉降级)
│ ├── game.js # Game / StateMachineGame 基类
│ └── style.css # 全屏黑底 + 封面铺满 + 屏蔽系统手势
│ ├── style.css # 全屏黑底 + 封面铺满 + 屏蔽系统手势 + 闪光降级样式
│ └── vendor/soundtouch.js # SoundTouch 0.3.0(voice 变速不变调,LGPL v2.1)
├── server/ # 用户系统服务器(静态托管 + 注册/登录/进度 API)
│ ├── server.js
│ └── README.md # API 文档
@@ -92,7 +93,8 @@ cp -r web/template web/<name>
iOS Safari 和 Chrome 都要求 AudioContext 必须在一次真实用户手势中创建/恢复。
引擎用启动遮罩解决:
1. 页面加载后 `showStartOverlay()` 显示全屏封面 + "轻触开始"(呼吸动画);
1. 页面加载后 `showStartOverlay()` 显示全屏封面 + "轻触开始"(呼吸动画,
提示文字位于屏幕 30% 高度处,避开封面图底部文字区);
2. 用户第一次点按遮罩 → 引擎在手势回调里 `new AudioContext()` + `resume()`;
3. 遮罩移除,Promise resolve → 你调 `game.start()` 接管。
@@ -130,6 +132,10 @@ new WebAudioEngine(basePath = 'audio/', opts?)
(与 Android 差异:Web 端**不需要**音量 0 假播预热,解码缓存即可。)
- `stopBgm(fadeOutMs = 800)` — 淡出后释放 BGM。
- `stopVoice()` — 打断当前对白并清空排队,不触发 onComplete,BGM 恢复。
- `skipVoice()` — **跳过当前对白但让语音链继续**:停掉当前 voice 并视为
这句自然播完——触发其 onComplete(游戏层的链播靠它推进)并继续等待队列。
与 stopVoice 的差异:stopVoice 作废整条链,skipVoice 只跳当前句。
游戏层自行绑定手势(如长按/特定滑动的"跳过这句")。
- `stopAll()` — stopBgm(200) + stopVoice()(sfx 很短,不主动停)。
三层行为(对齐 Android DefaultAudioEngine):
@@ -137,8 +143,31 @@ new WebAudioEngine(basePath = 'audio/', opts?)
| 层 | 播放器 | 特性 |
|---|---|---|
| BGM | AudioBufferSourceNode(双实例轮换) | 循环;切换时新源 0→满、旧源 →0 交叉淡入,淡出后 stop |
| voice | 单通道 | duck BGM 到 20%(300ms 渐降),播完 300ms 恢复;onComplete 驱动链播 |
| sfx | AudioBufferSourceNode 即发即弃 | 预解码 AudioBuffer 缓存,低延迟,可叠加 |
| voice | SoundTouch 时间拉伸管线(见下) | duck BGM 到 20%(300ms 渐降),播完 300ms 恢复;onComplete 驱动链播 |
| sfx | AudioBufferSourceNode 即发即弃 | 预解码 AudioBuffer 缓存,低延迟,可叠加;总线基础增益 0.8 |
**爆音防护**:master 链末端挂 `DynamicsCompressorNode` 限幅
(threshold -6dB / ratio 12 / attack 3ms / release 250ms),sfx 总线基础
增益 0.8。BGM+voice+多个 sfx 并发叠加超过 0dBFS 时由限幅器压限,
不再在 destination 硬削波(爆音/突然最大音量的根治)。
**voice 变速不变调(SoundTouch 管线)**:voice 层播放走
`vendor/soundtouch.js`(SoundTouch 0.3.0,LGPL v2.1,ScriptProcessorNode
实现):`WebAudioBufferSource → SimpleFilter(SoundTouch tempo) →
ScriptProcessorNode → gain → voiceBus`。`settings.speed` 只改 tempo 不改
pitch,1.0/1.35/1.7 均不变调;自然结束由"源消耗位置 ÷ tempo"定时器判定,
onComplete/打断/排队语义与旧 BufferSource 实现完全一致。注意 voice 有
~100ms 处理延迟(对白场景无感),且 ScriptProcessorNode 已废弃但全平台
(含 iOS Safari)可用。
**长按快进(游戏零改动)**:voice 播放中长按屏幕 → 旁白临时 1.7 倍速
(不变调),松开恢复 `settings.speed`。由 gesture.js 派发的 window 事件
(`soundgame:longpress` / `soundgame:longpressend`)驱动,audio.js 内置监听。
**状态 BGM 约定**:游戏按状态(菜单/探索/战斗/结算)直接
`play('bgm/xxx')` 切换即可——引擎 crossfade 已够用;`settings.bgm=0` 时
切换只更新"想播"不加载不播放,用户重新打开 BGM 时自动补播最后一个状态
BGM。游戏层无需为此写任何判断。
**用户设置自动生效(settings.js,游戏代码无需改动):**
@@ -146,9 +175,9 @@ new WebAudioEngine(basePath = 'audio/', opts?)
`play('voice/intro')` 依次尝试 `audio/voice/<gender>/intro.{ogg,m4a,mp3,wav}`,
全部找不到再回退平铺的 `audio/voice/intro.*`(过渡期资产未分性别也能播)。
缓存键含性别,游戏中途切换音色下一句即用新性别资产。
- `settings.speed`(`1.0|1.35|1.7`,默认 `1.0`):voice 层
`source.playbackRate`,在创建 source 时读取,**从下一句生效**
(进行中的句子不变速)。
- `settings.speed`(`1.0|1.35|1.7`,默认 `1.0`):voice 层 SoundTouch
tempo(**不变调**),在创建播放实例时读取,**从下一句生效**
(进行中的句子不变速——除非长按快进,见上)。
- `settings.bgm`(`1.0|0.3|0`,默认 `1.0`):BGM 层独立增益总线。
改动对**进行中的 BGM 立即生效**(150ms 渐变);调到 `0` 立即淡出停止
且此后 `play('bgm/...')` 只记意图不加载不播放;从 `0` 调回非零时,
@@ -195,7 +224,11 @@ vibrate('error'); // 预定义名称
vibrate([50, 30, 50]); // 自定义震型
```
- 特性检测 `'vibrate' in navigator`;不支持的平台(iOS Safari)**静默 no-op**。
- **视觉降级(iOS 等无 vibrate 平台)**:`hapticsSupported === false` 时
`vibrate()` 自动改为全屏边框闪光(`#haptics-flash` 覆盖层,样式在
style.css):震型 → 颜色/闪烁次数(confirm 白×1 / error 红×2 /
success 绿×1 / warning 橙×3 / heartbeat 粉×2 / pickup 青×1 / hit 强红×1 /
danger 红×3 快 / swipe、beat 极淡白×1)。游戏代码无需判断平台。
- gesture.js 已内置基础反馈:滑动→`swipe`,tap→`confirm`,doubletap→`confirm`。
**游戏逻辑层只需在语义事件(error/success/warning/heartbeat 等)处自行调用**,
不要对手势本身重复调 `vibrate('confirm')`/`vibrate('swipe')`,否则会双重震动。
@@ -204,7 +237,8 @@ vibrate([50, 30, 50]); // 自定义震型
```js
const detach = attachGestures(element, onGesture, config?)
// onGesture: (g: 'up'|'down'|'left'|'right'|'tap'|'doubletap') => void
// onGesture: (g: 'up'|'down'|'left'|'right'|'tap'|'doubletap'
// |'longpress'|'longpressend') => void
// detach(): 解除绑定
```
@@ -216,6 +250,7 @@ const detach = attachGestures(element, onGesture, config?)
| `swipeMinVelocity` | 400 px/s | 平均速度阈值(位移 / 拖动时长) |
| `doubleTapWindow` | 300 ms | 双击判定窗口 |
| `tapSlop` | 24 px | 位移小于此才算点按 |
| `longpressMs` | 450 ms | 按住不动超过此值触发 `longpress` |
- 方向取位移较大的轴,斜滑归一到最近主轴(防斜滑误判)。
- 触摸 + 鼠标双支持(桌面可用鼠标拖拽/点击调试);多指触摸忽略。
@@ -224,8 +259,13 @@ const detach = attachGestures(element, onGesture, config?)
(`style.css` 的 `#game-root` 已带)。
- 手势绑定是全屏捕获层级的:绑在 `#game-root` 上即可覆盖整个屏幕。
- **内置基础震动反馈**(haptics.js):滑动识别成功→`swipe`、tap→`confirm`、
doubletap→`confirm`;不支持震动的平台自动静默。游戏语义反馈
(error/success/warning 等)由游戏层调用 `vibrate()`。
doubletap→`confirm`;不支持震动的平台自动走视觉降级(边框闪光)。
游戏语义反馈(error/success/warning 等)由游戏层调用 `vibrate()`。
- **长按**:按住不动 ≥450ms 触发 `'longpress'`,松开触发 `'longpressend'`;
长按期间不触发 tap;位移超 tapSlop 取消(已激活则先补发 `longpressend`,
滑动照常判定)。长按同时派发 window 事件 `soundgame:longpress(end)`——
audio.js 内置监听实现"长按=旁白临时 1.7 倍速(不变调)"。
**游戏若把 longpress 挪作他用,注意语音快进行为会同时发生。**
**单击有约 300ms 延迟**:引擎要等双击窗口关闭才能确定是单击,`tap`
回调因此延迟 ~300ms 触发(`doubletap` 和滑动是即时的)。这是引擎级行为,