RuView Coherence Gate Decision-Making Process: How Accept, Reject, and Recalibrate Decisions Work

The RuView coherence gate evaluates Channel State Information (CSI) frame consistency against configurable thresholds to emit Accept, PredictOnly, Reject, or Recalibrate decisions that control Kalman filter updates and trigger SONA/AETHER recalibration when data quality degrades.

The Coherence Gate in the ruvnet/RuView repository serves as a critical safety layer between raw WiFi sensing data and the pose estimation pipeline. Implemented in rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/coherence_gate.rs, this component determines whether a newly computed CSI-based pose measurement should be applied to the Kalman filter, ignored, or used to trigger a full system recalibration based on runtime coherence scores and stale-frame counters.

Policy Configuration and Thresholds

The gate's behavior is governed by the GatePolicyConfig struct, which defines static or adaptive thresholds that tune the sensitivity of the decision-making process.

GatePolicyConfig Structure

Located at lines 62-75 in coherence_gate.rs, the configuration struct exposes the following parameters:

pub struct GatePolicyConfig {
    pub accept_threshold: f32,      // ≥ 0.85 by default
    pub reject_threshold: f32,      // ≤ 0.5  by default
    pub max_stale_frames: u64,      // 200 frames ≈ 10 s at 20 Hz
    pub predict_only_noise: f32,    // 3.0 (noise inflation factor)
    pub adaptive: bool,             // not used in the default policy
}

A GatePolicy object is instantiated from this configuration via GatePolicy::from_config (lines 19-29). The policy holds mutable runtime state including consecutive_low (tracking how many frames in a row have been PredictOnly or Reject) and last_decision (the most recent GateDecision for debugging).

Decision Types and Enumeration

The gate emits one of four distinct decisions defined in the GateDecision enum.

GateDecision Enum Variants

The enumeration at lines 42-60 in coherence_gate.rs defines the possible outcomes:

pub enum GateDecision {
    Accept { noise_multiplier: f32 }, // full Kalman update, multiplier = 1.0
    PredictOnly,                      // only the predict step, inflated noise
    Reject,                           // drop measurement entirely
    Recalibrate { stale_frames: u64 } // trigger SONA/AETHER recalibration
}

Helper methods including allows_update, is_rejected, and noise_multiplier allow downstream code to query the decision without exhaustive pattern matching on every call (lines 42-60).

Core Evaluation Algorithm

The decision-making logic centers on the evaluate method, which processes runtime inputs against configured thresholds.

The evaluate Method Logic

The evaluate method (lines 31-50) implements a priority-ordered decision tree that processes three inputs: the coherence_score (a float ∈ [0, 1] computed by the coherence.rs module), the stale_count (consecutive low-coherence frames), and the configured thresholds.

Decision Tree Flow

The algorithm evaluates conditions in the following strict order:

  1. Recalibration Override: If stale_count >= max_stale_frames, the gate immediately returns GateDecision::Recalibrate regardless of the current coherence score.

    if stale_count >= self.max_stale_frames {
        GateDecision::Recalibrate { stale_frames: stale_count }
    }
  2. Accept Zone: When coherence_score >= accept_threshold, the gate returns GateDecision::Accept, resets consecutive_low to zero, and applies a noise multiplier of 1.0.

    else if coherence_score >= self.accept_threshold {
        self.consecutive_low = 0;
        GateDecision::Accept { noise_multiplier: 1.0 }
    }
  3. Predict-Only Zone: If the score falls between the reject and accept thresholds, the gate issues GateDecision::PredictOnly and increments the low-coherence counter.

    else if coherence_score >= self.reject_threshold {
        self.consecutive_low += 1;
        GateDecision::PredictOnly
    }
  4. Reject Zone: For scores below the reject threshold, the gate returns GateDecision::Reject and increments the low-coherence counter.

    else {
        self.consecutive_low += 1;
        GateDecision::Reject
    }

After the decision is selected, it is stored in self.last_decision (line 50) for later introspection.

Adaptive Noise Handling

The gate supports optional adaptive noise scaling for intermediate coherence scores.

Dynamic Noise Multipliers

Although the default policy uses a fixed predict_only_noise value, the module provides adaptive_noise_multiplier (lines 87-109) to compute a smooth noise inflation factor based on how far the coherence score lies between the accept and reject thresholds. Higher-level modules reference this function when GatePolicyConfig.adaptive is set to true, allowing the Kalman filter to receive proportionally scaled measurement noise rather than binary accept/reject behavior.

State Management and Recalibration

The gate maintains internal counters to track data quality degradation over time.

Stale Frame Tracking

The consecutive_low counter increments on every PredictOnly or Reject decision. When this counter exceeds max_stale_frames (default 200 frames, approximately 10 seconds at 20 Hz), the gate triggers the recalibration state regardless of the current coherence score.

Reset and Recovery

The reset() method (lines 74-78) clears consecutive_low and last_decision after a successful SONA/AETHER recalibration, allowing the system to resume normal operation with fresh baseline measurements.

Integration with the Kalman Filter

The coherence gate acts as a pre-filter for the pose estimation pipeline.

From CSI Frame to Filter Update

