Files
arah2/.planning/phases/08-alt-landmark-preprocessing-routing-backend-v2/08-02-SUMMARY.md
T

172 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 08-alt-landmark-preprocessing-routing-backend-v2
plan: 02
subsystem: routing
tags: [alt, landmark, arg2, codec, dijkstra, typed-arrays, quantization, fixture, tdd]
requires:
- phase: 08-alt-landmark-preprocessing-routing-backend-v2 (plan 08-01)
provides: the frozen ARG1 rejection vector (mini-graph-arg1.bin) this plan's G3 consumes, and the pre-ALT baseline record the codec change must not invalidate
- phase: 06-turn-by-turn-guidance-live-driving
provides: the ARG1 codec being versioned (graph.ts single authority), the A1 traversal law (bit0 never relaxed) the landmark Dijkstras mirror, and the committed mini-graph fixture basis
provides:
- routing/src/landmark.ts — the ONE pure landmark compute core: farthestPointAnchors (deterministic degree>=2 farthest-point over the ĉ metric) + landmarkTables (exact integer Dijkstras, u16 tables, 0xFFFF sentinel, 65000-quantum loud overflow) + landmarkCoverage
- ARG2 graph.ts — version-bumped single-authority codec: 72-byte header (landmarkCount @60, quantum f32 @64), append-only u16 table suffix, exact-size law extended over the variable suffix BEFORE allocation, one decode path (ARG1 bytes → null)
- the regenerated committed mini-graph.bin fixture (ARG2, L=2 anchors [0,18], quantum 0.1 s, coverage 1.0, byte-deterministic) through the SAME encode authority
- extended G-suite (G1–G11) incl. the ARG1-fixture rejection pin, table round-trip, 9-case encode-validation matrix, landmarkCount=0 + monster-count decode rejections
- RoadGraph.landmarkCount/landmarkQuantumS/landmarkTo/landmarkFrom/landmarkNodes — the typed-array fields Plan 08-03's ALT potentials consume
affects: [08-03 (ALT potentials read the tables), 08-04 (build script landmark stage via the same core), 08-05 (real-graph campaign), bot routing suite (corridor graphs now ARG2)]
tech-stack:
added: []
patterns:
- "ĉ-metric surrogate: floor-quantize EDGE WEIGHTS once (Math.floor(seconds/quantumS)), exact integer Dijkstras over Int32 labels — quantization without an admissibility epsilon (research Pattern 2)"
- "append-only layout versioning: new sections are a suffix, header grows into the old pad, ONE decode path, old-version bytes decode null (D-01 fail-honest)"
- "tiny-real-landmark-stage fixture regeneration: the committed fixture flows through the SAME farthestPointAnchors/landmarkTables/encodeGraph the real build uses (byte-deterministic re-runs)"
key-files:
created:
- routing/src/landmark.ts
- routing/test/landmark.test.mjs
modified:
- routing/src/graph.ts (ARG2 version bump)
- routing/test/graph.test.mjs (G-suite re-pin + G9–G11)
- routing/test/fixtures/build-mini-graph.mjs (landmark stage)
- routing/test/fixtures/mini-graph.bin (regenerated ARG2, 1668 → 1884 B)
- routing/test/astar.test.mjs (A3 synthetic carries the landmark block)
- routing/test/snap.test.mjs (S3 synthetic carries the landmark block)
- bot/test/webhook.test.mjs (corridor/sparse builders compute landmark blocks)
- bot/test/guidance-cron.test.mjs (unnamed-way builder computes the landmark block)
key-decisions:
- "Fixture regeneration pulled forward into task 2's commit (Rule 3): task 2's own gate demands routing:test 0 failed, which is impossible while the committed fixture is ARG1 once decode is ARG2-only; task 3 then sealed byte-stability and re-ran the (idempotent) builder"
- "Decode validates landmarkQuantumS (finite > 0) → null — hostile-quantum header defense in the T-08-01 family; nothing downstream can divide by it"
- "Reserved u32 @68 is written 0 and accepted-untouched at decode (the plan's recommended reading — no assert on an unused field)"
- "Anchor selection counts stored OUT-degree (blocked mirrors included) for the degree>=2 filter — a node on a oneway still sits on multi-edge road geometry"
- "Graphs with no degree>=2 candidate (sparse/2-node synthetics) pin their anchor explicitly and still compute tables through landmarkTables — the selection filter is a quality law, not a table law"
patterns-established:
- "TDD with hand-computed integer vectors: RED expectations verified against an INDEPENDENT brute force before pinning (the L6 lesson — intuition about 'farthest' failed on a speed-skewed metric)"
- "Tamper-vector tests construct CONSISTENT hostile buffers (byte size matches the tampered layout) so a specific decode law is isolated from the size law (G11)"
requirements-completed: [SC3, SC4]
duration: ~17min
completed: 2026-10-25
---
# Phase 8 Plan 08-02: Landmark Core + ARG2 Codec Summary
**The landmark compute core exists once as a pure typed-array module (deterministic farthest-point anchors + exact integer ĉ-metric Dijkstras with a 65000-quantum loud overflow), graph.ts is version-bumped to ARG2 with one fail-honest decode path whose exact-size law covers the u16 table suffix, and the committed fixture is regenerated as ARG2 through that same encode authority — routing 74/74 + bot 126/126 + both typechecks green, with astar.ts untouched (search behavior provably neutral at this commit).**
## Performance
- **Duration:** ~17 min (10:34–10:51 UTC)
- **Started:** 2026-10-25T10:34:07Z
- **Completed:** 2026-10-25T10:51:00Z
- **Tasks:** 3/3 (tasks 1 & 2 with full RED→GREEN gate commits)
- **Files modified:** 10 (2 created, 8 modified)
## Accomplishments
- **`routing/src/landmark.ts` — the ONE landmark compute core** (zero imports, no I/O): `farthestPointAnchors` (first degree-≥2 candidate by node id, greedy farthest-point with per-pick forward-pass reuse, lower-id tie-breaks, documented coverageFraction early-stop knob), `landmarkTables` (forward + reversed integer Dijkstras over floor-quantized weights — the ĉ metric, no stored-distance rounding, no epsilon — binary-heap, lazy deletion, bit0 edges never relaxed in EITHER direction exactly mirroring astar.ts), `landmarkCoverage` (the build's per-anchor assert feed). 0xFFFF is DATA; any label past 65,000 quanta throws naming the distance and the fix.
- **ARG2 in `graph.ts` in place** — no second codec: `GRAPH_MAGIC = 0x41524732`, `GRAPH_VERSION = 2`, `HEADER_SIZE = 72` (f64 alignment kept), landmark header fields @60/@64/@68, append-only suffix (u16 toTable, u16 fromTable, u32 anchors) folded into `layoutFor`'s `total` arithmetic so the exact-size check rejects monster counts BEFORE any view allocation. Decode refuses ARG1 bytes (the committed `mini-graph-arg1.bin` is the standing rejection vector), version ≠ 2, `landmarkCount = 0` (no landmark-less ARG2 exists — D-01/A5), hostile quantum, and any size mismatch. `GraphInput.landmarks` is REQUIRED and loud-validated (9 named-throw classes).
- **The fixture is ARG2 through the one authority**: the builder gained the tiny real landmark stage (L=2 → anchors [0, 18], quantum 0.1 s, coverage 1.00/1.00, fail-loud invariants before write) and is byte-deterministic on re-run; `mini-graph-arg1.bin` stayed byte-identical to its committed 08-01 bytes (`cmp`-verified).
- **The full battery is green at the final commit** — routing:test 74/74 (L1–L6 + G1–G11 + untouched A/P/R/S suites), bot 126/126 (RT/H/GC + the updated corridor/cron builders), routing+bot typechecks clean. `routing/src/astar.ts` has ZERO changes — A1's 625-pair sweep green over the ARG2 fixture proves the codec change alone is behavior-neutral to search (routeAstar still runs haversine potentials; ALT lands in 08-03).
## Task Commits
Each task was committed atomically (TDD tasks carry their RED gates):
1. **task 1: landmark.ts — the pure landmark compute core** — `c3a31ae` (test, RED) → `1bf413b` (test, L5 vector fix) → `5b7a130` (feat, GREEN)
2. **task 2: ARG2 codec in graph.ts** — `34d96b0` (test, RED) → `32a00b5` (feat, GREEN; includes the fixture regen pull-forward)
3. **task 3: bot call sites + full battery seal** — `9770867` (test)
## Files Created/Modified
- `routing/src/landmark.ts` — the pure landmark compute core (created)
- `routing/src/graph.ts` — ARG2 codec: 72-byte header, u16 suffix, version-bumped decode laws, required-landmarks encode validation
- `routing/test/landmark.test.mjs` — L1–L6: hand-computed vectors for anchors, quantum exactness, sentinels, asymmetry, overflow, the 5×5 grid (created)
- `routing/test/graph.test.mjs` — G-suite re-pinned (G3/G5/G7/G8) + extended (G9 table round-trip, G10 encode validation, G11 decode rejection)
- `routing/test/fixtures/build-mini-graph.mjs` — landmark stage + landmark stats in the summary line
- `routing/test/fixtures/mini-graph.bin` — regenerated ARG2 (1884 B; +208-byte table suffix + 4 header bytes + 4-byte alignment)
- `routing/test/astar.test.mjs`, `routing/test/snap.test.mjs` — synthetic encodeGraph call sites carry (inert until 08-03) landmark blocks
- `bot/test/webhook.test.mjs`, `bot/test/guidance-cron.test.mjs` — corridor/sparse/cron builders compute tiny tables via `farthestPointAnchors`/`landmarkTables` and pass the `landmarks` block
## Decisions Made
- **Pull the fixture regen forward (Rule 3)**: task 2's acceptance demands "runner output 0 failed" at its own commit — impossible with a committed ARG1 fixture under ARG2-only decode. The regen joined task 2's commit; task 3 verified the seal (idempotent re-run, byte-stable ARG1 vector) instead.
- **Decode-side quantum validation**: a non-finite/non-positive `landmarkQuantumS` header decodes null — cheap hostile-header defense so 08-03's potentials can never divide by a corrupt quantum.
- **Degree filter counts stored out-edges (mirrors included)**: the blocked mirror still evidences multi-edge road geometry at a node; endpoints/dead-ends stay excluded.
- **Test-vector discipline**: every pinned integer expectation was verified against an independent brute-force Dijkstra before pinning — which caught my own wrong intuition (below).
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Fixture regeneration pulled forward from task 3 into task 2's commit**
- **Found during:** task 2 (ARG2 codec)
- **Issue:** task 2's own verification gate (`npm run routing:test` → 0 failed) cannot pass while the committed fixture is ARG1 bytes once decode becomes ARG2-only (G7 + A-suite + snap suite all decode the fixture)
- **Fix:** extended `build-mini-graph.mjs` with the landmark stage and regenerated `mini-graph.bin` inside task 2's commit; task 3 then re-ran the builder (byte-identical output — determinism proven) and sealed ARG1-vector byte-stability
- **Files modified:** routing/test/fixtures/build-mini-graph.mjs, routing/test/fixtures/mini-graph.bin
- **Verification:** routing:test 74/74 at task-2 commit; `cmp` vs committed 08-01 ARG1 bytes passes; regen deterministic
- **Committed in:** `32a00b5`
**2. [Rule 3 - Blocking] Two encodeGraph call sites missing from the plan's enumeration**
- **Found during:** task 2 (call-site census before implementing)
- **Issue:** the required-landmarks law makes EVERY encodeGraph call throw — the plan's files list omitted `routing/test/astar.test.mjs` (A3's disconnected synthetic) and `routing/test/snap.test.mjs` (S3's bare 2-node synthetic)
- **Fix:** both synthetics carry small hand-written landmark blocks (inert steering data until 08-03), keeping the cases' original focus
- **Files modified:** routing/test/astar.test.mjs, routing/test/snap.test.mjs
- **Verification:** A-suite and S-suite green in the full battery
- **Committed in:** `32a00b5`
**3. [Rule 1 - Bug] Two hand-computed L-suite expectations were wrong (implementation was right)**
- **Found during:** task 1 GREEN
- **Issue:** (a) L4 indexed table rows by anchor NODE ID instead of anchor POSITION (row-major is `[ℓ·N+v]`, ℓ = position); (b) L6 assumed the ĉ-farthest node from corner 0 is the opposite corner 24 — with interior hops costing 2× perimeter quanta, interior node 18 (297 q) is farther than corner 24 (264 q)
- **Fix:** re-pinned both vectors; node 18's 297-quantum max verified against an independent brute-force Dijkstra before pinning
- **Files modified:** routing/test/landmark.test.mjs
- **Verification:** L-suite green; the independent brute force output recorded in-session
- **Committed in:** `5b7a130`
**4. [Rule 1 - Bug] Sparse/cron graphs have no degree-≥2 farthest-point candidate**
- **Found during:** task 3 (bot battery)
- **Issue:** the plan assumed every call site can `farthestPointAnchors(...)` — but a single-segment graph's nodes all have out-degree 1, so selection legitimately returns empty and the first battery run failed at suite load
- **Fix:** those call sites pin the anchor explicitly (`Uint32Array.of(0)`) and still compute tables through `landmarkTables` — the selection filter is a quality law, not a table law
- **Files modified:** bot/test/webhook.test.mjs, bot/test/guidance-cron.test.mjs
- **Verification:** bot suite 126/126
- **Committed in:** `9770867`
---
**Total deviations:** 4 auto-fixed (2 blocking-call-site/closure, 2 test-vector bugs)
**Impact on plan:** All four were forced by the plan's own laws (required-landmarks encode, exact gate order) or by verified arithmetic; no scope creep — the 08-04/08-05 boundaries are untouched (`build-road-graph.mjs` still belongs to 08-04; no search code changed).
## TDD Gate Compliance
Both `tdd="true"` tasks ran full RED→GREEN with separate commits: task 1 `c3a31ae`(+`1bf413b`) test gates BEFORE `5b7a130` feat; task 2 `34d96b0` test gate BEFORE `32a00b5` feat. RED failures were verified to fail for the RIGHT reason each time (ERR_MODULE_NOT_FOUND for the new module; specific assertion deltas for the re-pinned codec laws). No gate skipped.
## Issues Encountered
- A first battery run piped bot tests through `| tail -2` without `pipefail`, masking the suite's exit code behind tail's; caught by reading the output (npm error line), re-run with `set -o pipefail` — all subsequent battery commands use pipefail.
- The plan's acceptance grep contains a typo ("landmarms"); the intent (both bot files pass a `landmarks: {` block) is satisfied literally.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- **08-03 (ALT potentials):** `RoadGraph` now exposes `landmarkCount/landmarkQuantumS/landmarkTo/landmarkFrom/landmarkNodes` as zero-copy views; only `potential()` in `routeAstar` changes. The table values are inert until then (documented coexistence — A-suite green at this commit is the neutrality proof).
- **08-04 (build script):** `build-road-graph.mjs` still encodes WITHOUT landmarks (line 336) — its planned preprocessing stage joins between edge assembly and the encode call, importing the SAME `landmark.ts` core the fixture builder now uses.
- **08-05 (campaign):** the real KL binary on disk is still ARG1 → decodes null until rebuilt+re-uploaded (the designed router-pending window, D-01); the committed baseline record (08-01) is untouched and its recorder's ARG1-version guard is exactly what makes that safe.
---
*Phase: 08-alt-landmark-preprocessing-routing-backend-v2*
*Completed: 2026-10-25*
## Self-Check: PASSED
All 8 claimed files exist on disk; all 6 task commits (`c3a31ae`, `1bf413b`, `5b7a130`, `34d96b0`, `32a00b5`, `9770867`) exist in git history. Final battery at `9770867`: routing:test 74/74, bot 126/126, routing:typecheck + bot:typecheck clean.