Racing the Odds

Back to Projects

PropBet

Race the Odds - A Real-Time Crash Betting Game Built with Next.js & Vibe Coding, Cash Out Before It Flies Away

PropBet - real-time crash betting game cover
Role
Developer
Vibe Coder
Type
Web Crash Game
iGaming
Stack
Next.js · React
TypeScript
Build
Game Engine
Vercel
Approach
Vibe-Coded
AI-Assisted

PropBet is a fully playable browser crash game - a plane takes off and climbs an ever-steepening multiplier curve that can burst at any instant. Players stake up to two bets per round and race to cash out before it flies away. It ships with 50,000 demo credits, dual bet panels, auto cash-out, and a live crash-history rail - all driven client-side by a seeded round engine, with no backend to arbitrate a single outcome. PropBet is one game inside a shared "Game Engine" platform, and it was built entirely through vibe coding.

01 --The Challenge

The Build Challenge

A crash game looks like nothing more than a single number going up - but the whole experience lives in the last frame before it stops. The multiplier has to climb smoothly at 60fps on any screen, the crash point has to be decided fairly and stay hidden until the exact instant it bursts, and the cash-out has to resolve against the precise multiplier on the frame the player tapped. Two independent bets and their optional auto-cash-out targets all settle inside the same tick - and there is no server to referee any of it. On top of that, PropBet is one game mounted inside a shared game-engine shell, so its entire round loop had to be self-contained and swappable. All of it was built through vibe coding.

"A crash game is a single number going up - but everything that matters happens in the last frame before it stops."

🎯

Design Problem

Build a tense aviation "cockpit HUD" - a plane climbing a neon curve, a giant live multiplier, a colour-graded crash-history rail and two bet panels - that reads at a glance and stays playable on both desktop and a phone held in one hand.

⚙️

Technical Problem

Drive a 60fps rising-multiplier curve and plane animation from a deterministic round engine, resolve manual and auto cash-outs against the exact frame multiplier, and keep the crash point sealed until the moment the plane flies away.

🤖

The Constraint

Describe each sub-system to an AI and iterate - the round state machine, the crash RNG, the cash-out resolver, the curve easing - which demands a precise mental model of the timing race, or the generated code silently mis-pays a round.

02 --Tech Stack

Tech Stack & Approach

PropBet is a single Next.js (App Router) application in TypeScript, deployed to Vercel as one game inside a shared "Game Engine" platform - each game mounts into a common shell at its own /games/<name>/play route. The key architectural decision is its canvas-and-refs game loop - Game.tsx renders the canvas and both bet panels once, then a requestAnimationFrame loop drives the entire simulation from refs, so it animates at 60fps while React state updates only a few times a second - just what the panels need to show. The engine (engine.ts) seals the crash point the instant a round begins; the loop simply flies the plane toward a number it already knows.

A Naive Crash Clone vs. How PropBet Is Engineered

The Naive Clone
  • setInterval ticks the multiplier → jittery curve, timer drift, dropped frames
  • Crash checked live against React state → stale-closure cash-out bugs
  • One bet, no auto cash-out, no history - nothing to hedge with
The PropBet Approach
  • requestAnimationFrame loop with a fixed-timestep accumulator → smooth 60fps climb
  • Crash point sealed at take-off; cash-outs resolved against the exact frame multiplier
  • Dual bet panels + per-panel auto cash-out + a live crash-history rail

Stack at a Glance

Framework
Next.js · TypeScript · Vercel
App RouterMulti-Game ShellVercel

"PropBet is one game inside a shared Game Engine - no database, no server. The round loop runs entirely in the browser."

✈️
Game Engine
engine.ts · blockchainRng.ts
Crash DistributionBlockchain SeedProvably Fair

"The crash point is sampled once at take-off from a real blockchain block - sealed the instant a round begins, and the loop just flies the plane toward a number it already knows."

🎨
Presentation
Game.tsx · Canvas · CSS
Canvas 60fpsParallax SkyNeon HUD

"The plane, the curve, the clouds and the multiplier are painted every frame on a canvas - React only re-renders the panels a few times a second."

Core Modules

Responsibility
Key Challenge
How It's Solved
✈️ engine.ts
Pure crash math - crash distribution, multiplier growth, phase timings.
House-edge-correct odds along an exponential curve.
P(crash ≥ x) = (1−edge)/x at a 3% edge; m(t) = e^(0.1·t).
🕹️ Game.tsx
Run the 60fps loop and paint the canvas HUD.
Keep the plane and multiplier smooth at any refresh rate - without re-rendering React every frame.
requestAnimationFrame drives the whole simulation from refs; React state syncs a few times a second.
🎲 blockchainRng.ts · /api/crash-seed
Provably-fair seed for each round's crash point.
Verifiable randomness, fetched without a browser-console error on upstream failure.
A real blockchain block → mulberry32 PRNG → crash point; a server route proxies the chain and falls back to Math.random().
03 --Architecture

