So funktioniert Provably Fair bei Limbo
So spielst du Limbo auf Cloudbet, und so überprüfst du, dass jeder Multiplikator provably fair ist.
So spielst du Limbo
Limbo ist ein schnelles, einfaches Multiplikator-Spiel: Vor jeder Runde legst du einen Zielmultiplikator fest — wie groß der Gewinn sein soll, den du anstrebst. Platziere deine Wette, dann wird ein Ergebnismultiplikator generiert. Landet er bei oder über deinem Ziel, gewinnst du deinen Zielmultiplikator mal deinen Einsatz. Landet er darunter, verlierst du die Runde.
Je höher der Zielmultiplikator, den du festlegst, desto größer dein potenzieller Gewinn — aber desto unwahrscheinlicher, dass das Ergebnis ihn erreicht. Du kannst dein Ziel vor jeder Wette so niedrig oder hoch ansetzen, wie du möchtest.
Cloudbet Originals Limbo
Cloudbets eigenes Originals Limbo hat einen RTP von 99 %, einen maximalen Multiplikator von x1.000.000 und akzeptiert Einsätze von 0,01 $ bis 20.000 $.
Limbos Zufallszahlen werden auf dieselbe verifizierbare Weise generiert wie bei jedem anderen Cloudbet Original — siehe Cloudbets Provably-Fair-System dafür, wie Server-Seed, Client-Seed und Nonce zu einer Round Signature und anschließend über SHAKE256 zu einem Strom von Zufallszahlen kombiniert werden. Diese Seite behandelt, was bei Limbo spezifisch ist: wie diese Zufallszahl zu dem Multiplikator wird, gegen den du antrittst.
So wird der Multiplikator berechnet
Limbo startet mit einer zufälligen 32-Bit-Zahl — generiert von SHAKE256, gleichverteilt, das heißt, jeder Wert zwischen 0 und 2³²-1 ist gleich wahrscheinlich.
Das ist jedoch nicht die Form, die wir brauchen. Ein Multiplikator sollte weit häufiger auf kleinen Werten landen als auf riesigen — die Chance auf einen 1,01x sollte deutlich höher sein als die Chance auf einen 100.000x. Die Zufallszahl wird daher Schritt für Schritt in diese Verteilung umgeformt:
- Rejection Sampling wird angewendet, aber nur leicht — lediglich um zu vermeiden, dass die einzige größtmögliche Zahl gezogen wird, da die Zahl gleich um eins erhöht wird.
- Die Zahl wird um eins erhöht (sodass sie nie null ist, was für die Division in Schritt 4 wichtig ist).
- Sie wird auf eine Dezimalzahl zwischen 0 und 1 herunterskaliert, indem sie durch ihren maximal möglichen Wert geteilt wird.
- 0,99 (Limbos RTP) wird durch diese Dezimalzahl geteilt — das ist der Schritt, der eine Gleichverteilung in eine Verteilung verwandelt, in der kleine Multiplikatoren exponentiell häufiger vorkommen als große.
- Das Ergebnis wird auf zwei Nachkommastellen abgerundet.
- Schließlich wird es auf Limbos gültigen Bereich „begrenzt": Alles unter 1 wird zu 1 (der Mindesteinsatz ist 1,01, sodass Ergebnisse unter 1 ohnehin ein Verlust wären), und alles über 1.000.000 wird zu 1.000.000 (Limbos maximale Auszahlung).

