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

  1. Check registration, casino channels, per-game enabled flag, and cooldowns.
  2. Validate bet against min/max and available cash + bonus.
  3. reserveCasinoBet with a generated betId (or multi-bet slip for session games).
  4. Run game logic - RNG for instant games, button-driven state for interactive ones.
  5. 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 * 100
Tunable 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.

Repositories & community