Files
arah2/routing/test/fixtures/build-mini-graph.mjs
T
araharah-executor 1ce35907d5 feat(06-01): CSR graph codec + committed mini-graph fixture + RouteStep.kind (GREEN)
- graph.ts: decodeGraph/encodeGraph over the pinned little-endian CSR
  layout; header magic+version+exact byte-size validation BEFORE any view
  allocation (T-06-03/T-06-03b fail-honest null); zero-copy typed-array
  views; street-name pool with UNNAMED_NAME_ID sentinel
- header padded 60 -> 64 bytes so f64 views are 8-byte aligned — unaligned
  Float64Array throws RangeError, making plan-literal offsets incompatible
  with the plan's own zero-copy law (documented deviation; field
  order/content/endian unchanged; consumers inherit via encodeGraph)
- build-mini-graph.mjs + committed mini-graph.bin: hand-built 5x5 grid,
  25 nodes, 80 directed edges, 3 named streets, exactly ONE oneway pair,
  two speed classes (60/30 km/h) — the A* ground truth (G7 pins it)
- types.ts: RouteStep gains optional kind (8 turn kinds) — Engine A
  populates it (06-01); fixture-world steps stay empty arrays (D-10)
2026-10-24 12:07:17 +08:00

86 lines
3.5 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.
#!/usr/bin/env node
/*
* build-mini-graph.mjs — builds the committed mini-graph.bin fixture.
* ----------------------------------------------------------------------
* A MAINTAINER-TIME REPO TOOL: regenerates routing/test/fixtures/
* mini-graph.bin — the A* correctness ground truth (plan 06-01) and the
* snap/astar suites' shared fixture. NOT a runtime dependency; nothing in
* bot/src/, web/src/, or routing/src/ imports it. Tests consume the
* COMMITTED binary; only a deliberate fixture redesign re-runs this.
*
* Usage: node routing/test/fixtures/build-mini-graph.mjs
* Exit: 0 on write; throws on any invariant violation (never writes).
*
* 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)
* - three named streets: "Jalan Mini 1" (interior E-W rows),
* "Jalan Mini 2" (interior N-S columns), "Jalan Persiaran Mini" (perimeter)
* - exactly ONE oneway pair: 2 -> 3 traversable, 3 -> 2 flagged bit0
* - two speed classes: perimeter 60 km/h, interior 30 km/h
* (weights = segment meters / (kmh / 3.6), meters via haversineM)
*/
import { writeFileSync } from "node:fs";
import { encodeGraph } from "../../src/graph.ts";
import { haversineM } from "../../src/geo.ts";
const GRID = 5;
const LAT0 = 3.15;
const LNG0 = 101.69;
const STEP_DEG = 0.0005;
/** The single oneway pair (grid ids: row 0, cols 2->3). */
const ONEWAY_U = 2;
const ONEWAY_V = 3;
const NAME_MINI_1 = 0; // interior E-W rows
const NAME_MINI_2 = 1; // interior N-S columns
const NAME_PERIMETER = 2;
const names = ["Jalan Mini 1", "Jalan Mini 2", "Jalan Persiaran Mini"];
const SPEED_PERIMETER_KMH = 60;
const SPEED_INTERIOR_KMH = 30;
const lat = [];
const lng = [];
for (let r = 0; r < GRID; r++) {
for (let c = 0; c < GRID; c++) {
// 1e-6 integer grid then divide — exact decimal coordinates (the codec's
// grid law); naive `3.15 + r * 0.0005` accumulates float error.
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, nameId) {
const kmh = nameId === NAME_PERIMETER ? SPEED_PERIMETER_KMH : SPEED_INTERIOR_KMH;
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), nameId });
}
for (let r = 0; r < GRID; r++) {
for (let c = 0; c < GRID - 1; c++) {
const nameId = r === 0 || r === GRID - 1 ? NAME_PERIMETER : NAME_MINI_1;
addEdge(id(r, c), id(r, c + 1), nameId);
addEdge(id(r, c + 1), id(r, c), nameId);
}
}
for (let c = 0; c < GRID; c++) {
for (let r = 0; r < GRID - 1; r++) {
const nameId = c === 0 || c === GRID - 1 ? NAME_PERIMETER : NAME_MINI_2;
addEdge(id(r, c), id(r + 1, c), nameId);
addEdge(id(r + 1, c), id(r, c), nameId);
}
}
// Exactly ONE oneway: the reverse mirror of U->V carries flags bit0.
const reverse = edges.find((e) => e.from === ONEWAY_V && e.to === ONEWAY_U);
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 bin = encodeGraph({ lat, lng, edges, names, maxSpeedKmh: SPEED_PERIMETER_KMH });
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`);