Skip to content

KPI Math

The generator package exports pure functions for computing game statistics - RTP, volatility, hit rate, win distribution, and more - from generated entry data. Use these to validate simulation output and produce compliance reports.

Import

KPI functions are available from the main package or via a dedicated @hizi.io/engine-generator/math sub-export (useful when you only need the math utilities without the full generator):

typescript
// From the main package
import {
  computeAllKpis,
  computeGameRtp,
  computeEffectiveEntries,
  computeOverallKpis,
  computeMetaTagKpis,
  computeWinDistribution,
  computeWinOdds,
  classifyVolatility,
  generateBucketName,
} from '@hizi.io/engine-generator';

// Or from the math sub-export
import { computeAllKpis, computeGameRtp } from '@hizi.io/engine-generator/math';

Functions

computeAllKpis()

Compute a full KPI suite in one call. Optionally includes a game RTP breakdown when feature data is provided.

typescript
computeAllKpis(
  entries: IKpiEntry[],
  baseEntries?: IKpiEntry[],
  featureEntriesMap?: Map<string, IKpiEntry[]>,
  progressionCounters?: IProgressionCounterConfig[],
  wagerFeatures?: Set<string>,
  playerChoiceAnalysis?: boolean
): IAllKpis
ParameterTypeDescription
entriesIKpiEntry[]Entries to compute KPIs over (typically base game entries)
baseEntriesIKpiEntry[]Base game entries (for game RTP calculation)
featureEntriesMapMap<string, IKpiEntry[]>Map of feature name → entries (e.g. 'freespin' → freespin entries)
progressionCountersIProgressionCounterConfig[]Progression counter configs (for counter-triggered feature RTP)
wagerFeaturesSet<string>Feature names that use multiplicative wins (excluded from RTP contribution)
playerChoiceAnalysisbooleanWhen true, computes min/max RTP range based on player choice strategy. Adds minTotalRtp/maxTotalRtp to IGameRtp and per-feature minRtpContribution/maxRtpContribution.
typescript
import { readFileSync } from 'fs';
import { brotliDecompressSync } from 'zlib';
import { HiziEngineGenerator, computeAllKpis } from '@hizi.io/engine-generator';

const entriesJsonl = brotliDecompressSync(readFileSync('./output/entries.jsonl.br')).toString();
const scenariosJsonl = brotliDecompressSync(readFileSync('./output/scenarios.jsonl.br')).toString();

const baseEntries = HiziEngineGenerator.loadEntries(entriesJsonl, scenariosJsonl, 'basegame');
const freespinEntries = HiziEngineGenerator.loadEntries(entriesJsonl, scenariosJsonl, 'freespin');

const featureMap = new Map([['freespin', freespinEntries]]);
const kpis = computeAllKpis(baseEntries, baseEntries, featureMap);

console.log(`RTP: ${kpis.overall.rtp}%`);
console.log(`Hit Rate: ${kpis.overall.hitRate}%`);
console.log(`Volatility: ${kpis.overall.volatilityClass}`);

if (kpis.gameRtp) {
  console.log(`Total Game RTP: ${kpis.gameRtp.totalRtp}%`);
  for (const f of kpis.gameRtp.featureBreakdowns) {
    console.log(`  ${f.featureName}: ${f.rtpContribution}%`);
  }
}

computeGameRtp()

Compute the RTP breakdown per feature and the overall game RTP, including retrigger expansion and progression counter contributions.

typescript
computeGameRtp(
  baseEntries: IKpiEntry[],
  featureEntriesMap: Map<string, IKpiEntry[]>,
  requiredFeatures: Set<string>,
  progressionCounters?: IProgressionCounterConfig[],
  wagerFeatures?: Set<string>,
  playerChoiceAnalysis?: boolean
): IGameRtp
ParameterTypeDescription
baseEntriesIKpiEntry[]Base game entries
featureEntriesMapMap<string, IKpiEntry[]>Feature name → entries
requiredFeaturesSet<string>All feature names referenced by feature awards
progressionCountersIProgressionCounterConfig[]Progression counter configs
wagerFeaturesSet<string>Feature names that use multiplicative wins (excluded from RTP contribution)
playerChoiceAnalysisbooleanWhen true, computes min/max RTP range for player choice scenarios

