6.7 KiB
6.7 KiB
Phase 5: Routing + Route Display — Research (light pass)
Researched: 2026-10-24 (light pass — the pivot removed Valhalla; per roadmap note "standard patterns, no research phase needed", this pass covers only the three open questions planning actually faces) Scope: fixture encoding format · OSRM demo build-time script shape · RoutingSource interface analogs. Everything else is decided in 05-CONTEXT.md (D-01..D-12) and 05-UI-SPEC.md (binding).
Finding 1 — Fixture encoding: encoded polyline6 VERBATIM (not GeoJSON)
Decision: store each route as the encoded-polyline string exactly as the OSRM demo returns it (geometries=polyline6), plus distanceM/durationS. A decoder lives in the routing workspace.
- Verified live (2026-10-24):
GET https://router.project-osrm.org/route/v1/driving/101.7117,3.1578;101.6869,3.1343?alternatives=2&overview=full&geometries=polyline6→code: Ok, 2 routes, route 1 = 6907.9 m / 663.2 s with a 1200-char geometry string; route 2 = 6572.8 m / 696 s / 1252 chars. - Size math vs the D-11 budget: ~45 routes (15 pairs × up to 3) × ~1.2–2.5 KB ≈ 40–60 KB raw → ~12–20 KB gzipped. The ≤ 200 KB gz budget leaves an order of magnitude of headroom. GeoJSON coordinate arrays for the same geometry measure ~10–20× larger for zero fidelity gain — rejected.
- Verbatim storage is the lossless choice: the string is what the demo server returned — no float rounding, no re-serialization. The decoder (standard Google encoded-polyline varint/delta algorithm, precision 1e6) is ~40 lines, pure, and round-trip testable (encode(decode(s)) === s per fixture — a free corpus assertion).
- Snapped-progress support (D-07, GUID-05/06 upstream obligation): fixtures do NOT precompute cumulative distances — the runtime derives
cumulativeDistanceMonce at decode/load (deterministic haversine walk, memoized). Keeps fixtures lean; the MODEL still carries the field from day one.
Finding 2 — OSRM demo build-time script shape (verified shape, repo-tool precedent)
- Endpoint:
https://router.project-osrm.org/route/v1/driving/{lng},{lat};{lng},{lat}?alternatives=2&overview=full&geometries=polyline6—alternatives=2returns up to 2 alternates (≤ 3 total routes; matches RTNG-02's "up to 3"),overview=fullgives the complete geometry,steps=false(turn instructions are Phase 6; fixturestepsstay typed-but-empty — never fake). - Response contract:
code: "Ok",routes[]best-first (preserve order), each withdistance(m),duration(s),geometry(polyline6 string). Acode !== "Ok"or 0-route response is a script HARD FAILURE for that pair — never a silently missing pair. - Script posture — exact analog
search/scripts/overpass-topup.mjs(Phase 4 D-06 "repo tools never runtime deps"): runs on the MAINTAINER's machine (npm run routing:fixtures), never in CI, never atvite build, never at Worker request time. Mandatory descriptive User-Agent, polite delay between pair fetches, writes the committedrouting/src/data/route-fixtures.json, soft-fails with a per-pair diagnostic. D-12 boundary stated in the script header: it hits FIXED PUBLIC coordinates (gazetteer landmark points) only; no user coordinate can ever enter a fixture; the app itself gains no network dependency. - Sustainability honesty: the demo server is a free best-effort service. That is acceptable precisely because its output is COMMITTED — the runtime never touches it. A server policy change breaks the (rare) regeneration script, never the product.
Finding 3 — RoutingSource interface (the SearchSource analog)
Mirror of search/src/types.ts (verbatim shape correspondence, D-01):
| SearchSource (Phase 4, shipped) | RoutingSource (this phase) |
|---|---|
readonly name: string |
readonly name: string — "fixture" now; "wasm-router" (#2) / "valhalla" (#3) documented, not shipped |
search(q): Promise<SearchHit[]> |
route(req): Promise<RouteResult | null> |
| EMPTY array = honest refusal (D-13) | null = honest "router pending" (D-03) — never a blank map, never a fake route |
byId(id): GazetteerEntry | null (sync) |
not needed this phase (the webview already holds the resolved GazetteerEntry from Phase 4) |
RouteResultcarriesoptions: RouteOption[]— index 0 is the best route (OSRM returns best-first; order preserved), 1..3 entries. Each option: geometry (decoded points + derived cumulative distances), summary (distance/duration), and the day-onestepsfield (typed, empty in fixture world — D-10 defers instructions to Phase 6; an empty array is honest "not wired", a fabricated step list would not be).- Fixture matching law: a pair serves a request when
destinationIdmatches exactly AND the requested origin sits within a fixed public tolerance (200 m haversine) of the pair's pinned origin. Anything else →null(the pending state). The 200 m pad absorbs GPS jitter around a known landmark without ever fabricating a route for an unknown origin. - Lazy-index + three-environment laws carry over verbatim from
search/src/gazetteer.ts(LAW 1 lazy memoized load; LAW 2 static JSON import inlined by esbuild AND Vite, importable by bare Node 24 withwith { type: "json" }; LAW 3 no logging/no storage — the D-15 analog: request coordinates never leave the caller).
Validation Architecture
- Framework: existing hand-rolled runners; plan 05-01 adds the fifth workspace's identical pair (
routing/test/runner.mjs+routing/test/harness.mjs, copied verbatim fromsearch/test/). No new framework (Phase 4 precedent). - Primary acceptance instruments: (1) the routing workspace suite (codec vectors, contract gates, lazy/no-logging/no-storage laws); (2) the golden-pair corpus gate (offline: every pair's geometry↔summary consistency recomputed by haversine + round-trip codec proof + alternatives floor) — the D-14 corpus-gate shape, adapted to fixtures; (3) the web suite extensions (route state machine, copy gate, BackButton walk); (4) BA9 bundle-budget gate over the production build; (5) the capture harness legs (
?fixpair=) with theARAHARAH-ROUTE state=token. - Offline law: no test, suite, or gate ever touches a network. The OSRM demo is exercised ONLY by the maintainer script. This is what makes the corpus gate deterministic and the "fixture resolves cannot fail at runtime" claim (UI-SPEC Copywriting §Error state) true.
- Blocking checkpoint: none this phase — no real-device leg is required (route display is fixture-world; the Phase 3/4 device gates already proved map + deep-link on hardware). Capture legs + structural gates carry verification.
RESEARCH COMPLETE
Light pass as directed: three findings, folded into plans 05-01/05-02/05-04/05-05. No open questions remain.