cancel_crossfade_isi on BPSK's HARD path — the shipped receive path — costs decode rate on moderate_f1 once the delay spread reaches ~1 ms. It was validated on AWGN only.
plugins/bpsk/src/demodulate.rs:114 applies the cancellation on the hard differential path. #821's evidence for it is a single fixed-seed AWGN BER at σ = 0.9 (plugins/bpsk/src/lib.rs:286), generalised to a rung whose entire purpose is the fade.
Measured
Ablation on shipped code, comparing the two paths that already differ by exactly this transform — soft (no cancellation) vs hard (cancellation) — through combine_and_decode_llrs, 64 independent fade realisations per cell, BPSK250 + Rs. Frames decoded of 64:
| channel |
8 dB |
12 dB |
16 dB |
| Doppler only (1 Hz, 0 ms) |
45 / 60 |
64 / 64 |
64 / 64 |
| multipath only (0 Hz, 1 ms) |
54 / 54 |
57 / 56 |
60 / 57 |
both (moderate_f1) |
28 / 26 |
50 / 38 |
54 / 45 |
(soft / hard — so the right-hand number is what ships.)
Delay-spread sweep at 1 Hz Doppler, hard minus soft: 0 ms +15, 0.25 ms +6, 0.5 ms ≈0, 1 ms −12, 2 ms −6 (both nearly dead at 2 ms). The sign flips near 0.5 ms. On pure Doppler the cancellation is a clear win; combined with ≥1 ms of delay spread it is a clear loss, and moderate_f1 is the combined case.
These fade measurements were made during adversarial review of a related question; I measured the AWGN half (below) and reproduced its numbers independently. The mechanism is not established.
Leading hypothesis, untested
1 ms is 8 samples, a quarter symbol at BPSK250. Under Doppler the dominant ray alternates mid-frame, so preamble-locked timing sits a quarter symbol off for stretches; the effective β is then no longer 1/3, and fixed-β cancellation injects error instead of removing it. That is the #695 shape — "cancel where the ISI structure differs and you inject the next symbol as error" — which is already a known sharp edge in this repo for the QPSK/8PSK crossfade.
Why no gate sees it
hpx_hf_rungs_survive_fade uses MIN_DECODE = 0.25 at floor+4 dB; the hard path reads 0.4–0.6 there, comfortably above the bar while losing a quarter of its frames to a transform that is supposed to help.
Scope — do not generalise past BPSK250
BPSK63 at 8–16 dB is saturated in both arms (31–32 of 32), so its operating point was never reached, and 1 ms is 1/16 of a symbol there rather than 1/4. The slower rungs need their own measurement at floor…floor+4.
Suggested next measurement, before any fix
- Timing ablation to test the hypothesis: a static two-ray channel plus a forced quarter-symbol timing offset, or log the timing-lock error against frame position on
moderate_f1. If the harm tracks the timing error, the fix is timing, not the cancellation.
- BPSK31/63/100 at floor…floor+4 on
moderate_f1, soft vs hard, to find where the sign flip lands on the slower rungs.
Related
#1361 is the soft-path half of the same transform; the soft/hard split question is downstream of this one, because if the cancellation is wrong on the fade for the hard path, "make the soft path match the hard path" is the wrong target. The AWGN A/B that led here is recorded there.
Caveats on the fade numbers: one payload, one Watterson implementation, release profile (the gate runs debug), 64 paired realisations per cell, and it was not checked whether delay_spread_ms maps to an integer-sample delay — which matters directly to the timing hypothesis.
🤖 Generated with Claude Code
https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6
cancel_crossfade_ision BPSK's HARD path — the shipped receive path — costs decode rate onmoderate_f1once the delay spread reaches ~1 ms. It was validated on AWGN only.plugins/bpsk/src/demodulate.rs:114applies the cancellation on the hard differential path. #821's evidence for it is a single fixed-seed AWGN BER at σ = 0.9 (plugins/bpsk/src/lib.rs:286), generalised to a rung whose entire purpose is the fade.Measured
Ablation on shipped code, comparing the two paths that already differ by exactly this transform — soft (no cancellation) vs hard (cancellation) — through
combine_and_decode_llrs, 64 independent fade realisations per cell, BPSK250 +Rs. Frames decoded of 64:moderate_f1)(soft / hard — so the right-hand number is what ships.)
Delay-spread sweep at 1 Hz Doppler, hard minus soft: 0 ms +15, 0.25 ms +6, 0.5 ms ≈0, 1 ms −12, 2 ms −6 (both nearly dead at 2 ms). The sign flips near 0.5 ms. On pure Doppler the cancellation is a clear win; combined with ≥1 ms of delay spread it is a clear loss, and
moderate_f1is the combined case.These fade measurements were made during adversarial review of a related question; I measured the AWGN half (below) and reproduced its numbers independently. The mechanism is not established.
Leading hypothesis, untested
1 ms is 8 samples, a quarter symbol at BPSK250. Under Doppler the dominant ray alternates mid-frame, so preamble-locked timing sits a quarter symbol off for stretches; the effective β is then no longer 1/3, and fixed-β cancellation injects error instead of removing it. That is the #695 shape — "cancel where the ISI structure differs and you inject the next symbol as error" — which is already a known sharp edge in this repo for the QPSK/8PSK crossfade.
Why no gate sees it
hpx_hf_rungs_survive_fadeusesMIN_DECODE = 0.25at floor+4 dB; the hard path reads 0.4–0.6 there, comfortably above the bar while losing a quarter of its frames to a transform that is supposed to help.Scope — do not generalise past BPSK250
BPSK63 at 8–16 dB is saturated in both arms (31–32 of 32), so its operating point was never reached, and 1 ms is 1/16 of a symbol there rather than 1/4. The slower rungs need their own measurement at floor…floor+4.
Suggested next measurement, before any fix
moderate_f1. If the harm tracks the timing error, the fix is timing, not the cancellation.moderate_f1, soft vs hard, to find where the sign flip lands on the slower rungs.Related
#1361 is the soft-path half of the same transform; the soft/hard split question is downstream of this one, because if the cancellation is wrong on the fade for the hard path, "make the soft path match the hard path" is the wrong target. The AWGN A/B that led here is recorded there.
Caveats on the fade numbers: one payload, one Watterson implementation, release profile (the gate runs debug), 64 paired realisations per cell, and it was not checked whether
delay_spread_msmaps to an integer-sample delay — which matters directly to the timing hypothesis.🤖 Generated with Claude Code
https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6