Returns an IGameRtp with baseRtp, per-feature breakdowns, cashCounterBreakdowns (one entry per counter whose awards array contains at least one cash option), and totalRtp (= baseRtp + Σ featureContributions + Σ cashCounterContributions).

A counter's onComplete.awards array may mix feature and cash options. The math handles each option independently:

  • Feature options contribute through the empirical feature breakdowns — the counter-awarded spins end up in the target feature's DB, so its empirical RTP picks them up automatically.
  • Cash options never enter any DB, so computeGameRtp adds them explicitly. For each counter with at least one cash option:
    • weightedIncrement = Σ(entry.weight × progressionAwards[counter]) summed across base + every feature DB.
    • expectedCashPerCompletion = Σ(option.winMultiplier × selectionFraction) summed over the cash options only (feature options contribute 0 cash here).
    • rtpContribution = weightedIncrement × expectedCashPerCompletion / baseTotalWeight × 100.

Player-choice min/max for a cash counter breakdown collapses to min/max over option.winMultiplier if isCash(option) else 0 — picking a feature option contributes 0 cash to this breakdown (its EV flows through the feature breakdowns instead).


computeEffectiveEntries()

For each base entry, compute an "effective win" that includes the expected value of any awarded feature spins. Useful for auto-balancing.

typescript
computeEffectiveEntries<T extends IKpiEntry>(
  baseEntries: T[],
  featureEntriesMap: Map<string, IKpiEntry[]>,
  progressionCounters?: IProgressionCounterConfig[],
  wagerFeatures?: Set<string>,
  playerChoiceAnalysis?: boolean
): (T & { effectiveWin: number; minEffectiveWin?: number; maxEffectiveWin?: number })[]
ParameterTypeDescription
baseEntriesT[]Base game entries
featureEntriesMapMap<string, IKpiEntry[]>Feature name → entries
progressionCountersIProgressionCounterConfig[]Progression counter configs
wagerFeaturesSet<string>Feature names that use multiplicative wins (excluded from effective win bonus)
playerChoiceAnalysisbooleanWhen true, adds minEffectiveWin and maxEffectiveWin based on player choice strategy

The effectiveWin formula: for each base entry, add win + sum(featureAwards × featureEvPerSpin) plus, for every progression counter increment on this entry, the option-by-option contribution inc × selectionFraction × (count × featureEvPerSpin for feature options, or winMultiplier for cash options). PlayerChoice min/max evaluate each option independently — feature options contribute their feature EV, cash options contribute their winMultiplier.


computeOverallKpis()

Compute overall statistics: RTP, hit rate, volatility, and max win.

typescript
computeOverallKpis(entries: IKpiEntry[]): IOverallKpis

computeMetaTagKpis()

Compute per-tag statistics: frequency, RTP contribution, average win, etc.

typescript
computeMetaTagKpis(entries: IKpiEntry[]): IMetaTagKpis[]

computeWinDistribution()

Compute a bucketed win distribution (e.g. "0x", "0.001–1x", "1.001–5x", etc.).

typescript
computeWinDistribution(entries: IKpiEntry[]): IWinDistribution

computeWinOdds()

Compute win odds at standard thresholds (e.g. "1 in 5 chance of winning 2x or more").

typescript
computeWinOdds(entries: IKpiEntry[]): IWinOdds

classifyVolatility()

Classify a standard deviation value into a volatility category.

typescript
classifyVolatility(stdDev: number): TVolatilityClass
RangeClassification
< 5'Low'
5–10'Medium'
10–15'High'
>= 15'Very High'

generateBucketName()

Generate a human-readable label for a win bucket range.

typescript
generateBucketName(min: number, max: number): string

Examples: "0x", "0.001–1x", "100x+".


Types

IKpiEntry

Input type for all KPI functions. Matches the shape of loaded entries.

typescript
interface IKpiEntry {
  win: number;
  weight: number;
  metaTags: string[];
  featureAwards?: TFeaturesAwarded | null;
  progressionAwards?: Record<string, number> | null;
}

IAllKpis

Complete KPI result returned by computeAllKpis().

