Skip to content

feat(falcon): v0.4.0 — attitude controller + mixer + cascade SITL#15

Merged
avrabe merged 1 commit into
mainfrom
falcon/v0.4
May 19, 2026
Merged

feat(falcon): v0.4.0 — attitude controller + mixer + cascade SITL#15
avrabe merged 1 commit into
mainfrom
falcon/v0.4

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 19, 2026

Summary

Full inner control cascade closes. ATT → RATE → MIX → plant; commanded 20° tilt reached in 0.352 s with 0.029° steady-state error.

What's in

  • crates/relay-att — quaternion-error proportional attitude controller, 250 Hz, shortest-arc selection, small-angle linearisation within ~1.5 % of exact for |θ| < 30°, NaN-safe. 10 unit + proptest cases.
  • crates/relay-mix-quad — X-config 4-motor mixer (standard PX4 quad-x convention). Priority-preserving saturation. 10 unit + proptest cases.
  • examples/falcon-sitl-hover attitude scenario — full cascade closed-loop bench. 2 added integration tests.
  • FV-FALCON-ATT-001 + FV-FALCON-MIX-001 verification artifacts with extractable fields.steps.
  • FEAT-FALCON-v0.4 bumped pendingapproved with achieved metrics.

Achieved bench metrics

metric value budget
cascade convergence (to <2° tilt error) 0.352 s ≤ 1.5 s
RMS-steady attitude error (last 1 s) 0.029° ≤ 2.0°
loop wall time (5000 samples) 545 µs
NaN/∞ in cascade none none
check result
cargo test --workspace 59 suites green (was 55 in v0.3)
cargo test -p relay-att 10/10 PASS
cargo test -p relay-mix-quad 10/10 PASS
cargo test -p falcon-sitl-hover 7/7 PASS
cargo run -p falcon-sitl-hover PASS on all 4 scenarios
Verification gate ✅ 7/7 artifacts, 26/26 steps
rivet validate 0 broken cross-references

Test plan

  • CI green
  • Verification gate posts ✅ 7/7 sticky comment
  • cargo run -p falcon-sitl-hover --release PASSes all 4 scenarios locally
  • cargo run -p falcon-sitl-hover --release -- --scenario attitude prints convergence + RMS metrics

After merge

TAG=falcon-v0.4.0 bash scripts/tag-and-release.sh

Honestly deferred to v0.5

  • Drake-derived MultibodyPlant export of the mixer matrix
  • Rocq formal proof of ATT-P01 (needs rules_rocq_rust)
  • Verus SMT contracts on mixer arithmetic
  • Gazebo Harmonic SITL (lands with position controller)
  • rerun.io .rrd evidence recording
  • Differential test vs PX4 mc_att_control reference

🤖 Generated with Claude Code

Full inner control cascade closes in pure-Rust SITL. Attitude
controller cascades into rate PID into mixer into plant; commanded
20° tilt reached in 0.352 s with 0.029° steady-state error.

What lands:

Crates
- relay-att — quaternion-error proportional attitude controller.
  250 Hz outer loop. Shortest-arc selection; small-angle linearised
  output within ~1.5 % of exact for |θ| < 30°; rate command clamped
  to rate_max; NaN-safe sanitisation. no_std + no_alloc; in-tree
  Newton sqrt avoids libm dependency. 10 unit + proptest cases.
- relay-mix-quad — X-config 4-motor mixer for falcon-quad. Standard
  PX4 quad-x convention (motors 1-4 CW from front-right, diagonal
  pairs CW/CCW). Priority-preserving saturation (thrust first, yaw
  sacrificed last); negative outputs clipped; NaN sanitised.
  10 unit + proptest cases.

Example
- examples/falcon-sitl-hover gains an `attitude` scenario:
  full ATT(250 Hz) → RATE(1 kHz) → MIX → plant cascade for a 20° tilt
  setpoint. Achieved: convergence 0.352 s, RMS-steady 0.029°,
  peak 19.989° (initial), loop wall 545 µs for 5000 samples.
  2 added integration tests.

Rivet
- FV-FALCON-ATT-001 — 5 extractable fields.steps covering relay-att
  unit + proptest + cascade bench.
- FV-FALCON-MIX-001 — 3 extractable fields.steps for relay-mix-quad.
- FEAT-FALCON-v0.4 bumped pending → approved with achieved metrics.

