You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(sync): two-crater discipline for the sync re-anchor (ULTRA_CRATER_REANCHOR_HOLD, default-off)
The RECEIVER forced a full chirp+LTS re-anchor (resetFrameArrivalTrackingLocked +
expect_full_ofdm_anchor_=true) on EVERY 0-CW group crater — treating a deep fade (an
AMPLITUDE event; warm TIMING survives, the samples are just attenuated) as a lost sync.
Cost: ~7 full re-anchors per 50KB transfer, each delaying the ACK (the RX re-acquires
instead of signaling the failure) and forcing a ~1.2s chirp on the next group — the
operator-observed "long ACK -> sender RTO -> resend" loop, watched repeatedly.
The RATE controller already declines to react to a single crater (F122 two-crater rule:
one crater is a fade the ARQ absorbs, not evidence). The SYNC re-anchor never got that
discipline. This gives it the same one: new crater_reanchor_streak_ (reset on any decoded
frame); behind ULTRA_CRATER_REANCHOR_HOLD (default-off => byte-identical), the full
re-anchor is HELD on crater #1 (RX tries warm light-sync on the next group) and fires only
on crater #2 consecutive.
Safe: expect_full_ofdm_anchor_ is RECEIVER-LOCAL (drives the RX's own search mode, NOT a
sender signal — the WAITING-REBASE voice is a separate ARQ-timeout path). A held crater
just makes the RX attempt warm next group: if warm works, a full re-anchor + ~1.2s chirp
is saved and the ACK isn't delayed; if the CFO genuinely walked, the next group craters
and #2 re-anchors + re-centers exactly as before. Worst case = ONE extra warm-attempt
group (bounded); crater #2 onward unchanged.
Verification: ctest -j4 full suite green (86/86, knob off = byte-identical). Rig A/B
(on vs off, MPG@20) is the throughput proof; default-on pending it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019btwFxDt7D19SZSqPYvZnk
Copy file name to clipboardExpand all lines: docs/CHANGELOG.md
+34Lines changed: 34 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,6 +10,40 @@ This log tracks all bug fixes and behavioral changes to prevent re-doing work du
10
10
11
11
---
12
12
13
+
## 2026-07-21 — feat(sync): two-crater discipline for the sync re-anchor (ULTRA_CRATER_REANCHOR_HOLD, default-off) — stop nuking warm sync on a single fade
14
+
15
+
**What was broken.** On every 0-CW group crater the RECEIVER
16
+
(`streaming_ofdm_decode.cpp` decodeCurrentFrame) forced a full chirp+LTS re-anchor —
17
+
`resetFrameArrivalTrackingLocked()` + `expect_full_ofdm_anchor_ = true`. A deep fade is
18
+
an AMPLITUDE event (warm TIMING survives it — the samples are there, just attenuated),
19
+
so re-acquiring timing+CFO from a fresh chirp is over-reaction: it delays the ACK (the RX
20
+
is re-acquiring instead of signaling the failure), and the next group must prepend a
21
+
~1.2 s chirp. Measured on the rig: ~7 full re-anchors per 50 KB transfer, and the
22
+
operator watched the resulting "long ACK → sender RTO → resend" loop repeatedly. The RATE
23
+
controller already declines to react to a single crater (F122 two-crater rule: one crater
24
+
is a fade the ARQ absorbs, not evidence) — but the SYNC re-anchor never got that discipline
25
+
and fired on the FIRST crater every time.
26
+
27
+
**What changed.** New `crater_reanchor_streak_` counter (StreamingDecoder), reset on any
28
+
decoded frame. Behind `ULTRA_CRATER_REANCHOR_HOLD` (default-off ⇒ byte-identical): the
29
+
full re-anchor is HELD on crater #1 (warm sync kept — the RX tries warm light-sync on the
30
+
next group instead of a full blind chirp search) and fires only on crater #2 consecutive.
31
+
Same restraint the rate controller already uses.
32
+
33
+
**Why safe.** `expect_full_ofdm_anchor_` is RECEIVER-LOCAL (drives the RX's own search
34
+
mode; NOT wired to force a sender full-anchor — the WAITING-REBASE voice is a separate
35
+
ARQ-timeout path). So a held crater just makes the RX attempt warm on the next group:
36
+
if warm works, a full re-anchor + ~1.2 s chirp is saved and the ACK isn't delayed; if the
37
+
CFO genuinely walked, the next group craters and #2 re-anchors + re-centers exactly as
38
+
before. Worst case = ONE extra warm-attempt group (bounded); crater #2 onward is unchanged.
39
+
40
+
**Verification.** `ctest -j4` full suite green (86/86, knob off = byte-identical). Rig A/B
41
+
(ULTRA_CRATER_REANCHOR_HOLD on vs off, MPG@20) is the throughput proof — expected to cut
42
+
the per-run full-re-anchor count and the ACK-delay→RTO→resend loop the operator flagged.
43
+
Default-on pending the rig A/B.
44
+
45
+
---
46
+
13
47
## 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
48
15
49
**What changed.** `connection_policy.hpp::burstCrossFrameInterleaveOn(mod)` now returns
Copy file name to clipboardExpand all lines: docs/MODEM_INFRASTRUCTURE_MAP.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -378,6 +378,7 @@ Buckets per the env-knobs→runtime-derivation workstream: **[FEAT]** in-flight
378
378
|`ULTRA_ZF_LLR_UNBIAS`| 🟡 EXPERIMENTAL (default OFF) | channel_equalizer_equalize.cpp (end of equalize()) | Stage A ZF-consistent LLR unbias — measured MARGINAL on the failing sim cell (docs/LLR_NOTCH_CALIBRATION_2026_07_06.md §measured); controls exactly invariant (QPSK/AWGN); keep OFF unless rig evidence diverges from the sim cell |
379
379
|`ULTRA_NOTCH_NV`| 🟡 EXPERIMENTAL (default OFF) | channel_equalizer_equalize.cpp nv denominator (+ raw-obs writer channel_equalizer_pilot.cpp) | Stage B per-carrier reliability floor from raw LS pilot observations — same experiment, same marginal verdict; keep OFF |
380
380
|`ULTRA_RX_PREDICTIVE_CLIMB`| 🟢 ACTIVE (default ON, `=0` opt-out) | connection.cpp updateRxAuthorityCommand | EESM per-carrier virtual rung evaluation — direct multi-rung climbs from measured gamma snapshots (docs/RX_AUTHORITY_PREDICTIVE_2026_07_07.md); fallback = one-rung ladder |
381
+
| `ULTRA_CRATER_REANCHOR_HOLD` | 🟡 EXPERIMENTAL (A/B knob, default OFF ⇒ byte-identical; throughput-ceiling lever #3, 2026-07-21, CHANGELOG). TWO-CRATER discipline for the SYNC re-anchor: the RX forces a full chirp+LTS re-anchor (`resetFrameArrivalTrackingLocked` + `expect_full_ofdm_anchor_=true`) on EVERY 0-CW group crater (`streaming_ofdm_decode.cpp` decodeCurrentFrame, ~7×/50KB transfer), treating a deep fade (an AMPLITUDE event — warm TIMING survives) as a lost sync → delayed ACK + ~1.2s chirp on the next group → the operator's "long ACK → RTO → resend" loop. ON: hold warm sync through crater #1 (RX tries warm light-sync next group), re-anchor only on crater #2 — the same restraint the RATE controller's F122 two-crater rule already uses. Safe: `expect_full_ofdm_anchor_` is RECEIVER-LOCAL (own search mode, not a sender signal); worst case = 1 extra warm-attempt group, crater #2 re-anchors + re-centers CFO as before. `crater_reanchor_streak_` resets on any decoded frame. | **OFF** | `streaming_ofdm_decode.cpp` decodeCurrentFrame (`crater_reanchor_streak_` + `ULTRA_CRATER_REANCHOR_HOLD` gate); member `streaming_decoder.hpp` | FEAT (A/B) |
381
382
| `ULTRA_RX_EMA_HOLD` | 🟡 EXPERIMENTAL (A/B knob, default OFF ⇒ byte-identical; throughput-ceiling audit lever #1, 2026-07-21, CHANGELOG). Two coupled corrections to the RX-authority crater response, targeting the fast-vs-slow variance (F344 2.61 vs F372 1.16 kbps SAME channel = rate-controller limit cycle, not PHY): (1) **EMA-supported HOLD** — a confirmed crater does not demote/ratchet while the fade-averaged ring `snr_avg` STRICTLY exceeds the current rung's class floor (`rungClassAnchorDb`, new ladder-aware+class-specific anchor); consecutive craters at a supported average are deep-null brushes the ARQ resends through. (2) **Censored failed-group SNR** — a non-delivered group enters the obs ring right-censored at `min(obs, calibrationAnchorDbFor)` instead of the stale-crest `burst_obs_snr_db_`, killing the survivor bias so sustained real failure drives `snr_avg` below the class anchor and DOES demote (strict `>` breaks the censor==anchor latch). No new magic constants (thresholds are the rung's own anchors). Preserves the F122-F160 crater_streak≥2 / one-rung-down / ratchet / dwell machinery — only makes the demote conditional on the measured average. Unset/`=0` → byte-identical. Read per-group (togglable in tests). | **OFF** | `connection.cpp` `emaHoldEnabled()` / `updateRxAuthorityCommand` (censored ring feed + `ema_supports_cur`/`demote_on_crater`); `waveform_selection.hpp` `rungClassAnchorDb`; tests `test_rx_authority.cpp` (`test_ema_hold_absorbs_supported_crater`, `test_ema_hold_still_demotes_sustained_failure`) | FEAT (A/B) |
382
383
ABSOLUTE rung selection: receiver maps its fresh per-group observation (broadband SNR EMA +
383
384
coherence-adjusted fading, `Connection::setBurstChannelObservation` fed by the modem binding)
0 commit comments