Skip to main content
Audits
LIQD.com/Limbo
Provisional audit

Not yet certified. Pre-launch tests passed; final certification requires public launch and an independent recheck of live bets using anonymous, self-funded accounts.

LIQD: Limbo Audit

Limbo

Provisional

Independent verification, backed by open-source code.

PF-2026-LQ05Audited September–October 2026 · Framework 1.1 · Release v1.0.1September–October 2026 · Framework 1.1 · v1.0.1
AUDIT RESULTPassedPassed 25/25 · Hard fails 0VERDICT: PROVABLY FAIR — Full Pass, as printed by npm test.
Live Bets Verified
6,700
Parity Rate
100%
Simulated Rounds
38M
Simulated RTP 99.0128% over the reference run’s 20M rounds at 2.00×
Proven RTP
99.00%
Exact on every target from 1.01× to 10,000×: 98.990018%–99.000000%
25 / 25Verification steps passed

Verification pipeline

Real bets. Independently reproduced.

Captured on LIQD’s pre-launch environment. We rebuilt the game logic from scratch and matched all 6,700 recorded outcomes.

LIQD LIMBO$0.10 · Target 2.07×
Bet #DPdrSHU7jMUPR_9FN-8k7Nonce 0 · Phase A
50 real bets · Phase ABet 1 of 50
✓ Exact match
14.77× vs 2.07× · WIN

The casino result, reproduced.

Casino outcome
14.77× · WIN
Our reproduction
14.77× · WIN
Recorded payout
$0.2070
Open source

Run this audit yourself

Open GitHub ↗

The repo is the credential. You don’t have to trust us — every scored check ships as code. Run npm test to re-run the full audit with no dependency install: 27 unit tests, the 25 verification steps and 6,700 live bets re-verified — or run the two bundles directly with node test.js && node verify.js.

npm testCopy includes clone & installView reproduction commands ↓
Limbo Audit Overview

This audit independently validates the Limbo game operated by LIQD under the ProvablyFair.org Audit Framework 1.1. We placed 6,700 real bets across 134 seed epochs — targets from 1.01× to 500,000×, two stakes, ten custom client seeds — and recomputed every result, every win flag and every credited amount with our own implementation of the algorithm, written from a specification without LIQD’s code. Targets above 10,000× were played on the captured build; LIQD’s server now accepts targets from 1.01× to 10,000×, and the certificate covers every one of them. The capture ran on LIQD’s pre-launch environment, so the certificate is provisional until an anonymous recapture on liqd.com confirms the same behaviour.

Results at a glance

Audit Verdict

CheckResultEvidence
Overall StatusPassPROVABLY FAIR — Full Pass · Passed 25/25 · Hard fails 0 · Certified for every target from 1.01× to 10,000×, the range LIQD’s server accepts (Step 25). Provisional: captured on LIQD’s pre-launch environment; an anonymous recapture on liqd.com completes certification
RTP VerifiedPass98.990018%–99.000000% Proven RTP on every target from 1.01× to 10,000× (999,900 targets, exact) · house edge at least 1.00% on all 99,999,900 targets · simulation re-derived on every run: 10 targets × 1,000,000 rounds, each within 5 SD + 1 win of its exact win count, worst 1.56 SD (Step 23) · RTP claim rule: LIQD’s published 99% holds on all 999,900 targets, worst gap −0.009982 pp against the ±0.1 pp band (RTP-CLAIM 1.2.0, Steps 16, 23, 25)
Live ↔︎ Verifier ParityPass100% — 6,700 / 6,700 results and win flags reproduced from the revealed seeds, every win credited exactly stake × target (Steps 5, 6, 9) · the 300 supplement rounds of 26 September recompute the same way (Step 25) · LIQD’s own verifier, as a cross-check, agrees on 4 / 4 Verify-calculator values and 2 / 2 /verify replies
Commit-Reveal SystemPass134 / 134 revealed seeds hash to their commitments; the next-seed chain is consistent on 133 / 133 rotations plus the pre-capture link into epoch 0; every epoch holds nonces 0–49 under one commitment and one client seed (Steps 1–4)
Client SeedPassPlayer-controlled and customizable — 500 / 500 Phase D rounds under 10 client seeds we chose recompute exactly; the other 124 epochs ran on fresh random seeds our capture set after LIQD had published the commitment; adding one character to the seed changes the draw on 64 / 64 probed rounds (Step 18)
RNG AnalysisPassOur own implementation, written from the specification: HMAC-SHA256 keyed by the decoded server seed, the first 4-byte chunk below 4,200,000,000 taken modulo 100,000,000 — modulo bias 0; 126 rounds had their first chunk discarded by that guard, and all 126 reproduce (Step 5)
Payout LogicPassAll 6,700 settlements verified — 2,822 wins credit exactly stake × target, an integer identity with zero tolerance; 3,878 losses pay 0; 20 rounds landed exactly on their target and were paid, as the rule result ≥ target requires (Steps 6, 8–10)
Anti-CircularityPassThe win chance comes from the draw grid, never from LIQD: target m wins on exactly floor(9.9 × 10⁹ ÷ 100m) of the 100,000,000 draws — an integer identity on all 1,104 captured targets, confirmed by four literal full-grid tallies — and RTP = win chance × target; the check rejects a result one tick away (Steps 14, 15)
Fairness IntegrityPass34 fairness integrity tests across 12 categories — all 34 pass; no test altered, replayed or injected an outcome
DeterminismPassFull reproducibility confirmed — 6,700 / 6,700 outcomes and credits are determined by (server seed, client seed, nonce, target, stake) alone; two forward passes and one reverse pass agree on every round (Steps 7, 20)
Overall StatusPass
PROVABLY FAIR — Full Pass · Passed 25/25 · Hard fails 0 · Certified for every target from 1.01× to 10,000×, the range LIQD’s server accepts (Step 25). Provisional: captured on LIQD’s pre-launch environment; an anonymous recapture on liqd.com completes certification
RTP VerifiedPass
98.990018%–99.000000% Proven RTP on every target from 1.01× to 10,000× (999,900 targets, exact) · house edge at least 1.00% on all 99,999,900 targets · simulation re-derived on every run: 10 targets × 1,000,000 rounds, each within 5 SD + 1 win of its exact win count, worst 1.56 SD (Step 23) · RTP claim rule: LIQD’s published 99% holds on all 999,900 targets, worst gap −0.009982 pp against the ±0.1 pp band (RTP-CLAIM 1.2.0, Steps 16, 23, 25)
Live ↔︎ Verifier ParityPass
100% — 6,700 / 6,700 results and win flags reproduced from the revealed seeds, every win credited exactly stake × target (Steps 5, 6, 9) · the 300 supplement rounds of 26 September recompute the same way (Step 25) · LIQD’s own verifier, as a cross-check, agrees on 4 / 4 Verify-calculator values and 2 / 2 /verify replies
Commit-Reveal SystemPass
134 / 134 revealed seeds hash to their commitments; the next-seed chain is consistent on 133 / 133 rotations plus the pre-capture link into epoch 0; every epoch holds nonces 0–49 under one commitment and one client seed (Steps 1–4)
Client SeedPass
Player-controlled and customizable — 500 / 500 Phase D rounds under 10 client seeds we chose recompute exactly; the other 124 epochs ran on fresh random seeds our capture set after LIQD had published the commitment; adding one character to the seed changes the draw on 64 / 64 probed rounds (Step 18)
RNG AnalysisPass
Our own implementation, written from the specification: HMAC-SHA256 keyed by the decoded server seed, the first 4-byte chunk below 4,200,000,000 taken modulo 100,000,000 — modulo bias 0; 126 rounds had their first chunk discarded by that guard, and all 126 reproduce (Step 5)
Payout LogicPass
All 6,700 settlements verified — 2,822 wins credit exactly stake × target, an integer identity with zero tolerance; 3,878 losses pay 0; 20 rounds landed exactly on their target and were paid, as the rule result ≥ target requires (Steps 6, 8–10)
Anti-CircularityPass
The win chance comes from the draw grid, never from LIQD: target m wins on exactly floor(9.9 × 10⁹ ÷ 100m) of the 100,000,000 draws — an integer identity on all 1,104 captured targets, confirmed by four literal full-grid tallies — and RTP = win chance × target; the check rejects a result one tick away (Steps 14, 15)
Fairness IntegrityPass
34 fairness integrity tests across 12 categories — all 34 pass; no test altered, replayed or injected an outcome
DeterminismPass
Full reproducibility confirmed — 6,700 / 6,700 outcomes and credits are determined by (server seed, client seed, nonce, target, stake) alone; two forward passes and one reverse pass agree on every round (Steps 7, 20)
Audit scope & reference

What Was Audited

✓ Audited

  • The result is deterministic: every round reproduces from server seed, client seed and nonce
  • Server seeds are committed by SHA-256 hash, and each rotation pre-commits the next seed’s hash
  • The client seed is player-controlled and changes the result
  • Nonces run 0–49 in every seed epoch of the capture, with no gaps or reuse
  • The draw is one HMAC-SHA256 value on a 100,000,000-point grid, with bias-free rejection sampling
  • The result is floor(9,900,000,000 ÷ (draw + 1)) ÷ 100, never below 1×
  • The win rule is result ≥ target — a result exactly on the target wins
  • A win pays exactly stake × target; a loss pays 0
  • Proven RTP for every target from 1.01× to 10,000× — 98.990018% to 99.000000% — and a house edge of at least 1.00% on every target
  • LIQD’s published 99% RTP holds on all 999,900 targets under the RTP claim rule (±0.1 pp)
  • LIQD’s server refuses targets above 10,000× and limits a bet’s potential payout to $50,000
  • Bet amount does not influence the result or the edge
  • Players can independently verify every bet

What Audit Excludes

  • Infrastructure or server security
  • Wallet, payments, or operational systems outside game logic
  • Cross-account sampling
What Audit Covers
2 columns
AreaDescription
Commit-Reveal SystemSHA-256 server seed hashing, pre-bet commitment, next-seed pre-commitment, reveal on rotation
Client Seed OriginPlayer-controlled seed set after the server commitment — our own random seeds in 124 epochs and 10 custom seeds in Phase D
Seed HandlingClient seed control, nonce lifecycle, seed epoch rotation
RNG AnalysisOur own HMAC-SHA256 implementation, written from the specification; LIQD’s verifier used as a cross-check; rejection sampling, the result map and its 1× floor, boundary semantics
Payout LogicCredit = stake × target as an exact integer identity, loss settlement, wallet-field reconciliation, the $50,000 potential-payout limit, bet-size invariance (Phase C)
Live ParityIndependent result and win-flag recomputation vs live game results across all four capture phases and the 300-round supplement
RTP ValidationExact RTP of all 999,900 targets, the 1.00% edge floor over all 99,999,900 targets, literal grid tallies, the RTP claim rule, simulation re-derived on every run (Step 23) and a 38M-round reference run, cherry-pick test on the played-target return
Fairness IntegrityIntegrity tests on LIQD’s running service — 34 across 12 categories: race conditions, input validation, atomic balance / idempotency, broken authorization, max-win bypass, nonce manipulation, RNG / PF verification, RNG / information leak, entropy / PF verification, precision / payout, event replay and adversarial seeds
What Audit Guarantees
  • Outcomes are deterministic and reproducible from the recorded inputs
  • Live game results match independent recomputation for the verified sample (6,700 / 6,700)
  • Draws are consistent with the uniform distribution expected from the rejection-sampled HMAC draw — 2,000,000 simulated draws and the 6,700 captured draws both pass their uniformity screens
  • RTP is proven analytically: win chance × target = floor(9.9 × 10⁹ ÷ 100m) × 100m ÷ 10¹⁰ for every target m, in exact integers — never above 99%
  • LIQD’s published 99% RTP is met by every one of the 999,900 targets the server accepts — 98.990018% to 99.000000%, all within 0.01 pp of 99%
  • Simulation agrees with the exact figures: 10 targets from 1.01× to 100,000×, 1,000,000 rounds each, all within 5 SD + 1 win of their exact win counts (worst 1.56 SD), re-derived on every verify run (Step 23)
  • The result is fixed by the seed pair and nonce before the target is applied — which target you choose and how much you stake never change the result
  • Client seed is a genuine, player-controlled input that materially influences results (500 / 500 custom-seed rounds recompute; adding one character to the seed changes the draw on 64 / 64 probed rounds)
  • The house edge is at least 1.00% on every one of the 99,999,900 targets and never favours the player — at most 1.009982% up to 10,000× — and settlement is exact, so no rounding widens it
  • Every fairness integrity test in Section 5 passed — no test altered, replayed or injected an outcome

References

Limbo — Game Rules7 sections▶

How LIQD Limbo plays, exactly as verified by this audit — the interactive explainer below is wired to the audited result map and win chance.

Game Rules

Limbo turns one random draw into a result — a multiplier of at least 1×. Before the round you choose a target — any multiplier from 1.01× to 10,000× in steps of 0.01. If the result reaches your target (result ≥ target), you win and are paid stake × target; otherwise the bet is lost. The result comes from one HMAC-SHA256 draw seeded by (serverSeed, clientSeed, nonce), fixed before your target or stake is applied; your choices set the odds and the price, never the result.

How to Play

1. Choose a target — any multiplier from 1.01× to 10,000×; the higher the target, the lower the chance (2× wins 49.5% of the time, 10,000× 0.0099%).
2. Set the stake — a bet’s potential payout, stake × target, can be up to $50,000.
3. Play — the platform derives the result from the committed server seed, your client seed and the current nonce.
4. Settle — a result at or above the target credits exactly stake × target; a lower result pays 0. The nonce advances by 1.
5. Verify — after rotating your seed pair, recompute any round from the revealed seed (Section 6 walks it).

Boundary note: a result exactly on the target wins. 20 captured rounds landed exactly on their target and LIQD paid all 20 (Step 6), and LIQD’s own verifier returns the same answer at the boundary.
Win Conditions

One rule — win = result ≥ target. A target’s win chance is the share of the 100,000,000 equally likely draws whose result reaches it, and a win pays the target, not the result:

3 columns
TargetWin chanceA win pays, on a $1 stake
1.01×98.019801%$1.01
2×49.500000%$2.00
10×9.900000%$10.00
100×0.990000%$100.00
1,000×0.099000%$1,000.00
10,000×0.009900%$10,000.00
The win/loss label is not taken on trust: it is recomputed from the seed-derived result for every one of the 6,700 rounds in the dataset (Step 6), and every credited amount is rebuilt from the target and stake alone (Step 20).
Risk vs Reward

Every target returns 99% to within 0.01 pp — the target changes the shape of the risk, never the fairness of the price:

  • Near-certain: 1.01× wins 98.019801% of the time — the highest win chance on offer
  • Even money: 2× wins 49.5% of the time
  • Long shots: 100× wins 0.99% of the time; 10,000×, the highest target the server accepts, 0.0099%
  • The price is exact: win chance × target is never above 99%, and between 98.990018% and 99.000000% on every target up to 10,000×
  • Variance grows with the target, the edge does not: the house edge stays between 1.00% and 1.009982% on every target you can bet
Parameters
3 columns
ParameterValueNotes
Draw domain0 – 99,999,999100,000,000 equally likely draws (RANGE = 100000000)
Resultfrom 1×Two decimals; the 1× floor lifts 1% of draws. LIQD’s game code names 1,000,000× as the maximum — only draws 0–97 would go higher, and they win every target either way
Target1.01× – 10,000×Steps of 0.01 — 999,900 settings, every one held to the RTP claim (S4.2)
Win chance0.0099% – 98.019801%floor(9,900,000,000 ÷ (100 × target)) ÷ 10⁸ — LIQD’s server states the same range
House edge1.00% – 1.009982%Per target up to 10,000×; LIQD states a flat 1.00%, met within ±0.1 pp on all 999,900
Minimum stake$0.10LIQD’s client configuration; every captured stake is at or above it
Potential payoutup to $50,000stake × target per bet, enforced when the bet is placed
Seed epoch50 betsNonces 0–49, then the pair rotates — the cadence this capture chose
Settlementexactstake × target — a 2-decimal stake times a 2-decimal target, reproduced with zero tolerance
Stakes captured$0.10 and $10.006,500 bets at $0.10, 200 at $10.00 (Phase C)
Seed Formats

The three verification inputs, exactly as they appear in LIQD’s panel and the bet record:

4 columns
Seed TypeFormatExamplePurpose
Server seed32-char hex (16 bytes)ee4724f00cfc46e28425e9de3239d427HMAC key (decoded); committed via SHA-256 of the string before play
Client seedstring, set by the playeraudita5a8f43bd18dSet at each seed rotation — mixed into every draw
Nonceinteger from 00The bet counter under the active pair — 0–49 in this capture
Two hashing domains: the commitment hashes the hex string (UTF-8); the HMAC keys the decoded bytes. Both conventions are verified in S1–S2.
Multiplier & Payout

The payout is a formula — no paytable file exists. The draw k becomes the result, the result is compared with your target, and a win pays the target. Every row below is a real captured round, recomputed by this audit:

k = first 4-byte chunk below 4,200,000,000, mod 100,000,000
result = max(1, floor(9,900,000,000 / (k + 1)) / 100)
win chance = floor(9,900,000,000 / (100 × target)) / 10⁸
payout = stake × target when result ≥ target, otherwise 0
4 columns
Draw kResultTargetOutcome
534185,046.72×1.17×Win — pays the target, $0.10 × 1.17 = $0.117, not the result (bet S4mFs74B…)
924,515107.08×100×Win — $0.10 × 100 = $10.00, the round worked through in Section 4.9 (bet S8UAp699…)
40,058,9962.47×4.58×Loss — pays 0 (bet USzRL9gy…)
84,760,0191.16×1.16×Win — exactly on the target, $0.10 × 1.16 = $0.116 (bet bYS2viIe…)
99,762,6681×3.76×Loss — the raw value 0.99 is lifted to the 1× floor (bet yAVLBL0Q…)
Every win pays the target: each of the 2,822 credits is exactly stake × target — credit × 10⁸ = hundredths(stake) × hundredths(target) × 10⁴, with zero tolerance (Step 9). The round worked through in Section 4.9 paid $0.10 × 100 = $10.00.
Why Provably Fair Matters▶

Traditional online casinos require players to trust that games are fair. Provably fair systems eliminate that requirement — three properties, all verified in this audit, make trust unnecessary:

  • The casino commits to the inputs that determine a result before you play — the SHA-256 commitment locks the server seed while it is still secret
  • A source of randomness exists that the casino cannot predict — an unpredictable client seed of your own, set after the commitment is published
  • Anyone can verify the outcome after the fact — the revealed seed plus public code reproduces every round exactly
High-Level Overview8 sections▶

The whole system in one pass — from commitment to payout, with the capture design that tested it.

Checklist Reference

Commit-Reveal

2 columns
CheckWhat it proves
Server seed hash shown before playWith the hash shown first, the casino cannot change the result after you bet
SHA-256 of the revealed seed matches the commitmentThe revealed seed is the one that was committed
Next epoch's hash pre-committed at rotationThe casino cannot mint a fresh seed in response to your play
One commitment per epoch, no mid-epoch switchingEvery bet is pinned to a single verified commitment

Seed System

2 columns
CheckWhat it proves
Client seed is set by the player after the next commitment is publishedAn unpredictable seed set then cannot be pre-evaluated by the operator
Client seed changes the result when substitutedYour seed genuinely enters every derivation
Nonces run contiguously 0–49 in every captured epochNo skipped bet where a discarded outcome could hide

RNG & Parity

2 columns
CheckWhat it proves
Result recomputes from (serverSeed, clientSeed, nonce)No hidden inputs influence the result
Rejection sampling below maxFair = 4,200,000,000Every one of the 100,000,000 draws is exactly equally likely
The rule result ≥ target reproduces 6,700 / 6,700 win flagsThe game settles a result exactly on the target as a win — 20 captured rounds show it

Payout & RTP

2 columns
CheckWhat it proves
Wins credit exactly stake × targetThe payout is the target you chose, never more or less
floor(N / ci) × ci ≤ N on all 99,999,900 targets, as integersThe 1.00% house edge is a floor — an identity, not a sampled estimate
Closed-form win count equals a literal 100,000,000-draw tally at four targetsThe probability side cannot share an arithmetic slip with the formula

Integrity

2 columns
CheckWhat it proves
Losses pay exactly 0No round settles money against a losing result
Identical engine at $0.10 and $10.00Stake size cannot reach the result
Dataset SHA-256 pinned (a mismatch stops the suite); supplement, probe journals and simulations pinned and checked in Steps 19, 23 and 25The evidence itself is tamper-evident
High-Level Flow

One round of LIQD Limbo, from commitment to settlement:

1. Commit — the server generates a 32-hex server seed and publishes SHA-256(utf8(seed)) as activeServerSeedHash. You see only the hash.
2. Seed pair active — the client seed you set at rotation and a nonce starting at 0 pair with the committed seed.
3. Bet — you choose a target and a stake. The result is already fully determined by (serverSeed, clientSeed, nonce).
4. Draw — one HMAC-SHA256 value, reduced to a draw k on the 100,000,000-point grid, becomes the result floor(9,900,000,000 / (k + 1)) / 100, never below 1×.
5. Settle — a result at or above the target credits stake × target; a lower result pays 0. The nonce increments by 1.
6. Rotate & verify — rotation reveals the outgoing seed (and pre-commits the next). Check the hash, recompute any round.

High-Level Flow — illustrated flow
Provably Fair Model

The model splits control of the randomness. The operator controls the server seed but must commit to it before play; the player controls the client seed and can change it after seeing the commitment; the nonce is a public counter. Because the result is a pure function of those three inputs — and the target and stake enter only afterwards, as the rule and the price — neither side can steer an outcome without the other side's input, and everything needed to check the result is disclosed by design.

Commit-Reveal Model

Four phases, each independently verified by this audit:

Commit-Reveal Model — illustrated flow

Commit phase
The server generates a 32-hex seed and publishes SHA-256 of that string (UTF-8) as activeServerSeedHash (recorded as hashedServerSeed). The raw seed stays secret. Verified for all 134 epochs (Step 1).

Bet phase
Bets settle under the committed pair; the nonce counts up from 0 (0–49 in this capture). The result is fixed per (seed, clientSeed, nonce) before the target is applied. Verified by full recomputation (Step 5).

Reveal phase
Rotation reveals the outgoing seed and pre-commits the next epoch’s hash — the chain is consistent on 133 / 133 transitions, plus the pre-capture link into epoch 0 (Step 2).

Verify phase
Anyone re-hashes the reveal against the commitment and recomputes every round it served — LIQD’s own verifier, the ProvablyFair.org verifier or the standalone scripts in S6 all do it.

Player-Controlled Client Seed

The client seed is the mechanism that structurally contains server-seed cherry-picking — when it is unpredictable and set after the commitment — and this audit exercised the control live:

  • Set by the player at every seed rotation — after the next server seed’s hash is already published
  • Mixed into every HMAC message (clientSeed:nonce:cursor) — it mathematically cannot be ignored
  • Phase D played 500 rounds under 10 client seeds we chose — all recomputed exactly
  • Sensitivity check: adding one character to the client seed changed the draw on 64 / 64 probed rounds (Step 18)
Nonce Lifecycle

The nonce is the bet counter under an active pair. In this capture the audit rotated the pair after every 50 bets: the nonce starts at 0 and increments by 1 per bet. A gap or reuse would be the fingerprint of a discarded outcome — the audit found neither, anywhere.

  • Contiguous 0–49 in every one of the 134 epochs
  • 0 gaps, 0 duplicates, 0 resets across 6,700 bets
  • Each seed record pins nonceStart / nonceEnd — the capture’s own record of the window it served
seed record · nonce window · Verified
"epoch": 0,"nonceStart": 0,"nonceEnd": 49,"hashedServerSeed": "394004d9c91573202b92489b53a5e8ef…"
Determinism Guarantee

Determinism is what turns a fairness claim into a checkable fact: the same inputs must always produce the same result. LIQD's derivation is a pure function of three arguments — verified by reproducing every captured result exactly, on the two-decimal grid, with an independent implementation.

the pure function · Verified
limboResult(serverSeed, clientSeed, nonce)  → 2.47   // one result, at least 1× — nothing else in, nothing else out
Phase Map

The capture is engineered — four phases, each isolating one claim, and a supplement that fills the high-target decades:

3 columns
PhaseRoundsWhat it isolates
A5,000Random targets from 1.01× to 49.45× (win chance drawn in 2%–98%), $0.10 — 1,104 distinct targets over the whole capture
B1,000The high-multiplier tail — 250 bets each at 100×, 1,000×, 100,000× and 500,000×
C200Stake independence — 2.00× at $10.00, 100× the $0.10 stake
D500Client-seed influence — 10 client seeds we chose, live
Supplement, 26 September300The high-target decades — 100 rounds each from 100× to 999.99×, from 1,000× to 9,999.99× and at 10,000× (Step 25)
Why phases matter: a random sample proves averages, but a designed capture proves mechanisms. Each phase removes one degree of freedom that an unfair implementation would need — targets across the range, the rare-win tail thickened, stake varied 100×, and ten auditor-chosen seeds.
Technical Glossary7 categories▶

Every term used in this report, defined the way the audit uses it.

Core Concepts
2 columns
TermDefinition
Provably FairA cryptographic scheme in which the game outcome can be independently verified by the player after the fact, using seeds committed to before the round. The player cannot alter the server seed; the server cannot alter it after committing to its hash; neither party can change the result without detection.
Commit-RevealThe mechanism underlying provably fair. The server publishes a commitment (activeServerSeedHash, recorded here as hashedServerSeed) before any bet, then reveals the plaintext seed (revealedServerSeed) when the seed rotates. The auditor checks SHA-256(utf8(serverSeed)) == hashedServerSeed for every epoch. All 134 revealed seeds in this dataset passed.
House EdgeThe operator’s cut. LIQD’s Limbo page states “Edge: 1.00%” and RTP 99%. The effective edge is derived independently for every target: because the win chance is rounded down to a whole number of draws, it is at least 1.00% on all 99,999,900 targets and at most 1.009982% on the targets up to 10,000× the server accepts (Step 16).
RTP (Return to Player)1 − houseEdge. For LIQD Limbo the theoretical RTP of a target is win chance × target, exactly: 98.990018% – 99.000000% across the 999,900 targets from 1.01× to 10,000× (Step 16), anchored by literal tallies of all 100,000,000 draws (Step 15) and by simulation (Step 23). Live-bet RTP (100.4475%) is informational only.
Anti-CircularityThe requirement that theoretical RTP and simulation expectations derive the win probability from the draw grid alone — floor(9.9 × 10⁹ ÷ 100m) winning draws out of 100,000,000 — never from an operator-supplied odds or win-chance field, so the RTP claim cannot validate itself (Steps 14, 15).
RTP Claim RuleRTP-CLAIM 1.2.0, part of the audit method: the RTP a casino publishes — on the game page, fairness page, help page, bet panel, rules or game settings — must hold at every setting to within ±0.1 percentage points. A house-side miss ends the verdict as RTP FIGURE OVERSTATED (exit 5). LIQD’s published 99% holds on all 999,900 Limbo targets the server accepts; the largest gap, −0.009982 pp, is at 9,989.91×.
Seed System
2 columns
TermDefinition
Server SeedA 32-character (16-byte) hexadecimal string generated by LIQD. Kept secret during betting, revealed after epoch rotation. Used as the HMAC key in its hex-decoded form: Buffer.from(serverSeed, 'hex').
Commitment / Server Seed HashSHA-256(utf8(serverSeed hex string)) — the text-hash convention: LIQD hashes the UTF-8 bytes of the 32-character hex string, not the 16 decoded bytes. Published before any bet under the pair. A revealed seed that fails this check would prove tampering. Distinct from the HMAC key, which uses the decoded bytes.
Client SeedA string the player supplies at each seed rotation, mixed into the HMAC message as ${clientSeed}:${nonce}:${cursor}. Changing it changes the draw — adding one character changed it on 64 / 64 probed rounds (Step 18). When the player sets an unpredictable seed after the server-seed commitment is published, it is the primary structural defence against server-seed cherry-picking; a predictable seed does not deliver that protection.
NonceAn incrementing integer identifying a bet's position within an epoch — 0–49 in this capture, which rotated the pair after 50 bets. Part of the HMAC message. Every epoch in this dataset has contiguous nonces 0–49 — no gaps.
EpochThe bets placed under one (serverSeed, clientSeed) pair — in this capture exactly 50, nonces 0–49. At epoch end the seed rotates and the previous server seed is revealed. This dataset covers 134 epochs.
Cryptographic Functions
2 columns
TermDefinition
HMAC-SHA256Hash-based Message Authentication Code using SHA-256. Produces a 256-bit (32-byte) deterministic output from a key and message. Here it generates the single uniform integer behind each result: the digest is scanned in 4-byte big-endian uint32 chunks, and the first chunk below the modulo-bias ceiling is taken % 100000000.
CursorThe third component of the HMAC message. Limbo makes one draw per round, so cursor = 0 for every round, and the digest’s eight 4-byte chunks are scanned in order. All eight would be rejected about 5.7 × 10⁻¹⁴ of the time — effectively never; no captured round needed more than its third chunk.
Modulo-Bias RejectionDiscarding uint32 values at or above maxFair = floor(2³² / 10⁸) × 10⁸ = 4,200,000,000 before taking % 100000000, so every draw is exactly equally likely. A chunk is discarded 2.2111% of the time: 126 of the 6,700 captured rounds had their first chunk discarded, and all 126 reproduce (Step 5).
Draw (k)The round’s random number: the first accepted chunk taken % 100000000 — one of 100,000,000 equally likely values, 0 to 99,999,999. The round worked through in Section 4.9 drew k = 924,515.
Result Mapmax(1, floor(9,900,000,000 / (k + 1)) / 100) — the draw k placed on the two-decimal result scale, never below 1×. The served result is compared with the recomputed value exactly (Step 5).
Verification Terms
2 columns
TermDefinition
Win Chancefloor(9,900,000,000 / ci) / 10⁸, with ci = 100 × target — the exact share of the 100,000,000 draws whose result reaches the target. An exact integer identity on all 1,104 captured targets, confirmed by literal full-grid tallies at 1.01×, 2×, 100× and 500,000× (Step 15).
Inclusive Win Ruleresult ≥ target — a result exactly on the target wins; a strict > rule would lose those rounds. Reproduced on all 6,700 win flags (Step 6).
Boundary RoundA round whose result equals its target exactly — the case the inclusive rule decides. The capture holds 20, against 18.24 expected, and LIQD paid all 20 — for example 1.16× on a 1.16× target, $0.116 on $0.10 (bet bYS2viIe…).
House Edge FloorBecause the win chance is rounded down to a whole number of draws, floor(N / ci) × ci ≤ N for every target — so the edge is never below 1.00%. Checked as integers on all 99,999,900 targets (Step 16); up to 10,000× it is at most 1.009982%.
Exact Settlementstake × target — both two-decimal values, so the product is exact at the 4th decimal: credit × 10⁸ = hundredths(stake) × hundredths(target) × 10⁴ on all 2,822 wins, with zero tolerance (Step 9); every loss pays 0 (Step 10).
Result FloorThe result never goes below 1×: 1% of draws have a raw value below 1 and are lifted (78 captured rounds; 147 show 1×). The floor never changes an outcome — every target is at least 1.01×.
Result CeilingLIQD’s game code names 1,000,000× as the maximum result. Only draws 0–97 (98 of 100,000,000) would go higher, and a result that high wins every target either way, so the maximum never changes an outcome. The highest captured result is 185,046.72×, from k = 534.
Game Mechanics
2 columns
TermDefinition
Target (params.target)The multiplier you bet on — any two-decimal value from 1.01× to 10,000×, the range LIQD’s server accepts. The only game input; there are no risk levels or variants. 1,104 distinct targets were captured.
ResultThe round’s outcome, a multiplier of at least 1×. It decides the win, but the payout is the target — the 185,046.72× round on a 1.17× target paid 1.17×.
Potential Payoutstake × target, limited to $50,000 per bet: LIQD’s refusal of a $0.10 bet at 1,000,000× named a $0.05 maximum stake for that target (0.05 × 1,000,000 = 50,000), and 250 captured rounds — 500,000× at $0.10 — were placed at exactly that limit.
Target CapThe highest target LIQD’s server accepts: 10,000×. On 2 October 2026 a control round at 10,000× settled, and four requests between 100,101.12× and 990,000.01× were refused with the server’s own message, “must be a multiple of 0.01 between 1.01 and 10000” (Step 25).
Audit Terms
2 columns
TermDefinition
Draw UniformityA simulation test: 2,000,000 draws binned into 100 cells against N/100, χ² with df = 99. χ² = 94.393, p = 0.6122 (Step 23); the 6,700 captured draws give p = 0.2315 on the same test.
Serial ScreenLag-1 autocorrelation and the Wald-Wolfowitz runs test on the 2,000,000 simulated draws, each judged at α/3: lag-1 z = 0.937, runs p = 0.8679 (Step 23).
Bonferroni CorrectionThe three Pass 1 screens (uniformity, lag-1, runs) each run at α/3 = 0.00333 so the family has level α = 0.01.
Played-Target ReturnThe Pass 2 cherry-pick statistic (the repo’s “served-target return”): each of the 134 revealed seeds is replayed for 10,000 nonces in 200 windows of 50, every window betting the 50 targets played in that epoch, and the captured window is ranked against the other 199. Rank-sum Z = −0.1711 against the −3.090 threshold (α 0.001) — no cherry-picking indicated (Step 24).
Cherry-Picking (Server-Seed)The residual risk in any beacon-free commit-reveal scheme: an operator with foreknowledge of the upcoming client seed could pre-evaluate candidate server seeds and commit to an unfavourable one. The structural defence is an unpredictable client seed set after the commitment — a fresh random seed in 124 of the 134 epochs; the statistical check is the played-target return in Pass 2, which catches, at least half the time, a selection putting 16.1% or more of the epochs’ captured windows in their worst decile.
Selective OrphaningA distinct residual channel: an operator observing a round server-side and orphaning or re-issuing an unfavourable result rather than surfacing it. In this capture the nonces run contiguously 0–49 in all 134 epochs (Step 4).
Hard FailAny FAIL on a scored step — a broken commitment or chain link, a result, win-flag, multiplier or credit mismatch, an operator-side nonce gap, a population or artifact-integrity fault. A hard fail ends the verdict as NOT PROVABLY FAIR (exit 1). None triggered: Passed 25/25, Hard fails 0.
Data Formats
2 columns
TermDefinition
winningAmountThe settle-response field holding the amount credited on a win or 0 on a loss. The audit's payout evidence: verified against betAmount × target at zero tolerance, and reconciled with the wallet-denominated winningAmountWallet on all 6,700 bets.
result, winThe served result (two decimals) and win flag, stored as LIQD returned them. result is compared exactly on the two-decimal grid; win is re-derived from the result and the target. The dataset’s multiplier column is written by our capture script — the target on a win, 0 on a loss — and checked against the target (Step 8).
Phase A / B / C / DThe four capture phases. A: 5,000 bets on random targets from 1.01× to 49.45×. B: 1,000 bets, 250 each at 100×, 1,000×, 100,000× and 500,000×. C: 200 bets on 2.00× at $10.00 (stake independence). D: 500 bets under 10 custom pfaudit- client seeds. A 300-round supplement on 26 September adds Phase A targets from 100× to 10,000× (Step 25).
01