Verification posture
- cargo test --workspace: 59 test suites green (was 55 in v0.3)
- python3 scripts/run-falcon-verification.py: ✅ 7/7 artifacts,
  26/26 steps green
- rivet validate: 0 broken cross-references
- cargo run -p falcon-sitl-hover --release: PASS on all 4 scenarios
  (step, disturbance, hover, attitude)

Test math note (caught during dev)
- ATT-P01 shortest-arc test reformulated: my first cut tangled the
  direction of the short-arc through ±180° wraparound. The
  property under test is "q_err scalar non-negative after correction
  + small vec magnitude for small angular error" — the absolute sign
  of vec depends on which side you're approaching from and is
  irrelevant to the controller's correctness. Reformulated to test
  the actual invariant rather than a specific arc direction.

Honestly deferred to v0.5
- Drake-derived MultibodyPlant export for mixer matrix
- Rocq formal proof of ATT-P01 (needs rules_rocq_rust)
- Verus SMT contracts on mixer arithmetic
- Gazebo Harmonic SITL hookup (lands with position controller)
- rerun.io .rrd evidence recording
- Differential test vs PX4 mc_att_control reference

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

running 10 tests
test tests::att_p01_quaternion_error_is_unit_for_unit_inputs ... ok
test tests::att_p01_shortest_arc_q_err_scalar_non_negative ... ok
test tests::att_p03_small_angle_within_one_percent ... ok
test tests::degenerate_input_does_not_propagate_nan ... ok
test tests::identity_setpoint_at_identity_gives_zero_rate ... ok
test tests::negative_roll_setpoint_gives_negative_x_rate ... ok
test tests::rate_command_clamped_to_rate_max ... ok
test tests::sqrt_f32_matches_libm_within_tolerance ... ok
test tests::twenty_deg_roll_setpoint_gives_positive_x_rate ... ok
test tests::att_p01_property ... ok

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 tests::att_p01_quaternion_error_is_unit_for_unit_inputs ... ok
test tests::att_p01_shortest_arc_q_err_scalar_non_negative ... ok
test tests::att_p03_small_angle_within_one_percent ... ok
test tests::degenerate_input_does_not_propagate_nan ... ok
test tests::negative_roll_setpoint_gives_negative_x_rate ... ok
test tests::rate_command_clamped_to_rate_max ... ok
test tests::identity_setpoint_at_identity_gives_zero_rate ... ok
test tests::sqrt_f32_matches_libm_within_tolerance ... ok
test tests::twenty_deg_roll_setpoint_gives_positive_x_rate ... ok
test tests::att_p01_property ... ok

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 tests::att_p01_shortest_arc_q_err_scalar_non_negative ... ok
test tests::att_p01_quaternion_error_is_unit_for_unit_inputs ... ok
test tests::att_p03_small_angle_within_one_percent ... ok
test tests::degenerate_input_does_not_propagate_nan ... ok
test tests::identity_setpoint_at_identity_gives_zero_rate ... ok
test tests::negative_roll_setpoint_gives_negative_x_rate ... ok
test tests::rate_command_clamped_to_rate_max ... ok
test tests::sqrt_f32_matches_libm_within_tolerance ... ok
test tests::twenty_deg_roll_setpoint_gives_positive_x_rate ... ok
test tests::att_p01_property ... ok

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 7 tests
test tests::deterministic_disturbance_recovers ... ok
test tests::deterministic_hover_settles ... ok
test tests::deterministic_step_response_passes ... ok
test tests::plant_step_preserves_unit_quaternion ... ok
test tests::deterministic_attitude_cascade_passes ... ok
test tests::noisy_step_response_still_passes ... ok
test tests::noisy_attitude_cascade_still_passes ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

--- scenario: attitude ---
samples 5000
final ω (rad/s) [-0.0001, +0.0000, +0.0000]
peak attitude err 19.989°
RMS error (steady) 0.029° (last 1s)
convergence time 0.352s
loop wall time 930 µs
NaN/∞ seen false
outcome PASS
falcon-sitl-hover: PASS

running 13 tests
test tests::at_zero_error_output_is_zero ... ok
test tests::fresh_pid_has_zero_state ... ok
test tests::negative_error_drives_negative_torque ... ok
test tests::out_of_range_dt_clamped_safely ... ok
test tests::positive_error_drives_positive_torque ... ok
test tests::rate_p02_infinite_setpoint_does_not_propagate ... ok
test tests::rate_p02_nan_input_does_not_propagate ... ok
test tests::output_clamped_to_torque_max ... ok
test tests::rate_p03_step_response_drives_error_to_zero ... ok
test tests::reset_clears_state ... ok
test tests::rate_p02_property ... ok
test tests::rate_p01_anti_windup_holds_integral_at_bound ... ok
test tests::rate_p01_property ... ok

