RankHub
各游戏

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 里至少要给出机台侧的两个标识之一:

字段类型必填说明示例
friendCodestring否(与 userId 至少给一个)机台好友码,15 位数字;上游原生是整数,wire 上统一为字符串"1234567890123"
userIdnumber否(与 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)

字段类型可空单位说明
namestring可空—玩家显示名
ratingnumber可空DX Rating(整数)玩家总分。建档时服务端会置 0 并按成绩重算,返回的一律是存量值

player.rating 是服务端按最佳成绩重算的整数(不是上游 dx_rating 的小数), 与 GET .../bests 的 totals.total 同源;两者在曲库版本漂移时可能不一致(见文末「消费者注意事项」)。

原生字段(player.native)

player.native 是游戏原生格式的玩家块(camelCase);机台原生玩家块里的收藏品(trophy / icon / name_plate / frame)不出现在读侧。

原生键类型单位 / 取值语义可空
userIdnumber—机台侧数值 id(原生 user_id)可空
friendCodestring15 位数字机台好友码(原生 friend_code)可空
courseRanknumber段位 id段位(Gen 3 起不再计入总分,只作展示)可空
classRanknumber阶级 idCLASS 阶级可空
starnumber≥ 0搭档觉醒数可空

机台原生玩家块里的 trophy / icon / name_plate / frame(收藏品) 不出现在 player.native 里。

Score 格式

成绩键与比较值

项值说明
keyFieldssongId / type / levelIndex一条成绩 = 曲目 + 谱面类型 + 难度下标,三者共同唯一
compareFieldachievements「只升不降」按达成率比较;新值不更高 ⇒ 进 ignored(notImproved)
ratingFieldnull服务端算:单曲 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):

字段类型单位 / 量纲可空说明
scoreIdstring—否成绩行 id(排序 tie-break 用)
songIdstring—否曲目 id(数字 id 的字符串形式)
songNamestring—可空曲名
typestringstandard / dx / utage可空谱面类型
levelIndexnumber0~4可空难度下标
levelstring难度标签(如 13+)可空展示用难度
achievementsnumber百分比,0~101.x(BUDDY 谱面到 202)可空比较值:达成率
ratingnumber整数可空单曲 DX Rating(服务端算)
rankstring14 值评级枚举(sssp~d)可空评级(原生 rate)
dxScorenumber0~物量×3可空DX 分数
dxStarnumber0~5可空DX 星级
fcstringapp / ap / fcp / fc可空FULL COMBO 档位
fsstringfsdp / fsd / fsp / fs / sync可空FULL SYNC 档位
playTimestring(RFC3339)—可空游玩时间(上游只到分钟)

恒为 null 的字段:score(maimai 没有「裸分」概念,DX 分在 dxScore)、difficulty (难度在 levelIndex + type)、native(见下)。

原生字段(score.native)

maimai 的 score.native 恒为 null:读侧只给统一字段,不透传原生对象(是 null,不是空对象)。

机台原生成绩另行支持:写入时可在 score item 里带 native(详见「写入(第三方)」),读取侧仍映射成上表字段。

原生字段与统一字段的对应

原生字段统一字段转换
idsongIdString(id)
typetype原样
level_indexlevelIndex原样(整数)
levellevel原样
achievementsachievements原样(float)
dx_ratingrating服务端重算(不看上游这个值)
raterank原样(14 值枚举)
dx_scoredxScore原样
dx_stardxStar原样
fc / fsfc / fs原样,可空
play_time / last_played_timeplayTime归一成 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 返回两个桶(按谱面版本二分):

keylabel成员规则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)

