Verification
Slot math is a standard discipline. A competent slot mathematician with our fairness protocol and a game config (delivered in the PAR sheet alongside the full math model) can recompute theoretical RTP from first principles — without any access to our engine source. Below is the exact verification workflow.
Open the Game Configs page on your dashboard, find the config, and click Download PAR Sheet. The PDF bundles the full config, theoretical RTP, and five deterministic verification fixtures.
Alternatively, use the API for a JSON bundle that also includes inline spec HTML:
# Bash curl -s -H "Authorization: Bearer cy_live_..." \ https://cymba.dev/api/v1/par-sheet/my-config-id \ > par-sheet.json
Your math person follows §5–§16 of the math model (bundled in the PAR sheet as
spec_full_html) using standard slot-math techniques.
For configs with ≤ 100M combinations they enumerate exactly; otherwise they Monte Carlo.
The output is compared to theoretical_rtp.total_rtp in the PAR sheet.
# Pseudocode (any language) totalCombinations = product( len(strip) for strip in config.reels.strips ) EV = 0 for combo in 0..totalCombinations-1: stops = mixRadixDecompose(combo, stripLengths) grid = buildGridFromStops(stops, config) payout = resolveMath(grid, config) / linesPlayed # per math model §5 EV += payout / totalCombinations rtp = EV * 100 assert |rtp − par_sheet.theoretical_rtp.total_rtp| < tolerance
Tolerance: bit-identical for exact enumeration; max(2σ, 1% relative, 0.1% absolute)
for Monte Carlo at reasonable sample counts.
Five deterministic triples in the PAR sheet. Auditor reproduces each spin using their own implementation
of §2 (RNG) + §5–§13 (math) from the PAR sheet bundle. Every result_hash must
match byte-identically.
# For each fixture in par_sheet.verification_fixtures: outcomes = ProvablyFairOutcomeSource( fixture.inputs.server_seed, fixture.inputs.client_seed, fixture.inputs.nonce ) result = resolveSpin( par_sheet.config, outcomes, round_id = fixture.inputs.round_id # reuse the published round_id ) # Canonical encode per protocol §17, then HMAC over the result (minus result_hash itself): payload = canonicalEncode( result without result_hash ) expected_hash = HMAC_SHA256(key=fixture.inputs.server_seed, data=payload) assert expected_hash == fixture.expected.result_hash assert configHash(par_sheet.config) == fixture.expected.config_hash
If any hash differs: either the auditor's implementation deviates from the spec, the config was tampered with, or the engine has a regression. All three are detectable.
Every spin response carries config_hash. To prove a past spin used the
config described in the PAR sheet, compare:
assert spin_response.config_hash == par_sheet.config_hash
Mismatched hashes mean the spin was generated by a different config than the one in the PAR sheet — cymba can't quietly swap configs without the auditor noticing.
For real player spins (not fixtures), the customer logs server_seed_hash
+ nonce per spin. After
POST /api/v1/seed/rotate reveals the prior seed:
assert sha256(revealed_server_seed) == previously_committed_server_seed_hash
# Then replay each spin (as in step 2) using the now-revealed seed.
Commitment proves cymba locked in the seed before the bet was placed — it could not have chosen outcomes after seeing the player's stake or behaviour.
config_hash on every spin proves no quiet bait-and-switch (step 4).Together these cover the math-and-fairness audit surface that GLI / BMM testing addresses for certification — without requiring access to our engine source.
Public protocol layer: /spec. Full math model: bundled inline in the PAR sheet (no Cymba login needed for auditors). Tenants click "PAR sheet" on any config in the dashboard's Game Configs list to download.
Questions about a specific audit? [email protected].