feat(08-02): ARG2 codec — one version, one decode path (G-suite GREEN)

graph.ts versioned IN PLACE (no second codec): GRAPH_MAGIC 0x41524732,
GRAPH_VERSION 2, HEADER_SIZE 72 (72%8==0 keeps f64 views aligned);
header gains landmarkCount u32 @60 (the old pad), landmarkQuantumS f32
@64, reserved u32 @68; the landmark suffix (u16 toTable, u16 fromTable,
u32 landmarkNodes) extends layoutFor's total arithmetic — exact-size
law covers the variable suffix BEFORE any view allocation (T-08-01).
D-01 fail-honest: ARG1 bytes / version!=2 / landmarkCount=0 / hostile
quantum / size mismatch all decode null (committed mini-graph-arg1.bin
is the standing rejection vector). GraphInput.landmarks REQUIRED and
loud-validated (T-08-04). RoadGraph gains the five landmark fields
(Plan 08-03's ALT potentials consume them).

Fixture regenerated as ARG2 through the SAME encode authority: the
builder gains the tiny real landmark stage (L=2, anchors [0,18],
quantum 0.1s, coverage 1.0 — identical bytes every re-run).

Rule-3 call-site updates forced by the required-landmarks law (missed
by the plan's files list): astar.test A3 + snap.test S3 synthetics
carry inert hand landmark blocks. Fixture regen pulled forward from
task 3 so this commit's own battery is green (74 passed, 0 failed).
This commit is contained in:
araharah-executor
2026-10-25 18:48:23 +08:00
parent 34d96b0cdb
commit 32a00b5b0b
5 changed files with 206 additions and 24 deletions
+43 -2
View File
@@ -16,6 +16,12 @@
* Usage: node routing/test/fixtures/build-mini-graph.mjs
* Exit: 0 on write; throws on any invariant violation (never writes).
*
* ARG2 (plan 08-02): the fixture carries a tiny real landmark stage —
* L=2 anchors via farthestPointAnchors + quantized c-metric tables via
* landmarkTables (the SAME pure module the real build script uses), passed
* through the ONE encodeGraph authority. Deterministic: identical output
* bytes on every re-run.
*
* Design (pinned by 06-01): a hand-built 5x5 grid in a ~0.002 deg x 0.002 deg
* bbox near KL center (lat 3.1500–3.1520, lng 101.6900–101.6920):
* - 25 nodes, 80 directed edge entries (20 H + 20 V segments, both ways)
@@ -28,6 +34,7 @@
import { writeFileSync } from "node:fs";
import { encodeGraph } from "../../src/graph.ts";
import { haversineM } from "../../src/geo.ts";
import { LANDMARK_QUANTUM_S, farthestPointAnchors, landmarkTables, landmarkCoverage } from "../../src/landmark.ts";
const GRID = 5;
const LAT0 = 3.15;
@@ -85,6 +92,40 @@ reverse.flags = 1;
if (edges.length !== 80) throw new Error(`expected 80 directed edges, built ${edges.length}`);
const bin = encodeGraph({ lat, lng, edges, names, maxSpeedKmh: SPEED_PERIMETER_KMH });
// LANDMARK STAGE (plan 08-02): the fixture regenerates as ARG2 THROUGH the
// one encode authority — the same farthestPointAnchors + landmarkTables the
// real build script (08-04) will run, at a tiny L. Fail-loud invariants
// before any write (the `edges.length !== 80` style).
const LANDMARK_COUNT = 2; // tiny L: the 5x5 grid needs exactly 2 well-spread anchors
const landmarkInput = { nodeCount: GRID * GRID, edges, quantumS: LANDMARK_QUANTUM_S };
const anchors = farthestPointAnchors(landmarkInput, LANDMARK_COUNT);
const { to, from } = landmarkTables(landmarkInput, anchors);
if (anchors.length !== LANDMARK_COUNT) throw new Error(`expected ${LANDMARK_COUNT} anchors, got ${anchors.length}`);
if (to.length !== LANDMARK_COUNT * GRID * GRID || from.length !== LANDMARK_COUNT * GRID * GRID) {
throw new Error("landmark tables are not L*N row-major");
}
for (let l = 0; l < anchors.length; l++) {
const a = anchors[l];
if (to[l * GRID * GRID + a] !== 0 || from[l * GRID * GRID + a] !== 0) {
throw new Error(`anchor ${l} (node ${a}) own cells are not 0 — table law broken`);
}
}
const coverage = landmarkCoverage(to, anchors, GRID * GRID);
if (coverage.some((c) => c !== 1)) {
throw new Error(`the grid is strongly connected — coverage must be 1.0, got ${coverage.join(", ")}`);
}
const bin = encodeGraph({
lat,
lng,
edges,
names,
maxSpeedKmh: SPEED_PERIMETER_KMH,
landmarks: { quantumS: LANDMARK_QUANTUM_S, nodes: anchors, to, from },
});
writeFileSync(new URL("./mini-graph.bin", import.meta.url), Buffer.from(bin));
console.log(`mini-graph.bin: ${GRID * GRID} nodes, ${edges.length} edges, 3 names, ${bin.byteLength} B`);
console.log(
`mini-graph.bin: ${GRID * GRID} nodes, ${edges.length} edges, 3 names, ` +
`L=${LANDMARK_COUNT} landmarks (nodes ${[...anchors].join(",")}), quantum ${LANDMARK_QUANTUM_S}s, ` +
`coverage ${coverage.map((c) => c.toFixed(2)).join("/")}, ${bin.byteLength} B`,
);