steel_utils/random/
worldgen_random.rs1use crate::random::{
2 Random, RandomSplitter, gaussian::MarsagliaPolarGaussian, xoroshiro::Xoroshiro,
3};
4
5#[derive(Clone)]
11pub struct WorldgenRandom {
12 source: Xoroshiro,
13 next_gaussian: Option<f64>,
14}
15
16impl WorldgenRandom {
17 #[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 pub const fn set_seed(&mut self, seed: i64) {
33 self.source.set_seed(seed);
34 }
35
36 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 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}