Skip to content

hizi engine Generator API

Constructor

typescript
new HiziEngineGenerator(options?: IHiziEngineGeneratorCoreOptions)

Options

PropertyTypeDefaultDescription
maxScenariosPerEntrynumber1000Maximum number of scenario snapshots stored per entry. Once reached, new occurrences still increase weight but don't add scenario data.
maxScenariosPerZeroWinEntrynumber2000Separate, 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.
looseScenarioCapbooleanfalseWhen 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.
typescript
// 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.

typescript
async start(outputDirectory: string): Promise<void>
ParameterTypeDescription
outputDirectorystringDirectory for output files. Created if it doesn't exist.
typescript
// 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().

typescript
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
ParameterTypeDefaultDescription
scenarioRecord<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.featurestring'basegame'Feature name (e.g. 'basegame', 'freespin'). All features write to the same output files, distinguished by the feature field on each entry.
options.winnumber0The win amount for this outcome
options.metaTagsstring[]-Classification labels for this outcome
options.featureAwardsTFeaturesAwarded-Feature awards triggered by this outcome
options.weightnumber1Weight for this occurrence. Useful when importing pre-aggregated data.
options.progressionAwardsRecord<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:

typescript
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.

typescript
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:

typescript
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 }).

typescript
buildBuyFeatures(definitions: IBuyFeatureDefinition[]): IBuyFeatureEntry[]
ParameterTypeDescription
definitionsIBuyFeatureDefinition[]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 })).

typescript
// 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.

typescript
async end(options?: IEndOptions): Promise<void>

Options

PropertyTypeDefaultDescription
configIGameConfig-Game configuration to write as config.json. featureWeights is auto-populated if not set.
buyFeaturesIBuyFeatureEntry[]-Pre-resolved buy-feature pools, materialised into entries.jsonl as bf_<id> features.
buyFeatureDefinitionsIBuyFeatureDefinition[]-Buy-feature definitions to auto-resolve from entry metaTags. Resolved pools are merged with buyFeatures (if provided).
typescript
// 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.

typescript
getOutputFiles(): Map<string, string>
typescript
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.

typescript
get dbEntryCount(): number

featureTotalWeights property

Feature → total weight map. Available after end(). Use this to populate the featureWeights field in your game's config.json.

typescript
get featureTotalWeights(): Record<string, number>
typescript
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().

typescript
get outputDirectory(): string | null
typescript
await 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.

typescript
static loadEntries(entriesJsonl: string, scenariosJsonl: string, feature?: string): TEntries
ParameterTypeDescription
entriesJsonlstringContent of the entries.jsonl file
scenariosJsonlstringContent of the scenarios.jsonl file
featurestringOptional feature name to filter by (e.g. 'basegame')
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();

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.

typescript
static loadEntryMetadata(entriesJsonl: string, feature?: string): TEntryMetadata[]
ParameterTypeDescription
entriesJsonlstringContent of the entries.jsonl file
featurestringOptional feature name to filter by
typescript
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

typescript
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.

typescript
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.