hizi engine Generator API
Constructor
new HiziEngineGenerator(options?: IHiziEngineGeneratorCoreOptions)Options
| Property | Type | Default | Description |
|---|---|---|---|
maxScenariosPerEntry | number | 1000 | Maximum number of scenario snapshots stored per entry. Once reached, new occurrences still increase weight but don't add scenario data. |
maxScenariosPerZeroWinEntry | number | 2000 | Separate, larger cap for zero-win (dead-spin) entries — they dominate spin volume, so more scenario variety is wanted there. Applies in both strict and loose modes. Since 0.3.0. |
looseScenarioCap | boolean | false | When true, the cap is pooled per win + featureAwards + progressionAwards bucket instead of per full unique entry, so outcomes differing only in incidental metaTags (cascade, multiplier, win tier) share one scenario budget. Entries and weights are untouched, and every entry keeps at least one scenario. Keeps scenarios.jsonl small on the large simulations needed to reach jackpot-odds wins. Since 0.3.0. |
// Use defaults (1000 scenarios per entry)
const generator = new HiziEngineGenerator();
// Custom limit
const generator = new HiziEngineGenerator({ maxScenariosPerEntry: 500 });
// Large simulation tuned for jackpot-odds wins: pool the cap across cosmetic
// metaTags, and keep more dead-spin variety (since 0.3.0)
const generator = new HiziEngineGenerator({
maxScenariosPerEntry: 1000,
maxScenariosPerZeroWinEntry: 2000,
looseScenarioCap: true,
});Methods
start()
Start streaming results to JSONL files. Scenarios are written during addResult(), entries and config at end(). JSONL files are automatically brotli-compressed to .br at the end. In Node.js, writes to the local filesystem. In the browser, writes to OPFS.
async start(outputDirectory: string): Promise<void>| Parameter | Type | Description |
|---|---|---|
outputDirectory | string | Directory for output files. Created if it doesn't exist. |
// Node.js
await generator.start('./output/');
// Creates ./output/entries.jsonl and ./output/scenarios.jsonl
// Browser (OPFS)
await generator.start('/output');WARNING
Calling start() while output is already open throws an error. Call end() first.
addResult()
Record a single game outcome. This is the primary method you'll call in your simulation loop.
Scenarios are streamed directly to disk during this call. Entry metadata is kept in memory and written during end().
addResult(
scenario: Record<string, unknown> | Record<string, unknown>[] | null,
options?: {
feature?: string;
win?: number;
metaTags?: string[];
featureAwards?: TFeaturesAwarded;
weight?: number;
progressionAwards?: Record<string, number>;
}
): void| Parameter | Type | Default | Description |
|---|---|---|---|
scenario | Record<string, unknown> | Record<string, unknown>[] | null | - | A single game state snapshot, an array of snapshots for multi-result features, or null for a weight-only update. Single records are automatically wrapped in an array. |
options.feature | string | 'basegame' | Feature name (e.g. 'basegame', 'freespin'). All features write to the same output files, distinguished by the feature field on each entry. |
options.win | number | 0 | The win amount for this outcome |
options.metaTags | string[] | - | Classification labels for this outcome |
options.featureAwards | TFeaturesAwarded | - | Feature awards triggered by this outcome |
options.weight | number | 1 | Weight for this occurrence. Useful when importing pre-aggregated data. |
options.progressionAwards | Record<string, number> | - | Progression counter increments keyed by counter name (e.g. { "scatter-collection": 0.01 }). See Progression Counters. |
Entry matching: Results with the same win, metaTags, and featureAwards are merged into a single entry. The entry's weight increments and the scenario is appended (up to the configured limit).
Single-result (recommended) - pass a plain object. The library wraps it in an array internally:
generator.addResult(
{ result: { reelIndexes: [1, 2, 3], visibleSymbols: [[5, 5, 5]] } },
{
feature: 'basegame',
win: 100,
metaTags: ['big-win'],
featureAwards: {
type: 'randomChoice',
awards: [{ count: 10, feature: 'freespin' }],
},
},
);Multi-result - pass an array when spin results are dependent on each other (e.g. sticky symbols with progressive state). See Scenarios for trade-offs.
generator.addResult(
[
{ grid: spin1Grid, stickyPositions: [] },
{ grid: spin2Grid, stickyPositions: [[0, 1]] },
{
grid: spin3Grid,
stickyPositions: [
[0, 1],
[2, 0],
],
},
],
{
feature: 'basegame',
win: totalWin,
metaTags: ['sticky-feature'],
},
);Weight-only - pass null to just count the occurrence:
generator.addResult(null, { feature: 'basegame', win: 0 });buildBuyFeatures()
Resolve buy-feature definitions against current entries using metaTag-based subsetting. Call this after addResult() and before end(), or pass definitions directly via end({ buyFeatureDefinitions }).
buildBuyFeatures(definitions: IBuyFeatureDefinition[]): IBuyFeatureEntry[]| Parameter | Type | Description |
|---|---|---|
definitions | IBuyFeatureDefinition[] | Buy-feature definitions to resolve against the current entry pool. |
Resolution strategy: entrypool — only entries with at least one matching metaTag are included. metaTagWeights scales tagged weights per tag; weightOverrides replaces specific tagged entry weights for fine-tuning.
This method is pure — it resolves and returns the pools without writing anything. The pools are materialised into entries.jsonl as bf_<id> features only when you pass them to end() (as buyFeatures, or by handing the definitions to end({ buyFeatureDefinitions })).
// Run your simulation first...
for (const spin of simulate()) {
generator.addResult(spin.scenario, {
win: spin.win,
metaTags: spin.tags, // e.g. ['freespin-trigger', 'big-win']
});
}
// Resolve buy features from metaTags
const buyFeatures = generator.buildBuyFeatures([
{
id: 'freespin',
type: 'entrypool',
metaTags: ['freespin-trigger'],
},
{
id: 'boost',
type: 'entrypool',
metaTags: ['big-win'],
},
]);
// Pass resolved pools to end() — materialised into entries.jsonl as bf_<id> features
await generator.end({ buyFeatures });WARNING
Throws an error if no output is open or no entries exist. Call start() and addResult() first.
end()
Finalize output — write entries (including materialised bf_<id> buy-feature pools), optional config, and compress. This is the single call that produces all output files.
async end(options?: IEndOptions): Promise<void>Options
| Property | Type | Default | Description |
|---|---|---|---|
config | IGameConfig | - | Game configuration to write as config.json. featureWeights is auto-populated if not set. |
buyFeatures | IBuyFeatureEntry[] | - | Pre-resolved buy-feature pools, materialised into entries.jsonl as bf_<id> features. |
buyFeatureDefinitions | IBuyFeatureDefinition[] | - | Buy-feature definitions to auto-resolve from entry metaTags. Resolved pools are merged with buyFeatures (if provided). |
// Minimal — just entries + scenarios
await generator.end();
// Full — config, buy features, and brotli compression in one call
await generator.end({
config: {
gameCode: 'my-slot',
gameType: 'slot',
rtp: 95.97,
// featureWeights auto-populated from generated data
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,
}],
},
buyFeatures: [
{ name: 'buy-freespin-10', entries: [{ feature: 'freespin', id: 3, weight: 500 }] },
],
});
// Output: entries.jsonl.br (incl. bf_buy_freespin_10), scenarios.jsonl.br, config.json
// Auto-resolve buy features from metaTags in one call
await generator.end({
config: { gameCode: 'my-slot' },
buyFeatureDefinitions: [
{ id: 'freespin', type: 'entrypool', metaTags: ['freespin-trigger'] },
{ id: 'boost', type: 'entrypool', metaTags: ['big-win'] },
],
});WARNING
Throws an error if no output is open. Call start() first.
getOutputFiles() Browser
Get all generated files as a Map<string, string> of filename → content. Available after start() + end() in browser/OPFS mode.
getOutputFiles(): Map<string, string>await generator.end();
const files = generator.getOutputFiles();
const entriesContent = files.get('entries.jsonl');dbEntryCount property
Number of unique entries created so far in streaming mode. Read this before calling end() since finalization resets the counter.
get dbEntryCount(): numberfeatureTotalWeights property
Feature → total weight map. Available after end(). Use this to populate the featureWeights field in your game's config.json.
get featureTotalWeights(): Record<string, number>await generator.end();
console.log(generator.featureTotalWeights);
// { basegame: 636874, freespin: 80000 }outputDirectory property · Browser
OPFS output directory path. Available after end() in browser/OPFS mode. Use this to read generated files directly from OPFS without loading them into memory via getOutputFiles().
get outputDirectory(): string | nullawait generator.end();
console.log(generator.outputDirectory);
// '/sim-output' (OPFS path) or null (Node.js)Returns null in Node.js — read files from the output directory on disk instead.
Static Methods
loadEntries()
Load all entries with their full scenario data from JSONL content strings.
static loadEntries(entriesJsonl: string, scenariosJsonl: string, feature?: string): TEntries| Parameter | Type | Description |
|---|---|---|
entriesJsonl | string | Content of the entries.jsonl file |
scenariosJsonl | string | Content of the scenarios.jsonl file |
feature | string | Optional feature name to filter by (e.g. 'basegame') |
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();
const entries = HiziEngineGenerator.loadEntries(entriesJsonl, scenariosJsonl, 'basegame');
for (const entry of entries) {
console.log(`win=${entry.win}, weight=${entry.weight}, scenarios=${entry.scenarios.length}`);
}loadEntryMetadata()
Load entry metadata without scenarios. Useful for displaying an overview before lazy-loading full scenario data.
static loadEntryMetadata(entriesJsonl: string, feature?: string): TEntryMetadata[]| Parameter | Type | Description |
|---|---|---|
entriesJsonl | string | Content of the entries.jsonl file |
feature | string | Optional feature name to filter by |
const metadata = HiziEngineGenerator.loadEntryMetadata(entriesJsonl, 'basegame');
for (const meta of metadata) {
console.log(`Entry ${meta.entryId}: win=${meta.win}, weight=${meta.weight}`);
}Utility Exports
The package also exports helper constants and functions for working with output files.
File Name Constants
import { entriesFile, scenariosFile, configFile } from '@hizi.io/engine-generator';
entriesFile // 'entries.jsonl'
scenariosFile // 'scenarios.jsonl'
configFile // 'config.json'sanitizeFeatureName()
Converts a feature name to a safe identifier by replacing hyphens and spaces with underscores.
import { sanitizeFeatureName } from '@hizi.io/engine-generator';
sanitizeFeatureName('card-color-red'); // 'card_color_red'
sanitizeFeatureName('free spin'); // 'free_spin'This is the same normalization the generator applies internally — use it when you need to match feature names against entry data.