Chapter 3
Casino Engine & Fairness
A full suite of configurable games, from instant rolls to multi-bet interactive sessions, share one reserve/settle pipeline. RTP is computed from guild settings and probability models, not marketing labels. Per-game enable toggles let ops turn titles on or off without redeploying.
Shared play pipeline
- Check registration, casino channels, per-game enabled flag, and cooldowns.
- Validate bet against min/max and available cash + bonus.
- reserveCasinoBet with a generated betId (or multi-bet slip for session games).
- Run game logic - RNG for instant games, button-driven state for interactive ones.
- settleCasinoWinnings / refunds and render result embeds.
Instant games
Instant titles still share the reserve/settle pipeline, channel gates, cooldowns, and per-game enable flags. They just resolve in one interaction instead of a multi-message session.
Dice
- Pick a side (1-6); optional multi-roll in one command.
- Win when the roll matches; payout scales with guild winMultiplier.
- RTP model: (1/6) x winMultiplier from shared calculateRTP.
Limbo
- Target-multiplier risk: players choose how far to push.
- House edge and minimum multiplier are guild-configurable.
- Designed as a high-agency instant game next to dice/coinflip.
Coinflip
- Heads/tails with optional multi-flip.
- winMultiplier sets payout and RTP (0.5 × multiplier).
Lottery
- Pick numbers; draws use fixed shared pool sizes.
- Match-count payouts; RTP from combinatoric odds.
Golden Jackpot
- High-variance tickets against oneInChance with a large winMultiplier.
- RTP ≈ winMultiplier / oneInChance.
- Moderator simulation helpers exist for stress-testing jackpot math.
Interactive sessions
Unlike instant titles (dice, coinflip, limbo, lottery, golden jackpot), these games stay open across messages. Each keeps a durable MongoDB session, Discord button UI, locked balances, and workers that nudge or settle when a player walks away: blackjack, baccarat, mines, hi-lo, plinko, roulette, and slots.
How a session runs
The player opens a table, configures a stake (or a multi-line slip), plays through buttons, then rebet / change / close without restarting the command. While a stake is live, balances stay locked so a refresh, reconnect, or mid-hand crash cannot double-spend. House edge and multipliers are snapshotted when the round locks so mid-play config edits cannot rewrite an open hand.
What keeps it safe
- Shared lifecycle: open table → configure bet → play → rebet / change / close.
- Locks held until settle, cash-out, refund, or idle recovery.
- Idle nudge workers DM after long inactivity; timeout workers auto-resolve or close and refund.
- Orphan cleanup and lock reconciliation cover guild leaves and stuck in-flight rows.
- Multi-bet slips where it matters - baccarat sides, blackjack insurance and pairs.
- Per-game enable flags and channel gates still apply before a session can open.
Blackjack
Full table blackjack inside Discord: persistent shoe state, multi-hand play, insurance, and optional Perfect Pairs / 21+3 side bets - all settled through the same reserve/settle ledger as every other game.
- Hit, stand, double, and split from Discord buttons on a persisted session (phases: betting, insurance, player, dealer, result).
- Multi-split up to 4 hands. Matching ranks only; Aces split once from the original deal (no Ace resplit) and auto-finish after one card each.
- Double only on two-card hands: reserves an equal extra stake and doubles that hand bet.
- Natural blackjack settled at dealer peek: player BJ pays the blackjack multiplier, both BJ pushes, dealer BJ loses the main. Mid-hand 21 pays normal win, not BJ.
- Insurance on Ace-up when enabled (half the main bet). Perfect Pairs and 21+3 side bets with their own multipliers; set any to 0 to disable.
- Dealer stands on soft 17 (S17). Fresh shoe every hand from guild deckCount (2-8, default 6).
- Idle nudge after hours of inactivity; auto-stand after a long stall. Empty tables idle-close with lock cleanup. Rebet / Change / Close between hands.
- Main-game RTP approximated from S17 outcome weights; insurance / pairs / 21+3 use exact helpers vs shoe size.
Baccarat
Punto banco on a fresh 8-deck shoe each round, with a multi-side betting slip instead of a single stake. Players tap sides, enter amounts, then Deal - same side can be topped up by merging amounts on the slip.
- Main lines: Player, Banker, Tie. Player/Banker push on tie (1x return). Banker pays a slightly lower multiplier by default to encode commission-style edge.
- Side lines: player/banker/either/perfect pairs, big (5-6 cards), small (4 cards), player/banker Dragon Bonus (tiered win-by-4-9 + natural rules), Lucky 6 (banker total 6 with 2-card vs 3-card tiers).
- Slip resolution pays each line independently; total winnings sum stake x multiplier across the slip.
- Session UI supports Rebet / Change between rounds. Idle nudge and 24h close refund a locked slip if the player abandons mid-table.
- Per-side RTP from published 8-deck probabilities in the shared package - dashboard editors show the same numbers the bot uses.
Mines
A 5x4 (20-cell) board with a fisher-yates mine layout. Players reveal safe cells as the fair multiplier climbs, then cash out - or clear the board for an automatic cash-out. Hitting a mine busts the round.
- Mine count is guild-configurable (default 1-10, hard max 19 so at least one safe cell exists). Board size is fixed for consistent Discord UX.
- Payout = fair combinatorial multiplier x (1 - houseEdge). House edge (default 3%) is snapshotted on the session so mid-game setting edits cannot rewrite an open board.
- Cash-out requires at least one safe reveal. Clearing all safe cells auto cash-outs.
- Session states: SETUP, ACTIVE, RESULT. Idle nudge; after 24h ACTIVE games auto-resolve (cash out if any safe reveals, otherwise forfeit).
- Reported RTP is simply (1 - houseEdge) x 100 - transparent and tunable from the admin casino accordion.
Hi-Lo
Continuous streak table on a full 52-card deck without replacement. Open a table, set a bet, Deal, then guess Higher / Draw / Lower to compound the multiplier. Cash out after a correct guess, or bust on a miss. After a round: Rebet, Change bet, or Close.
- Phases: BETTING → WAITING → SETTLING → RESULT, with remaining deck, streak, and compound multiplier persisted on the session.
- Step multiplier is fair given remaining cards and the chosen guess, then scaled by (1 - houseEdge). Same-rank outcomes lose Higher/Lower (Draw is its own choice).
- Correct guesses CONTINUE the streak (card-to-beat updates); empty deck forces cash-out; bust ends the round at 0.
- Optional skipAnimations for faster table play. House edge is snapshotted when the round locks.
- Idle: DM nudge after ~30m waiting. After ~1h, an active streak auto cash-outs; otherwise the safest first guess is auto-played. Abandoned BETTING/RESULT tables close after ~24h.
- RTP tracks (1 - houseEdge) x 100: same mental model as Mines for managers.
Plinko
Plinko is no longer a one-shot slash reply. It is a durable board session: set a unit bet, then Drop x1 / x5 / x10 as many times as you want. Each ball pays the bin multiplier where it lands. Rebet / Change / Close sit between drops the same way as other interactive tables.
- Session phases: ready → dropping → result. Pending ball paths are predetermined for an in-flight batch and recovered if the process restarts mid-drop.
- Unit bet + ballsCount (1-10) lock exposure up front; activeBetId + lockedAmount track the open batch until settle.
- RTP still uses binomial path probabilities over nine bins (Galton-board 50/50 steps). Bin multipliers are editable with live RTP preview; mirrored bin editors keep the board symmetric.
- Optional skipAnimations. Idle nudge after ~3h; boards close after ~24h with no new drop (locks refunded via the same recovery path as other session games).
- Covered by plinkoIdleNudge / plinkoIdleClose workers, locked-balance reconciliation, and orphan cleanup when a guild leaves.
Roulette
Live European single-zero table in Discord (0-36: one green, eighteen red, eighteen black) with a multi-bet slip, up to 8 lines per spin. Same type+value lines merge by summing amounts before the wheel spins. Animation and result use the full IRL pocket order - any pocket can land.
- Bet types: straight number, color, parity, range (low 1-18 / high 19-36), dozens, and columns. Zero loses color/parity/range/dozen/column.
- Live table session: betting, spinning, result, then Rebet / Change. Idle nudge and close refund locked slip funds.
- Defaults match European geometry (straight number 36x). Per-type RTP from shared calculateRTP over the full wheel.
Slots
Interactive machine session - not a single slash reply. Players set a unit bet and batch 1-10 spins, locking the full exposure up front, then spin through weighted reels in Discord.
- Three independent weighted symbol picks; only exact triples listed in winMultipliers pay (defaults like cherry/blueberry/watermelon/bell/seven with rising payouts).
- Mid-batch spinning phase is recoverable via casino in-flight recovery if the process restarts mid-spin.
- Idle nudge / 24h close refunds a mid-spin lock. RTP = sum of P(symbol)^3 x triple multiplier from guild weights.
Rock Paper Scissors (PvP)
Player-vs-player challenge with matched stakes under one shared betId. Both balances lock before choices; timeouts cancel with full refunds.
- Challenger tags a non-bot user. Target picks first, then challenger (about 30s each) or cancel and refund both.
- Winner takes pot x (1 - houseEdge). Default house edge 2.5% (legacy casinoCut migrated to houseEdge). Draw refunds both players.
- No long-lived table document, only ephemeral Discord collectors, but locked-balance reconciliation still understands pending RPS pairs so stuck locks do not rot.
RTP & per-game controls
calculateRTP in gambling-bot-shared derives effective return from settings and game-specific probability, not hardcoded marketing labels. The dashboard shows live RTP (with warnings when RTP ≤ 90% or ≥ 100%) while editing casino settings. Each game also has an enabled flag so managers can kill a title without touching global betting switches.
- Dice: (1/6) x winMultiplier.
- Coinflip: 0.5 x winMultiplier.
- Slots: Σ P(symbol)^3 x triple multiplier from guild weights.
- Lottery: hypergeometric match probabilities x tier multipliers.
- Roulette: separate RTP per bet type from the European 0-36 single-zero layout.
- Plinko: binomial path probabilities x bin multipliers (interactive Drop batches on a durable board).
- RPS: (1 - houseEdge) x 100 on the matched pot.
- Golden Jackpot: winMultiplier / oneInChance.
- Blackjack: ~99.5% main-game baseline at S17 defaults; exact helpers for insurance, Perfect Pairs, and 21+3 vs shoe size.
- Baccarat: per-side RTP from published 8-deck probabilities (player/banker/tie + side bets).
- Mines & Hi-Lo: (1 - houseEdge) x 100 with fair combinatorial / remaining-deck streak math underneath.
- Limbo: house-edge aware target-multiplier model.
- Prediction / raffle markets: odds and house cut are explicit; not folded into the same auto-RTP map as table games.
Example: Plinko RTP (binomial bins)
for (let k = 0; k <= N; k++) {
const p = C(N,k) * 0.5^k * 0.5^(N-k)
rtp += p * binMultipliers[k]
}
return rtp * 100Tunable without redeploy
Multipliers, weights, cuts, bet limits, side-bet payouts, deckCount, mine ranges, and per-game enable flags live in GuildConfiguration.casinoSettings and are editable from the admin casino accordion without restarting the bot.