fix(dsp): a signal-free HARQ attempt voted at full strength (#1364) - #1426
Merged
Merged
Conversation
`differential_llr_scale` returned `2*mean|dot|/var(cross)`. On an attempt carrying NO signal that emits LLRs of std 1.41 at EVERY sigma from 0.1 to 2.0 — measured through the shipped function. `combine_llrs_map` sums attempts and the OTA arm retains failed bursts, so a worthless attempt did not merely fail to help: it outvoted the attempts carrying the frame. hpx_hf SL2-SL5 run this path. MY DIAGNOSIS WAS WRONG TWICE AND REVIEW CORRECTED BOTH. At A=0 the old estimator returns EXACTLY the 1/sigma^2 its contract promises — sqrt(2) is that contract honoured. The defect is the CONTRACT: 1/sigma^2 is the high-SNR LIMIT of the true DBPSK LLR slope, which vanishes with the signal; against the exact pairwise LLR the old formula is 8x over-confident at -12 dB. And my "target 0.04" was a category error — sigma1^2/sigma2^2 is a SCALE ratio while the gate's 0.21 is a MAGNITUDE ratio, and under exact 1/sigma^2 the magnitude ratio really is ~0.20. Implementation: A^2 = sqrt(max(0, <dot^2> - <cross^2>)), scale = 2A^2/<cross^2> — the Gaussian-approximation LLR with an unbiased fourth-moment amplitude, tending to 1/sigma^2 at high SNR and to 0 as the signal vanishes. f64 accumulators (the subtraction is catastrophic cancellation by construction) and a SCALE_FLOOR so a zero estimate cannot emit -0.0, which an `l < 0.0` consumer reads as bit 0. PREMISE PINNED, NOT ASSUMED: the identity holds only for noise uncorrelated at lag 1. `cancel_crossfade_isi` induces rho = -1/3, under which A=0 yields 2*rho^2*v^2 and the defect returns. A test makes #1361's open question a failing test rather than a silent regression in HARQ weighting. WHY NOTHING CAUGHT IT: the old unit test claimed this exact property and passed, because it SYNTHESISED dots/crosses from the high-SNR asymptotic model — omitting the n*conj(n) term that causes the floor — and swept only 10/20 dB. Its `expected = 1/sigma2` was the wrong target anyway. Rebuilt from actual complex symbols, swept to A=0, with the old formula retained as a control asserting it votes ~sqrt(2). A FUNDAMENTAL LIMIT, not a shortfall: at A=0 the score for A^2 equals the score for v, so the Fisher information is singular and any blind estimate is sampling-noise limited at N^(-1/4). The tracking sweep is asserted only where A^2 is resolvable TO THE STATED TOLERANCE — derived as >= -6 dB at N=20000, after -12 dB read 2.01x and -9 dB 1.36x. Measured, both arms, 16 seed triples: old 7/16 failing (mean delta 0.656 dB) -> new 1/16 (0.156 dB). `a_deeply_faded_extra_attempt_does_not_hurt` still passes. NO FADE REGRESSION, which was the review's live risk (Jensen, sqrt(E[A^4]) >= E[A^2]): moderate_f1, 32 seeds, 1 clean + 1 faded gives 19/23/27 new against 20/22/27 old at 6/8/10 dB. No fade BENEFIT either — the gain is AWGN-specific. The wrong contract was written in five places; all swept. Filed separately as #1425: the property is a CLASS — 8PSK500 votes at 0.44 of a good attempt on pure noise against BPSK250's 0.009 — and this fix is specific to a differential detector's dot/cross pair. Refactors: CAP-20 Refactors: CAP-12 Verification-objective: a HARQ attempt's LLR magnitude must fall with its signal, not merely with its noise power, so a signal-free attempt cannot outvote a good one Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1364.
differential_llr_scalereturned2·mean|dot|/var(cross). On an attempt carrying no signal thatemits LLRs of std 1.41 at every σ from 0.1 to 2.0 — measured through the shipped function.
combine_llrs_mapSUMS attempts and the OTA arm retains failed bursts, so a worthless attempt didnot merely fail to help: it outvoted the attempts carrying the frame.
hpx_hfSL2–SL5 run thispath.
My diagnosis was wrong twice, and review corrected both
1/σ²its contract promises — √2 is thatcontract honoured, not violated. The defect is the contract:
1/σ²is the high-SNR limit ofthe true DBPSK LLR slope, which vanishes with the signal. Against the exact pairwise LLR the old
formula is 8× over-confident at −12 dB (slope 0.94 against 0.11).
σ₁²/σ₂²is a scale ratio; the gate's 0.21 is amagnitude ratio, and under exact
1/σ²the magnitude ratio really is ≈0.20.The fix
² = √(max(0, ⟨dot²⟩ − ⟨cross²⟩)), scale2²/⟨cross²⟩— the Gaussian-approximation LLR with anunbiased fourth-moment amplitude, tending to
1/σ²at high SNR and to 0 as the signal vanishes.For iid circular noise
E[dot²] − E[cross²] = A⁴exactly, since theA²vandv²/2terms arecommon to both. f64 accumulators (the subtraction is catastrophic cancellation by construction) and
a
SCALE_FLOORso a zero estimate cannot emit−0.0, which anl < 0.0consumer reads as bit 0.Premise pinned, not assumed
The identity holds only for noise uncorrelated at lag 1.
cancel_crossfade_isiinduces ρ = −1/3,under which A = 0 yields
2ρ²v²and the defect returns.differential_llr_scale_assumes_iid_noisemakes #1361's open question a failing test rather than a silent regression in HARQ weighting.
Why nothing caught it
The old unit test claimed this exact property and passed, because it synthesised
dots/crossesfrom the high-SNR asymptotic model — omitting the
n·conj(n)term that causes the floor — andswept only 10/20 dB. Its
expected = 1/σ²was the wrong target anyway. Rebuilt from actual complexsymbols, swept to A = 0, with the old formula retained inside as a control asserting it votes ≈ √2.
A limit that is fundamental, not a shortfall
At A = 0 the score for
A²equals the score forv, so the Fisher information is singular — theknown blind-SNR degeneracy — and any blind estimate is sampling-noise limited at
N^(−1/4). Thetracking sweep is therefore asserted only where
²is resolvable to the stated tolerance,derived as ≳ −6 dB at N = 20 000 after −12 dB read 2.01× and −9 dB read 1.36×.
Measured, both arms
a_deeply_faded_extra_attempt_does_not_hurtstill passes — #832's gate is improved, not changed.(#1364 recorded 3/16 on its own smaller triple set; this is a different set, not a reproduction.)
No fade regression, which was the review's live risk (Jensen:
√E[A⁴] ≥ E[A²], ×1.41 on aRayleigh envelope).
moderate_f1, 32 seeds, 1 clean + 1 faded: 19/23/27 new against 20/22/27 oldat 6/8/10 dB; the 2-clean rows are identical. No fade benefit either — the gain is AWGN-specific,
and saying so is the honest scope.
Sabotage, both directions
The rebuilt tests fail on the old estimator (
v=0.02: signal-free vote 1.439 exceeds derived bound 0.464) and pass on the new one.Test results
scripts/gate.shon the branch tip, clean tree:All 13 steps
ok. Test count is 2576 againstmain's 2574: three new tests replacing the one whose fixture could not exhibit the defect.Filed separately, not folded in
#1425 — this is a class. Every blind scale-invariant calibrator votes at a σ-independent
magnitude on pure noise; 8PSK500 does so at 0.44 of a good attempt against BPSK250's 0.009. The
fix here is specific to a differential detector's dot/cross pair and does not generalise to the
coherent plugins.
The wrong contract was written in five places and all were swept:
constellation.rs's doc, the twobpsk_demodulate_softcomments,llr_calibration.rs's header,fec.rs'scombine_llrs_mapdoc,and CLAUDE.md's "LLRs already carry 1/σ²" edge.
Refactors: CAP-20
Refactors: CAP-12
Verification-objective: a HARQ attempt's LLR magnitude must fall with its signal, not merely with
its noise power, so a signal-free attempt cannot outvote a good one
Review: docs/dev/reviews/review-1364-differential-llr-scale.md
🤖 Generated with Claude Code
https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6