Back to Ruview

ADR-022: Enhanced Windows WiFi DensePose Fidelity via RuVector Multi-BSSID Pipeline

docs/adr/ADR-022-windows-wifi-enhanced-fidelity-ruvector.md

0.7.055.1 KB
Original Source

ADR-022: Enhanced Windows WiFi DensePose Fidelity via RuVector Multi-BSSID Pipeline

FieldValue
StatusPartially Implemented
Date2026-02-28
Decidersruv
Relates toADR-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:

PropertyValue
AdapterIntel Wi-Fi 7 BE201 320MHz (NDIS 6.89)
Visible BSSIDs23
Bands2.4 GHz (channels 3,5,8,11), 5 GHz (channels 36,48)
Radio types802.11n, 802.11ac, 802.11ax
Signal range18% 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

TierMethodSubcarriersSample RateImplementation
Currentnetsh show interfaces1~2 HzSubprocess spawn
Tier 1netsh show networks mode=bssid23~2 HzParse multi-BSSID output
Tier 2Windows WLAN API (wlanapi.dll FFI)2310-20 HzNative FFI, no subprocess
Tier 3Intel Wi-Fi Sensing SDK (802.11bf)56+100 HzVendor 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 PrimitiveRole 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<BssidId, BssidEntry>,
    /// Ordered list of BSSID IDs for consistent subcarrier mapping
    /// (sorted by first-seen time for stability)
    subcarrier_map: Vec<BssidId>,
    /// 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<f64>,
    /// 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<usize>,
}

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<Vec<BssidObservation>>;
    /// Get the connected BSSID (if any)
    async fn connected(&self) -> Option<BssidObservation>;
    /// 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<Vec<BssidObservation>> {
        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<Vec<BssidObservation>> {
    // 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<BssidObservation> 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<Vec<BssidObservation>> {
        // 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<dyn WlanScanPort>,
    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<MultiApFrame>) {
        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<DriftDetector>,

    // ── 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<EnhancedSensingResult> {
        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<f32> = frame.rssi_dbm.iter()
            .map(|&r| 10.0f32.powf((r as f32 + 100.0) / 20.0))
            .collect();

        let has_change = self.predictive.should_transmit(&amplitudes);
        self.predictive.update(&amplitudes);
        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(&amplitudes)
        } 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),
            &amplitudes,
            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<NodeInfo> = 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 StageRuVector CrateSpecific TypePurpose
Predictive Gateruvector-nervous-systemPredictiveLayerRMS residual gating (threshold 0.05); suppresses scans with no body-caused changes
Attention Weightruvector-attentionScaledDotProductAttentionQuery=breathing variance profile, Key=per-BSSID variance, Value=amplitude; outputs per-BSSID importance weights
Spatial Correlatorruvector-gnnRuvectorLayer + LayerNormCorrelation graph over BSSIDs; single message-passing layer identifies co-varying BSSID clusters
Breathing Extractionruvector-nervous-systemOscillatoryRouter0.15 Hz oscillator phase-locks to strongest breathing component in weighted BSSID variance
Fingerprint Matchingruvector-nervous-systemModernHopfieldStores 4 templates: empty-room, standing, sitting, walking; exponential capacity retrieval
Signal Qualityruvector-coherenceSpectralCoherenceScoreSpectral gap of BSSID correlation graph; higher gap = cleaner body signal
Quality GateruQuFilterPipeline + AdaptiveThresholdsThree-filter PERMIT/DENY/DEFER; self-tunes thresholds with Welford/EMA
Drift MonitorruQuDriftDetectorPer-BSSID baseline tracking; 5 profiles (Stable/Linear/StepChange/Oscillating/VarianceExpansion)
Environment AdaptsonaSonaEnginePer-deployment micro-LoRA adaptation of attention weights and filter parameters
Tiered Storageruvector-temporal-tensorTieredStore8-bit hot / 5-bit warm / 3-bit cold; 23 BSSIDs × 1024 samples ≈ 24 KB hot
Pattern Searchruvector-coreVectorDB (HNSW)BSSID fingerprint nearest-neighbor lookup (<1ms for 1000 templates)

5.2 Data Volume Estimates

MetricTier 1 (netsh)Tier 2 (wlanapi)
BSSIDs per scan2323
Scan rate2 Hz20 Hz
Samples/sec46460
Bytes/sec (raw)184 B1,840 B
Ring buffer memory (1024 samples × 23 BSSIDs × 8 bytes)188 KB188 KB
PredictiveLayer savings80-95% suppressed90-99% suppressed
Net processing rate2-9 frames/sec2-46 frames/sec

6. Expected Fidelity Improvements

6.1 Quantitative Targets

MetricCurrent (1 RSSI)Tier 1 (Multi-BSSID)Tier 2 (+ Native API)
Presence detection accuracy~70% (threshold)~88% (multi-AP attention)~93% (temporal + spatial)
Presence detection latency500ms500ms50ms
Motion level classification2 levels4 levels (static/minimal/moderate/active)4 levels + direction
Room-level localizationNoneCoarse (nearest AP cluster)Moderate (3-AP trilateration)
Breathing rate detectionNoneMarginal (0.3 confidence)Fair (0.5-0.6 confidence)
Heart rate detectionNoneNoneNone (need CSI for HR)
Posture classificationNone4 classes (empty/standing/sitting/walking)4 classes + confidence
Environmental drift resilienceNoneGood (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::<f32>() / 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<dyn WlanScanPort> = {
        #[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(&registry, &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, &registry, 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

StageTier 1 LatencyTier 2 LatencyNotes
BSSID scan~200ms (netsh)~5ms (wlanapi)Process spawn vs FFI
Registry update<1ms<1msHashMap lookup
PredictiveLayer gate<10us<10us23-element RMS
Attention weighting<50us<50us23×64 matmul
GNN correlation<100us<100us23-node single layer
Motion estimation<20us<20usWeighted variance
Breathing extraction<30us<30usBandpass + peak detect
ruQu quality gate<10us<10usThree comparisons
Fingerprint match<50us<50usHopfield retrieval
Total per tick~200ms~5msScan dominates Tier 1

8.2 Memory Budget

ComponentMemory
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)

  • Create wifi-densepose-wifiscan crate with DDD module structure
  • Implement BssidId, BssidObservation, BandType, RadioType value objects
  • Implement BssidRegistry aggregate with ring buffer history and Welford stats
  • Implement NetshBssidScanner adapter (parse netsh wlan show networks mode=bssid)
  • Implement MultiApFrame, EnhancedSensingResult, WlanScanPort, error types
  • 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:

#IssueFix Applied
1GnnLayer does not exist in ruvector-gnn; actual export is RuvectorLayerRenamed all references to RuvectorLayer
2ScaledDotProductAttention has no .forward() method; actual API is .compute(query, keys, values) with &[&[f32]] slice-of-slicesUpdated Stage 2 code to use .compute() with correct parameter types
3SonaEngine::new(SonaConfig{...}) incorrect; actual constructor is SonaEngine::with_config(config) and SonaConfig uses micro_lora_lr not learning_rateFixed constructor and field names in Section 14
4apply_micro_lora returns nothing; actual signature writes into &mut [f32] output bufferFixed to use mutable output buffer pattern
5TieredStore.put(&data) missing required params; actual signature: put(key, data, tier, tick)Added BlockKey, Tier, and tick parameters
6WindowsWifiPipeline mislabeled as "Aggregate Root"; it is a domain service/orchestratorRelabeled 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<Vec<f32>>,
    /// 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<f32> {
        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

AspectRaw RSSILearned Embedding
Device normalizationNoYes (micro-LoRA adapts per adapter)
AP gain compensationNoYes (learned per BSSID)
Channel/band encodingLostPreserved as features
Temporal dynamicsNot capturedVelocity + trend features
Cross-environment transferNoEWC++ preserves learnings
Attention qualityNoisyClean (adapted features)
Fingerprint matchingRaw distanceSemantically 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<Vec<f32>> = 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
    &amplitudes,
);

// 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:

StageModuleImplementation
1predictive_gateEMA-based residual filter (replaces PredictiveLayer)
2attention_weighterSoftmax dot-product attention (replaces ScaledDotProductAttention)
3correlatorPearson correlation + BFS clustering (replaces RuvectorLayer GNN)
4motion_estimatorWeighted variance + EMA smoothing
5breathing_extractorIIR bandpass (0.1-0.5 Hz) + zero-crossing
6quality_gateThree-filter gate (structural/shift/evidence), inspired by ruQu
7fingerprint_matcherCosine similarity templates (replaces ModernHopfield)
8orchestratorWindowsWifiPipeline 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