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
68 changes: 67 additions & 1 deletion artifacts/features/FEAT-FALCON-rollout.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1930,6 +1930,72 @@ artifacts:
- type: depends-on
target: FEAT-FALCON-v0.19.4

- id: FEAT-FALCON-v0.19.6
type: feature
title: "v0.19.6 — frame-correctness oracle + SDF/MIXER_X alignment"
status: approved
description: >
LANDED. The NED↔ENU frame unlock for the full verified cascade.
A frame-check oracle established the true actuator-path
convention; the fix was an SDF rotor index→position+spin
alignment to relay-mix-quad's MIXER_X convention — not a code
sign-flip. Roll/pitch torque now produce correctly-signed body
rate; the verified rate-PID damping holds the body level.
Horizontal drift collapsed from −13 m (v0.19.5) to ~±0.15 m.

Oracle (--scenario=frame-{roll,pitch,yaw}): commands unit torque
on one axis + hover thrust, reports sensed body-rate sign.
AGREE = +torque→+rate (needed for rate-PID negative feedback to
stabilise).

Before (scrambled SDF): roll ≈ 0 (no authority!), pitch OPPOSE,
yaw AGREE. Root cause: SDF rotor index→position map scrambled vs
MIXER_X (0=front-right CW, 1=back-right CCW, 2=back-left CW,
3=front-left CCW). The v0.19.4/.5 negate-torque hack was
compensating for the scramble.

After (aligned SDF, identity correction): roll +4.77 AGREE,
pitch +4.14 AGREE, yaw -0.0001 (weak authority, deferred).
frame_correct_torque is identity; unit test
frame_correction_is_identity pins it.

Horizontal-stability proof: v0.19.5 drifted to n=−13/e=−7 →
crash; v0.19.6 holds n,e within ±0.15 m (alt-rate) / ±1.4 m
(full cascade, no runaway). The frame fix removed the runaway.

Ships:
- --scenario=frame-{roll,pitch,yaw} oracle.
- SDF rotor index→position+spin aligned to MIXER_X.
- frame_correct_torque adapter (identity) on rate→mixer path.
- unit test frame_correction_is_identity.
- bench-evidence/gz-sim/2026-05-28-v0.19.6-frame-correctness.md.
- FV-FALCON-SIM-012.

Verification:
- cargo test --workspace --all-targets → 407 passing.
- cargo test -p falcon-sitl-gz --features gazebo → 9/9.
- frame-check oracle: roll + pitch AGREE under gz.
- rivet validate → PASS.

Honestly NOT claimed:
- Full cascade hovers. Doesn't lift yet — relay-pos default
hover_thrust (0.5) insufficient for the 2 kg body (~0.72
needed). v0.19.7. Frame fix removed horizontal runaway;
lift + sustained hover is next.
- Yaw controlled. Authority ≈ 0 (momentConstant); separate
tuning, non-critical for hover.
tags: [falcon, milestone, v0.19.6, gazebo, frame-correctness, oracle, landed]
fields:
release-target: "frame-correctness oracle + SDF/MIXER_X alignment"
bench-date: "2026-05-28"
oracle: "--scenario=frame-{roll,pitch,yaw}"
frame-correction: "identity (SDF alignment was the fix)"
horizontal-drift: "±0.15 m (was −13 m at v0.19.5)"
deferred-to-v0.19.7: "relay-pos thrust tuning for 2 kg → lift + sustained hover"
links:
- type: depends-on
target: FEAT-FALCON-v0.19.5

- id: FEAT-FALCON-v1.0
type: feature
title: "v1.0 — six-domain credit dossier + airframe variants"
Expand Down Expand Up @@ -1966,4 +2032,4 @@ artifacts:
- type: implements
target: SYSREQ-FALCON-010
- type: depends-on
target: FEAT-FALCON-v0.19.5
target: FEAT-FALCON-v0.19.6
85 changes: 85 additions & 0 deletions artifacts/verification/FV-FALCON-SIM-012.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
artifacts:
- id: FV-FALCON-SIM-012
type: sw-verification
title: "v0.19.6 — frame-correctness oracle + SDF/MIXER_X alignment (horizontal drift eliminated)"
status: approved
description: >
The NED↔ENU frame unlock for the full verified cascade. A
frame-check oracle established the true actuator-path convention;
the fix was an SDF rotor index→position+spin alignment to the
verified relay-mix-quad MIXER_X convention — not a code sign-flip.
Roll/pitch torque now produce correctly-signed body rate and the
verified rate-PID damping holds the body level: horizontal drift
collapsed from −13 m (v0.19.5) to ~±0.15 m.

