// Extract display geometry only. Never change score notes or collection baselines. import { readFile, writeFile, mkdir } from 'node:fs/promises'; import { createHash } from 'node:crypto'; import { prepareGameDbSource } from './game-db-source.mjs'; import { parseSusTimeline } from './sus-timeline.mjs'; prepareGameDbSource('kor'); const catalog = JSON.parse(await readFile('src/lib/data/exact-chart-catalog.json', 'utf8')); const outputs = []; for (const entry of catalog.charts) { const key = `${entry.songId}.${entry.difficulty}`; const score = JSON.parse(await readFile(`static${entry.path}`, 'utf8')); const raw = await readFile(`data/raw/sus/chart_${entry.songId}_${entry.difficulty}.sus`); const sha256 = createHash('sha256').update(raw).digest('hex'); let visual; let source; if (sha256 === score.source.susSha256) { const parsed = parseSusTimeline(raw.toString('utf8')); if (parsed.songId !== entry.songId || parsed.notes.length !== score.notes.length || parsed.notes.some((note, i) => Math.abs(note.timeSeconds - score.notes[i].timeSeconds) > 0.00001)) { throw new Error(`${key}: visual and score timing differ`); } visual = parsed.visual; source = { kind: 'sus', susSha256: sha256 }; } else { throw new Error(`${key}: regenerate score and visual from the same game SUS first`); } for (const point of [...visual.notes, ...visual.holds.flat()]) { if (![point.timeSeconds, point.lane, point.width].every(Number.isFinite) || point.width <= 0 || point.lane < -0.001 || point.lane + point.width > 12.001) { throw new Error(`${key}: invalid chart geometry ${JSON.stringify(point)}`); } // Match the six-decimal timestamps of the existing score source at note boundaries. const near = score.notes.find(n => Math.abs(n.timeSeconds - point.timeSeconds) < 0.000001); point.timeSeconds = near?.timeSeconds ?? point.timeSeconds; } outputs.push([key, { schemaVersion: 1, songId: entry.songId, difficulty: entry.difficulty, durationSeconds: score.durationSeconds, ...visual, source }]); } // Validate the entire catalog before writing any output. await mkdir('static/data/chart-visuals', { recursive: true }); for (const [key, data] of outputs) await writeFile(`static/data/chart-visuals/${key}.json`, JSON.stringify(data)); console.log(`채보 시각화 ${outputs.length}개 생성 (동일 게임 SUS 검증 완료)`);