Skip to content

Frogger Game Responses ​

Apply the shared frontend requirements for loading, stakes, operator events, jurisdiction controls, and recovery.

This guide explains how to read the scenario field that placeBet returns for a frogger-style (chicken-run / crash-walk) game. It also explains how to use the loadConfig data to display cashout multipliers.

Overview ​

Frogger is a multi-step wager game. The player places an initial bet at a chosen risk level. Then the player progresses through steps. At each step, the player can cash out or continue. If the player survives a step, the step multiplies the cashout value. If the player dies, the player loses everything. Each risk level has its own wager multiplier and cashout progression.

The game uses wager stake features (wagerStakeFeatures). Each step is a separate feature. The entry's win is the absolute cashout multiplier for that step. If the player chooses to continue, the engine triggers the next step's feature automatically.

The Frogger Scenario ​

When placeBet returns a frogger result, gameResult.scenario contains:

typescript
interface IFroggerScenario {
  step: number; // Current step index (0 = initial bet)
  risk: string; // Risk level name: "low" | "medium" | "high" | "extreme"
  alive: boolean; // Whether the player survived this step
  cashoutMultiplier?: number; // Cashout value if the player collects here
}

cashoutMultiplier is present only on a surviving entry. A death entry is { step, risk, alive: false } and carries no multiplier.

Step 0: Initial Bet ​

The first placeBet call places the initial bet. This call must include a featureToBuy field that selects the risk level (see Risk Selection via Buy Features). The result shows whether the player survived entry:

typescript
const response = await placeBet({ config, backendURL, token, stake, featureToBuy: `risk_${selectedRisk}` });
if (response.success) {
  const { scenario, engineData, totalWin } = response.result.result;
  const frogger = scenario as IFroggerScenario;

  if (frogger.alive) {
    // Player survived - show cashout offer
    console.log(`Cashout available: ${frogger.cashoutMultiplier}x`);
    // engineData.inProgress === true - player must wager or collect
  } else {
    // Instant death - player loses their bet
    console.log('Died on entry');
    // engineData.inProgress === false - round is over
  }
}

Subsequent Steps: Wager or Collect ​

After the player survives, the game is in progress (engineData.inProgress === true). The player chooses one of these options:

  1. Collect: cash out at the current multiplier
  2. Continue: wager the current cashout for a higher multiplier
typescript
// Player wants to continue - place a wager bet
const wagerResponse = await placeBet({
  config,
  backendURL,
  token,
  stake, // Same stake as the original bet
});

if (wagerResponse.success) {
  const { scenario, engineData, totalWin } = wagerResponse.result.result;
  const frogger = scenario as IFroggerScenario;

  if (frogger.alive) {
    // Survived - new cashout multiplier available
    console.log(`Step ${frogger.step}: ${frogger.cashoutMultiplier}x`);

    if (!engineData.inProgress) {
      // Final step - auto-cashout
      console.log(`Auto-cashout: ${totalWin}`);
    }
  } else {
    // Died - everything is lost
    console.log(`Busted at step ${frogger.step}`);
    // engineData.inProgress === false
  }
}

To collect instead of continuing, use the collect/cashout mechanism rather than calling placeBet again.

Collect Response: deathStep and maxSteps ​

When the player collects, the engine copies the last step's scenario and adds two extra fields to it:

typescript
interface IFroggerCollectScenario {
  risk: string;
  step: number; // Step the player cashed out on
  alive: boolean;
  cashoutMultiplier?: number; // carried over from the collected step
  /**
   * The step the player *would have* busted on had they kept going -
   * `null` if the simulated run would have survived to the final step.
   */
  deathStep: number | null;
  /** Maximum steps configured for this frogger table. */
  maxSteps: number;
}

deathStep is purely cosmetic / informational. It is a "what would have happened" readout for the UI (for example, "You cashed out at step 3. You would have busted on step 5!"). It is:

  • Simulated with Math.random(), not certified RNG.
  • Not part of certified gameplay. It does not affect the payout or the audit log.
  • null when the simulated run would have reached the final step without busting.

maxSteps is the total number of progression steps configured for the table. Use it to render "cashed out at N of M" style displays.

