Skip to content

Over/Under 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 an Over/Under game. It also explains how to use the loadConfig data to display odds and multipliers.

Overview ​

Over/Under is a single-roll wager game. The engine rolls a virtual 100-sided die (values 1–100). The player bets whether the result is over or under a chosen threshold.

  • Under T: wins if the roll is ≤ T. Win chance = T/100.
  • Over T: wins if the roll is > T. Win chance = (100 − T)/100.

The payout multiplier scales inversely with win probability: multiplier = RTP / P(win). RTP (Return to Player) is the average percentage of all stakes that the game pays back to players. Lower risk means smaller rewards. Higher risk means bigger wins.

Each bet is a buy feature (a bet that the player buys directly, in one step). The player selects a bet and places it in a single placeBet call. The game has no multi-step flow. It has no collect step.

The Over/Under Scenario ​

When placeBet returns an Over/Under result, gameResult.scenario contains:

typescript
interface IOverUnderScenario {
  roll: number;         // Die result: 1–100
  threshold: number;    // The chosen threshold
  betType: string;      // "under" or "over"
  won: boolean;         // Whether the bet was correct
  multiplier?: number;  // Win multiplier - present on wins
}

Game Flow ​

Single-Step: Place Bet and Roll ​

Each bet is a buy feature. The player picks a bet (over_75, under_30, etc.). The player calls placeBet with featureToBuy:

typescript
const response = await placeBet({
  config,
  backendURL,
  token,
  stake: 100,
  featureToBuy: 'over_75',
});

if (response.success) {
  const { scenario, totalWin } = response.result.result;
  const result = scenario as IOverUnderScenario;

  console.log(`Rolled: ${result.roll}`);

  if (result.won) {
    console.log(`Win! ${result.multiplier}x → payout ${totalWin}`);
  } else {
    console.log(`Loss`);
  }
}

TIP

The game resolves instantly. It has no collect step, no playerChoice, and no follow-up calls. One request produces one result.

Feature Naming ​

Buy features follow the pattern {direction}_{threshold}:

FeatureBetWin condition
under_1Under 1roll ≤ 1
over_1Over 1roll > 1
under_50Under 50roll ≤ 50
over_50Over 50roll > 50
under_99Under 99roll ≤ 99
over_99Over 99roll > 99

The game provides 198 bets (under_1 through under_99, over_1 through over_99) as buy features.

Loading Game Config ​

The loadConfig response provides the odds table:

typescript
const config = configResponse.result.config;

config.diceMax;  // 100 (die sides)
config.odds;     // Array of threshold odds

Using the Odds Table ​

typescript
interface IOverUnderOdds {
  threshold: number;       // 1–99
  pUnder: number;          // P(roll <= threshold)
  pOver: number;           // P(roll > threshold)
  underMultiplier: number; // Payout for correct "under" bet
  overMultiplier: number;  // Payout for correct "over" bet
}

const odds = config.odds as IOverUnderOdds[];

// Show odds for threshold 75
const t75 = odds[74]; // 0-indexed: threshold 75 is index 74
console.log(`Under 75: ${(t75.pUnder * 100).toFixed(0)}% → ${t75.underMultiplier}x`);
console.log(`Over 75: ${(t75.pOver * 100).toFixed(0)}% → ${t75.overMultiplier}x`);

How RTP Works ​

The rtpMode setting in the builder controls the house edge in two ways:

Adjust Multipliers (default) ​

This mode keeps natural win probabilities. It reduces the multiplier to reach the target RTP. The house edge is visible in the payout.

P(win) × multiplier = target RTP

BetP(win)Multiplier (98% RTP)EV
Under 5050%1.96x0.98
Over 5050%1.96x0.98
Under 9898%1.0x0.98
Over 982%49.0x0.98

Adjust Weights ​

This mode uses a fair multiplier (1/P). It reduces the win probability to reach the target RTP. The house edge stays hidden in the win rate.

adjusted P(win) × fair multiplier = target RTP

BetMultiplierAdjusted P(win)EV
Under 502.0x49%0.98
Over 502.0x49%0.98
Under 981.0204x96.04%0.98
Over 9850.0x1.96%0.98

Every bet has the same expected value, regardless of threshold or RTP mode. The player trades risk for reward.

Multiplier rounding ​

If you enable multiplier rounding, the builder snaps multipliers to clean numbers (e.g. 1x, 1.5x, 2x). The builder does this once, at generate time. In "Adjust Multipliers" mode, this may cause the actual RTP to deviate slightly from the target. In "Adjust Weights" mode, the builder adjusts weights to maintain the target RTP exactly.

Feature Structure ​

Over/Under uses standalone buy features. It has no basegame. Every placeBet call must include a featureToBuy. The engine rejects a call without one with PARAMETERMISSING ("This game requires a buy feature. Provide featureToBuy.").

under_50 (buy feature, resolves instantly)
  ├─ win entries (rolls 1–50): win = multiplier
  └─ lose entries (rolls 51–100): win = 0

over_75 (buy feature, resolves instantly)
  ├─ win entries (rolls 76–100): win = multiplier
  └─ lose entries (rolls 1–75): win = 0

Key points:

  • Every bet is a buy feature that featureToBuy triggers.
  • No wager features. Each feature resolves directly to a final win amount.
  • Win entries show specific roll values from the winning range for visual variety.
  • Lose entries show specific roll values from the losing range.
  • Single round. One placeBet call gives an immediate result.

Complete Example ​

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

interface IOverUnderScenario {
  roll: number;
  threshold: number;
  betType: string;
  won: boolean;
  multiplier?: number;
}

// After login and loadConfig...
const odds = config.odds;

// Player wants "Over 75" - show them the odds first
const t75 = odds[74];
console.log(`Over 75: ${(t75.pOver * 100).toFixed(0)}% chance → ${t75.overMultiplier}x`);

// Place bet and roll in one call
const response = await placeBet({
  config,
  backendURL,
  token,
  stake: 100,
  featureToBuy: 'over_75',
});

if (response.success) {
  const result = response.result.result.scenario as IOverUnderScenario;
  const totalWin = response.result.result.totalWin;

  if (result.won) {
    console.log(`Rolled ${result.roll} - Win! ${result.multiplier}x → payout ${totalWin}`);
  } else {
    console.log(`Rolled ${result.roll} - Loss`);
  }
}

Provably Fair Verification ​

Over/Under is one-shot. A single entry draw (the random number the engine generates for the round) fixes the roll. The threshold and direction (from featureToBuy) decide the payout. Use pfVerify to replay the draw.

Request ​

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

const reply = await pfVerify({
  backendURL,
  token,
  serverSeed: pf.revealedServerSeed,
  clientSeed: pf.clientSeed,
  stake: 100,
  featureToBuy: 'over_50',          // the round's threshold + direction package
});

Reading the response ​

This is one-shot. steps has one entry.

  • rngData: one int row (the entry draw). Compare it to the live pf.rngData. The arrays must be equal.
  • steps[0].scenario.roll: this value equals the live roll value.
  • steps[0].totalWin: this value equals the live multiplier.
typescript
if (!reply.success) return;
const { rngData, steps } = reply.result;
const rollOk = steps[0].scenario.roll === liveScenario.roll;
const winOk = steps[0].totalWin === liveResult.totalWin;

Next Steps ​