Skip to content

Response Handling

This guide explains the structure of game results returned by placeBet and how to handle different game scenarios.

The IGameResult Structure

Every placeBet response contains an IGameResult object at response.result.result:

typescript
const response = await placeBet({ backendURL, token: sessionToken, stake });
if (response.success) {
  const gameResult: IGameResult = response.result.result;
}

IGameResult has three fields:

typescript
interface IGameResult {
  // Your scenario data - whatever was stored during generation
  scenario: Record<string, unknown>;

  // Engine state tracking
  engineData: {
    // Fixed-odds (entries-DB) games only — omitted by rules-engine games
    // such as blackjack and crash.
    entryIndex?: number;
    scenarioInfo?: IScenarioInfo;
    spinInfo?: ISpinInfo[];
    playerChoice?: TPlayerChoiceFeatureAward[];
    currentFeature?: string;
    nextFeature?: string;
    progressionCounters?: Record<string, number>;
    canCollect?: boolean;
    inProgress: boolean;
  };

  // Cumulative win amount (as a multiplier of stake)
  totalWin: number;
}

The scenario Field

The scenario field contains whatever scenario data you stored when calling addResult() in hizi engine generator. Its shape is entirely game-specific.

For example, if your generator stored:

typescript
await generator.addResult(
  {
    visibleSymbols: [
      [1, 2, 3],
      [4, 5, 6],
      [7, 8, 9],
    ],
    reelIndexes: [12, 45, 78],
    winLines: [{ line: 0, symbols: [1, 1, 1], payout: 10 }],
  },
  { feature: 'basegame', win },
);

Then gameResult.scenario will contain exactly that object at runtime:

typescript
const { visibleSymbols, reelIndexes, winLines } = gameResult.scenario as MyGameResult;

TIP

Define a TypeScript interface for your game's scenario data and cast gameResult.scenario to it. This gives you type safety for your game-specific fields.

The engineData Field

engineData tells you about the engine's internal state and what to do next.

inProgress

The most important field. When true, the round is not complete - you must call placeBet again to continue.

typescript
if (gameResult.engineData.inProgress) {
  // Round continues - call placeBet again to get the next result
  const next = await placeBet({ backendURL, token: sessionToken });
} else {
  // Round complete
  // Games without wager features: round auto-ends, winnings credited automatically
  // Games with wager features (wagerFeatures or wagerStakeFeatures): call collect() to cash out
}

scenarioInfo

Tracks multi-result scenario progression:

typescript
interface IScenarioInfo {
  scenarioIndex: number; // Which scenario was selected
  currentScenarioIndex: number; // Current step (0-based)
  inProgress: boolean; // More steps in this scenario?
}

Multi-result scenarios are created when you pass an array to addResult() in hizi engine generator:

typescript
// Generator side - creates a 3-step scenario
await generator.addResult(
  [
    { grid: step1Grid }, // Step 0
    { grid: step2Grid }, // Step 1
    { grid: step3Grid }, // Step 2 (final - win awarded here)
  ],
  { feature: 'basegame', win: totalWin },
);

At runtime, each placeBet call returns one step:

placeBet callscenarioInfo.currentScenarioIndexscenarioInfo.inProgresstotalWin
1st (initial)0true0
2nd1true0
3rd2falseEntry's win value

INFO

The win amount (totalWin) is only awarded on the final step of a scenario. Earlier steps return totalWin: 0.

spinInfo and nextFeature

These fields track free spins and bonus features. spinInfo is an array with one entry per feature awarded so far this round:

typescript
interface ISpinInfo {
  feature: string; // Feature name (e.g. 'freespin')
  count: number; // Total spins AWARDED for this feature (not remaining)
  used: number; // Spins already consumed (incl. the current spin)
}

count is the total awarded, not the remaining count

Remaining spins = count - used, and the feature run ends when used === count. count is the running total of spins awarded to the feature: it starts at the award size and grows when the feature retriggers or is re-entered (the engine increments the existing entry rather than replacing it). Don't add count + used — that double-counts.

nextFeature tells you which feature entries will be used for the next spin. When it's undefined and the scenario is complete, the round ends.

Example free spin flow:

StepnextFeaturespinInfoWhat happened
Base spin'freespin'[{feature: 'freespin', count: 10, used: 0}]Base spin triggered 10 free spins
Free spin 1'freespin'[{feature: 'freespin', count: 10, used: 1}]First free spin played
Free spin 2'freespin'[{feature: 'freespin', count: 10, used: 2}]Second free spin
............
Free spin 10undefined[{feature: 'freespin', count: 10, used: 10}]Last free spin - round ends

Where this data lives: engineData vs scenario

The flow above is the non-consolidated case, where the engine draws each feature spin at runtime and exposes progression on engineData (spinInfo, currentFeature, nextFeature).

