Files
Halcyon/rng/rng.go
T
2026-09-19 20:21:47 +02:00

75 lines
1.6 KiB
Go

// 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)
}