feat(simulation): SeedChain seed-derivation primitive in server/src/seed.rs (#952, D-224)
New top-level seed module that owns every deterministic-RNG primitive: - splitmix64 — the one canonical mixer (was duplicated as a private fn in simulation/rng.rs; EntityRng now imports the shared one, no behavior change) - AtlasRng — moved here from atlas/rng.rs (it is a generation-RNG primitive, not atlas-specific); atlas now depends on seed, not the reverse - SeedDomain — append-only domain tags (Body/Layer1Topography/Layer3Settlement/ Layer4Quarter/Block/Npc) for collision-proof per-domain seed separation - SeedChain — root(world_seed) → derive(domain, id) → atlas_rng()/seed(), per the D-224 formula splitmix64(self ^ splitmix64(domain)) ^ splitmix64(id) Structural move only — SeedChain is not yet threaded through the cascade callers (skeleton_gen still uses ad-hoc wrapping_add pre-mixing); that is the next step. Unit tests cover the splitmix64 known-vector, avalanche, determinism, domain/id separation, and chain composition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
//! **Determinism (D-010, D-194):** Integer weights throughout. No f32 in the
|
||||
//! district count computation. Seed-driven noise uses seeded RNG.
|
||||
|
||||
use crate::atlas::rng::AtlasRng;
|
||||
use crate::seed::AtlasRng;
|
||||
use crate::simulation::generator::{DistrictType, PoliticalArchetype};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -12,7 +12,6 @@ pub mod features;
|
||||
pub mod gen_queue;
|
||||
pub mod heightmap;
|
||||
pub mod layer1;
|
||||
pub mod rng;
|
||||
pub mod skeleton_gen;
|
||||
pub mod subbiome;
|
||||
pub mod tile_condition;
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
//! Seeded LCG for deterministic generation (D-010).
|
||||
//!
|
||||
//! Shared by all atlas generation modules that need seeded randomness.
|
||||
//! Uses Knuth's LCG parameters — integer-only arithmetic, no f32, D-010 compliant.
|
||||
//!
|
||||
//! Callers are responsible for any seed pre-mixing before calling `AtlasRng::new`.
|
||||
|
||||
/// Seeded linear congruential generator (D-010).
|
||||
pub struct AtlasRng {
|
||||
state: u64,
|
||||
}
|
||||
|
||||
impl AtlasRng {
|
||||
/// Create a new RNG from a pre-mixed seed.
|
||||
///
|
||||
/// Callers must ensure the seed is non-degenerate (avoid passing 0 directly
|
||||
/// if the seed could realistically be 0 — add a constant before calling).
|
||||
pub fn new(seed: u64) -> Self {
|
||||
Self { state: seed }
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
self.state = self
|
||||
.state
|
||||
.wrapping_mul(6364136223846793005)
|
||||
.wrapping_add(1442695040888963407);
|
||||
self.state
|
||||
}
|
||||
|
||||
/// Next pseudorandom `u32` (top 31 bits of the LCG state).
|
||||
pub fn next_u32(&mut self) -> u32 {
|
||||
(self.next_u64() >> 33) as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deterministic_sequence() {
|
||||
let mut a = AtlasRng::new(42);
|
||||
let mut b = AtlasRng::new(42);
|
||||
for _ in 0..100 {
|
||||
assert_eq!(a.next_u32(), b.next_u32());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_seeds_differ() {
|
||||
let mut a = AtlasRng::new(1);
|
||||
let mut b = AtlasRng::new(2);
|
||||
let vals_a: Vec<u32> = (0..10).map(|_| a.next_u32()).collect();
|
||||
let vals_b: Vec<u32> = (0..10).map(|_| b.next_u32()).collect();
|
||||
assert_ne!(vals_a, vals_b);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
use crate::atlas::block_irregularity::block_irregularity;
|
||||
use crate::atlas::district_mix::{compute_district_mix, population_tier};
|
||||
use crate::atlas::rng::AtlasRng;
|
||||
use crate::seed::AtlasRng;
|
||||
use crate::simulation::generator::{
|
||||
BlockPlacement, BlockSkeleton, CityGenerationContext, ComplexityTier, DistrictId,
|
||||
DistrictLayoutMode, DistrictSkeleton, DistrictType, MultiBlockReservation, PoliticalArchetype,
|
||||
|
||||
Reference in New Issue
Block a user