fix: calculate chart scores with song formulas and hidden note weights
Publish GitHub Pages output / publish (push) Successful in 54s
Publish GitHub Pages output / publish (push) Successful in 54s
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# 코로네 Expert 원본 SUS 분모 계산
|
||||
|
||||
2026-09-06, 공개 에셋 카탈로그 revision 70에서 `chart_m0341_expert.sus`를 확보했다. SHA-256은 `b6523da612971b3c8816a8fbb5d86d14d569b259aae84387f2818dd31944d969`이며 서비스 채보의 원본 해시와 일치한다.
|
||||
|
||||
## 집계
|
||||
|
||||
| 종류 | 개수 | 1개 가중치 | 합 |
|
||||
|---|---:|---:|---:|
|
||||
| 일반 | 464 | 1000 | 464000 |
|
||||
| 독립 플릭 | 85 | 1050 | 89250 |
|
||||
| 홀드 시작 | 109 | 1000 | 109000 |
|
||||
| 홀드 끝 | 83 | 1000 | 83000 |
|
||||
| 홀드 끝 플릭 | 26 | 1000 | 26000 |
|
||||
| 보이는 릴레이 | 40 | 100 | 4000 |
|
||||
| 홀드 중간 판정 | 151 | 100 | 15100 |
|
||||
| 비가시 릴레이 | 105 | 100 | 10500 |
|
||||
| 베지어 제어점 | 0 | 100 | 0 |
|
||||
| **전체 분모** | | | **800850** |
|
||||
|
||||
득점 판정 958개의 합은 790350이다. 비가시 릴레이 105개를 분모에만 더한다. 일반 노트=1 단위로는 **800.85**다.
|
||||
|
||||
공개 [SUS 파서](https://github.com/UntitledCharts/sonolus-level-converters/blob/main/sonolus_converters/holodori_sus/loader.py)의 종류 구분과 압축 토큰 규칙을 대조했다. family 3의 kind 4/5를 베지어/비가시 릴레이로 집계한다. `0xNN`은 base62 빈 칸 압축이며 노트가 아니다. 같은 채널이 여러 줄로 나와도 모두 유지한다. 생성되는 홀드 중간 판정은 SUS 헤더의 카테고리별 개수를 사용한다. 따라서 이 도구는 전체 득점 타임라인을 복원하는 파서가 아니다.
|
||||
|
||||
## 검증
|
||||
|
||||
- 기존 실측 대상 18개 채보 + 코로네 Easy/Normal/Hard = **알려진 분모 21개 전부 일치**.
|
||||
- 코로네 Hard: 득점 합 495550 + 비가시 48×100 = **500350**.
|
||||
- 실제 엔진의 기존 일반 PERFECT 실측 19건은 계속 모두 일치한다.
|
||||
- 이번 Expert 분모는 원본 구조와 검증된 집계법으로 산출한 값이다. 코로네 Expert의 신규 게임 실측값은 아직 없다.
|
||||
|
||||
SUS 헤더의 `SCORE_LEVEL`은 26이지만, 현재 공개 마스터 및 서비스 난이도는 **28**이다. 환산식에는 28을 사용한다. 곡별 계수는 5다.
|
||||
|
||||
```text
|
||||
곡·난이도 배율 = 2.3 × (1 + 5/1000 × (28 − 5)) = 2.5645
|
||||
scoreRatio = 800.85 / 2.5645 = 312.28309612010145
|
||||
일반 PERFECT 기본점수 = 종합력 / 312.28309612010145
|
||||
```
|
||||
|
||||
## 적용과 재현
|
||||
|
||||
신규 값은 외부 연구 메타데이터와 구분하여 `sus-score-formulas.json`에 출처·해시·집계표·검증 대상과 함께 기록했다. 공식 적용 및 채보 수집은 이 값을 함께 읽고, SUS 해시가 달라지면 적용을 거부한다. 현재 서비스 카탈로그 **388개 모두 formula-v1**이며 추정값으로 남아 있는 채보는 없다. 신규 곡의 분모가 자동으로 확보되는 것은 아니며 원본 확보·집계·검증 과정이 필요하다.
|
||||
|
||||
```sh
|
||||
node scripts/calculate-sus-score-formula.mjs data/raw/sus/chart_m0341_expert.sus
|
||||
node scripts/validate-sus-score-formulas.mjs data/raw/sus
|
||||
npm run test:sus-score
|
||||
```
|
||||
|
||||
원본 SUS는 `data/raw/sus`에 저장되어 있으며 Git 및 공개 배포에는 포함하지 않는다.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,119 @@
|
||||
# 곡별 점수 환산식 검증 — 2026-09-06
|
||||
|
||||
## 결론
|
||||
|
||||
최신 후속 결과: 원본 SUS로 m0341 Expert 분모 800850을 산출하여 적용했다. 현재 **388개 전체가 공식 기반**이다. 기존 분모 21개와의 교차 검증 및 산출 과정은 [코로네 Expert SUS 분석](m0341-expert-sus.md)에 기록했다. 아래 387개 적용 및 누락 관련 설명은 이전 검증 단계의 이력이다.
|
||||
|
||||
후속 구현: 387개 채보에 `formula-v1`을 적용했다. `m0341.expert`는 기존 추정값을 유지한다. 아래 분석 수치와 비교표는 적용 전 상태의 기록이며, 검증 스크립트를 현재 상태에서 실행하면 공식 적용 채보의 모델 간 차이는 0으로 표시된다. 채보 수집 시에도 공식이 우선되며, 실측 적용 명령은 공식 기반 값을 덮어쓰지 않는다.
|
||||
|
||||
연구 공식에 곡별 계수·난이도와 `perfect_note_weight`를 적용하면 저장된 **13곡, 18개 곡/난이도, 19건의 일반 PERFECT 실측값을 모두 정확히 재현**한다. 실측값에 맞춘 새로운 상수 피팅은 하지 않았다. 기존 공통 상수보다 근거가 강하며, 곡별 환산값을 생성할 공식으로 채택할 수 있다.
|
||||
|
||||
단, 서비스의 `notes` 가중치 합을 분모로 그대로 쓰면 안 된다. 비가시 지점을 포함한 환산용 분모는 `scoreFormula.perfectNoteWeight`로 별도 보관한다. 득점 노트 및 스페셜 시각과 이벤트 계산은 변경하지 않았다.
|
||||
|
||||
## 자료와 재현 방법
|
||||
|
||||
- 연구 문서: https://docs.google.com/document/d/1P1newxmgxS8b-0raHk5vqnbb8X1nXh1xs3d4gYuhUMQ
|
||||
- 문서가 제공하는 메타데이터: https://airei.dev/metadata/music_meta.json
|
||||
- 메타데이터는 이번 조회에서 HTTP 200으로 접근 가능했다. 원문 775개 레코드 중 서비스 카탈로그 및 실측표에 필요한 389개 레코드의 관련 필드만 `song-score-formula-reference.json`에 보존했다. 조회 시각·원본 전체 SHA-256도 포함했다.
|
||||
- 실측 원본: `data/score-calibrations.json`. 이 자료는 곡/난이도, 종합력, 가창 보너스, 일반 PERFECT 점수를 제공한다. 플랫폼·전체 플레이 로그·측정 시각은 기록되어 있지 않다.
|
||||
- 로컬 HolodoriDB 한국어 원본 `origin/main`: `1d7c7850bf2df34239c440b5b0cc53bbd72e5d5d`.
|
||||
- `Music.json`의 곡별 계수 및 `MusicDifficulty.json`의 난이도 레벨은 참고 데이터 389개 모두 일치했다. `Setting.json`의 `liveDeckEvaluationCoefficientPermilMultiply`는 2300이다.
|
||||
|
||||
실행:
|
||||
|
||||
```sh
|
||||
node scripts/validate-song-score-formula.mjs
|
||||
```
|
||||
|
||||
스크립트는 고정 참고 데이터와 현재 저장소를 읽기만 한다. 실측 점수는 BigInt 유리수 연산으로 마지막 올림까지 검증하며, 기존 `scoreRatio`는 공식 입력으로 사용하지 않는다. 누락된 실측 참고값, 실측 불일치, 카탈로그 난이도 또는 콤보 수 불일치에서는 실패한다. 실측이 없는 카탈로그의 참고 데이터 누락은 별도로 보고한다.
|
||||
|
||||
## 검증 공식
|
||||
|
||||
`W`는 일반 PERFECT=1000 단위의 `perfect_note_weight`, `k`는 곡별 `liveScoreCoefficientPermil`, `L`은 난이도 레벨이다.
|
||||
|
||||
```text
|
||||
C = 2.3 × (1 + k/1000 × (L − 5))
|
||||
scoreRatio = (W/1000) / C
|
||||
일반 PERFECT 점수 = ceil(종합력 / scoreRatio × (1 + 가창 보너스/100))
|
||||
```
|
||||
|
||||
실측 조건은 콤보·스킬 배율이 없는 일반 PERFECT 기본 점수로 해석한다. 기존 실측 검산도 같은 전제를 사용한다.
|
||||
|
||||
올림된 점수 S로부터 ratio는 단일 값으로 결정되지 않는다. A=종합력×가창배율이면 가능한 구간은 `[A/S, A/(S−1))`다. 모든 실측에서 연구식의 ratio가 이 구간 안에 들었으며, 같은 채보에 여러 측정이 있으면 모두 충족했다. 기존 보정 ratio와 연구 ratio가 소수점까지 같을 필요는 없다.
|
||||
|
||||
## 전체 실측 결과
|
||||
|
||||
| 곡 | 난이도 | 실측 점수 | 연구식 |
|
||||
|---|---|---:|---:|
|
||||
| Our Promise | Easy | 6,571 | 6,571 |
|
||||
| Our Promise | Normal | 4,091 | 4,091 |
|
||||
| Our Promise | Hard | 2,295 | 2,295 |
|
||||
| Our Promise | Expert | 1,455 | 1,455 |
|
||||
| PSYCHO | Hard | 2,023 | 2,023 |
|
||||
| PSYCHO | Expert, 가창 10% | 1,203 | 1,203 |
|
||||
| PSYCHO | Expert, 가창 8.26% | 1,184 | 1,184 |
|
||||
| Los! Los! Los! | Hard | 1,633 | 1,633 |
|
||||
| Los! Los! Los! | Expert | 1,008 | 1,008 |
|
||||
| Erandekurete Arigatou | Expert | 1,742 | 1,742 |
|
||||
| Bling-Bang-Bang-Born | Expert | 998 | 998 |
|
||||
| ALiCE&u | Expert | 980 | 980 |
|
||||
| Ahoy!! We are Houshou Pirates | Expert | 1,128 | 1,128 |
|
||||
| Alien Alien | Expert | 761 | 761 |
|
||||
| Asuenokyokaisen | Expert | 1,014 | 1,014 |
|
||||
| Boom! Boom! Tropica Vacation | Expert | 937 | 937 |
|
||||
| UNDEAD | Expert | 1,113 | 1,113 |
|
||||
| Zenryoku-Jumping! | Expert | 1,671 | 1,671 |
|
||||
| NAKAMAKA | Expert | 841 | 841 |
|
||||
|
||||
## 원인: 점수를 받는 노트와 환산 분모의 차이
|
||||
|
||||
아래는 일반 노트=1로 정규화한 값이다.
|
||||
|
||||
| 채보 | 현재 notes 가중치 합 | 참고 playable 가중치 (`base_weight`) | 참고 환산 분모 (`perfect_note_weight`) |
|
||||
|---|---:|---:|---:|
|
||||
| Our Promise Expert | 632.05 | 632.05 | 633.95 |
|
||||
| PSYCHO Expert | 766.25 | 763.55 | 786.55 |
|
||||
| Boom! Boom! Tropica Vacation Expert | 801.75 | 798.55 | 839.95 |
|
||||
|
||||
Our Promise는 playable 집계가 같아도 분모가 1.9 크다. 따라서 현재 노트 분류만 정리한다고 해결되지 않는다. 연구 문서는 비가시 릴레이·베지어 지점에도 분모 가중치를 부여한다고 설명하며, 참고 메타데이터의 `perfect_note_weight − base_weight`는 그 설명과 부합한다. 이번에는 SUS 원문을 독립 파싱해 비가시 지점 개수를 재구성하지는 않았다.
|
||||
|
||||
PSYCHO 등에는 현재 notes와 참고 playable 집계 사이에도 차이가 있다. 비가시 분모 누락 외에 홀드·릴레이·끝 플릭 분류의 차이가 추가로 존재한다. 이 차이의 개별 노트 원인은 이번 검증에서 확정하지 않았다. 실제 노트 점수 및 전체 곡 점수 정확성은 별도 점검이 필요하다.
|
||||
|
||||
예를 들어 Boom! Boom! Tropica Vacation Expert는 종합력 296,408, 가창 0%, k=7, L=27이다.
|
||||
|
||||
```text
|
||||
현재 notes 합을 분모로 사용: ceil(296408 × 2.6542 / 801.75) = 982
|
||||
참고 환산 분모 사용: ceil(296408 × 2.6542 / 839.95) = 937
|
||||
실측: 937
|
||||
```
|
||||
|
||||
환산 분모를 늘리기 위해 비가시 노트를 서비스의 득점·콤보 배열에 추가해서는 안 된다. 분모 전용 필드로 보관해야 한다.
|
||||
|
||||
## 서비스 전체 범위 비교
|
||||
|
||||
현재 카탈로그 388개 중 참고 데이터가 있는 것은 **387개**다. 이들 모두 난이도 레벨 및 로컬 노트 수와 참고 full-combo 수가 일치한다. 같은 노트 수가 같은 노트 분류·가중치를 보장하지는 않는다.
|
||||
|
||||
- 실측 보정 채보: 16개. 올림 전 기본 점수의 연구식 대비 평균 절대 차이 0.0432%, 최대 0.0772%. 위 실측 점수는 기존 보정과 연구식 모두 재현한다.
|
||||
- 공통 상수 추정 채보: 371개. 연구식 대비 평균 절대 차이 **3.9301%**. 절대 차이 5% 초과 114개, 10% 초과 15개.
|
||||
- KINGWORLD Expert: 현재 기본 점수가 연구식보다 **21.8420% 낮음**.
|
||||
- Excuse My Rudeness, But Could You Please RIP? Expert: 현재 기본 점수가 연구식보다 **13.9990% 높음**.
|
||||
- 참고 데이터 누락: **m0341.expert**. 현재 추정 채보 372개 중 하나다.
|
||||
|
||||
위 백분율은 `(현재 올림 전 기본 점수 / 연구식 기본 점수 − 1) × 100`이다. 실측하지 않은 채보의 수치는 두 모델 간 차이이며, 실제 게임 오차율로 검증한 값은 아니다. 카탈로그 포함 전체를 집계했으며 공개 시각에 따른 필터는 적용하지 않았다.
|
||||
|
||||
## 적용 시 경계와 남은 검증
|
||||
|
||||
1. 곡별 계수·난이도·독립적인 PERFECT 분모를 보존하고 공식으로 ratio를 생성한다. 서비스의 `종합력 / scoreRatio` 인터페이스는 유지 가능하다.
|
||||
2. 실측값은 회귀 검증 기준으로 유지한다. 현재 보정값과 연구 ratio의 작은 차이는 올림된 1노트 점수만으로는 우열을 판별할 수 없다.
|
||||
3. 참고 데이터가 없는 m0341 Expert는 분모를 별도 확보하거나 기존 추정과 구분하여 유지해야 한다.
|
||||
4. 실제 총점까지 검증됐다고 표시하지 않는다. 이번 증거는 일반 PERFECT 1노트 19건이며 스킬, 콤보, PC/모바일 차이, 홀드 중간 올림을 검증하지 않았다.
|
||||
5. 참고 메타데이터 제공자와 연구 문서는 같은 출처 계열이므로, 완전히 독립적인 두 연구의 교차 검증으로 보지 않는다. 독립적인 대조 기준은 저장소에 이미 있던 실측 점수다.
|
||||
|
||||
## 실행 확인
|
||||
|
||||
- 신규 검증 스크립트: 19/19 정확 일치.
|
||||
- 기존 `npm run data:validate-score-calibrations`: 18개 보정 항목 통과.
|
||||
- `npm run check`: 오류 0, 경고 0.
|
||||
- `npm run build`: 온라인 빌드 확인.
|
||||
|
||||
최초 검증 단계에서는 사용자 동작을 변경하지 않았다. 후속 공식 적용은 UPDATES.md에 기록했다. 기존 작업 중인 다른 변경은 보존했다.
|
||||
@@ -0,0 +1,159 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"source": {
|
||||
"kind": "decrypted-sus",
|
||||
"catalogRevision": 70,
|
||||
"parser": "scripts/sus-score-weight.mjs"
|
||||
},
|
||||
"charts": [
|
||||
{
|
||||
"songId": "m0341",
|
||||
"fullComboNoteCount": 958,
|
||||
"counts": {
|
||||
"NORMAL": 464,
|
||||
"FLICK": 85,
|
||||
"LONG_START": 109,
|
||||
"LONG_END": 83,
|
||||
"LONG_FLICK_END": 26,
|
||||
"LONG_RELAY": 40,
|
||||
"LONG_CONTINUE": 151
|
||||
},
|
||||
"invisibleRelayCount": 105,
|
||||
"bezierPointCount": 0,
|
||||
"baseWeight": 790350,
|
||||
"perfectNoteWeight": 800850,
|
||||
"difficulty": "expert",
|
||||
"susSha256": "b6523da612971b3c8816a8fbb5d86d14d569b259aae84387f2818dd31944d969",
|
||||
"liveScoreCoefficientPermil": 5,
|
||||
"difficultyLevel": 28
|
||||
}
|
||||
],
|
||||
"validationCharts": [
|
||||
{
|
||||
"songId": "m0010",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "3205deef6bee19b03fd745257421e5003cfd066b6464e70205675a6822b4fe58",
|
||||
"perfectNoteWeight": 638250
|
||||
},
|
||||
{
|
||||
"songId": "m0035",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "bf9a6cf50046633e83e4a3a53113f388ea85ee0f332ed627a14671e2c3f75228",
|
||||
"perfectNoteWeight": 483100
|
||||
},
|
||||
{
|
||||
"songId": "m0040",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "b2857491f68e385d437731e07f26be90a1e3ea19be3e43231b819de7daf3bb6c",
|
||||
"perfectNoteWeight": 709850
|
||||
},
|
||||
{
|
||||
"songId": "m0047",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "db36ac771d525f867c155bb62df46a9bcbc33503f4e5a4b884661d02e6f10f43",
|
||||
"perfectNoteWeight": 731250
|
||||
},
|
||||
{
|
||||
"songId": "m0108",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "cfc11a8411f49be4b3d89f386f8f9557bc6efaf8a56e36bc7b4eeae6891b841d",
|
||||
"perfectNoteWeight": 968500
|
||||
},
|
||||
{
|
||||
"songId": "m0121",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "475f5236d936b13dca7898aac181e42ccb18901d23faf923b4363c4e747bbd6b",
|
||||
"perfectNoteWeight": 786550
|
||||
},
|
||||
{
|
||||
"songId": "m0121",
|
||||
"difficulty": "hard",
|
||||
"susSha256": "6d3a096b459ebf3543c8756686795d0e4515a7e4789f853ad2533071a617473c",
|
||||
"perfectNoteWeight": 457300
|
||||
},
|
||||
{
|
||||
"songId": "m0144",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "6771f00103c6624021e462ccd1fac4ba56b5ea3dc10858d6b36ba82ad2d088a3",
|
||||
"perfectNoteWeight": 1054200
|
||||
},
|
||||
{
|
||||
"songId": "m0157",
|
||||
"difficulty": "easy",
|
||||
"susSha256": "0ce41e57bb03b05db9f8b6e2624464fd3adb112e03165630683b3072b1313ead",
|
||||
"perfectNoteWeight": 126900
|
||||
},
|
||||
{
|
||||
"songId": "m0157",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "6584e49b665276de1df1f8ce69027d290b643f74484b4b643269b9663add45cf",
|
||||
"perfectNoteWeight": 633950
|
||||
},
|
||||
{
|
||||
"songId": "m0157",
|
||||
"difficulty": "hard",
|
||||
"susSha256": "2d6a7bff36c9561c7368b35f54033a625c2c208f44b0b72cf5e0786cfb9e19f2",
|
||||
"perfectNoteWeight": 385500
|
||||
},
|
||||
{
|
||||
"songId": "m0157",
|
||||
"difficulty": "normal",
|
||||
"susSha256": "b17cfb6933fbfdc58197ea6024003c6053446c1eafe9f900fd7512034731bfe7",
|
||||
"perfectNoteWeight": 210000
|
||||
},
|
||||
{
|
||||
"songId": "m0182",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "144c7b3be2c2ff51e203ee697371929fa7748e921766b1a6f20ec66f2f9196cd",
|
||||
"perfectNoteWeight": 972400
|
||||
},
|
||||
{
|
||||
"songId": "m0182",
|
||||
"difficulty": "hard",
|
||||
"susSha256": "f73f824ed32165e60476ef56d7a5cfab30aecf21c2e5eab72425b9f30b2403d7",
|
||||
"perfectNoteWeight": 581150
|
||||
},
|
||||
{
|
||||
"songId": "m0201",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "907ae19b907106d488365765f1787bdf0f117b16b3867feeb011d7593bf652f0",
|
||||
"perfectNoteWeight": 539400
|
||||
},
|
||||
{
|
||||
"songId": "m0309",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "348869afc7110b14e2f84f702a787eee32d46dd34e1463961baac431a4623af8",
|
||||
"perfectNoteWeight": 839950
|
||||
},
|
||||
{
|
||||
"songId": "m0312",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "3c57185b1315e440069d86c5f2df7c0679c34e0ef6be409f89ed22aa67d9cca8",
|
||||
"perfectNoteWeight": 758700
|
||||
},
|
||||
{
|
||||
"songId": "m0319",
|
||||
"difficulty": "expert",
|
||||
"susSha256": "d853bf70aeb3ad267df53a8f445788a2b22cd55ff07e6209d39bff0480e7f43b",
|
||||
"perfectNoteWeight": 906700
|
||||
},
|
||||
{
|
||||
"songId": "m0341",
|
||||
"difficulty": "easy",
|
||||
"susSha256": "ea9fbe7b4c4d466dc50cb30410bc2f410cd5d72fa3f4d58db3a1652c0fa0f3f1",
|
||||
"perfectNoteWeight": 177000
|
||||
},
|
||||
{
|
||||
"songId": "m0341",
|
||||
"difficulty": "hard",
|
||||
"susSha256": "139c990b740a2fb42e5a1c18580b02e91e9da16b0f49c8ab34e544a982404820",
|
||||
"perfectNoteWeight": 500350
|
||||
},
|
||||
{
|
||||
"songId": "m0341",
|
||||
"difficulty": "normal",
|
||||
"susSha256": "13c9e219f5d1ab5a47b78460ace319557c236b9b92574c78d8897bb64297f475",
|
||||
"perfectNoteWeight": 301800
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user