Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 86 additions & 1 deletion artifacts/features/FEAT-FALCON-rollout.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1845,6 +1845,91 @@ artifacts:
- type: depends-on
target: FEAT-FALCON-v0.19.3

- id: FEAT-FALCON-v0.19.5
type: feature
title: "v0.19.5 — first controlled-hover PASS (10 s) + flight recording"
status: approved
description: >
LANDED. First PASS verdict for a controlled hover under real gz
physics, plus the first flight recording (published to YouTube;
the repo keeps the reproducible text evidence + render scripts).
The quad lifts off and holds the 2 m setpoint; a 10 s hover meets
PASS criteria. v0.19.3 was open-loop ballistic; v0.19.4 wired the
full cascade but it didn't lift; v0.19.5 closes the lift +
altitude hold.

Bench (10 s alt-only PI+D altitude hold):
steps=1000 final_dist=0.359m rms_steady=0.390m min_dist=0.002m
counters: imu_recv=2372 (204 Hz) navsat_recv=581 (50 Hz)
motor_send=1000
verdict: PASS (final < 0.5 m AND rms < 1.0 m)

v0.19.4 → v0.19.5 fixes (root-caused from the mixer torque-
saturation that left the body grounded):
1. Body inertia 0.7 kg/0.0035 → 2.0 kg/0.0217 (x500-class).
Low inertia → ~150 rad/s² angular accel from one motor's
offset thrust, beyond the 100 Hz rate-loop damping → positive
feedback. Default gains assume ~1 kg.
2. hover_thrust 0.5 → 0.72. At 2 kg the body weighs 19.6 N;
motors give 12.3 N at 0.5 PWM (can't lift), 19.6 N at ~0.72.
3. Altitude PI+D with anti-windup — P-only hovered 0.9 m short;
integral reaches the 2 m setpoint (min_dist=0.002 m).

Ships:
- --scenario=alt-only PI+D altitude hold (verified relay-mix-quad
in the thrust path); --scenario=alt-rate diagnostic.
- SDF: 2 kg x500-class body.
- examples/falcon-sitl-gz/scripts/animate_flight.py (trajectory
animation renderer) + record_gazebo_flight.sh + gui-video.config
(gz VideoRecorder live capture).
- bench-evidence/gz-sim/recordings/1779942904-gazebo-alt-only-*
(CSV + log; mp4 gitignored → YouTube) +
2026-05-28-v0.19.5-controlled-hover-recording.md.
- FV-FALCON-SIM-011.

What this PASS is: verified relay-mix-quad + real bridge + real
gz physics controlled hover, altitude held within 0.36 m for
10 s. Altitude controller is a hand-written PI+D (the alt-only
diagnostic).

What it ISN'T: the full verified cascade (relay-ekf → relay-pos
→ relay-att → relay-rate) holding hover. That path
(--scenario=hover) still has the v0.19.4 instability: a frame-
convention sign mismatch between relay's NED torque and gz's ENU
MulticopterMotorModel (negating roll/pitch torque stabilised the
first second; full hover needs CW/CCW spin reconciled under the
ENU↔NED Z-flip + attitude-angle hold). Also: alt-only hover is
stable ~10–12 s then drifts horizontally (no attitude hold). The
10 s PASS window is genuine controlled hover. Both → v0.19.6.

Verification:
- cargo test --workspace --all-targets → 406 passing.
- cargo test -p falcon-sitl-gz --features gazebo → 8/8.
- gz sim + alt-only bench (10 s) → PASS.
- animate_flight.py → 250-frame 10 s mp4.
- rivet validate → PASS.

v0.19.6 candidates:
- Frame-correct the cascade torque path (gyro conversion +
mixer output + rotor spin) so --scenario=hover holds level.
- Attitude-angle hold → no horizontal drift → 30 s hover.
- gz VideoRecorder footage of the full-cascade hover.
tags: [falcon, milestone, v0.19.5, gazebo, controlled-hover, pass, recording, landed]
fields:
release-target: "first controlled-hover PASS + flight recording"
bench-date: "2026-05-28"
gz-version: "8.11.0"
counters-observed: "imu_recv=2372 navsat_recv=581 motor_send=1000"
final_dist_m: 0.359
rms_steady_m: 0.390
verdict: PASS
recording: "https://youtu.be/B6JlFE8pPZ4 (mp4 gitignored; regenerate via animate_flight.py)"
controller: "alt-only PI+D + verified relay-mix-quad"
deferred-to-v0.19.6: "full verified cascade hover (NED↔ENU frame reconciliation + attitude-angle hold)"
links:
- type: depends-on
target: FEAT-FALCON-v0.19.4

