Public audit reference

Fairness Protocol

Version 1.0 · the RNG, hash, and verification layer Cymba publishes openly.

Scope of this document

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

Math model — multipliers, not money

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

Bet units by win mode

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)linesPlayedEach active payline is an independent bet
WaysΠ visibleSymbols[reel]Ways is a per-total-bet mechanic
Cluster1Per-total-bet
Count-pay1Per-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

RNG protocol

Outcome space

Every random decision is driven by a sequence of outcome floats in [0, 1) with 52-bit precision (full IEEE-754 mantissa).

Derivation

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.

Consumption order

Outcomes are consumed in a single deterministic order, identical across implementations:

Step Source Outcomes
1Grid generation (static reels)one per reel → floor(outcome × stripLength)
1aDynamic per-reel heightreelCount height picks, then stop positions
1bDynamic reel count1 count pick, then count stops
2Mystery resolution (uniform)1 outcome
2aMystery (per_position)one per mystery cell
3Wild multiplier (per-position)one per wild cell
3aWild multiplier (per-reel)one per reel-with-wilds
4Hold-respin refillsunlockedPositions × respins
5Cascade refill (standard)0 (deterministic strip cursor)
5aCascade + on-cascade-win growthone per newly activated reel
6Free-spin trigger resolutionsone per RNG pick
7Each free sub-spinrecursively repeats steps 1–6
8Random multiplier1 outcome
9Gambleone per round
10Jackpot random-trigger poolsone per pool with trigger_mode = random

The order is invariant. Any implementation consuming outcomes in a different order will diverge.

Replay and verification

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

Output format and result hash

Result envelope

{
  "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..."
}

Canonical encoding

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

Verification flow

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:

  1. Check seed commitment. sha256(server_seed) == server_seed_hash for that key's history.
  2. Check config binding. config_hash on the spin must equal sha256(canonicalEncode(exportedConfig)).
  3. Replay the spin. With (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).
  4. Re-derive the result hash. 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].