42 lines
2.3 KiB
JavaScript
42 lines
2.3 KiB
JavaScript
// 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 검증 완료)`);
|