Skip to Content
Provably FairGame events

Game events

A game event is the last step of the chain: it takes floats from the generator and turns them into the result of a bet. This page gives the rule for every game, with code.

The examples all use the same revealed seeds as the worked example, with every game at its default settings:

serverSeed 8b6d6ac5c3b1b0f1e1f4a2e3d9c7b5a3f1e2d3c4b5a6978877665544332211ff clientSeed maktub-docs nonce 1

The first floats of this stream are 0.92299, 0.38355, 0.06895, 0.12514, 0.70865, 0.45205, 0.34114, 0.93119, 0.26380, 0.73987. Many games below read the same stream from cursor 0, so you will see these numbers again.

Conventions

  • Cursor and count. A game reads count floats starting at a byte cursor (4 bytes per float). The float number n, counting from 0, is at cursor 4n.
  • What the draw depends on. The draw is fixed by the seeds, the nonce and the game’s own settings (difficulty, number of mines, rows, and so on). It never depends on the bet amount. The house edge and the operator’s RTP preset change what an outcome pays, not which outcome it is. The exceptions are noted per game: Limbo, Pulse and Launch fold the house edge into the result itself.
  • Multipliers. Where a game pays from a table (risk, rows, difficulty, RTP preset), this page says which row of the table is read. Open the game’s paytable to see the table your operator uses. It already includes the operator’s house edge or RTP preset.
  • Numbering. The code below counts from 0. The Verify tab, like the game screens, counts tiles, lanes, steps and positions from 1, so a position 3 in the code is tile 4 on screen.
  • Rounding. “Floor to 2 decimals” is a decimal operation, not a binary-float one, so the snippets use decimal.js  (npm install decimal.js).
GameCursorFloats read
Dice0 (Lightning: 1)1 (Lightning: 11)
Limbo0 (Lightning: 1)1 (Lightning: 2)
Coin Flip0one per coin
Roulette01
Wheel01
Plinko and Wire0one per row
Keno0 (Lightning: 64)10 (Lightning: 5)
Mines0one per mine
Diamonds05
HiLo4 for the first card, then 8, 12, …1 per card
Blackjack0, 4, 8, … per card1 per card
Baccarat06
Video Poker010
Tower, Doors, Soccer, Bridge09 × safe tiles per row
Chicken0 (Lightning: 64)one per car (Lightning: 7)
Balloon and Stairs0one per hazard
Safe04
Spark05
Drop07
Defuse010
Volt01
Pulse01
Launch011
Crash, Slide and Doublesee its own page1

Shared helpers

The snippets below assume these helpers and the reference code saved as fairness.mjs. seeds is { serverSeed, clientSeed, nonce }.

import Decimal from "decimal.js"; import { generateFloats } from "./fairness.mjs"; // Partial Fisher–Yates shuffle. After k floats, slots[0..k-1] are k distinct // picks out of `size` slots. function partialShuffle(floats, size) { const slots = Array.from({ length: size }, (_, i) => i); floats.forEach((float, i) => { const j = i + Math.floor(float * (size - i)); [slots[i], slots[j]] = [slots[j], slots[i]]; }); return slots; } // Draws without replacement: each float picks from what is left of the pool. function drawFromPool(floats, pool) { const left = [...pool]; return floats.map((float, i) => { const remaining = left.length - i; const pick = Math.floor(float * remaining); const drawn = left[pick]; left[pick] = left[remaining - 1]; return drawn; }); } // One 52-card deck. Index = 4 × rank position + suit position. const RANKS = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]; const SUITS = ["D", "H", "S", "C"]; // diamonds, hearts, spades, clubs const DECK = RANKS.flatMap((rank) => SUITS.map((suit) => ({ rank, suit })));

The deck order is 2D 2H 2S 2C 3D 3H 3S 3C … KD KH KS KC AD AH AS AC, so index 0 is the 2 of diamonds and index 51 the ace of clubs. A card is written as rank then suit: 6C is the six of clubs.

