405 lines
11 KiB
Rust
405 lines
11 KiB
Rust
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);
|
||
}
|
||
}
|