Skip to main content

zeitgeist_protocol/
precision.rs

1//! Deadband funnel precision tracking
2
3use serde::{Deserialize, Serialize};
4
5/// Precision state tracks the deadband funnel — how tightly
6/// a value is converging toward its target.
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct PrecisionState {
9    /// Current deadband width (must be > 0 and < covering_radius)
10    pub deadband: f64,
11    /// Position in funnel: 0 = wide open, 1 = snap
12    pub funnel_pos: f64,
13    /// Whether we're within the closing threshold
14    pub snap_imminent: bool,
15}
16
17impl PrecisionState {
18    pub const COVERING_RADIUS: f64 = 1e6;
19
20    pub fn new(deadband: f64, funnel_pos: f64, snap_imminent: bool) -> Self {
21        Self { deadband, funnel_pos, snap_imminent }
22    }
23
24    pub fn default() -> Self {
25        Self {
26            deadband: Self::COVERING_RADIUS / 2.0,
27            funnel_pos: 0.0,
28            snap_imminent: false,
29        }
30    }
31
32    /// Check alignment: deadband must be > 0 and < covering_radius
33    pub fn check_alignment(&self) -> Vec<String> {
34        let mut violations = Vec::new();
35        if self.deadband <= 0.0 {
36            violations.push("precision.deadband must be > 0".into());
37        }
38        if self.deadband >= Self::COVERING_RADIUS {
39            violations.push("precision.deadband must be < covering_radius".into());
40        }
41        if !(0.0..=1.0).contains(&self.funnel_pos) {
42            violations.push("precision.funnel_pos must be 0-1".into());
43        }
44        violations
45    }
46
47    /// Merge: take the tighter deadband, higher funnel position, snap if either snaps
48    pub fn merge(&self, other: &Self) -> Self {
49        Self {
50            deadband: self.deadband.min(other.deadband),
51            funnel_pos: self.funnel_pos.max(other.funnel_pos),
52            snap_imminent: self.snap_imminent || other.snap_imminent,
53        }
54    }
55}