Weak-Signal Engineering, Part 14: The Weak-Signal Playbook

Part 14 — the finale — of Weak-Signal Engineering. Thirteen posts ago we met a capture that locked and then decoded almost nothing: ~10 dB in-channel SNR, 22% of its BSCH bursts CRC-clean, a resync storm eating the rest. Along the way we learned which metrics lie, inverted linear channels blind and trained, froze taps in front of differential decoders, carried confidence through the FEC, combined two antennas without breaking anything, and — twice — proved a deficit wasn’t ours to fix. That capture now decodes ~100% of its BSCH. This closing part compresses the whole method into the thing you actually need at the bench: a decision tree from symptom to lever, the testing discipline that makes each lever safe to pull, and an honest list of what is still open.

TL;DR: The playbook is four questions asked in order. Is it signal-limited? — measure in-channel SNR and dBFS headroom; if the samples don’t contain the call, no DSP recovers it (fix RF, get captures — #764’s lesson). Is it ISI-limited? — a smeared constellation at decent SNR is a linear, invertible channel: equalize (SnapshotCMA blind in the receiver, SnapshotLMS trained in the extractor). Is it reliability-limited? — clean-ish symbols failing hard CRC gates want soft decisions (DecodeRCPCTetraMotherSoft and friends). Is it fading-limited? — a second branch and coherence-gated MRC (CrossStats, TrackingCalibrator). Every lever lands the same way: a failing-first test, a byte-identical opt-out, a capture A/B where one exists, and decode yield as the only verdict. On the thread capture the CC-path equalizer alone took CRC-clean BSCH from ~12% to ~100%; the same equalizer nearly doubled voice TCH/S (410→778) across six captures, and soft decisions recovered the ~70% of marginal bursts the hard gate dropped.

Key takeaways

  • Classify before you fix. The four regimes — signal-, ISI-, reliability-, and fading-limited — have distinct fingerprints and disjoint fixes. The costliest failure mode in this series was always applying a real lever to the wrong regime.
  • Yield is the only verdict; everything else is advisory. EVM collapsed 34%→8% on an equalizer that decoded zero frames. CRC-valid frames per opportunity is the number that cannot flatter you.
  • The discipline stack is what made the levers safe: failing-first regressions, byte-identical opt-outs, capture A/Bs against operators’ own recordings, and structural guards against the self-consistent-synthetic trap.
  • The method transfers. Nothing in the four questions is TETRA-specific — the same triage indicts the P25 C4FM path (Part 13) and will grade its fix when the capture arrives.

Cheat sheet

Regime The lever that matches it Where it lives
Signal-limited (deficit survives an independent-path control) fix RF, capture rate, gain staging — not DSP Part 12, internal/scanner/ccdecoder/ddc_highrate_test.go
ISI-limited (smeared constellation at decent SNR) blind SnapshotCMA / trained SnapshotLMS internal/dsp/equalizer/, Parts 4–7
Reliability-limited (symbols mostly right, hard CRCs failing) soft LLRs through depuncture + Viterbi internal/radio/framing/soft_tetra.go, Part 8
Fading-limited (yield swings with time / position) coherence-gated MRC across two branches internal/dsp/diversity/, Parts 10–11
Any (“improved” metrics, unchanged yield) distrust the metric — decode to CRC Part 2
Any (green synthetic, live symptom) operator capture A/B before closing Part 12, CONTRIBUTING.md

In this post

  • The four questions — the decision tree, in order.
  • The levers, ranked by cost — what each one demands before it pays.
  • The discipline stack — how a risky lever becomes a safe landing.
  • The thread capture, closed out — the scorecard.
  • What’s still open — the honest punch list.

The four questions

Ask them in order — each one is cheaper to answer than the one after it, and a wrong answer early wastes every step later.

1. Is it signal-limited? Measure before theorising: in-channel SNR at the demod (not wideband carrier SNR — Part 2 showed the wideband number grading the worse #764 capture higher), peak dBFS for headroom, and where possible the Part 12 control: route the failing samples through a proven path via an independent resampler. If the deficit travels with the samples, stop — no equalizer, soft decoder, or combiner manufactures information the front end never delivered. The fix is on the other side of the ADC: gain staging, capture rate, antenna, filtering — the territory of The Analog Edge.

2. Is it ISI-limited? Decent SNR but a smeared constellation — multipath, band-edge group delay, simulcast — is a linear channel, and linear is invertible (Part 3). Equalize: blind SnapshotCMA in the streaming receiver where no reference exists, trained SnapshotLMS in the burst extractor where a midamble does. The confirmation is never the constellation looking rounder — it is yield moving when the equalizer is enabled on the same capture.

3. Is it reliability-limited? Symbols mostly land in the right region but hard CRC gates keep failing — noise, not smear, is eating the margin. Go soft: carry LLRs through descramble, deinterleave, depuncture, and the correlation-metric Viterbi, and let erasures be erasures (Part 8). This lever stacks with the previous one — equalized symbols feed re-derived LLRs, not re-sliced bits.

4. Is it fading-limited? Yield that swings with time or antenna position while the average SNR looks adequate means the channel spends part of its time in a null. One antenna cannot fix a null it is standing in; a second branch, coherence-gated and MRC-combined (Parts 10–11), buys the odds that somewhere on your roof the signal still exists.

measure: in-channel SNR, dBFS headroom, yield deficit survives the independent-path control? constellation smeared at decent SNR? symbols right, hard CRC gates failing? yield swings with time / position? SIGNAL-limited fix RF / rate / gain — no DSP ISI-limited SnapshotCMA / SnapshotLMS RELIABILITY-limited LLRs → soft Viterbi FADING-limited coherence-gated MRC verdict: decode yield, same capture
Four questions in cost order, four disjoint levers — and every branch terminates at the same verdict box: CRC-valid frames per opportunity on the capture that showed the symptom.

The levers, ranked by what they cost

  • Blind snapshot equalization (Parts 4–6) costs CPU and two hard-won rules: frozen taps in front of anything differential, and a constant normalisation reference with a divergence guard. It needs no reference symbols and no new data plumbing — the cheapest real lever.
  • Trained equalization (Part 7) costs the raw-symbol plumbing (SymbolSink/StashSymbols) and works only where a known training sequence exists — but it pins the true channel inverse, phase included, which blind CMA cannot on a short reference.
  • Soft decisions (Part 8) cost a parallel float32 stream and a soft mirror of the channel-coding chain — paid once per protocol, then every marginal burst benefits.
  • Diversity (Parts 10–11) is the only lever that costs hardware — a second coherent receive branch — plus the calibration machinery to avoid combining into a cancellation. It is also the only lever that helps a flat fade.
  • The meta-lever is Part 9’s pattern — parallel buffers with byte-identical opt-outs — which is what kept the other four’s risk off the working fleet.

The discipline stack

Every lever above landed through the same gauntlet, and the gauntlet is the part most worth stealing:

  1. Failing-first. A regression test that fails against the old code and passes with the change — no reproduction, no fix.
  2. Byte-identical opt-out. Off must be the identity function, and a test must compare the bytes (TestTrafficExtractorSoftUnchangedWithoutEqualizer is the template).
  3. No-harm on clean. The lever must not tax the signals that already decode (TestSnapshotCMAHarmlessOnCleanSignal, TestTrafficExtractorLMSNoHarmOnCleanChannel).
  4. Capture A/B. Where an operator capture exists, the lever is graded on it — TestDiversityCombinerReplay’s four arms, the pipelines_tetra_equalizer_test.go fixture — because a green synthetic is not proof of an on-air fix.
  5. Yield verdicts only. Every harness in this series prints CRC-valid counts and says, in as many words, not to conclude anything from EVM.
  6. Guards against self-consistency. Encode-side behaviour pinned to real-air behaviour, cross-checks through independent implementations, opcode constants pinned to upstream literals — because a test that shares its assumption with the code validates nothing.

The thread capture, closed out

The capture introduced in Part 1 — a TETRA control channel that locks at ~10 dB in-channel SNR — decoded ~22% of its BSCH in the live session that produced it, with a destructive-resync storm; the pinned 2-second fixture (testdata/tetra_cc_sync_loss_2s_144k.cs16) baselines at ~12% through the bare pipeline. Enabling the receiver’s SnapshotCMA on that one remaining un-equalized CC path lifted it to ~100% CRC-clean BSCH — pinned by pipelines_tetra_equalizer_test.go. One lever, matched to its regime, recovered essentially everything the channel had to give; the residual −44 dBFS front end remains an RF condition the equalizer mitigates but does not replace — question 1 never goes away.

The same levers, scored on their own ground: the equalizer roughly doubled CRC-valid voice TCH/S across the six concurrent-load captures (soft-decision counts 410→778, with single calls going 4→207); soft decisions recovered the ~70% of a marginal call’s bursts the hard gate dropped; and on the DMO capture the receiver equalizer lifted CRC-valid SCH/S from 6 to 64. Different paths, different protocols, one pattern: classify, lever, yield.

What’s still open

An honest playbook ends with its open items, each parked at a named gate:

  • Trained LMS in production TETRA voice — staged and synthetic-pinned; gated on the GT_TETRA_LMS=1 capture A/B showing it beats CMA-plus-soft on air.
  • Tracking MRC as the diversity default — differential-safe by measured construction; gated on TestDiversityCombinerReplay on an operator’s own pre-combine capture showing the tracking arm winning.
  • P25 Phase 1 C4FM voice — the odd path out (Part 13); gated on a real weak C4FM voice capture landing in samples/p25/.
  • Per-channel diversity combining — the answer to the wideband-scalar caveat; unbuilt, and honestly scoped as a much larger change.

Every one of them is blocked on evidence, not effort — which is the series’ thesis restated: in the marginal regime, the scarce resource is not clever DSP, it is a measurement you can trust.

FAQ

Where do I start when a system decodes badly and I know nothing else? Question 1, always: in-channel SNR at the demod and peak dBFS. Five minutes of measurement sorts most cases into “the samples are fine, work the DSP levers” or “no DSP will save this” — and the second answer, found early, is the biggest time-saver in this series.

How do the levers combine on one channel? Equalize first (it operates on symbols), decode soft second (it operates on the equalized symbols’ LLRs), diversify beneath both (it operates on the wideband stream before any of this). They are complementary regimes, not alternatives — the TETRA voice path today runs all of: CMA in the receiver, soft TCH/S in the decoder, and optionally a second branch under MRC.

What single habit most improves weak-signal debugging? Refusing any verdict that isn’t decode yield on the capture that showed the symptom. Every trap this series named — the EVM collapse, the flattering wideband SNR, the self-consistent synthetic — is a version of accepting a proxy because it was easier to obtain.

Is any of this specific to Go or to GopherTrunk? The identifiers are; the method isn’t. The four regimes, the yield rule, the frozen-snapshot constraint ahead of differential decoders, the independent-implementation cross-check — all of it transfers to any SDR stack. GopherTrunk’s contribution is having the tests in the tree so each claim stays pinned.

What should I contribute if I want to move an open item? Captures, over code. A weak C4FM P25 voice call, a pre-combine diversity pair from diversity_capture, or a marginal TETRA call with GT_TETRA_LMS A/B numbers each unblocks a parked lever — the analysis machinery for all three is already merged and waiting.

Series navigation

Part 14 of 14 · ← Part 13: The Odd Path Out — P25 Phase 1 C4FM · This is the finale — back to the series index.

Where to next? The theory you’ve just finished has a protocol-length case study — watch every lever land on one real network in TETRA End to End — or cross to the other side of the ADC, where question 1 lives, with The Analog Edge.