Skip to content

hizi engine Generator API

Constructor

typescript
new HiziEngineGenerator(options?: IHiziEngineGeneratorCoreOptions)

Options

PropertyTypeDefaultDescription
maxScenariosPerEntrynumber1000The maximum number of scenario snapshots that the generator stores per entry. After the generator reaches this limit, new occurrences still increase the weight. New occurrences do not add more scenario data.
maxScenariosPerZeroWinEntrynumber2000A separate, larger cap for zero-win entries (dead spins). These entries dominate the spin volume, so the generator needs more scenario variety for them. This cap applies in both strict and loose modes. Since 0.3.0.
looseScenarioCapbooleanfalseWhen set to true, the generator pools the cap per feature + win + featureAwards + progressionAwards + progressionInfo bucket instead of per full unique entry. Outcomes that differ only in incidental metaTags (cascade, multiplier, win tier) then share one scenario budget. The generator does not change entries or weights. Every entry keeps at least one scenario. This setting keeps scenarios.jsonl small in large simulations needed to reach jackpot-odds wins. Since 0.3.0.
maxTotalScenarioRowsnumber10_000_000Hard cap on the total number of scenario rows across all features. Every row becomes one item in the deploy archive. To stay below the cap, lower maxScenariosPerEntry / maxScenariosPerZeroWinEntry, or set looseScenarioCap. Extra rows buy scenario variety only, never RTP precision.
maxEntriesPerGamenumber500_000Hard cap on the number of unique entries across all features of the game. Entry counts grow with the cardinality of the match key (win × metaTags × featureAwards × progressionAwards). Reduce the granularity there instead of raising the cap.
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,
});

Hard caps

addResult() throws as soon as the game passes maxEntriesPerGame unique entries, or maxTotalScenarioRows scenario rows. The generator calls console.warn earlier, at the soft thresholds of 100,000 entries and 1,000,000 scenario rows.


Methods

start()

Start streaming results to JSONL files (JSON Lines, one JSON object per line). The generator writes scenarios during addResult(). The generator writes entries and config during end(). The generator automatically compresses JSONL files to .br with brotli at the end. In Node.js, the generator writes to the local filesystem. In the browser, the generator writes to OPFS (Origin Private File System).

typescript
async start(outputDirectory: string): Promise<void>
ParameterTypeDescription
outputDirectorystringDirectory for output files. The generator creates the directory if it does not exist.
typescript
// Node.js
await generator.start('./output/');
// Creates ./output/ and begins streaming scenarios.jsonl.
// end() writes entries.jsonl and compresses both files to .br

// 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 that you call in your simulation loop.

The generator streams scenarios directly to disk during this call. The generator keeps entry metadata in memory and writes it during end().

Pass options on every call. This parameter is required.

typescript
addResult(
  scenario: Record<string, unknown> | Record<string, unknown>[],
  options: {
    feature?: string;
    win?: number;
    metaTags?: string[];
    featureAwards?: TFeaturesAwarded;
    weight?: number;
    progressionAwards?: Record<string, Record<number, number>>;
    progressionInfo?: IProgressionInfo;
    forced?: boolean;
  }
): void
ParameterTypeDefaultDescription
scenarioRecord<string, unknown> | Record<string, unknown>[]-A single game state snapshot, or an array of snapshots for multi-result features. The generator automatically wraps single records in an array.
options.featurestring'basegame'Feature name (e.g. 'basegame', 'freespin'). All features write to the same output files. The feature field on each entry distinguishes them.
options.winnumber0The win amount for this outcome
options.metaTagsstring[]-Classification labels for this outcome
options.featureAwardsTFeaturesAwarded-Feature awards that this outcome triggers
options.weightnumber1Weight for this occurrence. Useful when importing pre-aggregated data.
options.progressionAwardsRecord<string, Record<number, number>>-Progression counter increments keyed counter name → 0-based scenario step index → fractional increment (e.g. { "scatter-collection": { "2": 0.2 } }). Per-step attribution lets the runtime stamp counter changes onto the scenario step that caused them. See Progression Counters.
options.progressionInfoIProgressionInfo-Explicit feature-run boundaries plus the flat per-step counter increments for this entry. It is part of the match key, so two outcomes that differ only here stay separate. See Progression Counters.
options.forcedbooleanfalseMarks this result as a forced (authored) outcome instead of a natural simulation hit. end() folds the forced weight in as max(natural, forced). A forced scenario bypasses the scenario cap, so the authored board stays replayable. The written rows carry no trace of the forcing.

Entry matching: The generator merges results with the same feature, win, metaTags, featureAwards, progressionAwards, and progressionInfo into a single entry. The entry's weight increments. The generator appends the scenario, up to the configured limit. progressionAwards and progressionInfo are part of the match key. Entries that differ only in which counter they increment, or by how much, stay separate. See Progression Counters · Entry matching.

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'],
  },
);

Every call needs a scenario

The scenario parameter has no null variant in the type signature. Pass a scenario object, or an array of scenario objects, on every call. There is no typed way to record an occurrence without scenario data.


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: For entrypool, the generator includes only entries with at least one matching metaTag. metaTagWeights scales the 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 generator materialises the pools 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), write optional config, and compress the files. 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. The generator auto-populates featureWeights if not set.
buyFeaturesIBuyFeatureEntry[]-Pre-resolved buy-feature pools. The generator materialises them into entries.jsonl as bf_<id> features.
buyFeatureDefinitionsIBuyFeatureDefinition[]-Buy-feature definitions to auto-resolve from entry metaTags. The generator merges resolved pools 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.


getEntries() Browser

Get the entries.jsonl content that end() wrote. Available after start() + end() in browser/OPFS mode.

typescript
getEntries(): string
typescript
await generator.end();
const entriesJsonl = generator.getEntries();

WARNING

getEntries() throws in three cases: before end() runs, in Node.js, or when end() writes no entries. In Node.js, read entries.jsonl.br from the output directory instead.

The generator does not return scenarios.jsonl this way. That file can reach several gigabytes, so it stays on OPFS. Read it through outputDirectory.


getConfig() Browser

Get the config.json content that end() wrote. Returns null when end() runs without a config.

typescript
getConfig(): string | null
typescript
await generator.end({ config: { gameCode: 'my-slot' } });
const configJson = generator.getConfig();

WARNING

getConfig() throws before end() runs, or in Node.js. In Node.js, read config.json from the output directory instead.


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

scenarioRowCount property

Number of scenario rows written to scenarios.jsonl so far. Read this before calling end() since finalization resets the counter.

typescript
get scenarioRowCount(): 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 getEntries().

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()

Convert 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 that the generator applies internally. Use it when you need to match feature names against entry data.

resolveBuyFeatures()

Resolve entry-pool buy-feature definitions against a list of entries by metaTag subsetting. buildBuyFeatures() calls this function internally. Use it directly when you hold the entries yourself, outside a generator session.

typescript
import { resolveBuyFeatures } from '@hizi.io/engine-generator';

resolveBuyFeatures(
  entries: readonly IBuyFeatureResolvableEntry[],   // { id, weight, metaTags? }
  definitions: readonly IBuyFeatureDefinition[],
): IResolvedBuyFeature[]                            // { id, entries: { id, weight }[] }

The package also exports the matching types: IBuyFeatureResolvableEntry, IResolvedBuyFeature, and IResolvedBuyFeatureEntry.

Award Type Guards

Discriminate a single progression-counter award option at runtime. A cash award carries winMultiplier. A feature award carries count and feature.

typescript
import { isCashAward, isFeatureAward } from '@hizi.io/engine-generator';

for (const award of counter.onComplete.awards) {
  if (isCashAward(award)) payCash(award.winMultiplier);
  else if (isFeatureAward(award)) awardSpins(award.count, award.feature);
}

Limit Constants

The hard caps and the soft warning thresholds that addResult() enforces. Read them instead of hard-coding the numbers.

typescript
import {
  warnTotalScenarioRows,       // 1_000_000  - console.warn threshold
  defaultMaxTotalScenarioRows, // 10_000_000 - default maxTotalScenarioRows
  warnEntriesPerGame,          // 100_000    - console.warn threshold
  defaultMaxEntriesPerGame,    // 500_000    - default maxEntriesPerGame
} from '@hizi.io/engine-generator';