Repository navigation
feat(anan): select the G2's receive band-pass filters and mute its speaker amplifier (RFC #6209). Principle VIII - #6673
Conversation
There was a problem hiding this comment.
Issue fit
Yes. This is RFC #6209 parts 2 and 3, and it implements what @jensenpat approved on 2026-10-17 — including the parts of the approval that constrain it. The RFC's three required shapes are all present and correct: the verb is partial per field (AnanBackend.cpp:1484-1500, fields.contains() before each read), it persists nothing (the dialog writes AnanSettings then invokes), and it is gated on backendDeclaresExtension("anan") rather than a family string (RadioSetupDialog.cpp:5369-5379) — byte-for-byte the HL2 hw.set precedent at RadioSetupDialog.cpp:4586-4590. adcFrontEnd stays a fieldless record, so #5554 and the #5262 M2 boolean freeze both hold.
The approval's one explicit reservation was that "RFC approval does not establish hardware proof for the untested filter selections" — the RFC had only 3 of 7 confirmed. The bench table in the body now covers all seven on a G2, including the 1.5 MHz BPF, the 20 MHz BPF and the 6 m preamp, and it names what it could not show (the 6 m passband; the 20 MHz filter on 12 m and 10 m, where the band sat at the receiver's own floor). Reporting the dead-path floor as a fourth state, so "Follow is 20 dB above a removed signal path" is a measurement rather than an inference, is the right way to evidence this control. That is a genuine Principle VIII fit, not a checkbox.
Scope
Everything in the diff is explained by RFC #6209's own "Parts 2 and 3" file list, and the two files beyond it are required consequences.
| File(s) | Change | Claimed? | Verdict |
|---|---|---|---|
P2Protocol.{h,cpp} |
AlexRxFilter, alexRxFilterBits/For, HighPriorityOptions trailing param |
Yes | RFC scope |
P2Client.{h,cpp} |
two live setters, highPriorityOptions(), sendRunningHighPriority(), m_ddc0FreqHz |
Yes | RFC scope |
AnanSettings.{h,cpp} |
two fields inside the existing nested Anan document |
Yes | RFC scope; Principle V holds (no flat key) |
AnanBackend.{h,cpp} |
frontend.set, two connect params, test-access friend |
Yes | RFC scope |
RadioModel.cpp |
two params seeded in populateFamilyParams |
Yes | RFC scope |
RadioSetupDialog.cpp |
combo, mute checkbox, pushLive, reseed |
Yes | RFC scope |
tests/anan_{front_end,p2_protocol,settings}_test.cpp, tests.cmake |
new target + ~50 checks | Yes | In scope |
docs/architecture/radio-capabilities-map.md |
adcFrontEnd row |
Yes | In scope |
docs/user/docs/{anan-g2,radio-setup}.md |
Setup table, two troubleshooting entries | Yes | Required by the checklist |
docs/user/src/components/LogAnalyzer/rules.json |
13 RadioModel.cpp line anchors, +7 |
Yes ("refreshes the line numbers that moved") | Mechanical and correct — I re-resolved all 13 anchors against the head tree and every one matches its recorded text; 0 mismatches |
No file is unexplained, no new public surface arrives as a side effect (the verb is the RFC's whole subject), and I read the - lines for removed guards: the only deletions are the four inlined buildHighPriority call sites folded into sendRunningHighPriority(), and the Q_UNUSED(arg) that arg now genuinely uses. No guard, early return or confirmation was removed.
Personal-preference check: the two modified existing elements are the bypass-checkbox hint text and the page intro, both of which change because the sentence they asserted ("no band filter is ever selected") stops being true. The bypass checkboxes are disabled, not removed, and the default is byte-identical to today. No default changed, nothing was restyled or reordered for taste. This is a feature, not a smuggled preference.
Socket test (recording per §2, not a finding). tests/anan_front_end_test.cpp is new and binds a socket: QUdpSocket on UDP 127.0.0.1:1027 (kHighPriorityPort). tests.cmake:1320-1331 discloses it in the block comment ("Loopback only, no radio. Binds 127.0.0.1:1027 (kHighPriorityPort)") and sets SKIP_RETURN_CODE 77, and the test exits 77 on a bind failure rather than consuming its timeout. This is legitimate under canon: the listener is inert — it never replies and nothing in the test needs a reply, so it is not a synthetic radio peer standing in for p2app. The subject is our own P2Client's outbound bytes. CI does not run it on PR: the only ctest -R steps in ci.yml are the DV, cross-needle, mac_nr_filter_test and asr_gpu_probe_test ones, so this and the two extended tests gate at merge/weekly via full-suite.yml (#5405's frozen gate), not here.
Blockers
None.
Nits (all non-blocking)
-
src/core/backends/anan/P2Client.cpp:196-199— the stop-packet comment overclaims. It reads:// change them, so a clean stop hands the radio back bypassed and
// unmuted."Unmuted" is unconditional and correct (byte 1400 is zero). "Bypassed" is not:
stop()passesm_bypassAdc0Filters/m_bypassAdc1Filterswith defaultopts, so with a bypass box unticked and Follow on, the stop packet writes0x0000into both bank words — neither a filter nor HF Bypass, which is no signal path at all, not "bypassed". Your own second test block sets up exactly that configuration (bypassAdc0Filters = false,bandFiltersEnabled = true) and asserts0x0000for the live case, but never checks that block's stop packet. Harmless in practice — no client is listening by then — but the same sentence is in the PR body's design notes and indocs/user/docs/anan-g2.md, so it is worth making conditional:// The clean-stop packet, mirroring metisStop(): run=0 reaches the // radio before the socket that would carry any further keepalive // goes away. Without highPriorityOptions(): p2app keeps the last // byte 1400 and Alex words it was sent, and no client is left to // change them, so a clean stop hands the radio back unmuted, and // with the bypass flags -- not a band filter -- deciding the banks.
-
src/core/backends/anan/P2Client.h:267-270— this comment is now stale:// Set once from Params at start(); every buildHighPriority() call for // the rest of this session (the keepalive tick included) carries these // same values, since there is no live setter for either (see Params' // own comment on why these are connect-time-only).
After this PR a run=1 packet carries these values only while
m_bandFiltersEnabledis false; otherwisehighPriorityOptions()replaces them with a filter bit in the same relay position. The members are still connect-time-only, so the second half holds — it is the "every call carries these same values" clause that no longer does. Worth one clause noting the override, since this is the comment a future reader checks when the bypass flags appear not to take. -
src/gui/RadioSetupDialog.cpp:5376-5378— the page intro says "The filter bank and the speaker amplifier mute take effect at once" with no qualifier, whileanan-g2.mdcorrectly scopes the same claim to "a connected G2". The page is shown with nothing connected (RFC ruling (a)), where "at once" is vacuous. The tooltips and the user guide cover it; only the intro sentence is unqualified. -
tests/tests.cmake:1328-1331— the fixed1027bind means a developer with a realp2app, a second AetherSDR, or another process holding the High Priority port gets a silentSKIPrather than a failure. No sibling collides (anan_speaker_audio_testbindskSpeakerAudioPort;anan_link_telemetry_testbinds nothing), soctest -jis clean today, but aRESOURCE_LOCKproperty would keep it that way if a future ANAN test wants the same port.
What I tried to break
- The byte-offset trap this PR's own header comment warns about. The "32-bit Alex0" at 1432 is two 16-bit words to p2app, so a receive filter written into 1432-1433 reaches the TX relays.
writeU32be(&pkt[1432], alex0)puts1u << 24in byte 1432 and the 16-bit filter in 1434-1435 — correct — andanan_p2_protocol_test.cpp:303-321pinsreadU16(hp0, 1434) == 0x0002andreadU16(hp0, 1432) == 0x0100separately, so a filter leaking into the TX half fails loudly. These tests are not self-confirming: guthighPriorityOptions()and the "20 m selects the 13 MHz filter" checks fail. I also confirmed theopts1case asserts ADC0 keepsANT1 | bypassas a fullreadU32, so a cross-contamination between the two banks is caught too. - A stale filter surviving a session restart.
m_ddc0FreqHzis reset instart()atP2Client.cpp:92, adjacent to the pre-existingm_ddc0FreqWord = 0at line 84, so the two cannot drift — andanan_front_end_test.cpp:230pins "the next session's first packet bypasses rather than reselecting 20 m", which is the one check that mutation kills. A session that never retunes stays onBypass(AnanBackend.cpp:844guardsm_sliceFreqHz > 0.0), which is the fail-safe direction: signal flows. - A sender that forgets the options. I enumerated every
buildHighPrioritycall site insrc/: exactly two remain,sendRunningHighPriority()(reached from start, retune, both live setters, and the keepalive) and the single run=0 packet instop(). There is no fifth run=1 sender to miss, andsetDdcRateLive()touches only the DDC-Specific packet, which carries no Alex word. The#5547single-source-of-truth property is preserved rather than just claimed. - Non-finite, negative and zero tuning.
alexRxFilterForguards!std::isfinitebefore the ladder, which is the live bug it would otherwise have: every NaN comparison is false, so an unguarded ladder falls through toPreamp6m, i.e. silence on HF. Both NaN and infinity are tested, and negative or sub-1.5 MHz takes theBypassarm.alexRxFilterBitsis a total switch with aBypass— not zero — fallback, so even a garbage enumerator yields a valid relay rather than an open path. - Whether
m_pendingParamsis the right place to record the verb's change. It is, and for a non-obvious reason: the rate-change restart atAnanBackend.cpp:1178-1179re-start()s the client fromm_pendingParamswithout re-readingAnanSettings, so a live option that only lived inP2Clientwould be lost on a zoom-driven restart. The verb mirrorssetStepAttenuationDb's existing precedent atAnanBackend.cpp:1317-1321("resends everything fromm_pendingParams") exactly. An external reconnect still re-readsAnanSettings, which is why the verb-without-persist caveat is true as documented. - Slice vs. DDC centre. I expected the filter to follow the DDC centre while the docs say "the receiver's frequency", which would mis-select near a band edge at 1536 ksps. It does not diverge: on ANAN
m_sliceFreqHzis both the tune target (AnanBackend.cpp:845, 921) and the pan centre (:1751), so there is one frequency, not two. - The dialog's reseed loop. Every control on the page persists on change and two now also push to the radio, so an unblocked
setCurrentIndex()during reseed would write back and transmit what it just read.QSignalBlocker b5(feBankCombo) … b9(ampMuteChk)covers both new controls, andsyncBypassEnabled(followTuned)is then called explicitly so the dimming is not left behind by the blocked signal. No gap. - Qt lifetime and threading. Both live setters are
Q_INVOKABLEand reached byQt::QueuedConnection, sohighPriorityOptions()only ever reads its members on the client's own I/O thread. Both GUI lambdas are connected withthisas the context object, so they die with the dialog.friend struct P2ClientTestAccessatP2Client.h:213pre-exists; the new methods sit after theprivate:at line 212, not under theprivate slots:at 206, so moc does not register them. The test flushes withQt::BlockingQueuedConnectionrather than assuming queued calls run without an event loop — which is the detail that would have made this test pass vacuously, and it doesn't.one()returns an empty array on a wrong count, andu16At/byteAtthen return sentinels that match no expected value, so an empty capture fails rather than passes. - CodeGuard, all 40 flags on the changed files. I ran the required security audit and refute every one. The
CS-VLN-CMDI-BACKTICKhits are the known lexical matcher firing on prose comments and Qt stylesheet strings — no process execution exists in any audited file.CS-PII-BANK/-DL-1/-PHONE-2/-IPare spec page citations (p.91,pp.34,36) and MAC/IP field descriptions in comments.CS-VLN-SSRF-LOCALHOST/-HEXare the test's loopback fixtures and hex bit masks.rules.json:921-936'sCS-SEC-PASSWORDis a log-analyzer regex matching the words "username or password" in a diagnostic string, not a credential. The audit independently confirmed the file list is main's tip rather than this diff —RigctlProtocol.*,FlexBackend.cpp,MeterModel.cpp,TransmitModel.*,PanadapterModel.*,SpectrumOverlayMenu.*andSliceTroubleshootingDialog.cpphave no textual intersection with this PR and match #6657–#6660 already on main. Credit: security-audit (CodeGuard), verdict no findings. It separately confirmed the new writes are all compile-time-constant offsets inside the 1444-bytestd::array(1432→1436, 1430→1432, 1400, 1443 = last valid index), that nothing inHighPriorityOptionsis used as an index, and that the two newAnanSettingsaccessors end inQJsonValue::toBool(false)and so cannot yield a non-bool from a hand-edited settings row. - Reachability of the new verb. The audit enumerated every
invokeExtension/invokeBackendExtensioncaller in the tree.AutomationServer.cpphas no generic(ns, verb, arg)passthrough — it invokes a fixed hardcoded verb per command — sofrontend.setis reachable only from the dialog lambda. Theanannamespace was already declared fordroop.apply, soextensionNamespacesgrows no new externally-visible entry. The path stays RX-only:buildHighPrioritywrites byte 4 bit[0] and has no PTT parameter, so Principle VI is not in play. - Governance. RFC #6209 carries
RFC Approvedwith an explicit maintainer ruling, so GOVERNANCE.md's RFC-first requirement is satisfied. NoCHANGELOG.mdentry was added — correct. No credential or flat settings key is introduced. There is no touchpoint-manifest or seam-table obligation:aetherd-touchpoint-tags.jsonlists no extension verb, including the existingdroop.apply,hw.setandnb.get. All 11 checks onc01eca7are green, includingcheck-windows— relevant here because that is the only-DENABLE_ASR=OFFlane, andStatic checksis where the a11y, capability-record and colour-ratchet gates live.
What I could not check. Everything above is reasoned from the code and from the head checkout at /tmp/aetherclaude/pr-6673; I have no build, no test run and no G2, so nothing here was reproduced at runtime. Three claims rest on sources outside this repository and I am taking them on trust rather than verifying them: that p2app's InHighPriority.c reads 1434-1435 as ADC0's receive word and 1430-1431 as ADC1's; that p2app applies byte 1400 on every High Priority packet including run=0; and that the six band edges (1.5 / 2.1 / 5.5 / 11 / 22 / 35 MHz) are deskHPSDR's for SATURN and ORION2. The bench table is the evidence that matters for the third, and it is consistent with the ladder as coded — the 21.2 MHz reading sitting on the 13 MHz filter rather than the 20 MHz one is the counter-intuitive case, and it was measured. What would settle the first two is a p2app source citation by line, as RFC #6209 gave for the bit table.
Recommendation
Approve with nits. This is careful work: the one wire-level trap in the change (the 1432 vs 1434 split) is called out in the header, implemented correctly, and pinned by tests that fail when mutated; the four run=1 senders are collapsed into one so a future sender cannot drop the options; the fail-safe direction is chosen everywhere (NaN → Bypass, untuned → Bypass, unreachable enumerator → Bypass, default off); and the eight disclosed mutation runs plus the dead-path-floor bench method mean the test plan is evidence rather than assertion. The approval's one open reservation — hardware proof for the four unconfirmed selections — is now answered, with the two bands that could not be shown named as such.
The four nits are all comment- and prose-accuracy, and three of them are the same underlying fact: a filter selection replaces the bypass flags, which makes two older comments and one doc sentence conditional where they read as absolute. None affects a byte on the wire. Next step is the maintainer's call on whether to take those wording fixes in this PR or as a follow-up; the code itself needs no change.
Thanks for the bench table with the fourth attenuator state — reading the removed-path floor so "Follow is 29 dB above dead" is measured rather than assumed is the thing that makes this control reviewable at all.
🤖 aethersdr-agent · cost: $14.5503 · model: claude-opus-5
|
Cisco CodeGuard — static analysis of this PR (231 finding(s))
Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them. 🤖 aethersdr-agent · cost: $15.1446 · model: claude-opus-5 |
… the loopback tests' port. Principle VIII. Four nits from the aethersdr#6673 review. Nothing on the wire changes. The stop packet's comment said a clean stop hands the radio back bypassed. It hands it back with the bypass flags deciding both banks, which is bypassed only while both boxes are ticked. The bypass members' comment said every packet carries them; while the bank is on, a run=1 packet's filter replaces them. The page intro now says the filter bank and the mute apply at once to a connected G2, as the user guide does. anan_front_end_test counts the High Priority packets that reach 127.0.0.1:1027. anan_speaker_audio_test and anan_link_telemetry_test start their own clients against 127.0.0.1, which send there too, so under ctest -j a stray packet could fail a count. The three tests now share a RESOURCE_LOCK. Full Suite runs ctest serially, so only a local -j run was exposed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
All four nits are fixed in the second commit. Nothing on the wire changed.
The two p2app claims, by line (
The band edges: deskHPSDR |
|
Welcome to AetherSDR, @tropo1234! Thanks for your first pull request. A few things that might help:
If you have questions, feel free to ask here or in Discussions. — AetherClaude (automated agent for AetherSDR) 🤖 aethersdr-agent · cost: $4.2249 · model: claude-opus-5 |
…eaker amplifier (RFC aethersdr#6209). Principle VIII. The follow-up RFC aethersdr#6209 approved: a filter-bank selector for both ADCs, the speaker-amplifier mute, and the anan/frontend.set verb that applies both live. Filter bank. "Follow the tuned frequency" selects the Alex band-pass filter covering the receiver's frequency on ADC0 (bytes 1434-1435) and ADC1 (bytes 1430-1431), the receive words p2app's InHighPriority.c reads. The edges are deskHPSDR's for SATURN/ORION2 boards: 1.5, 2.1, 5.5, 11, 22 and 35 MHz. The default stays Bypassed, byte-identical to before. The bypass checkboxes switch the same relay, so they are disabled while the bank follows the frequency. Speaker amplifier mute. High Priority byte 1400 bit 1 (p2app SetSpkrMute). Sending the radio silence leaves the amplifier running and hissing; this stops it. Bits 0 and 2 stay clear. Both options are live. P2Client builds every run=1 High Priority packet in one place, so start, retune, attenuator and keepalive all carry them. The stop packet carries neither, so a clean disconnect hands the radio back bypassed and unmuted. Both are seeded at connect from AnanSettings. frontend.set is partial and persists nothing; the page writes AnanSettings and then invokes it. Tests: anan_front_end_test reads the High Priority datagrams P2Client sends on loopback and drives frontend.set through AnanBackend; the protocol and settings tests pin the ladder, the bits and the defaults. Eight mutations each failed their own checks. Bench, one G2 through the automation bridge: every selection passes a signal well above the receiver's dead-path floor (1.5, 6.5, 9.5 and 13 MHz filters by 20-31 dB; the 20 MHz filter by at least 5 dB on WWV at 25 MHz; 15 m by 5 dB). The 6 m preamp switches in (+12 dB); its passband is unproven, the band was quiet. The mute silences the speaker and persists across a reconnect. A clean disconnect's unmute could not be heard: the speaker is silent with no client either way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the loopback tests' port. Principle VIII. Four nits from the aethersdr#6673 review. Nothing on the wire changes. The stop packet's comment said a clean stop hands the radio back bypassed. It hands it back with the bypass flags deciding both banks, which is bypassed only while both boxes are ticked. The bypass members' comment said every packet carries them; while the bank is on, a run=1 packet's filter replaces them. The page intro now says the filter bank and the mute apply at once to a connected G2, as the user guide does. anan_front_end_test counts the High Priority packets that reach 127.0.0.1:1027. anan_speaker_audio_test and anan_link_telemetry_test start their own clients against 127.0.0.1, which send there too, so under ctest -j a stray packet could fail a count. The three tests now share a RESOURCE_LOCK. Full Suite runs ctest serially, so only a local -j run was exposed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
e5967d1 to
e1dfbc4
Compare
jensenpat
left a comment
There was a problem hiding this comment.
Final-head landing review at 818fb3c8ad1722743fc57b04e2629331cca158fb.
No remaining blockers. At the operator’s request, 818fb3c8a removes the UDP capture test, its registration/resource locks, and the otherwise unused AnanFrontEndTestAccess friend. The socket test was not executed during this landing. ctest -N -R '^anan_front_end_test$' reports Total Tests: 0; registration validation passes. Contributor commits and attribution are retained.
Cross-radio and seam audit:
RadioModel.cpp:740-750: the two new namespaced parameters are inside the existing ANAN-only branch ofpopulateFamilyParams(). Flex’s connect path and Icom’s credentials/CI-V parameters remain unchanged.RadioSetupDialog.cpp:5373-5380: live writes require the declaredanannamespace and use the existinginvokeBackendExtension()channel. Flex declaresflex, Icom declaresicom, and neither can receive this UI dispatch. No backend interface, capability record, raw Flex command, vendor include, signal route, or TX gate is added.- UI edits remain within
buildAnanFrontEndTab(). Page visibility/capability policy is unchanged from approved RFC #6209; the existing disconnected page remains available for pre-connection configuration. Title-bar mute, shared audio routing, global theme tokens, common controls, and global defaults are unchanged. - The four architecture ratchets produce identical per-file findings at merge base
a87f11c6and final head: 101 pre-existing engine-boundary findings, no new violation; 70 frozen booleans; 125 pre-existing command-plane entries; touchpoint manifest unchanged. Color ratchet delta is zero on all three metrics.
Evidence on the final tree:
- macOS Qt 6.12.0 RelWithDebInfo app and focused targets build successfully with 22 jobs; clean tree. Seven headless CTests pass: protocol, settings, family switch, seam affinity, seam consumers, Radio Setup routing, and Radio Setup window size. Family-switch proof includes Flex and Icom construction/teardown and stale-delivery isolation; it is not live firmware validation.
- Three isolated protocol mutations are rejected: 22 MHz
<changed to<=(1 failed assertion), ADC0 filter shifted into TX half (3), mute moved to bit 0 (1). Original implementation passes. - Final-head bridge, private settings, explicit
/tmp/aethersdr-pr6673-land, andAETHER_AUTOMATION_NO_TX=1: Follow disables both bypass boxes and an attempted toggle returnsdisabled:true; Bypassed restores both; filter and amplifier-mute values survive process restart. Demo then reportsserial:DEMO-0001,sliceCount:1,panCount:1,transmitting:false, andwhoami.txAllowed:false. The ANAN controls become invisible on connected sim via the unchanged capability gate. Owned instances were closed after validation. - Registration, frozen CI gate, generated reference pages, seam probes, diff whitespace, and all 707 log-rule checks pass. Final-head hosted CI and Docs results were checked before approval.
- Verified the external packet facts against p2app InHighPriority.c (receive words at 1430/1434 and mute at byte 1400 even on stop) and deskHPSDR new_protocol.c (SATURN/ORION2 ladder).
The prior review’s four nits are satisfied: corrected stop/bypass comments, connected-G2 qualification, and no remaining new listener/port-lock requirement after the requested test removal. The advisory CodeGuard comment’s prose/backtick, numeric-mask, field-description, and unrelated-file hits do not identify executable injection or exposed secrets in this diff. No unresolved threads or changes-requested reviews remain.
Limits: no live Flex/Icom/G2 or RF/TX test was run in this pass. Contributor G2 bench evidence is attributed, not reproduced; quiet-band passbands and audible idle unmute remain unverified. The local build lacks QtKeychain, DFNR, and sherpa-onnx. Automated code-review was unavailable; the adversarial pass was performed manually. No post-merge CI result is claimed.
Generated with OpenAI Codex (GPT-6 Astra)
Summary
Implements RFC #6209 parts 2 and 3 on the existing ANAN Front End page:
anan/frontend.setextension applies either option live without persisting it. The page owns persistence in the existing nestedAnansettings document; connect seeds both options before the first High Priority packet.Wire encoding stays inside the ANAN backend. The packet builder preserves the RX/TX word split, and every running High Priority sender uses the same options. Clean stop clears the amplifier mute and lets the existing bypass flags determine the banks.
The RadioModel changes are confined to its existing ANAN connect-parameter branch. There are no changes to the backend interface, Flex or Icom backends, shared audio controls, themes, or global defaults. The existing page visibility and capability gates remain intact.
Validation
818fb3c8a, working tree clean.QT_QPA_PLATFORM=offscreen:anan_p2_protocol_test,anan_settings_test,backend_family_switch_test,backend_seam_affinity_test,backend_seam_consumer_test,radio_setup_control_routing_test, andradio_setup_window_size_test.DEMO-0001connection verifies the shared simulator path; no physical radio was connected during this landing.818fb3c8a. Documentation builds ran in hosted CI.Hardware evidence and limits
The contributor reports receive-only bench validation on one ANAN-G2: the selected filters passed signals above the measured dead-path floor; the 6 m preamp switched in; the speaker mute silenced the amplifier and persisted across reconnect. That bench evidence was not repeated during this landing. The 6 m passband and the 20 MHz filter on quiet 12 m/10 m bands remain unproven, as does clean-stop unmute by ear when the idle radio speaker is already silent.
The local build lacks QtKeychain, DFNR, and sherpa-onnx; their behavior is not validated here. No live Flex, Icom, RF, or TX validation is claimed. The PR adds no socket-owning test; the socket-free protocol and settings coverage remains.
Generated with OpenAI Codex (GPT-6 Astra)