Architecture & Game Loop

The Round Life-Cycle

Every round runs a small state machine: place up to two bets and set auto-cash-out targets, the plane takes off, the multiplier climbs the curve, and then it either gets cashed out or the round crashes and the plane flies away. The crucial detail is that the crash point is computed once at take-off. The loop never "decides" mid-flight - each frame it only checks whether the current multiplier has reached the sealed crash point, or crossed any auto-cash-out target. That separation makes the outcome fixed before the first frame plays, which keeps the render loop fully decoupled from the engine.

💰
Bet
🛫
Take Off
📈
Climb
✂️
Cash Out
🏆
Payout
💡

A round's crash point has no fixed length - most rounds burst low (1.0x-2x), while a rare few rocket past 40x, exactly like the history rail (1.24x · 2.00x · 5.71x · 18.4x · 42.0x). The engine seals that number at take-off from a seeded draw, so the outcome is fixed before the plane leaves the ground - the loop only reveals it.

The Canvas-and-Refs Game Loop

Game.tsx renders the canvas and both bet panels once, then a useEffect starts the requestAnimationFrame loop. The entire simulation - plane, multiplier, parallax clouds, exhaust particles and the other-player bots - lives in refs, and React state is synced only a few times a second to mirror what the panels show: phase, the throttled multiplier, balance and history. So the canvas animates at 60fps without React re-rendering every frame - which also sidesteps the stale-closure bug that would mis-price a cash-out if the player tapped the button at 8.4x while React held a frame-old multiplier in state.

📁

engine.ts

Pure crash math. sampleCrashPoint draws the bust multiplier from P(crash ≥ x) = (1−edge)/x at a 3% house edge; multiplierAt maps flight time to the live multiplier via m(t) = e^(0.1·t). Owns phase timings, bet limits and the 5,000x cap.

🎮

Game.tsx

The component and the loop in one. Renders the canvas + panels once, boots the requestAnimationFrame loop, runs the whole simulation from refs, and syncs throttled state - phase, multiplier, balance, history - to the panels.

🔗

blockchainRng.ts

Provably-fair seed. Fetches a real block-derived number from /api/crash-seed, derives a per-round seed, and feeds a mulberry32 PRNG whose first output is the uniform the crash formula consumes - with a quiet Math.random() fallback.

🔊

sounds.ts

Audio cues wired to the round phase - take-off, the climbing tone, cash-out and the crash - each mutable from the HUD's sound toggle.

04 --Game Logic

Game Systems & Visual Identity

The multiplier climbs an exponential curve - m(t) = e^(0.1·t), so 2x lands around 6.9s and 10x around 23s. A round's crash point is drawn once at take-off from the industry-standard distribution P(crash ≥ x) = (1−edge)/x at a 3% house edge - a 97% long-run RTP, with about 3% of rounds busting instantly at 1.00x and a rare few flying past 40x toward the 5,000x cap. Two independent bet panels let a player hedge: cash one out early at 2x for safety and let the second ride. The whole cycle - bet, climb, cash out or crash, payout - resolves in the browser with a 50,000-credit demo balance and no real money.

// engine.ts - the crash math (deterministic + provably fair) HOUSE_EDGE = 0.03 // 97% long-run RTP GROWTH_K = 0.1 // m(t) = e^(k·t): 2x≈6.9s, 10x≈23s, 100x≈46s MAX_MULTIPLIER = 5000 // hard cap so a round can't run forever // crash point - sampled once at take-off from the block seed sampleCrashPoint(rand): // rand = mulberry32(blockSeed) u = max(rand(), 1e-12) raw = (1 - HOUSE_EDGE) / u // P(crash ≥ x) = (1-edge)/x return clamp(floor(raw*100)/100, 1, MAX_MULTIPLIER) // each animation frame (Game.tsx rAF loop) m = multiplierAt(elapsedMs) // = e^(GROWTH_K · ms/1000) if auto-cashout target hit → settle panel(bet × m) if m >= crashPoint → CRASH // plane flies away, open bets lost

The Betting System - Where Rounds Are Won

PropBet gives the player three levers, and the tension between them is the whole game. All three are resolved by the same loop that draws the curve - so a payout lands on the exact frame its condition is met.

💰
Dual Bet Panels
Two Independent Stakes
Panel A + Panel BHedge or DoubleSettle Separately

"Place two bets - cash out one at 2x for safety and let the second chase 20x. Each panel settles on its own."