The oracle (--scenario=frame-{roll,pitch,yaw}): commands a unit
torque on one axis (post frame-correction) + hover thrust, reports
the sensed body-rate sign. AGREE = +torque → +rate (required for
the rate-PID negative feedback to be stabilising).

Before (v0.19.0–.5 SDF, identity correction):
roll: +0.15 → -0.0002 rad/s OPPOSE (≈0 — no roll authority)
pitch: +0.15 → -0.0087 rad/s OPPOSE
yaw: +0.15 → +0.1246 rad/s AGREE

Root cause: the SDF rotor index→position map was scrambled vs
MIXER_X (0=front-right CW, 1=back-right CCW, 2=back-left CW,
3=front-left CCW). The v0.19.4/.5 "negate roll+pitch torque"
hack was compensating for the scramble, not a real frame flip.

Fix: align SDF rotor index→position+spin to MIXER_X in gz ENU
coords (NED right = gz −Y; CW-about-NED-down = ccw-about-gz-up).

After (aligned SDF, identity correction):
roll: +0.15 → +4.77 rad/s AGREE ✓
pitch: +0.15 → +4.14 rad/s AGREE ✓
yaw: +0.15 → -0.0001 rad/s OPPOSE (weak authority, deferred)

frame_correct_torque is identity; unit test
frame_correction_is_identity pins it. If a future SDF/frame
change reintroduces a flip, the frame-check oracle catches it.

Horizontal-stability proof:
v0.19.5 (scrambled + negate): drifts to n=−13, e=−7 → crash.
v0.19.6 (aligned, identity): n,e within ±0.15 m (alt-rate) /
±1.4 m (full cascade, no runaway).

Ships:
- --scenario=frame-{roll,pitch,yaw} oracle.
- SDF rotor index→position+spin aligned to MIXER_X.
- frame_correct_torque adapter (identity) on the rate→mixer
path in alt-rate + the full hover cascade.
- unit test frame_correction_is_identity.
- bench-evidence/gz-sim/2026-05-28-v0.19.6-frame-correctness.md.

Verification:
- cargo test --workspace --all-targets → 407 passing (+1
frame_correction_is_identity).
- cargo test -p falcon-sitl-gz --features gazebo → 9/9.
- frame-check oracle: roll + pitch AGREE under gz.
- rivet validate → PASS.

Honestly NOT claimed:
- That the full cascade hovers. It doesn't lift yet —
relay-pos's default hover_thrust (0.5) is insufficient for
the 2 kg body (needs ~0.72). v0.19.7. The frame fix removed
the horizontal runaway; lift + sustained hover is the next
release.
- That yaw is controlled. Authority ≈ 0 (momentConstant);
separate tuning item, non-critical for hover.
- That the oracle covers attitude angle, only rate sign.
tags: [falcon, sim, gazebo, frame-correctness, oracle, bench-evidence, v0.19.6]
fields:
bench-evidence-dir: bench-evidence/gz-sim/
bench-date: "2026-05-28"
gz-version: "8.11.0"
oracle: "--scenario=frame-{roll,pitch,yaw}"
frame-check-roll: "+4.77 rad/s AGREE"
frame-check-pitch: "+4.14 rad/s AGREE"
frame-check-yaw: "-0.0001 rad/s OPPOSE (weak authority, deferred)"
frame-correction: "identity (SDF alignment was the fix)"
root-cause: "SDF rotor index→position map scrambled vs relay-mix-quad MIXER_X"
deferred-to-v0.19.7: "relay-pos thrust tuning for 2 kg body → lift + sustained hover"
links:
- type: verifies
target: SWREQ-FALCON-SIM-P04
113 changes: 113 additions & 0 deletions bench-evidence/gz-sim/2026-05-28-v0.19.6-frame-correctness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# v0.19.6 — frame-correctness oracle + fix — 2026-05-28

The unlock for the full verified cascade. A frame-check oracle
established the true NED↔ENU actuator-path convention; the fix was an
SDF rotor-index alignment (not a code sign-flip). Roll/pitch torque
now produces correctly-signed body rate, and the cascade's rate
damping holds the body level — horizontal drift collapsed from −13 m
(v0.19.5) to ~±0.15 m.

## The oracle