test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 13 tests
test tests::at_zero_error_output_is_zero ... ok
test tests::fresh_pid_has_zero_state ... ok
test tests::negative_error_drives_negative_torque ... ok
test tests::out_of_range_dt_clamped_safely ... ok
test tests::positive_error_drives_positive_torque ... ok
test tests::rate_p02_infinite_setpoint_does_not_propagate ... ok
test tests::rate_p02_nan_input_does_not_propagate ... ok
test tests::output_clamped_to_torque_max ... ok
test tests::rate_p03_step_response_drives_error_to_zero ... ok
test tests::rate_p02_property ... ok
test tests::reset_clears_state ... ok
test tests::rate_p01_anti_windup_holds_integral_at_bound ... ok
test tests::rate_p01_property ... ok

test result: ok. 13 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 13 tests
test tests::at_zero_error_output_is_zero ... ok
test tests::fresh_pid_has_zero_state ... ok
test tests::out_of_range_dt_clamped_safely ... ok
test tests::negative_error_drives_negative_torque ... ok
test tests::positive_error_drives_positive_torque ... ok
test tests::rate_p02_infinite_setpoint_does_not_propagate ... ok
test tests::rate_p02_nan_input_does_not_propagate ... ok
test tests::output_clamped_to_torque_max ... ok
test tests::rate_p03_step_response_drives_error_to_zero ... ok
test tests::reset_clears_state ... ok
test tests::rate_p01_anti_windup_holds_integral_at_bound ... ok
test tests::rate_p02_property ... ok
test tests::rate_p01_property ... ok

test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.66s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 7 tests
test tests::deterministic_disturbance_recovers ... ok
test tests::deterministic_hover_settles ... ok
test tests::deterministic_step_response_passes ... ok
test tests::plant_step_preserves_unit_quaternion ... ok
test tests::deterministic_attitude_cascade_passes ... ok
test tests::noisy_step_response_still_passes ... ok
test tests::noisy_attitude_cascade_still_passes ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

--- scenario: step ---
samples 5000
final ω (rad/s) [+0.5007, -0.3003, +0.4006]
peak ω above sp 0.0061 rad/s
overshoot 1.2 %
RMS error (steady) 0.0012 rad/s
convergence time 0.139s
loop wall time 692 µs
NaN/∞ seen false
outcome PASS
--- scenario: disturbance ---
samples 5000
final ω (rad/s) [+0.0000, -0.0042, +0.0000]
peak ω above sp 0.8958 rad/s
recovery time 0.141s after impulse
loop wall time 725 µs
NaN/∞ seen false
outcome PASS
--- scenario: hover ---
samples 5000
final ω (rad/s) [-0.0020, +0.0013, -0.0007]
peak ω above sp 0.7683 rad/s
convergence time 0.175s
loop wall time 730 µs
NaN/∞ seen false
outcome PASS
--- scenario: attitude ---
samples 5000
final ω (rad/s) [-0.0001, +0.0000, +0.0000]
peak attitude err 19.989°
RMS error (steady) 0.029° (last 1s)
convergence time 0.352s
loop wall time 891 µs
NaN/∞ seen false
outcome PASS
falcon-sitl-hover: PASS

running 9 tests
test tests::args_default_ports_for_gcs_mode ... ok
test tests::args_rejects_missing_mode ... ok
test tests::args_default_ports_for_vehicle_mode ... ok
test tests::args_rejects_unknown_mode ... ok
test tests::handle_inbound_propagates_bad_crc ... ok
test tests::handle_inbound_rejects_unsupported_message ... ok
test tests::handle_inbound_truncated ... ok
test tests::current_timestamp_is_monotone_within_a_run ... ok
test tests::vehicle_and_gcs_exchange_heartbeats_over_udp ... ok

test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s

[falcon-hello-demo] building release binary...
[falcon-hello-demo] launching gcs on 127.0.0.1:14700
[falcon-hello-demo] launching vehicle (4 Hz × 4s)
[falcon-hello-demo] vehicle sent 13 heartbeat(s)
[falcon-hello-demo] gcs received 13 heartbeat(s)
[falcon-hello-demo] PASS

