Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
# Capability: noise-functions
## ADDED Requirements
### Package identity
- REQ-NOISE-001: Package name `@suaveplan/noise`, `packages/universal/noise`.
- REQ-NOISE-002: Runtime deps: `@suaveplan/error`, `tslib`. Zero PRNG deps — noise uses its own internal seeded hash permutation tables.
- REQ-NOISE-003: `"sideEffects": false`.
### Output contract
- REQ-NOISE-010: All noise functions return values normalised to `[-1, 1]` except Worley which returns `[0, 1]` distance values.
- REQ-NOISE-011: All noise functions accept `seed: number` as first parameter. Same seed + same coordinates always returns same value.
### Perlin noise
- REQ-NOISE-020: Exports `perlin2(seed: number, x: number, y: number): number`.
- REQ-NOISE-021: Exports `perlin3(seed: number, x: number, y: number, z: number): number`.
### Simplex noise
- REQ-NOISE-030: Exports `simplex2(seed: number, x: number, y: number): number`.
- REQ-NOISE-031: Exports `simplex3(seed: number, x: number, y: number, z: number): number`.
- REQ-NOISE-032: Exports `simplex4(seed: number, x: number, y: number, z: number, w: number): number`.
### Value noise
- REQ-NOISE-040: Exports `value2(seed: number, x: number, y: number): number`.
- REQ-NOISE-041: Exports `value3(seed: number, x: number, y: number, z: number): number`.
### Worley / cell noise
- REQ-NOISE-050: Exports `WorleyMode = "F1" | "F2" | "F1-F2"`.
- REQ-NOISE-051: Exports `worley2(seed: number, x: number, y: number, mode?: WorleyMode): number`. Default mode `"F1"`.
- REQ-NOISE-052: Exports `worley3(seed: number, x: number, y: number, z: number, mode?: WorleyMode): number`.
### Fractional Brownian Motion
- REQ-NOISE-060: Exports `FbmOptions = { octaves?: number; lacunarity?: number; gain?: number }`. Defaults: `octaves=6, lacunarity=2.0, gain=0.5`.
- REQ-NOISE-061: Exports `fbm2(noiseFn: (seed, x, y) => number, seed: number, x: number, y: number, opts?: FbmOptions): number`.
- REQ-NOISE-062: Exports `fbm3(noiseFn: (seed, x, y, z) => number, seed: number, x: number, y: number, z: number, opts?: FbmOptions): number`.
### Blue noise / Poisson disk sampling
- REQ-NOISE-070: Exports `blueNoiseSample(width: number, height: number, minDistance: number, opts?: { seed?: number; rng?: Rng; maxSamples?: number }): [number, number][]`.
- REQ-NOISE-071: Uses Bridson's algorithm. Default `maxSamples = 100_000`; throws `NoiseError("NOISE.BLUE.SAMPLE_LIMIT_EXCEEDED")` if exceeded.
### Error surface
- REQ-NOISE-080: Exports `NoiseError extends SuaveplanError<NoiseCode>`.
- REQ-NOISE-081: Codes: `NOISE.BLUE.SAMPLE_LIMIT_EXCEEDED`, `NOISE.FBM.INVALID_OCTAVES`.
### Quality
- REQ-NOISE-090: 100% statement + branch coverage.
- REQ-NOISE-091: fast-check: determinism (same seed+coords → same output), output range `[-1,1]` (or `[0,1]` for Worley).
- REQ-NOISE-092: mitata benchmarks: each function at 1k and 100k evaluations.

View File

