Cum funcționează Provably Fair în Limbo
Iată cum se joacă Limbo pe Cloudbet și cum poți verifica dacă fiecare multiplicator este provably fair.
Cum se joacă Limbo
Limbo este un joc de multiplicare rapid și simplu: înainte de fiecare rundă, stabilești un multiplicator țintă — cât de mare vrei să fie câștigul tău. Plasează pariul, apoi este generat un multiplicator rezultat. Dacă acesta atinge sau depășește ținta ta, câștigi multiplicatorul țintă înmulțit cu paria ta. Dacă este sub țintă, pierzi runda.
Cu cât stabilești un multiplicator țintă mai mare, cu atât câștigul potențial este mai mare — dar cu atât este mai puțin probabil ca rezultatul să îl atingă. Îți poți stabili ținta cât de mică sau cât de mare dorești înainte de fiecare pariu.
Cloudbet Originals Limbo
Limbo-ul propriu al Cloudbet Originals are un RTP de 99%, un multiplicator maxim de x1.000.000 și acceptă pariuri de la 0,01 $ la 20.000 $.
Numerele aleatorii ale Limbo sunt generate în același mod verificabil ca orice alt joc Cloudbet Original — vezi sistemul provably fair al Cloudbet pentru modul în care Server Seed-ul, Client Seed-ul și Nonce-ul se combină într-un Round Signature și apoi într-un flux de numere aleatorii prin SHAKE256. Această pagină acoperă ce este specific la Limbo: cum devine acel număr aleatoriu multiplicatorul împotriva căruia concurezi.
Cum este calculat multiplicatorul
Limbo pornește de la un număr aleatoriu de 32 de biți — generat de SHAKE256, distribuit uniform, ceea ce înseamnă că fiecare valoare între 0 și 2³²-1 este la fel de probabilă.
Totuși, aceasta nu este forma de care avem nevoie. Un multiplicator ar trebui să atingă valori mici mult mai des decât valori uriașe — șansele unui 1,01x ar trebui să fie mult mai mari decât șansele unui 100.000x. Așa că numărul aleatoriu este remodelat, pas cu pas, în acea distribuție:
- Se folosește rejection sampling, dar ușor — doar pentru a evita extragerea celui mai mare număr posibil, deoarece numărul urmează să fie incrementat cu unu.
- Numărul este incrementat cu unu (astfel încât nu este niciodată zero, ceea ce contează pentru împărțirea de la pasul 4).
- Este redus la o zecimală între 0 și 1, prin împărțirea la valoarea sa maximă posibilă.
- 0,99 (RTP-ul Limbo) este împărțit la acea zecimală — acesta este pasul care transformă o distribuție uniformă într-una în care multiplicatorii mici sunt exponențial mai frecvenți decât cei mari.
- Rezultatul este rotunjit în jos la două zecimale.
- În final, este „tăiat” la intervalul valid al Limbo: orice valoare sub 1 devine 1 (paria minimă este 1,01, astfel încât rezultatele sub 1 sunt toate pur și simplu o pierdere), iar orice valoare peste 1.000.000 devine 1.000.000 (câștigul maxim al Limbo).

Odată generat, multiplicatorul este fixat și ascuns — nu poate fi schimbat după plasarea pariului tău. Încasarea (sau nu) înainte de a fi atins este ceea ce decide runda.
Verifică-ți propriile jocuri Limbo
Codul de mai jos este fidel implementării Limbo de pe Cloudbet. Introdu Server Seed-ul, Client Seed-ul și Nonce-ul pentru a-ți reproduce propriul rezultat. Apropo: pentru a dezvălui Server Seed-ul unui joc pe Cloudbet, trebuie să începi să folosești un alt Server Seed.
Dependențe:
npm install js-sha3
Cod:
// 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);
})();
