Skip to main content

vimp_engine_core/
rng.rs

1use serde::{Deserialize, Serialize};
2
3/// Детерминированный PRNG (SplitMix64): замена Math.random() у ботов и
4/// разброса оружия. Целочисленное ядро — результат бит-в-бит одинаков на
5/// любой платформе, что сохраняет детерминизм WASM (Spike C).
6#[derive(Clone, Serialize, Deserialize)]
7pub struct Rng {
8    state: u64,
9}
10
11impl Rng {
12    pub fn new(seed: u64) -> Self {
13        Self { state: seed }
14    }
15
16    /// Текущее состояние генератора — для отладочного дампа (два прогона
17    /// одного сценария обязаны сойтись и по нему).
18    pub fn state(&self) -> u64 {
19        self.state
20    }
21
22    fn next_u64(&mut self) -> u64 {
23        self.state = self.state.wrapping_add(0x9e37_79b9_7f4a_7c15);
24        let mut z = self.state;
25
26        z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
27        z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
28        z ^ (z >> 31)
29    }
30
31    /// Аналог Math.random(): значение в [0, 1).
32    pub fn next_f32(&mut self) -> f32 {
33        (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
34    }
35
36    /// Аналог randomRange(min, max) из lib/math.js.
37    pub fn range(&mut self, min: f32, max: f32) -> f32 {
38        self.next_f32() * (max - min) + min
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn deterministic_for_same_seed() {
48        let mut a = Rng::new(42);
49        let mut b = Rng::new(42);
50
51        for _ in 0..100 {
52            assert_eq!(a.next_u64(), b.next_u64());
53        }
54    }
55
56    #[test]
57    fn next_f32_in_unit_range() {
58        let mut rng = Rng::new(7);
59
60        for _ in 0..1000 {
61            let v = rng.next_f32();
62            assert!((0.0..1.0).contains(&v));
63        }
64    }
65
66    #[test]
67    fn range_respects_bounds() {
68        let mut rng = Rng::new(3);
69
70        for _ in 0..1000 {
71            let v = rng.range(-0.25, 0.25);
72            assert!((-0.25..0.25).contains(&v));
73        }
74    }
75}