Skip to content

[codex] Fix profile FFT settling on load#3604

Open
rfoust wants to merge 1 commit into
aethersdr:mainfrom
rfoust:codex/profile-load-fft-settle
Open

[codex] Fix profile FFT settling on load#3604
rfoust wants to merge 1 commit into
aethersdr:mainfrom
rfoust:codex/profile-load-fft-settle

Conversation

@rfoust

@rfoust rfoust commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes intermittent bogus FFT rendering during app startup and profile load. The panadapter display now waits until radio-reported FFT pixel height, local decoder scale, and widget dimensions are aligned before drawing restored-profile FFT/waterfall frames. It also drops stale/unmatched stream frames during profile rebuild, clears old display contents while the profile topology is settling, and lets client-side auto FFT leveling reacquire locally without writing radio-owned pan dBm state back to the radio.

Constitution principle honored

Principle XI — Fixes Are Demonstrated. This addresses a user-reported visual regression and was iterated against live radio behavior, with local build and focused regression checks run before PR.

Test plan

  • Local build passes (cmake --build build --parallel)
  • Behavior verified on a real radio if applicable
  • Existing tests pass (CI)
  • Reproduction steps documented if user-reported bug
    • App startup with restored multi-pan layout no longer briefly draws high/incorrect FFT traces during layout reorg.
    • Profile load no longer briefly draws stale/high FFT traces while restored pan dimensions and auto FFT leveling settle.

Local checks run:

git diff --check
cmake --build build --parallel
ctest --test-dir build --output-on-failure -R profile_load_command_test
ctest --test-dir build --output-on-failure -R panadapter_model_rx_antenna_test

Checklist

  • Commits are signed (docs/COMMIT-SIGNING.md)
  • No new flat-key AppSettings calls — use nested-JSON-under-one-key
    (Principle V)
  • All meter UI uses MeterSmoother (Principle II)
  • Documentation updated if user-visible behavior changed
  • Security-sensitive changes reference a GHSA if applicable

@rfoust rfoust requested review from a team as code owners June 15, 2026 01:46
Copilot AI review requested due to automatic review settings June 15, 2026 01:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to eliminate intermittent bogus FFT/waterfall rendering during startup and profile load by deferring spectrum drawing until panadapter dimensions/FFT scaling have stabilized, dropping stale/unmatched frames during rebuild, and allowing client-side noise-floor auto-adjust to reacquire without fighting radio-owned state.

Changes:

  • Added a RadioModel signal for radio-reported FFT pixel height (y_pixels) and wired it into MainWindow/SpectrumWidget to coordinate decoder + smoothing resets.
  • Introduced profile-load “pan display settling” state in MainWindow to defer drawing/restores, clear displays while topology settles, and drop unmatched stream frames.
  • Updated SpectrumWidget noise-floor logic to support a fast-lock reacquire path (resumeNoiseFloorAutoAdjust, snap frames, and “consumed frame” tracking).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/models/RadioModel.h Adds a new signal to publish radio-reported FFT pixel height per pan.
src/models/RadioModel.cpp Emits FFT scale updates and avoids applying pixel updates to a fallback pan when IDs don’t match.
src/gui/SpectrumWidget.h Adds noise-floor resume/query helpers and changes baseline updater to return “frame consumed”.
src/gui/SpectrumWidget.cpp Implements fast-lock noise-floor reacquire and integrates “consumed frame” semantics into fresh-lock logic.
src/gui/MainWindow.h Adds per-pan profile-load settling state tracking and extends pan-dimension push APIs for decoder timing.
src/gui/MainWindow_Wiring.cpp Defers/flushes pan dimension pushes during profile load and coordinates decoder updates + noise floor hold/reacquire.
src/gui/MainWindow_Session.cpp Drops stale/unmatched FFT/waterfall frames during profile rebuild and gates drawing while pan display is settling.

