Skip to content

Fixed wing: drop temporary turn tuning settings (stacked on #11812) - #11962

Draft
b14ckyy wants to merge 29 commits into
iNavFlight:maintenance-10.xfrom
b14ckyy:fw-turn-settings-cleanup
Draft

b14ckyy wants to merge 29 commits into
iNavFlight:maintenance-10.xfrom
b14ckyy:fw-turn-settings-cleanup

Conversation

@b14ckyy

@b14ckyy b14ckyy commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #11812 — merge only after that PR. The diff shows the whole stack until #11812 lands; the actual change is the last commit (375dc5f).

Why

#11812 kept two settings as a safety valve in case the defaults misbehaved on some builds:

  • nav_fw_turn_ff_gain (default 100 %)
  • nav_fw_wp_turn_control_ease (default 100 ms)

Field testing did not need either, so this PR removes them and hardcodes the defaults, as agreed with the maintainer. Draft until #11812 is merged and it is clear which release this lands in.

What

  • Both settings removed from settings.yaml, docs/Settings.md regenerated with update_cli_docs.py
  • turn_ff_gain / wp_turn_control_ease removed from navConfig_t.fw
  • FF gain: at 100 % the factor is unity, so the multiplier and the gain == 0 bypass are dropped from getFwTurnFeedForward(). Pure-PID behaviour stays reachable via nav_fw_wp_turn_mode = DIRECT
  • Control ease: new constant NAV_FW_TURN_CONTROL_EASE_MS (100 ms) in navigation_fixedwing.c, used by fwTurnEaseTimeMs() and the arc roll-out lead
  • PG_NAV_CONFIG version bumped (12→13, non-VTOL variant 9→10) — the navigation group resets to defaults on update

nav_fw_wp_turn_max_lead_time stays; it depends on the airframe.

Testing

🤖 Generated with Claude Code

b14ckyy and others added 29 commits August 24, 2026 11:27
Replace static-radius nav_fw_wp_turn_smoothing with nav_fw_wp_turn_mode.
FLY_BY anticipates the turn from the real coordinated-turn radius
R = V^2/(g*tan(bank)) at d = R*tan(angle/2), so the turn starts at the correct
distance at any speed; FLY_OVER flies over the WP then turns. Landing approach
always uses FLY_BY. PG_NAV_CONFIG 7->8.
…curve roll-in

Builds on the FLY_BY/FLY_OVER turn predictor:
- Energy/altitude bank guard (always-on): reduce the effective nav bank limit when a
  commanded climb cannot be sustained near the pitch/throttle limit while banked,
  from target-vs-actual vertical speed (filtered + Schmitt deadband + bank-entry
  baseline). Widens the turn/loiter so the climb recovers.
- Loiter-radius floor: never demand a circle tighter than the effective bank allows.
- Coordinated-turn feed-forward (nav_fw_turn_ff_gain, dev/experimental, default 100):
  command the geometric bank for the active turn/loiter radius so the PID only trims.
- Roll-in S-curve replacing the control_smoothness PT1 (roll axis only): step-triggered,
  control_smoothness*50ms time constant, smoothstep then 1:1. Pitch PT1 retained.
- DEBUG_FW_TURN channel for tuning. PG_NAV_CONFIG 8->9.

(Includes minor comment trims to the B1 turn-predictor code per AGENT.md.)
…iter-only headroom

Refinements on the B2 feed-forward/guard work (navigation_fixedwing.c):
- Loiter-radius floor -> stabilised per-revolution peak hold with 1 m/s gradual decay
  (getFwStableLoiterRadius): ratchet up at once, hold the peak over a full revolution
  (orbital azimuth net 360deg), ease down at <=1 m/s. Stops the commanded circle
  thrashing with wind-driven ground-speed swings.
- Bank-limit semantics: nav_fw_bank_angle is the planning TARGET; control output may
  use reserve up to the hard ceiling max_angle_inclination_rll to hold the radius
  (getFwBankCeilingDeg / getFwEffectiveBankLimit / getFwPlanningBankDeg). Energy guard
  reduces the ceiling and snaps straight to the target on trigger.
- Headroom is loiter-only (getFwControlBankLimit): WP turns/cruise clamp to the
  planning target so coordinated turns fly a clean arc, not the hard ceiling.
- Roll-in S-curve time constant -> control_smoothness*100ms (cap 1000ms).
Replace the heading-PID corner turn with an explicit coordinated arc on real
WP-to-WP turns (>30 deg). New nav_fw_wp_turn_coordination = COORDINATED (default)
/ DIRECT (legacy fallback).

The turn is a variable-radius spline driven directly on the roll axis:
- RAMP_IN: smoothstep bank 0->phi_nom (no servo slam; control_smoothness folded
  into the ramp time and bypassed during the arc).
- STEADY: direct radius control (nominal + radial pull-back + tangent alignment)
  against an inscribed circle placed tangent to BOTH legs, so the exit lands on
  the out-leg instead of offset.
- CAPTURE: closed-loop roll-out, bank proportional to the heading still to go ->
  levels exactly on the out-leg, cannot overshoot the heading.

Roll-aware easing: ease time = 1.5*phi/roll_rate + control_smoothness + the new
nav_fw_wp_turn_control_ease (servo/inertia margin); the FLY_BY turn-start lead is
sized from it so the longer eased path still starts in time.

PG_NAV_CONFIG 10 -> 11. Settings: nav_fw_wp_turn_coordination,
nav_fw_wp_turn_handback_angle, nav_fw_wp_turn_max_lead_time,
nav_fw_wp_turn_control_ease (all dev/experimental).
Two state-handling fixes in the arc turn coordinator (found in code review):

- The coordinator's engage latch and leg-bearing memory were function-local
  statics that survived resetFixedWingPositionController(). Interrupting nav
  mid-arc (switch to ANGLE/ALTHOLD/COURSE_HOLD) and re-entering WP mode
  resumed the arc with stale geometry, banking toward an outdated out-leg.
  State is now file-scope (fwArcEngaged, fwArcPrevLegBearing) and cleared on
  controller reset; the roll S-curve smoother is re-seeded the same way
  (fwRollSmoothReseed) so stale filter state cannot fire a spurious ramp.

- A leg change while an arc was still active (short legs: FLY_BY early-reach
  advances the mission mid-turn) was silently consumed: the arc completed
  onto the stale out-bearing and the new corner got no coordination at all.
  Now the closed-loop capture is retargeted onto the new leg (bounded
  +/-phi_nom, hands back once aligned), so quick consecutive corners degrade
  gracefully instead of being skipped.
…ual throttle

Maintainer decisions 2026-08-20 after the resume code review:

nav_fw_wp_turn_max_lead_time: min 0 -> 1000ms (0 silently disabled the FLY_BY
anticipation: lead distance 0 -> WP reached by proximity radius, arc engages
uncapped at the corner - an undesigned mode). Stays a permanent user setting
instead of being hardcoded before release; DEV note dropped, Settings.md
regenerated.

Energy bank guard: evaluate the near-throttle-limit branch against the AUTO
throttle demand (before allow_manual_thr_increase is added). Pilot-held full
throttle permanently armed the branch even though the autopilot still had
throttle authority; the throttle branch now cleanly means 'auto-throttle
authority exhausted'. A genuine energy crisis is still caught by the
OR-connected pitch branch (climb pitch saturates).
The arc coordinator's original exit (design v1) handed control back to the
heading PID at a fixed heading error (nav_fw_wp_turn_handback_angle, 15 deg)
with a 300ms progress guard against premature handback on cog noise
(NAV_FW_ARC_HANDBACK_GUARD_MS). Both became obsolete when the exit was
replaced by the closed-loop capture phase (bank proportional to remaining
heading, cannot hand back early or overshoot) but survived as dead code:
the guard define was never referenced, the setting was stored but never read.

Remove both (PG_NAV_CONFIG 11 -> 12 for the struct change) and skip the
turn feed-forward computation entirely while the arc coordinator drives the
roll: its result was discarded (the arc bank command already is the
coordinated bank), and its arc branch only fed a debug channel with values
that were never applied. fwArcDir is unused after that and removed.
…edictive roll-out lead

- loiter FF only once established on the circle: fed during the (much
  larger) approach cone it fought the approach carrot and slewed the
  entry across the circle
- capped and >150 deg corners fly the bounded closed-loop capture
  instead of the reactive PID / a degenerate tangent circle (flutter)
- capture leads the roll-out by omega*(tau + control_ease), tau from
  the angle-P gain: the airframe sheds bank slower than the command
  falls, the residual turn rate was overshooting the out-leg
- keep the roll smoother's reseed baseline current while the arc drives
  (a stale reset-time seed caused a brief roll twitch at arc handback)
- nav_fw_wp_turn_max_lead_time bounds/default now 3000/6000/12000 (the
  3 s ceiling capped nearly every cruise-speed corner)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- FLY_OVER now engages the arc coordinator as a reverse FLY_BY: the
  turn circle is pinned at the overfly point (led by the roll-in
  drift) and the exit course is the tangent from that circle through
  the next waypoint, so the roll-out lands exactly on a straight line
  to it. Handles any turn angle including full reversals; HITL: exit
  course within 2 deg of the direct line at all tested corners
- shaped roll-out for all arc exits: the capture command's collapse is
  rate-limited to the entry ramp's build-up rate (phiNom/tEase) with
  the no-overshoot envelope kept on top; the lead gains the ramp's
  heading share (0.5*omega*tEase) and STEADY hands over early enough
  for the ramp to fit; handback waits until nearly level
- debug: ch2 = active exit course while the arc runs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt S

Third wp_turn_mode: turn away onto a counter arc first, then cross the WP
already aligned on the outbound course. The two equal-radius circles are
spaced sqrt((2R)^2 + Ls^2) so an internal-tangent gap (Ls = 2 ease times of
travel) gives the roll reversal room; the away arc rolls out onto the tangent
course through the shared capture predictor and the main arc is picked up at
the touch point with a standard ramp. Scales itself with the corner angle and
degenerates cleanly to a teardrop at a full 180 reversal - no fallback needed.

ARC_STEADY feed-forward now tracks current groundspeed (phi = atan(v^2/gR))
so wind-driven speed change along the arc is commanded immediately instead of
recovered through the radial error term.

Mission legs only (NAV_AUTO_WP) - the landing approach keeps FLY_BY. Debug
ch1 shows the FLY_INTO sequencer stage (energy-guard bank-limit write muted
in that mode).

PG_NAV_CONFIG 12 -> 13: the restack onto the updated fw-roll-smoothing head
pulled in the cruise_lock_on_level field, changing the struct layout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… onto the leg

With nav_fw_wp_tracking_accuracy enabled, FLY_OVER now rolls out ON the new
leg instead of on the direct course to the next WP: the main arc exits onto a
bounded intercept course (gamma = half the turn, capped at 45 deg), a short
straight gives the bank reversal room, and a standard corner-cut arc rolls
out tangentially on the line. At the pickup the second circle is re-solved
from CURRENT groundspeed (the arming radius may be unflyable downwind) and
anchored along the leg line through the actual position, so wind drift
becomes an along-track shift instead of a parallel roll-out offset.

Robustness from the same HITL campaign: the arc bank command is clamped to
the effective ceiling (wind can drive the radial term arbitrarily large, and
rate limits, handoff checks and the smoother seed must not run on a command
the airframe cannot reach); no hand-back mid-S (path tracking must not see
the transient offset); ramp duration scales with the commanded bank span;
the cross-track rate estimator is seeded from the geometric closing speed on
hand-back so path tracking re-engages without commanding a full-error kick;
comments trimmed to project style.

HITL (X-Plane, 15 km/h wind): all corners incl. the 180 reversal and the
sharpest downwind corner roll out within 3-9 m of the line, hand-back course
within 2 deg; wind-free within 0.5-6.5 m.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e debug layout

- nav_fw_wp_turn_coordination merged into nav_fw_wp_turn_mode
  (DIRECT / COORD_FLYBY / COORD_FLYOVER / COORD_FLYINTO), PG_NAV_CONFIG -> 14
- DEBUG_FW_TURN: one owner per channel, documented in
  docs/development/fw-turn-debugging.md for the official docs
- remove unreachable not-engaged S pickup branch (superseded by the
  engaged pickup; defensive stale release on controller reset mid-S)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n path tracking is on

Reversal/capped fallbacks ended parallel to the leg a full turn-diameter off
(logged: 116 m after the 180 at WP1, 46 deg tracker cut). The capture target
now tracks a live intercept course onto the leg line, tapering 1 cd/cm and
capped at nav_fw_wp_tracking_max_angle. Sim: handoff on ~50 deg intercept,
on the line within 0.4-4 m.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The arc coordinator followed the global nav_fw_wp_turn_mode on the landing
approach legs; clean transitions between the approach tracks are mandatory,
so the effective mode is forced to COORD_FLYBY there (from DIRECT too).
Landing doc updated - approach turning points come from the coordinated
turn radius, not nav_wp_radius; turn-mode setting description shortened.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ve FF assist

Additive FF on the carrot PID settles inside the commanded circle (HITL:
86-92 m flown at 120 m set) and its binary engage gate sat exactly in that
error band, toggling the full circulation bank (POSHOLD bank flapping).
Once established on the hold circle the steady arc law now takes over as a
latch and regulates the radius directly; approach stays on stock guidance,
loiter PID limit back to the planning bank. HITL: round circles at 30 km/h
wind across a 65-145 km/h speed sweep, clean adaptive radius migration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lags from turn tuning settings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- corner-cut anticipation gated to FLY_BY legs (landing approach still
  forces it in every mode); DIRECT no longer advances waypoints early
- turn feed-forward returns 0 in DIRECT outside the landing approach
- PG_NAV_CONFIG collapsed to a single increment vs maintenance-10.x (8 -> 9)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-predictor

Sync with maintenance-10.x after the MAVLink guided-mode work landed
(iNavFlight#11717, iNavFlight#11731 et al.).

Conflict in src/main/navigation/navigation_fixedwing.c,
calculateVirtualPositionTarget_FW(): upstream iNavFlight#11717 changed the loiter
radius source to navigationGetLoiterRadius(), which adds a GCS loiter
radius override (POSHOLD states only); this branch wraps the same spot
with the stable-loiter-radius peak hold. Resolved by keeping the
stable-radius block and feeding it navigationGetLoiterRadius() as the
base radius, so the GCS override is honored underneath the peak hold.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review request: both circle-line intersection solves in updateFwTurnArc()
called raw sqrtf(); navigation/ convention is fast_fsqrtf(), which maps to
the CMSIS VSQRT path on ARM targets. No behavior change - both
discriminants are checked > 0 before the call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HITL logs at 495 Hz showed the roll shudder was not in the arc phases but
at the transitions between them, where a shaped command was replaced by an
unshaped one in a single nav update.

- Arc hand-back: the arc releases with the wings near level while the PID
  and turn FF still see the full carrot error (measured 21 deg while the
  arc's own exit error was 1.7 deg), so their command was adopted as a step
  of -1818 cd. Crossfade from the arc's last command over the turn ease
  time, edge-triggered so every release path is covered, and independent of
  nav_fw_control_smoothness - that defaults to 0, which makes the S-curve
  smoother a pass-through.
- ARC_RAMP_IN -> ARC_STEADY: the ramp ends on phiNom while the steady law
  recomputes from phiLive plus the accumulated eR/eH. Measured steps of
  +453 to +2000 cd. The steady law now blends from the ramp's final command.
- ARC_STEADY had no slew limit at all, unlike the other two phases. On a
  tight arc the tangent bearing is ill-conditioned near the centre and eH
  inverted between two updates, commanding +8140 cd into the bank limit and
  saturating rcCommand(roll). Two-sided rate limit added.
- Turn FF: heading error and velXY arrive at GPS rate (5 Hz in HITL) while
  the controller runs at 50 Hz, so the FF was a staircase stepping 250-600
  cd every 200 ms. Slew limited rather than filtered, so a settled command
  is not lagged. The taper now also starts at the deadband edge instead of
  at zero, removing the step at the gate.
- The away arc of the S sequencer blocks the hand-back and stranded the
  aircraft for 39 s with path tracking suppressed when the second-arc pickup
  never triggered. Bounded by the time to fly half the away circle.
- nav_fw_land/turn hand-off threshold raised from 1.5 to 3 deg: over 13
  logged turns the 1.5 deg gate was never satisfied before release, and the
  residual bank is bounded by the separate bank gate, not by this one.
- Debug: clear the phase and heading-error channels on release, and show the
  pickup along-track distance while the away arc runs. Stale channels
  previously read as arc episodes minutes after the arc had ended.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nav_cruise_lock_on_level makes the roll-out gate optional, so with it off the
course locks immediately while the aircraft is still turning - the case the
'cog - gyroRateDps(YAW)' lead was there for. Restore it for that path; the
gated path locks after the roll-out and the forced timeout lock does not need
it either (2.5 s of level command means level or already in trouble).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The missed-waypoint bearing check compares the bearing to the waypoint
against the inbound leg bearing. Once the aircraft is established on the
outbound leg that angle settles at (180 - turn angle), so beyond a ~80 deg
turn it can never reach the 100 deg limit - it can only fire on the brief
swing right next to the waypoint. In a HITL log of a 114 deg turn that
swing peaked at 99.4 deg and missed by 0.6 deg.

The waypoint then stayed active behind the aircraft, which correctly
steered back to it: a full reversal 81 m past the waypoint before the
check finally tripped. With nav_wp_radius small (3 m there) the radius
path cannot cover this either, and FLY_OVER/FLY_INTO never set
wpTurnSmoothingActive, so the anticipated-turn path does not apply.

Treat the waypoint as passed once the aircraft crosses the plane through
it normal to the inbound leg. Independent of turn angle and of cross-track
offset, and it cannot fire early: at activation the aircraft sits a leg
length short of that plane. Scoped to airplanes in WP mission mode, so RTH,
trackback and the autoland approach are unaffected; FLY_BY returns earlier
via wpTurnSmoothingActive and never reaches it.

Verified against the logs: fires 0.1 m past the plane (6.95 s and 81 m
earlier than before) on the affected turn, and never fires on a clean
FLY_BY run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The into-turn corner cut ends on the outbound leg without crossing the
passage plane through the waypoint - beyond a 90 deg turn it even recedes
from that plane, so no geometric reached-check can fire. The old carrot
then steers back toward the previous leg after hand-back until a recovery
arc undoes the excursion (HITL: two of three laps at the same WP, FF
pinned at full bank for 2.6-3.3 s; the third lap escaped by chance).

Mark the WP reached when the main arc commits onto the outbound leg, via
the same wpTurnSmoothingActive path FLY_BY has always used at turn start.
State-based, so it fires at any lateral offset. Guard the mid-arc
retarget so the mission advance this causes does not degrade the running
arc to a bare capture; gated to FLY_INTO because FLY_OVER shares the
pickup but has already advanced its WP at the overfly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
25 Hz HITL long-runs across all three WP turn modes show the residual
hand-back swings come from command amplitude, not steps - rate limiting
had stretched them without removing them.

- Turn feed-forward: navHeadingError measures to the path-tracking
  carrot, so a lateral offset after a completed turn re-entered the FF
  at up to full bank for a plain leg capture (reproducible per waypoint
  to a few centidegrees), snaking every transition onto the leg. Arm
  the FF on a leg change and disarm it once aligned or when the arc
  flies the turn; leg changes the arc consumes itself do not re-arm.
- Capture law: slew-limit the rise like the decay (it stepped to full
  bank at capped engages and mid-capture mission advances) and seed a
  fresh engage from the live nav command instead of zero.
- RAMP_IN: seed the ramp from the live nav command as well - engaging
  out of a banked loiter dipped the command to zero before rebuilding
  (-3285 cd in one update at 50 m/s, a visible roll twitch).
- Loiter exits: the loiter-time reset wipes the leg reference every
  cycle, so a WP advance landing on the first tracked cycle was
  invisible to the leg-change detection and the exit never engaged an
  arc - the raw FF then flew the whole turn. Unseeded plus grossly off
  the leg course now counts as a leg change.
- Set arcR in the capture-only engage; it sizes the away-arc timeout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restack on the roll-smoothing branch freshly merged with upstream
maintenance-10.x (VTOL auto-transition rework, terrain, 9.1 back-merge).
navigation.c resolutions (both define blocks, conditional PG_NAV_CONFIG
12/9) carried over cleanly from the fw-roll-smoothing merge.

Resolved here: DEBUG_FW_TURN moved behind the new DEBUG_VTOL_* entries in
debugType_e, debugModeNames and the settings.yaml debug_modes table (order
is wire-visible, upstream entries keep their indices); Settings.md
regenerated with update_cli_docs.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	docs/Settings.md
#	src/main/build/debug.h
#	src/main/fc/cli.c
#	src/main/fc/settings.yaml
# Conflicts:
#	src/main/navigation/navigation_fixedwing.c
# Conflicts:
#	docs/Settings.md
#	src/main/build/debug.h
#	src/main/fc/cli.c
#	src/main/fc/settings.yaml
…ttings

nav_fw_turn_ff_gain and nav_fw_wp_turn_control_ease were kept in iNavFlight#11812 as
a safety valve for builds where the defaults might misbehave. Field testing
did not need them, so the defaults become constants: the 100 % FF gain is a
unity factor and is dropped from the roll feed-forward path, the 100 ms
control ease becomes NAV_FW_TURN_CONTROL_EASE_MS. The pure-PID fallback stays
reachable through nav_fw_wp_turn_mode = DIRECT.

navConfig struct layout changes, PG_NAV_CONFIG version bumped (12->13 /
9->10), the group resets to defaults.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant