Skip to content

Getting started

By the end you'll have run a real preflight check on a bundled Alpine mission, read its go/no-go verdict, and produced a one-line summary you could wire into CI. Takes about five minutes; everything runs offline.

Before you begin

  • Python 3.12+
  • uv
git clone https://github.com/Monotox/bvlos-sim
cd bvlos-sim
uv sync
uv run bvlos-sim --help   # verify the CLI answers

1. Run the preflight checklist

The repository ships a pre-fetched mission over the Lucerne/Zug Alps — real SRTM terrain, a real Open-Meteo wind grid, real OpenStreetMap landing zones — so no network is needed:

uv run bvlos-sim estimate \
  examples/real_world/alpine_mission.yaml \
  examples/real_world/quadplane_v1.yaml \
  --format checklist

You should see every evaluated check pass, and still:

## Pre-Flight Checklist: alpine_demo_001

✓ Energy feasibility        PASS   reserve 591.06 Wh above threshold (816.06 Wh at landing, 225.00 Wh threshold)
◌ Geofence clearance        N/A    not evaluated
✓ Landing-zone coverage     PASS   reachable zone found at all 166 checked state(s)
◌ Resource availability     N/A    not evaluated
...
  Warnings                  2      ENERGY_MODEL_UNCALIBRATED, HOVER_POWER_USED_AS_DESCENT_POWER

Status: NO-GO
Blocked by: missing evidence (geofence, resource, link, obstacle, ground_risk); blocking warnings (ENERGY_MODEL_UNCALIBRATED, HOVER_POWER_USED_AS_DESCENT_POWER) — the checklist is fail-closed

The exit code is 10. This is deliberate: the checklist is fail-closed — evidence that was never evaluated (◌ N/A) can never contribute to a GO. The demo mission omits geofence, resource, link, obstacle, and ground-risk inputs, so it cannot pass an operational preflight, and the Blocked by: line names exactly what's missing.

Both warnings apply that same principle to the numbers themselves. ENERGY_MODEL_UNCALIBRATED says this vehicle profile ships placeholder power values, so every energy figure above is arithmetic on invented coefficients. HOVER_POWER_USED_AS_DESCENT_POWER says the profile declares no energy.descent_power_w, so the powered descent of this hover-capable aircraft is billed at hover_power_w — conservative, because a VTOL descends on its lift rotors, but unmeasured. Step 4 shows the profile that has been calibrated against a flight log.

2. Get the engineering verdict

When you only want the physics — is the route flyable with this battery in this wind — opt out of the operational gate:

uv run bvlos-sim estimate \
  examples/real_world/alpine_mission.yaml \
  examples/real_world/quadplane_v1.yaml \
  --format summary --engineering-only
FEASIBLE   reserve 262.7 %   flight 7m 58s   warnings 2

Exit code 0. reserve 262.7 % means the predicted energy at landing is 262.7% above the reserve threshold (25% of battery for this mission) — it is a margin over the reserve, not battery state of charge.

The flag matters for the first word as well as the exit code. Drop --engineering-only and the same run prints INFEASIBLE and exits 10, with the identical reserve 262.7 %: the summary's status token follows the readiness gate, and this mission is missing evidence. INFEASIBLE there is a statement about the preflight, not about the physics — which is why the engineering verdict needs its own flag.

3. See a failure

The same route with a battery that cannot make it:

uv run bvlos-sim estimate \
  examples/real_world/alpine_infeasible.yaml \
  examples/real_world/quadplane_small_battery.yaml \
  --format summary
INFEASIBLE   reserve −95.0 %   flight 7m 58s   RTH infeasible   warnings 2   [RESERVE_BELOW_THRESHOLD]

Exit code 10 again — but the bracketed failure code is what tells you this one is physical. Step 2's INFEASIBLE carried no code because nothing was computationally wrong with it; here the reserve genuinely lands 95.0 % under its threshold, and --engineering-only prints the same line and the same exit code. size-battery tells you the smallest battery that fixes it — but it needs two fields quadplane_small_battery.yaml does not carry, so use the sibling profile that does:

uv run bvlos-sim size-battery \
  examples/real_world/alpine_infeasible.yaml \
  examples/real_world/quadplane_small_battery_sizing.yaml
## Battery Sizing: alpine_infeasible_001

Mission energy required:   86.8 Wh
Reserve threshold (25 %):  38.5 Wh (of battery capacity)

