//! Seeded RNG service (GameKernel K5) with the null/reference impl pair //! required by AM-11. The only randomness source available to game code. use crate::ids::Seed; use rand_core::{RngCore, SeedableRng}; /// Kernel randomness: deterministic draws from the game seed. pub trait KernelRng { /// Uniform draw in `0..bound` (bound ≥ 1). fn draw(&mut self, bound: u32) -> u32; /// Deterministic Fisher–Yates shuffle of `items`. fn shuffle(&mut self, items: &mut [T]) { for i in (1..items.len()).rev() { #[allow(clippy::cast_possible_truncation)] let j = self.draw(i as u32 + 1) as usize; items.swap(i, j); } } } /// Reference implementation: ChaCha12, seeded from the game `Seed`. pub struct ChaChaRng(rand_chacha::ChaCha12Rng); impl ChaChaRng { pub fn from_seed(seed: Seed) -> Self { Self(rand_chacha::ChaCha12Rng::seed_from_u64(seed.0)) } } impl KernelRng for ChaChaRng { fn draw(&mut self, bound: u32) -> u32 { assert!(bound >= 1, "draw bound must be >= 1"); // Rejection sampling for uniformity. let zone = u32::MAX - (u32::MAX % bound); loop { let v = self.0.next_u32(); if v < zone { return v % bound; } } } } /// Null implementation for tests: always returns 0 (first choice, no /// shuffle movement). Makes scenario fixtures fully predictable. #[derive(Default)] pub struct NullRng; impl KernelRng for NullRng { fn draw(&mut self, _bound: u32) -> u32 { 0 } } #[cfg(test)] mod tests { use super::*; /// AM-8 seed determinism at the unit level: same seed, same draws. #[test] fn chacha_is_deterministic_per_seed() { let mut a = ChaChaRng::from_seed(Seed(42)); let mut b = ChaChaRng::from_seed(Seed(42)); let mut c = ChaChaRng::from_seed(Seed(43)); let draws_a: Vec = (0..64).map(|_| a.draw(1000)).collect(); let draws_b: Vec = (0..64).map(|_| b.draw(1000)).collect(); let draws_c: Vec = (0..64).map(|_| c.draw(1000)).collect(); assert_eq!(draws_a, draws_b); assert_ne!(draws_a, draws_c); } #[test] fn null_rng_never_moves_a_shuffle() { let mut rng = NullRng; let mut items = vec![1, 2, 3, 4]; // Fisher–Yates with j=0 each step rotates deterministically. rng.shuffle(&mut items); let mut again = vec![1, 2, 3, 4]; NullRng.shuffle(&mut again); assert_eq!(items, again); } }