Skip to main content

steel_utils/random/
legacy_random.rs

1use crate::random::{
2    PositionalRandom, Random, RandomSource, RandomSplitter, gaussian::MarsagliaPolarGaussian,
3    get_seed, name_hash::NameHash,
4};
5
6/// LCG multiplier (matches `java.util.Random`).
7const LCG_A: u64 = 0x0005_DEEC_E66D;
8/// LCG additive constant (matches `java.util.Random`).
9const LCG_C: u64 = 0xB;
10/// 48-bit state mask.
11const LCG_MASK: u64 = 0xFFFF_FFFF_FFFF;
12
13/// Legacy Minecraft random number generator based on a Linear Congruential Generator (LCG).
14/// This implementation mirrors Java's `java.util.Random` which Minecraft originally used.
15pub struct LegacyRandom {
16    seed: i64,
17    next_gaussian: f64,
18}
19
20/// A positional random number generator factory for the legacy Minecraft LCG algorithm.
21/// This can create random sources based on position, hash, or seed.
22#[derive(Clone)]
23pub struct LegacyRandomSplitter {
24    seed: i64,
25}
26
27impl LegacyRandom {
28    /// Creates a new `LegacyRandom` instance from the given seed.
29    /// The seed is `XORed` with the LCG multiplier and masked to 48 bits, matching Java's behavior.
30    #[must_use]
31    pub const fn from_seed(seed: u64) -> Self {
32        Self {
33            seed: (seed as i64 ^ LCG_A as i64) & LCG_MASK as i64,
34            next_gaussian: f64::NAN,
35        }
36    }
37
38    /// Returns the internal seed (for debugging/checkpointing).
39    #[must_use]
40    pub const fn get_seed(&self) -> i64 {
41        self.seed
42    }
43
44    /// Re-seeds this generator, matching Java's `Random.setSeed`.
45    pub const fn set_seed(&mut self, seed: i64) {
46        self.seed = (seed ^ 0x0005_DEEC_E66D) & 0xFFFF_FFFF_FFFF;
47        self.next_gaussian = f64::NAN;
48    }
49
50    /// Re-seeds this generator for a large feature at a chunk position,
51    /// mixing the chunk coordinates into the seed.
52    pub fn set_large_feature_seed(&mut self, seed: i64, chunk_x: i32, chunk_z: i32) {
53        self.set_seed(seed);
54        let x_mul = self.next_i64();
55        let z_mul = self.next_i64();
56        self.set_seed(
57            i64::from(chunk_x).wrapping_mul(x_mul) ^ i64::from(chunk_z).wrapping_mul(z_mul) ^ seed,
58        );
59    }
60
61    /// Re-seeds this generator for a large feature at a position, mixing the
62    /// coordinates and a per-feature salt into the seed.
63    pub fn set_large_feature_with_salt(&mut self, seed: i64, x: i32, z: i32, salt: i32) {
64        self.set_seed(
65            i64::from(x)
66                .wrapping_mul(341_873_128_712)
67                .wrapping_add(i64::from(z).wrapping_mul(132_897_987_541))
68                .wrapping_add(seed)
69                .wrapping_add(i64::from(salt)),
70        );
71    }
72
73    const fn next(&mut self, bits: u64) -> i32 {
74        (self.next_random() >> (48 - bits)) as i32
75    }
76
77    const fn next_random(&mut self) -> i64 {
78        let l = self.seed as u64;
79        let m = (l.wrapping_mul(LCG_A).wrapping_add(LCG_C)) & LCG_MASK;
80        self.seed = m as i64;
81        m as i64
82    }
83
84    /// Advance the LCG by `count` steps in O(log count) time.
85    ///
86    /// Equivalent to calling `next_random()` `count` times and discarding the
87    /// results. Each LCG step is the affine map `T(s) = LCG_A · s + LCG_C` (mod 2^48).
88    /// Composing two such maps `(A₂, C₂) ∘ (A₁, C₁)` yields `(A₂·A₁, A₂·C₁ + C₂)`,
89    /// so binary exponentiation over the composition computes `T^count` in
90    /// O(log count) without ever evaluating individual steps.
91    ///
92    /// `const fn` so the compiler can fold the entire skip when `count` is a
93    /// compile-time constant (as in `EndIslands::new` where it's literally 17292).
94    const fn skip(&mut self, count: u64) {
95        // Accumulator starts as the identity transform (s ↦ s); base is one step.
96        let mut acc_a: u64 = 1;
97        let mut acc_c: u64 = 0;
98        let mut base_a: u64 = LCG_A;
99        let mut base_c: u64 = LCG_C;
100        let mut k = count;
101        while k > 0 {
102            if k & 1 == 1 {
103                // acc ← base ∘ acc
104                acc_c = base_a.wrapping_mul(acc_c).wrapping_add(base_c) & LCG_MASK;
105                acc_a = base_a.wrapping_mul(acc_a) & LCG_MASK;
106            }
107            // base ← base ∘ base
108            base_c = base_a.wrapping_mul(base_c).wrapping_add(base_c) & LCG_MASK;
109            base_a = base_a.wrapping_mul(base_a) & LCG_MASK;
110            k >>= 1;
111        }
112        let s = self.seed as u64;
113        self.seed = (acc_a.wrapping_mul(s).wrapping_add(acc_c) & LCG_MASK) as i64;
114    }
115}
116
117impl MarsagliaPolarGaussian for LegacyRandom {
118    fn stored_next_gaussian(&self) -> Option<f64> {
119        if self.next_gaussian.is_nan() {
120            None
121        } else {
122            Some(self.next_gaussian)
123        }
124    }
125
126    fn set_stored_next_gaussian(&mut self, value: Option<f64>) {
127        self.next_gaussian = value.unwrap_or(f64::NAN);
128    }
129}
130
131impl Random for LegacyRandom {
132    fn fork(&mut self) -> Self {
133        Self::from_seed(self.next_i64() as u64)
134    }
135
136    fn next_i32(&mut self) -> i32 {
137        self.next(32)
138    }
139
140    fn next_i32_bounded(&mut self, bound: i32) -> i32 {
141        if bound & bound.wrapping_sub(1) == 0 {
142            (i64::from(bound).wrapping_mul(i64::from(self.next(31))) >> 31) as i32
143        } else {
144            loop {
145                let i = self.next(31);
146                let j = i % bound;
147                if i.wrapping_sub(j).wrapping_add(bound.wrapping_sub(1)) >= 0 {
148                    return j;
149                }
150            }
151        }
152    }
153
154    fn next_i64(&mut self) -> i64 {
155        let i = self.next_i32();
156        let j = self.next_i32();
157        (i64::from(i) << 32).wrapping_add(i64::from(j))
158    }
159
160    fn next_f32(&mut self) -> f32 {
161        self.next(24) as f32 * 5.960_464_5e-8_f32
162    }
163
164    fn next_f64(&mut self) -> f64 {
165        // Matches vanilla's BitRandomSource.nextDouble():
166        //   double DOUBLE_MULTIPLIER = 1.110223E-16F;  // stored as double = 2^-53
167        //   return combined * DOUBLE_MULTIPLIER;
168        // The field is declared double; the float literal `1.110223E-16F` is widened to
169        // double at compile time (= exactly 2^-53). javac inlines static final interface
170        // fields, so the bytecode uses `ldc2_w (double)` → double multiplication.
171        let combined = (i64::from(self.next(26)) << 27) + i64::from(self.next(27));
172        combined as f64 * (1.0 / (1_i64 << 53) as f64)
173    }
174
175    fn next_bool(&mut self) -> bool {
176        self.next(1) != 0
177    }
178
179    fn next_gaussian(&mut self) -> f64 {
180        self.calculate_gaussian()
181    }
182
183    fn next_positional(&mut self) -> RandomSplitter {
184        RandomSplitter::Legacy(LegacyRandomSplitter::new(self.next_i64()))
185    }
186
187    fn consume_count(&mut self, count: i32) {
188        if count > 0 {
189            self.skip(count as u64);
190        }
191    }
192}
193
194impl LegacyRandomSplitter {
195    /// Creates a new `LegacyRandomSplitter` with the given seed.
196    /// This seed is used to initialize positional random sources.
197    #[must_use]
198    pub const fn new(seed: i64) -> Self {
199        Self { seed }
200    }
201}
202
203impl PositionalRandom for LegacyRandomSplitter {
204    fn at(&self, x: i32, y: i32, z: i32) -> RandomSource {
205        let seed = get_seed(x, y, z);
206        RandomSource::Legacy(LegacyRandom::from_seed((seed as u64) ^ (self.seed as u64)))
207    }
208
209    fn with_hash_of(&self, hash: &NameHash) -> RandomSource {
210        RandomSource::Legacy(LegacyRandom::from_seed(
211            (hash.java_hash as u64) ^ (self.seed as u64),
212        ))
213    }
214
215    fn with_seed(&self, seed: u64) -> RandomSource {
216        RandomSource::Legacy(LegacyRandom::from_seed(seed))
217    }
218}
219
220#[cfg(test)]
221mod test {
222    use crate::random::{PositionalRandom, Random, RandomSplitter, name_hash::NameHash};
223
224    use super::LegacyRandom;
225
226    #[test]
227    fn test_next_i32() {
228        let mut rand = LegacyRandom::from_seed(0);
229
230        let values = [
231            -1_155_484_576,
232            -723_955_400,
233            1_033_096_058,
234            -1_690_734_402,
235            -1_557_280_266,
236            1_327_362_106,
237            -1_930_858_313,
238            502_539_523,
239            -1_728_529_858,
240            -938_301_587,
241        ];
242
243        for value in values {
244            assert_eq!(rand.next_i32(), value);
245        }
246    }
247
248    #[test]
249    fn test_next_i32_bounded() {
250        let mut rand = LegacyRandom::from_seed(0);
251
252        let values = [0, 13, 4, 2, 5, 8, 11, 6, 9, 14];
253
254        for value in values {
255            assert_eq!(rand.next_i32_bounded(0xf), value);
256        }
257
258        let mut rand = LegacyRandom::from_seed(0);
259        for _ in 0..10 {
260            assert_eq!(rand.next_i32_bounded(1), 0);
261        }
262
263        let mut rand = LegacyRandom::from_seed(0);
264        let values = [1, 1, 0, 1, 1, 0, 1, 0, 1, 1];
265        for value in values {
266            assert_eq!(rand.next_i32_bounded(2), value);
267        }
268    }
269
270    #[test]
271    fn test_next_i32_between() {
272        let mut rand = LegacyRandom::from_seed(0);
273
274        let values = [1, 5, 2, 12, 12, 6, 12, 10, 4, 3];
275
276        for value in values {
277            assert_eq!(rand.next_i32_between(1, 12), value);
278        }
279    }
280
281    #[test]
282    fn test_next_i32_between_exclusive() {
283        let mut rand = LegacyRandom::from_seed(0);
284
285        let values = [1, 7, 9, 6, 7, 3, 3, 7, 3, 1];
286
287        for value in values {
288            assert_eq!(rand.next_i32_between_exclusive(1, 12), value);
289        }
290    }
291
292    #[test]
293    fn test_next_f64() {
294        let mut rand = LegacyRandom::from_seed(0);
295
296        // Values match vanilla's BitRandomSource.nextDouble():
297        //   double DOUBLE_MULTIPLIER = 1.110223E-16F;  // stored as double = 2^-53
298        //   return combined * DOUBLE_MULTIPLIER;        // double multiplication
299        let values = [
300            0.730_967_787_376_657,
301            0.240_536_415_671_485_87,
302            0.637_417_425_350_108_3,
303            0.550_437_005_117_633_9,
304            0.597_545_277_797_201_8,
305            0.333_218_399_476_649_8,
306            0.385_189_184_740_718_5,
307            0.984_841_540_199_809,
308            0.879_182_517_872_480_1,
309            0.941_249_179_482_114_4,
310        ];
311
312        for value in values {
313            assert_eq!(rand.next_f64(), value);
314        }
315    }
316
317    #[test]
318    fn test_next_f32() {
319        let mut rand = LegacyRandom::from_seed(0);
320
321        let values: [f32; 10] = [
322            0.730_967_76,
323            0.831_441,
324            0.240_536_39,
325            0.606_345_2,
326            0.637_417_4,
327            0.309_050_56,
328            0.550_437,
329            0.117_006_6,
330            0.597_545_27,
331            0.781_534_6,
332        ];
333
334        for value in values {
335            assert_eq!(rand.next_f32(), value);
336        }
337    }
338
339    #[test]
340    fn test_next_i64() {
341        let mut rand = LegacyRandom::from_seed(0);
342
343        let values: [i64; 10] = [
344            -4_962_768_465_676_381_896,
345            4_437_113_781_045_784_766,
346            -6_688_467_811_848_818_630,
347            -8_292_973_307_042_192_125,
348            -7_423_979_211_207_825_555,
349            6_146_794_652_083_548_235,
350            7_105_486_291_024_734_541,
351            -279_624_296_851_435_688,
352            -2_228_689_144_322_150_137,
353            -1_083_761_183_081_836_303,
354        ];
355
356        for value in values {
357            assert_eq!(rand.next_i64(), value);
358        }
359    }
360
361    #[test]
362    fn test_next_bool() {
363        let mut rand = LegacyRandom::from_seed(0);
364
365        let values = [
366            true, true, false, true, true, false, true, false, true, true,
367        ];
368
369        for value in values {
370            assert_eq!(rand.next_bool(), value);
371        }
372    }
373
374    #[test]
375    fn test_next_gaussian() {
376        let mut rand = LegacyRandom::from_seed(0);
377
378        let values = [
379            0.802_533_063_739_030_5,
380            -0.901_546_088_417_512_2,
381            2.080_920_790_428_163,
382            0.763_770_768_436_489_4,
383            0.984_574_532_882_512_8,
384            -1.683_412_258_767_342_8,
385            -0.027_290_262_907_887_285,
386            0.115_245_702_862_023_15,
387            -0.390_167_041_379_937_74,
388            -0.643_388_813_126_449,
389        ];
390
391        for value in values {
392            assert_eq!(rand.next_gaussian(), value);
393        }
394    }
395
396    #[test]
397    fn test_triangle() {
398        let mut rand = LegacyRandom::from_seed(0);
399
400        let values = [
401            124.521_568_585_258_56,
402            104.349_021_011_623_72,
403            113.216_343_916_027_6,
404            70.017_382_227_045_47,
405            96.896_666_919_518_28,
406            107.302_840_758_085_41,
407            106.168_176_758_131_44,
408            79.112_644_826_080_78,
409            73.967_216_139_270_62,
410            81.724_195_210_806_46,
411        ];
412
413        for value in values {
414            assert_eq!(rand.triangle(100_f64, 50_f64), value);
415        }
416    }
417
418    #[test]
419    fn consume_count_matches_naive_loop() {
420        const COUNTS: &[i32] = &[0, 1, 2, 7, 31, 32, 33, 100, 262, 1023, 1024, 17292, 100_000];
421        const SEEDS: &[u64] = &[0, 1, 0xDEAD_BEEF, 0x1234_5678_9ABC_DEF0];
422
423        for &seed in SEEDS {
424            for &count in COUNTS {
425                let mut fast = LegacyRandom::from_seed(seed);
426                let mut slow = LegacyRandom::from_seed(seed);
427                fast.consume_count(count);
428                for _ in 0..count {
429                    slow.next_i32();
430                }
431                assert_eq!(
432                    fast.next_i64(),
433                    slow.next_i64(),
434                    "mismatch at seed={seed:#x} count={count}"
435                );
436            }
437        }
438    }
439
440    #[test]
441    fn consume_count_negative_is_noop() {
442        let mut a = LegacyRandom::from_seed(42);
443        let mut b = LegacyRandom::from_seed(42);
444        a.consume_count(-1);
445        a.consume_count(i32::MIN);
446        assert_eq!(a.next_i64(), b.next_i64());
447    }
448
449    #[test]
450    fn test_fork() {
451        let mut original_rand = LegacyRandom::from_seed(0);
452        assert_eq!(original_rand.next_i64(), -4_962_768_465_676_381_896_i64);
453
454        let mut original_rand = LegacyRandom::from_seed(0);
455        {
456            let RandomSplitter::Legacy(splitter) = original_rand.next_positional() else {
457                unreachable!()
458            };
459            assert_eq!(splitter.seed, -4_962_768_465_676_381_896_i64);
460
461            let mut rand = splitter.with_hash_of(&NameHash::new("minecraft:offset"));
462            assert_eq!(rand.next_i32(), 103_436_829);
463        }
464
465        let mut original_rand = LegacyRandom::from_seed(0);
466        let mut new_rand = original_rand.fork();
467        {
468            let splitter = new_rand.next_positional();
469
470            let mut rand1 = splitter.with_hash_of(&NameHash::new("TEST STRING"));
471            assert_eq!(rand1.next_i32(), -1_170_413_697);
472
473            let mut rand2 = splitter.with_seed(10);
474            assert_eq!(rand2.next_i32(), -1_157_793_070);
475
476            let mut rand3 = splitter.at(1, 11, -111);
477            assert_eq!(rand3.next_i32(), -1_213_890_343);
478        }
479
480        assert_eq!(original_rand.next_i32(), 1_033_096_058);
481        assert_eq!(new_rand.next_i32(), -888_301_832);
482    }
483
484    #[test]
485    fn test_set_seed_matches_from_seed() {
486        let mut fresh = LegacyRandom::from_seed(12345);
487        let mut reseeded = LegacyRandom::from_seed(0);
488        reseeded.set_seed(12345);
489        for _ in 0..10 {
490            assert_eq!(fresh.next_i64(), reseeded.next_i64());
491        }
492    }
493
494    #[test]
495    fn test_set_large_feature_with_salt_trivial() {
496        let mut rng = LegacyRandom::from_seed(0);
497        rng.set_large_feature_with_salt(0, 0, 0, 10_387_312);
498        let mut expected = LegacyRandom::from_seed(0);
499        expected.set_seed(10_387_312);
500        for _ in 0..5 {
501            assert_eq!(rng.next_i32(), expected.next_i32());
502        }
503    }
504
505    #[test]
506    fn test_set_large_feature_with_salt() {
507        let mut rng = LegacyRandom::from_seed(0);
508        rng.set_large_feature_with_salt(123_456_789, 5, -3, 10_387_312);
509        let expected_seed: i64 =
510            5_i64 * 341_873_128_712 + (-3_i64) * 132_897_987_541 + 123_456_789 + 10_387_312;
511        let mut expected = LegacyRandom::from_seed(0);
512        expected.set_seed(expected_seed);
513        for _ in 0..5 {
514            assert_eq!(rng.next_i32(), expected.next_i32());
515        }
516    }
517
518    #[test]
519    fn test_set_large_feature_seed() {
520        let x_mul = -4_962_768_465_676_381_896_i64;
521        let z_mul = 4_437_113_781_045_784_766_i64;
522        let expected_seed = 3_i64.wrapping_mul(x_mul) ^ 5_i64.wrapping_mul(z_mul);
523
524        let mut rng = LegacyRandom::from_seed(0);
525        rng.set_large_feature_seed(0, 3, 5);
526        let mut expected = LegacyRandom::from_seed(0);
527        expected.set_seed(expected_seed);
528        for _ in 0..5 {
529            assert_eq!(rng.next_i32(), expected.next_i32());
530        }
531    }
532}