/** A 32-bit unsigned integer seed. */ export type Seed = number; export interface Rng { /** Float in [0, 1). */ next(): number; /** Integer in [min, max], inclusive. */ int(min: number, max: number): number; /** A uniformly chosen element. Throws on an empty list. */ pick(list: readonly T[]): T; } /** Seeded PRNG (mulberry32). The same seed always yields the same sequence. */ export function createRng(seed: Seed): Rng { let state = seed >>> 0; const next = () => { state = (state + 0x6d2b79f5) >>> 0; let t = state; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; return { next, int(min, max) { return min + Math.floor(next() * (max - min + 1)); }, pick(list) { if (list.length === 0) throw new Error("Cannot pick from an empty list"); return list[Math.floor(next() * list.length)]!; }, }; } /** Derives an independent child seed from a parent seed and a label (FNV-1a, then a final mix). */ export function deriveSeed(parent: Seed, label: string): Seed { let hash = 0x811c9dc5 ^ (parent >>> 0); for (let i = 0; i < label.length; i++) { hash ^= label.charCodeAt(i); hash = Math.imul(hash, 0x01000193); } hash ^= hash >>> 16; hash = Math.imul(hash, 0x85ebca6b); hash ^= hash >>> 13; hash = Math.imul(hash, 0xc2b2ae35); hash ^= hash >>> 16; return hash >>> 0; }