Progression Counters
Progression counters track player progress across game rounds. When a counter accumulates to 100% (value 1.0), it triggers an award and resets, carrying any excess. Each option in the counter's awards array is independently either a feature spin award ({ count, feature }) or a cash award ({ winMultiplier } × stake) — the two kinds may be mixed freely in the same array.
Configuration
Define counters in your game's config.json. Selection mode (randomChoice or playerChoice) is at the top level; individual options inside awards choose their own kind by which field they carry.
Feature spins only
{
"progressionCounters": [
{
"name": "scatter-collection",
"onComplete": {
"type": "randomChoice",
"awards": [{ "count": 10, "feature": "freespins" }]
},
"stakeSpecific": false
}
]
}Cash only
winMultiplier is in stake-multiplier units — when the counter completes the engine pays winMultiplier × stake directly.
{
"progressionCounters": [
{
"name": "cashbox",
"onComplete": {
"type": "randomChoice",
"awards": [
{ "winMultiplier": 5, "weighting": 3 },
{ "winMultiplier": 20, "weighting": 1 }
]
},
"stakeSpecific": false
}
]
}Mixed (feature + cash in the same awards array)
A single counter completion can offer the player a choice between a feature spin and a cash payout, or randomly pick between the two:
{
"progressionCounters": [
{
"name": "treasure",
"onComplete": {
"type": "playerChoice",
"awards": [
{ "count": 10, "feature": "freespins" },
{ "winMultiplier": 25 }
]
},
"stakeSpecific": false
}
]
}For playerChoice, the client receives the option list in engineData.playerChoice and submits a playerChoiceIndex on the next placeBet call. The engine resolves the pick per its kind: a cash option adds winMultiplier × stake to totalWin (and resumes any pending feature spin or ends the round); a feature option starts the awarded feature spin.
Fields
| Field | Type | Description |
|---|---|---|
name | string | Counter identifier. Must match keys in entry progressionAwards. |
onComplete | TProgressionAwarded | Award triggered when the counter reaches 1.0. |
onComplete.type | "randomChoice" | "playerChoice" | Whether the engine picks a weighted option or the player picks from a list. |
onComplete.awards | TPlayerChoiceAward[] / TRandomChoiceAward[] | Mixed list of feature-spin and cash options. Each option carries either { count, feature } or { winMultiplier }, plus an optional weighting for randomChoice. |
stakeSpecific | boolean | If true, each stake level maintains its own counter. |
Entry-Level Setup
During game generation, assign progressionAwards to entries that should increment counters:
await generator.addResult(scenario, {
win,
metaTags,
featureAwards,
weight,
progressionAwards: { 'scatter-collection': 0.01 }, // 1% increment per occurrence
});In the editor, use the Progression Awards section on each entry to configure counter name + increment value pairs.
How It Works
- Round start: The engine loads current counter values from player data.
- Each spin: If the selected entry has
progressionAwards, the engine increments the corresponding counters. - Completion: When a counter reaches >= 1.0, the engine picks (or asks the player to pick) one option from the
onComplete.awardsarray and dispatches it by kind — a feature option merges spins intospinInfo; a cash option addswinMultiplier × staketototalWin. The counter then resets with any excess carried over. - Round end: Updated counter values are persisted to player data.
Counter values are available on engineData.progressionCounters in every IGameResult:
const counters = gameResult.engineData.progressionCounters;
// { "scatter-collection": 0.55 } // 55% progressClient Integration
loadConfig Response
When a game has progression counters, loadConfig returns:
{
"config": {
"progressionCounters": [
{ "name": "scatter-collection", "onComplete": { ... }, "stakeSpecific": false }
],
"progressionCounterValues": {
"scatter-collection": 0.55
}
}
}placeBet Response
Each placeBet response includes current counter values in engineData:
{
"result": {
"engineData": {
"progressionCounters": { "scatter-collection": 0.56 },
"inProgress": true
}
}
}Example
Feature counter
A slot game where collecting 100 scatter symbols awards 10 free spins:
- Config: Define a counter
scatter-collectionwithonComplete: { type: "randomChoice", awards: [{ count: 10, feature: "freespins" }] }. - Generation: On entries where a scatter lands, set
progressionAwards: { "scatter-collection": 0.01 }. - Gameplay: After 100 scatter lands (100 x 0.01 = 1.0), the counter triggers 10 free spins and resets to 0.
Cash counter
A coin-collect mechanic where every 20 coin landings pays out 5× stake:
- Config: Define a counter
coin-collectwithonComplete: { type: "randomChoice", awards: [{ winMultiplier: 5 }] }. - Generation: On entries where a coin lands, set
progressionAwards: { "coin-collect": 0.05 }. - Gameplay: After 20 coin lands (20 × 0.05 = 1.0), the counter pays
5 × stakeintototalWinand resets to 0.
Mixed treasure-chest counter
A "pick your prize" mechanic where every 50 chest landings offers the player a choice between free spins and a flat cash payout:
- Config:json
{ "name": "chest-pick", "onComplete": { "type": "playerChoice", "awards": [ { "count": 8, "feature": "freespins" }, { "winMultiplier": 30 } ] }, "stakeSpecific": false } - Generation: On each chest land, set
progressionAwards: { "chest-pick": 0.02 }. - Gameplay: After 50 chest lands the engine surfaces both options in
engineData.playerChoice. The client renders both kinds (use theisCashChoice/isFeatureChoiceguards) and postsplayerChoiceIndex. The engine pays out per the picked option's kind.
Float Precision
Counter arithmetic uses Math.round(val * 1e6) / 1e6 to avoid floating-point drift, ensuring consistent behavior across rounds.
Backward Compatibility
- All progression fields are optional. Existing games without progression counters are unaffected.
- Old output files without
progressionAwardsfields are handled gracefully. progressionCountersonengineDatais optional; clients that don't use it can ignore it.