API 目前处于 Early Preview 阶段, 可能存在诸多不完善与错误之处,欢迎加群交流反馈:加入官方交流群(1073434398)
CHUNITHM
chunithm 的 Profile 与 Score 格式:好友码身份、player.native 的原生块、原样存储的成绩对象、BEST30/SELECTION10/NEW20 三个分组桶,以及它和官方 50 张平均口径的差距。
本页是 chunithm(中二节奏)在 RankHub 上的字段级格式说明。它属于「本地游戏」族
(chunithm / orzmic):成绩是上游原始对象,服务端不做结构归一、也不代算任何评分。
从落雪(LXNS)迁过来?
通用差异(三种鉴权、success/code 包络、端点、限速)先看
如果你熟悉落雪查分器 API 的开发;本页只讲 chunithm 的字段与桶。
机器可读版本
GET /v1/games/chunithm/format 返回与本页一致的格式数据(同一份字段表)。
文档可能滞后,以端点返回为准。
chunithm 的评分由上游给
chunithm 的单曲 rating 与档案 rating 都不是服务端算的:单曲值随成绩原样入库,
总分用上游快照里算好的值。所以文档里的公式只解释「这个数是什么」,
不能据此断言 RankHub 会复算它。
Profile 格式
身份字段(建档用)
| 字段 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
friendCode | string | 是 | 街机侧好友码(不是 LXNS 的 id);上游原生是整数,wire 上为字符串 | "123456789012345" |
- 建档走
POST /v1/games/chunithm/profiles:只落库。 - 该好友码已有未解绑档案 ⇒
409。 chunithm没有externalId这个身份字段(那是 orzmic 的):对外身份就是friendCode。name也是建档必填项,但它是统一字段(player.name)而不是身份键,所以不在上表;10 个游戏一致。
统一字段(player)
| 字段 | 类型 | 可空 | 单位 | 说明 |
|---|---|---|---|---|
name | string | 可空 | — | 显示名(取 player.name,回落 userName / playerName / friend_code) |
rating | number | 可空 | Rating(小数,展示 2 位、向下截断) | 总分;取快照 summary.rating,回落 player.rating |
原生字段(player.native)
player.native 是三个顶层键,其中两个是嵌套的原生 JSON(不摊平):
| 键 | 类型 | 单位 / 取值 | 语义 | 可空 |
|---|---|---|---|---|
externalId | string | — | 档案的身份列原值(好友码的字符串形式) | 可空 |
player | object | 嵌套对象 | 原生玩家块原样(常见内部键:name、level、rating、rating_possession、friend_code、class_emblem、over_power、over_power_progress、reborn_count、currency、total_play_count、trophy、character、name_plate、map_icon、upload_time) | 可空 |
summary | object | 嵌套对象 | 快照构建时算出的汇总(score_count、rating、best30_total、selection10_total、new20_total),三个 *_total 都是组内 rating 之和 | 可空 |
player/summary的内部键属于上游数据,不逐个声明为nativeFields: 它们是嵌套 JSON,键集合可能随上游版本变化。对外契约只有这三个顶层键。
Score 格式
成绩键与比较值
| 项 | 值 | 说明 |
|---|---|---|
keyFields | songId / difficulty | 一条成绩 = 曲目 + 难度;写入方必须给出 difficulty,缺了会把多个难度并成一键 |
compareField | score | 「只升不降」按分数比较(0~1,010,000),更差不改并进 ignored |
ratingField | rating | 单曲 Rating,上游/调用方给的原值;服务端不代算 |
- 推成绩时
scores[].bucket决定落哪个桶:best30/selection10/new20,缺省best30。 songId的读取有候选键:song_id/id/music_id/songId;score取score,回落achievement。
本页
keyFields/compareField/ratingField里的名字都是 v1 wire 字段名(GameScoreDto的顶层字段);游戏原生列名写在说明列。
统一字段
chunithm 会填的统一字段:
| 字段 | 类型 | 单位 / 量纲 | 可空 | 说明 |
|---|---|---|---|---|
scoreId | string | — | 否 | 成绩行 id |
songId | string | — | 否 | 曲目 id(候选键命中值;都不命中时回落行 id) |
songName | string | — | 可空 | 曲名(title,回落 song_name) |
difficulty | string | 难度标识 | 可空 | 难度(difficulty,回落 difficulty_name;原生 level_index 0~5) |
achievements | number | 与 score 同量纲(0~1,010,000,不是百分比) | 可空 | 达成率位;本地游戏没有独立达成率字段,填的是 score |
score | number | 0~1,010,000 | 可空 | 分数(比较值) |
rating | number | 小数 | 可空 | 单曲 Rating(上游算好的值) |
rank | string | 14 值评级枚举(sssp~d) | 可空 | 评级 |
playTime | string | — | 恒 null | 本地游戏不映射游玩时间(原生 play_time 留在 native 里) |
恒为 null 的字段:type(非 maimai)、levelIndex(本地族没有下标)、level、fc / fs
(full_combo / full_chain 只在 native 里)、dxScore / dxStar。
原生字段(score.native)
chunithm 的 score.native 没有固定字段集合:它是整份原生成绩对象原样透传,
键名可能随上游版本变化。
所以本页不列 score.native 字段表(nativeFields 为空数组;注意这与 maimai 的「native 恒 null」不同——
chunithm 的 native 有值,只是键集合不固定)。
成绩对象的候选键
服务端会按下面这些候选键读取(命中即用):
| 用途 | 候选键 | 落到统一字段 |
|---|---|---|
| 曲目 id | song_id / id / music_id / songId | songId |
| 曲名 | title / song_name | songName |
| 难度 | difficulty / difficulty_name(原生成绩对象里更常见的是 level_index 0~5) | difficulty |
| 分数 | score / achievement | score 与 achievements |
| 单曲评分 | rating | rating |
| 评级 | rank | rank |
原生成绩对象里还可能带:over_power、clear、full_combo(AJC/AJ/FC)、full_chain
(fullchain=铂、fullchain2=金,字面与语义顺序不一致)、play_time、upload_time、
last_played_time、level。这些都只在 native 里,不进统一字段。
评分语义与量纲
单曲 Rating 不是乘法,而是「定数 + 分数档加成」的分段线性函数(现行 VERSE 口径):
| 分数段 | 单曲 Rating |
|---|---|
| ≥ 1,009,000(SSS+) | c + 2.15(封顶) |
| 1,007,500 ~ 1,008,999(SSS) | c + 2.0 + (s - 1,007,500) / 10,000 |
| 1,005,000 ~ 1,007,499(SS+) | c + 1.5 + (s - 1,005,000) / 5,000 |
| 1,000,000 ~ 1,004,999(SS) | c + 1.0 + (s - 1,000,000) / 10,000 |
| 975,000 ~ 999,999(S) | c + (s - 975,000) / 25,000 |
| 925,000 ~ 974,999(AA) | c - 3.0 + ((s - 925,000) / 50,000) × 3 |
| 900,000 ~ 924,999(A) | c - 5.0 + ((s - 900,000) / 25,000) × 2 |
| 800,000 ~ 899,999(BBB) | ((c - 5.0) / 2) × (1 + (s - 800,000) / 100,000) |
| 500,000 ~ 799,999(C~BB) | ((s - 500,000) / 300,000) × ((c - 5.0) / 2) |
| < 500,000(D) | 0 |
s= 分数(0~1,010,000,1,010,000 是 All Justice Critical 理论值);c= 谱面定数。- 单曲上限
c + 2.15(1,009,000 分起不再增长);低分可能算出负数,最终max(0, ·)。 - 精度:单曲值展示向下截断到 2 位小数;总分是否同样截断未见官方说明(未知项)。
- 官方总分口径(VERSE 起):BEST 30 + NEW 20 共 50 张谱面(互不重复)的单曲值算术平均, 分母恒 50;旧制为 BEST 30 + RECENT 10,分母 40。
RankHub 的桶合计是「和」,不是官方的「平均」
buckets[].total 与 totals.total 都是组内 rating 之和(服务端按条相加),
不是官方「50 张平均」的 Rating。两者量级差约 50 倍,不要把 total 当玩家 Rating 展示。
最佳成绩桶
chunithm 的桶直接来自上游快照分组(组名即 key),服务端不重算成员:
| key | label | 成员规则 | totals 键 |
|---|---|---|---|
best30 | BEST30 | 上游 bests 组原样 | total |
selection10 | SELECTION10 | 上游 selections 组原样。官方语义是「Best30 之外的候选成绩」,不是独立评分桶 | total |
new20 | NEW20 | 上游 new_bests 组原样(现版本新曲) | total |
- 三个 key 总是存在(服务端按固定组名逐个输出),但对应数组可能为空。
totals.total=best30+new20两组rating之和(不含selection10),与官方 「50 张平均」口径不同源,也不等于推成绩响应里的rating.before/rating.after(后者把全部组一起求和,含selection10)。buckets[].total= 该桶内rating之和(不是条数)。label是大写形式(BEST30/SELECTION10/NEW20),与key只差大小写。 按key渲染、label只作参考(展示名可自行本地化)。- 桶数与顺序不是契约:以响应里的
buckets数组为准,不要按数组下标。
写入(第三方)
写入只有两种形状:RankHub 格式(上文两张统一字段表里的字段)与游戏原生格式(native)。
共享字段、批量上限与失败语义见写入格式;本节只列 chunithm 真正会读的字段。
建档(POST /v1/games/chunithm/profiles)
player 用上文的身份字段(friendCode 必填)+ name。建档不能带成绩:成绩必须用下面的端点单独推。
推成绩(POST .../profiles/{profileId}/scores)
| 字段 | 类型 | 必给 | 说明 |
|---|---|---|---|
songId | string | number | 支持 | 曲目 id,归一成字符串后作为成绩键的一半 |
difficulty | string | number | 支持 | 难度标识,归一成字符串;缺失时回落 native.difficulty,两者都没有 ⇒ 多难度塌成一条 |
score | number | 支持 | 分数(0~1,010,000 量级);比较值,缺省回落 achievements 再缺省 0 |
rating | number | 支持 | 单曲 Rating,调用方给;缺省落 null ⇒ 桶内改按 score 排序、合计量级会爆 |
bucket | string | 建议 | best30 / selection10 / new20,缺省 best30;只能是这三个固定词汇 |
native | object | 强烈建议 | 上游算好的整份成绩对象,原样存进档案;缺 difficulty 时从它取 |
playTime | string | 不读取 | 本地游戏落库不带时间 |
curl -sS -X POST https://api.rankhub.cc/v1/games/chunithm/profiles/$PROFILE_ID/scores \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"scores": [
{
"songId": "3",
"difficulty": "4",
"score": 1010000,
"rating": 16.15,
"bucket": "best30",
"native": { "id": 3, "level_index": 4, "score": 1010000, "rating": 16.15, "clear": "clear" }
}
]
}'以上为示意数据:字段真实,数值虚构。
原生数据(native)
chunithm 的 native 只用于 profile:player.native 里除身份字段外的键会原样写进档案的玩家块,读回在 player.native.player。
成绩不做 native → 字段的换算(scores[].native 只是原样存储的原始成绩对象)。
可写字段 / 不可改
| 位置 | 可写 |
|---|---|
player.native(profile) | name + 其余任意键(除身份 friend_code):原样进档案玩家块;PATCH .../profiles/{profileId} 的 native 同样生效 |
scores[].native(score) | 无:不做字段换算,仅原样存储(唯一例外:顶层没给 difficulty 时用它当成绩键) |
- 不可改:身份字段
friend_code(与现值不同 ⇒400 common.invalidArgument); 成绩唯一键songId+difficulty—— 本游戏没有 native 换算,这两个只能由顶层字段给。
本游戏的写入规则
- 必给:
songId+difficulty+rating+score;rating尤其重要,缺了合计会用score兜底。 - 比较值是
score:新值不高于现值 ⇒ 不覆盖,回improved: false+ignored(not-improved)。 - 谁算分:服务端不算分(
chunithm属于「调用方给」族)。单曲 Rating 用你给的rating,总分是桶内求和。 - 桶:
bucket只接受best30/selection10/new20(缺省best30);拼错 ⇒400。 - 两处合计口径不同:推成绩响应的
rating.before/after把全部组(含selection10)求和; 读侧totals.total只加best30+new20。两者不能互相校对,也都不是官方 Rating。
从 LXNS 迁移
落雪 API 的通用差异(认证、包络、端点、限速)见如果你熟悉落雪查分器 API 的开发。 这里只保留 chunithm 最容易被迁移写错的一处:Rating 由哪几个桶构成。
Player 字段对照
LXNS Player | RankHub | 位置 |
|---|---|---|
name | player.name | 统一字段 |
rating(float,官方 Rating) | player.rating | 统一字段,取快照里算好的值;与 LXNS 的官方 Rating 可能因口径不同而不同 |
friend_code(int) | friendCode(string) | 建档身份字段 |
level / rating_possession / class_emblem / reborn_count / over_power / over_power_progress / currency / total_currency / total_play_count / trophy / character / name_plate / map_icon | 全部保留在 player.native.player(整份 LXNS Player 原样) | 原生块;不进统一字段 |
upload_time | 无对应 | lastSyncedAt 只表示"RankHub 自己同步过",第三方档案为 null |
Score 字段对照
LXNS Score | RankHub | 说明 |
|---|---|---|
id(int) | songId(string) | WORLD'S END 难度时 LXNS 用 origin_id,迁移时要按此取值 |
song_name | songName | |
level | level | |
level_index(0~5) | difficulty(string) | 0~5:BASIC/ADVANCED/EXPERT/MASTER/ULTIMA/WORLD'S END |
score(int) | score(统一字段) | 比较值;achievements 也等于它 |
rating(float) | rating(统一字段) | 写入必须给;RankHub 不代算 |
rank | rank | 枚举相同 |
clear / full_combo / full_chain / over_power | 只存在于 score.native(原样) | 统一字段没有对应;写侧放进 native |
play_time / upload_time / last_played_time | 读侧 playTime 目前为 null | 中二成绩是"每谱面最佳",没有逐次时间 |
| 整个对象 | score.native | 原样透传(所以迁移时 native 可以直接用 LXNS 的 Score) |
Rating 构成对照(最容易踩)
| LXNS 字段 | RankHub 桶 | 说明 |
|---|---|---|
bests | key="best30" | 旧版本 Best 30(评分对象曲) |
selections | key="selection10" | 候选集:Best30 之外、理论能进 Best30 的成绩;不是独立评分桶 |
new_bests | key="new20" | 当前版本 Best 20(新曲) |
RatingTrend.recents_rating(2026 起) | 没有对应桶 | 迁移时不要把它塞进 new20 |
注意:RankHub 的
totals.total是best30+new20的桶内求和,不是官方「50 张平均」的 Rating; 两者量级不同(见「最佳成绩桶」)。
消费者注意事项
- 桶合计不是玩家 Rating:
buckets[].total/totals.total是「和」;官方是「50 张平均」。 - 两处合计口径不同:
totals.total只加best30+new20,而推成绩响应的rating.before/rating.after把全部组(含selection10)一起求和。 两者不能互相校对,也都不是官方 Rating。 achievements与score同值:本地游戏没有独立达成率字段,两者都填分数 (0~1,010,000)。不要把它当百分比。rating不是服务端算的:它是调用方给的值,服务端只存不算;缺rating时合计会 按score兜底(量级会爆),所以推成绩时务必给rating。difficulty必须显式给:成绩键是songId+difficulty;不传会被并成一键,多个难度塌成一条。playTime恒null:原生play_time留在native里,统一字段不映射。full_combo/full_chain只在native:且fullchain=铂、fullchain2=金, 枚举字面与语义顺序不一致,不要按字面推断高低。score.native没有固定字段集合:键名由数据源决定,读之前先判空、再按候选键取。