Pumpkin/crates/pumpkin-util/src/random/legacy_rand.rs
2026-08-06 12:56:40 +02:00

405 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::{
RandomDeriver, RandomDeriverImpl, RandomGenerator, RandomImpl, gaussian::GaussianGenerator,
hash_block_pos,
};
use crate::{math::java_string_hash, population_seed_fn};
/// Legacy random number generator compatible with older Minecraft versions.
///
/// This implementation uses a 48-bit linear congruential generator (LCG) with the formula:
/// `seed = (seed * 0x5DEECE66D + 11) & 0xFFFFFFFFFFFF`
///
/// It maintains compatibility with Minecraft's original random number generator used in
/// versions prior to 1.13. This is essential for world generation compatibility with
/// older worlds and for certain features that still rely on the legacy algorithm.
pub struct LegacyRand {
/// The current 48-bit seed value (stored in lower 48 bits).
seed: u64,
/// Stored Gaussian value for the next Gaussian generation.
internal_next_gaussian: Option<f64>,
}
impl LegacyRand {
population_seed_fn!();
/// Creates a new legacy random generator from the given seed.
///
/// The seed is `XORed` with the constant `0x5DEECE66D` and masked to 48 bits.
///
/// # Arguments
/// - `seed` – The initial seed value.
///
/// # Returns
/// A new `LegacyRand` instance.
#[must_use]
pub const fn from_seed(seed: u64) -> Self {
Self {
seed: (seed ^ 0x0005_DEEC_E66D) & 0xFFFF_FFFF_FFFF,
internal_next_gaussian: None,
}
}
/// Generates the next random value and advances the internal state.
///
/// This implements the core LCG algorithm: `seed = seed * 0x5DEECE66D + 11`
/// and returns the new seed.
///
/// # Returns
/// The new 48-bit seed value.
const fn next_random(&mut self) -> i64 {
let l = self.seed as i64;
let m = l.wrapping_mul(0x0005_DEEC_E66D).wrapping_add(11) & 0xFFFFFFFFFFFF;
self.seed = m as u64;
m
}
/// Generates a random integer with the specified number of bits.
///
/// # Arguments
/// - `bits` – The number of bits to extract (1-48).
///
/// # Returns
/// A random value with the given number of bits.
const fn next(&mut self, bits: u64) -> i32 {
(self.next_random() >> (48 - bits)) as i32
}
}
impl GaussianGenerator for LegacyRand {
fn stored_next_gaussian(&self) -> Option<f64> {
self.internal_next_gaussian
}
fn set_stored_next_gaussian(&mut self, value: Option<f64>) {
self.internal_next_gaussian = value;
}
}
impl RandomImpl for LegacyRand {
fn skip(&mut self, count: i32) {
// Advance exactly `count` single LCG steps using `next_i32()`.
// `next_i64()` consumes two LCG steps, so we override the default.
for _ in 0..count {
self.next_i32();
}
}
fn split(&mut self) -> Self {
Self::from_seed(self.next_i64() as u64)
}
fn next_splitter(&mut self) -> RandomDeriver {
RandomDeriver::Legacy(LegacySplitter::new(self.next_i64() as u64))
}
fn next_i32(&mut self) -> i32 {
self.next(32)
}
fn next_bounded_i32(&mut self, bound: i32) -> i32 {
if (bound & bound.wrapping_sub(1)) == 0 {
(i64::from(bound).wrapping_mul(i64::from(self.next(31))) >> 31) as i32
} else {
loop {
let i = self.next(31);
let j = i % bound;
if (i.wrapping_sub(j).wrapping_add(bound.wrapping_sub(1))) >= 0 {
return j;
}
}
}
}
fn next_i64(&mut self) -> i64 {
let i = self.next_i32();
let j = self.next_i32();
(i64::from(i) << 32).wrapping_add(i64::from(j))
}
fn next_bool(&mut self) -> bool {
self.next(1) != 0
}
fn next_f32(&mut self) -> f32 {
self.next(24) as f32 * 5.9604645E-8f32
}
fn next_f64(&mut self) -> f64 {
let i = self.next(26);
let j = self.next(27);
let l = (i64::from(i) << 27).wrapping_add(i64::from(j));
l as f64 * f64::from(1.110223E-16f32)
}
fn next_gaussian(&mut self) -> f64 {
self.calculate_gaussian()
}
}
/// A splitter for creating derived legacy random generators.
///
/// This struct allows for deterministic creation of multiple independent
/// random generators from a single seed, which is essential for
/// parallelizable world generation.
#[derive(Clone)]
pub struct LegacySplitter {
/// The base seed for deriving new generators.
seed: u64,
}
impl LegacySplitter {
/// Creates a new legacy splitter with the given seed.
///
/// # Arguments
/// - `seed` – The base seed value.
///
/// # Returns
/// A new `LegacySplitter` instance.
const fn new(seed: u64) -> Self {
Self { seed }
}
}
impl RandomDeriverImpl for LegacySplitter {
fn split_string(&self, seed: &str) -> RandomGenerator {
let string_hash = java_string_hash(seed);
RandomGenerator::Legacy(LegacyRand::from_seed((string_hash as u64) ^ self.seed))
}
fn split_u64(&self, seed: u64) -> RandomGenerator {
RandomGenerator::Legacy(LegacyRand::from_seed(seed))
}
fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator {
let pos_hash = hash_block_pos(x, y, z);
RandomGenerator::Legacy(LegacyRand::from_seed((pos_hash as u64) ^ self.seed))
}
}
#[cfg(test)]
mod test {
use crate::random::{RandomDeriver, RandomDeriverImpl, RandomImpl};
use super::LegacyRand;
#[test]
fn next_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [
-1155484576,
-723955400,
1033096058,
-1690734402,
-1557280266,
1327362106,
-1930858313,
502539523,
-1728529858,
-938301587,
];
for value in values {
assert_eq!(rand.next_i32(), value);
}
}
#[test]
fn next_bounded_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [0, 13, 4, 2, 5, 8, 11, 6, 9, 14];
for value in values {
assert_eq!(rand.next_bounded_i32(0xf), value);
}
let mut rand = LegacyRand::from_seed(0);
for _ in 0..10 {
assert_eq!(rand.next_bounded_i32(1), 0);
}
let mut rand = LegacyRand::from_seed(0);
let values = [1, 1, 0, 1, 1, 0, 1, 0, 1, 1];
for value in values {
assert_eq!(rand.next_bounded_i32(2), value);
}
}
#[test]
fn next_inbetween_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [1, 5, 2, 12, 12, 6, 12, 10, 4, 3];
for value in values {
assert_eq!(rand.next_inbetween_i32(1, 12), value);
}
}
#[test]
fn next_inbetween_exclusive_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [1, 7, 9, 6, 7, 3, 3, 7, 3, 1];
for value in values {
assert_eq!(rand.next_inbetween_i32_exclusive(1, 12), value);
}
}
#[test]
fn next_f64() {
let mut rand = LegacyRand::from_seed(0);
let values = [
0.730967787376657,
0.24053641567148587,
0.6374174253501083,
0.5504370051176339,
0.5975452777972018,
0.3332183994766498,
0.3851891847407185,
0.984841540199809,
0.8791825178724801,
0.9412491794821144,
];
for value in values {
assert_eq!(rand.next_f64(), value);
}
}
#[test]
fn next_f32() {
let mut rand = LegacyRand::from_seed(0);
let values: [f32; 10] = [
0.73096776, 0.831441, 0.24053639, 0.6063452, 0.6374174, 0.30905056, 0.550437,
0.1170066, 0.59754527, 0.7815346,
];
for value in values {
assert_eq!(rand.next_f32(), value);
}
}
#[test]
fn next_i64() {
let mut rand = LegacyRand::from_seed(0);
let values: [i64; 10] = [
-4962768465676381896,
4437113781045784766,
-6688467811848818630,
-8292973307042192125,
-7423979211207825555,
6146794652083548235,
7105486291024734541,
-279624296851435688,
-2228689144322150137,
-1083761183081836303,
];
for value in values {
assert_eq!(rand.next_i64(), value);
}
}
#[test]
fn next_bool() {
let mut rand = LegacyRand::from_seed(0);
let values = [
true, true, false, true, true, false, true, false, true, true,
];
for value in values {
assert_eq!(rand.next_bool(), value);
}
}
#[test]
fn next_gaussian() {
let mut rand = LegacyRand::from_seed(0);
let values = [
0.8025330637390305,
-0.9015460884175122,
2.080920790428163,
0.7637707684364894,
0.9845745328825128,
-1.6834122587673428,
-0.027290262907887285,
0.11524570286202315,
-0.39016704137993774,
-0.643388813126449,
];
for value in values {
assert_eq!(rand.next_gaussian(), value);
}
}
#[test]
fn next_triangular() {
let mut rand = LegacyRand::from_seed(0);
let values = [
124.52156858525856,
104.34902101162372,
113.2163439160276,
70.01738222704547,
96.89666691951828,
107.30284075808541,
106.16817675813144,
79.11264482608078,
73.96721613927062,
81.72419521080646,
];
for value in values {
assert_eq!(rand.next_triangular(100f64, 50f64), value);
}
}
#[test]
fn split() {
let mut original_rand = LegacyRand::from_seed(0);
assert_eq!(original_rand.next_i64(), -4962768465676381896i64);
let mut original_rand = LegacyRand::from_seed(0);
{
if let RandomDeriver::Legacy(splitter) = original_rand.next_splitter() {
assert_eq!(splitter.seed, (-4962768465676381896i64) as u64);
let mut rand = splitter.split_string("minecraft:offset");
assert_eq!(rand.next_i32(), 103436829);
} else {
panic!("Expected RandomDeriver::Legacy");
}
}
let mut original_rand = LegacyRand::from_seed(0);
let mut new_rand = original_rand.split();
{
let splitter = new_rand.next_splitter();
let mut rand1 = splitter.split_string("TEST STRING");
assert_eq!(rand1.next_i32(), -1170413697);
let mut rand2 = splitter.split_u64(10);
assert_eq!(rand2.next_i32(), -1157793070);
let mut rand3 = splitter.split_pos(1, 11, -111);
assert_eq!(rand3.next_i32(), -1213890343);
};
assert_eq!(original_rand.next_i32(), 1033096058);
assert_eq!(new_rand.next_i32(), -888301832);
}
}