Skip to content

Fixed wing: predictive coordinated waypoint turns (Needs/Includes #11804) - #11812

Open
b14ckyy wants to merge 33 commits into
iNavFlight:maintenance-10.xfrom
b14ckyy:fw-turn-predictor
Open

Fixed wing: predictive coordinated waypoint turns (Needs/Includes #11804) #11812
b14ckyy wants to merge 33 commits into
iNavFlight:maintenance-10.xfrom
b14ckyy:fw-turn-predictor

Conversation

@b14ckyy

@b14ckyy b14ckyy commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

FW waypoint turns are currently flown reactively: the heading PID chases a moving carrot, so
every corner is a late, speed-dependent overshoot followed by a correction back to track. This
PR plans the turn instead: from the aircraft's actual coordinated-turn radius
R = V²/(g·tan φ) (speed and nav_fw_bank_angle), each corner is constructed geometrically and
flown as an explicit coordinated arc — smoothstep roll-in, steady arc with live feed-forward plus
radial/tangent feedback, and a predictive roll-out that hands back aligned and level. Loiter
circles are held by the same steady law once established.

Detailed technical information:
coordinated-turns-overview.html

Companion PR: iNavFlight/inav-configurator#2711
Note: branch is stacked on fw-roll-smoothing (triggered S-curve roll smoothing + cruise
course lock). Either merge that first and rebase, or include it here — maintainer's call.

What it does

Turn modes (one consolidated setting, nav_fw_wp_turn_mode):

  • DIRECT — legacy heading-PID behaviour (opt-out).
  • COORD_FLYBY (default) — corner cut: the turn is anticipated by R·tan(Δ/2) plus a
    roll-in lead, so the arc joins the next leg tangentially; the WP is passed abeam.
  • COORD_FLYOVER — the WP is overflown, then the aircraft rolls out exactly on the tangent
    line to the next WP. With nav_fw_wp_tracking_accuracy enabled it instead flies a
    bounded-intercept S (intercept ≤ 45° to the new leg, reverse-roll straight, corner-cut arc)
    that rolls out ON the new leg.
  • COORD_FLYINTO — survey/mapping line entry: an opposite-sense ease-away arc plus a main
    arc pinned at the WP cross the waypoint already aligned with the outbound course.

Robustness

  • Sharp corners (>160°) and lead-time-capped turns fly a bounded direct capture; with path
    tracking enabled the capture steers onto the leg line (tapered intercept, capped at
    nav_fw_wp_tracking_max_angle) instead of ending parallel to it a turn-diameter off.
  • Legs too short for the full manoeuvre degrade gracefully: consecutive turns merge into one
    continuous capture; missions continue without error (HITL-verified).
  • Mission advance mid-arc retargets the capture onto the new leg; stale S geometry is released.
  • The autoland approach always flies COORD_FLYBY transitions, regardless of the configured mode.

Loiter

  • Once established on the hold circle (±15 % of R, roughly tangential), the steady arc law
    replaces the carrot PID as a latch and regulates the radius directly (the pursuit carrot
    plus any additive feed-forward always settles inside the commanded circle). Approach and
    entry stay on stock guidance.
  • The commanded radius is floored by the physically flyable radius, stabilised with a
    per-revolution peak hold (ratchet up instantly, decay slowly) — no more "kidney" deformation
    in wind; the circle widens smoothly at speed and migrates back down afterwards.

Bank semantics: target vs. ceiling

  • nav_fw_bank_angle changes meaning: it is now the sustained target bank — the value all
    turn and loiter radii are planned for — not an absolute limit for nav modes. Corrections
    (holding an arc against wind, capturing the exit course) may exceed it temporarily.
  • max_angle_inclination_rll remains the absolute hard ceiling; no nav command ever
    exceeds it. The reserve between target and ceiling is what makes the planned geometry
    robust: the radius is sized for a bank the aircraft can always deliver, with headroom left
    for corrections.

Energy bank guard (introduced to make that headroom safe)

  • Bank costs climb performance: at high bank the vertical lift component shrinks, and a
    commanded climb near the pitch/throttle limit can stall while the reserve is being used.
  • The guard monitors climb-rate deficit against the commanded climb and the bank-induced sink.
    When a banked climb stagnates, the effective bank ceiling eases down toward (and if needed
    below) the planning target — the turn widens, the climb recovers. After a hold-off it
    gradually releases back to the hard ceiling.
  • The effective ceiling feeds every consumer: arc bank commands, the loiter circle controller,
    the PID clamp and the planning bank itself (a reduced ceiling also widens newly planned
    radii). Manual throttle raises are respected as recovery input.
  • The arc coordinator only runs while WP tracking is active; RTH trackback and landing approach
    use the FLY_BY construction.

Settings

Setting Change
nav_fw_wp_turn_mode Changed: DIRECT / COORD_FLYBY (default) / COORD_FLYOVER / COORD_FLYINTO
nav_fw_bank_angle Meaning changed: sustained target bank (planning basis), temporarily exceedable for corrections; max_angle_inclination_rll stays the absolute ceiling
nav_fw_wp_turn_max_lead_time New: cap on how early a COORD_FLYBY turn may start. 3000–12000 ms, default 6000
nav_fw_turn_ff_gain New (CLI): turn feed-forward gain. 0–200 %, default 100 (0 = pure PID). Adjustable while the system proves itself in the field; hardcoding candidate later
nav_fw_wp_turn_control_ease New (CLI): unmodelled roll-response lag added to the ease time. 0–500 ms, default 100. Same status: adjustable for now, hardcoding candidate later

Migration: PG_NAV_CONFIG is version-bumped — nav settings reset to defaults on first boot.

Debugging

debug_mode = FW_TURN exposes the full system (radius, coordinator state machine, exit course,
bank command, FF, guard ceiling, ease time) — documented in
docs/development/fw-turn-debugging.md. docs/Fixed Wing Landing.md updated (approach turning
points now derive from the coordinated turn radius).

Validation (HITL: X-Plane, MATEKF765, MSP debug telemetry)

  • FLY_BY: exits within centimetres–metres of the out-leg across 48–179° corners; residuals
    correlate with the (uncompensated) wind vector estimated from the logs.
  • FLY_OVER tangent exit: hand-back within ±2° of the direct line at every corner incl. a full
    reversal. Tracking-ON S: 14/14 corners at 0.5–6.5 m from the leg wind-off; worst 9.1 m at
    ≈15 km/h wind.
  • FLY_INTO: WP crossings 3–14 m across 47–92° corners.
  • Reversal fallback with path tracking: on the line within metres (previously 116 m parallel
    offset and a late 46° tracker correction).
  • Loiter: round circles at 30 km/h wind across a 65–145 km/h speed sweep with clean adaptive
    radius migration (previous additive-FF experiment settled 28 % inside the commanded radius —
    replaced by the circle controller).
  • Mini-legs, autoland sequence (forced FLY_BY, headwind final): clean.
  • Geometry changes were pre-validated in a desktop kinematic simulator (controller logic + wind
    • bank-lag plant) before HITL.

Known limitations / follow-ups

  • Ground-frame geometry: radius, anticipation and the steady law use ground speed; wind
    shifts the effective radius around the turn (±3–9 m residuals at ~15 km/h wind).
    Wind-vector compensation is planned as a follow-up PR (10.1).
  • Loiter entry/exit shaping (arc-follow approach) — later PR, builds on this module.
  • nav_fw_turn_ff_gain and nav_fw_wp_turn_control_ease stay CLI-adjustable until the system
    is field-proven across airframes; both are candidates for hardcoding in a follow-up.

b14ckyy and others added 4 commits August 20, 2026 20:19
…y-state lag)

nav_fw_control_smoothness applied a PT1 low-pass to the FW nav roll command.
That trades smoothness for a permanent, uncompensated lag between what the
navigation controller commands and what is executed: every course correction
is delayed, also during steady tracking where no smoothing is needed, and the
lag grows with the smoothness setting.

Replace the roll-axis PT1 with a triggered S-curve easing:

- Fires only on an abrupt commanded-bank step (setpoint-rate change above 20%
  of the configured roll rate between nav loops), e.g. a new course at a
  waypoint or a nav-mode entry (RTH engage, WP start).
- Eases from the pre-step output to the live target with a smoothstep over a
  control_smoothness-derived window (n x 100 ms, 0 = off, capped at 1000 ms),
  then passes the command 1:1 again.
- The window timer does not reset on further steps mid-ramp, so the smoother
  can never get stuck damping steady tracking.
- On position-controller reset the smoother re-seeds from the last applied
  nav roll command when nav was commanding until just now (nav-mode to
  nav-mode transition, e.g. RTH -> CRUISE: the level-off is eased), and from
  the neutral baseline after a pilot-flown phase (stick release: a roll-out
  in progress is not re-commanded). Stale state can never fire a spurious
  ramp.

Same knob, same range and same intent (soft control feel, structural
protection on large airframes); the pitch/pitch-to-throttle PT1 smoothing is
deliberately unchanged. No settings or PG layout changes.

HITL-tested on real hardware (window rescaled to n x 100 ms from flight
observation; re-seed behavior derived from RTH engage, cruise stick release
and RTH->CRUISE fallback tests).
In COURSE_HOLD/CRUISE the course is locked the moment the mode engages or the
pilot releases the stick (roll-stick path: last course stored in ADJUSTING;
yaw path: on release with a one-iteration gyro lead; mode entry: in
INITIALIZE). If the aircraft is still banked at that moment - stick released
mid-turn, or the mode switched out of e.g. an RTH turn - it keeps turning
through the level-off, overshoots the locked course and flies a reverse
correction turn. A longstanding annoyance, made more visible by softer
roll-out (control smoothing).

Delay the course lock until the roll-out is actually complete: while the bank
is above 10 deg the course keeps following the actual COG (roll-stick path
stays in ADJUSTING; yaw release and banked mode entry share one lock-pending
flag), then locks with the gyro-lead compensation. The course now locks where
the aircraft has effectively stopped turning - no overshoot, no reverse
correction - and the controller reset/re-engage happens near wings-level, so
the smoothing re-seed cannot cause a roll jerk.

Fixed-wing only; multicopter course hold is unaffected.
…asing cap to 900ms

The course-lock applied 'cog - DEGREES_TO_CENTIDEGREES(gyroRateDps(YAW))',
mixing a rate (deg/s) into an angle - effectively a fixed one-second yaw
lead. With the new bank gate the turn has essentially stopped at lock time,
so lock directly to the current COG.

NAV_FW_SMOOTH_TCONST_MAX_MS claimed a 1000ms cap that was unreachable with
control_smoothness max 9 (n x 100ms = 900ms); set the cap and comments to
900ms to match the setting range and documentation.
…fault ON)

