Files
Astral/tools/chart_achievement/README.md
T

154 lines
7.0 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.
# chart_achievement —— 舞萌DX 谱面达成率 / DX 分计算器
给定谱面(`.ma2`)和判定数,算出**达成率**与 **DX 分**;反过来,给定目标达成率,
倒算出所有能打出该达成率的判定组合。
纯本地、纯标准库(不需要联网、不写任何游戏文件)。
## 怎么跑
```powershell
cd tools/chart_achievement
python chart_achievement.py --help
python chart_achievement.py # 不给参数 = 交互向导
```
## 谱面从哪来(二选一)
| 参数 | 说明 |
| --- | --- |
| `--chart <路径>` | 直接给 `.ma2` 文件,如 `.../music/music000008/000008_03.ma2` |
| `--game <目录> --music-id <ID>` | 给游戏目录(`Sinmai_Data`、`StreamingAssets`、`A000`、`music` 都能认),按 `musicid` 自动找谱面 |
### 难度(--level)
难度用**游戏里规定的数字**表示,也就是谱面文件名后缀 `_00` ~ `_04` 的那个数字
(对应游戏主数据 `musicDifficulty/MusicDifficulty00000X` 的 `<id>`):
| 数字 | 难度 | 谱面文件名 |
| --- | --- | --- |
| 0 | BASIC | `<id>_00.ma2` |
| 1 | ADVANCED | `<id>_01.ma2` |
| 2 | EXPERT | `<id>_02.ma2` |
| 3 | MASTER | `<id>_03.ma2` |
| 4 | RE:MASTER | `<id>_04.ma2` |
`--level 0` ~ `--level 4` 或 `--level BASIC` / `ADVANCED` / `EXPERT` / `MASTER` / `RE:MASTER`
(大小写随意;`--difficulty` 是同一个参数的别名)都行,**默认 3(MASTER)**;
不给时会在 stderr 提示用了默认值。
注意:这里的「等级」指**谱面难度**。谱面的数值等级(如 `12+`)是另一回事,
由 `Music.xml` 自动读出,只用于显示。
## 正向:已知判定数 → 达成率 + DX 分
```powershell
python chart_achievement.py --chart "<...>\000008_03.ma2" --cp 290 --perfect 8 --great 3 --good 1
python chart_achievement.py --game "<...>\Sinmai_Data" --music-id 8 --level 3 --cp 290 --perfect 8 --great 3 --good 1 # 3 = MASTER
```
- `--cp` / `--perfect` / `--great` / `--good` 都给个数,`--miss` 留空时自动补足
(`miss = 总音符数 - CP - Perfect - Great - Good`)。
- 输出达成率的**区间**(下界 ~ 上界)和「按比例分摊」的参考值:
只给聚合判定数时,达成率取决于这些 Great/Good/Miss **落在哪些音符上**
(落在绝赞上扣得多、落在 Tap 上扣得少),所以是一个区间而不是一个数。
下界 = 扣分尽量落在高权重音符上,上界 = 尽量落在低权重音符上。
- DX 分是确定的:`3×CP + 2×Perfect + 1×Great`。
## 反向:已知达成率 → 判定组合 + DX 分
```powershell
python chart_achievement.py --game "D:\SDGB2026\Package\Sinmai_Data" --music-id 8 --level 3 --achievement 100.75 # 3 = MASTER
python chart_achievement.py --game "D:\SDGB2026\Package\Sinmai_Data" --music-id 8 --level 2 --achievement 99 --mode range # 2 = EXPERT
```
两种模式(`--mode`):
| 模式 | 含义 | 什么时候用 |
| --- | --- | --- |
| `exact`(默认) | 只列**真的能打出**该达成率的 (Miss, Good, Great) 组合,每组都做过严格验证 | 数据可信、想知道精确答案 |
| `range` | 列达成率**区间覆盖**该值的组合(更快、更宽松) | 判定数只是估算 / 想快速看个大概 |
`--limit N` 限制最多输出多少组(默认 200)。`--json` 输出 JSON。
输出里的 `CP+P` 是「Critical Perfect + Perfect 的总数」;`CP 范围` 是其中
CP 的物理可能范围(Perfect 只能落在绝赞音符上,所以 CP 不能少于
`CP+P - 绝赞音符数`)。CP 越多 DX 分越高,所以 DX 分也是这个范围。
### 精确模式的限制
精确验证用一个「坏音符背包 DP」,代价随坏音符个数增长,所以:
- 只严格验证 `Miss+Good+Great ≤ 12` 的组合;
- 坏音符更多的组合不验证(输出里会提示「坏音符超过 12 个的候选组合没有验证」);
- 若候选组合全都超过 12 个坏音符(例如 1000+ 绝赞的异常谱面),会自动退回区间模式并说明原因;
- DP 有 8 秒时间预算,超时也退回区间模式。
## 计分模型(与游戏一致)
达成率按**音符权重**加权,不是按个数平均:
```
达成率 = 100 × Σ(单音符得分) / 理论分W + Σ(绝赞奖励分) / 绝赞理论奖励分B
```
- 理论分 W = Σ 每个计分音符的权重(Tap/Star/Touch 500、Hold 系 1000、Slide 系 1500、Break 系 2500)
- 绝赞理论奖励分 B = 100 × 绝赞音符个数(Break / Break(长按判定) / BreakSlide / EXBreak)
- 满分 = **101.0000%**(谱面有绝赞)/ **100.0000%**(无绝赞)
- 接续滑条节点(`CNSI_` 等 ConnectSlide)**不计分、不计入总数**
- DX 分 = `3×CP + 2×Perfect + 1×Great`(与音符类型无关)
### 各类音符的失分 / 奖励分
| 音符组(权重) | CP | Perfect | Great | Good | Miss |
| --- | --- | --- | --- | --- | --- |
| Tap / Star / Touch(500) | 0 | 0 | 100 | 250 | 500 |
| Hold 系(1000) | 0 | 0 | 200 | 500 | 1000 |
| Slide 系(1500) | 0 | 0 | 300 | 750 | 1500 |
| Break(2500,分档) | 0 | 0 | 500 / 1000 / 1250 | 1500 | 2500 |
| Break 长按判定(2500) | 0 | 0 | 500 | 1500 | 2500 |
| BreakSlide(2500,分档) | 0 | 0 | 500 / 1000 / 1250 | 1500 | 2500 |
| EX(500) | 0 | — | — | — | 500 |
| EXBreak(2500) | 0 | — | — | — | 2500 |
(表里是「失分」,即 `权重 - 得分`;CP/Perfect 的失分都是 0,所以
**除绝赞外,Critical Perfect 与 Perfect 加的达成率完全一样**。)
绝赞音符额外有奖励分:
| 判定 | 奖励分 |
| --- | --- |
| Critical Perfect | 100 |
| Perfect | 75(FastPerfect/LatePerfect)/ 50(Perfect2nd) |
| Great | 40 |
| Good | 30 |
| Miss | 0 |
这就是绝赞必须区分 CP / Perfect 的原因:普通分一样,但奖励分不同
(CP 100、Perfect 75 或 50)。
### 音符组的划分依据(反编译源码)
- 走长按结算的 Break(`BRHLD`)判定档位更少(Great 只有一档、Perfect 只有 75 档),所以单独成组。
- 滑条系(`BRSI_`/`BRSCL`…)走 SlideOut 判定表,只有 Great 三档、没有 Perfect 档。
- EX 音符(`XTP`/`XST`/`BXTAP`/`BXSTR`,即 ExTap/ExStar/ExBreakTap/ExBreakStar)
走 ExTap 判定表,非 Critical 的窗口宽度为 0,所以**只能判 Critical 或 Miss**。
- `XHO`/`BXHLD`(ExHold/ExBreakHold)头部用 ExTap 表但结果还要过长按结算,
等价于普通 Hold / Break(长按判定)。
## 文件
| 文件 | 内容 |
| --- | --- |
| `ma2.py` | `.ma2` 解析、指令→音符组映射、`Music.xml` 读取、路径拼接 |
| `score.py` | 计分模型、正向求解、反向求解(区间 / 精确) |
| `chart_achievement.py` | 命令行界面 / 交互向导 |
## 已知局限
- 只按谱面文本计算,不涉及游戏运行时的实际判定时机分布(例如绝赞 Perfect 是 75 还是 50 档,
正向计算里只能给区间)。
- 精确反向只验证坏音符数 ≤ 12 的组合(见上文)。
- 若 `.ma2` 里的 `T_NUM_ALL` 与解析出的计分音符数不一致,会在输出里给警告
(全部 5419 个游戏谱面实测一致)。