Appearance
Frogger Game Responses
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
}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({ 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:
- Collect: cash out at the current multiplier
- Continue: wager the 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 engine adds two extra fields to the response's scenario, 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. 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.
nullwhen 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:
| Step | Cashout | Survive chance | Cumulative RTP |
|---|---|---|---|
| 0 (entry) | N/A | 47.5% | N/A |
| 1 | 2x | 50% | 95% |
| 2 | 4x | 50% | 95% |
| 3 | 8x | 50% | 95% |
| 4 | 16x | 50% | 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 engine rounds the cashout values at each step to clean numbers (for example, 2x, 4x, 8x instead of 1.97x, 3.88x, 7.65x). The engine adjusts two things 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
wagerStakeFeaturesmode. 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({
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:
- Initial bet: call
placeBetwithfeatureToBuyfor risk selection (required) - Check result: if
alive, show the cashout offer; if dead, the round is over - Player decides: collect to end the round, or continue by calling
placeBetagain - 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 overengineData.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. 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: 'frogger_medium', // the round's risk tier
});There is no actions[] array. Each hop chains automatically from the previous step's nextFeature. The verify path runs the same cascade.
Reading the response
rngData: oneintrow per hop (each entry draw). Compare it array-equal against the livepf.rngData.steps[i]: theIGameResultafter hopi, includingscenario.currentMultiplier. Compare it against the live continuation chain.steps[steps.length - 1].totalWin: the final cashout multiplier at the end of the round (or0if 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
- Response Handling:
IGameResultstructure andengineDatafields. - Plinko Responses: reads plinko game responses.
- Buy Features: explains how buy features work.