# How to Implement the Persistent Field Model for Room RF Fingerprinting in RuView

> Learn how to implement the persistent field model for room RF fingerprinting in RuView. Discover how it isolates human perturbations from background drift for accurate environmental sensing.

- Repository: [rUv/RuView](https://github.com/ruvnet/RuView)
- Tags: how-to-guide
- Published: 2026-03-08

---

**The persistent field model learns a room-wide electromagnetic baseline during empty-room calibration, extracts environmental eigen-modes via SVD, and uses this stored fingerprint at runtime to isolate human-generated perturbations from background drift.**

The **persistent field model** (also called *Field Normal Mode*) is the cornerstone of long-term RF fingerprinting in the RuView repository. It enables the system to distinguish between natural environmental fluctuations and actual human presence by maintaining a compact, reusable model of the room's electromagnetic signature. This guide walks through the complete implementation using the actual source code from `ruvnet/RuView`.

## Core Concepts of the Persistent Field Model

Understanding the model requires familiarity with four key abstractions that define how RuView processes Channel State Information (CSI).

### Baseline Vectors

The **baseline** is a per-link mean CSI amplitude vector maintained for every transmitter-receiver pair. During calibration, the system accumulates statistics using a Welford accumulator (`WelfordStats`) to compute running means without storing all frames in memory.

### Environmental Modes

**Environmental modes** are the top-K orthonormal vectors extracted via SVD that capture dominant variance in the baseline. These represent temperature, humidity, and day-night effects that cause slow drift in the RF signature. The model typically retains ≤5 modes to balance accuracy with computational overhead.

### Body Perturbation

**Body perturbation** is the residual CSI signal after subtracting the baseline and projecting out environmental modes. This residual encodes the presence and motion of people in the room, representing the actual detection signal used by downstream modules.

### Persistence Mechanism

**Persistence** ensures the model survives across process restarts. The `FieldNormalMode` struct stores a calibration timestamp and a *geometry hash* that uniquely identifies the mesh layout. This enables reuse across days while detecting when furniture rearrangements invalidate the model.

## Architectural Flow

The implementation in [`field_model.rs`](https://github.com/ruvnet/RuView/blob/main/field_model.rs) follows a seven-stage pipeline that moves from raw CSI to actionable perturbation vectors.

1. **Empty-room data collection** – During a quiet period (≥10 minutes at 20 Hz), each link streams CSI amplitudes.
2. **Online statistics** – The `WelfordStats` accumulator maintains running mean, variance, and count per link without buffering all frames.
3. **Baseline construction** – After accumulating `min_calibration_frames`, the per-link mean vectors become the stored baseline.
4. **Covariance approximation and SVD** – The system builds a diagonal covariance approximation from per-subcarrier variances across links, then selects the highest-variance subcarriers as eigen-modes using power iteration on the diagonal matrix.
5. **Mode storage** – The `FieldNormalMode` struct persists the baseline, environmental mode vectors, their energies, variance explained fraction, calibration timestamp, and mesh geometry hash.
6. **Runtime extraction** – Upon receiving new CSI, the model subtracts the baseline, projects out the stored modes, and returns a `BodyPerturbation` containing residuals, per-link energies, total energy, and environmental projection magnitude.
7. **Freshness checking** – The model expires after `baseline_expiry_s` (default 24 hours) and automatically resets, forcing recalibration when the room layout or environment has drifted significantly.

## Configuration and Setup

All parameters are controlled through `FieldModelConfig`, defined in [`rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/field_model.rs`](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/field_model.rs).

```rust
use wifi_densepose_signal::ruvsense::{
    FieldModel, FieldModelConfig, CalibrationStatus,
};

let config = FieldModelConfig {
    n_links: 6,
    n_subcarriers: 56,
    n_modes: 3,
    min_calibration_frames: 12_000, // 10 min @ 20 Hz
    baseline_expiry_s: 86_400.0,    // 24 hours
};

let mut model = FieldModel::new(config).expect("valid config");
assert_eq!(model.status(), CalibrationStatus::Uncalibrated);

```

The `n_modes` parameter typically ranges from 3 to 5. Higher values capture more environmental variance but increase computational cost during runtime projection.

## Calibration Process

Calibration involves feeding empty-room CSI frames until the statistical requirements are met, then finalizing the model.

### Feeding Calibration Frames

The `feed_calibration` method accepts observations from all links simultaneously. Each observation is a vector of subcarrier amplitudes.

```rust
// obs: Vec<Vec<f64>> where outer vec = links, inner vec = subcarrier amplitudes
for frame in calibration_frames {
    model.feed_calibration(&frame).unwrap();
}

```

The underlying `WelfordStats` accumulator updates running mean and variance without storing the full calibration buffer, enabling operation on memory-constrained devices.

### Finalizing the Model

Once `min_calibration_frames` have been processed, call `finalize_calibration` to compute the SVD and store the persistent model.

```rust
let timestamp_us = 1_642_000_000_000_000; // microseconds since epoch
let geometry_hash = 0xDEADBEEF; // hash derived from mesh layout
let mode = model.finalize_calibration(timestamp_us, geometry_hash).unwrap();

println!(
    "Calibration completed, variance explained: {:.2}%",
    mode.variance_explained * 100.0
);

```

The `geometry_hash` ensures that moving a router or changing the mesh layout invalidates the old model, preventing false detections from altered multipath profiles.

## Runtime Extraction

During operation, the model processes incoming CSI to isolate human-generated perturbations.

### Extracting Body Perturbation

The `extract_perturbation` method returns a `BodyPerturbation` struct containing the residual signal after removing baseline and environmental modes.

```rust
// new_obs: fresh CSI observation from all links
let perturb = model.extract_perturbation(&new_obs).expect("model calibrated");

// Total residual energy indicates presence of a person
if perturb.total_energy > 0.5 {
    println!(
        "Human perturbation detected! Energy = {}",
        perturb.total_energy
    );
}

```

The `total_energy` field aggregates per-link residual energies, providing a scalar detection metric robust to single-link failures.

## Freshness Checking and Model Reset

The persistent field model includes automatic expiration to handle long-term environmental drift.

### Checking Model Freshness

Use `check_freshness` to determine if the calibration is still valid based on the elapsed time since calibration.

```rust
let now_us = 1_642_864_000_000_000;
match model.check_freshness(now_us) {
    CalibrationStatus::Fresh => println!("Model still fresh"),
    CalibrationStatus::Stale => println!("Model stale – consider recalibration"),
    CalibrationStatus::Expired => {
        println!("Model expired – resetting");
        model.reset_calibration();
    }
    _ => {}
}

```

When expired, `reset_calibration` clears the baseline and returns the model to `CalibrationStatus::Uncalibrated`, forcing a new empty-room collection phase.

## Key Files and Integration Points

The persistent field model implementation spans multiple files in the RuView repository:

| File | Role |
|------|------|
| [`rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/field_model.rs`](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/field_model.rs) | Core implementation containing `FieldModel`, `FieldModelConfig`, `FieldNormalMode`, and `BodyPerturbation` structs along with calibration and extraction methods. |
| [`rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/mod.rs`](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/mod.rs) | Module entry point that re-exports the field model for use by downstream signal processing pipelines. |
| [`docs/adr/ADR-030-ruvsense-persistent-field-model.md`](https://github.com/ruvnet/RuView/blob/main/docs/adr/ADR-030-ruvsense-persistent-field-model.md) | Architectural Decision Record documenting the design rationale, tier breakdown, and high-level behavior of the persistent field model. |
| [`rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/adversarial.rs`](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/adversarial.rs) | Integration module that uses the field model to enforce physical plausibility by detecting violations of the learned baseline. |
| [`rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/longitudinal.rs`](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/longitudinal.rs) | Builds on the field model to compute longitudinal drift statistics for each detected person over time. |

These components work together to provide a robust, reusable RF fingerprint that persists across device reboots and environmental changes while maintaining sensitivity to human motion.

## Summary

- The **persistent field model** creates a reusable RF fingerprint by learning an empty-room baseline and extracting environmental eigen-modes via SVD.
- **Calibration** requires at least 10 minutes of empty-room data at 20 Hz, processed through Welford accumulators to compute per-link means and variances.
- The **FieldNormalMode** struct stores the baseline, top-K environmental modes, calibration timestamp, and geometry hash to enable persistence and stale-model detection.
- **Runtime extraction** subtracts the baseline and projects out environmental modes to yield **BodyPerturbation**, isolating human-generated signal components.
- **Freshness checking** automatically expires models after 24 hours (configurable), forcing recalibration when environmental drift exceeds the learned subspace.

## Frequently Asked Questions

### How long does calibration take for the persistent field model?

Calibration requires a minimum of 10 minutes of empty-room data collected at 20 Hz, which equals at least 12,000 frames per link. This duration ensures statistical significance for the Welford accumulator to compute stable mean and variance estimates. Longer calibration periods (20-30 minutes) improve robustness against transient environmental fluctuations.

### What happens if I move furniture or change the WiFi mesh layout?

The model stores a **geometry hash** derived from the mesh layout during calibration. If the physical arrangement of routers or furniture changes significantly, the hash mismatch will be detected, and the model will be marked as invalid. Additionally, the `baseline_expiry_s` parameter (default 86,400 seconds) ensures automatic expiration after 24 hours, preventing stale models from generating false detections.

### Can I adjust the number of environmental modes retained by the model?

Yes, the `n_modes` field in `FieldModelConfig` controls how many eigen-modes are retained after SVD. The default is 3 modes, but you can increase this to 5 for rooms with high environmental variance (e.g., areas with HVAC cycling or large windows). Higher values capture more background drift but increase computational cost during the projection step in `extract_perturbation`.

### How does the model handle memory constraints during calibration?

Instead of buffering all calibration frames, the implementation uses **Welford's online algorithm** through the `WelfordStats` accumulator. This approach updates running mean and variance statistics using only O(1) memory per link, making it feasible to run on embedded devices with limited RAM. The full calibration dataset never needs to reside in memory simultaneously.