字段类型必给说明
songIdinteger支持曲目数字 id;同一曲的 standard / dx 共用它,靠 type 区分
typestring建议standard / dx / utage,缺省 dx;utage 不进最佳桶
levelIndexinteger建议0~4(BASIC → Re:MASTER),缺省 0(会把成绩写到 BASIC 上)
achievementsnumber支持达成率百分比(硬上界:普通谱 101、BUDDY 谱 202;参与算分时封顶 100.5);比较值
dxScoreinteger可选DX 分,缺省 0;不得超过该谱面「物量 × 3」
dxStarinteger可选0~5,必须与 dxScore 自洽,否则 422
fcstring可选app / ap / fcp / fc;app / ap 要求达成率分别 ≥ 100.5 / 100
fsstring可选fsdp / fsd / fsp / fs / sync
playTimestring可选RFC3339;晚于服务端 5 分钟以上 ⇒ 422
ratingnumber不要给被忽略:单曲 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 键类型取值换算
musicIdinteger机台曲目 id<10000 → standard;10000..99999 → dx 且 songId = musicId - 10000;>=100000 → utage
levelinteger0~4,或 10(宴谱面)难度下标;10 表示 utage
achievementinteger整数百分比 ×10000(1005000 = 100.5%)/10000 得 achievements
deluxscoreMaxinteger≥0dxScore
comboStatusinteger0~40→null,1→fc,2→fcp,3→ap,4→app
syncStatusinteger0~50/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 PlayerRankHub位置
nameplayer.name统一字段
rating(官方 DX Rating)player.rating统一字段,但值由 RankHub 重算,不等于 LXNS 的 rating
friend_code(int,15 位)friendCode(string)建档身份字段(player.friendCode)
course_rankcourseRankplayer.native.courseRank
class_rankclassRankplayer.native.classRank
starstarplayer.native.star
trophy / icon / name_plate / frame不出现在 wirenative 不提供
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 ScoreRankHub GameScoreDto / 写入字段说明
id(int)songId(string)落雪的 id 无 10000 偏移(标准/DX 共用);读回来是字符串,写入时数字或字符串都收
song_namesongName读侧有;写侧没有该字段(服务端按曲库补)
level(如 14+)level展示用
level_index(0~4)levelIndexmaimai 用下标,不用 difficulty
type(standard/dx/utage)type写入缺省 dx
achievements(float)achievements直接映射;比较值也是它
dx_scoredxScore缺省 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_timeplayTime写入可带(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": [] } ] }
LXNSRankHub说明
dx(现版本 Best 15)buckets[key="latest"].items桶键固定 latest
standard(旧版本 Best 35)buckets[key="archive"].items桶键固定 archive
dx_total / standard_totaltotals.latest / totals.archive
两者相加totals.totalRankHub 的 total 是重算值,可能 ≠ LXNS player.rating
dx_selections / standard_selections不暴露候选集不进桶;只用于内部选曲

想按单曲查成绩:LXNS 的 ?song_id=&song_type= 在 RankHub 没有对应查询参数 —— 直接拉 /scores(可按 difficulty 过滤,maimai 的 difficulty 传 levelIndex)后自行筛。

消费者注意事项

  1. achievements 是百分比,不是 0~1:0~101.x,BUDDY 谱面到 202。直接乘除前先读本页。
  2. 不要拿上游 dx_rating 覆盖 rating:maimai 的 rating 是服务端重算的整数; 上游小数精度在计算中被丢弃,两者可能差 1。
  3. score 恒 null:maimai 没有「裸分」,比较与展示请用 achievements / dxScore。
  4. score.native 恒 null:看不到原生 dx_rating / rate 原始值,只能读统一字段。
  5. difficulty 恒 null:难度维度由 type + levelIndex 表达,level 只是展示标签。
  6. totals.total 与 player.rating 同源但不同时刻:曲库版本变化(新曲入库/定数调整)会让 历史档案的展示值漂移;computedAt 是本次计算时刻。
  7. 桶成员可能不足额:新档案或曲库缺失时 latest / archive 会少于 15 / 35 条,不要按 15/35 硬编码。
  8. fc / fs 是档位字符串不是布尔:fc="ap" 表示 AP(不是「已全连」的真假)。

相关

本页内容

官方交流群二维码官方交流群问题反馈 · 版本资讯 · 成绩交流群号 1073434398