running 33 tests
test crc::tests::accumulate_slice_equals_individual ... ok
test crc::tests::empty_input_keeps_seed ... ok
test crc::tests::order_matters ... ok
test crc::tests::mavlink_reference_vector_123456789 ... ok
test crc::tests::single_byte_zero ... ok
test frame::tests::encode_rejects_payload_length_mismatch ... ok
test frame::tests::encode_rejects_too_small_output ... ok
test frame::tests::frame_msg_id_three_bytes_little_endian ... ok
test frame::tests::frame_payload_length_byte_matches ... ok
test frame::tests::frame_starts_with_magic_v2 ... ok
test frame::tests::peek_message_id_finds_heartbeat ... ok
test frame::tests::peek_rejects_v1_magic ... ok
test frame::tests::rejects_bad_crc ... ok
test frame::tests::rejects_truncated_header ... ok
test frame::tests::rejects_truncated_payload ... ok
test frame::tests::rejects_v1_magic ... ok
test frame::tests::rejects_wrong_crc_extra ... ok
test frame::tests::round_trip_heartbeat_through_frame ... ok
test heartbeat::tests::crc_extra_is_fifty ... ok
test heartbeat::tests::custom_mode_little_endian ... ok
test heartbeat::tests::decode_empty_payload ... ok
test heartbeat::tests::decode_rejects_short_payload ... ok
test heartbeat::tests::decode_rejects_long_payload ... ok
test heartbeat::tests::field_offsets_match_spec ... ok
test heartbeat::tests::mav_mode_flag_or_and_contains ... ok
test heartbeat::tests::payload_length_constant ... ok
test heartbeat::tests::round_trip_falcon_default ... ok
test heartbeat::tests::round_trip_gcs_default ... ok
test heartbeat::tests::round_trip_max_values ... ok
test heartbeat::tests::round_trip_zero ... ok
test heartbeat::tests::round_trip_arbitrary ... ok
test frame::tests::encode_parse_round_trip_arbitrary_seq ... ok
test frame::tests::parser_never_panics ... ok

test result: ok. 33 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 33 tests
test crc::tests::accumulate_slice_equals_individual ... ok
test crc::tests::empty_input_keeps_seed ... ok
test crc::tests::mavlink_reference_vector_123456789 ... ok
test crc::tests::order_matters ... ok
test crc::tests::single_byte_zero ... ok
test frame::tests::encode_rejects_payload_length_mismatch ... ok
test frame::tests::encode_rejects_too_small_output ... ok
test frame::tests::frame_msg_id_three_bytes_little_endian ... ok
test frame::tests::frame_payload_length_byte_matches ... ok
test frame::tests::frame_starts_with_magic_v2 ... ok
test frame::tests::peek_message_id_finds_heartbeat ... ok
test frame::tests::peek_rejects_v1_magic ... ok
test frame::tests::rejects_bad_crc ... ok
test frame::tests::encode_parse_round_trip_arbitrary_seq ... ok
test frame::tests::rejects_truncated_header ... ok
test frame::tests::rejects_truncated_payload ... ok
test frame::tests::rejects_v1_magic ... ok
test frame::tests::rejects_wrong_crc_extra ... ok
test frame::tests::round_trip_heartbeat_through_frame ... ok
test heartbeat::tests::crc_extra_is_fifty ... ok
test heartbeat::tests::custom_mode_little_endian ... ok
test heartbeat::tests::decode_empty_payload ... ok
test heartbeat::tests::decode_rejects_long_payload ... ok
test heartbeat::tests::decode_rejects_short_payload ... ok
test heartbeat::tests::field_offsets_match_spec ... ok
test heartbeat::tests::mav_mode_flag_or_and_contains ... ok
test heartbeat::tests::payload_length_constant ... ok
test heartbeat::tests::round_trip_falcon_default ... ok
test heartbeat::tests::round_trip_gcs_default ... ok
test heartbeat::tests::round_trip_max_values ... ok
test heartbeat::tests::round_trip_zero ... ok
test heartbeat::tests::round_trip_arbitrary ... ok
test frame::tests::parser_never_panics ... ok

