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:
@@ -33,9 +33,15 @@
|
||||
* if the raw draw runs out first the run fails honestly. Same binary +
|
||||
* seed ⇒ the same compared set, always.
|
||||
*
|
||||
* Gate: 0 suboptimal pairs. 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
|
||||
* Gate: 0 |Δ| pairs — the compare is SYMMETRIC (08-review WR-01): the
|
||||
* oracle is the ground truth in BOTH directions. A result slower than the
|
||||
* 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.
|
||||
*
|
||||
* Honest-skip law: the real binary is gitignored — when it is absent this
|
||||
@@ -48,8 +54,9 @@
|
||||
* Usage:
|
||||
* node routing/scripts/measure-astar-oracle-sweep.mjs
|
||||
*
|
||||
* Exit codes: 0 sweep passed (or the honest skip); 1 any suboptimal /
|
||||
* class-break violation, or an undecodable binary.
|
||||
* Exit codes: 0 sweep passed (or the honest skip); 1 any |Δ| violation
|
||||
* (SUBOPTIMAL or ORACLE-MISMATCH) or class-break violation, or an
|
||||
* undecodable binary.
|
||||
*/
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
@@ -188,12 +195,13 @@ function dijkstraSeconds(graph, origin, goal) {
|
||||
* @param graph decoded RoadGraph (the current binary)
|
||||
* @param pairs generateCrossMetroPairs output (fresh, seeded)
|
||||
* @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) {
|
||||
const { snap, astar, oracle } = opts;
|
||||
const rows = [];
|
||||
let suboptimal = 0;
|
||||
let oracleMismatch = 0;
|
||||
let classBreaks = 0;
|
||||
let routed = 0;
|
||||
let unreachable = 0;
|
||||
@@ -227,21 +235,30 @@ export function sweepOracle(graph, pairs, opts) {
|
||||
continue;
|
||||
}
|
||||
routed += 1;
|
||||
const deltaPct = ((result.seconds - optimal) / optimal) * 100;
|
||||
const suboptimalHere = result.seconds > optimal + ORACLE_EPSILON_S;
|
||||
if (suboptimalHere) suboptimal += 1;
|
||||
if (deltaPct > worstDeltaPct) worstDeltaPct = deltaPct;
|
||||
// SYMMETRIC compare (08-review WR-01): |Δ| beyond the epsilon is a
|
||||
// violation in BOTH directions. A FASTER-than-oracle result is not a
|
||||
// pass — it is the traversal-law-break signature (e.g. a oneway
|
||||
// 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({
|
||||
pairId: pair.pairId,
|
||||
seconds: result.seconds,
|
||||
oracleSeconds: optimal,
|
||||
deltaPct,
|
||||
verdict: suboptimalHere ? "SUBOPTIMAL" : "pass",
|
||||
verdict: !violates ? "pass" : deltaS > 0 ? "SUBOPTIMAL" : "ORACLE-MISMATCH",
|
||||
});
|
||||
}
|
||||
|
||||
const pass = suboptimal === 0 && classBreaks === 0;
|
||||
return { rows, suboptimal, classBreaks, routed, unreachable, unsnappable, worstDeltaPct, pass };
|
||||
const pass = suboptimal === 0 && oracleMismatch === 0 && classBreaks === 0;
|
||||
return { rows, suboptimal, oracleMismatch, classBreaks, routed, unreachable, unsnappable, worstDeltaPct, pass };
|
||||
}
|
||||
|
||||
/* ————— CLI reporter ————— */
|
||||
@@ -306,20 +323,24 @@ async function main() {
|
||||
const delta = row.deltaPct === null ? "Δ —" : `Δ ${row.deltaPct >= 0 ? "+" : ""}${row.deltaPct.toFixed(3)}%`;
|
||||
console.log(` ${row.pairId}: astar ${mine} vs oracle ${truth} (${delta}) ${row.verdict}`);
|
||||
}
|
||||
const worst = results.worstDeltaPct;
|
||||
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.suboptimal} SUBOPTIMAL, ${results.classBreaks} class-breaks`,
|
||||
`${results.suboptimal} SUBOPTIMAL, ${results.oracleMismatch} ORACLE-MISMATCH, ` +
|
||||
`${results.classBreaks} class-breaks`,
|
||||
);
|
||||
if (!results.pass) {
|
||||
console.error(
|
||||
`GATE FAIL: ${results.suboptimal} suboptimal + ${results.classBreaks} class-break pair(s) across ${results.rows.length} fresh pairs — ` +
|
||||
`routeAstar seconds must equal the Dijkstra oracle (gate: 0 suboptimal). Escalate: this is RC-1's signature.`,
|
||||
`GATE FAIL: ${results.suboptimal} SUBOPTIMAL + ${results.oracleMismatch} ORACLE-MISMATCH + ` +
|
||||
`${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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user