Einmal generiert, ist der Multiplikator festgelegt und verborgen — er kann nach Platzierung deiner Wette nicht mehr geändert werden. Ob und wann du vor seinem Erreichen aussteigst, entscheidet über die Runde.
Verifiziere deine eigenen Limbo-Runden
Der folgende Code entspricht exakt Cloudbets Limbo-Implementierung. Gib deinen Server-Seed, Client-Seed und Nonce ein, um dein eigenes Ergebnis zu reproduzieren. Übrigens: Um den Server-Seed einer Runde auf Cloudbet offenzulegen, musst du zu einem anderen Server-Seed wechseln.
Abhängigkeiten:
npm install js-sha3
Code:
// If you are running this yourself, then you can input your own data here
// You can then compare:
// 1) Cloudbet's provided Commitment to the Commitment outputted here. This proves that the Server Seed was not altered during gameplay.
// 2) Cloudbet's provided game outcome to the game outcome outputted here. This proves that your game's outcome was determined solely by the pre-game seeds and was not altered afterwards.
let myServerSeed = 'your_server_seed'; // ✍️YOUR_INPUT✍️: Enter your (unhashed) Server Seed here
let myClientSeed = 'your_client_seed'; // ✍️YOUR_INPUT✍️: Enter your Client Seed here
let myNonce = 0; // ✍️YOUR_INPUT✍️: Enter your Nonce here
const usingCustomData = false; // Change this to true if you are using your own data
// Load libraries
const crypto = require('crypto');
const { sha3_256, shake256 } = require('js-sha3');
// Step 1: Generate a Server Seed and Commitment
// And generate a Client Seed - either by player input or by the player's browser
function generateServerSeed() {
let seed = crypto.randomBytes(32).toString('hex');
if (usingCustomData) { seed = myServerSeed; }
const commitment = sha3_256(seed);
return { 'serverSeed': seed, 'commitment': commitment };
}
function getClientSeed() {
let seed = crypto.randomBytes(32).toString('hex');
if (usingCustomData) { seed = myClientSeed; }
return seed;
}
// Step 2: Create a Round Signature with SHA3 using a Server Seed, Client Seed and Nonce
function createRoundSignature(serverSeed, clientSeed, nonce) {
return sha3_256(`${serverSeed}:${clientSeed}:${nonce}`);
}
// Step 3: Create a SHAKE256 stream generator from a Round Signature.
// To simplify this example program, this function doesn't create a stream, but instead a sequence of custom length.
function* createRandomNumberSequence(numbersToGenerate, roundSignature) {
const numOfBytes = 4 * numbersToGenerate; // 4 bytes (32 bits) per random number we generate
const byteStream = shake256.create(8 * numOfBytes); // shake takes bits as input
byteStream.update(roundSignature); // Use the Round Signature to seed the generator
const buf = Buffer.from(byteStream.digest()); // The Buffer class lets us package every 4 bytes into one 32-bit integer
for (let i = 0; i < numbersToGenerate; i++) {
yield buf.readUInt32BE(i * 4); // Read buf in 4-byte steps
}
throw new Error("No more numbers in the random number sequence! Fix: make 'numbersToGenerate' larger.");
}
// Step 4: Draw from the random number sequence to calculate the game result
function getGameResult(rng) {
let randomInt32 = rng.next().value; // 'randomInt32' ranges from 0 and 4294967295
while (randomInt32 == 0xFFFFFFFF) { randomInt32 = rng.next().value; } // This can basically be ignored
const fraction = (randomInt32 + 1) / 0xFFFFFFFF; // 'fraction' ranges from 0 to 1
const inverse = 0.99 / fraction; // 'inverse' ranges from 0 to (basically) infinity
let crashPoint = Math.floor(inverse * 100) / 100; // Cut off everything past the second decimal place
// We force the crashpoint to range from 1 to 1,000,000
if (crashPoint < 1.0) {
crashPoint = 1.0;
}
if (crashPoint > 1000000) {
crashPoint = 1000000;
}
return crashPoint;
}
// Example usage
(function main() {
let nonce = 1;
if (usingCustomData) { nonce = myNonce; }
const { serverSeed, commitment } = generateServerSeed();
const clientSeed = getClientSeed();
const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
const numbersToGenerate = 1 * 2; // We need 1 random number, but there is an improbable case we'll need two
const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);
const value = getGameResult(rng);
console.log('📝 Commitment (shown before the game):', commitment);
console.log('🔒 Server Seed (revealed after the game):', serverSeed);
console.log('🎲 Client Seed:', clientSeed);
console.log('🔢 Nonce:', nonce);
console.log('🔁 Round Signature (can be verified after the game):', roundSignature);
console.log('⛓️💥 Break Point:', value);
})();
