Skip to content

Commit 1a4a40c

Browse files
secupclaude
andcommitted
fix(snr): SNR-SANITY exclusion connected-only + ring-distribution conviction (F241: chirp samples met the >=3 bar and a single-sample compare still blinded the entry)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019btwFxDt7D19SZSqPYvZnk
1 parent be8d1e0 commit 1a4a40c

2 files changed

Lines changed: 31 additions & 8 deletions

File tree

docs/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ This log tracks all bug fixes and behavioral changes to prevent re-doing work du
1010

1111
---
1212

13+
## 2026-07-13 — fix(snr): SNR-SANITY exclusion is connected-only + distribution-convicted (F241: the ≥3 threshold was satisfied by chirp samples, and a single-sample comparison still blinded the entry)
14+
15+
F241 failed identically AFTER the ≥3-sample threshold: the §6 chirp samples
16+
legitimately fill the ring to ≥3 by CONNECT time, and the comparison still
17+
used the LATEST single physical sample — which on this bench episodically
18+
under-reads (the filed IONOS gap-reference instability). Two rules now:
19+
(1) NO exclusion authority pre-connected — the physical entry CAP already
20+
bounds hot entries there; a false conviction blinds the pick entirely.
21+
(2) Conviction requires usable > ring_mean + ring_spread + 2.0 — on fading,
22+
one sample is one fade instant; the invariant only binds distributions.
23+
24+
---
25+
1326
## 2026-07-13 — fix(snr): SNR-SANITY attribution threshold — the gate may only exclude with a ≥3-sample physical distribution (rig F238/F239: single-snapshot exclusion left entries BLIND at 15.0/none)
1427

1528
At the handshake the physical ring holds 1-2 samples in an unknown fade state.

src/gui/modem/streaming_sync_acquisition.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -312,19 +312,29 @@ void StreamingDecoder::populateDecodeMetrics(DecodeResult& result, bool is_ofdm,
312312
// fallback). Single-sample disagreement is logged by the WARN below
313313
// when it trips with n>=3; below that, silence — a snapshot cannot
314314
// convict a meter.
315+
// F241 (2026-07-13): two hard-won rules for exclusion authority.
316+
// (1) NEVER pre-connected — at the handshake the physical entry CAP
317+
// already bounds a hot reading physically, so exclusion buys
318+
// nothing there and a false conviction blinds the entry pick
319+
// entirely (15.0/source-none default -> DQPSK fallback; F238/239/
320+
// 241 all died this way, each against a single trough/ref-glitch
321+
// physical sample).
322+
// (2) Convict against the ring DISTRIBUTION (mean + spread + margin),
323+
// never a single sample — on fading, usable and any one physical
324+
// sample are different fade instants; the invariant usable<=channel
325+
// only binds same-moment or distribution-vs-distribution.
315326
float ph_mean = 0.0f, ph_spread = 0.0f;
316327
const size_t ph_n = physicalSnrStats(ph_mean, ph_spread);
317328
if (result.snr_source == SNRSource::MCDPSK_IN_BAND &&
329+
connected_ &&
318330
ph_n >= 3 &&
319-
last_physical_snr_valid_.load() &&
320-
result.snr_db > last_physical_snr_db_.load() + 2.0f) {
331+
result.snr_db > ph_mean + ph_spread + 2.0f) {
321332
LOG_MODEM(WARN,
322-
"[%s] SNR-SANITY: usable %.1f dB EXCEEDS channel %.1f dB "
323-
"(+%.1f > 2.0 margin) — meter suspect, reading excluded "
324-
"from rate selection for this frame",
325-
log_prefix_.c_str(), result.snr_db,
326-
last_physical_snr_db_.load(),
327-
result.snr_db - last_physical_snr_db_.load());
333+
"[%s] SNR-SANITY: usable %.1f dB EXCEEDS channel "
334+
"%.1f±%.1f dB (n=%zu, margin 2.0) — meter suspect, "
335+
"reading excluded from rate selection for this frame",
336+
log_prefix_.c_str(), result.snr_db, ph_mean, ph_spread,
337+
ph_n);
328338
result.snr_source = SNRSource::SYNC_QUALITY;
329339
}
330340
}

0 commit comments

Comments
 (0)