On maintainer feedback the level-off course lock in course hold is a
behavior change, so make it optional: ON locks the course only once
rolled out below 10 deg bank (new behavior), OFF locks on stick
center / mode entry as before. Bumps PG_NAV_CONFIG to 9.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fixed-wing: predictive coordinated waypoint turns + loiter arc control

✨ Enhancement ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add coordinated-turn planning and an arc-based turn coordinator for FW waypoint missions.
• Replace continuous roll PT1 smoothing with triggered S-curve easing to avoid steady-state lag.
• Introduce new FW nav settings/debugging (turn modes, FF gain, lead-time cap, cruise lock gate).
Diagram

graph TD
  A["FW navigation loop"] --> B["FLY_BY lead planner"] --> C["Arc turn coordinator"] --> F["Roll command path"] --> G["RC roll output"]
  A --> D["Loiter radius stabilizer"] --> E["Loiter circle controller"] --> F
  A --> H["Energy bank guard"] --> F
  I["Settings/CLI/OSD"] --> A
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Adopt an L1 / vector-field path follower (course+cross-track law)
  • ➕ Single continuous controller for lines/circles; less explicit state machine
  • ➕ Well-known behavior across many autopilots; simpler tuning surface
  • ➖ Harder to guarantee exact tangent roll-out and waypoint-specific semantics (fly-by/over/into)
  • ➖ Still needs special handling for very sharp corners and short legs
  • ➖ Would require retuning against existing INAV FW dynamics and options
