Files
suaveplan/openspec/archive/2026-04-19-enterprise-random-family/specs/seeded-prng/spec.md
2026-05-27 23:29:53 +01:00

3.8 KiB
Raw Blame History

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<T>(array: T[], rng?: Rng): T[] — Fisher-Yates, returns new array.
  • REQ-PRNG-081: Exports shuffleInPlace<T>(array: T[], rng?: Rng): T[] — Fisher-Yates, mutates.
  • REQ-PRNG-082: Exports sample<T>(array: T[], n: number, rng?: Rng): T[] — n distinct elements without replacement.
  • REQ-PRNG-083: Exports weightedChoice<T>(items: T[], weights: number[], rng?: Rng): T.

Error surface

  • REQ-PRNG-090: Exports PseudoRandomError extends SuaveplanError<PseudoRandomCode>.
  • 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.