Files
SoundGame/docs/design/03-rhythm-rebuild.md
T
SoundGame Dev cc0f8c008d HTML5 重写:6 款游戏 + 用户系统 + 全局设置 + 5 轮迭代重构
- 新增 web/ 纯音频 HTML5 平台:共享引擎(手势/WebAudio 三层/设置/震动)、
  Node 零依赖服务器(静态 + 注册登录 + 进度存档 API)
- 游戏:剑与声(RPG 新作)、声命线、暗室(重设计)、音序回响(重设计)、
  节拍行者(跟随领奏重构)、盲航(同向闪避重构);声东击西删除
- 全局功能:男声/女声双套 TTS 资产、三档语速、BGM 音量三档、
  震动反馈语言(10 种震型)、服务器进度同步
- 资产:899 ogg + 899 m4a(Qwen3-TTS 双性别语音、Stable Audio 3 音效、
  Mureka BGM 4+3 首),终检零缺失
- 文档:docs/design/ 13 篇设计审查文档、docs/audio/ 资产管线、
  web/server/README.md API 文档
2026-08-29 21:55:44 +08:00

186 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 03 · 《节拍行者》重构施工稿(rhythm rebuild,R3 施工依据)
> **给开发者的实现要点摘要**
> 1. 玩法 = **跟随领奏(call & response)**:系统奏 1 小节节奏型(2-4 个手势乐器音),玩家在下一小节的相同拍点用相同手势跟打。3 首曲目(90/105/120 BPM)解锁制。完整谱面见 §3(每首前 8 句全部写出,后续句按 §3.5 规则生成)。
> 2. 判定沿用现版常量:PERFECT ±0.12s / GOOD ±0.25s / TAP_DELAY 0.30s 补偿 / LOOKAHEAD 0.6s / 调度泵 40ms;tap 只允许出现在第 1、3 拍(强拍)。
> 3. 判定反馈全改震动(`engine/haptics.js`):出手震动引擎 gesture.js 已内置(swipe/confirm),游戏层只加 Miss=`error`、16 连击=`success`、乐句全 Perfect=自定义 [20,40,20];领奏音符锚点自定义 BEAT=[8]。`judge_*` 提示音删除。
> 4. 语音台词全表在 **[03-rhythm-voice-ids.md](03-rhythm-voice-ids.md)**(音频总监照录):旁白 female=Vivian / male=Uncle_Fu(R1 音色分配表),准确率分档整句播报。
> 5. 进度 data:`{ summary, unlocked, grades, bestCombo }`(§7),大厅卡片显示 summary。
---
## 1. 现版问题与重构方向(结论,不再展开)
现版"听令行动"(指令音→下一拍做手势)认知负荷四层(认音色→忆映射→换算拍点→精确出手),且 judge 提示音制造"考试感"。重构为跟随领奏:记忆负荷压到 ≤4 音短句,负荷转移到节奏精度;玩家动作触发与 BGM 同调性的**乐器音**,做对时听到自己在演奏。现版的 AudioContext 预排程泵(`_pump`/`_scheduleSfx`/tap 补偿)工程质量好,**保留复用**,只重写谱面与判定逻辑。
## 2. 常量与判定(施工契约,直接抄)
```js
const PERFECT = 0.12; // Perfect 窗口 ±120ms
const GOOD = 0.25; // Good 窗口 ±250ms(蓝牙延迟由它吸收)
const TAP_DELAY = 0.30; // 引擎 tap 回调延迟补偿(沿用现版)
const LOOKAHEAD = 0.6; // 调度前瞻(秒)
const PUMP_MS = 40; // 调度泵周期
```
- **tap 补偿**:判定时 `if (g === 'tap') t -= TAP_DELAY`;tap 音符的 Miss 超时检查放宽 `TAP_DELAY + 0.08`,其他手势放宽 `0.05`(现版 game.js:159-164 原样保留)。
- **tap 位置约束**:谱面中 tap 只允许落在第 1、3 拍(强拍),给补偿留足余量;doubletap 不落在 x.5 反拍(曲目三之前)。
- 判定结果:
- 手势对 + |dt|≤0.12 → Perfect:玩家手势乐器音 + accent 亮音层叠加。
- 手势对 + |dt|≤0.25 → Good:只播乐器音。
- |dt|>0.25 或手势错 → Miss:**不播乐器音**(错过=没出声,本身就是反馈)+ `vibrate('error')`。
- 手势对但窗口内找错音符归属:按现版"最近未判定音符"归属逻辑(game.js:278-293)保留。
- 计分:Perfect=2 / Good=1 / Miss=0;`rate = 得分 / (2 × 总音符数)`;评级 S≥0.95 / A≥0.85 / B≥0.70 / C<0.70。不掉血、不中断,歌照唱完。
- 连击:每 16 连击 `vibrate('success')`(不打断演奏,不播报);乐句全部 Perfect 时乐句末 `vibrate([20,40,20])`。
## 3. 谱面
### 3.1 记号与结构
- 拍点记号:`拍号:手势`,1 小节 = 4 拍(1, 2, 3, 4),`x.5` = 八分音符反拍。
- 手势:`tap`(T) `doubletap`(D) `left`(L) `right`(R) `up`(U) `down`(N)。
- 每个乐句 = **领奏 1 小节 + 应答 1 小节**(应答拍点 = 领奏拍点 + 4 拍)。
- 曲目流程:`数拍 1 小节(BGM 内鼓点,无独立节拍器)→ 逐乐句 → 结算`。
- 乐句间休止:曲目一前 4 句应答后各休 1 小节;曲目二前 2 句各休 1 小节;曲目三不休。
### 3.2 曲目一《启程》90 BPM(SPB=0.667s,10 句,≈70s)
定位:教学曲。全强拍,2→4 音,无反拍。
| 句 | 领奏节奏型 | 音符数 |
|---|---|---|
| 1 | `1:tap, 3:tap` | 2 |
| 2 | `1:left, 3:right` | 2 |
| 3 | `1:tap, 2:doubletap, 3:tap` | 3 |
| 4 | `1:left, 2:doubletap, 3:right` | 3 |
| 5 | `1:tap, 3:left, 4:doubletap` | 3 |
| 6 | `1:right, 3:tap, 4:doubletap` | 3 |
| 7 | `1:up, 3:down, 4:doubletap` | 3 |
| 8 | `1:tap, 2:up, 3:right, 4:doubletap` | 4 |
| 9 | `1:left, 2:right, 3:tap, 4:doubletap` | 4 |
| 10 | `1:tap, 2:doubletap, 3:tap, 4:doubletap` | 4(终句,全鼓组收束) |
### 3.3 曲目二《追风》105 BPM(SPB=0.571s,12 句,≈75s)
定位:引入反拍(第 5 句起)与 4 音密集句。
| 句 | 领奏节奏型 | 音符数 |
|---|---|---|
| 1 | `1:tap, 3:right` | 2 |
| 2 | `1:left, 2:doubletap, 3:tap` | 3 |
| 3 | `1:up, 3:down, 4:doubletap` | 3 |
| 4 | `1:tap, 2:right, 3:left, 4:doubletap` | 4 |
| 5 | `1:tap, 2.5:right, 3:tap` | 3(首个反拍 2.5) |
| 6 | `1:left, 2:up, 3.5:right, 4:doubletap` | 4 |
| 7 | `1:tap, 2:doubletap, 3:down, 3.5:up` | 4 |
| 8 | `1:right, 1.5:left, 3:tap, 4:doubletap` | 4 |
| 9 | `1:tap, 2.5:left, 3:tap, 4:doubletap` | 4 |
| 10 | `1:up, 2:right, 3:down, 3.5:left` | 4 |
| 11 | `1:tap, 2:doubletap, 2.5:right, 3:tap` | 4 |
| 12 | `1:left, 1.5:right, 2:doubletap, 3:tap, 4:doubletap` | 5(终句) |
### 3.4 曲目三《星爆》120 BPM(SPB=0.5s,12 句,≈70s)
定位:4-6 音密集句、切分、doubletap 上反拍。不设十六分音符(盲玩极限)。
| 句 | 领奏节奏型 | 音符数 |
|---|---|---|
| 1 | `1:tap, 2:doubletap, 3:tap, 4:doubletap` | 4 |
| 2 | `1:left, 2:right, 3:left, 4:doubletap` | 4 |
| 3 | `1:up, 2:down, 3:up, 4:doubletap` | 4 |
| 4 | `1:tap, 2.5:left, 3:tap, 4:right` | 4 |
| 5 | `1:left, 1.5:right, 2.5:left, 3:tap` | 4 |
| 6 | `1:tap, 2:up, 2.5:down, 3:tap, 4:doubletap` | 5 |
| 7 | `1:right, 2:doubletap, 3:left, 3.5:right, 4:up` | 5 |
| 8 | `1:tap, 1.5:doubletap, 2.5:left, 3:tap, 3.5:right, 4:doubletap` | 6(高潮句) |
| 9 | `1:left, 2:doubletap, 2.5:up, 3:right, 4:doubletap` | 5 |
| 10 | `1:tap, 2.5:down, 3:tap, 3.5:up, 4:doubletap` | 5 |
| 11 | `1:right, 1.5:left, 2:right, 3:tap, 4:doubletap` | 5 |
| 12 | `1:tap, 2:doubletap, 2.5:left, 3:tap, 3.5:right, 4:doubletap` | 6(终句) |
### 3.5 难度曲线规则(谱面设计约束,续作/改谱遵守)
1. 乐句长度:曲目一 2→4 音,曲目二 2→5 音,曲目三 4→6 音;同曲内单调不减(终句允许 +1 爆发)。
2. 反拍(x.5)引入:曲目一禁用;曲目二第 5 句起、每句至多 1 个反拍音;曲目三不限。
3. tap 只落 1/3 拍(全曲目铁律);doubletap 曲目一/二只落整拍,曲目三可落反拍。
4. 同一手势连续出现 ≤2 次(跨音符);同一乐句内手势种类 ≥2(第 1、10 句的全鼓句除外)。
5. up/down 作为"音高对"尽量成对出现在同一乐句(↑↓ 或 ↓↑),利于听觉记忆。
6. 左右声道手势不放在相邻反拍连续位(如 `1:left, 1.5:right` 只允许曲目三)——声像跳变过快在耳机上易混。
## 4. 手势 → 乐器音映射(sfx 资产)
| 手势 | 乐器音 | sfx id | 声像/音高 |
|---|---|---|---|
| tap | 底鼓 kick | `sfx/inst_kick` | 中央,低频 |
| doubletap | 军鼓 snare | `sfx/inst_snare` | 中央偏上 |
| left | 闭合镲 closed hat | `sfx/inst_hat_l` | 极左 |
| right | 开放镲 open hat | `sfx/inst_hat_r` | 极右 |
| up | 高通 tom | `sfx/inst_tom_hi` | 中央,升调感 |
| down | 低通 tom | `sfx/inst_tom_lo` | 中央,降调感 |
| (叠加层) | Perfect accent 亮音 | `sfx/accent` | 共用 1 个,叠在乐器音上 |
全部与 BGM 同调性(曲目一/二/三 BGM 根音需在 Mureka prompt 里统一,建议 A minor)。乐器音时长 ≤0.4s,起音要硬(盲玩靠起音对拍)。生成方式:Stable Audio 3(打击乐短采样质量足够)或程序合成兜底;accent 可用程序合成(高频短琶音)。
## 5. 流程与教学(30 秒上手)
### 5.1 状态机
```
idle → intro(语音) → tut(3 步教学) → select(选曲,二周目起) → countin → main(逐乐句) → end(结算)
↑_________________|
```
- `select`:左右滑切曲目(播曲目名 + 已解锁状态),单击进入,下滑回大厅。未解锁曲目播报"上一首拿到 B 级以上解锁"。
- 教学只在首次进游戏出现(`progress.data.tutorialDone`);之后 intro 缩减为一句"跟着音乐,我奏一句,你跟一句。"
### 5.2 教学 3 步(台词见 voice-ids 文档 T 组)
| 步 | 内容 | 机制 |
|---|---|---|
| T1 | 领奏 `1:tap`(第 1 拍单底鼓)→ 玩家在第 2 小节同位置跟打 | 做错:error 震动 + 纠正语音(早了/晚了/不是这个手势,按 dt 与手势选句)→ 重播领奏重试;2 次失败跳过进 T2 |
| T2 | 领奏 `1:left, 3:right` → 跟打 | 同上 |
| T3 | 领奏 `1:tap, 2:doubletap, 3:tap` → 跟打 | 通过后进曲目一 |
纠正判定复用正式判定窗口,只是不计分。教学期间 BGM 已在播(从 intro 第 6 秒起),数拍感来自 BGM 本身。
## 6. 震动与语音在流程中的落点
| 时机 | 震动 | 语音 |
|---|---|---|
| 领奏每个音符 | [8](BEAT,自定义) | — |
| 玩家出手 | (引擎内置 swipe/confirm,游戏层不重复) | 乐器音 |
| Miss | error | — |
| 16 连击 | success | — |
| 乐句全 Perfect | [20,40,20] | — |
| 曲目过半 | — | `mid_song`("过半了,保持。") |
| 结算 | success(S/A)/ 无(B/C) | `grade_*` + `restart_hint` |
| 解锁新曲 | success + [20,40,20] | `unlock_song2/3` |
## 7. 进度存储
```json
{
"schema": 1,
"updatedAt": "2026-08-29T12:00:00Z",
"summary": "《追风》B级 · 已解锁2/3首",
"tutorialDone": true,
"unlocked": 2,
"grades": { "song1": "a", "song2": "b" },
"bestCombo": 42
}
```
- 评级提升(如 b→a)、解锁变化、bestCombo 刷新时在结算后 PUT;`summary` 格式:`《曲名》X级 · 已解锁N/3首`(取最高评级的曲目)。
- 解锁规则:曲目 n 拿到 B 级以上 → 解锁 n+1。
## 8. 实现要点(给开发 agent)
1. 保留现版:`AudioContext.currentTime` 预排程、40ms 泵、LOOKAHEAD、`TAP_DELAY` 补偿与 Miss 放宽(现 game.js:26-30, 149-176, 267-293)、BGM 手动 `src.start(t0)` 对网格(game.js:221-239)。
2. 重写:谱面数据从 40 指令流改为 `phrases: [{ notes: [{ beat, gesture }] }]`(本文 §3 表格直接转 JSON);调度时领奏音符排第 k 小节、应答 `_cues` 期望时刻 = 领奏时刻 + 4 拍;间奏休止按曲目参数插入空小节。
3. 玩家乐器音不走预排程:出手即播(sfx 即发即弃),Perfect 时叠播 `sfx/accent`。
4. BGM:3 首重生成(Mureka,90/105/120 BPM 精确、鼓组清晰、A minor、60-90s 无缝循环,沿用现版 groove 归一化工艺);现版 `bgm/groove` 弃用。数拍融入 BGM,删独立 tick 节拍器。
5. 震动接 `engine/haptics.js`;BEAT 用自定义数组 `vibrate([8])`,领奏音符在调度时同步排一个 `setTimeout` 对齐(或用 pump 在音符时刻触发,误差 <40ms 可接受)。
6. 语音全表见 03-rhythm-voice-ids.md;现版 `voice/welcome/rules/tut_start/tut_done` 弃用,`grade_*`/`restart` id 沿用但文案变更(重录)。
7. 结算画面:单击=再来一遍(同曲),右滑=下一首(若已解锁),下滑=回大厅。
8. 更新 `web/rhythm/README.md`(玩法、手势表、进度结构)。