Optimizing your Game
Achieve perfect RTP with optimization
Introduction
After simulating and analyzing the outcomes of your game, you may notice that your RTP may be too high or too low and that the game may generally be unbalanced. To achieve a desired target RTP between 94% and 98%, the optimization program can be used.
A big thanks to Stake for providing the entire game optimizer code. (Original source code)
The optimization program (from 13.08.25) has been integrated into Slot Engine, enabling you to precisely adjust your games to achieve the desired RTP. Additionally, you can further tweak the weights of certain payout groups using custom configs.
After optimization is done, you can observe that the weights in your lookup tables have been redistributed.
Before:
1,1,780
2,1,1000
3,1,0
...After:
1,1816455674,780
2,58062661,1000
3,19165815565,0
...To other developers:
The optimizer works, but it would be nice to not depend on Stake updating their original optimizer code. Since their optimizer is designed to work with their math SDK, integrating new optimizer changes into Slot Engine might not always work.
Also their optimizer is not well documented. All info on this page is gathered from the Stake Engine Discord. Go there if you have specific questions about the optimizer not documented here.
I don't know Rust and have no mathematical background, and the optimization algorithm seems complex. If anyone would like to try to implement such an algorithm in TypeScript, feel free to open an issue and let's talk about it.
Having a first-party optimization algorithm would be good in the long run.
Configuration
Install Rust and cargo if you haven't already. This is necessary because the optimization program is written in Rust.
Ensure your simulations cover a wide range of outcomes. Otherwise the optimizer might struggle to work properly. You can configure analysis to get an overview of all payout ranges and how often certain outcomes appear. Ensure there are sufficient outcomes for each payout range.
Call configureOptimization() on your game
const game = createSlotGame({
/* ... */
})
game.configureOptimization({
gameModes: {
base: {
conditions: {
maxwin: new OptimizationConditions({
rtp: 0.01,
avgWin: 2000,
searchConditions: {
criteria: "maxwin",
},
priority: 8,
}),
"0": new OptimizationConditions({
rtp: 0,
avgWin: 0,
searchConditions: 0,
priority: 6,
}),
superFreespins: new OptimizationConditions({
rtp: 0.05,
hitRate: 500,
searchConditions: {
criteria: "superFreespins",
},
priority: 3,
}),
freespins: new OptimizationConditions({
rtp: 0.35,
hitRate: 150,
searchConditions: {
criteria: "freespins",
},
priority: 2,
}),
basegame: new OptimizationConditions({
rtp: 0.55,
hitRate: 4,
priority: 1,
}),
},
scaling: new OptimizationScaling([]),
parameters: new OptimizationParameters(),
},
// ...
},
})Finally call runTasks() on your game
game.runTasks({
doSimulation: true,
doOptimization: true,
optimizationOpts: {
gameModes: ["base"],
},
})configureOptimizationhas agameModesproperty specifying optimization configurations for different game modes.- The keys of
gameModesmust match the modes in your game configuration. - Each game mode has 3 configuration objects:
conditions,scalingandparameters.
Optimization Conditions
Each conditions key for a game mode can have multiple sub-properties (conditions).
A condition is used to select specific simulations which the optimization is applied to.
Each ResultSet of a game mode must have a corresponding optimization condition, named after its criteria.
Since you probably want to optimize every single simulation, you should define as many conditions for a game mode as you need, to enable optimization for all simulations.
For an optimization condition, specify the property searchConditions and set key/value pairs
which the optimizer will search for in the force records file.
The optimizer requires knowing what RTP to optimize a subset of outcomes to / how much RTP to contribute to the total RTP.
This is especially useful for defining the hit frequency of free spins, for example.
2 of 3 variables need to be defined, so the program can determine the target RTP for a set of results.
These being: rtp, avgWin, and/or hitRate.
Each condition is given a priority. This determines the order in which simulations are picked
out of the pool of all simulations. Setting priorities correctly is very important, because once a simulation
is optimized, it can't be optimized again. Let's break it down with an example:
- You have two simulations: ID 1 - free spins, and ID 2 - free spins max win.
- Both simulations are tagged in the records as "triggeredFreeSpins".
- You have two optimizer conditions: Priority 5 - selects free spins, and priority 1 - selects max wins.
- The free spins subset with 5 priority should get a target hit rate of 1/200, and the max win subset with 1 priority should get a target hit rate of 1/500.000.
- The optimizer first works on simulations that match the conditions with the highest priority. In this case it would get all simulations tagged as "triggeredFreeSpins" and optimize them.
- The "normal free spins" will get optimized and receive their target hit rate. But since the max win simulations are also tagged as being free spins, those will get optimized too and result in a hit rate which is way too high.
- All optimized simulations are removed from the pool.
- When it's time to optimize the max wins (with priority 1), there are actually no simulations left in the pool that match the criteria, since all max wins have already been (wrongly) optimized. This will not only result in an error, but also your game will be unbalanced.
The highest priority should always be given to the most specific simulation subsets.
Fortunately, Slot Engine automatically tags every simulation by their result set criteria, creating groups of simulations that don't overlap one another.
When creating your optimization conditions, you can simply specify the result set criteria
as the only key in searchConditions. This way the priority won't matter at all
(given you did not add additional records which may cause overlap).
freespinsUpgradeToSuper: new OptimizationConditions({
rtp: 0.03,
hitRate: 500,
searchConditions: {
criteria: "freespinsUpgradeToSuper",
},
priority: 4,
}),Developer Comment
Since the optimizer is created by Stake and not the Slot Engine team, consult their docs for any more information you may need, or ask in the Stake Engine Discord server.
Properties
| Property | Type | Description | Required |
|---|---|---|---|
rtp | number | "x" | The desired RTP contribution. Total RTP of all optimization conditions should equal RTP in your game configuration. | |
avgWin | number | The desired average win per spin. | |
hitRate | number | "x" | The desired hit rate (e.g. 200 to hit 1 in 200 spins). | |
searchConditions | number | Record<string, string> | [number, number] | - A number (payout multiplier), e.g. 5000- Force record values, e.g. { "symbolId": "scatter" }- Payout multiplier range, e.g. [0, 100] | |
priority | number | As explained in the paragraphs above. | yes |
At most one optimization condition of a game mode can have either rtp: "x" or hitRate: "x".
The optimizer can determine one missing value automatically.
Optimization Scaling
With scaling you can artifically increase the chances of a certain win range being hit. This should be used carefully!
A new OptimizationScaling([]) must be provided, even if you don't intend to utilize it.
You can just pass an empty array to the constructor.
scaling: new OptimizationScaling([
{
criteria: "freespins",
scaleFactor: 1.2,
winRange: [50, 150],
probability: 1,
},
// ...
])Properties
All properties are required.
| Property | Type | Description |
|---|---|---|
criteria | string | The criteria name of a result set or optimization conditions, respectively. |
scaleFactor | number | The factor which the odds of hitting the win range is multiplied by. |
winRange | [number, number] | The target win range, e.g. [50, 150]. |
probability | number | The probability (0-1) of the scaling being actually applied. Typically 1. |
Developer Comment
Since the optimizer is created by Stake and not the Slot Engine team, consult their docs for any more information you may need, or ask in the Stake Engine Discord server.
Optimization Parameters
Configure special parameters to control the optimization algorithm.
You can just pass new OptimizationParameters() to apply the default settings.
Properties
All properties are optional.
| Property | Type |
|---|---|
numShowPigs | number |
numPigsPerFence | number |
threadsFenceConstruction | number |
threadsShowConstruction | number |
testSpins | number[] |
testSpinsWeights | number[] |
simulationTrials | number |
graphIndexes | number[] |
run1000Batch | false |
minMeanToMedian | number |
maxMeanToMedian | number |
pmbRtp | number |
scoreType | "rtp" |
Developer Comment
Since the optimizer is created by Stake and not the Slot Engine team, consult their docs for any more information you may need, or ask in the Stake Engine Discord server.
Use of AI on this page: All texts were initially written by hand and many were later revised by AI for improved flow. All AI generated revisions were carefully reviewed and edited as needed.