Gambling — The House Always Wins¶
The four casino games offered at SpaceDock facilities: Cosmic Slots, Nebula Dice, Sector Sweep lottery, and Stellar Blackjack. Each is a deliberate, high-house-edge credit sink — players who chase the jackpot feed the economy's net drain (see lifecycle.md). Every game settles atomically against the player's wallet under a row lock, so no concurrent request can double-spend a bet or claim an unsettled payout.
Source: services/gameserver/src/api/routes/gambling.py.
A player bets credits, the server resolves the game with a server-side RNG (never trusting client-sent outcomes), deducts the stake, and credits any winnings — all inside one with_for_update() transaction on the player row.
1. Venue gating¶
Every game requires the player to be docked (Player.is_docked). The route docstring describes gambling as offered "at SpaceDock facilities," and the station model carries a dedicated Station.is_spacedock flag (services/gameserver/src/models/station.py — bang's Port.isSpaceDock sentinel, mirrored gameserver-side).
✅ Shipped — every game is gated on the docked station being a SpaceDock: each handler calls a _require_spacedock guard that returns 403 unless the docked Station.is_spacedock flag is set. Gambling is offered only at SpaceDock facilities, so the docstring's promise holds; "which stations offer gambling" is every SpaceDock a player can dock at.
Source: services/gameserver/src/api/routes/gambling.py (each handler's _require_spacedock guard after the dock check), services/gameserver/src/models/station.py:is_spacedock.
Status: ✅ Shipped — gambling is gated on the docked station being a SpaceDock.
2. Cosmic Slots¶
A three-reel slot machine. Each reel is drawn independently from a weighted symbol pool; the symbols are 🌍 planet, ⭐ star, 🚀 ship, 💳 credits, 🕳️ black hole, and 💎 jackpot.
Source: services/gameserver/src/api/routes/gambling.py (spin_slots, weighted_random_symbol, SYMBOL_WEIGHTS, SLOT_PAYOUTS).
2.1 Symbol weights¶
Reel draws are weighted (weights sum to 100, so each weight is a direct percent probability per reel):
| Symbol | Weight | Per-reel probability |
|---|---|---|
| 🌍 planet | 25 | 25% |
| ⭐ star | 25 | 25% |
| 🚀 ship | 20 | 20% |
| 💳 credits | 15 | 15% |
| 🕳️ black hole | 10 | 10% |
| 💎 jackpot | 5 | 5% |
2.2 Payouts¶
Three of a kind pays the bet times the symbol's multiplier (win_amount = bet × multiplier):
| Three of a kind | Multiplier |
|---|---|
| 💎💎💎 jackpot | 50× |
| 🚀🚀🚀 ship | 10× |
| ⭐⭐⭐ star | 8× |
| 🌍🌍🌍 planet | 5× |
| 💳💳💳 credits | 3× |
| 🕳️🕳️🕳️ black hole | 0× (loses the bet) |
Two adjacent matching symbols (reels 1–2 or reels 2–3 equal, but not all three) return half the bet (bet // 2). Any other outcome loses the bet. A jackpot result (💎💎💎) sets the response jackpot flag.
2.3 Return to player¶
With these weights and payouts the machine's return to player is RTP ≈ 45.6% — a house edge of ≈ 54.4%. The three-of-a-kind tier contributes ≈ 30.0% RTP; the two-adjacent-match consolation (probability ≈ 31.25%, paying only 0.5×) adds ≈ 15.6%. The black-hole symbol exists purely to deepen the edge: three of them pays nothing.
Status: ✅ Shipped — live and concurrency-hardened: the bet deduction and payout are atomic under a with_for_update() player-row lock with populate_existing() (row-locked settle, commit 9a91895).
3. Nebula Dice¶
Two six-sided dice. The player bets on the total: high (8–12), low (2–6), or an exact total (2–12). Two outcomes override the bet type.
Source: services/gameserver/src/api/routes/gambling.py (roll_dice).
3.1 Special outcomes¶
| Outcome | Trigger | Payout |
|---|---|---|
| Supernova | double sixes (6 + 6) | 35× the bet, regardless of bet type |
| The Void | total of 7 | the house always wins — 0× |
The Void claims the natural mode of two dice (total 7 is the single most likely roll), which is why neither high nor low covers it.
3.2 Bet types¶
| Bet type | Wins when | Payout |
|---|---|---|
| high | total is 8–12 (and not 7, not double-six) | 2× |
| low | total is 2–6 | 2× |
| exact N | total equals the chosen N | per-target multiplier (below) |
3.3 Exact-total payouts¶
An exact bet pays a multiplier scaled to the rarity of each total:
| Total N | Multiplier |
|---|---|
| 2 | 35× |
| 3 | 17× |
| 4 | 11× |
| 5 | 8× |
| 6 | 6× |
| 7 | 5× |
| 8 | 6× |
| 9 | 8× |
| 10 | 11× |
| 11 | 17× |
| 12 | 35× |
An exact bet on 7 nominally pays 5×, but a rolled 7 is The Void (house wins), so an exact-7 bet can only ever pay out via a double-six Supernova (which pays 35×, not 5×). An unrecognised exact target defaults to 5×.
Status: ✅ Shipped — live and concurrency-hardened: row-locked atomic settle (commit 9a91895).
4. Sector Sweep (lottery)¶
A pick-4 lottery. The player chooses 4 unique sector numbers from 1–12; the server draws 4 unique winning numbers from the same pool (random.sample(range(1,13), 4)). The payout scales with how many of the player's numbers match the draw.
Source: services/gameserver/src/api/routes/gambling.py (buy_lottery_ticket, LOTTERY_PAYOUTS).
Ticket cost is bounded 100–5,000 credits per ticket. All four chosen numbers must be unique and within 1–12, or the request is rejected.
4.1 Match-tier payouts¶
| Matches | Multiplier | Result |
|---|---|---|
| 4 | 1000× | Jackpot |
| 3 | 50× | |
| 2 | 5× | |
| 1 | 1× | returns the bet |
| 0 | 0× | loses the bet |
Matching all 4 (a 4-of-12 sweep) is the jackpot and sets the response jackpot flag; matching exactly 1 returns the stake (break-even).
Status: ✅ Shipped — live and concurrency-hardened: row-locked atomic settle (commit 9a91895).
5. Stellar Blackjack¶
A single-hand blackjack table against the dealer. The flow is two endpoints: /blackjack/deal opens a hand, /blackjack/action resolves each hit / stand / double until the hand ends.
Source: services/gameserver/src/api/routes/gambling.py (blackjack_deal, blackjack_action, reconstruct_blackjack_hands).
5.1 Bet bounds¶
A blackjack stake is bounded 10–10,000 credits (the same bound applies to slots and dice; lottery tickets use the 100–5,000 range). The stake is deducted at deal time.
5.2 Rules¶
- Standard card values: face cards 10, ace 11 or 1 (soft-counted down automatically when the hand would bust).
- The deck is a single 52-card deck, shuffled with a server-chosen seed at deal time.
- The dealer shows one card and holds one card hidden until the player stands or busts.
- Natural blackjack (player's first two cards total 21) pays 3:2 (
win_amount = bet × 2.5); if the dealer also has a natural it is a push (bet returned). - The dealer hits on 16 or less and stands on 17 or more (stands on all 17s, including soft).
- Double down is allowed only on the first two cards: the stake doubles, the player takes exactly one card, and the hand stands. A player who busts after doubling always loses — a busted hand never out-ranks the dealer.
- A non-blackjack win pays 2× (even money); a push returns the stake; a bust or a loss to the dealer forfeits the stake.
5.3 Server-authoritative anti-faucet design¶
Blackjack is fully server-authoritative to close credit-fabrication exploits. The server stores the authoritative game state (deck seed, stake, player-card count) on the player record at deal time and rebuilds both hands deterministically from the seed on every action — client-sent cards, seed, and bet are ignored. Consequences:
- A payout cannot be claimed without a real, un-settled deal:
/blackjack/actionwith no stored active hand is rejected ("deal first"). - A settled hand cannot be replayed for a second payout — the stored game is cleared the moment the hand ends.
- A double-down deducts the stored stake, never a client-supplied amount, so the bet cannot be inflated mid-hand.
- The player row is locked (
with_for_update()) across the deal/action so two concurrent requests cannot run two live hands or double-spend.
Status: ✅ Shipped — live, server-authoritative, and concurrency-hardened: anti-faucet server-side hand reconstruction landed in commit b0b988d; the row-locked atomic settle pattern matches the other three games.
6. Cross-references¶
- lifecycle.md — the full credit faucet/sink loop; gambling is catalogued there as a net-house-edge sink.
- trading.md — the legitimate way to grow credits (gambling is the opposite).
- docking-slips.md — docking at stations, the precondition for gambling.