Headline
The injector works, and 5 GHz delivers what you ask for while 2.4 GHz loses about 4%.
Delivered 25.03 Hz on 5 GHz against 25 commanded (100.1%); 23.96 Hz on 2.4 GHz (95.8%). The paired difference is 1.06 Hz with a 95% t-CI of [0.30, 1.83], and all six A/B pairs point the same way (sign test p = 0.031). The cause is mundane — ch11 is congested and the node's own 2.4 GHz management radio desenses its receiver, while ch36 was clean.
The pacing result matters more for what comes after. Inter-arrival CV measured 0.017 on 5 GHz and 0.24 on 2.4 GHz, against 2.6 for the hostapd multicast illuminator this replaced. Anything under 0.5 supports a quantitative Doppler axis; 2.6 does not. The absolute-deadline injector therefore did not merely improve the illuminator, it created a measurement axis that did not previously exist on this hardware.
Why an ABBA and not two long arms
Band is confounded with time of day, with radio warm-up, and with whatever the building is doing. A single 30-minute 2.4 arm followed by a single 30-minute 5 GHz arm cannot separate any of that from the band. Interleaving in an ABBA · BAAB · ABBA order and analysing per-pair differences removes the common-mode drift, which is what makes a 1.06 Hz effect legible at all — the paired design halves the variance (devore2012_62c8 §9.3).
Subcarrier parity is native rather than reconstructed: legacy OFDM is 52 used tones at 312.5 kHz over 20 MHz on both bands, so a legacy-rate injector gives the identical basis on each arm by construction. That is why both arms are HT20 and why the record-class census (99.8–100% single class) is a validity check rather than a formality.
What this does NOT show
-
No occupancy or counting claim of any kind. The room was empty for every arm. This measures the empty-room noise floor, not a signal.
-
It does not test band-5ghz-occupancy-discriminability. That hypothesis needs the same protocol run with a controlled occupancy sweep. The natural next step is exactly that.
-
n = 1 environment and n = 1 geometry. One office, one desk pair, one set of positions. The RF numbers (RSSI, delay spread) come from three pairs and should be read as characterisation, not as a band property.
-
Bundle width is motion-confounded here (0.97–5.87 dB) and is deliberately not used as a band metric.
-
The "delay-spread std 51 ns versus 87 ns" figure is withdrawn (2026-08-23), and it was in this card's headline result until then. Two things are wrong with it. It is not a delay spread: the phrase names the construction in
notebooks/python/real_csi_env_fingerprint.py:101, which builds its feature as mean and std over antennas of(mean_amp_db, rician_k_db, delay_spread_ns), so the quoted number is the across-antenna standard deviation of a per-antenna RMS delay spread — a second-order statistic of a first-order one. Feeding it to a coherence-bandwidth formula is a category error, and doing so produced an estimate an order of magnitude off (csi/dsp.py:coherence()measures B₅₀ directly at 0.29 MHz on ch48 and 0.40 MHz on ch3, against 2.3–3.9 MHz from the rule of thumb).Second — and this was re-reduced on 2026-08-24, so it is now measured rather than suspected — the contrast does not survive a correct estimator, and the quantity is not measurable on this hardware at all.
Six sessions, three per band, the clean ABBA members on monad02 (the clock-seam and no-records members are quarantined), ~1,180 records each, run through
csi/dsp.py:cir()whose −20 dB gate is the intended estimator:gated RMS delay spread across-chain std chain amplitude imbalance 2.4 GHz ch11 214.4 ns 7.1 ns 0.1 dB 5 GHz ch36 220.4 ns 10.4 ns 0.0 dB card claimed (51 vs 87 ns) — (15 dB on 2.4) The band difference is 6 ns on 217 ns — 2.8%, against the 71% the card reported. So the contrast is gone, which is what the literature expects: path loss is strongly frequency-dependent, delay spread is not.
Two of my own hypotheses died here too, and both were wrong in the direction that flattered the story. The 51/87 ns pair does not reproduce under any reading — not as a gated RMS (214/220) and not as an across-chain std (7/10). And the chain-imbalance explanation is unsupported: measured from the CFR these sessions are balanced to 0.0–0.1 dB across chains. The card's 15 dB figure is per-antenna RSSI from the radiotap header, a different quantity from CFR magnitude, so it cannot be what inflated an across-antenna statistic.
The deeper reason is bandwidth, and it retires the measurement rather than correcting it. At 52 tones and 312.5 kHz spacing the occupied bandwidth is 16.25 MHz, so the honest delay resolution is
1/BW= 61.5 ns (xie2015_0389). The TGn indoor models put real delay spread at 15–50 ns — 1.2× to 4.1× below what this hardware can resolve. The 512-point FFT reports a 6.25 ns bin, but bins finer than 1/BW are interpolation, and the ~217 ns both bands return is the estimator floor for this bandwidth and Hann window, not a property of the room.So: no delay-spread number from HT20 CSI on this fleet means anything about multipath. Not this one, not a corrected one. Measuring indoor delay spread needs materially more bandwidth, and on this fleet 80 MHz buys nothing because the ambient traffic is legacy 20 MHz OFDM. Do not cite this card for multipath richness, and do not cite it for "5 GHz has richer multipath" — that claim is now positively contradicted, not merely unsupported.
Follow-on at library scale, and it changes the headline (2026-08-23)
The headline above — 5 GHz delivers what you ask for, 2.4 GHz loses about 4% — was measured at 25 Hz commanded, one office, one desk pair. Two arms this week put the same injector on a nine-node library floor at 250–500 Hz, and the band gap is an order of magnitude wider than 4%.
| ch48 / 5 GHz | ch3 / 2.4 GHz | |
|---|---|---|
| when | 2026-08-23 03:05–04:05 UTC | 2026-08-23 10:12 and 10:29 UTC |
| commanded | 25 → 500 → 25 Hz | 250 Hz |
| frames injected | 555,193 (0 errors, 0 skipped) | 75,024 + 75,036 |
| delivery, median | 97.5% | 38.8% |
| delivery, range | 91.4 – 99.2% (8 nodes) | 32.5 – 97.3% (6 nodes) |
Delivery is computed as an injector-scoped rate ratio
(timesync.rows ÷ session duration ÷ commanded Hz), not a count ratio. Every ch3
session ran exactly 300 s, so the spread is not a duration artefact. Receiver-side
CSI yield was 99.2–100.0% on every node in both arms, so nothing is being
discarded after reception — the frames simply did not arrive.
The one node that breaks the pattern is the informative one
On ch3 run 1, monad04 delivered 243.2 Hz (97.3%) while five other nodes sat at 81–100 Hz (32.5–40.0%). Seventeen minutes later, in run 2, monad04 itself fell to 97.1 Hz (38.8%). Since all receivers are decoding the same transmitted frames, a receiver reaching 243 Hz proves the injector really did put ~250 Hz on the air. So this is not a transmit-rate problem and not a receive-rate cap: it is per-receiver decode success, and it moved by 2.5× on one node between two adjacent runs.
How this squares with the 25 Hz result above
Two readings fit the 25 Hz and 250 Hz points, and they differ in what they predict:
- Proportional loss. A fixed ~60% frame-loss rate on 2.4 GHz. This does not fit — it would have shown up as ~40% delivery at 25 Hz too, and that arm measured 95.8%.
- Congestion-limited absolute throughput. The channel supports roughly 100 Hz of decodable OFDM at library scale, so a 25 Hz command passes almost untouched while a 250 Hz command clamps. This fits both points, and it fits the mechanism this card already named: ch11/ch3 are congested and the node's own 2.4 GHz management radio desenses its receiver. At close range with 25 Hz neither effect bites; at library scale with 250 Hz both do.
Reading 2 is the one the data supports, and it is not yet established — two commanded rates on two different channels in two different rooms is not a characterisation. What settles it is a ch3 rate ladder: run 25/50/100/250/500 Hz in one session and read the delivered Hz. A plateau near 100 Hz confirms reading 2; a constant ~39% confirms reading 1.
Why this matters beyond the injector
Every 2.4 GHz arm commanded above ~100 Hz may be silently rate-limited, and any delivery figure computed against its commanded rate would understate the channel rather than describe it. That includes the BLE-coexistence arms, whose whole design assumes a known 2.4 GHz illumination rate. Until the ch3 ladder runs, treat a commanded 2.4 GHz rate above 100 Hz as an upper bound, not a setting.
Two channels are not two observations (2026-08-23)
The first run of lib-multichan was a rehearsal — machinery test, room not
declared empty, run id rehearsal-multichan-2026-08-23. It refuted the arm's own
design assumption on its first firing, which is the best thing a rehearsal can do.
The design. Ten nodes split into two cells 60 MHz apart, interleaved by hostname parity so the two channel groups share a geometry: monad01 illuminating ch36 for monad03/05/07/09, monad02 illuminating ch48 for monad04/06/08/10. One room, one clock, 30 minutes, 250 Hz commanded per injector.
Both injectors were clean. 450,037 and 450,010 frames against a 450,000 target, zero skipped and zero errors on each. So nothing below is a pacing artefact — the transmitters delivered what they were asked for.
Every receiver heard both injectors
Scoped by source MAC. 02:6d:6f:6e:01:01 is the ch36 injector,
02:6d:6f:6e:01:02 the ch48 one:
| receiver | tuned to | own cell's injector | the other cell's |
|---|---|---|---|
| monad04 | ch48 | 448,442 (99.6%) | 447,294 (99.4%) |
| monad03 | ch36 | 216,593 (48.1%) | 216,029 (48.0%) |
The ratio is essentially 1:1 on both nodes. Extending via timesync.rows, which
sums the two transmitters and matches the censused sum on both nodes to within
0.3%:
| node | cell | rows | per injector | delivery |
|---|---|---|---|---|
| monad04 | ch48 | 893,945 | 446,972 | 99.3% |
| monad06 | ch48 | 893,086 | 446,543 | 99.2% |
| monad08 | ch48 | 895,717 | 447,858 | 99.5% |
| monad10 | ch48 | 895,470 | 447,735 | 99.5% |
| monad05 | ch36 | 726,571 | 363,286 | 80.7% |
| monad07 | ch36 | 648,940 | 324,470 | 72.1% |
| monad09 | ch36 | 556,484 | 278,242 | 61.8% |
| monad03 | ch36 | 431,197 | 215,598 | 47.9% |
ch48 spread: 0.3 pp. ch36 spread: 32.8 pp. The quiet channel is uniform to three significant figures across four receivers; the loud one is not.
It is leakage, not a misconfiguration
Sixty megahertz outside a 20 MHz passband cannot be a passband effect, so the
first suspicion was that the radios never retuned. The ambient composition rules
that out. monad03 logged a Ubiquiti AP (54:d7:e3:2c:2c:b1) at 17.3% of its
records and 110,614 protected frames. monad04 logged no AP at all and zero
protected frames. Two receivers seeing different ambient populations are on
different passbands.
So the mechanism is cross-channel leakage at close range. A transmit spectral mask specifies relative attenuation, and in a room this small the absolute level of an injector's out-of-band emission 60 MHz out still arrives above the noise floor for 6 Mbps legacy OFDM. The receiver decodes it because there is nothing wrong with it except where it came from.
What it does to the multi-channel question
lib-multichan exists to ask whether a second receiver on a new channel beats
a second receiver on the same channel. That question presumes the two are
different observations of the room. At this range they are not: a cross-channel
pair is partly a same-channel pair, and any diversity advantage is contaminated by
frames that arrived over the leakage path rather than the intended one.
This does not retire the arm. It reorders it. Leakage is now the first quantity the arm measures, and the diversity comparison is interpretable only once it is bounded. The follow-up costs one session: run one injector instead of two, and read how much of it a receiver on the other channel still hears. That separates leakage from any genuine multi-channel effect, and it is a cleaner measurement than the two-injector configuration can give.
It also bears on band-5ghz-occupancy-discriminability from an unexpected
direction. If two channels 60 MHz apart in one room are not independent
observations, then the band contrast — 2.4 against 5 GHz, roughly 2.8 GHz apart
— is safe from this particular objection, but any future within-band channel
comparison run simultaneously is not. A within-band comparison has to be
sequential, which is what lib-chan-abba already does for ch44 against ch48 and
is now a second reason that design was right.
The unexplained half
The ch48 cell is uniform at 99.2–99.5% while the ch36 cell varies 47.9–80.7%.
Contention is the obvious candidate — ch36 carries a real AP and protected frames,
ch48 carries neither — and it is consistent with the 2026-08-17 survey putting ch44
at 258 Hz of decodable ambient against ch48's 28 Hz. But that is a hypothesis, not
a result: position is not controlled between the two cells beyond parity
interleaving, and four nodes per cell cannot separate contention from geometry.
lib-chan-abba, which puts every node on both channels sequentially, is the arm
that can.
What it fed
The injection illuminator was promoted to the 2.4 GHz arms of every subsequent campaign on the strength of this run, and 5 GHz became the preferred band for work that needs a quantitative timing axis — at the cost of ~4% delivery and the off-band management traffic on 2.4. The rate question this pilot leaves open — how far above 25 Hz the injector holds — is EXP-007 Packet-Rate Floor for Motion Sensing.
Sessions: see Hardware Capture Ledger. Predecessor: EXP-P2 Illuminator Bring-Up and First Controlled Link, which established the first controlled link using hostapd and is the CV 2.6 baseline this run improved on.