Dice

One float, cursor 0. The roll is a number from 0.00 to 100.00:

const toRoll = (float) => new Decimal((float * 10001) / 100).toDecimalPlaces(2, Decimal.ROUND_DOWN).toNumber(); function diceRoll(seeds) { const [float] = generateFloats({ ...seeds, cursor: 0, count: 1 }); return toRoll(float); }

There are 10 001 possible rolls (0.00 to 100.00), each equally likely.

  • Over / under. With “under” a bet wins when roll < target; with “over” when roll > target.
  • Multiplier. The win chance is target for “under” and 100 - target for “over”. The multiplier is (100 / winChance) × (100 − houseEdge) / 100, truncated to 4 decimals. The house edge is the operator’s setting (default 1%, between 1% and 10%).

Lightning mode adds a second draw from cursor 1 with 11 floats: how many lightning numbers (2 to 5), where they sit (rolls from 0.00 to 100.00) and which multiplier (50, 100, 200, 300, 400 or 500) each carries. Duplicate positions are skipped.

const DICE_LIGHTNING = [50, 100, 200, 300, 400, 500]; function diceLightning(seeds) { const f = generateFloats({ ...seeds, cursor: 1, count: 11 }); const count = Math.floor(f[0] * 4) + 2; const seen = new Set(); const numbers = []; for (let i = 0; i < count; i++) { const number = toRoll(f[1 + i]); if (seen.has(number)) continue; seen.add(number); const pick = Math.min(Math.floor(f[6 + i] * DICE_LIGHTNING.length), DICE_LIGHTNING.length - 1); numbers.push({ number, multiplier: DICE_LIGHTNING[pick] }); } return numbers; }

In Lightning mode the normal multiplier is lowered by a fixed amount, and a winning roll that lands exactly on a lightning number pays that number’s multiplier instead (never less than the normal one).

Example. Float 0.922989992890507 gives a roll of 92.30.

Limbo

One float, cursor 0. The result is a multiplier, at least 1.00:

function limboResult(seeds, houseEdgePercent = 1) { const [float] = generateFloats({ ...seeds, cursor: 0, count: 1 }); const edge = Math.min(10, Math.max(1, houseEdgePercent)); const point = (1e8 / (float * 1e8)) * ((100 - edge) / 100); return Math.max(new Decimal(point).times(100).floor().div(100).toNumber(), 1); }

A bet wins when the target multiplier is at most the result. The house edge is part of the result: default 1%, an operator setting between 1% and 10%.

Lightning mode reads 2 floats from cursor 1. The first sets a threshold between 2× and 100×, the second picks a multiplier among 2, 3, 4, 5, 6 and 8:

function limboLightning(seeds) { const [a, b] = generateFloats({ ...seeds, cursor: 1, count: 2 }); const threshold = new Decimal(2).plus(new Decimal(a).times(98)).times(100).floor().div(100).toNumber(); const pool = [2, 3, 4, 5, 6, 8]; return { threshold, multiplier: pool[Math.min(Math.floor(b * pool.length), pool.length - 1)] }; }

Lightning is available for targets up to 100×. When the result reaches the threshold, a winning bet’s payout is multiplied by the lightning multiplier.

Example. (1 / 0.922989992890507) × 0.99 = 1.0726…, floored to 1.07×.

Coin Flip

One float per coin, from cursor 0. A float of 0.5 or more is heads, below is tails:

function coinFlips(seeds, coins) { return generateFloats({ ...seeds, cursor: 0, count: coins }).map((f) => (f >= 0.5 ? "heads" : "tails")); }

You choose a side and a minimum number of matches (minHeadTails, from 1 to the number of coins). The bet wins when at least that many coins show your side. The multiplier is 1 / P × (100 − houseEdge) / 100, truncated to 4 decimals, where P is the chance of that outcome (the binomial tail of fair coins) and the house edge is the operator’s setting (default 1%).

Example. With one coin, the first float 0.92299 is heads.

Roulette

One float, cursor 0. The pocket is a number from 0 to 36 (single zero):

function roulettePocket(seeds) { const [float] = generateFloats({ ...seeds, cursor: 0, count: 1 }); return Math.floor(float * 37); }

Bets are settled on the standard European layout: red is 1 3 5 7 9 12 14 16 18 19 21 23 25 27 30 32 34 36, black is every other number from 1 to 36, and 0 is green.

Example. floor(0.92299 × 37) = 34 (red).

Wheel

One float, cursor 0:

function wheelSegment(seeds, segments) { const [float] = generateFloats({ ...seeds, cursor: 0, count: 1 }); return Math.floor(float * segments); }

The segment index counts from 0. The multiplier is the entry at that index in the paytable for your risk (low, medium or high) and your number of segments (10, 20, 30, 40 or 50). The operator’s RTP preset, if any, swaps in its own table.

Example. At low risk with 10 segments, floor(0.92299 × 10) is segment 9, which pays 0×.

Plinko

One float per row, from cursor 0. Rows run from 8 to 16. For every row, a float of 0.5 or more sends the ball one step to the right, below sends it left. The bucket is the number of rights:

function plinko(seeds, rows) { const floats = generateFloats({ ...seeds, cursor: 0, count: rows }); const path = floats.map((f) => (f >= 0.5 ? "R" : "L")); return { path, bucket: path.filter((step) => step === "R").length }; }

Buckets count from the left, 0 to rows. The multiplier is the entry for that bucket in the paytable for your risk (low, medium or high) and rows, or in the operator’s RTP preset table.

When one action drops several balls, each ball has its own nonce (consecutive nonces, in order) and is drawn independently.

Example. At low risk with 16 rows, the 16 floats give the path R L L L R L L R L R L L L R L L: five rights, so bucket 5, which pays 1.2×.

Wire

Wire has the same draw as Plinko (one float per row, from cursor 0; a float of 0.5 or more counts as a step to the right) and pays from its own paytable. It uses the same plinko function above.

Example. At low risk with 16 rows: the same path, bucket 5, 1.2×.

Keno

Ten floats, cursor 0. Ten distinct numbers from 1 to 40 are drawn without replacement. Each float picks from the numbers still left:

function kenoDraw(seeds) { const floats = generateFloats({ ...seeds, cursor: 0, count: 10 }); const squares = Array.from({ length: 40 }, (_, i) => i + 1); return floats.map((float, i) => squares.splice(Math.floor(float * (40 - i)), 1)[0]); }

You pick 1 to 10 numbers. The number of hits is how many of your picks are in the draw. The multiplier is the entry for your risk (classic, low, medium or high), your number of picks and your hits, or the same entry in the RTP preset table. The order in which numbers are drawn does not matter for the payout.

Lightning mode draws its tiles from a separate part of the stream, cursor 64 (the first byte of HMAC round 2), so they are independent of the draw. It reads 5 floats: how many tiles (1 or 2), their numbers (1 to 40) and their multipliers (2, 3, 4, 5, 6, 8 or 100). Duplicate tiles are skipped.

const KENO_LIGHTNING = [2, 3, 4, 5, 6, 8, 100]; function kenoLightning(seeds) { const f = generateFloats({ ...seeds, cursor: 64, count: 5 }); const count = Math.floor(f[0] * 2) + 1; const seen = new Set(); const tiles = []; for (let i = 0; i < count; i++) { const number = Math.floor(f[1 + i] * 40) + 1; if (seen.has(number)) continue; seen.add(number); const pick = Math.min(Math.floor(f[3 + i] * KENO_LIGHTNING.length), KENO_LIGHTNING.length - 1); tiles.push({ number, multiplier: KENO_LIGHTNING[pick] }); } return tiles; }

Example. The draw is 37, 15, 3, 6, 29, 19, 14, 38, 11, 30.

Mines

