v2.8 · RNG PRE-SUBMISSION PACKAGE v1.2
RAIN RNG · PRE-SUBMISSION PACKAGE v1.2 · DOCUMENT

SCALING-PROOF — INTBELOW EXACTLY UNIFORM; LEGACY % N

Source sdk-v2/lab/SCALING-PROOF.md · commit 96368332 · GLI CSR §2.2: Scaling / mapping (GLI-19 §3.2.3) · ← package index · raw on GitHub ↗

RAIN RNG v2.1 — Scaling proof (one page)

GLI-19 v3.0 §3.2.3 "Scaling / mapping"; GLI-11 §3.2.3 "entirely free of bias"; NJ 13:69E-1.28G(b)6; UKGC RTS 7A. Extracted from RNG-DESCRIPTION.md §6 + Appendix B so a reviewer finds it in one place. Code: packages/rng-core/src/drbg.ts (intBelow, shuffle); legacy packages/rng-core/src/rng.ts (outcomeMod). Evidence that the proof holds in practice: REPORT-STATISTICAL.md §5 (10⁸ scaled outcomes per game, chacha; hmac see EVIDENCE-INDEX.md).

1. Certified path — intBelow(n): rejection sampling, exactly uniform

limit = 2^32 − (2^32 mod n)          // = m·n with m = ⌊2^32 / n⌋
loop: v = next uint32 word            // fresh 32-bit word from the DRBG stream
      if v < limit: return v mod n    // accept
      else: discard v and repeat      // reject ("the discard of RNG values is permissible", GLI-19 §3.2.3(a))

Theorem. For 1 ≤ n ≤ 2³² and U uniform on [0, 2³²), the output V is exactly uniform on {0, …, n−1}.

Proof. Let m = ⌊2³²/n⌋ and limit = m·n. Conditioned on acceptance (U < limit), U is uniform on [0, limit). For every v ∈ [0, n) the set {u < limit : u mod n = v} = {v, v+n, …, v+(m−1)n} has exactly m elements, so P(V = v) = m / limit = m / (m·n) = 1/n. ∎

Rejections. Each trial is independent with P(reject) = (2³² − limit)/2³² = (2³² mod n)/2³² < n/2³² ≤ ½ (worst case n = 2³¹+1). The number of words consumed is geometric with mean 2³²/limit ≤ 2 and is independent of the value returned (acceptance is a function of the word, and the returned value is conditioned on acceptance). Concretely: n = 100 → P(reject) = 96/2³² ≈ 2.2·10⁻⁸; n = 37 → 27/2³² ≈ 6.3·10⁻⁹; n = 52 → 4/2³² ≈ 9.3·10⁻¹⁰; n = 2³¹+1 → 0.5, mean 2 words per value.

Fisher–Yates shuffle(arr) (Durstenfeld): for j = len−1 … 1 swap position j with intBelow(j+1). At step j the element for position j is chosen uniformly among the j+1 remaining, so each of the len! permutations has probability ∏_{j=1}^{len−1} 1/(j+1) = 1/len!. A 52-card shuffle consumes 51 words in expectation plus ≈ 10⁻⁶ rejections. ∎

Replay. cursor() advances by the number of words consumed including rejections, so a verifier replaying the same r_k reproduces the same outcomes bit-for-bit (REPORT-STATISTICAL.md §5 data sets are regenerable from their seed).

2. Legacy path — uint256(r) % N: bias bound (NOT in the certification scope)

Let R be uniform on [0, 2²⁵⁶) and V = R mod N. Write 2²⁵⁶ = q·N + s (0 ≤ s < N). Values v < s occur with probability (q+1)/2²⁵⁶, the others q/2²⁵⁶. Hence

quantity bound N = 100 N = 37
absolute deviation from 1/N < 1/2²⁵⁶ 8.6·10⁻⁷⁸ 8.6·10⁻⁷⁸
relative deviation < N/2²⁵⁶ 8.6·10⁻⁷⁶ (s = 2²⁵⁶ mod 100 = 36 cells over-weighted) 3.2·10⁻⁷⁶ (s = 2²⁵⁶ mod 37)
draws to detect at 99 % ((2.58)²(1−p)/(pδ²)) — ≈ 10¹⁵³ ≈ 10¹⁵³

No experiment of any feasible size can distinguish the legacy reduction from uniform; the bias is 10⁶⁰ times smaller than the probability of a hash collision. Nevertheless it is not the certified path: outcomeMod is retained only for byte-identity with already-deployed Solidity verifiers (rng.ts:117) and is listed as legacy in SCALING-INVENTORY.md; every game consumed by the collection tool (lab/tools/collect-outcomes.mjs) and every current engine goes through intBelow/shuffle, so the standard's wording "entirely free of bias" is met literally (bias = 0), not approximately.

Other % n sites in the inventory (SCALING-INVENTORY.md): 64-bit fields → relative bias < 10⁶/2⁶⁴ ≈ 5.4·10⁻¹⁴ (detection ≈ 10²⁸ draws); the 32-bit nextInt of Slot Engine V2 → < n/2³² (≤ 2.3·10⁻⁵ for n = 10⁵) — the one site where migration to intBelow is recommended and tracked.

3. Where this is verified

what where
unit tests: intBelow exact-uniform on small n, consumption accounting, shuffle permutation counts packages/rng-core/test/ (npm test)
10⁸ dice(100), roulette(37), coin(2) outcomes; 10⁸ reel stops; 2·10⁶ decks — total χ², blocked-χ² KS, serial correlation, runs, coupon, Fisher–Yates 52×52 REPORT-STATISTICAL.md §5, results/outcomes/<mech>/
legacy % N data set (hmac, 2·10⁷ dice + 2·10⁷ roulette) for comparison only results/outcomes/hmac/legacy-{dice,roulette}.analysis.md

← Back to the package index · Rendered 2026-09-14 09:34 UTC from the repository copy; the markdown in the zip / repo is the document of record.