Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/StakeEngine/math-sdk/llms.txt

Use this file to discover all available pages before exploring further.

Game analysis produces an Excel (.xlsx) PAR sheet summarizing key statistics of the optimized win distribution. It is designed to be run after optimization is complete.

Prerequisites

  • A completed optimization run — analysis uses the optimized lookup tables (lookUpTable_<mode>_0.csv), not the raw simulation output.
  • force_record_<mode>.json files in library/forces/, containing the event data recorded during simulation.
  • GameConfig.paytable populated with symbol names so the analysis can resolve kind/symbol pairs to readable names.
For meaningful statistics, run at least 100,000 simulations per mode before analyzing. Smaller batches produce noisy hit-rate and RTP estimates that are not representative of the final game distribution.

Enabling analysis in run.py

Set run_analysis to True in the run_conditions dictionary:
run_conditions = {
    "run_sims":         True,
    "run_optimization": True,
    "run_analysis":     True,
}
Then call create_stat_sheet after the optimization step:
from utils.game_analytics.run_analysis import create_stat_sheet

custom_keys = [{"symbol": "scatter"}]
create_stat_sheet(gamestate, custom_keys=custom_keys)

What the analysis uses

SourcePurpose
lookUpTableSegmented_<mode>.csvDetermines which game type (basegame, freegame) contributed to each simulation’s final win
force_record_<mode>.jsonProvides event frequencies — symbol, kind, mult, gametype per recorded win
GameConfig.paytableMaps (kind, symbol) pairs to payout amounts and provides valid symbol names
Force records must use the following key format to be recognized by the analysis tool:
{"symbol": "<name>", "kind": "<num_symbols_in_win>"}
For example, the Lines class records wins as:
def record_line(kind: int, symbol: str, mult: int, gametype: str) -> None:
    gamestate.record({"kind": kind, "symbol": symbol, "mult": mult, "gametype": gametype})

The run() function and custom search keys

The run() function inside run_analysis.py accepts an optional custom_keys argument. Each entry is a dictionary matching fields in gamestate.record() calls — the analysis will compute hit-rates specifically for events matching those keys.
# Hit-rate for any event where symbol == "scatter"
custom_keys = [{"symbol": "scatter"}]
create_stat_sheet(gamestate, custom_keys=custom_keys)
This is useful for tracking the frequency of specific events (e.g., scatter triggers, wild substitutions) that are not captured by the standard win-range bins.

PAR sheet output

The generated .xlsx file contains:
  • Per-symbol hit-rates — frequency of each (kind, symbol) combination, using symbol names from GameConfig.paytable.
  • Per-range RTP contributions — how much each win-range bucket contributes to total RTP.
  • Simulation counts by win range — how many simulations fall into each range, useful for checking distribution coverage.
  • Game-type breakdown — hit-rates and RTP split by basegame vs freegame, sourced from lookUpTableSegmented_<mode>.csv and the gametype field in force records.

Analyzing win distributions

Once a lookup table has been optimized, you can inspect the resulting win distribution — a dictionary where keys are all unique, ordered payout values and values are the probability of obtaining each payout.

Comparing alternative lookup tables

The optimization algorithm outputs several viable candidate lookup tables to library/optimization_files/. The swap_lookups utility lets you swap out the weights in lookUpTable_<mode>_0.csv with weights from any of these candidates, so you can compare how different distributions perform without re-running the optimizer.

File hash checking

Use get_file_hash() to print the SHA-256 value of a file or all non-Python files in a directory. Compare these values against the SHA values stored in config.json to verify that file contents have not been altered since the configs were generated.
from utils.get_file_hash import get_file_hash

# Single file
get_file_hash("library/lookup_tables/lookUpTable_base_0.csv")

# All non-Python files in a directory
get_file_hash("library/lookup_tables/")

Understanding runtime RTP output

During a simulation or optimization run, each thread prints a summary line when it finishes:
Thread 0 finished with 1.632 RTP. [baseGame: 0.043, freeGame: 1.588]
This means thread 0 completed with a raw (pre-optimization) RTP of 163.2%, with 4.3% contributed by basegame wins and 158.8% by freegame wins. Raw RTP is higher than the target (e.g., 97%) because forced simulations — such as wincap and freegame triggers — are overrepresented in the raw pool. The optimization algorithm adjusts the selection weights to bring the final sampled RTP in line with the target.

Next steps

Optimization algorithm

Learn how the optimizer adjusts weights to hit the RTP target.

Setting up optimization

Configure conditions, scaling, and parameters for your game.

Build docs developers (and LLMs) love