Gap
The RX-chain enable mask (0x808 byte 0 — path A/B/C/D CCK+OFDM enable) is the adaptive-link's spatial-diversity lever: with the antennas decorrelated (RX motion), combining more chains buys MRC gain; when they're correlated it pays nothing. Today the only way to move it is the env var DEVOURER_RX_PATHS, either fixed at bring-up or cycled on a fixed timer (0xAA:0xBB@<ms> toggle-spec, src/jaguar1/RtlJaguarDevice.cpp:656-692, thread at :904). A fixed 300 ms toggle is a measurement convenience — it cannot respond to a measured fade/motion signal, which is what docs/adaptive-link-building-blocks.md describes the lever as being driven by.
This mirrors the motivation for the runtime TX-power API (#188): a lever the adaptive controller needs to move on a measured signal shouldn't be locked behind an env var and a demo-side timer.
Proposal
A tiny runtime setter on the concrete Jaguar1 device (the 4T4R 8814AU is where diversity actually has ≥2 chains to trade):
void SetRxPathMask(uint8_t mask); // write 0x808[7:0]; 0x11=A, 0x33=A+B, 0x77=A+B+C, 0xFF=all
uint8_t GetRxPathMask(); // read-back
Cost is one byte write — no calibration, no thread. It supersedes the demo's timer thread for a controller that has its own motion signal (e.g. beamforming-variance from the sensing path), and it makes tests/mrc_mobility.py able to switch masks in lockstep with RX motion rather than on a blind clock.
Whether it belongs on IRtlDevice or stays Jaguar1-concrete is an open call: the 2T2R Jaguar2/3 parts have only A+B to trade and no 4-chain diversity story, so a J1-only concrete method (reached by dynamic_cast, like the existing research helpers) may be the honest scope. GetRxPathMask is trivially generation-agnostic if we do put it on the interface.
Constraints to document (pre-existing, not introduced here)
- Reverts on channel switch: IQK saves/restores 0x808, and a later
SetMonitorChannel reverts the mask to the table default (RtlJaguarDevice.cpp:663). A runtime setter is a single-channel knob unless it's re-applied after each channel set — the API doc must say so (the same stickiness question the TX-power offset answered by re-folding; here the honest answer is likely "re-apply after SetMonitorChannel if you want it sticky", since there's no per-rate table to re-fold against).
- Not a gain knob: masking a chain drops its per-chain RSSI to the noise floor (validated on the 8814); it changes which chains combine, not their gain.
Validation
tests/mrc_mobility.py + docs/measuring-spatial-diversity.md: alternate a fixed chain vs all-4 in response to RX motion (not a fixed timer) over a marginal link, confirm the per-chain RSSI collapse on masked chains and the realised combining gain when they're enabled. Register-parity: 0x808 read-back == written mask; confirm a SetMonitorChannel reverts it (documents the single-channel contract).
Context
Falls out of the RX-vs-TX runtime-control research after #188 — the one RX knob that earns a runtime API (IGI/DIG and NBI/CSI-mask do not: RX gain is chip-closed, and the adaptive response to a spur is a channel hop, already runtime). See also the J3 DIG parity gap (separate issue).
Gap
The RX-chain enable mask (0x808 byte 0 — path A/B/C/D CCK+OFDM enable) is the adaptive-link's spatial-diversity lever: with the antennas decorrelated (RX motion), combining more chains buys MRC gain; when they're correlated it pays nothing. Today the only way to move it is the env var
DEVOURER_RX_PATHS, either fixed at bring-up or cycled on a fixed timer (0xAA:0xBB@<ms>toggle-spec,src/jaguar1/RtlJaguarDevice.cpp:656-692, thread at:904). A fixed 300 ms toggle is a measurement convenience — it cannot respond to a measured fade/motion signal, which is whatdocs/adaptive-link-building-blocks.mddescribes the lever as being driven by.This mirrors the motivation for the runtime TX-power API (#188): a lever the adaptive controller needs to move on a measured signal shouldn't be locked behind an env var and a demo-side timer.
Proposal
A tiny runtime setter on the concrete Jaguar1 device (the 4T4R 8814AU is where diversity actually has ≥2 chains to trade):
Cost is one byte write — no calibration, no thread. It supersedes the demo's timer thread for a controller that has its own motion signal (e.g. beamforming-variance from the sensing path), and it makes
tests/mrc_mobility.pyable to switch masks in lockstep with RX motion rather than on a blind clock.Whether it belongs on
IRtlDeviceor stays Jaguar1-concrete is an open call: the 2T2R Jaguar2/3 parts have only A+B to trade and no 4-chain diversity story, so a J1-only concrete method (reached bydynamic_cast, like the existing research helpers) may be the honest scope.GetRxPathMaskis trivially generation-agnostic if we do put it on the interface.Constraints to document (pre-existing, not introduced here)
SetMonitorChannelreverts the mask to the table default (RtlJaguarDevice.cpp:663). A runtime setter is a single-channel knob unless it's re-applied after each channel set — the API doc must say so (the same stickiness question the TX-power offset answered by re-folding; here the honest answer is likely "re-apply after SetMonitorChannel if you want it sticky", since there's no per-rate table to re-fold against).Validation
tests/mrc_mobility.py+docs/measuring-spatial-diversity.md: alternate a fixed chain vs all-4 in response to RX motion (not a fixed timer) over a marginal link, confirm the per-chain RSSI collapse on masked chains and the realised combining gain when they're enabled. Register-parity: 0x808 read-back == written mask; confirm aSetMonitorChannelreverts it (documents the single-channel contract).Context
Falls out of the RX-vs-TX runtime-control research after #188 — the one RX knob that earns a runtime API (IGI/DIG and NBI/CSI-mask do not: RX gain is chip-closed, and the adaptive response to a spur is a channel hop, already runtime). See also the J3 DIG parity gap (separate issue).