Skip to content

Frogger Game Responses

This guide explains how to read the scenario field returned by placeBet for a frogger-style (chicken-run / crash-walk) game, and 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 progresses through steps — at each step they can cash out or continue. Surviving a step multiplies their cashout value; dying 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 where the entry's win is the absolute cashout multiplier. The player's choice to continue 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
}

Step 0 — Initial Bet

The first placeBet call places the initial bet. It must include a featureToBuy selecting the risk level (see Risk Selection via Buy Features). The result tells you whether the player survived entry:

typescript
const response = await placeBet({ 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 surviving, the game is in progress (engineData.inProgress === true). The player chooses to:

  1. Collect — cash out at the current multiplier
  2. Continue — wager their current cashout for a higher multiplier
typescript
// Player wants to continue — place a wager bet
const wagerResponse = await placeBet({
  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 response's scenario is enriched with two extra fields on top of the usual { risk, step, alive }:

typescript
interface IFroggerCollectScenario {
  risk: string;
  step: number; // Step the player cashed out on
  alive: boolean;
  /**
   * 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 — a "what would have happened" readout for the UI (e.g. "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 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, useful for rendering "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 target RTP (e.g. 95%) is applied entirely at step 0 — the initial bet. After that, every wager step is a fair game.

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

StepCashoutSurvive chanceCumulative RTP
0 (entry)47.5%
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)

The 5% house edge comes from step 0 being slightly harder than fair. Once the player survives entry, every subsequent wager is neutral — the player isn't fighting the house anymore, just 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 can't gain an advantage by always cashing out early or always going deep — the game is balanced at every step.

With multiplier rounding

When multiplier rounding is enabled, the cashout values at each step are rounded to clean numbers (e.g. 2x, 4x, 8x instead of 1.97x, 3.88x, 7.65x). Two things are adjusted automatically to maintain the target RTP:

  • 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 must include a featureToBuy; a call without one will be rejected 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({
  backendURL,
  token,
  stake,
  featureToBuy: featureId,
});

featureToBuy is required only on the opening bet; the wager-stake continuations (step 1, step 2, …) 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 betplaceBet with featureToBuy for risk selection (required)
  2. Check result — if alive, show cashout offer; if dead, round is over
  3. Player decides — collect (end round) or continue (call 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;
}

// 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({
  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({ 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, and there are no mid-round player choices the seeds don't 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: 'frogger_medium',   // the round's risk tier
});

No actions[] — each hop chains automatically from the previous step's nextFeature; the verify path runs the same cascade.

Reading the response

  • rngData — one int row per hop (each entry draw). Array-equal against the live pf.rngData.
  • steps[i] — the IGameResult after hop i, including scenario.currentMultiplier. Compare against the live continuation chain.
  • steps[steps.length - 1].totalWin — the final cashout multiplier the round terminated at (or 0 if the frog died).

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

Next Steps