Skip to content

Commit 94faa1f

Browse files
secupclaude
andcommitted
fix(arq): cross-frame burst interleave DEFAULT-OFF for all mods (was >=16QAM-ON) — rig-measured harmful on fading
burstCrossFrameInterleaveOn(mod) now returns false by default for every modulation (was isCoherentModulation && bits>=4, i.e. ON for >=16QAM). Interleave OFF => per-frame SR-ARQ SACK (resend only dead frames); ON => whole-group ACK. ULTRA_BURST_INTERLEAVE=1 re-enables (A/B / restore the Good@20 case), =0 forces off. Behavior change to main (not byte-identical): 16QAM file transfers now use per-frame SACK. WHY. Cross-frame TIME interleave spreads each LDPC codeword's bits across ALL N frames of the burst. The prior default turned it ON for dense mods on the theory that a frozen FREQUENCY null smeared across frames becomes a recoverable ~1/N nick (GUI/offline +47% on 16QAM R2/3 Good@20, 2026-06-14). But the HF fade the rig delivers is TIME-localized (a Watterson deep fade hits one stretch of the burst): interleaving then puts ~1/N corruption into EVERY codeword at once, and when that exceeds the LDPC budget (it does at MPG@20) ALL codewords fail together — a whole-group 0/N crater instead of one dead frame. MEASURED (forced-16QAM-R1/2 interleaved A/B, IONOS rig, MPG@20, F440-449, RX-authority+adaptive disabled so only interleave varies): interleave ON (old default): delivered 4/5, mean 1.13 kbps, 62 full / 13 partial craters interleave OFF (per-frame SACK): delivered 5/5, mean 1.55 kbps (+37%), 12 full / 41 partial OFF won 4/5 pairs, delivered every transfer, 5x fewer full craters — failures stay LOCAL (partials the SACK resends cheaply) instead of coupling to the burst's worst instant. The crater SHAPE is the proof: ON fails WHOLE, OFF fails LOCAL. RECONCILIATION. MPG@20 IS a "Multipath GOOD" channel, so this directly CONTRADICTS the 2026-06-14 Good@20 +47% — that was a calmer epoch / R2/3 / possibly-sim realization; the newer faithful hardware measurement wins. AWGN has no nulls to smear, so interleave buys nothing there either. No channel where it clearly earns its keep on the real rig. maxValidatedCoherentRate comment flagged: the 16QAM R2/3 cap's interleave-dependent margin is now unre-measured — re-A/B R2/3 vs R1/2 with per-frame SACK before trusting R2/3 as the 16QAM cap. ORIGIN. Operator observation ("we ACK, sender resends, we ACK over the resend") -> traced whole-group-resend to interleave -> forced A/B. Also kills that airtime waste. Verification: ctest -j4 full suite green (UltraTncSimAudio's lone fail was a parallel-load timeout at 265s, passes standalone in 57s). Rig A/B above. Natural-ladder auto-path A/B pending. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019btwFxDt7D19SZSqPYvZnk
1 parent cfb4fc1 commit 94faa1f

4 files changed

Lines changed: 77 additions & 20 deletions

File tree

docs/CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,54 @@ This log tracks all bug fixes and behavioral changes to prevent re-doing work du
1010

1111
---
1212