2. Keep carrot guidance but add only turn anticipation + feed-forward
  • ➕ Smaller change set; lower risk than full arc coordinator
  • ➕ Retains current PID-centric behavior and debugging mental model
  • ➖ Still fundamentally reactive; overshoot/corner behavior remains speed-dependent
  • ➖ Does not enable flyover/flyinto semantics or precise arc/radius holding
3. Spline/fillet mission preprocessing (path smoothing)
  • ➕ Turns become inherently smooth; fewer runtime decisions
  • ➕ Could merge consecutive short legs naturally
  • ➖ Needs mission lookahead and reprocessing on mission edits/advances
  • ➖ More complex failure modes when mission advances mid-turn; harder to debug in-flight

Recommendation: The PR’s explicit geometric arc coordinator is justified given the stated goals (predictive, radius-true coordinated turns with distinct fly-by/over/into semantics and robust short-leg behavior). If maintainers want a smaller first step, “anticipation + FF only” could be a staging approach, but it won’t deliver the same corner accuracy or mode semantics. If this is merged, prioritize follow-up wind compensation (ground-frame radius residuals are acknowledged) and consider eventually hardcoding/auto-tuning the temporary CLI gains once field data stabilizes.

Files changed (11) +1041 / -96 · 1 not counted

Enhancement (5) +894 / -75
debug.hAdd DEBUG_FW_TURN enum +1/-0

Add DEBUG_FW_TURN enum

• Registers a new debug type (DEBUG_FW_TURN) to expose coordinated-turn internal state via debug channels.

src/main/build/debug.h

cli.cAdd FW_TURN to debug_mode CLI names +2/-1

Add FW_TURN to debug_mode CLI names

• Extends the debugModeNames table to include FW_TURN, enabling selection via 'set debug_mode = FW_TURN'.

src/main/fc/cli.c

navigation.cAdd FW course-hold lock gating + bump NAV config PG version +42/-19

Add FW course-hold lock gating + bump NAV config PG version

• Adds optional FW COURSE HOLD behavior to delay course locking until the aircraft rolls out below ~10° bank (nav_cruise_lock_on_level). Bumps PG_NAV_CONFIG version (reset-on-first-boot migration) and ensures landing approach turn angles are always computed for coordinated FLY_BY transitions.

src/main/navigation/navigation.c

navigation.hDefine new FW WP turn mode enum and settings fields +10/-5

Define new FW WP turn mode enum and settings fields

• Replaces the wp_turn_smoothing enum with navFwWpTurnMode_e and extends navConfig_t with coordinated-turn parameters (mode, FF gain, lead-time cap, ease lag) plus cruise_lock_on_level.

src/main/navigation/navigation.h

navigation_fixedwing.cImplement predictive coordinated WP turns, loiter arc control, and energy bank guard +839/-50

Implement predictive coordinated WP turns, loiter arc control, and energy bank guard

• Replaces roll PT1 smoothing with a triggered smoothstep S-curve easing window to avoid steady tracking lag. Adds coordinated-turn radius calculation, FLY_BY anticipation using R·tan(Δ/2) with lead-time cap, an arc coordinator (ramp-in/steady/capture, plus S sequencing for flyover-tracking/flyinto), and a loiter circle controller that directly regulates radius once established. Introduces turn feed-forward, cross-track suppression and PID integrator freeze during arc flight, and an energy-aware effective bank ceiling that can reduce bank during banked climbs to preserve climb performance.

src/main/navigation/navigation_fixedwing.c

Bug fix (1)
pid.cFreeze level-trim PID integrator when trim conditions aren’t met not counted

Freeze level-trim PID integrator when trim conditions aren’t met

• Adds PID_FREEZE_INTEGRATOR gating to the fixed-wing level-trim controller when trim is inactive or updates are too sparse, preventing integrator drift outside valid trim conditions.

src/main/flight/pid.c

Documentation (3) +112 / -9
Fixed Wing Landing.mdDocument coordinated FLY_BY turns in autoland approach +4/-2

Document coordinated FLY_BY turns in autoland approach

• Clarifies that landing approach leg transitions are always flown as coordinated FLY_BY corner cuts based on the real coordinated-turn radius. Updates guidance for nav_wp_radius: it no longer shapes approach corners, but should remain low to avoid early waypoint-reached detection.

docs/Fixed Wing Landing.md

Settings.mdAdd/rename FW nav settings and new FW_TURN debug mode docs +49/-7

Add/rename FW nav settings and new FW_TURN debug mode docs

• Documents the new debug_mode FW_TURN, new nav_cruise_lock_on_level behavior, revised nav_fw_bank_angle semantics, and updated nav_fw_control_smoothness roll S-curve description. Replaces the old FW WP turn smoothing setting with the new nav_fw_wp_turn_mode plus additional coordinated-turn tuning parameters (FF gain, ease lag, lead-time cap).

docs/Settings.md

fw-turn-debugging.mdAdd FW coordinated-turn debug channel reference +59/-0

Add FW coordinated-turn debug channel reference

• Introduces a developer-facing guide for DEBUG_FW_TURN, including channel meanings, coordinator state encoding, and practical interpretation of logs/telemetry during turns and loiter.

docs/development/fw-turn-debugging.md

Other (2) +35 / -12
cms_menu_navigation.cExpose WP turn mode in CMS/OSD menu +1/-1

Expose WP turn mode in CMS/OSD menu

• Replaces the legacy “WP TURN SMOOTHING” menu entry with “WP TURN MODE” so users can select DIRECT vs coordinated turn modes from the navigation CMS menu.

src/main/cms/cms_menu_navigation.c

settings.yamlReplace wp_turn_smoothing with wp_turn_mode and add new FW turn settings +34/-11

Replace wp_turn_smoothing with wp_turn_mode and add new FW turn settings

• Defines the new nav_fw_wp_turn_mode enum (DIRECT/COORD_FLYBY/COORD_FLYOVER/COORD_FLYINTO) and adds nav_fw_turn_ff_gain, nav_fw_wp_turn_max_lead_time, nav_fw_wp_turn_control_ease, and nav_cruise_lock_on_level. Updates setting descriptions to reflect new bank-angle semantics and roll S-curve smoothing.

src/main/fc/settings.yaml

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 22, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. DIRECT mode cuts corners ✓ Resolved 🐞 Bug ≡ Correctness
Description
In DIRECT mode, the code can still arm the FLY_BY anticipation path and isWaypointReached() will
advance the mission as soon as the anticipated turn-start is reached, causing corner-cut/early
waypoint advance even though DIRECT is supposed to be legacy behavior.
Code

src/main/navigation/navigation_fixedwing.c[R1127-1130]

+    const bool flyIntoMissionLeg = navConfig()->fw.wp_turn_mode == NAV_FW_WP_TURN_COORD_FLY_INTO && (navGetCurrentStateFlags() & NAV_AUTO_WP)
+                                   && posControl.navState != NAV_STATE_FW_LANDING_APPROACH;   // landing approach keeps FLY_BY corner cuts
+    if (waypointTurnAngle > 3000 && waypointTurnAngle < 16000 && !flyIntoMissionLeg && isWaypointNavTrackingActive() && !needToCalculateCircularLoiter) {
+        const float turnRadius = getFwCoordinatedTurnRadius();
Evidence
The mission precomputes nextTurnAngle even when the configured mode is DIRECT (only excluded for
FLY_OVER). That nextTurnAngle is then used to arm wpTurnSmoothingActive without checking for
COORD_FLYBY, and isWaypointReached() treats wpTurnSmoothingActive as an immediate
waypoint-reached condition—advancing early and cutting the corner in DIRECT.

src/main/navigation/navigation.c[4300-4313]
src/main/navigation/navigation_fixedwing.c[1123-1144]
src/main/navigation/navigation.c[3103-3115]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
DIRECT (legacy) waypoint turning is not respected: FLY_BY turn anticipation can still arm in DIRECT, and the mission can advance early (waypoint considered reached at turn start).
### Issue Context
- `calculateAndSetActiveWaypoint()` currently populates `activeWaypoint.nextTurnAngle` for all modes except COORD_FLYOVER.
- `calculateVirtualPositionTarget_FW()` uses `nextTurnAngle` to arm `wpTurnSmoothingActive` without checking that the configured turn mode is COORD_FLYBY (or landing-approach forced FLY_BY).
- `isWaypointReached()` returns `true` immediately when `wpTurnSmoothingActive` is set, which advances the mission early.
### Fix Focus Areas
- src/main/navigation/navigation.c[4300-4313]
- src/main/navigation/navigation_fixedwing.c[1123-1144]
- src/main/navigation/navigation.c[3103-3115]
### Suggested change
- Only compute `activeWaypoint.nextTurnAngle` for modes that actually need anticipation (COORD_FLYBY, COORD_FLYINTO, and landing approach override).
- Additionally (defense-in-depth), gate the FLY_BY corner-cut arming (`wpTurnSmoothingActive`) so it only runs when `wp_turn_mode == NAV_FW_WP_TURN_COORD_FLY_BY` (or landing approach state forces FLY_BY).
- Ensure DIRECT mode never sets `wpTurnSmoothingActive` and therefore never triggers early `isWaypointReached()`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. DIRECT mode gets turn feedforward ✓ Resolved 🐞 Bug ≡ Correctness
Description
Even when nav_fw_wp_turn_mode is DIRECT, the roll controller still adds coordinated-turn
feed-forward (getFwTurnFeedForward()), so DIRECT cannot reproduce legacy heading-PID behavior.
Code

src/main/navigation/navigation_fixedwing.c[R1341-1344]

+    } else {
+        // Coordinated-turn feed-forward: command the bank for the active turn radius so the PID only trims.
+        rollAdjustment += getFwTurnFeedForward(navHeadingError);
+        rollAdjustment = applyFwRollInSmoothing(rollAdjustment, deltaMicros, fwRollSmoothReseed);
Evidence
The roll feed-forward is added in the main FW nav roll path regardless of the selected turn mode,
and the feed-forward function itself only checks tracking state and heading error—not the configured
mode—so DIRECT mode behavior is modified.

src/main/navigation/navigation_fixedwing.c[1291-1347]
src/main/navigation/navigation_fixedwing.c[551-565]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
DIRECT mode is documented as legacy heading-PID behavior, but the new coordinated-turn feed-forward is applied unconditionally whenever WP tracking is active.
### Issue Context
- `updatePositionHeadingController_FW()` always executes `rollAdjustment += getFwTurnFeedForward(navHeadingError);` when not in an arc.
- `getFwTurnFeedForward()` does not check `navConfig()->fw.wp_turn_mode`; it only checks WP tracking/heading error/speed.
### Fix Focus Areas
- src/main/navigation/navigation_fixedwing.c[1341-1347]
- src/main/navigation/navigation_fixedwing.c[551-565]
### Suggested change
- Gate feed-forward by turn mode, e.g. return 0 (or skip calling) when `navConfig()->fw.wp_turn_mode == NAV_FW_WP_TURN_DIRECT`.
- If you still want FF in DIRECT for some reason, update docs/setting semantics accordingly; otherwise DIRECT should be a true opt-out.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Nav config resets on upgrade 🐞 Bug ☼ Reliability
Description
Bumping PG_NAV_CONFIG from version 8 to 14 causes *all* navigation settings to be reset to
defaults on upgrade because PG loading only restores stored values when the version matches exactly.
Code

src/main/navigation/navigation.c[124]

+PG_REGISTER_WITH_RESET_TEMPLATE(navConfig_t, navConfig, PG_NAV_CONFIG, 14);
Evidence
navConfig is registered with version 14, and the PG loader only restores persisted values when
versions match; otherwise it keeps defaults after reset, which effectively resets the entire
navConfig on upgrade from older firmware.

src/main/navigation/navigation.c[124-127]
src/main/config/parameter_group.c[86-94]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`navConfig` PG version change forces a full reset of the navigation configuration on firmware upgrade, wiping unrelated nav settings (not just the new FW turn fields).
### Issue Context
`pgLoad()` resets to defaults and only copies stored data when the stored version equals the registered version.
### Fix Focus Areas
- src/main/navigation/navigation.c[124-127]
- src/main/config/parameter_group.c[86-94]
- src/main/navigation/navigation.h[471-515]
### Suggested change options
1) **Avoid version bump** by keeping binary layout compatible (e.g., retain the old `wp_turn_smoothing` field as a deprecated/unused placeholder and append new fields at the end), so existing stored nav settings still load.
2) If a reset is truly required, consider splitting new settings into a separate PG (so only the new PG resets) or add explicit migration support (if available in this codebase).
(With the current PG implementation shown, any version mismatch discards stored navConfig values.)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can ask Qodo to dismiss a finding you disagree with, with your reason on record

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/main/navigation/navigation_fixedwing.c Outdated
Comment thread src/main/navigation/navigation_fixedwing.c
Comment thread src/main/navigation/navigation.c Outdated
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown

RAM / Flash usage vs. base branch — commit e452bf0

Target Flash Δ RAM Δ
MATEKF405 ⚠️ +11652 B (+1.75%) +256 B (+0.18%)
MATEKF722 ⚠️ +8772 B (+1.89%) +280 B (+0.22%)
MATEKF765 ⚠️ +12524 B (+1.81%) +256 B (+0.17%)
MATEKH743 ⚠️ +12044 B (+1.66%) +268 B (+0.17%)

See RAM/flash optimization guide for techniques to reduce usage.

@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown

Test firmware build ready — commit e452bf0

Download firmware for PR #11812

246 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

A badly tuned model in strong wind can stay above the 10 deg bank gate
indefinitely, leaving the course lock pending. Once the roll/yaw sticks
are centred the lock now engages after 2500 ms regardless of bank.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@b14ckyy
b14ckyy force-pushed the fw-turn-predictor branch from 473588f to 41b058d Compare August 24, 2026 08:39
b14ckyy and others added 10 commits August 24, 2026 11:24
A yaw-stick turn holds the course setpoint ahead of the actual COG, winding
up the nav PID integrator to the bank that sustains the turn. Once the stick
centres the course follows COG, so the error is zero and neither the error
term nor back-calculation (only active while saturated) can unwind it: the
residual bank kept the turn going and blocked the roll-out the course lock
waits for. Reset the position controller once when the adjustment ends, as
the pre-existing code did before the lock-on-level gate was introduced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
@b14ckyy b14ckyy changed the title Draft — Fixed wing: predictive coordinated waypoint turns (Needs/Includes #11804) Fixed wing: predictive coordinated waypoint turns (Needs/Includes #11804) Aug 25, 2026
@breadoven

breadoven commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

I did some HITL testing on this and it does seem to be a noticeable improvement on the current method. However, I did notice that sometimes it behaves unpredictably flying off in a direction that doesn't make sense. It does always recover back to the next WP but something doesn't seem quite right. Needs more testing to better understand when this happens. Other times it seems slow to correct back to the WP course line when WP tracking is ON. It'll fly parallel to the course line for some time at some distance away then suddenly decide to head toward the course line as you'd expect. Almost as if something isn't triggering the way it should.

The other more significant issue is shaking/bouncing in roll usually when manoeuvring near a WP. Looking at logs there are high amplitude rcCommand(roll) spikes which probably account for the shuddering you see visually. #11804 has removed the rollAdjustment filtering which may be fine if the S-curve smoothing is used but there doesn't appear to be any smoothing when fwArcActive is active given it bypasses the S-curve smoothing. Is this the case ?

@b14ckyy

b14ckyy commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Do you have a log of that behavior and maybe the WP mission you tested and Weather parameters?

A few notes to the things you noticed but its guesswork without more details.

The parallel straight before correcting with path tracking:
After a FLYBY turn we expect to be on the track directly. In very strong winds that can be off and that will be a future follow up to fix, by implementing wind compensation to the turn calculation (I left that out since this PR is already quite big and I want to have it flight proven first). Usually that is only noticeable if the plane turns into a strong headwind and unexpectedly accelerates excessively. The expectation to hit the path within a few meters is the reason why the handover to the path tracking is relatively late. I can change that but it will just cause earlier sudden corrections.

The Fly-Off:
that's something where I would need to see the log. I have never seen that in my testing and what turn mode did you use?

Shaking/bouncing:
Here I definitely need a blackbox with debugging on when this happens. The turn state and triggers are all recorded. I need to see if the transition from Ease-In to Arc and from Arc to ease out is gap-less so the Path tracking does not kick in for a few nav loops. Thats the most likely explanation but I would like to confirm that before I change that chain.

PS:
Its really really important that you tune your plane in X-Plane with autotune and fly at reasonable speeds. The default tune that comes with the plugin is crap for modern INAV and causes all kind of mess including wobbling flight and unstable turns. And reduce your cruise throttle down to 1170 for a more realistic speed with a P2T of 18.

@sensei-hacker

sensei-hacker commented Aug 25, 2026

Copy link
Copy Markdown
Member

Both circle-line intersection solves in updateFwTurnArc() (the disc/disc2 quadratic in the FLY_INTO S-sequencer and the mid-arc retarget) call sqrtf() directly. Elsewhere in navigation/ (e.g. navigation_geozone.c, sqrt_controller.c) the convention is fast_fsqrtf() from common/maths.c, which uses CMSIS-DSP's arm_sqrt_f32 on ARM targets instead of newlib's sqrtf(). Since nothing else in the firmware currently calls raw sqrtf(), swapping those two call sites to fast_fsqrtf() would drop a new ~68-byte libc wrapper the linker currently has to pull in just for this PR, at no behavior/precision cost. Would you be open to that change?

@b14ckyy

b14ckyy commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Sounds reasonable. 68 bytes are 68 bytes. Will do and also fix the conflict and look at breadoven's twitching issue.

b14ckyy and others added 2 commits August 26, 2026 10:00
…-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>
@breadoven

breadoven commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

I did some more HITL testing and worked out the roll shaking is caused by fwCrossTrackErrorRateFilterState being defined twice with the second in updatePositionHeadingController_FW where it effectively wasn't filtering crossTrackErrorRate because no T_cut had been set (a hangover from #11571 and #11640).

However, even after fixing the above there was still a lot of spiking in rcCommand(roll) albeit at a reduced amplitude so it's not that visually obvious. On checking further though this turns out to be caused by the rollAdjustment D term. Admittedly nav_fw_pos_xy_d was set to 30 which is a bit high but I can't help but think that maybe this needs looking at separately in light of the issues with D term causing pitch oscillation when D term is based on Error rather than Measurement. The D term chatter doesn't seem to make it through to the servo outputs to any significant extent though so it's probably not an issue from that point of view. As I say though, something to look at as a separate issue.

The only odd looking flight path I saw this time can be seen in the following screenshot after waypoint 3. This is with wind (23kts N maybe), WP tracking accuracy OFF and I think the WP turn mode was set to COORD_FLYOVER . Also the throttle is set to 20% so it flies around 70km/hr which is reasonable. It flies parallel to the course line for longer than it should before finally deciding to head directly to the WP4. This just looks like the logic is getting stuck sometimes because other times it behaves as you expect ... heads directly to the next waypoint after completing the last WP turn. Incidentally WP2 is a 10s Poshold WP.

NK_FPVSW - 2026-08-25 22 53 41

@b14ckyy

b14ckyy commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks a lot that helps. I also did some new logs with different settings and was able to to replicate some rough angle changes that should not happen. Claude analyzed the code based on your previous description and also found some candidates but I will compare with high rate logs right now.

the odd looking path at WP3 is exactly what I meant. There is no wind taken into account yet. the turns are planned by ground speed. Plus: the strong wind from north must have turned your heading so far north, that it tripped the fallback function where it will basically do a fly-over if the turn is over 160° or the time to the turn start is above default 10s from the actual waypoint. That is normal at almost 180° turns as cutting the corner into the new path is literally impossible.

Will keep you updated with a new build after testing here. thanks a lot for your help with this.

@b14ckyy

b14ckyy commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks — both of your findings check out, and the HITL logs let me pin down the mechanism. Logging at 500 Hz onboard (SD card), so no aliasing.

Root cause of the roll shaking: the arc seams, not the arc phases. Claude response:

The arc phases themselves are shaped — ARC_RAMP_IN is a smoothstep and ARC_CAPTURE is rate-limited (a textbook-linear +137 cd/update in the log). What was unsmoothed were the transitions:

Arc → PID handback. In one nav update: rollFF 0 → -1477 cd, total roll command 0 → -1818 cd, rcCommand[ROLL] step of -303. The arc collapses bank to ~0 and releases, but the PID and turn FF still see the full carrot error, so their command was adopted as a step. Root cause is two different heading references: the arc's exit test uses the error to its planned exit course (-168 cd = 1.7° at that instant) while FF/PID use navHeadingError to the carrot (~21° at the same instant).
ARC_RAMP_IN → ARC_STEADY. The ramp ends at arcDir * phiNom, then the steady law recomputes from phiLive + eR/eH and these don't match. Measured steps: +453, +274, +202 cd with tracking on; +2000 cd (20°, straight into the bank limit) and +1369 cd with tracking off. Ramp-in doesn't steer onto the circle, so eR accumulates during the ramp and steady demanded all of it in one update.
The FF deadband was a hard gate — rollFF toggling 0 ↔ -1477 cd because the taper was measured from zero rather than from the deadband edge.
On your D-term point — confirmed, and it's larger than it looks. The blackbox logs fwPosP/I/D as the fw_nav PID terms, so I could measure it directly. At the handback instant: P = -740 cd, I = +20 cd, D = -1760 cd, output -2470 cd — D is 71% of the PID output, and it decays exponentially over ~100 ms after every error step. There's an aggravating factor: while the arc drives, the PID is decoupled but keeps running (mean |P| 3206 cd during arc vs 58 cd otherwise), so its output at the moment of handback carries a history it never controlled. Agreed this deserves a separate look; the handback crossfade covers the seam but not the underlying D-on-error noise.

On the cross-track filter — the fragility is real, though the current tree only has one declaration. dcc404ecc (maintenance-10.x only) replaced pt1FilterApply4(…, 3.0f, dT) with pt1FilterApply3 plus a one-time pt1FilterSetCutoff() in resetFixedWingPositionController(). If that reset hasn't run, RC stays 0, so alpha = dT/(0+dT) = 1 and the filter passes through unfiltered — exactly what you saw. master, release/9.1 and maintenance-9.x still use Apply4 and are unaffected. A duplicate declaration would produce the same result for the same reason. I've made the cutoff explicit at the point of use so it can't depend on an unrelated reset.

Changes in the next push:

Crossfade from the arc's last command into the PID+FF command over the turn's ease time, edge-triggered on arc release so it covers every exit path. Deliberately independent of nav_fw_control_smoothness — that stays the user-facing knob, but the arc seam must not depend on it being non-zero (it defaults to 0).
ARC_STEADY blends from the ramp's final command into its own law instead of applying it in full on the first update.
Turn FF tapers from the deadband edge instead of from zero.
Cross-track rate filter cutoff set at the point of use.
Debug channels cleared on both early-return paths — previously ch1/ch3 kept showing the last arc phase for minutes after release, which produced two phantom "arc episodes" in my own logs before I spotted it. Worth knowing if you're reading FW_TURN logs from before this change.
Not changed on purpose: the NAV_FW_ARC_EXIT_HANDOFF_CD threshold. The logs do answer which condition holds the arc open — hdgErrOut stalled at 152 cd against the 150 cd threshold while the bank condition had long been satisfied (176 cd vs 10% of phiNom) — but the crossfade removes the consequence of a late handback, so changing both at once would make the next flight unattributable. If the parallel-flight behaviour survives this round, that threshold is the next lever.

Your parallel-track screenshot after WP3 fits this: with WP tracking off, the arcToLegLine correction is gated behind wp_tracking_accuracy, so the arc rolls out on the leg bearing while displaced from the leg line, and the capture tail converges asymptotically from there. Not stuck logic — a slow asymptote plus a bearing-only rollout.

Additionally:
The long straight parallels after wind-offset turns are caused by the too tight course alignment handover (to make a smooth transition) and <1.5° was just too strict. After analyzing my own logs the maximum alignment error after the final ease out was 2.1-2.6° every time so that trigger never handed over until some gust accidently kicked the plane towards the waypoint or the plane got closer to the target WP. the threshold will be raised to 3° that still gives a smooth heading controller handover and in all my tests so far that happens latest at <1 second.

Probably also a tuning thing and in reality the planes are not as floaty as in X-Plane so that should be save.

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>
@b14ckyy

b14ckyy commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Alright, last commit should now fix the transition from loiter-WP into the next track with Path tracking enabled and not swerve multiple times anymore. Also wind induced turn offsets should now hand over to path tracking MUCH earlier right after the ease out. Wind gust induced offsets from ease in -> arc and arc > ease out should now always be corrected softly.

I will do more HITL testing before the real flights this weekend. Hope the predictions stay.

@sensei-hacker technically its safe to use. If you want to merge I can tweak any remaining issues during RC phase as well. #11804 should be merged beforehand though if you want to have both features separated but its also included here if you want to have it in one. Just tell me what you prefer.

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>
@b14ckyy

b14ckyy commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

cherry picked a missing commit from #11804 to avoid merge conflicts/misses

@breadoven

Copy link
Copy Markdown
Collaborator

I did another HITL test, mainly related to checking D term using measurement rather than error, and found a good example of an "errant" flight path at WP 5, shown in attached screenshot. For some reason it decided to do a turn reversal. This was no wind with tracking accuracy ON. I did also notice the flight path was snaking a lot more than before, e.g. between WP 1 and 2. Don't know if this is due to the D term change or the issue mentioned in the comment above. Pretty sure I noticed this turn reversal at a WP turn when I tested before without the D term changes though.

The D term change does eliminate the PID output spikes and in fact using measurement makes sense given error can change abruptly when virtualTargetBearing changes abruptly, something that can't happen using the COG measurement.

NK_FPVSW - 2026-08-26 11 28 34

@b14ckyy

b14ckyy commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

@breadoven thats strange and I have never seen that before. Can you send me that mission and blackbox please? No idea how to replicate that.

PS: can you please compare my tune with yours?
these wavy paths after a turn look like the bad stock tune of the Simulator profile. Here is mine:

# control_profile
control_profile 1

set fw_p_pitch = 10
set fw_i_pitch = 2
set fw_d_pitch = 10
set fw_ff_pitch = 179
set fw_p_roll = 15
set fw_i_roll = 2
set fw_d_roll = 10
set fw_ff_roll = 116
set fw_p_yaw = 20
set fw_i_yaw = 0
set fw_ff_yaw = 243
set max_angle_inclination_rll = 650
set dterm_lpf_hz = 10
set fw_turn_assist_pitch_gain =  0.300
set nav_fw_pos_z_p = 25
set nav_fw_pos_z_i = 6
set nav_fw_pos_z_d = 5
set nav_fw_alt_control_response = 45
set d_boost_min =  1.000
set d_boost_max =  1.000
set tpa_breakpoint = 1000
set rc_expo = 30
set rc_yaw_expo = 30
set roll_rate = 12
set pitch_rate = 8
set yaw_rate = 4

EDIT2: whats your WP radius btw? I have a suspicion. that should be set to 10m for fixed wings until a future change I have in mind is in.

@breadoven

breadoven commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

I've attached the log although it only includes debug_always debugs not the ones from this PR. However, the debugs are useful and defined as follows (all in updatePositionHeadingController_FW):

0 = virtualTargetBearing captured immediately before int32_t navHeadingError = wrap_18000(virtualTargetBearing - posControl.actualState.cog);
1 = crossTrackErrorRate captured within theif (navConfig()->fw.wp_tracking_accuracy && !needToCalculateCircularLoiter && !fwArcActive) {routine so indicates if fwArcActive is active.
3 = rollAdjustment immediately after the PID loop
4 = rollAdjustment immediately after rollAdjustment = constrainf(rollAdjustment, -DEGREES_TO_CENTIDEGREES(navBankLimit), DEGREES_TO_CENTIDEGREES(navBankLimit));, so is the final rollAdjustment output.

The first log is the one with the turn reversal and no wind. The second is with wind of around 30 kts although the reversal didn't happened in that case (it flew WP 5 perfectly in fact).

The mission is also attached. It's an old autospeed test mission but in this case the speeds are all set 0 to avoid complicating things.

I think the snaking might be caused by control smoothing being set as it was before (value of 7). After the recent changes I made it might need adjusting. If you look at the log between WP 1 and 2 debug 4 shows rollAdjustment is being increased compared to the output of the PID loop (debug 3) an increase that must be coming from the S-curve smoother (given that debug 1 is changing showing fwArcActive is not active). This overcorrection is probably causing the snaking although it's never obvious if changing the smoothing will make it worse or better given the way it feeds back on itself. Needs testing.

nav_wp_radius = 300.

inav_001.zip
testautospeedwp2.zip

EDIT: sorry I got confused over which Log had the turn reversal, seems it was second log with wind. But I guess you already worked that out !

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>
@b14ckyy

b14ckyy commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Found it — it's not the turn coordinator.

WP5 was flown correctly: passed 5.1 m from it, on the leg line, heading 303° vs a leg bearing of 295°. But it was never registered as reached, so it stayed the active waypoint and the controller correctly steered back to a waypoint behind the aircraft. That reversal is the loop in the track. In the log virtualTargetBearing matches the bearing back to WP5 to a tenth of a degree for 4.5 s while roll sits pinned at the bank limit. WP6 only activated 81 m past WP5.

All three detection paths missed:

  • nav_wp_radius was 3 m, the pass was 5.1 m
  • wpTurnSmoothingActive is only set for COORD_FLY_BY, so the anticipated-turn path doesn't apply to FLY_OVER/FLY_INTO
  • the missed-waypoint check compares the bearing to the WP against the inbound leg bearing, not the aircraft's course. Once on the outbound leg that angle settles at 180° − turn angle — 66° for this 114° turn, permanently under the 100° limit. It can only fire on the brief swing beside the waypoint, where it peaked at 99.4° and missed by 0.6°.

So beyond ~80° of turn that check structurally cannot fire in steady flight. Upstream masked this because turn smoothing dropped the limit to 60°; that reduction was lost when nav_fw_wp_turn_smoothing became nav_fw_wp_turn_mode.

Fix: treat the waypoint as passed once the aircraft crosses the plane through it normal to the inbound leg. Independent of turn angle and cross-track offset, and it can't fire early — at activation the aircraft is a leg length short of that plane. Scoped to airplanes in WP mission mode, so RTH, trackback and autoland are untouched; FLY_BY returns earlier via wpTurnSmoothingActive and never reaches it. Against your logs: fires 0.1 m past the plane on the affected turn (6.95 s earlier), never fires on the clean run.

Worth raising nav_wp_radius from 3 m regardless — 10 m is more usual for fixed wing.

This Commit is UNTESTED at this point! I will run more tests with similar edge cases later this evening. But your extreme wind condition test was definitely helpful.

@b14ckyy

b14ckyy commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

I am testing already with long flights and different WP modes. Also with insane high 30kn wind speed. So far no missed WP at all. its only the transition from the alignment turn into path tracking thats still not clean. Working on it.

b14ckyy and others added 2 commits August 26, 2026 20:54
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>
@b14ckyy

b14ckyy commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Alright I think I now have all that was left.
The previous WP trigger fix was false and did not work. Now the detection is reliable and no missed waypoints anymore in FLYOVER and FLYINTO.

In addition a second commit with multiple small fixes and tweaks to get rid of all the situations that could cause sudden heading errors that show as roll twitches. Tested under extreme wind conditions like @breadoven

  • Transition from arc turns to path tracking is now without FF and a smooth carrot follow as before
  • all phase and nav transitions should be soft without setpoint jumps
  • loiter exits are now smooth into the next WP leg depending on the exit angle (arc transfer or direct, depending on loiter exit angle)

Now WP Turn Smoothing should do a proper job with the rest (direct fallback) and anything else is limited by the not yet implemented wind awareness and the way path tracking works. But both don't belong in this PR.

b14ckyy and others added 2 commits August 27, 2026 09:40
…-smoothing

Brings in the VTOL auto-transition ownership rework, terrain support and the
9.1 back-merge. Two conflicts in navigation.c:

- Adjacent #define blocks: kept both (course-lock constants + the new
  USE_AUTO_TRANSITION MIXERAT retry constants).
- PG_NAV_CONFIG version: upstream made it conditional (11 with
  USE_AUTO_TRANSITION, else 8); this branch had bumped 8->9 for its added
  fields. Resolved one above upstream in both variants: 12 / 9.
  Stored nav settings reset to defaults on this bump.

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>
@breadoven

Copy link
Copy Markdown
Collaborator

I did some more HITL testing with the latest changes and it's certainly better where the snaking flight path is concerned. However, the thing I did find is it's over correcting after turning at a WP causing it to overshoot onto the other side of the course line. This can be seen in the following screenshots. Doesn't seem to be such a problem with COORD_FLYINTO but it is with the other 2 modes. COORD_FLYOVER in particular has snaking issues. The tests were with no wind flying around 70 km/h with WP tracking ON. Can this be improved with the settings ?

The other thing that I realised is ... how is COORD_FLYOVER any different to the legacy DIRECT mode ? The legacy mode overflies the WP then turns and heads directly to the next one when the heading aligns with the WP. COORD_FLYOVER appears to do the same thing.

COORD_FLYBY
NK_FPVSW - 2026-08-28 10 18 23

COORD_FLYOVER
NK_FPVSW - 2026-08-28 10 21 53

@b14ckyy

b14ckyy commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

However, the thing I did find is it's over correcting after turning at a WP causing it to overshoot onto the other side of the course line

This is 100% a bad tune (FF and Rates and/or too much bank angle) for the HITL what I was talking about earlier. The ease in and ease out with its ramped turn rate is pre-calculated. If the actual rates are too much off the setpoint rate, it will either roll slower than the FC anticipated or faster and both can cause overshoots like that. Nothing I can do about it.

EDIT: This overshoot behavior you see has btw cost me 2-3 days of work and HITL testing to fix until I did actual Debug logging over MSP to find the cause, just to have Claude tell to my face that my Tune is shit! And he was right. Autotuned until it felt right and the issue was gone.

how is COORD_FLYOVER any different to the legacy DIRECT mode

the difference is how the turn is flown towards the new waypoint or track. Legacy mode goes into a full nav bank angle and turns by best effort. So against the wind very tight, with the wind much longer and in crosswind all different shapes with a teardrop end. COORD_FLYOVER does a smooth circularized turn based on speed so its much more predictable with the same roll rate for ease in and ease out every time. This controlled turning is especially important for mapping tasks or gimbal use where you have consistent and reproducible roll behavior over the course even with different wind conditions (up to a reasonable limit).

@Jetrell

Jetrell commented Aug 29, 2026

Copy link
Copy Markdown

Have you guys tested this with a plane that uses a means of yaw control to also assist turning when nav_use_fw_yaw_control = ON ?

I had wrote some setup info here that gives insight into the settings that will cause a plane with active yaw control to go into a spiral dive. If those setting are increased too much or incorrectly tuned.
I encountered this in testing, when nav_fw_bank_angle is pushed past its limit, and less than max_angle_inclination_rll with the way this feature works. We need to remember that the higher the bank angle, the more that rudder deflection pushes the nose downwards, which starts a spiral.
But if those settings are kept within limits. A rudder, differential thrust or vectored thrust, makes for smooth, sharp and precise turns. As well as more accurate course tracking.

@b14ckyy

b14ckyy commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator Author

@Jetrell Prepairing my Ranger 2400 for tomorrow and will enable yaw control for that

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.

5 participants