Skip to content

fix(rc28): sync wheel accumulator to snapped frequency after auto-snap fires#3940

Merged
NF0T merged 1 commit into
aethersdr:mainfrom
wa2n-code:fix/rc28-autosnap-accumulator-sync
Jul 1, 2026
Merged

fix(rc28): sync wheel accumulator to snapped frequency after auto-snap fires#3940
NF0T merged 1 commit into
aethersdr:mainfrom
wa2n-code:fix/rc28-autosnap-accumulator-sync

Conversation

@wa2n-code

Copy link
Copy Markdown
Contributor

Summary

  • After the 600 ms auto-snap timer rounds the slice frequency to the nearest 1 kHz, m_flexTargetMhz was left pointing at the pre-snap value.
  • The re-base guard in applyFlexControlWheelAction only fires when the delta exceeds 1 kHz, so a sub-kHz snap (e.g. 200 Hz) was invisible to it — the next knob tick would resume from the stale pre-snap target, effectively undoing the snap.
  • Setting m_flexTargetMhz = snapped immediately after applyTuneRequest keeps the accumulator in sync with the displayed frequency.

Fixes #3939.

Test plan

  • Manually reproduced the auto-snap-then-knob-tick sequence and confirmed the snap now sticks
  • Verified no regression on normal (non-snap) incremental tuning via the control wheel

