Skip to content

Over/Under Game Responses

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({
  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 engine snaps multipliers to clean numbers (e.g. 1x, 1.5x, 2x). In "Adjust Multipliers" mode, this may cause the actual RTP to deviate slightly from the target. In "Adjust Weights" mode, the engine 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({
  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