Asking the fleet what it is doing…
monad-knowledge Wi-Fi sensing lab · FIIT STU
Campaign

Calibration-as-control — does a BLE anchor rescue a drifting CSI occupancy filter (sim)?

c-cac-drift

Archive snapshot, as of 18 h ago — the run corpus is rebuilt once a day, so this page is not a live reading. The fleet panel is the live one; it refreshes every 30 s.

Sessions

No sessions yet — this campaign has a brief but no execution.

Brief

Question

Static per-window CSI counting saturates at a hard information ceiling (PUB-1), and CSI models drift over time (brunello2025). We showed (a) a birth-death temporal filter beats the per-window ceiling by 60–78% on real CSI, and (b) calibration-as-control — a periodic BLE anchor that not only resets the count but re-identifies the drifted CSI→count emission — recovers 90% of drift-induced error vs 20% for a naive reset. But that 90% needed large drift; real WiMANS cross-environment drift is too small to show it (a plain BLE reset already suffices there).

This campaign supplies the missing regime: a controlled large-drift CSI-counting stream with a co-located BLE anchor, so we can demonstrate that emission re-identification (Bu_t) beats a plain reset in the regime where it matters — the load-bearing experiment for PUB-9 (publications/csi-calibration-control/PROPOSAL.md).

Design (the sim provides ground-truth drift + BLE)

A parameter sweep over a real floor (resplan-12439-floor-0, 5 GHz — matching WiMANS) provides the per-cell CSI + BLE; a reduction then streams them through the calibration-as-control filter.

  • Occupancy axis scenario.n_agents ∈ {0,…,5} — the count classes (match WiMANS).
  • Drift axis scene_overrides.device_jitter_m ∈ {0.0, 0.5, 1.0, 1.5, 2.0} — with a fixed seed this displaces the Tx/Rx geometry monotonically, so increasing jitter = a progressive environmental drift of the CSI→count emission (the refit lever hierarchy: device-jitter is the fine drift knob).
  • occupancy_diffuse ON (IP-118) so simulated CSI carries the realistic count→variance response.
  • BLE ON (ble.enabled) → ble_links.parquet co-located with the CSI (same solve) = the anchor.
  • Seeds {0,1} for a small replication. Grid = 6 × 5 × 2 = 60 cells; each emits csi.hdf5 + ble_links.parquet.

Configs: sweeps/cac-drift.base.yaml + sweeps/cac-drift.grid.yaml. Reduction: monad_knowledge/notebooks/python/cac_drift_reduce.py.

Method (reduction)

  1. Per cell: read csi.hdf5, compute the per-window CSI amplitude-variance feature Φ (the same feature reduced for WiMANS/OPERAnet); the run's n_agents is the ground-truth count; the BLE anchor count at calibration times = n_agents (a reliable presence/count anchor).
  2. Fit the emission p(Φ|count) at drift level 0 (the calibration set at deployment time).
  3. Assemble a stream: a birth-death count path + a monotone drift schedule (drift index rising over the stream); at each step sample a window from the cell matching (count_t, drift_level_t).
  4. Run the birth-death HMM filter under four arms — no-cal (stale emission), reset-only (BLE state reset), calibration-as-control (reset + emission re-ID), oracle (current-drift emission) — and report count MAE + the accuracy-vs-recalibration-cadence curve.

Success criteria

  • The sim reproduces meaningful drift: no-cal MAE rises with drift level (a defined degradation).
  • Calibration-as-control beats reset-only once drift is large (the PUB-9 core claim, on ground-truth sim drift + a co-located BLE anchor) — with the crossover pinned to a drift magnitude.
  • The cadence curve gives a deployable BLE-anchoring interval for a target error.
  • Honest scope: sim drift (device-jitter) is a controllable proxy; the hardware campaign supplies real weeks-scale drift.

Dispatch

Corpus generation is deterministic (no LLM supervisor). Build the image once, then:

uv run monad-knowledge sim build --sim-id exp-csi-crowd
export MONAD_SIM_SIMULATORS="$(uv run monad-knowledge sim build --sim-id exp-csi-crowd \
  --emit-env 2>/dev/null | sed -n 's/^export MONAD_SIM_SIMULATORS=//p')"
uv run monad-knowledge sim sweep exp-csi-crowd \
  --grid monad_knowledge/sim/exp_csi_crowd/sweeps/cac-drift.grid.yaml --where local
# then: uv run python monad_knowledge/notebooks/python/cac_drift_reduce.py <figdata_dir>

Material/temperature drift — was INERT under IP-118, RESOLVED by IP-119

The first material-drift check (concrete vs metal walls, scene_overrides.uniform_material, IP-118 overlay ON) came back byte-identical (log-var 1.1177, |H| 8.18) — material completely inert. Cause: occupancy_diffuse's absolute power (p_d0=35, gamma=4000) produced a diffuse floor amplitude ≈5.9 while the ray-traced channel is |H_det|≈1e-4 (≈80 dB below) — the overlay swamped the physics, so the "sim CSI" was essentially the scene-independent synthetic overlay and structural drift was negligible. Only post-overlay drift (SNR AWGN) moved the emission — why the SNR-drift test worked and the material test did not.

