Public audit reference
Version 1.0 · the RNG, hash, and verification layer Cymba publishes openly.
This is Cymba's fairness protocol — the public layer of the engine's math contract. It covers RNG construction, deterministic replay, canonical encoding, and the audit verification flow. It is sufficient to reproduce any past spin byte-identically given the seed material.
The full algorithmic model (win evaluation, cap clipping, RTP method, free-spin recursion) is distributed inline in every PAR sheet export. Tenants generate one per config from the Game Configs page on the dashboard. That separation matches how slot-math specs are normally shared with auditors (under NDA, alongside per-config PAR sheets).
§1
Every engine output is a multiplier expressed as multiples of one bet unit. The engine never handles currency. Operators compute monetary payouts as:
line_payout = win_multiplier × bet_per_line × denomination total_payout = total_multiplier × bet_per_line × denomination
The units divisor converts a per-total-bet multiplier into a
per-line-bet multiplier so that Σ breakdown == totalMultiplier
holds exactly.
| Win mode | units |
Rationale |
|---|---|---|
| Payline (consecutive, full_line, both) | linesPlayed | Each active payline is an independent bet |
| Ways | Π visibleSymbols[reel] | Ways is a per-total-bet mechanic |
| Cluster | 1 | Per-total-bet |
| Count-pay | 1 | Per-total-bet |
If max_win_multiplier is set, the capped bucket of the breakdown
is bounded by cap = max_win_multiplier × units.
Exact clipping rules — including bucket priority and the dynamic-grid free-spin anchor —
are described in the full math model.
§2
Every random decision is driven by a sequence of outcome floats in
[0, 1) with 52-bit precision (full IEEE-754 mantissa).
Outcomes are derived in batches of four from HMAC-SHA256:
OUTCOMES_PER_HASH = 4 OUTCOME_DIVISOR = 2^52 deriveBatch(server_seed, client_seed, nonce, hashIndex): message = client_seed + ":" + nonce + ":slots" hmac = HMAC_SHA256(key = server_seed, data = message + ":" + hashIndex) hex = hmac.hex_digest() # 64 hex chars outcomes = [] for i in 0..3: chunk = hex[i*13 : (i+1)*13] # 13 hex digits value = int(chunk, 16) / OUTCOME_DIVISOR outcomes.append(value) return outcomes
hashIndex starts at 0 and increments by 1 per batch. The engine
pulls one batch at a time, lazily, in deterministic consumption order.
Outcomes are consumed in a single deterministic order, identical across implementations:
| Step | Source | Outcomes |
|---|---|---|
| 1 | Grid generation (static reels) | one per reel → floor(outcome × stripLength) |
| 1a | Dynamic per-reel height | reelCount height picks, then stop positions |
| 1b | Dynamic reel count | 1 count pick, then count stops |
| 2 | Mystery resolution (uniform) | 1 outcome |
| 2a | Mystery (per_position) | one per mystery cell |
| 3 | Wild multiplier (per-position) | one per wild cell |
| 3a | Wild multiplier (per-reel) | one per reel-with-wilds |
| 4 | Hold-respin refills | unlockedPositions × respins |
| 5 | Cascade refill (standard) | 0 (deterministic strip cursor) |
| 5a | Cascade + on-cascade-win growth | one per newly activated reel |
| 6 | Free-spin trigger resolutions | one per RNG pick |
| 7 | Each free sub-spin | recursively repeats steps 1–6 |
| 8 | Random multiplier | 1 outcome |
| 9 | Gamble | one per round |
| 10 | Jackpot random-trigger pools | one per pool with trigger_mode = random |
The order is invariant. Any implementation consuming outcomes in a different order will diverge.
Given (config, server_seed, client_seed, nonce, player_choices, external_state),
the engine produces byte-identical results. The engine pre-commits to a server seed by publishing
sha256(server_seed); after the seed is rotated, the prior seed is
revealed and any past spin can be independently re-derived and its result_hash verified.
§17
{
"round_id": "01ARZ3...",
"spins": [
{ "type": "base", "reel_positions": [...], "wins": [...], "win_total": 12.5 },
{ "type": "cascade", "iteration": 1 },
{ "type": "hold_respin", "respin_index": 1 },
{ "type": "free_spin", "spin_index": 0, "win_total": 20.0 }
],
"summary": { "total_multiplier": 42.5, "win_capped": false, "breakdown": { ... } },
"state": { "meters": {...}, "jackpot_pools": {...} },
"result_hash": "d4e5f6..."
}
The result_hash is HMAC-SHA256 of a canonically encoded form of
the rest of the response, keyed by the server seed:
result_hash = HMAC_SHA256( key = server_seed, data = canonicalEncode( result_without_hash ) ) canonicalize(value): if value is not object/array: return value for each (k, v) in value: value[k] = canonicalize(v) if value is an associative object (not a list): sort keys ascending return value canonicalEncode(value) = json_encode( canonicalize(value), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION )
A naive json_encode will not match —
PHP and JavaScript do not guarantee object-key order, and
JSON_PRESERVE_ZERO_FRACTION is required so integer-valued floats
(e.g. 1.0) serialize with the trailing .0
the engine emitted.
§19
For each past spin the customer logs server_seed_hash,
result_hash, config_hash, and
nonce. After POST /api/v1/seed/rotate
reveals the prior server_seed:
sha256(server_seed) == server_seed_hash for that key's history.
config_hash on the spin must equal
sha256(canonicalEncode(exportedConfig)).
(config, server_seed, client_seed, nonce, externalState),
re-run the pipeline using your own implementation of the full math model
(distributed in the PAR sheet export).
HMAC_SHA256(canonicalEncode(replayedResult_without_hash), server_seed)
must equal the published result_hash.
Steps 1, 2, and 4 are pure cryptography and require no slot-math knowledge. Step 3 requires the auditor's own implementation of the math model included in their PAR sheet. Together these checks cover the math-and-fairness audit surface that GLI / BMM certification addresses — without requiring access to the Cymba engine source.
Need the full algorithmic model (win evaluation, RTP method, cap clipping, free-spin recursion)? It ships inline in every PAR sheet export so your auditor receives a self-contained reference. Tenants download a PAR sheet per config from the Game Configs page on the dashboard.
Questions about a specific audit? [email protected].