Skip to content

test(compat): extended receiver FEC×NAK×FREEZE evaluation harness - #39

Merged
andrescera merged 11 commits into
mainfrom
test/extended-receiver-fec-nak-evaluation
Jul 1, 2026
Merged

test(compat): extended receiver FEC×NAK×FREEZE evaluation harness#39
andrescera merged 11 commits into
mainfrom
test/extended-receiver-fec-nak-evaluation

Conversation

@andrescera

Copy link
Copy Markdown
Member

What

Extended receiver FEC×NAK×FREEZE evaluation harness for the SRTLA compat suite:

  • FEC-compile probe (tests/compat/lib/probe-fec-capability.sh) — detects whether the receiver's libsrt build can negotiate an FEC packet filter.
  • srt-sink sockopt read-back — the mock SRT receiver now reads back the negotiated SRTO_NAKREPORT / SRTO_LOSSMAXTTL / SRTO_REORDERFREEZE off the accepted socket, plus a proxy-fidelity assertion against irl-srt-server's L1/L2 profile tuples.
  • FEC caller passthrough via srt-live-transmit (CALLER_PACKETFILTER), since ffmpeg's libsrt wrapper has no packetfilter option.
  • Reverse-channel metering (reverse_wire_bytes / reverse_wire_amp) so a recipe cannot false-promote on forward wire_amp alone.
  • Stdlib exact-U stats engine (gain-hunt-matrix.sh --analyze) — pure-stdlib exact Mann-Whitney U + Holm-Bonferroni, no scipy.
  • Two-stage screen→deep campaign orchestrator with anti-false-NULL sentinel rescue and a PORT_MISMATCH falsifiability control.
  • Golden-fixture stats test and a geometry/wire-amp lint that both run anywhere python3/awk do.

Why

Empirically settle whether any FEC / NAK / FREEZE receiver recipe beats the Classic baseline, under a pre-registered decision rule (real gain + no regression), so the operator-facing receiver-capability catalog is populated by evidence rather than intuition. The campaign result was NULL — no combination cleared the gate — and that NULL is recorded as a first-class, falsifiable outcome.

How to verify

  • cmake -B build && cmake --build build && ctest --test-dir build → 187/187 green
  • tests/compat/scenarios/gain-hunt-analyze-test.sh → 4/4 golden fixtures (gain→promoted, regression→rejected, reverse-spam→rejected, tie→none)
  • tests/compat/scenarios/gain-hunt-geometry-lint.sh → geometry budget lint passes and discriminates (cols:16 PASS, cols:10/cols:8 REJECT)

Risks

Test-harness-only changes — no product or receiver-binary code is touched. The gain-hunt-matrix scenario is registered tier: informational in matrix.yaml (non-blocking; not iterated by run-matrix.sh --tier). The campaign's NULL verdict is documented in docs/GAIN-HUNT-PROTOCOL.md.

reorder-stress.sh gains an optional CALLER_PACKETFILTER env for the FEC arms
of the gain hunt. ffmpeg's libsrt wrapper has a fixed SRT-option allow-list
with no packetfilter (appending it hard-fails Option not found), so the FEC
arm routes ffmpeg's MPEG-TS into srt-live-transmit (libsrt 1.5.5, FEC-capable)
as the SRT caller carrying the filter -- the same caller as fec-connect-matrix.

- Unset (every non-FEC arm): ffmpeg-direct SRT caller, byte-identical to
  pre-change (Rule E).
- Set (must match ^fec,, arq:never refused): ffmpeg | srt-live-transmit with
  a fresh ms-based SRT options string (srt-live-transmit latency is ms, not
  ffmpeg's microseconds; timeout is ffmpeg-only).
- srt-live-transmit absent + filter set => SKIP exit 77 (this scenario's
  convention).
- result.json records config.caller_packetfilter and the read-back
  sink.negotiated_packetfilter.
The --stage deep code path wrote deep-results.json as only the manifest
({stage,out_dir,deep_set}); the per-cell U-stat, Holm-corrected p, and
guardrail outcomes lived solely in the co-located deep/analyze-verdict.json,
and no aggregate recorded each cell's NETEM_SEED. T-B3 requires deep-results.json
itself to list every deep cell (survivors, top-K, sentinel) with its per-cell
U-stat + Holm p + guardrails, each recording its NETEM_SEED.

Add write_deep_results(), invoked by do_deep after emit_verdict, merging the
deep manifest, the analyze-verdict stats, and each cell's per-rep NETEM_SEED into
the canonical deep-results.json. analyze-verdict.json is preserved unchanged.
Add --aggregate-deep <dir> to regenerate the aggregate from already-measured
evidence with no privilege and no campaign re-run (deterministic; verdict/stats
unchanged).
…lemented + ran)

Three doc surfaces still described the two-stage campaign orchestrator as an
unimplemented stub / follow-up seam, which is stale: T-A6 landed the screen->deep
structure and Wave B RAN the campaign to a NULL verdict.

- gain-hunt-matrix.sh header: drop "filled in by a follow-up effort (T-A6)"; state
  the two-stage screen->deep structure is implemented and the campaign RAN (NULL).
- GAIN-HUNT-PROTOCOL.md: the "Harness scaffold" bullet said orchestrator **stub**...
  **does not run the campaign** — now describes the fully-wired orchestrator that
  has run the campaign (verdict NULL), aligned with the Status block below it.
- AGENTS.md: "Gain-hunt scaffold (Task 2, pending)" -> "Task 2, DONE", campaign ran
  with a NULL verdict; points to the up-to-date gain-hunt section above.

Also reword the --analyze mode comment to describe it as the stats layer (not the
measurement driver) without the ambiguous 'does not run the campaign' phrasing.
@andrescera
andrescera merged commit c9ac15a into main Jul 1, 2026
22 checks passed
@andrescera
andrescera deleted the test/extended-receiver-fec-nak-evaluation branch July 1, 2026 03:59
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