Skip to content

Scenario Compression

Scenarios (see Core Concepts) are the per-spin game-state snapshots the engine returns to your game client. For rich games — slots especially — they are large and highly repetitive, so the hizi pipeline stores and transmits them in a minified form and expands them back to the full shape on the client.

This is transparent: with one line of setup, your game client sees exactly the same scenario objects it always has. This page explains what changes on the wire and how to opt in.

Why

  • Smaller payloads — less bandwidth per placeBet, and smaller items at rest.
  • Harder to read — a minified scenario is not trivially inspectable by a third party watching network traffic.

What "minified" means

Two transforms are applied to each scenario:

  1. Short keys — verbose keys become short codes (boardb, winAmountwa, winMultiplierwm, …).
  2. Integer symbols — symbol/enum string values become integers ("wild"6, "winline"0, …).

A slot board step looks like this on the wire:

jsonc
// full (what your client sees after decoding)
{ "board": [["low1", "wild", "scatter"], ["low2", "low1", "wild"]],
  "winAmount": 7,
  "wins": [{ "mode": "winline", "positions": [{ "column": 0, "row": 1 }], "winMultiplier": 5 }] }

// minified (what travels on the wire)
{ "b": [[0, 6, 7], [1, 0, 6]],
  "wa": 7,
  "w": [{ "m": 0, "p": [{ "c": 0, "r": 1 }], "wm": 5 }] }

The integer ↔ symbol-name mapping is published in the game's config as symbolMap, and a scenarioSchema marker flags the game as using minification. Both arrive on the loadConfig response:

typescript
const cfg = await loadConfig({ backendURL, token });
// cfg.result.config.scenarioSchema  -> e.g. 1   (present only for minified games)
// cfg.result.config.symbolMap       -> { "0": "low1", "1": "low2", "6": "wild", "7": "scatter", ... }

How it flows

Creator  ── builds game-data.zip with scenarios stored minified

Engine   ── stores minified, sends minified on the wire (placeBet / loadConfig / pfVerify)
   │       (expands internally where it needs to: game-history rendering, RTP simulation,
   │        and games whose logic reads the scenario, e.g. mines/keno)

SDK      ── decodes back to full string-symbol scenarios for your client

Decoding in the SDK

The easiest path: pass the config you got from loadConfig into placeBet (and pfVerify). The SDK decodes the returned scenario for you before resolving.

typescript
const cfg = await loadConfig({ backendURL, token });
const config = cfg.success ? cfg.result.config : undefined;

// placeBet decodes the scenario when you pass `config`.
const bet = await placeBet({ backendURL, token, stake, config });
if (bet.success) {
  const scenario = bet.result.result.scenario; // full, string-symbol form
}

loadConfig automatically decodes any scenarios embedded in its own reply (a resumed round or history in previousResults), so those are already expanded.

To decode a result you are handling yourself (for example one you stored), call decodeScenario directly:

typescript
import { decodeScenario } from '@hizi.io/engine-sdk';

decodeScenario(gameResult, config); // mutates gameResult.scenario in place

Safe by default / backward compatible

  • Passing config is always safe: decodeScenario is a no-op for games that don't minify (no scenarioSchema) and when config is missing. You never need to branch on game type.
  • Games that don't minify, and rounds recorded before minification was enabled, are returned unchanged.
  • If you do not pass config, placeBet returns the raw (minified) reply untouched — useful if you want to handle decoding yourself.

Notes

  • The scenario codec is defined once, canonically, in @hizi.io/engine-sdk; the engine (wire minify) and the builder (storage compression) import it, so the three sides can never disagree on the format. The SCHEME_VERSION constant guards the scheme.
  • Server-rendered game history expands scenarios automatically — nothing to do there.