Skip to content

fix(squelch): SQL line and Auto SQL from a backend-published scale; HL2 amsq gate referred to the LNA. Principle VIII. - #6127

Merged
ten9876 merged 11 commits into
mainfrom
fix/6092-sql-level-mapping
Oct 4, 2026
Merged

ten9876 merged 11 commits into
mainfrom
fix/6092-sql-level-mapping

Conversation

@ten9876

@ten9876 ten9876 commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #6092.

What was wrong

The panadapter SQL line and Auto SQL assume every radio's squelch level means −160 + level dBm. That is Flex's scale. On the HL2 it is wrong in three ways:

  • The gate is fixed in raw dBFS. amsq opens at −140 + 0.7·level dBFS (WdspChannel::levelSquelchThresholdDbfsForLevel), so every dB of LNA gain moves the antenna-referred point where squelch opens by a dB. on8st's re-run on a real HL2 (HL2: panadapter SQL line and Auto SQL use the Flex −160+level scale; the HL2 amsq gate is −140+0.7·level dBFS #6092, 2026-10-03) shows the map was fitted at LNA −12 dB. At the shipped +20 dB, level 50 sits 24 dB or more below the no-signal floor: it opens below about level 85 and never closes on a carrier.
  • The pan is not raw dBFS. HL2 bins carry offsetDb() = fullScale − LNA (−17 dB at +20). The line was drawn on Flex's scale with neither offset.
  • Auto SQL thresholds pan bins against a passband detector. amsq captures after the passband filter and before the AGC (xamsqcap, third_party/wdsp/upstream/RXA.c:650, after xnbp(nbp0) at :647, before xwcpagc at :665). It compares an averaged magnitude against 10^(dB/20) (amsq.c:125-126, :221-224). Pan noise is per bin, so the two differ by an amount that depends on both the filter width and the bin width. That is on8st's point on the issue.

