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:
2026-05-23 11:51:09 +02:00
co-authored by Claude Opus 4.7
parent f4f43e826c
commit de9142ce37
7 changed files with 222 additions and 70 deletions
+1 -10
View File
@@ -2,6 +2,7 @@
// Injectable ChaCha RNG resource for deterministic replay (D-030)
// Ensures same seed produces same outcomes
use crate::seed::splitmix64;
use bevy_ecs::prelude::*;
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
@@ -61,16 +62,6 @@ impl EntityRng {
}
}
/// Splitmix64 mixing function — good avalanche properties for seed derivation.
/// Ensures that similar inputs (e.g., consecutive StableIds) produce
/// uncorrelated output seeds.
fn splitmix64(mut x: u64) -> u64 {
x = x.wrapping_add(0x9e3779b97f4a7c15);
x = (x ^ (x >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
x = (x ^ (x >> 27)).wrapping_mul(0x94d049bb133111eb);
x ^ (x >> 31)
}
#[cfg(test)]
mod tests {
use super::*;