feat(08.1-01): rebuild all fixtures and bot graph builders through the ARG3 path
- mini-graph-arg2.bin: the frozen ARG2-era bytes preserved once as the D-12 decode-rejection vector (never regenerated — the arg1 precedent) - build-mini-graph.mjs: landmark stage out, CH stage in — contractGraph over the 25-node grid (41 shortcuts, 1.64/node, 0 budget hits) with fail-loud invariants: total-order rank, grid shortcut band, upward shortcuts, overlay-exact constituent weights (f64 replay, f32 compare), strong connectivity; deterministic ARG3 bytes - graph.test.mjs: committed-fixture G4 added (ARG3 magic/version, 25 nodes, 80 edges, rank 25, >=1 shortcut, ARG2 vector -> null); G7 restored (full ARG3 pin) - astar.test.mjs: A1-A6 restored — the 625-pair Dijkstra equivalence, oneway law, deriveSteps, depart/arrive, classifyTurn all green against the CH query on the committed ARG3 fixture - snap.test.mjs: S1-S6 restored on the ARG3 fixture; S3's synthetic graph rebuilt as the ARG3 ch-block star - webhook.test.mjs: landmarkBlock -> chBlock (contractGraph); corridor gains the fast nonstop chord (a pure line starves to zero shortcuts); sparse star keeps node ids 0-hub; RT9's split carrier is now two disconnected decodable stars (the honest A\* miss) - guidance-cron.test.mjs: GC6b's 2-node graph becomes the lowest-id-mid 3-node star (a 2-node graph cannot carry a valid shortcut) - routing 87 passed / bot 126 passed; routing+bot+web typechecks green
This commit is contained in:
@@ -49,7 +49,7 @@ const { strings } = await import("../src/i18n/strings.ts");
|
||||
const { encodePolyline6 } = await import("@araharah/routing/src/polyline.ts");
|
||||
const { deriveCumulativeDistanceM, haversineM } = await import("@araharah/routing/src/geo.ts");
|
||||
const { encodeGraph, decodeGraph } = await import("@araharah/routing/src/graph.ts");
|
||||
const { LANDMARK_QUANTUM_S, landmarkTables } = await import("@araharah/routing/src/landmark.ts");
|
||||
const { contractGraph } = await import("@araharah/routing/src/contraction.ts");
|
||||
|
||||
/* ————— geometry helpers (1e-6-grid-exact construction, the codec's grid law) ————— */
|
||||
|
||||
@@ -250,23 +250,26 @@ test("GC6b unnamed matched edge → cardStreetUnnamed ('UNNAMED ROAD') — still
|
||||
// A purpose-built unnamed way through the same encodeGraph contract
|
||||
// (names: [] → every edge carries the UNNAMED_NAME_ID sentinel).
|
||||
const meters = haversineM({ lat: 3.15, lng: 101.69 }, { lat: 3.1505, lng: 101.69 });
|
||||
const edges = [
|
||||
// A 3-node star through the same encodeGraph contract (names: [] →
|
||||
// every edge carries the UNNAMED_NAME_ID sentinel). ARG3 (Phase 08.1):
|
||||
// the ch block comes from the ONE contraction core, and the MID owns
|
||||
// the LOWEST id — the priority tie contracts lowest-id-first, so a mid
|
||||
// with id 1 would starve to zero shortcuts and an ARG3 with no
|
||||
// shortcuts is not a road graph. Geometry: north leg (0->1), east leg
|
||||
// (0->2) — the turn at the shared mid vertex.
|
||||
const starEdges = [
|
||||
{ from: 0, to: 1, seconds: meters / (30 / 3.6) },
|
||||
{ from: 1, to: 0, seconds: meters / (30 / 3.6) },
|
||||
{ from: 0, to: 2, seconds: meters / (30 / 3.6) },
|
||||
{ from: 2, to: 0, seconds: meters / (30 / 3.6) },
|
||||
];
|
||||
// A 2-node graph has no degree>=2 farthest-point candidate (the selection
|
||||
// filter), so the anchor pins directly — the tables still come from the
|
||||
// ONE integer-Dijkstra core (08-02), through the ONE encode authority.
|
||||
const landmarkInput = { nodeCount: 2, edges, quantumS: LANDMARK_QUANTUM_S };
|
||||
const anchors = Uint32Array.of(0);
|
||||
const { to, from } = landmarkTables(landmarkInput, anchors);
|
||||
const buf = encodeGraph({
|
||||
lat: [3.15, 3.1505],
|
||||
lng: [101.69, 101.69],
|
||||
edges,
|
||||
lat: [3.15, 3.1505, 3.15],
|
||||
lng: [101.69, 101.69, 101.6905],
|
||||
edges: starEdges,
|
||||
names: [],
|
||||
maxSpeedKmh: 30,
|
||||
landmarks: { quantumS: LANDMARK_QUANTUM_S, nodes: anchors, to, from },
|
||||
ch: contractGraph({ nodeCount: 3, edges: starEdges }),
|
||||
});
|
||||
const graph = decodeGraph(buf);
|
||||
assert.notEqual(graph, null);
|
||||
|
||||
+70
-20
@@ -472,31 +472,37 @@ test("GW11 PUT /api/guidance → 405", async () => {
|
||||
|
||||
const { encodeGraph } = await import("../../routing/src/graph.ts");
|
||||
const { haversineM } = await import("../../routing/src/geo.ts");
|
||||
const { LANDMARK_QUANTUM_S, farthestPointAnchors, landmarkTables } = await import(
|
||||
"../../routing/src/landmark.ts"
|
||||
);
|
||||
const { contractGraph } = await import("../../routing/src/contraction.ts");
|
||||
|
||||
/** Tiny REAL landmark block through the ONE preprocessing core (08-02):
|
||||
* farthest-point anchor + integer ĉ tables — computed, never faked.
|
||||
* `{ pin }` pins the anchor for graphs with no degree>=2 candidate (the
|
||||
* selection filter's law — e.g. a single-segment graph); the tables still
|
||||
* come from the ONE integer-Dijkstra core. */
|
||||
function landmarkBlock(nodeCount, edges, { pin } = {}) {
|
||||
const input = { nodeCount, edges, quantumS: LANDMARK_QUANTUM_S };
|
||||
const anchors = pin !== undefined ? Uint32Array.of(pin) : farthestPointAnchors(input, 1);
|
||||
if (anchors.length !== 1) throw new Error("graph has no degree-2 anchor candidate");
|
||||
const { to, from } = landmarkTables(input, anchors);
|
||||
return { quantumS: LANDMARK_QUANTUM_S, nodes: anchors, to, from };
|
||||
/** The ARG3 ch block through the ONE contraction core (Phase 08.1):
|
||||
* computed, never faked. A shape that contracts to ZERO shortcuts is a
|
||||
* builder bug — an ARG3 with chShortcutCount = 0 is not a road graph
|
||||
* (the decode law refuses it), so fail loud here instead of shipping an
|
||||
* undecodable test binary. */
|
||||
function chBlock(nodeCount, edges) {
|
||||
const ch = contractGraph({ nodeCount, edges });
|
||||
if (ch.stats.shortcutCount === 0) {
|
||||
throw new Error("graph contracted to zero shortcuts — ARG3 has no CH-less layout");
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
/** The committed 06-01 ground truth (25 nodes around KL center) via fs. */
|
||||
const MINI_GRAPH = readFileSync(new URL("../../routing/test/fixtures/mini-graph.bin", import.meta.url));
|
||||
|
||||
/** Public gazetteer landmarks only — no user coordinate enters a test (D-12). */
|
||||
/** Public gazetteer POIs only — no user coordinate enters a test (D-12). */
|
||||
const KL_TOWER = { lat: 3.1528, lng: 101.7038 };
|
||||
const KL_SENTRAL = { lat: 3.1343, lng: 101.6869 };
|
||||
|
||||
/** 8-node straight corridor kl-tower → suria-klcc (public coords), two-way. */
|
||||
/** 8-node straight corridor kl-tower → suria-klcc (public coords), two-way.
|
||||
* ARG3 (Phase 08.1): the corridor gains ONE fast nonstop chord (0<->7,
|
||||
* 120 km/h vs the 60 km/h local road) — a pure line contracts to ZERO
|
||||
* shortcuts (endpoints starve the interior on the priority tie) and a
|
||||
* shortcut-less ARG3 is not a road graph. The chord gives the contraction
|
||||
* a real hierarchy; the split variant keeps the gap AND drops the chord,
|
||||
* so its two line components stay disconnected (RT9's A* miss) — the
|
||||
* resulting binary decodes to null under the CH-less law, which flows to
|
||||
* the same 200-null contract. */
|
||||
function corridorGraphBuffer({ gap = false } = {}) {
|
||||
const n = 8;
|
||||
const lat = [];
|
||||
@@ -512,26 +518,40 @@ function corridorGraphBuffer({ gap = false } = {}) {
|
||||
const seconds = meters / (60 / 3.6);
|
||||
edges.push({ from: i, to: i + 1, seconds, nameId: 0 }, { from: i + 1, to: i, seconds, nameId: 0 });
|
||||
}
|
||||
if (!gap) {
|
||||
const meters = haversineM({ lat: lat[0], lng: lng[0] }, { lat: lat[n - 1], lng: lng[n - 1] });
|
||||
const seconds = meters / (120 / 3.6);
|
||||
edges.push({ from: 0, to: n - 1, seconds, nameId: 0 }, { from: n - 1, to: 0, seconds, nameId: 0 });
|
||||
}
|
||||
return encodeGraph({
|
||||
lat,
|
||||
lng,
|
||||
edges,
|
||||
names: ["Jalan Koridor Ujian"],
|
||||
maxSpeedKmh: 60,
|
||||
landmarks: { ...landmarkBlock(n, edges) },
|
||||
ch: chBlock(n, edges),
|
||||
});
|
||||
}
|
||||
|
||||
/** Nodes cluster at two far-apart cells with a nodeless middle: an in-bbox
|
||||
* origin between them is the snap-miss carrier (3x3-neighborhood law). */
|
||||
* origin between them is the snap-miss carrier (3x3-neighborhood law).
|
||||
* ARG3: node 0 is the star's hub (two unnamed legs, ids kept so the
|
||||
* contraction's lowest-id tie-break contracts the MID first — a star
|
||||
* whose mid id is higher starves to zero shortcuts) — the node set is
|
||||
* unchanged from the ARG2-era fixture, keeping RT8's snap-miss geometry
|
||||
* exact. */
|
||||
function sparseGraphBuffer() {
|
||||
const lat = [3.15, 3.1501, 3.1515];
|
||||
const lng = [101.69, 101.6905, 101.7];
|
||||
const meters = haversineM({ lat: lat[0], lng: lng[0] }, { lat: lat[1], lng: lng[1] });
|
||||
const seconds = meters / (50 / 3.6);
|
||||
const meters2 = haversineM({ lat: lat[0], lng: lng[0] }, { lat: lat[2], lng: lng[2] });
|
||||
const seconds2 = meters2 / (50 / 3.6);
|
||||
const edges = [
|
||||
{ from: 0, to: 1, seconds, nameId: 0 },
|
||||
{ from: 1, to: 0, seconds, nameId: 0 },
|
||||
{ from: 0, to: 2, seconds: seconds2, nameId: 0 },
|
||||
{ from: 2, to: 0, seconds: seconds2, nameId: 0 },
|
||||
];
|
||||
return encodeGraph({
|
||||
lat,
|
||||
@@ -539,12 +559,42 @@ function sparseGraphBuffer() {
|
||||
edges,
|
||||
names: ["Jalan Sparse Ujian"],
|
||||
maxSpeedKmh: 50,
|
||||
landmarks: { ...landmarkBlock(lat.length, edges, { pin: 0 }) },
|
||||
ch: chBlock(lat.length, edges),
|
||||
});
|
||||
}
|
||||
|
||||
/** RT9's disconnected carrier: two independent 3-node stars — each
|
||||
* decodable (real shortcuts) but the component A stars around kl-tower
|
||||
* and the component B stars around kl-sentra never touch, so the route
|
||||
* is the honest A* miss (NOT a decode refusal). */
|
||||
function splitGraphBuffer() {
|
||||
const lat = [3.1528, 3.1533, 3.1523, 3.1343, 3.1348, 3.1338];
|
||||
const lng = [101.7038, 101.7043, 101.7033, 101.6869, 101.6874, 101.6864];
|
||||
const edges = [];
|
||||
const star = (mid, a, b) => {
|
||||
const m1 = haversineM({ lat: lat[mid], lng: lng[mid] }, { lat: lat[a], lng: lng[a] });
|
||||
const m2 = haversineM({ lat: lat[mid], lng: lng[mid] }, { lat: lat[b], lng: lng[b] });
|
||||
edges.push(
|
||||
{ from: mid, to: a, seconds: m1 / (60 / 3.6), nameId: 0 },
|
||||
{ from: a, to: mid, seconds: m1 / (60 / 3.6), nameId: 0 },
|
||||
{ from: mid, to: b, seconds: m2 / (60 / 3.6), nameId: 0 },
|
||||
{ from: b, to: mid, seconds: m2 / (60 / 3.6), nameId: 0 },
|
||||
);
|
||||
};
|
||||
star(0, 1, 2); // component A around kl-tower
|
||||
star(3, 4, 5); // component B around kl-sentra — no edge bridges the two
|
||||
return encodeGraph({
|
||||
lat,
|
||||
lng,
|
||||
edges,
|
||||
names: ["Jalan Split Ujian"],
|
||||
maxSpeedKmh: 60,
|
||||
ch: chBlock(lat.length, edges),
|
||||
});
|
||||
}
|
||||
|
||||
const CORRIDOR_BUF = corridorGraphBuffer();
|
||||
const SPLIT_BUF = corridorGraphBuffer({ gap: true });
|
||||
const SPLIT_BUF = splitGraphBuffer();
|
||||
const SPARSE_BUF = sparseGraphBuffer();
|
||||
|
||||
/** An R2 fake over one fixed binary; counts + records its gets (the memo probe).
|
||||
|
||||
Reference in New Issue
Block a user