Skip to main content

steel_utils/random/
worldgen_random.rs

1use crate::random::{
2    Random, RandomSplitter, gaussian::MarsagliaPolarGaussian, xoroshiro::Xoroshiro,
3};
4
5/// Vanilla's `WorldgenRandom` when constructed for biome decoration.
6///
7/// Feature decoration always constructs `WorldgenRandom(new XoroshiroRandomSource(...))`.
8/// Sampling then goes through `BitRandomSource.next*`, so it does not match raw
9/// `XoroshiroRandomSource` for `nextInt`, bounded ints, doubles, longs, or gaussians.
10#[derive(Clone)]
11pub struct WorldgenRandom {
12    source: Xoroshiro,
13    next_gaussian: Option<f64>,
14}
15
16impl WorldgenRandom {
17    /// Creates a new `WorldgenRandom` backed by vanilla's `XoroshiroRandomSource`.
18    #[must_use]
19    pub const fn from_seed(seed: u64) -> Self {
20        Self {
21            source: Xoroshiro::from_seed(seed),
22            next_gaussian: None,
23        }
24    }
25
26    /// Re-seeds the backing `XoroshiroRandomSource`.
27    ///
28    /// Vanilla `WorldgenRandom` inherits its gaussian cache from
29    /// `LegacyRandomSource`, but overrides `setSeed` to only reseed the
30    /// wrapped source. That means `setDecorationSeed` / `setFeatureSeed`
31    /// intentionally preserve a pending gaussian value.
32    pub const fn set_seed(&mut self, seed: i64) {
33        self.source.set_seed(seed);
34    }
35
36    /// Vanilla's `WorldgenRandom.setDecorationSeed`.
37    pub fn set_decoration_seed(&mut self, seed: i64, block_x: i32, block_z: i32) -> i64 {
38        self.set_seed(seed);
39        let x_scale = self.next_i64() | 1;
40        let z_scale = self.next_i64() | 1;
41        let decoration_seed = i64::from(block_x)
42            .wrapping_mul(x_scale)
43            .wrapping_add(i64::from(block_z).wrapping_mul(z_scale))
44            ^ seed;
45        self.set_seed(decoration_seed);
46        decoration_seed
47    }
48
49    /// Vanilla's `WorldgenRandom.setFeatureSeed`.
50    pub const fn set_feature_seed(&mut self, decoration_seed: i64, feature_index: i32, step: i32) {
51        let feature_seed = decoration_seed
52            .wrapping_add(feature_index as i64)
53            .wrapping_add(10_000_i64.wrapping_mul(step as i64));
54        self.set_seed(feature_seed);
55    }
56
57    fn next_bits(&mut self, bits: u64) -> u64 {
58        self.source.next_i64() as u64 >> (64 - bits)
59    }
60}
61
62impl MarsagliaPolarGaussian for WorldgenRandom {
63    fn stored_next_gaussian(&self) -> Option<f64> {
64        self.next_gaussian
65    }
66
67    fn set_stored_next_gaussian(&mut self, value: Option<f64>) {
68        self.next_gaussian = value;
69    }
70}
71
72impl Random for WorldgenRandom {
73    fn fork(&mut self) -> Self {
74        Self {
75            source: self.source.fork(),
76            next_gaussian: None,
77        }
78    }
79
80    fn next_i32(&mut self) -> i32 {
81        self.next_bits(32) as i32
82    }
83
84    fn next_i32_bounded(&mut self, bound: i32) -> i32 {
85        if bound & bound.wrapping_sub(1) == 0 {
86            (i64::from(bound).wrapping_mul(i64::from(self.next_bits(31) as i32)) >> 31) as i32
87        } else {
88            loop {
89                let sample = self.next_bits(31) as i32;
90                let modulo = sample % bound;
91                if sample
92                    .wrapping_sub(modulo)
93                    .wrapping_add(bound.wrapping_sub(1))
94                    >= 0
95                {
96                    return modulo;
97                }
98            }
99        }
100    }
101
102    fn next_i64(&mut self) -> i64 {
103        let upper = self.next_i32();
104        let lower = self.next_i32();
105        (i64::from(upper) << 32).wrapping_add(i64::from(lower))
106    }
107
108    fn next_f32(&mut self) -> f32 {
109        self.next_bits(24) as f32 * 5.960_464_5e-8_f32
110    }
111
112    fn next_f64(&mut self) -> f64 {
113        let combined = ((self.next_bits(26) as i64) << 27) + self.next_bits(27) as i64;
114        combined as f64 * (1.0 / (1_i64 << 53) as f64)
115    }
116
117    fn next_bool(&mut self) -> bool {
118        self.next_bits(1) != 0
119    }
120
121    fn next_gaussian(&mut self) -> f64 {
122        self.calculate_gaussian()
123    }
124
125    fn next_positional(&mut self) -> RandomSplitter {
126        self.source.next_positional()
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::WorldgenRandom;
133    use crate::random::Random;
134
135    #[test]
136    fn set_decoration_seed_matches_vanilla_trace() {
137        let mut random = WorldgenRandom::from_seed(0);
138        assert_eq!(
139            random.set_decoration_seed(13_579, -6_695_392, 5_868_656),
140            7_632_291_757_650_236_667,
141        );
142    }
143
144    #[test]
145    fn feature_seed_matches_vanilla_first_ore_dirt_origin() {
146        let mut random = WorldgenRandom::from_seed(0);
147        let decoration_seed = random.set_decoration_seed(13_579, -6_695_392, 5_868_656);
148        random.set_feature_seed(decoration_seed, 0, 6);
149
150        let x = -6_695_392 + random.next_i32_bounded(16);
151        let z = 5_868_656 + random.next_i32_bounded(16);
152        let y = random.next_i32_bounded(161);
153        assert_eq!((x, y, z), (-6_695_386, 149, 5_868_662));
154    }
155
156    #[test]
157    fn feature_seed_preserves_pending_gaussian() {
158        let mut random = WorldgenRandom::from_seed(123);
159        let _ = random.next_gaussian();
160        random.set_feature_seed(456, 7, 8);
161
162        let mut cached_reference = WorldgenRandom::from_seed(123);
163        let _ = cached_reference.next_gaussian();
164        assert_eq!(random.next_gaussian(), cached_reference.next_gaussian());
165
166        let mut reseeded_reference = WorldgenRandom::from_seed(0);
167        reseeded_reference.set_feature_seed(456, 7, 8);
168        assert_eq!(random.next_gaussian(), reseeded_reference.next_gaussian());
169    }
170}