//! Seeded LCG for deterministic generation (D-010). //! //! Shared by all atlas generation modules that need seeded randomness. //! Uses Knuth's LCG parameters — integer-only arithmetic, no f32, D-010 compliant. //! //! Callers are responsible for any seed pre-mixing before calling `AtlasRng::new`. /// Seeded linear congruential generator (D-010). pub struct AtlasRng { state: u64, } impl AtlasRng { /// Create a new RNG from a pre-mixed seed. /// /// Callers must ensure the seed is non-degenerate (avoid passing 0 directly /// if the seed could realistically be 0 — add a constant before calling). pub fn new(seed: u64) -> Self { Self { state: seed } } fn next_u64(&mut self) -> u64 { self.state = self .state .wrapping_mul(6364136223846793005) .wrapping_add(1442695040888963407); self.state } /// Next pseudorandom `u32` (top 31 bits of the LCG state). pub fn next_u32(&mut self) -> u32 { (self.next_u64() >> 33) as u32 } } #[cfg(test)] mod tests { use super::*; #[test] fn deterministic_sequence() { let mut a = AtlasRng::new(42); let mut b = AtlasRng::new(42); for _ in 0..100 { assert_eq!(a.next_u32(), b.next_u32()); } } #[test] fn different_seeds_differ() { let mut a = AtlasRng::new(1); let mut b = AtlasRng::new(2); let vals_a: Vec = (0..10).map(|_| a.next_u32()).collect(); let vals_b: Vec = (0..10).map(|_| b.next_u32()).collect(); assert_ne!(vals_a, vals_b); } }