/** Seeded RNG (mulberry32) so every synthesized dataset is reproducible * from (base seed, run index) alone. */ export type Rng = () => number; export function mulberry32(seed: number): Rng { let a = seed >>> 0; return () => { 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; }; } /** Fisher-Yates shuffle in place. */ export function shuffle(arr: T[], rng: Rng): T[] { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(rng() * (i + 1)); const t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return arr; }