// Deterministic RNG (mulberry32). The generator state lives inside GameState so // that replaying the same commands from the same seed reproduces the game // exactly — the foundation for async play, spectating, and replays. export interface RngState { /** Current 32-bit mulberry32 state; advances by one per value drawn. */ readonly a: number; } export function createRng(seed: number): RngState { return { a: seed >>> 0 }; } /** Draw the next float in [0, 1). Returns the value and the advanced state. */ export function nextFloat(state: RngState): [number, RngState] { const a = (state.a + 0x6d2b79f5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; const value = ((t ^ (t >>> 14)) >>> 0) / 4294967296; return [value, { a: a >>> 0 }]; } /** Draw an integer in [0, n). */ export function nextInt(state: RngState, n: number): [number, RngState] { const [f, next] = nextFloat(state); return [Math.floor(f * n), next]; } /** Roll the Wiz-War die (numbered 1-4). */ export function rollDie(state: RngState): [number, RngState] { const [i, next] = nextInt(state, 4); return [i + 1, next]; } /** Fisher-Yates shuffle. Returns a new array and the advanced state. */ export function shuffle(state: RngState, items: readonly T[]): [T[], RngState] { const arr = [...items]; let rng = state; for (let i = arr.length - 1; i > 0; i--) { const [j, next] = nextInt(rng, i + 1); rng = next; [arr[i], arr[j]] = [arr[j]!, arr[i]!]; } return [arr, rng]; }