@@ -0,0 +1,69 @@
# Capability: random-utilities
## ADDED Requirements
### Package identity
- REQ-RAND-001: Package name `@suaveplan/random`, `packages/universal/random`.
- REQ-RAND-002: Runtime deps: `@suaveplan/types`, `@suaveplan/error`, `tslib`, `zod`. Zero crypto-specific deps.
- REQ-RAND-003: `"sideEffects": false`. Subpath exports per feature module.
### CSPRNG source
- REQ-RAND-010: Exports `cryptoRng(): Rng` — factory returning a WebCrypto-backed `Rng` object: `{ nextUint32, nextFloat }`. `crypto.getRandomValues` is the **only** callsite in this package, located exclusively in `src/source/crypto-rng.ts`.
- REQ-RAND-011: Exports `getDefaultRng(): Rng` and `setDefaultRng(rng: Rng): void`. Default is `cryptoRng()` at module init.
- REQ-RAND-012: `setDefaultRng` is documented as a test hook. Tests must restore the previous value in `afterEach`.
### Bytes and numbers
- REQ-RAND-020: Exports `randomBytes(n: number, rng?: Rng): Uint8Array` — fills via `nextUint32()` calls.
- REQ-RAND-021: Exports `randomInt(min: number, max: number, rng?: Rng): number` — uniform integer in `[min, max]` inclusive. Throws if `min > max`.
- REQ-RAND-022: Exports `randomFloat(rng?: Rng): number` — uniform float in `[0, 1)`.
### String generation
- REQ-RAND-030: Exports `randomString(length: number, opts?: { type?: StringType; characters?: string; rng?: Rng }): string`.
- REQ-RAND-031: `StringType = "hex" | "base64" | "base64url" | "url-safe" | "numeric" | "alphanumeric" | "distinguishable" | "ascii-printable"`.
- REQ-RAND-032: `distinguishable` charset: `CDEHKMPRTUWXY012458` (no visually ambiguous characters).
- REQ-RAND-033: `type` and `characters` are mutually exclusive; providing both throws `RandomError("RANDOM.STRING.INVALID_OPTIONS")`.
### ID generation
- REQ-RAND-040: Exports `randomUUID(rng?: Rng): string` — RFC 4122 v4 UUID.
- REQ-RAND-041: Exports `randomUUIDv7(rng?: Rng): string` — time-ordered UUID v7: 48-bit ms timestamp + 74 random bits. Monotonically increasing within the same millisecond via sequence counter.
- REQ-RAND-042: Exports `randomULID(rng?: Rng): string` — Crockford Base32 encoded ULID.
- REQ-RAND-043: Exports `randomNanoid(size?: number, rng?: Rng): string` — URL-safe, default 21 characters.
### Collection operations
- REQ-RAND-050: Exports `shuffle<T>(array: T[], rng?: Rng): T[]` — Fisher-Yates, immutable.
- REQ-RAND-051: Exports `sample<T>(array: T[], n: number, rng?: Rng): T[]` — n distinct elements.
- REQ-RAND-052: Exports `choice<T>(array: T[], rng?: Rng): T` — single random element.
- REQ-RAND-053: Exports `weightedChoice<T>(items: T[], weights: number[], rng?: Rng): T`.
- REQ-RAND-054: Exports `weightedSample<T>(items: T[], weights: number[], n: number, rng?: Rng): T[]` — weighted sampling without replacement.
- REQ-RAND-055: All collection functions throw `RandomError("RANDOM.COLLECTION.EMPTY")` on empty input.
### Object/Record operations
- REQ-RAND-060: Exports `randomKey<T extends object>(obj: T, rng?: Rng): keyof T`.
- REQ-RAND-061: Exports `randomValue<T extends object>(obj: T, rng?: Rng): T[keyof T]`.
- REQ-RAND-062: Exports `randomEntry<T extends object>(obj: T, rng?: Rng): [keyof T, T[keyof T]]`.
- REQ-RAND-063: Exports `randomSubset<T extends object>(obj: T, n: number, rng?: Rng): Partial<T>`.
- REQ-RAND-064: All object functions throw `RandomError("RANDOM.OBJECT.EMPTY")` on empty object.
### Game primitives
- REQ-RAND-070: Exports `coin(p?: number, rng?: Rng): boolean` — Bernoulli trial. Default `p = 0.5`. Throws if `p` outside `[0, 1]`.
- REQ-RAND-071: Exports `dice(sides: number, rng?: Rng): number` — uniform integer in `[1, sides]`.
- REQ-RAND-072: Exports `roll(count: number, sides: number, rng?: Rng): number` — sum of `count` dice.
### Error surface
- REQ-RAND-080: Exports `RandomError extends SuaveplanError<RandomCode>`.
- REQ-RAND-081: Codes: `RANDOM.STRING.INVALID_OPTIONS`, `RANDOM.COLLECTION.EMPTY`, `RANDOM.COLLECTION.INSUFFICIENT`, `RANDOM.OBJECT.EMPTY`, `RANDOM.GAME.INVALID_SIDES`, `RANDOM.GAME.INVALID_PROBABILITY`.
### Quality
- REQ-RAND-090: 100% statement + branch coverage.
- REQ-RAND-091: `tests/no-disallowed.test.ts`: no `Math.random`, `Date.now`, `crypto.getRandomValues` outside `src/source/crypto-rng.ts`.
- REQ-RAND-092: fast-check: all collection operations preserve input length constraints; UUID/ULID format invariants.

