1#[derive(Debug, Clone)]
10pub struct AgentRng {
11 state: u64,
12}
13
14impl AgentRng {
15 pub(crate) fn derive(seed: &str, index: usize, name: &str) -> Self {
18 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
20 let mut mix = |bytes: &[u8]| {
21 for &byte in bytes {
22 hash ^= u64::from(byte);
23 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
24 }
25 };
26 mix(seed.as_bytes());
27 mix(&index.to_le_bytes());
28 mix(name.as_bytes());
29 Self::from_u64(hash)
30 }
31
32 pub fn from_u64(seed: u64) -> Self {
34 Self { state: seed | 1 }
36 }
37
38 pub fn next_u64(&mut self) -> u64 {
40 self.state = self.state.wrapping_add(0x9e37_79b9_7f4a_7c15);
41 let mut z = self.state;
42 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
43 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
44 z ^ (z >> 31)
45 }
46
47 pub fn choose_index(&mut self, len: usize) -> Option<usize> {
49 if len == 0 {
50 return None;
51 }
52 let len_u64 = u64::try_from(len).unwrap_or(u64::MAX);
53 let pick = self.next_u64() % len_u64;
54 Some(usize::try_from(pick).unwrap_or(len - 1))
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::AgentRng;
61
62 #[test]
63 fn derivation_is_deterministic_and_agent_specific() {
64 let mut first = AgentRng::derive("seed-v1", 0, "alice");
65 let mut again = AgentRng::derive("seed-v1", 0, "alice");
66 let first_seq: Vec<u64> = (0..4).map(|_| first.next_u64()).collect();
67 let again_seq: Vec<u64> = (0..4).map(|_| again.next_u64()).collect();
68 assert_eq!(
69 first_seq, again_seq,
70 "same seed and identity yields the same stream"
71 );
72
73 let mut other = AgentRng::derive("seed-v1", 1, "bob");
74 let other_seq: Vec<u64> = (0..4).map(|_| other.next_u64()).collect();
75 assert_ne!(
76 first_seq, other_seq,
77 "a different agent yields a different stream"
78 );
79 }
80}