IP-119 fixed this by reparameterising the overlay to channel-relative Rician K: diffuse_power_ℓ(N) = |H_det,ℓ|² / K(N) with K_dB(N) = k0_db − k_slope·N (dimensionless K, no pdim dilution; a bounded fraction of each link's ray-traced power, never swamping it). The diffuse now inherits the scene physics, so wall material propagates into the count→variance response. Verified on Metal Sionna runs: concrete vs metal are no longer byte-identical (rel L2 0.49, mean|H| ratio 0.84); runner log augmented … det_power=2.76e-7 diffuse_power=1.74e-7 mean_K=1.58 (diffuse/det = 1/K exactly).

Phase 2 result — re-ID ≫ reset on GROUND-TRUTH structural (wall-material) drift

50-cell material-drift sweep (sweeps/cac-drift-material.grid.yaml: n_agents 1-5 × uniform_material {concrete, glass, brick, drywall, wood} × seed {0,1}, IP-119 overlay + BLE, Metal). Drift coordinate = |eps_r(material) − eps_r(concrete)| (concrete = deployment-calibration baseline). Reduction monad_knowledge/notebooks/python/cac_drift_material_reduce.py. Unlike the analytic SNR axis (cac-drift2), this drift is re-raytraced — a ground-truth structural emission shift no reduction can synthesise, only possible because IP-119 makes the diffuse track |H_det|².

The material moves the emission consistently across counts (mean log amp-var shift vs concrete: glass +0.22, brick +0.15, drywall +0.08, wood +0.12 — stable across all 5 counts, i.e. a whole-curve recalibration target). Count MAE (calib every 40 windows): no-cal 0.559, BLE reset-only 0.524 (7% recovery — stale emission mis-reads the drifted channel between resets), calibration-as-control 0.215 (74% recovery), oracle 0.096. Re-ID beats reset by +0.309 MAE, and CaC dominates reset-only at EVERY recalibration cadence (0.14 vs 0.46 at interval 15 → 0.36 vs 0.55 at 250). figdata/cac-drift-material/ in the publication. Same qualitative result as the SNR run (re-ID ≫ reset) but now on ray-traced physics, not overlay-dominated CSI — the material/temperature-drift case the IP-118 note said needed a channel-relative overlay. Honest scope: sim structural drift (one material palette, static per run) is a controllable proxy; the hardware campaign still supplies real weeks-scale drift.

Changelog

Date Author Change
2026-07-21 Claude AI Campaign scaffolded. Sweep (occupancy × device-jitter drift × seed) on resplan-12439 5 GHz with occupancy_diffuse + BLE; reduction streams CSI+BLE through the calibration-as-control filter to demonstrate emission re-ID > reset in the large-drift regime (PUB-9 load-bearing experiment). Not yet run.
2026-07-21 Claude AI ✅ RESULT (corrected sweep): calibration-as-control DOMINATES a plain BLE reset under structural (SNR) drift. 25-cell sweep n_agents 1-5 × snr_db {30,20,14,10,7} on resplan-12439 5 GHz, occupancy_diffuse gamma=4000 (pdim-fixed), reduction on csi_impaired.hdf5 (occupancy_diffuse + SNR-drift AWGN). Count MAE (calib every 40 windows): no-cal 1.89, BLE reset-only 1.84 (barely beats no-cal — stale emission mis-reads noisy CSI between resets), calibration-as-control 1.03 (re-ID beats reset by +0.81 MAE), oracle-emission 2.06. Cadence curve: CaC dominates reset-only at EVERY interval (0.73 vs 1.77 at interval 15 → 1.75 vs 1.88 at 250). The value is re-identifying the emission, not resetting the count. figdata/cac-drift2/cac_drift.png. Scope: sim SNR-drift (one drift type); weak-ish sim count response (gamma-fixed) makes absolute counting hard, so the RELATIVE re-ID≫reset result is the demonstration.
2026-07-21 Claude AI Debug trail to the result. Executed 25 cells (Metal, ~11 s each, CSI + BLE + impairments all emit; count=0 skips CSI — empty trajectory). THREE findings from debugging the flat count response: (1) device-jitter drift is INERT for the CSI count feature (per-count means identical across drift 0→2.0 m) → drift knob must be structural (impairment_profile.snr_db or uniform_material), not placement. (2) Occupancy is constant per run, not time-varying: seek_and_occupy agents don't leave (trajectory.parquet: all N agents present every frame), so run-level n_agents IS the per-window count — the label was fine. (3) THE REAL BUG — occupancy_diffuse gamma is diluted by tensor dimension. The overlay APPLIES (runner log: augmented, diffuse_power=200) but the coherent count-signal (gamma·N total power) spreads over pdim=260 antenna·subcarrier bins → per-bin variance increment gamma·N/260, swamped by the count-independent floor p_d0=35. Count response present but tiny (Spearman +0.98, but eff-rank only 50.5→51.2 over counts 1-5; scalar flat to 3 dp). The WiMANS-fitted gamma=33 (a scalar-variance increment) does NOT transfer to a 260-dim tensor — it needs ~100× larger. IP-118 unit tests missed this (tiny pdim). FIX for the campaign: occupancy_diffuse.gamma ≈ 4000 (pdim-scaled, ~0.5 log-var range) + snr_db drift axis, then re-run + reduce. Guard-rail working: executing surfaced a real IP-118 parameterization bug.
2026-07-21 Claude AI ✅ Phase 2 RESULT (IP-119): re-ID ≫ reset on GROUND-TRUTH ray-traced structural (wall-material) drift — the material/temperature case the IP-118 note said was untestable while the overlay swamped the channel. Reparameterised occupancy_diffuse to channel-relative Rician K (`diffuse ∝