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 1The 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
countfloats starting at a bytecursor(4 bytes per float). The float numbern, counting from 0, is at cursor4n. - 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 from1, so a position3in the code is tile4on 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).
| Game | Cursor | Floats read |
|---|---|---|
| Dice | 0 (Lightning: 1) | 1 (Lightning: 11) |
| Limbo | 0 (Lightning: 1) | 1 (Lightning: 2) |
| Coin Flip | 0 | one per coin |
| Roulette | 0 | 1 |
| Wheel | 0 | 1 |
| Plinko and Wire | 0 | one per row |
| Keno | 0 (Lightning: 64) | 10 (Lightning: 5) |
| Mines | 0 | one per mine |
| Diamonds | 0 | 5 |
| HiLo | 4 for the first card, then 8, 12, … | 1 per card |
| Blackjack | 0, 4, 8, … per card | 1 per card |
| Baccarat | 0 | 6 |
| Video Poker | 0 | 10 |
| Tower, Doors, Soccer, Bridge | 0 | 9 × safe tiles per row |
| Chicken | 0 (Lightning: 64) | one per car (Lightning: 7) |
| Balloon and Stairs | 0 | one per hazard |
| Safe | 0 | 4 |
| Spark | 0 | 5 |
| Drop | 0 | 7 |
| Defuse | 0 | 10 |
| Volt | 0 | 1 |
| Pulse | 0 | 1 |
| Launch | 0 | 11 |
| Crash, Slide and Double | see its own page | 1 |
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” whenroll > target. - Multiplier. The win chance is
targetfor “under” and100 - targetfor “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 card5 + 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.
| Difficulty | Positions per row | Safe per row |
|---|---|---|
| easy | 4 | 3 |
| medium | 3 | 2 |
| hard | 2 | 1 |
| expert | 3 | 1 |
| master | 4 | 1 |
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, 3Doors
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:
| Difficulty | Chance of a golden disc |
|---|---|
| easy | 0.30 |
| medium | 0.25 |
| hard | 0.20 |
| expert | 0.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:
| Difficulty | Chance of a lit bulb |
|---|---|
| easy | 0.50 |
| medium | 0.40 |
| hard | 0.30 |
| expert | 0.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:
| Difficulty | Chance a gate is open |
|---|---|
| easy | 3 of 4 |
| medium | 2 of 3 |
| hard | 1 of 2 |
| expert | 1 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:
| Charge | Band |
|---|---|
| 0 to 15 | 1 |
| 15 to 25 | 2 |
| 25 to 40 | 3 |
| 40 to 55 | 4 |
| 55 to 70 | 5 |
| 70 to 80 | 6 |
| 80 to 90 | 7 |
| 90 to 95 | 8 |
| 95 to 99 | 9 |
| 99 to 100 | 10 |
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:
| Multiplier | Beats |
|---|---|
| below 0.25 | 1 |
| 0.25 to 0.35 | 2 |
| 0.35 to 0.50 | 3 |
| 0.50 to 0.75 | 4 |
| 0.75 to 1.10 | 5 |
| 1.10 to 1.60 | 6 |
| 1.60 to 2.50 | 7 |
| 2.50 to 4.00 | 8 |
| 4.00 to 6.50 | 9 |
| 6.50 to 11.00 | 10 |
| 11.00 to 18.50 | 11 |
| 18.50 to 31.00 | 12 |
| 31.00 and above | 13 |
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.