tx.report coverage attribution: fw emission ceiling, interleaved drops, dead MISSED_RPT_NUM (#363 step 1) - #368
Conversation
…nterleaved per-report drops, MISSED_RPT_NUM stuffed constant tests/txrpt_coverage_attrib.py walks a recorded run's report stream and prints coverage, the tag-gap run-length histogram, and the missed-field distribution — the three facts that attribute a coverage collapse. Measured (8812CU TX, arq_e2e ledgers, envelope sweep 1.0-2.4k fps): full coverage to ~1.25k fps, then coverage ≈ ceiling/fps with the CCX emission path saturating at ~1.3-1.4k reports/s (99.4% @ 1.26k, 77.2% @ 1.82k, 53-56% @ 2.39k). The excess drops per-report and interleaved — 99.3% of unreported frames sit in tag gaps <= 2, the signature of an emission-time rate limiter, not of transport-batch loss (a dropped C2H aggregate would lose contiguous dozens). And the CCX MISSED_RPT_NUM field is stuffed with a constant (4) on this fw, drops or no drops — parse verified bit-exact against the 8822B/C/E vendor headers, so the fw offers no drop accounting and SW_DEFINE tag gaps are the only drop signal. TxReport.h documents the dead field; docs/scheduled-mac.md carries the envelope and the consequence: above the ceiling, sample SPE_RPT 1-in-N or account report-less frames as unknown. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR Summary by QodoAttribute tx.report coverage loss to fw emission ceiling; add analysis tool + docs
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
- tx.report events carry t (monotonic ms) — the CCX emission ceiling is a reports-per-second quantity and was previously unmeasurable from the stream itself (the envelope numbers were derived by hand from run spans). Verified on air: the attribution tool now computes achieved rpt/s live. - txrpt_coverage_attrib.py prints the literal fw-acknowledged-drops vs tag-gap reconciliation whenever a fw actually populates MISSED_RPT_NUM (with the 3-bit saturation caveat), keeps the constant-field diagnosis shortcut, and gains the main() guard. - docs/logging.md: tx.report row carries t + the dead-missed-field note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…der the fw emission ceiling (#363 step 2) (#369) Step 2 of #363 (step 3, windowed RX receipts, remains). ## What it does The report knob becomes the sampling divisor N: SPE_RPT is requested on every Nth frame (0 = off, 1 = every frame — previous semantics unchanged), while the halmac SW_DEFINE tag still stamps **every** frame. Received-tag deltas are therefore exact multiples of N; any other delta is a dropped report (k·N = k−1 sampled reports lost, non-multiples are anomalies). On the 8812 report format (no tag echo) sampling only relieves the rate, via a new per-device counter. Spec derived in #368: the CCX emission path saturates at ~1.3–1.4 k reports/s, so pick **N ≥ fps/1300** and coverage of the sampled frames is deterministic instead of load-collapsed. ## Measured (on air, 2.4 k fps) | run | coverage | achieved rpt/s | note | |---|---|---|---| | N=1 control | 56.4% | 1,329 | the collapse persists — fifth independent ceiling measurement | | **N=2** | **100.0% of sampled** (191,298/191,323) | 1,107 | **zero lost, zero off-modulo anomalies** | | N=1 @ 1 k fps | 99.9%, no tag gaps | 749 | old behaviour byte-for-byte; arq_e2e verdict stays acked_undelivered=0 | Statistical-accounting check: the N=2 sampled ok-rate read **99.71% against a 99.99% ledger ground truth** — pessimistic by the known ACK-loss asymmetry (`ok=0`-but-delivered), the safe direction for a delivery-accounting consumer. ## Changes - `DeviceConfig::Tx::report` bool → int (doc-tagged with the ceiling + the N ≥ fps/1300 rule); env parse clamps 0..255 (tag-unwrap ambiguity bound). - J2/J3: tag every frame, request every Nth (both fields inside the checksummed span, re-checksum unchanged). J1: new `_tx_ccx_ctr` drives the cadence. - `tests/txrpt_coverage_attrib.py --sample-n` — expected-delta accounting for sampled ledgers. - `tests/arq_e2e_delivery.sh` — `DRONE_REPORT_N` passthrough. - `docs/scheduled-mac.md` — the knob documented next to the measured envelope, favourable and adversarial numbers together. ## Validation Three fresh on-air runs (money/control/regression above); `ctest` 48/48; existing matrix scripts unaffected (they pin `DEVOURER_TX_REPORT=1`, whose semantics are unchanged). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…he rx.seq ledger (#363 step 3) (#370) Closes #363. ## The tier this completes The ARQ campaign established the two lower tiers' limits: the hardware ACK's horizon is chip-FIFO admission (it can never confirm host delivery — #359), and per-frame CCX reports collapse against the fw's ~1.3 k reports/s emission ceiling (#368, sampled in #369). This PR adds the tier neither can reach: **the receiving application counts what it consumed and mails it back.** - `src/cell/RxReceipt.h` — `ReceiptWindow` (receiver: sliding ring bitmap over the last W frame indices) + `ReceiptLedger` (transmitter: idempotent merge of overlapping, versioned receipt TLVs; strict-prefix parse; absorbs only receipts naming its own TA). Same caller-side contract as `UeRxAttribution`: demos feed frames, RX loops untouched, no payload convention assumed. - duplex: notes on the existing `rx.seq` path, injects a receipt frame every `DEVOURER_RX_RECEIPT_MS` on the feedback path (802.11 data at 6M; concurrent `send_packet` callers serialize on a demo mutex). `DEVOURER_RX_RECEIPT_WINDOW` sizes coverage (default 8192). - txdemo: `DEVOURER_TX_RECEIPTS` absorbs and emits **every** receipt as `tx.receipt` with the raw TLV hex. - `tests/receipt_verify.py`: replays the TLV merge and demands **set-equality** with the receiver's own `rx.seq` ledger — the issue's acceptance bar, literally. - `receipt_roundtrip` ctest selftest; `RECEIPT_MS` knob in the arq harness; docs (logging.md rows, scheduled-mac.md tier paragraph). ## Measured (on air) | regime | frames verified | receipts | verdict | |---|---|---|---| | clean, 5 cycles | 126,594 | 1,671 | **FRAME-EXACT** | | 150 ms consumer stalls @ 2.4 k fps, spsc-fat parking ring | 349,455 | 1,330 | **FRAME-EXACT** | ## Two bench-taught lessons, in the code 1. **Received bodies carry the trailing FCS** — the first live run absorbed zero receipts because the TLV parse demanded exact length; an ACK frame logging `len:14` (10 + FCS) was the tell. The parse is strict-prefix now, with the TA-match keeping arbitrary payloads out. 2. **The window must exceed the worst backlog drain in frames** — with a 2,048-bit window, a stalled spsc-fat pool draining ~3 k frames inside one receipt interval evicted 2,846 delivered frames before any receipt covered them (`MISSING` in the verifier, `late=0` — eviction, not reordering). The sizing rule is at the class comment: `window_bits > pool_bytes / min_frame_bytes + one encode interval of arrivals`; the 8192 default clears this bench's worst case ~2.7×. ## Validation `ctest` 49/49 (new selftest included); four on-air runs (first-light smoke, full-scale clean, failed 2048-window stress — kept as the sizing lesson — and the passing 8192-window stress). The verifier's `NO-RECEIPTS` and `MISMATCH` paths were both exercised for real during bring-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Step 1 of #363 (the issue stays open for SPE_RPT sampling and windowed RX receipts, steps 2–3).
What the recorded #359/#366/#367 ledgers + a 3-point envelope sweep establish
The 53% coverage collapse is a fw-side CCX emission ceiling, and the fw does not account its own drops.
coverage ≈ min(1, ceiling/fps)with ceiling ≈ 1.3–1.4 k reports/s fits every measured point (8812CU TX).Attribution to emission-time, not transport: 99.3% of unreported frames sit in SW_DEFINE tag gaps ≤ 2 (95 k singles + 47 k doubles at 2.39 k fps, max gap 28) — interleaved per-report loss, the signature of a rate limiter at report generation. Transport-batch loss (a dropped C2H aggregate) would lose contiguous dozens per event; the data shows the opposite. The tag-unwrap ground truth is trusted because the same runs' ledger joins matched 203,799/203,799 ok-frames in #359.
MISSED_RPT_NUM is dead on this fw: constant 4 on every report across all runs — including 100%-coverage runs where nothing was dropped. Parse verified bit-exact against the 8822B/8822C/8822E vendor headers (
CCX_RPT_GET_MISSED_RPT_NUM: dword +0x4, bits [15:13] — identical in all three trees), so this is fw behaviour, not a devourer bug. Tag gaps are the only drop signal available.What's in the PR
tests/txrpt_coverage_attrib.py— coverage, gap-shape histogram, missed-field distribution per recorded run (refuses tag-less J1 ledgers loudly).src/TxReport.h— themissed_rptfield documents its measured deadness.docs/scheduled-mac.md— the envelope + the consequence for accounting layers: keep the demanded report rate under ~1.3 k/s (SPE_RPT 1-in-N — step 2) or treat report-less frames as "unknown".Validation
Three fresh on-air envelope runs (1.5 k/2 k/3 k fps demanded) + re-analysis of four recorded runs; tool parity-checked against the exploratory pass;
ctest48/48 (comment-only library change).🤖 Generated with Claude Code