Add antenna-decorrelation measurement harness (spatial-diversity #129)#140
Conversation
Foundation tooling for the spatial-diversity roadmap (#127): measure the inter-chain envelope correlation and realised diversity gain a given antenna layout actually delivers, so downstream diversity work reports measured gain rather than the theoretical ceiling. - demo/main.cpp: opt-in DEVOURER_RX_ALLPATHS=1 emits a <devourer-rxpath> line per canonical-SA frame with all four RX chains (A,B,C,D) of rssi/snr/evm. Paths C/D are populated only on the 8814AU (4T4R); the canonical two-path <devourer-stream>/<devourer-body> format its regex consumers key on is left untouched (distinct tag). - tests/antenna_decorrelation.py: envelope-correlation matrix (Pearson on linear amplitude), SC/MRC combining gain over the best single chain at the 10%/1% outage points vs the 10*log10(N) array gain, and a verdict keyed off the worst pairwise |rho|. Hardware-independent --self-test validates the estimator and combining math against synthesised correlated Rayleigh fading. - tests/run_antenna_decorrelation.sh: build + capture + analyse runner with an exact-comm cleanup trap and a uv venv; optionally starts the beacon TX. - CLAUDE.md: document DEVOURER_RX_ALLPATHS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardening surfaced by running the #129 harness on two identical RTL8814AU dongles (CF-938AC vs CF-960AC), which enumerate with the same VID:PID:serial. - demo/main.cpp: DEVOURER_USB_BUS (+ optional DEVOURER_USB_PORT dotted port path) selects a device by USB topology when first-match VID:PID can't disambiguate two identical dongles. Falls back to the normal open loop when unset. - tests/compare_8814_decorrelation.sh: drive one controlled beacon TX and measure every plugged RTL8814AU as RX in turn (auto-detected by bus/port), so the only variable between runs is the adapter's antenna front-end. - tests/antenna_decorrelation.py: guard the estimate — a static (non-fading) channel (max per-chain std < MIN_FADING_STD) is reported INCONCLUSIVE rather than emitting a meaningless correlation, and a near-constant chain (std < RAIL_STD, e.g. the 8814 path-C RSSI railing at a fixed value) is flagged as a railed/no-signal reading. --self-test covers both. - tests/run_antenna_decorrelation.sh: install numpy directly instead of the editable project (the flat tests/ layout breaks setuptools autodiscovery). - CLAUDE.md: document DEVOURER_USB_BUS/PORT. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardware validation on two RTL8814AU donglesRan the harness on both plugged 8814s (CF-938AC, 2 external dipoles; CF-960AC, What worked end-to-end: the selector targeted the two identical devices by Two findings that hardened the tool:
Per-chain level fingerprint still distinguishes the adapters (means, static Next: repeat with a fading stimulus (walk the beacon / rotate the adapter / Pushed as |
Running the two-8814 comparison on hardware exposed two runner bugs: - The beacon TX was started with a fixed short sleep, so a capture could open its RX window before the TX was injecting and record zero frames. Both runners now poll the (line-buffered, via stdbuf) TX log for a confirmed inject before capturing, and abort with a clear message if the beacon never comes up — instead of silently producing an empty capture. - run_antenna_decorrelation.sh gains USB_BUS/USB_PORT passthrough (to pin one of several identical adapters via the demo's topology selector) and a ROTATE=1 stimulus mode with a start countdown, since a static bench channel is unmeasurable — the operator rotates the adapter to sweep each antenna's pattern against the fixed TX. Both runners clean up their beacon log on exit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First valid decorrelation measurement: two 8814s compared (rotation stimulus)Since a static bench beacon can't excite fading, the RX adapter was rotated / Both dongles are the same RTL8814AU silicon, differing only in antenna
Interpretation. The CF-938AC drives 4 RF chains from only 2 physical Incidental finding. The 8814 path-C RSSI that railed at a constant 82.00 Caveat / next. Rotation-on-a-bench is angular/pattern decorrelation under Tooling: |
- docs/effective-branches-rotation.md: a lab procedure for finding an adapter's *effective* diversity branches (not its chain count) by rotating the receiver against a controlled beacon. Covers why chain count != branch count, why a static bench can't measure it, the method, how to read the correlation matrix / effective-branch count / combining gain, the pitfalls that bit this measurement (gentle rotation lies, AGC-railed chains, beacon-not-injecting, identical-device selection, rotation-vs-multipath), and the worked two-dongle example. - tests/antenna_decorrelation.py: add the effective-branch metric N_eff (the participation ratio of the correlation matrix) to the report and self-test. - README: the RTL8814AU row now names both tested SKUs (CF-938AC 2 ext antennas, CF-960AC 4 internal) and their measured N_eff (~2.6 vs ~3.8), mirroring how the other chip rows cite a tested adapter, and links the new doc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Foundation tooling for the spatial-diversity roadmap (#127) — the effort to
exploit the RF chains a single-stream video link leaves idle (1 on a 2T2R part,
up to 3 on a 4T4R 8814) as diversity/robustness, the third axis alongside
frequency hopping (
docs/frequency-hopping.md) and the fused FEC(
docs/fused-fec.md).Closes the measurement half of #129. Every downstream diversity claim (RX MRC
#130,
ANTSELprobe #131, STBC #132, spatial×FEC #133) rests on the chains'antennas being decorrelated — on a small airframe they may not be. This adds
the harness that measures the decorrelation a given antenna layout actually
delivers, so gains are reported as measured rather than theoretical.
Changes
demo/main.cpp— opt-inDEVOURER_RX_ALLPATHS=1emits a<devourer-rxpath>line per canonical-SA frame carrying all four RX chains(A,B,C,D) of
rssi/snr/evm. The library already populates paths C/D on the8814AU; the demo previously printed only A,B. On a distinct tag so the
canonical
<devourer-stream>/<devourer-body>two-path format (and its regexconsumers) is untouched. Paths C/D read 0 on 2T2R parts.
tests/antenna_decorrelation.py— parses the per-chain metrics and reportsper-chain level stats, the pairwise envelope correlation matrix (Pearson on
linear amplitude — the diversity-relevant correlation, not on dB), and a
combining analysis: selection- and maximal-ratio-combining gain over the
best single chain at the 10 % and 1 % outage points vs the theoretical
10·log₁₀(N) array gain. Verdict is keyed off the worst pairwise |ρ| (ρ<0.7 =
"diversity still useful"). A hardware-independent
--self-testvalidates theestimator and combining math against synthesised correlated Rayleigh fading.
tests/run_antenna_decorrelation.sh— build + capture + analyse runner(exact-comm cleanup trap, uv venv, sudo for the USB claim); optionally starts
the beacon TX (
TX_PID).CLAUDE.md— documentDEVOURER_RX_ALLPATHS.Why measure against the canonical beacon
The library only surfaces per-chain metrics for the canonical SA
57:42:75:05:d6:00, so the harness measures a steady, known source (the standardtwo-adapter bench setup) — the right controlled condition for a correlation
estimate, rather than noisy ambient traffic.
Testing
uv run python tests/antenna_decorrelation.py --self-test— green. Recovers aknown envelope correlation to ±0.05 (synthesising with field-corr = √ρ_env,
since ρ_env = |ρ_field|² for Rayleigh), confirms independent 4-chain MRC mean
gain ≈ 10·log₁₀(4), and that correlated chains yield less low-outage gain than
independent ones.
WiFiDriverDemobuilds with the new emission.spacings/polarisations is the field-validation step, tracked in Antenna-decorrelation measurement harness #129/RX-side in-chip MRC on an 8814 ground station #130.
🤖 Generated with Claude Code