Skip to content

fix(radio): route VOX, monitor, speech processor, CW speed and break-in through the seam gate. Principle VIII. - #6111

Merged
ten9876 merged 5 commits into
mainfrom
fix/6086-icom-tx-drop-notice
Oct 3, 2026
Merged

ten9876 merged 5 commits into
mainfrom
fix/6086-icom-tx-drop-notice

Conversation

@ten9876

@ten9876 ten9876 commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

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 N and cw break_in N are bare cw … text, not transmit set keys, so they get prefix branches next to cw pitch. They are routed under hasRadioSideCwKeyer, the same capability their seam connections already use. cw break_in_delay has no seam setter and keeps its notice.

VOX, monitor, speech processor (second commit). Three new optional capability records:

record replaces present means Flex Icom (transmitting) HL2 / ANAN / Sim / RTL
voxControl { hasDelay } hasVoxDelay the radio has its own VOX. Without a command plane, setVox applies enable and level; hasDelay means the hang time is applied too ✅ hasDelay ✅ no delay absent
speechProcessorControl { levelMaximum, label } speechProcessorLevelMaximum, speechProcessorLabel the radio has its own processor (setSpeechProcessor) ✅ PROC 0..2 ✅ from the profile: IC-9700 COMP 0..100, others PROC 0..2 absent
txMonitorControl {} — the radio has its own SSB monitor (setTxMonitor: MON and its gain) ✅ ✅ absent

When speechProcessorControl is 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:

key treated as delivered when
vox_enable, vox_level voxControl is present
vox_delay voxControl->hasDelay. False on Icom (setVox ignores the delay), so the notice there is correct and stays
mon, mon_gain_sb txMonitorControl is present
speech_processor_enable, speech_processor_level speechProcessorControl is present, or hostModulates && canTransmit (the HL2's ClientComp, MainWindow::applySpeechProcessorToClientComp). ANAN sets hostModulates but not canTransmit, so nothing serves its PROC and the notice stands
mon_gain_cw, mon_pan_cw, cw break_in_delay never: no seam setter

The 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 && canTransmit arm. speechProcessorControl is only for a radio's own processor behind setSpeechProcessor.

Ratchet: hasVoxDelay moves into the VOX record, so the bool count drops from 71 to 70. FROZEN_BOOL_COUNT is lowered to 70 in the same commit. The checker prints capability-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 its setVox, setTxMonitor and setSpeechProcessor are now called; the last two were not called before. FlexBackend doesn't override them, so they are the base no-ops. flexSeamSettersWriteNothing pins two things. First, with all three of FlexBackend's command sinks recording, those calls write nothing. Second, the transmit set vox_enable=1 / vox_delay=30 / mon=1 / speech_processor_level=1 text is byte for byte what it was.

Choices a reviewer should see

  • On the unidentified receive-only Icom fallback, VOX, monitor and PROC no longer cross the seam. Before, setVox was called there unconditionally. That model has no transmitter.
  • The Sim's setVox/setSpeechProcessor (base no-ops) are no longer called. Its VOX delay control stays dimmed, as before.

Known limits

  • HL2 CW break-in and break-in delay keep a false notice. The HL2 has no radio-side keyer (hasRadioSideCwKeyer is false), so cw break_in and cw break_in_delay stay unrouted. Both act there anyway: RadioModel passes cwBreakIn() and cwDelay() to setCwKeying() at key time, and Hl2Backend::setCwKeying keys MOX on the first and clamps the second into its hang timer. Routing them needs a capability that says a backend implements setCwKeying(); hostModulates is the wrong proxy, because ANAN sets it and does not override that setter. Found by @on8st on hardware.
  • CW speed, break-in and pitch reach only the IC-705 and IC-7300MK2 among Icoms (Icom: CW speed, break-in and pitch do nothing on radios without a CW text keyer (IC-9700, IC-7610, IC-785x, IC-7300, IC-905) #6110).

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_delay with and without hasDelay; 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:

    • each key's routing forced to false, and forced to true;
    • vox_delay ignoring hasDelay;
    • each arm of the PROC disjunction removed;
    • the host arm without canTransmit.

    In the code around it:

    • each of the three connections with its gate removed;
    • the CW prefixes and the trailing space;
    • Flex's txMonitorControl declaration 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_test is unchanged and passes.

  • ctest: 568/568 pass, excluding the tests that own sockets (QUdpSocket/QTcp*/QLocal*/bind(), the automation tests and connection_panel_size_test (Four tests fail deterministically on clean main locally: the a11y announcement fixture never receives focus (offscreen) #5811).

Static gates: engine boundary --strict 0 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-dirty because that commit was made right after the build. Steps:

  1. Connected to DEMO-0001, then set the slice to CW.
  2. invoke "CW speed" setValue and invoke "CW break-in" click: get transmit read cwSpeed 20→24 and cwBreakIn false→true.
  3. Set the slice to USB, then invoke on VOX level (65), Speech processor, Processor level (2), TX monitor and Monitor volume (35): get transmit read voxLevel 50→65, speechProc false→true, speechProcLevel 0→2, monitor false→true, monGainSb 50→35.
  4. A marked log window with aether.protocol on contains no dropping or 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

🤖 Generated with Claude Code

https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g

ten9876 and others added 2 commits October 2, 2026 21:43
… 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
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 4 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 1
Files with findings: 1
Total findings : 4

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@on8st

on8st commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Checked on the HL2 at 8ed9dc0.

One HL2 gap remains, outside PROC: cw break_in still raises the notice (RadioModel.cpp:1919-1921; the HL2 has hasRadioSideCwKeyer = false, Hl2Backend.cpp:1922), yet break-in acts there: RadioModel.cpp:4728 passes cwBreakIn() to setCwKeying, which the HL2 uses (Hl2Backend.cpp:4282). Your call whether it belongs in this PR.

PROC: no notice (RadioModel.cpp:1954-1957), and the button still reaches ClientComp, since speechProcessorCommandIssued → applySpeechProcessorToClientComp (MainWindow_DspApplets.cpp:943-945) is untouched. VOX and MON keep their notice (Hl2Backend.cpp:1968-1969), which is correct on main.

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 Hl2Backend instead, 13/13 pass. Happy to add it if useful.

73, ON8ST

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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:4728 hands cwDelay() to setCwKeying, and Hl2Backend::setCwKeying clamps 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 both breakIn and breakInDelayMs at key time, yet hasRadioSideCwKeyer=false (Hl2Backend.cpp:1922) keeps both notices. Not this PR's job (the gate would need something that says "the backend implements setCwKeying", and hostModulates is 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, and cwSpeedAndBreakInWithoutKeyerKeepDropNotice'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:423 is canTransmit = false, so hostModulates && canTransmit is false there and MainWindow::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 labelled maintainer-review and no maintainer has ruled in the thread.
  • Micro: the three re-gated connections now build a full RadioCapabilities by value per signal, and monitorCommandIssued/voxCommandIssued fire per slider tick. The commandReady gate already did m_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 to icomTransmitter().

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, setCwBreakIn all emit commandReady and their …CommandIssued unconditionally, side by side — crucially cwSpeedCommandIssued sits outside the speedChanged guard, so cw wpm has none of the change-gating that forced the cwPitchHandedToBackend comparison.
  • The PROC host arm forgiving a no-op. applySpeechProcessorToClientComp only writes a preset when on && (levelChanged || switchedOn), so I expected a level move with PROC off to be forgiven while nothing applied. It isn't a hole: m_lastAppliedProcLevel is still updated, and the next off→on applies the current level. Same for Icom, whose setSpeechProcessor skips 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 six capabilities() implementations are updated; the only surviving uses of the old names are IcomModelProfile's own fields, which are the record's input. src/core/control/ (the aetherd/bridge transmit target, a RadioModel with no MainWindow and 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/setSpeechProcessor override anywhere outside IcomCivBackend — so on Flex these are the base no-ops and flexSeamSettersWriteNothing is a real pin. (It does mean the connections' only guard is now the record: if FlexBackend ever 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_test counts 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-6111 with 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

Comment thread src/models/RadioModel.cpp Outdated
Comment thread src/models/RadioModel.cpp
Comment thread src/models/RadioModel.cpp
Comment thread src/core/backends/anan/AnanBackend.cpp Outdated
@aethersdr-agent

Copy link
Copy Markdown
Contributor

Antares Detector — candidate vulnerable file(s)

  • src/core/AudioEngine.cpp

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

@ten9876 ten9876 self-assigned this Oct 3, 2026
ten9876 and others added 3 commits October 3, 2026 10:40
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
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 4 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 1
Files with findings: 1
Total findings : 4

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@ten9876

ten9876 commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

Maintainer pass, now at fae9e3f:

@ten9876
ten9876 merged commit 579efd7 into main Oct 3, 2026
7 checks passed
@ten9876
ten9876 deleted the fix/6086-icom-tx-drop-notice branch October 3, 2026 19:25
@ten9876

ten9876 commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

Admin-merged on the maintainer's approval (self-authored). Head fae9e3f6f:

Ozy311 added a commit that referenced this pull request Oct 3, 2026
…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>
ten9876 added a commit that referenced this pull request Oct 4, 2026
…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>
rfoust pushed a commit that referenced this pull request Oct 12, 2026
#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Icom: VOX, speech processor, CW speed and break-in raise a false "dropping transmit set" notice

2 participants