fix(08-05): D-03 optimality gate green — fromTable families removed from the ALT potential

Task 1: the D-03 gate script (measure-optimality-gate.mjs) reads the frozen
pre-ALT record, re-routes the same coordinate-pinned pairs on the current
ARG2 binary, and gates on seconds-equality (±0.1%) + verdict-class
preservation — never on node paths (equal-cost ties are documented behavior).

Rule-1 deviation the gate forced on its first run: routeAstar returned
SUBOPTIMAL real-graph routes (x038: 1234.6s vs the 1214.8s Dijkstra optimum;
x045: 1475.5s vs 1460.6s) — the fixture sweep could not see it. Root cause:
the fromTable bound d̂(ℓ,t) − d̂(ℓ,v) is one-directional Lipschitz (its u-side
term needs the REVERSE edge; a oneway does not provide one — the anchor
reached node 264611 126.8s faster than its own 1.49s upstream neighbour
264610), and the shared Ikeda average p = (h_f − h_b)/2 is consistent only
when every family inside h_f/h_b is two-sided. The toTable bound
d̂(v,ℓ) − d̂(t,ℓ) rides stored traversable edges in BOTH directions; it alone
joins the haversine now. Stop rule, reductions, meet probes, reconstruction:
untouched. landmarkTables/build verified EXACT (0 mismatches vs independent
integer Dijkstras, both rows, all 24 anchors).

- measure-optimality-gate.mjs: exported core + CLI guard (route-corpus
  pattern); epsilon mirror + argVersion asserts precede any measurement;
  per-pair rows, extract-drift WARNING (never a re-record), honest exit law
- astar.ts: potential() consults landmarkTo only; doc-comments carry the
  one-directional-Lipschitz law and the x038/x045 evidence
- astar.test.mjs: A11 re-pinned (landmarkFrom must NOT appear in the
  comment-stripped search — re-adding it trips the pin); A12 oneway-ring
  sweep added (directed reach asymmetry stays Dijkstra-equal on every pair)

Battery: routing 80/80, bot 126/126, both typechecks green.
D-03 gate: GATE PASS — 53/53 routed pairs equal within ±0.1% (max |Δ| 0.005%),
verdict classes preserved (6 unreachable + 41 unsnappable, 0 re-snapped).
This commit is contained in:
araharah-executor
2026-10-25 19:54:24 +08:00
parent ab0b465218
commit 149636ce0a
3 changed files with 441 additions and 46 deletions
+83 -1
View File
@@ -368,6 +368,81 @@ test("A8 structural — no `new Map(` in astar.ts (typed arrays only in the hot
assert.ok(!code.includes("new Map("), "no Map allocation — the hot loop is typed arrays");
});
/* ————— A12: oneway ring under ALT (plan 08-05) ————— */
/**
* A directed 8-node ring (10 s one-way hops; every reverse direction stored
* with bit0 — never relaxed) + a bidirectional chord (0<->4, 15 s) + a
* one-way-in spur (8->3: nothing reaches 8, so its fromTable cells are the
* 0xFFFF sentinel while its toTable cells are finite). This is the fixture
* shape the 08-05 consistency fix is ABOUT: directed reach asymmetry (the
* anchor reaches a node far faster than its upstream neighbour) is exactly
* what made the four-family potential inconsistent on the real graph — the
* D-03 gate caught it as x038/x045 suboptimality. Under the amended
* toTable-only potential EVERY pair must stay Dijkstra-equal.
*/
test("A12 oneway ring under ALT — directed reach asymmetry stays Dijkstra-equal on every pair", async () => {
const { encodeGraph, decodeGraph } = await loadGraph();
const { routeAstar } = await loadAstar();
const { LANDMARK_QUANTUM_S, landmarkTables } = await import("../src/landmark.ts");
const lat = [];
const lng = [];
// Ring + chord + spur laid out around (3.10, 101.70), integer / 1e6.
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, off-ring
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, nameId: 0 }); // the one-way hop
edges.push({ from: next, to: i, seconds: 10, flags: 1, nameId: 0 }); // bit0: stored, never relaxed
}
edges.push({ from: 0, to: 4, seconds: 15, flags: 0, nameId: 0 }); // bidirectional chord
edges.push({ from: 4, to: 0, seconds: 15, flags: 0, nameId: 0 });
edges.push({ from: 8, to: 3, seconds: 10, flags: 0, nameId: 0 }); // one-way-in spur
const anchors = new Uint32Array([0, 4]); // hand-pinned, in the ring's SCC
const { to, from } = landmarkTables(
{ nodeCount: 9, edges, quantumS: LANDMARK_QUANTUM_S },
anchors,
);
const g = decodeGraph(
encodeGraph({
lat,
lng,
edges,
names: ["Ring Road"],
maxSpeedKmh: 60,
landmarks: { quantumS: LANDMARK_QUANTUM_S, nodes: anchors, to, from },
}),
);
assert.ok(g, "the oneway-ring synthetic decodes");
assert.equal(g.nodeCount, 9, "9 nodes: ring + spur head");
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 = routeAstar(g, o, t);
if (optimal === Infinity) {
assert.equal(result, null, `pair ${o}->${t}: unreachable must resolve null`);
continue;
}
reachable++;
assert.ok(result, `pair ${o}->${t}: reachable must route`);
assert.ok(
Math.abs(result.seconds - optimal) < 1e-6,
`pair ${o}->${t}: A* cost ${result.seconds} vs Dijkstra ${optimal}`,
);
}
}
assert.equal(pairs, 81, "the full 9x9 sweep ran");
assert.ok(reachable > 60, `the ring is densely reachable (${reachable} pairs)`);
});
test("A9 ALT island — anchors pinned in component A only; EVERY pair still Dijkstra-equal", async () => {
const { routeAstar } = await loadAstar();
const g = await buildIslandFixture(Uint32Array.of(0, 8));
@@ -442,9 +517,16 @@ test("A11 structural (ALT) — astar.ts consults the landmark tables in CODE, no
.replace(/(^|[^:\\])\/\/[^\n]*/g, "$1");
// The potential provably consults Plan 08-02's ARG2 table fields — a
// comment-only mention would strip away and fail this pin.
for (const token of ["landmarkTo", "landmarkFrom", "landmarkQuantumS"]) {
for (const token of ["landmarkTo", "landmarkQuantumS"]) {
assert.ok(code.includes(token), `comment-stripped astar.ts must reference ${token} in code`);
}
// 08-05 amendment pin: the fromTable families are EXCLUDED from the
// potential — their bounds are one-directional Lipschitz (the u-side term
// needs the reverse edge; a oneway does not have one), which broke the
// shared Ikeda average's consistency and returned suboptimal real-graph
// routes (the D-03 gate's x038/x045 catch). Re-adding landmarkFrom to the
// search trips this pin and re-opens that bug.
assert.ok(!code.includes("landmarkFrom"), "comment-stripped astar.ts must NOT consult landmarkFrom (08-05 consistency law)");
// Raw u16 sentinel handling on the table families — one spelling of the
// sentinel (0xffff in code, never the decimal 65535).
assert.ok(code.includes("0xffff"), "the sentinel is compared raw (u16, before float conversion)");