Skip to main content
The optimization algorithm adjusts the selection weights of simulations in the lookup table so that, when sampled according to those weights, the overall win distribution converges on a specified RTP and hit-rate profile. All simulations initially carry a weight of 1; the optimizer modifies these weights without changing the underlying simulation outcomes.

How it works

The algorithm uses iterative weighted sampling:
  1. Generate trial distributions — candidate sets of weights are drawn using Gaussian sampling, with optional scaling applied to specific win ranges.
  2. Evaluate against targets — each candidate distribution is scored against the configured RTP and hit-rate targets per win-type condition.
  3. Accept viable distributions — distributions that fall within the mean-to-median bounds (volatility controls) are retained.
  4. Rank and select — retained distributions are ranked by running simulated test spins, and the best-scoring distribution is written out as the final lookup table.

Rust implementation

The algorithm is implemented in Rust for performance. It compiles to a binary executable. The source lives in optimization_program/src/main.rs.
You only need to rebuild the binary the first time you set up the project, or after modifying optimization_program/src/main.rs. For routine optimization runs the pre-built binary is used automatically by OptimizationExecution.
To build the binary:
Run this from the optimization_program/ directory, or let the Python wrapper invoke it via cargo run --release.

Lookup tables and weights

Each row in a lookup table CSV has the form:
Before optimization every weight is 1, meaning all simulations are equally likely to be selected. After optimization the weights are rewritten to lookUpTable_<mode>_0.csv, reflecting the probability mass the optimizer assigned to each simulation.

Key inputs

Key outputs

Conditions and simulation assignment

The conditions dictionary partitions simulations into exclusive groups — one group per win type (for example, wincap, freegame, 0, basegame). The optimizer reads these entries in order and assigns matching simulation IDs to each condition, removing them from the available pool before moving to the next.
The order of keys in conditions matters. A simulation can belong to multiple win-type categories — for example, a wincap result is also a freegame result. List more specific conditions first (e.g., wincap before freegame) so that those simulations are claimed by the correct condition before the broader one consumes them. Incorrect ordering will mis-classify simulations and produce wrong RTP splits.

Example conditions ordering

wincap is listed before freegame because every wincap simulation also triggers the freegame; listing it first ensures it is assigned to the wincap bucket rather than the freegame bucket.

Next steps

Setting up optimization

Configure OptimizationSetup and run the optimizer from run.py.

Game analysis

Generate a PAR sheet and analyze the optimized win distribution.