Files
arah2/.planning/phases/05-routing-route-display/05-RESEARCH.md
T

51 lines
6.7 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 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 `cumulativeDistanceM` once 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=2` returns up to 2 alternates (≤ 3 total routes; matches RTNG-02's "up to 3"), `overview=full` gives the complete geometry, `steps=false` (turn instructions are Phase 6; fixture `steps` stay typed-but-empty — never fake).
- **Response contract:** `code: "Ok"`, `routes[]` best-first (preserve order), each with `distance` (m), `duration` (s), `geometry` (polyline6 string). A `code !== "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 at `vite build`, never at Worker request time. Mandatory descriptive User-Agent, polite delay between pair fetches, writes the committed `routing/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) |
- `RouteResult` carries `options: 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-one `steps` field (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 `destinationId` matches 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 with `with { 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 from `search/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 the `ARAHARAH-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.*