Files
HolodoriCalc/scripts/collect-chart-visuals.mjs
T
dal4segno 07b98db520
Publish GitHub Pages output / publish (push) Successful in 44s
data: switch to recovered game DB and refresh cards and charts
2026-09-29 22:48:08 +09:00

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 검증 완료)`);