From 477961adddf3604caf19b80570aeda79a3a31c89 Mon Sep 17 00:00:00 2001 From: araharah-executor Date: Sun, 25 Oct 2026 17:15:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(routing):=2008-01=20task=203=20=E2=80=94?= =?UTF-8?q?=20--extended=20D-04=20verdict=20on=20the=20CPU=20gate=20(100?= =?UTF-8?q?=20cross-metro=20pairs,=20seed=200x08a1=20shared=20with=20the?= =?UTF-8?q?=20baseline);=20default=20zero-arg=20mode=20byte-compatible,=20?= =?UTF-8?q?budget-audit=2019/19?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routing/scripts/measure-route-cpu.mjs | 82 ++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/routing/scripts/measure-route-cpu.mjs b/routing/scripts/measure-route-cpu.mjs index eda3004..58e262d 100644 --- a/routing/scripts/measure-route-cpu.mjs +++ b/routing/scripts/measure-route-cpu.mjs @@ -40,7 +40,12 @@ * landmark graph (build-time preprocessing, still $0 at runtime). * * Usage: - * node routing/scripts/measure-route-cpu.mjs + * node routing/scripts/measure-route-cpu.mjs # the 25-route CI gate + * node routing/scripts/measure-route-cpu.mjs --extended # D-04 extended verdict + * # (opt-in, maintainer-time: ~100 coordinate-pinned cross-metro + * # pairs — the SAME pair set as the 08-01 optimality baseline — + * # with its own p95 + gate verdict; the default mode above is + * # byte-compatible and untouched) * * Exit codes: 0 p95 inside budget; 1 p95 over budget or absent binary. */ @@ -51,6 +56,8 @@ import { decodeGraph } from "../src/graph.ts"; import { snapToGraph as snap } from "../src/snap.ts"; import { routeAstar as astar } from "../src/astar.ts"; import { getEntries } from "../../search/src/gazetteer.ts"; +import { haversineM } from "../src/geo.ts"; +import { generateCrossMetroPairs } from "./measure-optimality-baseline.mjs"; const here = dirname(fileURLToPath(import.meta.url)); // routing/scripts const GRAPH_PATH = resolve(here, "..", "data", "kl-roadgraph.v1.bin"); @@ -63,6 +70,18 @@ const ROUTE_COUNT = 25; /** The PRNG seed — fixed so re-runs sample identical pairs. */ const SAMPLE_SEED = 0x06d4; +/** D-04 extended verdict: ~100 cross-metro coordinate-pinned pairs + * (maintainer-time, opt-in via --extended; the default mode above — the + * CI-size snapshot that budget-gates.mjs re-runs with ZERO args — is + * byte-compatible and untouched). */ +const EXTENDED_PAIR_COUNT = 100; +/** Shared pair-set seed — identical to the 08-01 baseline recorder's + * (the baseline and this verdict measure the SAME pairs; one definition). */ +const EXTENDED_SEED = 0x08a1; +/** Cross-metro floor: straight-line endpoint distance below this is NOT a + * worst-case pair. */ +const EXTENDED_MIN_PAIR_KM = 12; + function fail(message) { console.error(`measure-route-cpu: ${message}`); process.exit(1); @@ -83,6 +102,67 @@ console.log( `bbox lat ${graph.bbox.minLat.toFixed(3)}..${graph.bbox.maxLat.toFixed(3)} lng ${graph.bbox.minLng.toFixed(3)}..${graph.bbox.maxLng.toFixed(3)}`, ); +/* ————— the D-04 EXTENDED verdict (opt-in; the default mode below is untouched) ————— */ + +const extended = process.argv.includes("--extended"); +if (extended) { + console.log(`extended sample (D-04): ~${EXTENDED_PAIR_COUNT} coordinate-pinned cross-metro pairs (seed ${EXTENDED_SEED}, min ${EXTENDED_MIN_PAIR_KM} km straight-line — the SAME pair set as the 08-01 optimality baseline)`); + const crossPairs = generateCrossMetroPairs(graph.bbox, { + count: EXTENDED_PAIR_COUNT, + minPairKm: EXTENDED_MIN_PAIR_KM, + seed: EXTENDED_SEED, + haversineM, + }); + // One unmeasured warm-up (printed, never hidden) — same warm-isolate law as + // the default mode: the reverse-CSR build must not land inside a sample. + const extWarm = cpuMs(() => astar(graph, 0, Math.min(1, graph.nodeCount - 1))); + console.log(`warm-up (unmeasured): ${extWarm.ms.toFixed(3)} ms`); + const extendedSamples = []; + let extendedMisses = 0; + let extendedMissMs = 0; + for (const pair of crossPairs) { + const origin = snap(graph, pair.origin); + const goal = snap(graph, pair.destination); + if (origin === null || goal === null) { + // An off-road random coordinate: the honest snap miss, reported + // separately — never inside the p95 sample (the same law as the + // default mode's unreachable probes). + extendedMisses += 1; + console.log(` ${pair.pairId}: unsnappable (honest null)`); + continue; + } + const { ms, result } = cpuMs(() => astar(graph, origin.node, goal.node)); + if (result === null) { + extendedMisses += 1; + extendedMissMs += ms; + console.log(` ${pair.pairId}: ${ms.toFixed(3)} ms (unreachable — honest null, D-03)`); + continue; + } + const km = `${(result.distanceM / 1000).toFixed(1)} km / ${(result.seconds / 60).toFixed(1)} min`; + console.log(` ${pair.pairId}: ${ms.toFixed(3)} ms (${km})`); + extendedSamples.push(ms); + } + if (extendedSamples.length === 0) fail("no routable pairs in the extended sample — the graph may be fragmented; rebuild"); + extendedSamples.sort((a, b) => a - b); + const extIdx = Math.min(extendedSamples.length - 1, Math.ceil(0.95 * extendedSamples.length) - 1); + const extP95 = extendedSamples[extIdx]; + const extMax = extendedSamples[extendedSamples.length - 1]; + const extMean = extendedSamples.reduce((acc, v) => acc + v, 0) / extendedSamples.length; + console.log( + `routed pairs: ${extendedSamples.length}/${crossPairs.length}` + + (extendedMisses > 0 + ? `; snap-miss/unreachable probes: ${extendedMisses} (mean ${(extendedMissMs / extendedMisses).toFixed(3)} ms, reported separately)` + : ""), + ); + console.log(`p95: ${extP95.toFixed(3)} ms (max ${extMax.toFixed(3)} ms, mean ${extMean.toFixed(3)} ms, n=${extendedSamples.length})`); + if (extP95 > P95_BUDGET_MS) { + console.error(`GATE FAIL: p95 ${extP95.toFixed(3)} ms exceeds the ${P95_BUDGET_MS} ms budget — D-04 extended verdict`); + process.exit(1); + } + console.log(`GATE PASS: p95 ${extP95.toFixed(3)} ms <= ${P95_BUDGET_MS} ms budget — D-04 extended verdict`); + process.exit(0); +} + /** mulberry32 — tiny deterministic PRNG (fixed seed => comparable runs). */ function mulberry32(seed) { let a = seed >>> 0;