typescript
const collectResponse = await collect({ backendURL, token });
if (collectResponse.success) {
  const { scenario, totalWin } = collectResponse.result.result;
  const frogger = scenario as IFroggerCollectScenario;

  if (frogger.deathStep === null) {
    showMessage(`Cashed out at step ${frogger.step}/${frogger.maxSteps} - you would have survived!`);
  } else {
    showMessage(`Cashed out at step ${frogger.step}/${frogger.maxSteps} - you would have busted on step ${frogger.deathStep}.`);
  }
}

Note: deathStep and maxSteps are only present on the collect response. The placeBet scenario during normal play contains { risk, step, alive, cashoutMultiplier }.

Cashout Multipliers from loadConfig ​

The loadConfig response includes the full cashout progression for each risk level. Use this to render the step ladder in your UI before the player starts.

typescript
const configResponse = await loadConfig({ backendURL, token });
if (configResponse.success) {
  const lc = configResponse.result.config;

  // lc.maxSteps - maximum number of progression steps
  // lc.risks - array of { name, steps, multipliers }
  //   where multipliers is the cashout values for that risk per step
  //   (index 0 is always 0, index N is the cashout multiplier at step N)

  const risks = lc.risks as Array<{ name: string; steps: number; multipliers: number[] }>;

  // risks[0] → { name: "low",     steps: 10, multipliers: [0, 1.25, 1.5, 2, 2.5, 3, ...] }
  // risks[3] → { name: "extreme", steps: 5,  multipliers: [0, 3, 9, 27, 81, 243] }
}

Rendering the Step Ladder ​

typescript
function renderStepLadder(risk: { name: string; multipliers: number[] }) {
  // multipliers[0] = 0 (step 0 has no cashout)
  // multipliers[1] = cashout after surviving step 0
  // multipliers[N] = cashout after surviving step N-1
  for (let step = 1; step < risk.multipliers.length; step++) {
    renderStep(step, `${risk.multipliers[step]}x`);
  }
}

How RTP Works in Frogger ​

The house edge is taken once, on entry ​

The engine applies the target RTP (for example, 95%) entirely at step 0, the initial bet. After that, every wager step is a fair game.

Here is a concrete example with a 2x wager multiplier and 95% target RTP:

StepCashoutSurvive chanceCumulative RTP
0 (entry)N/A47.5%N/A
12x50%95%
24x50%95%
38x50%95%
416x50%95%
  • Step 0 survive chance = 95% ÷ 2.0 = 47.5% (slightly below the "fair" 50%)
  • Steps 1+ survive chance = 50% (exactly fair, a coin flip for double-or-nothing)

Step 0 is slightly harder than fair. This creates the 5% house edge. Once the player survives entry, every subsequent wager is neutral. The player no longer fights the house, only the odds.

Why it's the same RTP at every cashout point ​

No matter which step the player decides to cash out at, their expected return on the original bet is always 95%:

  • Cash out at step 1: 47.5% × 2x = 0.95x
  • Cash out at step 2: 47.5% × 50% × 4x = 0.95x
  • Cash out at step 5: 47.5% × 50%⁴ × 32x = 0.95x

This means players cannot gain an advantage by always cashing out early or by always going deep. The game is balanced at every step.

With multiplier rounding ​

When multiplier rounding is enabled, the builder rounds the cashout values at each step to clean numbers (for example, 2x, 4x, 8x instead of 1.97x, 3.88x, 7.65x). This happens once, when the game is generated. The builder then derives the survive weights from the rounded values, so the target RTP stays exact. Two things follow from this:

  • Survive weights at each step may vary slightly from the "fair" rate.
  • Per-step win values are absolute cashout multipliers (e.g. 1.25, 1.5, 2.0), not relative ratios. This avoids compounding rounding errors across many steps. The engine uses wagerStakeFeatures mode. Each entry's win IS the cashout multiplier at that step, applied directly to the stake.

Risk Selection via Buy Features ​

Every risk level is a buy feature. There is no default basegame. Every opening placeBet call must include a featureToBuy field. The engine rejects a call without one with PARAMETERMISSING ("This game requires a buy feature. Provide featureToBuy.").

typescript
import { placeBet } from '@hizi.io/engine-sdk';

