> For the complete documentation index, see [llms.txt](https://docs.overtime.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.overtime.io/overtime-casino/how-the-games-are-built.md).

# How the Games are Built

## How the Games are Built

Overtime Casino has eighteen games built across two generations of architecture. They play the same way from a user's seat - connect a wallet, place a bet, get a verifiable result from Chainlink VRF - but under the hood there are two designs, and the differences matter if you're integrating, auditing, or just want to understand exactly what guarantees you're getting.

This page explains both. You don't need it to play. You do need it to build.

***

#### The two generations at a glance

**The original five** - Roulette, Dice, Blackjack, Baccarat, Slots - are standalone contracts. Each one holds its own bankroll, talks to Chainlink VRF directly, and manages its own reservations, free bets, and referrals.

**The newer thirteen** - Plinko, Hi-Lo, Keno, Video Poker, Three Card Poker, Ultimate Texas Hold'em, Bonus Texas Hold'em, the four streak games (Penalty Shootout, Hoop Streak, Standoff, Shooting Range), Cascade and Tumble - are built on a shared core. A single contract, `CasinoCoreV2`, holds the treasury and the shared services; each game contract is thin, containing only its own rules and bet state. The four streak games go one step further: they share a single game contract, `StreakGame`, each registered as a mode.

Here's the contrast, dimension by dimension:

|                     | Original five                                            | Newer eight                                                                          |
| ------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| **Funds**           | Each game holds its own bankroll                         | One shared treasury (`CasinoCoreV2`)                                                 |
| **Randomness**      | Game calls VRF and receives the callback directly        | Core calls VRF and routes the callback to the game                                   |
| **User cancel**     | Yes - after a timeout, you can cancel your own stuck bet | No - recovery is operator-only                                                       |
| **Free bets**       | Separate `…WithFreeBet` functions                        | A flag on `placeBet` (or a separate function for side-bet games)                     |
| **Circuit breaker** | None                                                     | Per-game auto-pause on cumulative loss                                               |
| **Read aggregator** | `CasinoData`                                             | `CasinoDataV2` (streak games, Cascade and Tumble: records read on the game contract) |

Both generations use **Chainlink VRF v2.5**, support **USDC / WETH / $OVER**, enforce a **3 USD minimum bet**, and are deployed as upgradeable proxies. Neither has a function to void a resolved win, bias the RNG, or ban a winning wallet.

***

#### The original five: standalone contracts

Each of the first five games is a self-contained contract that holds its own liquidity.

**Lifecycle.** You call `placeBet` (or `placeMultiBet` for Roulette, or the per-action functions in Blackjack) directly on the game contract. It pulls your collateral via `safeTransferFrom` into its own balance, validates your selection, and reserves the worst-case payout. It requests a random word from Chainlink VRF. When the VRF coordinator calls the game's own `rawFulfillRandomWords`, the game derives the outcome, pays you if you won, pays your referrer if you lost, and emits a resolution event.

**Full reservation.** This is the defining property of the V1 design. Every game tracks `reservedProfitPerCollateral` - the sum of the worst-case payouts of *all* its pending bets. Before accepting a new bet, it checks that its entire balance covers that entire cumulative liability:

```
balanceOf(this) >= reservedProfitPerCollateral[collateral]
```

If a new bet would push reserved liability past the bankroll, it reverts with `InsufficientAvailableLiquidity`. In other words, every pending bet in a V1 game is fully backed at all times - the contract could pay every outstanding bet's maximum simultaneously and still be solvent. This is conservative and capital-heavy, but it means a V1 game can never be caught short.

**User-callable cancel.** Each V1 game lets *you* recover a stuck bet. If Chainlink fails to fulfill within `cancelTimeout` (a minimum of 30 seconds, set per game), you can call the cancel function yourself - `cancelBet` in Roulette, Dice, and Baccarat; `cancelHand` in Blackjack; `cancelSpin` in Slots - and get your stake back. You don't need to wait for an operator. There's also an `adminCancelBet`-style path (resolver-only) for emergencies, but the user path is the primary one.

**Free bets and referrals.** Free bets use dedicated entry points - `placeBetWithFreeBet`, and `placeMultiBetWithFreeBet` for Roulette - that pull from the free-bets holder instead of your wallet. Referrals are wired per-game through the shared `IReferrals` registry; a losing real-money bet pays the referrer automatically.

**Reading them.** A separate read-only aggregator, `CasinoData`, merges paginated bet history across all five games into a uniform record shape, so a frontend can fetch a page of history in one call per page rather than one call per bet.

***

#### The newer thirteen: one shared core

The thirteen newer games share `CasinoCoreV2`, a singleton treasury and services contract. The game contracts hold no funds; they're pure game logic and lifecycle state.

`CasinoCoreV2` handles:

* **Funds** - pulling stakes from your wallet (`pullFromUser`) or your free-bet balance (`useFreeBet`), and paying out (`payOut`)
* **Randomness** - requesting words from Chainlink VRF (`requestRandomWords`) and routing the fulfilled callback to the right game
* **Reservations** - reserving and releasing worst-case payouts
* **Free bets and referrals** - the shared wiring, identical in spirit to V1 but centralized
* **Limits** - effective minimum bet, maximum bet, and maximum profit, per game
* **Circuit breaker** - per-game cumulative-loss accounting that auto-pauses a game that's bleeding

Each game - `Plinko`, `Keno`, `HiLo`, `StreakGame`, `Cascade`, `Tumble` and the rest - implements only its rules and its bet's state machine. When a VRF word arrives, core calls the game's `onVrfFulfilled`, the game derives its outcome, and tells core how to settle.

There's also a third contract: **`CasinoDataV2`**, a read-only aggregator over core and the newer games - a treasury overview, per-game status (including live circuit-breaker state), and paginated bet records, all from one place. (See Integrate Overtime Casino for how to use it.)

The streak games, Cascade and Tumble are the exceptions: their bet records are read from their own contracts. On `StreakGame` that's `getUserRecords`, `getRecentRecords`, `getFullRecords` and `getBetRounds`; on `Cascade` and `Tumble` it's `getFullRecord`, `getUserBetIds` and `getRecentBetIds`. Their live game status, circuit breaker included, still comes from `CasinoDataV2.getGameStatus`.

**Lifecycle.** You call `placeBet` on the game contract (passing an `isFreeBet` flag, or using `placeBetWithFreeBet` for the side-bet games). The game asks core to pull your stake and reserve the worst case, then requests a VRF word. Single-shot games (Plinko, Keno, Cascade, Tumble) resolve on the first callback. Cascade and Tumble play their entire round, every cascade and every free or bought spin, inside that one callback. Multi-step games (Hi-Lo, Video Poker, the poker games) take further actions via a `makeAction(betId, action)` dispatcher, each potentially triggering a new VRF request, until the bet resolves. The VRF coordinator calls **core's** `rawFulfillRandomWords`, which looks up which game owns that request and forwards to its `onVrfFulfilled`.

The streak games are multi-round too, but use their own three calls instead of the dispatcher (below).

**Multi-step games use an action dispatcher.** The games where you make mid-hand decisions expose one `makeAction(betId, action)` function with a small integer action code, which keeps them uniform and easy to drive from a frontend, bot, or AI agent:

* **Hi-Lo:** `0` = guess Above, `1` = guess Below, `2` = cash out
* **Three Card Poker:** `0` = Play, `1` = Fold
* **Ultimate Texas Hold'em:** `0` = play pre-flop, `1` = check pre-flop, `2` = play post-flop, `3` = check post-flop, `4` = raise river, `5` = fold
* **Bonus Texas Hold'em:** `0` = play pre-flop, `1` = fold, `2` = raise flop, `3` = check flop, `4` = raise turn, `5` = check turn, `6` = raise river, `7` = check river

(Video Poker uses a dedicated `draw(holdMask)` rather than the dispatcher.) An unknown action code reverts (`InvalidAction`) rather than silently doing nothing.

**The streak games** run on `StreakGame`, one contract with each game registered as a mode: Penalty Shootout is `modeId = 1`, Hoop Streak `2`, Shooting Range `3` and Standoff `4`. There are three calls: `placeBet(collateral, amount, referrer, modeId, tierIdx, choice, isFreeBet)` places the bet and plays the first round, `playRound(betId, tierIdx, choice)` plays the next one, and `cashout(betId)` banks `stake × multiplier`. Players never supply odds. A round is a difficulty (plus an aim zone in Penalty Shootout); the contract looks up that difficulty's odds in its registry and derives the payout factor itself, so every round carries the same house edge.

**Cascade and Tumble** take a single `placeBet` and no further actions. The VRF word seeds a keccak256 stream that draws every cell of every grid in the round, so the whole round, every cascade, free spin and bomb included, is a pure function of that one word. `simulateRound` replays any round from its word.

***

#### Staged VRF: hidden cards stay hidden

Several games - in both generations - depend on you acting before you've seen certain cards: the dealer's hand in Blackjack and the poker games, the next street in Hold'em, your draw cards in Video Poker. In every one of those cases, the unseen cards are drawn from a **separate VRF request that fires only when the game needs to reveal them.** They are never written to contract storage before you commit your decision.

This matters because contract storage is publicly readable (`eth_getStorageAt`). If a future card were sitting in storage while you decided, a sophisticated player could read it and play perfectly. Staging the VRF requests so each card is generated only at the moment of reveal closes that hole. The principle was introduced in the original Blackjack contract (the dealer's hole card isn't generated until you stand) and is applied consistently across every newer game that has hidden information.

The streak games apply the same rule to every round: each kick, shot or draw is a fresh VRF request, fired only after your choice is committed onchain. Cascade and Tumble are the opposite case and need no staging: you make no decision after placing the bet, so one word, generated after your bet is locked, can safely settle the entire round.

***

#### The circuit breaker (newer thirteen only)

`CasinoCoreV2` tracks each game's cumulative house net P\&L, in USD, as bets settle. If a game's net result falls below a configured loss threshold - a default set on core (`defaultMaxNetLossPerGameUsd`, currently 3,000 USD on Optimism, Arbitrum and Base), adjustable per game - that game **auto-pauses**: no new bets until an admin resets it. Pending bets always continue to settle even while a game is paused. An auto-pause stops new bets and new rounds; it never blocks settlement, so a streak game player mid-run can still cash out. The four streak games share one contract, so they share one breaker: if it trips, all four stop taking new runs together.

This protects the shared treasury from a single game bleeding out due to a bug or an extreme run of player wins, and it's a feature the V1 games (with their isolated bankrolls) didn't need. The live state - `houseNetUsd`, `maxNetLossUsd`, `autoPaused` - is readable onchain through `CasinoDataV2`.

***

#### Cancellation: the key behavioral difference

If a VRF request stalls, hoSingle-shot gamesw a bet recovers depends on which generation it belongs to.

**Original five:** you can cancel your own stuck bet after `cancelTimeout` (minimum 30 seconds). The operator can also admin-cancel, but you are never dependent on them.

**Newer thirteen:** there is **no user-callable cancel.** This was a deliberate decision - a user cancel sitting in the mempool alongside a pending VRF request is a front-run surface (a player could cancel selectively based on the pending result). The only recovery path is `adminCancelBet`, callable by the resolver role. Core still exposes a `cancelTimeout` value (minimum 30s, currently 600 seconds on all three chains), but for stalled bets in the newer games it's an **operational SLA, not an onchain rule** - the soonest the operator commits to admin-cancelling a stalled bet, surfaced so a frontend can show a "maximum wait before refund." It is not enforced by the contract, because there's no user-callable cancel for it to gate.

**Penalty Shootout adds a second operator path, `adminCashout`.** A run left idle after a goal for longer than `cancelTimeout` can be settled by the resolver at exactly `stake × current multiplier`, the same amount a cash out pays, never less. Here `cancelTimeout` *is* enforced onchain: the contract rejects an `adminCashout` before the run has been idle that long. It exists so abandoned runs don't hold treasury reservations forever.

A cancel - in either generation - refunds your original stake; for the multi-step newer games it forfeits any accumulated multiplier or raises and returns the base stake. Free-bet cancels route the refund back to your free-bet balance.

***

#### House edge, transparently bounded

Across both generations, each game's economics are bounded in code and readable onchain:

* **Roulette** - European single-zero, house edge fixed at \~2.7% by the wheel
* **Dice** - house edge configurable, capped at 5%; payout derived from `(1 − houseEdge) / probability`
* **Blackjack** - 3:2 naturals, dealer hits soft 17; edge depends on rules and play
* **Baccarat** - Banker payout a public parameter, bounded \[1.00×, 2.00×]
* **Slots** - pay table fully public; house edge capped at 5%
* **Plinko** - RTP capped at 98% (≥2% edge), enforced on every paytable update
* **Hi-Lo** - house edge configurable within 2%–5%; per-guess multiplier from a published formula
* **Penalty Shootout, Hoop Streak, Standoff, Shooting Range** - 2% per round, configurable within 2%–5% and snapshotted per bet; each difficulty's payout factor is derived from its odds, `den × (1 − edge) / num` (in Penalty Shootout, `zones × (1 − edge) / openZones`); multiplier cap 100×
* **Cascade** - no closed form, so no onchain edge cap; paytable, symbol weights and bomb table are public state, calibrated by simulation to 96.8% RTP in Normal and 96.7% in Ante and Buy at a 3 USD base bet; max win 1000× base bet, capped by the 3,000 USD per-game profit limit
* **Tumble** - same principle; calibrated to 97.9% RTP for both the 5 and 10 spin rounds; max win is the price plus the 3,000 USD per-game profit limit
* **Keno** - paytables calibrated to \~1.9%–3.1% edge per pick count; multipliers capped at 300×
* **Video Poker** - 8/5 Jacks or Better, \~96.5% RTP under optimal play
* **Three Card Poker** - locked paytables with a guaranteed ≥2% edge
* **Ultimate / Bonus Hold'em** - locked Vegas-standard paytables, side-bet ceilings at 500×

In every case you can read the relevant parameters directly from the contracts and verify the math yourself before you play. For Cascade and Tumble, that means simulating the public model, or replaying any past round with `simulateRound`. That's the whole point: the casino's edge is a number you can check, not a claim you have to trust.
