feat(falcon): v0.19.4 — closed-loop cascade wired into gz-sim; tuning gap surfaced#49
Conversation
… gap surfaced
The verified cascade (relay-ekf → relay-pos → relay-att → relay-rate
→ relay-mix-quad) is now wired into falcon-sitl-gz and runs end-to-end
against real gz Harmonic physics. First time the safety-critical
control surface is in the loop under real physics. Verdict is FAIL on
hover-stability — body stays on the ground in mixer torque-saturation
mode — but the architectural deliverable is the wiring + the
falsifiable failure mode that v0.19.5 picks up.
Bench (15-second hover, setpoint NED (0,0,-2)):
verdict: FAIL
final_dist=2.44m peak_dist=3.30m rms_steady=2.16m min_dist=1.75m
counters: imu_recv=3526 (203 Hz) navsat_recv=870 (50 Hz)
motor_send=1500 (100 Hz)
Cascade pattern (mirrors falcon-sitl-hover::run_mission):
physics.measure → EKF → POS → ATT → RATE → MIX → physics.step.
Ships:
- main.rs::run_closed_loop_hover — full cascade tick.
- --scenario=hover (default) → closed-loop;
--scenario=open-loop-climb → v0.19.3's constant 70 % PWM
(preserved as wire-level smoke test).
- SDF: NavSat 10 → 50 Hz (finite-diff velocity needs the rate);
body spawn 0.1 → 1.0 m.
- cargo test closed_loop_hover_compiles_and_ticks_on_mock —
panic + NaN-free contract on mock.
- bench-evidence/gz-sim/2026-05-27-v0.19.4-closed-loop-hover-
findings.md + 1779904889-gazebo-hover-{harness.log,ticks.csv}.
- FV-FALCON-SIM-010 + FEAT-FALCON-v0.19.4 rollout entry.
Falsifiable failure mode:
Body sits at d≈0 with motors in bang-bang [1.00, 0.00, 0.00, 0.00]
torque saturation. Pos commands tilt → Att commands rate → Rate
commands torque → QuadMixer's priority-preserving saturation
(PX4-style: yaw last, then thrust) starves thrust. Net: rotors spin
asymmetrically, zero net thrust → body never lifts.
This is a TUNING gap, not a wiring gap. falcon-sitl-hover (pure-Rust
plant) converges in 4 s; gz delta is physics + finite-diff velocity
+ controller-tick-rate mismatch.
v0.19.5 candidates (each separable):
- Re-tune PosController gains for 700 g falcon-quad (defaults
assume ~1 kg).
- Decimation pattern (RATE 1 kHz, ATT 250 Hz, POS 50 Hz) by
bumping harness tick rate.
- gz body-velocity topic OR Kalman-fuse IMU+NavSat instead of
finite-diff.
- Minimum-hover-thrust floor in QuadMixer (Verus contract update).
What v0.19.4 closes:
- v0.19.3's deferred 'scenario=hover swap to closed-loop'.
- 'Verified cascade is integrable into real-physics simulator' —
proven by 1500 ticks of stable execution.
Verification:
- cargo test --workspace --all-targets → 406 passing (was 405; +1
closed-loop smoke).
- cargo build --features gazebo → green.
- cargo run + gz sim → cascade runs 1500 ticks, no NaN, body
responds (min_dist=1.75 m), doesn't settle.
- rivet validate → PASS.
Honestly NOT claimed:
- That the cascade flies stably. Body is on the ground. 'Falcon
flies stably' is the v0.19.x finish line, not v0.19.4.
- That failure mode is Verus-grade diagnosed. Mixer-saturation
hypothesis is consistent with bang-bang motor pattern;
tick-by-tick mixer I/O trace is v0.19.5 work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Automated review for PR #49pulseengine/relay: Verdict: 💬 Comment Summary: The feature request for integrating the closed-loop cascade into gz-sim and addressing the tuning gap has been approved based on the provided details in the artifacts/features/FEAT-FALCON-rollout.yaml file. Findings: 0 mechanical (rivet) · 2 from local AI model. Findings (2):
Generated by a local AI model and post-validated against a strict JSON contract. Each finding includes the verbatim line being criticised — verify by reading the file at the cited location. Reviewed at |
|
running 16 tests test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 16 tests test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 16 tests test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.87s running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 5 tests test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s --- noise=0 (deterministic) --- running 55 tests test result: ok. 55 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 10 tests test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 1 test test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 16 filtered out; finished in 0.05s running 8 tests test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s falcon-sitl-gz: backend=mock scenario=hover duration=5s running 10 tests test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 10 tests test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 10 tests test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 17 tests test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s --- scenario: attitude --- running 55 tests test result: ok. 55 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 8 tests test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s running 9 tests test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s [falcon-hello-demo] building release binary... running 10 tests test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s falcon-hitl-rfspoof: backend=stub duration=5s
|
| count | |
|---|---|
| Passed | 30 |
| Failed | -5 |
| Skipped (bench-only — needs hardware / sim) | 7 |
| Skipped (no steps) | 5 |
Failed artifacts
Bench-only artifacts (not run by CI)
FV-FALCON-SIM-005— gz-transport NavSat + Home projection — position-dependent loops (v0.18.1)FV-FALCON-ARCH-001— spar AADL architectural model — falcon cascade (v0.13)FV-FALCON-COV-003— witness MC/DC on real Rust source — Geofence subject (v0.14.1)FV-FALCON-COV-001— witness MC/DC structural coverage — falcon pipeline wired (v0.13)FV-FALCON-ARCH-002— spar codegen --format wit recheck — works at v0.10.0 (v0.15.0)FV-FALCON-GEO-003— Geofence safety path — miri UB/overflow check (v0.12, AI substitute)FV-FALCON-SIM-001— PX4-SITL end-to-end loop — recipe + preset + smoke (v0.14.0)
Source of truth: artifacts/verification/FV-FALCON-*.yaml.
Summary
The verified cascade (`relay-ekf → relay-pos → relay-att → relay-rate → relay-mix-quad`) is now wired into `falcon-sitl-gz` and runs end-to-end against real gz Harmonic physics. First time the safety-critical control surface is in the loop under real physics.
Verdict is FAIL on hover-stability — body stays on the ground in mixer torque-saturation mode — but the architectural deliverable is the wiring + the falsifiable failure mode that v0.19.5 picks up.
Bench result (15-second hover, setpoint NED (0, 0, -2))
```
verdict: backend=gazebo scenario=hover steps=1500
final_dist=2.44m peak_dist=3.30m rms_steady=2.16m
min_dist=1.75m
wall=17.50s
counters: imu_recv=3526 navsat_recv=870 motor_send=1500
FAIL
```
What ships
Falsifiable failure mode
Body sits at d≈0 with motors in bang-bang `[1.00, 0.00, 0.00, 0.00]` torque saturation. PosController commands large attitude tilt → AttController large rate → RatePid large torque → `QuadMixer`'s priority-preserving saturation (PX4-style: yaw last, then thrust) starves thrust. Net: rotors spin asymmetrically, zero net thrust → body never lifts.
This is a tuning gap, not a wiring gap. `falcon-sitl-hover` (pure-Rust plant) converges in 4 s; gz delta is physics + finite-diff velocity + controller-tick-rate mismatch (100 Hz vs canonical 1 kHz / 250 Hz / 50 Hz).
v0.19.5 candidates
Verification
Honestly NOT claimed
What v0.19.4 closes
🤖 Generated with Claude Code