13+
## 2026-07-21 — fix(arq): cross-frame burst interleave DEFAULT-OFF for all modulations (was >=16QAM-ON) — rig-measured harmful on fading, +37% with per-frame SACK
14+
15+
**What changed.** `connection_policy.hpp::burstCrossFrameInterleaveOn(mod)` now returns
16+
`false` by default for every modulation (was `isCoherentModulation(mod) && bits>=4`, i.e.
17+
ON for >=16QAM). Interleave OFF ⇒ per-frame SR-ARQ SACK (resend only dead frames);
18+
ON ⇒ whole-group ACK/NACK. `ULTRA_BURST_INTERLEAVE=1` re-enables it (for A/B / restoring
19+
the Good@20 case), `=0` forces off (same as default). One caller (modem_mode.cpp), no test
20+
pinned the old default. NOTE: this is a behavior change to main (not byte-identical) — 16QAM
21+
file transfers now use per-frame SACK.
22+
23+
**Why (rig evidence).** Cross-frame TIME interleave spreads each LDPC codeword's bits across
24+
ALL N frames of the burst. The prior default turned it ON for dense mods on the theory that
25+
a frozen FREQUENCY null smeared across frames becomes a recoverable ~1/N nick (GUI/offline
26+
+47% on 16QAM R2/3 Good@20, 2026-06-14). But the HF fade the rig actually delivers is
27+
TIME-localized (a Watterson deep fade hits one stretch of the burst): interleaving then puts
28+
~1/N corruption into EVERY codeword at once, and when that exceeds the LDPC budget (it does
29+
at MPG@20) ALL codewords fail together — a whole-group 0/N crater instead of one dead frame.
30+
31+
**Measured (forced-16QAM-R1/2 interleaved A/B, IONOS rig, MPG@20, F440-449, 2026-07-21;
32+
RX-authority+adaptive disabled so only interleave varies):**
33+
34+
| arm | delivered | mean kbps | full craters | partial craters |
35+
|-----|-----------|-----------|--------------|-----------------|
36+
| interleave ON (old default) | 4/5 | 1.13 | **62** | 13 |
37+
| interleave OFF (per-frame SACK) | **5/5** | **1.55 (+37%)** | **12** | 41 |
38+
39+
OFF won 4/5 pairs, delivered every transfer, and had **5× fewer full craters** — because
40+
failures stay LOCAL (partial groups the per-frame SACK resends cheaply) instead of coupling
41+
to the burst's worst instant. The crater SHAPE is the mechanism proof: ON fails WHOLE (62
42+
full / 13 partial), OFF fails LOCAL (12 full / 41 partial).
43+
44+
**Reconciliation with the 2026-06-14 +47%.** MPG@20 IS a "Multipath GOOD" channel, so the new
45+
rig result directly CONTRADICTS the old Good@20 interleave benefit — that +47% was a calmer
46+
epoch / R2/3 / possibly-sim realization. The newer, faithful hardware measurement wins. AWGN
47+
has no nulls to smear, so interleave buys nothing there either → no channel where it clearly
48+
earns its keep on the real rig. `maxValidatedCoherentRate` comment flagged: the 16QAM R2/3
49+
cap's interleave-dependent margin is now UNRE-MEASURED — re-A/B R2/3 vs R1/2 with per-frame
50+
SACK before trusting R2/3 as the 16QAM cap.
51+
52+
**Origin.** Operator observation ("we ACK, sender resends, we ACK over the resend") →
53+
traced the whole-group-resend to interleave (streaming_burst_interleave.cpp:1207) → forced
54+
A/B. Also directly kills the whole-group-resend airtime waste that prompted the investigation.
55+
56+
**Verification.** `ctest -j4` full suite green. Rig A/B above. Natural-ladder (auto-path) A/B
57+
pending to confirm the auto 16QAM path doesn't regress without the R2/3 interleave margin.
58+
59+
---
60+
1361
## 2026-07-21 — feat(rate): EMA-supported crater-hold + censored failed-group SNR (ULTRA_RX_EMA_HOLD, default-OFF) — throughput-ceiling audit lever #1
1462

1563
**Motivation (throughput-ceiling audit, docs + workflow 2026-07-21).** A 6-facet

docs/MODEM_INFRASTRUCTURE_MAP.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Rate→K (per `getCodeParams`, `ldpc_encoder.cpp:39`): R1/4=162, R1/2=324, R2/3=
134134
| **FrameInterleaver** (intra-frame across CWs) | `frame_interleaver.*`; TX `frame_v2.cpp:1944` | 🟢 **always on for OFDM** (skipped when cw=1) |
135135
| **ChannelInterleaver** (within-CW time×freq) | `include/ultra/fec.hpp:133`; `frame_v2.cpp:1914` | 🟢 **default ON** |
136136
| **carrier_ldpc_interleaver** (air-grid carrier spread V1) | `carrier_ldpc_interleaver.*`; `ofdm_chirp_waveform.cpp:73` | 🟡 default-OFF, **auto-engages on masked carriers** (likely removal candidate) |
137-
| **BurstInterleaver** (cross-frame N-group byte permute) | `burst_interleaver.*`; TX `streaming_encoder.cpp:530`, RX `streaming_burst_interleave.cpp:496` | 🟢 **default-ON for dense coherent mods (≥16QAM)** via `burstCrossFrameInterleaveOn(mod)` (2026-06-14); OFF (per-frame SR-ARQ) for QPSK/8PSK. TIME diversity → +47% 16QAM R2/3 Good@20. `ULTRA_BURST_INTERLEAVE` overrides. |
137+
| **BurstInterleaver** (cross-frame N-group byte permute) | `burst_interleaver.*`; TX `streaming_encoder.cpp:530`, RX `streaming_burst_interleave.cpp:496` | 🟡 **DEFAULT-OFF for ALL mods** via `burstCrossFrameInterleaveOn(mod)` (2026-07-21, flipped from ≥16QAM-ON). Interleave couples time-localized deep fades across all codewords → whole-group craters; rig A/B MPG@20 (F440-449) interleave-OFF +37% goodput, 5/5 vs 4/5, 5× fewer full craters. Contradicts the 2026-06-14 Good@20 +47% (calmer epoch). Now per-frame SR-ARQ SACK everywhere. `ULTRA_BURST_INTERLEAVE=1` re-enables (A/B the Good@20 claim). Mechanism preserved, only the default flipped. |
138138

139139
**ARQ-profile INVARIANT**`connection_policy::burstCrossFrameInterleaveOn()`
140140
(`connection_policy.hpp:94`, default **false**) is the SINGLE source of truth deriving (a) TX
@@ -216,7 +216,7 @@ Buckets per the env-knobs→runtime-derivation workstream: **[FEAT]** in-flight
216216
|------|--------|---------|----------|--------|
217217
| ~~`ULTRA_BURST_TRANSPORT`~~ | **REMOVED 2026-06-02** — burst transport is now unconditional (THE OFDM file path); the `=0` opt-out was deleted, no env read remains. RX default `burst_transport_rx_` flipped `false→true`. | (gone) | (removed from `connection.cpp`/`app.cpp`/`modem_engine.hpp`) ||
218218
| `ULTRA_ADAPTIVE_RATE` | per-block decode-headroom quality-feedback computation (GUI "Adapt:" bars + diagnostics); `=0` also disables the rate-CHANGE path below (`adaptive_rate_enabled_` gate). (Map previously said OFF — stale; the code default has been ON since 2026-06-07 "visibility" change.) | **ON** (opt-out `=0`) | `connection.cpp` ctor (`ULTRA_ADAPTIVE_RATE` read) | FEAT |
219-
| `ULTRA_BURST_INTERLEAVE` | force cross-frame interleave ON→whole-group ACK / OFF→SR masks (overrides the mod-gated default). 2026-06-14: default is now **mod-gated** — ON for dense coherent ≥16QAM (TIME diversity, +47% 16QAM R2/3 Good@20), OFF for QPSK/8PSK | mod-gated (16QAM ON) | `connection_policy.hpp` `burstCrossFrameInterleaveOn(Modulation)` | FEAT |
219+
| `ULTRA_BURST_INTERLEAVE` | force cross-frame interleave ON→whole-group ACK / OFF→SR masks (overrides the default). **2026-07-21: default flipped to OFF for ALL mods** (was ≥16QAM-ON) — rig A/B MPG@20 (F440-449) showed interleave couples time-localized fades → whole-group craters; OFF (+37% goodput, 5× fewer full craters) via per-frame SACK. `=1` re-enables (A/B the 2026-06-14 Good@20 +47% claim, now contradicted); `=0` = default | **default OFF** | `connection_policy.hpp` `burstCrossFrameInterleaveOn(Modulation)` | FEAT |
220220
| `ULTRA_BURST_GROUP_FRAMES` | burst group size, clamp [2,32] | code **6** (`kBurstInterleaveGroupFrames` `:64`, reconciled 16→6 on 2026-05-30 — mask-width-matched to the 6-bit SACK frame_mask; the old 16 was un-SR-addressable on the default interleave-OFF path) | `connection_policy.hpp:74` | FEAT |
221221
| `ULTRA_MAX_BURST_AIRTIME_MS` | per-key-down burst airtime ceiling → group size (frame count DERIVED from live per-frame airtime), clamp [5000,12000] | code **8600** (= **5 frames** at QPSK R2/3; 7000→8600 codified 2026-06-07 after a 20-seed Good@16 sweep tied groups 5/6 on goodput, 5 wins on key-down/SACK margin) | `connection.cpp` `burstAirtimeBudgetFrames` | FEAT |
222222
| `ULTRA_COHERENT_WINDOW` | 🟢 ACTIVE (**DEFAULT-ON 16 since 2026-07-03**) — wide coherent ARQ window: SR-ARQ in-flight window for **coherent** wideband OFDM rungs (QPSK/8PSK/16QAM via `isCoherentModulation`) at rate ≥ R2/3 (was `kWideOFDMWindowFrames` 8 — the window-16 predicate was a differential-only fossil). Differential DQPSK/D8PSK keep their own high-throughput predicate; rates < R2/3 keep 8. Enabled by the tone-burst SACK `frame_mask` 8→16-bit widen (**WIRE-BREAKING** — payload 32→40 bits, burst 27→34 symbols, 675→850 ms baseline ACK; lockstep builds) + `kToneBurstAckWindowCapFrames` 8→16. A/B 2026-07-03 (all CRC-clean): Good@20 +18/+57/+26% (seeds 42/43/7), AWGN@20 3370→**3520 record**. Env: unset→16, `=8` legacy window, `=0` disable; clamp [0,16], read ONCE. PA-duty airtime budget still mins the real burst size. | **16 (on)** | `connection_policy.hpp` `coherentOFDMWindowOverride()` + `ofdmWindowSize()`; cap `kToneBurstAckWindowCapFrames` | FEAT |

src/protocol/connection_policy.hpp

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -143,24 +143,30 @@ inline size_t burstInterleaveGroupFrames() {
143143
// QAM16 via a QPSK||QAM8 hardcode, but the ARQ stayed whole-group from the env default)
144144
// -> ALPHA ignored BRAVO's per-frame masks and skipped partial-group holes (the QAM16
145145
// offset-skip bug). Invariant: interleave OFF -> per-frame SR masks; ON -> whole-group
146-
// ACK/NACK. Default OFF for ALL modulations/rates "for now"; re-enable for Moderate/Poor
147-
// time-diversity later. Override: ULTRA_BURST_INTERLEAVE=1 (force ON), =0 (force OFF).
148-
inline bool burstCrossFrameInterleaveOn(Modulation mod) {
146+
// ACK/NACK.
147+
//
148+
// DEFAULT OFF FOR ALL MODULATIONS (2026-07-21, rig-measured — flips the prior >=16QAM-ON
149+
// default). Cross-frame TIME interleave spreads each LDPC codeword's bits across ALL N
150+
// frames of the burst. The prior default turned it ON for dense mods (>=16QAM) on the
151+
// theory that spreading a frozen FREQUENCY null into a ~1/N NICK is recoverable (GUI/offline
152+
// +47% on 16QAM R2/3 Good@20, 2026-06-14). But the HF fade the rig actually delivers is
153+
// TIME-localized (a Watterson deep fade hits one stretch of the burst): interleaving then
154+
// puts ~1/N corruption into EVERY codeword at once, and when that exceeds the LDPC budget
155+
// (it does on MPG@20) ALL codewords fail together -> a whole-group 0/N crater instead of one
156+
// dead frame. Forced-16QAM-R1/2 interleaved A/B on the IONOS rig (F440-449, MPG@20,
157+
// 2026-07-21): interleave OFF = +37% goodput (1.55 vs 1.13 kbps), 5/5 vs 4/5 delivery, and
158+
// 5x FEWER full craters (12 vs 62) — because failures stay LOCAL (partial groups the
159+
// per-frame SACK resends cheaply) instead of coupling to the burst's worst instant. MPG@20
160+
// IS a "Good" channel, so this DIRECTLY CONTRADICTS the 2026-06-14 Good@20 +47% (a calmer
161+
// epoch / R2/3 / possibly-sim realization) — the newer, faithful rig measurement wins, and
162+
// AWGN has no nulls to smear so interleave buys nothing there either. Net: no channel where
163+
// interleave clearly earns its keep on the real rig -> OFF everywhere, per-frame SACK.
164+
// Re-enable to A/B the Good@20 claim: ULTRA_BURST_INTERLEAVE=1 (force ON), =0 (force OFF).
165+
inline bool burstCrossFrameInterleaveOn(Modulation /*mod*/) {
149166
if (const char* env = std::getenv("ULTRA_BURST_INTERLEAVE")) {
150167
return env[0] == '1'; // explicit override forces ON/OFF for ALL modulations (testing)
151168
}
152-
// Default: cross-frame TIME interleave ON for DENSE coherent mods (>=16QAM, >=4 bits/symbol),
153-
// OFF (per-frame SR-ARQ) for QPSK/8PSK/BPSK. GUI-measured 2026-06-14: +47% on 16QAM R2/3
154-
// Good@20 (6/6 seeds, deint-fails ~halved). MECHANISM: a dense constellation's tight rings
155-
// cannot absorb a frequency-null codeword-wipe (frame << coherence time -> the null is frozen
156-
// within a frame); the burst spans ~1.7 coherence times, so spreading each codeword across the
157-
// burst's frames turns a static-null WIPE into a recoverable ~1/N NICK. Frequency interleaving
158-
// is already structurally maxed (each CW touches all 59 carriers) — TIME diversity is the only
159-
// remaining axis. Safe because 16QAM is only SELECTED on benign channels, so the whole-group-
160-
// ACK cost (lost per-frame SR masks) is ~0 there (no-regress measured on AWGN@30 + QPSK R3/4
161-
// Good@20). QPSK/8PSK stay per-frame SR-ARQ — their margin absorbs nulls and they serve the
162-
// lossier channels where fine-grained retransmit is the proven robustness lever.
163-
return ofdm_link_adaptation::isCoherentModulation(mod) && getBitsPerSymbol(mod) >= 4;
169+
return false; // per-frame SR-ARQ SACK for every modulation (rig evidence above)
164170
}
165171
// ═════════════════════ Software-ALC (closed-loop TX-drive control, 2026-07-02) ═════════════════════
166172
// BUG-QAM16-RIG-LEVEL-BUDGET: rig wire captures showed OFDM data arriving at only ~6-7 dB

src/protocol/waveform_selection.hpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -505,9 +505,12 @@ inline bool rxRateAuthorityEnabled() {
505505
// rate_controller.hpp). R5_6 is no longer a cap value.
506506
inline CodeRate maxValidatedCoherentRate(Modulation mod) {
507507
switch (mod) {
508-
// 2026-06-14: lifted R1/2 -> R2/3. Cross-frame TIME interleave (auto-on for QAM16 via
509-
// burstCrossFrameInterleaveOn) makes 16QAM R2/3 Good@20 viable at ~1790 bps GUI-measured
510-
// (6/6 seeds), now ABOVE the R1/2 clean rung (~1550). 2026-07-03: the R2/3 measured
508+
// 2026-06-14: lifted R1/2 -> R2/3. Cross-frame TIME interleave (then auto-on for QAM16 via
509+
// burstCrossFrameInterleaveOn) made 16QAM R2/3 Good@20 viable at ~1790 bps GUI-measured
510+
// (6/6 seeds), then ABOVE the R1/2 clean rung (~1550). NOTE (2026-07-21): interleave is now
511+
// DEFAULT-OFF (rig-measured harmful on MPG@20 fading — see burstCrossFrameInterleaveOn); the
512+
// R2/3 cap stands but its interleave-dependent margin is UNRE-MEASURED without it — re-A/B
513+
// 16QAM R2/3 vs R1/2 on the rig (per-frame SACK) before trusting R2/3 as the 16QAM cap. 2026-07-03: the R2/3 measured
511514
// ceiling is 3520 bps AWGN@20 with the wide window (the old "~2850 AWGN@30" figure is
512515
// stale); R3/4 raw = 9/8 of R2/3 -> projected ceiling ~3900. R3/4 measured damage-bound
513516
// PRE-interleave (55-70% frame loss — fable_analysis/07), so it ships knob-gated

0 commit comments

Comments
 (0)