76 lines
3.2 KiB
JavaScript
76 lines
3.2 KiB
JavaScript
/**
|
||
* One-off audit: do generated galaxies obey the two tether rules?
|
||
* Rule 1: every jump gate within 1 tether region (level1Radius) of a
|
||
* planet or space station — unless the system is a dead end
|
||
* (a barren system: no planets/stations, single gate).
|
||
* Rule 2: every planet & space station within 2 tether lengths
|
||
* (2 × level1Radius) of at least one other planet/station —
|
||
* unless the system holds exactly one such object.
|
||
*/
|
||
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 T1 = config.get('tether.level1Radius', 5120);
|
||
const T2 = T1 * 2;
|
||
const EPS = 1;
|
||
|
||
for (const seed of ['audit-a', 'audit-b', 'audit-c']) {
|
||
const g = Galaxy.create(seed);
|
||
g.generateAll();
|
||
let r1Bad = 0, r1Checked = 0;
|
||
let r2Bad = 0, r2Checked = 0;
|
||
const r1Examples = [];
|
||
const r2ByN = new Map(); // N → [checked, bad]
|
||
for (const rec of g.records) {
|
||
const c = g.contentOf(rec.id);
|
||
const isHome = rec.id === g.homeSystemId;
|
||
const objects = [...c.planets.map((p) => ({ x: p.x, y: p.y }))]
|
||
.concat(c.settlements.filter((s) => s.anchor?.type === 'space').map((s) => ({ x: s.x, y: s.y })));
|
||
if (isHome) objects.push({ x: 0, y: 0 }); // the home world
|
||
const isBarren = objects.length === 0;
|
||
// Rule 1
|
||
for (const j of c.jumps) {
|
||
r1Checked++;
|
||
if (isBarren) continue;
|
||
let best = Infinity;
|
||
for (const o of objects) best = Math.min(best, Math.hypot(j.x - o.x, j.y - o.y));
|
||
if (best > T1 + EPS) {
|
||
r1Bad++;
|
||
if (r1Examples.length < 3) r1Examples.push(`${rec.id} gate→${j.to} d=${Math.round(best)}`);
|
||
}
|
||
}
|
||
// Rule 2
|
||
if (objects.length >= 2) {
|
||
for (const o of objects) {
|
||
r2Checked++;
|
||
let best = Infinity;
|
||
for (const q of objects) {
|
||
if (q === o) continue;
|
||
best = Math.min(best, Math.hypot(o.x - q.x, o.y - q.y));
|
||
}
|
||
const [cc, bb] = r2ByN.get(objects.length) ?? [0, 0];
|
||
r2ByN.set(objects.length, [cc + 1, bb + (best > T2 + EPS ? 1 : 0)]);
|
||
if (best > T2 + EPS) r2Bad++;
|
||
}
|
||
}
|
||
}
|
||
console.log(`\nseed="${seed}" systems=${g.records.length} (tether L1=${T1}px, 2×L1=${T2}px)`);
|
||
console.log(` Rule 1 (gates ≤ ${T1}px from a planet/station, barren exempt): ${r1Checked - r1Bad}/${r1Checked} OK${r1Bad ? ' — BAD: ' + r1Examples.join(', ') : ''}`);
|
||
const byN = [...r2ByN.entries()].map(([n, [cc, bb]]) => `${n}-obj ${cc - bb}/${cc}`).join(', ');
|
||
console.log(` Rule 2 (every object ≤ ${T2}px from another, N=1 exempt): ${r2Checked - r2Bad}/${r2Checked} OK — ${byN}`);
|
||
}
|