Files
wifi-densepose/docs/ddd/ruvsense-domain-model.md
Claude 25b005a0d6 docs: add RuvSense sensing-first RF mode architecture
Research, ADR, and DDD specification for multistatic WiFi DensePose
with coherence-gated tracking and complete ruvector integration.

- docs/research/ruvsense-multistatic-fidelity-architecture.md:
  SOTA research covering bandwidth/frequency/viewpoint fidelity levers,
  ESP32 multistatic mesh design, coherence gating, AETHER embedding
  integration, and full ruvector crate mapping

- docs/adr/ADR-029-ruvsense-multistatic-sensing-mode.md:
  Architecture decision for sensing-first RF mode on existing ESP32
  silicon. GOAP integration plan (9 actions, 4 phases, 36 cost units).
  TDMA schedule for 20 Hz update rate from 4-node mesh.
  IEEE 802.11bf forward-compatible design.

- docs/ddd/ruvsense-domain-model.md:
  Domain-Driven Design with 3 bounded contexts (Multistatic Sensing,
  Coherence, Pose Tracking), aggregate roots, domain events, context
  map, anti-corruption layers, and repository interfaces.

Acceptance test: 2 people, 20 Hz, 10 min stable tracks, zero ID swaps,
<30mm torso keypoint jitter.

https://claude.ai/code/session_01QTX772SDsGVSPnaphoNgNY
2026-03-02 00:17:30 +00:00

27 KiB
Raw Blame History

RuvSense Domain Model

Domain-Driven Design Specification

Ubiquitous Language

Term Definition
Sensing Cycle One complete TDMA round (all nodes TX once): 50ms at 20 Hz
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
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)

Bounded Contexts

1. Multistatic Sensing Context

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:

  • MultiBandCsiFrame
  • LinkGeometry (tx_pos, rx_pos, distance, angle)
  • SensingSchedule
  • ChannelHopConfig

Domain Services:

  • PhaseAlignmentService — Corrects LO-induced phase rotation between channels
  • MultiBandFusionService — Merges per-channel CSI into wideband virtual frame
  • MultistaticFusionService — Attention-based fusion of N nodes into one frame

RuVector Integration:

  • ruvector-solver → Phase alignment (NeumannSolver)
  • ruvector-attention → Cross-channel feature weighting
  • ruvector-attn-mincut → Cross-node spectrogram attention gating
  • ruvector-temporal-tensor → Per-link compressed ring buffers

2. Coherence Context

Responsibility: 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 state

Value 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 coherence
  • StaticDynamicDecomposerService — Separates environmental drift from body motion
  • GatePolicyService — Applies threshold-based gating rules

RuVector Integration:

  • ruvector-solver → Coherence matrix decomposition (static vs. dynamic)
  • ruvector-attn-mincut → Gate which subcarriers contribute to template update

3. Pose Tracking Context

Responsibility: 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 covariance

Value Objects:

  • TrackedPose — Immutable snapshot: 17 keypoints + confidence + track_id + lifecycle
  • PersonCluster — Subset of links attributed to one person
  • AssignmentCost — Combined Mahalanobis + embedding distance
  • TrackLifecycleState (Tentative / Active / Lost / Terminated)

Domain Services:

  • PersonSeparationService — Min-cut partitioning of cross-link correlation graph
  • TrackAssignmentService — Bipartite matching of detections to existing tracks
  • KalmanPredictionService — Predict step at 20 Hz (decoupled from measurement rate)
  • KalmanUpdateService — Gated measurement update (subject to coherence gate)
  • EmbeddingIdentifierService — AETHER cosine similarity for re-ID

RuVector Integration:

  • ruvector-mincut → Person separation (DynamicMinCut on correlation graph)
  • ruvector-mincut → Detection-to-track assignment (DynamicPersonMatcher)
  • ruvector-attention → Embedding similarity via ScaledDotProductAttention

Core Domain Entities

FusedSensingFrame (Value Object)

pub 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,
}

PoseTrack (Aggregate Root)

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,
}

KeypointState (Entity)

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,
}

CoherenceState (Aggregate Root)

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,
}

Domain Events

Sensing Events

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,
    },
}

Coherence Events

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,
    },
}

Tracking Events

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,
}

Context Map

┌──────────────────────────────────────────────────────────────────┐
│                      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:

  • Multistatic Sensing → Pose Tracking: Customer/Supplier (Sensing produces FusedFrames; Tracking consumes)
  • Coherence → Multistatic Sensing: Subscriber (monitors frame quality)
  • Coherence → Pose Tracking: Gate/Interceptor (controls measurement updates)
  • RuvSense → Upstream crates: Conformist (adapts to their types)
  • RuvSense → AETHER/MERIDIAN/MAT: Partnership (shared embedding/geometry/tracking abstractions)

Anti-Corruption Layer

Hardware Adapter (Multistatic Sensing → wifi-densepose-hardware)

/// 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>;
}

Model Adapter (Pose Tracking → wifi-densepose-nn)

/// 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,
}

MAT Adapter (Pose Tracking → wifi-densepose-mat)

/// 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;
}

Repository Interfaces

/// 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>;
}

Invariants

Multistatic Sensing

  • At least 2 nodes must be active for multistatic fusion (fallback to single-node mode otherwise)
  • Channel hop sequence must contain at least 1 non-overlapping channel
  • TDMA cycle period must be ≤50ms for 20 Hz output
  • Guard interval must be ≥2× clock drift budget (≥1ms for 50ms cycle)

Coherence

  • Reference template must be recalculated every 10 minutes during quiet periods
  • Gate threshold must be calibrated per-environment (initial defaults: accept=0.85, drift=0.5)
  • Stale count must not exceed max_stale (200 frames = 10s) without triggering recalibration
  • Static/dynamic decomposition must preserve energy: ||S|| + ||D|| ≈ ||C||

Pose Tracking

  • Exactly one Kalman predict step per output frame (20 Hz, regardless of measurement availability)
  • Birth gate: track not promoted to Active until 2 consecutive measurement updates
  • Loss threshold: track marked Lost after 5 consecutive missed measurements
  • Re-ID window: Lost tracks eligible for re-identification for 5 seconds
  • Embedding EMA decay: 0.95 (slow adaptation preserves identity across environmental changes)
  • Joint assignment cost must use both position (60%) and embedding (40%) terms