Diagnosed against real 2-node mesh data: the occupancy-blind bimodality is NOT a
subcarrier-width artifact (frames are a fixed width at any instant) nor a
transmitter/RSSI split (RSSI is identical across modes). It is a *frame-structure*
split — promiscuous capture interleaves ~50% structureless "null" frames
(near-constant amplitude vectors, spatial CoV² ≈ 0) with real CSI frames
(CoV² ~0.4) at the SAME width and RSSI. Diffing a null frame against a real one in
temporal_motion_score fabricates large fake "motion", which is what makes presence
bimodal and occupancy-blind.
Fix: a per-node adaptive gate (`admit_frame_structure`) admits a frame into the
feature pipeline only if its spatial CoV² is >= DECONFOUND_FRACTION (0.1) of the
node's rolling-P95 CoV². No brittle absolute threshold — it self-calibrates per
node and is scale-invariant. Gates BOTH the global and per-node frame_history so
the person-count fallback stays consistent too. Excluded frames are counted
(`off_structure_frames`). Enabled by default; disable with SENSING_DECONFOUND=0.
Supersedes the static-MAC `filter_mac` approach, which starved CSI on the mesh,
and the modal-width gate, which is a no-op here (width is fixed at any instant).
Tests (3): CoV² is zero for flat / large for structured / scale-invariant; the
gate excludes null frames after warmup and counts them; and gating collapses the
fabricated temporal motion of an alternating null/rich stream (>10x reduction).
Full binary suite green (127 passed).
Open: real-occupancy validation (does de-confounding make presence track people?)
needs raw-frame capture through the running server — the recordings only preserve
a processed amplitude, not raw frames, so it could not be confirmed offline yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>