test result: ok. 33 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 33 tests
test crc::tests::accumulate_slice_equals_individual ... ok
test crc::tests::empty_input_keeps_seed ... ok
test crc::tests::mavlink_reference_vector_123456789 ... ok
test crc::tests::order_matters ... ok
test crc::tests::single_byte_zero ... ok
test frame::tests::encode_rejects_payload_length_mismatch ... ok
test frame::tests::encode_rejects_too_small_output ... ok
test frame::tests::frame_msg_id_three_bytes_little_endian ... ok
test frame::tests::frame_payload_length_byte_matches ... ok
test frame::tests::frame_starts_with_magic_v2 ... ok
test frame::tests::peek_rejects_v1_magic ... ok
test frame::tests::peek_message_id_finds_heartbeat ... ok
test frame::tests::rejects_bad_crc ... ok
test frame::tests::rejects_truncated_header ... ok
test frame::tests::rejects_truncated_payload ... ok
test frame::tests::rejects_v1_magic ... ok
test frame::tests::rejects_wrong_crc_extra ... ok
test frame::tests::round_trip_heartbeat_through_frame ... ok
test heartbeat::tests::crc_extra_is_fifty ... ok
test heartbeat::tests::custom_mode_little_endian ... ok
test heartbeat::tests::decode_empty_payload ... ok
test heartbeat::tests::decode_rejects_long_payload ... ok
test heartbeat::tests::decode_rejects_short_payload ... ok
test heartbeat::tests::field_offsets_match_spec ... ok
test heartbeat::tests::mav_mode_flag_or_and_contains ... ok
test heartbeat::tests::payload_length_constant ... ok
test heartbeat::tests::round_trip_falcon_default ... ok
test heartbeat::tests::round_trip_gcs_default ... ok
test heartbeat::tests::round_trip_max_values ... ok
test heartbeat::tests::round_trip_zero ... ok
test frame::tests::encode_parse_round_trip_arbitrary_seq ... ok
test heartbeat::tests::round_trip_arbitrary ... ok
test frame::tests::parser_never_panics ... ok

test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.39s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 9 tests
test tests::deterministic_across_ticks_with_same_time ... ok
test tests::fresh_stub_has_zero_time ... ok
test tests::innovation_is_quiet_in_stub ... ok
test tests::tick_passes_time_through ... ok
test tests::tick_returns_identity_quaternion ... ok
test tests::tick_returns_zero_position_and_velocity ... ok
test tests::unit_quaternion_check_rejects_clearly_non_unit ... ok
test tests::tick_always_emits_unit_quaternion ... ok
test tests::tick_innovation_within_healthy_range ... ok

test result: ok. 9 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 9 tests
test tests::deterministic_across_ticks_with_same_time ... ok
test tests::innovation_is_quiet_in_stub ... ok
test tests::fresh_stub_has_zero_time ... ok
test tests::tick_passes_time_through ... ok
test tests::tick_returns_identity_quaternion ... ok
test tests::tick_returns_zero_position_and_velocity ... ok
test tests::unit_quaternion_check_rejects_clearly_non_unit ... ok
test tests::tick_innovation_within_healthy_range ... ok
test tests::tick_always_emits_unit_quaternion ... ok

test result: ok. 9 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 9 tests
test tests::deterministic_across_ticks_with_same_time ... ok
test tests::fresh_stub_has_zero_time ... ok
test tests::innovation_is_quiet_in_stub ... ok
test tests::tick_passes_time_through ... ok
test tests::tick_returns_identity_quaternion ... ok
test tests::tick_returns_zero_position_and_velocity ... ok
test tests::unit_quaternion_check_rejects_clearly_non_unit ... ok
test tests::tick_innovation_within_healthy_range ... ok
test tests::tick_always_emits_unit_quaternion ... ok

test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s

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 tests::cross_of_parallel_is_zero ... ok
test tests::ekf_p01_tick_preserves_unit_quaternion ... ok
test tests::ekf_p02_extreme_accel_does_not_produce_nan ... ok
test tests::ekf_p02_zero_accel_does_not_produce_nan ... ok
test tests::ekf_p03_innovation_monotone_with_tilt_disagreement ... ok
test tests::ekf_p04_static_rest_converges_to_gravity_aligned ... ok
test tests::ekf_p05_pure_yaw_gyro_does_not_destabilise_attitude ... ok
test tests::fresh_estimator_is_at_identity ... ok
test tests::normalise_nan_returns_none ... ok
test tests::normalise_zero_returns_none ... ok
test tests::quat_mul_identity_left ... ok
test tests::quat_mul_identity_right ... ok
test tests::rotate_inverse_identity_passes_through ... ok
test tests::ekf_p01_property ... ok
test tests::bias_estimate_bounded ... ok
test tests::ekf_p02_property_sequence ... ok

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 tests::cross_of_parallel_is_zero ... ok
test tests::ekf_p01_tick_preserves_unit_quaternion ... ok
test tests::ekf_p02_extreme_accel_does_not_produce_nan ... ok
test tests::ekf_p02_zero_accel_does_not_produce_nan ... ok
test tests::ekf_p03_innovation_monotone_with_tilt_disagreement ... ok
test tests::ekf_p04_static_rest_converges_to_gravity_aligned ... ok
test tests::ekf_p05_pure_yaw_gyro_does_not_destabilise_attitude ... ok
test tests::fresh_estimator_is_at_identity ... ok
test tests::normalise_nan_returns_none ... ok
test tests::normalise_zero_returns_none ... ok
test tests::ekf_p01_property ... ok
test tests::quat_mul_identity_left ... ok
test tests::quat_mul_identity_right ... ok
test tests::rotate_inverse_identity_passes_through ... ok
test tests::ekf_p02_property_sequence ... ok
test tests::bias_estimate_bounded ... ok

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 tests::cross_of_parallel_is_zero ... ok
test tests::ekf_p01_tick_preserves_unit_quaternion ... ok
test tests::ekf_p02_extreme_accel_does_not_produce_nan ... ok
test tests::ekf_p02_zero_accel_does_not_produce_nan ... ok
test tests::ekf_p03_innovation_monotone_with_tilt_disagreement ... ok
test tests::ekf_p04_static_rest_converges_to_gravity_aligned ... ok
test tests::ekf_p05_pure_yaw_gyro_does_not_destabilise_attitude ... ok
test tests::fresh_estimator_is_at_identity ... ok
test tests::normalise_nan_returns_none ... ok
test tests::normalise_zero_returns_none ... ok
test tests::quat_mul_identity_left ... ok
test tests::quat_mul_identity_right ... ok
test tests::rotate_inverse_identity_passes_through ... ok
test tests::bias_estimate_bounded ... ok
test tests::ekf_p01_property ... ok
test tests::ekf_p02_property_sequence ... ok

test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.84s

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 tests::phases_are_consistent_with_trajectory_duration ... ok
test tests::quat_error_is_zero_for_identical_quaternions ... ok
test tests::rotate_ned_to_body_identity_passes_through ... ok
test tests::deterministic_bench_passes ... ok
test tests::noisy_bench_passes_loose ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

--- noise=0 (deterministic) ---
samples 5000
RMS error (full) 3.306°
RMS error (steady) 3.312° (last 2.5 s)
peak error 19.804°
final error 3.023°
convergence time 0.68s (first sustained <5°)
estimator wall time 619 µs
NaN/∞ seen false
falcon-ekf-bench: PASS

running 10 tests
test tests::high_thrust_with_torque_saturates_gracefully ... ok
test tests::mix_p01_zero_command_gives_thrust_only ... ok
test tests::mix_p02_outputs_in_unit_interval ... ok
test tests::mix_p03_pure_pitch_drives_front_and_back_motors_opposite ... ok
test tests::mix_p03_pure_roll_drives_diagonal_motor_pairs_opposite ... ok
test tests::mix_p03_pure_yaw_drives_cw_and_ccw_pairs_opposite ... ok
test tests::nan_input_does_not_propagate ... ok
test tests::negative_thrust_clipped_to_zero ... ok
test tests::mix_p03_property_single_axis ... ok
test tests::mix_p02_property ... ok

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 tests::high_thrust_with_torque_saturates_gracefully ... ok
test tests::mix_p01_zero_command_gives_thrust_only ... ok
test tests::mix_p02_outputs_in_unit_interval ... ok
test tests::mix_p03_pure_pitch_drives_front_and_back_motors_opposite ... ok
test tests::mix_p03_pure_roll_drives_diagonal_motor_pairs_opposite ... ok
test tests::mix_p03_pure_yaw_drives_cw_and_ccw_pairs_opposite ... ok
test tests::nan_input_does_not_propagate ... ok
test tests::negative_thrust_clipped_to_zero ... ok
test tests::mix_p03_property_single_axis ... ok
test tests::mix_p02_property ... ok

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 tests::high_thrust_with_torque_saturates_gracefully ... ok
test tests::mix_p01_zero_command_gives_thrust_only ... ok
test tests::mix_p02_outputs_in_unit_interval ... ok
test tests::mix_p03_pure_pitch_drives_front_and_back_motors_opposite ... ok
test tests::mix_p03_pure_roll_drives_diagonal_motor_pairs_opposite ... ok
test tests::mix_p03_pure_yaw_drives_cw_and_ccw_pairs_opposite ... ok
test tests::nan_input_does_not_propagate ... ok
test tests::negative_thrust_clipped_to_zero ... ok
test tests::mix_p03_property_single_axis ... ok
test tests::mix_p02_property ... ok

test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.10s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

falcon verification gate (filter: (has-tag "falcon"))

7 artifact(s) matched: FV-FALCON-ATT-001, FV-FALCON-RATE-001, FV-FALCON-WORLD-001, FV-FALCON-MAVLINK-001, FV-FALCON-EKF-STUB-001, FV-FALCON-EKF-001, FV-FALCON-MIX-001

[ PASS] ( 8.62s) FV-FALCON-ATT-001: cargo test -p relay-att
[ PASS] ( 9.61s) FV-FALCON-ATT-001: cargo test -p relay-att --release
[ PASS] ( 0.18s) FV-FALCON-ATT-001: PROPTEST_CASES=4096 cargo test -p relay-att
[ PASS] ( 1.11s) FV-FALCON-ATT-001: cargo test -p falcon-sitl-hover
[ PASS] ( 1.32s) FV-FALCON-ATT-001: cargo run -q -p falcon-sitl-hover --release -- --scenario attitude
[ PASS] ( 0.40s) FV-FALCON-RATE-001: cargo test -p relay-rate
[ PASS] ( 0.80s) FV-FALCON-RATE-001: cargo test -p relay-rate --release
[ PASS] ( 0.73s) FV-FALCON-RATE-001: PROPTEST_CASES=4096 cargo test -p relay-rate
[ PASS] ( 0.05s) FV-FALCON-RATE-001: cargo test -p falcon-sitl-hover
[ PASS] ( 0.04s) FV-FALCON-RATE-001: cargo run -q -p falcon-sitl-hover --release
[ PASS] ( 0.37s) FV-FALCON-WORLD-001: cargo test -p falcon-hello
[ PASS] ( 4.77s) FV-FALCON-WORLD-001: scripts/falcon-hello-demo.sh
[ PASS] ( 0.46s) FV-FALCON-MAVLINK-001: cargo test -p relay-mavlink
[ PASS] ( 1.08s) FV-FALCON-MAVLINK-001: cargo test -p relay-mavlink --release
[ PASS] ( 0.46s) FV-FALCON-MAVLINK-001: PROPTEST_CASES=4096 cargo test -p relay-mavlink
[ PASS] ( 0.36s) FV-FALCON-EKF-STUB-001: cargo test -p relay-ekf-stub
[ PASS] ( 0.74s) FV-FALCON-EKF-STUB-001: cargo test -p relay-ekf-stub --release
[ PASS] ( 0.16s) FV-FALCON-EKF-STUB-001: PROPTEST_CASES=4096 cargo test -p relay-ekf-stub
[ PASS] ( 0.44s) FV-FALCON-EKF-001: cargo test -p relay-ekf
[ PASS] ( 0.83s) FV-FALCON-EKF-001: cargo test -p relay-ekf --release
[ PASS] ( 0.92s) FV-FALCON-EKF-001: PROPTEST_CASES=4096 cargo test -p relay-ekf
[ PASS] ( 0.17s) FV-FALCON-EKF-001: cargo test -p falcon-ekf-bench
[ PASS] ( 0.19s) FV-FALCON-EKF-001: cargo run -q -p falcon-ekf-bench --release
[ PASS] ( 0.35s) FV-FALCON-MIX-001: cargo test -p relay-mix-quad
[ PASS] ( 0.77s) FV-FALCON-MIX-001: cargo test -p relay-mix-quad --release
[ PASS] ( 0.18s) FV-FALCON-MIX-001: PROPTEST_CASES=4096 cargo test -p relay-mix-quad

✅ Rivet verification gate — falcon

7/7 passed

count
Passed 7
Failed 0
Skipped (no steps) 0

Source of truth: artifacts/verification/FV-FALCON-*.yaml.

@avrabe avrabe merged commit 6519d0f into main May 19, 2026
7 checks passed
@avrabe avrabe deleted the falcon/v0.4 branch May 19, 2026 17:38
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