Crash games are one of the simplest games of chance you can model in code. A multiplier starts at 1.00x and climbs until it "crashes" at a random point. Players cash out before the crash to win their stake × the multiplier, or lose it if they wait too long. Aviator, JetX and Spaceman all work this way.
Because the rules are so simple, they're a great playground for Monte Carlo simulation. They also attract a huge amount of nonsense: "predictor" apps, Telegram "signal" groups and betting systems that promise to beat the game.
So we built a seeded simulator, ran 10 million rounds, and tested the most popular strategies. Here's how it works, with code you can run.
1. Generating crash points
Most crash games use a distribution where the chance of reaching multiplier x is roughly:
P(reach x) ≈ (1 − houseEdge) / x
With a 3% house edge (97% RTP), you reach 2x about 48.5% of the time, 10x about 9.7% and 100x about 0.97%.
You can sample that distribution from a uniform random number U in [0, 1):
function sampleCrash(u, edge = 0.03) {
return Math.max(1, Math.floor((100 * (1 - edge)) / (1 - u)) / 100);
}
The floor(... ) / 100 rounds down to two decimals, the way games display multipliers, and Math.max(1, ...) produces the occasional instant crash at 1.00x.
2. A seeded PRNG, so results are reproducible
Math.random() can't be seeded, and a study you can't reproduce isn't worth much. We use mulberry32, a tiny, fast 32-bit generator:
function mulberry32(seed) {
let a = seed >>> 0;
return () => {
a = (a + 0x6d2b79f5) >>> 0;
let t = a;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const rand = mulberry32(20261102); // same seed, same 10M rounds, every time
Quick sanity check: generate a million crash points and count how often each target is reached.
const N = 1_000_000;
const targets = [1.2, 2, 10, 100];
const hits = Object.fromEntries(targets.map(t => [t, 0]));
for (let i = 0; i < N; i++) {
const c = sampleCrash(rand());
for (const t of targets) if (c >= t) hits[t]++;
}
for (const t of targets) {
const observed = (100 * hits[t] / N).toFixed(2);
const theory = (100 * 0.97 / t).toFixed(2);
console.log(`${t}x observed ${observed}% theory ${theory}%`);
}
Over 10 million rounds, observed and theoretical rates matched to within a few hundredths of a percent.
3. Simulating a betting strategy
A session starts with a 100-unit bankroll and a 1-unit base bet, and plays up to 1,000 rounds. Each strategy just decides the next stake:
function runSession(strategy, { rounds = 1000, bankroll = 100, base = 1, target = 2, edge = 0.03 } = {}) {
let bank = bankroll, stake = base;
for (let r = 0; r < rounds; r++) {
if (stake > bank) return { bank, bust: true, rounds: r }; // can't cover the next bet
const crash = sampleCrash(rand(), edge);
const win = crash >= target;
bank += win ? stake * (target - 1) : -stake;
if (strategy === 'martingale') stake = win ? base : stake * 2; // double after a loss
else stake = base; // fixed stake
}
return { bank, bust: false, rounds };
}
Run 10,000 sessions per strategy and you get the distribution of outcomes rather than one lucky (or unlucky) run:
function study(strategy, opts, sessions = 10_000) {
const results = Array.from({ length: sessions }, () => runSession(strategy, opts));
const finals = results.map(r => r.bank).sort((a, b) => a - b);
return {
bust: (100 * results.filter(r => r.bust).length / sessions).toFixed(1) + '%',
inProfit: (100 * results.filter(r => r.bank > opts.bankroll).length / sessions).toFixed(1) + '%',
median: finals[Math.floor(sessions / 2)],
};
}
console.log('Martingale 2x', study('martingale', { bankroll: 100, target: 2 }));
console.log('Fixed 1.20x ', study('fixed', { bankroll: 100, target: 1.2 }));
4. What 10 million rounds showed
From the full study (3% house edge, 100-unit bankroll, 1,000 rounds per session, 10,000 sessions per strategy):
| Strategy | Went bust | Sessions in profit |
|---|---|---|
| Fixed cash-out at 1.20x | 0% | 1.9% |
| Fixed cash-out at 2x | 2.2% | 16.1% |
| Fixed cash-out at 10x | 39.5% | 33.6% |
| Anti-Martingale (Paroli) | 31.7% | 19.6% |
| Fibonacci | 76.8% | 22.9% |
| Martingale | 87.8% | 12.0% |
| D'Alembert | 93.7% | 6.1% |
Two results stand out:
Winning often isn't winning overall. Cashing out at 1.20x won 80.8% of rounds and never went bust, yet the typical session still lost about 30% of its bankroll over 1,000 rounds. That's the house edge: 3% of everything wagered, quietly adding up.
Martingale fails all at once. Doubling after every loss feels safe because each win recovers everything. But losing streaks are longer and more common than intuition suggests. With a finite bankroll, one bad streak eventually costs more than you can cover, and 87.8% of sessions hit that wall.
5. Testing the "streak" myth
The most common claim in signal groups is that after a run of low rounds, a big one is "due". That's easy to test: after k consecutive rounds under 2x, how often does the next round reach 2x?
let streak = 0;
const after = {}; // k -> { n, hits }
for (let i = 0; i < 10_000_000; i++) {
const c = sampleCrash(rand());
const k = Math.min(streak, 8);
after[k] ??= { n: 0, hits: 0 };
after[k].n++;
if (c >= 2) after[k].hits++;
streak = c < 2 ? streak + 1 : 0;
}
for (const [k, { n, hits }] of Object.entries(after)) {
console.log(`after ${k} lows: P(next ≥ 2x) = ${(100 * hits / n).toFixed(2)}%`);
}
In our run, the probability stayed at about 48% whether the previous 0, 3 or 8 rounds were low. The correlation between consecutive rounds was effectively zero (0.0003). Every round is independent, which also means no app can predict the next one.
6. Bonus: verifying a real round
Real crash games usually make each round provably fair: the crash point is derived from an HMAC-SHA256 of a server seed (revealed after the round) and a client seed, so players can check the result wasn't changed. We open-sourced a small, dependency-free verifier in TypeScript, using Web Crypto in the browser and node:crypto in Node:
👉 crash-provably-fair-verifier on GitHub
Try it yourself
- The full study, with charts, a CSV of the results and the exact seeded script: 10-million-round crash game simulation study
- Play with the parameters in the browser: crash strategy simulator
- How the hash-to-multiplier step works in detail: provably fair crash games explained
The code in this post is simplified for readability. The published script uses the same distribution, PRNG and seed, so its numbers are reproducible exactly.
The takeaway for developers: simulation is a great way to make probability concrete. The takeaway for anyone playing these games: no strategy changes the house edge. Strategies only change how the ride feels. If you play, treat it as entertainment with a fixed budget (18+).
Data and code are released under CC BY 4.0. Questions or improvements welcome in the comments.
Top comments (0)