use xxhash_rust::xxh3::xxh3_64_with_seed;
use crate::coord::WorldPos;
#[inline]
pub fn world_seed(pos: WorldPos, salt: u32) -> u64 {
let mut bytes = [0u8; 16];
bytes[0..8].copy_from_slice(&pos.x.to_bits().to_le_bytes());
bytes[8..16].copy_from_slice(&pos.y.to_bits().to_le_bytes());
xxh3_64_with_seed(&bytes, salt as u64)
}
#[inline]
pub fn cell_seed(i: i64, j: i64, salt: u32) -> u64 {
let mut bytes = [0u8; 16];
bytes[0..8].copy_from_slice(&i.to_le_bytes());
bytes[8..16].copy_from_slice(&j.to_le_bytes());
xxh3_64_with_seed(&bytes, salt as u64)
}
#[inline]
pub fn next_unit(state: &mut u64) -> f32 {
*state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let x = (*state >> 32) as u32;
(x as f32) * (1.0 / (1u64 << 32) as f32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same_position_same_seed() {
let a = world_seed(WorldPos::new(0.123, 0.456), 1);
let b = world_seed(WorldPos::new(0.123, 0.456), 1);
assert_eq!(a, b);
}
#[test]
fn different_salt_different_seed() {
let pos = WorldPos::new(0.5, 0.5);
assert_ne!(world_seed(pos, 1), world_seed(pos, 2));
}
#[test]
fn cell_seed_is_stable_and_distinct() {
assert_eq!(cell_seed(-7, 12, 3), cell_seed(-7, 12, 3));
assert_ne!(cell_seed(-7, 12, 3), cell_seed(12, -7, 3));
assert_ne!(cell_seed(-7, 12, 3), cell_seed(-7, 12, 4));
}
#[test]
fn next_unit_stays_in_range() {
let mut state = cell_seed(1, 2, 0);
for _ in 0..1000 {
let u = next_unit(&mut state);
assert!((0.0..1.0).contains(&u), "u = {u}");
}
}
#[test]
fn next_unit_spans_the_unit_interval() {
let mut state = cell_seed(7, 9, 1);
let n = 20_000;
let mut sum = 0.0f64;
let mut lo = f32::MAX;
let mut hi = f32::MIN;
for _ in 0..n {
let u = next_unit(&mut state);
sum += u as f64;
lo = lo.min(u);
hi = hi.max(u);
}
assert!(lo < 0.01, "never drew near 0 (min {lo})");
assert!(hi > 0.99, "never drew near 1 (max {hi})");
let mean = sum / n as f64;
assert!((mean - 0.5).abs() < 0.02, "mean {mean}");
}
}