/** * Frame-diversity test (dev tool, run with Node — no browser needed): * * node dev/frames.test.mjs * * Asserts the galaxy-wide (class, frame) pass (js/galaxy/PlanetFrames.js, * wired in js/galaxy/Galaxy.js, data/planets.json → frames): * - every planet carries a sheet frame inside its class's pool, and the * starting system's home world carries a terran-pool frame * (content.homeFrame); * - two planets of the SAME class in one system never wear the same * face (intra-system diversity); * - the pass beats a naive random pick: the (class, frame) collision * rate among each planet's 8-nearest stars is LOWER than the same * metric for an independent random-per-planet assignment; * - determinism: same seed ⇒ identical frames (galaxy and content), * different seed ⇒ different assignment (spot check); * - lazy === eager: the stamped frames equal a fresh galaxy's. */ 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 = 'frames-test-seed'; const g = Galaxy.create(SEED); const POOL = Math.max(1, Math.floor(g.params.neighbors ?? 8)); const poolFor = (cls) => { const p = config.get(`planets.frames.${cls}`); return Array.isArray(p) && p.length > 0 ? p : [0]; }; // ---------------------------------------------------------------------- // 1. Frames are present and in-pool // ---------------------------------------------------------------------- { let inPool = true; let why = ''; for (const rec of g.records) { const c = g.ensureContent(rec.id); for (const p of c.planets) { if (typeof p.frame !== 'number' || !Number.isInteger(p.frame) || !poolFor(p.class).includes(p.frame)) { inPool = false; if (!why) why = `${rec.id}: ${p.class} planet frame ${p.frame} ∉ ${poolFor(p.class)}`; } } } const home = g.ensureContent(g.currentSystemId); if (typeof home.homeFrame !== 'number' || !poolFor('terran').includes(home.homeFrame)) { inPool = false; if (!why) why = `home world frame ${home.homeFrame} ∉ terran pool`; } check('every planet frame is an integer in its class pool (home world: terran pool)', inPool, why); // Intra-system diversity: same-class worlds wear different faces UNLESS // the system holds more of that class than the class pool has faces // (pigeonhole — then every face is used, and the lexicographic pass // guarantees exactly pool-size distinct faces). let distinct = true; let whyD = ''; for (const rec of g.records) { const c = g.ensureContent(rec.id); const byClass = new Map(); for (const p of c.planets) { if (!byClass.has(p.class)) byClass.set(p.class, []); byClass.get(p.class).push(p.frame); } for (const [cls, fs2] of byClass) { const m = fs2.length; const poolN = poolFor(cls).length; const used = new Set(fs2).size; if (used !== Math.min(m, poolN)) { distinct = false; if (!whyD) whyD = `${rec.id}: ${m} ${cls} worlds use ${used} faces (want ${Math.min(m, poolN)})`; } } } check('same-class worlds in one system wear distinct faces (pigeonhole-permitting)', distinct, whyD); } // ---------------------------------------------------------------------- // 2. The pass beats a naive random assignment // ---------------------------------------------------------------------- { // The pass's (class, frame) collision rate: for each planet, count its // 8-nearest stars' planets wearing the SAME class AND frame (directed). const collisionRate = (frameOf) => { let coll = 0; let checks = 0; for (const rec of g.records) { const c = g.ensureContent(rec.id); c.planets.forEach((p, i) => { for (const nb of g.neighborsOf(rec.id, POOL)) { checks++; const nbC = g.ensureContent(nb.id); if (nbC.planets.some((q, j) => q.class === p.class && frameOf(nb.id, q, j) === p.frame)) coll++; } }); } return { coll, checks, rate: coll / checks }; }; const passed = collisionRate((id, p, i) => g.planetFrames.get(id)[i]); // Naive: independent random pool picks (seeded, per planet). const naive = new Map(); for (const rec of g.records) { const c = g.ensureContent(rec.id); naive.set(rec.id, c.planets.map((p, i) => { const pool = poolFor(p.class); return pool.length === 1 ? pool[0] : pool[Math.floor(Rng.derive(SEED, 'naive', rec.id, String(i), p.class).next() * pool.length)]; })); } const na = collisionRate((id, p, i) => naive.get(id)[i]); check( `(class, frame) collisions vs the ${POOL}-nearest pool: pass ${(passed.rate * 100).toFixed(1)}% < naive ${(na.rate * 100).toFixed(1)}%`, passed.rate < na.rate, `${passed.coll}/${passed.checks} vs ${na.coll}/${na.checks} (density sets the floor — same-class neighbors dominate)`, ); } // ---------------------------------------------------------------------- // 3. Determinism + lazy === eager // ---------------------------------------------------------------------- { const g2 = Galaxy.create(SEED); let same = true; for (const rec of g.records) { if (JSON.stringify(g.planetFrames.get(rec.id)) !== JSON.stringify(g2.planetFrames.get(rec.id))) { same = false; break; } } check('same seed ⇒ same (class, frame) assignment galaxy-wide', same); check('same seed ⇒ same home-world frame', g.homeWorldFrame === g2.homeWorldFrame); // Content frames === galaxy frames (the stamping contract). let stamped = true; for (const rec of g.records) { const c = g.ensureContent(rec.id); if (JSON.stringify(c.planets.map((p) => p.frame)) !== JSON.stringify(g.planetFrames.get(rec.id))) { stamped = false; break; } } check('content planet frames === the galaxy pass (lazy === eager stamping)', stamped); const g3 = Galaxy.create('frames-test-OTHER'); let diff = false; for (const rec of g.records) { if (JSON.stringify(g.planetFrames.get(rec.id)) !== JSON.stringify(g3.planetFrames.get(rec.id))) { diff = true; break; } } check('different seed ⇒ different assignment (spot check)', diff || g3.records[0].id !== g.records[0].id); } console.log(failures === 0 ? '\nAll frame-diversity tests passed ✔' : `\n${failures} test(s) FAILED ✘`); process.exit(failures === 0 ? 0 : 1);