For slots built with consolidated rounds (consolidateRounds), the whole round is one scenario array replayed step by step, so there are no runtime counters — the engine leaves engineData.spinInfo empty. The equivalent data is baked onto each scenario step instead: scenario.spinInfo, scenario.featureName (the current feature), and scenario.nextFeature. The count / used semantics are identical, so this is purely a question of where you read from.

Read it through a single accessor that prefers the scenario copy and falls back to engineData, so your code works regardless of how the game was built:

typescript
import type { IGameResult, ISpinInfo } from '@hizi.io/engine-sdk';

// Feature fields baked onto a slot scenario (subset of IResult from @hizi.io/slot-types).
interface SlotScenarioFeatureFields {
  featureName?: string;
  featureRoundId?: number;
  spinInfo?: ISpinInfo[];
  nextFeature?: string;
}

function featureProgress(result: IGameResult) {
  const s = result.scenario as SlotScenarioFeatureFields;
  const e = result.engineData;
  return {
    spinInfo: s.spinInfo ?? e.spinInfo, // ISpinInfo[] | undefined
    currentFeature: s.featureName ?? e.currentFeature, // undefined ⇒ base game
    nextFeature: s.nextFeature ?? e.nextFeature, // undefined ⇒ round ends after this spin
  };
}

const fp = featureProgress(gameResult);
if (fp.currentFeature) {
  const si = fp.spinInfo?.find(s => s.feature === fp.currentFeature);
  if (si) console.log(`${fp.currentFeature}: spin ${si.used} of ${si.count} (${si.count - si.used} left)`);
}

Retriggers vs re-entries

Because count is cumulative per feature, two separate runs of the same feature share one accumulating { count, used }. To scope a per-run "spin X of N" display, use the scenario's featureRoundId: a retrigger keeps the same featureRoundId and grows count; a re-entry increments featureRoundId.

playerChoice

When set, the player must make a selection before the game can continue. This is used with playerChoice type feature awards from hizi engine generator.

Each option has a count (number of spins) and feature (which feature entries to use):

typescript
if (gameResult.engineData.playerChoice) {
  // Present options to the player
  const options = gameResult.engineData.playerChoice;
  for (let i = 0; i < options.length; i++) {
    console.log(`Option ${i}: ${options[i].count} spins for feature: ${options[i].feature}`);
  }

  // After the player selects an option, send the choice index:
  const chosenIndex = 0; // player's selection
  const response = await placeBet({
    backendURL,
    token: sessionToken,
    playerChoiceIndex: chosenIndex,
  });
}

The engine merges the chosen award into the existing spinInfo (preserving any in-progress features), then starts playing the chosen feature's spins. After this, the round continues as normal - keep calling placeBet until inProgress is false.

The totalWin Field

totalWin is the cumulative win amount as a multiplier of stake. To get the currency value:

typescript
const winInCurrency = gameResult.totalWin * stakeAmount;

During multi-step rounds, totalWin accumulates across all steps. Each subsequent placeBet response adds to the running total.

Handling Common Game Features

Since gameResult.scenario is game-specific (whatever you stored in hizi engine generator), how you handle features depends on your scenario data structure. Here are common patterns:

Cascades / Tumbles

If your generator stores cascade data, multi-result scenarios work well:

typescript
// In your generator
await generator.addResult(
  [
    { grid: initialGrid, wins: initialWins }, // Initial spin
    { grid: afterCascadeGrid, wins: cascadeWins }, // After cascade
    { grid: afterSecondCascade, wins: secondCascadeWins }, // Second cascade
  ],
  { feature: 'basegame', win: totalWin },
);

At runtime, each placeBet call returns one cascade step. Use scenarioInfo.inProgress to know if more cascades are coming.

Free Spins

Free spins use featureAwards in hizi engine generator. The engine handles them automatically:

typescript
// Check if entering free spins (use the unified accessor — see "Where this data lives")
const fp = featureProgress(gameResult);
if (fp.nextFeature === 'freespin') {
  const spinInfo = fp.spinInfo?.find(s => s.feature === 'freespin');
  if (spinInfo) console.log(`Free spins: ${spinInfo.used} / ${spinInfo.count}`); // count is the total awarded
}

Decision Flow

placeBet response received


engineData.canCollect && engineData.playerChoice?

    ├── yes → Gamble opportunity: present gamble options to player
    │         Player collects → collect({ backendURL, token })
    │         Player gambles → placeBet({ backendURL, token, playerChoiceIndex: N })
    │         └── Loop back to start

    └── no


   engineData.playerChoice?

        ├── yes → Present options to player
        │         Player selects option index
        │         Call placeBet({ backendURL, token, playerChoiceIndex: N })
        │         └── Loop back to start

        └── no


       engineData.inProgress?

            ├── true → Call placeBet({ backendURL, token })
            │          └── Loop back to start

            └── false → Round complete, winnings credited
                        Enable spin button

Next Steps