Ticket 108: Terrain-, Obstacle-, and Geofence-Aware Contingency Paths¶
Status¶
Planned (deferred). The gap is real and reproducible today; closing it changes
the meaning of rth_is_feasible and every divert verdict, so it is scheduled
rather than patched.
Goal¶
Make the return-to-home and divert paths the estimator computes obey the same terrain, obstacle, and geofence evidence the planned route already obeys. Today they are energy-only: a contingency path is "feasible" when the battery covers it, regardless of what is in the way.
Why This Matters¶
The README asks the question the tool exists to answer — "can it still fly home
from every waypoint?" — and the checklist prints ✓ RTH reserve PASS reserve
intact for RTH from all N leg(s). Both read as a statement about the route
home. Both are statements about watt-hours.
For a BVLOS operator this is the wrong failure mode in the wrong direction: the mission that most needs a contingency check is the one flying a valley or a ridge line, and that is exactly the mission where a straight-line energy budget is most optimistic. The same gap covers landing-zone divert reachability, which is the other half of the contingency story.
Current gap¶
estimate_emergency_path (bvlos_sim/estimator/execution/energy.py:1094)
builds the RTH path as wind-triangle segments plus a terminal vertical phase.
compute_divert_estimate (bvlos_sim/estimator/execution/divert.py:45) does
the same for landing-zone diverts using a geodesic Dubins path. Neither module
contains a single reference to terrain, obstacles, or geofences:
$ grep -c "terrain" bvlos_sim/estimator/execution/energy.py
0
$ grep -c "terrain" bvlos_sim/estimator/execution/divert.py
0
$ grep -c "geofence\|obstacle" bvlos_sim/estimator/execution/divert.py
0
The terrain provider is loaded, active, and consulted for the planned route — it is simply never queried along the path home.
Reproduction¶
A terrain grid holding a 1500 m ridge in a box on the direct line between the last waypoint and home, and an L-shaped route at 300 m AMSL that dodges the box on the way out:
| Route item | is_feasible |
rth_distance_m |
|---|---|---|
takeoff |
True |
0 |
wp1 |
True |
3003 |
wp2 |
True |
5397 |
landing |
True |
5230 |
rth_is_feasible: True. Every timeline point is feasible. The direct path from
wp2 to home crosses a ridge 1200 m above the cruise altitude.
The route is not merely un-checked — it is checked and passed. With
constraints.min_terrain_clearance_m: 30.0 and the same terrain asset, the
mission reports obstacle feasible, because the planned route genuinely clears
the box. The evidence covers the ridge; only the contingency path ignores it.
Scope¶
- Sample the RTH path from
estimate_emergency_pathagainst the terrain provider at the same sub-segment resolution the planned route uses, and applyconstraints.min_terrain_clearance_mwhen it is set. - Apply
constraints.min_obstacle_clearance_mand the obstacle provider to the same samples. - Evaluate geofence conflicts along the RTH and divert geometry, honouring
floor_m/ceiling_maltitude bands andactive_from/active_untilwindows as the planned-route check does. - Do the same for
compute_divert_estimate, so landing-zone reachability means reachable, not merely within energy range. - Report the outcome structurally: a per-timeline-point reason (for example
rth_terrain_clearance,rth_geofence_conflict) rather than a bareFalse, so an operator can tell an energy shortfall from an obstruction. - Decide and document the altitude policy: an RTH path that must climb over terrain costs more energy than the straight line currently billed, so the clearance check and the energy model have to agree on one profile.
Integration Requirements¶
- Contingency checks must consume the same providers already on
EstimationContext; no new asset types and no live lookups. - When no terrain, obstacle, or geofence asset is configured, behaviour is unchanged — this ticket must not turn a missing asset into a failure, which is the readiness gate's job, not the energy model's.
estimator-envelopefield names stay stable; the RTH timeline point gains fields rather than changing the meaning of existing ones. A changedis_feasibleon a mission with terrain configured is a deliberate, version-bumped contract change and needs a golden-fixture update in the same commit.- Determinism is preserved: same inputs, same path samples, same verdict.
Acceptance Criteria¶
- The reproduction above returns
rth_is_feasible: Falsewith a structured terrain reason on the points whose path home crosses the ridge, while the planned route continues to pass its own clearance check. - A divert to a landing zone on the far side of a geofenced or obstructed volume is reported unreachable, not merely energy-feasible.
- A mission with no terrain, obstacle, or geofence asset produces byte-identical output to today.
- The RTH energy figure and the RTH clearance check are computed from the same altitude profile; a path that climbs to clear terrain is billed for the climb.
docs/missions.mdand the README stop implying that "can it fly home" is answered when only the battery was checked.
Out of Scope¶
- Replanning: computing an alternative obstacle-free path home. This ticket detects that the modelled path is obstructed; choosing a different one is Ticket 036 / 044 territory.
- 3D Dubins or terrain-following contingency profiles.
- Dynamic obstacles or traffic.