namespace Theriapolis.Core.Util; /// /// SplitMix64-based pseudo-random number generator with named sub-stream support. /// All game randomness must go through this class — no new System.Random() anywhere. /// public sealed class SeededRng { private ulong _state; public SeededRng(ulong seed) { // Mix the seed to avoid bad low-entropy states _state = seed == 0 ? 0x9e3779b97f4a7c15UL : seed; // Warm up the state NextUInt64(); NextUInt64(); } /// Create a sub-stream for a specific subsystem using the world seed and a named tag constant. public static SeededRng ForSubsystem(ulong worldSeed, ulong subsystemTag) => new(worldSeed ^ subsystemTag); public ulong NextUInt64() { // SplitMix64 step _state += 0x9e3779b97f4a7c15UL; ulong z = _state; z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9UL; z = (z ^ (z >> 27)) * 0x94d049bb133111ebUL; return z ^ (z >> 31); } public uint NextUInt32() => (uint)(NextUInt64() >> 32); /// Returns a double in [0, 1). public double NextDouble() => (NextUInt64() >> 11) * (1.0 / (1UL << 53)); /// Returns a float in [0, 1). public float NextFloat() => (float)NextDouble(); /// Returns a float in [min, max). public float NextFloat(float min, float max) => min + NextFloat() * (max - min); /// Returns an int in [min, max). public int NextInt(int min, int max) { if (max <= min) return min; return min + (int)(NextUInt64() % (ulong)(max - min)); } /// Returns an int in [0, max). public int NextInt(int max) => NextInt(0, max); /// Returns true with probability p (0–1). public bool NextBool(double p = 0.5) => NextDouble() < p; /// Shuffles a span in place using Fisher–Yates. public void Shuffle(Span span) { for (int i = span.Length - 1; i > 0; i--) { int j = NextInt(0, i + 1); (span[i], span[j]) = (span[j], span[i]); } } }