CLI reference¶
Exact facts for every command: usage, key flags, output formats, and exit codes. For a guided first run, see Getting started; for authoring the YAML inputs, see Missions and vehicles.
All commands run as uv run bvlos-sim <command> from a source checkout, or
bvlos-sim <command> from an installed wheel. Input files may be .yaml,
.yml, or .json; relative asset paths resolve from the referencing file's
directory. Every mission file must declare schema_version: mission.v7 —
upgrade older files with migrate.
Exit codes¶
| Code | Meaning |
|---|---|
0 |
Success (for estimate/scenario/batch: operational GO, or engineering-only pass) |
10 |
Infeasible, failed, or operational NO-GO |
11 |
Invalid input |
12 |
Unsupported operation |
13 |
Internal or adapter runtime error |
14 |
Cancelled by SIGINT/SIGTERM (console entrypoint only) |
A missing or unreadable input file is invalid input (11, with an error
envelope) like any other load failure. Only genuine command-line usage errors
— an unknown flag or a missing required argument — exit 2 from the argument
parser.
Failures are machine-readable on stdout: the estimator commands emit their
result envelope with an error status, and the commands without an envelope —
migrate included — emit a {"command", "status", "message"} JSON error
object. batch, convert and export are the remaining exceptions: they
still print load failures as plain text on stderr. estimate reports the
failing input path exactly as it was given on the command line, so identical
inputs produce an identical envelope from any checkout directory.
Per-command behavior:
| Command | 0 |
10 |
11 |
12 |
13 |
Notes |
|---|---|---|---|---|---|---|
estimate |
✓ | ✓ | ✓ | ✓ | ✓ | 11 can also be a computed invalid-input failure. |
scenario |
✓ | ✓ | ✓ | ✓ | Every non-passed outcome collapses to 10. |
|
batch |
✓ | ✓ | ✓ | ✓ | 10 if any run is infeasible or NO-GO; 11 if any run fails to load. |
|
sample |
✓ | ✓ | ✓ | Never 10: an infeasible Monte Carlo result is in the body, exit 0. |
||
propagate |
✓ | ✓ | ✓ | Never 10: same divergence as sample. |
||
size-battery |
✓ | ✓ | ✓ | A NO answer (no feasible capacity) is in the body, not 10. |
||
sora |
✓ | ✓ | ✓ | ✓ | ✓ | 10 for out-of-scope Step 8, GRC > 7, rejected mitigation credit, or an infeasible mission; 12 for an unsupported estimator failure. |
validate |
✓ | ✓ | ✓ | ✓ | 10 when an acceptance threshold fails. |
|
calibrate |
✓ | ✓ | ✓ | Fitted profile is in the body. | ||
compare |
✓ | ✓ | ✓ | ✓ | ✓ | 10 drifted/failed; 12 for a contract-only bundle. |
convert |
✓ | ✓ | ✓ | ✓ | Missing --vehicle-profile and parse errors are 11; a lossy conversion without --allow-lossy is 12. |
|
migrate |
✓ | ✓ | ✓ | Legacy input that cannot be migrated is 11; an unexpected write/adapter failure is 13. |
||
export |
✓ | ✓ | ✓ | Mission load / exportability failures are 11. |
||
ingest-log |
✓ | ✓ | ✓ | Unknown/oversized logs and missing readers are 11. |
||
sitl |
✓ | ✓ | ✓ | Input failures are 11; live adapter/timeout failures are 13. |
||
verify |
✓ | ✓ | ✓ | ✓ | 10 on any checksum mismatch or missing artifact; 11 for an unreadable or invalid bundle. |
|
schema-versions |
✓ | Read-only; always 0. |
||||
bump |
✓ | ✓ | ✓ | Developer-only release tool; 11 on version drift. |
Two rules hold everywhere:
- The exit verdict never depends on the output format.
estimate,scenario, andbatchapply the fail-closed operational readiness gate for JSON, Markdown, summary, checklist, profile, sensitivity, GeoJSON, KML, and CSV alike.--engineering-onlyopts out of the gate for non-operational analysis: onestimateit forgives missing evidence and nothing else — a check that ran and failed, or a non-waivable warning, still exits10; onscenarioandbatchit still bypasses the whole verdict. The JSON envelope always records the structuredoperational_readinessverdict. --outputwrites are atomic (temp file, thenos.replace). An interrupted run never leaves a truncated file.
Preflight validation¶
estimate, scenario, sample, propagate, batch, sora, convert,
export, calibrate, compare and size-battery support
--validate-only: load and schema-check all inputs — including referenced
mission assets — and exit without running anything. Exit 0 on success,
11 otherwise. ingest-log, migrate, verify-evidence and sitl do
not offer it.
uv run bvlos-sim estimate mission.yaml vehicle.yaml --validate-only
# mission: mission.yaml: OK
# vehicle: vehicle.yaml: OK
Add --validate-format json for a machine-readable
preflight-validation.v1 envelope with one entry per file; a failure pins the
offending file with a stable stage (schema, asset-load, reference) and
error code.
estimate¶
Deterministic mission estimation and static feasibility checks.
Usage: bvlos-sim estimate MISSION VEHICLE [--format FMT] [--output PATH]
| Flag | Default | Description |
|---|---|---|
--format |
json |
json, markdown, summary, checklist, profile, sensitivity, ground-risk, geojson, kml |
--output, -o |
stdout | Write the artifact to a file |
--engineering-only |
off | Exit 0 on computational feasibility despite missing operational evidence; failed checks and non-waivable warnings still exit 10 |
--fidelity |
v1 |
v2 adds turn arcs and fixed-wing circular loiter |
--wind-layer |
— | ALT:EAST:NORTH altitude-banded wind; repeatable |
--max-segment-length-m |
— | Sample straight legs at bounded intervals (works in v1 and v2) |
--calibration |
— | Apply a calibration-profile.v2 artifact |
--calibration-traces |
— | Source flight-trace.v1 document; repeat for every trace used by --calibration. Without them the profile still applies, but earns no log_calibrated credit |
uv run bvlos-sim estimate \
examples/real_world/alpine_mission.yaml \
examples/real_world/quadplane_v1.yaml \
--format checklist
Formats:
json— canonicalestimator-envelope.v11: provenance, diagnostics, route legs, totals, energy/geofence/landing-zone/resource/link/obstacle/weather/ ground-risk blocks, RTH reserve timeline, andoperational_readiness.markdown— human-readable report of the same result.summary— one line:FEASIBLE reserve 279.8 % flight 2m 49s [warnings N] [FAILURE_CODE].reserveis the margin above (positive) or below (negative) the reserve threshold, as a percentage of that threshold — not battery state of charge.checklist— the pre-flight go/no-go view: one✓/✗/◌row per check,Status: GOorStatus: NO-GO, and aBlocked by:line naming the missing evidence, failed checks, or blocking warnings.GOrequires every check present and passed and no unacknowledged warnings (see Warnings); missing evidence (◌ N/A) is NO-GO, never an implicit pass.profile— per-leg altitude table with terrain elevation and clearance columns when a terrain asset is configured.sensitivity— deterministic reserve sweep over cruise power (--sensitivity-power-steps, default10,20,30percent), headwind (--sensitivity-wind-steps, default1,2,3m/s), and battery capacity (--sensitivity-battery-steps, default10,20,30percent);ROBUSTwhen every variation stays feasible.ground-risk— SORA iGRC table (mission and per-leg) from a population grid and the vehicle'scharacteristic_dimension_mandmax_speed_mps. This is the intrinsic class only; thesoracommand adds ARC, SAIL, and OSOs.geojson/kml— map-ready route layers (see Map exports).
scenario¶
Deterministic scenario events (lost link, wind change, landing zone unavailable) and machine-readable assertions over the resulting estimate.
Usage: bvlos-sim scenario SCENARIO [--format FMT] [--output PATH]
Formats: json (scenario-report.v4), markdown, summary, checklist,
profile, geojson, kml. Also accepts --engineering-only, and takes
--calibration with one --calibration-traces flag per source trace.
uv run bvlos-sim scenario examples/scenarios/pipeline_demo_001_scenario.yaml \
--format summary
# PASSED 3/3 reserve 279.8 % flight 2m 49s warnings 3
PASSED n/total counts assertions; policy <ACTION> appears when a lost-link
event fired. PASSED describes assertions, not operational readiness — with
warnings or missing evidence the process still exits 10 unless
--engineering-only is set. Scenario YAML structure (events, triggers,
assertions, lost-link policies) is documented in
Missions and vehicles.
batch¶
Multiple estimate, scenario, or propagate runs from one batch.v1 manifest.
Usage: bvlos-sim batch MANIFEST [--format FMT] [--output-dir DIR]
format_version: "batch.v1"
runs:
- id: alpine_standard
mission: ../real_world/alpine_mission.yaml
vehicle: ../real_world/quadplane_v1.yaml
run_type selects what every run in the manifest does — estimate (the
default, and what an unversioned manifest means), scenario, or propagate:
format_version: "batch.v1"
run_type: scenario # each run points at a scenario file
runs:
- {id: nominal, scenario: scenarios/nominal.yaml}
- {id: lost_link, scenario: scenarios/lost_link.yaml}
format_version: "batch.v1"
run_type: propagate # each run points at a stochastic plan
runs:
- {id: wind_sweep, plan: stochastic/wind.yaml}
Paths resolve relative to the manifest. The table columns match the run type:
estimate shows reserve margin and flight time; scenario shows the assertion
count (passed/total); propagate shows the modeled pass rate. Exit is 10
when any estimate run is infeasible/NO-GO or any scenario run fails; propagate
runs are diagnostic and never exit 10.
--output-dir writes one file per run id, in that run type's envelope
(estimator-envelope.v11, scenario-report.v4, or stochastic-envelope.v2).
--format selects json, markdown, or summary for any run type; the
route-shaped formats geojson, kml, checklist, and profile are
estimate-only.
Only two formats render to stdout: summary (the default run table) and csv
(the same table comma-separated, columns matching the run type). Every other
format is a per-run file, so it requires --output-dir — asking for one
without a directory exits 11 with an error object rather than quietly
printing the summary table. When --output-dir is set, stdout keeps the run
table and the artifacts go to the directory.
sample¶
Seeded Monte Carlo parameter sweep over wind, cruise speed, cruise power, and
battery capacity. Emits uncertainty-report.v2.
Usage: bvlos-sim sample UNCERTAINTY [--format json|markdown|summary]
uv run bvlos-sim sample \
examples/uncertainty/pipeline_demo_001_wind_uncertainty.yaml --format summary
# DIAGNOSTIC modeled_pass 100% conditional_end_energy p5 811.3 Wh p50 854.9 Wh p95 898.2 Wh time p50 2m 50s n=200
This is a diagnostic, not a probability: modeled_pass is the fraction of
evaluated samples whose deterministic run passed the modeled constraints, and
the percentiles are conditional on those passing samples. Read p5 as the
pessimistic tail — 95% of modeled-pass samples land with at least that much
energy — and plan against it, not the median. The command always exits 0
once the run completes.
propagate¶
Time-stepped stochastic particle propagation over the mission timeline, with
optional GPS/battery sensor models and EKF estimation-error traces. Emits
stochastic-envelope.v2. Same formats and diagnostic semantics as sample;
the timeline adds a per-step conditional_reserve_violation_rate.
Sample accounting is three-way: sample_count + infeasible_sample_count +
failed_sample_count == plan.samples, and spatial_infeasible_count is a
subset of the infeasible count. Vehicles with a controller block and
non-zero wind_process_noise_std_mps are rejected rather than approximated.
Long sample, propagate, and batch runs can stream progress:
--progress-format jsonl emits one JSON object per ~5% of the run to stderr
({"event":"progress","command":"propagate","completed":250,"total":1000,"elapsed_s":75.3};
batch records also carry run_id — the id of the run that just completed —
so a worker can attribute stalls);
--progress-file PATH writes the same stream to a tailable file. Progress
never touches the result envelope or the exit code.
size-battery¶
Search the minimum battery capacity that makes the mission feasible, including
the candidate pack's mass. The vehicle must define
energy.battery_excluded_operating_mass_kg and
energy.battery_specific_energy_wh_per_kg.
Usage: bvlos-sim size-battery MISSION VEHICLE [--margin N]... [--format FMT]
The search stops at mass.max_takeoff_kg and does not assume feasibility
improves monotonically with capacity — heavier packs can become infeasible
again — so the report gives a verified feasible interval at 1 Wh resolution.
A --margin target above the verified upper bound is reported UNAVAILABLE,
never silently substituted. Formats: markdown (default), json
(battery-sizing-report.v2), summary.
sora¶
SORA 2.5 pre-assessment: intrinsic and final GRC, ARC, SAIL, Step 8 adjacent-area and containment requirements, and all 17 Table 14 OSO rows.
Usage: bvlos-sim sora MISSION VEHICLE [--format markdown|json]
The command is strictly evidence-gated: it requires population-grid.v2
population evidence, a complete airspace descriptor, and an explicit
sora.ground_risk_footprint (see
Missions and vehicles). Applied M1/M2 mitigation
declarations earn no credit until an Annex B criteria evaluator exists — the
assessment is still produced with the final GRC equal to the intrinsic GRC,
each declaration is recorded as credit_rejected_pending_annex_b, and the
command exits 10 so the no-credit result stays auditable. It never assesses
Annex E containment or OSO compliance and is a planning aid, not an
authorization.
The maximum-AGL/1:1-buffer gate takes the conservative minimum terrain over
the full declared operational/contingency-volume plus Ground Risk Buffer
corridor; centreline-only terrain providers and grids that do not cover the
width are refused. If assets.geofences_file supplies ICAO
properties.airspace_class labels, a label with explicit altitude/time scope
can refute the declared worst case only when it overlaps the modeled corridor
and implies a higher initial ARC. A different letter that maps to an equal or
lower ARC is not a contradiction. Unbounded/timeless labels stay advisory, and
neither a match nor an absence verifies the declaration: the fetch workflow
retains keep-out airspace only, SORA 2.5 permits multiple classifications across
one operational volume, and no NOTAM/TFR/UAS geo-zone currency source is
consulted. The SORA envelope records the geofence input digest.
validate¶
Compare a predicted mission estimate against an observed flight trace
(flight-trace.v1, produced by ingest-log).
Usage: bvlos-sim validate MISSION VEHICLE TRACE [--format markdown|json]
Reports predicted-vs-observed flight time, horizontal distance, mean groundspeed, and landing reserve at mission and per-phase level, each with absolute and percent error. Acceptance thresholds gate the exit code:
| Flag | Default | Gates |
|---|---|---|
--max-time-error-percent |
20 |
Mission-time error |
--max-distance-error-percent |
10 |
Horizontal-distance error |
--max-speed-error-percent |
15 |
Mean-groundspeed error |
--max-reserve-error-percent |
10 |
Landing-reserve error |
A failed gate still writes the report and exits 10. A trace whose embedded
mission/vehicle hashes do not match the supplied inputs is rejected (11).
calibrate¶
Fit a calibration profile from observed flights. Kinematics — cruise_speed_mps,
climb_rate_mps, descent_rate_mps, max_station_keep_wind_mps — and the phase
energy coefficients — hover_power_w, cruise_power_w, climb_power_w,
descent_power_w, derived from observed battery_current_a x
battery_voltage_v — each with observed range, spread, sample count, and
provenance. Parameters with no supporting samples are listed in notes, never
fabricated.
Usage: bvlos-sim calibrate VEHICLE TRACE... [--format markdown|json]
Apply the resulting calibration-profile.v2 artifact to estimate or
scenario with --calibration PATH plus one repeatable
--calibration-traces TRACE per source trace. The command parses and segments
those traces again, reproduces the dataset id and source ids, reruns the
deterministic fitter, and compares a canonical digest of every parameter record.
Only then is a runtime-only proof allowed to clear ENERGY_MODEL_UNCALIBRATED.
Supplying --calibration without the traces is not an error: the fitted
coefficients are applied and provenance.calibration_lineage.state reports
unverified, so the artifact states that nothing reproduced the profile. The
vehicle never reaches log_calibrated that way, so ENERGY_MODEL_UNCALIBRATED
keeps blocking the operational verdict until the traces arrive or the code is
acknowledged. Traces that do not reproduce the profile are a different matter
and remain invalid input. The proof is deliberately absent from serialized
vehicle files, so copying calibration_status: log_calibrated and an
energy_calibration block does not copy trust.
validate --calibration is intentionally different: it applies an untrusted
candidate so an independent held-out trace can assess it, and grants no
operational calibration credit.
Only the energy coefficients answer ENERGY_MODEL_UNCALIBRATED, and the
profile must at least fit cruise_power_w, the coefficient every moving mission
uses or falls back to. A trace without battery telemetry yields no energy fit,
and a kinematics-only profile cannot clear that warning or unblock a GO.
Legacy calibration-profile.v1 artifacts still parse for inspection and
candidate validation. They cannot earn operational credit without source traces
that reproduce the current deterministic artifact.
This proves reproducibility and content integrity, not flight authenticity. A
normalized trace can itself be fabricated, and its raw_log_sha256 remains an
assertion until the raw log bytes are independently supplied and checked.
ingest-log¶
Normalize an ArduPilot DataFlash text/binary log or PX4 ULog into
flight-trace.v1. Requires the optional readers:
uv sync --extra flight-logs
uv run bvlos-sim ingest-log flight.bin \
--trace-id my-flight-001 \
--mission mission.yaml --vehicle vehicle.yaml \
--output my-flight-001_trace.json
Supplying --mission/--vehicle embeds their content hashes, which
validate requires. Ingestion snapshots the source bytes before parsing; the
hard size ceiling is 64 MiB (--max-size-mib can only lower it).
convert / export¶
QGroundControl .plan interchange, in both directions:
# .plan -> mission.v7 YAML (--vehicle-profile is required)
uv run bvlos-sim convert plan.plan --vehicle-profile quadplane_v1 -o mission.yaml
# mission.v7 YAML -> .plan
uv run bvlos-sim export mission.yaml -o mission.plan
convert reads the planned home, cruise/hover speeds, and supported mission
items (takeoff, VTOL takeoff, waypoint, loiter-time, RTL, land, VTOL land).
Altitude frames map onto altitude_reference: frame 0 -> amsl, frame 3
-> relative_home, frame 10 (terrain) -> terrain; mixed frames become
per-item overrides.
Conversion is fail-closed: any dropped item (Survey/ComplexItem, an
unsupported MAVLink command, an unknown altitude frame) and any populated
geoFence or rallyPoints section is a loss. By default every loss is listed
on stderr (kind plus item index or section), nothing is written, and the
command exits 12. --allow-lossy restores convert-what-we-can behavior:
each loss is still reported on stderr and the run ends with a one-line
summary — lossy conversion: N item(s) dropped, sections: geoFence,
rallyPoints — so lossy imports stay visible in CI logs. --validate-only
reports the same losses under the same exit-code contract.
export maps route actions back to MAVLink commands and omits
bvlos-sim-specific fields (constraints, assets, policy) with a stderr
note. Semantic rewrites — terrain altitudes falling back to frame 3, a
fixed-wing takeoff exported as VTOL takeoff — are warned per item and
summarised with the same one-line lossy conversion summary, but export
keeps exit 0: the .plan format simply cannot represent them. The result
round-trips through convert.
migrate¶
Upgrade an unversioned or mission.v6 file to mission.v7. Only this command
treats a missing schema_version as legacy.
uv run bvlos-sim migrate mission.yaml --dry-run # show versions + diff, write nothing
uv run bvlos-sim migrate mission.yaml --backup # in-place, writes FILE.bak first
uv run bvlos-sim migrate missions/ --glob "*.yaml" --backup
Migration refuses semantic guesses: SORA 2.0 blocks, applied legacy mitigation declarations, and ambiguous classifications require operator reassessment instead of silent relabeling.
A refusal — an unreadable file, an unsupported schema_version, or a payload
needing operator reassessment — exits 11 and prints the same JSON error
object as every other command on stdout:
{
"command": "migrate",
"status": "error",
"message": "unsupported mission schema_version 'mission.v5'"
}
An unexpected failure while writing exits 13 with the same shape.
sitl / compare¶
Build SITL evidence bundles and compare them against deterministic expectations. See SITL for the container setup, the live workflow, and the adapter contract.
# contract-only bundle from an existing scenario
uv run bvlos-sim sitl SCENARIO --output evidence.json
# live run against a running ArduPilot SITL
uv run bvlos-sim sitl SCENARIO --live --host 127.0.0.1 --port 5770 \
--artifact-dir artifacts/ --output evidence.json
# compare a completed bundle against its embedded expectations
uv run bvlos-sim compare evidence.json --output comparison.json
verify¶
Re-verify the chain of custody of a sitl-evidence.v1 bundle: recompute the
SHA-256 of every referenced artifact file (relative paths resolve against the
bundle's directory) and compare against the recorded checksums.
Usage: bvlos-sim verify EVIDENCE.json
One line per artifact — OK, MISMATCH, MISSING, or SKIPPED (no
recorded checksum) — then a final verdict. Exit 0 when everything matches,
10 on any mismatch or missing artifact, 11 for an unreadable or invalid
bundle.
Provenance and output safety¶
Three flags exist for evidence hygiene, all default-off so outputs stay byte-identical unless you opt in:
--operator-id TEXT(estimate,scenario,sitl) — records the operator identity in the result's free-form metadata map.--generated-at ISO8601|now(same commands) — records a generation timestamp;nowresolves to the current UTC time.--no-clobber(every command with--output) — refuse to overwrite an existing output file (exit11) instead of replacing it, so a re-run cannot silently destroy prior evidence.
schema-versions¶
Print every supported input and output contract version plus the tool version
as canonical JSON, without loading any file. Alias: contracts. Use it to pin
compatibility from a backend instead of parsing versions off an envelope.
tool_version — here and in every envelope's provenance — is this checkout's
pyproject.toml version when running from source, and the installed
distribution metadata otherwise. A vendored deployment (pip install
--target, a Lambda layer, a PEX) reports the vendored package's own version,
never the host application's.
bump¶
Developer-only release tool (absent from published wheels): bumps the version
in pyproject.toml and rolls CHANGELOG.md. --dry-run previews, --check
fails CI when the version is behind the latest v* tag. It never tags,
pushes, or publishes.
Map exports¶
estimate --format geojson|kml (and the same on scenario) emit the computed
route as map layers; batch --format geojson|kml with --output-dir writes
one file per run.
GeoJSON layers (RFC 7946, coordinates [lon, lat, altitude_m]):
route— one LineString per leg withleg_id,action,energy_margin_pct, and RTH reserve margin/color when available.landing_zones— one Point per zone withzone_idandreachable.geofences— one Polygon per zone withkindandconflict.obstacles— configured obstacle geometries withheight_mandconflict.
Color thresholds (energy and RTH margin as percent of battery capacity):
green above 30, amber 10–30, red below 10. KML uses the same thresholds
and opens directly in Google Earth and QGroundControl; GeoJSON opens in QGIS
and QGroundControl.
Warnings¶
Warnings mark conditions that do not make the mission infeasible but block an
operational GO (they appear in failed_checks as warnings). The full JSON
envelope carries each warning's code, message, and location; the checklist
lists the codes on its Warnings row.
Most can be accepted per mission via constraints.accepted_warning_codes (see
Missions and vehicles): acknowledged codes stay in
every artifact but stop blocking GO; any unlisted warning still blocks.
Nine cannot. MAX_WIND_EXCEEDED and RESERVE_BELOW_FAILSAFE_ABORT_THRESHOLD
report that a limit the vehicle profile itself declares was exceeded, and
VEHICLE_WIND_ENVELOPE_UNDECLARED reports that the wind limit is absent, so
the same envelope cannot be checked at all;
GROUND_RISK_FOOTPRINT_INVALID reports that evidence the mission itself
declares fails its own invariants. GEOFENCE_ZERO_ZONES,
GEOFENCE_COVERAGE_MISSING, OBSTACLE_ZERO_FEATURES and
OBSTACLE_COVERAGE_MISSING report that a gated evidence category was
configured but consulted nothing, and OBSTACLE_KEEP_OUT_NOT_CONFIGURED says
the same a third way — every loaded obstacle has zero width and no clearance is
configured, so only an exact overflight could register — an empty collection, or one whose features
all sit outside the route relevance envelope, leaves the
clearance check indistinguishable from one that was never supplied an asset.
Accepting any of the eight would let the file that supplies the evidence waive
the check on it. Listing any of them is a schema error rather than a waiver,
--engineering-only does not forgive them either, and they are marked
non-waivable below.
| Code | Meaning |
|---|---|
MAX_WIND_EXCEEDED |
Non-waivable. Leg wind exceeds vehicle.performance.max_wind_mps (not enforced as a hard limit). |
VEHICLE_WIND_ENVELOPE_UNDECLARED |
Non-waivable. Wind was evaluated but vehicle.performance.max_wind_mps is absent, so the aircraft's wind envelope is unverifiable rather than satisfied. |
RESERVE_BELOW_FAILSAFE_ABORT_THRESHOLD |
Non-waivable. Predicted landing reserve is below the autopilot abort threshold. |
GROUND_RISK_FOOTPRINT_INVALID |
Non-waivable. The mission's declared sora.ground_risk_footprint is not usable as ground-risk evidence. |
RESERVE_BELOW_FAILSAFE_WARN_THRESHOLD |
Predicted landing reserve is below the low-battery warning threshold. |
GEOFENCE_EVALUATED_2D_ONLY |
At least one zone declares neither floor_m nor ceiling_m, so it is evaluated as an unbounded 2D column. Zones declaring either bound are altitude-constrained and do not raise this. |
GEOFENCE_ZERO_ZONES |
Non-waivable. A geofence file is configured but contains zero zones — the clearance check evaluated no airspace. |
GEOFENCE_COVERAGE_MISSING |
Non-waivable. Zones were loaded, but none lies inside the route relevance envelope — the clearance check evaluated no airspace the mission can reach. |
OBSTACLE_ZERO_FEATURES |
Non-waivable. No obstacle was consulted: either the configured file contains zero obstacles, or min_obstacle_clearance_m is set with no assets.obstacles_file at all. |
OBSTACLE_COVERAGE_MISSING |
Non-waivable. Obstacles were loaded, but none lies inside the route relevance envelope — the clearance check evaluated no vertical structure the mission can reach. |
OBSTACLE_KEEP_OUT_NOT_CONFIGURED |
Non-waivable. Every obstacle has zero radius and uncertainty and min_obstacle_clearance_m is unset, so the keep-out volume has no width. |
DEPARTURE_TIME_MISSING |
A geofence has a time window but the mission has no departure_time; the zone is treated as always active. |
DIVERT_ENERGY_TAS_ONLY |
A scenario divert estimate used TAS without wind correction. |
POPULATION_DENSITY_DIMENSION_MISSING |
Population grid present but the vehicle omits characteristic_dimension_m; iGRC cannot be computed. |
GUST_DATA_UNAVAILABLE |
max_gust_mps is set but no provider supplies gust data. |
ENERGY_MODEL_UNCALIBRATED |
Energy coefficients have no matching runtime refit proof. This includes an absent/placeholder_values status and a serialized log_calibrated + energy_calibration claim loaded without the source traces. Supply a fitted profile with --calibration and every source trace with repeatable --calibration-traces. Declaring manufacturer_derived instead raises the separate waivable ENERGY_MODEL_CALIBRATION_SELF_DECLARED. |
HOVER_POWER_USED_AS_DESCENT_POWER |
A hover-capable vehicle omits vehicle.energy.descent_power_w, so powered descent is billed at the greater of hover_power_w and cruise_power_w. The substitution is conservative — a VTOL descends on its lift rotors, not on a wing, and the greater of the two is taken because a bucket-shaped power curve lets a cruise value calibrated near Vne exceed hover — but the figure is unmeasured. Declare descent_power_w. |
ENERGY_MODEL_PARTIALLY_CALIBRATED |
The calibration profile was verified against its source traces, but it does not fit an energy coefficient this mission bills energy against — the message names which. Those phases are still costed at whatever the vehicle file declares. Fit a profile from a trace covering the missing phase, or accept the code to record that judgement. |
ENERGY_MODEL_CALIBRATION_SELF_DECLARED |
vehicle.calibration_status is manufacturer_derived: an operator attestation that the power coefficients come from published data, which nothing in the run checks against observed flight. Accept the code to put the attestation on record, or fit a profile with calibrate, pass it with --calibration, and supply every source trace with --calibration-traces. |
ENERGY_REFERENCE_CONDITIONS_MISSING |
The vehicle declares operating_mass_kg without reference_mass_kg (or a reference density), so mass/density scaling is inert. |
ROUTE_ACTIONS_AFTER_RTL |
Route items after an RTL are estimated but unreachable. |
LOITER_RADIUS_IGNORED |
loiter_radius_m is ignored; loiter is modeled as station-keep. |
LOITER_ASSUMED_ZERO_GROUND_DISTANCE |
Loiter dwell is modeled with zero ground-path distance. |
LOW_GROUNDSPEED_MARGIN |
Groundspeed within 10% of min_groundspeed_mps. |
HIGH_CRAB_MARGIN |
Crab angle within 10% of max_crab_angle_deg. |
HOVER_SPEED_USED_AS_STATION_KEEP_AUTHORITY |
max_station_keep_wind_mps unset; hover_speed_mps used as fallback. |
Evidence relevance¶
Geofence and obstacle evidence is tested for relevance, not only for
presence. The estimator builds a route relevance envelope: a circle centred
on the route, with a radius covering the route itself plus a fixed 100 km
locality buffer. For obstacles the envelope is widened to at least the declared
keep-out width (min_obstacle_clearance_m plus the obstacle's own radius_m
and uncertainty_m) so nothing that can conflict is ever outside it.
The buffer is deliberately a constant rather than anything derived from the
vehicle. It used to be battery_capacity_wh / cruise_power_w hours at
cruise_speed_mps — the aircraft's outer reach — which meant the operator
whose evidence was being audited also chose the threshold it was judged by, and
it failed both ways: declaring a 3 W cruise power stretched the envelope to
19 440 km and let assets on another continent certify the route, while a small
pack shrank it to 5.76 km and rejected a real mast 6.45 km away.
If no loaded zone or obstacle falls inside the envelope, the file is describing
somewhere else, and GEOFENCE_COVERAGE_MISSING / OBSTACLE_COVERAGE_MISSING
fire. Both are non-waivable, so the repair is to re-fetch for the area actually
being flown. One relevant feature is enough — a national fetch that happens to
include the route is real evidence. The envelope answers "is this evidence
about this mission at all", not "does this file cover the whole route". This
mirrors TERRAIN_COVERAGE_MISSING and POPULATION_COVERAGE_MISSING, which
likewise compare route geometry against the data's own extent.
Failure codes¶
A failure stops the estimate and lands in the envelope's failure.code. The
accompanying failure.kind selects the exit code — infeasible → 10,
invalid_input → 11, unsupported → 12 — and a few codes carry different
kinds depending on where they are raised, so read the kind from the envelope
rather than assuming it from the code. Unlike a warning, no mission file can
accept a failure.
| Code | Meaning |
|---|---|
UNSUPPORTED_ALTITUDE_REFERENCE_TERRAIN |
altitude_reference: terrain was used with no terrain provider configured. |
TERRAIN_COVERAGE_MISSING |
The terrain grid has no coverage at a sampled route position, a landing-zone surface, or is absent while min_terrain_clearance_m is set. |
POPULATION_COVERAGE_MISSING |
The population grid does not cover the assessed route footprint. |
WIND_COVERAGE_MISSING |
A wind query fell outside the grid's own time_s, lat or lon extent, or above the top of its altitude_m axis. Queries below the lowest altitude band still clamp, because a forecast's lowest band sits above ground level. The context names the axis, the queried value, and the grid's bounds. |
SORA_INPUT_UNSUPPORTED |
The SORA assessment needs an input the mission or vehicle does not supply in a supported form (unsupported version, missing max_speed_mps, invalid assessment buffer). |
UNSUPPORTED_LOITER_FOR_VEHICLE_CLASS |
Station-keep loiter was requested for a vehicle without hover capability. |
MISSING_REQUIRED_SPEED_PROFILE |
No speed source resolves for a leg: no TAS for transit, RTH or divert, no station-keep authority, or no turn_radius_m for fixed-wing loiter. |
INVALID_SPEED_PROFILE |
A resolved speed or angle limit is out of range — non-positive TAS, non-positive min_groundspeed_mps, max_crab_angle_deg outside 0–90. |
INVALID_ENERGY_MODEL |
A vehicle.energy power value is not greater than zero. |
INVALID_ENERGY_POLICY |
A reserve percentage is outside 0–100. |
INVALID_MISSION_PROFILE |
The route cannot be expanded: a missing altitude_m, half-specified lat/lon, loiter_time without loiter_time_s, a capability the vehicle lacks, or a vehicle_profile that does not match the vehicle. |
WIND_TRIANGLE_NO_SOLUTION |
The wind triangle has no solution: wind exceeds the achievable airspeed. |
CRAB_ANGLE_LIMIT_EXCEEDED |
Required crab angle exceeds max_crab_angle_deg. |
GROUNDSPEED_NON_POSITIVE |
The solved groundspeed is zero or negative. |
GROUNDSPEED_BELOW_MIN |
The solved groundspeed is below min_groundspeed_mps. |
STATION_KEEP_INFEASIBLE_WIND |
Loiter dwell wind exceeds the station-keep authority. |
INSUFFICIENT_ENERGY |
The route needs more energy than the pack delivers. |
RESERVE_BELOW_THRESHOLD |
Reserve at landing is below the required threshold. |
RTH_RESERVE_BELOW_THRESHOLD |
Return-to-home reserve drops below the threshold at some point on the route. |
MISSING_ENERGY_MODEL |
A required vehicle.energy field is absent, or an energy estimate a later check needs was never produced. |
UNSUPPORTED_PHASE_ENERGY_MODEL |
A leg phase has no deterministic energy model. |
RESOURCE_FEASIBILITY_FAILED |
No configured resource system can support the mission. |
LINK_FEASIBILITY_FAILED |
No configured link system covers the route. |
ROUTE_ENTERS_FORBIDDEN_ZONE |
The flown path enters a forbidden geofence zone while it is active. |
ROUTE_EXITS_REQUIRED_ZONE |
The flown path leaves a required geofence zone. |
ASSET_LOAD_ERROR |
A referenced asset file could not be read or parsed. |
INVALID_GEOMETRY |
A loaded geometry is invalid, or a path could not be materialized — an unsampleable route, a turn radius with no connected tangent arc, or a timing loop that did not converge. |
UNSUPPORTED_GEOMETRY_TYPE |
A geofence or obstacle geometry type is not supported. |
NO_REACHABLE_LANDING_ZONE |
No landing zone is reachable from an evaluated route state. |
LANDING_ZONE_REACHABLE_BUT_BELOW_RESERVE |
The nearest landing zone is reachable only by spending the landing reserve. |
UNSUPPORTED_LANDING_ZONE_GEOMETRY |
A landing-zone geometry type is not supported. |
ALL_LANDING_ZONES_UNAVAILABLE |
Every landing zone is marked unavailable at an evaluated route state. |
UNKNOWN_LANDING_ZONE_REFERENCE |
A scenario's landing_zone_unavailable names a zone id the mission does not configure. |
WIND_LIMIT_EXCEEDED |
Sustained wind exceeds constraints.max_wind_mps. |
GUST_LIMIT_EXCEEDED |
Gust speed exceeds constraints.max_gust_mps. |
CROSSWIND_LIMIT_EXCEEDED |
Crosswind exceeds constraints.max_crosswind_mps. |
WEATHER_DATA_UNAVAILABLE |
A configured weather minimum needs an observation the active provider does not supply. |
OBSTACLE_CLEARANCE_VIOLATED |
A route segment violates the obstacle keep-out volume. |
TERRAIN_CLEARANCE_VIOLATED |
A route segment violates min_terrain_clearance_m. |
Python API¶
The stable Python surface is bvlos_sim.estimator:
from bvlos_sim.estimator import (
EstimationOptions, FidelityMode, LayeredWindProvider, WindLayer,
estimate_mission_distance_time, try_estimate_mission_distance_time,
run_scenario, run_monte_carlo,
)
result = estimate_mission_distance_time(
mission, vehicle,
wind_provider=LayeredWindProvider([
WindLayer(altitude_m=0.0, wind_east_mps=2.0, wind_north_mps=0.0),
WindLayer(altitude_m=500.0, wind_east_mps=6.0, wind_north_mps=-1.0),
]),
options=EstimationOptions(fidelity=FidelityMode.V2, max_segment_length_m=500.0),
)
Symbols exported from bvlos_sim.estimator.__all__ are the supported surface; internal
module layout is not a contract.