feat: seeded RNG with per-label deterministic rolls
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
// FNV-1a 32-bit string hash.
|
||||||
|
export function hash32(str) {
|
||||||
|
let h = 0x811c9dc5;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
h ^= str.charCodeAt(i);
|
||||||
|
h = Math.imul(h, 0x01000193);
|
||||||
|
}
|
||||||
|
return h >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mulberry32 PRNG: returns function yielding uniforms in [0,1).
|
||||||
|
export function makeRng(seed) {
|
||||||
|
let a = seed >>> 0;
|
||||||
|
return function () {
|
||||||
|
a |= 0;
|
||||||
|
a = (a + 0x6d2b79f5) | 0;
|
||||||
|
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
||||||
|
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||||
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// One deterministic uniform in [0,1) for a (seed, label) pair.
|
||||||
|
// This is the whole hidden-variable mechanism: outcomes are fixed
|
||||||
|
// the moment their label exists, revealed whenever first asked for.
|
||||||
|
export function rollFor(seed, label) {
|
||||||
|
return makeRng(hash32(seed + ':' + label))();
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { hash32, makeRng, rollFor } from '../src/sim/rng.js';
|
||||||
|
|
||||||
|
describe('rng', () => {
|
||||||
|
it('hash32 is deterministic and spreads', () => {
|
||||||
|
expect(hash32('abc')).toBe(hash32('abc'));
|
||||||
|
expect(hash32('abc')).not.toBe(hash32('abd'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makeRng produces a deterministic sequence in [0,1)', () => {
|
||||||
|
const a = makeRng(42), b = makeRng(42);
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const v = a();
|
||||||
|
expect(v).toBe(b());
|
||||||
|
expect(v).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(v).toBeLessThan(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rollFor depends on seed and label, independently', () => {
|
||||||
|
expect(rollFor('s1', 'x')).toBe(rollFor('s1', 'x'));
|
||||||
|
expect(rollFor('s1', 'x')).not.toBe(rollFor('s1', 'y'));
|
||||||
|
expect(rollFor('s1', 'x')).not.toBe(rollFor('s2', 'x'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rollFor is roughly uniform', () => {
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < 2000; i++) sum += rollFor('u', `l${i}`);
|
||||||
|
expect(sum / 2000).toBeGreaterThan(0.45);
|
||||||
|
expect(sum / 2000).toBeLessThan(0.55);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user