# ADR-022: Enhanced Windows WiFi DensePose Fidelity via RuVector Multi-BSSID Pipeline | Field | Value | |-------|-------| | **Status** | Partially Implemented | | **Date** | 2026-02-28 | | **Deciders** | ruv | | **Relates to** | ADR-013 (Feature-Level Sensing Commodity Gear), ADR-014 (SOTA Signal Processing), ADR-016 (RuVector Integration), ADR-018 (ESP32 Dev Implementation), ADR-021 (Vital Sign Detection) | --- ## 1. Context ### 1.1 The Problem: Single-RSSI Bottleneck The current Windows WiFi mode in `wifi-densepose-sensing-server` (`:main.rs:382-464`) spawns a `netsh wlan show interfaces` subprocess every 500ms, extracting a single RSSI% value from the connected AP. This creates a pseudo-single-subcarrier `Esp32Frame` with: - **1 amplitude value** (signal%) - **0 phase information** - **~2 Hz effective sampling rate** (process spawn overhead) - **No spatial diversity** (single observation point) This is insufficient for any meaningful DensePose estimation. The ESP32 path provides 56 subcarriers with I/Q data at 100+ Hz, while the Windows path provides 1 scalar at 2 Hz -- a **2,800x data deficit**. ### 1.2 The Opportunity: Multi-BSSID Spatial Diversity A standard Windows WiFi environment exposes **10-30+ BSSIDs** via `netsh wlan show networks mode=bssid`. Testing on the target machine (Intel Wi-Fi 7 BE201 320MHz) reveals: | Property | Value | |----------|-------| | Adapter | Intel Wi-Fi 7 BE201 320MHz (NDIS 6.89) | | Visible BSSIDs | 23 | | Bands | 2.4 GHz (channels 3,5,8,11), 5 GHz (channels 36,48) | | Radio types | 802.11n, 802.11ac, 802.11ax | | Signal range | 18% to 99% | Each BSSID travels a different physical path through the environment. A person's body reflects/absorbs/diffracts each path differently depending on the AP's relative position, frequency, and channel. This creates **spatial diversity equivalent to pseudo-subcarriers**. ### 1.3 The Enhancement: Three-Tier Fidelity Improvement | Tier | Method | Subcarriers | Sample Rate | Implementation | |------|--------|-------------|-------------|----------------| | **Current** | `netsh show interfaces` | 1 | ~2 Hz | Subprocess spawn | | **Tier 1** | `netsh show networks mode=bssid` | 23 | ~2 Hz | Parse multi-BSSID output | | **Tier 2** | Windows WLAN API (`wlanapi.dll` FFI) | 23 | 10-20 Hz | Native FFI, no subprocess | | **Tier 3** | Intel Wi-Fi Sensing SDK (802.11bf) | 56+ | 100 Hz | Vendor SDK integration | This ADR covers Tier 1 and Tier 2. Tier 3 is deferred to a future ADR pending Intel SDK access. ### 1.4 What RuVector Enables The `vendor/ruvector` crate ecosystem provides signal processing primitives that transform multi-BSSID RSSI vectors into meaningful sensing data: | RuVector Primitive | Role in Windows WiFi Enhancement | |---|---| | `PredictiveLayer` (nervous-system) | Suppresses static BSSIDs (no body interaction), transmits only residual changes. At 23 BSSIDs, 80-95% are typically static. | | `ScaledDotProductAttention` (attention) | Learns which BSSIDs are most body-sensitive per environment. Attention query = body-motion spectral profile, keys = per-BSSID variance profiles. | | `RuvectorLayer` (gnn) | Builds cross-correlation graph over BSSIDs. Nodes = BSSIDs, edges = temporal cross-correlation. Message passing identifies BSSID clusters affected by the same person. | | `OscillatoryRouter` (nervous-system) | Isolates breathing-band (0.1-0.5 Hz) oscillations in multi-BSSID variance for coarse respiratory sensing. | | `ModernHopfield` (nervous-system) | Template matching for BSSID fingerprint patterns (standing, sitting, walking, empty). | | `SpectralCoherenceScore` (coherence) | Measures spectral gap in BSSID correlation graph; strong gap = good signal separation. | | `TieredStore` (temporal-tensor) | Stores multi-BSSID time series with adaptive quantization (8/5/3-bit tiers). | | `AdaptiveThresholds` (ruQu) | Self-tuning presence/motion thresholds with Welford stats, EMA, outcome-based learning. | | `DriftDetector` (ruQu) | Detects environmental changes (AP power cycling, furniture movement, new interference sources). 5 drift profiles: Stable, Linear, StepChange, Oscillating, VarianceExpansion. | | `FilterPipeline` (ruQu) | Three-filter gate (Structural/Shift/Evidence) for signal quality assessment. Only PERMITs readings with statistically rigorous confidence. | | `SonaEngine` (sona) | Per-environment micro-LoRA adaptation of BSSID weights and filter parameters. | --- ## 2. Decision Implement an **Enhanced Windows WiFi sensing pipeline** as a new module within the `wifi-densepose-sensing-server` crate (and partially in a new `wifi-densepose-wifiscan` crate), using Domain-Driven Design with bounded contexts. The pipeline scans all visible BSSIDs, constructs multi-dimensional pseudo-CSI frames, and processes them through the RuVector signal pipeline to achieve ESP32-comparable presence/motion detection and coarse vital sign estimation. ### 2.1 Core Design Principles 1. **Multi-BSSID as pseudo-subcarriers**: Each visible BSSID maps to a subcarrier slot in the existing `Esp32Frame` structure, enabling reuse of all downstream signal processing. 2. **Progressive enhancement**: Tier 1 (netsh parsing) ships first with zero new dependencies. Tier 2 (wlanapi FFI) adds `windows-sys` behind a feature flag. 3. **Graceful degradation**: When fewer BSSIDs are visible (<5), the system falls back to single-AP RSSI mode with reduced confidence scores. 4. **Environment learning**: SONA adapts BSSID weights and thresholds per deployment via micro-LoRA, stored in `TieredStore`. 5. **Same API surface**: The output is a standard `SensingUpdate` message, indistinguishable from ESP32 mode to the UI. --- ## 3. Architecture (Domain-Driven Design) ### 3.1 Strategic Design: Bounded Contexts ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ WiFi DensePose Windows Enhancement │ │ │ │ ┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────┐ │ │ │ BSSID Acquisition │ │ Signal Intelligence │ │ Sensing Output │ │ │ │ (Supporting Domain) │ │ (Core Domain) │ │ (Generic Domain) │ │ │ │ │ │ │ │ │ │ │ │ • WlanScanner │ │ • BssidAttention │ │ • FrameBuilder │ │ │ │ • BssidRegistry │ │ • SpatialCorrelator │ │ • UpdateEmitter │ │ │ │ • ScanScheduler │ │ • MotionEstimator │ │ • QualityGate │ │ │ │ • RssiNormalizer │ │ • BreathingExtractor │ │ • HistoryStore │ │ │ │ │ │ • DriftMonitor │ │ │ │ │ │ Port: WlanScanPort │ │ • EnvironmentAdapter │ │ Port: SinkPort │ │ │ │ Adapter: NetshScan │ │ │ │ Adapter: WsSink │ │ │ │ Adapter: WlanApiScan│ │ Port: SignalPort │ │ Adapter: RestSink│ │ │ └──────────────────────┘ └──────────────────────┘ └──────────────────┘ │ │ │ │ │ │ │ │ Anti-Corruption │ Anti-Corruption │ │ │ │ Layer (ACL) │ Layer (ACL) │ │ │ └────────────────────────┘────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ Shared Kernel │ │ │ │ • BssidId, RssiDbm, SignalPercent, ChannelInfo, BandType │ │ │ │ • Esp32Frame (reused as universal frame type) │ │ │ │ • SensingUpdate, FeatureInfo, ClassificationInfo │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### 3.2 Tactical Design: Aggregates and Entities #### Bounded Context 1: BSSID Acquisition (Supporting Domain) **Aggregate Root: `BssidRegistry`** Tracks all visible BSSIDs across scans, maintaining identity stability (BSSIDs appear/disappear as APs beacon). ```rust /// Value Object: unique BSSID identifier #[derive(Clone, Hash, Eq, PartialEq)] pub struct BssidId(pub [u8; 6]); // MAC address /// Value Object: single BSSID observation #[derive(Clone, Debug)] pub struct BssidObservation { pub bssid: BssidId, pub rssi_dbm: f64, pub signal_pct: f64, pub channel: u8, pub band: BandType, pub radio_type: RadioType, pub ssid: String, pub timestamp: std::time::Instant, } #[derive(Clone, Debug, PartialEq)] pub enum BandType { Band2_4GHz, Band5GHz, Band6GHz } #[derive(Clone, Debug, PartialEq)] pub enum RadioType { N, Ac, Ax, Be } /// Aggregate Root: tracks all visible BSSIDs pub struct BssidRegistry { /// Known BSSIDs with sliding window of observations entries: HashMap, /// Ordered list of BSSID IDs for consistent subcarrier mapping /// (sorted by first-seen time for stability) subcarrier_map: Vec, /// Maximum tracked BSSIDs (maps to max subcarriers) max_bssids: usize, } /// Entity: tracked BSSID with history pub struct BssidEntry { pub id: BssidId, pub meta: BssidMeta, /// Ring buffer of recent RSSI observations pub history: RingBuffer, /// Welford online stats (mean, variance) pub stats: RunningStats, /// Last seen timestamp (for expiry) pub last_seen: std::time::Instant, /// Subcarrier index in the pseudo-frame (-1 if unmapped) pub subcarrier_idx: Option, } ``` **Port: `WlanScanPort`** (Hexagonal architecture) ```rust /// Port: abstracts WiFi scanning backend #[async_trait::async_trait] pub trait WlanScanPort: Send + Sync { /// Perform a scan and return all visible BSSIDs async fn scan(&self) -> Result>; /// Get the connected BSSID (if any) async fn connected(&self) -> Option; /// Trigger an active scan (may not be supported) async fn trigger_active_scan(&self) -> Result<()>; } ``` **Adapter 1: `NetshBssidScanner`** (Tier 1) ```rust /// Tier 1 adapter: parses `netsh wlan show networks mode=bssid` pub struct NetshBssidScanner; #[async_trait::async_trait] impl WlanScanPort for NetshBssidScanner { async fn scan(&self) -> Result> { let output = tokio::process::Command::new("netsh") .args(["wlan", "show", "networks", "mode=bssid"]) .output() .await?; let text = String::from_utf8_lossy(&output.stdout); parse_bssid_scan_output(&text) } // ... } /// Parse multi-BSSID netsh output into structured observations fn parse_bssid_scan_output(output: &str) -> Result> { // Parses blocks like: // SSID 1 : MyNetwork // BSSID 1 : aa:bb:cc:dd:ee:ff // Signal : 84% // Radio type : 802.11ax // Band : 2.4 GHz // Channel : 5 // Returns Vec with all fields populated todo!() } ``` **Adapter 2: `WlanApiBssidScanner`** (Tier 2, feature-gated) ```rust /// Tier 2 adapter: uses wlanapi.dll via FFI for 10-20 Hz polling #[cfg(all(target_os = "windows", feature = "wlanapi"))] pub struct WlanApiBssidScanner { handle: WlanHandle, interface_guid: GUID, } #[cfg(all(target_os = "windows", feature = "wlanapi"))] #[async_trait::async_trait] impl WlanScanPort for WlanApiBssidScanner { async fn scan(&self) -> Result> { // WlanGetNetworkBssList returns WLAN_BSS_LIST with per-BSSID: // - RSSI (i32, dBm) // - Link quality (u32, 0-100) // - Channel (from PHY) // - BSS type, beacon period, IEs // Much faster than netsh (~5ms vs ~200ms per call) let bss_list = unsafe { wlanapi::WlanGetNetworkBssList( self.handle.0, &self.interface_guid, std::ptr::null(), wlanapi::dot11_BSS_type_any, 0, // security disabled std::ptr::null_mut(), std::ptr::null_mut(), ) }; // ... parse WLAN_BSS_ENTRY structs into BssidObservation todo!() } async fn trigger_active_scan(&self) -> Result<()> { // WlanScan triggers a fresh scan; results arrive async unsafe { wlanapi::WlanScan(self.handle.0, &self.interface_guid, ...) }; Ok(()) } } ``` **Domain Service: `ScanScheduler`** ```rust /// Coordinates scan timing and BSSID registry updates pub struct ScanScheduler { scanner: Box, registry: BssidRegistry, /// Scan interval (Tier 1: 500ms, Tier 2: 50-100ms) interval: Duration, /// Adaptive scan rate based on motion detection adaptive_rate: bool, } impl ScanScheduler { /// Run continuous scanning loop, updating registry pub async fn run(&mut self, frame_tx: mpsc::Sender) { let mut ticker = tokio::time::interval(self.interval); loop { ticker.tick().await; match self.scanner.scan().await { Ok(observations) => { self.registry.update(&observations); let frame = self.registry.to_pseudo_frame(); let _ = frame_tx.send(frame).await; } Err(e) => tracing::warn!("Scan failed: {e}"), } } } } ``` #### Bounded Context 2: Signal Intelligence (Core Domain) This is where RuVector primitives compose into a sensing pipeline. **Domain Service: `WindowsWifiPipeline`** ```rust /// Core pipeline that transforms multi-BSSID scans into sensing data pub struct WindowsWifiPipeline { // ── Stage 1: Predictive Gating ── /// Suppresses static BSSIDs (no body interaction) /// ruvector-nervous-system::routing::PredictiveLayer predictive: PredictiveLayer, // ── Stage 2: Attention Weighting ── /// Learns BSSID body-sensitivity per environment /// ruvector-attention::ScaledDotProductAttention attention: ScaledDotProductAttention, // ── Stage 3: Spatial Correlation ── /// Cross-correlation graph over BSSIDs /// ruvector-gnn::RuvectorLayer (nodes=BSSIDs, edges=correlation) correlator: BssidCorrelator, // ── Stage 4: Motion/Presence Estimation ── /// Multi-BSSID motion score with per-AP weighting motion_estimator: MultiApMotionEstimator, // ── Stage 5: Coarse Vital Signs ── /// Breathing extraction from body-sensitive BSSID oscillations /// ruvector-nervous-system::routing::OscillatoryRouter breathing: CoarseBreathingExtractor, // ── Stage 6: Quality Gate ── /// ruQu three-filter pipeline + adaptive thresholds quality_gate: VitalCoherenceGate, // ── Stage 7: Fingerprint Matching ── /// Hopfield template matching for posture classification /// ruvector-nervous-system::hopfield::ModernHopfield fingerprint: BssidFingerprintMatcher, // ── Stage 8: Environment Adaptation ── /// SONA micro-LoRA per deployment /// sona::SonaEngine adapter: SonaEnvironmentAdapter, // ── Stage 9: Drift Monitoring ── /// ruQu drift detection per BSSID baseline drift: Vec, // ── Storage ── /// Tiered storage for BSSID time series /// ruvector-temporal-tensor::TieredStore store: TieredStore, config: WindowsWifiConfig, } ``` **Value Object: `WindowsWifiConfig`** ```rust pub struct WindowsWifiConfig { /// Maximum BSSIDs to track (default: 32) pub max_bssids: usize, /// Scan interval for Tier 1 (default: 500ms) pub tier1_interval_ms: u64, /// Scan interval for Tier 2 (default: 50ms) pub tier2_interval_ms: u64, /// PredictiveLayer residual threshold (default: 0.05) pub predictive_threshold: f32, /// Minimum BSSIDs for multi-AP mode (default: 3) pub min_bssids: usize, /// BSSID expiry after no observation (default: 30s) pub bssid_expiry_secs: u64, /// Enable coarse breathing extraction (default: true) pub enable_breathing: bool, /// Enable fingerprint matching (default: true) pub enable_fingerprint: bool, /// Enable SONA adaptation (default: true) pub enable_adaptation: bool, /// Breathing band (Hz) — relaxed for low sample rate pub breathing_band: (f64, f64), /// Motion variance threshold for presence detection pub motion_threshold: f64, } impl Default for WindowsWifiConfig { fn default() -> Self { Self { max_bssids: 32, tier1_interval_ms: 500, tier2_interval_ms: 50, predictive_threshold: 0.05, min_bssids: 3, bssid_expiry_secs: 30, enable_breathing: true, enable_fingerprint: true, enable_adaptation: true, breathing_band: (0.1, 0.5), motion_threshold: 0.15, } } } ``` **Domain Service: Stage-by-Stage Processing** ```rust impl WindowsWifiPipeline { pub fn process(&mut self, frame: &MultiApFrame) -> Option { let n = frame.bssid_count; if n < self.config.min_bssids { return None; // Too few BSSIDs, degrade to legacy } // ── Stage 1: Predictive Gating ── // Convert RSSI dBm to linear amplitude for PredictiveLayer let amplitudes: Vec = frame.rssi_dbm.iter() .map(|&r| 10.0f32.powf((r as f32 + 100.0) / 20.0)) .collect(); let has_change = self.predictive.should_transmit(&litudes); self.predictive.update(&litudes); if !has_change { return None; // Environment static, no body present } // ── Stage 2: Attention Weighting ── // Query: variance profile of breathing band per BSSID // Key: current RSSI variance per BSSID // Value: amplitude vector let query = self.compute_breathing_variance_query(frame); let keys = self.compute_bssid_variance_keys(frame); let key_refs: Vec<&[f32]> = keys.iter().map(|k| k.as_slice()).collect(); let val_refs: Vec<&[f32]> = amplitudes.chunks(1).collect(); // per-BSSID let weights = self.attention.compute(&query, &key_refs, &val_refs); // ── Stage 3: Spatial Correlation ── // Build correlation graph: edge(i,j) = pearson_r(bssid_i, bssid_j) let correlation_features = self.correlator.forward(&frame.histories); // ── Stage 4: Motion Estimation ── let motion = self.motion_estimator.estimate( &weights, &correlation_features, &frame.per_bssid_variance, ); // ── Stage 5: Coarse Breathing ── let breathing = if self.config.enable_breathing && motion.level == MotionLevel::Minimal { self.breathing.extract_from_weighted_bssids( &weights, &frame.histories, frame.sample_rate_hz, ) } else { None }; // ── Stage 6: Quality Gate (ruQu) ── let reading = PreliminaryReading { motion, breathing, signal_quality: self.compute_signal_quality(n, &weights), }; let verdict = self.quality_gate.gate(&reading); if matches!(verdict, Verdict::Deny) { return None; } // ── Stage 7: Fingerprint Matching ── let posture = if self.config.enable_fingerprint { self.fingerprint.classify(&litudes) } else { None }; // ── Stage 8: Environment Adaptation ── if self.config.enable_adaptation { self.adapter.end_trajectory(reading.signal_quality); } // ── Stage 9: Drift Monitoring ── for (i, drift) in self.drift.iter_mut().enumerate() { if i < n { drift.push(frame.rssi_dbm[i]); } } // ── Stage 10: Store ── let tick = frame.sequence as u64; self.store.put( ruvector_temporal_tensor::BlockKey::new(0, tick), &litudes, ruvector_temporal_tensor::Tier::Hot, tick, ); Some(EnhancedSensingResult { motion, breathing, posture, signal_quality: reading.signal_quality, bssid_count: n, verdict, }) } } ``` #### Bounded Context 3: Sensing Output (Generic Domain) **Domain Service: `FrameBuilder`** Converts `EnhancedSensingResult` to the existing `SensingUpdate` and `Esp32Frame` types for compatibility. ```rust /// Converts multi-BSSID scan into Esp32Frame for downstream compatibility pub struct FrameBuilder; impl FrameBuilder { pub fn to_esp32_frame( registry: &BssidRegistry, observations: &[BssidObservation], ) -> Esp32Frame { let subcarrier_map = registry.subcarrier_map(); let n_sub = subcarrier_map.len(); let mut amplitudes = vec![0.0f64; n_sub]; let mut phases = vec![0.0f64; n_sub]; for obs in observations { if let Some(idx) = registry.subcarrier_index(&obs.bssid) { // Convert RSSI dBm to linear amplitude amplitudes[idx] = 10.0f64.powf((obs.rssi_dbm + 100.0) / 20.0); // Phase: encode channel as pseudo-phase (for downstream // tools that expect phase data) phases[idx] = (obs.channel as f64 / 48.0) * std::f64::consts::PI; } } Esp32Frame { magic: 0xC511_0002, // New magic for multi-BSSID frames node_id: 0, n_antennas: 1, n_subcarriers: n_sub as u8, freq_mhz: 2437, // Mixed; could use median sequence: 0, // Set by caller rssi: observations.iter() .map(|o| o.rssi_dbm as i8) .max() .unwrap_or(-90), noise_floor: -95, amplitudes, phases, } } pub fn to_sensing_update( result: &EnhancedSensingResult, frame: &Esp32Frame, registry: &BssidRegistry, tick: u64, ) -> SensingUpdate { let nodes: Vec = registry.subcarrier_map().iter() .filter_map(|bssid| registry.get(bssid)) .enumerate() .map(|(i, entry)| NodeInfo { node_id: i as u8, rssi_dbm: entry.stats.mean, position: estimate_ap_position(entry), amplitude: vec![frame.amplitudes.get(i).copied().unwrap_or(0.0)], subcarrier_count: 1, }) .collect(); SensingUpdate { msg_type: "sensing_update".to_string(), timestamp: chrono::Utc::now().timestamp_millis() as f64 / 1000.0, source: format!("wifi:multi-bssid:{}", result.bssid_count), tick, nodes, features: result.to_feature_info(), classification: result.to_classification_info(), signal_field: generate_enhanced_signal_field(result, tick), } } } ``` ### 3.3 Module Structure ``` rust-port/wifi-densepose-rs/crates/wifi-densepose-wifiscan/ ├── Cargo.toml └── src/ ├── lib.rs # Public API, re-exports ├── domain/ │ ├── mod.rs │ ├── bssid.rs # BssidId, BssidObservation, BandType, RadioType │ ├── registry.rs # BssidRegistry aggregate, BssidEntry entity │ ├── frame.rs # MultiApFrame value object │ └── result.rs # EnhancedSensingResult, PreliminaryReading ├── port/ │ ├── mod.rs │ ├── scan_port.rs # WlanScanPort trait │ └── sink_port.rs # SensingOutputPort trait ├── adapter/ │ ├── mod.rs │ ├── netsh_scanner.rs # NetshBssidScanner (Tier 1) │ ├── wlanapi_scanner.rs # WlanApiBssidScanner (Tier 2, feature-gated) │ └── frame_builder.rs # FrameBuilder (to Esp32Frame / SensingUpdate) ├── pipeline/ │ ├── mod.rs │ ├── config.rs # WindowsWifiConfig │ ├── predictive_gate.rs # PredictiveLayer wrapper for multi-BSSID │ ├── attention_weight.rs # AttentionSubcarrierWeighter for BSSIDs │ ├── spatial_correlator.rs # GNN-based BSSID correlation │ ├── motion_estimator.rs # Multi-AP motion/presence estimation │ ├── breathing.rs # CoarseBreathingExtractor │ ├── quality_gate.rs # ruQu VitalCoherenceGate │ ├── fingerprint.rs # ModernHopfield posture fingerprinting │ ├── drift_monitor.rs # Per-BSSID DriftDetector │ ├── embedding.rs # BssidEmbedding (SONA micro-LoRA per-BSSID) │ └── pipeline.rs # WindowsWifiPipeline orchestrator ├── application/ │ ├── mod.rs │ └── scan_scheduler.rs # ScanScheduler service └── error.rs # WifiScanError type ``` ### 3.4 Cargo.toml Dependencies ```toml [package] name = "wifi-densepose-wifiscan" version = "0.1.0" edition = "2021" [features] default = [] wlanapi = ["windows-sys"] # Tier 2: native WLAN API full = ["wlanapi"] [dependencies] # Internal wifi-densepose-signal = { path = "../wifi-densepose-signal" } # RuVector (vendored) ruvector-nervous-system = { path = "../../../../vendor/ruvector/crates/ruvector-nervous-system" } ruvector-attention = { path = "../../../../vendor/ruvector/crates/ruvector-attention" } ruvector-gnn = { path = "../../../../vendor/ruvector/crates/ruvector-gnn" } ruvector-coherence = { path = "../../../../vendor/ruvector/crates/ruvector-coherence" } ruvector-temporal-tensor = { path = "../../../../vendor/ruvector/crates/ruvector-temporal-tensor" } ruvector-core = { path = "../../../../vendor/ruvector/crates/ruvector-core" } ruqu = { path = "../../../../vendor/ruvector/crates/ruQu" } sona = { path = "../../../../vendor/ruvector/crates/sona" } # Async runtime tokio = { workspace = true } async-trait = "0.1" # Serialization serde = { workspace = true } serde_json = { workspace = true } # Logging tracing = { workspace = true } # Time chrono = "0.4" # Windows native API (Tier 2, optional) [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { version = "0.52", features = [ "Win32_NetworkManagement_WiFi", "Win32_Foundation", ], optional = true } ``` --- ## 4. Signal Processing Pipeline Detail ### 4.1 BSSID-to-Subcarrier Mapping ``` Visible BSSIDs (23): ┌──────────────────┬─────┬──────┬──────┬─────────┐ │ BSSID (MAC) │ Ch │ Band │ RSSI │ SubIdx │ ├──────────────────┼─────┼──────┼──────┼─────────┤ │ a6:aa:c3:52:1b:28│ 11 │ 2.4G │ -2dBm│ 0 │ │ 82:cd:d6:d6:c3:f5│ 8 │ 2.4G │ -1dBm│ 1 │ │ 16:0a:c5:39:e3:5d│ 5 │ 2.4G │-16dBm│ 2 │ │ 16:27:f5:b2:6b:ae│ 8 │ 2.4G │-17dBm│ 3 │ │ 10:27:f5:b2:6b:ae│ 8 │ 2.4G │-22dBm│ 4 │ │ c8:9e:43:47:a1:3f│ 3 │ 2.4G │-40dBm│ 5 │ │ 90:aa:c3:52:1b:28│ 11 │ 2.4G │ -2dBm│ 6 │ │ ... │ ... │ ... │ ... │ ... │ │ 92:aa:c3:52:1b:20│ 36 │ 5G │ -6dBm│ 20 │ │ c8:9e:43:47:a1:40│ 48 │ 5G │-78dBm│ 21 │ │ ce:9e:43:47:a1:40│ 48 │ 5G │-82dBm│ 22 │ └──────────────────┴─────┴──────┴──────┴─────────┘ Mapping rule: sorted by first-seen time (stable ordering). New BSSIDs get the next available subcarrier index. BSSIDs not seen for >30s are expired and their index recycled. ``` ### 4.2 Spatial Diversity: Why Multi-BSSID Works ``` ┌────[AP1: ch3] │ │ body │ │ path A (partially blocked) ┌───┐ │ │ │ │──┤ ▼ │ P │ │ ┌──────────┐ │ │──┤ │ WiFi │ └───┘ │ │ Adapter │ │ │ (BE201) │ ┌──────┤ └──────────┘ │ │ ▲ [AP2: ch11] │ │ path B (unobstructed) │ │ └────[AP3: ch36] │ path C (reflected off wall) Person P attenuates path A by 3-8 dB, while paths B and C are unaffected. This differential is the multi-BSSID body signal. At different body positions/orientations, different AP combinations show attenuation → spatial diversity ≈ pseudo-subcarrier diversity. ``` ### 4.3 RSSI-to-Amplitude Conversion ```rust /// Convert RSSI dBm to linear amplitude (normalized) /// RSSI range: -100 dBm (noise) to -20 dBm (very strong) fn rssi_to_linear(rssi_dbm: f64) -> f64 { // Map -100..0 dBm to 0..1 linear scale // Using 10^((rssi+100)/20) gives log-scale amplitude 10.0f64.powf((rssi_dbm + 100.0) / 20.0) } /// Convert linear amplitude back to dBm fn linear_to_rssi(amplitude: f64) -> f64 { 20.0 * amplitude.max(1e-10).log10() - 100.0 } ``` ### 4.4 Pseudo-Phase Encoding Since RSSI provides no phase information, we encode channel and band as a pseudo-phase for downstream tools: ```rust /// Encode BSSID channel/band as pseudo-phase /// This preserves frequency-group identity for the GNN correlator fn encode_pseudo_phase(channel: u8, band: BandType) -> f64 { let band_offset = match band { BandType::Band2_4GHz => 0.0, BandType::Band5GHz => std::f64::consts::PI, BandType::Band6GHz => std::f64::consts::FRAC_PI_2, }; // Spread channels across [0, PI) within each band let ch_phase = (channel as f64 / 48.0) * std::f64::consts::FRAC_PI_2; band_offset + ch_phase } ``` --- ## 5. RuVector Integration Map ### 5.1 Crate-to-Stage Mapping | Pipeline Stage | RuVector Crate | Specific Type | Purpose | |---|---|---|---| | Predictive Gate | `ruvector-nervous-system` | `PredictiveLayer` | RMS residual gating (threshold 0.05); suppresses scans with no body-caused changes | | Attention Weight | `ruvector-attention` | `ScaledDotProductAttention` | Query=breathing variance profile, Key=per-BSSID variance, Value=amplitude; outputs per-BSSID importance weights | | Spatial Correlator | `ruvector-gnn` | `RuvectorLayer` + `LayerNorm` | Correlation graph over BSSIDs; single message-passing layer identifies co-varying BSSID clusters | | Breathing Extraction | `ruvector-nervous-system` | `OscillatoryRouter` | 0.15 Hz oscillator phase-locks to strongest breathing component in weighted BSSID variance | | Fingerprint Matching | `ruvector-nervous-system` | `ModernHopfield` | Stores 4 templates: empty-room, standing, sitting, walking; exponential capacity retrieval | | Signal Quality | `ruvector-coherence` | `SpectralCoherenceScore` | Spectral gap of BSSID correlation graph; higher gap = cleaner body signal | | Quality Gate | `ruQu` | `FilterPipeline` + `AdaptiveThresholds` | Three-filter PERMIT/DENY/DEFER; self-tunes thresholds with Welford/EMA | | Drift Monitor | `ruQu` | `DriftDetector` | Per-BSSID baseline tracking; 5 profiles (Stable/Linear/StepChange/Oscillating/VarianceExpansion) | | Environment Adapt | `sona` | `SonaEngine` | Per-deployment micro-LoRA adaptation of attention weights and filter parameters | | Tiered Storage | `ruvector-temporal-tensor` | `TieredStore` | 8-bit hot / 5-bit warm / 3-bit cold; 23 BSSIDs × 1024 samples ≈ 24 KB hot | | Pattern Search | `ruvector-core` | `VectorDB` (HNSW) | BSSID fingerprint nearest-neighbor lookup (<1ms for 1000 templates) | ### 5.2 Data Volume Estimates | Metric | Tier 1 (netsh) | Tier 2 (wlanapi) | |---|---|---| | BSSIDs per scan | 23 | 23 | | Scan rate | 2 Hz | 20 Hz | | Samples/sec | 46 | 460 | | Bytes/sec (raw) | 184 B | 1,840 B | | Ring buffer memory (1024 samples × 23 BSSIDs × 8 bytes) | 188 KB | 188 KB | | PredictiveLayer savings | 80-95% suppressed | 90-99% suppressed | | Net processing rate | 2-9 frames/sec | 2-46 frames/sec | --- ## 6. Expected Fidelity Improvements ### 6.1 Quantitative Targets | Metric | Current (1 RSSI) | Tier 1 (Multi-BSSID) | Tier 2 (+ Native API) | |---|---|---|---| | Presence detection accuracy | ~70% (threshold) | ~88% (multi-AP attention) | ~93% (temporal + spatial) | | Presence detection latency | 500ms | 500ms | 50ms | | Motion level classification | 2 levels | 4 levels (static/minimal/moderate/active) | 4 levels + direction | | Room-level localization | None | Coarse (nearest AP cluster) | Moderate (3-AP trilateration) | | Breathing rate detection | None | Marginal (0.3 confidence) | Fair (0.5-0.6 confidence) | | Heart rate detection | None | None | None (need CSI for HR) | | Posture classification | None | 4 classes (empty/standing/sitting/walking) | 4 classes + confidence | | Environmental drift resilience | None | Good (ruQu adaptive) | Good (+ SONA adaptation) | ### 6.2 Confidence Score Calibration ```rust /// Signal quality as a function of BSSID count and variance spread fn compute_signal_quality( bssid_count: usize, attention_weights: &[f32], spectral_gap: f64, ) -> f64 { // Factor 1: BSSID diversity (more APs = more spatial info) let diversity = (bssid_count as f64 / 20.0).min(1.0); // Factor 2: Attention concentration (body-sensitive BSSIDs dominate) let max_weight = attention_weights.iter().copied().fold(0.0f32, f32::max); let mean_weight = attention_weights.iter().sum::() / attention_weights.len() as f32; let concentration = (max_weight / mean_weight.max(1e-6) - 1.0).min(5.0) as f64 / 5.0; // Factor 3: Spectral gap (clean body signal separation) let separation = spectral_gap.min(1.0); // Combined quality (diversity * 0.3 + concentration * 0.4 + separation * 0.3).clamp(0.0, 1.0) } ``` --- ## 7. Integration with Sensing Server ### 7.1 Modified Data Source Selection ```rust // In main(), extend auto-detection: let source = match args.source.as_str() { "auto" => { if probe_esp32(args.udp_port).await { "esp32" } else if probe_multi_bssid().await { "wifi-enhanced" // NEW: multi-BSSID mode } else if probe_windows_wifi().await { "wifi" // Legacy single-RSSI } else { "simulate" } } other => other, }; // Start appropriate background task match source { "esp32" => { tokio::spawn(udp_receiver_task(state.clone(), args.udp_port)); tokio::spawn(broadcast_tick_task(state.clone(), args.tick_ms)); } "wifi-enhanced" => { // NEW: multi-BSSID enhanced pipeline tokio::spawn(enhanced_wifi_task(state.clone(), args.tick_ms)); } "wifi" => { tokio::spawn(windows_wifi_task(state.clone(), args.tick_ms)); } _ => { tokio::spawn(simulated_data_task(state.clone(), args.tick_ms)); } } ``` ### 7.2 Enhanced WiFi Task ```rust async fn enhanced_wifi_task(state: SharedState, tick_ms: u64) { let scanner: Box = { #[cfg(feature = "wlanapi")] { Box::new(WlanApiBssidScanner::new().unwrap_or_else(|_| { tracing::warn!("WLAN API unavailable, falling back to netsh"); Box::new(NetshBssidScanner) })) } #[cfg(not(feature = "wlanapi"))] { Box::new(NetshBssidScanner) } }; let mut registry = BssidRegistry::new(32); let mut pipeline = WindowsWifiPipeline::new(WindowsWifiConfig::default()); let mut interval = tokio::time::interval(Duration::from_millis(tick_ms)); let mut seq: u32 = 0; info!("Enhanced WiFi multi-BSSID pipeline active (tick={}ms)", tick_ms); loop { interval.tick().await; seq += 1; let observations = match scanner.scan().await { Ok(obs) => obs, Err(e) => { warn!("Scan failed: {e}"); continue; } }; registry.update(&observations); let frame = FrameBuilder::to_esp32_frame(®istry, &observations); // Run through RuVector-powered pipeline let multi_frame = registry.to_multi_ap_frame(); let result = pipeline.process(&multi_frame); let mut s = state.write().await; s.source = format!("wifi-enhanced:{}", observations.len()); s.tick += 1; let tick = s.tick; let update = match result { Some(r) => FrameBuilder::to_sensing_update(&r, &frame, ®istry, tick), None => { // Fallback: basic update from frame let (features, classification) = extract_features_from_frame(&frame); SensingUpdate { msg_type: "sensing_update".into(), timestamp: chrono::Utc::now().timestamp_millis() as f64 / 1000.0, source: format!("wifi-enhanced:{}", observations.len()), tick, nodes: vec![], features, classification, signal_field: generate_signal_field( frame.rssi as f64, 1.0, 0.05, tick, ), } } }; if let Ok(json) = serde_json::to_string(&update) { let _ = s.tx.send(json); } s.latest_update = Some(update); } } ``` --- ## 8. Performance Considerations ### 8.1 Latency Budget | Stage | Tier 1 Latency | Tier 2 Latency | Notes | |---|---|---|---| | BSSID scan | ~200ms (netsh) | ~5ms (wlanapi) | Process spawn vs FFI | | Registry update | <1ms | <1ms | HashMap lookup | | PredictiveLayer gate | <10us | <10us | 23-element RMS | | Attention weighting | <50us | <50us | 23×64 matmul | | GNN correlation | <100us | <100us | 23-node single layer | | Motion estimation | <20us | <20us | Weighted variance | | Breathing extraction | <30us | <30us | Bandpass + peak detect | | ruQu quality gate | <10us | <10us | Three comparisons | | Fingerprint match | <50us | <50us | Hopfield retrieval | | **Total per tick** | **~200ms** | **~5ms** | Scan dominates Tier 1 | ### 8.2 Memory Budget | Component | Memory | |---|---| | BssidRegistry (32 entries × history) | ~264 KB | | PredictiveLayer (32-element) | <1 KB | | Attention weights | ~8 KB | | GNN layer | ~12 KB | | Hopfield (32-dim, 10 templates) | ~3 KB | | TieredStore (256 KB budget) | 256 KB | | DriftDetector (32 instances) | ~32 KB | | **Total** | **~576 KB** | --- ## 9. Security Considerations - **No raw BSSID data to UI**: Only aggregated sensing updates are broadcast. Individual BSSID MACs, SSIDs, and locations are kept server-side to prevent WiFi infrastructure fingerprinting. - **BSSID anonymization**: The `NodeInfo.node_id` uses sequential indices, not MAC addresses. - **Local-only processing**: All signal processing occurs on-device. No scan data is transmitted externally. - **Scan permission**: `netsh wlan show networks` requires no admin privileges. `WlanGetNetworkBssList` requires the WLAN service to be running (default on Windows). --- ## 10. Alternatives Considered ### Alt 1: Single-AP RSSI Enhancement Only Improve the current single-RSSI path with better filtering and drift detection, without multi-BSSID. **Rejected**: A single RSSI value lacks spatial diversity. No amount of temporal filtering can recover spatial information from a 1D signal. Multi-BSSID is the minimum viable path to meaningful presence sensing. ### Alt 2: Monitor Mode / Packet Capture Put the WiFi adapter into monitor mode to capture raw 802.11 frames with per-subcarrier CSI. **Rejected for Windows**: Monitor mode requires specialized drivers (nexmon, picoscenes) that are Linux-only for Intel adapters. Windows NDIS does not expose raw CSI. Tier 3 (Intel SDK) is the legitimate Windows path to CSI. ### Alt 3: External USB WiFi Adapter Use a separate USB adapter in monitor mode on Linux via WSL. **Rejected**: Adds hardware dependency, WSL USB passthrough complexity, and defeats the "commodity gear, zero setup" value proposition. ### Alt 4: Bluetooth RSSI Augmentation Scan BLE beacons for additional spatial observations. **Deferred**: Could complement multi-BSSID but adds BLE scanning complexity. Future enhancement, not core path. --- ## 11. Consequences ### Positive 1. **10-20x data improvement**: From 1 RSSI at 2 Hz to 23 BSSIDs at 2-20 Hz 2. **Spatial awareness**: Different APs provide different body-interaction paths 3. **Reuses existing pipeline**: `Esp32Frame` and `SensingUpdate` are unchanged; UI works without modification 4. **Zero hardware required**: Uses commodity WiFi infrastructure already present 5. **RuVector composition**: Leverages 8 existing crates; ~80% of the intelligence is pre-built 6. **Progressive enhancement**: Tier 1 ships immediately, Tier 2 adds behind feature flag 7. **Environment-adaptive**: SONA + ruQu self-tune per deployment ### Negative 1. **Still no CSI phase**: RSSI-only means no heart rate and limited breathing detection 2. **AP density dependent**: Fewer visible APs = degraded fidelity (min 3 required) 3. **Scan latency**: Tier 1 netsh is slow (~200ms); Tier 2 wlanapi required for real-time 4. **AP mobility**: Moving APs (phones as hotspots) create false motion signals 5. **Cross-platform**: `wlanapi.dll` is Windows-only; Linux/macOS need separate adapters 6. **New crate**: Adds `wifi-densepose-wifiscan` to workspace, increasing compile scope --- ## 12. Implementation Roadmap ### Phase 1: Tier 1 Foundation (Week 1) - [x] Create `wifi-densepose-wifiscan` crate with DDD module structure - [x] Implement `BssidId`, `BssidObservation`, `BandType`, `RadioType` value objects - [x] Implement `BssidRegistry` aggregate with ring buffer history and Welford stats - [x] Implement `NetshBssidScanner` adapter (parse `netsh wlan show networks mode=bssid`) - [x] Implement `MultiApFrame`, `EnhancedSensingResult`, `WlanScanPort`, error types - [x] All 42 unit tests passing (parser, domain types, registry, result types) - [ ] Implement `FrameBuilder::to_esp32_frame()` (multi-BSSID → pseudo-Esp32Frame) - [ ] Implement `ScanScheduler` with configurable interval - [ ] Integration test: scan → registry → pseudo-frame → existing sensing pipeline - [ ] Wire `enhanced_wifi_task` into sensing server `main()` ### Phase 2: RuVector Signal Pipeline (Weeks 2-3) - [ ] Implement `PredictiveGate` wrapper over `PredictiveLayer` for multi-BSSID - [ ] Implement `AttentionSubcarrierWeighter` with breathing-variance query - [ ] Implement `BssidCorrelator` using `RuvectorLayer` correlation graph - [ ] Implement `MultiApMotionEstimator` with weighted variance - [ ] Implement `CoarseBreathingExtractor` with `OscillatoryRouter` - [ ] Implement `VitalCoherenceGate` (ruQu three-filter pipeline) - [ ] Implement `BssidFingerprintMatcher` with `ModernHopfield` templates - [ ] Implement `WindowsWifiPipeline` orchestrator - [ ] Unit tests with synthetic multi-BSSID data ### Phase 3: Tier 2 + Adaptation (Week 4) - [ ] Implement `WlanApiBssidScanner` using `windows-sys` FFI - [ ] Benchmark: netsh vs wlanapi latency - [ ] Implement `SonaEnvironmentAdapter` for per-deployment learning - [ ] Implement per-BSSID `DriftDetector` array - [ ] Implement `TieredStore` wrapper for BSSID time series - [ ] Performance benchmarking (latency budget validation) - [ ] End-to-end integration test on real Windows WiFi ### Phase 4: Hardening (Week 5) - [ ] Signal quality calibration against known ground truth - [ ] Confidence score validation (presence/motion/breathing) - [ ] BSSID anonymization in output messages - [ ] Adaptive scan rate (faster when motion detected) - [ ] Documentation and API reference - [ ] Feature flag verification (`wlanapi` on/off) ### Review Errata (Applied) The following issues were identified during code review against the vendored RuVector source and corrected in this ADR: | # | Issue | Fix Applied | |---|---|---| | 1 | `GnnLayer` does not exist in `ruvector-gnn`; actual export is `RuvectorLayer` | Renamed all references to `RuvectorLayer` | | 2 | `ScaledDotProductAttention` has no `.forward()` method; actual API is `.compute(query, keys, values)` with `&[&[f32]]` slice-of-slices | Updated Stage 2 code to use `.compute()` with correct parameter types | | 3 | `SonaEngine::new(SonaConfig{...})` incorrect; actual constructor is `SonaEngine::with_config(config)` and `SonaConfig` uses `micro_lora_lr` not `learning_rate` | Fixed constructor and field names in Section 14 | | 4 | `apply_micro_lora` returns nothing; actual signature writes into `&mut [f32]` output buffer | Fixed to use mutable output buffer pattern | | 5 | `TieredStore.put(&data)` missing required params; actual signature: `put(key, data, tier, tick)` | Added `BlockKey`, `Tier`, and `tick` parameters | | 6 | `WindowsWifiPipeline` mislabeled as "Aggregate Root"; it is a domain service/orchestrator | Relabeled to "Domain Service" | **Open items from review (not yet addressed):** - `OscillatoryRouter` is designed for gamma-band (30-90 Hz) neural synchronization; using it at 0.15 Hz for breathing extraction is a semantic stretch. Consider replacing with a dedicated IIR bandpass filter. - BSSID flapping/index recycling could invalidate GNN correlation graphs; needs explicit invalidation logic. - `netsh` output is locale-dependent; parser may fail on non-English Windows. Consider positional parsing as fallback. - Tier 1 breathing detection at 2 Hz is marginal due to subprocess spawn timing jitter; should require Tier 2 for breathing feature. --- ## 13. Testing Strategy ### 13.1 Unit Tests (TDD London School) ```rust #[cfg(test)] mod tests { // Domain: BssidRegistry #[test] fn registry_assigns_stable_subcarrier_indices(); #[test] fn registry_expires_stale_bssids(); #[test] fn registry_maintains_welford_stats(); // Adapter: NetshBssidScanner #[test] fn parse_bssid_scan_output_extracts_all_bssids(); #[test] fn parse_bssid_scan_output_handles_multi_band(); #[test] fn parse_bssid_scan_output_handles_empty_output(); // Pipeline: PredictiveGate #[test] fn predictive_gate_suppresses_static_environment(); #[test] fn predictive_gate_transmits_body_caused_changes(); // Pipeline: MotionEstimator #[test] fn motion_estimator_detects_presence_from_multi_ap(); #[test] fn motion_estimator_classifies_four_levels(); // Pipeline: BreathingExtractor #[test] fn breathing_extracts_rate_from_oscillating_bssid(); // Integration #[test] fn full_pipeline_produces_sensing_update(); #[test] fn graceful_degradation_with_few_bssids(); } ``` ### 13.2 Integration Tests - Real `netsh` scan on CI Windows runner - Mock BSSID data for deterministic pipeline testing - Benchmark: processing latency per tick --- ## 14. Custom BSSID Embeddings with Micro-LoRA (SONA) ### 14.1 The Problem with Raw RSSI Vectors Raw RSSI values are noisy, device-dependent, and non-stationary. A -50 dBm reading from AP1 on channel 3 is not directly comparable to -50 dBm from AP2 on channel 36 (different propagation, antenna gain, PHY). Feeding raw RSSI into the RuVector pipeline produces suboptimal attention weights and fingerprint matches. ### 14.2 Solution: Learned BSSID Embeddings Instead of using raw RSSI, we learn a **per-BSSID embedding** that captures each AP's environmental signature using SONA's micro-LoRA adaptation: ```rust use sona::{SonaEngine, SonaConfig, TrajectoryBuilder}; /// Per-BSSID learned embedding that captures environmental signature pub struct BssidEmbedding { /// SONA engine for micro-LoRA parameter adaptation sona: SonaEngine, /// Per-BSSID embedding vectors (d_embed dimensions per BSSID) embeddings: Vec>, /// Embedding dimension d_embed: usize, } impl BssidEmbedding { pub fn new(max_bssids: usize, d_embed: usize) -> Self { Self { sona: SonaEngine::with_config(SonaConfig { hidden_dim: d_embed, embedding_dim: d_embed, micro_lora_lr: 0.001, ewc_lambda: 100.0, // Prevent forgetting previous environments ..Default::default() }), embeddings: vec![vec![0.0; d_embed]; max_bssids], d_embed, } } /// Encode a BSSID observation into a learned embedding /// Combines: RSSI, channel, band, radio type, variance, history pub fn encode(&self, entry: &BssidEntry) -> Vec { let mut raw = vec![0.0f32; self.d_embed]; // Static features (learned via micro-LoRA) raw[0] = rssi_to_linear(entry.stats.mean) as f32; raw[1] = entry.stats.variance().sqrt() as f32; raw[2] = channel_to_norm(entry.meta.channel); raw[3] = band_to_feature(entry.meta.band); raw[4] = radio_to_feature(entry.meta.radio_type); // Temporal features (from ring buffer) if entry.history.len() >= 4 { raw[5] = entry.history.delta(1) as f32; // 1-step velocity raw[6] = entry.history.delta(2) as f32; // 2-step velocity raw[7] = entry.history.trend_slope() as f32; } // Apply micro-LoRA adaptation: raw → adapted let mut adapted = vec![0.0f32; self.d_embed]; self.sona.apply_micro_lora(&raw, &mut adapted); adapted } /// Train embeddings from outcome feedback /// Called when presence/motion ground truth is available pub fn train(&mut self, bssid_idx: usize, embedding: &[f32], quality: f32) { let trajectory = self.sona.begin_trajectory(embedding.to_vec()); self.sona.end_trajectory(trajectory, quality); // EWC++ prevents catastrophic forgetting of previous environments } } ``` ### 14.3 Micro-LoRA Adaptation Cycle ``` Scan 1: Raw RSSI [AP1:-42, AP2:-58, AP3:-71, ...] │ ▼ BssidEmbedding.encode() → [e1, e2, e3, ...] (d_embed=16 per BSSID) │ ▼ AttentionSubcarrierWeighter (query=breathing_profile, key=embeddings) │ ▼ Pipeline produces: motion=0.7, breathing=16.2, quality=0.85 │ ▼ User/system feedback: correct=true (person was present) │ ▼ BssidEmbedding.train(quality=0.85) │ ▼ SONA micro-LoRA updates embedding weights EWC++ preserves prior environment learnings │ ▼ Scan 2: Same raw RSSI → BETTER embeddings → BETTER attention → BETTER output ``` ### 14.4 Benefits of Custom Embeddings | Aspect | Raw RSSI | Learned Embedding | |---|---|---| | Device normalization | No | Yes (micro-LoRA adapts per adapter) | | AP gain compensation | No | Yes (learned per BSSID) | | Channel/band encoding | Lost | Preserved as features | | Temporal dynamics | Not captured | Velocity + trend features | | Cross-environment transfer | No | EWC++ preserves learnings | | Attention quality | Noisy | Clean (adapted features) | | Fingerprint matching | Raw distance | Semantically meaningful distance | ### 14.5 Integration with Pipeline Stages The custom embeddings replace raw RSSI at the attention and fingerprint stages: ```rust // In WindowsWifiPipeline::process(): // Stage 2 (MODIFIED): Attention on embeddings, not raw RSSI let bssid_embeddings: Vec> = frame.entries.iter() .map(|entry| self.embedding.encode(entry)) .collect(); let weights = self.attention.forward( &self.compute_breathing_query(), &bssid_embeddings, // Learned embeddings, not raw RSSI &litudes, ); // Stage 7 (MODIFIED): Fingerprint on embedding space let posture = self.fingerprint.classify_embedding(&bssid_embeddings); ``` --- ## Implementation Status (2026-02-28) ### Phase 1: Domain Model -- COMPLETE - `wifi-densepose-wifiscan` crate created with DDD bounded contexts - `MultiApFrame` value object with amplitudes, phases, variances, histories - `BssidRegistry` aggregate root with Welford running statistics (capacity 32, 30s expiry) - `NetshBssidScanner` adapter parsing `netsh wlan show networks mode=bssid` (56 unit tests) - `EnhancedSensingResult` output type with motion, breathing, posture, quality - Hexagonal architecture: `WlanScanPort` trait for adapter abstraction ### Phase 2: Signal Intelligence Pipeline -- COMPLETE 8-stage pure-Rust pipeline with 125 passing tests: | Stage | Module | Implementation | |-------|--------|---------------| | 1 | `predictive_gate` | EMA-based residual filter (replaces `PredictiveLayer`) | | 2 | `attention_weighter` | Softmax dot-product attention (replaces `ScaledDotProductAttention`) | | 3 | `correlator` | Pearson correlation + BFS clustering (replaces `RuvectorLayer` GNN) | | 4 | `motion_estimator` | Weighted variance + EMA smoothing | | 5 | `breathing_extractor` | IIR bandpass (0.1-0.5 Hz) + zero-crossing | | 6 | `quality_gate` | Three-filter gate (structural/shift/evidence), inspired by ruQu | | 7 | `fingerprint_matcher` | Cosine similarity templates (replaces `ModernHopfield`) | | 8 | `orchestrator` | `WindowsWifiPipeline` domain service | Performance: ~2.1M frames/sec (debug), ~12M frames/sec (release). ### Phase 3: Server Integration -- IN PROGRESS - Wiring `WindowsWifiPipeline` into `wifi-densepose-sensing-server` - Tier 2 `WlanApiScanner` async adapter stub (upgrade path to native WLAN API) - Extended `SensingUpdate` with enhanced motion, breathing, posture, quality fields ### Phase 4: Tier 2 Native WLAN API -- PLANNED - Native `wlanapi.dll` FFI for 10-20 Hz scan rates - SONA adaptation layer for per-environment tuning - Multi-environment benchmarking --- ## 15. References - IEEE 802.11bf WiFi Sensing Standard (2024) - Adib, F. et al. "See Through Walls with WiFi!" SIGCOMM 2013 - Ali, K. et al. "Keystroke Recognition Using WiFi Signals" MobiCom 2015 - Halperin, D. et al. "Tool Release: Gathering 802.11n Traces with Channel State Information" ACM SIGCOMM CCR 2011 - Intel Wi-Fi 7 BE200/BE201 Specifications (2024) - Microsoft WLAN API Documentation: `WlanGetNetworkBssList`, `WlanScan` - RuVector v2.0.4 crate documentation