// Package rng provides a small deterministic PRNG (xoshiro256**) seeded via // splitmix64. Streams are derived from a seed plus labels so that independent // parts of the simulation never share (and thus never perturb) each other's // random sequence. package rng import ( "math/bits" "wh/fixed" ) type R struct{ s [4]uint64 } func splitmix(x *uint64) uint64 { *x += 0x9e3779b97f4a7c15 z := *x z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9 z = (z ^ (z >> 27)) * 0x94d049bb133111eb return z ^ (z >> 31) } // New returns a generator for the given seed. func New(seed uint64) *R { r := &R{} for i := range r.s { r.s[i] = splitmix(&seed) } return r } // Derive returns a generator for an independent stream identified by seed and // labels, e.g. Derive(worldSeed, day, shipID). func Derive(seed uint64, labels ...uint64) *R { h := seed for _, l := range labels { h ^= l + 0x9e3779b97f4a7c15 + (h << 6) + (h >> 2) splitmix(&h) } return New(h) } func (r *R) Uint64() uint64 { s := &r.s res := bits.RotateLeft64(s[1]*5, 7) * 9 t := s[1] << 17 s[2] ^= s[0] s[3] ^= s[1] s[1] ^= s[2] s[0] ^= s[3] s[2] ^= t s[3] = bits.RotateLeft64(s[3], 45) return res } // Intn returns a uniform value in [0, n). n must be > 0. func (r *R) Intn(n uint64) uint64 { hi, lo := bits.Mul64(r.Uint64(), n) if lo < n { thresh := -n % n for lo < thresh { hi, lo = bits.Mul64(r.Uint64(), n) } } return hi } // Fixed returns a uniform value in [0, 1). func (r *R) Fixed() fixed.F { return fixed.F(r.Uint64() >> 32) } // FixedRange returns a uniform value in [lo, hi). func (r *R) FixedRange(lo, hi fixed.F) fixed.F { return lo + r.Fixed().Mul(hi-lo) }