One float per mine, cursor 0. The mines are the first positions of a partial Fisher–Yates shuffle of the 25 tiles:

function minesPositions(seeds, mines) { const floats = generateFloats({ ...seeds, cursor: 0, count: mines }); return partialShuffle(floats, 25).slice(0, mines); // tile indexes, 0 to 24 }

The board is 5 × 5. Tiles are numbered from 1, left to right and top to bottom, so tile index 0 is tile 1 (top left) and index 24 is tile 25 (bottom right). With more mines, the shuffle simply reads more floats, so the first mines stay in place: the positions for 3 mines are the first three of the positions for 5.

The multiplier after each safe pick follows from the number of mines and picks and the operator’s house edge. The game’s paytable shows it.

Example. With 3 mines the mines are on tiles 24, 11 and 4.

Diamonds

Five floats, cursor 0. Each is a gem of one of seven colors:

const GEMS = ["yellow", "pink", "blue", "green", "red", "cyan", "purple"]; function diamondsGems(seeds) { return generateFloats({ ...seeds, cursor: 0, count: 5 }).map((f) => GEMS[Math.floor(f * 7)]); } function diamondsCombination(gems) { const counts = {}; for (const gem of gems) counts[gem] = (counts[gem] ?? 0) + 1; const [a = 0, b = 0] = Object.values(counts).sort((x, y) => y - x); if (a === 5) return "fiveOfAKind"; if (a === 4) return "fourOfAKind"; if (a === 3 && b === 2) return "fullHouse"; if (a === 3) return "threeOfAKind"; if (a === 2 && b === 2) return "twoPairs"; if (a === 2) return "pair"; return "zeroMatch"; }

The multiplier is the entry for the combination in the Diamonds paytable, or in the operator’s RTP preset table.

Example. The gems are purple, blue, yellow, yellow, red: a pair, which pays 0.1×.

HiLo

You choose the start card yourself. Each guess then draws one card, with replacement, so the same rank can come up again. The n-th card drawn in a round (n = 1 for the first guess) reads the float at cursor 4n; cursor 0 is not used by the round:

function hiloCard(seeds, n) { const [float] = generateFloats({ ...seeds, cursor: 4 * n, count: 1 }); return DECK[Math.floor(float * 52)]; }

Ranks compare as A = 1, 2 … 10, J = 11, Q = 12, K = 13. A guess wins when the new card is higher, lower, equal, higher-or-equal or lower-or-equal than the previous card, as chosen. Skipping a card also uses up the next draw. Each correct guess multiplies the payout by a factor derived from the probability of that guess and the house edge. The multiplier shown for each choice is the one that applies.

Example. The first ten cards after your start card are 6C 2C 3S JD 7C 6H AD 5H JS 6C.

Blackjack

Blackjack is dealt from six decks (312 cards) without replacement. The i-th card of the round (counting from 0) reads the float at cursor 4i and picks from the cards still in the shoe:

function blackjackShoe(seeds, cards) { const floats = generateFloats({ ...seeds, cursor: 0, count: cards }); const shoe = Array.from({ length: 312 }, (_, i) => i % 52); // six decks of DECK indexes return drawFromPool(floats, shoe).map((index) => DECK[index]); }
  • Cards 1 and 2 are the player’s, card 3 is the dealer’s up card.
  • Card 4 is always the dealer’s second card.
  • Every further card (hits, doubles, split hands, dealer draws) takes the next position in the shoe, in the order it is dealt.

Because the shoe is dealt without replacement, a card at position i can depend on the earlier draws, so to verify a late card you need to compute all the earlier positions, as blackjackShoe does.

Example. The shoe starts 8C 5C 7H JS 4S 10S 2D 8D 9D 6D 4D AC: the player holds 8♣ 5♣, and the dealer shows 7♥ (second card J♠).

Baccarat

Six floats, cursor 0. Each float is one card, DECK[floor(float × 52)]; the cards are independent draws (not taken from a shoe):

  • Cards 1 and 2 are the Player’s, cards 3 and 4 the Banker’s.
  • Card 5 is the Player’s third card, if the Player draws.
  • Card 6 is the Banker’s third card, if the Banker draws.
const baccaratValue = (card) => card.rank === "A" ? 1 : ["10", "J", "Q", "K"].includes(card.rank) ? 0 : Number(card.rank); const handValue = (cards) => cards.reduce((sum, card) => sum + baccaratValue(card), 0) % 10; function bankerDraws(bankerValue, playerThird) { if (playerThird === null) return bankerValue <= 5; switch (bankerValue) { case 0: case 1: case 2: return true; case 3: return playerThird !== 8; case 4: return playerThird >= 2 && playerThird <= 7; case 5: return playerThird >= 4 && playerThird <= 7; case 6: return playerThird === 6 || playerThird === 7; default: return false; } } function playBaccarat(seeds) { const cards = generateFloats({ ...seeds, cursor: 0, count: 6 }).map((f) => DECK[Math.floor(f * 52)]); const player = [cards[0], cards[1]]; const banker = [cards[2], cards[3]]; let playerValue = handValue(player); let bankerValue = handValue(banker); let playerThird = null; if (playerValue < 8 && bankerValue < 8) { // a natural 8 or 9 ends the hand if (playerValue <= 5) { player.push(cards[4]); playerThird = baccaratValue(cards[4]); playerValue = handValue(player); } if (bankerDraws(bankerValue, playerThird)) { banker.push(cards[5]); bankerValue = handValue(banker); } } const winner = playerValue > bankerValue ? "player" : bankerValue > playerValue ? "banker" : "tie"; return { player, banker, playerValue, bankerValue, winner }; }

Aces count 1, tens and face cards 0, and a hand is worth its total modulo 10. Card 6 is the Banker’s third card even when the Player did not draw a third card. The multiplier for Player, Banker and Tie comes from the game’s paytable (or the RTP preset). Player and Banker push on a tie.

Example. The six cards are KC 6C 2C 3S JD 7C. The Player holds K♣ 6♣ (6), so stands. The Banker holds 2♣ 3♠ (5) and draws card 6, 7♣, for 2. Player wins, 6 to 2.

Video Poker

Ten floats, cursor 0. The ten cards come from one 52-card deck without replacement:

function videoPokerCards(seeds) { const floats = generateFloats({ ...seeds, cursor: 0, count: 10 }); const deck = Array.from({ length: 52 }, (_, i) => i); return drawFromPool(floats, deck).map((index) => DECK[index]); }
  • Cards 1 to 5 are the deal.
  • If you discard the card in position n (1 to 5), it is replaced by card 5 + n. Held cards stay where they are.

The final hand is then ranked and paid from the paytable. All ten cards exist at the deal, so the replacements are fixed before you decide what to hold.

Example. The deal is KC 6C 2C 3S 10S; the replacement cards 6 to 10 are 7H 5C QH 4C 9C. Discarding the second card (6♣) would give 5 + 2 = card 7, which is 5♣.

Tower

Nine rows, each with a number of positions and some safe ones, depending on the difficulty. Per row, the safe positions are drawn with the same partial shuffle as Mines, from 9 × safe per row floats read from cursor 0: floats 0 to safe − 1 for row 1, the next safe floats for row 2, and so on.

DifficultyPositions per rowSafe per row
easy43
medium32
hard21
expert31
master41
const LADDER = { easy: { positions: 4, safe: 3 }, medium: { positions: 3, safe: 2 }, hard: { positions: 2, safe: 1 }, expert: { positions: 3, safe: 1 }, master: { positions: 4, safe: 1 }, }; function ladderRows(seeds, difficulty) { const { positions, safe } = LADDER[difficulty]; const floats = generateFloats({ ...seeds, cursor: 0, count: 9 * safe }); return Array.from({ length: 9 }, (_, row) => partialShuffle(floats.slice(row * safe, (row + 1) * safe), positions) .slice(0, safe) .sort((a, b) => a - b), ); }

Row 1 is the bottom row, and positions count from the left. Each row lists the safe positions; every other position ends the round. The multiplier after each row is the one in the paytable for your difficulty.

Example. On easy the safe positions, row 1 to row 9, are:

row 1: 2, 3, 4 row 4: 1, 2, 3 row 7: 2, 3, 4 row 2: 1, 3, 4 row 5: 2, 3, 4 row 8: 1, 2, 3 row 3: 2, 3, 4 row 6: 1, 2, 4 row 9: 1, 2, 3

Doors

Doors uses exactly the draw, the difficulty table and the ladderRows function of Tower: nine rows, safe doors per row, row 1 first and positions counted from the left.

Example. On easy the safe doors are the same as the Tower example above, since the draw is identical.

Soccer

Soccer uses exactly the draw, the difficulty table and the ladderRows function of Tower: nine rows, safe positions per row, row 1 first and positions counted from the left.

Example. On easy the safe positions are the same as the Tower example above.

Bridge

Bridge is read the same way as Tower, with glasses instead of tiles: nine rows, each with a number of glasses and some safe ones. The difficulty table is the same numbers (glasses per row, safe per row), and ladderRows computes it. Row 1 is the first step and glasses count from the left.

Example. On easy the safe glasses are the same as the Tower example above.

Chicken

The road has 20 slots. A number of them hide a car, according to the difficulty: easy 1, medium 3, hard 5, expert 10. The cars are drawn without replacement with the Mines shuffle, one float per car from cursor 0:

const CARS = { easy: 1, medium: 3, hard: 5, expert: 10 }; function chickenCars(seeds, difficulty) { const cars = CARS[difficulty]; const floats = generateFloats({ ...seeds, cursor: 0, count: cars }); return partialShuffle(floats, 20).slice(0, cars).sort((a, b) => a - b); // lane indexes, 0 to 19 }

The chicken crosses the lanes in order and the round ends on the first car. A difficulty offers one jump for every car-free slot (19, 17, 15 or 10 jumps), and the multiplier after k jumps is the one in the paytable for your difficulty. Lanes are numbered from 1 on screen.

Lightning mode reads 7 floats from cursor 64, a separate part of the stream from the cars. It places 1 or 2 lightning bands on the crossable lanes with a multiplier of 2, 3 or 5:

const CHICKEN_LIGHTNING = [2, 3, 5]; function chickenLightning(seeds, difficulty) { const jumps = 20 - CARS[difficulty]; const f = generateFloats({ ...seeds, cursor: 64, count: 7 }); const count = Math.floor(f[0] * 2) + 1; const seen = new Set(); const bands = []; for (let i = 0; i < count; i++) { const row = Math.floor(f[1 + i] * jumps); if (seen.has(row)) continue; seen.add(row); const pick = Math.min(Math.floor(f[3 + i] * CHICKEN_LIGHTNING.length), CHICKEN_LIGHTNING.length - 1); bands.push({ row, multiplier: CHICKEN_LIGHTNING[pick] }); } return bands; // row is 0-based; the game shows row + 1 }

Example. On easy the single car is in lane 19.

Balloon

The board is 25 steps, and the balloon pops on hazard steps. The number of hazards depends on the difficulty: easy 1, medium 3, hard 6, expert 9. They are the first positions of the Mines shuffle:

const HAZARDS = { easy: 1, medium: 3, hard: 6, expert: 9 }; function hazardSteps(seeds, difficulty) { const hazards = HAZARDS[difficulty]; const floats = generateFloats({ ...seeds, cursor: 0, count: hazards }); return partialShuffle(floats, 25).slice(0, hazards); // step indexes, 0 to 24 }

Each pump is a step; the balloon pops on pump s when step s is a hazard. Steps count from 1 on screen. The multiplier is the one in the paytable for your difficulty.

Example. On easy the balloon pops on pump 24.

Stairs

Stairs is drawn exactly like Balloon: 25 steps and the same number of hazards per difficulty (easy 1, medium 3, hard 6, expert 9), using hazardSteps. A hazard step is a step that drops you.

Example. On easy the stairs fall on step 24.

Safe

Four discs, one float each from cursor 0. A disc is golden when its float is below the difficulty’s chance:

DifficultyChance of a golden disc
easy0.30
medium0.25
hard0.20
expert0.15
const SAFE_CHANCE = { easy: 0.3, medium: 0.25, hard: 0.2, expert: 0.15 }; function safeDiscs(seeds, difficulty) { const discs = generateFloats({ ...seeds, cursor: 0, count: 4 }).map((f) => f < SAFE_CHANCE[difficulty]); return { discs, golden: discs.filter(Boolean).length }; }

The multiplier is the entry for the number of golden discs (0 to 4) in the paytable for your difficulty, or in the operator’s RTP preset table.

Example. On easy the floats 0.92299 0.38355 0.06895 0.12514 give two golden discs (the third and fourth), which pay 1.2×.

Spark

Five bulbs, one float each from cursor 0. A bulb is lit when its float is below the difficulty’s chance:

DifficultyChance of a lit bulb
easy0.50
medium0.40
hard0.30
expert0.20
const SPARK_CHANCE = { easy: 0.5, medium: 0.4, hard: 0.3, expert: 0.2 }; function sparkBulbs(seeds, difficulty) { const bulbs = generateFloats({ ...seeds, cursor: 0, count: 5 }).map((f) => f < SPARK_CHANCE[difficulty]); return { bulbs, lit: bulbs.filter(Boolean).length }; }

The multiplier is the entry for the number of lit bulbs (0 to 5) in the paytable for your difficulty, or in the operator’s RTP preset table.

Example. On easy the bulbs are off, lit, lit, lit, off: three lit, which pay 0.75×.

Drop

The ball falls through 7 gates, one float each from cursor 0. A gate is open when its float is below the difficulty’s chance, which is the share of safe spots in a row:

DifficultyChance a gate is open
easy3 of 4
medium2 of 3
hard1 of 2
expert1 of 4
const DROP_CHANCE = { easy: 3 / 4, medium: 2 / 3, hard: 1 / 2, expert: 1 / 4 }; function dropGates(seeds, difficulty) { const gates = generateFloats({ ...seeds, cursor: 0, count: 7 }).map((f) => f < DROP_CHANCE[difficulty]); const firstClosed = gates.indexOf(false); return { gates, floor: firstClosed === -1 ? 8 : firstClosed + 1 }; }

The ball stops at the first closed gate. The floor is that gate’s number from 1 to 7, or 8 when every gate is open. The multiplier is the entry for the floor in the paytable for your difficulty, or in the operator’s RTP preset table. The gates after the first closed one are shown by the Verify tab but do not matter.

Example. On easy the first gate is closed (0.92299 is not below 0.75), so the floor is 1 and the payout is 0.1×.

Defuse

There are 10 wires, and the difficulty sets the number of detonators among them: easy 1, medium 2, hard 3, expert 4. You cut wires one at a time; wire i (from 0) reads float i from cursor 0. With wiresLeft = 10 − i wires remaining, the wire is a detonator when its float is below detonators / wiresLeft, so every remaining wire is equally likely to be a detonator:

const DETONATORS = { easy: 1, medium: 2, hard: 3, expert: 4 }; function defuseCuts(seeds, difficulty) { const detonators = DETONATORS[difficulty]; const maxSafe = 10 - detonators; const floats = generateFloats({ ...seeds, cursor: 0, count: 10 }); let safeCuts = 0; for (let i = 0; i < 10 && safeCuts < maxSafe; i++) { if (floats[i] < detonators / (10 - i)) return { safeCuts, defused: false }; safeCuts++; } return { safeCuts, defused: true }; }

The round ends at the first detonator, or when every safe wire has been cut (10 − detonators safe cuts). The multiplier is the entry for the number of safe cuts in the paytable for your difficulty, or in the operator’s RTP preset table.

Example. On easy the first two wires are safe (0.92299 and 0.38355 are not below 1/10 and 1/9) and the third is a detonator (0.06895 is below 1/8): two safe cuts, which pay 0×.

Volt

One float, cursor 0. The charge is the float cubed, as a percentage with two decimals:

function voltCharge(seeds) { const [float] = generateFloats({ ...seeds, cursor: 0, count: 1 }); return Math.floor(Math.pow(float, 3) * 10000) / 100; }

The charge falls in one of ten bands:

ChargeBand
0 to 151
15 to 252
25 to 403
40 to 554
55 to 705
70 to 806
80 to 907
90 to 958
95 to 999
99 to 10010

Each band pays the multiplier of that band in the paytable. An operator’s RTP preset keeps the same bands and changes only what each pays.

Example. 0.922989992890507³ is a charge of 78.63%, in band 6, which pays 2.5×.

Pulse

One float, cursor 0. A float below 0.20 busts (0 beats, 0×). Otherwise the float is rescaled to the remaining range and turned into a multiplier, capped at 50×:

function pulseMultiplier(seeds, houseEdgePercent = 3) { const [float] = generateFloats({ ...seeds, cursor: 0, count: 1 }); if (float < 0.2) return 0; const edgeFactor = new Decimal(100 - Math.min(10, Math.max(1, houseEdgePercent))).div(100); const scaled = (float - 0.2) / (1 - 0.2); const raw = new Decimal(0.19).times(edgeFactor).div(new Decimal(1).minus(scaled)); return Math.min(50, raw.times(100).floor().div(100).toNumber()); }

The house edge is part of the result: default 3%, an operator setting between 1% and 10%. An operator RTP preset replaces the edge factor with its own certified factor. The number of beats shown is the band the multiplier falls in:

MultiplierBeats
below 0.251
0.25 to 0.352
0.35 to 0.503
0.50 to 0.754
0.75 to 1.105
1.10 to 1.606
1.60 to 2.507
2.50 to 4.008
4.00 to 6.509
6.50 to 11.0010
11.00 to 18.5011
18.50 to 31.0012
31.00 and above13

Example. scaled = (0.92299 − 0.2) / 0.8 = 0.90374, and 0.19 × 0.97 / (1 − 0.90374) floors to 1.91×, which is 7 beats.

Launch

Eleven floats, cursor 0. The rocket has 10 systems. System i passes when float i is below 0.85; the first failed system ends the launch. If all ten pass, float 10 sets the altitude, which is the payout:

function launchResult(seeds, houseEdgePercent = 3) { const floats = generateFloats({ ...seeds, cursor: 0, count: 11 }); let systemsPassed = 0; while (systemsPassed < 10 && floats[systemsPassed] < 0.85) systemsPassed++; if (systemsPassed < 10) return { systemsPassed, altitude: null }; const edgeFactor = new Decimal(100 - Math.min(10, Math.max(1, houseEdgePercent))).div(100); const raw = new Decimal(1.05).times(edgeFactor).div(new Decimal(1).minus(floats[10])); return { systemsPassed, altitude: Math.min(30, raw.times(100).floor().div(100).toNumber()) }; }

When the launch fails, the number of systems passed picks a partial payout from the paytable (typically nothing for the first systems). When it succeeds, the altitude, up to 30×, is the multiplier. The house edge is part of the altitude: default 3%, an operator setting between 1% and 10%, or the factor of the operator’s RTP preset.

Example. The first float 0.92299 is not below 0.85, so system 1 fails. No systems pass, and the payout is 0×.

Crash, Slide and Double

These three games are played in shared rounds and do not use your seed pair. Their result comes from a server seed committed for each round and a public drand  beacon. See Crash, Slide & Double.

Last updated on