# Capability: seeded-prng ## ADDED Requirements ### Package identity - REQ-PRNG-001: Package name `@suaveplan/pseudo-random`, `packages/universal/pseudo-random`. - REQ-PRNG-002: Runtime deps: `@suaveplan/error`, `@suaveplan/types`, `tslib`, `zod`. Zero crypto deps, zero browser deps. - REQ-PRNG-003: `"sideEffects": false`. ### SeededRng interface - REQ-PRNG-010: Exports `SeededRng` interface extending `Rng` from `@suaveplan/types`: `{ readonly seed: number; readonly position: number; skip(n: number): this; reset(): this; at(offset: number): SeededRng; fork(id: number | string): SeededRng; checkpoint(): RngCheckpoint; restore(checkpoint: RngCheckpoint): this }`. - REQ-PRNG-011: Exports `RngCheckpoint = { seed: number; position: number; state: unknown }` — serialisable to JSON. ### Seed normalisation - REQ-PRNG-020: Exports `normalizeSeed(seed: string | number): number`. - REQ-PRNG-021: String seeds: splitmix64 mixing over UTF-16 char codes. Produces stable 32-bit integer. - REQ-PRNG-022: Numeric seeds: single splitmix64 round to avalanche input bias. Rejects NaN, Infinity, non-safe-integers with `PseudoRandomError("PRNG.SEED.INVALID")`. - REQ-PRNG-023: Zero input produces a non-zero seed (avoids degenerate PRNG state). ### Mulberry32Rng - REQ-PRNG-030: Exports `Mulberry32Rng` implementing `SeededRng`. 32-bit state. - REQ-PRNG-031: `nextUint32()` uses the standard Mulberry32 algorithm: `a = (a + 0x6d2b79f5) >>> 0; ...`. - REQ-PRNG-032: Byte-identical output to the inline implementation in `@suaveplan/testing/context`. ### Sfc32Rng - REQ-PRNG-040: Exports `Sfc32Rng` implementing `SeededRng`. 128-bit state (4×uint32). - REQ-PRNG-041: State initialised with 12 warmup rounds from `seedToSFC32State`. - REQ-PRNG-042: Byte-identical output to `BulletproofPRNG` (SFC32 mode) in the bingo app for same seed. ### Xoshiro256Rng - REQ-PRNG-050: Exports `Xoshiro256Rng` implementing `SeededRng`. 256-bit state (4×uint64 via BigInt or two uint32 pairs). - REQ-PRNG-051: Uses xoshiro256++ algorithm. - REQ-PRNG-052: `skip(2**128)` and `skip(2**192)` use the algebraic jump polynomials (O(log n)); other `skip(n)` uses linear burn. ### CounterRng - REQ-PRNG-060: Exports `CounterRng` implementing `SeededRng`. Counter-based via splitmix64 hash. - REQ-PRNG-061: `at(offset)` is O(1) — directly sets the counter. - REQ-PRNG-062: `nextUint32()` = `splitmix64(seed XOR counter++)` truncated to 32 bits. ### Shared SeededRng behaviour - REQ-PRNG-070: `fork(id)` derives child seed via `normalizeSeed(parentSeed XOR hash(id))`. Children are statistically independent. - REQ-PRNG-071: `checkpoint()` returns a plain object that `JSON.stringify` round-trips without loss. - REQ-PRNG-072: `restore(checkpoint)` resets the instance to the checkpointed state and returns `this`. - REQ-PRNG-073: `at(offset)` returns a **new instance** at the given offset (does not mutate the original). ### Utilities - REQ-PRNG-080: Exports `shuffle(array: T[], rng?: Rng): T[]` — Fisher-Yates, returns new array. - REQ-PRNG-081: Exports `shuffleInPlace(array: T[], rng?: Rng): T[]` — Fisher-Yates, mutates. - REQ-PRNG-082: Exports `sample(array: T[], n: number, rng?: Rng): T[]` — n distinct elements without replacement. - REQ-PRNG-083: Exports `weightedChoice(items: T[], weights: number[], rng?: Rng): T`. ### Error surface - REQ-PRNG-090: Exports `PseudoRandomError extends SuaveplanError`. - REQ-PRNG-091: Codes: `PRNG.SEED.INVALID`, `PRNG.OFFSET.OUT_OF_RANGE`, `PRNG.SAMPLE.INSUFFICIENT_ITEMS`, `PRNG.CHECKPOINT.INVALID`. ### Quality - REQ-PRNG-100: 100% statement + branch coverage. - REQ-PRNG-101: fast-check: determinism, fork independence (10k samples, correlation < 0.01), checkpoint round-trip. - REQ-PRNG-102: mitata benchmarks: `nextUint32` throughput for each algorithm. - REQ-PRNG-103: `Sfc32Rng` output verified against known vectors from bingo test suite.