# Phase 8: ALT Landmark Preprocessing (Routing Backend v2) - Context
**Gathered:** 2026-10-25
**Status:** Ready for planning
## Phase Boundary
Cross-metro routes route in-budget on the free plan: ALT (A\*, Landmarks, Triangle-inequality) preprocessing over the CSR graph upgrades engine A's worst case while staying $0 — the c2 rung of the measured ladder, executed (committed by Phase 6's post-planning A1 ratification). Concretely: build-time landmark distance tables, a graph-layout v2 binary that carries them, the same R2 hosting + unchanged upload script, a corpus re-run proving optimality is unaffected, the free-plan CPU gate passing with p95 under the ceiling, and the c3 degradation class retiring from KNOWN-LIMITATIONS.
No new product surfaces — the user-visible outcome is unchanged routing behavior that now covers the longest cross-metro KL routes inside the CPU budget.
## Implementation Decisions
### Binary layout compatibility (user decision D-01)
- **D-01:** **ARG2-only, fail-honest.** The graph layout version bumps; the landmark-carrying layout is a new version, and old ARG1 binaries decode to null (the existing fail-honest law in `graph.ts`) → the Phase 6 designed **router-pending state** covers them. There is exactly **one decode path and one search path** — no v1 fallback branch, no silent quality regression; a fork (or our own R2 during rollout) routes nothing until the v2 binary is uploaded once. Chosen over dual-version decode to keep the single-authority law: the permanent cost of two search paths (and a silently degraded mode that contradicts fail-honest) outweighs a small, honest, temporary unavailability window. The upload script stays unchanged (roadmap lock) — re-uploading the rebuilt binary is the whole migration.
### Landmark budget (user decision D-02)
- **D-02:** **A scaling rule, not a fixed count.** The selection *approach* is locked — build-time Dijkstra passes over well-spread anchor nodes (farthest-point/hill-climb family over the road graph; anchors are road-network nodes, NOT POIs — restaurants/shops live in the Phase 4 gazetteer). The landmark *count* is a measurement-justified, node-count-proportional quantity: the KL build ships whatever count the CPU-gate measurement justifies (expected in the lean ~16–32 range; the curve flattens fast at metro scale). Rationale locked in discussion: per-landmark bytes scale with node count (~1.2 MB/landmark at KL's 311k nodes), so a fixed "generous" count explodes at future Malaysia scale (~12–24 MB/landmark at country network size) while ALT's advantage itself narrows at that scale (that tier is Contraction Hierarchies' job, still deferred per Phase 6 D-02). A future Malaysia-wide build applies the same scaling rule at its own scale — nothing is over-provisioned now for a graph that doesn't exist.
### Optimality verification definition (user decision D-03)
- **D-03:** **Equal seconds, ties allowed.** The corpus re-run gate asserts: total route **seconds** match the pre-ALT baseline exactly (or within a named tiny epsilon, ~±0.1%) and every pair stays in the same verdict class (reachable/unreachable). ALT changes A\*'s steering, never its cost model — optimal seconds are guaranteed; but bidirectional search + different potentials may legitimately change **tie-breaking** (a different equal-cost path). Tie-path differences are documented behavior, NOT failures. Bit-identical node paths is explicitly NOT the gate (brittle — fails on legitimate ties).
### Measurement sample (user decision D-04)
- **D-04:** **Extend the sample for the ALT verdict.** The existing 25-route seeded gate (`measure-route-cpu.mjs`, seed `0x06d4`) stays as the CI-size snapshot; the ALT verdict additionally runs a maintainer-time extended seeded sample (~100 cross-metro worst-case pairs) for confidence that the longest routes route in-budget. Maintainer-time measurement is free — bigger evidence at zero runtime cost. Same print-the-measurement law.
### Limitations retirement (user decision D-05)
- **D-05:** **Retire the c3 class with evidence.** `docs/KNOWN-LIMITATIONS.md`'s cross-metro CPU degradation section is replaced by a short retirement record: what changed (ALT preprocessing), the measured numbers that retired it (p95 under the ceiling, from D-04's run), and the date. The ALT row in the limitations' roadmap-pointer table flips from "Next-milestone item, not built" to built (Phase 8). The honest audit trail stays in-document (measurements print; limitations never overpromise). Note: because of D-01 there is NO "fallback for landmark-less graphs" to document — an old graph is simply router-pending, which is already a designed, documented degradation.
### OpenCode's Discretion
- Landmark-selection algorithm specifics (farthest-point vs hill-climb vs hybrid), anchor count formula constants, and any quantization of stored distances (e.g., u16-seconds research) within the scaling rule
- Preprocessing build-time cost budget (each landmark = one full graph pass; build script runtime stays sanity-bounded)
- The exact epsilon value and its name (named constant, replay-tested, per phase-6 constant law)
- ARG2 layout byte design (header extensions aligned to the typed-array law; corrupt-binary fail-honest validation carried forward)
- How the committed mini-graph fixture and test suite version-bump (new-ARG2 fixtures; old-v1 fixture kept only as a decode-rejection case)
- KNOWN-LIMITATIONS retirement wording within the honest-engineering tone law
## Specific Ideas
- The user explicitly asked for (and received) the landmark concept explanation: ALT anchors are abstract road-graph reference points, not places. This distinction should survive into research/planning docs so future readers don't confuse it with POI search.
- The whole-malaysia question shaped D-02: the decision is deliberately future-compatible — a Malaysia build should re-derive landmark count from the same rule (and may reasonably graduate to CH per Phase 6's deferral), never inherit a KL-tuned constant.
- "Fail-honest beats silent degradation" was the deciding principle for D-01 — consistent with the repo's designed-degradation philosophy (router-pending is a feature, not an error).
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Phase scope + decisions
- `.planning/ROADMAP.md` §Phase 8 — goal, 3 success criteria, "graph-layout v2 with landmark distances precomputed at build time; upload script unchanged"
- `.planning/phases/06-turn-by-turn-guidance-live-driving/06-CONTEXT.md` — engine A pivot (D-01/D-02), the A1 ratification committing c2 (ALT) as this phase and rejecting Workers Paid; Phase 6's CH/turn-restrictions deferral list
- `.planning/phases/06-turn-by-turn-guidance-live-driving/06-04-SUMMARY.md` — the measured ladder history (full graph unidirectional 38.4 ms p95 → drop-classes rejected on correctness → bidirectional BH kept) and the real-graph build facts (311,325 nodes / 677,068 directed edges / 14.7 MB / all landmarks in one strongly-connected component)
- `.planning/phases/07-hardening-private-launch/07-CONTEXT.md` — KNOWN-LIMITATIONS doc shape (07 D-08) and the consolidated budget record (07 D-06) that SC's numbers join
### Codebase
- `routing/src/graph.ts` — the pinned CSR binary layout ("ARG1" v1, version field, fail-honest decode): the codec this phase versions to ARG2; encode remains the single authority
- `routing/src/astar.ts` — the bidirectional A\* (BH) ALT builds on; its traversal law, equilibrate rule, and pure-module law
- `routing/scripts/measure-route-cpu.mjs` — the CPU gate (P95_BUDGET_MS 8, 25-route seeded sample, ladder history in its header): the gate this phase must pass
- `routing/scripts/build-road-graph.mjs` — the build tool where the preprocessing stage joins ( Overpass extract → dedup → encode )
- `routing/scripts/route-corpus.mjs` — the corpus re-run tool the optimality gate (D-03) rides
- `routing/scripts/upload-road-graph.mjs` — the unchanged fork migration path (D-01)
- `docs/KNOWN-LIMITATIONS.md` — the c3 section + ALT roadmap-pointer row that D-05 retires
- `bot/src/routing/worker-router.ts` — the R2-hosted graph consumer (key `kl-roadgraph.v1.bin`) that serves `/api/route`; consumes the v2 layout unchanged at its API
## Existing Code Insights
### Reusable Assets
- `graph.ts` encode/decode single authority — the ARG2 layout extends one module, pins one header, keeps zero-copy typed-array laws
- `astar.ts` bidirectional search — ALT potentials slot into the existing lazy-potential mechanism; no new search skeleton
- `measure-route-cpu.mjs` + `route-corpus.mjs` — the gate and corpus harness both re-run as-is (sample extension is a parameter, not a rewrite)
- Committed mini-graph fixture — test corpus basis; version-bumped fixtures derive through the same encode path
- `snap.ts` — unchanged; snapping is orthogonal to ALT (D-05 phase-6 single-authority law holds)
### Established Patterns
- Fail-honest decode → router-pending (designed degradation, never an error)
- Named constants, replay-tested no magic numbers (applies to epsilon + landmark scaling constants)
- Budget gates print their measurement and gate on p95 with margin
- TDD RED→GREEN with hand-rolled runners; pure modules (no I/O in routing/src)
### Integration Points
- `build-road-graph.mjs` gains the preprocessing stage (one Dijkstra pass per landmark at build time)
- R2 `ROAD_GRAPH` binding + `upload-road-graph.mjs` — same key path, new-version binary
- Engine-B (Phase 11) downloads this same asset — the scaling rule + lean KL count keep the future phone payload in the ~30–50 MB class
- `docs/KNOWN-LIMITATIONS.md` and `docs/CONFIGURATION.md` — the record surfaces D-05 touches
## Deferred Ideas
- Contraction Hierarchies — remains the Phase 6 deferral; the noted future tier if routing ever graduates to Malaysia-wide scale (D-02's rationale points there)
- Engine B client-side router consuming the ARG2 asset — Phase 11 (the phone-download payload concern in this phase's discussion feeds its budget documentation)
- Turn restrictions in the graph — Phase 6 deferral stands
- Any traffic/time-dependent weighting — v2 per REQUIREMENTS
---
*Phase: 08-alt-landmark-preprocessing-routing-backend-v2*
*Context gathered: 2026-10-25*