#!/usr/bin/env node /* * ch-query.test.mjs — the uphill bidirectional CH query gates (Q1–Q5) * plus the in-memory ARG3 codec pins (G1–G3). * ---------------------------------------------------------------------------- * RED first (plan 08.1-01 task 2): every case fails while * routing/src/ch-query.ts does not exist (lazy-import pattern). * * This suite is where the deleted ALT cases (astar.test.mjs A9/A10/A12/A13) * FOLDED per D-04/D-05: their surviving content — Dijkstra equivalence over * directed fixtures, the unreachable miss, the oneway law — is re-pinned * against the CH query; the ALT table assertions are gone with the tables. * ALL fixtures here are IN-MEMORY (encodeGraph → decodeGraph through the * ONE authority); the committed mini-graph.bin is still ARG2 in this task * and is NOT read (task 3 regenerates it and re-pins the committed-fixture * cases in graph/astar.test.mjs). * * Q1 exhaustive equivalence — the CH query equals brute-force Dijkstra * for EVERY pair of the 25-node in-memory * grid; path endpoints are origin/goal * Q2 directed oneway law — ring + chord + one-way-in spur: every pair * matches the oracle in BOTH directions and * every returned hop is a traversable * original edge (bit0 never relaxed) * Q3 original-edge seconds — reported seconds equal a fresh sum over * the returned ORIGINAL-edge path (shortcut * weights steer only — Pitfall 3) * Q4 unreachable -> null — the island fold (A9/A10): cross-component * pairs resolve null; internal pairs stay * Dijkstra-equal * Q5 structural deletion — no ALT tokens in astar.ts; landmark.ts and * landmark.test.mjs ABSENT; index.ts exports * no landmark symbols (D-04/D-05) * G1 ARG3 round-trip — contractGraph output round-trips rank + * shortcut CSR exactly through zero-copy views * G2 rejection vectors — ARG1 bytes -> null; a version-2 tamper -> null * G3 fail-honest decode — truncated / hostile-count / chShortcutCount=0 * buffers -> null BEFORE any typed-array * allocation (T-08.1-01/T-08.1-02) * * Run: npm run routing:test (exit 0 = all green) */ import { strict as assert } from "node:assert"; import { readFileSync, existsSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { test } from "./harness.mjs"; const here = path.dirname(fileURLToPath(import.meta.url)); // routing/test let chQueryPromise = null; function loadChQuery() { if (!chQueryPromise) chQueryPromise = import("../src/ch-query.ts"); return chQueryPromise; } let graphPromise = null; function loadGraph() { if (!graphPromise) graphPromise = import("../src/graph.ts"); return graphPromise; } let contractionPromise = null; function loadContraction() { if (!contractionPromise) contractionPromise = import("../src/contraction.ts"); return contractionPromise; } function toArrayBuffer(buf) { return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); } let geoPromise = null; async function haversineM(a, b) { if (!geoPromise) geoPromise = import("../src/geo.ts"); const { haversineM: h } = await geoPromise; return h(a, b); } /** Brute-force O(n^2) Dijkstra — plain arrays, SAME traversal law. */ function dijkstra(g, origin, goal) { const dist = new Array(g.nodeCount).fill(Infinity); const done = new Array(g.nodeCount).fill(false); dist[origin] = 0; for (;;) { let u = -1; let best = Infinity; for (let i = 0; i < g.nodeCount; i++) { if (!done[i] && dist[i] < best) { best = dist[i]; u = i; } } if (u === -1 || u === goal) return dist[goal]; done[u] = true; for (let e = g.edgeStart[u]; e < g.edgeStart[u + 1]; e++) { if ((g.edgeFlags[e] & 1) !== 0) continue; // oneway-reverse: never relaxed const v = g.edgeTarget[e]; const nd = dist[u] + g.edgeSeconds[e]; if (nd < dist[v]) dist[v] = nd; } } } /** The traversable (bit0 clear) original edge u -> v, or -1. */ function findTraversableEdge(g, u, v) { for (let e = g.edgeStart[u]; e < g.edgeStart[u + 1]; e++) { if (g.edgeTarget[e] === v && (g.edgeFlags[e] & 1) === 0) return e; } return -1; } /** Fresh sum over the returned ORIGINAL-edge path (the Pitfall 3 oracle). */ function resumSeconds(g, nodes) { let seconds = 0; for (let i = 1; i < nodes.length; i++) { const e = findTraversableEdge(g, nodes[i - 1], nodes[i]); if (e < 0) return -1; // a non-traversable hop: the caller asserts loud seconds += g.edgeSeconds[e]; } return seconds; } /* ————— in-memory fixtures (encodeGraph → decodeGraph, the ONE authority) ————— */ /** * The 25-node grid, the committed fixture's shape rebuilt in memory: * 5x5, 80 directed edge entries, one oneway pair (2->3), perimeter 60 * km/h + interior 30 km/h, three street names. Contracted through the * ONE CH core, encoded through the ONE codec. */ async function buildGridGraph() { const { encodeGraph, decodeGraph } = await loadGraph(); const { contractGraph } = await loadContraction(); const lat = []; const lng = []; for (let r = 0; r < 5; r++) { for (let c = 0; c < 5; c++) { lat.push((3150000 + r * 500) / 1e6); lng.push((101690000 + c * 500) / 1e6); } } const id = (r, c) => r * 5 + c; const edges = []; const addEdge = async (u, v, perimeter) => { const kmh = perimeter ? 60 : 30; const meters = await haversineM({ lat: lat[u], lng: lng[u] }, { lat: lat[v], lng: lng[v] }); edges.push({ from: u, to: v, seconds: meters / (kmh / 3.6) }); }; for (let r = 0; r < 5; r++) { for (let c = 0; c < 4; c++) { await addEdge(id(r, c), id(r, c + 1), r === 0 || r === 4); await addEdge(id(r, c + 1), id(r, c), r === 0 || r === 4); } } for (let c = 0; c < 5; c++) { for (let r = 0; r < 4; r++) { await addEdge(id(r, c), id(r + 1, c), c === 0 || c === 4); await addEdge(id(r + 1, c), id(r, c), c === 0 || c === 4); } } const reverse = edges.find((e) => e.from === 3 && e.to === 2); if (!reverse) throw new Error("oneway reverse edge not found — grid invariant broken"); reverse.flags = 1; if (edges.length !== 80) throw new Error(`expected 80 directed edges, built ${edges.length}`); const ch = contractGraph({ nodeCount: 25, edges }); const g = decodeGraph( encodeGraph({ lat, lng, edges, names: ["Jalan Mini 1", "Jalan Mini 2", "Jalan Persiaran Mini"], maxSpeedKmh: 60, ch, }), ); assert.ok(g, "the in-memory ARG3 grid decodes"); assert.ok(g.chShortcutCount > 0, "the contracted grid carries a hierarchy"); return g; } /** * The directed oneway fixture (the A12 fold): a directed 8-node ring * (10 s one-way hops; every reverse mirror stored with bit0 — never * relaxed), a bidirectional chord (0<->4, 15 s), and a one-way-in spur * head (node 8 -> 3: nothing reaches 8). Directed reach asymmetry is * exactly what broke the ALT four-family potential; under CH the * shortcuts inherit direction structurally. */ async function buildRingGraph() { const { encodeGraph, decodeGraph } = await loadGraph(); const { contractGraph } = await loadContraction(); const lat = []; const lng = []; for (let i = 0; i < 8; i++) { lat.push((3100000 + Math.round(400 * Math.sin((i / 8) * 2 * Math.PI))) / 1e6); lng.push((101700000 + Math.round(400 * Math.cos((i / 8) * 2 * Math.PI))) / 1e6); } lat.push(3095000 / 1e6); // node 8: the spur head lng.push(101705000 / 1e6); const edges = []; for (let i = 0; i < 8; i++) { const next = (i + 1) % 8; edges.push({ from: i, to: next, seconds: 10, flags: 0 }); // the one-way hop edges.push({ from: next, to: i, seconds: 10, flags: 1 }); // bit0: stored, never relaxed } edges.push({ from: 0, to: 4, seconds: 15, flags: 0 }); // bidirectional chord edges.push({ from: 4, to: 0, seconds: 15, flags: 0 }); edges.push({ from: 8, to: 3, seconds: 10, flags: 0 }); // one-way-in spur const ch = contractGraph({ nodeCount: 9, edges }); const g = decodeGraph( encodeGraph({ lat, lng, edges, names: ["Ring Road"], maxSpeedKmh: 60, ch, }), ); assert.ok(g, "the oneway-ring synthetic decodes"); assert.equal(g.nodeCount, 9); return g; } /** * The island fixture (the A9/A10 fold): component A is a 3x3 grid * (nodes 0-8), component B a separate 4-node path (nodes 9-12) far * apart. Cross-component pairs are unreachable — the honest miss. */ async function buildIslandGraph() { const { encodeGraph, decodeGraph } = await loadGraph(); const { contractGraph } = await loadContraction(); const lat = []; const lng = []; for (let r = 0; r < 3; r++) { for (let c = 0; c < 3; c++) { lat.push((3150000 + r * 500) / 1e6); lng.push((101690000 + c * 500) / 1e6); } } for (let i = 0; i < 4; i++) { lat.push((3200000 + i * 500) / 1e6); lng.push((101750000 + i * 500) / 1e6); } const edges = []; const link = (u, v) => { edges.push({ from: u, to: v, seconds: 10, flags: 0 }); edges.push({ from: v, to: u, seconds: 10, flags: 0 }); }; for (let r = 0; r < 3; r++) { for (let c = 0; c < 3; c++) { if (c < 2) link(r * 3 + c, r * 3 + c + 1); if (r < 2) link(r * 3 + c, (r + 1) * 3 + c); } } for (let i = 0; i < 3; i++) link(9 + i, 10 + i); const ch = contractGraph({ nodeCount: 13, edges }); const g = decodeGraph( encodeGraph({ lat, lng, edges, names: ["Island Road"], maxSpeedKmh: 60, ch, }), ); assert.ok(g, "the island synthetic decodes"); assert.equal(g.nodeCount, 13); return g; } /* ————— Q1–Q5 ————— */ test("Q1 exhaustive equivalence — the CH query matches brute-force Dijkstra on EVERY 25x25 node pair", async () => { const { routeCH } = await loadChQuery(); const g = await buildGridGraph(); let pairs = 0; let reachable = 0; for (let o = 0; o < g.nodeCount; o++) { for (let t = 0; t < g.nodeCount; t++) { pairs++; const optimal = dijkstra(g, o, t); const result = routeCH(g, o, t); if (optimal === Infinity) { assert.equal(result, null, `pair ${o}->${t}: unreachable must resolve null, never a path`); continue; } reachable++; assert.ok(result, `pair ${o}->${t}: reachable must route`); assert.ok( Math.abs(result.seconds - optimal) < 1e-6, `pair ${o}->${t}: CH cost ${result.seconds} vs Dijkstra ${optimal}`, ); assert.equal(result.nodes[0], o, `pair ${o}->${t}: path starts at origin`); assert.equal(result.nodes[result.nodes.length - 1], t, `pair ${o}->${t}: path ends at goal`); } } assert.equal(pairs, 625, "the full 25x25 sweep ran"); assert.ok(reachable > 600, `the grid is densely connected (${reachable} reachable pairs)`); }); test("Q2 directed oneway law — ring + chord + spur matches the oracle in BOTH directions; every hop is a traversable original edge", async () => { const { routeCH } = await loadChQuery(); const g = await buildRingGraph(); let pairs = 0; let reachable = 0; let crossNulls = 0; for (let o = 0; o < g.nodeCount; o++) { for (let t = 0; t < g.nodeCount; t++) { pairs++; const optimal = dijkstra(g, o, t); const result = routeCH(g, o, t); if (optimal === Infinity) { assert.equal(result, null, `pair ${o}->${t}: unreachable must resolve null`); crossNulls++; continue; } reachable++; assert.ok(result, `pair ${o}->${t}: reachable must route`); assert.ok( Math.abs(result.seconds - optimal) < 1e-6, `pair ${o}->${t}: CH cost ${result.seconds} vs Dijkstra ${optimal}`, ); // The unpacking law: every returned hop is a TRAVERSABLE original // edge — no bit0 mirror and no unpacked shortcut survives. for (let i = 1; i < result.nodes.length; i++) { assert.ok( findTraversableEdge(g, result.nodes[i - 1], result.nodes[i]) >= 0, `pair ${o}->${t}: hop ${result.nodes[i - 1]}->${result.nodes[i]} is a traversable original edge`, ); } } } assert.equal(pairs, 81, "the full 9x9 sweep ran"); assert.ok(reachable > 40, `the ring is reachable (${reachable} pairs)`); assert.ok(crossNulls >= 8, `the spur head is one-way-in (${crossNulls} pairs can never reach it)`); }); test("Q3 original-edge seconds — reported seconds equal a fresh sum over the unpacked ORIGINAL-edge path", async () => { const { routeCH } = await loadChQuery(); const g = await buildGridGraph(); // A long cross-grid pair (the route rides shortcuts) and the oneway // detour pair: both must re-sum exactly over original edges. for (const [o, t] of [[0, 24], [3, 2], [6, 19], [12, 12]]) { const result = routeCH(g, o, t); if (o === t) { assert.deepEqual(result.nodes, [o], "the trivial pair is the single node"); assert.equal(result.seconds, 0, "the trivial pair costs 0"); continue; } assert.ok(result, `${o}->${t} routes`); const fresh = resumSeconds(g, result.nodes); assert.ok(fresh >= 0, `${o}->${t}: every returned hop is a traversable original edge`); assert.ok( Math.abs(result.seconds - fresh) < 1e-6, `${o}->${t}: reported ${result.seconds} === original-edge re-sum ${fresh} (shortcut weights never enter seconds)`, ); const optimal = dijkstra(g, o, t); assert.ok(Math.abs(result.seconds - optimal) < 1e-6, `${o}->${t}: and it is the Dijkstra optimum`); // distanceM is the haversine sum over the SAME unpacked path. let meters = 0; for (let i = 1; i < result.nodes.length; i++) { const a = { lat: g.lat[result.nodes[i - 1]], lng: g.lng[result.nodes[i - 1]] }; const b = { lat: g.lat[result.nodes[i]], lng: g.lng[result.nodes[i]] }; meters += await haversineM(a, b); } assert.ok( Math.abs(result.distanceM - meters) < 1e-6, `${o}->${t}: distanceM is the haversine sum over the unpacked path`, ); } }); test("Q4 unreachable -> null — the island fold: cross pairs resolve null, internal pairs stay Dijkstra-equal", async () => { const { routeCH } = await loadChQuery(); const g = await buildIslandGraph(); let pairs = 0; let crossNulls = 0; for (let o = 0; o < g.nodeCount; o++) { for (let t = 0; t < g.nodeCount; t++) { pairs++; const optimal = dijkstra(g, o, t); const result = routeCH(g, o, t); const inA = o < 9 && t < 9; const inB = o >= 9 && t >= 9; if (!inA && !inB) { assert.equal(optimal, Infinity, `cross pair ${o}->${t}: Dijkstra agrees unreachable`); assert.equal(result, null, `cross pair ${o}->${t}: resolves null, never a fabricated path`); crossNulls++; continue; } assert.ok(result, `pair ${o}->${t} (${inA ? "A" : "B"}-internal) routes`); assert.ok( Math.abs(result.seconds - optimal) < 1e-6, `pair ${o}->${t}: CH cost ${result.seconds} vs Dijkstra ${optimal}`, ); } } assert.equal(pairs, 169, "the full 13x13 sweep ran"); assert.equal(crossNulls, 72, "every cross-component pair is a null"); }); test("Q5 structural deletion — no ALT tokens in astar.ts; landmark.ts + landmark.test.mjs ABSENT; index exports none (D-04/D-05)", () => { const src = readFileSync(path.join(here, "..", "src", "astar.ts"), "utf8"); const code = src .replace(/\/\*[\s\S]*?\*\//g, " ") .replace(/(^|[^:\\])\/\/[^\n]*/g, "$1"); for (const token of ["landmarkTo", "landmarkFrom", "cleanColumnsFor", "LANDMARK_SENTINEL"]) { assert.ok(!code.includes(token), `comment-stripped astar.ts must not contain ${token}`); } assert.equal(existsSync(path.join(here, "..", "src", "landmark.ts")), false, "landmark.ts is deleted (D-05)"); assert.equal( existsSync(path.join(here, "landmark.test.mjs")), false, "landmark.test.mjs is deleted (D-05)", ); const indexSrc = readFileSync(path.join(here, "..", "src", "index.ts"), "utf8"); const indexCode = indexSrc .replace(/\/\*[\s\S]*?\*\//g, " ") .replace(/(^|[^:\\])\/\/[^\n]*/g, "$1"); assert.ok(!indexCode.includes("landmark"), "index.ts exports no landmark symbols"); }); /* ————— G1–G3 — the in-memory ARG3 codec pins ————— */ test("G1 ARG3 round-trip — contractGraph output round-trips rank + shortcut CSR exactly through zero-copy views", async () => { const { encodeGraph, decodeGraph } = await loadGraph(); const { contractGraph } = await loadContraction(); const g0 = await buildGridGraph(); // builds through encode/decode already assert.ok(g0, "the grid decoded (G1's round-trip ran inside the builder)"); // Explicit round-trip on a fresh contraction output. const { contractGraph: contract2 } = await loadContraction(); const edges = [ { from: 0, to: 1, seconds: 1, flags: 0 }, { from: 1, to: 0, seconds: 1, flags: 0 }, { from: 0, to: 2, seconds: 1, flags: 0 }, { from: 2, to: 0, seconds: 1, flags: 0 }, ]; const ch = contract2({ nodeCount: 3, edges }); const { encodeGraph: enc, decodeGraph: dec } = await loadGraph(); const buf = enc({ lat: [3.15, 3.1505, 3.151], lng: [101.69, 101.6905, 101.691], edges, names: [], maxSpeedKmh: 60, ch, }).slice(0); const g = dec(buf); assert.ok(g, "decode succeeds"); assert.equal(g.chShortcutCount, ch.shortcutTarget.length, "the header count matches the contraction"); assert.deepEqual([...g.chRank], [...ch.rank], "rank restores exactly"); assert.deepEqual([...g.shortcutStart], [...ch.shortcutStart], "shortcutStart restores exactly"); assert.deepEqual([...g.shortcutTarget], [...ch.shortcutTarget], "shortcutTarget restores exactly"); assert.deepEqual([...g.shortcutSeconds], [...ch.shortcutSeconds], "shortcutSeconds restores exactly"); assert.deepEqual([...g.shortcutVia], [...ch.shortcutVia], "shortcutVia restores exactly"); assert.equal(g.chRank.buffer, buf, "chRank is a zero-copy view"); assert.equal(g.shortcutVia.buffer, buf, "shortcutVia is a zero-copy view"); }); test("G2 rejection vectors — ARG1 bytes -> null; a version-2 tamper -> null (D-07 one decode path)", async () => { const { decodeGraph } = await loadGraph(); // The frozen Plan 08-01 vector (the ARG1 rejection fixture). const arg1 = toArrayBuffer(readFileSync(path.join(here, "fixtures", "mini-graph-arg1.bin"))); assert.equal(decodeGraph(arg1), null, "ARG1 bytes -> null, never a throw or a fallback decode"); // A version-2 tamper of an in-memory ARG3 buffer refuses too. const { encodeGraph } = await loadGraph(); const { contractGraph } = await loadContraction(); const edges = [ { from: 0, to: 1, seconds: 1, flags: 0 }, { from: 1, to: 0, seconds: 1, flags: 0 }, { from: 0, to: 2, seconds: 1, flags: 0 }, { from: 2, to: 0, seconds: 1, flags: 0 }, ]; const buf = encodeGraph({ lat: [3.15, 3.1505, 3.151], lng: [101.69, 101.6905, 101.691], edges, names: [], maxSpeedKmh: 60, ch: contractGraph({ nodeCount: 3, edges }), }); const v2 = buf.slice(0); new DataView(v2).setUint32(4, 2, true); assert.equal(decodeGraph(v2), null, "version 2 -> null (ARG2 bytes decode to nothing — D-07)"); }); test("G3 fail-honest decode — truncated / hostile-count / chShortcutCount=0 buffers -> null BEFORE any allocation", async () => { const { decodeGraph, GRAPH_MAGIC, GRAPH_VERSION } = await loadGraph(); const { encodeGraph } = await loadGraph(); const { contractGraph } = await loadContraction(); const edges = [ { from: 0, to: 1, seconds: 1, flags: 0 }, { from: 1, to: 0, seconds: 1, flags: 0 }, { from: 0, to: 2, seconds: 1, flags: 0 }, { from: 2, to: 0, seconds: 1, flags: 0 }, ]; const ch = contractGraph({ nodeCount: 3, edges }); const buf = encodeGraph({ lat: [3.15, 3.1505, 3.151], lng: [101.69, 101.6905, 101.691], edges, names: [], maxSpeedKmh: 60, ch, }); assert.equal(decodeGraph(buf.slice(0, buf.byteLength - 1)), null, "truncated -> null"); const hostile = new ArrayBuffer(72); const dv = new DataView(hostile); dv.setUint32(0, GRAPH_MAGIC, true); dv.setUint32(4, GRAPH_VERSION, true); dv.setUint32(8, 1_000_000, true); // a million nodes on a 72-byte buffer assert.equal(decodeGraph(hostile), null, "hostile count -> null on arithmetic, no allocation"); const zeroed = buf.slice(0); new DataView(zeroed).setUint32(60, 0, true); assert.equal(decodeGraph(zeroed), null, "chShortcutCount = 0 -> null even when the size matches"); });