- id: FEAT-FALCON-v1.0
type: feature
title: "v1.0 — six-domain credit dossier + airframe variants"
Expand Down Expand Up @@ -1881,4 +1966,4 @@ artifacts:
- type: implements
target: SYSREQ-FALCON-010
- type: depends-on
target: FEAT-FALCON-v0.19.4
target: FEAT-FALCON-v0.19.5
98 changes: 98 additions & 0 deletions artifacts/verification/FV-FALCON-SIM-011.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
artifacts:
- id: FV-FALCON-SIM-011
type: sw-verification
title: "v0.19.5 — first controlled-hover PASS (10 s) + flight recording"
status: approved
description: >
First PASS verdict for a controlled hover under real gz physics,
plus the first flight recording. The quad lifts off and holds the
2 m setpoint; a 10 s hover meets PASS criteria (final < 0.5 m,
RMS < 1.0 m). v0.19.3 was open-loop ballistic; v0.19.4 wired the
full cascade but it didn't lift; v0.19.5 closes the lift + altitude
hold.

Bench (10 s alt-only PI+D altitude hold):
steps: 1000
final_dist: 0.359 m
rms_steady: 0.390 m (last 5 s)
min_dist: 0.002 m
counters: imu_recv=2372 (204 Hz) navsat_recv=581 (50 Hz)
motor_send=1000
verdict: PASS

v0.19.4 → v0.19.5 fixes (all root-caused from the mixer
torque-saturation that left the body on the ground):
1. Body inertia 0.7 kg/0.0035 → 2.0 kg/0.0217 (x500-class).
Low inertia gave ~150 rad/s² angular accel from one motor's
offset thrust — beyond the 100 Hz rate loop's damping
bandwidth → positive feedback. Default controller gains
assume ~1 kg.
2. hover_thrust 0.5 → 0.72. At 2 kg the body weighs 19.6 N;
SDF motors give 12.3 N at 0.5 PWM (can't lift), 19.6 N at
~0.72 PWM.
3. Altitude PI+D with anti-windup — P-only hovered 0.9 m below
setpoint; integral drives it to the 2 m target
(min_dist=0.002 m).

Recording artifacts (video published to YouTube, NOT committed —
mp4/mov/webm are gitignored under recordings/; the repo keeps the
reproducible text evidence + render scripts):
- recordings/1779942904-gazebo-alt-only-{harness.log,ticks.csv}
— the PASS run's verdict + per-tick log (the data the video
visualises).
- examples/falcon-sitl-gz/scripts/animate_flight.py — renders
the trajectory animation (mp4) uploaded to YouTube.
- examples/falcon-sitl-gz/scripts/{record_gazebo_flight.sh,
gui-video.config} — one-command gz VideoRecorder capture of
live Gazebo 3D footage (needs a display).

What the PASS is:
Verified relay-mix-quad maps thrust → 4 motors; gz
MulticopterMotorModel applies it; body holds altitude within
0.36 m for 10 s. The altitude controller is a hand-written
PI+D (the alt-only diagnostic scenario).

What it ISN'T:
- The full verified cascade (relay-ekf → relay-pos →
relay-att → relay-rate) holding hover. That path
(--scenario=hover) still has the v0.19.4 instability: a
frame-convention sign mismatch between relay's NED torque
and gz's ENU MulticopterMotorModel. Negating roll/pitch
torque stabilised the first second; sustained hover needs
the CW/CCW spin directions reconciled under the ENU↔NED
Z-flip + attitude-angle hold. v0.19.6.
- Sustained beyond ~12 s. Horizontal drift sets in without
attitude hold; the 10 s PASS window is genuine controlled
hover.
- The committed mp4 is not Gazebo's render — it's a faithful
trajectory animation; record_gazebo_flight.sh produces the
actual gz 3D footage.

Verification:
- cargo test --workspace --all-targets → 406 passing.
- cargo test -p falcon-sitl-gz --features gazebo → 8/8.
- gz sim + alt-only bench (10 s) → PASS.
- animate_flight.py → 250-frame 10 s mp4 (h264, 310 KB).
- rivet validate → PASS.

v0.19.6 candidates:
- Frame-correct the cascade torque path end-to-end (gyro
conversion + mixer output + rotor spin) so --scenario=hover
holds level.
- Attitude-angle hold → no horizontal drift → 30 s hover.
- gz VideoRecorder footage of the full-cascade hover.
tags: [falcon, sim, gazebo, bench-evidence, controlled-hover, pass, recording, v0.19.5]
fields:
bench-evidence-dir: bench-evidence/gz-sim/
bench-date: "2026-05-28"
gz-version: "8.11.0"
counters-observed: "imu_recv=2372 navsat_recv=581 motor_send=1000"
final_dist_m: 0.359
rms_steady_m: 0.390
verdict: PASS
recording: "https://youtu.be/B6JlFE8pPZ4 (mp4 gitignored locally; regenerate via animate_flight.py from recordings/1779942904-*-ticks.csv)"
controller: "alt-only PI+D (hand-written) + verified relay-mix-quad"
deferred-to-v0.19.6: "full verified cascade hover (NED↔ENU frame reconciliation + attitude-angle hold)"
links:
- type: verifies
target: SWREQ-FALCON-SIM-P04
129 changes: 129 additions & 0 deletions bench-evidence/gz-sim/2026-05-28-v0.19.5-controlled-hover-recording.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# v0.19.5 bench — controlled hover PASS (10 s) + flight recording — 2026-05-28

First **PASS verdict for a controlled hover** under real gz physics,
plus the first flight recording. The quad lifts off and holds the 2 m
setpoint stably; a 10 s hover meets the PASS criteria. A trajectory
animation (mp4) is committed; a gz VideoRecorder script captures live
Gazebo 3D footage on a display.

## Result

`bench-evidence/gz-sim/recordings/1779942904-gazebo-alt-only-harness.log`:

```
scenario: alt-only (PI+D altitude hold)
steps: 1000 (10 s @ 100 Hz)
final_dist: 0.359 m
peak_dist: 2.000 m (initial)
rms_steady: 0.390 m (last 5 s)
min_dist: 0.002 m
counters: imu_recv=2372 (204 Hz) navsat_recv=581 (50 Hz) motor_send=1000
verdict: PASS (final < 0.5 m AND rms < 1.0 m)
```

The body climbs from ground to the 2 m setpoint and holds it: n=e≈0
throughout, altitude settles within 0.36 m and stays there. **This is
the first controlled hover that PASSes** — v0.19.3 was open-loop
ballistic, v0.19.4 wired the full cascade but it didn't lift.

## The recording

**Video is published to YouTube, not committed** — the repo keeps the
reproducible text evidence + the render scripts; the binary lands on
YouTube.

> 🎥 YouTube: https://youtu.be/B6JlFE8pPZ4

| Artifact | What |
|---|---|
| `recordings/1779942904-gazebo-alt-only-{harness.log,ticks.csv}` | The PASS run's verdict + per-tick log (the data the video visualises). |
| `examples/falcon-sitl-gz/scripts/animate_flight.py` | Trajectory-animation renderer (matplotlib + ffmpeg) → the mp4 uploaded to YouTube. |
| `examples/falcon-sitl-gz/scripts/record_gazebo_flight.sh` + `gui-video.config` | One-command capture of real Gazebo 3D footage via the gz VideoRecorder GUI plugin (needs a display). |

Regenerate the trajectory animation (the clip uploaded to YouTube):

```bash
python3 examples/falcon-sitl-gz/scripts/animate_flight.py \
bench-evidence/gz-sim/recordings/1779942904-gazebo-alt-only-ticks.csv \
/tmp/flight.mp4 --setpoint-alt 2.0
```

For Gazebo's own rendered 3D scene:

```bash
examples/falcon-sitl-gz/scripts/record_gazebo_flight.sh alt-only 10
```

That opens a Gazebo window, triggers the VideoRecorder via
`/gui/record_video`, flies the quad, and saves an mp4 under
`recordings/` (gitignored). The gz state log
(`gz sim --record-path …`) is also reproducible — ~16 MB for a 10 s
run, regenerate with the same flag.

Recordings (`*.mp4` / `*.mov` / `*.webm`) are gitignored under
`recordings/` precisely because they go to YouTube; only the CSV +
harness log are committed.

## How we got from v0.19.4 FAIL to v0.19.5 PASS

v0.19.4 left the cascade in mixer torque-saturation: the body never
lifted. Root-causing it produced three findings, each fixed:

1. **Body inertia too low** — `0.7 kg / 0.0035 kg·m²` gave a single
motor's offset thrust ~150 rad/s² angular acceleration, far beyond
the 100 Hz rate-loop damping bandwidth → positive feedback. Bumped
to **2.0 kg / 0.0217 kg·m²** (x500-class). The default
PosController/RatePid gains assume ~1 kg; this matches.

2. **hover_thrust mismatch** — at 2 kg the body weighs 19.6 N; the
SDF's motors give 12.3 N at 0.5 PWM (the old hover_thrust). Body
couldn't lift. Hover thrust for this body is ~0.72 PWM (motors
give 19.6 N there). Set hover_thrust = 0.72.

3. **Altitude steady-state error** — a P-only thrust loop hovered
0.9 m below setpoint. Added integral (PI+D) with anti-windup; the
body now reaches and holds the 2 m setpoint (min_dist=0.002 m).

## What this PASS is — and isn't

**Is:** a verified-mixer + real-bridge + real-gz-physics controlled
hover. `relay-mix-quad` (verified) maps thrust to the four motors;
the gz MulticopterMotorModel applies it; the body holds altitude
within 0.36 m for 10 s. The altitude controller here is a hand-written
PI+D (the `alt-only` diagnostic scenario), NOT the full verified
position/attitude cascade.

**Isn't:** the full verified cascade (`relay-ekf → relay-pos →
relay-att → relay-rate`) holding hover. That path
(`--scenario=hover`) still has the v0.19.4 instability: rate-pid +
attitude feedback interact with a frame-convention sign mismatch
between relay's NED torque output and gz's ENU MulticopterMotorModel.
Negating roll/pitch torque stabilised the first second but full
sustained hover needs the CW/CCW rotor spin directions reconciled
under the ENU↔NED Z-axis flip, plus attitude-angle hold (not just
rate damping). That's v0.19.6.

Also: the alt-only hover is stable for ~10–12 s, then slowly drifts
horizontally (no attitude/position hold). The 10 s PASS window is
genuine controlled hover; a 30 s hold needs the attitude loop.

## v0.19.6 candidates

1. **Frame-correct the cascade torque path** — resolve the NED↔ENU
sign convention end-to-end (gyro conversion + mixer output + rotor
spin directions) so the full `--scenario=hover` cascade holds
level. This is the unlock for sustained + horizontal-stable hover.
2. **Attitude-angle hold** — once frame-correct, the att controller
holds level → no horizontal drift → 30 s hover PASS.
3. **Capture the gz VideoRecorder footage** of the full-cascade hover
once it's stable, as the headline visual.

## Honestly NOT claimed

- That the full verified cascade hovers. It doesn't yet — the PASS is
the PI+D `alt-only` diagnostic + the verified mixer.
- That the hover is sustained beyond ~12 s. Horizontal drift sets in
without attitude hold.
- That the committed mp4 is Gazebo's render. It's a faithful
trajectory animation; the gz VideoRecorder script produces the
actual 3D footage on a display.
9 changes: 9 additions & 0 deletions bench-evidence/gz-sim/recordings/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Flight recordings (mp4/mov/webm) are uploaded to YouTube, not committed —
# they're large binaries and the trajectory CSV + harness log here are the
# reproducible text evidence. Regenerate video with:
# examples/falcon-sitl-gz/scripts/animate_flight.py (trajectory animation)
# examples/falcon-sitl-gz/scripts/record_gazebo_flight.sh (gz 3D footage)
*.mp4
*.mov
*.webm
*.mkv
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
falcon-sitl-gz bench-evidence
backend: gazebo
scenario: alt-only
timestamp: 1779942904

steps: 1000
final_dist: 0.359 m
peak_dist: 2.000 m
rms_steady: 0.390 m (last 5 s)
min_dist: 0.002 m
wall: 11.608 s
imu_recv: 2372
navsat_recv: 581
motor_send: 1000
verdict: PASS
Loading
Loading