[codex] Stabilize diversity VFO flag placement#3880
Conversation
There was a problem hiding this comment.
Pull request overview
Stabilizes VFO flag placement for diversity-receive A/H pairs by making pair side-locking and overlay updates authoritative (vs. transient screen position), adding short “echo hold” windows during fast drag to suppress stale radio echoes, and using z-order to resolve overlap with nearby slice flags.
Changes:
- Add diversity metadata to slice overlays and use it to lock left/right flag sides for attached diversity pairs (plus active-group z-order handling).
- Update tune/drag paths to push frequency changes to both member overlays together and suppress stale pan/center echoes during fast drag.
- Extend the VFO flag placement test coverage for edge/locking behavior and diversity ordering.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/vfo_flag_placement_test.cpp | Adds regression tests for new placement/ordering behavior. |
| src/models/SliceModel.cpp | Emits diversityChanged when diversity role/index fields change (not just on/off). |
| src/gui/VfoWidget.h | Introduces reusable FlagPlacement + placement helper and diversity ordering key. |
| src/gui/VfoWidget.cpp | Refactors flag positioning to use the shared placement helper. |
| src/gui/SpectrumWidget.h | Extends overlay metadata with diversity fields; adds z-order helper; adds VFO-drag echo hold state. |
| src/gui/SpectrumWidget.cpp | Implements diversity-aware direction assignment, hit-testing exclusions, z-order handling, and drag echo suppression. |
| src/gui/MainWindow.h | Tracks slice-drag target/echo-hold state for overlay suppression during drag. |
| src/gui/MainWindow.cpp | Centralizes “push slice freq to overlay(s)” including diversity partner updates; threads diversity metadata into overlays. |
| src/gui/MainWindow_Wiring.cpp | Integrates drag/tuning overlay updates + echo-hold suppression into wiring paths; updates diversityChanged handling. |
| src/gui/MainWindow_Controllers.cpp | Routes controller-driven tuning updates through the unified overlay push path. |
| const bool activeDiversityPartner = [this, s]() { | ||
| SliceModel* active = m_radioModel.slice(m_activeSliceId); | ||
| if (!active || active == s || !active->diversity() || !s->diversity()) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Thanks for this, @rfoust — solid, well-structured fix. The extraction of the flag-placement geometry into a pure, testable VfoWidget::placementForMarker(...) / diversityPairOrderKey(...) and the consolidation of the three near-identical direction-assignment blocks (GPU repositionVfoFlags, software paintEvent, and vfoFlagOnLeftForSlice) into the shared assign*Directions helpers is a real readability win and removes a known drift hazard between those paths. Null-handling looks careful throughout (pushSliceFrequencyToOverlays, applyActiveVfoZOrder, and overlaysAreAttachedDiversityPair all guard their pointers), and the SliceModel change to emit diversityChanged on any of child/parent/diversity/index transitioning (not just the diversity bool) is more correct. CI is green across build/macos/windows/CodeQL.
A couple of points, both echoing Copilot — I verified both against the diff and agree they're worth addressing:
1. Stale displayMhz capture in the drag-echo singleShot (MainWindow_Wiring.cpp:619-633) — valid, minor. The lambda captures displayMhz by value at schedule time, but its own guard reads the live m_sliceDragTargetMhz. If the drag advances before the singleShot(0) runs, the lambda pushes the older captured frequency — a momentary backward nudge, which is exactly the snap-back class this PR is suppressing. A following echo would correct it, so it's transient, but since you already read the member in the guard, just read it for the push too:
QTimer::singleShot(0, this, [this, sliceId]() {
const double displayMhz = m_sliceDragTargetMhz;
if (displayMhz <= 0.0) return;
if (!m_sliceDragInProgress
&& QDateTime::currentMSecsSinceEpoch() >= m_sliceDragEchoHoldUntilMs) return;
if (SliceModel* slice = m_radioModel.slice(sliceId))
pushSliceFrequencyToOverlays(slice, displayMhz);
});2. Duplicated diversity-pair detection — valid, maintainability. The same "are these two an attached A/H diversity pair?" check now exists in four spots with slightly different shapes: sameDiversityReceivePair() (MainWindow.cpp, file-static), the activeDiversityPartner and dragDiversityPartner lambdas (MainWindow_Wiring.cpp), and overlaysAreAttachedDiversityPair() (SpectrumWidget.cpp, over overlays). The two Wiring lambdas are byte-for-byte identical apart from the slice they test, and they replicate sameDiversityReceivePair exactly. Since pairing rules for diversity_index/roles have already changed once, this is a real drift risk. The slice-based copies could collapse to one shared bool isSameDiversityReceivePair(const SliceModel*, const SliceModel*) reused at all three call sites; the overlay variant necessarily stays separate (overlays carry no panId).
Neither is blocking. Nice work, and the focused test additions for the geometry and ordering helpers are appreciated.
🤖 aethersdr-agent · cost: $2.2082 · model: claude-opus-4-8
|
Addressed the review feedback in e9a1f74:
Local validation rerun:
|
ten9876
left a comment
There was a problem hiding this comment.
Approved. Reviewed the current head (e9a1f74):
- Solid follow-on to #3866 — extracts flag geometry into pure, testable
placementForMarker()/diversityPairOrderKey()and consolidates the three direction-assignment blocks into sharedassign*Directionshelpers, removing the drift hazard between the GPU/software/vfoFlagOnLeftForSlicepaths. - Diversity pair placement is now authoritative (stable L/R sides), both overlays update together, stale echoes suppressed during fast drag, and overlap resolves by active-group z-order rather than pushing flags to a lower row.
SliceModelemittingdiversityChangedon any child/parent/diversity/index transition is more correct. - Both review notes addressed in e9a1f74: the drag-echo
singleShotreads the livem_sliceDragTargetMhz(no stale-capture backward nudge — verified), and the duplicated pair detection is collapsed into a sharedisSameDiversityReceivePair()helper used at all slice-based call sites. - Not a keying path (Principle VI N/A);
vfo_flag_placement_testextended for the geometry + ordering helpers. CI green on all 6.
Thanks @rfoust — clean fix.
Summary
Fixes the diversity-receive VFO flag instability by making attached A/H diversity pair placement authoritative over transient screen position and split-pair deconfliction. The pair now keeps stable left/right flag sides, updates both member overlays together during tune/drag paths, suppresses stale radio echoes during fast drag, and resolves overlap with nearby ordinary slice flags by active-group z-order instead of pushing flags to a lower row.
Constitution principle honored
Principle XI — Fixes Are Demonstrated. The fix was validated with focused tests plus automation-bridge captures against a live A/H diversity pair with an additional slice on the same panadapter.
Test plan
cmake --build build --target vfo_flag_placement_test AetherSDR -j8)Additional validation:
git diff --checkctest --test-dir build -R '^vfo_flag_placement_test$' --output-on-failure0x40000000; selected B and A throughslice select, captured visible panadapter screenshots, confirmed flags remain on the normal row and active group owns overlap z-ordertransmitting=false,mox=false,tuning=falseChecklist
docs/COMMIT-SIGNING.md)AppSettingscalls — use nested-JSON-under-one-key (Principle V)MeterSmoother(AGENTS.md convention; no new meter UI added)Generated with OpenAI Codex.