`--scenario=frame-{roll,pitch,yaw}` commands a unit torque on one axis
(post frame-correction) with hover thrust and reports the sign of the
sensed body rate. For the verified rate-PID's negative feedback to be
*stabilising*, +torque[axis] must yield +rate[axis] (AGREE).

### Before — raw signs with the v0.19.0–.5 SDF (identity correction)

```
roll(X): +0.15 → -0.0002 rad/s OPPOSE (≈ 0 — no roll authority!)
pitch(Y): +0.15 → -0.0087 rad/s OPPOSE
yaw(Z): +0.15 → +0.1246 rad/s AGREE
```

Roll authority ≈ 0 was the smoking gun: the SDF's rotor index→position
map was **scrambled** relative to the verified mixer, so the mixer's
roll column applied to the wrong physical rotors nearly cancelled.

### Root cause — SDF vs MIXER_X mismatch

`crates/relay-mix-quad`'s actual `MIXER_X` convention:

```
index 0 = front-right, CW (NED)
index 1 = back-right, CCW
index 2 = back-left, CW
index 3 = front-left, CCW
```

The v0.19.0–.5 SDF assigned a different index→position map (0=FR,
1=BL, 2=FL, 3=BR with mismatched spins). The v0.19.4/.5 "negate
roll+pitch torque" hack was compensating for *this scramble*, not a
real frame inversion.

### Fix — align SDF to MIXER_X (gz ENU coords)

NED right = gz −Y; a CW-about-NED-down rotor is ccw-about-gz-up:

```
index 0 → gz (+X, −Y), ccw (was +X,−Y ccw — already right)
index 1 → gz (−X, −Y), cw (was −X,+Y ccw)
index 2 → gz (−X, +Y), ccw (was +X,+Y cw)
index 3 → gz (+X, +Y), cw (was −X,−Y cw)
```

### After — raw signs with the aligned SDF (identity correction)

```
roll(X): +0.15 → +4.77 rad/s AGREE ✓
pitch(Y): +0.15 → +4.14 rad/s AGREE ✓
yaw(Z): +0.15 → -0.0001 rad/s OPPOSE (weak authority — see below)
```

Roll + pitch now respond strongly and correctly with **identity**
frame-correction — confirming the SDF alignment was the real fix.
`frame_correct_torque` is identity; the unit test
`frame_correction_is_identity` pins it.

## Horizontal-stability proof

The decisive end-to-end check: with the verified rate-PID damping
attitude, does the body stay put horizontally?

| Scenario | Horizontal excursion over 20 s |
|---|---|
| v0.19.5 (scrambled SDF + negate hack) | drifts to n=−13 m, e=−7 m → crash |
| **v0.19.6 (aligned SDF, identity)** | **n, e within ±0.15 m (alt-rate) / ±1.4 m (full cascade)** |

The frame fix eliminated the horizontal runaway. The verified rate
loop is now genuinely stabilising.

## Known-open: yaw authority + lift

Two items deferred to v0.19.7, both *not* frame-sign problems:

1. **Yaw authority ≈ 0.** Yaw torque comes from rotor drag-reaction
(`momentConstant`), far weaker than thrust differential. For hover
it's non-critical (heading holds roughly); proper yaw control is a
`momentConstant` / mixer-gain tuning item.
2. **Full cascade doesn't lift yet.** `--scenario=hover` now stays
near origin (n,e oscillate ±1.4 m, no runaway — the frame fix
works) but altitude stays at 0: `relay-pos`'s default
`hover_thrust` gain (0.5) gives insufficient thrust for the 2 kg
body (needs ~0.72). That's the v0.19.7 tuning — wire PosGains for
this airframe → the body lifts → sustained hover.

## What v0.19.6 ships

- `--scenario=frame-{roll,pitch,yaw}` frame-check oracle.
- SDF rotor index→position+spin aligned to MIXER_X.
- `frame_correct_torque` boundary adapter (identity) applied on the
rate→mixer path in both `alt-rate` and the full `hover` cascade.
- Unit test `frame_correction_is_identity`.
- This evidence doc.

## Honestly NOT claimed

- That the full cascade hovers. It doesn't lift yet (v0.19.7 thrust
tuning). v0.19.6 is the *frame* unlock: roll/pitch correct, no
horizontal runaway.
- That yaw is controlled. Authority is ~0; a separate tuning item.
- That the oracle covers attitude *angle*, only rate sign. Angle hold
(att controller) lands with the lift fix in v0.19.7.
Loading
Loading