The fix (Jeremy's ruling on #6092: C+R)

  1. The HL2 amsq gate is referred to the LNA. gate = −108 + 0.7·L − lnaOffsetDb() dBFS. It equals on8st's map at LNA −12 and tracks the LNA dB for dB, the same way the AGC ceiling already does.
    • WdspChannel stays family-neutral. setSquelch(on, level, levelOffsetDb) adds a caller-supplied offset to the amsq map only, and the channel has no gain of its own to refer to.
    • The HL2 owns the referral in Hl2DbReference::levelSquelchOffsetDb(). That class already "owns everything relating raw dBFS to dBm, plus the AGC setpoint that must move with it" (docs/agents/backends.md: radio-facing logic stays in the family backend).
    • Hl2Backend sends the offset with every squelch push and re-pushes on every LNA change (pushEffectiveLnaGain), including auto RF gain steps. Hl2RxDsp keeps the last finite offset if it is handed a non-finite one, so its retry loop only ever means "the channel is busy".
    • The −12 dB anchor is "supported, not established", in on8st's words. His 27 Sep no-signal and carrier brackets fit the −12 row, and the busy-40 m bracket does not. The 1:1 shift is supported (carrier moved 10.9 dB for 12 and 19.3 for 20, with 2.1 dB fading between repeats), not proven.
    • At the default +20 dB LNA, his rows put the working range at a few steps around level 50: noise opened the gate up to about 49, his carrier up to about 54. radio-capabilities-map.md says so.
  2. A new optional capability record, RadioCapabilities::squelchLevelScale (core/backends/SquelchLevelScale.h). It holds offset, dB per step, modes, and whether Auto SQL may use it. Absent means no SQL line and no Auto SQL.
    • Flex: {−160, 1, all modes, Auto}, unchanged behaviour.
    • HL2: −140 + (G + 12) + (3 − G) + 6.02 = −118.98 + 0.7·L on the pan axis, in AM/SAM/DSB/LSB/USB only. The LNA terms cancel, so the record is a constant. FM is excluded: fmsq is a noise-quieting gate with no dB place.
    • Auto SQL is off on the HL2 for the detector-mismatch reason above. If it comes back, the floor should come from the detector side; a pan-derived floor would need both widths applied.
    • Icom, the demo, ANAN and RTL-SDR: legacyDbmSquelchScale(), i.e. Flex's {−160, 1, all modes, Auto} (maintainer decision on this PR). None of them has a measured map of its own, so their SQL line and Auto SQL behave exactly as on main. On main, ANAN and RTL-SDR had both features in the GUI too: the SQL button is live in voice modes, so the line drew and Auto SQL ran. They therefore get the same record, so nothing regresses. Kiwi keeps its own server-relative branch, untouched.
    • The +6.02 dB carrier term is folded in only because it is verified on both sides. Hl2Spectrum divides by half the window sum, so a steady carrier of magnitude A reads 20·log10(A) + 6.02. squelch_level_scale_test pins this against a synthetic carrier. amsq opens on the magnitude itself, which wdsp_channel_test's new carrier case pins: ±2 dB around the gate, at two offsets. A carrier off a bin centre reads up to 1.4 dB lower (Hann scalloping).
    • No ratchet growth. check_capability_records.py --strict stays at the frozen count (70/70 on current main): the record is an optional, and its autoSquelch bool sits inside the nested struct.
  3. The GUI reads the record.
    • SpectrumWidget draws the line from thresholdDb(level). Auto SQL computes levelForThresholdDb(floor + margin), which is bit-for-bit the old Flex result across −175..−40 dB, and returns early when the record is absent or has no Auto SQL.
    • MainWindow::syncSquelchScaleToUi() pushes the record for the active slice's mode. It runs on a capability change, an active-slice switch (before RxApplet::setSlice), a slice mode change, and when a pan is added. autoSquelchShouldRunOnSpectrum gates on it too.
    • The SQL button cycle skips Auto where Auto is unavailable (RxApplet; VfoWidget mirrors through it). Every route into Auto, including restore, lands on Manual instead. A slice already in Auto drops to Manual with squelch on at the operator's manual level.
    • The button stays visible and enabled, because Off and Manual still work. The reason goes on its accessibleDescription and tooltip (translated), mirrored on the VFO's SQL button. Before a slice is active the gate stays permissive, as it is while disconnected.

Behaviour changes to note

  • Icom, the demo, ANAN, RTL-SDR: no change. They keep Flex's scale for the line and Auto SQL.
  • HL2 FM, CW and data modes: no SQL line. fmsq has no dB place.
  • HL2 manual squelch: at the default +20 dB LNA, a given level now gates 32 dB higher (in raw dBFS) than before. That restores on8st's measured calibration at the gain he measured, which is the point of the change.

Validation

All of this was run locally on Arch Linux with an offscreen Qt platform. No HL2 or FLEX hardware was touched.

The unit tests, the full suite and the static gates were re-run at 1c57410, after main (3e4b14e) was merged in with a signed merge. The click table below was recorded at 33711e2. The reconnect check was run at 8bc6ebc.

Unit tests (socket-free, mutation-checked).

  • squelch_level_scale_test (new).
    • The gate formula at LNA −12/−4/+8/+20/+48 dB and levels 0..100.
    • The 1:1 LNA shift.
    • At level 50, the gate sits between on8st's no-signal and carrier brackets on all four rows he measured: +20 → −73.0 (noise top −73.5, carrier −70.0); +8 → −85.0; −4 → −97.0; −12 → −105.0.
    • The record each backend publishes: Flex and HL2 their own; Icom, Demo, ANAN and RTL-SDR pinned to {−160, 1, all modes, Auto}.
    • A synthetic carrier through Hl2Spectrum reads 20·log10(A) + 6.02, and a carrier at the amsq gate peaks exactly on the drawn line at LNA −12/+8/+20.
    • Flex's Auto SQL level matches the old −160 + level arithmetic across −175..−40 dB.
    • The mode filter (HL2 FM and CW give no line; absent gives nothing).
  • wdsp_channel_test.
    • The offset reaches amsq and only amsq, and a non-finite offset is refused at the channel.
    • New carrier-threshold case: a 50% AM carrier 2 dB above the gate opens amsq (audio equals the squelch-off reference), and 2 dB below stays muted (0). Checked at offset 0 (gate −105 dBFS) and at +32 (gate −73).
  • hl2_rxdsp_squelch_test.
    • The offset reaches the channel and survives configure().
    • A NaN offset keeps the last good one and leaves nothing pending.
    • Through Hl2Backend on its real I/O thread: setSliceSquelch lands at the current LNA's offset, setLnaAutoOffsetDb(10) moves the gate −10 dB, and 0 moves it back.
  • rx_applet_squelch_reconciliation_test.
    • With Auto unavailable, the cycle runs Off→Manual→Off.
    • The reason is on both the RxApplet and VFO buttons, and both buttons stay visible and enabled.
    • A restored Auto intent lands on Manual.
    • A slice in Auto drops to Manual with squelch on at its manual level.
  • Mutation checks. Each mutant was applied, the test target rebuilt, the test run, and the file restored (sha256-checked). All 16 were killed:
    • LNA referral returning 0
    • slope dropped from thresholdDb
    • the +6.02 term dropped
    • FM added to the HL2 modes
    • Flex record removed
    • cycle skip removed
    • the setSqlMode Auto coercion removed (this one survived at first and was killed by the added restore-path test)
    • Auto→Manual drop removed
    • the reason dropped from the description
    • the VFO mirror description removed
    • the channel ignoring the offset
    • the legacy record removed from Icom, from the demo, and from ANAN (three mutants)
    • the NaN-offset guard in Hl2RxDsp::setSquelch
    • the pushSquelch in Hl2Backend::pushEffectiveLnaGain

Full local suite. ctest -j8 ran 563 tests: 561 passed, one skipped by design, and connection_panel_size_test failed, which is known on this host (#5811). Excluded: 68 tests whose sources use a socket API, or whose names match the socket-test families (automation, TCI, rigctl, CAT, discovery, bridge, DAX and so on), classified by grepping each test's sources from tests.cmake.

Agent automation bridge (demo sim, offscreen, own socket, AETHER_AUTOMATION_NO_TX=1).

  • Connected serial DEMO-0001; get radio returns AetherSDR Demo.
  • The demo now publishes Flex's scale. dumpTree shows "Squelch mode" with the description "Cycle squelch through Off, Manual, and Auto modes".
  • Three invoke RxApplet/Squelch mode click calls, reading the button and get slice after each:
Click Button Slice squelch
start SQL off, level 20
1 SQL on, 20
2 AUTO on, 66 (Auto SQL picked floor + 10 + 160 from the pan)
3 SQL off, 20

That is main's behaviour, unchanged.

  • Reconnect with Auto on (at 8bc6ebc): disconnect and then connect … DEMO-0001 again, which goes through the connected-but-no-active-slice window. Afterwards the button still reads AUTO (slice on, 66), with the Auto-available description, so it is not demoted to Manual.
  • The Auto-unavailable path: only the HL2 publishes it now, so the demo cannot reach it. It is proven in rx_applet_squelch_reconciliation_test: the cycle skips Auto, the reason reaches both buttons, a restored Auto intent lands on Manual, and a slice in Auto drops to Manual.

Not verified:

  • No HL2 hardware run. The HL2 path is proven against on8st's measured rows and synthetic IQ only. Two things still need an on-air check: the anchor (−12 dB) and the 1:1 shift.
  • No pan screenshot. Offscreen, the spectrum renderer is unavailable on this host, with or without AETHER_NO_GPU=1, so the line could not be grabbed.

Static gates (merge base vs head)

Run on the merge base and on the head; findings diffed per file.

  • check_engine_boundary.py --strict: 102 → 102 findings. The only diffs are line shifts in MainWindow_Wiring.cpp from one added include.
  • gen_touchpoint_manifest.py --check: up to date after regeneration. One new row, core/backends/SquelchLevelScale.h, tagged universal, with 2 includers (SpectrumWidget.h, MainWindow_Wiring.cpp).
    • Maintainer decision (Jeremy, on this PR): the new touchpoint is accepted — core/backends/SquelchLevelScale.h, universal, 2 GUI includers. It is the record type and its pure mode/Auto predicates. It follows the NoiseFloorAutoAdjustGate.h pattern, so the widget, MainWindow and the test read one function rather than copies.
  • check_capability_records.py --strict: 70 → 70 booleans. The record is a std::optional, and its autoSquelch bool is nested.
  • check_command_plane.py --strict: 400 → 400 (main's current baseline).
  • Also clean: check_test_registration, check_ci_test_gate, check_qsettings, gen_seam_probe_table --check. check_a11y reports 16 findings on the 8 touched GUI files, the same as main.

Thanks to @on8st for the hardware sweeps (the original map on #5982 and the LNA-pinned re-run on #6092), and for the RXA.c/amsq.c analysis of what amsq actually reads.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g

…shed scale; refer the HL2 amsq gate to the LNA. Principle VIII.

The panadapter SQL line and Auto SQL assumed every radio's squelch level is
Flex's -160 + level dBm. On the HL2 the amsq gate is -140 + 0.7*level in raw
dBFS, fixed against a pan axis that carries the LNA offset, so the line sat
up to tens of dB from the gate and Auto SQL held no margin.

- WdspChannel::setSquelch takes a caller-supplied offset for the amsq map.
  Hl2DbReference::levelSquelchOffsetDb() refers it to the LNA, anchored at
  -12 dB where on8st's measured brackets fit (supported, not established),
  so the gate is -108 + 0.7*L - lnaOffsetDb() and tracks the LNA dB for dB.
  Hl2Backend re-pushes it on every LNA change.
- RadioCapabilities::squelchLevelScale (optional record: offset, dB/step,
  modes, autoSquelch). Flex -160 + 1*L everywhere with Auto SQL; HL2
  -118.98 + 0.7*L on the pan axis in AM/SAM/DSB/LSB/USB, no Auto SQL (amsq
  reads passband magnitude before AGC, the pan per-bin noise). Icom, demo,
  ANAN, RTL absent. The +6.02 dB carrier term is pinned on both sides.
- SpectrumWidget draws and computes from the record; absent or unlisted mode
  means no line and no Auto SQL. RxApplet's cycle skips Auto where it is
  unavailable, a slice in Auto drops to Manual, and the reason is on the SQL
  button's accessible description (mirrored on the VFO).

Fixes #6092.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g
@ten9876
ten9876 marked this pull request as ready for review October 3, 2026 19:04
@ten9876
ten9876 requested a review from a team as a code owner October 3, 2026 19:04
@ten9876 ten9876 self-assigned this Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

Accessibility check findings

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

Files scanned : 8
Files with findings: 4
Total findings : 16

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 and others added 2 commits October 3, 2026 12:33
…TL-SDR. Principle II.

Per the maintainer's ruling on #6127: a family with no measured squelch map
of its own publishes legacyDbmSquelchScale() (-160 + 1*level, every mode,
Auto SQL), so its SQL line and Auto SQL behave exactly as before. Only the
HL2 carries its own map. squelch_level_scale_test pins each family.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g

@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.

Issue fit

Yes. #6092's three defects map cleanly onto the diff: the −160 + level line scale (SpectrumWidget::drawSquelchLine now reads thresholdDb()), the Auto SQL conversion (levelForThresholdDb() + the autoSquelchAvailable() early return), and the missing capability gate (autoSquelchShouldRunOnSpectrum + syncSquelchScaleToUi). The shape is the one the maintainer ruled on in the thread ("The plan (my decision): 1. a backend-published squelch-scale record… 2. refer the HL2 amsq map to the LNA… 3. Auto SQL stays hidden on the HL2"), with on8st's 2026-10-03 re-anchoring at −12 dB folded in. I checked the formula against his correction: −108 + 0.7L − lnaOffsetDb() and the implemented −140 + 0.7L + (G + 12) are the same function (−128 + 0.7L + G), since kReferenceLnaGainDb == kDefaultLnaGainDb == 20. The test asserts the first form and the code computes the second, which is the right way round.

Coverage is at the right layer — capabilities and DSP mapping, socket-free CTest. No socket test is added or modified; nothing in the diff touches QTcpServer/QUdpSocket/QLocalServer/bind/listen/connectToHost, and no fake radio peer appears.

Scope

File / group What it changes Claimed? Verdict
core/backends/SquelchLevelScale.h (new) the record + squelchScaleForMode / autoSquelchAvailable predicates yes in scope; new public surface, pre-ruled in the thread
RadioCapabilities.h std::optional<SquelchLevelScale> squelchLevelScale yes in scope
flex/FlexBackend.cpp, hl2/Hl2Backend.cpp publish the record yes in scope
hl2/Hl2DbReference.h, Hl2RxDsp.{h,cpp}, dsp/WdspChannel.{h,cpp} the LNA referral and its plumbing yes in scope
hl2/Hl2Spectrum.h kToneGainDb constant yes in scope — names existing behaviour, no code change
gui/SpectrumWidget.{h,cpp}, MainWindow*.{h,cpp}, RxApplet.{h,cpp}, VfoWidget.cpp read the record; skip/drop Auto; a11y text yes in scope
docs/architecture/* (3 files) caps-map row, touchpoint manifest + tags yes in scope; manifest is generated, 233→234 is consistent
tests/* (5 files) + tests.cmake new target, signature updates yes in scope

Nothing in the diff is unexplained by #6092 or its thread. No CHANGELOG.md entry (correct). No deleted guard — the only - lines of consequence are the removed kSqlMinDbm constants and the inlined SQL tooltip, both replaced. The three disclosed behaviour changes (Icom and Demo lose the line and Auto; HL2 manual squelch gates 32 dB higher at the default LNA) are all consequences of the ruled shape, not smuggled preference: the issue and the triage comment both establish that Icom's 0..255 register has no published dB mapping, so the line there was drawn on a scale that never applied.

Governance. New capability field arrives with the full trio (RadioCapabilities + radio-capabilities-map.md row + squelch_level_scale_test gating test), per AGENTS.md. New universal touchpoint header follows the NoiseFloorAutoAdjustGate.h precedent, and the justification is in the PR body. I re-ran the gates on the head checkout myself: check_capability_records.py --strict → 71 boolean(s), at the frozen 71 — ok, gen_touchpoint_manifest.py --check → manifest up to date, check_engine_boundary.py --strict → 102 findings / 0 blocking, check_test_registration.py → OK. The body's numbers reproduce.

Blockers

None.

Nits (all non-blocking)

  • A non-finite offset is refused, and the refusal is retried forever. WdspChannel::setSquelch returns false for !std::isfinite(levelOffsetDb), but Hl2RxDsp::pushSquelchToChannel cannot tell that apart from a transient control-op refusal: it sets m_squelchPending = true, and processIqBlock() then retries once per block for the rest of the session. A permanently-invalid argument and "busy, try later" want different answers. Inline comment on WdspChannel.cpp:602.
  • The unavailability reason can be wrong for a radio that does publish Auto. With no active slice yet (activeSlice() == nullptr) but isConnected() true, activeSliceSquelchScale() returns nullopt, and the reason selector falls through !published (false on Flex) and s && … (false) into the detector message — so a connecting Flex can briefly announce "this radio's squelch detector does not read the panadapter's noise floor". Harmless in practice: setActiveSliceInternal re-syncs before RxApplet::setSlice restores intent, which is exactly what the ordering comment protects. Treating !s as permissive (like disconnected) would close it. Inline on MainWindow_Wiring.cpp:1389.
  • At the shipped LNA default the usable slider span is ~5 steps. From on8st's +20 row and the implemented map, level 50 gates at −73.0 dBFS with his noise top at −73.5 and the carrier above −70 — the test's own 0.5 dB window. Everything below level 50 is under the floor (always open) and above ~55 is above any signal he measured. That is what the measurement says, and it is better than the pre-change behaviour (open below ~85), but the caps-map paragraph would be more useful to an operator if it said where the live range sits at +20. Inline on Hl2DbReference.h:105.
  • pushEffectiveLnaGain() now issues a second beginControlOperation()-taking push per LNA step (including every auto-RF-gain step), and pushSquelch sends even when squelch is off. Same cost class as the adjacent setAgc push that was already there, so probably fine — flagging only so it is a decision rather than an accident.
  • recordsPerBackend pins the HL2 mode list in publishedModeStrings() order, so a harmless reorder there fails a test that only cares about set membership. Inline on tests/squelch_level_scale_test.cpp:126.
  • Mixed i18n on the same user-visible sentence: MainWindow builds the reason with tr(), RxApplet embeds it in QStringLiteral scaffolding, so a translated reason lands inside an untranslated frame. Pre-existing for the surrounding strings.

What I tried to break

  • The arithmetic. Expanded the HL2 record offset by hand: −140 + (G + 12) + (3 − G) + 6.02 → −128.98 + 6.02, LNA-free, matching the test's −125 + kToneGainDb and the body's claim. Checked that a +10 dB LNA move leaves the displayed line fixed (gate moves +10 dBFS, offsetDb() moves −10) — the cancellation holds in both the gate and the pan direction, so the line does not walk with RF gain. Recomputed level 50 against all four of on8st's rows (+20 → −73.0, +8 → −85.0, −4 → −97.0, −12 → −105.0) and confirmed each sits inside his brackets, using his lower-bound −70 at +20 as the only thing that row can support.
  • Flex bit-for-bit equivalence. Walked levelForThresholdDb against the deleted clamp(int(target + 160 + 0.5f), 1, 100) at both ends of the test's sweep, including the negative-steps case where static_cast<int> truncates toward zero rather than rounding up as the comment says — both land on 1, so the comment is loose but the result is identical.
  • The Auto-drop as a Flex self-race (the attack that would have been a blocker): if isConnected() could be true while squelchLevelScale was absent on a Flex, every connect would silently drop a restored Auto to Manual and saveClientSquelchIntent() would persist autoEnabled = false with no path back. RadioModel::backendCapabilities() is a live call-through to m_backend->capabilities(), not a cached copy, and syncSquelchScaleToUi runs after m_activeSliceId is assigned and before m_appletPanel->setSlice(s). No window I can find.
  • Stale string-based invokers. The Q_INVOKABLE arity changed, which a wrong-arity invokeMethod would fail at runtime rather than at compile time. grep -rn '"setSquelch"' src tests on the head checkout returns exactly one hit, the updated Hl2Backend::pushSquelch. Confirmed m_dbRef.setLnaGainDb() runs before the push loop in pushEffectiveLnaGain.
  • The Kiwi paths. Checked that the new guards sit after the Kiwi branches in drawSquelchLine and updateAutoSquelchFromBins, that autoSquelchShouldRunOnSpectrum still returns on its Kiwi early-returns before reaching the new &&, and that RxApplet::autoSqlAvailable() ORs in usingExternalReceiveSquelch() so Kiwi keeps its own Auto. Kiwi is untouched.
  • The truncated diff. The wdsp_channel_test.cpp hunk is cut off above, so I read the file: runSquelchCarrierThresholdTest exists, is registered at line 3957, and does pin the ±2 dB carrier case at offsets 0 and +32 plus the non-finite refusal at :3607. The "verified on both sides" claim for the +6.02 dB term checks out.

Not verified, and it matters: everything above is reasoned from the code and from on8st's posted brackets — I ran no build, no test and no GUI. CI green means "it compiles." Every ctest step in ci.yml is -R-filtered and none of the filters names a squelch, wdsp-channel or rx-applet test, so the author's local run and the 11 mutation kills are the only evidence those tests pass. And as the body says honestly, the −12 dB anchor and the 1:1 LNA shift are supported, not established — one on-air sweep on an HL2 is what would settle the thing this PR is built on.

Recommendation

Approve with nits — with a maintainer note. The code is careful, the seam is the one that was ruled on, the gates reproduce locally, and the LNA referral is algebraically consistent in both directions. None of the nits break a user. What a maintainer should weigh is not the code: it is that HL2 manual squelch moves 32 dB at the shipped default gain on the strength of bracket fits from one afternoon, with the author saying so plainly. Worth landing and then getting an on-air confirmation of the anchor from @on8st, rather than holding the GUI fix hostage to it. Concrete next step: the isfinite-refusal nit is a two-line change and worth taking before merge; the rest can ride.

Nice work — in particular the mutation table, the honest "supported, not established" framing, and excluding Auto SQL on the HL2 instead of shipping a plausible-looking pan-derived floor.


🤖 aethersdr-agent · cost: $8.2055 · model: claude-opus-5

Comment thread src/core/dsp/WdspChannel.cpp
Comment thread src/gui/MainWindow_Wiring.cpp
Comment thread src/core/backends/hl2/Hl2DbReference.h
Comment thread tests/squelch_level_scale_test.cpp Outdated
@on8st

on8st commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

@ten9876 Checked 790ef15 against our rows by reading the source; nothing was built or run. Bad news first.

  • The +20 row carries the anchor, and it is our weakest leg. Its floor bracket is one coarse pass (the fine passes opened once in 30), on a frequency that had a signal on it 40 minutes later; the carrier there is only a lower bound. squelch_level_scale_test.cpp:85-90 accepts an anchor from −15 to −11.5 dB with that row, and −21.4 to −8.2 without it.
  • The line is exact for a steady carrier only (SquelchLevelScale.h:3-4). On noise alone the gate opens while the line is still 10·log10(passband / 1.5 bins) − 1 dB above the pan floor: 19.5 dB at 48 kHz in AM 8 kHz, 5.8 dB at 384 kHz in 2.7 kHz. Arithmetic, not measured.
  • I found no test that reaches the re-push on an LNA change (Hl2Backend.cpp:7043, :3293).

Level 50 from Hl2DbReference.h:105-112 and WdspChannel.cpp:651, raw dBFS:

LNA gate no signal carrier
+20 −73.0 −80.5..−73.5 above −70
+8 −85.0 −95.9..−93.8 −75.6..−71.4
−4 −97.0 −103.6..−100.8 −84.7..−82.6
−12 −105.0 −113.4..−109.9 −93.1..−91.0

Inside all four. The record includes the pan offset (Hl2Backend.cpp:1892-1893, :858), so the line holds across gain. FM is untouched (WdspChannel.cpp:677).

73, ON8ST

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

Accessibility check findings

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

Files scanned : 8
Files with findings: 4
Total findings : 16

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.

…pping

# Conflicts:
#	docs/architecture/aetherd-touchpoints.md
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

Accessibility check findings

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

Files scanned : 8
Files with findings: 4
Total findings : 16

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 and others added 2 commits October 3, 2026 15:10
Hl2RxDsp keeps the last finite level offset, so a non-finite one is not
retried at the IQ block rate as if the channel were busy; the reason
selector stays permissive before a slice is active; the HL2 mode-list
test checks membership, not order; the SQL button's frame strings go
through tr(); and the capabilities map says where the HL2's working
squelch range sits at the default LNA gain.

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 16 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 8
Files with findings: 4
Total findings : 16

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.

hl2_rxdsp_squelch_test now drives Hl2Backend: a slice squelch lands on
amsq at the current LNA's offset, and setLnaAutoOffsetDb moves the gate
dB for dB and back, read on the chain's own thread. Removing the
pushSquelch in pushEffectiveLnaGain fails it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AADGw3HVn5FMMVrx2RQZ9g
@ten9876

ten9876 commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

Maintainer pass on 8bc6ebc. This covers the points in the bot's review body and in @on8st's 19:49 check that have no inline thread. All four inline threads are answered and resolved.

on8st, untested re-push on an LNA change (Hl2Backend.cpp:7043, :3293): now pinned. In 8bc6ebc, hl2_rxdsp_squelch_test drives Hl2Backend with a configured chain on its real I/O thread:

  • setSliceSquelch(0, true, 50) lands amsq at the current LNA's offset.
  • setLnaAutoOffsetDb(10) moves the gate −10 dB.
  • Setting the offset back to 0 restores it.

The values are read on the chain's own thread. If the pushSquelch(r) in pushEffectiveLnaGain is deleted, the test fails with FAIL: an LNA change re-pushes the squelch: the gate moves with it. It passed 20 of 20 repeat runs.

on8st, the +20 row carries the anchor. The maintainer took −12 dB as supported, not established, and that is how the PR body and Hl2DbReference.h state it. The caps map now also states the working range at +20 dB (thread on Hl2DbReference.h:105). An on-air sweep stays the thing that would settle it.

on8st, the line is exact for a steady carrier only. That is the record's stated contract (SquelchLevelScale.h:3-4, "read as the pan peak of a steady carrier"). It is also the reason Auto SQL stays off on the HL2: amsq reads passband-limited magnitude while the pan reads per bin. No code change.

Bot body: extra beginControlOperation() push per LNA step, and pushSquelch sends while off. Kept as is. The push costs the same as the adjacent setAgc push that was already there. Sending while off keeps Hl2RxDsp's held offset in step with the LNA, so a later "on", or the re-application in installChannel() after a rebuild, never uses a stale offset. pushNoiseBlanker follows the same rule.

Bot body: mixed i18n. In f77795a, the SQL button's tooltip and accessible-description frames in RxApplet::applySqlButtonDescription / sqlButtonAccessibleDescription go through tr(), matching the tr() reason that MainWindow puts inside them.

Bot review predates 33711e2. Icom, the demo, ANAN and RTL-SDR now keep Flex's {−160, 1, all modes, Auto}, so they no longer lose the line or Auto SQL. squelch_level_scale_test pins this for every backend.

Local proof at 8bc6ebc: RelWithDebInfo build is clean. squelch_level_scale_test, hl2_rxdsp_squelch_test, wdsp_channel_test, rx_applet_squelch_reconciliation_test, hl2_apf_agc_off_test and the other wdsp/hl2_rxdsp tests pass. The full socket-free ctest passes 558/559; the one failure is connection_panel_size_test, which is known on this host (#5811). The static gates match main: engine-boundary 102 findings with 0 blocking, capability records at the frozen 70, command plane 401/32, manifest up to date. The only new touchpoint row is the accepted SquelchLevelScale.h.

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

Accessibility check findings

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

Files scanned : 8
Files with findings: 4
Total findings : 16

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.

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

Accessibility check findings

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

Files scanned : 8
Files with findings: 4
Total findings : 16

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.

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

Accessibility check findings

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

Files scanned : 8
Files with findings: 4
Total findings : 16

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.

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

Accessibility check findings

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

Files scanned : 8
Files with findings: 4
Total findings : 16

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.

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

Accessibility check findings

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

Files scanned : 8
Files with findings: 4
Total findings : 16

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 merged commit a8cfd99 into main Oct 4, 2026
7 checks passed
@ten9876
ten9876 deleted the fix/6092-sql-level-mapping branch October 4, 2026 01:53
@ten9876

ten9876 commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

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

  • HL2 amsq gate referred to the LNA, anchored at −12 per @on8st's re-measured sweep. That anchor is "supported, not established" and still needs an on-air check.
  • squelchLevelScale record. Icom, the demo, ANAN and RTL keep the Flex scale; HL2 has its own line and no Auto SQL.
  • Touchpoint core/backends/SquelchLevelScale.h accepted.
  • All bot threads answered and resolved. Required checks green, from a run created after main's tip; no drift.

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

PR #6127 — post-merge review of 1c574109331ed0855331d2eb3c822813533a54ea (merged while this review was running).

Issue fit: The implementation addresses #6092's HL2 scale mismatch: the backend publishes the line's scale, the DSP gate follows LNA gain, and Auto SQL is unavailable where a passband detector cannot use the per-bin pan floor. The issue thread explicitly authorizes this shape; the later maintainer PR comment accepts the new touchpoint and retaining legacy mappings on Icom/demo/ANAN/RTL. Those families' calibration is intentionally not solved here. Hardware confirmation of the −12 dB anchor remains outstanding and is disclosed.

Scope

File/group Change Claimed? Verdict
RadioCapabilities.h, new SquelchLevelScale.h Optional record, forward/inverse mapping, mode/Auto predicates Yes In scope; new surface has maintainer ruling
Flex/Icom/sim/ANAN/RTL backend declarations Preserve existing line and Auto behavior Yes In scope; accepted compatibility choice
hl2/Hl2Backend.{h,cpp}, Hl2DbReference.h, Hl2RxDsp.{h,cpp} LNA referral, queued offset propagation, gain-change re-push, test access Yes In scope
dsp/WdspChannel.{h,cpp}, hl2/Hl2Spectrum.h Offset only on level squelch; named existing carrier normalization Yes In scope
MainWindow.{h,cpp}, MainWindow_Wiring.cpp, SpectrumWidget.{h,cpp} Publish active-mode scale to rendering and Auto algorithm Yes In scope
RxApplet.{h,cpp}, VfoWidget.cpp Skip unavailable Auto, coerce restored Auto, explain availability Yes In scope; issue-authorized correction
Four test sources and tests/tests.cmake Mapping, DSP, lifecycle and UI regressions Yes In scope
Three docs/architecture/ files Capability map and generated touchpoint/tag entry Yes In scope; new universal include explicitly accepted

All 31 changed files are explained by the issue and accepted follow-up decisions. Commit author dates and messages reveal no unrelated bundled work. No CHANGELOG entry or ratchet weakening. The boundary audit reproduced 102→102 findings (only line-number changes), 70→70 capability booleans and 400→400 above-seam commands; both manifests passed. The new universal touchpoint is the documented 236→237 growth, not a vendor reclassification. No new family-string branch, concrete-backend cast, wire command above the seam, thread or TX path was introduced.

Blockers

None found.

Nits — non-blocking

  1. Isolate the newly settings-touching HL2 test. tests/hl2_rxdsp_squelch_test.cpp:89 starts QCoreApplication without TestSettingsProfile, and the new backend at :194 calls AutomationBridgeSettings::txAllowed() during construction. That instantiates AppSettings, whose constructor creates the normal configuration directory and runs the platform path-migration hook. The generated CTest environment only supplies the WDSP planner/wisdom overrides. With AETHER_SETTINGS_DIR unset and XDG_CONFIG_HOME redirected to a fresh scratch directory, I observed the head test create AetherSDR/; the identical probe against the built merge-base test did not create it. This was an isolated probe, not an experiment on the operator's profile; I did not observe settings-value modification on Linux. Add the same TestSettingsProfile used by the new scale test before QCoreApplication, and check isValid(). This follows the project test-isolation convention and keeps the backend fixture self-contained.

    Add #include "TestSettingsProfile.h", then start main with:

        TestSettingsProfile settingsProfile(QStringLiteral("hl2-rxdsp-squelch-test"));
        if (!settingsProfile.isValid()) {
            return 1;
        }
        QCoreApplication app(argc, argv);

What I tried to break

  • Built the exact PR head in an isolated worktree. All four focused socket-free tests passed: squelch_level_scale_test, hl2_rxdsp_squelch_test, wdsp_channel_test, rx_applet_squelch_reconciliation_test. The GUI test initially hit the host's inherited GTK3 platform theme/display dependency; it passed with offscreen generic/Fusion styling. The preflight covered full test sources and the cold backend constructors, including Flex's inert socket allocation; no test opens/binds/connects a socket or runs a synthetic firmware peer.
  • Mutation-checked the actual test sources using separate compiled objects so the production build remained unchanged: dropping the line slope caused ten scale-test failures; omitting the WDSP offset failed both WDSP and HL2 tests, including reconfigure and LNA-change assertions; removing the Auto coercion failed restoredAutoIntentLandsOnManualWhenUnavailable (Actual: 2, Expected Manual: 1). All mutants exited 1. The unmodified DSP test also exercised the ±2 dB AM carrier gate at offsets 0 and 32.
  • Drove the head's own offscreen simulator over /tmp/6127-demo-head/bridge.sock, isolated settings, AETHER_AUTOMATION_NO_TX=1. get radio confirmed "model":"AetherSDR Demo","serial":"DEMO-0001","transmitting":false. Clicking the real RxApplet button produced (squelch,level) false/20 → true/20 → true/66 → false/20; manual level stayed 20. Auto remained on at level 66 after disconnect/reconnect and USB→AM→USB. The accessible description retained all three modes. Only this owned instance was driven and it was closed.
  • Built exact merge base 3e4b14e2e and ran the identical demo script on its own /tmp/6127-demo-base/bridge.sock. All eight state snapshots matched the head, including manual-cache preservation, reconnect and the mode round trip. Re-launched both builds with their respective existing isolated profiles: each starts Off/20 and then produces the same sequence. Thus this demonstrates unchanged restart behavior, not cross-process Auto persistence. All four runs were explicitly compared by assertions. Both owned demo instances were closed after every run; the merge-base HL2 and RxApplet tests passed as well.
  • Read the failure paths and sibling surfaces: the non-finite-offset guard prevents permanent retries; the queued invoker uses the new arity; gain is updated before re-pushing; FM's detector ignores the added level offset; Kiwi retains its early-return path and availability override. The existing review's stale no-active-slice and non-finite-offset concerns are fixed in this head. No independent automated-review skill was available.

Evidence limits: The simulator cannot verify the HL2 hardware anchor or RF-gain transfer, nor its Auto-unavailable mode through a real HL2 session; the latter is covered by the injected UI and DSP tests. The screenshot request returned {"error":"grab produced an empty image for SpectrumWidget","ok":false}; no pixel-level line-placement proof is claimed. Synthetic carrier FFT arithmetic and the line mapping were tested, including all gain/level combinations in a separate header harness. No live radio was used. The head's CI and Static Checks were successful, created at 2026-10-04 01:28:12 UTC, after its included main tip's 01:13:20 UTC commit; this is not a claim that all tests were CI-gating or that the later squash has been independently certified.

Recommendation

Approve with nits, as a post-merge assessment rather than a pending merge action. No user-facing regression was found in the tested paths. Add test-profile isolation in a small follow-up and obtain the disclosed HL2 hardware confirmation of the anchor when feasible; this review does not substitute simulator evidence for RF measurements.

on8st added a commit to on8st/AetherSDR that referenced this pull request Oct 4, 2026
main gained aethersdr#6127 (squelch scale) after the first merge. No textual
conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
on8st added a commit to on8st/AetherSDR that referenced this pull request Oct 4, 2026
main gained aethersdr#6127 (squelch scale) after the first merge. No textual
conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
on8st added a commit to on8st/AetherSDR that referenced this pull request Oct 4, 2026
…VIII.

aethersdr#6127 added a row on main and this branch has one of its own, so the
merged tree again held one row more than its Totals line said.
Regenerated with tools/gen_touchpoint_manifest.py: 238 headers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
on8st added a commit to on8st/AetherSDR that referenced this pull request Oct 4, 2026
main gained aethersdr#6127 (squelch scale) after the first merge. No textual
conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ten9876 added a commit that referenced this pull request Oct 4, 2026
Main's #6127 added RadioCapabilities::squelchLevelScale for the SQL line and
Auto SQL; this branch had ReceiveSquelchModel for the same RTL gate. Folded
into one record (maintainer ruling): SquelchLevelScale gains `unit` and
`modesExclusive`. RTL publishes RtlSquelchGate's -120 + 1.2*level dBFS/bin,
FM/FMN, Auto SQL, exclusive; every other family leaves modesExclusive false,
so the #2504 mode rule and radio-owned squelch state are unchanged.
SpectrumWidget keeps one setSquelchScale and the finite-bin Auto estimator.

Where the registry marks SQL unavailable in a mode, RxApplet and VfoWidget no
longer overwrite its accessible reason with the Auto-SQL description.

peripheral_auth_dialog_test (#6008) links RadioSetupDialog.cpp, so it now
lists RtlReceiverSettingsWidget.cpp and ControlAvailabilityRegistry.cpp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ten9876 added a commit that referenced this pull request Oct 4, 2026
…#5924)

## Summary

RTL receive changes now publish only after hardware readback and DSP
adoption. Capture browsing preserves each slice's frequency and
settings, parks receivers outside usable capture, and resumes them when
capture returns. Offscreen markers reveal the receiver through a
confirmed capture transaction. Zoom uses a continuous 65,536-sample FFT
(36.621 Hz bins at 2.4 MS/s), independently of audio and capture
bandwidth.

FM/FM-N have symmetric capability-bounded filters, 48 kHz per-receiver
PCM, squelch, session/revision fences, rollback and accepted-state
persistence. Requested or refused state never becomes saved live state.
Spectrum averaging preserves RF history through display movement, and
coherent capture coverage supports the 3D waterfall. PPM/DC settings use
confirmed backend controls. Production admission at this checkpoint
remains one receiver.

Implements the bounded receive runtime of RFC #5468; respects the
backend seam obligations in #5554/#5262. WFM stereo, optional digital
decoding, live PPM interaction and multi-RX delivery are downstream
increments.

## Integration and validation

Reconciled with main at `a798558ac` (#6118).

The reconciliation preserves main's typed tune/filter/mode/APF intents,
HL2 squelch and WDSP changes. Each slice's SQL intent uses the existing
common audio-intent binding once; the merge does not add a duplicate
backend connection. The confirmed-pan dispatch comment now distinguishes
an accepted request awaiting observation from refusal. Runtime
documentation now describes the implemented 65,536-bin display FFT.

Checkpoint `efc358a1` native Apple Silicon/Qt 6.12 build and CTest:
**5/5 pass**, no skips, in 11.29 seconds (`receive_intent_routing_test`,
`slice_apf_agc_off_seam_test`, `flex_slice_mode_intent_test`,
`rtl_model_acceptance_test`, `slice_model_letter_test`). These are
socket-free model/dispatch regressions; they do not claim live-radio,
GUI or hardware timing qualification. The downstream #6083 application
and broader tests are separately revision-pinned there.

This requests human review and preserves the outstanding in-scope
maintainer decisions; it does not mark the free-pan amendment approved.
HD decoding is outside this PR and HD consent is not a merge condition
here.

Upstream's single spectrum outlet and socket-free test rehoming are
preserved. The real model derives paced waterfall rows from spectrum
observations; retired orphan waterfall signals are not restored. The new
upstream receive-contract test now expects RTL's implemented FM/FM-N
filter capability. Generated seam/touchpoint manifests are regenerated.

Earlier downstream aggregate evidence (not rerun on this individual
head): the aggregate builds AetherSDR with native Apple Silicon/Qt 6.12,
RTL required and the optional decoder OFF. Focused downstream tests
already pass for Flex mode dispatch, confirmed RTL model/capture state,
FFT averaging, WFM, the PPM widget and seam consumers. This is aggregate
evidence, not a claim that every platform was rerun on this individual
head. Fresh hosted checks are required on this PR.

The operator reports that the delivered `7601593f` build ran stably for
days. That is live hardware feedback on the delivered build, not an
instrumented soak of the new upstream integration. Current-head
sanitizer, ARM throughput and full release-artifact qualification are
not claimed.

## CI integration repair

Upstream Radio Setup test targets (including #6008's
`peripheral_auth_dialog_test`) compile `RadioSetupDialog.cpp`, so they
also list the RTL widget and its availability registry. Both repaired
tests build and pass on the native Mac decoder-OFF integration (2/2,
4.00 seconds). This follow-up changes test linkage only; earlier
application/regression measurements retain their stated revisions.
New-head CI is required and remains distinct from those measurements.

## Maintainer decisions

All of the decisions below were made by Jeremy, the maintainer.

- **2026-09-23 rulings.** The [mode-transition, seam and scope
rulings](#5924 (comment))
stand.
- **2026-10-03, free capture browsing.** The RFC #5468 amendment of
2026-09-25 (free capture browsing with parked slices) is **approved as
RTL-only**; other backends keep their existing pan behavior.
- **2026-10-03, later seam additions.** The seam pieces added after
09-23 are **ratified as RTL-owned**, on the same terms as 09-23: a
second backend must justify reuse. They are `SquelchLevelScale`'s `unit`
and `modesExclusive` fields; `requestReceiveTune(view)`,
`recenterReceiveCapture` and `ReceiveCapturePlacement`; and the
`BackendPanAveraging` persistence/description fields. Averaging
persistence and coherent 3D presentation are part of that ratification.
- **2026-10-04, one squelch record.** RTL's squelch is described by
#6127's `RadioCapabilities::squelchLevelScale`, not a second record.
`unit` carries RTL's dBFS/bin, and `modesExclusive` marks the record as
the receiver's own gate.

HL2 FFT-FPS/dBm persistence coordination stays separate, with sibling
ClientDisplay tables preserved.

## New gui/ touchpoints (ratified)

| Header | New includer | Why |
|---|---|---|
| `core/backends/RadioCapabilities.h` | `src/gui/ModeFilterPresets.cpp`
| FM/squelch availability reads the declared
`ReceiveFilterControl`/`SquelchLevelScale` records |
| `models/PanadapterModel.h` | `src/gui/PanFrameGuard.h` | Snapshots the
pan's geometry when a frame arrives, so a confirmed FFT is never painted
against a newer range |
| `models/RadioModel.h` | `src/gui/ReceiveCaptureAction.h`,
`src/gui/RtlReceiverSettingsWidget.cpp` | Capture-recenter action and
the RTL device settings both go through RadioModel's capability-gated
verbs |
| `models/SliceModel.h` | `src/gui/MainWindowHelpers.cpp` |
`requestSlicePanCenter` resolves the slice's pan and sends confirmed
backends a Center tune instead of a pan request |

## Effects on other radios

- **Squelch availability** follows one rule in both the RX applet and
the VFO (`ModeFilters::squelchAvailableInMode`). A `squelchLevelScale`
with `modesExclusive` (RTL only: FM/FM-N, −120 + 1.2·level dBFS/bin,
Auto SQL) lists the supported modes, keeps each receiver's manual/Auto
intent, and has Auto start from the absolute threshold. Otherwise the
#2504 rule disables SQL in DIGU/DIGL/NT/RTTY/CW, unless the radio
declares mode-independent squelch. Every other family's scale leaves
`modesExclusive` false, so its SQL line, Auto SQL and availability are
unchanged.
- **SQL's unavailable reason** stays on the button. Where SQL is
unavailable in the current mode, the RX applet and the VFO no longer
replace the registry's accessible reason with the Auto-SQL description.
- **FM-family passband availability** follows
`ModeFilters::fmFilterAdjustable`. Declared FM rows are authoritative
(RTL). A radio-owned filter with no FM row refuses FM edits (Flex, per
FlexLib `Slice.cs`). HL2, ANAN, Icom and the Demo keep their existing
adjustable passband.
- **WFM/WBFM** use carrier-centered FM edges and no longer inherit the
SSB preset ladder.
- **Cancelled VFO drags** release the drag hold without the final retune
(`SpectrumWidget::sliceDragCancelled`).
- **Off-thread `SliceModel` setters** are refused and logged.

## CI

The Linux `build` job now deletes unused host toolchains (Android, .NET,
GHC, CodeQL) before building. The full RelWithDebInfo tree, which now
includes this PR's RTL tests (about 3.6 GiB of test binaries), outgrew
the hosted runner's free disk. Jeremy authorized the workflow change.

## Integration with #6107

#6107 gated a stranded RTL worker on the `connectRadio()` wiring this PR
replaces. This PR's `startCapture()` already detaches the worker before
stopping it, and gates every relay on producer identity and the capture
session, so that wiring is kept. #6107's read-error guarantee is pinned
in `rtl_capture_worker_test`'s stop-timeout block. That test fails if
the producer gate is removed.

Principles II, V and XIV govern confirmed state and persistence; VIII/XI
govern evidence and regression tests.

---
73, Ozy **K6OZY** · Codex

Generated with OpenAI Codex (GPT-6 Astra)

---------

Co-authored-by: jensenpat <patjensen@gmail.com>
Co-authored-by: Jeremy [KK7GWY] <kk7gwy@aethersdr.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeremy [KK7GWY] <105957118+ten9876@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>
ten9876 added a commit that referenced this pull request Oct 8, 2026
…ick off. Principle II. (#6175)

Fixes #6172.

## What was wrong

On Icom, AetherSDR's squelch could get stuck **off**.

There is no squelch enable on these radios: the `14 03` threshold is the
control. `IcomCivBackend` therefore derived `squelchOn = level > 0` from
every readback, and the confirmation read of our own "on, level 0" write
published Off. `SliceModel` confirms receive controls from the radio, so
from then on:

- every `setSquelch(receiveSquelchOn(), level)` was sent as Off and
wrote `0000` again. That covers TCI `sql_level`/`sql_enable`, the SQL
shortcut and controller bindings;
- once the remembered manual level was 0, SQL → Manual wrote `0000` and
its readback returned the slice to Off. Squelch could not be turned on
from AetherSDR again.

This affects the IC-7300MK2, IC-705 and IC-9700, which share the write,
the decode and the confirmation read. The MK2 also re-reads `14 03`
every few seconds, so it re-asserted Off even without a write.

Live on v26.10.1, Manual at 75 when a TCI client connected:

```
TCI rx: "sql_enable:0,true;"   TCI rx: "sql_level:0,0;"
TX -> ... 14 03 00 00 fd       RX <- 14 03 00 00
TCI tx→all: sql_enable:0,false;            # our own write read back as Off
TCI rx: "sql_level:0,20;" ... "sql_level:0,100;"
TX -> ... 14 03 00 00 fd   (all six)       # every later level written as 0
```

## The fix

`IcomCivBackend` remembers that its last `14 03` write was **on at
threshold 0** (`m_squelchOnAtZero`).

- That write's 0 readback, and the MK2's periodic 0 poll, stay **on**.
- A radio-side non-zero value clears the flag, so a later front-panel
return to 0 still reads as off.
- An explicit Off write reads as off, and disconnect forgets the flag.
- The controls scrub re-asserts on-at-0 as on.

## Proof (live IC-7300MK2, proof build)

**1. The trapped state is no longer a trap.** Starting from SQL Off with
the remembered manual level at 0, clicking SQL → Manual wrote `0000`.
The radio echoed `0000` and squelch **stayed on** through three periodic
polls. Before this fix it bounced back to Off.


![after](https://raw.githubusercontent.com/jensenpat/AetherSDR/icom-comp-sql-assets/.pr-assets/6172-after-sql-manual-at-0.png)

**2. The TCI sweep that used to stick at 0** (`sql_enable:true`, then
`sql_level` 0 → 100) now writes every level as itself: `0000, 0051,
0102, 0153, 0204, 0255`. No `sql_enable:false` is broadcast back. RX
audio over TCI shows the radio honouring each write: open at -17 dBFS
through 0102, digital silence (-200 dBFS) from 0153 up.

## Tests (headless)

`icom_control_profile_test` runs a new case per model (IC-7300MK2,
IC-705, IC-9700):

| Readback | Expected |
|---|---|
| connect-time 0 | off |
| 0 after an on-at-0 write | on |
| periodic 0 poll after an on-at-0 write | on |
| non-zero threshold | on, at that level |
| radio-side 0 after a non-zero threshold | off |
| 0 after an explicit Off write | off |

Run against the pre-fix backend, the two on-at-0 checks fail on all
three models. All 27 Icom and squelch-related ctest targets pass.

## Not in this PR: the SQL line / Auto SQL scale on Icom

Icom still publishes `legacyDbmSquelchScale()` (Flex's `−160 + level`
dBm, #6127). I measured the MK2's SSB S-meter squelch through this
build: `14 03` stepped while the TCI RX audio was watched for the gate,
on 14.2968 USB band noise.

| Front end | S-meter | Gate (`14 03` raw) |
|---|---|---|
| preamp off | −106.5 dBm (raw ≈ 46) | ≈ 113 |
| P.AMP1 | −86.5 dBm (raw ≈ 90) | ≈ 146 |
| P.AMP2 | −77 dBm (raw ≈ 111) | ≈ 166 |
| 20 dB ATT | pinned at S0 | ≈ 85 |

That fits roughly `gate_raw ≈ 0.82 · smeter_raw + 75`, or about `−168 +
1.4 · level` dBm below S9. The current line is therefore around 15–20 dB
below where the radio gates.

A correct line also needs the Icom pan's nominal dBm axis reconciled
with the S-meter: the pan floor read −123 while the S-meter read −87 on
the same noise. That is a calibration change for its own PR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Jeremy [KK7GWY] <kk7gwy@aethersdr.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.

HL2: panadapter SQL line and Auto SQL use the Flex −160+level scale; the HL2 amsq gate is −140+0.7·level dBFS

2 participants