Seed, Nonce & Determinism

Can the casino change your outcome after you bet?

Every Limbo round on LIQD is generated from three inputs: server seed, client seed, and nonce. LIQD commits to its server seed by publishing a SHA-256 hash before the seed is used — and pre-commits the next epoch’s hash at every rotation. After you rotate your seed, the server reveals the actual seed, and anyone can check it against the hash. A matching hash shows which seed goes with which rounds and in what order, and our capture records show each hash was published before its rounds — so the casino cannot change the result after you bet.

Commit-Reveal Verification

134 / 134seeds verified

What we verified

  • Every revealed server seed hashes to the commitment published for its epoch — 134 / 134
  • The next server seed’s hash is pre-committed at every rotation — the chain is consistent on 133 / 133 links, plus the pre-capture link into epoch 0
  • Client seed is player-controlled and set after the server commitment
  • The player supplies the client seed at every seed rotation — our capture set its own in all 134 epochs, and every round recomputes under it
  • Nonce increments by 1 per round with no gaps or duplicates across 134 seed epochs — nonces 0–49 in every one
  • The result is fully determined by the seed inputs before the target or stake is applied
  • Identical inputs always produce the same result — confirmed across all 6,700 rounds, exact on the two-decimal grid
  • Your client seed is a genuine input — changing it changes the draw

What this means for you

  • With the commitment shown first, the casino cannot change the result after you bet
  • An unpredictable client seed of your own adds randomness the server cannot predict or pre-select against
  • Every round has its own derivation — the nonce guarantees no two rounds share the same inputs
  • Any result can be independently verified using the public tools and repo
  • Outcomes stay verifiable months later — the revealed seeds and the algorithm are all you need
  • Server-seed cherry-picking is structurally contained when you set an unpredictable client seed after the next commitment is published — the operator cannot pre-evaluate seeds against it
134 / 134seeds verified
  • Every revealed server seed hashes to the commitment published for its epoch — 134 / 134
  • The next server seed’s hash is pre-committed at every rotation — the chain is consistent on 133 / 133 links, plus the pre-capture link into epoch 0
  • Client seed is player-controlled and set after the server commitment
  • The player supplies the client seed at every seed rotation — our capture set its own in all 134 epochs, and every round recomputes under it
  • Nonce increments by 1 per round with no gaps or duplicates across 134 seed epochs — nonces 0–49 in every one
  • The result is fully determined by the seed inputs before the target or stake is applied
  • Identical inputs always produce the same result — confirmed across all 6,700 rounds, exact on the two-decimal grid
  • Your client seed is a genuine input — changing it changes the draw
HMAC-SHA256 Pipeline — seed inputs through one HMAC draw to the result, checked against the target — illustrated flow
CheckResultEvidence
Seed hash integrityPass134 / 134 revealed seeds hash to their commitments — SHA-256 of the seed text, 0 mismatches (Step 1)
Next-seed chainPassConsistent on 133 / 133 rotations, plus the pre-capture commitment that links into epoch 0 (Step 2)
One commitment per epochPassAll 50 rounds in each of 134 epochs recompute under their epoch’s one revealed seed, and the committed and revealed sets are identical — 134 against 134 (Steps 3, 5)
Nonce sequencingPassNonces 0–49 in every epoch: 0 gaps, 0 duplicates, one client seed per epoch (Step 4)
Client seed originPassSet after the server commitment — a fresh random audit… seed in each of the 124 Phase A–C epochs, and 10 seeds we chose in Phase D (Step 18)
Client seed controlPassLIQD uses the seed the player sets — 500 / 500 Phase D rounds recompute under the 10 seeds we chose (Step 18)
Client seed influencePassAdding one character to the client seed changes the draw on 64 / 64 probed rounds; a wrong or empty client seed changes the draw on all 6,700 (Step 18)
Deterministic outputPassSame (serverSeed, clientSeed, nonce) always produces the same result — 6,700 / 6,700 reproduced exactly; two forward passes and a reverse pass agree on every round (Steps 5, 7)
Win-rule reproductionPass6,700 / 6,700 served win flags reproduced by result ≥ target, including the 20 rounds that landed exactly on their target (Step 6)
Seed hash integrityPass
134 / 134 revealed seeds hash to their commitments — SHA-256 of the seed text, 0 mismatches (Step 1)
Next-seed chainPass
Consistent on 133 / 133 rotations, plus the pre-capture commitment that links into epoch 0 (Step 2)
One commitment per epochPass
All 50 rounds in each of 134 epochs recompute under their epoch’s one revealed seed, and the committed and revealed sets are identical — 134 against 134 (Steps 3, 5)
Nonce sequencingPass
Nonces 0–49 in every epoch: 0 gaps, 0 duplicates, one client seed per epoch (Step 4)
Client seed originPass
Set after the server commitment — a fresh random audit… seed in each of the 124 Phase A–C epochs, and 10 seeds we chose in Phase D (Step 18)
Client seed controlPass
LIQD uses the seed the player sets — 500 / 500 Phase D rounds recompute under the 10 seeds we chose (Step 18)
Client seed influencePass
Adding one character to the client seed changes the draw on 64 / 64 probed rounds; a wrong or empty client seed changes the draw on all 6,700 (Step 18)
Deterministic outputPass
Same (serverSeed, clientSeed, nonce) always produces the same result — 6,700 / 6,700 reproduced exactly; two forward passes and a reverse pass agree on every round (Steps 5, 7)
Win-rule reproductionPass
6,700 / 6,700 served win flags reproduced by result ≥ target, including the 20 rounds that landed exactly on their target (Step 6)
✓  Commit-reveal verified

All 134 revealed seeds match their commitments, and the next-seed chain is consistent across 133 rotations plus the pre-capture link into epoch 0. Nonces run 0–49 in every epoch of this capture with no gaps. Outcomes are fully deterministic — the same server seed, client seed and nonce always produce the same result. With each commitment shown before its rounds, as our capture records show, the casino cannot change your result after you bet.

How It Works — Seed, Nonce & Determinism7 sections
1.1Server Seed Commitment

Before any round is placed under a seed pair, LIQD shows the SHA-256 hash of a secret 32-character hex server seed as activeServerSeedHash (recorded with every round as hashedServerSeed). The commitment hashes the seed text as a UTF-8 string, not its 16 decoded bytes: SHA-256(utf8(serverSeed)). The HMAC that draws each result is keyed by the decoded bytes instead, so a verifier must keep the two forms apart. When the pair rotates, the server seed is revealed and anyone can recompute the hash. A match shows the revealed seed is the committed one: a substitute would need a second preimage of SHA-256, and would fail every recomputation that follows.

Server Seed Commitment — illustrated flow
src/rng.ts · commitHash · Verified
/** * §1.2: the published commitment is SHA-256 over the ASCII text of the seed * (the hex characters as written, not the bytes they encode), as lowercase hex. */export function commitHash(serverSeedHexString: string): string {  return createHash("sha256").update(Buffer.from(serverSeedHexString, "ascii")).digest("hex");}
Result: 134 / 134 revealed server seeds hash to their published commitment (Step 1). Zero mismatches.

Real Example from Live Data:

limbo-master-6700bets.json · seed record (epoch 0)
{  "epoch": 0,  "phase": "A",  "clientSeed": "audita5a8f43bd18d",  "hashedServerSeed": "394004d9c91573202b92489b53a5e8eff34ac5b3d34dac76c7379cadd0f34ded",  "nextHashedServerSeed": "7d89efbe2fc3a4418eaee5086ea1f0a97931c53ce9bc200d8072bdf09b364af0",  "serverSeed": "ee4724f00cfc46e28425e9de3239d427",  "nonceStart": 0,  "nonceEnd": 49}

Verification:

verify-seed.js · Verified
// verify-seed.js — check any revealed LIQD server seed against its commitmentconst { createHash } = require('node:crypto');const serverSeed = 'ee4724f00cfc46e28425e9de3239d427';            // revealed 32-hex seed (epoch 0)const committed  = '394004d9c91573202b92489b53a5e8eff34ac5b3d34dac76c7379cadd0f34ded';const recomputed = createHash('sha256').update(serverSeed, 'utf8').digest('hex');console.log(recomputed === committed ? 'MATCH — seed is the committed one' : 'MISMATCH');
1.2Next-Seed Pre-Commitment Chain

At every seed rotation, the next hash LIQD pre-commits during an epoch (nextServerSeedHash, recorded as nextHashedServerSeed) must equal the commitment that becomes active after the rotation, so the successor seed is fixed before the previous epoch ends. Step 2 walks all 133 epoch transitions and checks each link, and also checks the pre-capture record: the seed active before the first audited round (1eea2dae…) hashes to its own commitment (6e7ed0a2…), and its pre-committed next hash is epoch 0’s hashedServerSeed (394004d9…). A hash chain shows the values are consistent and fixes their order; it does not carry a date. Our capture records show each commitment was published before the rounds it covers.

tests/steps/commitment.ts · Step 2 · Verified
  // ── Step 2: Next-seed pre-commitment chain ──────────────────────────────────  // …  const byEpoch = [...seeds].sort((a, b) => a.epoch - b.epoch);  // …  let promoChecked = 0;  let promoFails = 0;  for (let i = 0; i + 1 < byEpoch.length; i++) {    promoChecked++;    if (byEpoch[i].nextHashedServerSeed !== byEpoch[i + 1].hashedServerSeed) promoFails++;  }  // …    const preCommitOk = commitHash(pre.revealedServerSeed) === pre.hashedServerSeed;    const preLinkOk = pre.nextHashedServerSeed === epoch0.hashedServerSeed;    preOk = preCommitOk && preLinkOk;
Result: 133 / 133 next-seed transitions consistent, plus the pre-capture link into epoch 0. The pre-committed next server seed hash matched the promoted active hash at every rotation boundary — the chain has no breaks.
1.3Hash Consistency Within an Epoch

Each round record carries its epoch’s hashedServerSeed — the commitment recorded when the pair became active, which the capture writes onto every row. Step 3 groups the 6,700 rounds by epoch, confirms that all 50 rounds in each epoch carry the same commitment and that the epochs present are exactly 0–133, and then compares two sets hash for hash: the commitments the rounds settle against and the revealed seed records — 134 against 134, every one matched on both sides. What shows that no seed changed mid-epoch is the recomputation: every round reproduces under its epoch’s revealed seed (Step 5), and that seed hashes to the epoch’s commitment (Step 1).

tests/steps/commitment.ts · Step 3 · Verified
  const betsByEpoch = new Map<number, typeof bets>();  for (const b of bets) {    const arr = betsByEpoch.get(b.epoch) ?? [];    arr.push(b);    betsByEpoch.set(b.epoch, arr);  }  let epochsMultipleHashes = 0;  for (const [, epochBets] of betsByEpoch) {    if (new Set(epochBets.map((b) => b.hashedServerSeed)).size !== 1) epochsMultipleHashes++;  }  const missingEpochs: number[] = [];  for (let e = 0; e < EXPECTED_SEEDS; e++) if (!betsByEpoch.has(e)) missingEpochs.push(e);  const strayEpochs = [...betsByEpoch.keys()].filter((e) => e < 0 || e >= EXPECTED_SEEDS);  // The seed set and the set of commitments actually settled against must be the same set,  // hash for hash — not merely the same size.  const seedHashSet = new Set(seeds.map((s) => s.hashedServerSeed));  const betHashSet = new Set(bets.map((b) => b.hashedServerSeed));  const unbackedBetHashes = [...betHashSet].filter((h) => !seedHashSet.has(h));  const unusedSeedHashes = [...seedHashSet].filter((h) => !betHashSet.has(h));  const setsIdentical = unbackedBetHashes.length === 0 && unusedSeedHashes.length === 0    && seedHashSet.size === betHashSet.size;  const s3 = step(3, 'Hash Consistency & Epoch Coverage',    epochsMultipleHashes === 0 && missingEpochs.length === 0 && strayEpochs.length === 0 && setsIdentical ? 'PASS' : 'FAIL',    // …  );
Result: 134 / 134 epochs — one commitment across all 50 rounds of every epoch, epochs 0–133 all present, and the committed and revealed sets identical. Every round recomputes under its epoch’s revealed seed (Step 5), so no seed changed mid-epoch.
1.4Client Seed Origin & Control

The client seed is the player’s input to every draw. LIQD takes it when the seed pair rotates — the rotate request carries it — and at that moment the next server seed’s hash is already published, so the server seed is fixed before the client seed it will be paired with is known. That ordering structurally contains cherry-picking when the client seed is unpredictable: an operator that cannot know the upcoming client seed cannot pre-evaluate candidate server seeds against it. In this capture each of the 124 Phase A–C epochs ran on a fresh random client seed — audit plus 12 hex characters, 6 random bytes from Node’s CSPRNG — submitted at rotation after LIQD had published that epoch’s commitment, and the 10 Phase D epochs ran on seeds we chose. Five of the Phase A–C seeds, one every 30 epochs:

  • audita5a8f43bd18d — epoch 0
  • audit63e25885f5be — epoch 30
  • audit6e338dd45fcb — epoch 60
  • auditf26306e848f1 — epoch 90
  • auditdc525d958d88 — epoch 120
Result: LIQD uses the seed the player sets: all 500 Phase D rounds recompute under the 10 seeds we chose. Over all 6,700 rounds, a wrong client seed and an empty one each changed the draw on every round, and adding one character to the seed changed the draw on 64 / 64 probed rounds (Step 18).
1.5Nonce Incrementation

The nonce begins at 0 and increments by 1 for each round under the same seed pair — one nonce per result. It resets to 0 when the player rotates their seed, starting a new epoch. In this audit we rotated after every 50 rounds as our sampling cadence (nonces 0–49 per epoch, 134 epochs). Step 4 audits every epoch for gaps, reuse and resets: a skipped nonce is where a discarded unfavourable outcome would hide, so the sequence must be contiguous. It also checks one client seed per epoch, that each epoch opens at the post-rotation nonce LIQD reported (nonceStart, 134 / 134), and that each epoch holds exactly 50 rounds — bound to the constant in src/config.ts rather than read from the dataset header, so a withheld round cannot be absorbed by shrinking the declared epoch.

