Off-route re-routing back to the course #57

Open
opened 2026-08-31 17:30:08 +02:00 by robert · 0 comments
robert commented 2026-08-31 17:30:08 +02:00 (Migrated from git.butzei.de)

Goal

When the rider leaves the route, compute a way back. BRouter is already a dependency and does this entirely offline, so no connectivity is needed mid-ride.

Acceptance criteria

  • Re-route offered after off-route is confirmed, not on the first stray metre
  • BRouter asked for a route from the current position to a rejoin point on the course
  • Rejoin point chosen sensibly - not the nearest point if that means riding backwards along the route
  • Returned leg enriched into cues and spliced into the active cue sheet
  • Watch guides along the return leg, then resumes the original route
  • Rider can decline and continue navigating manually
  • Re-routing never blocks the ride view or the recording, and runs off the main thread
  • Repeated failure to re-route degrades gracefully to plain off-route reporting
  • Behaviour when BRouter is unavailable is defined and tested

Files

  • companion/.../route/nav/Rerouter.kt

Notes

Pulled into Phase 3 so it is built while the navigation engine is fresh. See docs/DECISIONS.md D20.

Update — 2026-09-01: splicing is the hard half, and D7 gets one bounded exception

D7 says enrichment happens once at import and never during a ride. Re-routing necessarily enriches
a detour mid-ride. Both could not be true; D30 resolves it — re-routing is the single named exception,
and it is bounded:

  • Tier 1 only. A re-route enriches through BRouter offline, or not at all. No network call
    ever happens mid-ride
    — that is what D7's rule was really protecting
  • Detour enrichment runs under a hard time budget; if it does not complete, the watch shows
    off-route rather than a stale or partial cue sheet

This issue previously covered only the rejoin-point choice. The splice is the part that breaks things:

  • Detour cues merged into the existing cue sheet, with every later cue's index shifted
  • The result is a new route object with its own monotonic distance axis (#31), swapped in once
    when the detour is accepted
  • NAV_REMAIN_M and NAV_ETA_S recomputed against the new geometry, and must not jump
  • NAV_STATE = 3 (re-routing) shown while the detour is being computed, so the watch is honest
    about what it is doing
  • Declining or ignoring a re-route returns cleanly to plain off-route
  • Tests: re-route on an out-and-back, and a re-route that rejoins ahead of where the rider left

Update — 2026-09-02: scope cut — the bearing arrow, not the splice

D42 supersedes D30. Everything about enriching and splicing a detour moves to a new Phase 5 issue.
What ships in Phase 3 is the part an off-route rider actually needs: a direction and a distance.

With the map off by default, a rider who has taken a wrong turn is looking at a screen that says "off
route". A bearing arrow answers that. A freshly enriched cue sheet is a great deal of machinery for
the same question.

This issue now covers only:

  • Rejoin-point selection — D20's genuinely hard part, and now the whole of this issue. The
    nearest point on the course is often wrong, because it can mean riding backwards along the route
  • Bearing and distance to that point emitted as NAV_REJOIN_BEARING / NAV_REJOIN_M (#32)
  • Rejoin detected, and the original cue sheet resumed on its original distance axis
  • Nothing is enriched, nothing is spliced, no new route object is created
  • Tests: rejoin-point choice on an out-and-back, and on a route that passes close to itself

Removed from this issue (now the Phase 5 re-routing issue): the BRouter call for a return leg, the
tier-1-only rule, the mid-ride time budget, cue merging and index shifting, the new route object,
NAV_REMAIN_M / NAV_ETA_S recomputation, and NAV_STATE = 3.

Two consequences worth noting. D7's "enrichment happens once, at import — never during a ride" is true
again with no exception, which makes the airplane-mode acceptance test (NFR-C3) something a test
can assert absolutely. And #31's monotonic distance axis is now guaranteed by construction.

## Goal When the rider leaves the route, compute a way back. BRouter is already a dependency and does this entirely offline, so no connectivity is needed mid-ride. ## Acceptance criteria - [ ] Re-route offered after off-route is confirmed, not on the first stray metre - [ ] BRouter asked for a route from the current position to a rejoin point on the course - [ ] Rejoin point chosen sensibly - not the nearest point if that means riding backwards along the route - [ ] Returned leg enriched into cues and spliced into the active cue sheet - [ ] Watch guides along the return leg, then resumes the original route - [ ] Rider can decline and continue navigating manually - [ ] Re-routing never blocks the ride view or the recording, and runs off the main thread - [ ] Repeated failure to re-route degrades gracefully to plain off-route reporting - [ ] Behaviour when BRouter is unavailable is defined and tested ## Files - `companion/.../route/nav/Rerouter.kt` ## Notes Pulled into Phase 3 so it is built while the navigation engine is fresh. See docs/DECISIONS.md D20. ## Update — 2026-09-01: splicing is the hard half, and D7 gets one bounded exception D7 says enrichment happens once at import and **never during a ride**. Re-routing necessarily enriches a detour mid-ride. Both could not be true; D30 resolves it — re-routing is the single named exception, and it is bounded: - [ ] **Tier 1 only.** A re-route enriches through BRouter offline, or not at all. **No network call ever happens mid-ride** — that is what D7's rule was really protecting - [ ] Detour enrichment runs under a hard time budget; if it does not complete, the watch shows off-route rather than a stale or partial cue sheet This issue previously covered only the rejoin-point choice. The splice is the part that breaks things: - [ ] Detour cues merged into the existing cue sheet, with every later cue's index shifted - [ ] The result is a **new route object with its own monotonic distance axis** (#31), swapped in once when the detour is accepted - [ ] `NAV_REMAIN_M` and `NAV_ETA_S` recomputed against the new geometry, and **must not jump** - [ ] `NAV_STATE = 3` (re-routing) shown while the detour is being computed, so the watch is honest about what it is doing - [ ] Declining or ignoring a re-route returns cleanly to plain off-route - [ ] Tests: re-route on an out-and-back, and a re-route that rejoins ahead of where the rider left ## Update — 2026-09-02: scope cut — the bearing arrow, not the splice D42 supersedes D30. **Everything about enriching and splicing a detour moves to a new Phase 5 issue.** What ships in Phase 3 is the part an off-route rider actually needs: a direction and a distance. With the map off by default, a rider who has taken a wrong turn is looking at a screen that says "off route". A bearing arrow answers that. A freshly enriched cue sheet is a great deal of machinery for the same question. **This issue now covers only:** - [ ] **Rejoin-point selection** — D20's genuinely hard part, and now the whole of this issue. The nearest point on the course is often wrong, because it can mean riding backwards along the route - [ ] Bearing and distance to that point emitted as `NAV_REJOIN_BEARING` / `NAV_REJOIN_M` (#32) - [ ] Rejoin detected, and the **original** cue sheet resumed on its **original** distance axis - [ ] Nothing is enriched, nothing is spliced, no new route object is created - [ ] Tests: rejoin-point choice on an out-and-back, and on a route that passes close to itself **Removed from this issue** (now the Phase 5 re-routing issue): the BRouter call for a return leg, the tier-1-only rule, the mid-ride time budget, cue merging and index shifting, the new route object, `NAV_REMAIN_M` / `NAV_ETA_S` recomputation, and `NAV_STATE = 3`. Two consequences worth noting. D7's "enrichment happens once, at import — never during a ride" is true again with **no exception**, which makes the airplane-mode acceptance test (NFR-C3) something a test can assert absolutely. And #31's monotonic distance axis is now guaranteed by construction.
Sign in to join this conversation.
No description provided.