Skip to content

Router: don't fail hard when a route only exists below the requested safety depth — find it and warn (e.g. Flensburg→Marstal at default 3.0 m) #53

Description

@DocGerd

Problem

Planning Flensburg → Marstal at the default safety depth (3.0 m) fails outright with error.noRoute.unreachable ("No route found — the destination cannot be reached without crossing land or too-shallow water."). In reality the passage is fine for a 2.1 m-draft boat: Marstal's dredged approach channel is deep enough, but EMODnet bathymetry can't resolve it at 46 m cells, so in the shipped mask Marstal's snap cell sits in a 119-cell pocket that only 4-connects to open water at gate depths ≤ 2.3 m (documented in app/src/routing/realmask.repro.test.ts:114-122, CONNECTIVITY_EXCEPTIONS_M in pipeline/verify_mask.py, PR #8, #9).

A hard failure is the wrong UX for this class of data limitation. The user gets a dead end with no way forward except manually guessing a lower safety depth in settings.

Expected behavior

When no route exists at the requested safety depth, the router should degrade gracefully instead of failing completely:

  1. Retry with a relaxed depth gate (bounded below — never below boat draft 2.1 m; e.g. step down 3.0 → 2.7 → 2.4 → 2.1, or binary-search the highest depth that routes).
  2. If a relaxed route is found, return it with explicit shallow-water warnings instead of no-route:
  3. If even the relaxed floor fails, keep today's unreachable error.

Constraints / design notes

  • Navigability is decided at query time (cellDepth >= safetyDepthM), so the fallback is pure re-query — no mask regeneration involved.
  • The warning must be honest and prominent: the app is a passage-planning aid, not a navigation device — copy must say the charted data may understate/overstate real depths (dredged channels are exactly the case where the chart data is pessimistic), not imply the route is verified safe.
  • Both rig runs (genoa + fock) should relax consistently so the rig comparison stays apples-to-apples.
  • Plan stays structured-clone-safe; new warning fields must fit that contract.
  • All new user-facing strings go through both i18n dicts (de/en).
  • Relaxation multiplies solver runs — mind worker-time budget and progress reporting; the relaxed retries should reuse the already-transferred mask, not re-transfer it.
  • app/src/routing/realmask.repro.test.ts currently pins Flensburg → Marstal at 2.3 m with a comment saying "at 3.0 m 'unreachable' is the CORRECT answer for this data" — after this feature, the spec acceptance case should plan at DEFAULT_SETTINGS and assert a route with shallow warnings instead.

Acceptance criteria

  • Flensburg → Marstal at default settings (3.0 m safety depth) returns a route, flagged with shallow-water warnings, instead of unreachable.
  • The UI shows a clear warning (banner/summary + highlighted legs) identifying where and how shallow the flagged segments are.
  • Relaxation never goes below boat draft; a genuinely unreachable destination still errors as today.
  • Warnings persist with the saved plan and render identically on reload (IndexedDB round-trip).
  • de/en strings added to both dicts; realmask acceptance test updated to default settings.

Related: #9 (mask connectivity limits — this issue is the UX mitigation for routes those sub-cell features block).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions