docs/ddd/ruvsense-domain-model.md
RuvSense is the multistatic WiFi sensing subsystem of RuView. It turns raw radio signals from multiple ESP32 sensors into tracked human poses, vital signs, and spatial awareness — all without cameras.
This document defines the system using Domain-Driven Design (DDD): bounded contexts that own their data and rules, aggregate roots that enforce invariants, value objects that carry meaning, and domain events that connect everything. The goal is to make the system's structure match the physics it models — so that anyone reading the code (or an AI agent modifying it) understands why each piece exists, not just what it does.
Bounded Contexts:
| # | Context | Responsibility | Key ADRs | Code |
|---|---|---|---|---|
| 1 | Multistatic Sensing | Collect and fuse CSI from multiple nodes and channels | ADR-029 | signal/src/ruvsense/{multiband,phase_align,multistatic}.rs |
| 2 | Coherence | Monitor signal quality, gate bad data | ADR-029 | signal/src/ruvsense/{coherence,coherence_gate}.rs |
| 3 | Pose Tracking | Track people as persistent skeletons with re-ID | ADR-024, ADR-037 | signal/src/ruvsense/pose_tracker.rs |
| 4 | Field Model | Learn room baselines, extract body perturbations | ADR-030 | signal/src/ruvsense/{field_model,tomography}.rs |
| 5 | Longitudinal Monitoring | Track health trends over days/weeks | ADR-030 | signal/src/ruvsense/longitudinal.rs |
| 6 | Spatial Identity | Cross-room tracking via environment fingerprints | ADR-030 | signal/src/ruvsense/cross_room.rs |
| 7 | Edge Intelligence | On-device sensing (no server needed) | ADR-039, ADR-040 | firmware/esp32-csi-node/main/edge_processing.c |
All code paths shown are relative to rust-port/wifi-densepose-rs/crates/wifi-densepose- unless otherwise noted.
| Term | Definition |
|---|---|
| Sensing Cycle | One complete TDMA round (all nodes TX once): ~35ms at 28.5 Hz (measured) |
| Link | A single TX-RX pair; with N nodes there are N×(N-1) directed links |
| Multi-Band Frame | Fused CSI from one node hopping across multiple channels in one dwell cycle |
| Fused Sensing Frame | Aggregated observation from all nodes at one sensing cycle, ready for inference |
| Coherence Score | 0.0-1.0 metric quantifying consistency of current CSI with reference template |
| Coherence Gate | Decision rule that accepts, inflates noise, rejects, or triggers recalibration |
| Pose Track | A temporally persistent per-person 17-keypoint trajectory with Kalman state |
| Track Lifecycle | State machine: Tentative → Active → Lost → Terminated |
| Re-ID Embedding | 128-dim AETHER contrastive vector encoding body identity |
| Edge Tier | Processing level on the ESP32: 0 = raw passthrough, 1 = signal cleanup, 2 = vitals, 3 = WASM modules |
| WASM Module | A small program compiled to WebAssembly that runs on the ESP32 for custom on-device sensing |
| Node | An ESP32-S3 device acting as both TX and RX in the multistatic mesh |
| Aggregator | Central device (ESP32/RPi/x86) that collects CSI from all nodes and runs fusion |
| Sensing Schedule | TDMA slot assignment: which node transmits when |
| Channel Hop | Switching the ESP32 radio to a different WiFi channel for multi-band sensing |
| Person Cluster | A subset of links whose CSI variations are correlated (attributed to one person) |
Responsibility: Collect, normalize, and fuse CSI from multiple ESP32 nodes across multiple channels into a single coherent sensing frame per cycle.
┌──────────────────────────────────────────────────────────┐
│ Multistatic Sensing Context │
├──────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Link Buffer │ │ Multi-Band │ │
│ │ Collector │ │ Fuser │ │
│ │ (per-link │ │ (per-node │ │
│ │ ring buf) │ │ channel │ │
│ └───────┬───────┘ │ fusion) │ │
│ │ └───────┬───────┘ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Phase Aligner │ │
│ │ (cross-chan │ │
│ │ correction) │ │
│ └────────┬───────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Multistatic │ │
│ │ Fuser │──▶ FusedSensingFrame │
│ │ (cross-node │ │
│ │ attention) │ │
│ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
Aggregates:
FusedSensingFrame (Aggregate Root)Value Objects:
MultiBandCsiFrameLinkGeometry (tx_pos, rx_pos, distance, angle)SensingScheduleChannelHopConfigDomain Services:
PhaseAlignmentService — Corrects LO-induced phase rotation between channelsMultiBandFusionService — Merges per-channel CSI into wideband virtual frameMultistaticFusionService — Attention-based fusion of N nodes into one frameRuVector Integration:
ruvector-solver → Phase alignment (NeumannSolver)ruvector-attention → Cross-channel feature weightingruvector-attn-mincut → Cross-node spectrogram attention gatingruvector-temporal-tensor → Per-link compressed ring buffersResponsibility: Monitor temporal consistency of CSI observations and gate downstream updates to reject drift, transient interference, and environmental changes.
┌──────────────────────────────────────────────────────────┐
│ Coherence Context │
├──────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Reference │ │ Coherence │ │
│ │ Template │ │ Calculator │ │
│ │ (EMA of │ │ (z-score per │ │
│ │ static CSI) │ │ subcarrier) │ │
│ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Static/Dynamic│ │
│ │ Decomposer │ │
│ │ (separate env │ │
│ │ vs. body) │ │
│ └────────┬───────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Gate Policy │──▶ GateDecision │
│ │ (Accept / │ (Accept / PredictOnly / │
│ │ Reject / │ Reject / Recalibrate) │
│ │ Recalibrate) │ │
│ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
Aggregates:
CoherenceState (Aggregate Root) — Maintains reference template and gate stateValue Objects:
CoherenceScore (0.0-1.0)GateDecision (Accept / PredictOnly / Reject / Recalibrate)ReferenceTemplate (EMA of static-period CSI)DriftProfile (Stable / Linear / StepChange)Domain Services:
CoherenceCalculatorService — Computes per-subcarrier z-score coherenceStaticDynamicDecomposerService — Separates environmental drift from body motionGatePolicyService — Applies threshold-based gating rulesRuVector Integration:
ruvector-solver → Coherence matrix decomposition (static vs. dynamic)ruvector-attn-mincut → Gate which subcarriers contribute to template updateResponsibility: Track multiple people as persistent 17-keypoint skeletons across time, with Kalman-smoothed trajectories, lifecycle management, and identity preservation via re-ID.
┌──────────────────────────────────────────────────────────┐
│ Pose Tracking Context │
├──────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Person │ │ Detection │ │
│ │ Separator │ │ -to-Track │ │
│ │ (min-cut on │ │ Assigner │ │
│ │ link corr) │ │ (Hungarian+ │ │
│ └───────┬───────┘ │ embedding) │ │
│ │ └───────┬───────┘ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Kalman Filter │ │
│ │ (17-keypoint │ │
│ │ 6D state ×17)│ │
│ └────────┬───────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Lifecycle │ │
│ │ Manager │──▶ TrackedPose │
│ │ (Tentative → │ │
│ │ Active → │ │
│ │ Lost) │ │
│ └────────┬───────┘ │
│ │ │
│ ┌────────▼───────┐ │
│ │ Embedding │ │
│ │ Identifier │ │
│ │ (AETHER re-ID)│ │
│ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
Aggregates:
PoseTrack (Aggregate Root)Entities:
KeypointState — Per-keypoint Kalman state (x,y,z,vx,vy,vz) with covarianceValue Objects:
TrackedPose — Immutable snapshot: 17 keypoints + confidence + track_id + lifecyclePersonCluster — Subset of links attributed to one personAssignmentCost — Combined Mahalanobis + embedding distanceTrackLifecycleState (Tentative / Active / Lost / Terminated)Domain Services:
PersonSeparationService — Min-cut partitioning of cross-link correlation graphTrackAssignmentService — Bipartite matching of detections to existing tracksKalmanPredictionService — Predict step at 28 Hz (decoupled from measurement rate)KalmanUpdateService — Gated measurement update (subject to coherence gate)EmbeddingIdentifierService — AETHER cosine similarity for re-IDRuVector Integration:
ruvector-mincut → Person separation (DynamicMinCut on correlation graph)ruvector-mincut → Detection-to-track assignment (DynamicPersonMatcher)ruvector-attention → Embedding similarity via ScaledDotProductAttentionpub struct FusedSensingFrame {
/// Timestamp of this sensing cycle
pub timestamp_us: u64,
/// Fused multi-band spectrogram from all nodes
/// Shape: [n_velocity_bins x n_time_frames]
pub fused_bvp: Vec<f32>,
pub n_velocity_bins: usize,
pub n_time_frames: usize,
/// Per-node multi-band frames (preserved for geometry)
pub node_frames: Vec<MultiBandCsiFrame>,
/// Node positions (from deployment config)
pub node_positions: Vec<[f32; 3]>,
/// Number of active nodes contributing
pub active_nodes: usize,
/// Cross-node coherence (higher = more agreement)
pub cross_node_coherence: f32,
}
pub struct PoseTrack {
/// Unique track identifier
pub id: TrackId,
/// Per-keypoint Kalman state
pub keypoints: [KeypointState; 17],
/// Track lifecycle state
pub lifecycle: TrackLifecycleState,
/// Running-average AETHER embedding for re-ID
pub embedding: Vec<f32>, // [128]
/// Frames since creation
pub age: u64,
/// Frames since last successful measurement update
pub time_since_update: u64,
/// Creation timestamp
pub created_at: u64,
/// Last update timestamp
pub updated_at: u64,
}
pub struct KeypointState {
/// State vector [x, y, z, vx, vy, vz]
pub state: [f32; 6],
/// 6x6 covariance matrix (upper triangle, row-major)
pub covariance: [f32; 21],
/// Confidence (0.0-1.0) from DensePose model
pub confidence: f32,
}
pub struct CoherenceState {
/// Per-subcarrier reference amplitude (EMA)
pub reference: Vec<f32>,
/// Per-subcarrier variance over recent window
pub variance: Vec<f32>,
/// EMA decay rate for reference update
pub decay: f32,
/// Current coherence score
pub score: f32,
/// Frames since last accepted update
pub stale_count: u64,
/// Current drift profile classification
pub drift_profile: DriftProfile,
}
pub enum SensingEvent {
/// New fused sensing frame available
FrameFused {
timestamp_us: u64,
active_nodes: usize,
cross_node_coherence: f32,
},
/// Node joined or left the mesh
MeshTopologyChanged {
node_id: u8,
change: TopologyChange, // Joined / Left / Degraded
active_nodes: usize,
},
/// Channel hop completed on a node
ChannelHopCompleted {
node_id: u8,
from_channel: u8,
to_channel: u8,
gap_us: u32,
},
}
pub enum CoherenceEvent {
/// Coherence dropped below accept threshold
CoherenceLost {
score: f32,
threshold: f32,
timestamp_us: u64,
},
/// Coherence recovered above accept threshold
CoherenceRestored {
score: f32,
stale_duration_ms: u64,
timestamp_us: u64,
},
/// Recalibration triggered (>10s low coherence)
RecalibrationTriggered {
stale_duration_ms: u64,
timestamp_us: u64,
},
/// Recalibration completed via SONA TTT
RecalibrationCompleted {
adaptation_loss: f32,
timestamp_us: u64,
},
/// Environmental drift detected
DriftDetected {
drift_type: DriftProfile,
magnitude: f32,
timestamp_us: u64,
},
}
pub enum TrackingEvent {
/// New person detected (track born)
PersonDetected {
track_id: TrackId,
position: [f32; 3], // centroid
confidence: f32,
timestamp_us: u64,
},
/// Person pose updated
PoseUpdated {
track_id: TrackId,
keypoints: [[f32; 4]; 17], // [x, y, z, conf] per keypoint
jitter_mm: f32, // RMS jitter at torso
timestamp_us: u64,
},
/// Person lost (signal dropout)
PersonLost {
track_id: TrackId,
last_position: [f32; 3],
last_embedding: Vec<f32>,
timestamp_us: u64,
},
/// Person re-identified after loss
PersonReidentified {
track_id: TrackId,
previous_track_id: TrackId,
similarity: f32,
gap_duration_ms: u64,
timestamp_us: u64,
},
/// Track terminated (exceeded max lost duration)
TrackTerminated {
track_id: TrackId,
reason: TerminationReason,
total_duration_ms: u64,
timestamp_us: u64,
},
}
pub enum TerminationReason {
/// Exceeded max_lost_frames without re-acquisition
SignalTimeout,
/// Confidence below minimum for too long
LowConfidence,
/// Determined to be false positive
FalsePositive,
/// System shutdown
SystemShutdown,
}
┌──────────────────────────────────────────────────────────────────┐
│ RuvSense System │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ FusedFrame ┌──────────────────┐ │
│ │ Multistatic │──────────────▶│ Pose Tracking │ │
│ │ Sensing │ │ Context │ │
│ │ Context │ │ │ │
│ └────────┬─────────┘ └────────┬───────────┘ │
│ │ │ │
│ │ Publishes │ Publishes │
│ │ SensingEvent │ TrackingEvent │
│ ▼ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Event Bus (Domain Events) │ │
│ └────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌───────────▼───────────┐ │
│ │ Coherence Context │ │
│ │ (subscribes to │ │
│ │ SensingEvent; │ │
│ │ publishes │ │
│ │ CoherenceEvent; │ │
│ │ gates Tracking │ │
│ │ updates) │ │
│ └───────────────────────┘ │
│ │
├──────────────────────────────────────────────────────────────────┤
│ UPSTREAM (Conformist) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │wifi-densepose│ │wifi-densepose│ │wifi-densepose│ │
│ │ -hardware │ │ -nn │ │ -signal │ │
│ │ (CsiFrame │ │ (DensePose │ │ (SOTA algs │ │
│ │ parser) │ │ model) │ │ per link) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
├──────────────────────────────────────────────────────────────────┤
│ SIBLING (Partnership) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ AETHER │ │ MERIDIAN │ │ MAT │ │
│ │ (ADR-024) │ │ (ADR-027) │ │ (ADR-001) │ │
│ │ embeddings │ │ geometry │ │ triage │ │
│ │ for re-ID │ │ encoding │ │ lifecycle │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Relationship Types:
/// Adapts raw ESP32 CsiFrame to RuvSense MultiBandCsiFrame
pub struct MultiBandAdapter {
/// Group frames by (node_id, channel) within time window
window_ms: u32,
/// Hardware normalizer (from MERIDIAN, ADR-027)
normalizer: HardwareNormalizer,
}
impl MultiBandAdapter {
/// Collect raw CsiFrames from one TDMA cycle and produce
/// one MultiBandCsiFrame per node.
pub fn adapt_cycle(
&self,
raw_frames: &[CsiFrame],
) -> Vec<MultiBandCsiFrame>;
}
/// Adapts DensePose model output to tracking-compatible detections
pub struct PoseDetectionAdapter;
impl PoseDetectionAdapter {
/// Convert model output (heatmaps + offsets) to detected poses
/// with keypoint positions and AETHER embeddings.
pub fn adapt(
&self,
model_output: &ModelOutput,
fused_frame: &FusedSensingFrame,
) -> Vec<PoseDetection>;
}
pub struct PoseDetection {
pub keypoints: [[f32; 4]; 17], // [x, y, z, confidence]
pub embedding: Vec<f32>, // [128] AETHER embedding
pub person_cluster: PersonCluster,
}
/// Adapts RuvSense TrackedPose to MAT Survivor entity
/// for disaster response scenarios.
pub struct SurvivorAdapter;
impl SurvivorAdapter {
/// Convert a RuvSense TrackedPose to a MAT Survivor
/// with vital signs extracted from small-motion analysis.
pub fn to_survivor(
&self,
track: &PoseTrack,
vital_signs: Option<&VitalSignsReading>,
) -> Survivor;
}
/// Persists and retrieves pose tracks
pub trait PoseTrackRepository {
fn save(&self, track: &PoseTrack);
fn find_by_id(&self, id: &TrackId) -> Option<PoseTrack>;
fn find_active(&self) -> Vec<PoseTrack>;
fn find_lost(&self) -> Vec<PoseTrack>;
fn remove(&self, id: &TrackId);
}
/// Persists coherence state for long-term analysis
pub trait CoherenceRepository {
fn save_snapshot(&self, state: &CoherenceState, timestamp_us: u64);
fn load_latest(&self) -> Option<CoherenceState>;
fn load_history(&self, duration_ms: u64) -> Vec<(u64, f32)>;
}
/// Persists mesh topology and node health
pub trait MeshRepository {
fn save_node(&self, node_id: u8, position: [f32; 3], health: NodeHealth);
fn load_topology(&self) -> Vec<(u8, [f32; 3], NodeHealth)>;
fn save_schedule(&self, schedule: &SensingSchedule);
fn load_schedule(&self) -> Option<SensingSchedule>;
}
| Term | Definition |
|---|---|
| Field Normal Mode | The room's electromagnetic eigenstructure — stable propagation baseline when unoccupied |
| Body Perturbation | Structured change to field caused by a person, after environmental drift is removed |
| Environmental Mode | Principal component of baseline variation due to temperature, humidity, time-of-day |
| Personal Baseline | Per-person rolling statistical profile of biophysical proxies over days/weeks |
| Drift Event | Statistically significant deviation from personal baseline (>2sigma for >3 days) |
| Drift Report | Traceable evidence package: z-score, direction, window, supporting embeddings |
| Risk Signal | Actionable observation about biophysical change — not a diagnosis |
| Intention Lead Signal | Pre-movement dynamics (lean, weight shift) detected 200-500ms before visible motion |
| Occupancy Volume | Low-resolution 3D probabilistic density field from RF tomography |
| Room Fingerprint | HNSW-indexed embedding characterizing a room's electromagnetic identity |
| Transition Event | Person exiting one room and entering another, matched by embedding similarity |
Responsibility: Learn and maintain the room's electromagnetic baseline. Decompose all CSI observations into environmental drift, body perturbation, and anomalies. Provide the foundation for all downstream exotic capabilities.
┌──────────────────────────────────────────────────────────┐
│ Field Model Context │
├──────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Calibration │ │ Mode │ │
│ │ Collector │ │ Extractor │ │
│ │ (empty-room │ │ (SVD on │ │
│ │ CSI frames) │ │ baseline) │ │
│ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Perturbation │ │
│ │ Extractor │ │
│ │ (subtract │ │
│ │ baseline + │──▶ BodyPerturbation │
│ │ project out │ │
│ │ env modes) │ │
│ └────────┬───────┘ │
│ │ │
│ ┌────────▼───────┐ │
│ │ RF Tomographer│ │
│ │ (sparse 3D │──▶ OccupancyVolume │
│ │ inversion) │ │
│ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
Aggregates:
FieldNormalMode (Aggregate Root)Value Objects:
BodyPerturbation — Per-link CSI residual after baseline + environmental mode removalEnvironmentalMode — One principal component of baseline variationOccupancyVolume — 3D voxel grid of estimated mass densityCalibrationStatus — Fresh / Stale / Expired (based on time since last empty-room)Domain Services:
CalibrationService — Detects empty-room windows, collects calibration dataModeExtractionService — SVD computation for environmental modesPerturbationService — Baseline subtraction + mode projectionTomographyService — Sparse L1 inversion for occupancy volumeRuVector Integration:
ruvector-solver → SVD for mode extraction; L1 for tomographic inversionruvector-temporal-tensor → Baseline history compressionruvector-attn-mincut → Mode-subcarrier assignment partitioningResponsibility: Maintain per-person biophysical baselines over days/weeks. Detect meaningful drift. Produce traceable evidence reports. Enforce the signals-not-diagnoses boundary.
┌──────────────────────────────────────────────────────────┐
│ Longitudinal Monitoring Context │
├──────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Metric │ │ Baseline │ │
│ │ Extractor │ │ Updater │ │
│ │ (pose → gait,│ │ (Welford │ │
│ │ stability, │ │ online │ │
│ │ breathing) │ │ statistics) │ │
│ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Drift Detector│ │
│ │ (z-score vs │ │
│ │ personal │ │
│ │ baseline) │ │
│ └────────┬───────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Evidence │ │
│ │ Assembler │──▶ DriftReport │
│ │ (embeddings + │ │
│ │ timestamps + │ │
│ │ graph links) │ │
│ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
Aggregates:
PersonalBaseline (Aggregate Root)Entities:
DailyMetricSummary — One day's worth of compressed metric statistics per personValue Objects:
DriftReport — Evidence package with z-score, direction, window, embeddingsDriftMetric — GaitSymmetry / StabilityIndex / BreathingRegularity / MicroTremor / ActivityLevelDriftDirection — Increasing / DecreasingMonitoringLevel — Physiological (Level 1) / Drift (Level 2) / RiskCorrelation (Level 3)WelfordStats — Online mean/variance accumulator (count, mean, M2)Domain Services:
MetricExtractionService — Extract biomechanical proxies from pose tracksBaselineUpdateService — Update Welford statistics with daily observationsDriftDetectionService — Compute z-scores, identify significant deviationsEvidenceAssemblyService — Package supporting embeddings and graph constraintsRuVector Integration:
ruvector-temporal-tensor → Compressed daily summary storageruvector-attention → Weight metric significance in drift scoreruvector-mincut → Temporal changepoint detection in metric seriesInvariants:
Responsibility: Maintain cross-room identity continuity via environment fingerprinting and transition graphs. Track who is where across spaces without storing images.
┌──────────────────────────────────────────────────────────┐
│ Spatial Identity Context │
├──────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Room │ │ Transition │ │
│ │ Fingerprint │ │ Detector │ │
│ │ Index (HNSW) │ │ (exit/entry │ │
│ └───────┬───────┘ │ events) │ │
│ │ └───────┬───────┘ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Cross-Room │ │
│ │ Matcher │ │
│ │ (exit embed ↔ │──▶ TransitionEvent │
│ │ entry embed) │ │
│ └────────┬───────┘ │
│ │ │
│ ┌────────▼───────┐ │
│ │ Transition │ │
│ │ Graph │ │
│ │ (rooms, │ │
│ │ persons, │ │
│ │ timestamps) │ │
│ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
Aggregates:
SpatialIdentityGraph (Aggregate Root)Entities:
RoomProfile — HNSW-indexed electromagnetic fingerprint of a roomPersonSpatialRecord — Which rooms a person has visited, in orderValue Objects:
TransitionEvent — Person, from_room, to_room, timestamps, embedding similarityRoomFingerprint — 128-dim AETHER embedding of the room's CSI profileSpatialContinuity — Confidence score for cross-room identity chainDomain Services:
RoomFingerprintService — Compute and index room electromagnetic profilesTransitionDetectionService — Detect exits (track lost near boundary) and entries (new track)CrossRoomMatchingService — HNSW similarity between exit and entry embeddingsTransitionGraphService — Build and query the room-person-time graphRuVector Integration:
ruvector-mincut → Transition graph partitioning for occupancy analysisInvariants:
pub enum FieldModelEvent {
/// Baseline calibration completed (empty room detected and measured)
BaselineCalibrated {
room_id: RoomId,
n_modes: usize,
variance_explained: f32, // fraction of total variance captured
timestamp_us: u64,
},
/// Environmental drift detected (baseline shift without body cause)
EnvironmentalDriftDetected {
room_id: RoomId,
magnitude: f32,
drift_type: DriftProfile,
timestamp_us: u64,
},
/// Anomalous perturbation detected (does not match body or environment)
AnomalousPerturbation {
room_id: RoomId,
anomaly_score: f32,
affected_links: Vec<usize>,
timestamp_us: u64,
},
/// Occupancy volume updated
OccupancyUpdated {
room_id: RoomId,
occupied_voxels: usize,
total_voxels: usize,
timestamp_us: u64,
},
}
pub enum LongitudinalEvent {
/// Personal baseline established (7-day calibration complete)
BaselineEstablished {
person_id: PersonId,
observation_days: u32,
metrics_tracked: Vec<DriftMetric>,
timestamp_us: u64,
},
/// Drift detected — biophysical metric significantly changed
DriftDetected {
person_id: PersonId,
report: DriftReport,
timestamp_us: u64,
},
/// Drift resolved — metric returned to baseline range
DriftResolved {
person_id: PersonId,
metric: DriftMetric,
resolution_days: u32,
timestamp_us: u64,
},
/// Daily summary computed for a person
DailySummaryComputed {
person_id: PersonId,
date: u64, // day timestamp
metrics: Vec<(DriftMetric, f32)>, // metric, today's value
timestamp_us: u64,
},
}
pub enum SpatialEvent {
/// New room fingerprinted
RoomFingerprinted {
room_id: RoomId,
fingerprint_dims: usize,
timestamp_us: u64,
},
/// Person transitioned between rooms
PersonTransitioned {
person_id: PersonId,
from_room: RoomId,
to_room: RoomId,
similarity: f32,
gap_duration_ms: u64,
timestamp_us: u64,
},
/// Cross-room match failed (new person in destination room)
CrossRoomMatchFailed {
entry_room: RoomId,
entry_embedding: Vec<f32>,
candidates_checked: usize,
best_similarity: f32,
timestamp_us: u64,
},
}
┌──────────────────────────────────────────────────────────────────────┐
│ RuvSense Full System (ADR-029 + ADR-030) │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ FusedFrame ┌──────────────┐ │
│ │ Multistatic │────────────▶│ Pose Tracking │ │
│ │ Sensing │ │ Context │ │
│ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ │ │ TrackedPose │
│ │ │ │
│ ▼ ┌───────▼───────┐ │
│ ┌───────────────┐ │ Longitudinal │ │
│ │ Coherence │ │ Monitoring │ │
│ │ Context │ │ Context │ │
│ └───────┬───────┘ └───────┬───────┘ │
│ │ Gates │ DriftReport │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Field Model │ │ Spatial │ │
│ │ Context │ │ Identity │ │
│ │ (baseline, │ │ Context │ │
│ │ modes, │ │ (cross-room, │ │
│ │ tomography) │ │ transitions)│ │
│ └───────────────┘ └───────────────┘ │
│ │
│ ──────────────── Event Bus ────────────────── │
│ SensingEvent | CoherenceEvent | TrackingEvent | │
│ FieldModelEvent | LongitudinalEvent | SpatialEvent │
│ │
├──────────────────────────────────────────────────────────────────────┤
│ UPSTREAM: wifi-densepose-{hardware, nn, signal} │
│ SIBLINGS: AETHER (embeddings) | MERIDIAN (geometry) | MAT (triage) │
└──────────────────────────────────────────────────────────────────────┘
New Relationship Types:
/// Persists field normal modes and calibration history
pub trait FieldModelRepository {
fn save_baseline(&self, room_id: RoomId, mode: &FieldNormalMode);
fn load_baseline(&self, room_id: RoomId) -> Option<FieldNormalMode>;
fn list_rooms(&self) -> Vec<RoomId>;
fn save_occupancy_snapshot(&self, room_id: RoomId, volume: &OccupancyVolume, timestamp_us: u64);
}
/// Persists personal baselines and drift history
pub trait LongitudinalRepository {
fn save_baseline(&self, baseline: &PersonalBaseline);
fn load_baseline(&self, person_id: &PersonId) -> Option<PersonalBaseline>;
fn save_daily_summary(&self, person_id: &PersonId, summary: &DailyMetricSummary);
fn load_summaries(&self, person_id: &PersonId, days: u32) -> Vec<DailyMetricSummary>;
fn save_drift_report(&self, report: &DriftReport);
fn load_drift_history(&self, person_id: &PersonId) -> Vec<DriftReport>;
}
/// Persists room fingerprints and transition graphs
pub trait SpatialIdentityRepository {
fn save_room_fingerprint(&self, room_id: RoomId, fingerprint: &RoomFingerprint);
fn load_room_fingerprint(&self, room_id: RoomId) -> Option<RoomFingerprint>;
fn save_transition(&self, transition: &TransitionEvent);
fn load_transitions(&self, person_id: &PersonId, window_ms: u64) -> Vec<TransitionEvent>;
fn load_room_occupancy(&self, room_id: RoomId) -> Vec<PersonId>;
}
Responsibility: Run signal processing and sensing algorithms directly on the ESP32-S3, without requiring a server. The node detects presence, measures breathing and heart rate, alerts on falls, and runs custom WASM modules — all locally with instant response.
This is the only bounded context that runs on the microcontroller rather than the aggregator. It operates independently: the server is optional for visualization, but the ESP32 handles real-time sensing on its own.
┌──────────────────────────────────────────────────────────┐
│ Edge Intelligence Context │
│ (runs on ESP32-S3, Core 1) │
├──────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Phase │ │ Welford │ │
│ │ Extractor │ │ Variance │ │
│ │ (I/Q → φ, │ │ Tracker │ │
│ │ unwrap) │ │ (per-subk) │ │
│ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Top-K Select │ │
│ │ + Bandpass │ │
│ │ (breathing: │ │
│ │ 0.1-0.5 Hz, │ │
│ │ HR: 0.8-2 Hz) │ │
│ └────────┬───────┘ │
│ ▼ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌──────────┐ ┌──────────┐ │
│ │Presence│ │ Vitals │ │ Fall │ │
│ │Detector│ │ (BPM via │ │ Detector │ │
│ │(motion │ │ zero- │ │ (phase │ │
│ │ energy)│ │ crossing)│ │ accel) │ │
│ └────┬───┘ └────┬─────┘ └────┬─────┘ │
│ └───────────┼──────────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Vitals Packet │──▶ UDP 32-byte (0xC5110002) │
│ │ Assembler │ at 1 Hz to aggregator │
│ └────────┬───────┘ │
│ │ │
│ ┌────────▼───────┐ │
│ │ WASM3 Runtime │ │
│ │ (Tier 3: hot- │──▶ Custom module outputs │
│ │ loadable │ │
│ │ modules) │ │
│ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
Aggregates:
EdgeProcessingState (Aggregate Root) — Holds all per-subcarrier state, filter history, and detection flagsValue Objects:
VitalsPacket — 32-byte UDP packet: presence, motion, breathing BPM, heart rate BPM, confidence, fall flag, occupancyEdgeTier — Off (0) / BasicSignal (1) / FullVitals (2) / WasmExtended (3)PresenceState — Empty / Present / MovingBandpassOutput — Filtered signal in breathing or heart rate bandFallAlert — Phase acceleration exceeding configurable thresholdEntities:
WasmModule — A loaded WASM binary with its own memory arena (160 KB), frame budget (10 ms), and timer intervalDomain Services:
PhaseExtractionService — Converts raw I/Q to unwrapped phase per subcarrierVarianceTrackingService — Welford running stats for subcarrier selectionTopKSelectionService — Picks highest-variance subcarriers for downstream analysisBandpassFilterService — Biquad IIR filters for breathing (0.1-0.5 Hz) and heart rate (0.8-2.0 Hz)PresenceDetectionService — Adaptive threshold calibration (3-sigma over 1200-frame window)VitalSignService — Zero-crossing BPM estimation from filtered phase signalsFallDetectionService — Phase acceleration exceeding threshold triggers alertWasmRuntimeService — WASM3 interpreter: load, execute, and sandbox custom modulesNVS Configuration (runtime, no reflash needed):
| Key | Type | Default | Purpose |
|---|---|---|---|
edge_tier | u8 | 0 | Processing tier (0/1/2/3) |
pres_thresh | u16 | 0 | Presence threshold (0 = auto-calibrate) |
fall_thresh | u16 | 2000 | Fall detection threshold (rad/s^2 x 1000) |
vital_win | u16 | 256 | Phase history window (frames) |
vital_int | u16 | 1000 | Vitals packet interval (ms) |
subk_count | u8 | 8 | Top-K subcarrier count |
wasm_max | u8 | 4 | Max concurrent WASM modules |
wasm_verify | u8 | 0 | Require Ed25519 signature for uploads |
Implementation files:
firmware/esp32-csi-node/main/edge_processing.c — DSP pipeline (~750 lines)firmware/esp32-csi-node/main/edge_processing.h — Types and APIfirmware/esp32-csi-node/main/nvs_config.c — NVS key reader (20 keys)firmware/esp32-csi-node/provision.py — CLI provisioning toolInvariants:
Domain Events:
pub enum EdgeEvent {
/// Presence state changed
PresenceChanged {
node_id: u8,
state: PresenceState, // Empty / Present / Moving
motion_energy: f32,
timestamp_ms: u32,
},
/// Fall detected on-device
FallDetected {
node_id: u8,
acceleration: f32, // rad/s^2
timestamp_ms: u32,
},
/// Vitals packet emitted
VitalsEmitted {
node_id: u8,
breathing_bpm: f32,
heart_rate_bpm: f32,
confidence: f32,
timestamp_ms: u32,
},
/// WASM module loaded or failed
WasmModuleLoaded {
slot: u8,
module_name: String,
success: bool,
timestamp_ms: u32,
},
}
Relationship to other contexts: