Files

9.1 KiB
Raw Permalink Blame History

chart_achievement —— 舞萌DX 谱面达成率 / DX 分计算器

给定谱面(.ma2)和判定数,算出达成率与 DX 分;反过来,给定目标达成率, 倒算出所有能打出该达成率的判定组合。

纯本地、纯标准库(不需要联网、不写任何游戏文件)。

怎么跑

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 自动找谱面

🔎 不知道 musicId / 定数? maimai.lxns.net/songs 可以在线查询谱面信息(曲名、难度、定数等),查到 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 分

达成率是按音符权重加权的(Tap 500 / Hold 1000 / Slide 1500 / Break 2500), 所以有两种给判定数的方式:

① 精确:按音符类别给(推荐,得到确切数值)

python chart_achievement.py --game "<...>\Sinmai_Data" --music-id 8 --level 3 --tap 260/0/3 --break 6   # 3 = MASTER

每个类别写 CP/Perfect/Great/Good/Miss 五个数(/、, 或空格分隔,后面可以省略), 没给满的部分按 CP 算:

参数 类别 权重
--tap Tap / Star / Touch / EX 500
--hold Hold 系 1000
--slide Slide 系 1500
--break 绝赞(Break 系) 2500

绝赞的 Great / Perfect 本身还有档位,需要时可以再细分(不给就按 Great 失分 20%、Perfect 奖励 75 算):

参数 含义
--break-great40 N 绝赞里 N 个 Great 是「失分 40%」档(Great2nd)
--break-great50 N 绝赞里 N 个 Great 是「失分 50%」档(Great3rd)
--break-perfect50 N 绝赞里 N 个 Perfect 是「奖励分 50」档(Perfect2nd)

输出就是一个确切的达成率,并附上失分 / 绝赞奖励分的明细:

判定输入(按音符类别;Break = 绝赞;没给的部分按 CP 算)
  Tap/Star/Touch/EX (500)   263 个:CP 260 · Great 3
  Hold (1000)                14 个:CP 14
  Slide (1500)               19 个:CP 19
  Break (2500)                6 个:CP 6
结果
  达成率      100.8413%  (确切值)
  DX 分       900 / 906  (99.34%)

② 汇总:只给总判定数(得到估计值 + 区间)

python chart_achievement.py --chart "<...>\000008_03.ma2" --cp 290 --perfect 8 --great 3 --good 1

--cp / --perfect / --great / --good 都给总数,--miss 留空时自动补足 (miss = 总音符数 - CP - Perfect - Great - Good)。

这时达成率取决于这些 Great/Good/Miss 落在哪类音符上(落在绝赞上扣得多、落在 Tap 上扣得少), 所以给出的是「按类别比例分摊的估计值」+ 一个区间(下界 = 扣分都落在高权重音符上, 上界 = 都落在低权重音符上)。想要一个确切数值就用上面的 ①。

DX 分在两种方式下都是确定的:3×CP + 2×Perfect + 1×Great。

反向:已知达成率 → 判定组合 + DX 分

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 命令行界面 / 交互向导

已知局限

  • 只按谱面文本计算,不涉及游戏运行时的实际判定时机分布。绝赞的 Great 档位(失分 20% / 40% / 50%) 和 Perfect 档位(奖励 75 / 50)需要自己用 --break-great40 / --break-great50 / --break-perfect50 指定,不给就按 20% / 75 算。
  • 汇总模式(只给总判定数)只能给区间——这不是精度问题,是信息量不够:同样的判定总数, 落在绝赞上和落在 Tap 上扣的分不同。要确切值就按类别给(--tap / --hold / --slide / --break)。
  • 精确反向只验证坏音符数 ≤ 12 的组合(见上文)。
  • 若 .ma2 里的 T_NUM_ALL 与解析出的计分音符数不一致,会在输出里给警告 (全部 5419 个游戏谱面实测一致)。