Skip to content

Commit 8c3c67c

Browse files
secupclaude
andcommitted
feat(rate): handoff §6 second half — chirp-span physical samples close the trough gap (entry line carries channel X±Y at the pick; awgn@10 + good@10 PASS)
Every dual-chirp anchor contributes one channel sample (up-chirp span power vs the frame's gap noise ref, >=3 dB anti-noise latch) — the entry cap now has data even when the data span is in a fade null (F233's measured gap). Residual: chirp convention reads ~+1.9 vs dial — folded into the filed §2 derivation test; the cap margin absorbs it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019btwFxDt7D19SZSqPYvZnk
1 parent 59e138e commit 8c3c67c

2 files changed

Lines changed: 55 additions & 1 deletion

File tree

docs/SNR_CALIBRATION_HANDOFF_2026_07_08.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,14 @@ trough-zero wire reading (F233: "usable 0 dB" → boosted → R1/2, no physical
190190
sample at pick time) runs uncapped exactly where the boost is most dangerous.
191191
The chirp-accumulation half (this section's main spec) closes that: chirp
192192
|H|² samples have ~20 dB processing gain and measure cleanly inside data
193-
nulls. F233 also re-confirmed the mid-transfer dial-10 stall class
193+
nulls. **SHIPPED 2026-07-08 (second half): every dual-chirp anchor now
194+
contributes a physical sample (up-chirp span power vs the frame's gap noise,
195+
≥3 dB latch) — the entry line carries `channel X±Y` AT the pick (verified
196+
awgn@10 + good@10, both PASS, cap active at entry). RESIDUAL: chirp-referenced
197+
samples read ~+1.9 dB vs dial on AWGN (duty-factor + reference-convention
198+
detail) — fold into the §2 per-profile derivation test (derive
199+
10log10(P_chirp_span/P_ping_ref) from the generator, never tune by eye); the
200+
cap's 2.0 margin absorbs it until then.** F233 also re-confirmed the mid-transfer dial-10 stall class
194201
(rate thrash R1/2↔R2/3 riding fade epochs, then full-anchor-wait reject
195202
streak=1461 — the BUG-DECODE-BACKLOG/anchor family, pre-existing).
196203

src/gui/modem/streaming_sync_acquisition.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,53 @@ void StreamingDecoder::searchForSync() {
654654
}
655655
sync_noise_ref_rms_ = static_cast<float>(
656656
std::sqrt(sum_sq / static_cast<double>(meas_end - gs - skip)));
657+
// Handoff §6 second half (F233 trough gap): the CHIRP span is
658+
// a channel power sample that survives fade nulls — the
659+
// data-span physical correctly abstains in a trough (its
660+
// anti-noise gate), which left the entry cap blind exactly
661+
// where the affine boost is most dangerous (F233 entered off
662+
// a wire-0 trough with an empty ring). Every dual-chirp anchor
663+
// (PING, PONG, CONNECT, CONNECT_ACK) now contributes one
664+
// sample: in-band power of the up-chirp span vs THIS frame's
665+
// gap noise ref, latched only ≥3 dB above it (a false lock on
666+
// noise reads ~0 and is refused). Convention: chirp-referenced
667+
// ≈ dial within ~1 dB (the reference power is chirp-dominated;
668+
// the ring's consumers carry a 2 dB margin).
669+
if (sync_result.preamble_start_sample >= 0 &&
670+
sync_noise_ref_rms_ > 0.0f) {
671+
const size_t cs = std::min(
672+
static_cast<size_t>(sync_result.preamble_start_sample),
673+
search_buffer.size());
674+
const size_t clen = std::min(
675+
static_cast<size_t>(sync_result.interchirp_gap_start_sample >
676+
sync_result.preamble_start_sample
677+
? sync_result.interchirp_gap_start_sample -
678+
sync_result.preamble_start_sample
679+
: 0),
680+
search_buffer.size() - cs);
681+
if (clen > 4800) { // >=0.1 s of chirp
682+
FIRFilter chirp_filter =
683+
FIRFilter::bandpass(101, 50.0f, 2950.0f, 48000.0f);
684+
const size_t skip = clen / 10;
685+
for (size_t i = cs; i < cs + skip; ++i) {
686+
chirp_filter.process(search_buffer[i]);
687+
}
688+
double sum_sq = 0.0;
689+
for (size_t i = cs + skip; i < cs + clen; ++i) {
690+
const float y = chirp_filter.process(search_buffer[i]);
691+
sum_sq += static_cast<double>(y) * y;
692+
}
693+
const double p_c =
694+
sum_sq / static_cast<double>(clen - skip);
695+
const double p_n =
696+
static_cast<double>(sync_noise_ref_rms_) *
697+
static_cast<double>(sync_noise_ref_rms_);
698+
if (p_n > 0.0 && p_c > 2.0 * p_n) {
699+
notePhysicalSnrSample(static_cast<float>(
700+
10.0 * std::log10((p_c - p_n) / p_n)));
701+
}
702+
}
703+
}
657704
// Handoff §2: hand THIS frame's burst-time noise reference to
658705
// the waveform so its training-span decode computes the
659706
// physical channel SNR (per-frame — no stale latch).

0 commit comments

Comments
 (0)