// Buy features are named like "risk_low", "risk_medium", "risk_high", "risk_extreme"
const featureId = `risk_${selectedRisk}`;

const response = await placeBet({
  config,
  backendURL,
  token,
  stake,
  featureToBuy: featureId,
});

featureToBuy is required only on the opening bet. The wager-stake continuations (step 1, step 2, and so on) are plain placeBet calls. The engine tracks the active feature across the round.

Multi-Step Game Flow ​

Frogger is a multi-step wager game. The typical flow:

  1. Initial bet: call placeBet with featureToBuy for risk selection (required)
  2. Check result: if alive, show the cashout offer; if dead, the round is over
  3. Player decides: collect to end the round, or continue by calling placeBet again
  4. Repeat until the player collects, dies, or reaches the final step (auto-cashout)
placeBet (step 0) → alive? → placeBet (step 1) → alive? → ... → collect or bust
                  → dead? → round over

engineData.inProgress is true while the player has an active cashout to wager or collect. It becomes false when the player dies, collects, or hits the auto-cashout at the final step.

Complete Example ​

typescript
import { login, loadConfig, placeBet } from '@hizi.io/engine-sdk';

interface IFroggerScenario {
  step: number;
  risk: string;
  alive: boolean;
  cashoutMultiplier?: number; // absent on a death entry
}

// After login and loadConfig...
const config = configResponse.result.config;
const risks = config.risks as Array<{ name: string; steps: number; multipliers: number[] }>;

// Player selects "high" risk
const selectedRisk = 'high';
const featureId = `risk_${selectedRisk}`;

// Show the step ladder
const risk = risks.find(r => r.name === selectedRisk)!;
for (let i = 1; i < risk.multipliers.length; i++) {
  renderStep(i, `${risk.multipliers[i]}x`);
}

// Place initial bet with buy feature
let response = await placeBet({
  config,
  backendURL,
  token,
  stake,
  featureToBuy: featureId,
});

// Game loop
while (response.success) {
  const { scenario, engineData, totalWin } = response.result.result;
  const frogger = scenario as IFroggerScenario;

  if (!frogger.alive) {
    // Player died
    showBust(frogger.step);
    break;
  }

  if (!engineData.inProgress) {
    // Auto-cashout at final step
    showWin(totalWin * stake);
    break;
  }

  // Show cashout offer and wait for player decision
  const playerChoice = await showCashoutOffer(frogger.cashoutMultiplier);

  if (playerChoice === 'collect') {
    // Player collects - end the round
    showWin(totalWin * stake);
    break;
  }

  // Player continues - place next wager
  response = await placeBet({ config, backendURL, token, stake });
}

Provably Fair Verification ​

Frogger sits on top of the fixed-odds pipeline. Each hop is a weighted entry draw. The wager-stake cascade is deterministic from the entries. There are no mid-round player choices that the seeds do not already imply. Use pfVerify to replay every hop from the committed seeds.

Request ​

typescript
import { pfVerify } from '@hizi.io/engine-sdk';

const reply = await pfVerify({
  backendURL,
  token,
  serverSeed: pf.revealedServerSeed,
  clientSeed: pf.clientSeed,
  stake: 100,
  featureToBuy: 'risk_medium',   // the round's risk tier
  actions: [{ playerChoiceIndex: 0 }, { playerChoiceIndex: 0 }], // one per continuation hop
});

actions drives the replay. Pass one { playerChoiceIndex: 0 } for every continuation hop the live round played. The verifier chains each hop from the previous step's nextFeature, so the index value itself is ignored. Frogger surfaces no playerChoice. Without actions the reply contains the opening step only.

Reading the response ​

  • rngData: one int row per hop (each entry draw). Compare it array-equal against the live pf.rngData.
  • steps[i]: the IGameResult after hop i, including scenario.cashoutMultiplier. steps[0] is the opening bet. Compare steps[i] against the live round's step i, in order. Send enough actions to make the two chains the same length.
  • steps[steps.length - 1].totalWin: the final cashout multiplier at the end of the round (or 0 if the frog died).

The deathStep value shown by the live UI at collect time is not part of pfVerify's output. It is Math.random()-driven cosmetic enrichment in frogger.collect, not PF-derived.

Next Steps ​