Auto Cash-Out
Set a Target, Walk Away
Per-Panel TargetFrame-ExactHands-Off

"Set a target multiplier and the engine settles the moment the curve crosses it - no reflexes required."

📊
Crash-History Rail
The Only Memory On Screen
Last RoundsColour-GradedLow → High

"1.24x · 2.00x · 5.71x · 18.4x · 42.0x - the rail is the only history the game shows, and every round is independent."

Fairness & the Cash-Out Race

🎲 Provably-Fair Engine
  • Each round's seed is a real, public blockchain block fetched via /api/crash-seed - not Math.random()
  • That block seeds a mulberry32 PRNG whose first draw sets the crash point, so every round is a reproducible function of a verifiable number
  • A 3% house edge is baked into the distribution (97% RTP), with a quiet Math.random() fallback if the chain is down
✂️ The Cash-Out Race
  • Manual or auto - both resolved against the exact frame multiplier
  • Win = stake × multiplier at the instant you cash out; both panels settle independently
  • Wait too long and the plane flies away - any open bet on that round is lost
Orbitron
MediumBoldExtra BoldBlack
Color

PropBet's cockpit-HUD palette - deep midnight-blue instrument panels, electric sky-blue for the live curve and multiplier, altitude amber for the climb, a crash crimson for the moment the plane flies away, and cash-out green for a win.

#43E8FF
67, 232, 255
#9AF6FF
154, 246, 255
#F4C64A
244, 198, 74
#FF2E63
255, 46, 99
#2EE6A6
46, 230, 166
#0B1220
11, 18, 32

In-Game Screens

The game across its key states - the branded home screen, the betting board on the airfield, a round crashing mid-air, the round result and settlement, and the how-to-play guide. Click any screen to open it full-size.

05 --The Build

The Vibe-Coding Build Process

Vibe-coding a crash game works only if you own the timing model before you open a prompt. I designed the round state machine, the crash-point distribution and its house edge, the exponential curve, and the cash-out resolution rules on paper first. Only then did I start prompting - describing one sub-system at a time and verifying each against the spec before layering the next. The AI accelerated every implementation step; the spec is what kept a mis-timed cash-out from silently paying the wrong amount.

📐

1 · Design the Math

Specified the round life-cycle (bet → take-off → climb → cash out / crash → payout), the exponential curve, and the seeded crash-point distribution with its house edge - all verified on paper before a single prompt.

✈️

2 · Build the Engine

Implemented engine.ts first - the pure crash math: the (1−edge)/x distribution, the e^(0.1·t) growth curve, phase timings and bet limits. Validated the RTP and payouts by hand before touching the UI.

🎮

3 · The Canvas Loop

Built the requestAnimationFrame loop inside Game.tsx, driving the whole simulation from refs so the canvas runs at 60fps while React state syncs only a few times a second - no per-frame re-renders, no stale cash-outs.

🛩️

4 · Cockpit HUD Skin

Translated the type system, the sky-blue-to-crash-crimson palette, the climbing plane path across the parallax skies, and the neon curve into CSS and the canvas render layer.

🚀

5 · Polish & Ship

Added the second bet panel, per-panel auto cash-out, the colour-graded crash-history rail, sound, a how-to-play panel and 50,000 demo credits - then mounted it into the shared Game Engine shell on Vercel.

06 --Results & Reflection

Outcome & Impact

Key Outcomes

0
A smooth rising-multiplier loop driven by requestAnimationFrame
0
A complete crash-game engine running entirely client-side on Vercel

"From 1.00x to the moment it flies away - a seeded crash engine, dual bets, auto cash-out and a live history rail - built end-to-end through vibe coding."

📈 Outcome

A shipped, playable browser crash game: a 60fps canvas render loop, a blockchain-seeded provably-fair crash engine, dual bet panels, per-panel auto cash-out, a colour-graded crash-history rail, simulated participants, sound and 50,000 demo credits - all client-side, mounted in a shared Game Engine platform.

🧗 Challenge

The cash-out race is unforgiving - resolve it against a frame-old multiplier and every payout is wrong. Getting it right through vibe coding meant sealing the crash point at take-off and driving the loop with rAF instead of React state, all specced before a single prompt.

💡 What I Learned

The imperative game-loop pattern is the right architecture for a real-time game on React - no state, no reconciliation overhead. The AI produced it correctly only after I specified the pattern explicitly. Design-first vibe coding is the only vibe coding that survives a timing-critical loop.

🚀 Next Steps

Surface the block hash behind each round as an in-game "verify" link, persist balance and history across sessions, replace the simulated participant bots with a real multiplayer socket so bets are shared between players, and grow the shared Game Engine shell with more titles beside PropBet.