typescript
interface IAllKpis {
  overall: IOverallKpis;
  metaTags: IMetaTagKpis[];
  winDistribution: IWinDistribution;
  winOdds: IWinOdds;
  gameRtp: IGameRtp | null;
}

IOverallKpis

typescript
interface IOverallKpis {
  totalEntries: number;
  totalWeight: number;
  rtp: number;
  hitRate: number;
  volatilityStdDev: number;
  volatilityClass: TVolatilityClass;
  maxWin: number;
}
FieldTypeDescription
totalEntriesnumberNumber of unique entries
totalWeightnumberSum of all weights
rtpnumberReturn to Player percentage
hitRatenumberPercentage of spins with win > 0
volatilityStdDevnumberStandard deviation of win distribution
volatilityClassTVolatilityClass'Low' | 'Medium' | 'High' | 'Very High'
maxWinnumberHighest win amount

IGameRtp

typescript
interface IGameRtp {
  baseRtp: number;
  featureBreakdowns: IFeatureRtpBreakdown[];
  totalRtp: number;
  allFeaturesLoaded: boolean;
  minTotalRtp?: number;
  maxTotalRtp?: number;
}
FieldTypeDescription
baseRtpnumberBase game RTP percentage
featureBreakdownsIFeatureRtpBreakdown[]Per-feature RTP contributions (sorted by contribution descending)
totalRtpnumberBase + all feature contributions
allFeaturesLoadedbooleanWhether all detected features had data available
minTotalRtpnumber?Min possible total RTP (player picks worst option). Present when playerChoiceAnalysis is enabled.
maxTotalRtpnumber?Max possible total RTP (player picks best option). Present when playerChoiceAnalysis is enabled.

IFeatureRtpBreakdown

typescript
interface IFeatureRtpBreakdown {
  featureName: string;
  triggerRate: number;
  avgInitialSpins: number;
  evPerSpin: number;
  pRetrigger: number;
  expectedTotalSpins: number;
  rtpContribution: number;
  minRtpContribution?: number;
  maxRtpContribution?: number;
}
FieldTypeDescription
featureNamestringFeature name (e.g. 'freespin')
triggerRatenumberProbability of triggering (0–1)
avgInitialSpinsnumberWeighted average initial spins awarded
evPerSpinnumberExpected value per feature spin (in stake units)
pRetriggernumberRetrigger probability within the feature
expectedTotalSpinsnumberAverage total spins including retriggers
rtpContributionnumberPercentage points contributed to total game RTP
minRtpContributionnumber?Min contribution when player picks worst option. Present when playerChoiceAnalysis is enabled.
maxRtpContributionnumber?Max contribution when player picks best option. Present when playerChoiceAnalysis is enabled.

IMetaTagKpis

typescript
interface IMetaTagKpis {
  tag: string;
  entryCount: number;
  totalWeight: number;
  frequency: number;
  rtp: number;
  rtpContribution: number;
  hitRate: number;
  volatilityStdDev: number;
  averageWin: number;
  maxWin: number;
}

IWinDistribution

typescript
interface IWinDistribution {
  buckets: IWinBucket[];
}

interface IWinBucket {
  label: string;
  probability: number;
  entryCount: number;
}

IWinOdds

typescript
interface IWinOdds {
  thresholds: IWinOddsThreshold[];
}

interface IWinOddsThreshold {
  threshold: number;
  odds: string;
  probability: number;
}
FieldTypeDescription
thresholdnumberWin multiplier threshold (e.g. 2, 5, 10)
oddsstringHuman-readable odds (e.g. "1 in 5")
probabilitynumberProbability as a percentage (0–100)

TVolatilityClass

typescript
type TVolatilityClass = 'Low' | 'Medium' | 'High' | 'Very High';

IProgressionCounterConfig

Configuration for a progression counter that triggers feature spins on completion.

typescript
interface IProgressionCounterConfig {
  name: string;
  onComplete: {
    type: string;
    awards: { count: number; feature: string; weighting?: number }[];
  };
}
FieldTypeDescription
namestringCounter name (must match keys in progressionAwards)
onCompleteobjectWhat happens when the counter reaches 1.0
onComplete.typestring'playerChoice' or 'randomChoice'
onComplete.awardsarrayFeature spins awarded on completion