Nonce Incrementation — illustrated flow
tests/steps/commitment.ts · Step 4 · Verified
  for (const [hash, epochBets] of byHash) {    const sorted = [...epochBets].sort((a, b) => a.nonce - b.nonce);    const nonces = sorted.map((b) => b.nonce);    const epochNum = sorted[0].epoch;     if (new Set(sorted.map((b) => b.clientSeed)).size !== 1) {      hardFailures.push(`epoch ${epochNum}: ${new Set(sorted.map((b) => b.clientSeed)).size} distinct client seeds`);    }     const nonceSet = new Set(nonces);    // Nonce REUSE: interior contiguity alone treats a duplicated nonce as a benign gap, so a    // replayed draw would pass. Require distinct nonces == bet count.    if (nonceSet.size !== nonces.length) {      hardFailures.push(`epoch ${epochNum}: nonce reuse — ${nonces.length} bets, ${nonceSet.size} distinct nonces`);    }    // …    const outOfRange = nonces.filter((n) => !Number.isInteger(n) || n < 0 || n >= EXPECTED_EPOCH_SIZE);    if (outOfRange.length) hardFailures.push(`epoch ${epochNum}: ${outOfRange.length} nonce(s) outside [0, ${EXPECTED_EPOCH_SIZE})`);     const missing: number[] = [];    for (let n = 0; n < EXPECTED_EPOCH_SIZE; n++) if (!nonceSet.has(n)) missing.push(n);    // …      if (seedEntry.nonceStart != null) {        if (Math.min(...nonces) !== seedEntry.nonceStart) {          hardFailures.push(`epoch ${epochNum}: first observed nonce ${Math.min(...nonces)} != operator post-rotation nonce ${seedEntry.nonceStart} (leading bets withheld?)`);        }        leadingOperatorBound++;      }      // …      const declaredHere = DECLARED_CAPTURE_GAPS.filter((g) => g.epoch === epochNum && missing.includes(g.nonce)).length;      if (epochBets.length + declaredHere !== EXPECTED_EPOCH_SIZE) {        hardFailures.push(`epoch ${epochNum}: ${epochBets.length} bets + ${declaredHere} declared capture gap(s) != audited epoch size ${EXPECTED_EPOCH_SIZE} (src/config.ts)`);      }    // …
Result: 0 gaps and 0 duplicates across all 134 epochs. Nonces run 0–49 contiguously in every epoch — 6,700 rounds in a complete, ordered ledger, one client seed per epoch, and every epoch opens at the nonce LIQD reported (134 / 134).
1.6Deterministic Mapping

The result is a pure function of three inputs: (serverSeed, clientSeed, nonce) → draw k → result. One HMAC-SHA256 digest gives the draw k, an integer from 0 to 99,999,999 (2.1 shows how), and max(1, floor(9,900,000,000 / (k + 1)) / 100) turns it into the result, never below 1× (2.7). No timing, no stake, no target and no session state enters the derivation. Step 5 recomputes all 6,700 captured results exactly on the two-decimal grid — our draw (limboDraw) through the scored result map (resultMultiplier in src/config.ts), which equals our limboResult on every captured draw (the smallest is 534). Step 7 shows the derivation carries no state: the full 6,700-round recomputation run twice and once in reverse order agrees on every round, and 204 rounds recomputed five times each never change. The record below is the first round of the capture — recompute it yourself with the snippet.

Deterministic Mapping — illustrated flow
src/rng.ts · limboDraw, limboResult · Verified
/** §1.1 + §2 Step 2: the limbo draw uses range 10^8 and cursor 0. */export function limboDraw(serverSeed: string, clientSeed: string, nonce: number): number {  return generateProvablyFairNumber(serverSeed, clientSeed, nonce, 0, LIMBO_RANGE);} /** * §2 Step 3: raw_cents = floor(99 * 10^8 / (r + 1)) by integer division, * clamped up to 100 cents, then expressed as a 2-decimal multiplier. * No upper cap is applied (O3). */export function limboResult(serverSeed: string, clientSeed: string, nonce: number): number {  const r = limboDraw(serverSeed, clientSeed, nonce);  // Both operands are integers far below 2^53, so Math.floor of the double quotient  // equals true integer division here; BigInt is used anyway to make that explicit.  const rawCents = Number(BigInt(LIMBO_NUMERATOR) / BigInt(r + 1));  const resultCents = Math.max(RESULT_FLOOR_CENTS, rawCents);  return resultCents / 100;}
Result: All 6,700 rounds with revealed seeds: the recomputed result equals the served result exactly (Step 5), and two forward passes and a reverse pass agree on every round (Step 7). Zero mismatches, zero skipped. No external entropy is mixed into the Limbo RNG.

Real Round Verified:

limbo-master-6700bets.json · round USzRL9gy… (epoch 0, nonce 0) · Verified
{  "id": "USzRL9gyMBxZg8NLCNoYS",  "phase": "A",  "epoch": 0,  "nonce": 0,  "clientSeed": "audita5a8f43bd18d",  "params": { "target": 4.58 },  "result": 2.47,  "win": false,  "resultLabel": "lost",  "betAmount": 0.1,  "winningAmount": 0}

Verification:

verify-limbo.js · Verified
// verify-limbo.js — recompute a LIQD Limbo result from its seedsconst { createHmac } = require('node:crypto');function draw(seed, client, nonce) {  const digest = createHmac('sha256', Buffer.from(seed, 'hex'))    .update(`${client}:${nonce}:0`).digest();  const maxFair = Math.floor(2 ** 32 / 1e8) * 1e8;              // 4,200,000,000  for (let o = 0; o + 4 <= digest.length; o += 4) {    const chunk = digest.readUInt32BE(o);    if (chunk < maxFair) return chunk % 1e8;  }  throw new Error('all eight chunks rejected (about 5.7e-14 per round)');}const k = draw('ee4724f00cfc46e28425e9de3239d427', 'audita5a8f43bd18d', 0);const result = Math.max(100, Math.floor(9_900_000_000 / (k + 1))) / 100;console.log(k, result);           // → 40058996 2.47console.log(result >= 4.58);      // → false  (target 4.58× — a loss)
1.7Win-Rule Reproduction & Client Seed Influence

Determinism covers the number; Step 6 covers what LIQD did with it. For every round the recomputed result is compared with the target under the rule win = result ≥ target, and the outcome must equal the served win flag: 6,700 / 6,700 agree. The rule is inclusive, and the settled data show it: 20 rounds landed exactly on their target — 18.24 were expected from the targets played — and LIQD settled all 20 as wins and paid each one stake × target (Steps 6, 9), where a strict > rule would have lost every one. Bet bYS2viIeJOfIRm1YpweuJ is one of them: a 1.16× result on a 1.16× target, paid $0.10 × 1.16 = $0.116. The 1× floor never wins, because every target is at least 1.01× — all 147 rounds at 1× lost. To confirm the client seed is a genuine input, Step 18 recomputes all 500 Phase D rounds under the seeds we chose (500 / 500 reproduce), adds one character to the seed on 64 probed rounds (the draw changed on 64 / 64), and runs a wrong-seed and an empty-seed control over all 6,700 rounds — the draw changed on every one of them.

tests/steps/determinism.ts · Step 6 · Verified
  for (const b of bets) {    const target = Number(b.params.target);    // …    if (!isTargetLegal(target)) illegalTargets++;    const ss = seedMap.get(b.hashedServerSeed);    if (!ss) continue;    const local = resultMultiplier(limboDraw(ss, b.clientSeed, b.nonce));    if (limboWin(local, target) !== !!b.win) {      winFails++;      if (!firstWinFail) firstWinFail = `epoch ${b.epoch} nonce ${b.nonce}: R ${local} vs target ${target}, served win=${b.win}`;    }    if (local === target) { discriminating++; if (b.win) discriminatingWins++; }    if (local === MIN_RESULT) { floorRounds++; if (b.win) floorWins++; }    if ((local > target) !== !!b.win) flipsUnderStrictGt++;    winChecked++;  }   const s6 = step(6, 'Win-Rule Reproduction',    winFails === 0 && illegalTargets === 0 && winChecked === EXPECTED_BETS      && floorWins === 0 && MIN_RESULT < MIN_MULTIPLIER ? 'PASS' : 'FAIL',    // …  );
Result: 6,700 / 6,700 served win flags reproduced by result ≥ target on the recomputed result (Step 6), including all 20 rounds exactly on their target; 500 / 500 custom-seed rounds recompute and a changed client seed changes the draw on 64 / 64 probes (Step 18). The client seed is a genuine, material input.
Technical Evidence & Verification5 sections
1.8Evidence Coverage Summary8 checks
3 columns
Verification AreaCoverageResult
Seed hash integrity (Step 1)134 / 134 revealed seeds hash-verifiedPass
Next-seed pre-commitment chain (Step 2)133 / 133 epoch transitions + the pre-capture link into epoch 0Pass
Hash consistency & epoch coverage (Step 3)134 / 134 epochs (50 rounds each); committed and revealed sets identicalPass
Nonce audit (Step 4)0 gaps, 0 duplicates across 134 epochs; one client seed per epochPass
Result recomputation (Step 5)6,700 / 6,700 — exact on the two-decimal gridPass
Win-rule reproduction (Step 6)6,700 / 6,700 served win flags; all 20 rounds exactly on target settled as winsPass
Recomputation determinism (Step 7)Two forward passes + one reverse pass agree on 6,700 / 6,700; 204 rounds × 5 repeats stablePass
Custom client-seed control (Step 18)500 / 500 Phase D rounds recompute; 64 / 64 probes change the draw; wrong- and empty-seed controls change the draw on 6,700 / 6,700Pass
1.9Code References8 source files
2 columns
FilePurpose
tests/verify.ts25-step verification pipeline (Steps 1–7 and 18 cover S1)
tests/steps/commitment.tsSteps 1–4: commit-reveal integrity checks and nonce audit
tests/steps/determinism.tsSteps 5–7: result recomputation, win-rule reproduction and recomputation determinism
tests/steps/phase-d.tsStep 18: custom client-seed control, sensitivity probe and the wrong- and empty-seed controls
src/rng.tsHMAC-SHA256 draw and result (commitHash, generateProvablyFairNumber, limboDraw, limboResult) — written from docs/ALGORITHM.md
src/config.tsGame constants, the scored result map (resultMultiplier), the win rule (limboWin) and the audited population (134 epochs of 50)
src/loader.tsDataset loading + SHA-256 hash guard
capture/capture-limbo.reference.mjsCapture methodology record (Phases A–D), credentials removed
1.10Datasets UsedSource artifacts
Primary: data/limbo-master-6700bets.json
2 columns
PropertyValue
SourceLive Limbo game data from LIQD’s pre-launch environment (qa.liqd.com) — 6,700 real rounds across 4 phases, USDC
Capture environmentPre-launch host qa.liqd.com · one authenticated account · one session
Total Records6,700 rounds across 134 epochs (50 rounds each), plus 134 seed records
Captured11 September 2026 (13:16–14:12 UTC)
SHA-2568e3ce5b5e1494d1fcc3a783965d3ba7b6a7099f120a64d61e21a577098c07334

Fields used: serverSeed, hashedServerSeed, nextHashedServerSeed, nonceStart, nonceEnd, clientSeed, nonce, params.target, result, win, resultLabel, betAmount, winningAmount, stakeWallet, winningAmountWallet, and the pre-capture record meta.preCapture

src/loader.ts recomputes the SHA-256 above and aborts the suite before any step runs on a mismatch. The capture methodology ships as a reference record (capture/capture-limbo.reference.mjs — credentials removed; it documents the capture, it does not replay it). Every computational claim downstream of the dataset is reproducible from the file itself.

1.11Verified Invariants11 invariants
2 columns
InvariantResult
SHA-256(utf8(serverSeed)) = hashedServerSeed for all 134 revealed seedsPass
Next-seed pre-commitment chain intact for all 133 transitions, plus the pre-capture link into epoch 0Pass
Every round recomputes under its epoch’s revealed seed — no seed changed mid-epoch (Steps 3, 5)Pass
The commitments the rounds settle against and the revealed seed records are the same set — 134 against 134Pass
Zero nonce gaps within any epoch (contiguous 0–49)Pass
Zero nonce duplicates within any epochPass
Every epoch contains exactly 50 rounds, this capture’s cadence (134 / 134) — bound to the code constant, not the dataset headerPass
Every epoch opens at the post-rotation nonce LIQD reported (nonceStart = 0) — 134 / 134Pass
Same inputs produce the same result, exact on the two-decimal grid, for all 6,700 roundsPass
The recomputed result reproduces the served win flag under result ≥ target for all 6,700 roundsPass
Custom client seeds honoured: 500 / 500 Phase D rounds recompute; a changed seed changes the draw on 64 / 64 probesPass
1.12Reproduction InstructionsRun locally

Clone the repository, install dependencies, and run the verification suite:

reproduce-s1.sh · 5 lines · Verified
git clone https://github.com/ProvablyFair-org/liqd-limbo.gitcd liqd-limbo && git checkout v1.0.1npm cinpm run verify# Expected output: Steps 1–7 and 18 all PASS
Steps 1–7 and 18 cover all Seed, Nonce & Determinism checks. Expected output:

  [PASS] Step 1 — Seed Hash Integrity
  [PASS] Step 2 — Next-Seed Pre-Commitment Chain
  [PASS] Step 3 — Hash Consistency & Epoch Coverage
  [PASS] Step 4 — Nonce Audit
  [PASS] Step 5 — Draw & Result Recomputation
  [PASS] Step 6 — Win-Rule Reproduction
  [PASS] Step 7 — Recomputation Determinism
  [PASS] Step 18 — Custom Client-Seed Control (Phase D)
02

RNG & Entropy Model

Is the randomness genuinely random, or could it be rigged?

This section verifies that LIQD’s Limbo random number generation produces cryptographically sound, unbiased outputs using only the disclosed inputs. Each round makes one HMAC-SHA256 draw — the first 4-byte chunk of the digest below the maxFair ceiling, reduced modulo 100,000,000 to a draw k from 0 to 99,999,999, with bias-free rejection sampling — and max(1, floor(9,900,000,000 ÷ (k + 1)) ÷ 100) turns the draw into the result, never below 1×. The implementation is our own, written from a specification without LIQD’s code; it reproduces all 6,700 live results, and LIQD’s own verifier serves only as a cross-check.

Cryptographic Randomness Verification

6,700 / 6,700captured results reproduced

What we verified

  • Each round’s draw comes from HMAC-SHA256, a standard keyed hash whose output cannot be predicted without the server seed
  • Only disclosed inputs affect outcomes — no timestamps, no server-side state, no hidden entropy
  • Rejection sampling removes modulo bias exactly — every one of the 100,000,000 draws is equally likely, and 126 captured rounds show LIQD applies the guard
  • The RNG is our own implementation, written from the specification — it reproduces all 6,700 captured results
  • Draw uniformity and serial independence pass their screens on 2,000,000 simulated draws, and 10 targets × 1,000,000 simulated rounds all land within 5 SD + 1 win of their exact win count (Step 23)
  • A changed client seed changes the draw — 64 / 64 sensitivity probes, 500 / 500 custom-seed rounds

What this means for you

  • Each result is generated fairly and cannot be skewed
  • All 100,000,000 draws are equally likely — no result comes up more often than the published formula gives
  • No hidden randomness or server-side tricks influence the result
  • Consecutive rounds are not correlated — past results don’t affect future outcomes
  • The algorithm depends only on seeds you can verify
6,700 / 6,700captured results reproduced
  • Each round’s draw comes from HMAC-SHA256, a standard keyed hash whose output cannot be predicted without the server seed
  • Only disclosed inputs affect outcomes — no timestamps, no server-side state, no hidden entropy
  • Rejection sampling removes modulo bias exactly — every one of the 100,000,000 draws is equally likely, and 126 captured rounds show LIQD applies the guard
  • The RNG is our own implementation, written from the specification — it reproduces all 6,700 captured results
  • Draw uniformity and serial independence pass their screens on 2,000,000 simulated draws, and 10 targets × 1,000,000 simulated rounds all land within 5 SD + 1 win of their exact win count (Step 23)
  • A changed client seed changes the draw — 64 / 64 sensitivity probes, 500 / 500 custom-seed rounds
RNG Pipeline — HMAC-SHA256 → first chunk below 4,200,000,000 → mod 100,000,000 → k → result — illustrated flow
CheckResultEvidence
RNG derived only from disclosed inputsPassHMAC-SHA256(serverSeed, clientSeed:nonce:0) — one draw per round, no hidden entropy
Entropy purityPassNo timestamps, external APIs, Math.random, or server-side state
Our own implementationPassWritten from the specification (docs/ALGORITHM.md) by an implementer who never opened LIQD’s code; its 72 specification checks pass and it reproduces all 6,700 scored results exactly — 7,004 / 7,004 captured rounds in all
LIQD verifier cross-checkPassOur implementation reproduces the 4 values read from LIQD’s Verify calculator (unit test tests/limbo/rngTests.ts) and both replies of LIQD’s /verify endpoint at the boundary (Step 19) — a cross-check, not the source
Modulo biasPassRejection ceiling 4,200,000,000 = 42 × 100,000,000 — bias 0, computed from the bound the draw itself applies; 126 captured rounds had their first chunk discarded and all 126 reproduce (Step 5)
Key encoding verifiedPassServer seed hex-decoded to bytes for the HMAC key; the commitment hashes the seed text — both confirmed by recomputing every seed and every round (Steps 1, 5)
Serial independencePassLag-1 z = 0.937 against the applied critical 2.936; runs test p = 0.8679 — both clear at α/3 over 2,000,000 simulated draws (Step 23); the captured draws in capture order show no dependence either (lag-1 z −0.348, runs p 0.6423; Step 22, informational)
Client seed influencePassA changed client seed changes the draw on 64 / 64 probes; all 500 Phase D custom-seed rounds recompute (Step 18)
RNG derived only from disclosed inputsPass
HMAC-SHA256(serverSeed, clientSeed:nonce:0) — one draw per round, no hidden entropy
Entropy purityPass
No timestamps, external APIs, Math.random, or server-side state
Our own implementationPass
Written from the specification (docs/ALGORITHM.md) by an implementer who never opened LIQD’s code; its 72 specification checks pass and it reproduces all 6,700 scored results exactly — 7,004 / 7,004 captured rounds in all
LIQD verifier cross-checkPass
Our implementation reproduces the 4 values read from LIQD’s Verify calculator (unit test tests/limbo/rngTests.ts) and both replies of LIQD’s /verify endpoint at the boundary (Step 19) — a cross-check, not the source
Modulo biasPass
Rejection ceiling 4,200,000,000 = 42 × 100,000,000 — bias 0, computed from the bound the draw itself applies; 126 captured rounds had their first chunk discarded and all 126 reproduce (Step 5)
Key encoding verifiedPass
Server seed hex-decoded to bytes for the HMAC key; the commitment hashes the seed text — both confirmed by recomputing every seed and every round (Steps 1, 5)
Serial independencePass
Lag-1 z = 0.937 against the applied critical 2.936; runs test p = 0.8679 — both clear at α/3 over 2,000,000 simulated draws (Step 23); the captured draws in capture order show no dependence either (lag-1 z −0.348, runs p 0.6423; Step 22, informational)
Client seed influencePass
A changed client seed changes the draw on 64 / 64 probes; all 500 Phase D custom-seed rounds recompute (Step 18)
✓  Unbiased and Cryptographically Sound

The Limbo RNG uses only the disclosed inputs, and our own implementation — written from the specification — reproduces every captured result. The simulated draws are consistent with a uniform draw across the 100,000,000-point grid, the serial screens find no sign of dependence, and 10 targets simulated at 1,000,000 rounds each all land within 5 SD + 1 win of their exact win count, the worst at 1.56 SD (Step 23). The rejection guard is applied live — 126 captured rounds depend on it — and the client seed is a genuine input: adding one character to it alters the draw on every probe.

How It Works — RNG & Entropy Model8 sections
2.1RNG Function Implementation

Each Limbo round makes exactly one draw. An HMAC-SHA256 digest is computed with the hex-decoded server seed as key and clientSeed:nonce:cursor as the message, where cursor is 0. The 32-byte digest is scanned in up to eight 4-byte big-endian chunks; the first chunk below the maxFair ceiling of 4,200,000,000 is reduced % 100000000 to give the draw k, an integer in [0, 100000000). The result is max(1, floor(9,900,000,000 / (k + 1)) / 100) — two decimals, never below 1×. All eight chunks would be rejected about 5.7 × 10⁻¹⁴ of the time — effectively never; no captured round needed more than its third chunk. The target and stake never enter this function — they are applied to the result afterwards. The scored steps compute the same result map with resultMultiplier in src/config.ts, which also applies LIQD’s game-code maximum of 1,000,000×; the two agree on every draw except k ≤ 97 (98 in 100,000,000), where either value wins every target.

2 columns
ComponentDetail
Hash functionHMAC-SHA256 (32-byte digest)
Keyhex_decode(serverSeed) — 16 raw bytes (distinct from the commitment domain, which hashes the seed text)
MessageclientSeed:nonce:cursor — UTF-8, literal colons, no padding; cursor = 0
Extraction4-byte big-endian chunks → readUInt32BE → uint32, up to 8 per digest
Rejectionchunk < maxFair where maxFair = floor(2³² / 10⁸) × 10⁸ = 4,200,000,000
Reductionuint32 % 100000000 → draw k in [0, 100,000,000)
Resultmax(1, floor(9,900,000,000 / (k + 1)) / 100) → two decimals, from 1×
Exhaustion pathAll 8 chunks rejected → our implementation stops with an error (≈ 5.7 × 10⁻¹⁴ per round; no captured round needed more than its third chunk)
OutputOne result per round — the target is applied to it afterwards
src/rng.ts · walkChunks, generateProvablyFairNumber · Verified
function walkChunks(digest: Buffer, range: number): { index: number; r: number } | null {  const limit = Math.floor(TWO_POW_32 / range) * range;  for (let i = 0; i + CHUNK_BYTES <= DIGEST_BYTES; i += CHUNK_BYTES) {    const v = digest.readUInt32BE(i);    if (v < limit) {      return { index: i / CHUNK_BYTES, r: v % range };    }  }  return null;} /** * §2 Steps 1-2: the accepted draw r in [0, range). A rejected chunk moves to the * next chunk of the same digest; the cursor is NOT advanced (§2 Step 2). If all * 8 chunks are rejected this throws (O1). */export function generateProvablyFairNumber(  serverSeed: string,  clientSeed: string,  nonce: number,  cursor: number,  range: number,): number {  assertRange(range);  const hit = walkChunks(roundDigest(serverSeed, clientSeed, nonce, cursor), range);  if (hit === null) {    throw new Error(      // …    );  }  return hit.r;}
Result: Our implementation reproduces the result of every captured round — 7,004 of 7,004, the 6,700 scored rounds included — and Step 5 recomputes all 6,700 on every run through the scored result map, with zero mismatches. It was written from a specification (docs/ALGORITHM.md) by an implementer who never opened LIQD’s code, its client bundle or any port of it, and the specification’s 72 checks pass (tests/limbo/RngSpecTests.ts). LIQD’s own verifier is used only as a cross-check — our implementation reproduces the 4 values read from LIQD’s Verify calculator (tests/limbo/rngTests.ts) and both /verify replies (Step 19).
2.2Entropy Sources

All randomness derives exclusively from the deterministic HMAC-SHA256 function combining three cleanly separated inputs — each one part of the round record or the seed record:

Entropy Sources — illustrated flow
3 columns
SourceControlled ByPurpose
Server SeedCasinoBase randomness — the HMAC key, committed via SHA-256 hash of the seed text before betting
Client SeedPlayerPlayer-contributed entropy — mixed into every HMAC message
NonceSystemUniqueness per round (0–49 within each epoch in this capture, audited gap-free)
CursorSystemFixed at 0 — one draw per round. An index, not an entropy source
No mixed entropy sources detected. Run the same inputs multiple times — results are always identical. Timing, stake, target, balance and session state are all absent from the derivation. 6,700 / 6,700 confirmed.

How we know: 6,700 / 6,700 rounds were recomputed using only the three declared inputs. If any hidden entropy source existed, recomputation would fail. It does not.

Verified absent: No timestamps, no Math.random(), no external APIs, no server-side mutable state, and no external beacon (no drand round, block hash, or VRF). Only: HMAC-SHA256(hexDecode(serverSeed), clientSeed:nonce:0). Because no external entropy is used, cherry-pick containment is structural rather than cryptographic — the commitment plus the player-controlled client seed carry the fairness; S4 covers exactly what that means and how it is checked.

2.3Modulo Bias Analysis

The draw is chunk % 100000000. Modulo bias occurs because 2^32 is not evenly divisible by 100,000,000 — the 32-bit range holds 42 complete cycles of 100,000,000 plus a remainder of 94,967,296 values, so without a guard every draw below 94,967,296 would be reachable one extra time: 43 ways instead of 42, about 2.38% more often. The algorithm eliminates this bias entirely via rejection sampling: a ceiling maxFair = floor(2^32 / 100000000) × 100000000 ensures only unbiased chunks are used. Any chunk ≥ maxFair is discarded and the next 4-byte chunk of the same digest is tried:

range                = 100,000,000
2^32                 = 4,294,967,296
floor(2^32 / range)  = 42
maxFair = 42 × 100,000,000 = 4,200,000,000
dead values          = 2^32 − maxFair = 94,967,296
P(chunk rejected)    = 94,967,296 / 2^32 ≈ 2.2111%   (about one round in 45)
P(all 8 chunks rejected) = 0.022111^8 ≈ 5.7e-14
draws in this capture    = 6,700 (one per round)
expected first-chunk rejections ≈ 6,700 × 2.2111% ≈ 148.15
observed                  = 126 rounds (128 chunks: 124 rounds took the second chunk, 2 the third)
Result: Zero modulo bias confirmed. Rejection sampling via the maxFair ceiling makes every draw exactly equally likely, and LIQD applies the same guard: all 126 captured rounds whose first chunk fell in the discarded range reproduce (Step 5).

Below maxFair, each of the 100,000,000 draws owns exactly 42 chunk values — perfectly uniform. The 94,967,296 values at the top of the 32-bit space are discarded. Step 5 computes this bound from the same rule the draw applies (src/bias.ts, tied to src/rng.ts by an acceptance probe of 4,000 fixed rounds, 81 of which exercise the discarded range) and fails the audit if the accepted window is not a whole number of ranges. Limbo’s range makes the guard fire on about one round in 45, so the capture tests it live: 126 of the 6,700 rounds had their first chunk discarded, and our guarded draw reproduces the served result on all 126. A draw without the guard would have given a different result on every one of them — 2.8 traces the first.

2.4RNG Isolation

Each round uses a unique nonce in the HMAC message (clientSeed:nonce:0), ensuring per-round outputs are cryptographically independent. HMAC-SHA256 is a pseudorandom function — knowing one result gives no usable information about any other result under the same seed pair. There is no shared state between rounds or between epochs, and the target and stake have nowhere to enter: the draw is produced first, in isolation, and the target is applied to its result afterwards.

RNG Isolation — illustrated flow
Result: RNG isolation confirmed. No state leakage between rounds or epochs. Each HMAC call takes its key and message afresh.

Evidence: the limboDraw function in 1.6 takes exactly three arguments (serverSeed, clientSeed, nonce) — no stake, no target, no timestamp, no account identifier — and returns a number. Same inputs always produce the same output; if any hidden input influenced the result, our implementation (which lacks that input) could not reproduce 6,700 / 6,700 rounds. The strongest cross-phase evidence: Phase C repeats the game at $10.00 (100× the $0.10 stake) and Phase D swaps in ten client seeds we chose — both recompute under the identical three-argument function (Steps 13, 18).

2.5Monte Carlo Simulation — Pass 1 and the 38M Reference Run

Two simulations check the algorithm at scale, both on seed pairs of our own — derived from recorded master seeds, independent of LIQD’s seeds and rotated every 50 draws, like the epochs of this capture — so each one replays byte for byte. Pass 1 (Step 23) is re-derived on every verify run: 2,000,000 draws binned into 100 cells for uniformity and screened for serial structure, and 10 targets from 1.01× to 100,000× at 1,000,000 rounds each, every one compared with its exact win chance. The reference run has three parts — 2,000,000 uniformity draws, a 20,000,000-round convergence run at 2.00× and 8 targets at 2,000,000 rounds each — 38,000,000 simulated rounds, regenerated byte for byte by npm run test:full; S4’s convergence chart is drawn from it.

2 columns
Pass 1 (Step 23)Value
Draws and rounds2,000,000 uniformity draws + 10 targets × 1,000,000 rounds
Seedssha256(master | stream | block) from the recorded master seed pf.org/liqd-limbo/pass1/2026-09-26 — replayed byte for byte by npm run pass1:check
Draw uniformity (χ², 100 bins, df = 99)χ² = 94.393, p = 0.6122 — pass at α/3
Serial screenlag-1 z = 0.937 < 2.936; runs test p = 0.8679 ≥ 0.00333
Targets10 / 10 within 5 SD + 1 win of the exact win count; largest deviation 1.56 SD (10×)
6 columns
TargetWins of 1,000,000ExpectedzSimulated RTPExact RTP
1.01×980,236980,198.0+0.2799.0038%98.999999%
1.50×659,544660,000.0−0.9698.9316%99.000000%
2×494,575495,000.0−0.8598.9150%99.000000%
5×197,421198,000.0−1.4598.7105%99.000000%
10×98,53499,000.0−1.5698.5340%99.000000%
50×19,99119,800.0+1.3799.9550%99.000000%
100×9,9819,900.0+0.8299.8100%99.000000%
1,000×1,003990.0+0.41100.3000%99.000000%
10,000×11499.0+1.51114.0000%99.000000%
100,000×89.9−0.6080.0000%99.000000%
2 columns
Reference runValue
Rounds38,000,000 — 2M uniformity draws + 20M at 2.00× + 8 targets × 2M
Seedssha256(master | stream | n) from the recorded master seed pf.org/liqd-limbo/simulate/2026-09-26 — regenerated byte for byte by npm run test:full
Draw uniformity (χ², 100 bins, df = 99)χ² = 110.884, p = 0.1949
Serial screen (first 200,000 draws)lag-1 Z = −0.62 (critical 2.576); runs Z = −0.898, p = 0.3694
RTP convergence (20M at 2.00×)99.0128% against 99% — 0.57 SE
4 columns
Reference run targetSimulated RTPExact RTPDistance
1.1×99.0163%99.0000%0.7 SE
1.5×99.0135%99.0000%0.27 SE
2×98.9224%99.0000%1.1 SE
5×98.9073%99.0000%0.66 SE
10×99.2695%99.0000%1.28 SE
20.14×99.2348%99.0000%0.76 SE
100×98.5650%99.0000%0.62 SE
1,000×97.5000%99.0000%0.67 SE
src/pass1-grid.ts · pass1Seeds, pass1Draw · Verified
/** Deterministic seed pair for (stream, block): 32-hex server seed (operator format), 16-hex client seed. */export function pass1Seeds(stream: string, block: number): { serverSeed: string; clientSeed: string } {  const h = createHash('sha256').update(`${PASS1_MASTER_SEED}|${stream}|${block}`).digest('hex');  return { serverSeed: h.slice(0, 32), clientSeed: h.slice(32, 48) };} /** Draw i of a stream: epochs of PASS1_EPOCH nonces, a fresh seed pair per epoch (like the real game). */export function pass1Draw(stream: string, i: number): number {  const { serverSeed, clientSeed } = pass1Seeds(stream, Math.floor(i / PASS1_EPOCH));  return generateProvablyFairNumber(serverSeed, clientSeed, i % PASS1_EPOCH, CURSOR, RANGE);}
Result: Pass 1 re-derived on every run: the uniformity and serial screens clear at the Bonferroni-corrected level, and all 10 targets land within 5 SD + 1 win of their exact win count — largest deviation 1.56 SD (Step 23). The 38M-round reference run, regenerated byte for byte, converges to 99.0128% at 2.00× — 0.57 standard errors from 99%.

Step 23 decides every statistic itself, from the artifact’s integer rows — the 100-bin histogram, the integer sums behind the lag-1 and runs tests, and the win count of every target — and replays the first 50,000 draws and 20,000 rounds of every stream from the master seed on every run, so the rows are bound to the declared seeds. The three screens (uniformity, lag-1, runs) are each judged at α/3 = 0.00333, so the family has level α = 0.01; each target must land within 5 standard deviations plus one win of its exact win count, floor(9,900,000,000 / (100 × target)) of every 100,000,000 draws.

2.6Serial Independence

Serial independence ensures consecutive results are not correlated — a high result on one round does not make the next more or less likely to be high. Step 23 applies two tests to Pass 1’s 2,000,000 draws in stream order, each judged at the Bonferroni-corrected α/3 = 0.00333 (the third screen in the family is uniformity itself):

Lag-1 autocorrelation: The lag-1 autocorrelation of consecutive draws’ 100-bin index, r₁ = 0.000663, standardised as z = r₁ · √n = 0.937. The critical value is the two-sided normal quantile at α/3, 2.936; z clears it comfortably. Step 23 re-derives r₁ from the integer sums the artifact stores.

Wald-Wolfowitz runs test: Tests whether the sequence of draws above and below the midpoint has the expected number of runs: 1,000,117 runs against 999,999.4 expected, z = 0.166, two-sided p = 0.8679 — clear at α/3.

Reference run: The reference run screens the raw draws of its first 200,000 at α = 0.01: lag-1 Z = −0.62 (critical 2.576), runs Z = −0.898, p = 0.3694.

Captured draws (Step 22, informational): The 6,700 draws recomputed from the revealed seeds, in capture order: lag-1 z = −0.348, runs p = 0.6423; their fit to the uniform domain gives χ² p = 0.2315 and Kolmogorov–Smirnov p = 0.6985. These fits are reported for information rather than scored; every result is recomputed round by round in Step 5.

tests/steps/pass1.ts · serial screens, re-derived · Verified
  // 3. serial, re-derived from the integer sums  const mean = Number(u.sumB) / n;  const varSum = Number(u.sumB2) - n * mean * mean;  const lagSum = Number(u.sumLag) - (n - 1) * mean * mean;  const r1 = varSum > 0 ? lagSum / varSum : NaN;  const lagZ = r1 * Math.sqrt(n);  const n1 = Number(u.above), n0 = n - n1;  const muR = (2 * n1 * n0) / n + 1;  const varR = (2 * n1 * n0 * (2 * n1 * n0 - n)) / (n * n * (n - 1));  const runsZ = (Number(u.runs) - muR) / Math.sqrt(varR);  const runsP = twoSidedNormalP(runsZ);  const perTest = PASS1_ALPHA / 3;  const zCrit = inverseCriticalZ(perTest);  const uniformOk = histOk && chi.pValue >= perTest;  const serialOk = Number.isFinite(lagZ) && Math.abs(lagZ) < zCrit && Number.isFinite(runsP) && runsP >= perTest;
Result: Both serial screens clear at α/3: lag-1 z = 0.937 against 2.936, runs p = 0.8679 (Step 23). The captured draws show no sign of serial dependence either (Step 22).
2.7The Result Map & the 1× Floor

The draw k becomes the result through floor(9,900,000,000 / (k + 1)) / 100, lifted to 1× where it falls below: the smaller the draw, the higher the result. At the bottom of the scale, any draw above 98,999,999 — 1,000,000 of the 100,000,000, exactly 1.0000% — gives a raw value of 0.99, and LIQD serves it as 1×: 147 scored rounds show 1×, 78 of them lifted from 0.99, against 132.67 and 67.00 expected. The floor never changes a win or a loss, because every target is at least 1.01×. At the top, the formula reaches 99,000,000× at k = 0; LIQD’s game code names 1,000,000× as the maximum result, which only draws 0–97 would exceed — 98 in 100,000,000 — and a result that high wins every target either way. The highest captured result is 185,046.72×, from k = 534. The transitions:

3 columns
Draw kResultWhat it shows
099,000,000×the top of the scale — LIQD’s game code names 1,000,000× as the maximum; draws 0–97 exceed it and win every target either way
981,000,000×exactly 1,000,000.00×
99990,000×the first draw below 1,000,000×
534185,046.72×the highest captured result (bet S4mFs74B…)
98,019,8001.01×the last draw above 1×
98,019,8011×the first draw giving 1× — raw value 1.00, no lift
98,999,9991×the last draw with raw value 1.00
99,000,0001×the first draw lifted (raw 0.99)
99,999,9991×the bottom of the domain
Result: The result map reproduces all 6,700 served results, the 78 lifted rounds included (Step 5), and all 147 rounds at 1× lost, as they must (Step 6).
2.8Worked Example — Full RNG Trace

Real round from the dataset — bet krv88h79b6gDGRTDgswi9 (Phase A, epoch 5), nonce 2, target 1.59×: the earliest round in capture order whose first chunk was discarded. Verified from limbo-master-6700bets.json. One HMAC digest, eight candidate chunks — the first is at or above the ceiling, so the scan moves on to the second:

serverSeed = b73582d05dec4dd4a43b4e0b72ec3ebd
clientSeed = audited5df347a318
nonce      = 2
cursor     = 0
message    = audited5df347a318:2:0
digest     = fd8e759aaade3ebec782575f93f3c3fe4203937508034c42119a7c299ce951f6
maxFair    = 4,200,000,000
5 columns
chunkbytes (hex)uint32below 4,200,000,000?draw k
0fd8e759a4,253,971,866no — discarded—
1aade3ebe2,866,691,774yes — taken66,691,774
2c782575f3,347,208,031(not needed)—
79ce951f62,632,536,566(not needed)—
Parity verified: Bet krv88h79… — 2,866,691,774 % 100,000,000 = 66,691,774, and floor(9,900,000,000 / 66,691,775) = 148, so the result is 1.48×, exactly as the live game recorded (Step 5). Against the 1.59× target that is a loss, and LIQD credited 0 (Steps 6, 10). Without the guard, chunk 0 would have given k = 53,971,866 and a 1.83× result — a win; LIQD served 1.48×, the guarded value.
Live Game1.48×
Verifier1.48×
Technical Evidence & Verification5 sections
2.9Evidence Coverage Summary11 checks
3 columns
Verification AreaCoverageResult
Our implementation7,004 / 7,004 captured rounds, the 6,700 scored ones included — written from the specification; its 72 specification checks passPass
Result recomputation (Step 5)6,700 / 6,700 — exact on the two-decimal grid, recomputed on every runPass
LIQD verifier cross-check4 / 4 Verify-calculator values (unit test) and 2 / 2 /verify replies (Step 19) reproducedPass
Key encoding (hex vs UTF-8)Confirmed via recomputation — HMAC keys the decoded bytes, commitment hashes the textPass
Modulo bias analysis (Step 5)Rejection ceiling 4,200,000,000; bias 0 from the draw’s own bound; 126 / 126 rounds with a discarded first chunk reproducePass
External entropy non-participation (Step 5)6,700 / 6,700 reproduced from disclosed inputs alonePass
Draw uniformity (Step 23)χ² = 94.393, df = 99, p = 0.6122 over 2,000,000 draws — pass at α/3Pass
Serial independence (Step 23)lag-1 z = 0.937 < 2.936; runs p = 0.8679 (2,000,000 draws)Pass
Win counts at 10 targets (Step 23)10 / 10 within 5 SD + 1 win of the exact count at 1,000,000 rounds each; worst 1.56 SDPass
Live-sample fit (Step 22, informational)The 6,700 captured draws in order: χ² p = 0.2315, KS p = 0.6985, lag-1 z = −0.348, runs p = 0.6423Pass
Client seed influence (Step 18)500 / 500 Phase D + 64 / 64 sensitivity probesPass
2.10Code References11 source files
2 columns
FilePurpose
src/rng.tsHMAC-SHA256 single draw and result (commitHash, generateProvablyFairNumber, limboDraw, limboResult) — written from docs/ALGORITHM.md
docs/ALGORITHM.mdThe specification the RNG was written from — inputs, commitment, derivation, win rule and payout, with the source of each step
src/bias.tsThe draw-bias bound and the acceptance probe that ties it to src/rng.ts (Step 5)
src/config.tsGame constants, the scored result map (resultMultiplier) and the simulation constants
src/pass1-grid.tsPass 1 producer — seeds from the recorded master seed, integer rows only
tests/steps/pass1.tsStep 23: re-derives every Pass 1 statistic from the rows and replays the prefixes
tests/steps/live-fit.tsStep 22: fit of the 6,700 captured draws, in capture order (informational)
src/simulate.tsThe reference run (38M rounds) and its convergence chart
src/stats.tsChi-squared, lag-1 autocorrelation, Wald-Wolfowitz runs test, normal quantiles and tails
tests/limbo/RngSpecTests.tsThe specification’s 72 checks, run against src/rng.ts
tests/limbo/rngTests.tsThe 4 Verify-calculator values, the rejection branch, the commitment convention and client-seed sensitivity
2.11Verified Invariants11 invariants
2 columns
InvariantResult
HMAC-SHA256 output reproduces the live result for all 6,700 roundsPass
Key is hex-decoded (16 bytes, not UTF-8) — the wrong encoding produces wrong resultsPass
Message format is clientSeed:nonce:cursor with cursor 0 — literal colons, no paddingPass
Chunks scanned big-endian, 4 bytes, first accepted winsPass
Rejection sampling eliminates modulo bias — maxFair = 42 × 100,000,000, so every draw owns exactly 42 chunk valuesPass
The guard is applied live — 126 / 126 captured rounds with a discarded first chunk reproducePass
No external entropy sources required for result computationPass
Draw distribution consistent with uniform across the 100,000,000-point grid (χ² p = 0.6122, 2,000,000 draws)Pass
Lag-1 z < 2.936 and runs p ≥ 0.00333 on 2,000,000 consecutive drawsPass
Simulated wins within 5 SD + 1 win of the exact win count at 10 targets from 1.01× to 100,000× (worst 1.56 SD)Pass
Client seed change produces a different draw on 64 / 64 probes; 500 / 500 custom-seed rounds recomputePass
2.12Datasets UsedSource artifacts
Simulation: outputs/pass1-grid.json — Pass 1, re-derived on every verify run (Step 23) · outputs/simulation-results.json — the 38M-round reference run
Primary dataset: data/limbo-master-6700bets.json — 6,700 live rounds for draw and result recomputation (Steps 5, 18, 22) · SHA-256 8e3ce5b5e1494d1f…
Pass 1 output: outputs/pass1-grid.json — 2,000,000-draw histogram, the integer sums of the serial tests and 10 targets × 1,000,000 rounds, from the master seed pf.org/liqd-limbo/pass1/2026-09-26 · SHA-256 9999f98ba17fe534… (pinned; npm run pass1:check regenerates it byte for byte)
Reference run: outputs/simulation-results.json — 2M uniformity draws, the 20M-round convergence at 2.00× and 8 targets × 2M, from the master seed pf.org/liqd-limbo/simulate/2026-09-26 · SHA-256 2d609344650eac72… (pin checked by Step 19; npm run simulate:check regenerates it byte for byte)
Verification output: outputs/verification-results.json — the 25 steps, including Step 23’s statistic-by-statistic re-derivation
Reference vectors: tests/limbo/RngSpecTests.ts (the specification’s 72 checks) and tests/limbo/rngTests.ts — the 4 Verify-calculator values, the rejection branch, the commitment convention and client-seed sensitivity
2.13Reproduction InstructionsRun locally

Clone the repository, install dependencies, and regenerate both simulations before verifying:

reproduce-s2.sh · 5 lines · Verified
git clone https://github.com/ProvablyFair-org/liqd-limbo.gitcd liqd-limbo && git checkout v1.0.1npm cinpm run test:full   # unit tests, both simulations regenerated byte for byte, then the 25 stepscat outputs/pass1-grid.json | head -40
S2-related steps:

  [PASS] Step 5 — Draw & Result Recomputation
  [PASS] Step 18 — Custom Client-Seed Control (Phase D)
  [PASS] Step 19 — Artifact Integrity & Evidence Reproduction
  [PASS] Step 22 — Live-Sample Fit
  [PASS] Step 23 — Simulation Pass 1 — Pinned Master Seed, Re-Derived

The regenerated outputs/pass1-grid.json, outputs/simulation-results.json and outputs/rtp-convergence.html are byte-identical to the committed files (SHA-256 9999f98b…, 2d609344… and efd12115…). On our run of 22 October 2026 (Node 22.22.0) the whole of npm run test:full took 6 minutes 27 seconds.
03

Verifier Parity

Does the live game actually follow its own rules?

This section validates that our recomputation produces the exact same result as the live game for every round — equal on the two-decimal grid, with no tolerance. It also re-derives every win flag from the recomputed result and the player’s target (result ≥ target), and confirms every credited amount is exactly stake × target — a win pays the target, never the result — in exact integer arithmetic, so no rounding rule is needed. Any mismatch would invalidate the fairness guarantee.

Live ↔︎ Verifier Parity

6,700 / 6,700rounds matched

What we verified

  • Every round independently recomputed from its seeds — the result itself verified exactly, not just the payout
  • Payout correctness: all 2,822 wins credit exactly stake × target, checked in exact integers with zero tolerance; all 3,878 losses pay 0
  • Win/loss re-derived from the recomputed result and the target for every round — 0 mislabelled settlements; the 20 rounds that landed exactly on their target were all paid as wins
  • Bet amount is not an input to the RNG — the result depends only on the seeds and the nonce
  • All four capture phases recomputed identically (random targets, the 100×–500,000× tail, a $10.00 stake, client seeds we chose)

What this means for you

  • The verifier isn’t a simulation — it produces the exact same result as the live game
  • Every round you play can be independently recomputed by anyone
  • No hidden logic alters outcomes based on how much you bet or which target you choose
  • A win pays exactly your stake × your target — never more, never less
  • The game LIQD served in the capture follows the specified algorithm exactly
6,700 / 6,700rounds matched
  • Every round independently recomputed from its seeds — the result itself verified exactly, not just the payout
  • Payout correctness: all 2,822 wins credit exactly stake × target, checked in exact integers with zero tolerance; all 3,878 losses pay 0
  • Win/loss re-derived from the recomputed result and the target for every round — 0 mislabelled settlements; the 20 rounds that landed exactly on their target were all paid as wins
  • Bet amount is not an input to the RNG — the result depends only on the seeds and the nonce
  • All four capture phases recomputed identically (random targets, the 100×–500,000× tail, a $10.00 stake, client seeds we chose)
Parity Verification Flow — seeds → recompute the result → result ≥ target → stake × target → exact match — illustrated flow
6,700Live Rounds Tested
100%Parity Rate
$0.10 & $10.00Bet Sizes Tested
0Mismatches
CheckResultEvidence
Result recomputationPass6,700 / 6,700 exact — the result recomputed from the revealed seeds equals the served result on the two-decimal grid, strict equality, no tolerance; the 126 rounds decided by a later chunk and the 147 at the 1× floor included (Step 5)
Win-rule reproductionPass6,700 / 6,700 served win flags reproduced by result ≥ target on the recomputed result; all 20 rounds that landed exactly on their target were settled as wins (18.24 expected) (Step 6)
Multiplier derivationPassThe round record’s multiplier equals the target on all 2,822 wins, bit-exact (Step 8); our capture writes that column, and LIQD’s credited amount confirms every win paid the target, never the result (Step 9)
Credit arithmeticPassAll 6,700 credits exact: on 2,822 wins credit × 10⁸ = hundredths(stake) × hundredths(target) × 10⁴ with zero tolerance — stake × target to the last unit, no rounding rule needed; 3,878 losses pay exactly 0 (Step 9)
Wallet-field reconciliationPass3,878 losses pay 0 at multiplier 0, each with result < target (Step 10); stakeWallet / winningAmountWallet in LIQD’s settle response equal the game ledger on 6,700 / 6,700 (Step 11)
Bet-size independencePassBet amount is absent from the RNG input — the same draw function at $0.10 and $10.00; 200 / 200 Phase C rounds settle by the same rule (Step 13), and all 6,700 settlements follow from seeds, nonce, target and stake alone (Step 20)
Mode & phase completenessPassOne bet type, a target; all four planned phases at their planned counts and stakes (A 5,000 · B 1,000 · C 200 · D 500), Phase B in four tranches of 250 (Step 13); with the 26 September supplement, Phase A has at least 50 rounds in every target decade up to 10,000× (Step 25)
Result recomputationPass
6,700 / 6,700 exact — the result recomputed from the revealed seeds equals the served result on the two-decimal grid, strict equality, no tolerance; the 126 rounds decided by a later chunk and the 147 at the 1× floor included (Step 5)
Win-rule reproductionPass
6,700 / 6,700 served win flags reproduced by result ≥ target on the recomputed result; all 20 rounds that landed exactly on their target were settled as wins (18.24 expected) (Step 6)
Multiplier derivationPass
The round record’s multiplier equals the target on all 2,822 wins, bit-exact (Step 8); our capture writes that column, and LIQD’s credited amount confirms every win paid the target, never the result (Step 9)
Credit arithmeticPass
All 6,700 credits exact: on 2,822 wins credit × 10⁸ = hundredths(stake) × hundredths(target) × 10⁴ with zero tolerance — stake × target to the last unit, no rounding rule needed; 3,878 losses pay exactly 0 (Step 9)
Wallet-field reconciliationPass
3,878 losses pay 0 at multiplier 0, each with result < target (Step 10); stakeWallet / winningAmountWallet in LIQD’s settle response equal the game ledger on 6,700 / 6,700 (Step 11)
Bet-size independencePass
Bet amount is absent from the RNG input — the same draw function at $0.10 and $10.00; 200 / 200 Phase C rounds settle by the same rule (Step 13), and all 6,700 settlements follow from seeds, nonce, target and stake alone (Step 20)
Mode & phase completenessPass
One bet type, a target; all four planned phases at their planned counts and stakes (A 5,000 · B 1,000 · C 200 · D 500), Phase B in four tranches of 250 (Step 13); with the 26 September supplement, Phase A has at least 50 rounds in every target decade up to 10,000× (Step 25)
✓  Live game and verifier fully aligned

All 6,700 rounds matched our recomputation exactly — results, win flags and credited amounts, across all four capture phases and both stakes. Every win paid exactly stake × target, the target the player chose and never the result, and every loss paid zero, with the money checked in exact integers.

How It Works — Verifier Parity8 sections
3.1Why Parity Matters

If the verifier produces results that differ from the live game, players cannot trust the verification — the entire provably fair system becomes meaningless. Commit-reveal shows the seed didn’t change; parity shows the game the player experienced is the game the seed dictates. For every captured round the audit rebuilds the draw and the result from raw inputs with our own code, re-derives the win flag from the player’s target, and recomputes the credit as stake × target — then compares all three against what LIQD actually served and credited. 100% parity is required: a single mismatch anywhere in 6,700 rounds would indicate a bug in the verification logic, manipulation in the live game, or an inconsistent RNG implementation.

Why Parity Matters — illustrated flow
3.2Four-Phase Collection Design

Data was collected across four structured phases, each designed to test a specific fairness property. The phases are complementary — together they cover the everyday range of targets, the high-multiplier tail, bet-size invariance and client seeds we chose. A supplement captured on 26 September 2026 adds Phase A rounds in the higher target decades.

6 columns
PhaseRoundsTargetBetEpochsPurpose
A — Target coverage5,000Random targets 1.01×–49.45× (win chance drawn between 2% and 98%)$0.100–99Verify the draw, the result map and the win rule across the everyday range of targets
B — High-multiplier tail1,000250 each at 100×, 1,000×, 100,000× and 500,000×$0.10100–119Maximum variance; the largest potential payouts in the capture, up to the $50,000 limit
C — Stake equivalence2002.00×$10.00120–123Confirm bet amount is not an RNG input (100× the $0.10 stake)
D — Client-seed control500Random targets 1.01×–48.50× under 10 client seeds we chose (pfaudit-)$0.10124–133Confirm LIQD uses the client seed the player sets
Supplement (26 September 2026)300100 at 100×–999.99×, 100 at 1,000×–9,999.99×, 100 at exactly 10,000×$0.106 of its ownPhase A coverage of every target decade up to 10,000× — scored by Step 25
Total: 6,700 rounds across 134 seed epochs of exactly 50 rounds each in this capture — phase counts, stakes and Phase B’s tranches verified by Step 13 against constants in code, not the dataset header (Step 12). Total wagered: $2,650.00 ($500.00 Phase A + $100.00 Phase B + $2,000.00 Phase C + $50.00 Phase D). The 300 supplement rounds recompute from their own 6 revealed seeds and are scored by Step 25 only.
Targets above 10,000×: Phase B’s 100,000× and 500,000× tranches were played on the captured build, which accepted targets up to 500,000×; LIQD’s server now refuses targets above 10,000× (4.2, 4.7).
3.3Result Recomputation (Step 5)

For every round, the audit independently recomputes the draw and the result from the epoch’s revealed server seed, the client seed and the nonce — limboDraw(serverSeed, clientSeed, nonce) for the draw k (one HMAC-SHA256 digest keyed with the hex-decoded server seed over clientSeed:nonce:0, its first 4-byte chunk below 4,200,000,000, reduced modulo 100,000,000) and resultMultiplier(k) for the result, which on every captured draw is max(1, floor(9,900,000,000 ÷ (k + 1)) ÷ 100) (the function also holds LIQD’s game-code maximum of 1,000,000×, which only draws 0–97 would exceed, 2.7) — and compares it with the served result. The comparison is strict: the served value must be a two-decimal value inside the result domain and equal to the recomputed value — no tolerance. Every round counts, including the 126 whose first chunk was discarded by the bias guard (2.3) and the 147 at the 1× floor, 78 of them lifted from a raw 0.99 (2.7).

Result Recomputation (Step 5) — illustrated flow
tests/steps/determinism.ts · Step 5 · Verified
  for (const b of bets) {    const served = Number(b.result);    // …    if (!Number.isFinite(served) || served < MIN_RESULT || served > MAX_MULTIPLIER || !isTwoDecimal(served)) rangeViolations++;     const ss = seedMap.get(b.hashedServerSeed);    if (!ss) { unrevealed++; continue; }    const k = limboDraw(ss, b.clientSeed, b.nonce);    if (!Number.isInteger(k) || k < 0 || k >= RANGE) rangeViolations++;    const rej = rejectedLeadingChunks(ss, b.clientSeed, b.nonce, RANGE);    chunksRejected += rej;    const local = resultMultiplier(k);    if (rej > 0) { roundsFirstChunkRejected++; if (!servedResultMatches(served, local)) rejectedMismatch++; }    if (!servedResultMatches(served, local)) {      mismatches++;      if (!sample) sample = `epoch ${b.epoch} nonce ${b.nonce}: local ${local} vs served ${b.result}`;    }    if (served === MIN_RESULT) flooredRounds++;    if (Math.floor(RESULT_NUMERATOR / (k + 1)) / MULTIPLIER_SCALE < MIN_RESULT) clampedRaw++;    // …    checked++;  }
Result: 6,700 / 6,700 rounds verified, 0 skipped. Zero result mismatches — including every Phase B tail round, every Phase D round under a client seed we chose, the 126 rounds decided by a later chunk and the 147 rounds at 1×. Draws span k = 534 to 99,990,804 and results 1× to 185,046.72× (Step 5).
3.4Paid Multiplier & Credit Arithmetic (Steps 8, 9)

A Limbo win pays the target the player chose, never the result: the capture’s largest result, 185,046.72× on a 1.17× target, paid 1.17×. Step 8 checks the round record’s multiplier column — the column our capture script writes, the target on a win and 0 on a loss — against the target on all 2,822 wins, bit-exact; Step 9 checks the payment itself. Step 9 replays the money from LIQD’s credited amount, winningAmount: on every win the credit must be the exact decimal product of stake and target, asserted as the integer identity credit × 10⁸ = hundredths(stake) × hundredths(target) × 10⁴ with zero tolerance, and on every loss it must be exactly 0. A two-decimal stake times a two-decimal target is already exact to four decimals, so no rounding rule is needed. All 6,700 reconcile: 2,822 wins on targets from 1.01× to 100×, the largest credit $20.00 (a Phase C win, $10.00 on 2.00×), and 3,878 losses at 0. The same credits also equal ROUND_8(stake × target) computed in binary64, the form a re-implementer would write, on 2,822 / 2,822 wins; against the raw binary64 product the credit lands exactly on 1,472 of them and differs on the rest by at most 4.44e-16 — a property of binary64 itself, which is why Step 9 compares integers.

Paid Multiplier & Credit Arithmetic (Steps 8, 9) — illustrated flow
2 columns
MetricCount
Wins where the record’s multiplier equals the target, bit-exact (Step 8)2,822 / 2,822
Wins where credit × 10⁸ = hundredths(stake) × hundredths(target) × 10⁴ (Step 9)2,822 / 2,822
Losses crediting exactly 0 (Step 9)3,878 / 3,878
Wins where credit == ROUND_8(stake × target) in binary642,822 / 2,822
Targets won · largest credit1.01× to 100× · $20.00 (Phase C, $10.00 × 2.00)
Max |credit − binary64 stake × target| · net across the capture4.44e-16 · −2.33e-14 USDC (binary64 representation, not the settlement)
tests/steps/payouts.ts · Step 9 · Verified
  for (const b of bets) {    const stake = Number(b.betAmount);    const target = Number(b.params.target);    const credit = Number(b.winningAmount);    cChecked++;    if (b.win) {      const lhs = Math.round(credit * 1e8);      const rhs = hundredths(stake) * hundredths(target) * 1e4;      if (lhs !== rhs) {        intFails++;        if (!firstCFail) firstCFail = `epoch ${b.epoch} nonce ${b.nonce}: credit ${credit} vs exact decimal ${rhs / 1e8}`;      }      if (credit !== round8(stake * target)) r8Fails++;      const raw = credit - stake * target;      netResidual += raw;      if (Math.abs(raw) > maxRawResidual) maxRawResidual = Math.abs(raw);      if (raw === 0) landsOnProduct++;    } else {      if (credit !== 0) {        intFails++;        if (!firstCFail) firstCFail = `epoch ${b.epoch} nonce ${b.nonce}: losing bet credited ${credit}`;      }    }  }
Result: All 2,822 wins pay stake × target, never the result (Step 9); all 6,700 credits reconcile in exact integers with zero tolerance, losses exactly 0. Zero payout errors.
3.5Loss Settlement & Wallet Fields (Steps 10, 11)

Step 10 checks every losing round in isolation: winningAmount must be exactly 0 and the round record’s multiplier exactly 0 — and, the part that matters, the round’s result must genuinely have missed the target (result < target, on the served result, which Step 5 shows equals the recomputed one), so no round that reached its target could have been labelled a loss. 3,878 / 3,878 conform, and resultLabel agrees with the win flag on every round. Step 11 reads the wallet-denominated fields of LIQD’s settle response: stakeWallet must equal betAmount and winningAmountWallet must equal winningAmount — present on all 6,700 rounds and equal on all 6,700, 0 divergent. It also confirms every stake is at or above LIQD’s $0.10 minimum (its client configuration) and every round’s potential payout, stake × target, sits within LIQD’s $50,000 limit — compared in whole hundredths, because for 427 of the capture’s 1,105 distinct stake × target pairs the binary64 product is not the exact decimal (0.1 × 1.07 = 0.10700000000000001).

3 columns
ScopeCoverageVerified
Losses — winningAmount == 0 and multiplier == 03,878 rounds3,878 / 3,878
Losses genuinely missed — result < target (served result = recomputed, Step 5)3,878 rounds3,878 / 3,878
resultLabel agrees with the win flag6,700 rounds6,700 / 6,700 (3,878 lost · 2,822 won)
Wallet fields == game fields (stakeWallet = betAmount, winningAmountWallet = winningAmount)6,700 rounds6,700 / 6,700 (present on every round, 0 divergent)
Stake at or above LIQD’s $0.10 minimum6,700 rounds0 below
Potential payout (stake × target) within $50,000, in whole hundredths6,700 rounds0 over — maximum exactly $50,000, on 250 rounds
Result: 3,878 losses pay exactly zero with a zero multiplier, each with result < target (Step 10); the settle response’s wallet fields equal the game ledger on all 6,700 rounds, and every round sits inside LIQD’s stake and payout limits (Step 11).
3.6Settlement Determinism (Step 20)

Steps 8 and 9 check the round record and the credit against the target. Step 20 takes the served outcome out of the picture: for every round, the outcome and the credited amount are rebuilt from (serverSeed, clientSeed, nonce, target, stake) alone — the draw and the result from the seeds, the win flag from result ≥ target, the credit as stake × target in exact integers, 0 on a loss — and must match the served settlement with zero tolerance. The win flag, the credit and the record’s multiplier column are rebuilt from the inputs and only then compared with what was served and credited, so a settlement chosen after the draw has nowhere to hide. In a legitimate game nothing else can move the money: no hidden fee, no per-account adjustment, no stake-dependent formula. All 6,700 rounds conform: 2,822 wins and 3,878 losses, both stakes, all four phases, the 147 rounds at the 1× floor and the 20 rounds that landed exactly on their target — the two cases a naive model gets wrong.

tests/steps/standardization.ts · Step 20 · Verified
  // The whole chain, end to end, from inputs a player controls or can check:  //     (serverSeed, clientSeed, nonce) -> k -> R -> win -> credit  // and NOTHING served is read on the way. `b.result`, `b.multiplier`, `b.win` and `b.resultLabel`  // are all excluded here; they are only compared at the end. Steps 5/6/8/9 each pin one link  // against its served field — this step asserts the whole chain is determined, so there is no  // degree of freedom left for an operator to exercise after seeing the draw.    // …    for (const b of bets) {      const ss = seedMap.get(b.hashedServerSeed);      if (!ss) { bad++; fails.push(`epoch ${b.epoch} nonce ${b.nonce}: no revealed seed`); continue; }      chk++;      // …      const target = Number(b.params.target);      const stake = Number(b.betAmount);      const R = resultMultiplier(limboDraw(ss, b.clientSeed, b.nonce));      const win = limboWin(R, target);      if (win) wins++; else losses++;      // …      // determined credit, in the operator's own decimal number system      const determinedCreditH8 = win ? hundredths(stake) * hundredths(target) * 1e4 : 0;      const creditedH8 = Math.round(Number(b.winningAmount) * 1e8);      const determinedMultiplier = win ? target : 0;      if (win !== !!b.win || creditedH8 !== determinedCreditH8 || Number(b.multiplier) !== determinedMultiplier) {        bad++;        if (fails.length < 3) fails.push(`epoch ${b.epoch} nonce ${b.nonce}: determined (win=${win}, credit=${determinedCreditH8 / 1e8}, mult=${determinedMultiplier}) vs served (win=${b.win}, credit=${b.winningAmount}, mult=${b.multiplier})`);      }      // …    }
Result: 6,700 / 6,700 settlements are fully determined by (serverSeed, clientSeed, nonce, target, stake) — 0 mismatches; coverage: 2,822 wins and 3,878 losses, both stakes, all four phases, 147 rounds at 1× and 20 on target (Step 20).
3.7Phase C — Bet-Size Invariance (Steps 13, 20)

Phase C placed 200 rounds at $10.00 on a 2.00× target — 100× the $0.10 stake used in every other phase. All 200 results were recomputed exactly from revealed seeds (0 mismatches), and Step 13 settles each one again from primitives: 0 settled differently from the rule a $0.10 round follows (result, win flag, or a credit of exactly stake × target in integer hundredths). The structural point: limboDraw takes the server seed, the client seed and the nonce — no stake — so the result cannot depend on the bet amount, and the 2.00× target returns exactly 99.000000% at every stake. Beyond the settlement multiplication, the stake enters only LIQD’s limits — the $0.10 minimum and the $50,000 ceiling on stake × target (Step 11) — never the draw, and the only stakes in the dataset are exactly $0.10 and $10.00.

Result: Phase C: 200 / 200 results recomputed correctly at $10.00; every win credits exactly stake × 2.00 = $20.00 — 104 wins, each $20.00 (Step 9 covers the money). Bet amount is not an input to the RNG.
Variance context: Phase C’s 200 rounds on 2.00× produced 104 wins against the 99.0 expected — $2,080.00 returned on $2,000.00 staked. At N = 200 this is small-sample luck and structurally irrelevant: the claim verified here is result invariance, not RTP. The RTP is proven exactly in S4.
3.8Worked Example — Full Parity Verification

Real round from Phase C — bet pFaN6MRWzGVvE8DpQBMY8 (epoch 120), nonce 2, target 2.00× at $10.00. Verified from limbo-master-6700bets.json — one of the 104 Phase C wins, and at $20.00 the largest credit in the capture:

serverSeed = 141a7e0ab87055e753e0f8d289bbec1a
clientSeed = auditdc525d958d88
nonce      = 2
target     = 2.00×
betAmount  = $10.00
recorded   = result 2.38 · win true · resultLabel won · winningAmount 20 · winningAmountWallet 20
commitment = d56deeab14ebfe68…
3 columns
StepProcessOutput
1Recompute the commitment — SHA-256(utf8(serverSeed))Matches the pre-published hash d56deeab14eb…
2Scan the HMAC digest of auditdc525d958d88:2:0First chunk 0x6dc1f472 = 1,841,427,570 — below 4,200,000,000, accepted
3Reduce it modulo 100,000,000k = 41,427,570
4Result max(1, floor(9,900,000,000 ÷ 41,427,571) ÷ 100)2.38× — equals the recorded result
5Apply result ≥ target against 2.00×Win — the won label is correct
6Credit stake × target, 10.00 × 2.00$20.00 — the credited winningAmount and winningAmountWallet (both stored as 20)
HMAC-SHA256 recomputation + formula · Verified
commitHash(serverSeed)                         → d56deeab14eb… matches the published commitment ✓HMAC-SHA256(seed, "auditdc525d958d88:2:0")     → 6dc1f472c6a163b3…chunk 0 = 0x6dc1f472 = 1,841,427,570 < 4,200,000,000 → k = 1,841,427,570 mod 100,000,000 = 41,427,570 ✓floor(9,900,000,000 / 41,427,571) = 238         → result = max(1, 238 / 100) = 2.38× (the recorded result) ✓2.38 ≥ 2.00                                    → won ✓credit = 10.00 × 2.00 = 20.00                  → 1,000 × 200 × 10,000 = 2,000,000,000 = credit × 10⁸ ✓
Parity verified: Bet pFaN6MRWzGVv… — commitment, draw, result, win flag and credited payout all match exactly between the live game and our recomputation. This example exercises Steps 1, 5, 6, 9, 11, 13 and 20 end to end at the $10.00 stake — one of the 104 Phase C wins, each paid exactly $20.00.
Live Game$20.00
Our recomputation$20.00
Technical Evidence & Verification5 sections
3.9Evidence Coverage Summary12 checks
3 columns
Verification AreaCoverageResult
Result recomputation (Step 5)6,700 / 6,700 rounds — exact on the two-decimal grid, strict equalityPass
Win-rule reproduction (Step 6)6,700 / 6,700 win flags under result ≥ target; 20 / 20 on-target rounds paid as winsPass
Recomputation determinism (Step 7)Two forward passes and one reverse pass agree on 6,700 / 6,700; 204 rounds recomputed 5 times each, 0 unstablePass
Paid multiplier (Step 8)2,822 / 2,822 wins — the record’s multiplier equals the targetPass
Credit arithmetic (Step 9)6,700 / 6,700 — wins exactly stake × target in integers, losses 0, zero tolerancePass
Loss settlement (Step 10)3,878 / 3,878 losses pay 0, each with result < targetPass
Wallet fields, stake & exposure (Step 11)6,700 / 6,700 wallet fields equal the game fields; 0 stakes below $0.10; 0 rounds over $50,000Pass
Dataset integrity & population (Step 12)SHA-256 pin match; 6,700 rounds · 134 seeds · 50 per epoch, bound to code constants; 6,700 distinct bet idsPass
Phase plan conformance (Step 13)A 5,000 · B 1,000 · C 200 · D 500 at planned stakes; Phase B 250 per tranche; Phase C 200 / 200 settle by the same rulePass
Phase D client seeds (Step 18)500 rounds, 10 client seeds we chose — 500 / 500 matchPass
Settlement determinism (Step 20)6,700 / 6,700 rebuilt from (serverSeed, clientSeed, nonce, target, stake) alonePass
Supplement & target decades (Step 25)300 / 300 supplement rounds recompute; Phase A has at least 50 rounds in every decade up to 10,000×Pass
3.10Code References8 source files
2 columns
FilePurpose
tests/steps/determinism.tsSteps 5–7: Result recomputation, win-rule reproduction and recomputation determinism
tests/steps/payouts.tsSteps 8–11: Paid multiplier, credit arithmetic, loss settlement, wallet fields, stake floor and exposure limit
tests/steps/dataset.tsSteps 12–13: Dataset integrity and phase plan conformance — population bound to src/config.ts
tests/steps/phase-d.tsStep 18: Phase D client seeds and client-seed sensitivity
tests/steps/standardization.tsStep 20: Settlement determinism
tests/steps/supplement.tsStep 25: The 26 September supplement and the server’s target limit
src/config.tsresultMultiplier, limboWin, targetToHundredths, isTargetLegal, round8 — the formulas every round is replayed against
src/rng.tslimboDraw and rejectedLeadingChunks — the draw, written from the specification
3.11Datasets UsedSource artifacts
Primary dataset: data/limbo-master-6700bets.json — 6,700 live rounds across Phases A–D (134 epochs), one hash-pinned capture · SHA-256 8e3ce5b5e1494d1f…
Phase B slice: Epochs 100–119 — 250 rounds each at 100×, 1,000×, 100,000× and 500,000× inside the primary dataset (no separate file)
Supplement: data/liqd-limbo-capture-v2-2026-09-26T17-48-02-207Z.json and its journal — 300 Phase A rounds at 100×–10,000×, scored by Step 25 only · SHA-256 a2dee374cfd48c62…
Game config: The result map (resultMultiplier: max(1, floor(9,900,000,000 ÷ (k + 1)) ÷ 100), held at LIQD’s game-code maximum of 1,000,000×), the rule result ≥ target (limboWin) and the credit stake × target, in src/config.ts — algebraic, not a lookup table
Verification output: outputs/verification-results.json — Steps 5–13, 18, 20 and 25
3.12Verified Invariants13 invariants
2 columns
InvariantResult
Recomputed result equals the served result exactly for all 6,700 roundsPass
result ≥ target on the recomputed result reproduces the served win flag for all 6,700 roundsPass
The 20 rounds landing exactly on their target are settled as wins (≥, not >)Pass
Every win pays the target, never the result — the credit equals stake × target on all 2,822 wins (Step 9); the record’s multiplier column equals the target on all 2,822 (Step 8)Pass
credit × 10⁸ = hundredths(stake) × hundredths(target) × 10⁴ for all 2,822 wins, zero tolerancePass
winningAmount == 0 and multiplier == 0 for all 3,878 losses, each with result < targetPass
Wallet fields of the settle response equal the game fields on all 6,700 roundsPass
Credit is fully determined by (serverSeed, clientSeed, nonce, target, stake) — no served outcome field read — for all 6,700 roundsPass
Phase C ($10.00) results derive from the identical three-input draw as $0.10 roundsPass
No hidden inputs beyond (serverSeed, clientSeed, nonce) — stakes present are exactly {$0.10, $10.00}Pass
All four phases at planned counts and stakes; 1,104 distinct targets from 1.01× to 500,000×Pass
Phase D: 500 / 500 results verified across 10 client seeds we chosePass
Every stake ≥ $0.10 and every potential payout ≤ $50,000Pass
3.13Reproduction InstructionsRun locally

Clone the repository, install dependencies, and run the verification suite:

reproduce-s3.sh · 5 lines · Verified
git clone https://github.com/ProvablyFair-org/liqd-limbo.gitcd liqd-limbo && git checkout v1.0.1npm cinpm run verify# Expected output: Steps 5, 6, 7, 8, 9, 10, 11, 12, 13, 18, 20, 25 all PASS
S3-related steps:

  [PASS] Step 5 — Draw & Result Recomputation
  [PASS] Step 6 — Win-Rule Reproduction
  [PASS] Step 7 — Recomputation Determinism
  [PASS] Step 8 — Paid-Multiplier Derivation
  [PASS] Step 9 — Credit Arithmetic
  [PASS] Step 10 — Loss Settlement
  [PASS] Step 11 — Wallet Ledger, Stake Floor & Exposure Ceiling
  [PASS] Step 12 — Dataset Integrity & Population Identity
  [PASS] Step 13 — Phase Plan Conformance
  [PASS] Step 18 — Custom Client-Seed Control (Phase D)
  [PASS] Step 20 — Settlement Determinism
  [PASS] Step 25 — Target-Decade Supplement & Live Target Ceiling …
04

RTP & Payout Logic

Is the house edge what the casino claims?

This section verifies that LIQD’s advertised 1.00% house edge holds at every target a player can bet. The key test is anti-circularity: we prove the RTP from first principles, not from any casino-supplied figure — a target m wins on exactly ⌊9,900,000,000 ÷ (100 · m)⌋ of the 100,000,000 equally likely draws and pays m, so its RTP is that count × 100 · m ÷ 10¹⁰: never above 99%, and up to 10,000× never more than 0.009982 pp below it. We check the identity on all 1,104 captured targets, hold LIQD’s published 99% to every one of the 999,900 targets the server accepts under the RTP claim rule, compare it against 12 million simulated rounds, and test whether the casino pre-selected seeds against the player.

Return to Player Verification

98.99%–99.00%theoretical RTP

What we verified

  • The house edge is at least 1.00% at every target; up to 10,000× it is exactly 1.00% on 282 targets and never more than 1.009982%
  • RTP proven from first principles: a target m wins on ⌊9,900,000,000 ÷ 100m⌋ of the 100,000,000 draws and pays m — derived from the result map, checked as an integer identity on all 1,104 captured targets and confirmed by full-grid tallies at four of them
  • LIQD’s published 99% holds on all 999,900 targets the server accepts — worst gap −0.009982 pp against the ±0.1 pp band of the RTP claim rule
  • Every win pays exactly stake × target — no rounding at settlement, so the return at each target is exactly its win chance × target
  • 12,000,000 simulated rounds: uniformity and serial screens pass, and 10 targets from 1.01× to 100,000× all land within 5 SD + 1 win of their exact win count (Step 23)
  • Cherry-pick test: across LIQD’s 134 seeds, the captured windows rank among each seed’s other 199 windows as chance predicts (rank-sum Z −0.1711 against a −3.090 threshold) — no sign of seed selection; the test catches a selection putting 16.1% of epochs’ captured windows in their worst decile at least half the time
  • Bet amount does not influence the result — confirmed at $0.10 and $10.00

What this means for you

  • The house edge on Limbo is 1.00% to 1.01% at every target the server accepts, whatever target or bet size you choose
  • The 99% LIQD advertises is what every target returns, within a hundredth of a percentage point
  • The RTP proof is derived independently — it doesn’t rely on trusting the casino
  • The casino’s seeds show no sign of being chosen against the player
  • Your bet amount doesn’t affect the result
  • A win pays exactly your stake × your target — a result above your target doesn’t change the payout
  • High targets win rarely and pay large — the 99% is the long-run average at every target
98.99%–99.00%theoretical RTP
  • The house edge is at least 1.00% at every target; up to 10,000× it is exactly 1.00% on 282 targets and never more than 1.009982%
  • RTP proven from first principles: a target m wins on ⌊9,900,000,000 ÷ 100m⌋ of the 100,000,000 draws and pays m — derived from the result map, checked as an integer identity on all 1,104 captured targets and confirmed by full-grid tallies at four of them
  • LIQD’s published 99% holds on all 999,900 targets the server accepts — worst gap −0.009982 pp against the ±0.1 pp band of the RTP claim rule
  • Every win pays exactly stake × target — no rounding at settlement, so the return at each target is exactly its win chance × target
  • 12,000,000 simulated rounds: uniformity and serial screens pass, and 10 targets from 1.01× to 100,000× all land within 5 SD + 1 win of their exact win count (Step 23)
  • Cherry-pick test: across LIQD’s 134 seeds, the captured windows rank among each seed’s other 199 windows as chance predicts (rank-sum Z −0.1711 against a −3.090 threshold) — no sign of seed selection; the test catches a selection putting 16.1% of epochs’ captured windows in their worst decile at least half the time
  • Bet amount does not influence the result — confirmed at $0.10 and $10.00
INDEPENDENT DERIVATION
RTP(m) = ⌊9.9×10⁹ ÷ 100m⌋ × 100m ÷ 10¹⁰= 0.98990018–0.99000000
Win chance from the result map — a target m wins on ⌊9.9×10⁹ ÷ 100m⌋ of the 100,000,000 draws, tallied in full at four targets · Every target 1.01×–10,000× (999,900) · House edge ≥ 1.00% at every one of the 99,999,900 targets up to 1,000,000×
98.990018%–99.000000%Theoretical RTP (every target 1.01×–10,000×, exact)
99.0128%Simulated (reference run, 20M rounds at 2.00×)
≥ 1.00%House Edge (at every target)
999,900 / 999,900Targets within 0.1 pp of LIQD’s 99%
CheckResultEvidence
Anti-circularityPassWin probability derived from the draw grid, never read from LIQD — a target m wins on exactly ⌊9,900,000,000 ÷ 100m⌋ of the 100,000,000 draws: an integer identity on all 1,104 captured targets, anchored by full-grid tallies at 1.01×, 2×, 100× and 500,000×; the negative control accepts 2.47× and rejects 2.48×, NaN and Infinity on the same round (Steps 14, 15)
House edge auditPassEdge ≥ 1.00% asserted in exact integers — ⌊9,900,000,000 ÷ ci⌋ × ci ≤ 9,900,000,000 — at all 99,999,900 targets — 0 violations, 0 player-favourable; up to 10,000× exactly 1.00% on 282 targets and at most 1.009982%, at 9,989.91× (Step 16)
RTP claim (RTP-CLAIM 1.2.0)PassLIQD publishes 99% RTP (Edge 1.00%) on the Limbo game page; all 999,900 targets from 1.01× to 10,000× return 98.990018%–99.000000% — worst gap −0.009982 pp against the ±0.1 pp band (Step 16)
Simulated RTP (Pass 1)Pass12,000,000 rounds — draw uniformity p = 0.6122, serial screens clear at α/3, 10 / 10 targets from 1.01× to 100,000× within 5 SD + 1 win of their exact win count, worst 1.56 SD (Step 23)
Cherry-pick test (Pass 2)PassPlayed-target return over 134 seeds × 10,000 nonces: the captured windows rank among their seeds’ other 199 windows as chance predicts — rank-sum Z = −0.1711 (one-sided p = 0.4321) against the −3.090 rule, no cherry-picking indicated; a selection putting 16.1% of epochs’ captured windows in their worst decile is caught at least half the time (Step 24)
Bet-size invariancePassBet amount is not an input to the RNG — the same multiplier at every stake; 200 / 200 Phase C rounds at $10.00 settle by the same rule (Step 13), and all 6,700 settlements follow from seeds, nonce, target and stake (Step 20)
Payout formulaPassA win pays stake × target, never the result: every credit is exactly stake × target in integers, zero tolerance (Step 9), and the record’s multiplier column equals the target on all 2,822 wins (Step 8)
Target range & exposure ceilingPassStake × target within LIQD’s $50,000 limit on all 6,700 rounds (Step 11); the 1,000,000× target in LIQD’s client code could not be bought at any allowed stake (Step 17); the server accepts 1.01×–10,000× and refused all four higher targets probed on 2 October 2026 (Step 25)
Anti-circularityPass
Win probability derived from the draw grid, never read from LIQD — a target m wins on exactly ⌊9,900,000,000 ÷ 100m⌋ of the 100,000,000 draws: an integer identity on all 1,104 captured targets, anchored by full-grid tallies at 1.01×, 2×, 100× and 500,000×; the negative control accepts 2.47× and rejects 2.48×, NaN and Infinity on the same round (Steps 14, 15)
House edge auditPass
Edge ≥ 1.00% asserted in exact integers — ⌊9,900,000,000 ÷ ci⌋ × ci ≤ 9,900,000,000 — at all 99,999,900 targets — 0 violations, 0 player-favourable; up to 10,000× exactly 1.00% on 282 targets and at most 1.009982%, at 9,989.91× (Step 16)
RTP claim (RTP-CLAIM 1.2.0)Pass
LIQD publishes 99% RTP (Edge 1.00%) on the Limbo game page; all 999,900 targets from 1.01× to 10,000× return 98.990018%–99.000000% — worst gap −0.009982 pp against the ±0.1 pp band (Step 16)
Simulated RTP (Pass 1)Pass
12,000,000 rounds — draw uniformity p = 0.6122, serial screens clear at α/3, 10 / 10 targets from 1.01× to 100,000× within 5 SD + 1 win of their exact win count, worst 1.56 SD (Step 23)
Cherry-pick test (Pass 2)Pass
Played-target return over 134 seeds × 10,000 nonces: the captured windows rank among their seeds’ other 199 windows as chance predicts — rank-sum Z = −0.1711 (one-sided p = 0.4321) against the −3.090 rule, no cherry-picking indicated; a selection putting 16.1% of epochs’ captured windows in their worst decile is caught at least half the time (Step 24)
Bet-size invariancePass
Bet amount is not an input to the RNG — the same multiplier at every stake; 200 / 200 Phase C rounds at $10.00 settle by the same rule (Step 13), and all 6,700 settlements follow from seeds, nonce, target and stake (Step 20)
Payout formulaPass
A win pays stake × target, never the result: every credit is exactly stake × target in integers, zero tolerance (Step 9), and the record’s multiplier column equals the target on all 2,822 wins (Step 8)
Target range & exposure ceilingPass
Stake × target within LIQD’s $50,000 limit on all 6,700 rounds (Step 11); the 1,000,000× target in LIQD’s client code could not be bought at any allowed stake (Step 17); the server accepts 1.01×–10,000× and refused all four higher targets probed on 2 October 2026 (Step 25)
✓  RTP Behaves as Advertised

Every target from 1.01× to 10,000× returns between 98.990018% and 99.000000%, proven exactly from the draw grid — a target m wins on ⌊9,900,000,000 ÷ 100m⌋ of the 100,000,000 equally likely draws and pays m — so LIQD’s published 99% holds at all 999,900 targets the server accepts, within 0.01 pp. The house edge is at least 1.00% at every target and never favours the player. This is a first-principles proof, not a statistical estimate; 12 million simulated rounds agree with it, the cherry-pick test finds no sign of seed selection, and every win is paid exactly stake × target, with no settlement rounding.

How It Works — RTP & Payout Logic9 sections
4.1Anti-Circularity Proof (Steps 14, 15)

The anti-circularity proof establishes each target’s RTP from first principles without using any casino-supplied probability. The win probability never comes from LIQD: it is derived from the result map over the grid of 100,000,000 equally likely draws (2.3). Because the result is max(1, floor(N ÷ (k + 1)) ÷ 100) with N = 9,900,000,000, and every target is at least 1.01×, a result reaches target m exactly when k ≤ ⌊N ÷ ci⌋ − 1, with ci = 100 · m — so target m wins on exactly ⌊N ÷ ci⌋ of the 100,000,000 draws, and its RTP is that count ÷ 10⁸ × m. Step 14 proves the check is falsifiable and anchors that boundary: on the first captured round the very predicate Step 5 uses accepts the recomputed 2.47× and rejects 2.48× — one tick away — NaN and Infinity, and for each of the 1,104 captured targets the draw k = ⌊N ÷ ci⌋ − 1 wins and k = ⌊N ÷ ci⌋ loses under the real result map and rule. Step 15 asserts the count as an integer equality on all 1,104 captured targets — it equals the win chance LIQD’s own pricing formula gives — and anchors it with four literal tallies, each a full sweep of all 100,000,000 draws:

Anti-Circularity Proof (Steps 14, 15) — illustrated flow
3 columns
ComponentFormulaSource
Win probability⌊9,900,000,000 ÷ ci⌋ ÷ 100,000,000, ci = 100 · m — the draws whose result reaches mGrid arithmetic — computed by the audit
Counting anchorLiteral tally: run the result map and result ≥ target on every draw 0 … 99,999,999 and count4 full sweeps (1.01×, 2×, 100×, 500,000×) — 0 mismatches against the closed form, no tolerance
Paid multiplierThe target m — a win pays stake × mThe player’s own input; paid exactly on all 2,822 wins (Step 9)
RTP⌊9,900,000,000 ÷ ci⌋ × ci ÷ 10¹⁰ — win probability × targetThe product; at most 0.99, because the floor can only lower it
Integer form⌊N ÷ ci⌋ = winChance(m) × 10⁸Asserted as an integer equality on all 1,104 captured targets (Step 15)
5 columns
TargetTallied winning drawsClosed formWin chanceRTP
1.01×98,019,801⌊9,900,000,000 ÷ 101⌋ = 98,019,80198.019801%98.999999%
2.00×49,500,000⌊9,900,000,000 ÷ 200⌋ = 49,500,00049.500000%99.000000%
100.00×990,000⌊9,900,000,000 ÷ 10,000⌋ = 990,0000.990000%99.000000%
500,000.00×198⌊9,900,000,000 ÷ 50,000,000⌋ = 1980.000198%99.000000%
Result: Every target: RTP = ⌊N ÷ ci⌋ × ci ÷ 10¹⁰ exactly — derived, not estimated. The literal tallies match the closed form with integer equality at all four targets, the negative control rejects a result one tick away (Step 14), and the identity holds with integer equality on all 1,104 captured targets (Step 15). The theoretical RTP proof is non-circular and exact.

Anti-Circularity Verification:

tests/steps/anti-circularity.ts · Steps 14, 15 · Verified
/** * Literal tally over the ENTIRE draw grid: apply the published result formula and the published win * rule to every one of the 100,000,000 possible draws and count the winners. * * This is the arithmetic-error-immune anchor. It cannot share a flooring, clamping or off-by-one * mistake with the closed form `floor(N / ci(m))`, because it does not do any of that reasoning — * it just runs the game 1e8 times. ~0.35 s per target on the validated runtime. */function tallyWinningDraws(target: number): number {  let c = 0;  for (let k = 0; k < RANGE; k++) if (limboWin(resultMultiplier(k), target)) c++;  return c;}   // …  const trueResult = resultMultiplier(limboDraw(ss, probe.clientSeed, probe.nonce));  const perturbed = Math.round((trueResult + 0.01) * 100) / 100;  const accepts = (r: number) => servedResultMatches(r, trueResult);  const negControlRejects = accepts(trueResult) && !accepts(perturbed) && !accepts(NaN) && !accepts(Infinity);  // …  for (const m of distinctTargets) {    const ci = targetToHundredths(m);    const count = Math.floor(RESULT_NUMERATOR / ci);    if (count !== Math.round(winChance(m) * RANGE) || count / RANGE !== winChance(m)) {      identityFails++;      if (!firstIdentityFail) firstIdentityFail = `target ${m}: grid count ${count} vs priced ${winChance(m) * RANGE}`;    }    identityChecked++;  }

Why this proof is non-circular: the win probability comes from the draw grid and the result map, and the payout is the player’s own target — neither is read from LIQD. The count is ⌊9,900,000,000 ÷ ci⌋ because LIQD’s result map divides 9,900,000,000 by k + 1: the house edge is built into that numerator, 99% of 100 × 10⁸. A target can only win on a whole number of draws, and ⌊N ÷ ci⌋ is never more than N ÷ ci — which is why a target’s RTP can sit a little below 99% but never above it (4.3). LIQD’s page states two of the resulting win chances, 49.5% at 2× and 0.0099% at 10,000×, and both equal the count exactly (Step 15). The identity itself holds by algebra for every target; what ties it to LIQD is Step 5 — the same result map reproduces all 6,700 settled results — together with the tallies, which run the real map and rule over every draw instead of trusting the algebra.

4.2RTP Claim Rule (Step 16)

The RTP claim rule (RTP-CLAIM 1.2.0) is part of the audit method: the RTP a casino publishes must hold at every setting the game offers. A published figure counts wherever a player can find it — on the game page, the fairness page, the help page, the bet panel, the rules, or in the game settings the site loads. Each setting’s RTP is computed exactly, as a rational number, from the draw grid and the target — never from the casino’s figure — and must sit within ±0.1 percentage points of the claim. A setting below that band on the house side is a miss: the verdict then reads RTP FIGURE OVERSTATED and the suite exits with code 5, even when every draw check passes. A setting above the claim never fails. For Limbo a setting is a target, and the settings are every two-decimal target from 1.01× to 10,000× — 999,900 of them, the range LIQD’s server accepts. The rule is scored on Step 16, the step that derives the effective edge.

2 columns
ItemDetail
Claim99% RTP at every target — LIQD’s 1.00% house edge
Where LIQD publishes itThe Limbo game page, liqd.com/originals/limbo: header "Released: September 2026 | Edge: 1.00%", stats panel "RTP 99%" and "House Edge 1%", page text "2x lands 49.5% of the time, 10,000x lands 0.0099% of the time, and every target in between returns 99%" — read logged out on 1 October 2026 (evidence/rtp-page-labels-2026-10-01.json); both win chances it quotes equal the exact count (Step 15)
Settings tested999,900 — every two-decimal target from 1.01× to 10,000×, the range the server accepts (win chance 98.019801% down to 0.009900%)
MethodExact rational: ⌊N ÷ ci⌋ winning draws of 100,000,000, times the target — RTP = ⌊N ÷ ci⌋ × ci ÷ 10¹⁰, with N = 9,900,000,000 and ci = 100 × target
Band±0.1 percentage points (house side fails, player side never fails)
True minimum · maximum98.990018% (9,989.91×) · 99.000000% (first at 1.10×)
Worst gap−0.009982 pp (9,989.91×), inside the band
Settings beyond the band0 on the house side · 0 on the player side
Scored onStep 16 (profile check LIMBO-19), rule enforced
ResultPass — the claim holds at every target the server accepts
5 columns
TargetsSettingsExact RTPLowest gapResult
1.01×–1.99×9998.999998%–99.000000%−0.000002 ppPass — 99 / 99
2.00×–9.99×80098.999990%–99.000000%−0.000010 ppPass — 800 / 800
10.00×–99.99×9,00098.999901%–99.000000%−0.000099 ppPass — 9,000 / 9,000
100.00×–999.99×90,00098.999005%–99.000000%−0.000995 ppPass — 90,000 / 90,000
1,000.00×–10,000.00×900,00198.990018%–99.000000%−0.009982 ppPass — 900,001 / 900,001
All targets 1.01×–10,000×999,90098.990018%–99.000000%−0.009982 ppPass — 999,900 / 999,900
Result: Pass — LIQD’s published 99% holds at all 999,900 targets the server accepts: the true minimum is 98.990018% (9,989.91×, −0.009982 pp) and the true maximum 99.000000%, both inside the ±0.1 pp band (Step 16). The table — by target decade, with its extremes — ships as outputs/rtp-claim-table.json, pinned by SHA-256 and re-derived on every run.
Targets above 10,000× on the captured build: The build captured on 11 September 2026 accepted targets up to 500,000×. The exact RTP above 10,000× follows the same formula, and from 100,101.12× up some targets return more than 0.1 pp below 99% — 23,885,381 of the 49,999,900 targets up to 500,000×, the lowest 98.502513% at 497,487.44×. The 500 captured rounds above 10,000× were all at 100,000× or 500,000×, and both return exactly 99.000000%. On 2 October 2026 LIQD’s server refused every target tried above 10,000× — 100,101.12×, 497,487.44×, 500,000× and 990,000.01× (Step 25) — so every target a player can bet returns within 0.01 pp of 99%.
4.3House Edge — a 1.00% Floor & Exact Settlement (Steps 9, 16)

Step 16 derives the house edge of every target from first principles, by enumeration: edge = 1 − ⌊N ÷ ci⌋ × ci ÷ 10¹⁰, with N = 9,900,000,000 and ci = 100 × the target, computed in exact integers for all 99,999,900 targets from 1.01× to 1,000,000× (the range in LIQD’s client code). The number of winning draws is 99% of 10⁸ ÷ m rounded down to a whole draw, so ⌊N ÷ ci⌋ × ci ≤ N for every target: the RTP is never above 99% and the edge never below 1.00% — 0 violations, 0 player-favourable targets. Where N divides evenly by ci, nothing is rounded away and the edge is exactly 1.00%; up to 10,000× that holds on 282 targets, and the other 999,618 return less than 99% by at most 0.009982 pp — the most at 9,989.91×, an edge of 1.009982%. On every target the server accepts, the edge is therefore between 1.00% and 1.009982%: never in the player’s favour, never above 1.01%.

Settlement adds nothing on top. A win credits exactly stake × target (Step 9) — a two-decimal stake times a two-decimal target is exact to four decimals — so no rounding moves the payout in either direction, and the return a player receives at a target is exactly its win chance × target.

4 columns
TargetWin chanceRTPEdge
1.01×98.019801% (98,019,801 draws)98.999999%1.000001%
1.10×90.000000% (90,000,000 draws)99.000000%1.000000%
2.00×49.500000% (49,500,000 draws)99.000000%1.000000%
10.00×9.900000% (9,900,000 draws)99.000000%1.000000%
100.00×0.990000% (990,000 draws)99.000000%1.000000%
9,989.91×0.009909% (9,909 draws)98.990018%1.009982%
10,000×0.009900% (9,900 draws)99.000000%1.000000%
Result: Edge ≥ 1.00% at every target, asserted in exact integers over all 99,999,900 (Step 16); exactly 1.00% on 282 of the 999,900 targets up to 10,000× and at most 1.009982%; every win credited exactly stake × target, with no settlement rounding (Step 9).
Variance: Limbo’s high targets win rarely and pay large: at 10,000× a round wins 0.0099% of the time and pays 10,000× the stake. Over a session the return can sit far above or below 99% — the capture’s Phase B returned 40.000% on its 1,000 high-target rounds and Phase C 104.000% on its 200 rounds at 2.00×. The 99% is the long-run average at every target, and every round can be checked from its seeds.
4.4Simulation Pass 1 — Our Own Seeds (Step 23)

Section 4.1 proves each target’s RTP exactly. But does the game engine actually produce it in practice? To find out, we simulated rounds with our own implementation and our own seeds — every seed derived from a pinned master seed, pf.org/liqd-limbo/pass1/2026-09-26, rotated every 50 rounds like the epochs of this capture, and replayable byte for byte: 2,000,000 uniformity draws and 1,000,000 rounds at each of 10 targets from 1.01× to 100,000× — 12,000,000 simulated rounds, produced by src/pass1-grid.ts. The artifact stores counts, not verdicts; Step 23 re-derives every statistic from them on every verify run and replays the opening draws and rounds of every stream from the master seed. The uniformity, lag-1 and runs screens are each judged at α/3 (0.00333), together a family-wise level of 0.01, and each target’s wins must sit within 5 SD + 1 win of its exact win count:

Simulation Pass 1 — Our Own Seeds (Step 23) — illustrated flow

Draw uniformity: 2,000,000 draws over 100 bins (χ², df = 99) — χ² = 94.393, p = 0.6122.

Serial screen: Lag-1 autocorrelation of the bin index and a runs test above and below the midpoint, over all 2,000,000 draws — lag-1 r = 0.000663 (z = 0.937) against the applied critical 2.936; 1,000,117 runs against 999,999.4 expected (z = 0.166, p = 0.8679).

Win counts at 10 targets: 1,000,000 rounds at each of 1.01×, 1.50×, 2×, 5×, 10×, 50×, 100×, 1,000×, 10,000× and 100,000× — the table below. Every target within 5 SD + 1 win of its exact count ⌊N ÷ ci⌋ ÷ 10⁸ × rounds; largest deviation 1.56 SD, at 10×.

Replay: The first 50,000 draws and the first 20,000 rounds of every target stream are recomputed from the master seed on every verify run; npm run pass1:check regenerates all 12,000,000 byte for byte.

Reference run (the chart above): A separate 38,000,000-round run from its own master seed, pf.org/liqd-limbo/simulate/2026-09-26, regenerated byte for byte by npm run test:full: its 20,000,000 rounds at 2.00× converge to 99.0128%, 0.57 SE from 99%.

6 columns
TargetRoundsWinsExpectedzSimulated RTP
1.01×1,000,000980,236980,198.0+0.2799.0038%
1.50×1,000,000659,544660,000.0−0.9698.9316%
2×1,000,000494,575495,000.0−0.8598.9150%
5×1,000,000197,421198,000.0−1.4598.7105%
10×1,000,00098,53499,000.0−1.5698.5340%
50×1,000,00019,99119,800.0+1.3799.9550%
100×1,000,0009,9819,900.0+0.8299.8100%
1,000×1,000,0001,003990.0+0.41100.3000%
10,000×1,000,00011499.0+1.51114.0000%
100,000×1,000,00089.9−0.6080.0000%
src/pass1-grid.ts · Pass 1 target rounds · Verified
export function targetWins(target: number, n: number): number {  let wins = 0;  for (let i = 0; i < n; i++) if (limboWin(resultMultiplier(pass1Draw(`target:${target}`, i)), target)) wins++;  return wins;} export function buildPass1Grid(): Record<string, unknown> {  const uniformity = uniformityCounts(PASS1_UNIFORMITY_DRAWS);  const prefix = uniformityCounts(PASS1_PREFIX_DRAWS);  const targets = PASS1_TARGETS.map((t) => ({    target: t,    ci: targetToHundredths(t),    rounds: PASS1_ROUNDS_PER_TARGET,    wins: targetWins(t, PASS1_ROUNDS_PER_TARGET),    prefixRounds: PASS1_PREFIX_ROUNDS,    prefixWins: targetWins(t, PASS1_PREFIX_ROUNDS),  }));  // …
Result: 12,000,000 simulated rounds. Uniformity p = 0.6122, both serial screens clear at α/3, and 10 / 10 targets within 5 SD + 1 win of their exact win count — largest deviation 1.56 SD, at 10× (Step 23). The reference run’s 20,000,000 rounds at 2.00× land at 99.0128%, 0.57 SE from 99% (the chart above).

What is a Monte Carlo simulation? Instead of proving fairness with algebra alone, we simulate millions of game rounds. Each round derives a draw from scratch using the same HMAC-SHA256 pipeline as the live game, maps it to a result, and records whether the round reached its target. We then compare the win counts with the exact counts from 4.1. If they match within tolerance, the math holds in practice — not just on paper.

How the artifact is scored, not believed: Step 23 does not read a pass flag. It re-derives χ² from the stored 100-bin histogram, the lag-1 and runs statistics from the stored integer sums, and each target’s deviation from its integer win count against the exact win count — and it recomputes the stored prefixes from the master seed on every run, which binds the counts to the declared seeds. Because every seed comes from the pinned master seed, npm run pass1:check regenerates the whole 12,000,000-round experiment and reproduces the committed artifact byte for byte.

Reading the deviations: Each target is an independent 1,000,000-round experiment whose spread grows with the target: at 10,000× a single win moves the simulated return by a full percentage point, so 114 wins against 99.0 expected reads as 114.0000% — only 1.51 SD. The scored gate is the win count within 5 SD + 1 win; the RTP itself is proven exactly in 4.1–4.3.
4.5Cherry-Pick Test — Pass 2 (Step 24)

Could the casino have chosen server seeds that pay players less? Pass 2 takes every server seed LIQD actually used — all 134 revealed seeds — and replays each one for 10,000 nonces with the epoch’s recorded client seed: 1,340,000 draws in 200 windows of 50 per seed. Window 0 is the captured window (nonces 0–49) and reproduces all 6,700 captured results. The load-bearing statistic is the played-target return (the repo’s “served-target return”): each window bets the 50 targets played in that epoch’s captured rounds, at unit stake, standardised by each target’s exact mean and variance from its exact win count. If no seed was chosen against the player, the captured window is exchangeable with the other 199, so its rank among them has an exact permutation null. Comparing each captured window with its own seed’s other windows is what tells a chosen seed apart from ordinary variance. The rank-sum Z over the 134 epochs is tested one-sided against a fixed rule: cherry-picking is indicated only at Z < −3.090 (α = 0.001). Step 24 re-derives the whole Pass 2 block from the revealed seeds and compares it with the committed artifact leaf for leaf. A supplementary screen compares each seed’s first 50 draws with its next 50.

Primary defence — structural: LIQD takes the client seed from the player at each seed rotation, after the next server seed’s hash is already published. An operator that cannot predict the player’s seed cannot pre-evaluate candidate server seeds against it — so for a player who sets an unpredictable seed, cherry-picking is contained by design. In this capture 124 of the 134 epochs carry a fresh random client seed set after LIQD published that epoch’s commitment; the other 10 are Phase D’s, seeds we chose to show LIQD uses the seed the player sets.

Statistical check: The played-target return test above — a direct check on the money, backing the structural defence.

2 columns
TestDetail
Seeds tested134 — every revealed server seed in the capture, with its epoch’s client seed
Replay depth10,000 nonces per seed — 1,340,000 draws in 200 windows of 50; window 0 replays the captured results with 0 mismatches
StatisticPlayed-target return of the captured window, ranked among its seed’s 200 windows; rank-sum Z over the 134 epochs, one-sided
ResultZ = −0.1711 (one-sided p = 0.4321) against the −3.090 rule — no cherry-picking indicated: the captured windows rank as chance predicts, below their epoch’s median in 63 epochs, above it in 62 and level with it in the rest
Detection powerA selection that puts at least 16.1% of epochs’ captured windows in their worst decile is detected at least half the time
Early/late screenNonces 0–49 against 50–99 over 20 bins, exact multinomial null over 832 attainable values: critical χ² ≥ 30.8, attained size 0.045924; 10 epochs over the threshold against 5.8712 expected, exact binomial P = 0.0708 — consistent with chance (rejected only below 0.05)
Pooled captured drawsAll 6,700 captured draws, 100 bins: χ² = 108.985, df = 99, p = 0.2315 — informational
src/pass2.ts · Step 24 (played-target return) · Verified
    const served = group.map((b) => targetToHundredths(Number(b.params.target)));    const targets = group.map((b) => Number(b.params.target));    const cr = served.map((ci) => Math.floor(RESULT_NUMERATOR / ci));   // exact winning draws per target (floor(N/ci))    let mu = 0, v = 0;    for (let j = 0; j < PASS2_WINDOW; j++) { const p = cr[j] / RANGE, T = served[j] / 100; mu += T * p; v += T * T * p * (1 - p); }    const S: number[] = [];    const early: number[] = [], late: number[] = [];    for (let w = 0; w < WINDOWS; w++) {      let ret = 0;      for (let j = 0; j < PASS2_WINDOW; j++) {        const n = w * PASS2_WINDOW + j;        const k = generateProvablyFairNumber(s.serverSeed, s.clientSeed, n, CURSOR, RANGE);        noncesTotal++;        const R = resultMultiplier(k);        if (w === 0) { early.push(k); if (R !== Number(group[j].result) || group[j].nonce !== n) replayMismatches++; }        if (w === 1) late.push(k);        if (limboWin(R, targets[j])) ret += served[j] / 100;      }      S.push((ret - mu) / Math.sqrt(v));      if (w === 0) { capturedReturn += ret; capturedExpected += mu; }    }    const x0 = S[0];    const rank0 = S.filter((y) => y < x0).length + (S.filter((y) => y === x0).length + 1) / 2;    const meanR = (WINDOWS + 1) / 2;    rankSum += rank0 - meanR;    // …  const Z = rankSum / Math.sqrt(rankVar);  const oneSidedP = normalCdf(Z);
Result: PASS. On LIQD’s 134 seeds the captured windows rank among each seed’s other 199 windows as chance predicts: rank-sum Z = −0.1711, p = 0.4321 against the −3.090 rule, with window 0 reproducing every captured result. The early/late screen finds 10 epochs over its threshold against 5.8712 expected (P = 0.0708), and the 6,700 captured draws pool to a uniform χ² (p = 0.2315). The test finds no sign of seed selection (Step 24).
Why is this included? Cherry-pick testing is a standard part of our audit methodology. Commit-reveal constrains what happens after a commitment, not which seed gets committed. LIQD takes the client seed from the player at each rotation, after the next commitment is published, so cherry-picking is structurally contained for a player who sets an unpredictable seed. Pass 2 adds a statistical check on the money itself, and it replays identically on every run.
4.6Bet-Size Invariance (Steps 13, 20)

Phase C placed 200 rounds at $10.00 on 2.00× — 100× the $0.10 stake used in every other phase. All 200 results were recomputed exactly from revealed seeds using the same single-draw HMAC-SHA256 path, and every $10.00 win credited exactly stake × 2.00 = $20.00. Bet amount is not an input to the RNG and does not affect the result distribution, the win chance or the RTP — the same multiplier at every stake.

3 columns
MetricPhases A, B, D ($0.10)Phase C ($10.00)
Rounds6,500200
Result recomputation parity6,500 / 6,500200 / 200
Wins · losses2,718 · 3,782104 · 96
Paid multiplierThe target, on every win2.00×, on all 104 wins
Credit per winStake × target exactly, in integers (Steps 9, 20)$20.00 — exactly $10.00 × 2.00
Win chance and RTP at the target⌊N ÷ ci⌋ ÷ 10⁸ × target — no stake input49.500000% × 2.00 = 99.000000%
Stake argument in limboDrawNone — the function takes the server seed, the client seed and the nonceNone — same function, same code path
Result: Equivalence proven deterministically. Same RNG, same win rule, same payout — stake × target — at both stakes: 200 / 200 Phase C rounds settle by the rule (Step 13), and the outcome and credit follow from the seeds, the nonce, the target and the stake on all 6,700 rounds (Step 20).
4.7Target Range & Exposure Ceiling (Steps 11, 17, 25)

LIQD bounds a Limbo bet in three ways, and the audit checks each against the captured rounds and the evidence. The potential payout, stake × target, is capped at $50,000: on 11 September LIQD refused a $0.10 bet at 1,000,000× with errorCode 3138 (MaxExposureExceededError) and named $0.05 as the maximum stake for that target — $0.05 × 1,000,000 = $50,000. Every captured round sits within the limit (Step 11); the 250 rounds at 500,000× × $0.10 were placed at exactly $50,000, and none of them won. The stake has a $0.10 minimum in LIQD’s client configuration, and every captured stake is at or above it. The target starts at 1.01× (played in Phase A), and LIQD’s server accepts targets up to 10,000× in steps of 0.01: on 2 October 2026 a $0.10 control at 10,000× settled, and requests at 100,101.12×, 497,487.44×, 500,000× and 990,000.01× were each refused with HTTP 400, errorCode 3001 — "Too big: expected number to be <=10000" — with no nonce consumed (Step 25). The 1,000,000× target in LIQD’s client code could not be bought at any allowed stake on the captured build: at the $0.10 minimum it is $100,000 of exposure against the $50,000 limit (Step 17). LIQD’s game code also names 1,000,000× as the maximum result — only draws 0–97 would go higher, and they win every target either way (2.7).

3 columns
ItemLimitEvidence
Maximum potential payout (stake × target)$50,000Derived from LIQD’s own refusal — errorCode 3138 named $0.05 as the maximum stake at 1,000,000×, and $0.05 × 1,000,000 = $50,000; 0 of 6,700 rounds over it, 250 placed at exactly $50,000 (Step 11)
Minimum stake$0.10LIQD’s client configuration; every captured stake is at or above it (Step 11)
Minimum target1.01× (win chance 98.019801%)Played in Phase A (Step 13)
Maximum target the server accepts10,000× (win chance 0.009900%)The server’s own message: "must be a multiple of 0.01 between 1.01 and 10000 (win chance 0.009900%–98.019801%)" — 4 / 4 higher targets refused on 2 October 2026 (Step 25)
1,000,000× target in LIQD’s client code1,000,000×Not purchasable at any allowed stake on the captured build — $100,000 of exposure at $0.10 (Step 17)
Highest captured target500,000×250 rounds on the captured build, which accepted targets up to 500,000× — each recomputed exactly (Steps 5, 13)
Result: Every captured round is inside LIQD’s limits: stake ≥ $0.10 and stake × target ≤ $50,000 (Step 11); the 1,000,000× target in LIQD’s client code could not be bought (Step 17); and the server refuses every target above 10,000× (Step 25), so every target a player can bet is one whose RTP is proven within 0.01 pp of 99% (4.2).
4.8Informational Items (Not Scored)

These items are reported for transparency; none of them is a scored check. They provide context on the empirical return observed during data collection and on fields outside the scored surface. At the per-phase sample sizes in this audit, empirical RTP is not a meaningful measure of fairness — the exact proof in 4.1–4.3 is the authoritative evidence.

3 columns
ItemValueContext
Live capture return100.4475%$2,661.86 returned on $2,650.00 wagered — variance illustration only; Phase C’s 104 wins at $20.00 alone returned $2,080.00 on $2,000.00
Live win rate42.12% (2,822 / 6,700)Against 2,831.0 wins expected from each round’s own win chance — mixed targets, informational
Phase returnsA 99.245% · B 40.000% · C 104.000% · D 91.266%$496.23 on $500.00 · $40.00 on $100.00 (4 wins, all at 100×) · $2,080.00 on $2,000.00 · $45.63 on $50.00
Largest result185,046.72×Bet S4mFs74Bt-nTtdNWQDxk5, draw k = 534, on a 1.17× target — paid 1.17×, the target: a win pays stake × target, never the result
Results at the 1× floor147 (2.19%)Against 132.67 expected; 78 of them lifted from a raw 0.99 — every one a loss, as 1× is below the 1.01× minimum target
Rounds exactly on target20Against 18.24 expected — all 20 paid as wins (Step 6)
Edge on the captured targets1.000000%–1.000041%Across the 1,104 targets the capture used, 57 of them at exactly 1.00% — the full curve over every target is 4.3
Capture-side self-check fieldslocalDraw, localResult, localWin, verified, creditedOk, walletOkCapture annotations, not audit findings — scored verification derives every result from the recorded inputs and ignores them
4.9Worked Example — Payout Verification

Real round from Phase B — bet S8UAp699y0_Mx7AZDzOp5 (epoch 100), nonce 39, target 100× at $0.10. Verified from limbo-master-6700bets.json — one of the four 100× wins and the highest target won in the capture:

serverSeed = c3e4e4ddf3a2a4074439e34bffbe367a
clientSeed = audit3b6552f5138f
nonce      = 39
target     = 100.00×
betAmount  = $0.10
recorded   = result 107.08 · win true · resultLabel won · winningAmount 10
commitment = cdaf3cbded0a18c4…

Step 1 — Draw computation: HMAC-SHA256(seed, "audit3b6552f5138f:39:0") → first chunk 0x1ddb8063 = 500,924,515 < 4,200,000,000 → mod 100,000,000 = 924,515 → k = 924,515.

Step 2 — Result: floor(9,900,000,000 / 924,516) = 10,708 → result 107.08× — matches the record.

Step 3 — Win check: 107.08 ≥ 100 → the round is legitimately won. The payout multiple is the target, 100×; the result above it pays nothing extra.

Step 4 — Payout: 0.10 × 100 = 10 — in integers 10 × 10,000 × 10,000 = 1,000,000,000 = credit × 10⁸ → $10.00, the credited winningAmount (stored as 10).

3 columns
StepProcessOutput
1Recompute the draw from seedsk = 924,515
2Result from the draw107.08× — equal to the record
3Apply result ≥ target at 100×Win — won confirmed
4Credit stake × target, 0.10 × 100$10.00 credited
5RTP identity at this target990,000 ÷ 100,000,000 × 100 = 0.99 — exactly 99%
RTP proof for 100× · Verified
winningDraws(100×) = floor(9,900,000,000 / 10,000) = 990,000   →  P(win) = 990,000 / 100,000,000 = 0.0099payout multiplier  = the target                                  =  100RTP = P(win) × target    = 0.0099 × 100    = 0.99   →  99.000000%   (990,000 × 10,000 = 9,900,000,000: nothing rounded away)
Parity verified: Bet S8UAp699y0_M… — draw, result, win condition and payout all match. RTP at 100× = 99.000000% — one of the four targets tallied over all 100,000,000 draws (Step 15), independent of any casino data. Phase B’s 250 rounds at 100× produced 4 such wins against 2.475 expected.
Live Game$10.00
Our recomputation$10.00
Technical Evidence & Verification5 sections
4.10Evidence Coverage Summary12 checks
3 columns
Verification AreaCoverageResult
Anti-circularity (Step 14)Negative control rejects 2.48×, NaN and Infinity; boundary anchor on 1,104 / 1,104 captured targets; grid extremes checkedPass
Win-chance identity (Step 15)1,104 / 1,104 targets — integer equality; 4 full 100,000,000-draw tallies agree; LIQD’s 49.5% and 0.0099% matchPass
House edge audit (Step 16)Edge ≥ 1.00% at all 99,999,900 targets in exact integers, 0 violations, 0 player-favourable; at most 1.009982% up to 10,000×Pass
RTP claim rule (Step 16)999,900 / 999,900 targets within ±0.1 pp of 99%; worst gap −0.009982 ppPass
Payout & credit (Steps 8, 9)2,822 wins paid at the target; 6,700 credits exact in integersPass
Bet-size invariance (Steps 13, 20)200 / 200 Phase C at $10.00; 6,700 / 6,700 settlements determinedPass
Stake & exposure limits (Step 11)0 stakes below $0.10; 0 rounds over $50,000Pass
1,000,000× purchase check (Step 17)1,000,000× not purchasable at any allowed stake on the captured buildPass
Simulation Pass 1 (Step 23)12,000,000 simulated rounds — uniformity p = 0.6122, serial clear, 10 / 10 targets within 5 SD + 1 winPass
Simulation Pass 2 (Step 24)134 seeds × 10,000 nonces: rank-sum Z = −0.1711 against −3.090; early/late 10 against 5.8712 (P = 0.0708)Pass
Artifact integrity (Step 19)Dataset, simulation and chart pins match; 966 numeric values inside their declared domainsPass
Server target limit (Step 25)4 / 4 targets above 10,000× refused on 2 October 2026; 300 / 300 supplement rounds recomputePass
4.11Code References14 source files
2 columns
FilePurpose
tests/steps/anti-circularity.tsSteps 14–15: Negative control, grid boundary anchor, win-chance identity and the four full-grid tallies
tests/steps/edge.tsSteps 16–17: The effective edge of all 99,999,900 targets and the 1,000,000× purchase check
src/rtp-claim.tsThe RTP claim rule, RTP-CLAIM 1.2.0 — a byte-identical copy of the audit framework’s rule
src/rtp-settings.tsThe 999,900-target exact RTP table and the claim it is held to (LIQD’s published 99%)
tests/steps/payouts.tsSteps 8–11: Paid multiplier, credit arithmetic, loss settlement, wallet fields, stake floor and exposure limit
tests/steps/pass1.tsStep 23: Re-derives every Pass 1 statistic from the stored counts and replays the prefixes
src/pass1-grid.tsPass 1 producer — seeds from the pinned master seed, counts only
tests/steps/pass2.tsStep 24: Re-derives the Pass 2 block from the revealed seeds
src/pass2.tsPass 2 — the played-target return with its exact permutation null, and the early/late screen with its exact multinomial null
src/simulate.tsThe reference run (38M rounds) and its convergence chart
tests/steps/standardization.tsSteps 19–20: Artifact integrity and settlement determinism
tests/steps/supplement.tsStep 25: The supplement and the server’s target limit
tests/steps/statistical.tsInformational items — live return, win rate, phase returns, result distribution
src/config.tswinChance, theoreticalRTP, effectiveEdge, resultMultiplier, limboWin — closed-form formulas
4.12Datasets UsedSource artifacts
RTP claim table: outputs/rtp-claim-table.json — the exact RTP of every target from 1.01× to 10,000× (999,900 settings) against the 99% claim, by target decade with the extremes · SHA-256 cdfbf600e0713b82…, pinned in src/rtp-settings.ts
Pass 1 grid: outputs/pass1-grid.json — the 2,000,000-draw histogram, the serial sums and 10 targets × 1,000,000 rounds from the master seed pf.org/liqd-limbo/pass1/2026-09-26 · SHA-256 9999f98ba17fe534…
Simulation output: outputs/simulation-results.json — the reference run (2M draws, 20M rounds at 2.00×, 8 targets × 2M) and the Pass 2 block re-derived by Step 24 · SHA-256 2d609344650eac72…
Convergence chart: outputs/rtp-convergence.html — the reference run’s 20M-round series at 2.00× at seven marks, pinned by SHA-256 efd121156f8255f3…
Primary dataset: data/limbo-master-6700bets.json — payout reconciliation, Phase C bet-size invariance, Pass 2 seeds and the informational returns
Session evidence: evidence/rtp-page-labels-2026-10-01.json (LIQD’s Limbo page figures, 1 October 2026), evidence/E15-limbo-max-exposure.json (the $50,000 limit, 11 September 2026) and evidence/E21-target-cap-probe/ (the server’s 10,000× limit, 2 October 2026)
4.13Verified Invariants13 invariants
2 columns
InvariantResult
Target m wins on exactly ⌊N ÷ ci⌋ of the 100,000,000 draws — derived from the result map, not read from LIQD (non-circular)Pass
⌊N ÷ ci⌋ = winChance(m) × 10⁸ as integers on all 1,104 captured targets; full-grid tallies agree at 1.01×, 2×, 100× and 500,000×Pass
Negative control: a result one tick away (2.48× for 2.47×), NaN and Infinity are rejected by the Step 5 predicatePass
⌊N ÷ ci⌋ × ci ≤ N at all 99,999,900 targets — edge ≥ 1.00%, never player-favourablePass
LIQD’s published 99% is met by all 999,900 targets up to 10,000× — worst gap −0.009982 pp against ±0.1 ppPass
LIQD’s page figures equal the exact win chance: 49.5% at 2× and 0.0099% at 10,000×Pass
Every win pays stake × target exactly — credit × 10⁸ = hundredths(stake) × hundredths(target) × 10⁴ on 2,822 / 2,822Pass
Every captured potential payout within $50,000; every stake ≥ $0.10Pass
Draw uniformity χ² p = 0.6122 over 2,000,000 simulated draws, at α/3Pass
Lag-1 z = 0.937 < 2.936 and runs p = 0.8679 ≥ α/3 on 2,000,000 drawsPass
10 / 10 simulated targets within 5 SD + 1 win of their exact win count (worst 1.56 SD)Pass
No cherry-picking indicated across 134 casino seeds (rank-sum Z = −0.1711 against −3.090; 0 replay mismatches)Pass
Phase C ($10.00) results derive from the same three-input draw as $0.10 rounds; stake absent from the RNG input by constructionPass
4.14Reproduction InstructionsRun locally

Clone the repository, install dependencies, and regenerate both simulations before verifying:

reproduce-s4.sh · 6 lines · Verified
git clone https://github.com/ProvablyFair-org/liqd-limbo.gitcd liqd-limbo && git checkout v1.0.1npm cinpm run test:full   # regenerates both simulations from their pinned master seeds, then verifieshead -40 outputs/rtp-claim-table.jsoncat outputs/pass1-grid.json | head -40
S4-related steps and the RTP claim rule:

  [PASS] Step 8 — Paid-Multiplier Derivation
  [PASS] Step 9 — Credit Arithmetic
  [PASS] Step 11 — Wallet Ledger, Stake Floor & Exposure Ceiling
  [PASS] Step 13 — Phase Plan Conformance
  [PASS] Step 14 — Anti-Circularity (Negative Control + Grid Boundary Anchor)
  [PASS] Step 15 — Win-Chance vs Result-Distribution Identity
  [PASS] Step 16 — Effective-Edge Curve
  [PASS] Step 17 — Client-Code Maximum Multiplier …
  [PASS] Step 20 — Settlement Determinism
  [PASS] Step 23 — Simulation Pass 1 — Pinned Master Seed, Re-Derived
  [PASS] Step 24 — Simulation Pass 2 — Cherry-Pick Screen, 10,000 Nonces per Revealed Seed
  [PASS] Step 25 — Target-Decade Supplement & Live Target Ceiling …

  rtp-claim: ENFORCED — PASS
  rtp-claim:   …
  rtp-claim:   band ±0.10 pp · 999,900 settings · true min 98.990018% (target=9989.91) · true max 99.000000% (target=1.10)
  rtp-claim:   house side beyond band: 0 · player-favourable beyond band: 0 · worst target=9989.91 at -0.009982 pp
  rtp-claim:   …
  rtp-claim:   outcome PASS · minimal fix: none — the claim holds for every setting it states a figure for
  rtp-claim:   committed table outputs/rtp-claim-table.json: equals this run; sha256 cdfbf600e0713b82… matches the pin

Final convergence RTP in the reference run’s committed artifact: 0.990128 (exact RTP at 2.00×: 0.99). The reference run is deterministic: npm run test:full regenerates outputs/simulation-results.json and outputs/rtp-convergence.html byte for byte; the simulation the verdict scores is Step 23’s, re-derived from outputs/pass1-grid.json on every run. The chart above plots the reference run’s 2.00× convergence series.
05

Fairness Integrity Testing

Does the implementation maintain fairness under non-standard conditions?

Sections 1–4 establish that the game is mathematically fair; Section 5 tests whether the live implementation keeps its integrity under non-standard conditions. It covers 34 integrity tests run against LIQD’s running service across 12 categories: race conditions, input validation, idempotency, authorization, outcome and payout fields, nonce and seed handling, information disclosure, and the provably fair surface itself. 34 passed. No test altered, replayed or injected an outcome, and all 2,764 bets captured during the testing recompute exactly.

Fairness Integrity Testing

34pass

What we verified

  • Race conditions — can parallel bets, a high-stake over-commit, or a seed rotation racing a live bet change an outcome or double-settle?
  • Nonce tampering — can the sequence be forced, replayed, or skipped?
  • Seed handling — can unusual client seeds, or the active server seed, be exposed or overridden?
  • Outcome replay — can an identical bet be settled twice, or a timed-out bet settle twice?
  • Cross-player isolation — can one player act on another’s account, or after logging out?
  • Payout tampering — can the result, payout, status or multiplier be injected, the win target swapped, or the payout cap bypassed?
  • Parameter limits — can invalid targets, stakes, content types or currencies be submitted?

What this means for you

  • Across the 34 tests, no request path let anyone alter, replay or inject an outcome
  • Once a bet is placed, its result cannot be changed or replayed
  • Each bet gets its own server-assigned nonce under a committed seed
  • Your bets are bound to your session — no other account can act on them
  • Every live round checked against LIQD’s verifier agreed with our independent recomputation
34pass
  • Race conditions — can parallel bets, a high-stake over-commit, or a seed rotation racing a live bet change an outcome or double-settle?
  • Nonce tampering — can the sequence be forced, replayed, or skipped?
  • Seed handling — can unusual client seeds, or the active server seed, be exposed or overridden?
  • Outcome replay — can an identical bet be settled twice, or a timed-out bet settle twice?
  • Cross-player isolation — can one player act on another’s account, or after logging out?
  • Payout tampering — can the result, payout, status or multiplier be injected, the win target swapped, or the payout cap bypassed?
  • Parameter limits — can invalid targets, stakes, content types or currencies be submitted?
4 / 4Race Conditions
5 / 5Input Validation
3 / 3Atomic Balance / Idempotency
2 / 2Broken Authorization
5 / 5Max-win Bypass
4 / 4Nonce Manipulation
4 / 4RNG / PF Verification
1 / 1RNG / Information Leak
1 / 1Entropy / PF Verification
1 / 1Precision / Payout
1 / 1Event Replay
3 / 3Adversarial Seeds

Category Coverage

Race Conditions4/4
Input Validation5/5
Atomic Balance / Idempotency3/3
Broken Authorization2/2
Max-win Bypass5/5
Nonce Manipulation4/4
RNG / PF Verification4/4
RNG / Information Leak1/1
Entropy / PF Verification1/1
Precision / Payout1/1
Event Replay1/1
Adversarial Seeds3/3
CheckResultEvidence
Nonce integrityPassServer-assigned and sequential: injected reused, skipped, zero or negative and overflow-sized nonces are all refused (NONCE-001 … NONCE-004); the nonce advances by one per bet (PF-002) and restarts at 0 on rotation (ENTROPY-004)
Seed integrityPassThe active seed is never disclosed before rotation (DISCLOSE-001); 70 / 70 rotations racing live bets revealed the committed seed and never reused one (EXTRA-001); unusual and Unicode client seeds handled (SEED-001, SEED-002), and each bet uses the seed set just before it (SEED-003)
Outcome determinismPassAll 2,764 bets captured during the integrity testing recompute exactly, with LIQD’s verifier agreeing on every live round checked against it (EXTRA-002, PF-001, PARAM-003); 2,000 live bets pass the uniformity test (FAIR-001)
Round & player isolationPass13 / 13 foreign and malformed identifiers rejected; post-logout reuse rejected; parallel identical and high-stake bets settled once each and reconciled to the wallet
Payout integrityPassInjected result, payout, status, multiplier and alias win-target fields all rejected; the 50,000 USDC potential-payout cap refuses over-limit stakes at bet time; 200 consecutive rounds settled with no payout mismatch
Parameter limitsPassEvery out-of-range and wrong-type target tested was rejected, and no content-type variant let an invalid target settle; invalid stakes rejected with no silent conversion to zero; all six currency overrides rejected
Nonce integrityPass
Server-assigned and sequential: injected reused, skipped, zero or negative and overflow-sized nonces are all refused (NONCE-001 … NONCE-004); the nonce advances by one per bet (PF-002) and restarts at 0 on rotation (ENTROPY-004)
Seed integrityPass
The active seed is never disclosed before rotation (DISCLOSE-001); 70 / 70 rotations racing live bets revealed the committed seed and never reused one (EXTRA-001); unusual and Unicode client seeds handled (SEED-001, SEED-002), and each bet uses the seed set just before it (SEED-003)
Outcome determinismPass
All 2,764 bets captured during the integrity testing recompute exactly, with LIQD’s verifier agreeing on every live round checked against it (EXTRA-002, PF-001, PARAM-003); 2,000 live bets pass the uniformity test (FAIR-001)
Round & player isolationPass
13 / 13 foreign and malformed identifiers rejected; post-logout reuse rejected; parallel identical and high-stake bets settled once each and reconciled to the wallet
Payout integrityPass
Injected result, payout, status, multiplier and alias win-target fields all rejected; the 50,000 USDC potential-payout cap refuses over-limit stakes at bet time; 200 consecutive rounds settled with no payout mismatch
Parameter limitsPass
Every out-of-range and wrong-type target tested was rejected, and no content-type variant let an invalid target settle; invalid stakes rejected with no silent conversion to zero; all six currency overrides rejected
✓  34 of 34 Pass

34 integrity tests: 34 pass. No test altered, replayed or injected an outcome, and every live round checked against LIQD’s verifier agreed with our independent recomputation. Test procedures and payloads are withheld by policy — see 5.5.

How It Works — Fairness Integrity Testing2 sections
5.1Framework Overview

Testing follows the ProvablyFair.org Fairness Integrity Framework — a structured methodology derived from real, historically observed failures in provably fair systems. Cryptographic verification (S1–S3) shows the game computed every captured round from its disclosed inputs; fairness-integrity testing asks whether the surface around that computation holds up under non-standard use — seeds swapped, rounds replayed, parameters injected, settlements raced. These tests ran against LIQD’s running service, not against a dataset. This section covers 34 tests across 12 categories. Each card below gives the control assessed, the broad testing approach, the observed result, its impact and any limitation.

3 columns
CategoryTestsWhat It Catches
Race Conditions4Parallel bets racing placement, settlement or seed rotation
Input Validation5Malformed, wrong-type or out-of-range targets, stakes, content types and currencies
Atomic Balance / Idempotency3Double debits, orphaned debits and unsafe retry behaviour after a timeout or disconnect
Broken Authorization2Acting on another account, or after the session has ended
Max-win Bypass5Client-supplied outcome or win-target fields, and the stake and potential-payout limits
Nonce Manipulation4Reused, skipped, zero, negative or oversized nonces supplied by the client
RNG / PF Verification4Result distribution, commitment and reveal, and independent recomputation of live rounds
RNG / Information Leak1Disclosure of the active server seed, or of hidden game state, before it is due
Entropy / PF Verification1Server-seed rotation behaviour: fresh seeds, a new commitment and the nonce reset
Precision / Payout1Settlement arithmetic and wallet reconciliation over many rounds
Event Replay1Duplicate or replayed requests settling more or less than once
Adversarial Seeds3Unusual client seeds: length, character sets, Unicode forms and rapid rotation
5.2Severity Framework & Hard Fail Criteria

Two scales apply in this section. The audit framework decides whether a result can pass at all — any test that lets money settle against a tampered or unverifiable outcome is an automatic hard fail. The integrity matrix records each test’s status — Pass, Remediated, Warning or Fail — and each finding is graded Critical, High, Medium or Low. On this page: all 34 tests pass, so there is nothing to grade.

3 columns
StatusMeaningAction
PassThe service handled the scenario correctly: the control heldNone — recorded with its result
RemediatedA failed control that LIQD has since fixed, re-tested by ProvablyFair.org on the current buildRecorded with the fix and its re-test
WarningA real defect, or a property not yet proven, without a direct effect on funds or fairnessRecorded with its impact; open until a re-test resolves it
FailA defect affecting funds, fairness or securityRemediation and a re-test required
2 columns
ConditionConsequence
A substituted server seed is accepted against a published commitmentHard fail
A revealed seed fails its commitment and play continuesHard fail
Any settled round’s result cannot be reproduced from disclosed inputsHard fail
A credited payout deviates from stake × target on a win, or from zero on a lossHard fail
Nonce reuse or gaps that could conceal discarded outcomesHard fail
Hard fail criteria: These conditions are absolute — a single occurrence fails the game regardless of every other result. None of them occurred in testing: no test altered, replayed or injected an outcome.

34 tests · 34 pass

Race Conditions4/4
LIQD-LIMBO-EXTRA-001Pass

Seed rotation racing live bets

Control assessed
A rotation racing bets cannot reveal a seed early or inconsistently, or bring a revealed seed back into use.
Testing approach
Seventy rotations fired against bets in flight; each revealed seed checked against its pre-race commitment.
Observed result
All 70 rotations succeeded; each revealed seed matched its pre-race commitment, and none reappeared as a later active seed.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-RACE-001Pass

Nonce uniqueness under parallel bets

Control assessed
Every accepted bet gets its own server-assigned nonce in sequence, even under parallel load.
Testing approach
Bursts of parallel bets from one account; nonces and response order compared.
Observed result
All 55 bets were accepted with unique nonces and no server error; ordered by arrival, the nonces ascend without a gap across the run.
Impact
None — no nonce was shared or skipped.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-RACE-002Pass

Identical bets in parallel, at scale

Control assessed
Each accepted bet settles exactly once, and the balance equals the sum of all settled rounds.
Testing approach
Fifteen identical bets sent in parallel; round IDs and balance reconciled.
Observed result
All 15 settled once, as distinct rounds; the balance change matched their summed outcome exactly.
Impact
None — no lost or duplicated settlement.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-RACE-003Pass

High-stake parallel over-commit

Control assessed
Parallel high-stake bets cannot overdraw the balance, settle twice or leave a phantom round.
Testing approach
Parallel bets whose combined stake exceeds the balance, with a control bet; wallet and bet history reconciled.
Observed result
All 16 settled rounds, the control included, reconciled to the wallet and the bet history: no over-commit, no negative balance, no payout mismatch, no lost or phantom round.
Impact
None — settlement stayed consistent under parallel placement.
Limitations
None specific to this test; the general scope is in 5.5.
Input Validation5/5
LIQD-LIMBO-PARAM-001Pass

Target limits and input types

Control assessed
Valid targets are accepted; out-of-range and wrong-type targets are refused.
Testing approach
The valid boundary targets, out-of-range and wrong-type targets, with a control bet.
Observed result
The control and both valid boundaries were accepted, and every out-of-range and wrong-type target was rejected.
Impact
None — the control held.
Limitations
Targets above 10,000× are checked in Section 4 (Step 25).
LIQD-LIMBO-PARAM-002Pass

Stake edge cases

Control assessed
Minimum and free-play stakes are accepted; invalid stakes are refused, with no silent conversion to zero.
Testing approach
Minimum-stake and zero-stake controls, then negative, oversized, sub-precision, null and wrong-type stakes.
Observed result
Both controls settled; every invalid stake was rejected, with no silent conversion to zero.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-PARAM-003Pass

Lowest target, recomputed

Control assessed
The lowest valid target settles, and its outcome is independently reproducible from the revealed seed.
Testing approach
A bet at the 1.01× floor, recomputed after the reveal and checked against LIQD’s verify endpoint.
Observed result
The 1.01× bet settled, and its result, win flag and multiplier matched our recomputation exactly, with LIQD’s verifier agreeing.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-PARAM-004Pass

Content-type variations around validation

Control assessed
Changing how the request body is encoded cannot let an invalid target settle.
Testing approach
Invalid targets sent under each alternative content type, with a control bet.
Observed result
The control settled; both invalid targets were rejected under every content type, so no body-parsing path skipped validation.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-TOKEN-001Pass

Currency identifier manipulation

Control assessed
A client-supplied currency identifier cannot redirect or alter the stake.
Testing approach
Bets carrying six altered currency identifiers, with a control bet.
Observed result
All six were rejected without a server error; only the control settled, in the expected wallet.
Impact
None — the stake stayed in the account’s own currency.
Limitations
None specific to this test; the general scope is in 5.5.
Atomic Balance / Idempotency3/3
LIQD-LIMBO-IDEMP-001Pass

Same-key retry after a timeout

Control assessed
A retry with the same key returns the original round, with no second debit.
Testing approach
Bets sent with very short client timeouts, then retried with the same key; wallet reconciled over the nonces used.
Observed result
All 20 retries returned their original round; all 10 timed-out bets had settled server-side, and the wallet reconciled exactly over the 20 nonces.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-IDEMP-002Pass

Orphaned-debit check at scale

Control assessed
Every debit belongs to a settled round.
Testing approach
Twenty consecutive bets; the balance reconciled against their outcomes.
Observed result
All 20 settled, and the balance change matched their summed outcome exactly.
Impact
None — no unexplained debit.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-IDEMP-003Pass

Disconnect while a bet is processed

Control assessed
Every consumed nonce is explained, and no debit is left unexplained.
Testing approach
Bets abandoned by the client at different moments; rounds and balance reconciled.
Observed result
The earliest-abandoned bet never reached settlement; the other three settled. The balance moved by exactly the net of the three settled rounds.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
Broken Authorization2/2
LIQD-LIMBO-AUTH-001Pass

Foreign and malformed user identifiers

Control assessed
A user identifier supplied in the request is refused; bets stay bound to the caller’s own session.
Testing approach
Bets sent with foreign and malformed user identifiers in the request, alongside control bets; the caller’s balance reconciled.
Observed result
All 13 injected identifiers were rejected without a server error; the two control bets account for the caller’s entire balance change.
Impact
None — no injected identifier was accepted.
Limitations
Covers the caller’s balance.
LIQD-LIMBO-AUTH-003Pass

Session reuse after logout

Control assessed
A session ends at logout: its cookie cannot place another bet.
Testing approach
A control bet, a logout, then a bet attempted with the old session.
Observed result
The control bet settled and the logout completed; the reused session was rejected and no bet was placed.
Impact
None — the session was invalidated server-side.
Limitations
None specific to this test; the general scope is in 5.5.
Max-win Bypass5/5
LIQD-LIMBO-INJECT-001Pass

Single outcome field injected

Control assessed
No client-supplied result, payout, status or multiplier value can control settlement.
Testing approach
Bets each carrying one injected outcome field, with two control bets.
Observed result
All four injected fields were rejected by schema validation; the two controls account for the entire nonce advance and balance change.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-INJECT-002Pass

Win-target field injection

Control assessed
No injected field changes the win condition, and malformed targets are refused.
Testing approach
Bets carrying alias target fields, malformed targets and a parameter override.
Observed result
The alias fields and malformed targets were rejected; the override bet settled against the target it declared. No win condition or multiplier diverged from the request.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-MAXWIN-001Pass

Stake boundary enforcement

Control assessed
Out-of-range stake values are refused without touching the balance.
Testing approach
Oversized, negative and malformed stakes alongside a free-play control.
Observed result
The control settled; all four invalid stakes were rejected and none consumed balance.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-MAXWIN-002Pass

Several outcome fields injected at once

Control assessed
Outcome fields supplied by the client cannot steer settlement or consume a nonce.
Testing approach
One bet carrying several injected outcome fields together, with a control bet.
Observed result
The combined injection was rejected at validation; only the control consumed a nonce and moved the balance.
Impact
None — settlement stayed server-computed.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-MAXWIN-003Pass

Potential-payout cap at bet time

Control assessed
A bet whose potential payout exceeds the 50,000 USDC cap is refused when it is placed, and the boundary stake settles.
Testing approach
Stakes just inside and just outside the cap at a high target, and one far above it.
Observed result
The oversized and just-over stakes were refused, the reply naming the largest allowed stake; the boundary stake settled. Only the boundary bet consumed a nonce and moved funds.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
Nonce Manipulation4/4
LIQD-LIMBO-NONCE-001Pass

Reused nonce

Control assessed
The client cannot choose the nonce: an injected value is refused, and the server keeps its own sequence.
Testing approach
Bets carrying a reused nonce.
Observed result
The replayed nonce was rejected at validation and never echoed; only the control and recovery bets advanced the nonce and moved the balance.
Impact
None — the server kept its own sequence.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-NONCE-002Pass

Skipped-ahead nonce

Control assessed
The client cannot choose the nonce: an injected value is refused, and the server keeps its own sequence.
Testing approach
Bets carrying a skipped-ahead nonce.
Observed result
The skipped-ahead nonce was rejected at validation and never echoed; the server did not skip ahead in its sequence.
Impact
None — the server kept its own sequence.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-NONCE-003Pass

Zero and negative nonces

Control assessed
The client cannot choose the nonce: an injected value is refused, and the server keeps its own sequence.
Testing approach
Bets carrying zero and negative nonces.
Observed result
All three zero and negative nonces were rejected at validation and none was honoured; only the two controls advanced the nonce.
Impact
None — the server kept its own sequence.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-NONCE-004Pass

Overflow-sized nonces

Control assessed
The client cannot choose the nonce: an injected value is refused, and the server keeps its own sequence.
Testing approach
Bets carrying overflow-sized nonces.
Observed result
All three overflow-sized nonces were rejected at validation, with no server error; only the two controls advanced the nonce.
Impact
None — the server kept its own sequence.
Limitations
None specific to this test; the general scope is in 5.5.
RNG / PF Verification4/4
LIQD-LIMBO-EXTRA-002Pass

Independent result and outcome check

Control assessed
Live results can be reproduced independently from the revealed inputs.
Testing approach
Rounds recomputed with our own implementation and checked against LIQD’s verify endpoint, then every bet captured during the integrity testing recomputed.
Observed result
All five rounds recomputed to the settled outcome, with LIQD’s verifier agreeing; all 2,764 bets captured during the integrity testing recomputed exactly.
Impact
None — the control held.
Limitations
The recomputation covers the logic version tested; no result in testing came near the top of the result range.
LIQD-LIMBO-FAIR-001Pass

Draw distribution and recomputation

Control assessed
Every bet reproduces exactly from its revealed seed, the draw is uniform, and a winning bet pays exactly its target.
Testing approach
Two thousand live bets at 2×, recomputed after the reveal; the draw tested for uniformity and the win rate against its price.
Observed result
All 2,000 bets recomputed exactly — result, win flag and multiplier. The draw passed the uniformity test, the win rate matched the priced 0.495, and every win paid exactly 2×.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-PF-001Pass

Provably-fair data and commit / reveal

Control assessed
Each bet carries every provably-fair field, the revealed seed matches its commitment, and each outcome reproduces.
Testing approach
Three bets checked field by field, then the seed revealed and the rounds recomputed and checked against LIQD’s verify endpoint.
Observed result
All fields were present and the nonce advanced by exactly 3; the revealed seed hashed to its commitment, and all three rounds recomputed to the settled outcome, with LIQD’s verifier agreeing.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-PF-002Pass

Nonce sequence within a seed period

Control assessed
The nonce advances by exactly one per bet while the commitment stays unchanged.
Testing approach
Ten consecutive bets on one server seed, then a reveal.
Observed result
The nonce advanced from 0 to 10 with no gap while the commitment stayed unchanged; once the seed was revealed, all 10 bets recomputed exactly.
Impact
None — the sequence was complete and the commitment fixed.
Limitations
None specific to this test; the general scope is in 5.5.
RNG / Information Leak1/1
LIQD-LIMBO-DISCLOSE-001Pass

Active server-seed disclosure

Control assessed
The active server seed stays secret until rotation; only then is it revealed.
Testing approach
The responses around a bet checked for the active seed before rotation; a rotation then served as the positive control.
Observed result
No response checked exposed the active seed; after rotation, the revealed seed matched its commitment.
Impact
None — the seed stayed secret until it was due.
Limitations
Covers one bet and one seed period.
Entropy / PF Verification1/1
LIQD-LIMBO-ENTROPY-004Pass

Nonce reset after seed rotation

Control assessed
Rotating the seed changes the commitment and restarts the nonce at 0.
Testing approach
Bets, a rotation, then further bets.
Observed result
The nonce reset from 2 to 0, the commitment changed, and both follow-up bets settled normally.
Impact
None — rotation behaved as specified.
Limitations
None specific to this test; the general scope is in 5.5.
Precision / Payout1/1
LIQD-LIMBO-PREC-001Pass

Payout consistency at scale

Control assessed
Every payout follows the game’s settlement rule, and the wallet reconciles over the run.
Testing approach
Two hundred consecutive rounds; each payout and the aggregate balance change checked.
Observed result
All 200 rounds settled with no payout mismatch; the balance change matched the summed outcome exactly.
Impact
None — settlement arithmetic held.
Limitations
None specific to this test; the general scope is in 5.5.
Event Replay1/1
LIQD-LIMBO-REPLAY-001Pass

Duplicate identical bets sent together

Control assessed
Two identical requests sent at the same moment settle as two distinct rounds — none lost, none settled twice.
Testing approach
A pair of identical bet requests sent in parallel; rounds and balance reconciled.
Observed result
Both requests settled as distinct rounds — one won and one lost at 2×, so the balance was unchanged: two real rounds netting to zero.
Impact
None — each request settled exactly once.
Limitations
None specific to this test; the general scope is in 5.5.
Adversarial Seeds3/3
LIQD-LIMBO-SEED-001Pass

Client-seed length and character sets

Control assessed
Client seeds have a consistent length limit, and the game stays usable after every accepted or refused change.
Testing approach
Client seeds of increasing length and unusual character sets, each followed by a bet.
Observed result
Seeds up to 255 characters were accepted and longer ones consistently rejected; empty, markup and path-like seeds were rejected, other character sets accepted. Every following bet settled.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-SEED-002Pass

Unicode forms of one seed

Control assessed
Composed and decomposed Unicode forms of the same seed are both usable and handled consistently.
Testing approach
The same visible seed submitted in both forms, each followed by a bet.
Observed result
Both forms were accepted and stored as the same composed seed, and both following bets settled.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-LIMBO-SEED-003Pass

Rapid seed rotation

Control assessed
Each bet uses the seed set by the rotation immediately before it.
Testing approach
Back-to-back rotations, each followed by a bet.
Observed result
Every bet used the seed set just before it; no stale or mixed-up seed appeared.
Impact
None — each bet bound to the current seed.
Limitations
None specific to this test; the general scope is in 5.5.
Technical Evidence & Verification4 sections
5.3Coverage Summary12 categories

Coverage by category — 34 tests: 34 pass.

3 columns
CategoryTestsResult
Race Conditions4All pass
Input Validation5All pass
Atomic Balance / Idempotency3All pass
Broken Authorization2All pass
Max-win Bypass5All pass
Nonce Manipulation4All pass
RNG / PF Verification4All pass
RNG / Information Leak1All pass
Entropy / PF Verification1All pass
Precision / Payout1All pass
Event Replay1All pass
Adversarial Seeds3All pass
Method: Every test ran against LIQD’s running service on its pre-launch environment. Sections 1–4 verify the captured rounds offline — independent derivation, commitment and chain checks, nonce continuity and credit arithmetic — while Section 5 tests the live service around them. Per-test procedures, payloads and captures are retained under the engagement record and are not published — see 5.5.
5.4Findings & Remediation

No findings — all 34 tests in this section pass. Each test’s card in the matrix above gives its control, approach, result, impact and limitations.

5.5Scope & LimitationsScope note
What is published here is coverage, not method. Each test in this section appears with its identifier and category, the control it assessed, the broad testing approach, the observed result, its impact and any limitation; a finding also carries its severity, where graded, and its remediation state. Request payloads, preconditions, step sequences, endpoints, timings and response captures are withheld: they show how to repeat a test, and add nothing a reader needs to judge its result. The full record is retained and is available to LIQD and to regulators on request.
Coverage published, method withheld: The classes of issue tested are standard and publicly documented. What would help someone repeat a test — volumes, timings, settings and request sequences — stays in the engagement record.
Point-in-time, per build: Each result is scoped to the build tested.
Scope: The tests target the provably fair surface and the money path around it — seeds, rounds, placement and settlement. Wallet custody, account management and infrastructure hardening are outside their scope.
Test environment: Every test ran against LIQD’s pre-launch environment (qa.liqd.com), not the public site.
5.6Evidence & Disclosure

Live-surface record: Section 5 tests LIQD’s running service, so unlike Sections 1–4 it does not reproduce from the audit repository. The published record is the test cards above.

Retained under the engagement record: Per-test procedures, request and response captures, run timestamps and build identifiers — available to LIQD and to regulators on request.

Section 5 coverage · published on this page
Integrity tests ....................... 34Categories ............................ 12Pass .................................. 34Remediated ............................ 0Warning (open) ........................ 0Findings .............................. 0Procedures and payloads ............... withheld (5.5)
Why the runner is not published: The tests run against LIQD’s real-money service, here its pre-launch environment. A published runner, with its payloads, would let anyone repeat them against LIQD, or against another operator built the same way; coverage and results are published instead.
06

Player Verification

Can a player verify their own bets without trusting anyone?

Every Limbo round can be independently reproduced using publicly disclosed inputs. No hidden variables, no private backend data. If the revealed seed matches the hash you saw before betting and your recomputed result matches the game — and gives the same win or loss against your target — the round was provably fair. This section walks you through the process — with LIQD’s own Verify calculator, with the independent ProvablyFair.org verifier, or with a few lines of code you run yourself.

Independent Result Verification

4 Stepsto verify any bet

Key principles

  • Every Limbo round can be independently reproduced
  • No hidden variables — no private backend data
  • If the revealed seed matches the hash you saw before betting and your recomputed result matches the game, the round was provably fair
  • LIQD’s own Verify calculator recomputes any round’s result from its seeds and nonce

What you need

  • Server Seed — revealed after seed rotation (casino entropy)
  • Client Seed — your player-controlled seed
  • Nonce — the bet number in sequence (ensures uniqueness)
  • Target — the multiplier you set, from 1.01× to 10,000×; the result must reach it
4 Stepsto verify any bet
  • Every Limbo round can be independently reproduced
  • No hidden variables — no private backend data
  • If the revealed seed matches the hash you saw before betting and your recomputed result matches the game, the round was provably fair
  • LIQD’s own Verify calculator recomputes any round’s result from its seeds and nonce
From Bet to Independent Verification — 4-step flow — illustrated flow
Server SeedRevealed after rotation
Client SeedPlayer-controlled input
NonceRecorded bet sequence
Target1.01×–10,000× · the result must reach it
  1. 1

    Place a Limbo Bet

    Set a target multiplier from 1.01× to 10,000× and play. The result is drawn from the seeds and the nonce alone — the target and the stake never enter the draw. The target decides the win: a result at or above it pays stake × target; below it, the bet is lost.

  2. 2

    Open the Provably Fair Panel and Rotate the Seed

    Open the Provably Fair panel on the Limbo game page. Its Seeds tab shows the commitment for your seed pair — the hashed server seed, published before you play — and the hash of the next server seed, fixed before you choose your next client seed. Rotating ends the epoch and reveals the outgoing server seed in plaintext; the client seed you submit with the rotation becomes the new active one. First check the commitment: the SHA-256 of the revealed seed’s hex text must equal the hash you saw before betting — across this audit’s 134 epochs it always did.

  3. 3

    Enter the Seeds in the Verify Tab

    Open the panel’s Verify tab — a calculator that recomputes a round’s result from a server seed, a client seed and a nonce — and enter the revealed server seed, your client seed, the round’s nonce and your target.

  4. 4

    Recompute & Confirm

    Press Verify and compare the calculator’s result with your round — or enter the same inputs in the ProvablyFair.org verifier. If the commitment check in step 2 passed and the result matches, with the same win or loss against your target, the round was provably fair. The scripts under Manual Verification also take your stake and check the payout to the last decimal.

Visual Walkthroughstep-by-step
6.1Step 1: Place a Limbo Bet

Set your target — any multiplier from 1.01× to 10,000× in steps of 0.01 — and play. The platform computes the result from (serverSeed, clientSeed, nonce) before your target is applied: the target decides whether the result wins and what a win pays, never what is drawn.

Target — the multiplier you set; the round wins when the result reaches it, and a win pays stake × target.

Result — the multiplier the round draws, never below 1×; it is the same whatever target or stake you choose.

Win chance — 99% ÷ target, rounded down to whole draws: 49.5% at 2×, 0.0099% at 10,000× — the figures LIQD’s game page states.

LIQD Limbo on liqd.com — a $1.00 bet at a 2.00× target (win chance 49.50%, payout on win $2.00); this round drew 17.07×, the one before it 1.98×.

LIQD Limbo on liqd.com — a $1.00 bet at a 2.00× target (win chance 49.50%, payout on win $2.00); this round drew 17.07×, the one before it 1.98×.

6.2Step 2: Open the Provably Fair Panel and Rotate the Seed

The panel has two tabs, Seeds and Verify. Before you play, the Seeds tab shows the commitment for your active seed pair and for the next one, with your client seed and the bets made with the pair; LIQD’s provably-fair API publishes the same values. Rotation ends the epoch: the outgoing server seed is revealed for verification, the pre-committed next seed becomes active, and the client seed you submit with the rotation takes effect. That advance commitment is the chain the audit’s Step 2 checks across all 133 links, plus the pre-capture link into epoch 0.

Active commitment — Active Server Seed (Hashed) on the Seeds tab, activeServerSeedHash in LIQD’s provably-fair API: the SHA-256 of the current epoch’s server seed; the plaintext is revealed when you rotate and must hash to this value.

Next commitment — Next Server Seed under Rotate Seed Pair, nextServerSeedHash in the API: published before you set the next client seed; it becomes the active commitment when you rotate.

Revealed server seed — returned by the rotation as revealedServerSeed, 32 hex characters; its SHA-256, taken over the hex text, must equal the commitment you saw before betting.

Client seed — your input for the new epoch, sent with the rotation. Set after the next commitment is published, an unpredictable client seed means the server seed was fixed before your seed could be known.

The Seeds tab before rotating — Active Server Seed (Hashed) 523d9aa4…c2eeb5 after 2 bets, and the next server seed’s hash already published. The seed revealed by the rotation, 39e1fead…136654, hashes to 523d9aa4…c2eeb5.

The Seeds tab before rotating — Active Server Seed (Hashed) 523d9aa4…c2eeb5 after 2 bets, and the next server seed’s hash already published. The seed revealed by the rotation, 39e1fead…136654, hashes to 523d9aa4…c2eeb5.

6.3Step 3: Enter the Seeds in the Verify Tab

The Verify tab recomputes a round on demand from a server seed, client seed and nonce, and with your target it shows whether the round won — the audit read four of its values, and our implementation reproduces all four. Enter the server seed revealed in step 2, your client seed, the round’s nonce and your target. Everything this audit checks cryptographically, the calculator and the scripts below let you check by hand.

Verify calculator — the Verify tab: recomputes a round’s result from a server seed, client seed and nonce; our implementation reproduces the four values the audit read from it.

Nonce — counts your bets under the pair from 0 and resets at rotation; it locates each round within the epoch.

The Verify tab filled in for that round — client seed, the server seed revealed in step 2, nonce 1 and the 2.00× target.

The Verify tab filled in for that round — client seed, the server seed revealed in step 2, nonce 1 and the 2.00× target.

6.4Step 4: Recompute & Confirm

Press Verify. The calculator recomputes the result from the three seed inputs, and your target decides the round — a result at or above it wins and pays stake × target. A match shows the result is the one the committed seed produces. The scripts in the Manual Verification section below take the target and the stake as well, so they check the draw, the win and the payout too — with nothing but Node.js or Python, pre-loaded with the first round of this capture and the expected output of four more.

Commitment — the SHA-256 of the revealed seed’s hex text; it must equal the hash you saw before betting.

Result — must equal your round’s result to two decimals, as the scripts print it.

Win & payout — a result at or above your target wins stake × target; below it the payout is 0.

The result: 17.07×, a win at the 2.00× target. Our scripts in 6.8–6.9 reproduce it exactly: draw 5,797,579, result 17.07, $2.00 paid on the $1.00 stake.

The result: 17.07×, a win at the 2.00× target. Our scripts in 6.8–6.9 reproduce it exactly: draw 5,797,579, result 17.07, $2.00 paid on the $1.00 stake.

Manual Verification (Advanced)6 sections
6.5Why Manual Verification Matters

Hosted verifiers — including ours — are conveniences. The guarantee only becomes unconditional when you can reproduce a round with tools nobody else controls. The algorithm below is small enough to audit by eye and run anywhere: if the revealed seed matches the hash you saw before betting and your recomputation matches the round you played, no one in the chain — casino, auditor, or tool host — changed it after you bet.

6.6How the Algorithm Works (Plain English)

Five moving parts, in the order they act:

  • Commit — before you play, LIQD publishes SHA-256 of the server seed, hashed as the 32-character hex string. The seed itself stays secret until rotation.
  • Draw — one call to HMAC-SHA256(key = decoded seed, message = clientSeed:nonce:0); the digest is read as eight 4-byte big-endian chunks, and the first chunk below 4,200,000,000 is taken modulo 100,000,000 — that is the draw k, 0 to 99,999,999. The guard discards the chunks at or above 4,200,000,000 (2.2111% of them), so every draw is exactly as likely as every other.
  • Result — max(1, floor(9,900,000,000 ÷ (k + 1)) ÷ 100), a multiplier with two decimals, never below 1×: the smaller the draw, the higher the result.
  • Win — the round wins if the result is at or above your target; a result exactly on the target wins.
  • Pay — a win credits stake × target, never the result; a loss pays 0. Both are two-decimal amounts, so the credit is exact.

Note the two hashing domains: the commitment hashes the hex string as UTF-8 text, while the HMAC key is the 16 decoded bytes. Mixing them up is the most common reason a hand-rolled verifier disagrees with the game — both scripts below get it right, and both list the expected output of five rounds from this report in their comments so you can self-check the tool before trusting it. The other classic slips are the guard — taking the first chunk without checking it changes the result of about one round in 45, all 126 such rounds among the 6,700 scored — and the floor: the lowest result is 1×, not 1.01×, and it loses against every target. LIQD’s game code names 1,000,000× as the maximum result — only draws 0–97 would go higher, and they win every target either way.

6.7Casino Verifier vs ProvablyFair.org Verifier4 records

Both should agree — the point of having an independent one is what happens if they ever don’t:

3 columns
LIQD Built-in (Verify calculator)ProvablyFair.org
Who controls the codeThe casinoProvablyFair.org — open source
What it proves aloneInternal consistency of LIQD’s own mathIndependent recomputation from raw inputs
Checked against this captureAs a cross-check — our implementation reproduces the 4 values recorded from its Verify calculator (a unit test) and both replies of its /verify endpoint at the boundary (Step 19); live, the endpoint agreed on every round the integrity tests checked against itThe scripts in 6.8–6.9 implement the derivation this audit specifies — the HMAC-SHA256 draw and its guard, the result map, the win rule and the payout — and reproduce all 6,700 scored rounds
If they disagreeSuspect the casino’s tool or a tampered roundThe repo + scripts are the tiebreaker anyone can run
6.8JavaScript Verification Script

Complete, dependency-free, and pre-loaded with the first round of the capture as its default inputs — run it as-is to see a full match, then swap in your own round. Its comments list the expected output of four more rounds from this report: the win exactly on its target, a result lifted to 1×, a round whose first chunk was discarded, and the capture’s highest result. Run on every one of the 6,700 scored rounds, it reproduces each one: commitment, draw, result, win and payout.

verify-limbo.js · Standalone Node.js
// verify-limbo.js — standalone LIQD Limbo verifier (Node.js, no dependencies)// Usage: node verify-limbo.js <serverSeed> <clientSeed> <nonce> <target> [stake]const { createHash, createHmac } = require('node:crypto'); const argv = process.argv.slice(2);const [serverSeed, clientSeed, nonce, targetArg, stakeArg = '0.10'] = argv.length  ? argv  : ['ee4724f00cfc46e28425e9de3239d427', 'audita5a8f43bd18d', '0', '4.58'];// Amounts as exact whole hundredths: '4.58' → 458nconst hundredths = s => {  const m = /^(\d+)(?:\.(\d{1,2}))?$/.exec(s);  if (!m) throw new Error(`not a 2-decimal amount: ${s}`);  return BigInt(m[1]) * 100n + BigInt((m[2] || '').padEnd(2, '0'));};const target = hundredths(targetArg), stake = hundredths(stakeArg);const fmt = h => `${h / 100n}.${String(h % 100n).padStart(2, '0')}`; // 1) Commitment: SHA-256 of the hex STRING (UTF-8) — the hash LIQD showed before you playedconsole.log('commitment =', createHash('sha256').update(serverSeed, 'utf8').digest('hex')); // 2) The draw — HMAC-SHA256 keyed on the DECODED bytes, message clientSeed:nonce:0; the first//    4-byte big-endian chunk below 4,200,000,000 (= 42 × 10^8), taken modulo 10^8const digest = createHmac('sha256', Buffer.from(serverSeed, 'hex'))  .update(`${clientSeed}:${nonce}:0`).digest();let chunk = 0;while (chunk < 8 && digest.readUInt32BE(4 * chunk) >= 4200000000) chunk++;if (chunk === 8) throw new Error('all eight chunks rejected (about 5.7e-14 per round)');const k = digest.readUInt32BE(4 * chunk) % 100000000;const skipped = chunk === 0 ? '' : chunk === 1 ? '; chunk 0 discarded'  : `; chunks 0-${chunk - 1} discarded`;console.log(`draw       = ${k} (chunk ${chunk}${skipped})`); // 3) Result in hundredths: floor(9,900,000,000 / (k + 1)), never below 1.00. (LIQD's game code//    names 1,000,000× as the maximum result — only draws 0–97 would go higher, and they win//    every target either way.)const raw = 9900000000n / BigInt(k + 1);const result = raw < 100n ? 100n : raw;console.log('result     =', fmt(result) + (raw < 100n ? ` (raw ${fmt(raw)}, lifted to 1.00)` : '')); // 4) Win iff result >= target; a win pays stake × target exactly://    credit × 10^8 = stake¢ × target¢ × 10^4const win = result >= target;const credit = win ? stake * target * 10000n : 0n;             // in units of 10^-8const payout = `${credit / 100000000n}.${String(credit % 100000000n).padStart(8, '0')}`;const verdict = `${win} (${fmt(result)} ${win ? '>=' : '<'} ${fmt(target)})`;console.log(`win        = ${verdict} | payout = ${payout}`);// Expected, defaults (bet USzRL9gyMBxZg8NLCNoYS, epoch 0 nonce 0, 4.58× at $0.10 — a loss)://   commitment = 394004d9c91573202b92489b53a5e8eff34ac5b3d34dac76c7379cadd0f34ded//   draw       = 40058996 (chunk 0)//   result     = 2.47//   win        = false (2.47 < 4.58) | payout = 0.00000000// Expected, bet bYS2viIeJOfIRm1YpweuJ (epoch 17 nonce 32 — exactly on its target, a win)://   node verify-limbo.js 441045a40a9fdbea912bb7c77afb1d2e audite7134d0080bc 32 1.16 0.10//   commitment = e9fe937d57fa02b85c8a610297ea3caaba8decc6383d5c07e2e303067e822126//   draw       = 84760019 (chunk 0)//   result     = 1.16//   win        = true (1.16 >= 1.16) | payout = 0.11600000// Expected, bet yAVLBL0QWD0RVuewVxG_v (epoch 3 nonce 29 — lifted to the 1× floor)://   node verify-limbo.js 56481f25aad5d0b6644fbfff05d4e468 audit8b0f66ee7a38 29 3.76 0.10//   commitment = ecf1e700cd3852d869c2e44a5794e854aff5cdf50af7783a25196528d197e653//   draw       = 99762668 (chunk 0)//   result     = 1.00 (raw 0.99, lifted to 1.00)//   win        = false (1.00 < 3.76) | payout = 0.00000000// Expected, bet krv88h79b6gDGRTDgswi9 (epoch 5 nonce 2 — first chunk discarded)://   node verify-limbo.js b73582d05dec4dd4a43b4e0b72ec3ebd audited5df347a318 2 1.59 0.10//   commitment = 7d57ba291003c8aa316f52b6b94d6b0100d5ff1b830ee1c3c6ba677f7c9a3033//   draw       = 66691774 (chunk 1; chunk 0 discarded)//   result     = 1.48//   win        = false (1.48 < 1.59) | payout = 0.00000000// Expected, bet S4mFs74Bt-nTtdNWQDxk5 (epoch 34 nonce 29 — the capture’s highest result)://   node verify-limbo.js 06dd164fe01b803ae5a843b4b3fc4bbb audit02b3dbac08bb 29 1.17 0.10//   commitment = 7375ad4e0375ba1eca0bb0a97ce8b31dfcbf25cef3840e9d3594a00967ffa1ae//   draw       = 534 (chunk 0)//   result     = 185046.72//   win        = true (185046.72 >= 1.17) | payout = 0.11700000
6.9Python Verification Script

The same verifier in Python 3, standard library only, with exact integer arithmetic throughout — so it doubles as an independent check on the JavaScript version. It reproduces all 6,700 scored rounds as well:

verify-limbo.py · Standalone Python
# verify-limbo.py — standalone LIQD Limbo verifier (Python 3, standard library only)# Usage: python3 verify-limbo.py <serverSeed> <clientSeed> <nonce> <target> [stake]import hashlib, hmac, struct, sysfrom fractions import Fraction args = sys.argv[1:] or ['ee4724f00cfc46e28425e9de3239d427', 'audita5a8f43bd18d', '0', '4.58']server_seed, client_seed, nonce = args[0], args[1], int(args[2]) def hundredths(s):                              # amounts as exact whole hundredths: '4.58' -> 458    h = Fraction(s) * 100    if h.denominator != 1:        raise ValueError(f'not a 2-decimal amount: {s}')    return h.numerator target, stake = hundredths(args[3]), hundredths(args[4] if len(args) > 4 else '0.10')fmt = lambda h: f'{h // 100}.{h % 100:02d}' # 1) Commitment — SHA-256 of the hex string as UTF-8 textprint('commitment =', hashlib.sha256(server_seed.encode('utf-8')).hexdigest()) # 2) The draw — HMAC-SHA256 keyed on the 16 decoded bytes, message clientSeed:nonce:0; the first#    4-byte big-endian chunk below 4,200,000,000 (= 42 × 10^8), taken modulo 10^8message = f'{client_seed}:{nonce}:0'.encode()digest = hmac.new(bytes.fromhex(server_seed), message, hashlib.sha256).digest()for chunk, value in enumerate(struct.unpack('>8I', digest)):    if value < 4200000000:        k = value % 100000000        breakelse:    raise ValueError('all eight chunks rejected (about 5.7e-14 per round)')skipped = ('' if chunk == 0 else '; chunk 0 discarded' if chunk == 1           else f'; chunks 0-{chunk - 1} discarded')print(f'draw       = {k} (chunk {chunk}{skipped})') # 3) Result in hundredths: floor(9,900,000,000 / (k + 1)), never below 1.00raw = 9900000000 // (k + 1)result = max(100, raw)print('result     =', fmt(result) + (f' (raw {fmt(raw)}, lifted to 1.00)' if raw < 100 else '')) # 4) Win iff result >= target; a win pays stake × target exactly:#    credit × 10^8 = stake¢ × target¢ × 10^4win = result >= targetcredit = stake * target * 10**4 if win else 0   # in units of 10^-8payout = f'{credit // 10**8}.{credit % 10**8:08d}'verdict = f"{'true' if win else 'false'} ({fmt(result)} {'>=' if win else '<'} {fmt(target)})"print(f'win        = {verdict} | payout = {payout}')# Expected, defaults (bet USzRL9gyMBxZg8NLCNoYS, epoch 0 nonce 0, 4.58× at $0.10 — a loss):#   commitment = 394004d9c91573202b92489b53a5e8eff34ac5b3d34dac76c7379cadd0f34ded#   draw       = 40058996 (chunk 0)#   result     = 2.47#   win        = false (2.47 < 4.58) | payout = 0.00000000# Expected, bet bYS2viIeJOfIRm1YpweuJ (epoch 17 nonce 32 — exactly on its target, a win):#   python3 verify-limbo.py 441045a40a9fdbea912bb7c77afb1d2e audite7134d0080bc 32 1.16 0.10#   commitment = e9fe937d57fa02b85c8a610297ea3caaba8decc6383d5c07e2e303067e822126#   draw       = 84760019 (chunk 0)#   result     = 1.16#   win        = true (1.16 >= 1.16) | payout = 0.11600000# Expected, bet yAVLBL0QWD0RVuewVxG_v (epoch 3 nonce 29 — lifted to the 1× floor):#   python3 verify-limbo.py 56481f25aad5d0b6644fbfff05d4e468 audit8b0f66ee7a38 29 3.76 0.10#   commitment = ecf1e700cd3852d869c2e44a5794e854aff5cdf50af7783a25196528d197e653#   draw       = 99762668 (chunk 0)#   result     = 1.00 (raw 0.99, lifted to 1.00)#   win        = false (1.00 < 3.76) | payout = 0.00000000# Expected, bet krv88h79b6gDGRTDgswi9 (epoch 5 nonce 2 — first chunk discarded):#   python3 verify-limbo.py b73582d05dec4dd4a43b4e0b72ec3ebd audited5df347a318 2 1.59 0.10#   commitment = 7d57ba291003c8aa316f52b6b94d6b0100d5ff1b830ee1c3c6ba677f7c9a3033#   draw       = 66691774 (chunk 1; chunk 0 discarded)#   result     = 1.48#   win        = false (1.48 < 1.59) | payout = 0.00000000# Expected, bet S4mFs74Bt-nTtdNWQDxk5 (epoch 34 nonce 29 — the capture’s highest result):#   python3 verify-limbo.py 06dd164fe01b803ae5a843b4b3fc4bbb audit02b3dbac08bb 29 1.17 0.10#   commitment = 7375ad4e0375ba1eca0bb0a97ce8b31dfcbf25cef3840e9d3594a00967ffa1ae#   draw       = 534 (chunk 0)#   result     = 185046.72#   win        = true (185046.72 >= 1.17) | payout = 0.11700000
6.10Evidence Records6 records

The walkthrough’s screenshots are page captures from liqd.com, outside the package’s evidence set.

2 columns
FileShows
E13-limbo-boundary-probes.jsonE13 · Two replies of LIQD’s /verify endpoint on an already-revealed seed whose result is 7.5×: on a 7.5× target a win at 7.5×, on 7.51× a loss — the casino-verifier rows in 6.7 and the verdict table (Step 19)
E15-limbo-max-exposure.jsonE15 · LIQD’s bet-time refusal of $0.10 at 1,000,000× (MaxExposureExceededError, maximum stake 0.05) — the $50,000 exposure ceiling (4.7, Step 17)
E20-target-ceiling-probe-2026-09-26/liqd-limbo-capture-v2-probe-2026-09-26T17-43-21-558Z.journal.ndjsonE20 · The 26 September probe: a $0.10 bet at exactly 10,000× settled; the next request, above 10,000×, was not settled (4.7, Step 25)
E20-target-ceiling-probe-2026-09-26/liqd-limbo-capture-v2-probe-2026-09-26T17-47-53-359Z.journal.ndjsonE20 · The probe’s nonce check: the active seed read back at nonce 1, so the request above 10,000× consumed no nonce; further bets, one at 10,000×, settled (Step 25)
E21-target-cap-probe/liqd-limbo-cap-probe-2026-10-02T18-22-46-633Z.journal.ndjsonE21 · The 2 October cap probe: a $0.10 control at 10,000× settled, and 100,101.12×, 497,487.44×, 500,000× and 990,000.01× were each refused on the target rule with no nonce consumed — the certified range (4.7, Step 25)
rtp-page-labels-2026-10-01.jsonE23 · The RTP and house-edge figures on LIQD’s Limbo game page, read logged out on 1 October 2026 — the source of the RTP claim (S4.2)
From Bet to Independent Verification — 4-step flow — illustrated flow
How it works
  1. 1

    Place a Limbo Bet

    Set a target multiplier from 1.01× to 10,000× and play. The result is drawn from the seeds and the nonce alone — the target and the stake never enter the draw. The target decides the win: a result at or above it pays stake × target; below it, the bet is lost.

  2. 2

    Open the Provably Fair Panel and Rotate the Seed

    Open the Provably Fair panel on the Limbo game page. Its Seeds tab shows the commitment for your seed pair — the hashed server seed, published before you play — and the hash of the next server seed, fixed before you choose your next client seed. Rotating ends the epoch and reveals the outgoing server seed in plaintext; the client seed you submit with the rotation becomes the new active one. First check the commitment: the SHA-256 of the revealed seed’s hex text must equal the hash you saw before betting — across this audit’s 134 epochs it always did.

  3. 3

    Enter the Seeds in the Verify Tab

    Open the panel’s Verify tab — a calculator that recomputes a round’s result from a server seed, a client seed and a nonce — and enter the revealed server seed, your client seed, the round’s nonce and your target.

  4. 4

    Recompute & Confirm

    Press Verify and compare the calculator’s result with your round — or enter the same inputs in the ProvablyFair.org verifier. If the commitment check in step 2 passed and the result matches, with the same win or loss against your target, the round was provably fair. The scripts under Manual Verification also take your stake and check the payout to the last decimal.

Visual Walkthroughstep-by-step
6.1Step 1: Place a Limbo Bet

Set your target — any multiplier from 1.01× to 10,000× in steps of 0.01 — and play. The platform computes the result from (serverSeed, clientSeed, nonce) before your target is applied: the target decides whether the result wins and what a win pays, never what is drawn.

Target — the multiplier you set; the round wins when the result reaches it, and a win pays stake × target.

Result — the multiplier the round draws, never below 1×; it is the same whatever target or stake you choose.

Win chance — 99% ÷ target, rounded down to whole draws: 49.5% at 2×, 0.0099% at 10,000× — the figures LIQD’s game page states.

LIQD Limbo on liqd.com — a $1.00 bet at a 2.00× target (win chance 49.50%, payout on win $2.00); this round drew 17.07×, the one before it 1.98×.

LIQD Limbo on liqd.com — a $1.00 bet at a 2.00× target (win chance 49.50%, payout on win $2.00); this round drew 17.07×, the one before it 1.98×.

6.2Step 2: Open the Provably Fair Panel and Rotate the Seed

The panel has two tabs, Seeds and Verify. Before you play, the Seeds tab shows the commitment for your active seed pair and for the next one, with your client seed and the bets made with the pair; LIQD’s provably-fair API publishes the same values. Rotation ends the epoch: the outgoing server seed is revealed for verification, the pre-committed next seed becomes active, and the client seed you submit with the rotation takes effect. That advance commitment is the chain the audit’s Step 2 checks across all 133 links, plus the pre-capture link into epoch 0.

Active commitment — Active Server Seed (Hashed) on the Seeds tab, activeServerSeedHash in LIQD’s provably-fair API: the SHA-256 of the current epoch’s server seed; the plaintext is revealed when you rotate and must hash to this value.

Next commitment — Next Server Seed under Rotate Seed Pair, nextServerSeedHash in the API: published before you set the next client seed; it becomes the active commitment when you rotate.

Revealed server seed — returned by the rotation as revealedServerSeed, 32 hex characters; its SHA-256, taken over the hex text, must equal the commitment you saw before betting.

Client seed — your input for the new epoch, sent with the rotation. Set after the next commitment is published, an unpredictable client seed means the server seed was fixed before your seed could be known.

The Seeds tab before rotating — Active Server Seed (Hashed) 523d9aa4…c2eeb5 after 2 bets, and the next server seed’s hash already published. The seed revealed by the rotation, 39e1fead…136654, hashes to 523d9aa4…c2eeb5.

The Seeds tab before rotating — Active Server Seed (Hashed) 523d9aa4…c2eeb5 after 2 bets, and the next server seed’s hash already published. The seed revealed by the rotation, 39e1fead…136654, hashes to 523d9aa4…c2eeb5.

6.3Step 3: Enter the Seeds in the Verify Tab

The Verify tab recomputes a round on demand from a server seed, client seed and nonce, and with your target it shows whether the round won — the audit read four of its values, and our implementation reproduces all four. Enter the server seed revealed in step 2, your client seed, the round’s nonce and your target. Everything this audit checks cryptographically, the calculator and the scripts below let you check by hand.

Verify calculator — the Verify tab: recomputes a round’s result from a server seed, client seed and nonce; our implementation reproduces the four values the audit read from it.

Nonce — counts your bets under the pair from 0 and resets at rotation; it locates each round within the epoch.

The Verify tab filled in for that round — client seed, the server seed revealed in step 2, nonce 1 and the 2.00× target.

The Verify tab filled in for that round — client seed, the server seed revealed in step 2, nonce 1 and the 2.00× target.

6.4Step 4: Recompute & Confirm

Press Verify. The calculator recomputes the result from the three seed inputs, and your target decides the round — a result at or above it wins and pays stake × target. A match shows the result is the one the committed seed produces. The scripts in the Manual Verification section below take the target and the stake as well, so they check the draw, the win and the payout too — with nothing but Node.js or Python, pre-loaded with the first round of this capture and the expected output of four more.

Commitment — the SHA-256 of the revealed seed’s hex text; it must equal the hash you saw before betting.

Result — must equal your round’s result to two decimals, as the scripts print it.

Win & payout — a result at or above your target wins stake × target; below it the payout is 0.

The result: 17.07×, a win at the 2.00× target. Our scripts in 6.8–6.9 reproduce it exactly: draw 5,797,579, result 17.07, $2.00 paid on the $1.00 stake.

The result: 17.07×, a win at the 2.00× target. Our scripts in 6.8–6.9 reproduce it exactly: draw 5,797,579, result 17.07, $2.00 paid on the $1.00 stake.

Technical evidence
6.5Why Manual Verification Matters

Hosted verifiers — including ours — are conveniences. The guarantee only becomes unconditional when you can reproduce a round with tools nobody else controls. The algorithm below is small enough to audit by eye and run anywhere: if the revealed seed matches the hash you saw before betting and your recomputation matches the round you played, no one in the chain — casino, auditor, or tool host — changed it after you bet.

6.6How the Algorithm Works (Plain English)

Five moving parts, in the order they act:

  • Commit — before you play, LIQD publishes SHA-256 of the server seed, hashed as the 32-character hex string. The seed itself stays secret until rotation.
  • Draw — one call to HMAC-SHA256(key = decoded seed, message = clientSeed:nonce:0); the digest is read as eight 4-byte big-endian chunks, and the first chunk below 4,200,000,000 is taken modulo 100,000,000 — that is the draw k, 0 to 99,999,999. The guard discards the chunks at or above 4,200,000,000 (2.2111% of them), so every draw is exactly as likely as every other.
  • Result — max(1, floor(9,900,000,000 ÷ (k + 1)) ÷ 100), a multiplier with two decimals, never below 1×: the smaller the draw, the higher the result.
  • Win — the round wins if the result is at or above your target; a result exactly on the target wins.
  • Pay — a win credits stake × target, never the result; a loss pays 0. Both are two-decimal amounts, so the credit is exact.

Note the two hashing domains: the commitment hashes the hex string as UTF-8 text, while the HMAC key is the 16 decoded bytes. Mixing them up is the most common reason a hand-rolled verifier disagrees with the game — both scripts below get it right, and both list the expected output of five rounds from this report in their comments so you can self-check the tool before trusting it. The other classic slips are the guard — taking the first chunk without checking it changes the result of about one round in 45, all 126 such rounds among the 6,700 scored — and the floor: the lowest result is 1×, not 1.01×, and it loses against every target. LIQD’s game code names 1,000,000× as the maximum result — only draws 0–97 would go higher, and they win every target either way.

6.7Casino Verifier vs ProvablyFair.org Verifier4 records

Both should agree — the point of having an independent one is what happens if they ever don’t:

3 columns
LIQD Built-in (Verify calculator)ProvablyFair.org
Who controls the codeThe casinoProvablyFair.org — open source
What it proves aloneInternal consistency of LIQD’s own mathIndependent recomputation from raw inputs
Checked against this captureAs a cross-check — our implementation reproduces the 4 values recorded from its Verify calculator (a unit test) and both replies of its /verify endpoint at the boundary (Step 19); live, the endpoint agreed on every round the integrity tests checked against itThe scripts in 6.8–6.9 implement the derivation this audit specifies — the HMAC-SHA256 draw and its guard, the result map, the win rule and the payout — and reproduce all 6,700 scored rounds
If they disagreeSuspect the casino’s tool or a tampered roundThe repo + scripts are the tiebreaker anyone can run
6.8JavaScript Verification Script

Complete, dependency-free, and pre-loaded with the first round of the capture as its default inputs — run it as-is to see a full match, then swap in your own round. Its comments list the expected output of four more rounds from this report: the win exactly on its target, a result lifted to 1×, a round whose first chunk was discarded, and the capture’s highest result. Run on every one of the 6,700 scored rounds, it reproduces each one: commitment, draw, result, win and payout.

verify-limbo.js · Standalone Node.js
// verify-limbo.js — standalone LIQD Limbo verifier (Node.js, no dependencies)// Usage: node verify-limbo.js <serverSeed> <clientSeed> <nonce> <target> [stake]const { createHash, createHmac } = require('node:crypto'); const argv = process.argv.slice(2);const [serverSeed, clientSeed, nonce, targetArg, stakeArg = '0.10'] = argv.length  ? argv  : ['ee4724f00cfc46e28425e9de3239d427', 'audita5a8f43bd18d', '0', '4.58'];// Amounts as exact whole hundredths: '4.58' → 458nconst hundredths = s => {  const m = /^(\d+)(?:\.(\d{1,2}))?$/.exec(s);  if (!m) throw new Error(`not a 2-decimal amount: ${s}`);  return BigInt(m[1]) * 100n + BigInt((m[2] || '').padEnd(2, '0'));};const target = hundredths(targetArg), stake = hundredths(stakeArg);const fmt = h => `${h / 100n}.${String(h % 100n).padStart(2, '0')}`; // 1) Commitment: SHA-256 of the hex STRING (UTF-8) — the hash LIQD showed before you playedconsole.log('commitment =', createHash('sha256').update(serverSeed, 'utf8').digest('hex')); // 2) The draw — HMAC-SHA256 keyed on the DECODED bytes, message clientSeed:nonce:0; the first//    4-byte big-endian chunk below 4,200,000,000 (= 42 × 10^8), taken modulo 10^8const digest = createHmac('sha256', Buffer.from(serverSeed, 'hex'))  .update(`${clientSeed}:${nonce}:0`).digest();let chunk = 0;while (chunk < 8 && digest.readUInt32BE(4 * chunk) >= 4200000000) chunk++;if (chunk === 8) throw new Error('all eight chunks rejected (about 5.7e-14 per round)');const k = digest.readUInt32BE(4 * chunk) % 100000000;const skipped = chunk === 0 ? '' : chunk === 1 ? '; chunk 0 discarded'  : `; chunks 0-${chunk - 1} discarded`;console.log(`draw       = ${k} (chunk ${chunk}${skipped})`); // 3) Result in hundredths: floor(9,900,000,000 / (k + 1)), never below 1.00. (LIQD's game code//    names 1,000,000× as the maximum result — only draws 0–97 would go higher, and they win//    every target either way.)const raw = 9900000000n / BigInt(k + 1);const result = raw < 100n ? 100n : raw;console.log('result     =', fmt(result) + (raw < 100n ? ` (raw ${fmt(raw)}, lifted to 1.00)` : '')); // 4) Win iff result >= target; a win pays stake × target exactly://    credit × 10^8 = stake¢ × target¢ × 10^4const win = result >= target;const credit = win ? stake * target * 10000n : 0n;             // in units of 10^-8const payout = `${credit / 100000000n}.${String(credit % 100000000n).padStart(8, '0')}`;const verdict = `${win} (${fmt(result)} ${win ? '>=' : '<'} ${fmt(target)})`;console.log(`win        = ${verdict} | payout = ${payout}`);// Expected, defaults (bet USzRL9gyMBxZg8NLCNoYS, epoch 0 nonce 0, 4.58× at $0.10 — a loss)://   commitment = 394004d9c91573202b92489b53a5e8eff34ac5b3d34dac76c7379cadd0f34ded//   draw       = 40058996 (chunk 0)//   result     = 2.47//   win        = false (2.47 < 4.58) | payout = 0.00000000// Expected, bet bYS2viIeJOfIRm1YpweuJ (epoch 17 nonce 32 — exactly on its target, a win)://   node verify-limbo.js 441045a40a9fdbea912bb7c77afb1d2e audite7134d0080bc 32 1.16 0.10//   commitment = e9fe937d57fa02b85c8a610297ea3caaba8decc6383d5c07e2e303067e822126//   draw       = 84760019 (chunk 0)//   result     = 1.16//   win        = true (1.16 >= 1.16) | payout = 0.11600000// Expected, bet yAVLBL0QWD0RVuewVxG_v (epoch 3 nonce 29 — lifted to the 1× floor)://   node verify-limbo.js 56481f25aad5d0b6644fbfff05d4e468 audit8b0f66ee7a38 29 3.76 0.10//   commitment = ecf1e700cd3852d869c2e44a5794e854aff5cdf50af7783a25196528d197e653//   draw       = 99762668 (chunk 0)//   result     = 1.00 (raw 0.99, lifted to 1.00)//   win        = false (1.00 < 3.76) | payout = 0.00000000// Expected, bet krv88h79b6gDGRTDgswi9 (epoch 5 nonce 2 — first chunk discarded)://   node verify-limbo.js b73582d05dec4dd4a43b4e0b72ec3ebd audited5df347a318 2 1.59 0.10//   commitment = 7d57ba291003c8aa316f52b6b94d6b0100d5ff1b830ee1c3c6ba677f7c9a3033//   draw       = 66691774 (chunk 1; chunk 0 discarded)//   result     = 1.48//   win        = false (1.48 < 1.59) | payout = 0.00000000// Expected, bet S4mFs74Bt-nTtdNWQDxk5 (epoch 34 nonce 29 — the capture’s highest result)://   node verify-limbo.js 06dd164fe01b803ae5a843b4b3fc4bbb audit02b3dbac08bb 29 1.17 0.10//   commitment = 7375ad4e0375ba1eca0bb0a97ce8b31dfcbf25cef3840e9d3594a00967ffa1ae//   draw       = 534 (chunk 0)//   result     = 185046.72//   win        = true (185046.72 >= 1.17) | payout = 0.11700000
6.9Python Verification Script

The same verifier in Python 3, standard library only, with exact integer arithmetic throughout — so it doubles as an independent check on the JavaScript version. It reproduces all 6,700 scored rounds as well:

verify-limbo.py · Standalone Python
# verify-limbo.py — standalone LIQD Limbo verifier (Python 3, standard library only)# Usage: python3 verify-limbo.py <serverSeed> <clientSeed> <nonce> <target> [stake]import hashlib, hmac, struct, sysfrom fractions import Fraction args = sys.argv[1:] or ['ee4724f00cfc46e28425e9de3239d427', 'audita5a8f43bd18d', '0', '4.58']server_seed, client_seed, nonce = args[0], args[1], int(args[2]) def hundredths(s):                              # amounts as exact whole hundredths: '4.58' -> 458    h = Fraction(s) * 100    if h.denominator != 1:        raise ValueError(f'not a 2-decimal amount: {s}')    return h.numerator target, stake = hundredths(args[3]), hundredths(args[4] if len(args) > 4 else '0.10')fmt = lambda h: f'{h // 100}.{h % 100:02d}' # 1) Commitment — SHA-256 of the hex string as UTF-8 textprint('commitment =', hashlib.sha256(server_seed.encode('utf-8')).hexdigest()) # 2) The draw — HMAC-SHA256 keyed on the 16 decoded bytes, message clientSeed:nonce:0; the first#    4-byte big-endian chunk below 4,200,000,000 (= 42 × 10^8), taken modulo 10^8message = f'{client_seed}:{nonce}:0'.encode()digest = hmac.new(bytes.fromhex(server_seed), message, hashlib.sha256).digest()for chunk, value in enumerate(struct.unpack('>8I', digest)):    if value < 4200000000:        k = value % 100000000        breakelse:    raise ValueError('all eight chunks rejected (about 5.7e-14 per round)')skipped = ('' if chunk == 0 else '; chunk 0 discarded' if chunk == 1           else f'; chunks 0-{chunk - 1} discarded')print(f'draw       = {k} (chunk {chunk}{skipped})') # 3) Result in hundredths: floor(9,900,000,000 / (k + 1)), never below 1.00raw = 9900000000 // (k + 1)result = max(100, raw)print('result     =', fmt(result) + (f' (raw {fmt(raw)}, lifted to 1.00)' if raw < 100 else '')) # 4) Win iff result >= target; a win pays stake × target exactly:#    credit × 10^8 = stake¢ × target¢ × 10^4win = result >= targetcredit = stake * target * 10**4 if win else 0   # in units of 10^-8payout = f'{credit // 10**8}.{credit % 10**8:08d}'verdict = f"{'true' if win else 'false'} ({fmt(result)} {'>=' if win else '<'} {fmt(target)})"print(f'win        = {verdict} | payout = {payout}')# Expected, defaults (bet USzRL9gyMBxZg8NLCNoYS, epoch 0 nonce 0, 4.58× at $0.10 — a loss):#   commitment = 394004d9c91573202b92489b53a5e8eff34ac5b3d34dac76c7379cadd0f34ded#   draw       = 40058996 (chunk 0)#   result     = 2.47#   win        = false (2.47 < 4.58) | payout = 0.00000000# Expected, bet bYS2viIeJOfIRm1YpweuJ (epoch 17 nonce 32 — exactly on its target, a win):#   python3 verify-limbo.py 441045a40a9fdbea912bb7c77afb1d2e audite7134d0080bc 32 1.16 0.10#   commitment = e9fe937d57fa02b85c8a610297ea3caaba8decc6383d5c07e2e303067e822126#   draw       = 84760019 (chunk 0)#   result     = 1.16#   win        = true (1.16 >= 1.16) | payout = 0.11600000# Expected, bet yAVLBL0QWD0RVuewVxG_v (epoch 3 nonce 29 — lifted to the 1× floor):#   python3 verify-limbo.py 56481f25aad5d0b6644fbfff05d4e468 audit8b0f66ee7a38 29 3.76 0.10#   commitment = ecf1e700cd3852d869c2e44a5794e854aff5cdf50af7783a25196528d197e653#   draw       = 99762668 (chunk 0)#   result     = 1.00 (raw 0.99, lifted to 1.00)#   win        = false (1.00 < 3.76) | payout = 0.00000000# Expected, bet krv88h79b6gDGRTDgswi9 (epoch 5 nonce 2 — first chunk discarded):#   python3 verify-limbo.py b73582d05dec4dd4a43b4e0b72ec3ebd audited5df347a318 2 1.59 0.10#   commitment = 7d57ba291003c8aa316f52b6b94d6b0100d5ff1b830ee1c3c6ba677f7c9a3033#   draw       = 66691774 (chunk 1; chunk 0 discarded)#   result     = 1.48#   win        = false (1.48 < 1.59) | payout = 0.00000000# Expected, bet S4mFs74Bt-nTtdNWQDxk5 (epoch 34 nonce 29 — the capture’s highest result):#   python3 verify-limbo.py 06dd164fe01b803ae5a843b4b3fc4bbb audit02b3dbac08bb 29 1.17 0.10#   commitment = 7375ad4e0375ba1eca0bb0a97ce8b31dfcbf25cef3840e9d3594a00967ffa1ae#   draw       = 534 (chunk 0)#   result     = 185046.72#   win        = true (185046.72 >= 1.17) | payout = 0.11700000
6.10Evidence Records6 records

The walkthrough’s screenshots are page captures from liqd.com, outside the package’s evidence set.

2 columns
FileShows
E13-limbo-boundary-probes.jsonE13 · Two replies of LIQD’s /verify endpoint on an already-revealed seed whose result is 7.5×: on a 7.5× target a win at 7.5×, on 7.51× a loss — the casino-verifier rows in 6.7 and the verdict table (Step 19)
E15-limbo-max-exposure.jsonE15 · LIQD’s bet-time refusal of $0.10 at 1,000,000× (MaxExposureExceededError, maximum stake 0.05) — the $50,000 exposure ceiling (4.7, Step 17)
E20-target-ceiling-probe-2026-09-26/liqd-limbo-capture-v2-probe-2026-09-26T17-43-21-558Z.journal.ndjsonE20 · The 26 September probe: a $0.10 bet at exactly 10,000× settled; the next request, above 10,000×, was not settled (4.7, Step 25)
E20-target-ceiling-probe-2026-09-26/liqd-limbo-capture-v2-probe-2026-09-26T17-47-53-359Z.journal.ndjsonE20 · The probe’s nonce check: the active seed read back at nonce 1, so the request above 10,000× consumed no nonce; further bets, one at 10,000×, settled (Step 25)
E21-target-cap-probe/liqd-limbo-cap-probe-2026-10-02T18-22-46-633Z.journal.ndjsonE21 · The 2 October cap probe: a $0.10 control at 10,000× settled, and 100,101.12×, 497,487.44×, 500,000× and 990,000.01× were each refused on the target rule with no nonce consumed — the certified range (4.7, Step 25)
rtp-page-labels-2026-10-01.jsonE23 · The RTP and house-edge figures on LIQD’s Limbo game page, read logged out on 1 October 2026 — the source of the RTP claim (S4.2)
✓  Any Player Can Reproduce Limbo Results

Only disclosed inputs are used. Identical inputs always produce identical output.

07

Reproducibility & Artifacts

Can anyone independently reproduce every scored check in this audit?

This section consolidates the open-source repository, datasets, output artifacts, and reproducibility posture of the audit. Every scored step, every statistic and every pass/fail result of the audit package can be independently reproduced by anyone with a computer and an internet connection. The repository is the credential — not this report.

Open-Source Audit Repository

v1.0.1release audited

Repository Details

Prerequisites

  • Node.js 22.x (package.json engines and .nvmrc) — no dependency install for npm test or node test.js && node verify.js
  • Git
  • TypeScript toolchain via npm ci (TypeScript 5.9.3 · ts-node 10.9.2 · Mocha 11.8.0) — for npm run verify, npm run test:full and the source commands
  • Python 3 — only for the optional standalone checker in S6
v1.0.1release audited
github.com/ProvablyFair-org/liqd-limbo

Commands to Reproduce

The 6,700-round capture, the 26 September supplement and its journal, the evidence files and the committed results ship inside the repo — no separate download, and every pinned file is checked against its SHA-256, the capture before any step runs. npm test is zero-install: it runs the staleness gate, the release-manifest check, the 27 unit tests and the 25-step verification, and exits with the verdict’s code; npm ci adds the TypeScript tools for the source commands. Node.js 22.x is required. Output as run for this report on Node 22.22.0 in 37 s, abridged where marked “…”.

sh
git clone https://github.com/ProvablyFair-org/liqd-limbo.gitcd liqd-limbogit checkout v1.0.1npm ci # Run the entire audit end-to-end (staleness gate + release-manifest check + 27 unit tests + 25-step verification)npm test # Or the two bundles directly, with nothing but Node.jsnode test.js && node verify.js
Expected output
…[standalone] Source inventory, unit-test inventory and both compiled entry points match the build manifest.  [release-manifest] OK: release bar met; 8 artifact hashes match disk, verdict "PROVABLY FAIR — Full Pass", scored 25/25…# tests 27…# pass 27# fail 0…  Passed:     25/25  Hard fails: 0…  VERDICT: PROVABLY FAIR — Full Pass…  Reproduction vs the committed artifact on Node 22.22.0: REPRODUCED

Audit Reproducibility Pinning

Framework
ProvablyFair.org Audit Framework 1.1 · Limbo profile 1.0.2 · RTP-CLAIM 1.2.0
Release Signature
Release commit and tag signed with the ProvablyFair.org release key (SSH, SHA256:gEujiJm2pRyq7lQsfYQfQ1r97H/J7GdaKHbOW8GtikY), recorded in RELEASE-MANIFEST.json
Node Version
22.x (package.json engines, .nvmrc 22) — release runs on 22.23.1, this report’s runs on 22.22.0
Primary Dataset
data/limbo-master-6700bets.json (6,700 bets, 134 seed epochs)
Primary Dataset Hash (SHA-256)
8e3ce5b5e1494d1fcc3a783965d3ba7b6a7099f120a64d61e21a577098c07334
Supplement Dataset Hash (SHA-256)
a2dee374cfd48c62736cf5958fd67f42b1cb7c622ab0883c3f84278d8429ab1e
Supplement Journal Hash (SHA-256)
bcacab9c674df62a3b2219cba008542f2e798e9c81559c6321489e1a43949c32
Target-Cap Probe Journal Hash (SHA-256)
14f9880eff8294a5a7d8b0ce3fe715093eda5061899713ff87d3c9c098719809
Simulation Artifact Hash (SHA-256)
2d609344650eac723c262449fb8929b05c68b75f149ceee708b89ad4ade670cc
Convergence Chart Hash (SHA-256)
efd121156f8255f391eb9c1b3c6f9f06c008d35807cef9a69b1e82be33fd0d69
Pass-1 Grid Hash (SHA-256)
9999f98ba17fe534b5350274da8e423aa6c1c096f4e136d40daec60a0cbdcae7
RTP Claim Table Hash (SHA-256)
cdfbf600e0713b822acf00b420c10d12b7af7fca73cce96c395099f49634ba34
Reproduction Gate
Every run writes only to outputs/run/ and is compared with the committed results (outputs/run/diff.json) — exact content byte for byte, a derived floating-point statistic within 1e-14 absolute + 1e-12 relative; anything beyond tolerance exits 1
Exit Codes
0 Full Pass · 2 Conditional Pass · 5 RTP FIGURE OVERSTATED · 3 suite-integrity failure · 1 any FAIL, a reproduction beyond tolerance or an RTP claim table mismatch
Mutation Battery
117 entries — 75 single-file mutations, 41 structural forgeries and 1 declared survivor: 116 caught; the survivor is an exported helper nothing calls
Runtime
npm test ≈ 37 s · npm run verify ≈ 43 s · node test.js ≈ 0.7 s · npm run test:full ≈ 6½ min (this report’s runs, Node 22.22.0) · npm run mutate ≈ 27 min (as the release’s README records it)
Capture Environment
Pre-launch host qa.liqd.com · the 6,700 scored rounds from one authenticated account in one session · USDC
Audit Date
September–October 2026 — scored capture 11 September, supplement 26 September, target-cap probe 2 October 2026
Audit ID
PF-2026-LQ05
Output Artifacts5 files committed
3 columns
FileContentsSize
verification-results.jsonSteps 1–25 results — per-step status with evidence detail strings and profile ids, profile coverage (LIMBO-01 … LIMBO-31), the pinned hashes, the RTP claim block, the informational items and the verdict~66 KB
simulation-results.jsonThe reference run (2,000,000 uniformity draws, serial screens, the 20,000,000-round convergence series at 2.00×, 8 targets × 2,000,000 rounds) + the Pass 2 block (134 revealed seeds × 10,000 nonces, both screens, per-seed ranks)~23 KB
pass1-grid.jsonPass 1 on its pinned master seed — the 100-bin histogram of 2,000,000 draws, the serial sums and 10 targets × 1,000,000 rounds with their win counts, re-derived by Step 23~4.8 KB
rtp-claim-table.jsonThe RTP claim rule’s table — LIQD’s published 99% against all 999,900 targets from 1.01× to 10,000×, by target decade with the extremes and the outcome~3.5 KB
rtp-convergence.htmlConvergence chart rendered from the reference run’s saved series at 2.00× (self-contained SVG/HTML)~2.5 KB
Repository Structure
liqd-limbo/
├── src/
│   ├── rng.ts                  → commitment hash · HMAC draw with its guard (generateProvablyFairNumber) · limboDraw · limboResult — written from docs/ALGORITHM.md
│   ├── bias.ts                 → the draw-bias bound, from the same maxFair rule the draw applies, and its acceptance probe (Step 5)
│   ├── config.ts               → the result map (resultMultiplier), win chance and exact RTP, the win rule, the limits, population constants and artifact pins
│   ├── rtp-claim.ts · rtp-claim-switch.ts → the RTP claim rule (RTP-CLAIM 1.2.0) and its enforcement switch (on)
│   ├── rtp-settings.ts         → the RTP claim table over all 999,900 targets up to 10,000×, LIQD’s published claim and the table’s pin
│   ├── profile-map.ts          → which Limbo profile check (LIMBO-01 … LIMBO-31) each step implements
│   ├── loader.ts               → dataset loader + SHA-256 guard (aborts before any step on a mismatch)
│   ├── pass1-grid.ts           → Pass 1: 2,000,000 draws + 10 targets × 1,000,000 rounds from the pinned master seed → outputs/pass1-grid.json
│   ├── pass2.ts · pass2-emit.ts → Pass 2: 134 revealed seeds × 10,000 nonces, deterministic → the Pass 2 block of outputs/simulation-results.json
│   ├── simulate.ts             → the reference run (2M draws · 20M rounds at 2.00× · 8 targets × 2M) and the Pass 2 block → outputs/simulation-results.json, rtp-convergence.html
│   ├── stats.ts                → chi-squared (exact incomplete gamma), lag-1, runs test, normal and binomial tails
│   ├── domains.ts              → value domains for every numeric field of the committed artifacts (Step 19)
│   ├── diff.ts                 → field-level diff of a run vs the committed artifacts (outputs/run/diff.json)
│   └── types.ts                → capture record types
├── docs/
│   ├── ALGORITHM.md            → the specification the RNG is written from
│   └── INTERFACE.md            → the module interface the RNG implements
├── tests/
│   ├── verify.ts               → 25-step verification runner (results under outputs/run/; PF_EMIT=1 to regenerate the committed record)
│   ├── limbo/                  → 27 unit tests (RngSpecTests 1 [72 checks] · rngTests 14 · resultFloorTests 7 · diffTests 2 · capProbeTests 3)
│   ├── mutate.ts · mutations.json · structural-forgeries.json · mutation-survivors.json → 75 mutations + 41 forgeries, 1 declared survivor
│   ├── forgeries/              → the 41 structural forgeries, as 11 executable scripts
│   └── steps/
│       ├── commitment.ts       → Steps 1–4 (seed hashes, chain + pre-capture link, epoch coverage, nonce windows)
│       ├── determinism.ts      → Steps 5–7 (result recompute + bias bound, win rule, determinism)
│       ├── payouts.ts          → Steps 8–11 (paid multiplier, credit, losses, wallet fields + limits)
│       ├── dataset.ts          → Steps 12–13 (dataset identity + population, phase plan)
│       ├── anti-circularity.ts → Steps 14–15 (negative control + boundary anchor, win-chance identity)
│       ├── edge.ts             → Steps 16–17 (effective edge at every target, the 1,000,000× purchase check)
│       ├── phase-d.ts          → Step 18 (custom client seeds + controls)
│       ├── standardization.ts  → Steps 19–21 (artifact integrity, settlement determinism, declared limitations)
│       ├── live-fit.ts         → Step 22 (live-sample fit, informational)
│       ├── pass1.ts            → Step 23 (Pass 1 re-derived from the grid)
│       ├── pass2.ts            → Step 24 (Pass 2 re-derived from the revealed seeds)
│       ├── supplement.ts       → Step 25 (the supplement and the live target ceiling)
│       ├── statistical.ts      → informational items, not scored (live return, win rate, result range, per-phase returns)
│       └── context.ts          → the scored-step contract and its guards
├── scripts/                    → npm test’s staleness gate, release manifest + release bar, standalone-bundle builder
├── capture/                    → the capture runners, their shared engine and offline self-tests
├── data/
│   ├── limbo-master-6700bets.json → the scored capture: 6,700 rounds · 134 epochs · SHA-256 pinned
│   └── liqd-limbo-capture-v2-2026-09-26T17-48-02-207Z.json · .journal.ndjson → the 300-round supplement and its journal (Step 25) · SHA-256 pinned
├── outputs/
│   ├── verification-results.json    → 25 steps + profile coverage + artifact hashes + RTP claim block + informational items + verdict
│   ├── simulation-results.json      → the reference run + the Pass 2 block
│   ├── pass1-grid.json              → Pass 1’s histograms, serial sums and win counts
│   ├── rtp-claim-table.json         → the RTP claim rule over all 999,900 targets up to 10,000×
│   └── rtp-convergence.html         → self-contained convergence chart (the reference run at 2.00×)
├── evidence/
│   ├── E13-limbo-boundary-probes.json → LIQD’s /verify endpoint at the boundary (7.5× against 7.51×)
│   ├── E15-limbo-max-exposure.json  → the $50,000 exposure ceiling
│   ├── E20-target-ceiling-probe-2026-09-26/ → two probe journals at the 10,000× ceiling · SHA-256 pinned
│   ├── E21-target-cap-probe/        → the 2 October cap probe; Step 25’s journal is SHA-256 pinned
│   └── rtp-page-labels-2026-10-01.json → LIQD’s Limbo page figures — the RTP claim
├── .github/workflows/reproduce.yml → second-machine reproduction on a clean runner (ubuntu-latest, Node from .nvmrc, npm ci && npm test)
├── test.js · verify.js         → dependency-free bundles (Node 22)
├── README.md · MANIFEST.md     → the report (findings, evidence register, worked rounds, limitations register)
├── RELEASE-MANIFEST.json       → release identity: tag, framework, artifact hashes, verdict, signing key
├── package.json                → npm test = staleness gate, release-manifest check, node test.js, node verify.js · verify · test:full · mutate
└── package-lock.json · tsconfig.json · .mocharc.yml · .nvmrc · .gitattributes · .gitignore · standalone-manifest.json · hygiene-allow.json · LICENSE
Step-to-Section Cross-Reference25 verification steps mapped
3 columns
StepTest NameReport Section
1Seed Hash IntegrityS1, S2, S3, S6
2Next-Seed Pre-Commitment ChainS1, S6
3Hash Consistency & Epoch CoverageS1
4Nonce AuditS1
5Draw & Result RecomputationS1, S2, S3, S4, S6
6Win-Rule ReproductionS1, S2, S3, S4
7Recomputation DeterminismS1, S3
8Paid-Multiplier DerivationS3, S4
9Credit ArithmeticS1, S3, S4
10Loss SettlementS2, S3, S4
11Wallet Ledger, Stake Floor & Exposure CeilingS3, S4
12Dataset Integrity & Population IdentityS3, S7
13Phase Plan ConformanceS2, S3, S4
14Anti-Circularity (Negative Control + Grid Boundary Anchor)S4
15Win-Chance vs Result-Distribution IdentityS4
16Effective-Edge CurveS4
17Client-Code Maximum MultiplierS4, S6
18Custom Client-Seed Control (Phase D)S1, S2, S3, S6
19Artifact Integrity & Evidence ReproductionS2, S4, S6, S7
20Settlement DeterminismS3, S4
21Scope & Declared LimitationsS7
22Live-Sample FitS2
23Simulation Pass 1 — Pinned Master Seed, Re-DerivedS2, S4
24Simulation Pass 2 — Cherry-Pick Screen, 10,000 Nonces per Revealed SeedS4
25Target-Decade Supplement & Live Target CeilingS3, S4, S6, S7
Game Profile CoverageLimbo profile 1.0.2 · 31 checks

Every check in the ProvablyFair.org Limbo profile, version 1.0.2, is mapped to a scored step, to a package check, or marked n/a with a reason — 22 to scored steps, 7 to package checks, 2 n/a. Rows the repo marks full read Full; for LIMBO-09 and LIMBO-24 the row states the exact scope this audit covers. The map ships in src/profile-map.ts and is written beside the step results on every run; the committed outputs/verification-results.json carries it.

4 columns
CheckWhat it requiresCovered byCoverage
LIMBO-01Every revealed seed hashes to its commitmentStep 1Full
LIMBO-02Next-seed pre-commitment chain, including the pre-capture linkStep 2Full
LIMBO-03One commitment per epoch; the committed and revealed sets are identicalStep 3Full
LIMBO-04Nonce continuity — every epoch holds its nonces exactly once, with no reuse and no gapsStep 4Full
LIMBO-05Audited population bound to code constants, not the dataset headerSteps 12, 13Full
LIMBO-06Client-seed control and sensitivityStep 18Full
LIMBO-07Every served result recomputes exactly; the derivation is deterministicSteps 5, 7Full
LIMBO-08Draw-bias bound computed from the draw’s own rejection ruleStep 5Full
LIMBO-09Known-input unit vectors reproduce captured roundsPackage check: docs/ALGORITHM.md + tests/limbo/ (RngSpecTests, rngTests, resultFloorTests)The specification’s self-test (72 checks), LIQD’s 4 Verify-calculator values and the result-floor vector; the RNG is our own implementation, written from the specification
LIMBO-10Result range and the win rule — result ≥ target winsSteps 5, 6Full
LIMBO-11Settlement arithmetic — a win credits stake × target exactly, a loss pays 0, the credit set by (seeds, nonce, target, stake)Steps 9, 10, 20Full
LIMBO-12Bet-size invariance at an elevated stakeStep 13Full
LIMBO-13The multiplier recorded on every win equals the target; LIQD’s priced win chance equals the result distributionSteps 8, 15Full
LIMBO-14Target and stake limits — the target range, the minimum stake, the exposure ceiling and the live target ceilingSteps 11, 17, 25Full
LIMBO-15Target coverage — every decade up to the ceiling played at or above the profile floor; the minimum target playedSteps 13, 25Full
LIMBO-16Wallet balance ledger reconciliation—N/A — the capture records per-bet wallet fields, not a running balance; LIQD’s settle-response wallet fields (stakeWallet, winningAmountWallet) are reconciled against the game ledger in Step 11
LIMBO-17Boundary rules — the 1× result floor, and a result exactly on the target winsSteps 5, 6Full
LIMBO-18Anti-circularity — negative control, grid boundary anchor and literal grid talliesSteps 14, 15Full
LIMBO-19Effective edge at every target against the claimed edge; the RTP claim ruleStep 16Full
LIMBO-20Simulation Pass 1 — uniformity, serial screens and convergence at 10 targets; replayableStep 23Full
LIMBO-21Cherry-pick test — Simulation Pass 2Step 24Full
LIMBO-22Live-sample fit (informational)Step 22Full
LIMBO-23Framework fairness-integrity rows S5-1 … S5-8, kept as a package checklist (Section 5 reports the live integrity tests)Package check: MANIFEST.md S5 fairness-integrity matrixFull
LIMBO-24Verifier parity — LIQD’s verifier on known inputs; an independent verifier matched to the derivationLIQD’s verifier: tests/limbo/rngTests.ts and Step 19 · independent: this report’s 6.8–6.9 scriptsLIQD’s own verifier agrees on 6 of 6 checks — 4 / 4 Verify-calculator values and 2 / 2 /verify replies; the 6.8–6.9 scripts on this page reproduce all 6,700 scored rounds
LIMBO-25Player verification path, in LIQD’s field namesPackage check: MANIFEST.md Verify a betFull
LIMBO-26Captures pinned by SHA-256; the loader aborts on a mismatchSteps 12, 19Full
LIMBO-27Artifacts of record — no run metadata, every numeric value in a declared domainSteps 19, 23Full
LIMBO-28Release gate — zero-install entry points, pinned runtime, audit ID; regenerations byte-identicalPackage check: package.json, .nvmrc, verify.js · test.js, src/simulate.tsFull
LIMBO-29Mutation battery with declared survivorsPackage check: tests/mutations.json and companionsFull
LIMBO-30Limitations register and numbered evidencePackage check: MANIFEST.mdFull
LIMBO-31The same multiplier at every stake—N/A as a separate check (bet-size invariance is LIMBO-12, Step 13): LIQD’s client constants carry one house edge, 1.00%, and the edge varies with the target alone (Step 16)
reproduce-audit.sh
reproduce-audit.sh
# Clone and setupgit clone https://github.com/ProvablyFair-org/liqd-limbo.gitcd liqd-limbogit checkout v1.0.1npm ci # Run the full audit end-to-end (staleness gate + release-manifest check + 27 unit tests + 25-step verification)npm test # Or the dependency-free path — the standalone bundles need only Node.js 22node test.js && node verify.js # Or run individual componentsnpm run verify           # 25-step verification — writes outputs/run/, never the committed resultsnpm run test:full        # unit tests, the simulations regenerated byte for byte, then verifynpm run mutate           # mutation battery — 117 entries: 116 caught, 1 declared survivor # View the committed resultscat outputs/verification-results.jsoncat outputs/rtp-claim-table.json | head -40cat outputs/simulation-results.json | head -40
provablyfair.org/verify-bets/liqd
This is an open-source audit. Every scored check, dataset and line of verification code is public. You don't need to trust us or the casino. Clone the repo, run the tests, and verify every claim yourself.
✓  Fully Reproducible

All scored results reproduce from release v1.0.1, the pinned captures and the commands above. The 6,700-round capture is hash-checked before any step runs, so you are provably testing the same rounds; the supplement and its journal, the three probe journals Step 25 reads, the simulation artifact, the Pass 1 grid, the convergence chart and the RTP claim table are pinned by SHA-256 — ten pins in all — and every run is compared with the committed results. Step 21 keeps the declared scope honest too: on every run it re-checks four of the package’s declared limitations against the capture and fails if one stops being true. The simulations are deterministic — npm run test:full regenerates the reference run, the Pass 1 grid and the Pass 2 block — from their pinned master seeds and the capture’s revealed seeds — byte for byte.