SOURCE / PINNED RELEASE
Made of little things.
Pursuit PIT
- Release
- 565f4981bdc3…
- Author-recorded commit
- 197afdbed44b…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-951414e7fd7
Archive hash verified: 3b0485589942fd5c…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Checks the generated road and its signs over a long drive, for several seeds:
// smooth bends and slopes within limits, consistent heading/elevation, and sane town names.
// Run with: bun tools/road-check.ts
import { MAX_GRADE, MIN_RADIUS, MOUNTAIN_MIN_RADIUS, curvature, elevation, grade, heading, setRoadSeed } from '../src/game/road.ts';
import { signsIn } from '../src/game/signs.ts';
import { TOWN_NAMES_FOR_TESTS, town } from '../src/game/towns.ts';
import { closedLanes, closureOf, leftWall, townSpan, zoneAt, type Area } from '../src/game/zones.ts';
const LENGTH = 200_000;
const failures: string[] = [];
const fail = (msg: string) => {
if (failures.length < 20) failures.push(msg);
};
for (const seed of [1, 2, 3, 12345, 0xdeadbeef]) {
setRoadSeed(seed);
let maxK = 0, maxKOut = 0, maxG = 0, maxDK = 0, maxDG = 0, maxElev = 0;
let prevK = curvature(-500), prevG = grade(-500);
for (let s = -500; s < LENGTH; s += 1) {
const k = curvature(s), g = grade(s);
maxK = Math.max(maxK, Math.abs(k));
// Away from the mountains (and their blends) bends stay gentle.
if (zoneAt(s - 400).area !== 'mountains' && zoneAt(s).area !== 'mountains' && zoneAt(s + 400).area !== 'mountains') maxKOut = Math.max(maxKOut, Math.abs(k));
maxG = Math.max(maxG, Math.abs(g));
maxDK = Math.max(maxDK, Math.abs(k - prevK));
maxDG = Math.max(maxDG, Math.abs(g - prevG));
maxElev = Math.max(maxElev, Math.abs(elevation(s)));
prevK = k;
prevG = g;
}
if (maxK > 1 / MOUNTAIN_MIN_RADIUS + 1e-9) fail(`seed ${seed}: curvature ${maxK} too tight`);
if (maxKOut > 1 / MIN_RADIUS + 1e-9) fail(`seed ${seed}: mountain-tight bend outside the mountains (${maxKOut})`);
if (maxG > MAX_GRADE + 1e-9) fail(`seed ${seed}: grade ${maxG} too steep`);
// Per metre, curvature and grade change smoothly (no step at segment joins).
if (maxDK > 2 / MOUNTAIN_MIN_RADIUS / 100) fail(`seed ${seed}: curvature jumps by ${maxDK}`);
if (maxDG > (2 * MAX_GRADE) / 100) fail(`seed ${seed}: grade jumps by ${maxDG}`);
// Heading and elevation are integrals: compare with a numeric sum, and query out of order.
let h = heading(50_000), y = elevation(50_000);
for (let s = 50_000; s < 60_000; s += 0.5) {
h += curvature(s + 0.25) * 0.5;
y += grade(s + 0.25) * 0.5;
}
setRoadSeed(seed); // fresh caches, reached from the far end first
if (Math.abs(heading(60_000) - h) > 1e-4) fail(`seed ${seed}: heading integral off by ${heading(60_000) - h}`);
if (Math.abs(elevation(60_000) - y) > 1e-3) fail(`seed ${seed}: elevation integral off by ${elevation(60_000) - y}`);
if (Math.abs(heading(-3_000) - heading(-3_000)) > 0) fail('heading not repeatable');
// Towns: no repeats among neighbours, nothing on the block list.
const names: string[] = [];
for (let j = 0; j < 60; j++) {
const name = town(j, seed).name;
if (names.slice(-10).includes(name)) fail(`seed ${seed}: town ${name} repeats within 10`);
const squashed = name.toLowerCase().replace(/[^a-z]/g, '');
if (TOWN_NAMES_FOR_TESTS.BLOCK.some((b) => squashed.includes(b))) fail(`seed ${seed}: blocked name ${name}`);
names.push(name);
}
// Signs: a stable schedule, and windows agree with the whole.
const all = signsIn(0, 20_000);
const parts = [...signsIn(0, 7_000), ...signsIn(7_000, 20_000)];
if (all.length !== parts.length) fail(`seed ${seed}: sign windows disagree (${all.length} vs ${parts.length})`);
if (new Set(all.map((s) => s.key)).size !== all.length) fail(`seed ${seed}: duplicate sign keys`);
const kinds = new Map<string, number>();
for (const sign of all) kinds.set(sign.kind, (kinds.get(sign.kind) ?? 0) + 1);
console.log(`seed ${seed}: R min ${Math.round(1 / maxK)} m, grade max ${(maxG * 100).toFixed(1)}%, |elev| max ${Math.round(maxElev)} m,`,
'signs in 20 km:', Object.fromEntries(kinds), '| towns:', names.slice(0, 6).join(', '));
}
// Areas: towns in order, two different areas between each pair, closures inside road works.
for (const seed of [1, 2, 3, 12345]) {
setRoadSeed(seed);
const lengths = new Map<Area, number[]>();
let prev = zoneAt(0), wallPrev = leftWall(0), maxWallStep = 0, towns = 0;
for (let s = 10; s < 100_000; s += 10) {
const z = zoneAt(s);
if (s < z.start || s >= z.end) fail(`seed ${seed}: zone at ${s} is ${z.start}..${z.end}`);
if (z.start !== prev.start) {
if (prev.end !== z.start) fail(`seed ${seed}: gap or overlap at ${s}`);
const list = lengths.get(prev.area) ?? [];
list.push(prev.end - prev.start);
lengths.set(prev.area, list);
const bothCountry = prev.area !== 'town' && prev.area !== 'city' && z.area !== 'town' && z.area !== 'city';
if (bothCountry && prev.area === z.area && prev.start > 0) fail(`seed ${seed}: ${z.area} twice in a row at ${s}`);
if (z.area === 'town' || z.area === 'city') {
towns++;
// Exactly two areas since the last town.
const before = zoneAt(z.start - 1), earlier = zoneAt(before.start - 1);
if (z.town! > 0 && (earlier.area === 'town' || earlier.area === 'city' ? 0 : 1) + 1 !== 2) fail(`seed ${seed}: town ${z.town} not after two areas`);
if (z.town! > 0 && zoneAt(earlier.start - 1).area !== 'town' && zoneAt(earlier.start - 1).area !== 'city') fail(`seed ${seed}: more than two areas before town ${z.town}`);
}
prev = z;
}
const c = closedLanes(s);
if (c && !closureOf(z)) fail(`seed ${seed}: lanes closed outside road works or mountains at ${s}`);
const wall = leftWall(s);
maxWallStep = Math.max(maxWallStep, Math.abs(wall - wallPrev));
wallPrev = wall;
}
if (maxWallStep > 1) fail(`seed ${seed}: barrier line jumps ${maxWallStep.toFixed(2)} m in 10 m`);
const towns0 = townSpan(1).start - townSpan(0).end;
const summary = [...lengths].map(([a, l]) => `${a} ${l.length}x ~${Math.round(l.reduce((x, y) => x + y) / l.length)} m`).join(', ');
console.log(`seed ${seed}: ${towns} towns in 100 km (gap after the first ${Math.round(towns0)} m); ${summary}`);
}
if (failures.length) {
console.log('FAIL\n' + failures.join('\n'));
process.exit(1);
}
console.log('OK');
