Repository navigation
fix(radio): route VOX, monitor, speech processor, CW speed and break-in through the seam gate. Principle VIII. - #6111
Conversation
… radio-side keyer. Principle VIII. On a backend with no command plane that declares a radio-side CW keyer (IC-705, IC-7300MK2), `cw wpm N` and `cw break_in N` reached the backend through setCwSpeed/setCwBreakIn and were still reported as dropped. The seam gate now routes both under hasRadioSideCwKeyer, the same capability their seam connections are gated on. `cw break_in_delay` has no seam setter and keeps its notice. Refs #6086 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g
…y records. Principle VIII. On a backend with no command plane, VOX, the SSB monitor and the speech processor raised a false "dropping transmit set" notice even where the backend applied them (an Icom implements setVox, setTxMonitor and setSpeechProcessor). No capability said those setters were real. Three optional records now say so: voxControl (absorbs hasVoxDelay as hasDelay), speechProcessorControl (absorbs the PROC level maximum and label; absent presents PROC 0..2) and txMonitorControl. The drop-notice gate routes vox_enable/vox_level, vox_delay (only with hasDelay), mon/mon_gain_sb, and speech_processor_* (record present, or a host-modulating transmitter whose ClientComp runs PROC). The three seam connections are gated on the same records. Flex declares all three and keeps its wire text; its setters for them write nothing. The boolean count drops from 71 to 70, so FROZEN_BOOL_COUNT is lowered. Fixes #6086 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g
Accessibility check findingsThis PR touches
See Findings are warnings only — the build is not blocked. |
|
Checked on the HL2 at 8ed9dc0. One HL2 gap remains, outside PROC: PROC: no notice ( Full ctest GREEN, 610 tests (two -j8 flakes passed serially). Your test builds the HL2 caps by hand; we have a small test that takes them from a constructed 73, ON8ST |
There was a problem hiding this comment.
1. Issue fit
Yes, for the scope #6086 names. Each requirement maps to a hunk: cw wpm/cw break_in get prefix branches before the transmit set early return (Correction 1 of the triage comment — a k/v addition really would have been dead code); vox/speech_processor/mon get std::optional<…Control> records rather than new bools (Correction 2); the PROC gate is the disjunction with the host-ClientComp arm (Correction 3); and the test file's stale "setVox is deliberately NOT overridden" premise is corrected rather than left to mislead. I verified against the backends that the three records are honest on Icom: setSpeechProcessor (IcomCivBackend.cpp:4322), setTxMonitor (:4394) and setVox (:4485) are all model-independent CI-V writes with no profile gate, so declaring them for every hasTransmit model is right; and setVox really does Q_UNUSED(delayMs), so hasDelay=false keeps the vox_delay notice correctly.
Test layer is right: capability declarations and gate policy are socket-free CTests, and the new cases are real pins — each positive case fails against unfixed code (the setter was already called for VOX/MON; it was the notice that fired), and each has a capability-absent negative control.
2. Scope
| File / group | What it changes | Claimed by the PR? | Verdict |
|---|---|---|---|
RadioCapabilities.h |
3 new optional records; hasVoxDelay, speechProcessorLevelMaximum, speechProcessorLabel folded in |
yes | In scope |
flex/, hl2/, anan/, rtl/, sim/, icom/ backends |
declare or decline the records | yes | In scope |
RadioModel.cpp |
gate branches + 3 seam connections re-gated | yes | In scope |
MainWindow.cpp |
PROC presentation + VOX-delay availability read from the records | yes | In scope; no new widget, no default changed |
tools/check_capability_records.py |
FROZEN_BOOL_COUNT 71→70 |
yes | In scope — a sanctioned shrink, not a ratchet raise |
docs/architecture/radio-capabilities-map.md |
rows for the 3 records | yes | In scope (one wording error, inline) |
| 3 tests | updated + 12 new cases | yes | In scope |
Everything in the diff is explained by the issue. No CHANGELOG.md entry (correct). No new protocol verb, settings key or wire text — TransmitModel's text is byte-identical and src/core/control/ has no reference to any of the three records, so this is internal C++ capability surface, not third-party-visible. No preference change: no default moved, no control restyled, no confirmation removed. Both commits are on-topic and signed.
I ran the static gates in the head checkout myself: check_capability_records.py → "70 boolean(s), at the frozen 70 — ok (shrink only)"; check_command_plane.py → 401 = baseline; gen_touchpoint_manifest.py --check → up to date; gen_seam_probe_table.py --check → up to date; check_test_registration.py → OK; test_check_capability_records.py → 28 checks pass. CI on 8ed9dc04 is 7/7 green.
Socket surfacing (§2 disclosure, not a finding). tests/transmit_seam_drop_notice_test.cpp now constructs a real FlexBackend, whose ctor starts two QThreads whose init() slots do new QUdpSocket (PanadapterStream.cpp:87) and new QTcpSocket (RadioConnection.cpp:88). Those are inert socket objects — nothing binds, listens or connects, no peer — and the same construction already exists on main in icom_control_profile_test and ~8 other registered tests. ~FlexBackend is bounded (wait(3000)). Target transmit_seam_drop_notice_test, registered, run by CTest. Worth knowing that the body's socket-exclusion grep (QUdpSocket/QTcp*/bind() wouldn't have matched this file, so the claim "excluding the tests that own sockets" didn't deliberately include it.
Bot lead, refuted. The a11y check's 4 findings on MainWindow.cpp are byte-identical on main (m_swrScanAction, m_preTuneAction, m_clearAtuAction, m_agcTCalibrationMenuAction, line-shifted only) — pre-existing, nothing to do here. CodeGuard/Copilot sections were empty, which means no findings or no scan; I'm not reporting that as clean.
3. Blockers
None.
4. Nits (non-blocking)
- The gate comment overstates
cw break_in_delay(RadioModel.cpp:1906, inline).RadioModel.cpp:4728handscwDelay()tosetCwKeying, andHl2Backend::setCwKeyingclamps it into the hang timer (Hl2Backend.cpp:4296-4298). It has no setter of its own, which is what you meant, but the sentence as written will mislead the next reader. - HL2 break-in is the same false-notice class, still open (inline at
:1919). @on8st found it on hardware; it's wider than they said — HL2 consumes bothbreakInandbreakInDelayMsat key time, yethasRadioSideCwKeyer=false(Hl2Backend.cpp:1922) keeps both notices. Not this PR's job (the gate would need something that says "the backend implementssetCwKeying", andhostModulatesis wrong for that — ANAN sets it and doesn't override the setter), but it belongs in the body's known limits the way #6015 listed this issue, andcwSpeedAndBreakInWithoutKeyerKeepDropNotice's comment currently pins the gap as correct behaviour on the HL2-shaped caps. - ANAN's PROC is not served by ClientComp (inline at
AnanBackend.cpp:375, same wording in the doc row).AnanBackend.cpp:423iscanTransmit = false, sohostModulates && canTransmitis false there andMainWindow::hostModulatesTxAudio()is too — the notice correctly stands on ANAN. The behaviour is right; only the comment and the doc row (inherited from the triage comment's "ANAN … is covered by the same arm") are wrong. - One maintainer decision is still unruled (inline at
:1956): gate-forgives (here) vs. don't-emit (#6049's merged EQ shape) for host-served controls. #6086 is still labelledmaintainer-reviewand no maintainer has ruled in the thread. - Micro: the three re-gated connections now build a full
RadioCapabilitiesby value per signal, andmonitorCommandIssued/voxCommandIssuedfire per slider tick. ThecommandReadygate already didm_backend->capabilities()per tick, so this is not new in kind. unroutedVerbStillRaisesDropNotice's comment still says "keeps the first four honest … the same backend, the same model" — the backend changed toicomTransmitter().
5. What I tried to break (and could not)
- A wire text emitted without its typed intent — the way the gate would forgive a command that reached nothing. I read every emitter:
setVoxEnable/Level/Delay,setSbMonitor,setMonGainSb,setSpeechProcessorEnable/Level,setCwSpeed,setCwBreakInall emitcommandReadyand their…CommandIssuedunconditionally, side by side — cruciallycwSpeedCommandIssuedsits outside thespeedChangedguard, socw wpmhas none of the change-gating that forced thecwPitchHandedToBackendcomparison. - The PROC host arm forgiving a no-op.
applySpeechProcessorToClientComponly writes a preset whenon && (levelChanged || switchedOn), so I expected a level move with PROC off to be forgiven while nothing applied. It isn't a hole:m_lastAppliedProcLevelis still updated, and the next off→on applies the current level. Same for Icom, whosesetSpeechProcessorskips the level register while bypassed but writes it on the next enable. - A missed consumer. Grepped the whole head tree for
hasVoxDelay,speechProcessorLabel,speechProcessorLevelMaximum, the three new records and the three setters. All sixcapabilities()implementations are updated; the only surviving uses of the old names areIcomModelProfile's own fields, which are the record's input.src/core/control/(the aetherd/bridge transmit target, aRadioModelwith noMainWindowand so no ClientComp) exposes none of these controls, so the host arm cannot forgive anything headless. - UI drift from a "no behaviour change" claim. Walked the presentation for all six backends: Flex PROC/2 → PROC/2; Icom from the profile; HL2/Sim previously set PROC/2 explicitly and ANAN/RTL inherited the default — all four now go absent and
value_or(SpeechProcessorControl{})renders the identical PROC/2. VOX-delay availability resolves the same for every backend. The claim holds. - Flex gaining live setter calls. Confirmed no
setVox/setTxMonitor/setSpeechProcessoroverride anywhere outsideIcomCivBackend— so on Flex these are the base no-ops andflexSeamSettersWriteNothingis a real pin. (It does mean the connections' only guard is now the record: ifFlexBackendever implements one, the control would be applied twice, text and setter.) - A struct-revision or field-inventory ratchet that removing/adding fields should have bumped —
backend_capability_revision_testcounts announcements, not fields; nothing to bump. - Could not check: everything runtime. No build, no CTest, no GUI here, so "57 PASS", the 17-mutant table and the Demo bridge run are unverified by me; every finding above is reasoned from the head checkout at
/tmp/aetherclaude/pr-6111with file:line evidence, not reproduced. The Icom path has no hardware behind it either way.
6. Recommendation
Approve with nits — plus one named maintainer decision. The code is right and the records are evidenced against the actual CI-V writes rather than asserted; the tests fail against the unfixed code, and the ratchet genuinely shrinks. The three comment/doc corrections are cheap and worth doing before merge because each one is the artefact the next reader will trust. The decision a maintainer should make explicitly, since #6086 still carries maintainer-review: gate-forgives vs. #6049's don't-emit as the shape for host-served controls — picking gate-forgives here means two places (the gate and MainWindow) must both know about every host-served control forever. Next step: fix the three comments, add the HL2 break-in/break-in-delay limit to the body, and file it as a follow-up alongside #6110.
🤖 aethersdr-agent · cost: $9.5667 · model: claude-opus-5
|
Antares Detector — candidate vulnerable file(s)
The vox accumulator accumulates arbitrary PCM data without a fixed-size limit (m_voxAccumulator.append(pcmData)), and only processes when size >= TX_PCM_BYTES_PER_PACKET. The code then uses reinterpret_cast to read a fixed number of bytes into a float buffer, but there is no check that the accumulated PCM data fits within that buffer size before appending. This can lead to unbounded memory growth if packets are large or repeatedly triggered, violating memory-safety constraints. Localized by Cisco Foundation AI Antares-1B running locally in the AetherClaude sandbox, seeded by the Cartographer security map. Advisory only — please verify before acting. 🤖 aethersdr-agent · cost: $9.7197 · model: claude-opus-5 |
Resolves the transmit_seam_drop_notice_test conflict with #6104 by keeping both sides: the setTunePower recorder and the CW/VOX/MON/PROC recorders, and a header that names both seams. RadioModel's gate merged cleanly; it keeps #6104's tuneCarrierLive tunepower arm and #6086's record-gated keys. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g
…ice gate. Principle VIII. The gate comment said cw break_in_delay has no seam setter; it has none of its own, but Hl2Backend reads it and break-in from setCwKeying() at key time, so both keep a false notice on an HL2. The no-keyer test comment now names that gap instead of pinning it as correct. ANAN sets hostModulates but not canTransmit, so the ClientComp arm never serves its PROC; the backend comment and the capability-map row now say the notice stands there. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g
Picks up #6113 (HL2 closed-receiver DSP gate) and #6112 (AppImage). Clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g
Accessibility check findingsThis PR touches
See Findings are warnings only — the build is not blocked. |
|
Maintainer pass, now at fae9e3f:
|
|
Admin-merged on the maintainer's approval (self-authored). Head
|
…not the backend's own LAN-only link (#6125) ## Summary Fixes #6121. WAN/SmartLink receive edits reached a gate that checked only FlexBackend's undialed LAN connection, so tuning, mode, filter and AGC requests never reached the radio. - Gate receive intents on `RadioModel::isConnected()`, which checks LAN, WAN and backend connectivity. The WAN check also works without a LAN connection object. If a vended LAN connection is down, a connected backend can still establish connectivity; this includes the simulator's direct backend path. - Log a disconnected receive intent through `aether.protocol`. Do not emit `commandDropped`, whose UI consumer reports an unsupported control rather than a disconnected session. - Cover both a backend without a LAN connection and the real FlexBackend with its LAN connection undialed. Inject WAN connectivity without opening a socket. The real Flex fixture checks exact tune/mode/filter/AGC command strings through the injected slice command sink, disconnected refusal, warning output, and absence of the misleading UI signal. No new protocol verb, capability, setting, dependency, or transmit path is added. ## Constitution principle honored Principle VIII — Evidence Over Assertion. The reporter bisected the regression to #5907 (`142f1d63` versus `e48a242f`). Local mutation checks independently demonstrate that the regression suite detects the broken connectivity gates and diagnostics. ## Validation - macOS arm64, pinned Qt 6.12.0: full app build and focused `receive_intent_routing_test` pass; 50 assertions. After incorporating main #6111, both the receive test and neighboring `transmit_seam_drop_notice_test` pass (2/2 CTests). - Four independently rebuilt mutants fail: backend-only receive gate; LAN check masking a connected WAN link; restored unsupported-control signal; removed disconnected warning. The correct implementation is restored and rebuilt afterward. - Agent automation bridge: isolated offscreen `DEMO-0001`, TX disabled. Tune/mode/filter/AGC readbacks pass for LSB and USB; disconnected tuning produces no unsupported-control status notice. Owned demo process stopped after verification. - Engine-boundary, touchpoint, capability-record and command-plane checks pass. PR-only output matched the original merge base; the final candidate also includes the upstream #6111 capability ratchet decrease from 71 to 70. - The submitter reports real-radio verification on a FLEX-6500 firmware 3.9.18 over WAN and a separate FLEX-6500 firmware 4.2.20 over LAN. This review did not repeat hardware or RF testing. - Full CTest suite was not rerun in this review. The submitter's earlier 620/621 result predates #6126, which repairs the unrelated HL2 seam-test expectations now included from main. ## Checklist - [x] Commits are signed - [x] No new flat-key AppSettings calls - [x] Clean-room implementation - [x] No meter UI change or CHANGELOG edit - [x] Regression tests use injected state/command capture, with no network peer Original implementation generated with Claude Code. Additional review and regression coverage by Codex. --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Ozy <Ozy311@users.noreply.github.com>
…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>
#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>
Summary
On a radio with no Flex command plane, six transmit controls reach the radio through the backend seam and still raise the one-shot "dropping transmit set …" notice: VOX (enable, level), the speech processor, the SSB monitor, CW speed and CW break-in. On an Icom all of them apply. The drop-notice gate from #6015,
transmitCommandDeliveredThroughSeam(), did not route them, because no capability said their setters do anything.Fixes #6086.
What changes
CW speed and break-in (first commit).
cw wpm Nandcw break_in Nare barecw …text, nottransmit setkeys, so they get prefix branches next tocw pitch. They are routed underhasRadioSideCwKeyer, the same capability their seam connections already use.cw break_in_delayhas no seam setter and keeps its notice.VOX, monitor, speech processor (second commit). Three new optional capability records:
voxControl { hasDelay }hasVoxDelaysetVoxapplies enable and level;hasDelaymeans the hang time is applied toohasDelayspeechProcessorControl { levelMaximum, label }speechProcessorLevelMaximum,speechProcessorLabelsetSpeechProcessor)txMonitorControl {}setTxMonitor: MON and its gain)When
speechProcessorControlis absent, the P/CW face shows PROC 0..2, the same as when disconnected. The UI is unchanged on every backend. The unidentified receive-only Icom fallback declares none of the three.How the gate treats each key without a command plane:
vox_enable,vox_levelvoxControlis presentvox_delayvoxControl->hasDelay. False on Icom (setVoxignores the delay), so the notice there is correct and staysmon,mon_gain_sbtxMonitorControlis presentspeech_processor_enable,speech_processor_levelspeechProcessorControlis present, orhostModulates && canTransmit(the HL2's ClientComp,MainWindow::applySpeechProcessorToClientComp). ANAN setshostModulatesbut notcanTransmit, so nothing serves its PROC and the notice standsmon_gain_cw,mon_pan_cw,cw break_in_delayThe three seam connections are now gated on the same records. They used to check
m_backend(VOX),!usesFlexCommandPlane()(monitor) and!m_flexBackend(PROC).PROC on the HL2: @on8st, your PROC-on-HL2 work should use this gate. A host-side processor is covered by the
hostModulates && canTransmitarm.speechProcessorControlis only for a radio's own processor behindsetSpeechProcessor.Ratchet:
hasVoxDelaymoves into the VOX record, so the bool count drops from 71 to 70.FROZEN_BOOL_COUNTis lowered to 70 in the same commit. The checker printscapability-records: 70 boolean(s), at the frozen 70 — ok (shrink only). No ratchet grows.Flex is unchanged
Flex has a command plane, so the gate is never consulted for it, and
TransmitModel's wire text is untouched. Flex declares all three records. That means itssetVox,setTxMonitorandsetSpeechProcessorare now called; the last two were not called before.FlexBackenddoesn't override them, so they are the base no-ops.flexSeamSettersWriteNothingpins two things. First, with all three of FlexBackend's command sinks recording, those calls write nothing. Second, thetransmit set vox_enable=1 / vox_delay=30 / mon=1 / speech_processor_level=1text is byte for byte what it was.Choices a reviewer should see
setVoxwas called there unconditionally. That model has no transmitter.setVox/setSpeechProcessor(base no-ops) are no longer called. Its VOX delay control stays dimmed, as before.Known limits
hasRadioSideCwKeyeris false), socw break_inandcw break_in_delaystay unrouted. Both act there anyway:RadioModelpassescwBreakIn()andcwDelay()tosetCwKeying()at key time, andHl2Backend::setCwKeyingkeys MOX on the first and clamps the second into its hang timer. Routing them needs a capability that says a backend implementssetCwKeying();hostModulatesis the wrong proxy, because ANAN sets it and does not override that setter. Found by @on8st on hardware.Validation
Unit tests (Icom path; there is no Icom on this bench):
transmit_seam_drop_notice_test: 57 PASS, 0 FAIL. For each newly routed key there is a positive case (record present: setter called, no notice) and a negative control (record absent: setter not called, notice stands). Also covered:vox_delaywith and withouthasDelay; PROC on a host-modulating transmitter and on a non-transmitting one;cw break_in_delay;mon_gain_cw; and the Flex no-write case.Mutation check: 17 mutants, each caught by the case meant to catch it. In the gate:
vox_delayignoringhasDelay;canTransmit.In the code around it:
txMonitorControldeclaration removed.Updated:
icom_control_profile_test(Flex, IC-7300MK2, IC-9700 COMP, HL2 and the unidentified Icom records),phone_cw_mic_gain_authority_test(the MainWindow presentation fan-out).icom_family_testis unchanged and passes.ctest: 568/568 pass, excluding the tests that own sockets (QUdpSocket/QTcp*/QLocal*/bind(), the automation tests andconnection_panel_size_test(Four tests fail deterministically on clean main locally: the a11y announcement fixture never receives focus (offscreen) #5811).Static gates: engine boundary
--strict0 blocking, touchpoint manifest up to date, capability records 70 at frozen 70, command plane 401 = baseline.The agent automation bridge, Demo sim (Flex path only). The binary was built from the tree in the second commit; the build stamp reads
ddc9aa4b5-dirtybecause that commit was made right after the build. Steps:DEMO-0001, then set the slice to CW.invoke "CW speed" setValueandinvoke "CW break-in" click:get transmitread cwSpeed 20→24 and cwBreakIn false→true.invokeon VOX level (65), Speech processor, Processor level (2), TX monitor and Monitor volume (35):get transmitread voxLevel 50→65, speechProc false→true, speechProcLevel 0→2, monitor false→true, monGainSb 50→35.aether.protocolon contains nodroppingor seam lines, and the app log has none either.The VOX enable click was refused by the bridge's TX-safety guard, as it should be. The Demo has a command plane, so this run shows the Flex path is unchanged. The Icom path is covered by the unit tests only.
Related
hasRadioSideCwKeyermeans "the radio buffers CW text", but it also gatessetCwSpeed/setCwBreakIn/setCwPitch. So those controls do nothing on the IC-9700/7610/785x/7300/905, and this PR's CW routing reaches only the IC-705 and IC-7300MK2.🤖 Generated with Claude Code
https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g