// node scripts/calculate-sus-score-formula.mjs data/raw/sus/chart_m0341_expert.sus import { readFile } from 'node:fs/promises'; import { createHash } from 'node:crypto'; import { basename } from 'node:path'; import { susScoreWeight } from './sus-score-weight.mjs'; import { scoreRatioFromFormula } from '../src/lib/engine/score.ts'; const file = process.argv[2]; if (!file) throw new Error('원본 SUS 경로를 지정하세요.'); const bytes = await readFile(file); const result = susScoreWeight(bytes.toString('utf8')); const difficulty = basename(file).match(/_(easy|normal|hard|expert)\.sus$/)?.[1]; const reference = JSON.parse(await readFile(new URL('../docs/research/song-score-formula-reference.json', import.meta.url), 'utf8')); const catalog = JSON.parse(await readFile(new URL('../src/lib/data/exact-chart-catalog.json', import.meta.url), 'utf8')); const chart = catalog.charts.find((row) => row.songId === result.songId && row.difficulty === difficulty); const song = reference.charts.find((row) => row.songId === result.songId); if (!chart || !song) throw new Error('카탈로그 난이도 또는 곡별 계수를 찾지 못했습니다.'); const local = JSON.parse(await readFile(new URL(`../static${chart.path}`, import.meta.url), 'utf8')); const susSha256 = createHash('sha256').update(bytes).digest('hex'); if (local.source?.susSha256 !== susSha256 || local.notes.length !== result.fullComboNoteCount) throw new Error('현재 서비스와 다른 원본 채보입니다.'); // SUS SCORE_LEVEL is editor metadata; use the published master difficulty level. const formula = { perfectNoteWeight: result.perfectNoteWeight, liveScoreCoefficientPermil: song.liveScoreCoefficientPermil, difficultyLevel: chart.difficultyLevel }; console.log(JSON.stringify({ ...result, difficulty, susSha256, ...formula, scoreRatio: scoreRatioFromFormula(formula) }, null, 2));