Minimum feasible capacity: 153.9 Wh
Maximum feasible capacity: 900.0 Wh

Search resolution: 1.0 Wh
...
Recommendation: target 169.3 Wh (10 % margin); do not exceed the verified 900.0 Wh upper bound.

Status: SIZED

The elided lines are the 10/20/30 % margin table; --margin N narrows it to one. Exit code 0SIZED is a successful answer, not a verdict on the mission.

The sizing profile is identical to quadplane_small_battery.yaml plus energy.battery_specific_energy_wh_per_kg and energy.battery_excluded_operating_mass_kg. The command needs both to feed a candidate pack's mass back into the power model; without them it exits 11 with Battery sizing requires capacity-mass feedback inputs. See Missions and vehicles.

4. See a GO

Everything so far was blocked. This is what clearing the gate looks like:

uv run bvlos-sim estimate \
  examples/missions/pipeline_demo_001_go.yaml \
  examples/vehicles/quadplane_v1_complete.yaml \
  --calibration examples/calibration/quadplane_v1_calibration.json \
  --calibration-traces examples/flight_logs/pipeline_demo_001_trace.json \
  --format checklist
## Pre-Flight Checklist: pipeline_demo_001_go

✓ Energy feasibility        PASS   reserve 650.08 Wh above threshold (875.08 Wh at landing, 225.00 Wh threshold)
✓ Geofence clearance        PASS   0 conflicts across 1 zone(s)
✓ Landing-zone coverage     PASS   reachable zone found at all 14 checked state(s)
✓ Resource availability     PASS   system 'fiber-power-primary' sufficient
✓ Link availability         PASS   link 'mesh-primary' available
✓ Obstacle clearance        PASS   0 violations across 3 leg(s) and 1 obstacle(s)
✓ Weather limits            PASS   worst wind 2.72 m/s at leg 1 (wp1)
✓ RTH feasibility           PASS   selected external resource covers RTH peak power
  Ground risk class         INFO   mission iGRC 3
  Departure time            INFO   2026-07-21T12:00:00Z
  Warnings                  1      ENERGY_MODEL_PARTIALLY_CALIBRATED
  Acknowledged warnings     1      ENERGY_MODEL_PARTIALLY_CALIBRATED

Status: GO

Exit code 0. Three things earn it, and dropping any one of them puts the verdict back to NO-GO:

  • Every evidence category is supplied. Open examples/missions/pipeline_demo_001_go.yaml and you'll find geofence, landing-zone, obstacle, terrain, population, and wind assets, plus link_systems, airspace, and sora blocks. The vehicle supplies resource_systems and characteristic_dimension_m.
  • The coefficients are reproducibly calibrated. --calibration-traces supplies the exact normalized trace document; the command recomputes its dataset digest, reruns the deterministic fitter, and applies the profile only when every fitted parameter matches. That runtime proof clears ENERGY_MODEL_UNCALIBRATED. Only fitted energy coefficients clear it — cruise_power_w, hover_power_w, climb_power_w, descent_power_w, taken from observed battery_current_a × battery_voltage_v. This artifact fits two of the four (climb_power_w and cruise_power_w; the demo flight never hovers or descends with battery telemetry, and the other two are declined in the artifact's notes). cruise_power_w is the minimum required energy fit; a profile that fits cruise speed and climb rate alone leaves the warning in place, because the warning is about the power numbers.
  • No warning is waived. The mission has no accepted_warning_codes at all. A GO that needs a waiver is worth less than one that doesn't.

Drop either calibration flag and run it again: same route, same assets, back to NO-GO (or invalid input when a profile is present without its traces).

5. Check inputs without running anything

uv run bvlos-sim estimate \
  examples/real_world/alpine_mission.yaml \
  examples/real_world/quadplane_v1.yaml \
  --validate-only
mission: alpine_mission.yaml: OK (note: no policy block declared)
vehicle: quadplane_v1.yaml: OK

--validate-only schema-checks the mission, vehicle, and every referenced asset file, then exits — 0 when everything loads, 11 with a pointed error when it doesn't. Point the mission at a terrain file that isn't there and you get terrain: assets/does_not_exist.yaml: FAILED (Unable to read terrain file.) and exit 11. Use it in CI before long runs, and while authoring your own files.

What you built

You ran the full preflight pipeline: a fail-closed operational checklist, an engineering feasibility verdict, an infeasible counter-example, a complete-evidence GO, and input validation — all deterministic, all reproducible.

Next: