镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

DEVOURER_TX_RETRY_FALLBACK=off: pin retries at the descriptor rate; floor form measured and rejected (#362) - #371

Merged
josephnef merged 3 commits into
masterfrom
arq-retry-fallback
Aug 4, 2026
Merged

josephnef merged 3 commits into
masterfrom
arq-retry-fallback

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Closes #362 — with this, all four of the issue's acceptance items are met: the retry-limit curve and FEC dimensioning landed in #367 (steps 1+4), and steps 2–3 now have measured go/no-go with vendor citations plus the step-2 knob implemented.

What the measurements found (recorded sweep data + three fresh arms)

The fw fallback ladder is already on — every generation's inject path leaves DISDATAFB/DISABLE_FB at 0, and retried frames measurably step down: MCS3 → 54M → 24M → 18M → 9M → 6M, ~10% of retried frames finishing below the original rate. (My first issue comment quoted the vendor's injection path, which hard-disables fallback — devourer diverged; and a claimed J1 asymmetry was an NDPA-branch misread. Both corrected on the issue.)

arm (retry_limit 8, ~60 k reports) retried final_rate verdict
default 4×57, 5×13, 7×17, 8×37, 11×51, 15×1240 ladder confirmed
off 15×1200 — all pinned knob works exactly
MCS0 floor 15×24,642, 45×6,744 (= VHT1SS_MCS1!), 20× retry inflation, −1 pt delivery rejected

What ships

  • DEVOURER_TX_RETRY_FALLBACK=off (DeviceConfig::Tx::retry_fallback) on all three generations — sets DISDATAFB/DISABLE_FB so retries re-air at the descriptor DATARATE, for constant-rate links where a 6M re-air of an MCS3 frame costs ~4× airtime. Unset = byte-identical current behaviour.
  • Deliberately no floor form: DATA_RTY_LOWEST_RATE is not a plain DESC_RATE bound on this fw — with the inject path's RA-group 9 the fw reinterprets the bound inside the group's rate space and the ladder wanders into VHT rates. The enum note carries the finding; the FrameParser headers keep the field position documented as deliberately unwritten; the env parse warns on any non-"off" value.
  • docs/scheduled-mac.md — measured distributions beside the retry-limit guidance; DRONE_FALLBACK passthrough in the arq harness.

Step 3's disposition (also on the issue, vendor-cited): per-packet deadline NO-GO (no descriptor lifetime field on Jaguar3); per-AC PARTIAL via REG_PKT_LIFE_TIME 0x4C0 (1024 µs units) — effectively per-link since injection rides one queue; the fw's CCX state-2/3 lifetime signature must be bench-verified before anything trusts it.

Validation

Three fresh on-air arms (table above) + an off-arm hygiene smoke after the floor removal (888/888 pinned); ctest 49/49; default path byte-identical by construction.

🤖 Generated with Claude Code

josephnef and others added 2 commits August 4, 2026 07:52
…loor form measured and rejected

Every generation's inject path leaves the firmware retry rate-fallback
ladder enabled (measured on the 8812CU: retried frames step MCS3 -> 54M ->
24M -> 18M -> 9M -> 6M, ~10% of retried frames finishing below the original
rate). That is the right default for weak-signal links and the wrong one for
constant-rate FPV, where a 6M re-air of an MCS3 frame costs ~4x airtime —
the new knob's "off" sets DISDATAFB / DISABLE_FB and pins every retry at the
descriptor DATARATE (measured: 1,200/1,200 retried frames re-aired at the
original rate; default arm replicates the ladder; both at retry_limit 8,
~60k reports/arm).

A DATA_RTY_LOWEST_RATE floor form was implemented, measured, and REJECTED:
the fw reinterprets the bound inside the RA-group rate space (inject runs
RA-group 9) — an MCS0 floor sent retried HT frames wandering into VHT rates
(final_rate 45 = VHT1SS_MCS1) with a 20x retry inflation and a point of
delivery loss. The enum carries the finding, the FrameParser headers keep
the field position documented as deliberately unwritten, and the env parse
warns on any non-"off" value instead of surprising the link.

Knob on all three generations (same descriptor-bit semantics; J1's NDPA
branch keeps its own unconditional disable). docs/scheduled-mac.md carries
the measured distributions next to the retry-limit guidance;
arq_e2e_delivery.sh grows the DRONE_FALLBACK passthrough.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ff knob

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add DEVOURER_TX_RETRY_FALLBACK=off to pin TX retries to descriptor rate

✨ Enhancement 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Add an env-configured knob to disable firmware retry rate fallback on inject TX.
• Apply the knob across Jaguar1/2/3 TX descriptor builders without changing defaults.
• Document measured fallback behavior and explicitly reject a lowest-rate “floor” form.
Diagram

graph TD
  A["ENV: DEVOURER_TX_RETRY_FALLBACK"] --> B["env_config.cpp"] --> C["DeviceConfig::Tx"] --> D["RtlJaguar* build_tx_block"] --> E["TX desc DIS*FB bit"] --> F["FW retry ladder"]
  G["tests/arq_e2e_delivery.sh"] --> A
  H["docs/scheduled-mac.md"] --> A
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Implement an RA-group-aware retry floor
  • ➕ Would allow bounded fallback (avoid lowest legacy rates) while retaining some resilience on weak links
  • ➕ Could be more flexible than a binary on/off switch
  • ➖ Requires firmware-accurate mapping of DATA_RTY_LOWEST_RATE semantics per RA-group and PHY mode
  • ➖ Higher risk of surprising cross-rate behavior (as measured) without extensive bench validation
2. Auto-disable fallback when a fixed TX rate is configured
  • ➕ No extra knob for common constant-rate workflows
  • ➕ Reduces operator error/misconfiguration
  • ➖ Heuristics can be wrong (fixed rate may still want fallback in marginal links)
  • ➖ Harder to reason about and test than an explicit opt-in env switch
3. Expose a runtime setter instead of env-only
  • ➕ Can be toggled during experiments without process restart
  • ➕ Enables adaptive policies (e.g., disable fallback only in specific phases)
  • ➖ Requires API surface changes and synchronization rules across devices/threads
  • ➖ More code paths to validate across chip generations

Recommendation: The PR’s explicit opt-in knob ("off" only) is the safest and most reviewable approach given the measured firmware reinterpretation of DATA_RTY_LOWEST_RATE. Keeping default behavior byte-identical minimizes regression risk, while still enabling constant-rate links to avoid airtime blowups from deep-retry rate step-downs. If a bounded fallback is desired later, pursue an RA-group-aware design behind a separate experimental flag with dedicated bench coverage.

Files changed (9) +73 / -0

Enhancement (4) +38 / -0
DeviceConfig.hAdd RetryFallback enum and tx.retry_fallback config field +19/-0

Add RetryFallback enum and tx.retry_fallback config field

• Introduces RetryFallback { Default, Off } with detailed in-header notes capturing measured firmware behavior. Extends DeviceConfig::Tx with retry_fallback and documents env grammar and default/byte-identical behavior.

src/DeviceConfig.h

RtlJaguarDevice.cppDisable firmware retry fallback in Jaguar1 TX descriptors when configured +7/-0

Disable firmware retry fallback in Jaguar1 TX descriptors when configured

• Threads cfg.tx.retry_fallback into the Jaguar1 TX descriptor build path. When set to Off, sets the DISABLE_FB bit so retries re-air at the descriptor DATARATE; default leaves existing behavior unchanged.

src/jaguar1/RtlJaguarDevice.cpp

RtlJaguar2Device.cppDisable retry rate fallback in Jaguar2 TX descriptors when configured +6/-0

Disable retry rate fallback in Jaguar2 TX descriptors when configured

• Adds handling for cfg.tx.retry_fallback in the Jaguar2 build_tx_block path. When Off, sets DISDATAFB in the checksummed descriptor span and recalculates checksum as usual.

src/jaguar2/RtlJaguar2Device.cpp

RtlJaguar3Device.cppDisable retry rate fallback in Jaguar3 TX descriptors when configured +6/-0

Disable retry rate fallback in Jaguar3 TX descriptors when configured

• Adds handling for cfg.tx.retry_fallback in the Jaguar3 TX descriptor builder. When Off, sets DISDATAFB so deep retries do not step down toward legacy rates; defaults remain unchanged.

src/jaguar3/RtlJaguar3Device.cpp

Tests (1) +2 / -0
arq_e2e_delivery.shAdd DRONE_FALLBACK passthrough to DEVOURER_TX_RETRY_FALLBACK +2/-0

Add DRONE_FALLBACK passthrough to DEVOURER_TX_RETRY_FALLBACK

• Introduces a DRONE_FALLBACK parameter and conditionally forwards it as DEVOURER_TX_RETRY_FALLBACK for the drone-sim txdemo run. Enables A/B testing of pinned vs laddered retry behavior in the end-to-end ARQ harness.

tests/arq_e2e_delivery.sh

Documentation (3) +22 / -0
scheduled-mac.mdDocument retry rate-fallback ladder and the new off knob +11/-0

Document retry rate-fallback ladder and the new off knob

• Adds measured observations that injected-frame retries step down in rate by default. Documents DEVOURER_TX_RETRY_FALLBACK=off as a way to pin retries to the descriptor rate and explains why no lowest-rate floor is offered due to anomalous firmware behavior.

docs/scheduled-mac.md

FrameParserJaguar2.hDocument DATA_RTY_LOWEST_RATE field as deliberately unused +5/-0

Document DATA_RTY_LOWEST_RATE field as deliberately unused

• Adds a macro definition and comment for the DATA_RTY_LOWEST_RATE bitfield, explicitly documenting it as present but intentionally not written due to measured RA-group reinterpretation issues.

src/jaguar2/FrameParserJaguar2.h

FrameParserJaguar3.hDocument DATA_RTY_LOWEST_RATE field as deliberately unused +6/-0

Document DATA_RTY_LOWEST_RATE field as deliberately unused

• Adds a macro definition and comment for DATA_RTY_LOWEST_RATE on 8822C descriptors. Captures that it is not a plain descriptor-rate bound on the measured firmware and is intentionally unwritten.

src/jaguar3/FrameParserJaguar3.h

Other (1) +11 / -0
env_config.cppParse DEVOURER_TX_RETRY_FALLBACK with off-only semantics +11/-0

Parse DEVOURER_TX_RETRY_FALLBACK with off-only semantics

• Adds env parsing for DEVOURER_TX_RETRY_FALLBACK. Accepts only "off" (sets RetryFallback::Off) and warns for any other value while preserving the default firmware ladder behavior.

examples/common/env_config.cpp

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Missing stderr flush after warn ✓ Resolved 📘 Rule violation ◔ Observability
Description
The new DEVOURER_TX_RETRY_FALLBACK warning is printed to stderr without a per-line flush, so
when stderr is buffered (e.g., redirected) the diagnostic may be delayed/interleaved and can stall
piped consumers. This violates the requirement that each human diagnostic line be flushed per line.
Code

examples/common/env_config.cpp[R125-127]

+      std::fprintf(stderr,
+                   "devourer [W] DEVOURER_TX_RETRY_FALLBACK='%s' unsupported "
+                   "(only \"off\") — keeping the firmware ladder\n", e);
Evidence
PR Compliance ID 11 requires human diagnostic log lines to be flushed per line. The added warning
uses std::fprintf(stderr, ...) and does not follow it with std::fflush(stderr), so the line may
remain buffered when stderr is not unbuffered.

CLAUDE.md: Human Diagnostic Log Lines Must Be Written Atomically and Flushed Per Line: CLAUDE.md: Human Diagnostic Log Lines Must Be Written Atomically and Flushed Per Line: CLAUDE.md: Human Diagnostic Log Lines Must Be Written Atomically and Flushed Per Line: CLAUDE.md: Human Diagnostic Log Lines Must Be Written Atomically and Flushed Per Line
examples/common/env_config.cpp[125-127]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A human diagnostic warning is written to `stderr` but not flushed per line, which can cause buffered/late logs when `stderr` is redirected.
## Issue Context
Compliance requires each diagnostic log line be written atomically and flushed per line.
## Fix Focus Areas
- examples/common/env_config.cpp[125-128]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Stale Floor mode comments ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
Comments in both the Jaguar TX descriptor builders and the ARQ e2e harness advertise a
retry-fallback “Floor”/`` mode (including floor-bounding via rate-limit fields), but the actual
configuration shipped by this PR only supports Default and Off and explicitly documents that the
floor form was rejected/unsupported. This mismatch is misleading for maintainers and can cause bench
runs or debugging sessions to assume behavior that will never be applied.
Code

src/jaguar1/RtlJaguarDevice.cpp[R1180-1182]

+   * disables it. Off pins retries at the descriptor rate; Floor bounds the
+   * ladder via DATA_RATE_FB_LIMIT. Dword3/4, inside the checksummed 32
+   * bytes. */
Evidence
The cited config/docs introduce RetryFallback::{Default, Off} and explicitly state that a
lowest-rate “floor” form (e.g., DATA_RTY_LOWEST_RATE / bounding via DATA_RATE_FB_LIMIT) was
rejected, yet the newly added TX descriptor builder comments still describe a Floor mode and
rate-floor bounding behavior, creating a direct contradiction. Similarly, the ARQ e2e harness script
documents DRONE_FALLBACK as accepting off |  | "", but the env-to-DeviceConfig translation only
recognizes the literal off and treats any other value as unsupported (emitting a warning and
keeping the firmware fallback ladder), so the advertised `` behavior cannot occur.

src/DeviceConfig.h[82-93]
src/DeviceConfig.h[221-226]
src/jaguar1/RtlJaguarDevice.cpp[1178-1184]
src/jaguar2/RtlJaguar2Device.cpp[1542-1547]
src/jaguar3/RtlJaguar3Device.cpp[2020-2025]
tests/arq_e2e_delivery.sh[47-47]
examples/common/env_config.cpp[118-128]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Update documentation/comments to match the implemented retry-fallback configuration: remove or rewrite references to a non-existent `Floor`/rate-based fallback mode (including floor-bounding via rate-limit fields), because the PR only supports `RetryFallback` values `Default` and `Off` and explicitly rejects the floor form. Also align the ARQ e2e harness documentation for `DRONE_FALLBACK` with the env parser behavior, which only accepts `off` (all other values warn and leave firmware fallback enabled).
## Issue Context
This PR deliberately ships *no floor/rate form* (the floor concept was rejected/unsupported per `RetryFallback` documentation), but several newly added comments still describe “Floor bounds the ladder” and the test harness advertises a `<rate>` value for `DRONE_FALLBACK`. The env-to-config translation contradicts that harness documentation by only accepting `off` and otherwise preserving the firmware ladder.
## Fix Focus Areas
- src/jaguar1/RtlJaguarDevice.cpp[1178-1182]
- src/jaguar2/RtlJaguar2Device.cpp[1542-1545]
- src/jaguar3/RtlJaguar3Device.cpp[2020-2023]
- src/DeviceConfig.h[82-93]
- tests/arq_e2e_delivery.sh[47-47]
- examples/common/env_config.cpp[118-128]
- src/DeviceConfig.h[221-226]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread examples/common/env_config.cpp
Comment thread src/jaguar1/RtlJaguarDevice.cpp Outdated
…ment remnants

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef
josephnef merged commit 4543c56 into master Aug 4, 2026
24 checks passed
@josephnef
josephnef deleted the arq-retry-fallback branch August 4, 2026 05:06
gilankpam added a commit to gilankpam/devourer that referenced this pull request Oct 11, 2026
…ssion-scoped floor arm

