10 KiB
Phase 6: Turn-by-Turn Guidance (Live Driving) - Context
Gathered: 2026-10-24 Status: Ready for planning — with a phase-scope addition (engine A workstream, D-01)
## Phase Boundaryaraharah behaves like real navigation: prominent next-maneuver banner + collapsible list, silent self-healing rerouting powered by a REAL router (engine A: Worker-side A* over an R2-hosted KL road graph — RoutingSource backend #2, built this phase), honest ETA progression with smoothing, haptic cues, full BackButton state graph with PX-03 close protections, and trip-end data deletion (PRIV-01 in-app half). While minimized, the bot pushes schematic glyph+text turn-cards via a Worker Cron Trigger over the Phase 5-frozen guidance envelope. Verified via scripted drive replays plus one real drive.
PHASE-SCOPE ADDITION (user decision): the "defer WASM-router" deferral from Phase 5 is superseded — engine A is built IN this phase as a first-class workstream. The fixture backend remains RoutingSource #1 (golden pairs demo), engine A becomes #2 (arbitrary-origin routing), and GUID-02's rerouting is REAL from day one.
## Implementation DecisionsRouting engine — THE PIVOT #2 (user decision; supersedes the phase-5 deferral)
- D-01: Engine A ships this phase: OSM → compact road-graph build script (KL: ~30–80MB binary, typed arrays; one-ways + speed weights; turn restrictions deferred), graph hosted in R2 (upload script committed — forks upload their own), Worker-side A* (JS typed arrays, WASM optional) exposed as RoutingSource backend #2 via a JSON routing API on the Worker. Satisfies every law: $0, no VPS, fork-friendly, privacy (the Worker is our infrastructure).
- D-02: Valhalla-to-JS port ruled infeasible (C++/Boost/mmap tile stores) — recorded so it is never re-proposed. geojson-path-finder rejected (demo-grade: no alternatives/one-ways). Engine B (fully client-side webview router — graph on the phone) is the documented evolution of A, deferred post-v1.
- D-03: Fixture backend remains #1 (golden-pairs demo path); engine A is #2 and becomes the guidance engine: arbitrary current positions route for real. Router-pending state remains for graph-edge cases (e.g. R2 graph absent — a designed degradation, not an error).
Reroute semantics (GUID-02)
- D-04: The reroute trigger is a thresholds + grace state machine:
ON_ROUTE → OFF_ROUTE(deviation > threshold with a grace window + N consecutive off-route fixes before acting)→ REROUTING(engine A call; banner stays calm, no blame)→ ON_ROUTE(new route snapped in, list refreshed). Storm guard: minimum time between recalculations. Missed-turn detection = off-route past the maneuver point. All thresholds are named constants, replay-tested. - D-05: The router snaps raw GPS fixes to the graph (engine A owns snapping — one authority, no client/server drift). The watch fix streams raw; the router does the rest.
Guidance UX surface (UI-SPEC scope)
- D-06: Layout: top maneuver banner (distance + arrow + street) replacing the status strip's top register during guidance (location register compacts); collapsible full maneuver list (collapsed default, expands over the map, tap-out collapses); bottom guidance card (the route card evolves): ETA + remaining distance + progress.
- D-07: ETA smoothing: EMA + deadband — recompute from route model + snapped progress (never raw fix-to-destination distance); display updates only past a named threshold (±1 min / ±5% remaining); constants replay-tested. No visible whiplash.
- D-08: Haptics:
HapticFeedback.selectionChanged()at maneuver-approach threshold (~300m), impact at the maneuver point. Graceful no-op where unsupported. - D-09: PX-03: closing during guidance requires the "abandon route?" Rams confirm card (en/ms);
disableVerticalSwipes()while guidance is active; safe-area insets respected. PX-01: BackButton walks the full graph — search → routes → guidance → back to search — never dead-ends (extends the phase-5 two-state walk). - D-10: Camera: locked follow (center ≈65% down-screen, pitch 35°) easing along the snapped line; user pan unlocks follow → the Phase 3 recenter control returns with its semantics.
Backgrounded turn-cards (GUID-05)
- D-11: Minimize during guidance → app POSTs the guidance session (the frozen envelope contract goes LIVE: KV writes begin,
guidance:<id>, 7200s TTL). A Worker Cron Trigger (60–120s) scans active guidance keys, projects progress along the snapshot, and pushes a chat turn-card when the next maneuver changes (or every N minutes). App re-open resumes foreground guidance; cron pushes pause while foregrounded. - D-12: The card is a glyph + text card: committed arrow-glyph icon set (8 cardinal/turn PNGs, token colors) + ETA/distance/street text — no map bitmap, no renderer dependency, fully $0. Bitmap snapshots deferred to v1.x (Workers cannot rasterize; honest best-effort framing).
- D-13: "Best-effort" is honest: cron lag, Telegram delivery variance, and TTL expiry are documented behaviors, not failures; the card copy never promises what the mechanism can't (copy table in UI-SPEC).
Trip end (PRIV-01 in-app half)
- D-14: Ending a trip deletes the guidance session (KV record gone — completing the TTL law's promise actively) and shows "trip ended, data deleted" messaging (en/ms). Arrival detection = snapped progress ≥ end-of-route threshold; abandon = the PX-03 confirm path. Both delete.
Watch cadence + verification (GUID-02/03)
- D-15: Battery cadence:
watchPositionhigh-accuracy, but the app throttles by distance — guidance work runs on ≥10m moved or ≥5s elapsed, whichever first. D-05's gated-streaming law finally flips its watch ON here. - D-16: Scripted drive replays verify the state machine: recorded JSON position streams (corridor follow, off-route excursion, missed-turn excursion, GPS jitter) replayed through the machine in tests, asserting every transition, the storm guard, and ETA deadband. Plus one real drive as the device gate (phase 3/4 protocol).
OpenCode's Discretion
- Graph build toolchain choice (osmium/planetiler-graph/custom overpass extract), binary layout, A* variant (bidirectional), alternatives generation via penalty runs
- Deviation/storm/ETA constants' initial values (replay-tuned; named, not magic)
- Cron interval within 60–120s; "N minutes" repeat cadence; glyph icon production method (hand SVG → PNG)
- Banner/list micro-layout within the Rams system; exact copy wording within the tone law
- Envelope write timing (on guidance start vs first minimize — either honors the contract)
- The guidance screen is the product's cockpit: banner + guidance card are instruments, the map is the world — nothing decorative, everything reading-bearing.
- Reroute messaging follows the clinical-honest law: the banner never blames, never asks — it recalculates, states the new route, moves on.
- The 15 fixture pairs remain the demo script; engine A pairs with them (same destinations, arbitrary origins) for the real-drive gate.
<canonical_refs>
Canonical References
Downstream agents MUST read these before planning or implementing.
Phase scope + decisions
.planning/ROADMAP.md§Phase 6 — goal, 5 success criteria, research-recommended framing.planning/REQUIREMENTS.md§GUID-01..06, PX-01, PX-03 (+ PRIV-01 trip half).planning/phases/05-routing-route-display/05-CONTEXT.md— the envelope contract being activated; RoutingSource ladder; D-05 cadence law.planning/phases/03-mini-app-shell-geolocation/03-CONTEXT.md— memory-only law, follow/recenter semantics, capture harness.planning/phases/05-routing-route-display/05-UI-SPEC.md— shell conventions the guidance UI extends
Codebase
routing/— RoutingSource interface engine A implementsweb/src/location/— the chain; guidance watch extends it (D-05 flip)web/src/map/follow.ts+recenter.ts— camera semantics reusedweb/src/route/— phase 5 route surface evolving into guidance chromebot/src/kv/— TTL-only patterns; the envelope service lands beside thembot/wrangler.jsonc— Cron Trigger wiring surface
</canonical_refs>
<code_context>
Existing Code Insights
Reusable Assets
- RoutingSource + polyline6 + fixture backend (phase 5) — engine A plugs beneath the same seam
- GuidanceSessionEnvelope types (phase 5, frozen) — the service implements, not redefines
- Follow controller + recenter control (phase 3) — locked-follow camera reuses them
- Mock-Telegram harness + capture profiles — replay fixtures + guidance captures extend them
- glyphs: none yet — the 8-arrow set is new, committed once, reused everywhere
Established Patterns
- Interface + backend ladder (now 3 real stages); hand-rolled runners; TDD RED→GREEN
- TTL-only KV with keyed namespaces; category-only logging
- Device gates: real hardware at exit (this phase adds the real drive)
Integration Points
- Cron Trigger = new Worker surface (first scheduled handler in the repo)
- R2 = new storage surface (first binding) — upload script + fork docs
- HapticFeedback/BackButton/vertical-swipes = Telegram WebApp APIs behind capability guards
</code_context>
## Deferred Ideas- Engine B: fully client-side webview router (graph on the phone; privacy-maximal endgame)
- Turn restrictions in the graph; contraction hierarchies; CH preprocessing
- Bitmap map snapshots in turn-cards (needs a rasterizer we don't have — v1.x)
- Voice guidance, live trip share, toll toggle, speed limits (v1.x, REQUIREMENTS deferred table)
- Traffic-aware ETAs, incident-aware rerouting (v2)
Phase: 06-turn-by-turn-guidance-live-driving Context gathered: 2026-10-24
Post-planning ratification (2026-10-24)
- A1 CPU decision: c3 now (short routes in-budget; cross-metro → honest router-pending), c2 (ALT landmarks) committed as the next-milestone upgrade lever. c1 (Workers Paid) rejected — $0 law.
- D-12 ratification: street-bearing turn-cards confirmed (default stands).
- R2 activation pending user dashboard click (code 10042); non-blocking for cron/captures.