43 lines
3.0 KiB
JavaScript
43 lines
3.0 KiB
JavaScript
import { readFile, writeFile } from 'node:fs/promises';
|
|
const source = JSON.parse(await readFile(new URL('../data/cloudRiseLevelStats.json', import.meta.url), 'utf8'));
|
|
if (source.max_start_level !== 1960 || source.levels.length !== 2000) throw new Error('Unexpected source coverage');
|
|
for (const [index, row] of source.levels.entries()) {
|
|
if (row.level !== index + 1 || !Number.isSafeInteger(row.enter_total) || row.enter_total < 0
|
|
|| (row.pass_rate !== null && !(row.pass_rate >= 0 && row.pass_rate <= 1))) throw new Error('Invalid row ' + row.level);
|
|
}
|
|
const profileSource = JSON.parse(await readFile(new URL('../data/cloudRiseProfiles.json', import.meta.url), 'utf8'));
|
|
const cutoff = Date.parse(profileSource.inactiveBefore), seen = new Set();
|
|
const counts = { real: 0, game: 0 };
|
|
if (!Number.isFinite(cutoff) || profileSource.profiles.length !== 5000) throw new Error('Invalid profile snapshot');
|
|
const profiles = profileSource.profiles.map(row => {
|
|
if (!Number.isSafeInteger(row.onlyId) || row.onlyId <= 0 || seen.has(row.onlyId)
|
|
|| !Number.isSafeInteger(row.taskTime) || row.taskTime <= 0 || row.taskTime >= cutoff
|
|
|| typeof row.username !== 'string' || !row.username.trim()
|
|
|| typeof row.useravatar !== 'string' || typeof row.useravatarIcon !== 'string'
|
|
|| Object.keys(row).sort().join(',') !== 'onlyId,taskTime,useravatar,useravatarIcon,username') {
|
|
throw new Error('Invalid or duplicate profile');
|
|
}
|
|
seen.add(row.onlyId);
|
|
const displayed = /^https:\/\//.test(row.useravatarIcon) ? row.useravatarIcon : row.useravatar;
|
|
if (/^https:\/\//.test(displayed)) counts.real++;
|
|
else if (/^(?:icon_)?\d+$/.test(row.useravatar)) counts.game++;
|
|
else throw new Error('Unknown avatar type');
|
|
return { onlyId: row.onlyId, username: row.username, useravatar: row.useravatar, useravatarIcon: row.useravatarIcon };
|
|
});
|
|
if (counts.real !== profileSource.selected.real || counts.game !== profileSource.selected.game) throw new Error('Avatar counts do not match');
|
|
const text = '// Generated by tools/generate-cloud-rise-stats.mjs; edit data/cloudRiseLevelStats.json and data/cloudRiseProfiles.json instead.\n'
|
|
+ '// Source SHA256: ' + source.source_sha256 + '\n'
|
|
+ 'export const LEVEL_STATS = ' + JSON.stringify(source.levels) + ';\n'
|
|
+ '// Profile snapshot: ' + profileSource.sampledAt + '; inactive before ' + profileSource.inactiveBefore + '\n'
|
|
+ 'export const OPPONENT_PROFILES = [\n' + profiles.map(row => JSON.stringify(row)).join(',\n') + '\n];\n';
|
|
const target = new URL('../stats.ts', import.meta.url);
|
|
if (process.argv.includes('--check')) {
|
|
// Formatting is editable; validate the data and the internal-only export contract.
|
|
const generated = await import(target.href);
|
|
if (Object.keys(generated).sort().join(',') !== 'LEVEL_STATS,OPPONENT_PROFILES'
|
|
|| JSON.stringify(generated.LEVEL_STATS) !== JSON.stringify(source.levels)
|
|
|| JSON.stringify(generated.OPPONENT_PROFILES) !== JSON.stringify(profiles)) {
|
|
throw new Error('Generated stats are stale or expose unexpected exports');
|
|
}
|
|
} else await writeFile(target, text);
|