API 目前处于 Early Preview 阶段, 可能存在诸多不完善与错误之处,欢迎加群交流反馈:加入官方交流群(1073434398)
Notanote
notanote 的 Nrk 评分体系、Best 31 桶规则,以及 profile / score 的统一字段与原生字段(native)。
notanote 的原生评分叫 Nrk:单曲值由准确率而非分数决定,总评是 Best 31 的加权平均。 公式由官方公布,但总评的分母存在口径分歧,本页把事实写清、不下结论。
机器可读版本
本页字段表与服务端 GET /v1/games/notanote/format 一一对应(profile.identityFields /
profile.fields / profile.nativeFields、score.keyFields / score.compareField /
score.ratingField / score.fields / score.nativeFields / score.buckets)。文档可能滞后,以端点返回为准。
Profile 格式
一张 notanote 档案 = 一个 RankHub 用户在 notanote 上的一张卡。对外主键是 profileId(uuid),不是 notanote 的 uid。
身份字段(建档用)
POST /v1/games/notanote/profiles 的 player 里必须带这个字段:
| 字段 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
notanoteUid | string | 是 | 游戏侧账号 id。纯数字串(^\d{1,32}$)、且须是 > 0 的安全整数;对外按字符串透传,不做 Number 化 | "1008642" |
- 缺失/形状不对 ⇒
400(文案见写入格式 §4);服务端只落库,不校验上游存在性; player.name也是必填(展示名),但它属于下面的统一字段,不是身份字段。
统一字段(player)
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | null | 显示名,建档必填(可来自原生 PlayerName) |
rating | number | null | 总评 Nrk,取档案汇总里服务端算好的值,四舍五入到 3 位小数 |
player.rating 是「算出来的」
notanote 的原生存档只有成绩,没有 Nrk 字段;player.rating 是服务端派生值,分母口径见评分语义与量纲。
原生字段(player.native)
挑选透传(不是整份原生对象),键名是 wire 上的实际键名:
| 字段 | 类型 | 说明 |
|---|---|---|
uid | number | null | 玩家数字 id |
avatarId | string | null | 当前选中头像 id(原生 avatar_id) |
candy | string | null | 糖果。原生是字符串,存档里的 Candy 是数字,别混用 |
playedTime | number | null | 累计游玩时长(summary.played_time);单位未确认 |
没透出的原生字段:avatar_url、types、is_banned。
Score 格式
成绩键与比较值
| 项 | 值 |
|---|---|
成绩键 keyFields | songId + difficulty(wire 顶层只有这两个) |
| 完整写侧键 | 还含 score.native.source(common / fanmade)与 score.native.color |
比较字段 compareField | score(写侧比较列是服务端按判定重算的 calculated_score) |
单曲评分字段 ratingField | rating(原生列 nrk) |
| tie-break | 无(单个比较值) |
- 唯一键含来源与颜色:同一首歌同一难度,在
common(官方谱)/fanmade(自制谱)之间, 或不同轨道颜色(Red/White/Blue/Green/Yellow/Black)之间,是不同的成绩, 不要只按songId+difficulty合并; - 这两个字段不在 wire 顶层,在
score.native里;只按顶层字段拼 key 会把来源与颜色合并掉。
比较值不在响应里
compareField 写的是 score,而写侧实际比较的是存储列 calculated_score;
wire 上的 score 投影的是上游 highest_score。判定分布不完美时两者并不相等 ——
要复算就按下面的公式算,或读 score.native 里的判定计数。
统一字段
| 字段 | 类型 | 说明 |
|---|---|---|
scoreId | string | 成绩行 id(稳定排序 tie-break) |
songId | string | 曲目 id(可含空格/中文/下划线) |
songName | string | null | 曲名;曲库认不出时为 null |
difficulty | string | null | 难度:TL / SY / NT(没有数字下标,也没有 13+ 这类标签) |
achievements | number | null | 准确率,量纲 0~1(不是 0~100) |
score | number | null | 上游最高分,0 ~ 1,000,000 |
rating | number | null | 单曲 Nrk(原生列 nrk) |
native | object | null | 原生字段,见下表 |
恒为 null(原生没有或无映射):type、levelIndex、level、rank、dxScore、dxStar、fc、fs、playTime。
谱面身份由
songId+difficulty加上score.native里的source/color共同决定; 只按顶层两个字段做去重会把不同来源/颜色的成绩并成一条。
原生字段(score.native)
| 字段 | 类型 | 说明 |
|---|---|---|
chartKey | string | 谱面键:{songId}_{color}_{difficulty},如 The Fire_White_TL |
color | string | null | 轨道颜色(唯一键的一段) |
source | string | null | common(官方谱)/ fanmade(自制谱)(唯一键的一段) |
constant | number | null | 谱面定数(来自曲库;认不出曲目时为 null) |
noteCount | number | null | 物量 |
xperfectCount | number | null | XPerfect 判定数(官方分数/准确率公式里没有这一档) |
perfectCount / goodCount / badCount / missCount | number | null | 各判定数 |
combo | number | null | 本次最大连击 |
评分语义与量纲
单曲 Nrk(官方公布):
单曲 Rank =
(e^(Acc − 0.5) − 1) / (e^0.5 − 1) × (定数 + 5) , Acc ∈ [0.5, 1]
0 , Acc < 0.5| 符号 | 含义 | 量纲 |
|---|---|---|
Acc | 本局准确率 | 0~1 |
定数 | 谱面 chart constant(曲库给出的字符串数字,如 "15.6") | 小数 |
| 结果 | 单曲 Rank | 无上限概念;0 ~ 定数 + 5 |
Acc = 1⇒ 单曲 Rank =定数 + 5(上限);Acc < 0.5⇒0;- 准确率决定 Nrk,分数不决定:Good 判定只按
40%~90%计入 Acc,但按满值计入分数; - 同一张谱面反复提交时,只有
calculated_score更高才会覆盖当前成绩。
分数(官方公布):分数 = 1,000,000 × (Perfect数 + Good数) / 物量
—— Bad / Miss 不扣分、只扣 Acc,所以「分数高」与「Nrk 高」是两件事。
总评(官方公布):
Nrk = Σ(Rᵢ × ωᵢ) / Σωᵢ , i = 1..31
ωᵢ = 1 − 0.02 × (i − 1)Rᵢ= 单曲 Rank 降序第 i 位;同一首歌多个难度只保留最高那一条;- 分母分歧(只记事实):官方写
Σωᵢ,31 条齐全时为 21.7;社区日文 wiki 写/22, 本服务实现也硬编码/22。满 31 条时官方口径高约 1.38%,不足 31 条时差距更大; - 精度:三个公式官方都没有舍入说明;本服务总评四舍五入到 3 位小数,单曲值原样保留浮点;
- 桶内准入:只统计
source === 'common'且曲库能认出的谱面。官方只写了「同曲多难度取最高」, 没有写排除自制谱 —— 这条差异会影响 Nrk 高低。 - 同一首歌的多个难度:单曲 Rank 按难度各自计算,进总评时同曲只取最高的那一条(不重复计入)。
最佳成绩桶
GET /v1/games/notanote/profiles/{profileId}/bests 固定返回一个桶:
key | label | 成员规则 | totalsKeys |
|---|---|---|---|
best31 | Best 31 | 按曲目去重(同曲多难度只留最高单曲 Nrk,只统计官方谱)→ 降序取前 31 → 按 ωᵢ = 1 − 0.02(i−1) 加权平均 | nrk、bestCount |
totals.nrk:总评(四舍五入 3 位);totals.bestCount:前 31 里实际有几条(可能< 31);buckets[0].total:桶内各条rating的和,不是总评 Nrk(总评是加权平均)。
写入(第三方)
请求体形状、idempotencyKey、批量上限、428/403 等通用规则见写入格式;
这里只写 notanote 真正特殊的部分。
两种写形状
- RankHub 格式:直接给统一字段(
songId/difficulty/accuracy/rating…); - 游戏原生格式(
native):player.native/scores[].native里放原生形状,服务端按唯一一套换算归一, 与顶层同名字段同时出现时native优先;不合法 ⇒400 common.invalidArgument(message指明键)。
本游戏的写入规则
- 必给:
songId+difficulty(缺失直接400)+accuracy+rating。 后两条缺失不报错,但会写出「准确率 0 / Nrk 0」的错误成绩。 accuracy量纲是 0~1(写>1会被当成百分数自动除 100,最后夹到0~1),写错量纲会直接算错 Nrk。- 比较值是
score,但写侧比较的是calculated_score:推成绩时它由accuracy × 1,000,000得出,accuracy = 0时才用你给的score。所以想「变好」,本质是把accuracy抬高。 - 谁算分:单曲
rating(Nrk)由调用方给(maimai / arcaea 之外的游戏都是调用方给,只有那两者服务端算); 总评 Nrk(Best 31)由服务端重算并回写档案。 - 唯一键:来源 +
songId+ 颜色 +difficulty,见成绩键与比较值。 - 可选字段:
constant(曲库命中时以曲库为准)、color(缺省取曲库颜色,再退回Unknown)、chartKey、combo;playTime不读取(原生没有时间字段)。 - 409:身份已被别的 RankHub 用户绑定,或该用户已有另一张 notanote 档案(重复建档前请先查
GET .../profiles)。 - 通用上界(写入守卫开启时生效,默认
off):score越界400,rating/constant/accuracy越界或playTime晚于现在 1 小时422。
原生数据(native)
建档:原生存档里只有玩家名 —— PlayerName → name,其余忽略。
❗ 存档里没有 uid,notanoteUid 仍必须用顶层字段给。
成绩:原生成绩是 { "<songId>_<color>_<difficulty>": 原始值 } 的 map,单条推送要自带 key。
native 键 | 类型 | 换算 |
|---|---|---|
chartKey | string,必填 | 形如 <songId>_<color>_<difficulty>(从右往左切三段)→ 谱面键 + songId / color / difficulty |
HighestScore | 整数 0~1000000 | score |
Combo | 整数 0~100000 | combo |
source | common / fanmade | 谱面来源(唯一键的一段) |
可写字段(native,除身份与唯一键外全部):
| 位置 | 可写键 | 落库 / 读回 |
|---|---|---|
profile(建档或 PATCH /v1/games/notanote/profiles/{profileId} 的 native) | PlayerName、avatar_id、candy、played_time | name → player.name;其余读回 player.native.avatarId / candy / playedTime |
score(scores[].native) | HighestScore、Combo、source | 对应 score / combo / 来源 |
不可改:身份 notanoteUid(原生 uid,native 里没有这个键,PATCH 入参也没有身份字段 ⇒ 无入口);
成绩唯一键(chartKey 决定谱面)—— 顶层与 native 都给时必须一致,不一致以 native 为准,会写到另一张谱面上。
示例(示意数据) —— 一条推成绩请求(native 与顶层混用):
curl -sS -X POST https://api.rankhub.cc/v1/games/notanote/profiles/$PROFILE_ID/scores \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"scores": [
{
"native": { "chartKey": "The Fire_White_TL", "HighestScore": 998000, "Combo": 842, "source": "common" },
"accuracy": 0.9925,
"rating": 18.34
}
]
}'消费者注意事项
achievements是 0~1,不是 0~100、也不是 maimai 的 0~101。跨游戏渲染必须先按游戏分支。rating(单曲 Nrk)依赖曲库定数:曲库认不出该谱面时为0;player.rating是总评派生值。- 比较值不回传:写侧比较
calculated_score,响应里的score是上游highest_score,两者可能不同。 - 总评口径未统一:分母
21.7与22的差异会让满 31 条时差约 1.38%,与其它站点对不上属预期。 rank/fc/fs恒为null:官方有评级阈值表但未实现映射;连击要从score.native.combo与noteCount自己判断。playTime恒为null:原生没有游玩时间字段。playedTime单位未确认:可能是秒也可能是分钟,展示前不要硬编码换算。