test(08-02): add failing landmark-core suite (L1-L6) — RED gate
Hand-computed vectors for the pure landmark compute core: deterministic degree-filtered farthest-point anchors, exact integer quanta (floor(2.55/0.1)=25, two edges=50), 0xFFFF sentinel + self-cell laws, oneway asymmetry, the 65000-quantum overflow throw, and the 5x5-grid corner-to-corner 264-quanta vector with coverage 1.0.
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* landmark.test.mjs — the pure landmark compute core gates (L1–L6).
|
||||
* ------------------------------------------------------------------
|
||||
* RED first (plan 08-02 task 1): every case fails while
|
||||
* routing/src/landmark.ts does not exist (lazy-import pattern — each
|
||||
* case reports its own ERR_MODULE_NOT_FOUND).
|
||||
*
|
||||
* The ALT preprocessing core (research Pattern 2): distances live in the
|
||||
* ĉ metric — edge weights floor-quantized ONCE to fixed 0.1 s units —
|
||||
* and every table cell is an EXACT integer Dijkstra result. Hand-computed
|
||||
* expectations below; JS arithmetic is deterministic so the pinned quanta
|
||||
* (verified: floor(2.55/0.1)=25, floor(3/0.1)=30, floor(1.5/0.1)=15,
|
||||
* grid perimeter hop=33, interior hop=66) are stable.
|
||||
*
|
||||
* L1 anchors — farthestPointAnchors: deterministic, degree>=2
|
||||
* filtered, well-spread on the 5-node path
|
||||
* L2 quantum exactness — 2.55 s edge -> 25 quanta; two edges -> 50
|
||||
* exactly (integer arithmetic, no float drift)
|
||||
* L3 sentinel + self — unreachable pairs -> 0xFFFF; the landmark's
|
||||
* own cell = 0 (oneway chain + isolated node)
|
||||
* L4 asymmetry — to/from rows differ on a oneway graph
|
||||
* L5 overflow throws — any label > 65000 quanta throws, naming the
|
||||
* distance and the fix (raise landmarkQuantumS)
|
||||
* L6 5x5 grid shape — anchors [0, 24]; corner->corner 264 quanta
|
||||
* (8 perimeter hops x 33); coverage 1.0
|
||||
*
|
||||
* Run: npm run routing:test (exit 0 = all green)
|
||||
*/
|
||||
import { strict as assert } from "node:assert";
|
||||
import { test } from "./harness.mjs";
|
||||
|
||||
let landmarkPromise = null;
|
||||
function loadLandmark() {
|
||||
if (!landmarkPromise) landmarkPromise = import("../src/landmark.ts");
|
||||
return landmarkPromise;
|
||||
}
|
||||
|
||||
/* ————— hand-built vectors ————— */
|
||||
|
||||
/** 5-node path 0-1-2-3-4, two-way, 3.0 s per edge (30 quanta @ 0.1). */
|
||||
function pathInput() {
|
||||
const edges = [];
|
||||
for (let u = 0; u < 4; u++) {
|
||||
edges.push({ from: u, to: u + 1, seconds: 3, flags: 0 });
|
||||
edges.push({ from: u + 1, to: u, seconds: 3, flags: 0 });
|
||||
}
|
||||
return { nodeCount: 5, edges, quantumS: 0.1 };
|
||||
}
|
||||
|
||||
/** Oneway chain 0->1->2 (bit0 mirrors) + isolated node 3, 1.5 s/edge. */
|
||||
function onewayInput() {
|
||||
return {
|
||||
nodeCount: 4,
|
||||
edges: [
|
||||
{ from: 0, to: 1, seconds: 1.5, flags: 0 },
|
||||
{ from: 1, to: 0, seconds: 1.5, flags: 1 }, // blocked mirror
|
||||
{ from: 1, to: 2, seconds: 1.5, flags: 0 },
|
||||
{ from: 2, to: 1, seconds: 1.5, flags: 1 }, // blocked mirror
|
||||
],
|
||||
quantumS: 0.1,
|
||||
};
|
||||
}
|
||||
|
||||
/** The fixture builder's exact 5x5 grid shape (25 nodes, 80 edge entries,
|
||||
* perimeter 60 km/h / interior 30 km/h, one oneway mirror at 3->2). */
|
||||
async function gridInput() {
|
||||
const { haversineM } = await import("../src/geo.ts");
|
||||
const GRID = 5;
|
||||
const lat = [];
|
||||
const lng = [];
|
||||
for (let r = 0; r < GRID; r++) {
|
||||
for (let c = 0; c < GRID; c++) {
|
||||
lat.push((3150000 + r * 500) / 1e6);
|
||||
lng.push((101690000 + c * 500) / 1e6);
|
||||
}
|
||||
}
|
||||
const id = (r, c) => r * GRID + c;
|
||||
const edges = [];
|
||||
function addEdge(u, v, perimeter) {
|
||||
const kmh = perimeter ? 60 : 30;
|
||||
const meters = haversineM({ lat: lat[u], lng: lng[u] }, { lat: lat[v], lng: lng[v] });
|
||||
edges.push({ from: u, to: v, seconds: meters / (kmh / 3.6), flags: 0 });
|
||||
}
|
||||
for (let r = 0; r < GRID; r++) {
|
||||
for (let c = 0; c < GRID - 1; c++) {
|
||||
const perimeter = r === 0 || r === GRID - 1;
|
||||
addEdge(id(r, c), id(r, c + 1), perimeter);
|
||||
addEdge(id(r, c + 1), id(r, c), perimeter);
|
||||
}
|
||||
}
|
||||
for (let c = 0; c < GRID; c++) {
|
||||
for (let r = 0; r < GRID - 1; r++) {
|
||||
const perimeter = c === 0 || c === GRID - 1;
|
||||
addEdge(id(r, c), id(r + 1, c), perimeter);
|
||||
addEdge(id(r + 1, c), id(r, c), perimeter);
|
||||
}
|
||||
}
|
||||
const reverse = edges.find((e) => e.from === 3 && e.to === 2);
|
||||
reverse.flags = 1; // the fixture's single oneway-reverse mirror
|
||||
return { nodeCount: GRID * GRID, edges, quantumS: 0.1 };
|
||||
}
|
||||
|
||||
/* ————— L1–L6 ————— */
|
||||
|
||||
test("L1 anchors — deterministic, degree>=2-filtered, well-spread on the 5-node path", async () => {
|
||||
const { farthestPointAnchors } = await loadLandmark();
|
||||
const input = pathInput();
|
||||
const a = farthestPointAnchors(input, 2);
|
||||
assert.ok(a instanceof Uint32Array, "anchors are a Uint32Array");
|
||||
assert.equal(a.length, 2);
|
||||
// Candidates (degree>=2) are nodes 1,2,3 — endpoints excluded. First
|
||||
// anchor = first candidate by id; pass-from-1 minima are [30,0,30,60,90]
|
||||
// quanta, so the farthest candidate is node 3.
|
||||
assert.deepEqual([...a], [1, 3], "first candidate by id, then farthest spread");
|
||||
// L=3: after folding anchor 3, minima are [30,0,30,0,30] -> node 2.
|
||||
assert.deepEqual([...farthestPointAnchors(input, 3)], [1, 3, 2]);
|
||||
// Determinism: identical input -> identical anchors (no randomness).
|
||||
assert.deepEqual([...farthestPointAnchors(pathInput(), 2)], [...a]);
|
||||
// More anchors than candidates: stop early, never pad.
|
||||
assert.equal(farthestPointAnchors(input, 10).length, 3, "only 3 candidates exist");
|
||||
});
|
||||
|
||||
test("L2 quantum exactness — floor(2.55/0.1)=25 per edge; two edges sum to exactly 50 quanta", async () => {
|
||||
const { landmarkTables, LANDMARK_QUANTUM_S } = await loadLandmark();
|
||||
assert.equal(LANDMARK_QUANTUM_S, 0.1, "the default quantum is the doc'd 0.1 s");
|
||||
const input = {
|
||||
nodeCount: 3,
|
||||
edges: [
|
||||
{ from: 0, to: 1, seconds: 2.55, flags: 0 },
|
||||
{ from: 1, to: 0, seconds: 2.55, flags: 0 },
|
||||
{ from: 1, to: 2, seconds: 2.55, flags: 0 },
|
||||
{ from: 2, to: 1, seconds: 2.55, flags: 0 },
|
||||
],
|
||||
quantumS: 0.1,
|
||||
};
|
||||
const { to, from } = landmarkTables(input, Uint32Array.of(0));
|
||||
assert.equal(from.length, 1 * 3, "row-major L*N cells");
|
||||
assert.equal(from[0 * 3 + 1], 25, "one 2.55 s edge = exactly 25 quanta (floor(25.4999...))");
|
||||
assert.equal(from[0 * 3 + 2], 50, "two edges = exactly 50 quanta — integer arithmetic, no drift");
|
||||
assert.equal(to[0 * 3 + 2], 50, "the to-row mirrors on a two-way path");
|
||||
});
|
||||
|
||||
test("L3 sentinel + self-cell — unreachable pairs -> 0xFFFF; the landmark's own cell = 0", async () => {
|
||||
const { landmarkTables, LANDMARK_SENTINEL } = await loadLandmark();
|
||||
assert.equal(LANDMARK_SENTINEL, 0xffff);
|
||||
const input = onewayInput();
|
||||
const { to, from } = landmarkTables(input, Uint32Array.of(0));
|
||||
assert.equal(to[0 * 4 + 0], 0, "landmark's own to-cell is 0");
|
||||
assert.equal(from[0 * 4 + 0], 0, "landmark's own from-cell is 0");
|
||||
assert.equal(from[0 * 4 + 1], 15, "0->1 traverses (floor(1.5/0.1)=15 quanta)");
|
||||
assert.equal(from[0 * 4 + 2], 30, "0->1->2 traverses");
|
||||
assert.equal(to[0 * 4 + 1], LANDMARK_SENTINEL, "1 cannot reach 0 against the oneway -> sentinel");
|
||||
assert.equal(to[0 * 4 + 2], LANDMARK_SENTINEL, "2 cannot reach 0 -> sentinel");
|
||||
// Isolated node: every cell is the sentinel.
|
||||
assert.equal(to[0 * 4 + 3], LANDMARK_SENTINEL, "isolated node -> sentinel in the to-row");
|
||||
assert.equal(from[0 * 4 + 3], LANDMARK_SENTINEL, "isolated node -> sentinel in the from-row");
|
||||
});
|
||||
|
||||
test("L4 asymmetry — reversed vs forward rows differ on an oneway graph", async () => {
|
||||
const { landmarkTables } = await loadLandmark();
|
||||
const { to, from } = landmarkTables(onewayInput(), Uint32Array.of(1));
|
||||
// Anchor at the middle node 1: forward reaches 2 (15 quanta) but the
|
||||
// to-row for 2 is the sentinel (2 cannot get back to 1).
|
||||
assert.equal(from[1 * 4 + 2], 15, "1->2 traverses");
|
||||
assert.equal(to[1 * 4 + 2], 0xffff, "2->1 does not (oneway) — asymmetry is representable");
|
||||
assert.notEqual(to[1 * 4 + 2], from[1 * 4 + 2]);
|
||||
});
|
||||
|
||||
test("L5 overflow throws — any label > 65000 quanta fails loud, naming the fix", async () => {
|
||||
const { landmarkTables, farthestPointAnchors, LANDMARK_MAX_QUANTA } = await loadLandmark();
|
||||
assert.equal(LANDMARK_MAX_QUANTA, 65000);
|
||||
const input = {
|
||||
nodeCount: 2,
|
||||
edges: [
|
||||
{ from: 0, to: 1, seconds: 10, flags: 0 }, // floor(10/0.0001) = 100000 quanta
|
||||
{ from: 1, to: 0, seconds: 10, flags: 0 },
|
||||
],
|
||||
quantumS: 0.0001,
|
||||
};
|
||||
assert.throws(
|
||||
() => landmarkTables(input, Uint32Array.of(0)),
|
||||
/100000 exceeds 65000 \(raise landmarkQuantumS\)/,
|
||||
"the table pass names the offending distance and the fix",
|
||||
);
|
||||
assert.throws(
|
||||
() => farthestPointAnchors(input, 1),
|
||||
/exceeds 65000 \(raise landmarkQuantumS\)/,
|
||||
"the anchor-selection pass fails loud on the same law",
|
||||
);
|
||||
});
|
||||
|
||||
test("L6 5x5 grid shape — anchors [0,24]; corner->corner 264 quanta; coverage 1.0", async () => {
|
||||
const { farthestPointAnchors, landmarkTables, landmarkCoverage } = await loadLandmark();
|
||||
const input = await gridInput();
|
||||
const anchors = farthestPointAnchors(input, 2);
|
||||
assert.deepEqual([...anchors], [0, 24], "first candidate is node 0; farthest is the opposite corner");
|
||||
const { to, from } = landmarkTables(input, anchors);
|
||||
assert.equal(to.length, 2 * 25, "L*N cells (L=2, N=25)");
|
||||
assert.equal(to[0 * 25 + 0], 0, "self-cell 0");
|
||||
assert.equal(from[1 * 25 + 24], 0, "anchor 24's own from-cell 0");
|
||||
// Corner->corner: 8 perimeter hops (row 0 then column 4, 33 quanta each;
|
||||
// interior hops cost 66 — strictly worse) = 264 exactly.
|
||||
assert.equal(from[0 * 25 + 24], 264, "0->24 = 8 x 33 perimeter quanta, exact");
|
||||
assert.equal(to[1 * 25 + 0], 264, "the to-row mirrors on the grid");
|
||||
const coverage = landmarkCoverage(to, anchors, 25);
|
||||
assert.deepEqual(coverage, [1, 1], "the grid is strongly connected — full coverage");
|
||||
});
|
||||
Reference in New Issue
Block a user