View File

@@ -0,0 +1,71 @@
# 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.

View File

@@ -0,0 +1,39 @@
# Capability: statistical-distributions
## ADDED Requirements
### Location
- REQ-DIST-001: All distribution functions live in `@suaveplan/random` under `src/distributions/`.
### Continuous distributions
- REQ-DIST-010: Exports `normal(mu?: number, sigma?: number, rng?: Rng): () => number` — Gaussian via Box-Muller. Defaults `mu=0, sigma=1`.
- REQ-DIST-011: Exports `logNormal(mu?: number, sigma?: number, rng?: Rng): () => number` — `exp(normal(mu, sigma))`.
- REQ-DIST-012: Exports `exponential(lambda: number, rng?: Rng): () => number`. Throws if `lambda <= 0`.
- REQ-DIST-013: Exports `gamma(k: number, theta?: number, rng?: Rng): () => number` — Marsaglia-Tsang for k≥1, Ahrens-Dieter for k<1. Default `theta=1`.
- REQ-DIST-014: Exports `beta(alpha: number, betaParam: number, rng?: Rng): () => number` — ratio of two Gamma samples.
- REQ-DIST-015: Exports `pareto(alpha: number, rng?: Rng): () => number`. Throws if `alpha <= 0`.
- REQ-DIST-016: Exports `weibull(k: number, a?: number, b?: number, rng?: Rng): () => number`. Defaults `a=0, b=1`.
- REQ-DIST-017: Exports `cauchy(a?: number, b?: number, rng?: Rng): () => number`. Defaults `a=0, b=1`.
- REQ-DIST-018: Exports `logistic(a?: number, b?: number, rng?: Rng): () => number`. Defaults `a=0, b=1`.
### Discrete distributions
- REQ-DIST-020: Exports `bernoulli(p: number, rng?: Rng): () => 0 | 1`. Throws if `p` outside `[0, 1]`.
- REQ-DIST-021: Exports `geometric(p: number, rng?: Rng): () => number`. Throws if `p` outside `(0, 1]`.
- REQ-DIST-022: Exports `binomial(n: number, p: number, rng?: Rng): () => number`. Throws if `n < 0` or `p` outside `[0, 1]`.
- REQ-DIST-023: Exports `poisson(lambda: number, rng?: Rng): () => number` — Knuth for λ≤30, rejection method above. Throws if `lambda <= 0`.
### Unified dispatcher
- REQ-DIST-030: Exports `DistributionConfig` — discriminated union: `{ type: 'normal'; mu?: number; sigma?: number } | { type: 'exponential'; lambda: number } | ...` (one member per distribution).
- REQ-DIST-031: Exports `createSampler(config: DistributionConfig, rng?: Rng): () => number` — dispatches to the appropriate distribution factory.
- REQ-DIST-032: Exports `sample(config: DistributionConfig, rng?: Rng): number` — one-shot convenience: `createSampler(config, rng)()`.
- REQ-DIST-033: `DistributionConfig` is a plain JSON-serialisable type. `rng` is always passed externally, never embedded in config.
- REQ-DIST-034: `createSampler` throws `RandomError("RANDOM.DISTRIBUTION.UNKNOWN_TYPE")` for unrecognised `type` values (runtime guard for JS consumers without TypeScript).
### Quality
- REQ-DIST-040: fast-check: for each distribution, generate 10k samples and verify mean/variance are within 5% of theoretical values at standard parameters.
- REQ-DIST-041: `DistributionConfig` round-trips through `JSON.parse(JSON.stringify(config))` without loss for all distribution types.