/** * Station-variant-spread test (dev tool, run with Node — no browser): * * node dev/station-frames.test.mjs * * Asserts the galaxy-wide deep-space-station variant pass * (js/galaxy/StationFrames.js, wired in js/galaxy/Galaxy.js, * data/stations.json → variants): * - every deep-space station carries a sheet frame inside the variant * pool (settlement.stationFrame, stamped by the content generator); * - the pass beats a naive random pick: the variant collision rate * among each station's 8-nearest stars is LOWER than the same metric * for an independent random-per-station assignment; * - determinism: same seed ⇒ identical variant map, different seed ⇒ * different assignment (spot check); * - lazy === eager: the stamped settlement frames equal a fresh * galaxy's pass output. */ process.env.NODE_ENV = 'dev'; import { pathToFileURL } from 'node:url'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; const __dirname = dirname(fileURLToPath(import.meta.url)); const { config } = await import(pathToFileURL(join(__dirname, '../js/config/Config.js')).href); const fs = await import('node:fs'); const dataDir = join(__dirname, '../data'); const configData = {}; for (const f of fs.readdirSync(dataDir)) { if (!f.endsWith('.json') || f === 'manifest.json') continue; configData[f.replace(/\.json$/i, '')] = JSON.parse(fs.readFileSync(join(dataDir, f), 'utf8')); } config.init(configData); const { Galaxy } = await import(pathToFileURL(join(__dirname, '../js/galaxy/Galaxy.js')).href); const { Rng } = await import(pathToFileURL(join(__dirname, '../js/utils/Rng.js')).href); let failures = 0; const check = (label, cond, extra = '') => { console.log(`${cond ? '✔' : '✘ FAIL'} ${label}${cond ? '' : ' — ' + extra}`); if (!cond) failures++; }; const SEED = 'station-frames-test-seed'; const g = Galaxy.create(SEED); const POOL = Math.max(1, Math.floor(g.params.neighbors ?? 8)); const POOLV = (config.get('stations.variants') ?? []).filter((f) => Number.isInteger(f) && f >= 0); check('data/stations.json → variants is a non-empty integer pool', POOLV.length >= 1, JSON.stringify(config.get('stations.variants'))); // The station-bearing systems and their stamped frames (via content). const stations = new Map(); // id → [{ rec, frame, name }] (one per station) for (const rec of g.records) { const c = g.ensureContent(rec.id); for (const s of c.settlements) { if (s.kind === 'deepSpaceStation') { if (!stations.has(rec.id)) stations.set(rec.id, []); stations.get(rec.id).push({ rec, frame: s.stationFrame, name: s.name }); } } } console.log(` (galaxy: ${g.records.length} systems, ${[...stations.values()].reduce((n, a) => n + a.length, 0)} deep-space stations across ${stations.size} systems, pool [${POOLV}])`); // ---------------------------------------------------------------------- // 1. Frames are present and in-pool // ---------------------------------------------------------------------- { let inPool = true; let why = ''; for (const [id, sts] of stations) { for (const st of sts) { if (typeof st.frame !== 'number' || !Number.isInteger(st.frame) || !POOLV.includes(st.frame)) { inPool = false; if (!why) why = `${id}: frame ${st.frame} ∉ [${POOLV}]`; } } } check('every deep-space station wears a pool variant (settlement.stationFrame)', inPool, why); check('the galaxy pass covers exactly the station-bearing systems', stations.size === g.stationFrames.size, `${stations.size} settlements vs ${g.stationFrames.size} pass entries`); // A system holding two stations spreads them — two different variants. let distinctOk = true; let whyD = ''; if (POOLV.length > 1) { for (const [id, sts] of stations) { if (sts.length > 1) { const frames = sts.map((s) => s.frame); if (new Set(frames).size !== frames.length) { distinctOk = false; if (!whyD) whyD = `${id}: ${frames.join(', ')}`; } } } } check('a two-station system wears two different variants', distinctOk, whyD); } // ---------------------------------------------------------------------- // 2. The pass beats a naive random assignment // ---------------------------------------------------------------------- { // The pass's variant collision count: for each station, count its // POOL-nearest stars whose station wears the SAME frame (directed). // Aggregated over THREE seeds — a single galaxy is sparse in // station adjacency (most stars carry no station), and the naive // random baseline can be 0 in any given seed; summed, it is a // stable floor the pass must beat. const SEEDS = [SEED, 'sf-seed-b', 'sf-seed-c']; let passColl = 0; let naiveColl = 0; let checks = 0; for (const sd of SEEDS) { const gg = Galaxy.create(sd); const sts = new Map(); // id → [stamped frame, ...] for (const rec of gg.records) { for (const s of gg.ensureContent(rec.id).settlements) { if (s.kind === 'deepSpaceStation') { if (!sts.has(rec.id)) sts.set(rec.id, []); sts.get(rec.id).push(s.stationFrame); } } } // Naive: independent random pool picks (seeded, per station). const naiveF = new Map(); // id → [frame, ...] for (const [id, frames] of sts) { naiveF.set(id, frames.map((_, i) => POOLV.length === 1 ? POOLV[0] : POOLV[Math.floor(Rng.derive(sd, 'naive-station', id, i).next() * POOLV.length)])); } for (const [id, frames] of sts) { for (const fr of frames) { for (const nb of gg.neighborsOf(id, POOL)) { checks++; const nfs = sts.get(nb.id) ?? []; const nnfs = naiveF.get(nb.id) ?? []; if (nfs.includes(fr)) passColl++; if (nnfs.includes(fr)) naiveColl++; } } } } check( `variant collisions vs the ${POOL}-nearest pool (3 seeds): pass ${passColl} < naive ${naiveColl}`, passColl < naiveColl && naiveColl >= 1, `${passColl}/${checks} vs ${naiveColl}/${checks} (station density sets the floor — most stars carry none)`, ); // The spread should also produce ALL variants across the galaxy // (with 3 stations' worth of pool breadth and this many stations, // pigeonhole is generous) — the point is variety, not just local spread. const used = new Set([...stations.values()].flatMap((sts) => sts.map((s) => s.frame))); check('the galaxy wears more than one station type (the pool shows variety)', used.size >= Math.min(2, POOLV.length, stations.size), `used [${[...used].sort()}] of pool [${POOLV}]`); } // ---------------------------------------------------------------------- // 3. Determinism + lazy === eager // ---------------------------------------------------------------------- { const g2 = Galaxy.create(SEED); const same = JSON.stringify([...g.stationFrames.entries()].sort()) === JSON.stringify([...g2.stationFrames.entries()].sort()); check('same seed ⇒ same station-variant assignment galaxy-wide', same); // Content stamp === galaxy pass (the stamping contract). let stamped = true; let why = ''; for (const [id, sts] of stations) { const pass = g2.stationFrames.get(id) ?? []; if (!sts.every((st) => pass.includes(st.frame))) { stamped = false; if (!why) why = `${id}: content ${sts.map((s) => s.frame).join(',')} vs pass ${JSON.stringify(pass)}`; } } check('content settlement frames === the galaxy pass (lazy === eager stamping)', stamped, why); const g3 = Galaxy.create('station-frames-OTHER'); const diff = JSON.stringify([...g.stationFrames.entries()].sort()) !== JSON.stringify([...g3.stationFrames.entries()].sort()); check('different seed ⇒ different assignment (spot check)', diff || g3.records[0].id !== g.records[0].id); } console.log(failures === 0 ? '\nAll station-variant tests passed ✔' : `\n${failures} test(s) FAILED ✘`); process.exit(failures === 0 ? 0 : 1);