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:
araharah-executor
2026-10-26 07:30:15 +08:00
parent 06624bb688
commit 4ca7a71d15
8 changed files with 524 additions and 132 deletions
+78 -12
View File
@@ -31,6 +31,9 @@
* G3 bad version -> null — ARG1 fixture bytes -> null (D-01 one-path
* law); version 1, 2 and 4 tampers -> null
* (the accepted version is 3)
* G4 committed fixture — mini-graph.bin (ARG3) is 25 nodes / 80
* edges / rank 25 / >= 1 shortcut; the frozen
* mini-graph-arg2.bin decodes to null (D-12)
* G4 truncated/hostile -> null — byte-length ≠ header-declared size
* (incl. huge-count header on a tiny buffer,
* T-06-03b: size check BEFORE view allocation)
@@ -38,10 +41,12 @@
* flipping an input byte flips the decoded view
* G6 name pool — nameAt(0) === "Jalan Mini 1";
* nameAt(UNNAMED_NAME_ID) === undefined
* G7 committed fixture — SUSPENDED until task 3 regenerates
* mini-graph.bin through the ARG3 encode path
* (the committed bytes are still ARG2 here);
* task 3 re-pins this case in place
* G7 committed fixture — mini-graph.bin (ARG3, regenerated through
* the ONE encode authority) decodes: 25 nodes,
* 80 edges, 3 names, maxSpeed 60, exactly ONE
* oneway flag, a total-order rank, >= 1
* upward shortcut; the frozen ARG2 vector
* decodes to null
* G8 exported constants — GRAPH_MAGIC === 0x41524733, GRAPH_VERSION === 3
* G9 table round-trip — the CH fields restore exactly; the CH arrays
* are zero-copy views over the input buffer
@@ -164,9 +169,12 @@ test("G2 bad magic -> null — a tampered header never half-decodes (T-06-03)",
test("G3 bad version -> null — ARG1 fixture bytes refuse; version 1, 2 and 4 tampers to null (D-01 one-path law)", async () => {
const { encodeGraph, decodeGraph } = await loadGraph();
// The frozen Plan 08-01 vector: REAL ARG1 bytes decode to null — the
// The frozen rejection vectors: REAL ARG1 bytes decode to null — the
// Phase 6 router-pending state covers every pre-ARG3 binary (D-01).
// There is exactly one decode path; no v1/v2 fallback branch exists.
// (mini-graph-arg2.bin — the frozen ARG2-era bytes, D-12 — is pinned in
// the committed-fixture cases G4/G7; the version-2 tamper here covers
// the same law in memory.)
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");
const buf = encodeGraph(syntheticInput());
@@ -181,6 +189,26 @@ test("G3 bad version -> null — ARG1 fixture bytes refuse; version 1, 2 and 4 t
assert.equal(decodeGraph(v4), null, "version 4 -> null (the accepted version is 3)");
});
test("G4 committed ARG3 fixture — 25 nodes / 80 edges / rank 25 / >=1 shortcut; the preserved ARG2 vector decodes to null", async () => {
const { decodeGraph, GRAPH_MAGIC, GRAPH_VERSION } = await loadGraph();
// The committed ground truth, read as bytes: ARG3 header fields only.
const bin = readFileSync(path.join(here, "fixtures", "mini-graph.bin"));
assert.equal(bin.readUInt32LE(0), GRAPH_MAGIC, "the committed fixture is ARG3-magic");
assert.equal(bin.readUInt32LE(4), GRAPH_VERSION, "the committed fixture is version 3");
assert.equal(bin.readUInt32LE(8), 25, "25 nodes");
assert.equal(bin.readUInt32LE(12), 80, "80 directed edges");
assert.ok(bin.readUInt32LE(60) >= 1, "the committed hierarchy carries at least one shortcut");
// Decoding sees the rank table's length too.
const g = decodeGraph(toArrayBuffer(bin));
assert.ok(g, "the committed fixture decodes as ARG3");
assert.equal(g.chRank.length, 25, "one rank per node");
assert.equal(g.chShortcutCount, bin.readUInt32LE(60), "the header count agrees with the decoded graph");
// The preserved ARG2-era bytes (frozen before this fixture's
// regeneration — D-12) decode to null: one decode path, no fallback.
const arg2 = toArrayBuffer(readFileSync(path.join(here, "fixtures", "mini-graph-arg2.bin")));
assert.equal(decodeGraph(arg2), null, "ARG2 bytes -> null (the D-07 rejection vector)");
});
test("G4 truncated + hostile-size buffer -> null — exact byte-size check BEFORE any view allocation (T-06-03b)", async () => {
const { encodeGraph, decodeGraph } = await loadGraph();
const buf = encodeGraph(syntheticInput());
@@ -227,13 +255,51 @@ test("G6 name pool — nameAt(0) resolves the first pool name; the unnamed senti
assert.equal(g.nameAt(999), undefined, "out-of-range id -> undefined");
});
test("G7 committed ARG3 fixture — SUSPENDED until task 3 regenerates mini-graph.bin through the ARG3 encode path", async () => {
// The committed mini-graph.bin is still ARG2 at this point in the plan
// (task 2 flips the codec; task 3 regenerates the fixture through the
// ONE encode authority and re-pins this case in place, together with
// the preserved mini-graph-arg2.bin rejection vector). Documented
// no-op — the ONLY suspended case in this suite.
assert.ok(true, "suspended: deferred to task 3 (fixture regeneration)");
test("G7 committed ARG3 fixture — decodes to 25 nodes / 80 edges / 3 names / a real hierarchy, maxSpeed 60, ONE oneway", async () => {
const { decodeGraph } = await loadGraph();
const bin = toArrayBuffer(readFileSync(path.join(here, "fixtures", "mini-graph.bin")));
assert.ok(bin.byteLength > 1000, `the committed fixture is substantial (${bin.byteLength} B)`);
const g = decodeGraph(bin);
assert.ok(g, "the committed fixture decodes cleanly (ARG3 — regenerated through the ONE encode authority, task 3)");
assert.equal(g.nodeCount, 25, "5x5 grid");
assert.equal(g.edgeCount, 80, "20 H + 20 V segments, both directions");
assert.equal(g.nameCount, 3, "Jalan Mini 1 / Jalan Mini 2 / Jalan Persiaran Mini");
assert.equal(g.nameAt(0), "Jalan Mini 1");
assert.equal(g.nameAt(2), "Jalan Persiaran Mini");
assert.equal(g.maxSpeedKmh, 60, "the perimeter speed class is the header max");
// bbox of the hand-built grid (lat 3.1500–3.1520, lng 101.6900–101.6920)
assert.deepEqual(g.bbox, { minLat: 3.15, minLng: 101.69, maxLat: 3.152, maxLng: 101.692 });
let flagged = 0;
for (let e = 0; e < g.edgeCount; e++) if ((g.edgeFlags[e] & 1) !== 0) flagged++;
assert.equal(flagged, 1, "exactly ONE oneway-reverse edge in the whole fixture");
// both speed classes present: perimeter ~55.6 m / 16.67 ≈ 3.3 s; interior ≈ 6.7 s
let sawFast = false;
let sawSlow = false;
for (let e = 0; e < g.edgeCount; e++) {
if (g.edgeSeconds[e] < 5) sawFast = true;
if (g.edgeSeconds[e] > 5) sawSlow = true;
}
assert.ok(sawFast && sawSlow, "both speed classes (60 and 30 km/h) exist in the weights");
// ARG3 CH laws (Phase 08.1, D-12): the fixture regenerates THROUGH the
// encode authority with the tiny real contraction stage (S = 41 on the
// 5x5 grid — the healthy ~1.6/node band).
assert.equal(g.chRank.length, 25, "one rank per node");
assert.equal(g.shortcutStart.length, 26, "CSR row pointers: nodeCount + 1");
assert.equal(g.shortcutTarget.length, g.chShortcutCount, "shortcut CSR lengths agree with the header");
assert.equal(g.shortcutSeconds.length, g.chShortcutCount, "shortcut seconds length agrees");
assert.equal(g.shortcutVia.length, g.chShortcutCount, "shortcut via length agrees");
assert.ok(g.chShortcutCount >= 1, "the committed hierarchy carries at least one shortcut");
// Rank is a total order (Pitfall 9) and the shortcuts are upward.
assert.equal(new Set(g.chRank).size, 25, "ranks are DISTINCT");
for (let u = 0; u < g.nodeCount; u++) {
for (let s = g.shortcutStart[u]; s < g.shortcutStart[u + 1]; s++) {
const via = g.shortcutVia[s];
assert.ok(g.chRank[u] > g.chRank[via] && g.chRank[g.shortcutTarget[s]] > g.chRank[via], `shortcut ${u}->${g.shortcutTarget[s]} is upward against via ${via}`);
}
}
// The preserved ARG2-era bytes decode to null (D-12's rejection vector).
const arg2 = toArrayBuffer(readFileSync(path.join(here, "fixtures", "mini-graph-arg2.bin")));
assert.equal(decodeGraph(arg2), null, "ARG2 bytes -> null (the D-07 rejection vector)");
});
test("G8 exported constants — GRAPH_MAGIC === 0x41524733 ('ARG3'), GRAPH_VERSION === 3", async () => {