Skip to content

fix: false go verdicts - #28

Merged
Monotox merged 20 commits into
mainfrom
fix/false-go-verdicts
Jul 24, 2026
Merged

fix: false go verdicts#28
Monotox merged 20 commits into
mainfrom
fix/false-go-verdicts

Conversation

@Monotox

@Monotox Monotox commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Summary

Test Plan

  • uv run pytest passes
  • uv run ruff check . passes
  • Documentation updated, if user-facing behavior changed
  • Golden fixtures updated, if a public JSON or Markdown contract changed

Contract Impact

  • No public contract surfaces changed
  • Public contract changes are intentional and versioned per CONTRIBUTING.md (Public contracts)

Monotox added 20 commits July 24, 2026 08:58
Charging a whole leg at its vertical phase power understated descent
energy: supplying descent_power_w cut mission energy by 36% and turned
INFEASIBLE into FEASIBLE. Legs are now split by phase time.

max_segment_length_m defaults to 500 m so a leg is sampled along its
length rather than at its departure end, and usable_capacity_curve
derating now reaches the RTH and divert margins.
The check used a two-point line in degree space. A 40 km leg at 60N bows
about 54 m off that chord, so a zone in the gap was flown through and
reported clear; across the antimeridian the line wrapped the wrong
way round the globe.

Legs now come from the shared sampler with longitudes unwrapped. Also
stops out-of-window required zones gating altitude, and fixes the
transposed (lon, lat) unpack in the SORA route-AGL walk.
Deleting any of these left the suite green, and each one only makes a
verdict more permissive: obstacle uncertainty_m, the ground-risk and
landing-zone sampling-gap terms, and the RTH min-groundspeed abort.
An unbuffered figure was rendered as "Mission iGRC" with nothing to
separate it from a buffered assessment, which on the same route can be
two risk classes higher. The report now states the buffer and warns when
it is zero.
A scenario is only FAILED when an assertion actively fails, so SKIPPED
and UNSUPPORTED outcomes left it PASSED. A divert check is skipped when
the mission declares no lost_link_policy, so the contingency went
unevaluated and still reported a pass.

Inconclusive assertions are now missing evidence, which blocks GO.
Readiness was computed in the envelope but recomputed or ignored
elsewhere, so one run could report no_go in JSON while the CLI exited 0
and the checklist printed GO. Batch consulted only ScenarioStatus, so a
mission the estimator called INFEASIBLE reported PASSED.

All four surfaces now call scenario_readiness(), and --engineering-only
is threaded into batch dispatch instead of being permanently implied.
Declaring a resource system hands the battery and RTH gates to
resource_link, which budgeted nameplate: a 170 Wh pack delivering
93.5 Wh was budgeted at 170 Wh and INFEASIBLE became SUCCESS.

A leg's stored wind comes from the horizontal integration, which ends
early when the climb outlasts the ground track, so wind limits were
checked against the departure-end wind. Legs are now sampled over their
full duration and altitude band.
Proximity was an intersection test, which is empty unless the geometries
actually cross. Zero-radius obstacles were invisible under the default
clearance, and a vertical leg built a zero-length line that intersects
nothing, so a 75 m mast 18 m from the pad reported feasible. It is now a
distance test.

Vertical clearance also interpolated altitude linearly, sitting above
the flown profile; it now takes the lower endpoint. Adds
OBSTACLE_ZERO_FEATURES and OBSTACLE_KEEP_OUT_NOT_CONFIGURED.
This was the only wind-aware solver that never gated its wind triangle,
so a divert needing 70.8 deg of crab against a 35 deg limit passed.
Energy charged transit only and never read the zone altitude, so a 400 m
descent cost nothing, and the whole Dubins path was charged at the
straight-line groundspeed.

Both gates now fire, the terminal descent is budgeted, and the path is
bounded by the harshest heading the turn sweeps.
The readiness gate only rejected iGRC > 7 and never checked the
population assessment buffer, so a centerline-only figure counted as
ground-risk evidence.

A landing_zone_unavailable event naming an unconfigured zone removed
nothing, so the run came back unperturbed while the report echoed the id
back as closed. Unmatched ids now fail closed.
A calibration overrides vehicle performance, but provenance enumerated a
fixed set of inputs, so a calibrated run and the base run were
byte-identical in provenance. It now has its own entry.

input_schema_versions advertised population-grid.v1 while the loader
only accepts v2, and omitted obstacles and calibration entirely. The
test compared the CLI to the same constants, so the drift was invisible.

Bumps the envelope to estimator-envelope.v10.
Missions are preloaded to enumerate their assets, so one unreadable
mission aborted the whole batch before any run executed. The failing run
is now an ERROR row naming the run id and the file.

A leftover .tmp from an interrupted run blocked reruns into the same
--output-dir forever; our own scratch files are cleaned up instead.

migrate had no catch-all and raised TypeError on a non-string
airspace.class. Bounds dt_s so a tiny step cannot make the propagation
timeline unbounded.
render_sora_markdown discarded everything but the assessment, so the
filed artifact named neither the tool version, the SORA edition, the
population data vintage nor the input digests.

The envelope also omitted the terrain asset that gates the maximum-AGL
verification, so the assessment could not be reproduced from it.
Metadata was written with Python repr(), so a source name or reference
containing quotes produced a file the loader could not read back.

Also splits the forward-flight mass exponent out of the hover one:
transit scaled by a hardcoded 0.5, so induced_power_mass_exponent never
reached cruise. The new cruise_power_mass_exponent defaults to 0.5 and
keeps existing results.
A single missing SRTM cell aborted the whole fetch, so the documented
"fetch your own area" command died on our own coordinates and left the
output directory empty. Adds --void-policy {fail,interpolate} and lets
fetch_all warn and continue.

The bundled Alpine terrain had five voids written as 0.0 - sea level in
the Alps - now filled from the nearest sampled elevation. Corrects the
example README, which also claimed the bundled wind grid exercises
spatiotemporal interpolation when only its 10 m band holds real values.
adapters.cli registers every command at import time, so importing a
single command first left the module half-initialised and raised
ImportError. 18 command modules and adapters.preflight were affected.

Exit codes, output formats and error helpers move to a leaf
adapters.cli_contract that commands import instead; adapters.cli
re-exports them unchanged.

validate now refuses a calibration fitted from the trace being
validated: a model tuned on a flight reproduces that flight, so the
agreement was never evidence.
The wind grid held real values only in its 10 m band, on an axis
expressed above ground while the provider queries metres AMSL. The route
flies near 550 m AMSL, so every sample clamped to the grid edge and the
flagship example demonstrated the wind model on a null input: worst wind
0.00 m/s, now 3.55 m/s.

Terrain is re-fetched with --void-policy interpolate; SRTM voids over
the lakes had been written as 0.0, sea level in the Alps.

Corrects the README coverage table - the terrain grid spans lon
8.15-8.45, not 8.1-8.4 - and the reproduce commands.
The distribution installed five generic top-level names - main,
adapters, estimator, schemas, scripts - and pip does not detect file
conflicts, so any co-installed distribution shipping one of them
silently overwrote ours, and ours overwrote theirs. Installing a second
package with a schemas/ directory broke bvlos-sim outright, and
uninstalling it again did not repair the install.

Imports gain a prefix: "from estimator import ..." becomes "from
bvlos_sim.estimator import ...". Console entrypoints are unchanged and
python -m bvlos_sim now works.

CI and the suite assert the wheel exposes exactly one top-level name.
@Monotox
Monotox merged commit 380eedd into main Jul 24, 2026
6 checks passed
@Monotox
Monotox deleted the fix/false-go-verdicts branch July 24, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant