Skip to content

test(bpsk): pin that uncoded BPSK takes the UNCANCELLED arm, and fix a false comment (#1429) - #1431

Merged
dc0sk merged 1 commit into
mainfrom
fix/1429-uncoded-arm
Sep 22, 2026
Merged

dc0sk merged 1 commit into
mainfrom
fix/1429-uncoded-arm

Conversation

@dc0sk

@dc0sk dc0sk commented Sep 22, 2026

Copy link
Copy Markdown
Owner

Closes #1429 as far as measurement and evidence go. No behaviour change — no arm was switched, and
that is deliberate.

The finding

ModemEngine::receive_from_samples prefers demodulate_soft whenever the plugin advertises one, and
BpskPlugin::supports_soft_demod returns true unconditionally. So every uncoded decode
(FecMode::None, receive(), decode_burst_phase1) hard-decides the SOFT arm's LLRs — and BPSK's
soft arm deliberately skips cancel_crossfade_isi (#832, to protect the LLR calibration HARQ
combining relies on). Every coded decode takes the cancelled arm.

Measured on #821's own fixture — same 180-byte payload, same σ = 0.9, same deterministic LCG
noise, 8 seeds:

arm what runs it mean BER vs the < 0.02 bar
demodulate (cancelled) the test, and every coded decode 0.0127 passes
demodulate_soft hard-decided every uncoded decode that ships 0.0336 fails on all 8 seeds (0.0243–0.0444)

So crossfade_cancellation_lowers_awgn_ber — an uncoded BER test — guards a demodulator that no
uncoded production decode executes, and the arm that ships would not pass it.

What this PR does

  1. Pins the divergence. the_uncoded_production_path_takes_the_uncancelled_arm, on fix(bpsk): audit #1 — cancel crossfade ISI on the differential demod #821's
    fixture by construction — a different fixture would not be comparable to the bar being cited.
    Each of its three assertions names what its own failure would mean:
  2. Corrects a false comment. engine.rs justified preferring soft partly on the grounds that a
    hard retry "can't succeed where the soft pass failed — both share the same acquisition front
    end". That is false for the one plugin whose arms differ, and they differ by exactly the
    transform under discussion. The comment now carries the measurement, names the pinning test, and
    states the choice is open.

What it deliberately does NOT do

Decide which arm uncoded traffic should use. That is a real trade, not an oversight to reverse on
sight:

So today's split hands uncoded traffic the fade-favourable arm and coded traffic the
AWGN-favourable one
, which may well be right for what each actually carries. What was not
defensible is that it arose from a capability flag that exists for a different purpose, was defended
by a wrong comment, and that nothing tested the shipping path. Three candidate closures are on the
issue; the question may also dissolve if #1428's gated canceller lands and both arms can take it
(#1361's scope).

Test results

suites=339 tests_passed=2579 tests_failed=0
GATE: PASS 29a5dff0c9ec5fb342e9862110ae66285a98b46e clean 20260922T121242Z

All 13 steps ok. 2579 tests against main's 2578 — the one new pin.

Refactors: CAP-12
Verification-objective: the test guarding a transform must exercise the path that ships, and a
justification comment must not assert a property the code does not have

Review: none — the defect and its shape were identified in the #1428 design review, which
recommended "fix or document the receive_from_samples arm choice"; this documents and pins it and
changes no behaviour. The product decision it leaves open is recorded on #1429 for the maintainer.

🤖 Generated with Claude Code

https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6

…a false comment (#1429)

No behaviour change, deliberately — no arm was switched.

`receive_from_samples` prefers `demodulate_soft` whenever the plugin advertises one,
and `BpskPlugin::supports_soft_demod` returns `true` UNCONDITIONALLY. So every uncoded
decode (`FecMode::None`, `receive()`, `decode_burst_phase1`) hard-decides the soft arm's
LLRs, and BPSK's soft arm deliberately skips `cancel_crossfade_isi` (#832). Every coded
decode takes the cancelled arm.

MEASURED on #821's own fixture — same payload, same sigma 0.9, same LCG noise, 8 seeds:

    demodulate (cancelled)       -> mean BER 0.0127   passes its < 0.02 bar
    soft hard-decided (SHIPPED)  -> mean BER 0.0336   fails on every seed (0.0243-0.0444)

So `crossfade_cancellation_lowers_awgn_ber`, an UNCODED BER test, guards a demodulator
that no uncoded production decode runs — and the arm that ships would not pass it.

WHICH ARM UNCODED TRAFFIC SHOULD USE IS NOT DECIDED HERE, and that is on purpose: #1363
measures the cancellation as a win on AWGN and pure Doppler and a loss of 8 frames in 96
on a delayed-dominant fade, while the uncoded traffic on this path — §97.119 station ID,
handshake, QSY, relay (#1123) — lives on fading channels. The current split may be right
for the traffic each arm carries. What was indefensible is that it came from a
capability flag that exists for another purpose, was justified by a comment that is
false for the one plugin whose arms differ, and that nothing tested the shipping path.

The new test is a CHARACTERISATION test on #821's fixture by construction. Each of its
three assertions names what its own failure would mean — including that if the
production arm ever MEETS the bar, #1429 is resolved and the test should be deleted
rather than loosened.

engine.rs's comment claiming a hard retry "can't succeed where the soft pass failed —
both share the same acquisition front end" is corrected: false for BPSK, whose arms
differ by exactly the transform under discussion.

Refactors: CAP-12
Verification-objective: the test guarding a transform must exercise the path that ships,
and a justification comment must not assert a property the code does not have

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6
@dc0sk
dc0sk merged commit 6cfd287 into main Sep 22, 2026
11 checks passed
@dc0sk
dc0sk deleted the fix/1429-uncoded-arm branch September 22, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Uncoded BPSK decodes through the UNCANCELLED soft arm, and #821's guard tests the other one

1 participant