Skip to main content

temporal_field/
config.rs

1//! Field configuration
2//!
3//! ASTRO_004 compliant: No floats. Uses u8 for retention (255 = 1.0).
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8/// Configuration for a temporal field.
9#[derive(Clone, Debug)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11pub struct FieldConfig {
12    /// Number of dimensions per frame.
13    pub dims: usize,
14
15    /// Number of frames in the ring buffer.
16    pub frame_count: usize,
17
18    /// Decay retention per tick (0 = instant zero, 255 = no decay).
19    /// Scale: 255 = 1.0, 230 ≈ 0.90, 128 = 0.50
20    pub retention: u8,
21
22    /// Tick rate in Hz (for time calculations).
23    pub tick_rate_hz: u32,
24}
25
26impl FieldConfig {
27    /// Create a standard configuration.
28    /// retention: u8 where 255 = 1.0 (no decay), 230 ≈ 0.90
29    pub fn new(dims: usize, frame_count: usize, retention: u8) -> Self {
30        Self {
31            dims,
32            frame_count,
33            retention,
34            tick_rate_hz: 100,
35        }
36    }
37
38    /// Get temporal window duration in milliseconds.
39    pub fn window_ms(&self) -> u32 {
40        (self.frame_count as u32 * 1000) / self.tick_rate_hz
41    }
42
43    /// Validate configuration.
44    pub fn validate(&self) -> Result<(), &'static str> {
45        if self.dims == 0 {
46            return Err("dims must be > 0");
47        }
48        if self.frame_count == 0 {
49            return Err("frame_count must be > 0");
50        }
51        // retention is u8, always valid (0-255)
52        Ok(())
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn test_config_new() {
62        let config = FieldConfig::new(64, 10, 242); // 242 ≈ 0.95
63        assert_eq!(config.dims, 64);
64        assert_eq!(config.frame_count, 10);
65        assert_eq!(config.retention, 242);
66    }
67
68    #[test]
69    fn test_window_ms() {
70        let config = FieldConfig::new(64, 50, 255);
71        // 50 frames at 100Hz = 500ms
72        assert_eq!(config.window_ms(), 500);
73    }
74
75    #[test]
76    fn test_validate() {
77        let valid = FieldConfig::new(64, 10, 242);
78        assert!(valid.validate().is_ok());
79
80        let invalid_dims = FieldConfig::new(0, 10, 242);
81        assert!(invalid_dims.validate().is_err());
82
83        let invalid_frames = FieldConfig::new(64, 0, 242);
84        assert!(invalid_frames.validate().is_err());
85    }
86}