Appearance
Output Format
Output Files
The generator produces brotli-compressed JSONL files in the output directory. It also produces an optional plain JSON config file:
output/
├─ entries.jsonl.br (entry metadata - brotli-compressed JSONL)
├─ scenarios.jsonl.br (scenario data - brotli-compressed JSONL)
└─ config.json (game configuration - plain JSON, via end() config option)The generator writes all features (basegame, freespin, bonus, and other types) to the same entries.jsonl file. This includes buy-feature pools. Each entry line includes a feature field that identifies the feature it belongs to.
The generator automatically compresses JSONL files to .br (brotli, quality 9). It removes the raw .jsonl files. The generator writes config.json as plain JSON (not compressed).
Entry Format
Each line in entries.jsonl is a JSON object:
json
{"feature":"basegame","id":0,"weight":612483,"cumulativeWeight":612483,"win":0,"metaTags":["no-win"],"scenarioCount":1000}
{"feature":"freespin","id":3,"weight":80000,"cumulativeWeight":80000,"win":5,"metaTags":["small-win"],"scenarioCount":1000}
{"feature":"basegame","id":1,"weight":23891,"cumulativeWeight":636374,"win":50,"metaTags":["big-win"],"scenarioCount":1000}
{"feature":"basegame","id":2,"weight":500,"cumulativeWeight":636874,"win":0,"metaTags":["freespin-trigger"],"featureAwards":{"type":"randomChoice","awards":[{"count":10,"feature":"freespin"}]},"progressionAwards":{"scatter-collection":{"0":0.01}},"scenarioCount":500}The generator sorts every entry by weight, from highest to lowest, across all features. Rows of different features therefore interleave. It appends the bf_ buy-feature pool rows after that block.
Each entry includes a cumulativeWeight field: the running total of weights within that feature. The engine uses this value for efficient weighted selection.
The generator omits optional fields (metaTags, featureAwards, progressionAwards, progressionInfo) when they are not present. It writes no null values.
progressionAwards uses this key structure: counter name, then scenario step index (0-based), then fractional increment. This structure lets the runtime attribute a counter change to the scenario step that produced it. Write it via addResult(). See Progression Counters.
progressionInfo holds the entry's feature-run boundaries plus the same increments in a flat per-step list. The generator writes it onto the row when you pass it to addResult(). See Types · IProgressionInfo.
Scenario Format
Each line in scenarios.jsonl is a JSON object:
json
{"entryId":0,"scenarioIndex":0,"scenario":[{"reels":[[2,3,1],[2,2,2],[4,0,5]],"winLines":0}]}
{"entryId":1,"scenarioIndex":0,"scenario":[{"reels":[[5,5,5],[2,3,1],[4,0,5]],"winLines":3}]}The scenario field is an array of records that hold the game state. A normal spin has a single element. A multi-result feature, like sticky symbols, has multiple elements.
Config Format
The optional config.json file contains configuration for the game. The hizi engine consumes this file at import time:
json
{
"gameCode": "my-slot",
"gameType": "slot",
"rtp": 95.97,
"featureWeights": { "basegame": 636874, "freespin": 80000 },
"stakes": [0.20, 0.40, 1.00, 2.00, 5.00],
"features": ["freespin"],
"wagerFeatures": ["color-red", "color-black"],
"progressionCounters": [
{ "name": "scatter-collection", "onComplete": { "type": "randomChoice", "awards": [{ "count": 10, "feature": "freespin" }] }, "stakeSpecific": false }
]
}Write it by passing config to end(). If you do not set featureWeights, the generator auto-populates it from featureTotalWeights.
Buy-Features Format
The generator materialises buy-feature pools into entries.jsonl as synthetic features named bf_<sanitized-id>. There is no separate buyfeatures.jsonl file. Each pool entry is an ordinary entry line. It carries the pool's feature name and its selection weight:
json
{"feature":"bf_buy_freespin","id":3,"weight":500,"cumulativeWeight":500,"win":5,"metaTags":["small-win"],"scenarioCount":1000}
{"feature":"bf_buy_freespin","id":4,"weight":300,"cumulativeWeight":800,"win":8,"metaTags":["small-win"],"scenarioCount":1000}Pool entries reuse their source entry's id. This means they share scenarios (scenarios key on entryId, not on feature). The generator adds only the weight rows. It never adds scenario data.
The engine selects entries from a pool. It filters entries on the bf_<id> feature. It then picks a weighted entry via cumulativeWeight, bounded by featureWeights[bf_<id>].
Write pools by passing buyFeatures or buyFeatureDefinitions to end(). See Buy-Features.
Why JSONL?
- Streaming: Scenarios stream directly to disk as the generator creates them. This avoids memory accumulation. The generator can handle billions of results.
- Cross-platform: The format works in Node.js and in browsers. It has no native binary dependencies.
- Compressible: Brotli compression (quality 9) typically reduces file size by 90% or more. This gives efficient distribution.
INFO
File size depends on two factors: the complexity of your scenario data and the frequency of meta tags. Simpler scenarios produce smaller files.
Reading Output Files
TypeScript (Node.js)
typescript
import { readFileSync } from 'fs';
import { brotliDecompressSync } from 'zlib';
import { HiziEngineGenerator } from '@hizi.io/engine-generator';
const entriesJsonl = brotliDecompressSync(readFileSync('./output/entries.jsonl.br')).toString();
const scenariosJsonl = brotliDecompressSync(readFileSync('./output/scenarios.jsonl.br')).toString();
// Load all basegame entries with scenarios
const entries = HiziEngineGenerator.loadEntries(entriesJsonl, scenariosJsonl, 'basegame');Command Line
bash
# View entries
brotli -d output/entries.jsonl.br --stdout | jq -c . | head -5
# View config (plain JSON, no decompression needed)
cat output/config.json | jq .