The integration flow follows this sequence:

  1. Coherence computation: The coherence.rs module processes raw CSI frames and returns a coherence score float ∈ [0, 1].
  2. Stale-frame bookkeeping: CoherenceState increments the stale counter each time the gate returns a low-coherence decision.
  3. Gate evaluation: GatePolicy::evaluate(coherence, stale_counter) returns a GateDecision.
  4. Kalman filter action:
    • Accept → filter.update(measurement, noise_multiplier = 1.0)
    • PredictOnly → filter.predict() or update with inflated noise via predict_only_noise
    • Reject → No filter call; measurement dropped
    • Recalibrate → System freezes output, launches the SONA/AETHER recalibration pipeline, then calls GatePolicy::reset()

Code Examples

Creating a Gate with Custom Thresholds

use wifi_densepose_signal::ruvsense::{
    GatePolicy, GatePolicyConfig,
};

// Custom policy: stricter acceptance, longer stale timeout.
let cfg = GatePolicyConfig {
    accept_threshold: 0.90,
    reject_threshold: 0.40,
    max_stale_frames: 300,          // 15 s at 20 Hz
    predict_only_noise: 4.0,
    adaptive: false,
};

let mut gate = GatePolicy::from_config(&cfg);

Running a Decision Cycle

// Suppose we have a fresh coherence score from the coherence module.
let coherence_score = 0.72_f32;

// The stale-frame counter is maintained elsewhere (e.g., in CoherenceState).
let stale_frames = 5_u64;

let decision = gate.evaluate(coherence_score, stale_frames);

match decision {
    GateDecision::Accept { noise_multiplier } => {
        println!("Accept – update with noise ×{noise_multiplier}");
        // kalman.update(meas, noise_multiplier);
    }
    GateDecision::PredictOnly => {
        println!("PredictOnly – run predict step, inflate noise");
        // kalman.predict(); // or update with gate.predict_only_noise
    }
    GateDecision::Reject => {
        println!("Reject – discard measurement");
        // ignore this frame
    }
    GateDecision::Recalibrate { stale_frames } => {
        println!("Recalibrate after {stale_frames} low-coherence frames");
        // launch SONA/AETHER pipeline, then gate.reset();
    }
}

Adaptive Noise Example

// When adaptive mode is enabled, compute the noise multiplier dynamically:
let noise = adaptive_noise_multiplier(
    coherence_score,
    cfg.accept_threshold,
    cfg.reject_threshold,
    cfg.predict_only_noise,
);
println!("Adaptive noise multiplier: {noise}");

Key Files and Components

File Role
[coherence_gate.rs](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/coherence_gate.rs) Core gate policy, decision enum, evaluation algorithm.
[coherence.rs](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/coherence.rs) Calculates the coherence score from CSI frames.
[pose_tracker.rs](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/ruvsense/pose_tracker.rs) Consumes GateDecision to drive Kalman filter updates.
GatePolicyConfig Configuration struct that tunes the gate thresholds.
adaptive_noise_multiplier Helper for smooth noise inflation when adaptive is true.

Summary

  • The Coherence Gate in coherence_gate.rs acts as a safety filter between CSI measurements and the Kalman filter, preventing noisy data from corrupting pose estimates.
  • Four distinct decisions—Accept, PredictOnly, Reject, and Recalibrate—are determined by comparing coherence scores against configurable accept_threshold and reject_threshold values.
  • Recalibration triggers automatically when max_stale_frames (default 200 frames) consecutive low-coherence measurements occur, initiating the SONA/AETHER pipeline to restore system accuracy.
  • Adaptive noise scaling via adaptive_noise_multiplier allows smooth degradation of measurement confidence rather than binary filtering when GatePolicyConfig.adaptive is enabled.

Frequently Asked Questions

What inputs does the RuView coherence gate use to make decisions?

The coherence gate evaluates three primary inputs: the coherence score (a float between 0 and 1 computed by the coherence.rs module), the stale-frame counter (tracking consecutive low-coherence frames maintained in CoherenceState), and the gate policy configuration (GatePolicyConfig struct) containing threshold values and timeout limits.

How does the coherence gate decide when to trigger a full system recalibration?

The gate triggers GateDecision::Recalibrate when the internal stale_count exceeds max_stale_frames (default 200 frames, approximately 10 seconds at 20 Hz), regardless of the current coherence score. This override takes precedence over all other threshold checks in the evaluate method, ensuring the system initiates the SONA/AETHER recalibration pipeline before pose estimates become unreliable.

What is the difference between PredictOnly and Reject decisions in the coherence gate?

PredictOnly indicates the coherence score falls between the reject_threshold and accept_threshold, meaning the measurement is questionable but not useless; the Kalman filter runs only the prediction step (or updates with inflated noise using predict_only_noise), preserving temporal continuity while avoiding measurement corruption. Reject occurs when the coherence score falls below reject_threshold, indicating the frame is too unreliable; the measurement is discarded entirely with no Kalman filter update, though both decisions increment the consecutive_low counter toward potential recalibration.

Can the coherence gate adapt its noise levels based on coherence quality?

Yes, when GatePolicyConfig.adaptive is set to true, the gate utilizes the adaptive_noise_multiplier helper function (lines 87-109 in coherence_gate.rs) to compute a smooth noise inflation factor based on how far the coherence score lies between the accept and reject thresholds. This allows the Kalman filter to receive proportionally scaled measurement noise rather than binary accept/reject behavior, enabling graceful degradation of pose estimate confidence during marginal signal conditions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →