Skip to content

perf(maths): backport BF #14790 O(1) sin/cos range reduction - #1327

Open
nerdCopter wants to merge 12 commits into
emuflight:masterfrom
nerdCopter:feat/sincos-approx-backport
Open

perf(maths): backport BF #14790 O(1) sin/cos range reduction#1327
nerdCopter wants to merge 12 commits into
emuflight:masterfrom
nerdCopter:feat/sincos-approx-backport

Conversation

@nerdCopter

@nerdCopter nerdCopter commented Jul 24, 2026

Copy link
Copy Markdown
Member

AI Generated pull-request

Summary

Backports betaflight/betaflight#14790 (ledvinap, merged 2026-03-04) to EmuFlight's src/main/common/maths.c / maths.h. Closes #1269.

  • sin_approx/cos_approx: replaces the while-loop range reduction with an O(1) roundf-based quadrant split (t = x * INV_PIO2, qf = roundf(t), r = t - qf in [-0.5, 0.5]), matching BF's merged implementation.
  • sincosf_approx: now shares one range reduction and one r*r computation between a 5th-order sin polynomial and a 6th-order cos polynomial, instead of two full independent sin_approx/cos_approx evaluations.
  • maths.h: consolidates FAST_MATH/VERY_FAST_MATH into a single FAST_MATH guard, matching BF's post-#14790 header. EF had both always defined with no target ever undefining either, so this is a no-op for build output — a define-name delta reduction only.

Callsite migration

BF's own PR #14790 also migrated several same-angle sin/cos callsites (vector.c, imu.c, rc.c) to the combined sincosf_approx. This PR now includes the EmuFlight-local equivalents (upstream filenames don't map 1:1 to EF's structure):

  • src/main/common/maths.c buildRotationMatrix — roll/pitch/yaw pairs
  • src/main/common/filter.c biquadFilterUpdateomega
  • src/main/common/sdft.c twiddle-factor init — phi
  • src/main/fc/fc_rc.c scaleRcCommandToFpvCamAngle — both branches (cached cosFactor/sinFactor static storage preserved)
  • src/main/flight/imu.c applySensorCorrection — GPS courseOverGround
  • src/main/flight/imu.c imuQuaternionHeadfreeOffsetSetyawHalf

filter.c's SVF paths (svfLowpassFilterUpdate/svfNotchUpdate) already called sincosf_approx, no change needed there. Remaining sin_approx/cos_approx call sites (gps.c, imu.c small-angle/throttle-angle, pid.c, rangefinder.c, acos_approx internals) are single-use or not same-angle pairs — verified and left untouched.

No behavior change: each migrated site now shares one range reduction and one r*r between sin/cos instead of two independent evaluations; sincosf_approx is verified equal to independent sin_approx+cos_approx calls in TestSincosfApproxMatchesSeparateCalls.

Behavior note

The prior implementation clamped |x| > 32 rad (5*360 deg) to 0.0f. The new O(1) reduction has no such clamp (mechanical consequence of removing the while loop, matching BF exactly) — grep across src/main/ found no caller passing angles anywhere near that magnitude, so this is not expected to change any real behavior.

Numerical verification

src/test/unit/maths_unittest.cc extended:

  • TestFastTrigonometrySinCos: sweep [-10*pi, 10*pi) step pi/300 against sinf/cosf — measured max error: sin 3.173947e-06, cos 3.241003e-06 (tolerances: sin 3.2e-6 matching BF's own updated tolerance, cos 3.5e-6 retaining EF's prior tolerance headroom).
  • TestFastTrigonometryEdgeCases: exact 0, +-pi, +-pi/2, and large multiples of 2*pi (k in -50..50 step 10) against libm, with a magnitude-scaled epsilon for the large-x cases (float32 mantissa precision loss in representing x itself dominates at that magnitude — same property applies to any float32 trig approximation, not specific to this algorithm).
  • TestSincosfApproxMatchesSeparateCalls: verifies sincosf_approx output matches independent sin_approx+cos_approx calls on the same angle across [-20*pi, 20*pi).

Build/test verification

  • make clean_test && CCACHE_DISABLE=1 make test: full suite green, 0 failures (39 test binaries, includes test_maths_unittest, test_common_filter_unittest, test_flight_imu_unittest — all PASS post-callsite-migration).
  • CCACHE_DISABLE=1 make HELIOSPRING (real-hardware F4 bench target): build succeeded, FLASH1 35.97%, RAM 73.07%, CCM 26.06% — small flash increase (35.76% pre-migration) from additional inlined sincosf_approx call sites, no RAM/CCM change.

Independent verification (SITL + real hardware)

  • SITL A/B comparison: built master (9b144d436) and this branch (ebcdb65e8) as TARGET=SITL, forced non-default config (non-standard board alignment, D-term dynamic notch on) to exercise otherwise-conditional migrated sites, fed both an identical deterministic synthetic gyro/accel stream. buildRotationMatrix's output rotation matrix matched between builds to a max abs element diff of 8.34e-7 — an order of magnitude under the 3.2e-6 unit-test tolerance, confirming the migration is numerically equivalent rather than a behavior change. (sdft.c/biquadFilterUpdate sites weren't reached in this SITL session's observation window — a property of SITL's own init/calibration sequencing, not chased further given the generic sincosf_approx equivalence proof already covers the same code path.)
  • Real hardware bench test: independent hand-wiggle bench sessions on HELIOSPRING, master (9b144d4) vs this branch (ebcdb65) — no NaN/Inf in either blackbox log, no wild divergence or clipping in gyroADC/axisP/axisI/axisD/accSmooth/motor[]; differences attributable to the two takes' differing physical motion, not the code.
  • BF PR #14790's own CodeRabbit review cross-checked: BF's initial draft had two real bugs in its rc.c FPV-cam migration (lost static caching on cosFactor/sinFactor, and a missing degrees→radians conversion), both fixed before BF merged. Verified EF's fc_rc.c equivalent (scaleRcCommandToFpvCamAngle) has neither bug — static storage and the pre-existing * RAD conversion were both preserved by this migration.

CI status

  • Current HEAD a96e75f67 (synced with upstream/master, no further changes to this PR's own diff since 66afda72c): full build matrix (12 target groups) and both test jobs (Linux + macOS) PASS. sitl showed a transient GitHub Actions infrastructure failure (Failed to resolve action download info: Service Unavailable, during environment setup, before any EmuFlight build/test step ran) — not a code or test failure; re-run triggered.
  • Codacy Static Code Analysis fails with 2 issues, both on src/test/unit/maths_unittest.cc:252-253 ("Shifting by a negative value is undefined behaviour"). Those lines are EXPECT_NEAR(...) << "k=" << k << " x=" << x; — googletest's stream-insertion operator, not a bitwise shift; k (a for (int k = -50; k <= 50; k += 10) loop variable) is never used in an actual bit-shift anywhere in this function. Confirmed as a cppcheck false positive both by inspection and by CodeRabbit analysis (perf(maths): backport BF #14790 O(1) sin/cos range reduction #1327 (comment)), consistent with this repo's known Codacy/cppcheck false-positive pattern on similar constructs (see e.g. PR fix(board-info): remove colliding flags, wire up BOARD_NAME + USB string #1299, PR chore: suppress cppcheck missingIncludeSystem false positive (Codacy) #1156).

Real-flight verification

Flown 2026-08-06 on FOXEERF722V4 (primary F7 hardware test rig), firmware built from this exact
branch tip (a96e75f67). debug_mode = FFT_FREQ (watches the SDFT dynamic-notch center-frequency
path this PR's sdft.c twiddle-factor-init migration touches; raw/unfiltered gyro no longer needs
a debug-mode slot since it's now an always-on blackbox field per #1331). Log: 67,480 rows,
dterm_dyn_notch_enable=1, dynamic_gyro_notch_count=2, bounds 150400 Hz.

  • PASS — 0 NaN/Inf across every logged field.
  • Dynamic-notch center frequencies (debug[0]/debug[1]): 167–370 Hz and 187–400 Hz, both within
    the configured bounds throughout the flight.
  • gyroADC/gyroUnfilt track closely; motor[0..3] stay in normal DSHOT range with no sustained
    clipping; axisD normal. No regression signal.

Follow-up hardening: fc_rc.c cache sentinel

Associated with betaflight/betaflight#15498 (nerdCopter, still OPEN upstream) — closes betaflight/betaflight#15495, a real bug in BF's own scaleRawSetpointToFpvCamAngle (lost static caching, cross-checked and confirmed absent from EF above). BF's fix also widens its cache-invalidation sentinel from a value inside the config field's own domain to one outside it, so the first post-boot call is guaranteed to recompute rather than relying on coincidence. Applied the same hardening here: src/main/fc/fc_rc.c lastFpvCamAngleDegreesuint8_t (init 0) → int16_t (init -1, outside uint8_t's full 0-255 domain). No behavior change for the common case (fpvCamAngleDegrees = 0, the default): sincosf_approx(0) produces cos=1.0f/sin=0.0f, identical to the hardcoded static initializers the old skip-path relied on. rxConfig()->fpvCamAngleDegrees itself — the MSP-facing config field — is untouched, still uint8_t; no wire-format impact. Not gated on betaflight/betaflight#15498 merging — the fix is independently verified via BF's own added regression test (rc_unittest.cc, confirmed fails pre-fix/passes post-fix) plus this PR's local test suite.

CodeRabbit findings

  • Major: unguarded (int)qf conversion on non-finite/huge input in sin_approx/cos_approx/sincosf_approx. Analyzed further per review discussion — confirmed genuine undefined behavior on NaN/Inf/out-of-int-range input, matches faster cos and sin method betaflight/betaflight#14790's merged, reviewed implementation exactly (no guard present upstream either). No credible flight-path trigger identified (GPS course normalized to [-pi,pi], FPV camera angle constrained to [0,90] degrees, board-alignment inputs are integer degrees, IMU-derived angles bounded in normal operation). Not applied — adding a guard would diverge from the BF reference this change mechanically ports; would need to be a separately justified hardening commit if pursued.
  • Minor: sinCombined/cosCombined locals in the new sincosf_approx consistency test were bare-declared. Fixed — zero-initialized per UNIT-TEST.instructions.md convention.
  • Callsite migration (previously deferred as "optional follow-up") — added per review discussion, see Callsite migration section above.
  • A whole-series static analysis (perf(maths): backport BF #14790 O(1) sin/cos range reduction #1327 (comment)) surfaced a fc_rc.c scaleRcCommandToFpvCamAngle cache-staleness observation. Filed as FPV cam-angle factors can go stale after disabling cinematicYaw #1351, then closed not-planned: the finding's precondition needs cinematicYaw to toggle at runtime without a reboot, but it's a persistent config field changed only via save+reboot, which reinitializes the cache. Code-flow was correct; the precondition isn't reachable in normal use.
  • Correction: biquadFilterUpdate is no longer the D-term dynamic notch's call site on current master — refactor(filter): replace D-term dynamic notch biquad with SVF notch #1329 (merged) replaced it with svfNotchUpdate/svfNotchApply in pid.c, which already called sincosf_approx pre-perf(maths): backport BF #14790 O(1) sin/cos range reduction #1327 with zero source change. An earlier revision of a PR comment referenced the old call site; corrected in place.

Upstream findings (BF-side only, no EF impact)

A completeness cross-check of this PR against BF #14790/#15498 surfaced two BF-only issues, filed as betaflight/betaflight#15526 (no fix applied here — neither has an EmuFlight equivalent):

  • A live sign-flip bug in imu.c imuComputeQuaternionFromRPY, introduced by #14790 itself: the pre-#14790 code negated initialYaw before both the sin and cos calls; the migration to a single sincosf_approx call dropped that negation, silently flipping sinYaw's sign (cosine's evenness masked it). Confirmed still present in current BF master. EF has no equivalent function.
  • src/main/common/sdft.c:45 left unmigrated in BF's own #14790 (still separate cos_approx+sin_approx), the same callsite this PR's sdft.c migration covers on the EF side.

Credit

Source: betaflight/betaflight#14790, opened by @Quick-Flash implementing @ledvinap's proposed algorithm. Polynomial coefficients and range-reduction technique originate from that work, ported mechanically here.

Test plan

  • make clean && make test — 0 failures
  • make HELIOSPRING — build succeeds, no size regression
  • SITL A/B comparison (master vs branch, forced-config, identical synthetic input) — see Independent verification section
  • Real hardware bench test on HELIOSPRING (not required by AGENTS.md's hardware-verification gate, which scopes to driver/flash/SPI/gyro/DMA/USB changes — done anyway, no anomalies) — see Independent verification section
  • fc_rc.c cache-sentinel hardening re-verified: make clean_test && make test 39/39 PASS, make HELIOSPRING clean (+36 bytes, consistent with widening one static local by 1 byte)
  • Real-flight test on FOXEERF722V4 (debug_mode=FFT_FREQ) — PASS, no regression, see Real-flight verification section

Overall status: PASS. reviewDecision: APPROVED, mergeable: MERGEABLE, zero open review threads. No merge-blocking items; Codacy false positive is the only non-green check.

Summary by CodeRabbit

  • Performance Improvements

    • Improved trigonometric calculations across filtering, flight control, navigation, sensor processing, and signal analysis.
    • Combined sine and cosine calculations for more efficient real-time processing.
    • Improved fast-math accuracy and handling of large-angle and edge-case calculations.
  • Bug Fixes

    • Improved camera-angle updates and heading correction calculations.
  • Tests

    • Added coverage for trigonometric edge cases and consistency between combined and separate calculations.

nerdCopter and others added 2 commits July 24, 2026 16:14
Replaces while-loop range reduction in sin_approx/cos_approx with
roundf-based O(1) quadrant split (range_reduce -> r in [-0.5, 0.5]).
sincosf_approx shares one range reduction and one r^2 computation
between sin_poly5_r and cos_poly6_r instead of two full sin_approx
evaluations.

Consolidates maths.h FAST_MATH/VERY_FAST_MATH into a single FAST_MATH
guard, matching BF's post-#14790 maths.h; VERY_FAST_MATH was always
defined in EF (no target undefines it), so this is a no-op for build
output, only a define-name delta reduction vs BF.

Removes the prior |x| > 32 rad clamp-to-zero guard; the new reduction
has no such limit (mechanical consequence of dropping the while loop,
not a design choice made here).

Call sites (filter.c, imu.c, gps.c, sdft.c, rangefinder.c, fc_rc.c)
are unchanged per scope; BF's own callsite migration to sincosf_approx
in vector.c/imu.c/rc.c is left as optional follow-up, consistent with
the source issue's own "Optional follow-up" framing.

Extends maths_unittest.cc: sweeps sin_approx/cos_approx against sinf/
cosf at BF's own tolerance (3.2e-6 sin, 3.5e-6 cos, EF's prior cos
tolerance retained), adds explicit edge-case checks (0, +-pi, +-pi/2,
large multiples of 2*pi with a magnitude-scaled epsilon), and verifies
sincosf_approx output matches independent sin_approx/cos_approx calls
on the same angle.

Source: betaflight/betaflight#14790 (ledvinap, merged 2026-03-04).
Closes emuflight#1269.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CodeRabbit finding: sinCombined/cosCombined were bare-declared before
being passed to sincosf_approx(), inconsistent with repo's
UNIT-TEST.instructions.md zero-init convention.

Related major finding (unguarded (int)qf conversion on non-finite/huge
input in sin_approx/cos_approx/sincosf_approx) not applied: matches
betaflight/betaflight#14790's merged, reviewed implementation exactly
(no guard present upstream either); adding one would diverge from the
BF reference this is a mechanical backport of.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6353d5b2-7d34-47bd-8037-96db4fceee94

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The FAST_MATH sine and cosine approximations now use constant-time quadrant range reduction and shared polynomial evaluation. Paired trigonometric callsites use sincosf_approx, and unit tests cover edge angles, accuracy, and consistency.

Changes

Fast trigonometry migration

Layer / File(s) Summary
Trigonometric approximation implementation
src/main/common/maths.h, src/main/common/maths.c
Adds INV_PIO2, removes VERY_FAST_MATH, narrows FAST_MATH guards, and replaces loop-based trigonometric approximations with quadrant-based reduced-range polynomials.
Paired sine/cosine callsites
src/main/common/filter.c, src/main/common/maths.c, src/main/common/sdft.c, src/main/fc/fc_rc.c, src/main/flight/imu.c
Uses sincosf_approx for filter coefficients, rotation matrices, SDFT twiddles, camera-angle factors, GPS heading correction, and quaternion offsets.
Fast trigonometry tests
src/test/unit/maths_unittest.cc
Updates FAST_MATH guards and tolerances and adds edge-angle and combined-output consistency tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the requested O(1) trig reduction, combined sincos work, callsite migrations, and regression tests for issue #1269.
Out of Scope Changes check ✅ Passed The changes stay within the trig backport and its direct follow-on callsite/test updates, with no clear unrelated additions.
Title check ✅ Passed The title clearly identifies the performance-focused math change and the O(1) sine/cosine range reduction.
Description check ✅ Passed The description is comprehensive and covers the change, linked issue, testing, verification results, known limitations, and review status.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai two things:

  1. analyze and explain the Major: unguarded (int)qf conversion on non-finite/huge input in sin_approx/cos_approx/sincosf_approx.
  2. betaflight also migrated several callsites (vector.c, imu.c, rc.c) to use sincosf_approx . My AI agent chose to not scope this, but i think it should have.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== Relevant files ==='
fd -a '^(maths\.(c|h)|vector\.c|imu\.c|rc\.c|fc_rc\.c|filter\.c)$' src || true

printf '%s\n' '=== maths.c structure ==='
ast-grep outline src/main/common/maths.c --items all --type function --match 'sin|cos|sincos' || true

printf '%s\n' '=== Trigonometric implementations and uses ==='
rg -n -C 5 -P '\b(sin_approx|cos_approx|sincosf_approx)\s*\(' src/main \
  -g '*.{c,h}' || true

printf '%s\n' '=== Current PR diff for maths and candidate call sites ==='
git diff --find-renames --unified=40 'HEAD^' -- \
  src/main/common/maths.c src/main/common/maths.h \
  src/main/common/vector.c src/main/flight/imu.c \
  src/main/fc/rc.c src/main/fc/fc_rc.c src/main/common/filter.c || true

printf '%s\n' '=== Upstream Betaflight `#14790` metadata and changed files ==='
gh pr view 14790 --repo betaflight/betaflight --json title,state,mergedAt,url,body,files \
  --jq '{title, state, mergedAt, url, body, files: [.files[] | {path, additions, deletions}]}' || true

printf '%s\n' '=== Upstream Betaflight `#14790` patch (relevant paths) ==='
gh api -H 'Accept: application/vnd.github.diff' \
  repos/betaflight/betaflight/pulls/14790 \
  | awk '
      /^diff --git / {show = ($0 ~ /(maths\.(c|h)|vector\.c|imu\.c|rc\.c)$/)}
      show {print}
    ' || true

Length of output: 30010


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== Candidate function maps ==='
ast-grep outline src/main/common/maths.c src/main/common/filter.c src/main/fc/fc_rc.c src/main/flight/imu.c \
  --items all --type function --view expanded || true

printf '%s\n' '=== Enclosing candidate implementations ==='
for spec in \
  'src/main/common/maths.c:220:270' \
  'src/main/common/filter.c:90:205' \
  'src/main/fc/fc_rc.c:200:250' \
  'src/main/flight/imu.c:200:245' \
  'src/main/flight/imu.c:410:440'
do
  file="${spec%%:*}"
  rest="${spec#*:}"
  start="${rest%%:*}"
  end="${rest##*:}"
  printf '\n--- %s, Lines %s-%s ---\n' "$file" "$start" "$end"
  sed -n "${start},${end}p" "$file"
done

printf '%s\n' '=== Declarations and constraints for RC camera angle/configuration ==='
rg -n -C 4 -P '\b(cinematicYaw|fpvCamAngleDegrees|currentPitchAngle|lastFpvCamAngleDegrees)\b' \
  src/main -g '*.{c,h}' || true

printf '%s\n' '=== Angle type/source constraints for rotation and IMU paths ==='
rg -n -C 4 -P '\b(buildRotationMatrix|fp_angles_t|courseOverGround|yawHalf|qpAttitude)\b' \
  src/main -g '*.{c,h}' || true

printf '%s\n' '=== Existing maths tests and finite/non-finite coverage ==='
fd -a 'maths_unittest.cc' src/test \
  -x sh -c 'echo "--- $1 ---"; rg -n -C 5 -P "\b(TestFastTrigonometry|TestSincosf|sin_approx|cos_approx|sincosf_approx|NAN|INFINITY)\b" "$1"' sh {} || true

printf '%s\n' '=== Compiler language standard / float-conversion diagnostic flags ==='
rg -n -C 2 -P '(std=|Wconversion|float-conversion|fsanitize)' Makefile src -g 'Makefile' -g '*.mk' -g '*.cmake' || true

Length of output: 56285


@nerdCopter: I agree—the sincosf_approx call-site migration belongs in this PR. The upstream filenames do not map 1:1 to EmuFlight, but the equivalent local opportunities are clear.

1. (int)qf conversion: what the concern means

Each function now does:

float t = x * INV_PIO2;
float qf = roundf(t);
int q = (int)qf;
float r = t - qf;

q is then only used as a quadrant selector (q &= 3).

For finite, reasonably sized angles, this is correct:

  • qf is the nearest multiple of π/2,
  • r is nominally in [-0.5, 0.5] quadrant units,
  • q & 3 selects the sine/cosine sign and swap.

The problem is C conversion semantics: converting a floating-point value to an integer is undefined behavior if the value is NaN, +/-Inf, or outside the destination int range. Therefore, the new code has undefined behavior for:

  1. sin_approx(NAN), cos_approx(INFINITY), etc.; and
  2. sufficiently large finite x such that roundf(x * 2/π) cannot fit in int.

On the 32-bit embedded targets this becomes relevant at approximately:

|x| >= INT_MAX * π/2 ≈ 3.37 × 10^9 radians

Practical impact in EmuFlight

I found no normal operational path that approaches this:

  • GPS course is explicitly normalized to [-π, π] before its paired sin/cos evaluation.
  • FPV camera angle is constrained to [0, 90] degrees.
  • Board-alignment rotation inputs are integer degree settings.
  • IMU-derived angles are bounded by their source calculations in normal operation.

So this is not a credible flight-path regression for valid runtime data. The current test coverage also only exercises finite inputs.

However, it is still a real robustness/API-boundary issue: a corrupted sensor/configuration value that becomes non-finite can now execute undefined behavior before the rest of the control path propagates NaN. The previous implementation did not define a useful non-finite contract either, so I would not block a mechanical upstream backport on this alone—but I would not call it “nothing.”

A defensive guard would need to happen before the cast and should preserve the fast path for ordinary values. That would intentionally diverge from Betaflight #14790, so it should be a small, separately justified hardening commit rather than silently mixed into the mechanical port.

2. sincosf_approx migrations

Yes—leaving these independent same-angle calls behind misses the combined-path benefit: one range reduction and one shared r * r calculation instead of two.

The correct EmuFlight scope is broader than the three upstream filenames:

EmuFlight location Equivalent / reason
src/main/common/maths.cbuildRotationMatrix Local equivalent of Betaflight’s vector.c rotation-matrix work; three same-angle pairs.
src/main/common/filter.cbiquadFilterUpdate Same omega feeds both calls; this is a worthwhile direct conversion.
src/main/common/sdft.c Same phi feeds both calls during twiddle-factor initialization.
src/main/fc/fc_rc.cscaleRcCommandToFpvCamAngle Local equivalent of Betaflight’s rc.c; preserve the existing static cached factors and replace each paired calculation with one combined call.
src/main/flight/imu.c — GPS course correction courseOverGround is already normalized, then used for both values.
src/main/flight/imu.cimuQuaternionHeadfreeOffsetSet yawHalf is passed separately to cosine and sine.

src/main/common/filter.c’s SVF paths already use sincosf_approx, so they need no change.

Recommended PR expansion

  • Migrate every existing local, paired same-angle sin_approx/cos_approx calculation to sincosf_approx.
  • Preserve variable ordering and existing cached/static behavior.
  • Add focused regression coverage for the migrated math path; handle non-finite/oversized-input hardening separately if upstream parity remains a constraint.

  • 📌 Create a pull request with these changes

…pprox

Per CodeRabbit analysis on PR emuflight#1327 (comment #5095429276), completes
BF #14790's callsite migration for EmuFlight's local equivalents:

- maths.c buildRotationMatrix: roll/pitch/yaw pairs
- filter.c biquadFilterUpdate: omega
- sdft.c twiddle-factor init: phi
- fc_rc.c scaleRcCommandToFpvCamAngle: both branches
- imu.c applySensorCorrection: GPS courseOverGround
- imu.c imuQuaternionHeadfreeOffsetSet: yawHalf

Each site shares one range reduction and one r*r computation between
sin/cos instead of two independent evaluations. No behavior change —
sincosf_approx is verified equal to independent sin_approx+cos_approx
calls in maths_unittest's TestSincosfApproxMatchesSeparateCalls.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@nerdCopter

Copy link
Copy Markdown
Member Author

AI Generated comment

Callsite migration (item 2) implemented in commit ebcdb65: buildRotationMatrix (maths.c), biquadFilterUpdate (filter.c), twiddle-factor init (sdft.c), scaleRcCommandToFpvCamAngle (fc_rc.c), GPS courseOverGround correction and imuQuaternionHeadfreeOffsetSet (imu.c) now all use sincosf_approx. PR description updated with the full list and verification (39/39 test binaries PASS, make HELIOSPRING clean).

Item 1 ((int)qf UB): left unguarded, matching betaflight/betaflight#14790's merged implementation exactly. No credible flight-path trigger found — GPS course is normalized to [-pi,pi], FPV camera angle is constrained to [0,90] degrees, board-alignment inputs are integer degrees, and IMU-derived angles are bounded in normal operation. Not fixed here to keep this a mechanical BF-parity port; a guard would need to be a separately justified hardening commit.

lastFpvCamAngleDegrees stayed uint8_t initialized to 0, coinciding with
the field's own default value. Widened to int16_t initialized to -1
(outside uint8_t's full 0-255 range) so the cache-invalidation check
can never accidentally match a real config value, guaranteeing the
first post-boot call always recomputes cosFactor/sinFactor instead of
relying on the hardcoded 1.0/0.0 defaults matching by coincidence.

No functional change for the common case (angle 0): sincosf_approx(0)
produces the same cos=1.0/sin=0.0 the old code assumed by default.

Matches betaflight/betaflight#15498 (nerdCopter), which applies the
same hardening to BF's analogous scaleRawSetpointToFpvCamAngle after
betaflight/betaflight#14790 introduced the pattern. rxConfig()->fpvCamAngleDegrees
itself (the MSP-facing config field) is untouched, still uint8_t.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nerdCopter
nerdCopter marked this pull request as ready for review July 28, 2026 19:36
@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai analyze (not review) two things:

  1. Codacy flagged src/test/unit/maths_unittest.cc:252-253 ("Shifting by a negative value is undefined behaviour", https://app.codacy.com/gh/emuflight/EmuFlight/pull-requests/1327). Those lines are EXPECT_NEAR(...) << "k=" << k << " x=" << x; inside for (int k = -50; k <= 50; k += 10) — googletest's stream-insertion operator, not a bitwise shift, and k is never used in an actual bit-shift anywhere in this function. Confirm whether this is a cppcheck false positive (operator-overload << misread as bitshift).

  2. Sanity-check commit 66afda7 (src/main/fc/fc_rc.c lastFpvCamAngleDegrees widened from uint8_t(0) to int16_t(-1), matching fix: give fpv cam angle sin/cos factors static storage duration betaflight/betaflight#15498) for any interaction risk with the rest of scaleRcCommandToFpvCamAngle or other code touching that function, beyond what's already covered in the PR description's "Follow-up hardening" section.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== PR status and target commits ==='
git status --short
git show --no-patch --format='%H%n%P%n%s' 66afda72c
git show --no-patch --format='%H%n%P%n%s' ebcdb65e8

printf '%s\n' '=== Unit-test structure and reported region ==='
ast-grep outline src/test/unit/maths_unittest.cc --items all --type function --match 'TestFastTrigonometryEdgeCases|TestSincosfApproxMatchesSeparateCalls' --view expanded || true
nl -ba src/test/unit/maths_unittest.cc | sed -n '230,270p'

printf '%s\n' '=== Exact unit-test expressions involving k ==='
rg -n -C 4 -P '\bfor\s*\(\s*int\s+k\s*=|<<\s*k\b|\bk\s*<<|>>\s*k\b|\bk\s*>>' src/test/unit/maths_unittest.cc

printf '%s\n' '=== FPV function and cache identifier references ==='
ast-grep outline src/main/fc/fc_rc.c --items all --type function --match 'scaleRcCommandToFpvCamAngle' --view expanded || true
rg -n -C 12 -P '\bscaleRcCommandToFpvCamAngle\s*\(|\blastFpvCamAngleDegrees\b|\bfpvCamAngleDegrees\b|\bcinematicYaw\b' src/main/fc/fc_rc.c src/main -g '*.{c,h}'

printf '%s\n' '=== Follow-up commit diff ==='
git show --format=fuller --find-renames --unified=50 66afda72c -- src/main/fc/fc_rc.c

printf '%s\n' '=== Field declaration/default/reset paths ==='
rg -n -C 5 -P '\bfpvCamAngleDegrees\b' src/main -g '*.{c,h}'

Length of output: 2191


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '=== Reported test lines (numbered with awk) ==='
awk 'NR >= 230 && NR <= 270 { printf "%6d  %s\n", NR, $0 }' src/test/unit/maths_unittest.cc

printf '%s\n' '=== Every syntactic occurrence relevant to k and shift operators in this test file ==='
rg -n -C 3 -P '\bfor\s*\(\s*int\s+k\s*=|<<\s*k\b|\bk\s*<<|>>\s*k\b|\bk\s*>>|\bk\b' \
  src/test/unit/maths_unittest.cc || true

printf '%s\n' '=== FPV cache function ==='
awk '/static void scaleRcCommandToFpvCamAngle\(void\)/,/^}/ { printf "%6d  %s\n", NR, $0 }' src/main/fc/fc_rc.c

printf '%s\n' '=== All references to cache function and relevant configuration fields ==='
rg -n -C 6 -P '\b(scaleRcCommandToFpvCamAngle|fpvCamAngleDegrees|cinematicYaw|lastFpvCamAngleDegrees)\b' \
  src/main -g '*.{c,h}' || true

printf '%s\n' '=== Exact follow-up change ==='
git diff 66afda72c^ 66afda72c -- src/main/fc/fc_rc.c || true

printf '%s\n' '=== Cache-field type/range/default metadata ==='
rg -n -C 8 -P '\bfpvCamAngleDegrees\b' src/main -g '*.{c,h}' || true

Length of output: 65971


@nerdCopter: Analysis complete—no review findings generated.

  1. Codacy warning: confirmed false positive.

    At Lines 252-253, these are GoogleTest diagnostic chains:

    EXPECT_NEAR(...) << "k=" << k << " x=" << x;

    EXPECT_NEAR yields a GoogleTest assertion object whose overloaded operator<< appends failure context. This is stream insertion, not C/C++’s built-in bit-shift operator.

    k is used only:

    • as the signed loop counter,
    • in (float)k * 2.0f * M_PIf, and
    • as a diagnostic value in the assertion messages.

    There is no k << ..., ... >> k, or other actual shift expression. The “shifting by a negative value” report is therefore a cppcheck/Codacy macro- or overload-analysis false positive and can be marked ignored/suppressed for this location.

  2. Commit 66afda72c: the sentinel change is sound.

    The change is intentionally minimal:

    - static uint8_t lastFpvCamAngleDegrees = 0;
    + static int16_t lastFpvCamAngleDegrees = -1;

    fpvCamAngleDegrees remains an externally stored uint8_t, is read/written as a byte through MSP, and is CLI-constrained to [0, 90]. Consequently, -1 is an unambiguous local-only sentinel and cannot collide with any valid configured camera angle.

    This ensures the first non-cinematic cache use recomputes sinFactor/cosFactor, including the valid configuration that previously matched the uint8_t initial value and could retain the default factors rather than exercise the new approximation path. No other code accesses this function-local cache, and the change does not affect configuration persistence, MSP compatibility, or the call condition.

    One pre-existing, unrelated edge case is worth noting but is not introduced by 66afda72c: while cinematicYaw is enabled, factors are recalculated from current pitch but lastFpvCamAngleDegrees is neither updated nor invalidated. If cinematic yaw is then disabled with an unchanged nonzero configured FPV angle, the cache comparison can succeed and retain the last pitch-derived factors until the FPV angle changes. That behavior was present before this commit; the sentinel fix neither worsens nor resolves it.

@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai analyze (not review) the full functionality of this PR at current HEAD (66afda7) — the complete accumulated diff from base, not just the two narrow points in the prior comment.

Specifically: does the combination of (a) the O(1) sin/cos range-reduction rewrite in maths.c/maths.h, (b) the six callsite migrations to sincosf_approx (buildRotationMatrix, biquadFilterUpdate, sdft.c twiddle-init, scaleRcCommandToFpvCamAngle both branches, applySensorCorrection GPS course, imuQuaternionHeadfreeOffsetSet), and (c) the fc_rc.c lastFpvCamAngleDegrees cache-sentinel widening hold together correctly as a whole? Look for anything across those combined changes — argument-order mistakes, sign errors, missed callers, interaction effects between commits — not just each commit in isolation.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target="66afda72c"
base="$(git merge-base "$target" origin/master 2>/dev/null || git merge-base "$target" HEAD^)"
printf '=== Base and accumulated commits ===\n'
printf 'base=%s\ntarget=%s\n' "$base" "$target"
git log --oneline --reverse "${base}..${target}"

printf '\n=== Complete changed-file summary ===\n'
git diff --stat "$base" "$target"
git diff --name-status "$base" "$target"

printf '\n=== Aggregate diff: relevant implementation and tests ===\n'
git diff --find-renames --unified=35 "$base" "$target" -- \
  src/main/common/maths.c src/main/common/maths.h \
  src/main/common/filter.c src/main/common/sdft.c \
  src/main/fc/fc_rc.c src/main/flight/imu.c \
  src/test/unit/maths_unittest.cc

printf '\n=== Function maps ===\n'
ast-grep outline src/main/common/maths.c src/main/common/filter.c src/main/common/sdft.c \
  src/main/fc/fc_rc.c src/main/flight/imu.c src/test/unit/maths_unittest.cc \
  --items all --type function --view expanded || true

printf '\n=== Current approximate-trig definitions and all callsites ===\n'
rg -n -C 5 -P '\b(sin_approx|cos_approx|sincosf_approx)\s*\(' \
  src/main src/test -g '*.{c,cc,h}' || true

printf '\n=== Possible remaining adjacent same-angle sin/cos pairs ===\n'
rg -n -U -P '(?s)\b(?:sin_approx|cos_approx)\s*\(\s*([^)\n]+)\s*\)\s*;.{0,300}\b(?:sin_approx|cos_approx)\s*\(\s*\1\s*\)\s*;' \
  src/main -g '*.{c,h}' || true

printf '\n=== FPV cache, config range/default, and callers ===\n'
rg -n -C 8 -P '\b(scaleRcCommandToFpvCamAngle|lastFpvCamAngleDegrees|fpvCamAngleDegrees|cinematicYaw)\b' \
  src/main -g '*.{c,h}' || true

printf '\n=== FAST_MATH / VERY_FAST_MATH references and trig declarations ===\n'
rg -n -C 3 -P '\b(FAST_MATH|VERY_FAST_MATH|INV_PIO2|sin_approx|cos_approx|sincosf_approx)\b' \
  src/main src/test -g '*.{c,cc,h}' || true

Length of output: 119472


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target="66afda72c"
base="$(git merge-base "$target" origin/master 2>/dev/null || git merge-base "$target" HEAD^)"

printf '%s\n' '=== Exact per-commit changes in the accumulated series ==='
for commit in b3a6f6b5b bee1b0efe ebcdb65e8 66afda72c; do
  git show --format='%H %s' --stat --oneline "$commit"
done

printf '%s\n' '=== All VERY_FAST_MATH references (repository-wide) ==='
rg -n -C 3 -P '\bVERY_FAST_MATH\b' . -g '!**/.git/**' || true

printf '%s\n' '=== All approximate trig call expressions, grouped by source file ==='
rg -n -P '\b(?:sin_approx|cos_approx|sincosf_approx)\s*\(' src/main -g '*.{c,h}' || true

printf '%s\n' '=== FPV cache state-relevant history ==='
git log -p -S'lastFpvCamAngleDegrees' -- src/main/fc/fc_rc.c | sed -n '1,260p'

printf '%s\n' '=== Upstream source comparison: Betaflight PRs ==='
gh api -H 'Accept: application/vnd.github.diff' repos/betaflight/betaflight/pulls/14790 \
  | awk '/^diff --git a\\/src\\/main\\/common\\/maths\\.(c|h)/,/^diff --git / { print }' || true
gh api -H 'Accept: application/vnd.github.diff' repos/betaflight/betaflight/pulls/15498 \
  | awk '/^diff --git a\\/src\\/main\\/fc\\/rc\\.c/,/^diff --git / { print }' || true

printf '%s\n' '=== Static float32 model of quadrant selection against libm ==='
python3 - <<'PY'
import ctypes
import math
import random
import struct

f32 = lambda x: struct.unpack('<f', struct.pack('<f', x))[0]
pio2 = f32(math.pi / 2.0)
inv_pio2 = f32(2.0 / f32(math.pi))

def roundf_away(x):
    return math.floor(x + 0.5) if x >= 0 else math.ceil(x - 0.5)

def sin_poly(r):
    c0 = f32(float.fromhex('0x1.921f1cp0'))
    c1 = f32(-float.fromhex('0x1.4a974p-1'))
    c2 = f32(float.fromhex('0x1.3db294p-4'))
    s = f32(r * r)
    return f32(r * f32(f32(f32(c2*s) + c1)*s + c0))

def cos_poly(r):
    d1 = f32(-float.fromhex('0x1.3bd39cp0'))
    d2 = f32(float.fromhex('0x1.03bp-2'))
    d3 = f32(-float.fromhex('0x1.4e5eecp-6'))
    s = f32(r * r)
    return f32(f32(f32(f32(d3*s) + d2)*s + d1)*s + 1.0)

def approx(x):
    t = f32(x * inv_pio2)
    qf = f32(roundf_away(t))
    q = int(qf)
    r = f32(t - qf)
    sb, cb = sin_poly(r), cos_poly(r)
    s = cb if q & 1 else sb
    c = -sb if q & 1 else cb
    if q & 2:
        s, c = -s, -c
    return f32(s), f32(c)

angles = [f32(k * math.pi / 4) for k in range(-32, 33)]
angles += [f32(random.uniform(-1000.0, 1000.0)) for _ in range(10000)]
max_s = max_c = 0.0
for x in angles:
    s, c = approx(x)
    max_s = max(max_s, abs(s - f32(math.sin(x))))
    max_c = max(max_c, abs(c - f32(math.cos(x))))
print(f'finite samples={len(angles)} max_abs_sin_error={max_s:.9g} max_abs_cos_error={max_c:.9g}')
for k in range(-4, 5):
    x = f32(k * math.pi / 2)
    s, c = approx(x)
    print(f'k={k:+d}: sin={s:+.7f}, cos={c:+.7f}')
PY

printf '%s\n' '=== Diff completeness check ==='
git diff --check "$base" "$target"

Length of output: 7544


@nerdCopter: I completed a whole-series static analysis of the accumulated diff from base 9b144d4 through current HEAD 66afda72c. I did not generate review findings.

Overall conclusion

The three parts hold together correctly:

  1. The O(1) range reduction and quadrant reconstruction in maths.c;
  2. All migrated same-angle callsites using sincosf_approx(angle, &sine, &cosine); and
  3. The lastFpvCamAngleDegrees = -1 cache sentinel in fc_rc.c.

I found no argument-order swap, sign inversion, missed paired approximate-trig caller, or new cross-commit interaction defect.

Fast-trig implementation

The shared reduction is consistent across sin_approx, cos_approx, and sincosf_approx:

t  = x * INV_PIO2;
qf = roundf(t);
q  = (int)qf;
r  = t - qf;

The quadrant code then has the expected results for both positive and negative quadrants:

Angle sine cosine
0 -1
-π/2 -1 0
0 0 1
π/2 1 0
π 0 -1

The combined implementation uses the same sin_poly5_r, cos_poly6_r, quadrant swap, and sign application as the separate implementations. Its output contract is correctly implemented as:

sincosf_approx(angle, &sine, &cosine);

The known non-finite/out-of-range (int)qf conversion limitation remains exactly as previously discussed and intentionally out of scope for this parity backport.

One expected numerical characteristic of simple float range reduction remains: error increases for very large finite angles because float precision loses low-order range-reduction information. That is separate from the integer-conversion UB threshold and does not affect the operational ranges of the migrated paths; it is also inherent to the selected upstream implementation.

Callsite migration verification

All migrated uses preserve the old value flow:

  • buildRotationMatrix
    &sinx, &cosx, &siny, &cosy, and &sinz, &cosz match the existing matrix equations. No roll/pitch/yaw or sine/cosine swap exists.

  • biquadFilterUpdate
    sn remains the numerator of alpha = sn / (2Q) and cs remains the coefficient source for b1/a1. Correct.

  • SDFT twiddle initialization
    The prior expression was cos(phi) + i * sin(phi); the new expression remains:

    cosPhi + _Complex_I * sinPhi

    So twiddle direction/sign is preserved.

  • scaleRcCommandToFpvCamAngle
    Both branches correctly pass &sinFactor, &cosFactor, and the existing rotation:

    roll' = roll * cosFactor - yaw * sinFactor;
    yaw'  = yaw  * cosFactor + roll * sinFactor;

    is unchanged.

  • GPS course correction in applySensorCorrection
    The new sinCourseOverGround and cosCourseOverGround replace their respective original calls without changing either term’s sign or position in the heading-error expression.

  • imuQuaternionHeadfreeOffsetSet
    The quaternion scalar still receives cosine and z still receives sine:

    qOffset.w = cosYawHalf;
    qOffset.z = sinYawHalf;

    This preserves the original quaternion construction before conjugation.

A repository-wide scan found no remaining paired same-angle sin_approx/cos_approx calls in src/main. The remaining approximate-trig calls are single-output uses; the SVF filter paths were already using sincosf_approx.

FPV cache sentinel interaction

The sentinel change is correct in the combined series:

static int16_t lastFpvCamAngleDegrees = -1;

fpvCamAngleDegrees remains a persisted/MSP uint8_t and is CLI-limited to [0, 90], so -1 cannot collide with a valid setting. This guarantees that the first non-cinematic invocation recomputes both factors, including for the valid camera angle.

It does not alter configuration layout, persistence, MSP serialization, or the function’s externally observable API; the widened value is local static state only.

The previously noted behavior remains pre-existing: after a non-cinematic angle has been cached, enabling cinematic yaw recalculates from pitch without updating the non-cinematic cache key; disabling cinematic yaw with the same configured angle can retain pitch-derived factors until the setting changes. The new sentinel neither introduces nor worsens that behavior. It does improve the startup/cinematic-first transition because -1 forces the first subsequent non-cinematic recomputation.

Verification boundary

git diff --check passed. This was static source analysis only; I did not run the build or test suite in the sandbox.

@nerdCopter

nerdCopter commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai, AI Generated comment

Follow-up on the cinematicYaw cache-staleness observation in this comment (#1327 (comment)).

Filed as #1351, then closed not-planned: the finding's precondition requires cinematicYaw to transition enabled→disabled at runtime without a reboot, but cinematicYaw is a persistent rxConfig_t field changed only via CLI/MSP save+reboot, which reinitializes lastFpvCamAngleDegrees/sinFactor/cosFactor. Code-flow was correct; the triggering transition isn't reachable in normal use. No action needed in this PR.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@nerdCopter: Confirmed. The prior observation does not apply.

cinematicYaw cannot change during normal runtime. A CLI/MSP save requires a reboot. The reboot reinitializes lastFpvCamAngleDegrees, sinFactor, and cosFactor.

Therefore, the required cinematic-yaw transition cannot retain pitch-derived factors in the non-cinematic path. No change is needed in this PR.


✏️ Learnings added
Learnt from: nerdCopter
URL: https://github.com/emuflight/EmuFlight/pull/1327

Timestamp: 2026-08-04T20:26:47.456Z
Learning: In `src/main/fc/fc_rc.c`, `cinematicYaw` is a persistent `rxConfig_t` field. Changing it through CLI or MSP requires save and reboot. The reboot reinitializes `scaleRcCommandToFpvCamAngle()` function-local static state, including `lastFpvCamAngleDegrees`, `sinFactor`, and `cosFactor`. Do not report a runtime cinematicYaw enabled-to-disabled cache-staleness path because that transition is not reachable during normal operation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@nerdCopter

nerdCopter commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

AI Generated comment

Claude deemed safe to merge.

Reasoning:

  • reviewDecision: APPROVED, mergeable: MERGEABLE, no open review threads.
  • CI green on current HEAD (8fa2ecd05, post upstream/master merge): full build matrix, test, sitl all pass. Only Codacy fails, a documented false positive (googletest's << stream operator misread as bitshift by cppcheck).
  • All review discussion resolved; the one follow-up finding (cinematicYaw cache staleness) was filed as FPV cam-angle factors can go stale after disabling cinematicYaw #1351 and closed not-planned — precondition unreachable given cinematicYaw's persistent, reboot-on-save config lifecycle.
  • AGENTS.md's hardware-verification gate (flight test required before merge) scopes to driver changes touching flash/SPI/gyro/DMA/USB — this PR (trig-approximation math + one cache-sentinel fix) doesn't qualify, so a hover test isn't required.
  • Existing verification: unit tests prove sincosf_approx numerically matches the prior separate sin_approx+cos_approx calls to tight tolerance across a wide angle sweep; SITL A/B on buildRotationMatrix measured max diff 8.34e-7; real-hardware bench test on HELIOSPRING showed no NaN/Inf/anomalies.
  • Correction: an earlier revision of this comment flagged biquadFilterUpdate's D-term dynamic notch site as unexercised by the bench test. That call site no longer exists on current master — refactor(filter): replace D-term dynamic notch biquad with SVF notch #1329 (merged) replaced D-term dynamic notch with svfNotchUpdate/svfNotchApply in pid.c, which already used sincosf_approx pre-perf(maths): backport BF #14790 O(1) sin/cos range reduction #1327 with zero source change. The remaining real-time path this PR touches is sdft.c's twiddle-factor init (gyro dynamic-notch center-frequency detection via SDFT), covered by the SITL/bench verification above.

@nerdCopter

Copy link
Copy Markdown
Member Author

AI Generated comment

Status update: real-flight test flown on FOXEERF722V4 (debug_mode=FFT_FREQ), 67,480-row log — PASS. 0 NaN/Inf, dynamic-notch center frequencies (167–370 Hz / 187–400 Hz) stayed within configured bounds throughout, gyro/motor/D-term all in normal range. No regression signal.

PR description updated with this result plus: the cinematicYaw finding's disposition (#1351, closed not-planned — code-flow correct but the precondition isn't reachable given the field's persistent, reboot-on-save lifecycle), a correction that D-term dynamic notch runs through SVF not biquadFilterUpdate on current master (#1329, merged), and the two BF-side-only findings filed upstream as betaflight/betaflight#15526.

Overall: PASS. reviewDecision: APPROVED, mergeable: MERGEABLE, zero open review threads. Build matrix + both test jobs green on current HEAD (a96e75f67); sitl hit a transient GitHub Actions infrastructure failure (unrelated to this code, re-run in progress) and Codacy still shows its documented false positive — neither is a code regression. No merge-blocking items remain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant