Skip to content

tests: fade-SLA on-air validation harness (measure-only)#117

Merged
josephnef merged 1 commit into
masterfrom
fade-sla-validation
Jun 28, 2026
Merged

tests: fade-SLA on-air validation harness (measure-only)#117
josephnef merged 1 commit into
masterfrom
fade-sla-validation

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

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)

sudo bash tests/fade_sla_onair.sh
IGAIN=80 FADE_DEPTH=14 SECS=45 sudo bash tests/fade_sla_onair.sh

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 --help shows the new args; fade_sla_onair.sh passes bash -n and the delivery-parse logic is unit-checked. The harness itself runs on hardware (8812 + 8821 + B210).

🤖 Generated with Claude Code

…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 josephnef merged commit 15f93b9 into master Jun 28, 2026
7 checks passed
@josephnef josephnef deleted the fade-sla-validation branch June 28, 2026 12:25
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>
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.

1 participant