ace_sim/rng.rs
1// region: Rng Trait
2
3/// A seeded random number generator for deterministic simulation.
4///
5/// All randomness in the simulation must flow through this trait. The same seed must produce the
6/// same sequence of values - this is what makes simulation runs reproducible and replayable.
7pub trait Rng {
8 fn next_u64(&mut self) -> u64;
9
10 /// Returns a random `u32`.
11 fn next_u32(&mut self) -> u32 {
12 self.next_u64() as u32
13 }
14
15 /// Returns a random `u8`.
16 fn next_u8(&mut self) -> u8 {
17 self.next_u64() as u8
18 }
19
20 // Returns true with probability `numerator / denominator`.
21 fn chance(&mut self, numerator: u32, denominator: u32) -> bool {
22 if denominator == 0 {
23 return false;
24 }
25
26 (self.next_u32() % denominator) < numerator
27 }
28
29 /// Returns a random `usize` in `0..len`.
30 fn index(&mut self, len: usize) -> usize {
31 if len == 0 {
32 return 0;
33 }
34
35 (self.next_u64() as usize) % len
36 }
37}
38
39// endregion: Rng Trait
40
41// region: XorShift64
42
43/// A fast, seedable Xorshift64 RNG
44///
45/// Not cryptographically secure - suitable for simulation only. Produces a full cycle of 2^64-1
46/// values before repeating
47#[derive(Debug, Clone)]
48pub struct Xorshift64 {
49 state: u64,
50}
51
52impl Xorshift64 {
53 /// Creates a new RNG with the given seed.
54 ///
55 /// Seed must be non-zero - passing 0 will be replaced with a default non-zero seed.
56 pub fn new(seed: u64) -> Self {
57 Self {
58 state: if seed == 0 {
59 0xDECA_FC0F_FEEC_AFE1
60 } else {
61 seed
62 },
63 }
64 }
65}
66
67impl Rng for Xorshift64 {
68 fn next_u64(&mut self) -> u64 {
69 let mut x = self.state;
70 x ^= x << 13;
71 x ^= x >> 7;
72 x ^= x << 17;
73 self.state = x;
74 x
75 }
76}
77
78// endregion: XorShift64