RankHub
各游戏

API 目前处于 Early Preview 阶段, 可能存在诸多不完善与错误之处,欢迎加群交流反馈:加入官方交流群(1073434398)

Orzmic

orzmic 的 Rating 口径、Best 30 桶,以及本地文档存储下 profile / score 的统一字段与上游原样 native 字段。

orzmic 属于本地文档存储族:整份档案是一个 JSON 文档,player.native 与 score.native 装的都是 上游原样对象,服务端不计算任何 rating。官方算法从未公开,社区与逆向实现之间存在冲突。

机器可读版本

本页字段表与服务端 GET /v1/games/orzmic/format 一一对应(profile.identityFields / profile.fields / profile.nativeFields、score.keyFields / score.compareField / score.ratingField / score.fields / score.nativeFields / score.buckets)。文档可能滞后,以端点返回为准。

Profile 格式

一张 orzmic 档案 = 一个 RankHub 用户在 orzmic 上的一张卡,整份文档存在一行 JSON 里。对外主键是 profileId(uuid)。

身份字段(建档用)

字段类型必填说明示例
externalIdstring ≤128是外部标识。orzmic 没有官方账号体系,这个值由调用方与用户约定"orz-sample-0001"
  • 缺失 ⇒ 400;服务端只落库;syncCapable 恒为 false;
  • player.name 必填(展示名,可来自原生 name / userName / playerName / nickname)。

统一字段(player)

字段类型说明
namestring | null显示名。依次尝试原生 name → userName → playerName → friend_code
ratingnumber | null个人 Rating。取档案汇总 summary.rating,退回 player.rating —— 写入方给的值,服务端不代算

rating 由写入方给,服务端不算

本平台对 orzmic 的评分语义是 provided:服务端不做任何评分计算,桶和头部数字都由写入方给, 所以 player.rating 的可信度等于「谁写的这条数据」。 原生存档里个人 Rating 是字符串 Rat(末位还带评级字母),解析成数字时要把末位字符剥掉,否则会得到 null。

原生字段(player.native)

本地游戏的 player.native 是三层嵌套,不是扁平字段:

字段类型说明
externalIdstring档案身份列(与身份字段同源)
playerobject | null上游玩家块原样:Ver / Name / Coin / CharID / Rat / SPRat / B30Scores …
summaryobject | null上游汇总块原样(rating 等由写入方填)

上游玩家块里值得注意的原生键:

原生键类型说明
Vernumber存档格式 / 游戏版本号
Namestring玩家名
Ratstring个人 Rating,形如 "12.34X":末位字符是评级字母,直接 Number() 会失败
SPRatstring第二个 rating,语义未确认
B30Scoresstring[]最佳成绩列表,每项是逗号分隔的 5 字段字符串(不是对象数组)

Score 格式

成绩键与比较值

项值
成绩键 keyFieldssongId + difficulty
比较字段 compareFieldscore(本次得分)
单曲评分字段 ratingFieldrating(原生 native.rating,写入方给;服务端不代算)
tie-break无(单个比较值)

曲目键名不统一,服务端是「猜」出来的

本地游戏的成绩是 Record<string, unknown>,曲目键在上游有多个候选名: song_id / songId / id / music_id,服务端按这个顺序逐个尝试;difficulty 候选是 difficulty / difficulty_name。 所以同一个游戏的数据里可能混着不同键名(导入路径写 music_id,推成绩路径补 song_id)。 写数据时请明确用 song_id + difficulty,别再引入新拼法。

统一字段

字段类型说明
scoreIdstring成绩行 id
songIdstring曲目 id(按上面候选键名解析)
songNamestring | null曲名。依次尝试 title → song_name
difficultystring | null难度(原样字符串化)
achievementsnumber | null与 score 同源 —— 本地游戏没有独立的「达成率」字段
scorenumber | null本次得分
ratingnumber | null该谱面的评分值,写入方给(本地导入的存档通常为 null)
rankstring | null评级(通常缺失)
nativeobject | null整份原生成绩对象,见下表

恒为 null(原生没有或无映射):type、levelIndex、level、dxScore、dxStar、fc、fs、playTime。

achievements 与 score 是同一个数

本地游戏族没有「达成率」这个归一字段,所以 achievements 直接复用 score。 客户端不要把它当百分比用(orzmic 的满分是 1,000,000 + 物量,不是 1,000,000)。

原生字段(score.native)

端点声明 nativeFields 为空数组

score.native 就是整份上游成绩对象(原样透传),键名由数据源/写入方决定, 所以格式端点对这个游戏不声明固定的 nativeFields(空数组)。 空数组 ≠ native 为空:数据在,只是没有可承诺的固定键集合。下面列出常见键供参考。

键说明
曲目键song_id / id / music_id / songId 之一(导入路径常见 music_id,推成绩路径补 song_id)
difficulty难度。存档编码是 1=Special / 2=Hard / 3=Normal / 4=Easy —— 数值不是顺序,1 才是最难的
score本次得分,区间 0 ~ 1,000,000 + 物量
rating单曲 Rating(只有推成绩时由调用方给出才会有;本地导入的存档不带)
play_status进度状态:>=1 通关 / >=2 ID / >=3 PD(+ 变体被合并)
ex_score金/银标记:0=金、1=银、其他=无

除上面的常见键外,score.native 里可能还有调用方自己塞进来的任意键(原样存)。 本页只承诺上面几个稳定键的语义;其余字段的增减不视为破坏性变更。

评分语义与量纲

单曲 Rating(社区 / 逆向口径,无官方文档):单曲 Rating = 定数 + 分数补偿(mod) + 金/银加成

  • 定数(Constant):社区值域约 1.0 ~ 16.x,精度 0.1;
  • mod 是分段函数,随 Score 变化。两个来源在分段边界上互相冲突,仅两处在 Score = 1,000,000 收敛:
Score 区间社区口径(mod)逆向口径(mod)
= 1,000,000 + 物量+2.1+2.2
∈ (1,000,000, 1,000,000 + 物量)+2.0+2.1
= 1,000,000需由截断段推出+2.0
∈ [980,000, 1,000,000)+1.4(980,000 处)+1.0(980,000 处),向 1,000,000 线性升到 +2.0
∈ [950,000, 980,000)+0.7 → +1.4 线性+0.4 → +1.0 线性
< 900,000 等低段未取到完整片段逐段递减,< 700,000 直接 0
  • 金/银加成:ex_score = 0(金)为 +0.05(Score ≥ 1,000,000 时 +0.10); ex_score = 1(银)为 +0.02(Score ≥ 1,000,000 时社区说 +0.05、逆向实现算 +0.04);
  • 单曲聚合:一首歌只贡献一个分数 —— 取它所有难度里最高的那个,四舍五入保留 3 位小数;
  • 个人 Rating:社区现行口径是 floor2( Σ 单曲 Rating 前 30 / 30 )(向下取整保留 2 位小数); 历史上长期是 b20,改成 b30 的确切版本未知。原生存档字段名叫 B30Scores,是「30 条」的旁证。

服务端不实现这套公式

上面这些只用于解释数字从哪来。本平台服务端不做 orzmic 的 rating 计算:单曲值来自写入方, totals.total 是桶内评分之和(不是前 30 的平均)。所以别指望用端点返回值反推出官方 Rating。

最佳成绩桶

GET /v1/games/orzmic/profiles/{profileId}/bests 的桶来自上游快照分组,不是服务端重新选桶:

keylabel成员规则totalsKeys
best30BEST30游戏自己算好的前 30 条(成员关系由游戏决定,服务端不重选);推成绩时 bucket 的固定词汇只有 best30total
  • label 由桶键大写生成(best30 → BEST30);
  • 上游快照没有分组时会退化成单桶(key 为 best,label 取快照给的标签),保证 buckets 非空;
  • totals.total:桶内各条 rating(缺失则 score)之和。它不是原生个人 Rating (那是前 N 名的平均)—— 两者量纲完全不同(和 vs 均值)。

写入(第三方)

请求体形状、idempotencyKey、批量上限、428/403 等通用规则见写入格式; 这里只写 orzmic 真正特殊的部分。

两种写形状

  • RankHub 格式:直接给统一字段(songId / difficulty / rating / score / bucket …);
  • 游戏原生格式(native):只用于成绩 —— scores[].native 里放整份上游成绩对象, 服务端挑出必要字段后把整份对象原样存下来,与顶层同名字段同时出现时 native 优先; 不合法 ⇒ 400 common.invalidArgument(message 指明键)。

本游戏的写入规则

  • 必给:songId + difficulty + rating + score + native。 服务端不做任何评分计算(rating 一律由调用方给 —— maimai / arcaea 之外的游戏都是这样,只有那两者服务端算), rating 不给就是 null(桶内排序会退化成按 score)。
  • 比较值是 score;同一 (songId, difficulty) 只有分数更高才会覆盖。
  • difficulty 原样字符串化:1=Special / 2=Hard / 3=Normal / 4=Easy,数值不是顺序,1 最难。
  • bucket 是固定词汇:只接受 best30,拼错直接 400 orzmic best bucket must be one of best30(不是默默归桶)。
  • 不会重算玩家头部:推送只补成绩,不改 player / summary(player.rating 需要你自己维护或重新建档)。
  • 409:本地游戏一个用户只能有一份未解绑档案 ⇒ 重复建档返回 orzmic profile already exists for this identity (重复调用请改用推成绩)。
  • 通用上界(写入守卫开启时生效,默认 off):score 越界 400,rating 越界或 playTime 晚于现在 1 小时 422。

原生数据(native)

对 orzmic 来说,native 就是那条上游成绩 JSON 本身:服务端按既有推送契约挑出必要字段, 并把整份对象原样存进成绩的 native(读侧 score.native 就是它)。

身份字段 externalId 必须给

orzmic 没有官方账号体系,身份是调用方约定的 externalId:建档时仍要给它(顶层 player.externalId, 或 native 里的 externalId / external_id)。player.native 只贡献展示名与其余玩家字段。

native 键类型换算
song_id / songId / id / music_idstring / number,必填其一songId(按这个顺序取第一个非空)
difficultystring,必填difficulty(原样字符串化)
score / achievement / achievementsnumber,必填其一score(比较值)
ratingnumber ≥0rating(可选;不给不代算)
bucketstringbucket(可选,缺省 best30;目前只接受 best30)
其余键—原样落库,读侧 score.native 可完整取回
  • 可写字段(native,除身份与唯一键外全部):profile 侧 name / userName / playerName / nickname (→ name)+ 其余任意键(建档或 PATCH /v1/games/orzmic/profiles/{profileId} 的 native, 其余键原样进 playerJson,读回 player.native.player.<键>);score 侧除 song_id + difficulty 外的全部键。
  • 不可改:身份 externalId —— PATCH 的 native 里给了不同的 externalId / external_id ⇒ 400, message = native.externalId cannot be changed (identity field);成绩唯一键 song_id + difficulty 顶层与 native 都给时必须一致,不一致以 native 为准(会写到另一张谱面上)。

示例(示意数据) —— 一条推成绩请求(原生格式):

curl -sS -X POST https://api.rankhub.cc/v1/games/orzmic/profiles/$PROFILE_ID/scores \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "scores": [
      {
        "native": {
          "music_id": 10023,
          "difficulty": "2",
          "score": 1004820,
          "rating": 15.83,
          "play_status": 2,
          "ex_score": 0
        }
      }
    ]
  }'

消费者注意事项

  1. rating 不是服务端算的:来自写入方/上游,服务端只排序、只落库。要拿「官方 Rating」得自己按社区公式算,或让写入方带上。
  2. totals.total 是桶内求和,不是原生个人 Rating:原生是前 N 名的平均,这里是和,差 N 倍。
  3. achievements 与 score 同源,且 orzmic 满分是 1,000,000 + 物量,score 会大于 1,000,000 —— 当百分比用会错。
  4. 难度编码不是顺序:1 = Special、2 = Hard、3 = Normal、4 = Easy,1 是最难的,直接当数组下标会全错。
  5. 曲目键名有 4 个候选(song_id / id / music_id / songId),服务端按顺序猜;同一档案里可能混着两种拼法。
  6. bucket 是固定词汇:只接受 best30;拼错会 400(不是默默丢进 best30)。
  7. playTime 恒为 null,rank 通常也是 null。
  8. 桶的成员关系由游戏决定:社区公式不重新选桶,本平台也不重选(只在推成绩时按评分降序截断到 30)。
  9. 没有同步能力:syncCapable 恒 false,数据只能靠建档后的推成绩或重新导入维护。
  10. 推成绩时 native 原样存:服务端把它与 song_id / difficulty / score / rating 合并写进成绩存储, 读回的 native 是「你的 native + 这几个补齐键」。

相关

本页内容

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