Comment thread src/gui/MainWindow_Session.cpp Outdated

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @rfoust — this is a well-structured fix for a genuinely fiddly problem (queued old-height FFT frames decoded at the new scale producing those brief high traces). The settling state machine, the panMatchedById guard, and the client-local auto-floor reacquire all read cleanly, and routing the local-decoder update through the radio echo (with a timed fallback) instead of the optimistic immediate write is a nice tightening. A few notes, none blocking:

On the Copilot comment (re: profileLoadFrameMatchesWidgetDimensions) — partially fair, partially not:

  • The naming concern is legitimate. The helper is named …MatchesWidgetDimensions but only enforces binCount >= 128 (plus panPixelDimensionsReady); it never compares the frame's bin count against panXpixelsFor(sw). That's actually fine for the bug you're fixing — the visual regression is vertical (ypixels/scale), and the real ypixels alignment is enforced by the settling logic (m_profileLoadPendingFftYpixels + profileLoadPanDimensionsMatchExpected), while horizontal bin mismatch just reprojects. But the name overpromises. Consider renaming to something like profileLoadFrameLooksRenderable or adding a one-line comment that this is a coarse sanity floor, not a dimension match — otherwise a future reader will assume xpixels are validated here when they aren't.

  • The "permanently drop valid frames for narrow pans" claim is a false positive. profileLoadFrameMatchesWidgetDimensions is only reached from inside profileLoadFrameReady, which returns true at the top once !writesHeld && !panDisplaySettling. So the 128-floor only applies during the settle window, and on top of that scheduleProfileLoadRecovery clears both settling maps at 11250ms. At worst one frame is dropped on the release tick; the next frame draws. And a real panadapter pane is never < 128px wide in practice, so the floor never bites a legitimate pan. Not permanent.

One genuine question worth a look: RadioModel::handlePanadapterStatus now emits panadapterFftScaleChanged on every status carrying y_pixels (anything >= 2), and the new handler in wirePanLifecycle calls sw->prepareForFftScaleChange() unconditionally on each emit — which resets FFT smoothing, bumps the scale-settling window, and arms a fast lock. During profile load that's exactly right, but if the radio echoes an unchanged y_pixels in routine status updates, you'd be resetting smoothing and re-arming fast-lock on the steady-state display. markProfileLoadPanDimensionsReady early-returns when the pan isn't pending, so that side is safe — but prepareForFftScaleChange() isn't gated. Could you confirm the radio only sends y_pixels on actual dimension changes? If not, consider gating the prepareForFftScaleChange() call (or the emit) on the value actually differing from the last-known ypixels.

Everything else looks good — conventions are respected (no new flat-key AppSettings, QPointer guard on the deferred decoder lambda, stale-stream drops logged rather than silently swallowed). The live-radio verification in the test plan covers the path that matters most here. CI is still pending; the merge is blocked on that, not on anything above.


🤖 aethersdr-agent · cost: $3.5541 · model: claude-opus-4-8

@rfoust rfoust self-assigned this Jun 15, 2026
@rfoust rfoust force-pushed the codex/profile-load-fft-settle branch from feecf65 to be39674 Compare June 15, 2026 03:12
@rfoust

rfoust commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed follow-up addressing the review notes:

  • Renamed profileLoadFrameMatchesWidgetDimensions() to profileLoadFrameLooksRenderable() and documented that it is a coarse profile-load renderability check, not an x-pixel equality check. The vertical FFT scale agreement is still enforced separately through the radio-reported y_pixels settling path, and horizontal bin counts can be reprojected by SpectrumWidget.
  • Gated panadapterFftScaleChanged on actual y_pixels changes via PanadapterModel::setFftYPixels(), while still feeding every valid y_pixels report into PanadapterStream for decoder state.

Local validation re-run after the follow-up:

  • git diff --check
  • cmake --build build --parallel
  • ctest --test-dir build --output-on-failure -R profile_load_command_test
  • ctest --test-dir build --output-on-failure -R panadapter_model_rx_antenna_test

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants