tests: fade-SLA on-air validation harness (measure-only)#117
Merged
Conversation
…r change) A simulation in the linklab sandbox showed the adaptive controller's fixed margin may under-deliver at long range under time-correlated fading. Before changing any shipped behavior, this lands the on-air experiment to confirm (or refute) it. - tests/sdr_interferer.py: add a fading mode (--fade-coherence / --fade-depth-db) that modulates interference power with a time-correlated AR(1) envelope, so the victim link sees correlated SNR fades; auto-scales base amplitude to avoid DAC clipping and emits <interferer-fade> markers. - tools/precoder/adaptive_link.py: surface the VRX post-seq delivery (deliv=) in the <adaptive-vrx> trajectory line (observability only). - tests/fade_sla_onair.sh: A/B harness — runs the closed loop (8812 VTX <-> 8821 VRX) under STATIC then FADING interference at the same gain and compares VRX delivery (mean/p10/min) against the SLA target. If fading's worst-window delivery drops below the SLA while static holds it, the fixed-margin gap is confirmed — the precondition for a variance-aware fade margin. No controller behavior changes. Precoder suite green; harness is hardware-run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
josephnef
added a commit
that referenced
this pull request
Jun 28, 2026
…oom) (#118) ## What An **opt-in, default-off** fade margin for the adaptive controller, addressing the fading under-delivery surfaced by the linklab sim and validated on-air by the #117 harness. When `fade_margin_k > 0`, the controller tracks its **path-loss variance** (TX power removed) and adds proportional **TXAGC headroom** — *power only* — to the chosen operating point, so deep fades are covered. `fade_margin_k = 0` (default) ⇒ behaviour is **byte-for-byte unchanged**. ## Why power-only (the important bit) I first tried feeding the margin into row *selection* and **it made delivery worse** in sim. Mechanism: the energy-min argmin satisfies a higher SNR demand by dropping MCS / adding FEC (cheaper in power than TXAGC), which raises airtime and **overloads the channel during fades** (overload ticks 12→20/200). Buying the margin with **power, applied after selection** (MCS/FEC unchanged), avoids that. The rejected approach and this reasoning are in the commit message so the design choice is documented. ## Evidence (sim — linklab, K=4 dB fading, 6 held-out flights) | fade_margin_k | mean deliv | worst-flight deliv | E/bit | |---|---|---|---| | 0 (default) | 0.958 | 0.906 | 275 nJ | | 1.0 | **0.968** | **0.911** | 306 nJ (+11%) | A modest **worst-case** robustness gain for the long-range mission, traded for energy. **Honest caveat:** it does **not** fully restore the 0.99 SLA (0.968 < 0.99) — deep fades need FEC time-diversity or a learned policy. This is a knob, not a cure. ## Scope / validation status - **Sim-validated, opt-in.** The full on-air gate is `tests/fade_sla_onair.sh` (#117) run with `fade_margin_k` set; only after that should it be considered for default-on. - Changes are confined to `controller.py` (+ tests). `score.py` / `adaptive_link.py` were touched during exploration and reverted — the diff is minimal. ## Tests `off = unchanged`, `margin adds power not airtime` (MCS/FEC held), `variance tracked only when volatile`. Full precoder suite green (218). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
josephnef
added a commit
that referenced
this pull request
Jun 28, 2026
## What Hardens the fade-SLA validation tooling (#117) after the first on-air run exposed two confounds, and reports the (now valid) on-air result. **No controller changes.** ## Fixes - **`sdr_interferer.py` — mean-power-matched fading.** The fade mode scaled the base amplitude *down* for peak headroom, so at equal `--tx-gain` a fading run was weaker on average than static (fading scored spuriously *better*). Replaced with a **unit-mean log-normal power envelope** (`--fade-depth-db` = power std), so a fading run's mean power equals a static run's at the same gain — a fair A/B. - **`fade_sla_onair.sh` — reliability + regime sweep.** Free/reset adapters per phase (first-phase cold-start was failing to rendezvous), wait for `state=SESSION` before measuring, count delivery over SESSION samples only (drop the BEACONING placeholder), and **sweep `IGAINS`** to find the regime. ## On-air result (8812 ↔ 8821 + B210, mean-matched) | interferer gain | STATIC mean (p10) | FADING mean (p10) | |---|---|---| | 46 | 0.71 (0.04) | 0.70 (0.19) | | 52 | 0.82 (0.67) | 0.81 (0.70) | | 58 | 0.84 (0.75) | 0.83 (0.62) | | **64** | **0.79 (0.23)** | **0.41 (0.02)** | - **Fading degrades the controller far more than equivalent static interference** — at gain 64, matched mean power, delivery 0.79→0.41 (p10 0.23→0.02). This **directionally confirms** the sim hypothesis and the rationale for the opt-in fade margin (#118). ## Honest caveat The static baseline never reaches the 0.99 SLA on this bench (~0.7–0.84, non-monotonic) — there's a **baseline frame loss independent of the interferer**, likely half-duplex RCF feedback contending on the same channel plus the seq-gap delivery metric. So this shows the **relative** fade penalty, **not** an absolute "baseline holds 0.99 → fading breaks it." Closing that needs a separate look at the half-duplex/measurement path (or an attenuator rig to open link margin). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
On-air validation tooling to test a hypothesis surfaced by the linklab simulation sandbox: the adaptive controller's fixed margin may under-deliver at long range under time-correlated fading. This lands the experiment to confirm/refute it on the bench before any controller change — there is no shipped-behavior change here, only measurement.
Why
In sim, under correlated fading the fade-blind controller's delivery fell to ~0.92 at long range (below a 0.99 SLA), because its fixed margin can't cover deep fades. That was a nominal-calibration + synthetic-fading result — a hypothesis, not a measured fact. This harness measures it on real hardware so we don't change the controller blind.
Changes (all test/observability)
tests/sdr_interferer.py— new fading mode (--fade-coherence/--fade-depth-db): an AR(1) time-correlated envelope modulates interference power so the victim link sees correlated SNR fades. Auto-scales base amplitude to avoid DAC clipping; emits<interferer-fade>markers.tools/precoder/adaptive_link.py— surfaces the VRX post-seq delivery (deliv=) in the<adaptive-vrx>trajectory line (observability only).tests/fade_sla_onair.sh— A/B harness: runs the closed loop (8812 VTX ↔ 8821 VRX) under STATIC then FADING interference at the same gain and compares VRX delivery (mean / p10 / min) against the SLA target.How to run (hardware)
Decision rule: if the FADING run's worst-window (p10/min) delivery drops below the SLA while the STATIC run holds it at the same interferer gain, the fixed-margin gap is confirmed — and then a variance-aware fade margin in the controller is worth adding (and would be its own, validated PR).
Verification
Precoder suite green (215 passed);
sdr_interferer --helpshows the new args;fade_sla_onair.shpassesbash -nand the delivery-parse logic is unit-checked. The harness itself runs on hardware (8812 + 8821 + B210).🤖 Generated with Claude Code