Skip to content

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

json
{
  "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.

json
{
  "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:

json
{
  "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

FieldTypeDescription
namestringCounter identifier. Must match keys in entry progressionAwards.
onCompleteTProgressionAwardedAward 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.awardsTPlayerChoiceAward[] / TRandomChoiceAward[]Mixed list of feature-spin and cash options. Each option carries either { count, feature } or { winMultiplier }, plus an optional weighting for randomChoice.
stakeSpecificbooleanIf true, each stake level maintains its own counter.

Entry-Level Setup

During game generation, assign progressionAwards to entries that should increment counters:

typescript
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

  1. Round start: The engine loads current counter values from player data.
  2. Each spin: If the selected entry has progressionAwards, the engine increments the corresponding counters.
  3. Completion: When a counter reaches >= 1.0, the engine picks (or asks the player to pick) one option from the onComplete.awards array and dispatches it by kind — a feature option merges spins into spinInfo; a cash option adds winMultiplier × stake to totalWin. The counter then resets with any excess carried over.
  4. Round end: Updated counter values are persisted to player data.

Counter values are available on engineData.progressionCounters in every IGameResult:

typescript
const counters = gameResult.engineData.progressionCounters;
// { "scatter-collection": 0.55 }  // 55% progress

Client Integration

loadConfig Response

When a game has progression counters, loadConfig returns:

json
{
  "config": {
    "progressionCounters": [
      { "name": "scatter-collection", "onComplete": { ... }, "stakeSpecific": false }
    ],
    "progressionCounterValues": {
      "scatter-collection": 0.55
    }
  }
}

placeBet Response

Each placeBet response includes current counter values in engineData:

json
{
  "result": {
    "engineData": {
      "progressionCounters": { "scatter-collection": 0.56 },
      "inProgress": true
    }
  }
}

Example

Feature counter

A slot game where collecting 100 scatter symbols awards 10 free spins:

  1. Config: Define a counter scatter-collection with onComplete: { type: "randomChoice", awards: [{ count: 10, feature: "freespins" }] }.
  2. Generation: On entries where a scatter lands, set progressionAwards: { "scatter-collection": 0.01 }.
  3. 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:

  1. Config: Define a counter coin-collect with onComplete: { type: "randomChoice", awards: [{ winMultiplier: 5 }] }.
  2. Generation: On entries where a coin lands, set progressionAwards: { "coin-collect": 0.05 }.
  3. Gameplay: After 20 coin lands (20 × 0.05 = 1.0), the counter pays 5 × stake into totalWin and 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:

  1. Config:
    json
    {
      "name": "chest-pick",
      "onComplete": {
        "type": "playerChoice",
        "awards": [
          { "count": 8, "feature": "freespins" },
          { "winMultiplier": 30 }
        ]
      },
      "stakeSpecific": false
    }
  2. Generation: On each chest land, set progressionAwards: { "chest-pick": 0.02 }.
  3. Gameplay: After 50 chest lands the engine surfaces both options in engineData.playerChoice. The client renders both kinds (use the isCashChoice / isFeatureChoice guards) and posts playerChoiceIndex. 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 progressionAwards fields are handled gracefully.
  • progressionCounters on engineData is optional; clients that don't use it can ignore it.