This commit is contained in:
xsl
2026-10-01 23:26:47 +08:00
parent e943227136
commit 9e4d67aa84
50 changed files with 3913 additions and 8243 deletions
+188 -294
View File
@@ -1,185 +1,172 @@
# 24 · 《节拍行者》鼓手模式重构设计(rhythm / Drummer Mode)
# 24 · 《节拍行者》鼓手模式(26 号对齐)
> 用户裁定:现有模式「不错,保留」——跟随领奏的合拍玩法核心、S~C 评级、曲目解锁制、
> BPM 精确性延续;整体重构为**屏幕分区打击乐**:屏幕划分区域,每区一种打击乐音效,
> 玩家作为乐队鼓手与无鼓伴奏音乐合拍。本文取代 [03-rhythm-rebuild.md](03-rhythm-rebuild.md)
> 的全部施工依据(03 号文档仅作历史档案,文末有取代指针;其 rhythm 判定好评口径
> 「PERFECT ±0.15 / GOOD ±0.35」经 live 校准,本文沿用)。
>
> 语音台词全表在 [24-rhythm-voice-ids.md](24-rhythm-voice-ids.md)。断言清单在 §12。
> **给开发者的实现要点摘要**
> 1. **玩法保留**:3×2 分区鼓组、跟随领奏、PERFECT ±0.15 / GOOD ±0.35、S~C 评级、B 级解锁下一首、无鼓伴奏、BPM 网格。本文取代 03 号作为施工依据。语音表仍见 [24-rhythm-voice-ids.md](24-rhythm-voice-ids.md)。
> 2. **演奏期 = Floor `ACTION`**:不起等待声;点按即敲。Miss = 不按「敲对」计、该音符无领奏应答感 + 闷镲/回执 + 真话归因。禁止只震。
> 3. **新手期无双击手势**(`doubleTapWindow: 0`)。同区两记连续点按是两下敲击,不是「双击手势」。菜单禁止双击确认。双击手势直至阶段 3 才存在,且不绑音符类型。
> 4. 分区点按是核心;选曲/结算/暂停说明的**帮助=长按**(上半屏点按是鼓,上滑易混)。暂停恢复必须 **count-in**。
> 5. 第一句就摸鼓或跟打;第一次成功 ≤10s。
---
## 0. 可行性勘察决策(阶段零结论)
## 0. 设计单(26 号附录 C)
### 0.1 引擎手势事件是否携带坐标?
### 0.1 一句话核心动作
**不携带。** `web/engine/gesture.js` 的 `onGesture(g)` 只传手势名字符串,
触摸坐标只在引擎内部用于滑动/点按判定,回调时丢弃。
**屏幕是一面 3×2 鼓组。领奏敲一小节,你在下一小节同一拍点敲同一面鼓。**
### 0.2 两条路线的权衡
### 0.2 玩家可感知的状态(3 个)
| | (a) 引擎扩展:第二参数透传坐标 | (b) 游戏层自建 touch 监听 |
|---|---|---|
| 改动面 | gesture.js 每个 onGesture 调用点补 `{x,y}` 第二参;docs/web-engine-api.md 同步 | 游戏层加 touchstart 记录坐标 |
| 既有游戏影响 | **零**——全部 15 款游戏的回调都是单参 `(g) =>`,多余实参被忽略,手势分类/震动/时序逐字节不变(同 opts.pan / immediateTap / tapcancel 先例) | 零(不碰引擎) |
| 键盘通道坐标 | **免费**:空格虚拟指针本就走 `handleDown(keyCenter)`,天然拿到元素中心坐标 | 无法区分键盘 tap 与残留触摸坐标,需自行加时效启发式(脏) |
| 保持的引擎福利 | haptics 自动震动、多指守卫、tapcancel、长按快进 window 事件、双击状态机、键盘三通道全部保留 | 全部丢失,需在游戏层重写(双击窗/tapcancel/快进事件) |
| 代价 | 全量回归:15 游戏 playtest + guide.test + engine-voice + engine-input + docs 同步(铁律) | 回归只需 rhythm 自身,但收获一个与引擎并行的第二输入状态机(长期维护债) |
| 状态 | Floor | 听感 | 第一次教 |
|---|---|---|---|
| 演奏 / 教学跟打 | `ACTION` | 无鼓伴奏 + 鼓声音色(声像+音高) | 「敲屏幕。摸到哪是哪。」 |
| 选曲 | `YOUR_TURN` | `bgm/menu` + 等待声 | 「左右滑选歌。单击开始。」 |
| 结算 | `YOUR_TURN` | menu pad + 评级句 | 「单击,再来一遍。」 |
### 0.3 决策:路线 (a),引擎后向兼容扩展
暂停:音乐停,不叠 menu pad。恢复先 count-in,再进窗。
- `attachGestures` 的全部 `onGesture(...)` 调用补第二实参 `{x, y}`(视口像素
clientX/clientY 语义):tap 类事件(含 immediateTap 序曲 tap、双击改判、tapcancel)
与 longpress/longpressend 传**按下点**;四向滑动传**滑动起点**(出手瞄准位置);
键盘通道传虚拟指针坐标(`keyCenter()`,方向键滑动同样补)。
- 缺省行为逐字节不变:不加 config 开关、不改任何判定阈值与震动;回调签名扩为
`(g, info?: {x, y})`,单参回调零感知。
- 回归义务(已执行,见 research/rhythm-drummer/round0/engine-regression.md):
15 游戏 playtest + web/harness/guide.test.mjs + engine-voice.test.mjs +
engine-input.test.mjs 全绿,docs/web-engine-api.md §gesture 同步。
### 0.3 新手手势与解锁
### 0.4 打击乐音色:程序合成
| 手势 | 演奏 ACTION | 选曲 / 结算 | 解锁 |
|---|---|---|---|
| 单击(含分区坐标) | 敲该区 | 开始 / 再来 | 阶段 0;演奏期 `immediateTap` |
| 左 / 右滑 | 不作为音符(可 `invalid` 或忽略为非敲) | 切曲 / 右滑下一首 | 阶段 0 |
| 长按 | 死输入:`invalid` 或闷镲,不判定 | **帮助一句** | 阶段 1 |
| 下滑 | 阶段 2 前=`invalid`「现在只要敲。」;阶段 2 后无条件暂停菜单 | 菜单 | 阶段 2(第一局结束) |
| 双击手势 | **不存在**;300ms 内两下=两次 tap | 当单击 | 阶段 3 仅重听选曲说明/评级,禁止绑重开 |
六区音色 + accent 全部 numpy 程序合成(`tools/gen_rhythm_syn.py`,
gen_cipher_syn.py 同款工艺):可精确控制起音/衰减(盲玩靠起音对拍)、
零授权问题、零网络延迟、与 BGM 调性关系无关(打击乐无调性)。
键盘:Digit1–6 = 六区,按下即发。空格落 BC(家区)。选曲态数字键无效。
### 0.5 BGM:无鼓伴奏是硬需求
### 0.4 状态 × 手势矩阵(新手期)
玩家就是鼓手——BGM 含鼓则叠打混乱。Mureka instrumental 生成时 prompt
显式 `no drums, no percussion`,`tools/bpm_precise.py` 梳状扫描实测 BPM
(节奏类硬性要求,误差 >0.5 则 atempo 拉伸,R3 track90 先例),
无缝循环后处理沿用批次 1-4 工艺(强 onset 起点 → 整数小节裁剪 →
首尾 3s acrossfade → loudnorm I=-20 → ogg q3 + m4a 128k)。
| 状态 \ 手势 | 分区单击 | 左右滑 | 长按 | 下滑(未解锁菜单) | 双击 | 连点 |
|---|---|---|---|---|---|---|
| intro/tut 讲解 SPEAKING | 跳句 或 摸鼓(T0 探索) | 打断 | 帮助 | `invalid`+解释 | 当单击 | 400ms `busy`(讲解);T0 每下都出声 |
| 教学跟打 ACTION | 敲区判定 | `invalid` | 死输入 | `invalid` | 当两次单击 | 两下两击 |
| 演奏 ACTION | 敲区判定 | 非音符 | 死输入 | 未解锁=`invalid` | 两次单击 | 合法连击 |
| 选曲 | 开始当前曲 | 切曲报名 | 帮助 | 阶段 2 菜单 | 当单击 | `busy` |
| 暂停菜单 | 确认当前项 | 左右切换 | 重听说明 | 关闭菜单 | 当单击 | `busy` |
| 结算 | 同曲再来 | 右=下一首 | 重听评级 | 菜单 | 当单击 | 只处理第一下 |
### 0.5 等待点
| id | 期待 | `_full` | `_short` | 静默 | L2 / L3 |
|---|---|---|---|---|---|
| `tut_probe` | 任意敲 ≥1 | 「随便摸,听鼓在哪。」 | 「摸一下。」 | 8s 自动进 T1 | — |
| `tut_kick` | 应答敲 BC | 「家区在下中。点一下。」 | 「点下中。」 | Miss | 降速重演 → 演示 |
| `tut_sides` | BL 然后 BR | 「左下,再右下。」 | 「左右下。」 | Miss | 同上 |
| `select` | 单击 | 「单击,开始。」 | 「单击。」 | 18s | — |
| `settle` | 单击 | 「单击,再来一遍。」 | 「单击。」 | 20s | — |
演奏 ACTION 不挂等待声、应答小节内零语音。
### 0.6 首 3 分钟逐秒脚本
| 时间 | 游戏说 / 播 | 期待 | 没做 | 做错 |
|---|---|---|---|---|
| 0~3s | 「点一下屏幕。」或直接「随便摸。」 | 任意敲 | 8s 自动进 T1 | — |
| 3~10s | 区音色 + 区名(第一次成功)→ T1 数拍 → 领奏 BC | 应答敲家区 | 「这拍没接。」 | 错区:报名实际区 +「敲家区。」 |
| 10~30s | T2 左右下排 | 跟打 | 短句;2 败降速 | 真话归因 |
| 30~60s | T3 上排三音或进曲目一句 1 | 跟打 | 接管 | Miss 无「假成功」乐器归属 |
| 60~180s | 曲目一前句(无双击手势、反拍仅加花) | 敲对区 | 连 2 Miss 才 `gd_miss` 一次 | 早/晚/没接/错鼓分档 |
### 0.7 失败表
| 类型 | 判定 | 归因 | 回到 | 新手代价 |
|---|---|---|---|---|
| 没出手 | expected+GOOD+0.10s 无应答 | 「这拍没接。」 | 教学重来;正片歌继续 | 不中断 |
| 早 / 晚 | 同区超 Perfect 但仍在 Good | 教学事后「早了/晚了」;正片 Good 出声 | 教学重来 | 无 |
| 错鼓 | 异分区音符半窗内被敲 | 教学:区名+「敲错鼓了」;正片:错区音色本身(配额不叠语音) | 同上 | combo 清零 |
| 教学 2 败 | 同一步 | 「我放慢一些。」×0.75 | 该步 | 再败跳过不卡死 |
| 长按演奏期 | 按下≥450ms | 闷镲 + `invalid` | 不归属 | 无 |
### 0.8 台词类别
- `critical`:开场动手句 ≤3s。
- `content`:曲名、评级、解锁、`tut_map` 拆句后非关键段。
- `hint`:`gd_answer`、`gd_count`、区名、短教学。
- `feedback`:`gd_miss_none` / early / late / `retry_wrong`。
应答小节内:hint/feedback 播不出则丢。曲尾 3s 内不插 `gd_miss`。
### 0.9 断点复述
暂停:泵停、BGM 记 offset。恢复:cue.expected / `_bgmT0` 平移 = 暂停时长 + **count-in 小节**;guide full 先播 `gd_count`(每曲 1 次)。退出写进度 schema 1。
### 0.10 测试
§12 断言清单(已按新手无双击、菜单单击确认、ACTION、count-in 修订)。乱点:无声操作 0;被动原句重复 0。
---
## 1. 概述与继承/舍弃清单
## 1. 可行性决策(保留)
**一句话玩法**:屏幕是一面 3列×2行 的鼓组。系统(领奏)在无鼓伴奏上敲一小节
节奏型,玩家在下一小节的相同拍点敲相同的鼓。曲子从 90 BPM 打到 120 BPM,
拿 B 级以上解锁下一首。
### 1.1 继承自旧作(用户裁定「保留」的部分)
| 项 | 说明 |
|---|---|
| 跟随领奏 call & response 核心循环 | 领奏 1 小节 → 应答 1 小节,逐乐句推进 |
| 时值判定口径 | PERFECT ±0.15s / GOOD ±0.35s(live round1 §13 校准值,吸收蓝牙延迟) |
| 计分与评级 | Perfect=2/Good=1/Miss=0,rate = 得分/(2×总音符),S≥0.95 / A≥0.85 / B≥0.70 / C 其余;不掉血不中断,歌照唱完 |
| 曲目解锁制 | 曲目 n 拿到 B 级 → 解锁 n+1;localStorage + 已登录 PUT 同步 |
| BPM 精确性 | 谱面/判定全部锚定 BGM 实测 BPM 网格(bpm_precise 实测 + atempo 修正) |
| 工程质量遗产 | AudioContext 预排程泵(40ms + LOOKAHEAD 0.6s)、BGM 手动 `src.start(t0)` 对网格、模态菜单断点续播、`_speakGuarded` 看门狗语音链、结算后 menu pad 看门狗、旁白单击跳过/长按快进豁免、guide 层全量接入(状态签名/gd_answer/gd_miss 归因/教学 DDA 降速/count-in 恢复) |
### 1.2 舍弃/替换
| 旧 | 新 | 理由 |
|---|---|---|
| 手势→乐器映射(tap=kick、left=闭镲…6 手势 6 音色) | 屏幕分区→乐器(6 区 6 音色,tap 即敲击) | 用户裁定的新核心 |
| 滑动/双击作为音符类型(SWIPE_DELAY 0.15 / DOUBLE_DELAY 0.20 补偿、双击序曲撤销) | **全部音符 = 分区点按**,immediateTap 按下即判,零识别补偿 | 打击乐每次出手都是「敲一下」,滑动从音符类型降级为纯导航;双击=同区连击两下(两声都合法),**不再需要双击序曲撤销账簿** |
| BGM 含鼓组(track90/105/120 有鼓) | 无鼓伴奏 | 玩家是鼓手 |
| 下滑呼菜单的「down 音符临近」折中(live P0 加窗/复活窗一整套) | 下滑**无条件**呼菜单 | 音符全是点按,下滑永不与音符冲突;序曲 tap 仍出 ghost 音但判定时由 tapcancel 撤销 |
| 教学 T1-T3(手势记忆) | 分区心智模型教学(摸鼓认区 → 跟打) | 「摸到哪是哪」的建立是设计核心(§6) |
| 旧语音表 31 句 | 新表 41 句(+沿用向导 8 句 = 49 句双性别,配额见 §8) | 分区教学与区名播报需要新句 |
引擎 `onGesture(g, info?: {x,y})` 后向兼容扩展;键盘 `keyCenter` 免费得坐标。打击乐 numpy 合成。BGM 硬性 `no drums`。回归义务见 `research/rhythm-drummer/round0/`。
---
## 2. 屏幕分区与音色映射
### 2.1 分区布局(3 列 × 2 行,纯听觉可辨是硬约束)
## 2. 屏幕分区与音色(保留)
```
┌─────────┬─────────┬─────────┐
│ TL 左上 │ TC 上中 │ TR 右上 │ 上排:清亮/高频
│ 踩镲 │ 高嗵 │ 吊镲 │ pan: -0.6 / 0 / +0.6
│ TL 左上 │ TC 上中 │ TR 右上 │ 上排:清亮 pan -0.6 / 0 / +0.6
│ 踩镲 │ 高嗵 │ 吊镲 │
├─────────┼─────────┼─────────┤
│ BL 左下 │ BC 下中 │ BR 右下 │ 下排:低沉/低频
│ 军鼓 │ 底鼓 │ 低嗵 │ pan: -0.6 / 0 / +0.6
│ BL 左下 │ BC 下中 │ BR 右下 │ 下排:低沉
│ 军鼓 │ 底鼓 │ 低嗵 │
└─────────┴─────────┴─────────┘
```
**双轴心智模型**(教学的主话术):
- **列 = 声像**:左列在左耳、中列在中央、右列在右耳(opts.pan 引擎已支持)。
- **行 = 音高**:上排清亮、下排低沉。
- 在双轴之上,六件乐器音色本身两两不同(镲/嗵/鼓),盲玩家有
「声像 + 音高 + 音色」三重冗余线索。
| 区 | 乐器 | sfx | pan | 合成要点 |
|---|---|---|---|---|
| TL | 闭合踩镲 | `dm_hat` | -0.6 | 6–10kHz 噪声,0.06s |
| TC | 高音嗵 | `dm_tomhi` | 0 | 200→140Hz,0.25s |
| TR | 吊镲 | `dm_crash` | +0.6 | 长尾 ≤1.2s |
| BL | 军鼓 | `dm_snare` | -0.6 | 鼓皮+带通噪声,0.2s |
| BC | 底鼓 | `inst_kick` | 0 | 130→45Hz,0.25s |
| BR | 低音嗵 | `dm_tomlo` | +0.6 | 120→75Hz,0.3s |
### 2.2 分区 ↔ 音色 ↔ 声像总表
Perfect 叠 `dm_accent`。`zoneOf(x,y)` 以 `#game-root` 为界;缺 payload 落 BC。领奏与玩家同一批 sfx。
| 区码 | 位置 | 乐器 | sfx id | pan | 合成要点(起音硬、≤0.5s 残响) |
|---|---|---|---|---|---|
| TL | 左上 | 闭合踩镲 | `sfx/dm_hat` | -0.6 | 6-10kHz 高通噪声,0.06s 急衰减 |
| TC | 上中 | 高音嗵 | `sfx/dm_tomhi` | 0 | 200→140Hz 正弦扫频 + 轻皮噪,0.25s |
| TR | 右上 | 吊镲 | `sfx/dm_crash` | +0.6 | 4-12kHz 噪声 + 非谐波泛音列,1.2s 长尾 |
| BL | 左下 | 军鼓 | `sfx/dm_snare` | -0.6 | 200Hz 鼓皮 + 1-4kHz 带通噪声,0.2s |
| BC | 下中 | 底鼓 | `sfx/inst_kick`(沿用旧 id) | 0 | 130→45Hz 正弦扫频 + 触击瞬态,0.25s |
| BR | 右下 | 低音嗵 | `sfx/dm_tomlo` | +0.6 | 120→75Hz 正弦扫频 + 轻皮噪,0.3s |
- 附加层:`sfx/dm_accent`(Perfect 叠加亮音,程序合成木鱼 1.5kHz 短音)。
- 分区判定:`zoneOf(x, y)` 以 `#game-root` 的 `getBoundingClientRect()` 为界,
y 上半 = 上排、y 下半 = 下排;x 三等分 = 左/中/右。无布局环境(mock/harness)
由调用方显式传 payload;payload 缺失时落 BC(中央家区,兜底不炸)。
- **键盘平权**(游戏层 keydown,引擎键盘通道之外的补充,数字键引擎本就不拦截):
`Digit1/2/3/4/5/6`(及小键盘)= TL/TC/TR/BL/BC/BR,按下即发、零延迟;
引擎空格 tap 落在元素几何中心 → 判 BC(「家」)。选曲/菜单/结算态数字键不生效
(那些态沿用手势导航)。
### 2.3 领奏音色 = 玩家音色
领奏音符与玩家敲击走**同一批 sfx**(同音色同声像)——做对时听到自己在和
「另一个鼓手」合奏,这是旧作好评的「演奏感」来源,保留。
**同区连击**:谱面上 `1:BC, 2:BC` 是两记点按。新手 `doubleTapWindow: 0` 时两下都是 `tap`,各自判定。不要再走「双击改判 / 序曲撤销账簿」——只保留 immediateTap 的 `tapcancel`(转滑动/长按时撤销这一下的认领,ghost 音收不回,统计必须回滚且不计 Miss)。
---
## 3. 玩法循环与状态机
### 3.1 循环
## 3. 循环与状态机(保留)
```
选曲 → 数拍导入(1 小节底鼓×4,含节拍轻震) → 逐乐句(领奏 1 小节 + 应答 1 小节 [+ 休止])
→ 过门/加花(领奏 1 小节密集加花 + 应答 1 小节) → 终句吊镲 → 结算评级
选曲 → 数拍(1 小节底鼓×4 + 每拍轻震)→ 逐乐句 → 过门/加花 → 终句吊镲 → 结算
idle → intro → tut → select → countin → main → end
```
### 3.2 状态机
```
idle → intro(语音) → tut(分区教学) → select(选曲,二周目起) → countin → main(逐乐句/加花) → end(结算)
↑_________________|
```
- 数拍小节由游戏排程 4 记数拍(BGM 无鼓,数拍只能靠鼓手自己)+ 每拍 [8] 轻震:正片用底鼓 inst_kick×4(guide.test G3 的 _sched 锚点,13 号冻结文档不可改),教学步用木鱼 click(dm_accent,guide.test G2 要求教学 _sched 里 kick=领奏音)。
- 每曲第一个应答窗口前 1 小节落 `gd_answer` + turn earcon(旧作 G1 修正沿用)。
- 模态菜单(下滑呼出):单击循环 [继续演奏 / 回大厅]、双击确认、上滑帮助、
下滑关闭;暂停真冻结(泵停、BGM 断点记 offset),恢复时全网格平移 +
4 拍 count-in(guide full 档先播 `gd_count`,每局限 1 次,round3 G3 全套沿用)。
- 正片数拍:`inst_kick`×4(guide.test G3 `_sched` 锚点保留)。
- 教学数拍:木鱼 `dm_accent`(G2:教学 `_sched` 里 kick=领奏音)。
- 每曲第一个应答窗前 1 小节:`gd_answer` + `turn`。
- **模态菜单**(阶段 2):下滑呼出;**左右滑切换、单击确认**;长按=重听菜单说明;再下滑关闭。暂停真冻结。恢复全网格平移 + **4 拍 count-in**。
---
## 4. 谱面与难度曲线
## 4. 谱面与难度(保留;新手无「双击手势」需求)
### 4.1 记号
记号 `拍:区码`。加花尾音恒 TR。
`拍:区码`,1 小节 4 拍,`x.5` = 八分反拍。乐句 = 领奏 1 小节 + 应答 1 小节;
加花 = 特殊乐句(fill 标记),领奏为八分音符密集型,最后一音恒为 TR 吊镲收束。
### 4.2 曲目一《启程》90 BPM(教学曲,8 句 + 终曲加花,≈65s)
### 4.1 曲目一《启程》90 BPM(8 句 + 终曲加花,≈65s)
| 句 | 领奏节奏型 | 音符数 |
|---|---|---|
| 1 | `1:BC, 3:BC` | 2 |
| 2 | `1:BL, 3:BR` | 2 |
| 3 | `1:BC, 2:BC, 3:BL` | 3(双击底鼓引入同区连击) |
| 3 | `1:BC, 2:BC, 3:BL` | 3(同区两记点按,不是双击手势) |
| 4 | `1:BL, 2:BR, 3:BC` | 3 |
| 5 | `1:BC, 2:BL, 3:BC, 4:BR` | 4 |
| 6 | `1:TL, 3:BL, 4:BC` | 3(上排首次进正片) |
| 6 | `1:TL, 3:BL, 4:BC` | 3 |
| 7 | `1:BC, 2:TL, 3:BC, 4:TR` | 4 |
| 8 | `1:BL, 2:BR, 3:TL, 4:BC` | 4 |
| 终曲加花 | `1:BC, 1.5:BC, 2:BC, 2.5:BL, 3:BL, 3.5:BC, 4:TR` | 7(全曲唯一反拍,仅限加花) |
| 终曲加花 | `1:BC, 1.5:BC, 2:BC, 2.5:BL, 3:BL, 3.5:BC, 4:TR` | 7 |
前 4 句应答后各休 1 小节,其余不休。
前 4 句应答后各休 1 小节。曲目一普通句无反拍;反拍仅加花。
### 4.3 曲目二《追风》105 BPM(10 句 + 中途加花 + 终曲加花,≈80s)
### 4.2 曲目二《追风》105 BPM
| 句 | 领奏节奏型 | 音符数 |
|---|---|---|
@@ -196,9 +183,9 @@ idle → intro(语音) → tut(分区教学) → select(选曲,二周目起)
| 10 | `1:BL, 2:BR, 3:TL, 4:BC` | 4 |
| 终曲加花 | `1:BC, 1.5:BL, 2:BC, 2.5:BL, 3:BC, 3.5:BR, 4:TR` | 7 |
前 2 句应答后各休 1 小节;第 5 句与中途加花之间不休。
前 2 句应答后各休 1 小节。
### 4.4 曲目三《星爆》120 BPM(10 句 + 中途加花 + 终曲加花,≈75s)
### 4.3 曲目三《星爆》120 BPM
| 句 | 领奏节奏型 | 音符数 |
|---|---|---|
@@ -210,207 +197,114 @@ idle → intro(语音) → tut(分区教学) → select(选曲,二周目起)
| 6 | `1:BC, 2:TL, 2.5:BL, 3:BC, 4:TR` | 5 |
| 中途加花 | `1:TL, 1.5:TL, 2:BL, 2.5:BL, 3:BC, 3.5:BC, 4:TR` | 7 |
| 7 | `1:BR, 2:BC, 3:BL, 3.5:BR, 4:TL` | 5 |
| 8 | `1:BC, 1.5:BC, 2.5:BL, 3:BC, 3.5:TL, 4:TR` | 6(高潮句) |
| 8 | `1:BC, 1.5:BC, 2.5:BL, 3:BC, 3.5:TL, 4:TR` | 6 |
| 9 | `1:BL, 2:BC, 2.5:BR, 3:TL, 4:BL` | 5 |
| 10 | `1:BC, 2:BR, 3:BC, 3.5:TL, 4:BL` | 5 |
| 终曲加花 | `1:BC, 1.5:BL, 2:BC, 2.5:BL, 3:BC, 3.5:BC, 4:TR` | 7 |
全曲不休。
### 4.5 难度曲线规则(改谱遵守)
### 4.4 改谱规则
1. 乐句长度:曲目一 2→4 音、曲目二 2→4 音、曲目三 4→6 音;同曲单调不减
(终曲加花不计入)。
2. 反拍(x.5):曲目一**只允许出现在加花**;曲目二第 5 句起、普通乐句每句至多
1 个反拍音;曲目三不限。加花为八分音符基底,不受此限。
3. 同一分区连续出现 ≤2 次(同区双击是合法手法,三连击禁止——盲玩极限)。
4. 同一乐句内分区种类 ≥2(纯底鼓句仅允许曲目一句 1)。
5. 相邻反拍连续位禁止左列↔右列大跳(`BL↔BR` 相邻反拍只允许曲目三)——
声像跳变过快在耳机上易混(旧作 §3.5-6 同款)。
6. 加花尾音恒为 TR 吊镲(收束感 + 吊镲长尾盖住应答结束)。
1. 曲目一 2→4 音、曲目二 2→4、曲目三 4→6;加花不计。
2. 反拍:曲目一只允许加花;曲目二第 5 句起普通句每句 ≤1;曲目三不限。
3. 同一分区连续 ≤2(两连合法,三连禁止)。
4. 乐句内分区种类 ≥2(仅曲目一句 1 允许纯底鼓)。
5. 相邻反拍禁止左列↔右列大跳(仅曲目三允许 BL↔BR)。
6. 加花尾音恒 TR。
7. **谱面不得要求「双击手势」**;只需连续 tap。
---
## 5. 时值判定与计分评级
## 5. 判定与计分(数字保留)
```js
const PERFECT = 0.15; // Perfect 窗口 ±150ms(live round1 §13 校准)
const GOOD = 0.35; // Good 窗口 ±350ms(吸收蓝牙延迟)
const LOOKAHEAD = 0.6; // 调度前瞻(秒)
const PUMP_MS = 40; // 调度泵周期
const PERFECT = 0.15;
const GOOD = 0.35;
const LOOKAHEAD = 0.6;
const PUMP_MS = 40;
```
- **判定时刻 = 触摸/按键时刻**:immediateTap 按下即发 tap(零补偿);
键盘数字键 keydown 即发(零补偿)。旧作的 TAP_DELAY/SWIPE_DELAY/DOUBLE_DELAY
补偿与旧引擎闩锁、双击序曲撤销、超时复活窗**整体退役**(没有滑动/双击音符了)。
- 归属:同分区最近未判定音符 |dt|≤GOOD → 出手即播该区音色(pan 随行),
Perfect 叠 `sfx/dm_accent`;|dt|≤GOOD×0.5 的异分区音符被敲 = 判 Miss
(错鼓);窗口外 = 自由演奏(出声音不判定)。
- **序曲 tap 治理**(immediateTap 代价,旧作账簿简化版):每次按下先发 tap
(出声+可认领),转滑动/长按/触摸取消时引擎补 `tapcancel` → 撤销该 tap 的
认领(声音收不回当 ghost 音,判定/统计必须更正且不计 Miss)。双击不改判
账簿——两下敲击各自独立成立。
- Miss 超时:expected + GOOD + 0.10s 未应答 → 闷镲反馈音
(`sfx/dm_hat` 降调 0.5× 复用,零新资产)+ error 震动。
- 长按 = 死输入:Miss 反馈音 + error 震动,不判定不归属(演奏期长按快进
`setRateBoostEnabled(false)` 豁免照旧)。
- 计分/评级/连击:Perfect=2 / Good=1 / Miss=0;rate = 得分/(2×总音符);
S≥0.95 / A≥0.85 / B≥0.70 / C 其余;16 连击 success 震、乐句全 Perfect
[20,40,20]、结算 S/A success 震,全部沿用旧作。
- 判定时刻 = 触摸/数字键按下(immediateTap 零补偿)。
- 同分区最近未判定音符 \|dt\|≤GOOD → 出该区音;Perfect 叠 accent。
- 异分区音符 \|dt\|≤GOOD×0.5 被敲 = Miss(错鼓)。
- 窗外同区 = 自由演奏(出声不判定)。
- Miss 超时:expected+GOOD+0.10s → 闷镲(`dm_hat` 0.5×)+ `invalid` 回执 + 可选 error 震。**主反馈是声音。**
- 计分/评级/16 连击震/全 Perfect 震:数字沿用;震是补充。
退役:TAP_DELAY / SWIPE_DELAY / DOUBLE_DELAY、双击序曲撤销账簿、超时复活窗、滑动音符。
---
## 6. 教学编排(分区心智模型,设计核心)
## 6. 教学(分区心智,保留四步)
蒙眼玩家如何建立「摸到哪是哪」?分四步,全部零惩罚(教学不计分、不出
Miss 音),单步重教 ≤2 次(第 2 次失败 → 领奏降速 25% 重演一次 → 再败跳过,
旧作 G2 DDA 沿用):
零惩罚,单步重教 ≤2,第 2 次失败降速 25%,再败跳过。
| 步 | 内容 | 机制 |
|---|---|---|
| T0 摸鼓认区 | `tut_map` 讲双轴模型(横三列=左中右声像,竖两行=上亮下沉)→ `tut_probe` 进入**探索模式**:随便摸,敲到哪区就播哪区音色 + 区名(`zone_*` 六句) | 摸到 ≥3 个不同区自动进 T1(每区名每轮教学只播一次,重复摸只出声不报名,防刷屏);**防呆:8s 无触摸自动进 T1**(摸过 1-2 区再宽限 8s),探索是礼物不是门槛——guide.test G2 等无触摸驱动依赖此路径 |
| T1 家区底鼓 | `tut_z_kick`:BC 是家。数拍 1 小节 → 领奏 `1:BC` → 下一小节应答 | 做错:纠正链(§7 归因)→ 重播领奏重试 |
| T2 左右下排 | `tut_z_sides`:BL 军鼓、BR 低嗵。领奏 `1:BL, 3:BR` → 跟打 | 同上 |
| T3 上排三音 | `tut_z_patt`:TL 踩镲、TC 高嗵、TR 吊镲。领奏 `1:TL, 2:TC, 3:TR` → 跟打 | 同上;通过后 `tut_done` 进曲目一 |
- 教学在曲目一无鼓 BGM 上进行;每步领奏前都有 1 小节底鼓数拍(教学期
count-in 不省)。
- 下滑跳过教学(`tut_skip`)保留;教学 Miss 归因真话律沿用(没出手报
「这拍没接」gd_miss_none,不谎报早晚)。
- 加花预告:`tut_fill`「最后有加花,跟着我连打。」在**曲目一终曲加花领奏
前 1 小节**播,每账号一次。
---
## 7. 连击/错拍反馈
| 时机 | 反馈 |
| 步 | 内容 |
|---|---|
| 出手敲对 | 该区音色(pan 随行)+ Perfect 叠 accent |
| 敲错区(异分区音符半窗内被敲) | 该区音色照样出声(自由演奏语义)+ 闷镲 Miss 音 + error 震 + 播报实际敲中的区名(`zone_*`)+ `retry_wrong`「敲错鼓了,再听一遍。」(教学期)/ gd_miss 归因(正片,连 2 Miss 一次) |
| 早/晚(窗内同区但超 Perfect) | 不出错音(Good 照播),教学期事后归因 `retry_early`/`retry_late` |
| 没出手 | 音符超时闷镲 + error 震;教学归因 gd_miss_none「这拍没接」 |
| 16 连击 / 乐句全 Perfect / 解锁 / S·A 结算 | success 震 / [20,40,20] / success+[20,40,20] / success(沿用旧作) |
| 曲目过半 | `mid_song`(ducking 到 1/4 的旧作工艺沿用) |
| T0 摸鼓 | 双轴模型一句 → 探索:敲哪区出哪音+区名。摸 ≥3 区进 T1;**8s 无触摸自动进 T1** |
| T1 家区 | 数拍 → 领奏 `1:BC` → 应答 |
| T2 左右下 | `1:BL, 3:BR` |
| T3 上排 | `1:TL, 2:TC, 3:TR` → `tut_done` → 曲目一 |
教学 Miss 归因真话。下滑跳过教学仅阶段 2 以后;新手回访者用开场「点一下开始」跳过。`tut_fill` 在曲目一终曲加花前 1 小节,每账号一次。
---
## 8. 语音配额(拥挤控制)
## 7. 反馈与语音配额(保留数字)
打击乐密度高,语音是稀缺通道。全表 49 id 双性别(41 新 + 8 向导沿用),
运行时配额:
1. **应答小节内零语音**(含 guide 插播——idle-only 语义天然保证)。
2. 教学纠正:每次失败至多 1 条纠正链(归因 1 句 + 重听 1 句),同一步不叠加。
3. 正片:连 2 Miss 才播 gd_miss,**同曲一次**;mid_song 每曲一次;
加花预告 tut_fill 每账号一次。
4. 探索模式区名播报每区每轮教学一次(重复摸只出声)。
5. 结算链 = grade + [new_best_combo] + [unlock] + restart,无其他插话;
结算 BGM 看门狗按链长缩放(旧作 P1 修复沿用)。
6. 语音风暴保险丝(引擎 3s/5 次)是上届,游戏层不得绕过它连播同 id。
应答小节内零语音。教学失败至多 1 条纠正链。正片连 2 Miss 才 `gd_miss`,同曲一次。探索区名每区每轮一次。结算链 = grade + [new_best_combo] + [unlock] + restart。引擎 3s/5 次保险丝不得绕过。
---
## 9. 音频资产规格
## 8. 资产规格(保留)
### 9.1 打击乐 sfx(程序合成,tools/gen_rhythm_syn.py 新建)
7 件 one-shot:`inst_kick(沿用旧 id)/ dm_snare / dm_hat / dm_tomhi / dm_crash /
dm_tomlo / dm_accent`。QC 红线(gen_cipher_syn.py 同款):去直流 + 首尾 8ms cos² 淡化 +
编码后解码回读真峰值 ≤ -3dBFS(MDCT 过冲坑,超限自动降档重编)。起音必须硬
(盲玩对拍靠起音);kick/snare/hat ≤0.3s,tom ≤0.4s,crash ≤1.3s,accent ≤0.1s。
### 9.2 BGM(3 首无鼓伴奏,Mureka ≤$0.30)
| 曲 | BPM | prompt 特征 |
|---|---|---|
| track90《启程》 | 90 | bright optimistic pop rock backing, bass guitar + strummed acoustic + light synth pads, **no drums no percussion**, steady 90 bpm, A minor |
| track105《追风》 | 105 | driving energetic pop backing, punchy bass + rhythmic guitar chops + synth, **no drums no percussion**, steady 105 bpm, A minor |
| track120《星爆》 | 120 | high energy synth rock backing, aggressive bass + arpeggio synth + power chords, **no drums no percussion**, steady 120 bpm, A minor |
- `bpm_precise.py` 实测:|偏差|>0.5 BPM 则 atempo 拉伸到标称(R3 track90 先例),
成品复测记录台账。
- 后处理:强 onset 起点 → 整数小节裁剪(时长为 4 拍整数倍)→ 末 2 拍与开头
acrossfade(tri) 无缝循环 → loudnorm I=-20/TP=-2/LRA=11 → 44.1kHz → ogg q3 + m4a 128k。
- 旧 track90/105/120(含鼓)删除;`bgm/menu`(Mureka 状态曲,无鼓问题不影响
玩法——menu pad 不与演奏同响)保留沿用。
### 9.3 语音
见 24-rhythm-voice-ids.md:41 新 id 双性别(Qwen3-TTS,flock GPU),
gen_voice_dual.py rhythm 段全量替换;`--check`(时长/峰值/两性离群)+
`--convert`(ogg q3 + m4a 128k)。废弃 id(tut_1/tut_2/tut_3/retry_gesture
及旧 welcome/rules/retry_* 文案)的音频文件随 convert 后清理删除。
打击乐 7 件程序合成,QC:去直流、8ms 淡化、真峰值 ≤−3 dBFS。BGM 三首 prompt 仍要求 **no drums no percussion**,90/105/120 BPM,A minor,`bpm_precise.py` 误差 >0.5 则 atempo。`bgm/menu` 保留。旧含鼓 track 删除。
---
## 10. 进度存储
## 9. 进度
沿用旧作 schema 1 结构(`{tutorialDone, unlocked, grades, bestCombo, summary}`),
key `soundgame.rhythm.progress` 不变;PUT `/api/progress` 带 summary
(`《曲名》X级 · 已解锁N/3首` 取最高评级曲目)。解锁规则不变(B 级解锁下一首)。
结算手势:单击=再来一遍(同曲)、右滑=下一首(已解锁)、下滑=回大厅,不变。
key `soundgame.rhythm.progress`,schema 1:`tutorialDone, unlocked, grades, bestCombo, summary`(`《曲名》X级 · 已解锁N/3首`)。B 级解锁下一首。
结算:单击=同曲;右滑=下一首(未解锁播 `song_locked`;song3 右滑播 restart);阶段 2 下滑=菜单。
---
## 11. 断言清单(web/rhythm/test/playtest.mjs 覆盖全集)
## 10. 断言清单(修订)
> 13 号文档 §4 rhythm 段是旧玩法的断言,已被本文取代但**不回改**(13 号
> 文档冻结);本节是 24 号文档的断言明细,按 §编号可被测试与评审引用。
A1–A5、A7–A5、A8–A25 原编号保留,交互口径改:
- **A1 分区映射**:`zoneOf` 3×2 九宫边界正确(角落/边界点逐一断言);payload
缺失兜底 BC。
- **A2 判定窗**:|dt|≤0.15 → Perfect(+accent 叠加);0.15<|dt|≤0.35 → Good;
|dt|>0.35 同区出手 = 自由演奏(出声不判定、不判 Miss)。
- **A3 错鼓 Miss**:异分区音符 GOOD 半窗内被敲 → Miss(闷镲 + error 震 +
combo 清零;区名播报限教学期——正片错音的归因就是那一声敲错的音色本身,不再叠语音(配额)。
- **A4 超时 Miss**:expected+0.45s 无应答 → Miss(GOOD 0.35 + 放宽 0.10)。
- **A5 序曲 tap 撤销**:immediateTap 按下认领 → 引擎 tapcancel → 认领撤销
且不计 Miss(统计回滚)。
- **A6 双击=两连击**:300ms 内同区两 tap + doubletap → 两次敲击各自独立
(可认领两个相邻音符/一次认领一次自由演奏),无撤销、无崩溃。
- **A7 教学流程**:T0 探索摸 3 区(区名每区一次)→ T1-T3 跟打通过 →
`tutorialDone`;全程零 Miss 音、零计分。
- **A8 教学重教**:单步失败 2 次 → 降速 25% 重演(gd_slow,每教学局 1 次)→
再败跳过(retry_skip)不卡死;归因真话律(没出手 → gd_miss_none)。
- **A9 数拍**:曲目开始先排 4 记底鼓 count-in(1 小节)再进句 1 领奏;
网格对齐 BGM t0。
- **A10 菜单**:main 中下滑**无条件**呼菜单(无 down 音符折中);暂停冻结
(泵停、BGM 断点);恢复后 cue.expected/_bgmT0 平移量 = 暂停时长 +
count-in 小节;guide full 档恢复先播 gd_count(每曲限 1 次)。
- **A11 加花**:fill 句领奏为八分密集 + 尾音 TR;应答窗口与普句同规则;
曲目一终曲加花前 1 小节播 tut_fill(每账号一次)。
- **A12 结算评级**:全 Perfect → S + `grades.song1==='s'` + `unlocked===2` +
unlock_song2 播报;B 级也解锁;C 级不解锁。rate 公式与阈值逐档断言。
- **A13 结算导航**:单击=同曲重开、右滑=下一首(未解锁播 song_locked)、
song3 末位右滑播 restart 不播 song_locked(旧作裁定①回归)。
- **A14 连击里程碑**:16 连击 success 震、乐句全 Perfect [20,40,20]。
- **A15 mid_song**:曲中点播报且领奏 duck 到 1/4、播完恢复。
- **A16 进度同步**:PUT body 含 summary/grades/unlocked/bestCombo;B 级提升
才覆写 grades。
- **A17 语音配额**:应答小节内零 voice 调度;gd_miss 同曲一次;探索区名
每区一次(转写级断言放 transcribe 剧本核)。
- **A18 键盘平权**:Digit1-6 注入 = 对应区命中(零延迟判定);空格 tap
(元素中心坐标)→ BC;选曲态数字键无效。
- **A19 guide 接入**:select=menu / main=battle / end=settle 状态签名同点;
首个应答窗口前 1 小节 gd_answer+turn。
- **A20 资产完整**:check_all_assets.py rhythm 段全绿(含新 sfx/bgm/voice 双性别)。
- **A21 旁白跳过**:intro/tut(引导语音)/select/end 语音播放中单击=跳句(12 号
文档 §1.2);main 演奏期单击是演奏不跳。
- **A22 全局**:启动遮罩前无音频;无未捕获异常;settings.voice 切换下一句生效;
bgm=0 时不加载 BGM。
- **A23(R1 F3)** 教学没出手失败的纠正链 retry_listen 只播一次(不二连复读)。
- **A24(R1 F1)** 曲尾 3s 内连 2 Miss 不插播 gd_miss(结算链不被顶断)。
- **A25(R1 F2)** 探针三连摸的三个区名全部播完不被掐断,教学句 queue 其后
(进阶延迟 150ms 起步:引擎入队判定在 await 加载之前,同步连发会误判)。
- **A6**:新手期 300ms 内同区两 `tap` = 两次独立敲击(可认领两音),**不产生 `doubletap` 手势、无撤销账簿**。阶段 3 若引擎仍发 `doubletap`,游戏层当第二次 `tap`。
- **A10 菜单**:main 中阶段 2 后下滑呼菜单;**单击确认当前项,不是双击确认**;恢复后平移量 = 暂停 + count-in;`gd_count` 每曲 1 次。
- **A21**:演奏期单击是演奏;讲解期单击跳句,400ms 内再点=`busy`。
- 增补 **A26**:新手谱面/教程不依赖 `g==='doubletap'`。
- 增补 **A27**:演奏期 Floor=ACTION,无等待声;选曲 YOUR_TURN 有等待声或 menu pad。
- 增补 **A28**:无声操作 0;Miss 有闷镲或回执。
其余 A1 分区、A2 窗、A3 错鼓、A4 超时、A5 tapcancel、A9 数拍、A11 加花、A12–A16 评级进度、A17 配额、A18 键盘、A19 guide 签名、A20 资产、A22 全局、A23–A25 R1 修复:**保留**。
13 号旧 §4 手势模式断言不回改;鼓手验收以本节 + 13 号 §5 为准。
---
## 12. 资产预算与台账
## 11. 预算台账(保留)
- 语音:49 id × 2 性别 = 98 文件(Qwen3-TTS,flock .gpu.lock,零 API 费)。
- 打击乐:7 件程序合成(零成本)。
- BGM:3 任务 × $0.03,每首至多 1 次重提 → 上限 $0.18(预算线 $0.30,
累计 $1.26/$2.00 → 本轮后 ≤$1.44+$0.18=$1.56 上限)。台账追加
tools/mureka_log.md。
- 开发/测试/转写:零成本(复用 harness)。
语音 49 id×2;打击乐 7 件;BGM 上限见原文工艺。开发零 API 费。
---
## 12. 废止清单
| 废止 | 改为 |
|---|---|
| 双击手势=一种音符;双击确认菜单 | 只有分区点按;菜单左右滑+单击 |
| 新手 `doubleTapWindow` 300ms | 0;两下两击 |
| 演奏期等待声 / 帮助插播 | ACTION;应答内零语音 |
| 暂停恢复无 count-in | 4 拍 count-in |
| Miss 只震 | 闷镲 + 回执 + 真话归因 |
| 帮助用上滑(与上排鼓冲突) | 非演奏期长按 |
| 开场长规则 | T0 先摸鼓 |
| 03 号手势乐器谱作为现行施工 | 以本文为准 |
引擎坐标第二参、分区布局、时值、曲目表、合成与 BGM 工艺:**全部保留**。