feat(sim): add deterministic RNG and crash curve
Seed expansion uses SplitMix64 so all 32 seed bytes affect the stream; copying the seed directly into xoshiro state left the first draw dependent only on bytes 8-15. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
51
pkg/sim/rng_test.go
Normal file
51
pkg/sim/rng_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package sim
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRNGIsDeterministic(t *testing.T) {
|
||||
var seed [32]byte
|
||||
copy(seed[:], "quantum-arcade-test-seed")
|
||||
a, b := NewRNG(seed), NewRNG(seed)
|
||||
for i := 0; i < 1000; i++ {
|
||||
if x, y := a.Uint64(), b.Uint64(); x != y {
|
||||
t.Fatalf("iteration %d: %d != %d", i, x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifferentSeedsDiverge(t *testing.T) {
|
||||
var s1, s2 [32]byte
|
||||
copy(s1[:], "seed-one")
|
||||
copy(s2[:], "seed-two")
|
||||
a, b := NewRNG(s1), NewRNG(s2)
|
||||
same := 0
|
||||
for i := 0; i < 100; i++ {
|
||||
if a.Uint64() == b.Uint64() {
|
||||
same++
|
||||
}
|
||||
}
|
||||
if same > 1 {
|
||||
t.Fatalf("streams collided %d times in 100 draws", same)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZeroSeedDoesNotDegenerate(t *testing.T) {
|
||||
var seed [32]byte // all zeros
|
||||
r := NewRNG(seed)
|
||||
first := r.Uint64()
|
||||
if first == 0 && r.Uint64() == 0 {
|
||||
t.Fatal("zero seed produced a degenerate all-zero stream")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnitInRange(t *testing.T) {
|
||||
var seed [32]byte
|
||||
seed[0] = 9
|
||||
r := NewRNG(seed)
|
||||
for i := 0; i < 10000; i++ {
|
||||
u := r.Unit()
|
||||
if u < 0 || u >= 1<<32 {
|
||||
t.Fatalf("Unit() = %v out of [0,1)", u)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user