data: switch to recovered game DB and refresh cards and charts
Publish GitHub Pages output / publish (push) Successful in 44s

This commit is contained in:
2026-09-29 22:48:08 +09:00
parent 5783072b12
commit 07b98db520
1259 changed files with 11228 additions and 6809 deletions
+19 -21
View File
@@ -1,27 +1,25 @@
import { readFile } from 'node:fs/promises';
import { readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { readGameDbTable } from './game-db-source.mjs';
import { susScoreWeight } from './sus-score-weight.mjs';
import { scoreRatioFromFormula } from '../src/lib/engine/score.ts';
// Pinned research snapshot, with source URL, retrieval time and SHA-256.
const reference = JSON.parse(await readFile(new URL('../docs/research/song-score-formula-reference.json', import.meta.url), 'utf8'));
const susReference = JSON.parse(await readFile(new URL('../docs/research/sus-score-formulas.json', import.meta.url), 'utf8'));
// Newly recovered charts carry their full reproducibility record in validationCharts until
// the next research snapshot is consolidated.
const rows = [...reference.charts, ...susReference.charts, ...susReference.validationCharts.filter((row) => row.fullComboNoteCount)];
const formulas = new Map(rows.map((row) => [`${row.songId}.${row.difficulty}`, row]));
if (formulas.size !== rows.length) throw new Error('중복 악곡 점수 공식입니다.');
const music = new Map(readGameDbTable('kor', 'Music.json').map(({ data }) => [data.id, data]));
const levels = new Map(readGameDbTable('kor', 'MusicDifficulty.json').map(({ data }) =>
[data.musicId + '.' + data.difficultyType.split('_').at(-1).toLowerCase(), data.difficultyLevel]));
export function formulaScoreMetadata(songId, difficulty, difficultyLevel, totalNotes, liveScoreCoefficientPermil, susSha256) {
const row = formulas.get(`${songId}.${difficulty.toLowerCase()}`);
if (!row) return null;
if (row.susSha256 && row.susSha256 !== susSha256) throw new Error(`${songId}.${difficulty}: 원본 SUS 해시가 다릅니다.`);
if (row.difficultyLevel !== difficultyLevel || row.fullComboNoteCount !== totalNotes ||
(liveScoreCoefficientPermil !== undefined && row.liveScoreCoefficientPermil !== liveScoreCoefficientPermil)) {
throw new Error(`${songId}.${difficulty}: 채보 또는 마스터 데이터가 점수 공식 참고값과 다릅니다.`);
export function formulaScoreMetadata(songId, difficulty, difficultyLevel, totalNotes, coefficient, expectedHash) {
const master = music.get(songId), key = songId + '.' + difficulty;
if (!master || levels.get(key) !== difficultyLevel) throw new Error(key + ': DB metadata mismatch');
const bytes = readFileSync(new URL('../data/raw/sus/chart_' + songId + '_' + difficulty + '.sus', import.meta.url));
const hash = createHash('sha256').update(bytes).digest('hex');
if (expectedHash && hash !== expectedHash) throw new Error(key + ': SUS hash mismatch');
const weight = susScoreWeight(bytes.toString('utf8'));
if (weight.songId !== songId || weight.fullComboNoteCount !== totalNotes ||
(coefficient !== undefined && coefficient !== master.liveScoreCoefficientPermil)) {
throw new Error(key + ': SUS / DB score inputs mismatch');
}
const scoreFormula = {
perfectNoteWeight: row.perfectNoteWeight,
liveScoreCoefficientPermil: row.liveScoreCoefficientPermil,
difficultyLevel: row.difficultyLevel
};
const scoreFormula = { perfectNoteWeight: weight.perfectNoteWeight,
liveScoreCoefficientPermil: master.liveScoreCoefficientPermil, difficultyLevel };
return { scoreRatio: scoreRatioFromFormula(scoreFormula), scoreCalibrationSource: 'formula-v1', scoreFormula };
}