feat(06-04): R2 upload + A*-CPU measurement gate + the A1 ladder executed to its decision point
- upload-road-graph.mjs: wrangler-shelled maintainer tool (pre-flight 1-80MB, round-trip verify hint); bucket araharah-graphs NOT created — R2 is not yet enabled on the account (wrangler code 10042) — dashboard one-time activation is the phase's user_setup item - measure-route-cpu.mjs: 25-pair gate (corpus + seeded quadrant spread), hrtime around routeAstar, warm-up excluded, unreachable probes reported SEPARATELY from the routed p95, exits 1 over the 8ms budget - Rule 1 (measured): the class set lacked *_link ramps — the graph FRAGMENTED (7.7% of nodes in severed motorway islands; corpus pairs 'unreachable'). 5 link classes added -> 311325 nodes / 677068 edges / 14.7 MB, all landmarks in one component; enforced floor corrected 1KB -> 1MB (D-01) - ladder (a) drop tertiary+residential: p95 17.8ms but 12/25 pairs unreachable — REJECTED on correctness (D-03), reverted, ladder history recorded - ladder (b) bidirectional A* (BH, Ikeda potentials, reverse-CSR WeakMap): provably optimal (A1 625-pair sweep green), alternation balanced by heap size (raw-top comparison starves the backward side 19501:3), reduction signs in the standard feasible form. Measured: p95 ~38-93ms — NO GATE PASS. The potential split halves each side's steering, cancelling the halved radius - GATE FAIL is the honest verdict: ladder (c) — Workers Paid vs layout-v2 ALT (still $0) vs free-plan-with-degradation — requires maintainer sign-off; docs/ROAD-GRAPH.md carries the decision table and the fork contract
This commit is contained in:
@@ -70,8 +70,27 @@ const BBOX_MARGIN_DEG = 0.05;
|
||||
/** Node dedup granularity: 1e-6 deg ≈ 0.11 m — far under GPS noise. */
|
||||
const COORD_ROUND_DECIMALS = 6;
|
||||
/** drivable classes ONLY (06-RESEARCH Pitfall 1): footway/cycleway/service/
|
||||
* path are deliberately absent — a walking path must never enter a road A*. */
|
||||
const DRIVABLE_CLASSES = new Set([
|
||||
* path are deliberately absent — a walking path must never enter a road A*.
|
||||
* The *_link classes are LOAD-BEARING (measured 2026-10-24): they are the
|
||||
* interchange ramps tying motorways/trunk/primary into the surface network.
|
||||
* Without them the graph FRAGMENTS — 7.7% of nodes sat in severed motorway
|
||||
* islands and golden corpus pairs resolved "unreachable" because an endpoint
|
||||
* snapped onto an isolated fragment.
|
||||
*
|
||||
* A1 MITIGATION LADDER HISTORY (measure-route-cpu.mjs is the arbiter):
|
||||
* 2026-10-24 full 13-class linked graph (311k nodes): p95 38.4 ms, ALL
|
||||
* corpus pairs route — connectivity correct, CPU over budget.
|
||||
* 2026-10-24 ladder step (a) — drop tertiary+residential (127k nodes):
|
||||
* p95 17.8 ms BUT 12/25 pairs (incl. 8/15 golden corpus pairs) became
|
||||
* UNREACHABLE — the dropped classes were the fine mesh tying landmarks
|
||||
* to the arterial network. REJECTED on correctness (D-03: an honest
|
||||
* router-pending for half the product's destinations is not a smaller
|
||||
* graph, it is a broken one). REVERTED to the full 13-class set; the
|
||||
* CPU gate is carried by ladder step (b) — bidirectional A* in
|
||||
* routing/src/astar.ts — and the recorded verdict in the plan summary.
|
||||
* Ladder step (c) — Workers Paid — breaks the $0 law and requires user
|
||||
* sign-off; it is NOT applied in this script. */
|
||||
const ACTIVE_CLASSES = new Set([
|
||||
"motorway",
|
||||
"trunk",
|
||||
"primary",
|
||||
@@ -80,6 +99,11 @@ const DRIVABLE_CLASSES = new Set([
|
||||
"residential",
|
||||
"unclassified",
|
||||
"living_street",
|
||||
"motorway_link",
|
||||
"trunk_link",
|
||||
"primary_link",
|
||||
"secondary_link",
|
||||
"tertiary_link",
|
||||
]);
|
||||
/** Class speed defaults (km/h) when a way carries no parsable maxspeed. */
|
||||
const CLASS_SPEED_KMH = {
|
||||
@@ -92,8 +116,16 @@ const CLASS_SPEED_KMH = {
|
||||
unclassified: 30,
|
||||
living_street: 20,
|
||||
};
|
||||
/** Motorway link ramps inherit the parent class speed via these links. */
|
||||
const LINK_CLASS_SPEED_KMH = { motorway_link: 60, trunk_link: 60 };
|
||||
/** Motorway link ramps inherit a near-parent speed via these links. */
|
||||
const LINK_CLASS_SPEED_KMH = {
|
||||
motorway_link: 60,
|
||||
trunk_link: 60,
|
||||
primary_link: 50,
|
||||
secondary_link: 40,
|
||||
tertiary_link: 40,
|
||||
};
|
||||
/** ENFORCED floor — an at/below-1 MB "KL road graph" is a broken build (D-01). */
|
||||
const MIN_BINARY_BYTES = 1 * 1024 * 1024;
|
||||
|
||||
function fail(message) {
|
||||
console.error(`build-road-graph: ${message}`);
|
||||
@@ -122,7 +154,7 @@ if (checkIdx !== -1) {
|
||||
` bytes: ${size}`,
|
||||
);
|
||||
if (size > MAX_BINARY_BYTES) fail(`--check: ${size} bytes exceeds the ${MAX_BINARY_BYTES}-byte D-01 envelope`);
|
||||
if (size <= 1024) fail(`--check: ${size} bytes is implausibly small — not a real road graph`);
|
||||
if (size <= MIN_BINARY_BYTES) fail(`--check: ${size} bytes is at/below the ${MIN_BINARY_BYTES}-byte floor — not a real road graph`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -175,7 +207,7 @@ console.log(
|
||||
|
||||
/* ————— the Overpass fetch (ONE request, drivable classes only) ————— */
|
||||
|
||||
const CLASSES_RE = "^(" + [...DRIVABLE_CLASSES].join("|") + ")$";
|
||||
const CLASSES_RE = "^(" + [...ACTIVE_CLASSES].join("|") + ")$";
|
||||
const query =
|
||||
`[out:json][timeout:${Math.floor(REQUEST_TIMEOUT_MS / 1000)}];` +
|
||||
`way["highway"~"${CLASSES_RE}"](${bbox.minLat},${bbox.minLng},${bbox.maxLat},${bbox.maxLng});` +
|
||||
@@ -243,7 +275,7 @@ function effectiveKmh(highway, maxspeedTag) {
|
||||
|
||||
for (const way of ways) {
|
||||
const highway = way.tags?.highway;
|
||||
if (typeof highway !== "string" || !DRIVABLE_CLASSES.has(highway)) continue;
|
||||
if (typeof highway !== "string" || !ACTIVE_CLASSES.has(highway)) continue;
|
||||
const geometry = way.geometry;
|
||||
if (!Array.isArray(geometry) || geometry.length < 2) continue;
|
||||
const kmh = effectiveKmh(highway, way.tags?.maxspeed);
|
||||
@@ -316,11 +348,15 @@ console.log(
|
||||
` binary size: ${mb(bytes)} (${bytes} B)`,
|
||||
);
|
||||
|
||||
// THE ENFORCED GATE (D-01): over the ceiling → exit 1 WITHOUT writing.
|
||||
// THE ENFORCED GATE (D-01): outside the 1-80 MB envelope → exit 1 WITHOUT writing.
|
||||
if (bytes > MAX_BINARY_BYTES) {
|
||||
fail(`binary is ${mb(bytes)} — over the ${mb(MAX_BINARY_BYTES)} D-01 envelope. ` +
|
||||
`Tighten DRIVABLE_CLASSES (drop tertiary/residential first) or shrink the bbox, then rebuild.`);
|
||||
}
|
||||
if (bytes <= MIN_BINARY_BYTES) {
|
||||
fail(`binary is ${bytes} bytes — at/below the ${MIN_BINARY_BYTES}-byte floor; a real metro graph is never this small. ` +
|
||||
`Verify the Overpass query actually returned ways, then rebuild.`);
|
||||
}
|
||||
// THE ADVISORY BAND (A2's 18–50 MB forecast vs D-01's 30–80 MB expectation):
|
||||
// a small graph is a review prompt, not a failure — the enforced gate stays.
|
||||
if (bytes < EXPECTED_MIN_BYTES) {
|
||||
|
||||
Reference in New Issue
Block a user