data: add Watcha Gatcha charts from official master
Publish GitHub Pages output / publish (push) Successful in 36s

This commit is contained in:
2026-09-27 06:40:12 +09:00
parent 7c7f0d40c0
commit b87c5d2e18
20 changed files with 402 additions and 43 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9",
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd",
"members": [
{
"id": "chr-00001",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9",
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd",
"cards": [
{
"id": "card-00001-5-uniq-0000-00",
+112
View File
@@ -17122,6 +17122,118 @@
"difficultyLevel": 10
}
},
{
"songId": "m0525",
"title": "Watcha Gatcha!!!!!!!!",
"difficulty": "easy",
"difficultyLevel": 6,
"durationSeconds": 122,
"scoreRatio": 69.73826519576033,
"scoreCalibrationSource": "formula-v1",
"scoreFormula": {
"perfectNoteWeight": 161200,
"liveScoreCoefficientPermil": 5,
"difficultyLevel": 6
},
"totalNotes": 280,
"singerType": "group",
"singerCharacterIds": [
"chr-00030",
"chr-00031",
"chr-00032",
"chr-00034",
"chr-00035",
"chr-00036",
"chr-00037",
"chr-00039"
],
"releaseTime": 1790424000000,
"path": "/data/charts/m0525.easy.json"
},
{
"songId": "m0525",
"title": "Watcha Gatcha!!!!!!!!",
"difficulty": "expert",
"difficultyLevel": 27,
"durationSeconds": 122,
"scoreRatio": 341.73521347434394,
"scoreCalibrationSource": "formula-v1",
"scoreFormula": {
"perfectNoteWeight": 872450,
"liveScoreCoefficientPermil": 5,
"difficultyLevel": 27
},
"totalNotes": 1144,
"singerType": "group",
"singerCharacterIds": [
"chr-00030",
"chr-00031",
"chr-00032",
"chr-00034",
"chr-00035",
"chr-00036",
"chr-00037",
"chr-00039"
],
"releaseTime": 1790424000000,
"path": "/data/charts/m0525.expert.json"
},
{
"songId": "m0525",
"title": "Watcha Gatcha!!!!!!!!",
"difficulty": "hard",
"difficultyLevel": 22,
"durationSeconds": 122,
"scoreRatio": 239.47104788619518,
"scoreCalibrationSource": "formula-v1",
"scoreFormula": {
"perfectNoteWeight": 597600,
"liveScoreCoefficientPermil": 5,
"difficultyLevel": 22
},
"totalNotes": 812,
"singerType": "group",
"singerCharacterIds": [
"chr-00030",
"chr-00031",
"chr-00032",
"chr-00034",
"chr-00035",
"chr-00036",
"chr-00037",
"chr-00039"
],
"releaseTime": 1790424000000,
"path": "/data/charts/m0525.hard.json"
},
{
"songId": "m0525",
"title": "Watcha Gatcha!!!!!!!!",
"difficulty": "normal",
"difficultyLevel": 13,
"durationSeconds": 122,
"scoreRatio": 123.70401337792642,
"scoreCalibrationSource": "formula-v1",
"scoreFormula": {
"perfectNoteWeight": 295900,
"liveScoreCoefficientPermil": 5,
"difficultyLevel": 13
},
"totalNotes": 432,
"singerType": "group",
"singerCharacterIds": [
"chr-00030",
"chr-00031",
"chr-00032",
"chr-00034",
"chr-00035",
"chr-00036",
"chr-00037",
"chr-00039"
],
"releaseTime": 1790424000000,
"path": "/data/charts/m0525.normal.json"
},
{
"songId": "m0527",
"title": "Mekurumeku Rendezvous",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9",
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd",
"maxCount": 34,
"effects": [
{
+44
View File
@@ -1,3 +1,7 @@
import { createHash } from 'node:crypto';
import { leaderRatingCharts } from './song-rating.ts';
import overrides from '../../../data/song-metadata-overrides.json' with { type: 'json' };
import officialSong from '../../../docs/research/m0525-holodoridb.json' with { type: 'json' };
import assert from 'node:assert/strict';
import test from 'node:test';
import { readFile } from 'node:fs/promises';
@@ -62,3 +66,43 @@ test('잘못된 분모와 난이도로 공식 환산값을 만들지 않는다',
assert.throws(() => scoreRatioFromFormula({ ...valid, ...invalid }));
}
});
test('m0525는 보충 응답과 일치하는 공식 마스터를 우선 적용한다', async () => {
const source = overrides.songs.m0525;
const bytes = await readFile(new URL(`../../../${source.file}`, import.meta.url));
assert.equal(createHash('sha256').update(bytes).digest('hex'), source.sha256);
const master = JSON.parse(bytes.toString('utf8'));
const charts = catalog.charts.filter((row) => row.songId === master.id);
assert.equal(charts.length, 4);
const releaseTime = Number(master.startTime);
const weights = { easy: 161200, normal: 295900, hard: 597600, expert: 872450 };
for (const row of charts) {
const difficulty = master.difficulties.find((item: { difficultyType: string }) => item.difficultyType.toLowerCase().endsWith('_' + row.difficulty));
const chart = JSON.parse(await readFile(new URL(`../../../static${row.path}`, import.meta.url), 'utf8'));
const officialDifficulty = officialSong.difficulties.find((item) => item.difficultyType === difficulty.difficultyType);
const officialChart = officialSong.charts.find((item) => item.difficultyType === difficulty.difficultyType);
assert.equal(row.difficultyLevel, officialDifficulty?.difficultyLevel);
assert.equal(row.totalNotes, officialChart?.fullComboNoteCount);
assert.equal(officialChart?.chartHash, difficulty.chartHash);
assert.equal(row.difficultyLevel, difficulty.difficultyLevel);
assert.equal(row.totalNotes, difficulty.fullComboNoteCount);
assert.equal(row.title, master.title.kor);
assert.equal(row.durationSeconds, master.playingSeconds);
assert.equal(row.releaseTime, releaseTime);
assert.equal(row.scoreFormula.liveScoreCoefficientPermil, master.liveScoreCoefficientPermil);
assert.equal(row.scoreFormula.perfectNoteWeight, weights[row.difficulty as keyof typeof weights]);
assert.equal(row.singerType, 'group');
assert.deepEqual(row.singerCharacterIds, master.characterIds);
assert.deepEqual(chart.singerCharacterIds, master.characterIds);
assert.deepEqual(chart.source.masterDataSource, officialSong.source);
assert.equal(chart.source.metadataSource, undefined);
assert.deepEqual(chart.singerCharacterIds, officialSong.music.characterIds);
assert.equal(row.scoreFormula.liveScoreCoefficientPermil, officialSong.music.liveScoreCoefficientPermil);
assert.equal(chart.specialStartSeconds.length, 5);
assert.ok(chart.notes.every((note: { timeSeconds: number }) => Number.isFinite(note.timeSeconds) && note.timeSeconds <= row.durationSeconds));
}
assert.equal(master.characterIds.length, 8);
assert.equal(leaderRatingCharts(charts, 'chr-00030', 'all', releaseTime - 1).length, 0);
assert.equal(leaderRatingCharts(charts, 'chr-00030', 'all', releaseTime).length, 4);
assert.equal(leaderRatingCharts(charts, 'chr-00038', 'all', releaseTime).length, 0);
});