Repository navigation
aetherd: consolidate slice receive-control routing - #5919
Conversation
Add explicit concrete-family coverage levels ahead of M4, retain desktop versus daemon capability distinctions, and check command dispatch separately from injected radio observations. Correct Icom scope capacity to the single implemented pan stream without changing slice limits or lifecycle routing. Refs aethersdr#5890. Fixes aethersdr#5347.
Route complete capture requests through bounded transactions with verified rollback, superseded-result compensation, session fencing and block-boundary receiver adoption. Retain the legacy single-receiver DDC while later M1 work adds extraction and mixing. Pin injected worker publication and delayed-reader retirement.
Heads-up: merge order with #5924, and what the rebase needs to preserve@rfoust, I've ruled that #5924 (RTL M1) merges first, and this stack (#5892 → #5907 → #5919) rebases onto it. Details are in the rulings on #5924. This comment gives you the collision map up front, so it doesn't surprise you when the prerequisites land. The collisionA trial merge of this head (
The two PRs independently built the same guards in different shapes. This PR has typed tune requests with revision counters, What the rebase has to preserve
No action is needed until #5892 and #5907 land. This is so the refresh you already planned ("Hold for the prerequisites and refresh the diff after they merge") accounts for #5924 as well. — Jeremy (KK7GWY) Drafted with Claude Code at my direction. Generated by Claude Code |
…ch. Principle XI.
Preserve absolute receiver RF while validating whole-set DC placement. Crop genuine FFT bins for independent display pan/zoom, and admit confirmed receiver plus view intent together so refusals and rollbacks preserve observations. Add capability-gated explicit placement, accepted-capture diagnostics, and synthetic DSP/model/UI regressions. Nobara native RTL ON: full app and 27 focused tests passed. RTL OFF: full app and 15 focused tests passed. Three deliberate regressions fail and restored controls pass. Production receiver admission remains one. Scoped UX and neutral seam decisions require maintainer ratification under RFC aethersdr#5468.
Keep configured slice RF and settings fixed while a confirmed RTL capture drag moves across the tuner range. Park receivers outside the fully guarded usable capture and resume them after a bounded, verified bank transition. Expose capture membership to the desktop, automation observations, and headless resources. Center typed tunes against the actual FFT viewport, coalesce pending zoom intent, and guard parked FM filters. Cover transaction, model, worker, restore, and real widget paths with native Nobara validation.
…notice. Principle VIII. reportReceiveDispatch raised commandDropped for every refused typed request, including writes the operator never made. HL2, ANAN and RTL deliberately refuse the Kiwi suppression mute (Origin::ExternalReceiveSuppression), and the Kiwi virtual-antenna path has no Flex gate, so picking a Kiwi antenna on those radios showed "This radio doesn't support that control" with no control touched and spent the once-per-session notice. Refusals are still logged on aether.protocol, now tagged operator or compatibility origin; only operator-origin refusals emit commandDropped. Also give SliceSquelchRequest the valid() its siblings have and use it in the Flex, HL2, Icom and RTL adapters instead of four copied range checks, and log off-thread squelch writes through refuseOffThread like the other setters rather than dropping them silently. receive_intent_routing_test pins the notice split (fails with the origin guard removed); backend_receive_contract_test pins the squelch range on valid() and the Flex adapter. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
It bundles separately tracked #6096 lifecycle work and has two explicit coding-convention violations.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
This PR migrates desktop per-slice receive controls from raw Flex commands to typed backend dispatch while preserving backend-specific behavior and readback authority.
Changes:
- Adds typed DSP, audio, squelch, antenna, and lock requests across six backends.
- Consolidates guarded intent routing and reentrancy handling in
RadioModelandSliceModel. - Updates documentation, regression coverage, and the reduced command-plane baseline.
| File | Description |
|---|---|
docs/architecture/backend-receive-contract-tests.md |
Documents receive-control contracts and coverage. |
docs/rtl-m1-runtime.md |
Documents RTL typed control behavior. |
src/core/backends/IRadioBackend.h |
Adds typed desktop adapter methods. |
src/core/backends/ReceiveCommand.h |
Defines typed receive requests and results. |
src/core/backends/anan/AnanBackend.cpp |
Routes supported ANAN controls. |
src/core/backends/anan/AnanBackend.h |
Declares ANAN adapters. |
src/core/backends/flex/FlexBackend.cpp |
Encodes typed requests as Flex commands. |
src/core/backends/flex/FlexBackend.h |
Declares Flex adapters and command sink. |
src/core/backends/hl2/Hl2Backend.cpp |
Implements HL2 receive adapters. |
src/core/backends/hl2/Hl2Backend.h |
Declares HL2 adapters. |
src/core/backends/icom/IcomCivBackend.cpp |
Maps supported controls to CI-V. |
src/core/backends/icom/IcomCivBackend.h |
Declares Icom adapters. |
src/core/backends/rtl/RtlSdrBackend.cpp |
Adds confirmed RTL controls and lifecycle changes. |
src/core/backends/rtl/RtlSdrBackend.h |
Declares RTL adapters and test access. |
src/core/backends/sim/SimBackend.cpp |
Routes Demo NB and ANF controls. |
src/core/backends/sim/SimBackend.h |
Declares Demo adapter and test hook. |
src/core/backends/sim/SimSignalSource.h |
Exposes test-only generator access. |
src/gui/MainWindow_Wiring.cpp |
Learns split audio from typed intents. |
src/gui/SplitAudioProfile.h |
Translates typed audio requests for learning. |
src/models/RadioModel.cpp |
Consolidates guarded backend dispatch. |
src/models/RadioModel.h |
Declares receive-routing helpers. |
src/models/SliceModel.cpp |
Publishes typed intents with revision guards. |
src/models/SliceModel.h |
Defines intent signals and revision state. |
tests/anan_noise_blanker_readback_test.cpp |
Exercises typed ANAN NB dispatch. |
tests/antenna_alias_test.cpp |
Verifies typed antenna intent. |
tests/backend_receive_contract_test.cpp |
Covers backend adapter contracts. |
tests/fm_filter_controls_test.cpp |
Observes typed squelch requests. |
tests/phone_cw_applet_apf_test.cpp |
Verifies typed APF intent. |
tests/receive_intent_routing_test.cpp |
Covers routing, lifecycle, and reentrancy. |
tests/reroute_dead_controls_test.cpp |
Updates dead-control routing coverage. |
tests/rtl_backend_test.cpp |
Tests RTL controls and stranded-worker behavior. |
tests/rx_applet_squelch_reconciliation_test.cpp |
Updates squelch reconciliation assertions. |
tests/slice_apf_agc_off_seam_test.cpp |
Verifies APF through the typed seam. |
tests/slice_model_letter_test.cpp |
Updates Kiwi suppression expectations. |
tests/slice_model_squelch_memory_test.cpp |
Updates typed squelch-memory coverage. |
tests/split_audio_profile_test.cpp |
Covers intent-origin filtering. |
tools/check_command_plane.py |
Lowers the migrated command-plane baseline. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
rtl_backend_test uses QEventLoop::AllEvents and QEvent::DeferredDelete but relied on QCoreApplication pulling both in transitively; AGENTS.md requires direct includes because Qt's transitive set varies across supported versions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ten9876
left a comment
There was a problem hiding this comment.
Approved at 701a6327c, built and tested on Linux (full tree, no target filter).
Fixed in this pass
012ca37a1: refused compatibility-origin writes no longer raise the operator's "unsupported control" notice (RadioModel.cpp:9892).- Before: the Kiwi suppression mute that HL2, ANAN and RTL refuse showed the notice with no control touched, and used up its once-per-session slot. Found by the automated review pass.
- Proof:
receive_intent_routing_test"a refused compatibility-origin mute is logged…" fails with the guard removed and passes with it.
012ca37a1:SliceSquelchRequest::valid()replaces four copied range checks (Flex, HL2, Icom, RTL).backend_receive_contract_testpins the range and the Flex refusal. Off-threadsetSquelchnow logs throughrefuseOffThread.701a6327c: direct<QEvent>/<QEventLoop>includes inrtl_backend_test(Copilot thread).
Verified on head, earlier fixes
- HL2 squelch/APF adapters (my blocker), fixed by @rfoust in
5748e69b, with an applied-WDSP check atbackend_receive_contract_test.cpp:656-670. - Nits 3–6: comments, enum-sized epoch array, helper rename, ANF comment.
Decided by the maintainer
- Operator-moved controls the backend cannot apply refuse aloud.
- Demo AF/squelch/NB level and Icom mute/pan have no local effect, so the notice is accurate.
Refuted
- The RTL
wireWorker()hunks are not new #6096 scope. They re-land merged #6107 (c7513e125), which #5924's squasha325fb99dropped frommain. The PR body now says so. - The bot's
setLocked"redundant guard" nit: the second check runs afteremit lockedChanged, which handlers can re-enter, so it is the notify-then-dispatch guard.
Evidence
- 19 focused tests pass: receive contract/routing, split-audio, squelch memory/scale, GUI squelch/FM/APF/lifetime, Kiwi/slice-letter, ANAN NB, antenna alias, dead-controls, RTL backend/model acceptance, Flex mode intent, Icom family, HL2 dBref.
- Static gates, main vs head:
- Engine boundary: 102 findings, identical file by file.
- Touchpoint manifest: up to date, unchanged.
- Capability records: frozen at 70.
- Command plane: 400 → 369.
- Bridge on DEMO-0001 (offscreen, isolated settings, TX disabled): ANF accepted. AF gain and NB level log
refused … (operator). - CI: 5/5 green on this head. The run was created at 2026-10-04T08:23Z, after
main's tipa325fb99(06:03Z).
Not verified: live Flex/HL2/Icom/ANAN hardware; RTL with a dongle; the Kiwi path on HL2 (no Kiwi server).
|
@rfoust, thanks. This landed as squash I added two small commits on your branch before landing it:
On Copilot's split request: the RTL — Jeremy (KK7GWY) Drafted with Claude Code at my direction. |
…lay, Qt 6.12 (#6159) ## Summary Six-file release prep for **v26.10.1**, metadata and docs only. No source, test or CI changes. Cutoff: `origin/main` at `18c5245e5` (#6155, merged 2026-10-04). Anything that merges after it is folded in before the tag. **95 merged changes** (95 PRs, no direct commits) from **12 human contributors**, plus AetherClaude (2) and Dependabot (0). There are no first-time contributors this cycle. That was checked for each login by looking for a merged PR before v26.9.5's tag, not from `author_association`. ## Changelog Built from `v26.9.5..18c5245` mapped through the commit-to-PR API (`repos/…/commits/<sha>/pulls`), not from commit subjects. All 95 PRs are cited in the section. The notes lead with three things: - the sweep that makes every control on a radio without a Flex command plane either work or refuse with a reason (#6044, #6049, #6015, #6111); - the CTR2 relay (#6090, #6114, #6155) and authenticated 4O3A connections with a Peripherals page (#6008, #6027); - the Hermes-Lite 2's squelch, APF, AGC-off, RIT/XIT and FFT AVG. The structural half covers Qt 6.12 for every binary and source build (#5842, #6055) and the slice receive controls moving behind the seam (#5892, #5907, #5919; raw Flex commands above the seam 413 → 369, confirmed with `check_command_plane.py` at the cutoff). Upgrade-visible changes are called out with **On upgrading** inline: the Ulanzi default and removed MOX/TUNE pills (#5964), auto RF gain arming and law (#5976, #6062), DIGU/DIGL opening with AGC off (#6119), HL2 squelch calibration and the Auto SQL fallback (#6127), and the macOS 14.4 floor (#5842). The previous tag v26.9.5 is an ancestor of `main`, so nothing is excluded from the range. ## Docs refresh, not just version strings - **README:** Highlights "Broad hardware control" gains the CTR2 relay. Supported Hardware says direct TGXL/PGXL/Antenna Genius connections authenticate. The Other radio families paragraphs gain the HL2's squelch, APF, RIT/XIT, FFT averaging and CL1 reference, the ANAN-G2's receiver audio and radio speaker, and RTL-SDR's confirmed receive state, capture browsing and zoom FFT. In the Roadmap section, the aetherd line gains the receive controls and a CTR2 relay line is added. No maturity label changed. The six backends and seven NR engines were re-checked against `src/core/backends/` and `AetherRxProfiles.cpp`. - **ROADMAP, In flight:** - aetherd: the receive controls behind the seam. - Icom: TUNE power live, no false drop notices, the one-panadapter correction. The stale "VOX and CW break-in" leaves Remaining: `IcomCivBackend` has implemented both since #4799/#5113. - ANAN-G2: receiver audio and radio speaker. - RTL-SDR: M1, with Remaining restated from #5924's body. - Hermes-Lite 2: the v26.10.1 control parity, and the CW latency cost updated to 4096 taps. - A new **CTR2 controller relay** entry. - Nothing left In flight as finished. - **ROADMAP, Open RFCs:** diffed against `label:rfc`. #5893 leaves (closed). #6106 and #6079 join under Radios, protocol and devices. - **ROADMAP, Recently shipped:** 8 new `(v26.10.1)` entries. The list is trimmed to the v26.10.x cycle at the month rollover, and its intro names v26.10.x. Everything trimmed is still in CHANGELOG. ## Judgment calls flagged for the maintainer - **Recently shipped trimmed to v26.10.x at the rollover:** maintainer's call. This makes the validator's "only the four current-version lines lose `26.9.5`" check FAIL. The lines it lists are exactly the trimmed `(v26.9.5)` entry markers, and there is no historical mention elsewhere. That is the one FAIL below. One side effect: the trim removes ROADMAP's last mentions of #5630 and #5684, approved RFCs that shipped in v26.9.4. Both are now closed as completed, and both remain in CHANGELOG. - **A CTR2 relay In-flight entry:** maintainer's call. Wi-Fi works today. USB's host side shipped and has carried the radio's status stream to the firmware author's development firmware; it waits on a CTR2 USB firmware release. - **Intro wording with zero Dependabot updates:** "AetherClaude and no dependency updates within that total" keeps the house shape and passes the count check. The Contributors block has no Dependabot sentence. - **#6125 is filed under the seam section.** It fixes a WAN/SmartLink receive regression introduced by #5907 inside this range, so released builds never had the regression. The section states what ships: receive intents gate on `RadioModel::isConnected()`. - Ready for review, not draft: maintainer's call. ## Scope | file | change | |---|---| | `CMakeLists.txt` | `project(AetherSDR VERSION 26.10.1)` | | `README.md` | Current version line, plus the prose refresh above | | `AGENTS.md` | Current version line | | `CHANGELOG.md` | new `[v26.10.1] — 2026-10-04` section; `[Unreleased]` stays and stays empty; history below byte-identical | | `packaging/linux/io.github.aethersdr.aethersdr.metainfo.xml` | `<release version="26.10.1" date="2026-10-04"/>` first in `<releases>` | | `ROADMAP.md` | cycle heading → post-v26.10.1, plus the prose refresh above | ## Validation `check_release_prep.py --mapping mapping.json`: 29 checks, **1 FAIL** (explained above), 0 WARN. - All six version spots say 26.10.1. The CHANGELOG heading date equals the metainfo date, 2026-10-04. - `appstreamcli validate` passed. The `<release>` list is date-descending with unique versions (22 entries). - `[Unreleased]` stays empty, `[v26.10.1]` sits directly under it, and the CHANGELOG diff is exactly the inserted section, with history byte-identical to `origin/main`. - All 95 PRs in the range are cited. The only other numbers cited are #3849 (RFC), #5262 (tracking issue), #5468 (RFC), #6091 (RFC, approved and closed) and #6142 (open follow-up issue for #6119). - The intro (95/12), the Contributors counts (13 entries) and "first-time: none" are re-derived from the mapping, and they match. - ROADMAP `(vX)` markers lead with v26.10.1. Every local link in README and ROADMAP resolves. No removed heading is linked elsewhere. Code fences balance, with no duplicated table headers or doubled `---`. - **FAIL:** "only the four current-version lines lose `26.9.5`". The lines are the trimmed Recently-shipped markers; see the judgment calls above. - `git diff --check` is clean. The diff touches no `src/`, `tests/`, `third_party/` or `.github/workflows/` path. - Configure: `cmake -S . -B <scratch>` reports `CMAKE_PROJECT_VERSION=26.10.1`. No build or test was run, because this is metadata only; CI builds it. - `main` CI at the cutoff: `CI` passed on `18c5245e5`. After merge: tag the resulting **main** commit, not this branch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main gained the RTL M1 work (aethersdr#5924), receive-control routing (aethersdr#5919), the int16 cast guards (aethersdr#6089), BUILD-OPTIONS.md (aethersdr#6098) and the v26.10.1 release prep (aethersdr#6159). Seven files this branch touches merged without conflict. WdspChannel::Config now has a defaulted operator== (aethersdr#5924); this branch's txAmCarrierLevel and txFmDeviationHz take part in it. None of main's changes sits under AETHER_HL2_TX_TXA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lane, incl. two WDSP races. Principle VIII. (#6173) ## Summary Refs #6154, #6156. This doesn't close them: the sanitizer-slowdown timeouts are still open (see below). The weekly Sanitizers lane recently started building the full test tree again (#6136, #6152). Its first complete run on `main`, [37208806037](https://github.com/aethersdr/AetherSDR/actions/runs/37208806037), failed 6 tests under ASan + UBSan and 11 under TSan. This PR fixes every one of those failures that was an actual sanitizer report. **In the branch run [37230031178](https://github.com/aethersdr/AetherSDR/actions/runs/37230031178), neither lane reports a single sanitizer error.** What still fails there is timeouts, plus one plain assertion in `rtl_capture_worker_test` (below). Two of the eight commits change shipped DSP behaviour. They are vendored WDSP patches, documented in `third_party/wdsp/AETHERSDR-PATCHES.md`: - **Patch 16, transmit path.** `SetTXABandpassFreqs()` was the only one of four `TXASetupBPFilters()` callers that didn't take `csDSP`. `TXASetupBPFilters()` clears `bp1`/`bp2 run` first and re-enables them only after the FIR redesign. So a TX filter change while transmitting with COMP (and CESSB) on could run blocks with the post-compressor bandpass bypassed, which is out-of-band splatter. Fix: take the lock, as `SetTXAMode()` does. It adds no new lock order, and costs one FIR redesign stall per TX filter change, which upstream already accepts for a mode change. - **Patch 17, panadapter analyzer.** 1. `stop` and `end_dispatcher` are now atomic. 2. `input_busy` is released *after* `stitch()`, so the next frame's FFT workers can no longer overwrite results while the stitch is still copying them (a torn spectrum frame). 3. `sendbuf()` now hands off `IQout_index` inside `BufferControlSection`, closing a lost update. Only part 2 changes timing: the next frame's FFTs start one stitch later, tens of microseconds. | Commit | Lane | Finding | Fix | |---|---|---|---| | `1f9c4ed66` | ASan | `offline_health_registry_test`: stack-use-after-scope. Section 6 reuses a factory that captured section 5's block-scoped `RecorderState` by reference | `rec` moves to `main()` scope (test only) | | `cacf9fe30` + `49fa9a75a` | UBSan | `rx_applet_squelch_reconciliation`, `vfo_dsp_launcher_width`, `fm_filter_controls`: invalid `QPointer<QLabel>` downcast in `VfoWidget::eventFilter` during the collapsed label's teardown | Remove the filter before `~VfoWidget` deletes the label, **and** test the event type before the guarded-pointer comparison. The second covers the label dying *first*, after `beginDirectEntry()` `raise()`s the VFO (found in review). Regression: `gui_nested_lifetime_test` `vfoCollapsedLabelTeardownInEitherOrder`, covering both child orders | | `01d06c6d1` | TSan | `receive_intent_routing`, `tci_automation`, `tci_server_review`: lock-order inversion between two of Qt's own TLS-backend mutexes, both taken on the main thread | Anchored `deadlock:QSslSocketPrivate::tlsBackendInUse` suppression | | `f04a9ffb2` | TSan | `tci_server_review`: unlocked global `QStringList` log sink written from worker threads | One mutex for the sink and its lists (test only) | | `6f6e4a5fd` | TSan | `tx_operation_integration`: the command sink appends on the `RadioConnection` thread to a list destroyed before that thread is joined | Declare the list before `radio` (test only) | | `fbbd24087` | TSan | `wdsp_channel`: WDSP patch 16 (above) | Lock in `SetTXABandpassFreqs` | | `af9c15224` | TSan | `anan_rxdsp_handedness`, `spectrum_sequence_gap`, `wdsp_process_tally`: WDSP patch 17 (above) | Three analyzer hand-offs synchronised | | `31e249c3e` | TSan | `weather_radar_loading`: Qt's `QHttpThreadDelegate` teardown vs a main-thread reply delete. Three map tests were fetching live OSM tiles | Stub network in those three tests, plus an anchored `race:QHttpThreadDelegate::~QHttpThreadDelegate` suppression | Each suppression in `tests/sanitizers/tsan.supp` carries its reasoning and a caveat. Neither is anchored on a frame AetherSDR code runs inside. The file's DO-NOT-ADD list is untouched. Note that a suppression hides **every** stack it matches, not only the reported cycle or teardown. So zero TSan reports with these entries means no *unsuppressed* race was seen. It is not proof that every Qt TLS or HTTP-teardown interaction is race-free. **Still failing after this PR, tracked in #6154 / #6156:** - **Timeouts under the sanitizers' slowdown, with no sanitizer report.** ASan: `rtl_spectrum_averaging`, `weather_radar_loading`. TSan: the same two plus `audio_engine_rates`, `nr_stereo_independence`, `peripheral_auth_dialog`. - **`rtl_capture_worker_test`, TSan lane only:** fails the assertion "DC mode change discards old estimates and shows the actual first corrected window", with no race reported. This branch touches no RTL code, and the test passed in the same run's ASan lane. It passed in both lanes this morning, before `main` gained #5919's RTL changes. **Leak fixes (added after the first push, found by a full local LeakSanitizer sweep):** | Commit | Leak | Fix | |---|---|---| | `6121f6ad6` | `RadioModel::disconnectClientHandlesThen`: the `step` lambda captured its own `shared_ptr`, a cycle that leaked on every client-disconnect chain | Capture a `weak_ptr`; the in-flight `sendCmd` callback holds the strong reference | | `7577bab41` + `7f37cf045` | WDSP **patch 18**: `destroy_calcc()` never closed the five PureSignal `SemsPSCorr` semaphores. 86 leak blocks across 17 tests (every TX channel destroy) | Close them, and the exit event, only once the correction thread has signalled exit; on the 500 ms timeout they are not freed under a live thread. **This does not make the timeout path safe:** `destroy_calcc()` still frees `a`, which a late thread would use. That hazard predates this PR and is now #6179 | | `192e8f09a` | `phone_tx_filter_numeric_entry_test`: `sendEvent(…, new QFocusEvent)` leaked, since `sendEvent` never takes ownership | Stack event (test only) | **Why CI never reported these leaks:** the ASan lane's `ASAN_OPTIONS` include `halt_on_error=0`, and with that, a leak doesn't change a test's exit code. The test "passes" and `ctest --output-on-failure` throws its output away. Verified with `phone_tx_filter_numeric_entry_test`: it exits 0 with CI's options and 1 with plain `detect_leaks=1`. Fixing that is a `sanitizers.yml` change and is **not** in this PR. **Full local sweep (all 653 tests, leaks counted as failures):** 26 failed before these three commits. After them, the only leak left anywhere is whisper.cpp's ggml Vulkan backend registry, a process-lifetime static that only appears on a machine with a Vulkan GPU. Not ours, and CI has no GPU. The other non-leak failures are: the two known timeouts; `connection_panel_size` and `local_control_server`, which fail only on this machine with no sanitizer report and pass in CI; and `rtl_receive_pipeline` and `cw_pcm_consumer`, which fail intermittently (1 run in 3 serially, and under parallel load, respectively) with no sanitizer report. Those are flakes already on `main`, out of scope here. ## Constitution principle honored Principle VIII: every finding was reproduced before it was fixed. Each fix that could be checked locally was shown to fail without it and pass with it; the TSan-only fixes are proven by the branch dispatch. The two WDSP changes follow the vendoring rules: minimal, commented in place, and documented in `AETHERSDR-PATCHES.md` with what to keep on the next WDSP update. ## Test plan - [x] Local build passes: full tree, Debug, with CI's ASan + UBSan flags and `AETHER_SHARED_CORE=ON` - [x] **Red/green for both ASan fixes.** Reverted, all 4 tests reproduce CI's exact reports (stack-use-after-scope at `:95`, downcast at `qpointer.h:76`). With the fixes they pass 3/3. - [x] 71 WDSP, ANAN, TX, HL2 DSP, map and edited tests under ASan + UBSan. All pass apart from the 2 known 30 s timeouts. `weather_radar_loading_test` run directly: 44 passed, 0 failed, 26 s. - [x] **Red/green for the leak fixes.** In a full-suite LSan sweep on the same tree, before vs after the three leak commits: 86 `create_calcc` blocks, 5 `disconnectClientHandlesThen` blocks and the `QFocusEvent` leak, down to none. All the affected tests pass with leak detection on, `tx_operation_integration` with 421/421 assertions. Leaks from this machine only (fontconfig/pango/GTK, NVIDIA `cuInit`, ggml Vulkan) were suppressed locally, not in the repo. - [x] Sanitizers `workflow_dispatch` [37230031178](https://github.com/aethersdr/AetherSDR/actions/runs/37230031178) tested **`31e249c3e`**, the head before the leak fixes and the review fixes, **not** the final head. Zero sanitizer `SUMMARY:` reports in either lane, so every originally targeted test is green. The run still went red on the timeouts and on `rtl_capture_worker_test`. Its duplicate stickies (#6165, #6170) are closed into #6154 / #6156. - [x] Sanitizers `workflow_dispatch` on the **final head `7f37cf045`**, [37243767521](https://github.com/aethersdr/AetherSDR/actions/runs/37243767521): **zero** sanitizer `SUMMARY:` reports in either lane. ASan failed 2/656 and TSan 5/656, every one a known sanitizer-slowdown timeout. `gui_nested_lifetime_test` (the new VFO regression), `phone_tx_filter_numeric_entry_test`, `wdsp_channel_test` and `rtl_capture_worker_test` passed in both lanes. - [x] The same head **plus #6176**, so a leak fails its test, [37243769261](https://github.com/aethersdr/AetherSDR/actions/runs/37243769261), on the temporary branch `ci/integration-6173-6176` (since deleted): **no ASan, UBSan or LeakSanitizer report**, and the same timeouts only. Its stickies #6189 and #6191 are closed into #6154 / #6156. - [x] **VFO teardown red/green.** The new `vfoCollapsedLabelTeardownInEitherOrder` aborts on the `qpointer.h:76` downcast in its "label-dies-first" row with only the destructor fix. With the event-type-first check, all 10 cases pass, 3 of 3 runs. The 26 vfo/rx_applet/fm_filter/slice/lifetime tests pass. - [ ] Behaviour on a real radio: patch 16 is a TX-path change. A COMP-on filter-width change while keyed should be checked on hardware and a dummy load before it ships. - [ ] Existing tests pass (CI) ## Checklist - [x] Commits are signed (`docs/COMMIT-SIGNING.md`) - [x] No new flat-key `AppSettings` calls - [x] Code is clean-room - [x] No meter UI touched - [x] Documentation updated: `third_party/wdsp/AETHERSDR-PATCHES.md` (patches 16, 17) and the in-file rationale in `tests/sanitizers/tsan.supp`. No `CHANGELOG.md`. - [x] Not security-sensitive 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ite 2. Principle III. (#6140) **Update, `d60f4660` (2026-10-05):** `main` `12df6943` merged in; one conflict, in `docs/architecture/radio-capabilities-map.md`, both sides kept. No code of this PR changed. **Update, `dabf83e3` (2026-10-05):** one fix on top of the reviewed head: the 3D floor resync after a zoom is no longer adopted or stored as the operator's range. Maintainer ruling of 2026-10-05: this PR owns `dbmRanges[pan]`, its helpers and its accepted range; #6129 rebases onto it. Line references below are at `7a5ca748`; `MainWindow_Wiring.cpp` gains four lines after `:4269` since. **Update after review, `7a5ca748` (2026-10-05).** The change request is applied, and it narrows this PR: - **Only the Hermes-Lite 2 is covered now.** Storing and restoring FFT FPS and the dBm range is gated on an owner the backend declares, not on backend identity. The HL2 declares both. Icom, ANAN, RTL-SDR and the demo backend declare none and behave as on `main`. A demo run that showed FFT FPS restored at `295bff6e` will, by the code, not reproduce at this head; that was not run. - **#6129 (open) adds the same `dbmRanges[pan] = {min, max}` table** with a different accept-set (`min < max`, -250..100), a different gate (`!radioOwnsDbmScale`: ANAN, Icom) and a restore into the widget instead of the pan model. With the HL2-only opt-in no radio is claimed by both PRs any more. The two accept-sets still differ and this PR adopts whichever the maintainer rules. A trial merge of `7a5ca748` with #6129 at `04762258` conflicts in `MainWindow_Wiring.cpp` and the two touchpoint files; `ClientDisplaySettings.h` merges as text and then holds two definitions of `DbmRange`, `dbmRange()` and `saveDbmRange()`. The comparison is in the review reply. - **A stored dBm value is validated before it is narrowed to a float** (NaN, the infinities and ±1e308 are refused through the real store and reader). - The source-text pins match whole call statements. Refs #5678 (HL2 parity; rows P16, the dBm range, and the FPS half of P7 in our status table). No issue exists for this. #5111 (the Flex side) is not touched. ## Limits, first - **Where it is stored, since you ruled on that for #6119:** in the radio's settings document, not a flat key. Two optional tables, `fftFps[pan]` and `dbmRanges[pan] = {min, max}`, in the existing per-radio `ClientDisplay` feature document that already holds `waterfallRates`. No schema bump. - **Who may store and restore is declared per feature, by the backend.** FFT FPS: a new optional record, `PanFrameRateShaping`, field `clientPersistsFrameRate`, read through `RadioCapabilities::clientPersistsPanFrameRate()`. The dBm range: a new field in the existing `PanAmplitudeModel`, `clientPersistsDbmRange`, read through `RadioCapabilities::clientPersistsDbmRange()`. Same shape as `BackendPanAveraging::clientPersistsAveraging` (#5924). No new top-level capability bool: the count stays at the frozen 70. - **Two second conditions remain, and you may want them changed.** FFT FPS also needs `RadioModel::shapesDisplayRatesLocally()`, as the averaging path does: the restore sits where the local shaper is seeded. The range also needs absolute bins, inside its accessor: the client writes the range into the pan model, which is wrong on a radio whose bins are scaled by it or whose backend publishes its own range. Neither condition grants anything without the declaration. - **The waterfall-rate gate on `main` is not touched.** It still reads `shapesDisplayRatesLocally()` alone. - **Half of what was observed.** FFT AVG and Wt Avg are not fixed on the HL2. #5924 gave them a table in this document (`fftAverages`), kept only for a backend that declares `clientPersistsAveraging`; the HL2 declares it false. - **Not seen in a running app at this head, on any radio.** The defect was (HL2, 2026-10-01, 2 of 2 restarts: FPS 12 came back 25; a 120 dB scale came back -40 / 90 dB). - **It changes what the pan model holds, on an HL2 only:** an operator's range is now written into `PanadapterModel`. That range is absolute dB (dBFS under a dBm label). - **Families:** the Hermes-Lite 2 only, for both settings. It is the radio the defect was observed on, and it stores nothing itself. Flex, Icom, ANAN, RTL-SDR and the demo backend declare no owner: nothing is stored or restored for them, as on `main`. Widening to another family is one declaration in that family's backend, for its owner or the maintainer to make; #6129 claims the ANAN side of the range. - **Yours to move:** the FPS bounds 5..60 (the slider's own) and the plausibility bounds, moved unchanged from `MainWindow_Wiring.cpp:168` on `main` to `core/DbmRangePlausibility.h`. ## What was wrong on `main` A Flex stores both and reports them on connect. A radio with no display engine reports nothing (observed on the HL2, and fixed here for the HL2 only; line references are at `7a5ca748`): - **FFT FPS** stays at the widget default (`SpectrumWidget.h:1666`, 25) and pan wiring seeds the shaper from the widget (`MainWindow_Wiring.cpp:3320`). - **The dBm range:** pan wiring primes the widget from the model (`MainWindow_Wiring.cpp:4074`, `MainWindow_Session.cpp:1855`), the model starts at -130 / -40 (`PanadapterModel.h:279`), and on a radio that does not own the scale nothing ever moves it: on an HL2 the range command is attempted and dropped at the command plane (the HL2 leaves `radioOwnsDbmScale` at its default `true`, `RadioCapabilities.h:439`, so the gate at `MainWindow_Wiring.cpp:3779` does not stop it), and nothing echoes. - Of a pan's display settings only the waterfall rate (`core/ClientDisplaySettings.h:90-132`) and, since #5924, FFT averaging for a backend that declares it (`:48-88`) have a client store. ## The change - **FFT FPS**, where the backend declares the client its owner, is saved by the Display slider (before the adaptive-throttle return), Clone to all Pans and Reset to Defaults, and restored into the widget before the shaper is seeded from it. - **The dBm range**, where the backend declares the client its owner, is saved on a scale request or a finished drag, and restored into the pan model before the widget is primed from it, at both prime sites. FFT Floor Auto's own moves are not stored. - The pending-write key gains the field. It was (radio, pan), so two fields of one pan edited inside the 250 ms window replaced each other. - `dbm_range_plausibility_test` now includes the real predicate instead of a hand copy. One row added to the touchpoint manifest (240 with `main` at `12df6943` merged in). - A Flex is unchanged, and so are Icom, ANAN, RTL-SDR and the demo backend: none declares an owner, so every new reader returns nothing and every new writer returns before the store. Save and restore of each setting ask one predicate (`MainWindow::clientPersistsFftFps()`, `MainWindow::clientOwnsPanDbmRange()`), each reading one `RadioCapabilities` accessor. - `docs/architecture/radio-capabilities-map.md` has the new record and field with each family's value. ## Tests `client_display_pan_settings_test` (new, socket-free, real store): round trip per radio and pan; the Flex-shaped scope; ownership of both settings read through the two accessors off real HL2, Icom, ANAN and Flex backends (the HL2 stores and restores; the others and an undeclared backend write nothing, read nothing and leave a held document byte for byte); refusals on write and read; a newer schema; coexistence with an `fftAverages` table in #5924's shape; two fields in one deferral window. `spectrum_overlay_fft_fps_bounds_test` (new, real widget, in a `TestSettingsProfile`): the slider runs the stored bounds. **One source-text pin block, and its claim:** the order and presence of the save and restore calls inside `MainWindow` methods, which link into no test, and that the save and the restore of each setting ask the one predicate. Fail-before, one build and one run per row. The two ownership rows are at `7a5ca748`; the other rows were measured at `295bff6e` and not run again: | mutation | failing checks | |---|---| | this branch | 0 | | `main`'s wiring, and a store that neither reads nor writes the new tables | 17 | | pan wiring does not restore FFT FPS | 1 (source text) | | the store ignores who owns the value on read | 1 | | the pending-write key has no field | 2 | | both ownership accessors return true for every backend | 33 (8 each for Icom, ANAN, Flex and an undeclared backend, and the relative-bins check) | | both ownership accessors return false for every backend | 8 (7 HL2, and the absolute-bins control) | | a stored range is not validated on read | 1 | | a scale request does not adopt the range | 1 (source text) | | the writer replaces the whole document | 6, `fftAverages` and the waterfall rate among them | | the stored FPS maximum is 61 | 1 (the slider bounds test) | Full ctest, macOS, Qt 6.12.0, at `d60f4660` (`main` `12df6943` merged in, no code of this PR changed): 645 of 646 pass at `-j8`; the one failure is `peripheral_auth_dialog_test` (#6008), which also fails alone. The run before it, at `7a5ca748` (`main` `85f2a124` merged in): 644 of 646 pass at `-j8`. Two failures, neither from this PR: `peripheral_auth_dialog_test` (#6008; diagnostic on #6134), which also fails alone, and `vkamp_connection_test` (#5254), which passed when rerun alone. `hl2_receiver_count_restart_paced_test` and `libre_radar_test` passed at `-j8` in this run. Static checks (`static-checks.yml`, stdin from `/dev/null`): the script steps of the `Static checks` job pass, including the touchpoint manifest (240, up to date) and `check_capability_records.py --strict` (70 booleans, at the frozen 70); colour ratchet +0. `check_a11y` on the three changed GUI files: no findings. The `Sanitizer option configures` job was not run locally. ## With the open PRs - **#5924** (merged, and merged in here at `295bff6e`): it conflicted in `ClientDisplaySettings.h`, `MainWindow.h` and `MainWindow_Wiring.cpp`, all resolved by keeping both sides. The tables do not overlap and its pending-write key has its own prefix. - **#5919** (merged) is in this branch through `main`. - #6117 and #6115 (both merged) are in this branch. - **#6129** (open, head `04762258`): see the update block at the top. - **#6139** (open, ours): both PRs add to `RadioCapabilities.h` and to each backend's `capabilities()`, in different places. A trial merge of `7a5ca748` with its head `98be49a1` is clean; the merged tree was not compiled. ## Not verified - The restore in a running app, on any radio, at this head. - The older rows of the fail-before table at this head (see Tests). - Linux and Windows: built and tested on macOS only. - The 3D view in a running app. Its floor resync after a zoom is excluded from the adopt and the store by a guard (`dabf83e3`) that is pinned as source text only; the path was not driven with frames. Reset to Defaults does not reset the dBm range; it did not before. **Known red on `main`:** `peripheral_auth_dialog_test` (#6008) fails on this Mac at this head, as it did on clean `main` at `22507580` ("Clear code or Status is clipped at the default dialog size": a serial-port name on this host widens the page; diagnostic on #6134). Clean `main` at `85f2a124` was not built here. `hl2_pan_create_async_test` passed; `vkamp_connection_test` (#5254) failed at `-j8` and passed alone. 73, ON8ST 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ciple IV. ## Summary WDSP ships two impulse blankers. #5824 landed the first (ANB, `nob.c`); this adds the second (NOB, `nobII.c`), which deskHPSDR, pihpsdr and Thetis all call NB2. Instead of gating the blanked window to zero it reconstructs it from the samples either side: better when the impulse lands on a wanted signal, worse when the noise is dense enough that those samples are not clean. Every reference client offers both and replaces neither, and so does this. Host-side DSP on our own IQ throughout. No wire protocol, no radio firmware, and no behaviour change for a Flex or an Icom, whose blankers are the radio's. - **`WdspChannel` opens both stages** for the life of a receive channel and runs at most one: `processIq` feeds exactly one stage per block, chosen by the kind. Both are created even when no blanker is on, because `create_nobEXT` allocates and a lazily built stage would allocate on the real-time path the first time someone clicked NB2. - **The transmit hold covers both**, for the reason it covered one: the trigger is relative to a running average, so a transmit period of zeros would decay that average and gate the first real receive samples. - **Kind and fill ride #5919's typed route** (ruling 1). `SliceDspRequest` gains `kind` and `fill` members, carried on every `Feature::Nb` request, and one new field, `Field::Fill`. A change of blanker is an ordinary `Field::Enabled` request: `enabled` says whether one runs, `kind` says which, so a radio with one blanker (Flex, Sim) reads `enabled` exactly as before. `SliceDspRequest::requestedBlanker()` derives the three-state answer in one place, and a request built without a kind still means the first blanker. There is deliberately no `Field::Kind`: the NB button calls `setNbKind()` on every radio, so a new field would have reached Flex and Sim, which do not handle it. - **Host backends apply it through one verb**, `IRadioBackend::setSliceNoiseBlanker(id, kind, level, fill)`, which the HL2, ANAN and Icom `requestSliceDsp` call. Kind, level and fill arrive together, so no ordering between them exists. Icom treats anything but `Off` as on. Flex refuses `Field::Fill` as `Unsupported` rather than encoding it; the fill is only reachable where `hasHostNoiseBlanker` is set (HL2, ANAN), so no operator sees that refusal. - **Two enums, one conversion.** `core/backends/NoiseBlankerKind.h` is the seam's vocabulary; `WdspChannel` keeps its own, because `core/dsp` is a leaf. `core/backends/WdspNoiseBlanker.h` holds the conversion, with `static_assert`s that fail the build if an existing value is renumbered. - **`SliceModel` gains `nbKind()`/`nbFill()`, and `nbOn()` is now derived from the kind**, so there is one source of truth. `setNb(bool)` keeps its meaning: `true` selects the first blanker (ruling 2). rigctl's `NB`, SmartCat's `NB`, TCI's `rx_nb_enable`, `get slice`, the MIDI mapping, the keyboard shortcut and the band-stack entry are untouched. A radio's bool echo cannot downgrade a host NB2 to NB. - **UI: the NB button cycles Off → NB → NB2 → Off** where this host runs the blanker, and stays a plain toggle elsewhere (ruling 2). The label follows the running kind; the `objectName` does not, since the bridge addresses the button by it. An **NB2 fill combo** appears under the DSP grid while NB2 runs, and leaves with the NB button in FM (ruling 4). - **Bridge** (ruling 3): `slice dsp nb2 on <level> <fill>`, refused on a radio without `hasHostNoiseBlanker`, and `get slice` / `get hostnb` report the kind and fill beside the existing `on`. - **`aether_wdsp.h` gained the NOB declarations.** `nobII.c` was already compiled; only the curated header was missing it. ## Review and rulings | Item | Where | |---|---| | Blocker 1: #5919 removed the routing this was built on | Rebased onto `85f2a124` and moved onto `SliceDspRequest` in `340d00b8` | | Ruling 1: widen `SliceDspRequest` with kind and fill | `340d00b8` | | Ruling 2: the cycling button, and `setNb(true)` dropping NB2 to NB | Unchanged, as accepted | | Ruling 3: the bridge surface | Unchanged, as accepted | | Ruling 4: the fill row showing in FM | `3ef4cd36` | | Blocker 2: the run-flag ratchet | Removed in `3ef4cd36` | | Nits: stale line cites, the retired-test reference, the `static_assert` claim, comment length | `3ef4cd36` | **Blocker 2.** The review's mutations showed the ratchet guarded an inert property. A run flag left on the unchosen stage blanks nothing, because `processIq` never feeds that stage. The one mutation that does double-blank (both stages fed, the ANB run flag left on under NB2) passed the ratchet and failed DISCRIMINATION 2 in `hl2_noise_blanker_test`. `tools/check_nb_exclusivity.py`, its `Static checks` step and its `docs/agents/backends.md` entry are gone. The test's closing comment now names the discrimination checks as the exclusivity pin, and the run-flag comment in `WdspChannel::setNoiseBlanker` names the dispatch as the guarantee. **Ruling 4.** The fill row follows `m_nbModeOk`, and `refreshNbControls()` now runs at both places that recompute it: the `modeChanged` handler, and `syncFromSlice()`, whose earlier refresh ran before `m_nbModeOk` was set. The shared DSP level row is unchanged. ## Rebase onto `85f2a124` Main was 38 commits ahead and the branch was `dirty` in eight files. The move onto #5919 is a port, not a marker resolution: - **`SliceModel`** no longer has `noiseBlankerCommandIssued`. `setNb`, `setNbKind` and `setNbFill` publish through main's `notifyReceiveDspIntent`, and `currentDspRequest()` fills `kind` and `fill` into every Nb request. The per-field intent revisions grow from two fields to three. - **`RadioModel`**: the block of direct receive-DSP `connect()`s this branch carried is dropped; `wireSliceReceiveIntentsToBackend()` owns that wiring now. - **`WdspChannel.h`**: main's `FmReceive` and this branch's two enums landed at the same spot; both kept. - **Tests from main, ported**: `backend_receive_contract_test` (from #5919) asserted `Config::noiseBlankerEnabled`, which this branch replaces with the `noiseBlanker` kind, so it now asserts `Impulse`. `anan_noise_blanker_readback_test` keeps main's typed dispatch. New rows in the contract test pin that a Flex reads a host kind as `nb=1` and refuses the fill with no wire text, and `noise_blanker_kind_model_test` asserts on the `SliceDspRequest` the model emits. Main has since taken two Icom commits (to `12df6943`). They touch `IcomCivBackend.{h,cpp}`, which this PR also changes. The merge is clean, and neither side uses a signature the other changed. CI on `3ef4cd36` runs on the merge with `12df6943`. Earlier rebases, onto `b714cd3d` and `732b99a9`, resolved comment-prose conflicts from #6072, accepted #6075's retirement of `wdsp_nb_hold_invariant_test.cpp` (its behavioural successor `wdsp_nb_hold_test` is ported), kept #5982's squelch API, and ported two tests main had added since the earlier bases: `hl2_rxdsp_adaptive_taps_test.cpp` and `reroute_dead_controls_test.cpp`. ## Constitution principle honored Principle IV: clean-room. WDSP's NOB is in-tree. deskHPSDR was read for which knobs exist and what its defaults are (the 3-way choice, the shared threshold, the zero-fill default), and no code was copied. Principle VIII: the blanking, the fill reaching WDSP, single-stage dispatch, the model's echo handling, the request shape, the fill row's FM rule and the applied-state readback are each pinned by a test with a mutation run behind it, listed below. Squashed-from: #5966 Co-authored-by: tropo1234 <299180028+tropo1234@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#5974) ## Rebased on current main — 2026-10-06 Current head: `ebb35cfd20892431758fd8a165b7c66357b74090`. Base stack starts at main `5f09371e25ffc032666d36f0ea265dc506621066`; parent PR/branch is unchanged. This is a signed replay of the existing increment. It preserves main's unified window chrome and release debug-symbol archives, alongside the RTL dependency packaging and explicit Digital-OFF release flags. Generated touchpoint counts were regenerated from the combined sources. Fresh rebase validation on analog `ca3a36bd`: native Apple Silicon / Qt 6.12 / RTL required / Digital OFF application build succeeds. **51/52 selected CTests pass** in 226.55 seconds: all 47 RTL regressions and four additional UI checks pass. The new upstream `unified_title_bar_test` fails its Alt+F/menu-rounding assertions under Mac offscreen rendering; an independent rebuild and run on clean main `5f09371e` reproduces the identical two failures. This is an inherited test failure, not a green full-suite claim. Static engine/routing/capability, generated manifests, registration/frozen-gate, theme and build-options checks pass; package-helper tests pass 9/9 and required-RTL configuration checks pass 16 combinations. The Digital increment and vendor trees are unchanged by replay; decoder-ON tests and release packaging were not rerun for these heads. Hosted checks are being refreshed by the push. All validation recorded below predates this rebase and remains evidence for the specific revisions named there. No new live-RF, capacity, or release-package qualification is claimed. Readiness is unchanged; Digital #6002 remains draft. --- ## Previous checkpoint record — 2026-10-05 ## Scope Analog WFM backend and applet integration under RFC #5468, based on the nine-file DSP foundation #5973 and main `035a858356949a0c902e286ee5250c04f5ef86c0`. Head: `b8aba26e677f49f07d155685e5f7bef8d2bfe425`. Parent: #5973. Digital #6002 / RFC #6079 is not a dependency. The selected RTL WFM receiver exposes Mono/Auto stereo, 50/75 µs deemphasis, bandwidth and measured pilot/stereo diagnostics. Accepted backend state drives readback and per-device persistence. The `broadcastFmReceive` capability is documented for all six backend families; RTL advertises the implemented controls and other families do not. WFM requests use the existing typed receive dispatcher through `SliceWfmRequest` / `requestSliceWfm`, including thread, current receiver identity, ownership, mode, value and backend-support guards. Refusals use the shared reporting path. This carries the #5919 request-routing contract into WFM. Speaker renegotiation prefers 48 kHz only for an accepted 48 kHz producer. If opening the new sink fails, it makes one bounded attempt using the previous rate policy and reports failure if neither opens. Producer-domain buffers, DSP and rate state are preserved. This shared AudioEngine behavior affects every backend family. The associated recorder-rate test follows producer rate rather than inferring it from the speaker sink. The opt-in nonblocking WFM exchange uses eight slots: 42.667 ms total exchange buffering, 32 ms above the legacy depth. Existing blocking/legacy owners retain their prior depth. Vendored WDSP exchange changes and test exports are included. Generic ContainerWidget managed presentation is used by the WFM applet; the default WFM tile placement applies to RTL. ## Validation and remaining decisions Exact checkpoint, native Apple Silicon / Qt 6.12 / optional decoder OFF: 4/4 rebuilt tests pass in 17.10 seconds (`wfm_controls_test`, `audio_engine_rates_test`, `fm_filter_controls_test`, `rtl_model_acceptance_test`). Removing speaker recovery produces six failures in the rate test; restored code passes. The regression injects failed opens, checks bounded fallback and actual-rate stereo rendering, and preserves producer/DSP/lifecycle state. Static ratchets and generated manifests pass; independent source review found no remaining typed-WFM routing defect. Historical operator stability on `7601593f` is not a new-head soak. An earlier Mac filter/stereo transition intermittently withdrew a WFM receiver; passing diagnostic reruns did not establish a root cause, and this remains unresolved. Maintainer review still covers WFM applet/default presentation, the typed public seam and vendor exchange/test surface, and the buffering tradeoff. The approved RTL-only free-pan and confirmed-control seam from #5924 are not reopened. Normal admission remains one receiver; 2/4/8 evaluation and capacity qualification belong to the delivery increment. No Digital approval is requested. Principles II, V/XIV and XI: accepted state, scoped persistence and demonstrated behavior. --- 73, Ozy **K6OZY** · Codex
#6255) Refs #5678 (HL2 parity rows), #5263 (the drop notice). No `Fixes` line: I found no issue for this. ## Limits first - **Nothing here was seen in the running app or on a radio.** The defect is a source reading of `main`; the fix is tested on `RadioModel` with an injected backend, and the four `MainWindow` call sites are pinned as text only. - **This is not an M4 conversion.** The `display panafall set` text is not replaced by a typed intent. It moves out of `MainWindow_Wiring.cpp` into the two wire sites `RadioModel` already has. `tools/check_command_plane.py`: `MainWindow_Wiring.cpp` 20 to 14, `RadioModel.cpp` unchanged at 135. - **One design choice is yours: the Black Level gate.** It reads `RadioCapabilities::panBinsAbsolute()`, the same proxy for the row unit the renderer reads (`radio-capabilities-map.md` calls it a proxy). If the row unit gets its own signal later, this gate should follow it. An undeclared `panAmplitude` reads as not absolute, so a backend that computes dB rows and forgets the declaration gets a false notice, not a missing one. - **On Icom, Black Level keeps its notice, also when it comes from Clone to all Pans or Reset display defaults.** Reset turns auto black on, so the value it writes is not in use at that moment; the notice is still raised there, as it is on `main`. I did not make that case quieter. - **ANAN and RTL-SDR are reached** (both declare `binsAbsolute`), and the test does not run with their capabilities: only Hermes-Lite 2, Icom and Flex. - **Two other false notices on a Hermes-Lite 2 are not touched:** the dBm scale (`sendDbmRangeCommand`, beside the dBm-range work of my #6140, now merged) and `cw break_in` (left open by #6111). - **The test ends in a source-text pin** (six checks). It names its claim. - The branch is rebased onto `121340c0`, with no conflict; head `182bb99c` adds two review commits on `1935624d`. Two open PRs edit the same files: #6241 merges with this branch without conflict; #6199 conflicts with it only on adjacent new lines in the `tests/tests.cmake` test list (it also conflicts with `main` itself in `RadioModel.cpp`, independently of this PR). Neither touches the four sites, the two setters, or the gain and black text. Neither merged tree was built. ## What was wrong on main On a backend with no Flex command plane, four Display-panel controls set their value on the `SpectrumWidget`, which draws it, and then also sent Flex wire text: | control | site in `src/gui/MainWindow_Wiring.cpp` | text | |---|---|---| | WtrFall Gain | `wirePanadapter`, `SpectrumOverlayMenu::wfColorGainChanged` handler | `display panafall set <wf> color_gain=N` | | Black Level | `wirePanadapter`, `SpectrumOverlayMenu::wfBlackLevelChanged` handler | `display panafall set <wf> black_level=N` | | Clone to all Pans | `MainWindow::cloneDisplaySettingsToAllPans` | both, per target pan | | Reset display defaults | `wirePanadapter`, `SpectrumOverlayMenu::displaySettingsReset` handler | `color_gain=50`, `black_level=15` | Each went through `RadioModel::sendCommand()` to `sendCmd()`, which has no command plane to write to, logs the drop and emits `commandDropped`. `MainWindow` shows that once per session as "nothing was sent to the radio". So the notice was raised for a control that had acted, and the one-shot was spent. `RadioModel::setWaterfallColorGain()` and `setWaterfallBlackLevel()` already return early without a command plane. The four sites did not use them: those setters address the active pan, the sites address a named one. Manual Black Level acts on a Hermes-Lite 2 since #6115 (`WaterfallLevelMap::manualBlackThreshold` on absolute-dB rows). ## The change - `RadioModel` gains `setWaterfallColorGainFor(panId, gain, autoBlack)` and `setWaterfallBlackLevelFor(panId, level)`. All four setters go through two private senders, `sendWaterfallColorGain()` and `sendWaterfallBlackLevel()`, which hold the only wire site and the only command-plane gate for each key. - The four sites call those setters after the widget call, and pass the pan's `wfAutoBlack()` with the gain. They no longer build wire text. - **WtrFall Gain** is withheld without a command plane where the client draws it: auto black on, or `panBinsAbsolute()`. With manual black on any other row (Icom), every row is black whatever the gain, so it goes to `sendCmd()` and is reported. - **Black Level** is withheld without a command plane only where `panBinsAbsolute()` is true (Hermes-Lite 2, ANAN, RTL-SDR). Elsewhere it goes to `sendCmd()` and is reported. On Icom the manual black point is still in tile units against a dB row, so every position draws black: the slider does nothing there and says so. - The connect-time replay (`setWaterfallBlackLevel()` from `panadapterInfoChanged`) stays quiet on every backend with no command plane, as on `main`. No operator moved anything. - No capability is added and no family is named. - `docs/architecture/radio-capabilities-map.md` records the flag's new consumer. **A Flex is sent what it is sent today**: same text, same pan, same order. The sites used `sendCommand()` and the setters use `sendCmd()`; the only thing `sendCommand()` adds is the pan-ownership gate, which matches `display pan set ` and not `display panafall set `. ## How this sits with the #5919 review That review kept the notice for an operator-moved control the backend cannot apply, because nothing happened and the notice says so. It was about receive controls; nobody has ruled on display text, and I am not reading it as covering this. The rule used here is narrower than "no command plane, no notice": the send is withheld only where the control has acted in the client. Where it has not (Black Level on Icom, and WtrFall Gain on Icom with manual black), the notice stays and the test pins it. If you would rather keep the notice on these four until pan commands get a typed intent, close this and I will leave them. ## Tests `pan_display_drop_notice_test` (new, socket-free, 41 checks). The injected backend carries `capabilities()` read off a real `Hl2Backend`, `IcomCivBackend` and `FlexBackend`; panes are made the way a backend without Flex status makes them. - **Hermes-Lite 2 capabilities, no command plane (12 checks):** the renderer's law changes with gain (auto and manual black) and with the manual black level for a dB row; none of the four raises `commandDropped`; the same gain text sent raw is still reported, so the alarm is live in the fixture. - **Icom capabilities, no command plane (13 checks):** with auto black gain raises nothing; with manual black no gain changes a dB row and gain raises `commandDropped`, with the exact text; every Black Level position draws a dB row black and the slider still raises `commandDropped`; Clone reports the black level only with auto black, and both keys with manual black; Reset reports the black level only; the connect-time replay raises nothing. - **A command plane (10 checks):** the wire text compared whole through a command sink on an unopened `RadioConnection`, for each control, for the target pan and not the active one, gain before black. With absolute-dB rows and a command plane the black level is still sent. - **Source-text pin (6 checks).** The claim: each of the four sites sets the widget and then calls the model setter, and `MainWindow_Wiring.cpp` builds no gain or black wire text of its own. No behavioural seam reaches it: the sites are `MainWindow` lambdas and no test target constructs a `MainWindow`. It shows how the sites are written, not that they run. **Mutations**, each as a scratch commit on `182bb99c` through the full suite: | mutation | failing checks of 41 | other tests | |---|---|---| | both gates removed, the four sites as on `main` | 15 | `icom_family_test` | | Black Level gated on `hasCommandPlane()` alone | 4 | none | | colour gain ignores auto black (row unit only) | 4 | none | | colour gain ignores the row unit (auto black only) | 4 | none | | colour gain always withheld (the `1935624d` rule) | 2 | none | | the connect-time replay reports its drop | 1 | `icom_family_test` | The legs share one capture of dropped commands, so a wrong drop is also counted by the checks after it in that leg. Full ctest on macOS (Qt 6.12.0, Ninja, RelWithDebInfo) at `182bb99c`: 654 of 658 pass, at -j8 and on the serial rerun. The four failures are known on clean `main` on this machine, each with only its known check lines: `control_receive_test` ("RTL adjustable filter qualification excludes legacy WFM", #6248), `vkamp_connection_test` ("commands after a disconnect are dropped, not queued", #5254), `peripheral_auth_dialog_test` (#6008, a serial-port name of this host) and `unified_title_bar_test` (its two menu checks, #6224). This PR changes `MainWindow_Wiring.cpp`, but only the four display sites; the same two title-bar checks fail identically on two other branches at the same base that do not touch it. Nothing else this PR touches is built by those tests. Every script step of the `Static checks` job was run locally at `182bb99c` with stdin from `/dev/null` and is clean: colour ratchet +0, `gen_touchpoint_manifest.py --check` up to date with no change, `check_a11y.py` on the one changed GUI file with no findings. The `Sanitizer option configures` job needs the CI image and was not run. ## Not verified - Nothing in the running app; nothing on a Hermes-Lite 2, an Icom or a Flex. - Whether each of the four controls is enabled and reachable on a Hermes-Lite 2 today. - That the Icom Black Level slider is dead, and WtrFall Gain with it under manual black, is the renderer's arithmetic plus `IcomCivBackend` sending its scope rows through `toDbm`; I have no Icom. - That the three sites pass the pan's own `wfAutoBlack()` is pinned by source text only. - ANAN and RTL-SDR, beyond their `binsAbsolute` declaration. - Linux and Windows builds: CI will say. 73, ON8ST 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…I. (#6139) **Update after review, `ea2cf144` (2026-10-05):** ownership of the flag is now declared, not inferred. Only the Hermes-Lite 2 declares the client its owner (`RadioCapabilities::txWaterfallClientFlag`, `clientPersistsFlag` true). The demo, ANAN, Icom and RTL-SDR declare nothing and keep `main`'s behaviour: the toggle sends the wire text, the model stays false, nothing is stored or restored. That is narrower than this PR was. On the demo, `txwaterfall on` answers with the radio-echo note again. Full suite at this head: 644 of 645; the one failure is `peripheral_auth_dialog_test` (#6008), which fails on clean `main` too. Source line numbers below are `main` at `85f2a124`. Refs #5678 (HL2 parity; "Show TX in Waterfall", row P30 of our status table). No issue exists for this. ## Limits, first - **Nothing was keyed.** On an HL2 the toggle now sets the flag. What the waterfall draws during an over on an HL2 has not been seen, and what that radio's receive path reads while it transmits has not been measured. A `true` model flag shows neither the colour law nor real receive IQ while keyed; the demo cannot key at all. - **The fix has not been seen in a running app.** The dead toggle was (2026-10-01, bridge verb, 6 of 6 polls false). The fix is tested socket-free on the real models and dialog. - **It reaches the Hermes-Lite 2 only.** The request, the store and the restore are gated on one accessor, `RadioCapabilities::clientPersistsShowTxInWaterfall()`, which reads a declared per-feature record. Flex, Icom, ANAN, RTL-SDR and the demo leave the record absent. On Icom, ANAN, RTL-SDR and the demo the toggle is as dead as on `main`. Engaging another family is one declaration, for that family's owner or the maintainer to make. - **Yours to move:** the keyed rows take the colour law a Flex's keyed rows use (`dbmToWaterfallLevel`). On an HL2 they are absolute dB (dBFS under a dBm label); whether the law suits them is unknown until someone keys one. - **Persistence is in the radio's settings document**, not a flat key: one optional bool, `showTxInWaterfall`, in the existing per-radio `ClientDisplay` feature document. No schema bump. Written and read only where the backend declares the client its owner. ## What was wrong on `main` On a Flex the flag is the radio's: `transmit set show_tx_in_waterfall=` tells it, and its transmit status echoes it (`FlexBackend.cpp:1193`). That echo is the only writer of `TransmitModel::showTxInWaterfall()`. - The Radio Setup button sends the wire text unconditionally (`RadioSetupDialog.cpp:2717`), and so does the bridge verb (`AutomationServer.cpp:3599`). - A radio with no command plane drops it (`RadioModel.cpp:9764`) and nothing echoes, so the flag can never become true. The demo has a command plane and no handler for the flag, so nothing echoes there either. - While keyed, the pane holding the TX passband draws rows only when the flag is true (`SpectrumWidget.cpp:8488`). With it false that pane's waterfall stands still for the whole over. There is something to draw: the HL2 mutes its demodulator while keyed and the spectrum keeps running on real IQ (`Hl2RxDsp::setAudioMuted`). ## The change - `RadioModel::requestLocalShowTxInWaterfall(bool)`, the shape of the existing `requestLocalPanWeightedAverage`: where the backend declares the client the owner of the flag it applies the flag and returns true; everywhere else it returns false and the caller sends the wire text, which only a Flex answers. No raw command is added above the seam (`check_command_plane`: 400, baseline 400). - It writes the model through `TransmitModel::applyChanges`, the path an echo takes. - It stores the value per radio, on the same declaration. A disconnect clears the transmit model (`TransmitModel.cpp:43`) and no status puts the flag back, so `RadioModel::onConnected()` restores it, on the same declaration. - The Radio Setup button and the `txwaterfall` verb ask the model first. - `RadioCapabilities::txWaterfallClientFlag`: a new optional per-feature record with one field, `clientPersistsFlag` (no default). Engaged by the HL2 only. No top-level capability boolean is added (`check_capability_records`: 70 of 70). `SpectrumWidget` is not changed. Default unchanged: off. ## Tests `show_tx_in_waterfall_client_flag_test` (new, socket-free, real backends): on the real `Hl2Backend`, request, store per radio, restore on connect, and the bridge verb; on the real Icom, ANAN, RTL-SDR and demo backends, each with a `ClientDisplay` document that already holds the flag, nothing is restored, the request declines and the document is untouched; the same on a real `FlexBackend` (the echo still works) and on a backend that engages the record with the field false; the bridge verb's radio-echo note on Flex and on the demo; the store's refusals. `radio_setup_show_tx_waterfall_test` (new, production dialog, real backends): on an HL2 a click sets the model and sends no wire text; on an Icom, which declares no owner, and on a Flex the click sends the wire text and the model is not set. No source-text pin. Fail-before, one build and one run per row, failing checks in the two tests: | mutation | client flag test | dialog test | |---|---|---| | this branch | 0 | 0 | | `main`'s behaviour (request declines, connect restores nothing) | 11 | 1 slot | | the request does not write the model | 5 | 1 slot | | the connect edge does not restore | 1 | 0 | | the request is taken on a Flex too | 4 | 1 slot (Flex) | | the request does not store | 3 | 0 | | the button sends the wire text unguarded | 0 | 1 slot | | the bridge verb skips the local route | 2 | 0 | | the store ignores who owns the flag on read | 1 | 0 | | the ownership accessor is true for every backend | 40 | 2 slots | | the ownership accessor is false for every backend | 15 | 1 slot | The two accessor rows were measured at `ea2cf144`, one build and one full run each. The other rows were measured at earlier heads, before the opt-out checks existed, and were not re-run. Full ctest, macOS, Qt 6.12.0, at `ea2cf144`: 644 of 645 pass. The one failure is not ours; see below. Static checks (`static-checks.yml`, stdin from `/dev/null`): clean. `check_a11y` on `RadioSetupDialog.cpp`: 9 warnings, the same 9 as `main`, none on lines this PR touches. `check_capability_records`: 70 booleans, frozen at 70. The steps were run from this branch's `static-checks.yml`; `main`'s newer `check_build_options` steps are not on this branch and were not run. ## With the open PRs - #5924, #6117 and #6115 are merged and in this branch. #5919 is merged into `main` and is not in this branch. - #6140 (open, `7a5ca748`) adds its own records to `RadioCapabilities.h`; a trial merge with this head is clean and the merged header still counts 70 booleans. A trial merge with `main` `85f2a124` is clean. Neither merge result was built. ## Not verified - Anything keyed; see Limits. - Linux and Windows at this head. ANAN, Icom and RTL-SDR on a radio: they keep `main`'s behaviour by declaration, checked on their real backends, never connected. - A running app at this head, the demo included. - That the main window hands a restored flag to the spectrum widget. **Known red on `main`:** `peripheral_auth_dialog_test` (#6008) fails on this Mac at `ea2cf144` with the diagnostic it prints on clean `main` at `22507580` (pasted on #6134): "Clear code or Status is clipped at the default dialog size", `dialog 960x680, viewport 658, page minimum 784, horizontal scroll max 126` (a serial-port name on this host widens the page). Clean `main` was not re-run at `85f2a124`. `hl2_pan_create_async_test` and `vkamp_connection_test` passed here. 73, ON8ST 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Robbie Foust <rfoust@gmail.com>


Summary
Closes #5917. Continues the accepted aetherd RFC #3849, #5262 M4 and #5554 §2.3 with one consolidated desktop receive-control migration. It does not finish those parent trackers or add headless audio/control features.
#5907 and #5924 have merged. #5924 landed on 2026-10-04 as signed squash
a325fb99; signed integration merge5748e69breconciles that current main with all newer #5919 fixes, without a force-push. It preserves the landed RTL parked/free capture pipeline, current-main squelch scale/UI and Radio Setup test dependencies, HL2 APF/squelch/AGC-off additions, per-receiver RIT/XIT and WAN receive admission. Jeremy's merge-order ruling is satisfied. Jeremy's Oct 3 ruling ratified the prerequisite RTL scope and seam/include additions. The effective remaining diff is 37 files for this receive-control migration. HD Radio decoding and HD consent remain outside this PR. The local-success/unsupported classification is decided (maintainer ruling, 2026-10-04): an operator-moved control the backend cannot apply refuses aloud through the existing once-per-session notice and log; refused compatibility-origin writes (Kiwi suppression mute, NRS profile restore) are logged without the notice.SliceModel: the current-main command-plane count drops 400 → 369, without double-counting aetherd: migrate receive controls behind the backend seam #5907's seven sites.RtlSdrBackend::wireWorker(), read-error rule-6 check) and its stranded-worker regression inrtl_backend_test, which RTL M1: confirmed receive state, free capture browsing and finer zoom #5924's squasha325fb99dropped frommain; adapted to the token-qualified pipeline, not extended. IRadioBackend rule 6: spectrum has no base-class gate (RtlSdr/HL2 forwards unverified) #6096's base-class spectrum choke point stays with that issue.The new adapters are in-process C++ contracts, not wire-protocol methods.
ReceiveDispatchdistinguishes dispatch, client-only lock and unsupported requests; it is not hardware acknowledgement. Existing backend hooks remain for daemon/internal callers. Desktop support does not expand daemon capabilities or bypass its authorization/admission/readback rules.Not included: TX, RIT/XIT, FM repeater/digital offsets, diversity/session lifecycle, panadapter command conversion, DAX/PCM transport, new radio features, UI redesign, settings or dependencies. Flex manual notch remains unsupported (TNF is separate); no Demo capability flip or independent mixer is invented.
Constitution principle honored
Principle II: dispatch and observation remain distinct, including synchronous backend corrections and explicitly named compatibility origins. Principle XI: regression and mutation evidence demonstrate the nested-squelch ordering fix and retained Demo worker effect. Principle VI: no transmit authority or keying operation is added.
Current integration verification (2026-10-04)
Signed head:
5748e69b758ad57214096c1fdafccb0e1f828171; tested current main / landed #5924 squash:a325fb99e25c8f830f6fda835569abe23b7615db. Signed merge parents are8c88612banda325fb99; no force-push or historical contributor replacement.squelch_level_scale_test, and all seven Radio Setup consumers, includingperipheral_auth_dialog_test. This is a focused target build and test run, not the full repository build or suite. No fake firmware peer or frozen PR-gate test was added.8c88612bfailed hosted macOS/Linux linking atperipheral_auth_dialog_test: itsRadioSetupDialogobject lackedRtlReceiverSettingsWidget. Landed main supplies this and the companion Radio Setup dependencies; the reconciledCMakeLists.txtandtests/tests.cmakeare byte-identical to main. The formerly failing target and all seven consumers now build and test locally. This is not a disk-exhaustion fix.ReceiveSquelchModelduplicate is absent. The FM widget test observes the new typed request and retains both positive dispatch-count and level-44 assertions. Current-main per-receiver RIT/XIT, HL2 digital-mode AGC-off defaults/memory, and WAN admission remain intact.Previous integration verification (historical, 2026-10-03)
Signed head:
8c88612b46c93f50ab9d282097f1c9d79c20ddf8; current main:d9de044c7e895185bac86dfbdb3dc79e8e539c2e; prerequisite #5924:5dd50ff74dd8d397a9fdb428a4522338928a5924. No historical contributor changes were discarded; the branch is updated with signed integration merges, not a force-push.Previous integration verification (historical, 2026-10-01)
Current head:
4ab5ba8e160697b4992a320e9856dfaa29adaee4; application and nine-test production build:da77a241c5797c87de1de0438b5ac16713fc427f. The later commit changes only the FM widget test to observe the typed squelch request; main142f1d63; prerequisite #5924f89d2577.Earlier refresh evidence (historical)
Before integration with current main and #5924
Refreshed source head:
b9e4d90344e696ee45d417cd132d3c0bf398af18; main2700cb6d5ba08d9ee42c3a2fcbf626a07f1d915a; prerequisite #590704fd459e81ce8214e2762ca9ca6695e2ae19aace.The existing
anan_noise_blanker_readback_testfailed four reconnect/model assertions against the integrated source without the ANAN adapter. Its final run additionally drives the typed request through the actual worker and checks requested versus applied state.split_audio_profile_testshares production translation helpers and now pins compatibility suppression/readback exclusion inside an operator scope, alongside existing repeat/replay/restore/non-operator cases. Removing the shared production helper's operator-origin guard caused three assertions to fail; the mutation was restored before final validation. The focused set also includes the newly landedhl2_dbref_test.Historical implementation verification
The evidence below predates this refresh. It is retained as historical context, not fresh Windows/Linux VM or real-radio validation of the refreshed head.
Integrated source head:
c68e3b4714ae9f1c6dbb9b54964021db602f12c6.Main subsequently advanced to
7f68dda0(separate FFTW planner fix). An isolatedreview merge with this head was conflict-free (tree
db520634e4b575c98c8100f2e17ed349688bb9c9);native Mac builds and the two receive tests plus
fftw_planner_lock_testpassedon that merge result. The full three-platform matrix above uses the fixed head.
The focused set covers the two receive contract/routing tests; daemon receive/frequency admission; backend lifecycle, family switch, affinity and capability revision; Kiwi/slice-letter, squelch memory and GUI reconciliation; APF and antenna aliases; Demo, HL2 NB, ANAN and nested GUI lifetime regressions. These are not the entire repository suite. No test or workflow is added to the frozen per-PR gate; the existing unconditional targets run in main full-suite and sanitizer lanes.
All modified tests are socket-free. Icom uses the real scheduler with an unstarted transport and separately injected readback, not firmware simulation or a radio ACK. HL2 uses its actual RX worker without network/TX startup. Demo's NB is checked in the worker and ANF against generated sample energy, not merely a copied model flag. RTL is explicitly reported as not built where librtlsdr is absent; no USB hardware is opened.
Mutation evidence: removing the Demo NB worker call makes
backend_receive_contract_testfail; replacing the squelch pending-enable accumulation makes the nested-edit assertion inreceive_intent_routing_testfail. Both mutations were restored. Self-review found and fixed that squelch regression, cold Icom false acceptance, and an APF test namespace error. Added Kiwi handoff ordering and Icom antenna admission/readback coverage passed without further production changes.MCP checks use the unchanged production server, fresh settings, authenticated owned instances, Qt offscreen and TX disabled. They exercise the actual MainWindow linked-slice adapter on disconnected fixtures, then two Demo connect/control/disconnect cycles, NB/ANF, gain/mute/balance, lock, squelch and bounded positive RX capture. Demo AF/squelch are presentation checks, not a claim of independent mixer/DSP support. Offscreen proof does not establish GPU presentation. Each test-owned app is stopped afterward.
Receive-only FLEX-6700 / firmware 4.2.18.41174 smoke also passed on the implementation before the mechanical #5834 integration: NB/NR/ANF, AF gain/mute/balance and slice lock, successful native replies and lock status, restoration of original controls, preserved frequency/mode, then 497 RX chunks / 1,438,720 captured bytes and clean disconnect. TX remained disabled. RX antenna was only reselected, not changed; squelch, extended DSP and other models are not claimed as live-tested. First-run slice readiness and a capture attempted while muted were harness issues; the restored-state capture passed.
Known pre-existing test issue: the 40-ms startup assertion in
sim_backend_testintermittently failed during concurrent builds, reproduced on unchanged #5907 as well as this branch. Tracked separately in #5918; not claimed fixed here. The final focused test results are reported above.Linux linked debug sections are omitted to fit the VM's 17-GiB root disk; object debug information is preserved. Windows uses its matching Qt SDK plugin path for offscreen tests and native ARM64-hosted MSVC targeting x64. The compiler switch reset nested Opus options; restoring the repository's Release/DRED/OSCE settings fixed the resulting RADE/CRT link failure without disabling RADE or changing source. During the relocation rebuild, timestamps were reused only for files whose hashes matched both complete source manifests; changed files retained fresh timestamps. Packaging resumed only after re-verifying all source hashes. No source content or validation checks were skipped.
Static gates pass: command-plane baseline, engine boundary (102 existing findings, zero new), capability-record freeze (71), test registration, frozen CI allowlist, generated touchpoint manifest and whitespace. No
CHANGELOG.mdedit.c68e3b47; fresh-head status is reported aboveChecklist
Generated with OpenAI Codex (GPT-6 Astra)