…p fires (aethersdr#3939)

After the 600 ms auto-snap timer rounds the slice frequency to the nearest
1 kHz, m_flexTargetMhz was left pointing at the pre-snap value.  The
re-base guard in applyFlexControlWheelAction only fires when the delta
exceeds 1 kHz, so a sub-kHz snap (e.g. 200 Hz) was invisible to it —
the next knob tick would resume from the stale pre-snap target, effectively
undoing the snap.  Setting m_flexTargetMhz = snapped immediately after
applyTuneRequest keeps the accumulator in sync with the displayed flag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@wa2n-code wa2n-code requested a review from a team as a code owner July 1, 2026 04:25

@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 for the fix, @wa2n-code — clean, well-scoped, and the root-cause writeup is spot on.

I traced the full path to confirm the diagnosis:

  • The RC-28 wheel drives applyFlexControlWheelAction("WheelFrequency", …) (MainWindow_Controllers.cpp:2488), which updates the m_flexTargetMhz accumulator and also arms m_hidSnapTimer (:2486).
  • The snap timer fires 600 ms after the last tick, by which point the 20 ms m_flexCoalesceTimer is long inactive — so the re-base guard's live branch is the std::abs(m_flexTargetMhz - s->frequency()) > 0.001 (1 kHz) test at :1344.
  • A sub-kHz snap (e.g. 200 Hz) leaves m_flexTargetMhz under that 1 kHz threshold, so the guard doesn't re-base, and the next m_flexTargetMhz += steps*stepHz resumes from the stale off-grid target — undoing the snap. Exactly as described.

Setting m_flexTargetMhz = snapped right after applyTuneRequest realigns the accumulator with the displayed frequency, so the next guard comparison sees ~0 delta and continues from the snapped value. Units are consistent (both MHz; std::round(freq*1000)/1000 snaps to 1 kHz), and all accesses to m_flexTargetMhz here — wheel action, coalesce timer, and this snap callback — run on the main thread, so no new races are introduced.

A couple of notes, neither blocking:

  • Non-snap path unchanged: when snapped == frequency (already on-grid, > 1e-9 false) the accumulator is untouched, which is correct — in that case m_flexTargetMhz already tracks the on-grid frequency from the prior tune.
  • Test plan is manual-only. That's understandable for an RC-28 HID timing interaction, but if the automation bridge can drive an encoder tick + snap-timer window, a regression check for "sub-kHz snap sticks across the next tick" would be worth adding later given how easily this silently regresses.

LGTM.


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

@NF0T

NF0T commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

@NF0T review — PR #3940

One file, 4 lines. Precise root-cause writeup and a minimal fix.

The diagnosis is exactly right. The snap timer rounds the slice to 1 kHz, but the re-base guard only fires when |m_flexTargetMhz - s->frequency()| > 0.001 MHz. A sub-kHz snap (e.g. 200 Hz correction) falls below that threshold, so the accumulator stays pointing at the pre-snap value — the next tick resumes from there and silently undoes the snap. Setting m_flexTargetMhz = snapped right after applyTuneRequest closes the gap.

Three correctness points confirmed: units are consistent (both MHz; std::round(freq*1000)/1000 is correct), the non-snap path (already on-grid, > 1e-9 false) is correctly untouched, and all accesses to m_flexTargetMhz in the snap callback, wheel action, and coalesce timer run on the main thread — no new races. The brace addition is also the right style change (AGENTS.md requires braces on all control flow).

CI: All 6 checks pass including analyze (cpp) at 38 min. The HID timing interaction isn't reproducible in headless CI without physical hardware — manual reproduction is the right test plan here.

For future reference: a bridge-driven regression test for "sub-kHz snap sticks across the next tick" would be worth adding when the automation bridge gains encoder-tick synthesis — this pattern silently regresses otherwise. Not a merge blocker.

Tier 3 only (MainWindow_Controllers.cpp* catch-all). Merging now.

@NF0T NF0T self-assigned this Jul 1, 2026

@NF0T NF0T left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Root cause confirmed through the code path: sub-kHz snap falls below the 1 kHz re-base guard, leaving m_flexTargetMhz stale. Fix is correct — units consistent, non-snap path unaffected, main-thread-only access (no new races). All 6 CI checks pass. Tier 3 only.

Squash subject: fix(rc28): sync wheel accumulator to snapped frequency after auto-snap fires — Principle XI. (#3940)

@NF0T NF0T merged commit c3612cc into aethersdr:main Jul 1, 2026
6 checks passed

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at high effort (multi-angle + verify). The fix is correct and well-targeted:

  • Units consistent (all MHz; snapped = frequency rounded to 1 kHz, matching the 0.001 re-base threshold).
  • GUI-thread only; ordering is right (m_flexTargetMhz set after applyTuneRequest, and IncrementalTune doesn't reset it — only CommandedTargetCenter/AbsoluteJump do).
  • Genuinely fixes #3939: a sub-kHz snap no longer leaves a stale pre-snap target that undoes itself on the next tick.

Refuted two speculative concerns: 're-activation after a typed tune' (guard is false when already aligned, and it'd resync to the correct current freq regardless) and '1 kHz vs stepHz grid mismatch' (syncing the accumulator to the displayed frequency is the correct behavior; standard steps all divide 1 kHz).

One optional, non-blocking follow-up: the resync sits inside the >1e-9 guard, so a pre-existing mixed-controller edge (RC-28 + MIDI moving the slice onto the grid) can still leave m_flexTargetMhz stale. Moving the assignment outside the 'if' would close it for free — but that's pre-existing and out of scope here.

LGTM. Thanks @wa2n-code.

ten9876 added a commit that referenced this pull request Jul 2, 2026
#3961)

## Release documentation prep for **v26.7.1** (2026-07-02)

29 commits since v26.6.5. Docs-only + version bump — no code behavior
change.

### Version bump 26.6.5 → 26.7.1
The three canonical locations (per `AGENTS.md`):
- `CMakeLists.txt:2` — `project(AetherSDR VERSION 26.7.1)` →
`AETHERSDR_VERSION` (the app's version string)
- `README.md` — Current version line
- `AGENTS.md` — Current version line

### `CHANGELOG.md`
Promoted `[Unreleased]` → **`## [v26.7.1] — 2026-07-02`** in house style
(v-prefix, em-dash, "N commits since…" opener + headline), authored from
all merged PRs and categorized:
- **Added:** 3D stacked-trace spectrum (#3899), 3D dBm scale (#3937),
in-process NVIDIA BNR + AFX download-on-demand (#3902), TX meter
mouse-over readouts (#3936), SWR manual sweep range (#3885), CW sidetone
capture (#3895), KiwiSDR metadata scaffolding (#3898), bridge verbs
(#3920)
- **Changed:** BNR container/NIM backend removed, 60 fps + per-pixel GPU
FFT trace (#3958), FlexLib-sourced model capabilities (#3954/#2177), RX
speaker-latency cut (#3897), Display pane sections (#3935)
- **Fixed:** #3922, #3926, #3941, #3940, #3942, #3921, #3903, #3892,
#3924, #3891, #3890, #3900, #3947
- Fresh empty `[Unreleased]` restored above the release block.
Internal/CI/docs/self-fix PRs (#3931/#3916/#3934/#3929) omitted per
house style.

### `ROADMAP.md`
- Cycle header → "post-v26.7.1"
- NVIDIA BNR removed from **In flight** (shipped this cycle)
- Five v26.7.1 highlights prepended to **Recently shipped**

### `README.md`
- Highlights: GPU spectrum bullet now notes the **per-pixel FFT trace at
up to 60 fps** and the **3D stacked-trace** spectrum mode

### Reviewer notes
- Touches protected paths (`*.md` + `AGENTS.md` Tier-1 +
`CMakeLists.txt`) — needs `@aethersdr/infrastructure` sign-off.
- Tagging `v26.7.1` and cutting the release build are **not** part of
this PR (docs prep only).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

RC 28 resumes tuning from pre-snapped frequency when Auto-snap is enabled and knob is rotated again

3 participants