- DISDATAFB=1 was written on every 8822C frame, which silently turned the
  fw retry ladder (OpenIPC#362/OpenIPC#371, RetryFallback::Default) off at 20 MHz too.
  It now covers 40/80 MHz frames only. Measured on the CU, unACKed unicast
  MCS3 limit 8:
  - 20 MHz: MCS3 x4 > MCS2 > 6M restored, 1223/1224.
  - 40 MHz: pinned at MCS3, 1208/1209.
  - 20 MHz frames with DISDATAFB=0 on a 40 MHz tune: 14163 / 0 failed,
    so the wedge keys on frame width.
  DeviceConfig.h's RetryFallback note says the 8822C's wide frames have no
  ladder at either setting.
- The floor arm is now "this session has sent a wide frame" (_wide_tx_seen):
  - Set once, published after the floor is applied, cleared by Init,
    InitWrite and Stop.
  - The floor follows the tuned width, read under _reg_mu on both sides.
  - A retune racing the first wide frame can no longer skip it, and a
    re-Init no longer inherits it.
  - A 40 MHz frame on a 20 MHz tune still takes the lock once per session.
- Dropping the floor clamps IGI back into the channel's DIG window at once,
  instead of leaving 0x26 until the next tick. Keeping it at 0 touches no
  register, so the 8822E path is unchanged.

Not exercised on air: the drop clamp and the race. ctest 78/78. HT40 CU
and EU and HT20 cells re-run, 0 failed (experiment doc).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gilankpam added a commit to gilankpam/devourer that referenced this pull request Oct 11, 2026
…ssion-scoped floor arm

- DISDATAFB=1 was written on every 8822C frame, which silently turned the
  fw retry ladder (OpenIPC#362/OpenIPC#371, RetryFallback::Default) off at 20 MHz too.
  It now covers 40/80 MHz frames only. Measured on the CU, unACKed unicast
  MCS3 limit 8:
  - 20 MHz: MCS3 x4 > MCS2 > 6M restored, 1223/1224.
  - 40 MHz: pinned at MCS3, 1208/1209.
  - 20 MHz frames with DISDATAFB=0 on a 40 MHz tune: 14163 / 0 failed,
    so the wedge keys on frame width.
  DeviceConfig.h's RetryFallback note says the 8822C's wide frames have no
  ladder at either setting.
- The floor arm is now "this session has sent a wide frame" (_wide_tx_seen):
  - Set once, published after the floor is applied, cleared by Init,
    InitWrite and Stop.
  - The floor follows the tuned width, read under _reg_mu on both sides.
  - A retune racing the first wide frame can no longer skip it, and a
    re-Init no longer inherits it.
  - A 40 MHz frame on a 20 MHz tune still takes the lock once per session.
- Dropping the floor clamps IGI back into the channel's DIG window at once,
  instead of leaving 0x26 until the next tick. Keeping it at 0 touches no
  register, so the 8822E path is unchanged.

Not exercised on air: the drop clamp and the race. ctest 78/78. HT40 CU
and EU and HT20 cells re-run, 0 failed (experiment doc).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
josephnef pushed a commit that referenced this pull request Oct 13, 2026
…or on the C die (#484)

## The problem

The RTL8812CU (8822C) could not air a single 40 MHz injected frame on
most 5 GHz channels, though the same card does HT40 under mainline
rtw88. On 144(-), 36(+) and 149(+), bulk-OUT accepted 257 frames and
then failed with rc=-7, and nothing went on air. At the abort the TX
FIFO was full (`LQ 64/0 PUB 1745/19`): the MAC never completed the
**first** 40 MHz frame. HT20 was unaffected.

There were two independent faults, and fixing either alone is not
enough.

## The change

Both parts apply only to the 8822C. The 8822E's descriptor and DIG are
byte-identical to before.

- **DISDATAFB=1 on 40/80 MHz inject descriptors**
(`fill_data_tx_desc_8822c`, new `dis_data_fb` argument).
- With USE_RATE=1, the MAC's per-frame rate-fallback lookup still
consults the firmware's per-MACID rate entry, which an injecting MACID
does not have.
- On the 8822C that wedges a 40 MHz frame outright. DISDATAFB=1 is what
the vendor driver's fixed-rate xmit path writes.
- 20 MHz frames keep DISDATAFB=0 and with it the firmware retry ladder
(#362/#371). The 8822C's wide frames have no ladder at either
`DEVOURER_TX_RETRY_FALLBACK` setting, and `DeviceConfig.h` says so.
- **A DIG floor at 40/80 MHz** (`DIG_MIN_8822C_WIDE 0x26`,
`PhydmRuntimeJaguar3::set_igi_floor`).
- On a *quiet* channel the receiver parks below IGI 0x24, in a gain
state where the secondary 20 MHz never reads idle, so the MAC holds
every 40 MHz frame.
- Busy channels self-heal, because received frames re-run the AGC. That
is why the fault hides on a busy bench channel.
- The floor is armed for the session by the device's first >= 40 MHz
frame. Init, InitWrite and `Stop()` clear the arm.
- From then on it follows the tuned width, re-applied under `_reg_mu` by
every tune (Init / InitWrite / SetMonitorChannel / FastRetune /
FastSetBandwidth). It is on at 40/80 MHz. At a narrower width it drops,
with IGI clamped straight back into the normal DIG window.
- A device that only receives at 40 MHz is never armed. That includes a
CU ground station sending 20 MHz control frames, which keeps its normal
floor.
- Arming takes `_reg_mu` once per session and never on the steady-state
send path. The flag is published only after the floor is applied, so
neither a racing retune nor a concurrent sender can skip it.

Record, including the dead ends (firmware RA_INFO in every layout,
firmware version, IQK, TX power, CCA, AGC lower bound):
`docs/experiments/8822c-ht40-tx-stall.md`.

## Measured

txdemo on the CU, with an 8812EU witness on rxdemo at the same channel
and width. 1 kB QoS-data frames, MCS3/40/LDPC, 50 s per run, no knobs.
"Aired" means CRC-ok frames the witness decoded with `bw:1`.

| channel | width | submitted | failed | aired (witness) |
|---|---|---|---|---|
| 144(-) | 40 | 20552 | 0 | 20500 MCS3 bw:1 |
| 52(+) | 40 | 20525 | 0 | 20425 MCS3 bw:1 |
| 149(+) | 40 | 20590 | 0 | 20477 MCS3 bw:1 |
| 36(+) | 40 | 20483 | 0 | 19723 MCS3 bw:1 |
| 144(-) | 20 | 20679 | 0 | 20629 MCS3 bw:0 (IGI stays 0x20) |

Before the change, the same 40 MHz runs aired nothing.

How each half was isolated:
- With only DISDATAFB=1, 36(+) worked but the silent channels
52/100/120/144 hung after 0–130 frames.
- On silent 144, pinning IGI at 0x35, 0x2c, 0x26 or 0x24 gave about
20.6k frames with 0 failures each. 0x22 gave 22 frames, then a hang.
- On silent 144, a second card sending MCS0/20 at 50 fps also cleared
the hang (20.4k frames, 0 failed).
- With the floor armed, a 20 MHz frame into a 40 MHz tune ran 9928 / 0
at IGI 0x20, and an RX-only session at 40 MHz stayed at 0x20.

The counterpart: the floor costs idle-channel RX sensitivity at 40/80
MHz on a CU that transmits wide. That cost is **estimated from the 1 dB
IGI step, not measured**: 6 dB over the DFS floor 0x20, and 4–8 dB over
the 0x1e..0x22 coverage window on a non-DFS channel. Each row is a
single 50 s run. The effect size (0 vs ~20k) is far outside
probe-to-probe noise, but the 36(+) row's lower witness count (96.3% vs
~99.5% elsewhere) was not repeated.

**Re-test on the final code** (both adapters local, near-field, silent
channels confirmed by an 8 s scan beforehand, 30 s per cell):

| cell | DUT -> witness | tune | frames | submitted | failed | witness |
IGI |
|---|---|---|---|---|---|---|---|
| 144(-) | CU -> EU | 40 | MCS3/40 | 14161 | 0 | 14109 MCS3 bw:1 | 0x26
|
| 149(+) | CU -> EU | 40 | MCS3/40 | 14146 | 0 | 14094 MCS3 bw:1 | 0x26
|
| 144 | CU -> EU | **20** | MCS3/40 | 14282 | 0 | 0 (expected) | 0x20
(unarmed) |
| 144 | CU -> EU | 20 | MCS3/20 | 14198 | 0 | 14142 MCS3 bw:0 | 0x20 |
| 144(-) | **EU** -> CU | 40 | MCS3/40 | 14455 | 0 | 14321 MCS3 bw:1 |
0x20 |

- In the last row the 8822E transmits HT40 unchanged. The CU, receiving
only at 40 MHz as the witness, also stays at 0x20.
- The send rate is gap-limited (~460 fps), so these runs do not measure
the removed per-frame lock directly. The third row checks only that the
floor stays unarmed.

**After the review (f87521b)**, same bench, silent 144:

| cell | tune | frames | submitted | failed | witness | IGI |
|---|---|---|---|---|---|---|
| CU -> EU, 144(-) | 40 | MCS3/40 | 14117 | 0 | 14067 MCS3 bw:1 | 0x26 |
| CU -> EU, 144(-) | 40 | **MCS3/20** (DISDATAFB=0) | 14163 | 0 | 14111
MCS3 bw:0 | 0x20 |
| CU -> EU, 144 | 20 | MCS3/20 | 14285 | 0 | 14232 MCS3 bw:0 | 0x20 |
| EU -> CU, 144(-) | 40 | MCS3/40 | 14456 | 0 | 14406 MCS3 bw:1 | 0x20 |

Retry ladder, `tests/retry_ladder_probe.sh` shape: unACKed unicast,
MCS3, limit 8.
- **20 MHz:** 1223/1224 frames flew MCS3 x4 > MCS2 > 6M x4, the #371
ladder.
- **40 MHz:** 1208/1209 pinned at MCS3.

Headless: ctest full 78/78, without Jaguar3 78/78, 8822C-only 69/69.

## Not covered

- **80 MHz** inherits the floor untested.
- **2.4 GHz HT40 (ch 6)** drained on the TX side, but the witness
received none of it. This is unexplained, and the change does not
address it.
- **The RX-sensitivity cost** above was not measured.
- **Not exercised on air:** the IGI clamp when the floor drops (no demo
changes width mid-session), and the retune/first-frame race, which rests
on lock ordering.
- **Range:** the re-test witness was near-field, so those runs are
pass/fail, not range numbers. The original table used a separate
ground-station 8812EU.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <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.

TX retry policy for ARQ links: limit-vs-burst sweep, per-retry rate fallback, deadline-bounded retries, FEC dimensioning from the post-ARQ residual

1 participant