Something went wrong. Try again.
HEXWORK - A Lantern in the Deep: 3D ASCII terminal roguelike RPG in Rust.
Entirely designed and built by Muse Spark 1.3 to evaluate if I like the model.
Something went wrong. Try again.
3.2 kB · 96 lines
Rust
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697//! Seeded deterministic RNG: Xoshiro256** master stream with//! domain-tagged sub-streams (`seed ^ domain_tag`), so adding a particle//! never reschedules combat (docs/04 §4.2).
/// Invalid canonical Xoshiro256** state.#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub struct InvalidRngState;
/// Xoshiro256** — small, fast, fully deterministic across platforms.#[derive(Clone, Debug, PartialEq, Eq)]pub struct Rng { s: [u64; 4],}
impl Rng { /// Expand any 64-bit seed (via SplitMix64) into full state. pub fn from_seed(seed: u64) -> Self { let mut z = seed.wrapping_add(0x9E37_79B9_7F4A_7C15); let mut next = move || { z = z.wrapping_add(0x9E37_79B9_7F4A_7C15); let mut t = z; t = (t ^ (t >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); t = (t ^ (t >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); t ^ (t >> 31) }; Self { s: [next(), next(), next(), next()], } }
/// Restore canonical internal words, rejecting Xoshiro's absorbing state. pub const fn from_canonical_state(state: [u64; 4]) -> Result<Self, InvalidRngState> { if state[0] == 0 && state[1] == 0 && state[2] == 0 && state[3] == 0 { Err(InvalidRngState) } else { Ok(Self { s: state }) } }
/// Canonical internal words for persistence/replay hashing. Their order is /// the Xoshiro256** protocol order and must not be changed silently. #[must_use] pub const fn canonical_state(&self) -> [u64; 4] { self.s }
pub fn next_u64(&mut self) -> u64 { let result = self.s[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9); let t = self.s[1] << 17; self.s[2] ^= self.s[0]; self.s[3] ^= self.s[1]; self.s[1] ^= self.s[2]; self.s[0] ^= self.s[3]; self.s[2] ^= t; self.s[3] = self.s[3].rotate_left(45); result }
/// Unbiased (Lemire multiply-high) bounded draw. pub fn below(&mut self, n: u64) -> u64 { assert!(n > 0, "rng bound must be nonzero"); (((self.next_u64() as u128) * (n as u128)) >> 64) as u64 }
/// Fork an independent sub-stream for one subsystem domain. /// Domain tags are fixed constants per subsystem (see `domains`). pub fn fork(&mut self, domain_tag: u64) -> Rng { let mixed = self.next_u64() ^ domain_tag.rotate_left(32); Rng::from_seed(mixed) }}
/// Fixed domain tags — changing these reshuffles history, so treat as protocol.pub mod domains { pub const FLUIDS: u64 = 0xF101D5; pub const GAS: u64 = 0x6A5; pub const COMBAT: u64 = 0xC0B17; pub const WORLDGEN: u64 = 0xB0D1E5; pub const GOSSIP: u64 = 0x605519; pub const WORLDGEN_SEED: u64 = 0xBE550EED;}
#[cfg(test)]mod tests { use super::*;
#[test] fn canonical_state_restore_rejects_only_all_zero() { assert_eq!(Rng::from_canonical_state([0; 4]), Err(InvalidRngState)); let state = [0, 0, 0, 1]; assert_eq!( Rng::from_canonical_state(state).unwrap().canonical_state(), state ); }}