Skip to content

BPSK hard path: cancel_crossfade_isi loses ~24% of frames on moderate_f1 at 12-16 dB — validated on AWGN only (#821) #1363

Description

@dc0sk

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

  1. 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.
  2. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions