umi_memory/dst/
config.rs

1//! SimConfig - Simulation Configuration
2//!
3//! TigerStyle: Seed management for deterministic testing.
4
5use rand::Rng;
6use std::env;
7
8use crate::constants::DST_SIMULATION_STEPS_MAX;
9
10/// Configuration for a simulation run.
11///
12/// TigerStyle:
13/// - Immutable after creation
14/// - Seed logged for reproducibility
15/// - All limits explicit
16#[derive(Debug, Clone, Copy)]
17pub struct SimConfig {
18    /// Random seed for deterministic execution
19    seed: u64,
20    /// Maximum number of simulation steps
21    steps_max: u64,
22}
23
24impl SimConfig {
25    /// Create config with explicit seed.
26    ///
27    /// # Panics
28    /// Never panics - all u64 values are valid seeds.
29    ///
30    /// # Example
31    /// ```
32    /// use umi_memory::dst::SimConfig;
33    /// let config = SimConfig::with_seed(12345);
34    /// assert_eq!(config.seed(), 12345);
35    /// ```
36    #[must_use]
37    pub fn with_seed(seed: u64) -> Self {
38        // Postcondition
39        let config = Self {
40            seed,
41            steps_max: DST_SIMULATION_STEPS_MAX,
42        };
43        assert_eq!(config.seed, seed, "seed must be stored correctly");
44        assert!(config.steps_max > 0, "steps_max must be positive");
45        config
46    }
47
48    /// Create config from DST_SEED env var or random.
49    ///
50    /// If DST_SEED is set, uses that value.
51    /// Otherwise, generates a random seed and prints it for reproducibility.
52    ///
53    /// # Example
54    /// ```
55    /// use umi_memory::dst::SimConfig;
56    /// // Set DST_SEED=42 to get deterministic seed
57    /// let config = SimConfig::from_env_or_random();
58    /// ```
59    #[must_use]
60    pub fn from_env_or_random() -> Self {
61        let seed = match env::var("DST_SEED") {
62            Ok(seed_str) => {
63                // Precondition: DST_SEED must be valid u64
64                seed_str.parse::<u64>().unwrap_or_else(|_| {
65                    panic!("DST_SEED must be a valid u64, got: {}", seed_str);
66                })
67            }
68            Err(_) => {
69                let seed = rand::thread_rng().gen::<u64>();
70                eprintln!("DST: Generated random seed (replay with DST_SEED={})", seed);
71                seed
72            }
73        };
74
75        Self::with_seed(seed)
76    }
77
78    /// Get the seed.
79    #[must_use]
80    pub fn seed(&self) -> u64 {
81        self.seed
82    }
83
84    /// Get the maximum number of steps.
85    #[must_use]
86    pub fn steps_max(&self) -> u64 {
87        self.steps_max
88    }
89
90    /// Create a new config with a different steps_max.
91    #[must_use]
92    pub fn with_steps_max(self, steps_max: u64) -> Self {
93        // Precondition
94        assert!(steps_max > 0, "steps_max must be positive");
95
96        Self {
97            seed: self.seed,
98            steps_max,
99        }
100    }
101}
102
103impl Default for SimConfig {
104    fn default() -> Self {
105        Self::from_env_or_random()
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_with_seed() {
115        let config = SimConfig::with_seed(12345);
116        assert_eq!(config.seed(), 12345);
117        assert_eq!(config.steps_max(), DST_SIMULATION_STEPS_MAX);
118    }
119
120    #[test]
121    fn test_with_seed_zero() {
122        let config = SimConfig::with_seed(0);
123        assert_eq!(config.seed(), 0);
124    }
125
126    #[test]
127    fn test_with_seed_max() {
128        let config = SimConfig::with_seed(u64::MAX);
129        assert_eq!(config.seed(), u64::MAX);
130    }
131
132    #[test]
133    fn test_with_steps_max() {
134        let config = SimConfig::with_seed(42).with_steps_max(100);
135        assert_eq!(config.seed(), 42);
136        assert_eq!(config.steps_max(), 100);
137    }
138
139    #[test]
140    #[should_panic(expected = "steps_max must be positive")]
141    fn test_with_steps_max_zero_panics() {
142        let _ = SimConfig::with_seed(42).with_steps_max(0);
143    }
144
145    // Note: Environment variable tests are tricky because tests run in parallel.
146    // These tests are better run in isolation or with --test-threads=1.
147    // For now, we focus on the core functionality tests.
148
149    #[test]
150    fn test_random_seed_generation() {
151        // Clear env to ensure random generation
152        let _ = env::remove_var("DST_SEED");
153
154        // Just verify that from_env_or_random() works without DST_SEED
155        // We can't easily test the exact value since it's random
156        let config = SimConfig::from_env_or_random();
157        assert!(config.seed() > 0 || config.seed() == 0); // Any u64 is valid
158        assert!(config.steps_max() > 0);
159    }
160}