API 目前处于 Early Preview 阶段, 可能存在诸多不完善与错误之处,欢迎加群交流反馈:加入官方交流群(1073434398)
maimai
maimai DX 的 Profile 与 Score 格式:建档身份字段、player/score 的统一字段与原生字段、机台原生成绩上传、服务端算分的单曲 DX Rating、当期/往期两个最佳成绩桶。
本页是 maimai(舞萌 DX)在 RankHub 上的字段级格式说明:建档要填什么、player / score
上有哪些统一字段、native 里装着哪些原生字段、机台原生成绩怎么写、单曲评分怎么算、最佳成绩怎么分桶。
机器可读版本
GET /v1/games/maimai/format 返回与本页一致的格式数据(同一份字段表)。
文档可能滞后,以端点返回为准。
maimai 的评分由服务端算
maimai 与 arcaea 是两个服务端自己算单曲评分的游戏:推成绩时 scores[].rating 对你无效,
服务端会用 achievements + 曲库定数重算。其余 8 个游戏的 rating 由调用方给。
Profile 格式
一条 profile = 一个 RankHub 用户的一张 maimai 档案。主键是对外的 profileId(uuid),
不是机台侧的 friendCode / userId。
身份字段(建档用)
建档走 POST /v1/games/maimai/profiles,player 里至少要给出机台侧的两个标识之一:
| 字段 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
friendCode | string | 否(与 userId 至少给一个) | 机台好友码,15 位数字;上游原生是整数,wire 上统一为字符串 | "1234567890123" |
userId | number | 否(与 friendCode 至少给一个) | 机台侧数值 id(user_id) | 10000001 |
- 两个标识必须至少给一个:都不给时该游戏既有服务返回
400(Either user_id or friend_code is required)。 单个字段本身都不是必填,所以格式端点把两者都标required: false——「至少其一」这条约束只在响应notes与本页里表达。 - 重复身份 ⇒
409(该身份已被占用)。 courseRank/classRank/star可以在建档时一起填(缺省0),但它们不是身份, 填错不影响建号成败,只影响player.native的展示值。name也是建档必填项,但它是统一字段(player.name)而不是身份键,所以不在上表;10 个游戏一致。
统一字段(player)
| 字段 | 类型 | 可空 | 单位 | 说明 |
|---|---|---|---|---|
name | string | 可空 | — | 玩家显示名 |
rating | number | 可空 | DX Rating(整数) | 玩家总分。建档时服务端会置 0 并按成绩重算,返回的一律是存量值 |
player.rating 是服务端按最佳成绩重算的整数(不是上游 dx_rating 的小数),
与 GET .../bests 的 totals.total 同源;两者在曲库版本漂移时可能不一致(见文末「消费者注意事项」)。
原生字段(player.native)
player.native 是游戏原生格式的玩家块(camelCase);机台原生玩家块里的收藏品(trophy / icon / name_plate / frame)不出现在读侧。
| 原生键 | 类型 | 单位 / 取值 | 语义 | 可空 |
|---|---|---|---|---|
userId | number | — | 机台侧数值 id(原生 user_id) | 可空 |
friendCode | string | 15 位数字 | 机台好友码(原生 friend_code) | 可空 |
courseRank | number | 段位 id | 段位(Gen 3 起不再计入总分,只作展示) | 可空 |
classRank | number | 阶级 id | CLASS 阶级 | 可空 |
star | number | ≥ 0 | 搭档觉醒数 | 可空 |
机台原生玩家块里的 trophy / icon / name_plate / frame(收藏品)
不出现在 player.native 里。
Score 格式
成绩键与比较值
| 项 | 值 | 说明 |
|---|---|---|
keyFields | songId / type / levelIndex | 一条成绩 = 曲目 + 谱面类型 + 难度下标,三者共同唯一 |
compareField | achievements | 「只升不降」按达成率比较;新值不更高 ⇒ 进 ignored(notImproved) |
ratingField | null | 服务端算:单曲 DX Rating 不来自调用方,也不落独立列 |
type取值standard/dx/utage;utage(宴会场)不进最佳成绩桶。levelIndex是 0~4 的整数:0=BASIC、1=ADVANCED、2=EXPERT、3=MASTER、4=Re:MASTER。- 同一首曲的
standard与dx谱面共用同一个songId,靠type区分——只按songId合并会把两张谱面塌成一条。
本页
keyFields/compareField/ratingField里的名字都是 v1 wire 字段名(GameScoreDto的顶层字段);游戏原生列名写在说明列。
统一字段
maimai 会填的统一字段(其余字段恒 null):
| 字段 | 类型 | 单位 / 量纲 | 可空 | 说明 |
|---|---|---|---|---|
scoreId | string | — | 否 | 成绩行 id(排序 tie-break 用) |
songId | string | — | 否 | 曲目 id(数字 id 的字符串形式) |
songName | string | — | 可空 | 曲名 |
type | string | standard / dx / utage | 可空 | 谱面类型 |
levelIndex | number | 0~4 | 可空 | 难度下标 |
level | string | 难度标签(如 13+) | 可空 | 展示用难度 |
achievements | number | 百分比,0~101.x(BUDDY 谱面到 202) | 可空 | 比较值:达成率 |
rating | number | 整数 | 可空 | 单曲 DX Rating(服务端算) |
rank | string | 14 值评级枚举(sssp~d) | 可空 | 评级(原生 rate) |
dxScore | number | 0~物量×3 | 可空 | DX 分数 |
dxStar | number | 0~5 | 可空 | DX 星级 |
fc | string | app / ap / fcp / fc | 可空 | FULL COMBO 档位 |
fs | string | fsdp / fsd / fsp / fs / sync | 可空 | FULL SYNC 档位 |
playTime | string(RFC3339) | — | 可空 | 游玩时间(上游只到分钟) |
恒为 null 的字段:score(maimai 没有「裸分」概念,DX 分在 dxScore)、difficulty
(难度在 levelIndex + type)、native(见下)。
原生字段(score.native)
maimai 的 score.native 恒为 null:读侧只给统一字段,不透传原生对象(是 null,不是空对象)。
机台原生成绩另行支持:写入时可在 score item 里带 native(详见「写入(第三方)」),读取侧仍映射成上表字段。
原生字段与统一字段的对应
| 原生字段 | 统一字段 | 转换 |
|---|---|---|
id | songId | String(id) |
type | type | 原样 |
level_index | levelIndex | 原样(整数) |
level | level | 原样 |
achievements | achievements | 原样(float) |
dx_rating | rating | 服务端重算(不看上游这个值) |
rate | rank | 原样(14 值枚举) |
dx_score | dxScore | 原样 |
dx_star | dxStar | 原样 |
fc / fs | fc / fs | 原样,可空 |
play_time / last_played_time | playTime | 归一成 RFC3339 |
评分语义与量纲
单曲 DX Rating 由「谱面定数 × 达成率 × 评级系数」决定,结果向下取整成整数:
single_rating = floor( level_value × min(achievements, 100.5) × coefficient / 100 )| 量 | 量纲 | 说明 |
|---|---|---|
achievements | 百分比 0~101.x | 参与计算的达成率封顶 100.5;BUDDY(双人)谱面理论上限 202 |
level_value(谱面定数) | 1.0~15.0,步进 0.1 | 来自曲库,不在成绩里 |
single_rating | 整数 | 单曲值上限约为「定数 × 100.5 × 22.4 / 100」,定数 15 时约 33.7 |
player.rating(总分) | 整数 | 当期前 15 + 往期前 35 的单曲值之和,无官方硬上限 |
- 我方系数表是14 档(按评级档一刀切),与社区口径(低分档细分、评级跃迁前的临界档、
Gen 3.5 的 AP+
+1)存在已知差异;这些差异只记录、不在本轮修,所以对外不要承诺「与机台完全一致」。
最佳成绩桶
GET /v1/games/maimai/profiles/{profileId}/bests 返回两个桶(按谱面版本二分):
| key | label | 成员规则 | totals 键 |
|---|---|---|---|
latest | 当期 | 谱面版本 ≥ 曲库最新版本的谱面,按单曲 rating 降序取前 15 条 | total / latest / archive |
archive | 往期 | 其余谱面,按单曲 rating 降序取前 35 条 | 同上 |
totals.total= 两桶之和;totals.latest/totals.archive分别是两桶的合计(不是条数)。- 「最新版本」是数据驱动的(取曲库最大版本号),不是写死的版本常量。
- 桶内排序按
dx_rating降序 →achievements降序 →dx_score降序;成员可能不足额(新号)。 utage谱面、曲库里查不到的谱面、disabled曲目一律不进桶。
写入(第三方)
写入只有两种形状:RankHub 格式(上文两张统一字段表里的字段)与游戏原生格式(native)。
共享字段、批量上限与失败语义见写入格式;本节只列 maimai 真正会读的字段。
建档(POST /v1/games/maimai/profiles)
player 用上文的身份字段(friendCode / userId 至少一个)+ name,可另给 courseRank / classRank / star(缺省 0)。
可以同时带 scores[]:先建号、再落成绩,返回的 player.rating 已是重算后的值。身份重复 ⇒ 409。
推成绩(POST .../profiles/{profileId}/scores)
| 字段 | 类型 | 必给 | 说明 |
|---|---|---|---|
songId | integer | 支持 | 曲目数字 id;同一曲的 standard / dx 共用它,靠 type 区分 |
type | string | 建议 | standard / dx / utage,缺省 dx;utage 不进最佳桶 |
levelIndex | integer | 建议 | 0~4(BASIC → Re:MASTER),缺省 0(会把成绩写到 BASIC 上) |
achievements | number | 支持 | 达成率百分比(硬上界:普通谱 101、BUDDY 谱 202;参与算分时封顶 100.5);比较值 |
dxScore | integer | 可选 | DX 分,缺省 0;不得超过该谱面「物量 × 3」 |
dxStar | integer | 可选 | 0~5,必须与 dxScore 自洽,否则 422 |
fc | string | 可选 | app / ap / fcp / fc;app / ap 要求达成率分别 ≥ 100.5 / 100 |
fs | string | 可选 | fsdp / fsd / fsp / fs / sync |
playTime | string | 可选 | RFC3339;晚于服务端 5 分钟以上 ⇒ 422 |
rating | number | 不要给 | 被忽略:单曲 DX Rating 由服务端算(maimai / arcaea 属于这一类) |
curl -sS -X POST https://api.rankhub.cc/v1/games/maimai/profiles/$PROFILE_ID/scores \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"scores": [
{
"songId": 1234,
"type": "dx",
"levelIndex": 3,
"achievements": 100.5125,
"dxScore": 2500,
"dxStar": 5,
"fc": "ap",
"fs": "fs",
"playTime": "2026-10-20T12:00:00Z"
}
]
}'以上为示意数据:字段真实,数值虚构。
原生数据(native)
native 是游戏原生格式:与 canonical 字段同时出现时 native 优先(游戏原生对象里没有的字段仍可走顶层)。
机台原生成绩(scores[].native)
native 键 | 类型 | 取值 | 换算 |
|---|---|---|---|
musicId | integer | 机台曲目 id | <10000 → standard;10000..99999 → dx 且 songId = musicId - 10000;>=100000 → utage |
level | integer | 0~4,或 10(宴谱面) | 难度下标;10 表示 utage |
achievement | integer | 整数百分比 ×10000(1005000 = 100.5%) | /10000 得 achievements |
deluxscoreMax | integer | ≥0 | dxScore |
comboStatus | integer | 0~4 | 0→null,1→fc,2→fcp,3→ap,4→app |
syncStatus | integer | 0~5 | 0/5→null,1→fs,2→fsp,3→fsd,4→fsdp |
- 越界 / 非整数(
achievement不在0..1010000(普通谱)或0..2020000(宴谱/BUDDY)、level不在0..4且不为10)⇒400 common.invalidArgument。 dxStar与playTime不是原生键:dxStar在 native 路径恒写0,playTime走顶层字段。
可写字段
| 位置 | 可写(player.native 键 → 存储位) |
|---|---|
player.native(profile) | name、course_rank、class_rank、star、trophy、icon、name_plate、frame |
profile 的这些字段也可以用在 PATCH /v1/games/maimai/profiles/{profileId} 的 native 里,改完即时生效。
展示字段用 snake_case(course_rank / class_rank / star / name_plate),与 canonical 建档字段的 camelCase 不同。
不可改
- 身份字段:
user_id/friend_code—— 与现值不同 ⇒400 common.invalidArgument(native.friendCode cannot be changed (identity field))。 - 成绩唯一键:
musicId+level决定这行属于哪张谱面,native里必须给;换了就是另一条成绩。
本游戏的写入规则
- 必给:
songId+achievements。type/levelIndex缺省dx/0—— 不报错,但会写到错的谱面上。 - 比较值是
achievements(达成率),不是dxScore:新值不更高 ⇒ 当前成绩不变。 - 谁算分:单曲 DX Rating 与总分都由服务端算(
maimai/arcaea属于这一类),scores[].rating被忽略。 - 建档身份:
friendCode与userId至少给一个;同一身份重复建档 ⇒409。
从 LXNS 迁移
落雪 API 的认证、包络、端点、限速与通用坑见如果你熟悉落雪查分器 API 的开发, 这一节只保留 maimai 专有的字段对照(Player / Score / Best 50)。
Player 字段对照
LXNS Player | RankHub | 位置 |
|---|---|---|
name | player.name | 统一字段 |
rating(官方 DX Rating) | player.rating | 统一字段,但值由 RankHub 重算,不等于 LXNS 的 rating |
friend_code(int,15 位) | friendCode(string) | 建档身份字段(player.friendCode) |
course_rank | courseRank | player.native.courseRank |
class_rank | classRank | player.native.classRank |
star | star | player.native.star |
trophy / icon / name_plate / frame | 不出现在 wire | native 不提供 |
upload_time | 无对应字段 | lastSyncedAt 只表示"RankHub 自己同步过",第三方建的档案恒为 null |
// LXNS 返回的 Player(节选)
{ "name": "Player", "rating": 15234, "friend_code": 123456789012345,
"course_rank": 0, "class_rank": 0, "star": 12 }
// RankHub 建档请求体(等价映射)
{ "player": { "name": "Player", "friendCode": "123456789012345",
"courseRank": 0, "classRank": 0, "star": 12 } }Score 字段对照
LXNS Score | RankHub GameScoreDto / 写入字段 | 说明 |
|---|---|---|
id(int) | songId(string) | 落雪的 id 无 10000 偏移(标准/DX 共用);读回来是字符串,写入时数字或字符串都收 |
song_name | songName | 读侧有;写侧没有该字段(服务端按曲库补) |
level(如 14+) | level | 展示用 |
level_index(0~4) | levelIndex | maimai 用下标,不用 difficulty |
type(standard/dx/utage) | type | 写入缺省 dx |
achievements(float) | achievements | 直接映射;比较值也是它 |
dx_score | dxScore | 缺省 0 |
dx_star(0~5) | dxStar | 缺省 0 |
fc(app/ap/fcp/fc) | fc | 枚举相同 |
fs(fsdp/fsd/fsp/fs/sync) | fs | 枚举相同 |
rate(sssp…d) | rank | 注意字段名:RankHub 叫 rank,枚举值相同 |
dx_rating(float,官方单曲 DX Rating) | rating(读侧) | 写入会被忽略;服务端自己算 |
play_time | playTime | 写入可带(RFC3339) |
upload_time / last_played_time | 无对应 | playTime 之外的时间不在 v1 里 |
Best 50 结构对照
LXNS 的 GET .../bests 返回两个总分 + 两个列表(外加两个 selection 列表):
{ "standard_total": 8000, "dx_total": 7234,
"standard": [ /* Score[] */ ], "dx": [ /* Score[] */ ],
"standard_selections": [ /* ... */ ], "dx_selections": [ /* ... */ ] }RankHub 把它压成统一桶形状:
{ "game": "maimai", "computedAt": "…",
"totals": { "total": 15234, "latest": 7234, "archive": 8000 },
"buckets": [ { "key": "latest", "label": "当期", "total": 7234, "items": [] },
{ "key": "archive", "label": "往期", "total": 8000, "items": [] } ] }| LXNS | RankHub | 说明 |
|---|---|---|
dx(现版本 Best 15) | buckets[key="latest"].items | 桶键固定 latest |
standard(旧版本 Best 35) | buckets[key="archive"].items | 桶键固定 archive |
dx_total / standard_total | totals.latest / totals.archive | |
| 两者相加 | totals.total | RankHub 的 total 是重算值,可能 ≠ LXNS player.rating |
dx_selections / standard_selections | 不暴露 | 候选集不进桶;只用于内部选曲 |
想按单曲查成绩:LXNS 的
?song_id=&song_type=在 RankHub 没有对应查询参数 —— 直接拉/scores(可按difficulty过滤,maimai 的difficulty传levelIndex)后自行筛。
消费者注意事项
achievements是百分比,不是 0~1:0~101.x,BUDDY 谱面到202。直接乘除前先读本页。- 不要拿上游
dx_rating覆盖rating:maimai的rating是服务端重算的整数; 上游小数精度在计算中被丢弃,两者可能差 1。 score恒null:maimai 没有「裸分」,比较与展示请用achievements/dxScore。score.native恒null:看不到原生dx_rating/rate原始值,只能读统一字段。difficulty恒null:难度维度由type+levelIndex表达,level只是展示标签。totals.total与player.rating同源但不同时刻:曲库版本变化(新曲入库/定数调整)会让 历史档案的展示值漂移;computedAt是本次计算时刻。- 桶成员可能不足额:新档案或曲库缺失时
latest/archive会少于 15 / 35 条,不要按 15/35 硬编码。 fc/fs是档位字符串不是布尔:fc="ap"表示 AP(不是「已全连」的真假)。