1use crate::random::{
2 PositionalRandom, Random, RandomSource, RandomSplitter, gaussian::MarsagliaPolarGaussian,
3 get_seed, name_hash::NameHash,
4};
5
6const LCG_A: u64 = 0x0005_DEEC_E66D;
8const LCG_C: u64 = 0xB;
10const LCG_MASK: u64 = 0xFFFF_FFFF_FFFF;
12
13pub struct LegacyRandom {
16 seed: i64,
17 next_gaussian: f64,
18}
19
20#[derive(Clone)]
23pub struct LegacyRandomSplitter {
24 seed: i64,
25}
26
27impl LegacyRandom {
28 #[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 #[must_use]
40 pub const fn get_seed(&self) -> i64 {
41 self.seed
42 }
43
44 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 pub fn set_large_feature_seed(&mut self, seed: i64, chunk_x: i32, chunk_z: i32) {
52 self.set_seed(seed);
53 let x_mul = self.next_i64();
54 let z_mul = self.next_i64();
55 self.set_seed(
56 i64::from(chunk_x).wrapping_mul(x_mul) ^ i64::from(chunk_z).wrapping_mul(z_mul) ^ seed,
57 );
58 }
59
60 pub fn set_large_feature_with_salt(&mut self, seed: i64, x: i32, z: i32, salt: i32) {
62 self.set_seed(
63 i64::from(x)
64 .wrapping_mul(341_873_128_712)
65 .wrapping_add(i64::from(z).wrapping_mul(132_897_987_541))
66 .wrapping_add(seed)
67 .wrapping_add(i64::from(salt)),
68 );
69 }
70
71 const fn next(&mut self, bits: u64) -> i32 {
72 (self.next_random() >> (48 - bits)) as i32
73 }
74
75 const fn next_random(&mut self) -> i64 {
76 let l = self.seed as u64;
77 let m = (l.wrapping_mul(LCG_A).wrapping_add(LCG_C)) & LCG_MASK;
78 self.seed = m as i64;
79 m as i64
80 }
81
82 const fn skip(&mut self, count: u64) {
93 let mut acc_a: u64 = 1;
95 let mut acc_c: u64 = 0;
96 let mut base_a: u64 = LCG_A;
97 let mut base_c: u64 = LCG_C;
98 let mut k = count;
99 while k > 0 {
100 if k & 1 == 1 {
101 acc_c = base_a.wrapping_mul(acc_c).wrapping_add(base_c) & LCG_MASK;
103 acc_a = base_a.wrapping_mul(acc_a) & LCG_MASK;
104 }
105 base_c = base_a.wrapping_mul(base_c).wrapping_add(base_c) & LCG_MASK;
107 base_a = base_a.wrapping_mul(base_a) & LCG_MASK;
108 k >>= 1;
109 }
110 let s = self.seed as u64;
111 self.seed = (acc_a.wrapping_mul(s).wrapping_add(acc_c) & LCG_MASK) as i64;
112 }
113}
114
115impl MarsagliaPolarGaussian for LegacyRandom {
116 fn stored_next_gaussian(&self) -> Option<f64> {
117 if self.next_gaussian.is_nan() {
118 None
119 } else {
120 Some(self.next_gaussian)
121 }
122 }
123
124 fn set_stored_next_gaussian(&mut self, value: Option<f64>) {
125 self.next_gaussian = value.unwrap_or(f64::NAN);
126 }
127}
128
129impl Random for LegacyRandom {
130 fn fork(&mut self) -> Self {
131 Self::from_seed(self.next_i64() as u64)
132 }
133
134 fn next_i32(&mut self) -> i32 {
135 self.next(32)
136 }
137
138 fn next_i32_bounded(&mut self, bound: i32) -> i32 {
139 if bound & bound.wrapping_sub(1) == 0 {
140 (i64::from(bound).wrapping_mul(i64::from(self.next(31))) >> 31) as i32
141 } else {
142 loop {
143 let i = self.next(31);
144 let j = i % bound;
145 if i.wrapping_sub(j).wrapping_add(bound.wrapping_sub(1)) >= 0 {
146 return j;
147 }
148 }
149 }
150 }
151
152 fn next_i64(&mut self) -> i64 {
153 let i = self.next_i32();
154 let j = self.next_i32();
155 (i64::from(i) << 32).wrapping_add(i64::from(j))
156 }
157
158 fn next_f32(&mut self) -> f32 {
159 self.next(24) as f32 * 5.960_464_5e-8_f32
160 }
161
162 fn next_f64(&mut self) -> f64 {
163 let combined = (i64::from(self.next(26)) << 27) + i64::from(self.next(27));
170 combined as f64 * (1.0 / (1_i64 << 53) as f64)
171 }
172
173 fn next_bool(&mut self) -> bool {
174 self.next(1) != 0
175 }
176
177 fn next_gaussian(&mut self) -> f64 {
178 self.calculate_gaussian()
179 }
180
181 fn next_positional(&mut self) -> RandomSplitter {
182 RandomSplitter::Legacy(LegacyRandomSplitter::new(self.next_i64()))
183 }
184
185 fn consume_count(&mut self, count: i32) {
186 if count > 0 {
187 self.skip(count as u64);
188 }
189 }
190}
191
192impl LegacyRandomSplitter {
193 #[must_use]
196 pub const fn new(seed: i64) -> Self {
197 Self { seed }
198 }
199}
200
201impl PositionalRandom for LegacyRandomSplitter {
202 fn at(&self, x: i32, y: i32, z: i32) -> RandomSource {
203 let seed = get_seed(x, y, z);
204 RandomSource::Legacy(LegacyRandom::from_seed((seed as u64) ^ (self.seed as u64)))
205 }
206
207 fn with_hash_of(&self, hash: &NameHash) -> RandomSource {
208 RandomSource::Legacy(LegacyRandom::from_seed(
209 (hash.java_hash as u64) ^ (self.seed as u64),
210 ))
211 }
212
213 fn with_seed(&self, seed: u64) -> RandomSource {
214 RandomSource::Legacy(LegacyRandom::from_seed(seed))
215 }
216}
217
218#[cfg(test)]
219mod test {
220 use crate::random::{PositionalRandom, Random, RandomSplitter, name_hash::NameHash};
221
222 use super::LegacyRandom;
223
224 #[test]
225 fn test_next_i32() {
226 let mut rand = LegacyRandom::from_seed(0);
227
228 let values = [
229 -1_155_484_576,
230 -723_955_400,
231 1_033_096_058,
232 -1_690_734_402,
233 -1_557_280_266,
234 1_327_362_106,
235 -1_930_858_313,
236 502_539_523,
237 -1_728_529_858,
238 -938_301_587,
239 ];
240
241 for value in values {
242 assert_eq!(rand.next_i32(), value);
243 }
244 }
245
246 #[test]
247 fn test_next_i32_bounded() {
248 let mut rand = LegacyRandom::from_seed(0);
249
250 let values = [0, 13, 4, 2, 5, 8, 11, 6, 9, 14];
251
252 for value in values {
253 assert_eq!(rand.next_i32_bounded(0xf), value);
254 }
255
256 let mut rand = LegacyRandom::from_seed(0);
257 for _ in 0..10 {
258 assert_eq!(rand.next_i32_bounded(1), 0);
259 }
260
261 let mut rand = LegacyRandom::from_seed(0);
262 let values = [1, 1, 0, 1, 1, 0, 1, 0, 1, 1];
263 for value in values {
264 assert_eq!(rand.next_i32_bounded(2), value);
265 }
266 }
267
268 #[test]
269 fn test_next_i32_between() {
270 let mut rand = LegacyRandom::from_seed(0);
271
272 let values = [1, 5, 2, 12, 12, 6, 12, 10, 4, 3];
273
274 for value in values {
275 assert_eq!(rand.next_i32_between(1, 12), value);
276 }
277 }
278
279 #[test]
280 fn test_next_i32_between_exclusive() {
281 let mut rand = LegacyRandom::from_seed(0);
282
283 let values = [1, 7, 9, 6, 7, 3, 3, 7, 3, 1];
284
285 for value in values {
286 assert_eq!(rand.next_i32_between_exclusive(1, 12), value);
287 }
288 }
289
290 #[test]
291 #[expect(clippy::float_cmp, reason = "exact match against vanilla test vectors")]
292 fn test_next_f64() {
293 let mut rand = LegacyRandom::from_seed(0);
294
295 let values = [
299 0.730_967_787_376_657,
300 0.240_536_415_671_485_87,
301 0.637_417_425_350_108_3,
302 0.550_437_005_117_633_9,
303 0.597_545_277_797_201_8,
304 0.333_218_399_476_649_8,
305 0.385_189_184_740_718_5,
306 0.984_841_540_199_809,
307 0.879_182_517_872_480_1,
308 0.941_249_179_482_114_4,
309 ];
310
311 for value in values {
312 assert_eq!(rand.next_f64(), value);
313 }
314 }
315
316 #[test]
317 #[expect(clippy::float_cmp, reason = "exact match against vanilla test vectors")]
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 #[expect(clippy::float_cmp, reason = "exact match against vanilla test vectors")]
376 fn test_next_gaussian() {
377 let mut rand = LegacyRandom::from_seed(0);
378
379 let values = [
380 0.802_533_063_739_030_5,
381 -0.901_546_088_417_512_2,
382 2.080_920_790_428_163,
383 0.763_770_768_436_489_4,
384 0.984_574_532_882_512_8,
385 -1.683_412_258_767_342_8,
386 -0.027_290_262_907_887_285,
387 0.115_245_702_862_023_15,
388 -0.390_167_041_379_937_74,
389 -0.643_388_813_126_449,
390 ];
391
392 for value in values {
393 assert_eq!(rand.next_gaussian(), value);
394 }
395 }
396
397 #[test]
398 #[expect(clippy::float_cmp, reason = "exact match against vanilla test vectors")]
399 fn test_triangle() {
400 let mut rand = LegacyRandom::from_seed(0);
401
402 let values = [
403 124.521_568_585_258_56,
404 104.349_021_011_623_72,
405 113.216_343_916_027_6,
406 70.017_382_227_045_47,
407 96.896_666_919_518_28,
408 107.302_840_758_085_41,
409 106.168_176_758_131_44,
410 79.112_644_826_080_78,
411 73.967_216_139_270_62,
412 81.724_195_210_806_46,
413 ];
414
415 for value in values {
416 assert_eq!(rand.triangle(100_f64, 50_f64), value);
417 }
418 }
419
420 #[test]
421 fn consume_count_matches_naive_loop() {
422 const COUNTS: &[i32] = &[0, 1, 2, 7, 31, 32, 33, 100, 262, 1023, 1024, 17292, 100_000];
423 const SEEDS: &[u64] = &[0, 1, 0xDEAD_BEEF, 0x1234_5678_9ABC_DEF0];
424
425 for &seed in SEEDS {
426 for &count in COUNTS {
427 let mut fast = LegacyRandom::from_seed(seed);
428 let mut slow = LegacyRandom::from_seed(seed);
429 fast.consume_count(count);
430 for _ in 0..count {
431 slow.next_i32();
432 }
433 assert_eq!(
434 fast.next_i64(),
435 slow.next_i64(),
436 "mismatch at seed={seed:#x} count={count}"
437 );
438 }
439 }
440 }
441
442 #[test]
443 fn consume_count_negative_is_noop() {
444 let mut a = LegacyRandom::from_seed(42);
445 let mut b = LegacyRandom::from_seed(42);
446 a.consume_count(-1);
447 a.consume_count(i32::MIN);
448 assert_eq!(a.next_i64(), b.next_i64());
449 }
450
451 #[test]
452 fn test_fork() {
453 let mut original_rand = LegacyRandom::from_seed(0);
454 assert_eq!(original_rand.next_i64(), -4_962_768_465_676_381_896_i64);
455
456 let mut original_rand = LegacyRandom::from_seed(0);
457 {
458 let RandomSplitter::Legacy(splitter) = original_rand.next_positional() else {
459 unreachable!()
460 };
461 assert_eq!(splitter.seed, -4_962_768_465_676_381_896_i64);
462
463 let mut rand = splitter.with_hash_of(&NameHash::new("minecraft:offset"));
464 assert_eq!(rand.next_i32(), 103_436_829);
465 }
466
467 let mut original_rand = LegacyRandom::from_seed(0);
468 let mut new_rand = original_rand.fork();
469 {
470 let splitter = new_rand.next_positional();
471
472 let mut rand1 = splitter.with_hash_of(&NameHash::new("TEST STRING"));
473 assert_eq!(rand1.next_i32(), -1_170_413_697);
474
475 let mut rand2 = splitter.with_seed(10);
476 assert_eq!(rand2.next_i32(), -1_157_793_070);
477
478 let mut rand3 = splitter.at(1, 11, -111);
479 assert_eq!(rand3.next_i32(), -1_213_890_343);
480 }
481
482 assert_eq!(original_rand.next_i32(), 1_033_096_058);
483 assert_eq!(new_rand.next_i32(), -888_301_832);
484 }
485
486 #[test]
487 fn test_set_seed_matches_from_seed() {
488 let mut fresh = LegacyRandom::from_seed(12345);
489 let mut reseeded = LegacyRandom::from_seed(0);
490 reseeded.set_seed(12345);
491 for _ in 0..10 {
492 assert_eq!(fresh.next_i64(), reseeded.next_i64());
493 }
494 }
495
496 #[test]
497 fn test_set_large_feature_with_salt_trivial() {
498 let mut rng = LegacyRandom::from_seed(0);
499 rng.set_large_feature_with_salt(0, 0, 0, 10_387_312);
500 let mut expected = LegacyRandom::from_seed(0);
501 expected.set_seed(10_387_312);
502 for _ in 0..5 {
503 assert_eq!(rng.next_i32(), expected.next_i32());
504 }
505 }
506
507 #[test]
508 fn test_set_large_feature_with_salt() {
509 let mut rng = LegacyRandom::from_seed(0);
510 rng.set_large_feature_with_salt(123_456_789, 5, -3, 10_387_312);
511 let expected_seed: i64 =
512 5_i64 * 341_873_128_712 + (-3_i64) * 132_897_987_541 + 123_456_789 + 10_387_312;
513 let mut expected = LegacyRandom::from_seed(0);
514 expected.set_seed(expected_seed);
515 for _ in 0..5 {
516 assert_eq!(rng.next_i32(), expected.next_i32());
517 }
518 }
519
520 #[test]
521 fn test_set_large_feature_seed() {
522 let x_mul = -4_962_768_465_676_381_896_i64;
523 let z_mul = 4_437_113_781_045_784_766_i64;
524 let expected_seed = 3_i64.wrapping_mul(x_mul) ^ 5_i64.wrapping_mul(z_mul);
525
526 let mut rng = LegacyRandom::from_seed(0);
527 rng.set_large_feature_seed(0, 3, 5);
528 let mut expected = LegacyRandom::from_seed(0);
529 expected.set_seed(expected_seed);
530 for _ in 0..5 {
531 assert_eq!(rng.next_i32(), expected.next_i32());
532 }
533 }
534}