fix(08): WR-01 — oracle-sweep optimality compare is symmetric (|Δ| both directions)

- sweepOracle: Math.abs(deltaS) > ORACLE_EPSILON_S replaces the one-sided
  'seconds > optimal + eps' check — a result FASTER than the Dijkstra
  oracle (the traversal-law-break signature, e.g. a oneway relaxed) is
  now a violation, stamped ORACLE-MISMATCH, never a pass
- worstDeltaPct tracks MAGNITUDE not sign — a -40% law break moves it
- pass gate: suboptimal === 0 && oracleMismatch === 0 && classBreaks === 0
- header gate description + exit codes updated: the check is |Δ| in both
  directions; GATE FAIL names both violation classes; GATE PASS reads
  0/N |Δ| violations
- row-level signed Δ printout unchanged (sign/direction reporting kept)
This commit is contained in:
araharah-executor
2026-10-25 22:35:37 +08:00
parent 97d6e35c3a
commit 11e55c11bc
+39 -18
View File
@@ -33,9 +33,15 @@
* if the raw draw runs out first the run fails honestly. Same binary + * if the raw draw runs out first the run fails honestly. Same binary +
* seed ⇒ the same compared set, always. * seed ⇒ the same compared set, always.
* *
* Gate: 0 suboptimal pairs. Equal-cost ties pass (seconds only, never node * Gate: 0 |Δ| pairs — the compare is SYMMETRIC (08-review WR-01): the
* paths — the D-03 tie law); null-vs-null (both sides unreachable) passes; * oracle is the ground truth in BOTH directions. A result slower than the
* a verdict-class break (one side routes, the other does not) is a * oracle stamps SUBOPTIMAL; a result FASTER than the oracle stamps
* ORACLE-MISMATCH — the traversal-law-break signature (e.g. edgeFlags
* bit0 relaxed would cut a oneway and come in BELOW the legal optimum),
* which the original one-sided `seconds > optimal + ε` check wrongly
* stamped pass. Equal-cost ties pass (seconds only, never node paths —
* the D-03 tie law); null-vs-null (both sides unreachable) passes; a
* verdict-class break (one side routes, the other does not) is a
* violation. * violation.
* *
* Honest-skip law: the real binary is gitignored — when it is absent this * Honest-skip law: the real binary is gitignored — when it is absent this
@@ -48,8 +54,9 @@
* Usage: * Usage:
* node routing/scripts/measure-astar-oracle-sweep.mjs * node routing/scripts/measure-astar-oracle-sweep.mjs
* *
* Exit codes: 0 sweep passed (or the honest skip); 1 any suboptimal / * Exit codes: 0 sweep passed (or the honest skip); 1 any |Δ| violation
* class-break violation, or an undecodable binary. * (SUBOPTIMAL or ORACLE-MISMATCH) or class-break violation, or an
* undecodable binary.
*/ */
import { existsSync, readFileSync } from "node:fs"; import { existsSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path"; import { dirname, resolve } from "node:path";
@@ -188,12 +195,13 @@ function dijkstraSeconds(graph, origin, goal) {
* @param graph decoded RoadGraph (the current binary) * @param graph decoded RoadGraph (the current binary)
* @param pairs generateCrossMetroPairs output (fresh, seeded) * @param pairs generateCrossMetroPairs output (fresh, seeded)
* @param opts { snap, astar, oracle } — the live modules + the oracle * @param opts { snap, astar, oracle } — the live modules + the oracle
* @returns {{ rows: Array<{ pairId, seconds: number|null, oracleSeconds: number|null, deltaPct: number|null, verdict: "pass"|"SUBOPTIMAL"|"unreachable"|"unsnappable"|"class-break" }>, suboptimal: number, classBreaks: number, routed: number, unreachable: number, unsnappable: number, worstDeltaPct: number, pass: boolean }} * @returns {{ rows: Array<{ pairId, seconds: number|null, oracleSeconds: number|null, deltaPct: number|null, verdict: "pass"|"SUBOPTIMAL"|"ORACLE-MISMATCH"|"unreachable"|"unsnappable"|"class-break" }>, suboptimal: number, oracleMismatch: number, classBreaks: number, routed: number, unreachable: number, unsnappable: number, worstDeltaPct: number, pass: boolean }}
*/ */
export function sweepOracle(graph, pairs, opts) { export function sweepOracle(graph, pairs, opts) {
const { snap, astar, oracle } = opts; const { snap, astar, oracle } = opts;
const rows = []; const rows = [];
let suboptimal = 0; let suboptimal = 0;
let oracleMismatch = 0;
let classBreaks = 0; let classBreaks = 0;
let routed = 0; let routed = 0;
let unreachable = 0; let unreachable = 0;
@@ -227,21 +235,30 @@ export function sweepOracle(graph, pairs, opts) {
continue; continue;
} }
routed += 1; routed += 1;
const deltaPct = ((result.seconds - optimal) / optimal) * 100; // SYMMETRIC compare (08-review WR-01): |Δ| beyond the epsilon is a
const suboptimalHere = result.seconds > optimal + ORACLE_EPSILON_S; // violation in BOTH directions. A FASTER-than-oracle result is not a
if (suboptimalHere) suboptimal += 1; // pass — it is the traversal-law-break signature (e.g. a oneway
if (deltaPct > worstDeltaPct) worstDeltaPct = deltaPct; // relaxed), labeled ORACLE-MISMATCH for a distinct escalation.
const deltaS = result.seconds - optimal;
const deltaPct = (deltaS / optimal) * 100;
const violates = Math.abs(deltaS) > ORACLE_EPSILON_S;
if (violates) {
if (deltaS > 0) suboptimal += 1;
else oracleMismatch += 1;
}
// Track MAGNITUDE, not sign: a -40% law break must move worstDeltaPct.
if (Math.abs(deltaPct) > Math.abs(worstDeltaPct)) worstDeltaPct = deltaPct;
rows.push({ rows.push({
pairId: pair.pairId, pairId: pair.pairId,
seconds: result.seconds, seconds: result.seconds,
oracleSeconds: optimal, oracleSeconds: optimal,
deltaPct, deltaPct,
verdict: suboptimalHere ? "SUBOPTIMAL" : "pass", verdict: !violates ? "pass" : deltaS > 0 ? "SUBOPTIMAL" : "ORACLE-MISMATCH",
}); });
} }
const pass = suboptimal === 0 && classBreaks === 0; const pass = suboptimal === 0 && oracleMismatch === 0 && classBreaks === 0;
return { rows, suboptimal, classBreaks, routed, unreachable, unsnappable, worstDeltaPct, pass }; return { rows, suboptimal, oracleMismatch, classBreaks, routed, unreachable, unsnappable, worstDeltaPct, pass };
} }
/* ————— CLI reporter ————— */ /* ————— CLI reporter ————— */
@@ -306,20 +323,24 @@ async function main() {
const delta = row.deltaPct === null ? "Δ —" : `Δ ${row.deltaPct >= 0 ? "+" : ""}${row.deltaPct.toFixed(3)}%`; const delta = row.deltaPct === null ? "Δ —" : `Δ ${row.deltaPct >= 0 ? "+" : ""}${row.deltaPct.toFixed(3)}%`;
console.log(` ${row.pairId}: astar ${mine} vs oracle ${truth} (${delta}) ${row.verdict}`); console.log(` ${row.pairId}: astar ${mine} vs oracle ${truth} (${delta}) ${row.verdict}`);
} }
const worst = results.worstDeltaPct;
console.log( console.log(
`summary: ${results.routed} routed (worst Δ +${results.worstDeltaPct.toFixed(3)}%), ` + `summary: ${results.routed} routed (worst |Δ| ${worst >= 0 ? "+" : ""}${worst.toFixed(3)}%), ` +
`${results.unreachable} unreachable, ${results.unsnappable} unsnappable, ` + `${results.unreachable} unreachable, ${results.unsnappable} unsnappable, ` +
`${results.suboptimal} SUBOPTIMAL, ${results.classBreaks} class-breaks`, `${results.suboptimal} SUBOPTIMAL, ${results.oracleMismatch} ORACLE-MISMATCH, ` +
`${results.classBreaks} class-breaks`,
); );
if (!results.pass) { if (!results.pass) {
console.error( console.error(
`GATE FAIL: ${results.suboptimal} suboptimal + ${results.classBreaks} class-break pair(s) across ${results.rows.length} fresh pairs — ` + `GATE FAIL: ${results.suboptimal} SUBOPTIMAL + ${results.oracleMismatch} ORACLE-MISMATCH + ` +
`routeAstar seconds must equal the Dijkstra oracle (gate: 0 suboptimal). Escalate: this is RC-1's signature.`, `${results.classBreaks} class-break pair(s) across ${results.rows.length} fresh pairs — ` +
`routeAstar seconds must equal the Dijkstra oracle in BOTH directions (gate: 0 |Δ| violations). ` +
`An ORACLE-MISMATCH (faster than the oracle) is a traversal-law break; a SUBOPTIMAL is RC-1's signature. Escalate.`,
); );
process.exit(1); process.exit(1);
} }
console.log( console.log(
`GATE PASS: 0/${results.rows.length} suboptimal — shipped routeAstar equals the brute-force oracle on every fresh pair`, `GATE PASS: 0/${results.rows.length} |Δ| violations — shipped routeAstar equals the brute-force oracle in both directions on every fresh pair`,
); );
process.exit(0); process.exit(0);
} }