chunkgen 0.2.0

Minecraft world generation library
Documentation
use std::mem;

use super::gaussian::MarsagliaPolarGaussian;
use super::name_hash::NameHash;
use super::{RandomFactory, RandomSource, get_seed};

const GOLDEN_RATIO_64: u64 = 0x9E37_79B9_7F4A_7C15;
const SILVER_RATIO_64: u64 = 0x6A09_E667_F3BC_C909;

pub struct XoroshiroRandomSource {
    seed_lo: u64,
    seed_hi: u64,
    gaussian: MarsagliaPolarGaussian,
}

impl XoroshiroRandomSource {
    pub const fn from_seed(seed: u64) -> Self {
        let (lo, hi) = Self::upgrade_seed_to_128_bit(seed);
        let lo = mix_stafford_13(lo);
        let hi = mix_stafford_13(hi);
        Self::new(lo, hi)
    }

    const fn new(lo: u64, hi: u64) -> Self {
        let (lo, hi) = if (lo | hi) == 0 {
            (GOLDEN_RATIO_64, SILVER_RATIO_64)
        } else {
            (lo, hi)
        };
        Self {
            seed_lo: lo,
            seed_hi: hi,
            gaussian: MarsagliaPolarGaussian::new(),
        }
    }

    const fn upgrade_seed_to_128_bit(seed: u64) -> (u64, u64) {
        let lo = seed ^ SILVER_RATIO_64;
        let hi = lo.wrapping_add(GOLDEN_RATIO_64);
        (lo, hi)
    }

    const fn next(&mut self, bits: u64) -> u64 {
        self.next_random() >> (64 - bits)
    }

    const fn next_random(&mut self) -> u64 {
        let s0 = self.seed_lo;
        let mut s1 = self.seed_hi;
        let result = s0.wrapping_add(s1).rotate_left(17).wrapping_add(s0);
        s1 ^= s0;
        self.seed_lo = s0.rotate_left(49) ^ s1 ^ (s1 << 21);
        self.seed_hi = s1.rotate_left(28);
        result
    }

    pub const fn set_seed(&mut self, seed: i64) {
        *self = Self::from_seed(seed as u64);
    }

    pub const fn fork(&mut self) -> Self {
        Self::new(self.next_random(), self.next_random())
    }

    pub const fn next_i32(&mut self) -> i32 {
        self.next_random() as i32
    }

    pub fn next_i32_bounded(&mut self, bound: i32) -> i32 {
        let mut random_bits = (self.next_i32() as u64) & 0xFFFF_FFFF;
        let mut multiplied_random_bits = random_bits.wrapping_mul(bound as u64);
        let mut fractional_part = multiplied_random_bits & 0xFFFF_FFFF;
        if fractional_part < bound as u64 {
            let unbiased_buckets_start_index =
                u64::from(((!bound as u32).wrapping_add(1)) % bound as u32);
            while fractional_part < unbiased_buckets_start_index {
                random_bits = (self.next_i32() as u64) & 0xFFFF_FFFF;
                multiplied_random_bits = random_bits.wrapping_mul(bound as u64);
                fractional_part = multiplied_random_bits & 0xFFFF_FFFF;
            }
        }
        let integer_part = multiplied_random_bits >> 32;
        integer_part as i32
    }

    pub fn next_i32_between(&mut self, min: i32, max: i32) -> i32 {
        self.next_i32_bounded(max - min + 1) + min
    }

    pub fn next_i32_between_exclusive(&mut self, min: i32, max: i32) -> i32 {
        min + self.next_i32_bounded(max - min)
    }

    pub const fn next_i64(&mut self) -> i64 {
        self.next_random() as i64
    }

    pub fn next_f32(&mut self) -> f32 {
        self.next(24) as f32 * 5.960_464_5e-8
    }

    pub fn next_f64(&mut self) -> f64 {
        self.next(53) as f64 * f64::from(1.110_223e-16_f32)
    }

    pub const fn next_bool(&mut self) -> bool {
        (self.next_random() & 1) != 0
    }

    pub fn next_gaussian(&mut self) -> f64 {
        let mut gaussian = mem::take(&mut self.gaussian);
        let value = gaussian.next(|| self.next_f64());
        self.gaussian = gaussian;
        value
    }

    pub fn triangle(&mut self, min: f64, max: f64) -> f64 {
        min + max * (self.next_f64() - self.next_f64())
    }

    pub fn triangle_f32(&mut self, min: f32, max: f32) -> f32 {
        min + max * (self.next_f32() - self.next_f32())
    }

    pub const fn next_positional(&mut self) -> RandomFactory {
        RandomFactory::Xoroshiro(XoroshiroRandomFactory {
            seed_lo: self.next_random(),
            seed_hi: self.next_random(),
        })
    }

    pub fn consume_count(&mut self, count: i32) {
        for _ in 0..count {
            self.next_i32();
        }
    }
}

const fn mix_stafford_13(z: u64) -> u64 {
    let z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
    let z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
    z ^ (z >> 31)
}

#[derive(Clone)]
pub struct XoroshiroRandomFactory {
    seed_lo: u64,
    seed_hi: u64,
}

impl XoroshiroRandomFactory {
    pub fn at(&self, x: i32, y: i32, z: i32) -> RandomSource {
        let positional_seed = get_seed(x, y, z) as u64;
        let random_seed = positional_seed ^ self.seed_lo;

        RandomSource::Xoroshiro(XoroshiroRandomSource::new(random_seed, self.seed_hi))
    }

    pub const fn with_hash_of(&self, hash: &NameHash) -> RandomSource {
        let [hash_lo, hash_hi] = hash.md5_digest;
        RandomSource::Xoroshiro(XoroshiroRandomSource::new(
            hash_lo ^ self.seed_lo,
            hash_hi ^ self.seed_hi,
        ))
    }

