Slot Engine is in Beta - Expect bugs!
Slot EngineSlot Engine
Configuration Options

Result Sets

Force specific outcomes with Result Sets


Introduction

Slot games have thousands if not millions of possible outcomes—each spin is different. A player shouldn't see the exact same outcome twice. With result sets you ensure diverse simulation results. You want to ensure there are plenty of zero-win scenarios as well as many different max win simulations— and everything in between.

Before a simulation run starts, each simulation is assigned one of the specified result sets based on the total quota of all result sets for a game mode, and must fulfill its criteria. Only when a simulation meets the expected result set criteria does the program proceed to the next simulation.

Result sets commonly cover the following scenarios:

  • Zero-win spins
  • Win spins
  • Free spins
  • Max wins

But developers can also specify very detailed and rare criteria:

  • Free spins upgraded to super bonus resulting in max win

Result sets are a crucial part of simulating a game. Depending on the configuration and implementation of a game, outcomes like max wins may be extremely rare and possibly don't occur naturally during, for example, 1,000,000 simulations. With result sets, the program forces such outcomes and retries until the given criteria are met.

Keep this in mind

The more unlikely a scenario is to happen, the longer a simulation run may take.

Defining Result Sets for a Game Mode

The following example shows the definition of 4 common result sets. This setup ensures that a large portion of all simulations are either zero-win spins or normal base game hits. A smaller quota is dedicated to generating free spins simulations and a very small portion ensures enough max win simulations.

export const gameModes = defineGameModes({
  base: new GameMode({
    name: "base",
    cost: 1,
    rtp: 0.96,
    /* the rest of the configuration */
    resultSets: [
      new ResultSet({
        criteria: "0",
        quota: 0.4,
        multiplier: 0,
        reelWeights: {
          [SPIN_TYPE.BASE_GAME]: { base1: 1 },
          [SPIN_TYPE.FREE_SPINS]: { bonus1: 1 },
        },
      }),
      new ResultSet({
        criteria: "basegame",
        quota: 0.4,
        reelWeights: {
          [SPIN_TYPE.BASE_GAME]: { base1: 1 },
          [SPIN_TYPE.FREE_SPINS]: { bonus1: 1 },
        },
      }),
      new ResultSet({
        criteria: "freespins",
        quota: 0.1,
        forceFreespins: true,
        reelWeights: {
          [SPIN_TYPE.BASE_GAME]: { base1: 1 },
          [SPIN_TYPE.FREE_SPINS]: { bonus1: 3, bonus2: 1 },
        },
      }),
      new ResultSet({
        criteria: "maxwin",
        quota: 0.005,
        forceMaxWin: true,
        forceFreespins: true,
        reelWeights: {
          [SPIN_TYPE.BASE_GAME]: { base1: 1 },
          [SPIN_TYPE.FREE_SPINS]: { bonus1: 1, bonus2: 3 },
          evaluate: maxwinReelsEvaluation,
        },
      }),
    ],
  }),
})

Small quotas

Keep in mind that you'll simulate at least 100.000 spins. If you have a max win quota of 0.005 and (quota / totalQuota) * simsAmount = (0.005 / 1) * 100.000 = 500, that's perfectly enough variety in max win simulations.

ResultSet Options

criteria

PropertyTypeRequired
criteriastringyes

A unique descriptive identifier for the result set.

quota

PropertyTypeRequired
quotanumberyes

The quota of simulations that should fall under this result set.

Example

Let's say you have these 3 result sets:

  • "basegame": 90 quota
  • "freespins": 9 quota
  • "maxwin": 1 quota

The total quota here is 100 (but can be any number, doesn't have to be 100). This means that, for example, every 100th simulation will result in a max win.

When using optimization, the quota does not represent the actual frequency of specific outcomes when playing, because the optimizer will redistribute the weights of all simulations.

multiplier

PropertyTypeRequired
multipliernumber

The exact payout multiplier to be hit.

forceMaxWin

PropertyTypeRequired
forceMaxWinboolean

Whether a max win should be simulated.

forceFreespins

PropertyTypeRequired
forceFreespinsboolean

Whether free spins should be simulated.

Information

Setting forceFreespins alone won't do anything. It's up to you to include logic in your game implementation to check whether ctx.state.currentResultSet.forceFreespins is true and draw the board accordingly.

userData

PropertyTypeRequired
userDataRecord<string, any>

Additional data that can be used in a custom evaluation function or in your game loop.

evaluate

PropertyTypeRequired
evaluate(ctx: GameContext) => boolean

Inject custom checks into the simulation evaluation logic. Use this to check for free spins that upgraded to super free spins or any other arbitrary simulation criteria not supported in the core.

For example, you can use the userData from the result set to handle different game behavior. Then in your game implementation, set the state accordingly to ensure the simulation passes the criteria.

new ResultSet({
  criteria: "freespinsUpgradeToSuper",
  quota: 0.01,
  forceFreespins: true,
  reelWeights: { /* ... */ },
  userData: { upgradeFreespins: true }, 
  evaluate: freeSpinsUpgradeEvaluation, 
}),

reelWeights

PropertyTypeRequired
reelWeightsRecord<string, mixed>yes

Configure the weights of the reels in this ResultSet.

If you need to support dynamic / special reel weights based on the simulation context, you can provide an evaluate function that returns the desired weights.

If the evaluate function returns a falsy value, the usual spin type based weights will be used.

Example 1

During the first spin of a "freespins" simulation (when scatters trigger FS), the reels with the ID base1 will always be used to draw the board.

During free spins, one of two possible reels will be picked by doing a weighted draw (bonus1: 75%, bonus2: 25%).

new ResultSet({
  criteria: "freespins",
  quota: 1,
  forceFreespins: true,
  reelWeights: {
    [SPIN_TYPE.BASE_GAME]: { base1: 1 },
    [SPIN_TYPE.FREE_SPINS]: { bonus1: 3, bonus2: 1 },
  },
}),

Example 2

Similar to the first example, but the evaluate function runs first and tells the program to use the superbonus reels after super free spins have been triggered.

new ResultSet<UserStateType>({ // Add your custom state as a type parameter ...
  criteria: "superFreespins",
  quota: 0.01,
  forceFreespins: true,
  reelWeights: {
    [SPIN_TYPE.BASE_GAME]: { base1: 1 },
    [SPIN_TYPE.FREE_SPINS]: { bonus1: 3, bonus2: 1 },
    evaluate: (ctx) => {
      if (ctx.state.userData.triggeredSuperFreespins) { // ... for type-safety!
        return { superbonus: 1 }
      }
    },
  },
  userData: { forceSuperFreespins: true },
}),

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.

On this page