orbit/dev/tether-rules.audit.mjs

90 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* One-off audit: do generated galaxies obey the two tether rules?
* Rule 1: every jump gate strictly < 5000 px from a planet or space
* station (its ANCHOR sits in the 3500–4990 px placement band)
* — 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 anchorDs = []; // gate→anchor distances (to verify the variation)
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;
const anchorSet = isHome
? [...c.planets.map((p) => ({ x: p.x, y: p.y })), { x: 0, y: 0 }]
: c.planets.map((p) => ({ x: p.x, y: p.y }));
// Rule 1 (strictly < 5000 px from a planet/station; anchor band 3500–4990; barren exempt)
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 > 5000 + 1e-6) {
r1Bad++;
if (r1Examples.length < 3) r1Examples.push(`${rec.id} gate→${j.to} d=${Math.round(best)}`);
}
let aBand = -1;
for (const a of anchorSet) {
const d = Math.hypot(j.x - a.x, j.y - a.y);
if (d >= 3500 - 1e-6 && d <= 4990 + 1e-6) { aBand = d; break; }
}
if (aBand >= 0) anchorDs.push(aBand); // the gate's anchoring planet
}
// 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 < 5000px from a planet/station, anchor band 3500–4990, barren exempt): ${r1Checked - r1Bad}/${r1Checked} OK${r1Bad ? ' — BAD: ' + r1Examples.join(', ') : ''}`);
const byN = [...r2ByN.entries()].map(([n, [cc, bb]]) => `${n}-obj ${cc - bb}/${cc}`).join(', ');
const anchorSorted = [...anchorDs].sort((a, b) => a - b);
const distinct = new Set(anchorSorted.map((d) => Math.round(d / 50))).size;
console.log(` gate→anchor distances: min=${Math.round(anchorSorted[0])} max=${Math.round(anchorSorted[anchorSorted.length - 1])}, ~${distinct} distinct radii (band 3500–4990)`);
console.log(` Rule 2 (every object ≤ ${T2}px from another, N=1 exempt): ${r2Checked - r2Bad}/${r2Checked} OK — ${byN}`);
}