    pub const fn with_seed(&self, seed: u64) -> RandomSource {
        RandomSource::Xoroshiro(XoroshiroRandomSource::new(
            seed ^ self.seed_lo,
            seed ^ self.seed_hi,
        ))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const MIX_STAFFORD_13_CASES: &[(u64, i64)] = &[
        (0, 0),
        (1, 6238072747940578789),
        (64, -8456553050427055661),
        (4096, -1125827887270283392),
        (262144, -120227641678947436),
        (16777216, 6406066033425044679),
        (1073741824, 3143522559155490559),
        (16, -2773008118984693571),
        (1024, 8101005175654470197),
        (65536, -3551754741763842827),
    ];

    #[test]
    fn mix_stafford_13_matches_java() {
        for &(input, expected) in MIX_STAFFORD_13_CASES {
            assert_eq!(mix_stafford_13(input), expected as u64);
        }
    }

    #[test]
    fn next_i32_sequence_matches_java() {
        const EXPECTED: [i32; 5] = [-160476802, 781697906, 653572596, 1337520923, -505875771];
        let mut rng = XoroshiroRandomSource::from_seed(0);
        for &expected in &EXPECTED {
            assert_eq!(rng.next_i32(), expected);
        }
    }

    #[test]
    fn next_i32_bounded_matches_java() {
        const SMALL: [i32; 5] = [9, 1, 1, 3, 8];
        let mut rng = XoroshiroRandomSource::from_seed(0);
        for &expected in &SMALL {
            assert_eq!(rng.next_i32_bounded(10), expected);
        }
    }

    #[test]
    fn next_i32_between_uses_inclusive_bound() {
        let mut inclusive = XoroshiroRandomSource::from_seed(0);
        let mut exclusive = XoroshiroRandomSource::from_seed(0);
        assert_eq!(
            inclusive.next_i32_between(50, 100),
            exclusive.next_i32_between_exclusive(50, 100) + 1
        );
    }

    #[test]
    fn next_f64_matches_java() {
        const EXPECTED: [f64; 3] = [0.16474369376959186, 0.7997457290026366, 0.2511961888876212];
        let mut rng = XoroshiroRandomSource::from_seed(0);
        for &expected in &EXPECTED {
            assert_eq!(rng.next_f64(), expected);
        }
    }

    #[test]
    fn next_f32_matches_java() {
        const EXPECTED: [f32; 3] = [0.16474366, 0.7997457, 0.25119615];
        let mut rng = XoroshiroRandomSource::from_seed(0);
        for &expected in &EXPECTED {
            assert_eq!(rng.next_f32(), expected);
        }
    }

    #[test]
    fn next_i64_matches_java() {
        const EXPECTED: [i64; 3] = [
            3038984756725240190,
            -3694039286755638414,
            4633751808701151732,
        ];
        let mut rng = XoroshiroRandomSource::from_seed(0);
        for &expected in &EXPECTED {
            assert_eq!(rng.next_i64(), expected);
        }
    }

    #[test]
    fn next_bool_matches_java() {
        const EXPECTED: [bool; 6] = [false, false, false, true, true, true];
        let mut rng = XoroshiroRandomSource::from_seed(0);
        for &expected in &EXPECTED {
            assert_eq!(rng.next_bool(), expected);
        }
    }

    #[test]
    fn next_gaussian_matches_java() {
        const EXPECTED: [f64; 3] = [
            -0.48540690699780015,
            0.43399227545320296,
            -0.3283265251019599,
        ];
        let mut rng = XoroshiroRandomSource::from_seed(0);
        for &expected in &EXPECTED {
            assert_eq!(rng.next_gaussian(), expected);
        }
    }

    #[test]
    fn triangle_matches_java() {
        const EXPECTED: [f64; 3] = [6.824989823834776, 10.670356470906125, 6.71516367803936];
        let mut rng = XoroshiroRandomSource::from_seed(0);
        for &expected in &EXPECTED {
            assert_eq!(rng.triangle(10.0, 5.0), expected);
        }
    }

    #[test]
    fn fork_creates_independent_rng() {
        let mut rng = XoroshiroRandomSource::from_seed(0);
        let mut forked = rng.fork();

        assert_eq!(forked.next_i32(), 542195535);
        assert_eq!(rng.next_i32(), 653572596);
    }

    #[test]
    fn positional_factory_produces_java_matching_sources() {
        let mut rng = XoroshiroRandomSource::from_seed(0);
        let mut forked = rng.fork();
        assert_eq!(forked.next_i32(), 542195535);

        let factory = forked.next_positional();

        let RandomSource::Xoroshiro(mut rand1) =
            factory.with_hash_of(&NameHash::new("TEST STRING"))
        else {
            panic!("expected Xoroshiro variant");
        };
        assert_eq!(rand1.next_i32(), -641435713);

        let RandomSource::Xoroshiro(mut rand2) = factory.with_seed(42069) else {
            panic!("expected Xoroshiro variant");
        };
        assert_eq!(rand2.next_i32(), -340700677);

        let RandomSource::Xoroshiro(mut rand3) = factory.at(1337, 80085, -69420) else {
            panic!("expected Xoroshiro variant");
        };
        assert_eq!(rand3.next_i32(), 790449132);

        assert_eq!(rng.next_i32(), 653572596);
        assert_eq!(forked.next_i32(), 435917842);
    }

    #[test]
    fn zero_seed_falls_back_to_golden_silver_ratio() {
        let mut rng = XoroshiroRandomSource::new(0, 0);
        assert_eq!(rng.next_i64(), 6807859099481836695);
    }
}