Files
wownerogue/src/.env.example
T
jwinterm 0f68eec26b feat: real-time multiplayer race mode (free, prestige, crypto)
Adds a complete block-cadence multiplayer race mode alongside solo and tavern.
  Disabled by default; enable with MATCH_ENABLED=true.

  Highlights:
  - New shared engine: MatchRoom, MatchEngine, MatchState (transport-agnostic, unit-tested).
  - Per-economy queues: free, credits_prestige, crypto_race with atomic escrow/refund.
  - MatchScheduler drains queues on every new block and starts 2–32 player races.
  - MatchManager handles Socket.IO lifecycle, server-authoritative movement, reconnect grace,
    forfeit/AFK handling, and end-of-match persistence.
  - Crypto races use race-entry tickets (grants.race_entries) to avoid on-chain refunds; winner
    payout is queued through the existing batch payout/retry service.
  - Leaderboard integration: free → Pleb, prestige → /api/leaderboard?board=prestige,
    crypto → Hall of Champions.
  - Tavern integration: Queue for Race panel, active race list, and live spectator rendering.
  - Dedicated /match.html client with economy selector, race HUD, and post-match scoreboard.
  - Migration 022 adds matches, match_entrants, match_events, match_queue_entries,
    race_entry_transactions, users.race_entries, and payouts.match_id.

  Docs:
  - New docs/MATCH_MODE.md with full architecture, config, events, and operational notes.
  - Updated README.md, docs/TAVERN_AND_MULTIPLAYER.md, docs/TODO.md, src/.env.example,
    and src/.env.

  Tests: 40 new unit tests across 7 suites, all passing.

  Known follow-ups for real-world validation:
  - Full integration test with Postgres + Socket.IO.
  - Server boot check with MATCH_ENABLED=false (default) to ensure solo/tavern remain unaffected.
  - Browser render-kit verification in /match.html and tavern spectator.
  - Crypto-race payout end-to-end with real wallet-rpc.
2026-07-09 13:26:52 -04:00

521 lines
24 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# Wownerogue Server Configuration
# =============================================================================
# Copy this file to .env and customize for your environment.
# The server validates these settings at startup and warns about misconfigurations.
#
# QUICK START CONFIGURATIONS:
# ---------------------------
# 1. FREE MODE (no payments, just play):
# PAYMENTS_ENABLED=false
#
# 2. PAID MODE (1 WOW per game, 2x payout on win):
# PAYMENTS_ENABLED=true
# PAYMENT_MODES=direct
# + Configure wallet RPC and database
#
# 3. CREDITS MODE (buy bundles, play multiple games):
# PAYMENTS_ENABLED=true
# PAYMENT_MODES=credits
# + Configure wallet RPC and database
#
# 4. BOTH MODES (let users choose):
# PAYMENTS_ENABLED=true
# PAYMENT_MODES=direct,credits
# =============================================================================
# UNDERSTANDING ATOMIC UNITS
# =============================================================================
# Wownero (like Monero) uses "atomic units" - the smallest indivisible unit.
#
# 1 WOW = 100,000,000,000 atomic units (10^11, or 100 billion)
#
# Examples:
# 1 WOW = 100000000000
# 0.5 WOW = 50000000000
# 0.1 WOW = 10000000000
# 5 WOW = 500000000000
# 10 WOW = 1000000000000
#
# Why? Floating point math is imprecise. Atomic units ensure exact calculations.
# =============================================================================
# SECTION 1: BASIC SERVER SETTINGS
# =============================================================================
# Server port (default: 3000)
PORT=3000
# Environment: production | development | debug
# - production: minimal logging, real blockchain
# - development: verbose logging, can use simulated blocks
# - debug: maximum logging, debug hotkeys enabled
NODE_ENV=production
# Network selection (only matters for Monero - Wownero only has mainnet)
MONERO_NETWORK=mainnet
# Optional: Display "Hosted by" link in footer (leave empty to hide)
# Example: https://yoursite.com or https://github.com/yourname
# HOSTED_BY=https://example.com
# HOSTED_BY_NAME=Example Host
# =============================================================================
# SECTION 2: DATABASE (PostgreSQL) - REQUIRED
# =============================================================================
# The game stores user sessions, payment history, and credits in PostgreSQL.
# You must create the database and user first (see README).
DB_HOST=localhost
DB_PORT=5432
DB_NAME=wownerogue
DB_USER=wownerogue
DB_PASSWORD=your_secure_password_here
# =============================================================================
# SECTION 3: GAME DIFFICULTY
# =============================================================================
# Controls dungeon size, monster behavior, and effective house edge.
#
# Preset | Dungeon | Monster Speed | Player Win Rate | Best For
# ------------|----------|---------------|-----------------|------------------
# easy | 30x15 | Slow | ~70% | Free play, demos
# normal | 45x22 | Medium | ~45% | Balanced gameplay
# hard | 55x28 | Fast | ~35% | Challenge mode
# casino | 70x35 | Fast | ~30% | Paid modes (default)
#
# If unset, defaults to 'casino' for paid modes, 'normal' for free mode.
# DIFFICULTY_PRESET=casino
# Fine-tune individual difficulty parameters (override preset defaults).
# Only set these if you want to deviate from the chosen preset.
#
# Dungeon layout:
# DUNGEON_WIDTH=70 # Map width in tiles
# DUNGEON_HEIGHT=35 # Map height in tiles
# DUNGEON_DUG_PERCENTAGE=0.10 # % of map that's open space (lower = tighter)
# DUNGEON_ROOM_WIDTH_MIN=3 # Room width range (min)
# DUNGEON_ROOM_WIDTH_MAX=4 # Room width range (max)
# DUNGEON_ROOM_HEIGHT_MIN=3 # Room height range (min)
# DUNGEON_ROOM_HEIGHT_MAX=4 # Room height range (max)
# DUNGEON_CORRIDOR_MIN=6 # Corridor length range (min)
# DUNGEON_CORRIDOR_MAX=15 # Corridor length range (max)
#
# Monster behavior:
# MONSTER_SPEED=1.0 # Moves per player move (1.0 = same speed, 0.5 = half)
# MONSTER_CHASE=0.97 # Chase probability 0.0-1.0 (rest is random wander)
# MONSTER_VISION=22 # Vision range in tiles
# MONSTER_DISTANCE=0.30 # Starting distance ratio 0.0-1.0 (lower = closer)
#
# Treasure placement:
# TREASURE_ROOM_POSITION=0.20 # Room position ratio 0.0-1.0 (lower = earlier room)
# TREASURE_EXIT_DISTANCE=0.85 # Distance from exit ratio 0.0-1.0 (higher = farther)
# =============================================================================
# SECTION 4: PAYMENT SYSTEM OVERVIEW
# =============================================================================
#
# PAYMENTS_ENABLED is the master switch:
# false = FREE mode, anyone can play, no crypto involved
# true = Paid mode, requires wallet RPC configuration
#
# PAYMENT_MODES determines HOW users pay (when payments enabled):
#
# ┌─────────────────────────────────────────────────────────────────────────┐
# │ MODE: direct │
# ├─────────────────────────────────────────────────────────────────────────┤
# │ • User pays X WOW per game │
# │ • If they WIN (escape dungeon): get PAYOUT_ESCAPE × X back (e.g., 2x) │
# │ • If they WIN with treasure: get PAYOUT_TREASURE × X back (e.g., 3x) │
# │ • If they LOSE (die): house keeps the payment │
# │ │
# │ Example: Pay 1 WOW, win = get 2 WOW. Die = lose 1 WOW. │
# └─────────────────────────────────────────────────────────────────────────┘
#
# ┌─────────────────────────────────────────────────────────────────────────┐
# │ MODE: credits │
# ├─────────────────────────────────────────────────────────────────────────┤
# │ • User buys a PACKAGE of credits upfront (e.g., 10 credits for 5 WOW) │
# │ • Each game costs 1 credit (or CREDITS_PER_GAME) │
# │ • Packages can include bonus credits (bulk discount) │
# │ • Payouts are optional - credits are usually just for playing │
# │ │
# │ Example: Buy 10 credits for 5 WOW = 0.5 WOW/game (50% discount!) │
# └─────────────────────────────────────────────────────────────────────────┘
#
# ┌─────────────────────────────────────────────────────────────────────────┐
# │ MODE: direct,credits (both enabled) │
# ├─────────────────────────────────────────────────────────────────────────┤
# │ • Users can choose which mode to use │
# │ • If PREFER_CREDITS_FIRST=true, credits are auto-used when available │
# │ • Good for giving users flexibility │
# └─────────────────────────────────────────────────────────────────────────┘
# Master switch: Set to false for completely free play (no wallet needed)
PAYMENTS_ENABLED=true
# FREE_PLAY_ENABLED: offer FREE play as a CHOICE even when payments are on. Free games
# are recorded as game_mode=FREE and appear on the Pleb leaderboard (NOT the Hall of
# Champions, which is for paid games). Always effectively on when PAYMENTS_ENABLED=false.
#
# DEPLOYMENT PATTERNS
# • Real-money / mainnet (legitimacy): free-to-play + sell credits/entry, but NO PAYOUTS.
# Players pay for prestige (Hall of Champions), not crypto winnings — not gambling.
# PAYMENTS_ENABLED=true FREE_PLAY_ENABLED=true PAYOUTS_ENABLED=false
# DIRECT_PAYOUTS_ENABLED=false CREDITS_PAYOUTS_ENABLED=false
# DIRECT_REQUIRES_ADDRESS=false CREDITS_REQUIRES_ADDRESS=false (no payout = no address)
# • Test/stagenet (fake money): full pay-to-play WITH payouts is fine.
# PAYMENTS_ENABLED=true PAYOUTS_ENABLED=true *_PAYOUTS_ENABLED=true
FREE_PLAY_ENABLED=false
# Which payment modes to enable (comma-separated)
PAYMENT_MODES=direct,credits
# =============================================================================
# SECTION 5: DIRECT MODE CONFIGURATION
# =============================================================================
# Only relevant if PAYMENT_MODES includes "direct"
# Enable/disable direct payments
DIRECT_PAYMENT_ENABLED=true
# Price per game in atomic units (100000000000 = 1 WOW)
DIRECT_GAME_PRICE=100000000000
# Must user provide a payout address before playing?
# true = User must set address first (ensures they can receive winnings)
# false = User can play without address (but can't receive payouts!)
DIRECT_REQUIRES_ADDRESS=true
# Allow guest play without any address? (Usually false for paid modes)
DIRECT_ALLOW_GUEST_PLAY=false
# Payout multipliers for direct mode wins:
# If user pays 1 WOW and ESCAPE multiplier is 2.0, they get 2 WOW on escape.
DIRECT_PAYOUTS_ENABLED=true
DIRECT_PAYOUT_ESCAPE=2.0 # Escape dungeon = 2x payment
DIRECT_PAYOUT_TREASURE=3.0 # Escape WITH treasure = 3x payment
# =============================================================================
# SECTION 6: CREDITS MODE CONFIGURATION
# =============================================================================
# Only relevant if PAYMENT_MODES includes "credits"
# Enable/disable credits system
CREDITS_ENABLED=true
# How many credits does one game cost?
CREDITS_PER_GAME=1
# Must user provide address for credits mode?
CREDITS_REQUIRES_ADDRESS=true
# When both modes enabled:
ALLOW_MIXED_MODE=true # Show both options to user
PREFER_CREDITS_FIRST=true # Auto-deduct credits if user has them
# Credits payouts (usually DISABLED - credits are a bulk discount, not gambling)
CREDITS_PAYOUTS_ENABLED=false
CREDITS_PAYOUT_BASE=50000000000 # Base amount for credit wins
CREDITS_PAYOUT_ESCAPE=2.0 # Same as direct mode
CREDITS_PAYOUT_TREASURE=3.0 # Same as direct mode
# Credit packages - JSON array of available bundles
# Fields:
# id - Unique identifier (shown in UI)
# credits - Number of game credits included
# price - Cost in atomic units (as STRING to avoid JS number precision issues)
# bonus - Extra free credits (0 for no bonus)
#
# Default packages (small discounts to encourage bulk purchase):
# small: 10 credits for 9 WOW (~10% discount vs 1 WOW/game direct)
# medium: 20 credits for 17 WOW (~15% discount)
# large: 50 credits for 40 WOW (~20% discount)
#
# Tip: Format JSON with newlines for readability, then minify for .env:
# [
# {"id":"small", "credits":10, "price":"900000000000", "bonus":0},
# {"id":"medium", "credits":20, "price":"1700000000000", "bonus":0},
# {"id":"large", "credits":50, "price":"4000000000000", "bonus":0}
# ]
#
CREDITS_PACKAGES='[{"id":"small","credits":10,"price":"900000000000","bonus":0},{"id":"medium","credits":20,"price":"1700000000000","bonus":0},{"id":"large","credits":50,"price":"4000000000000","bonus":0}]'
# Credit packages can bundle cosmetic/render pack grants and a premium tier via
# a "grants" field. Example:
# CREDITS_PACKAGES='[{"id":"supporter_25","credits":20,"bonus":5,"price":"1000000000000","grants":{"packs":["generated-skins","kenney-3d-characters"],"premiumLevel":"supporter"}}]'
# Standalone cosmetic products (no game entry or credits required). Each product
# may grant packs and/or a premium tier via "grants". JSON array; empty by default.
# COSMETIC_PRODUCTS='[{"id":"pack_3d","label":"3D Character Pack","price":"250000000000","grants":{"packs":["kenney-3d-characters"]}}]'
# =============================================================================
# SECTION 6b: EARLY ENTRY
# =============================================================================
# Early entry lets players start a game immediately without waiting for the
# next block. Risk: if a new block arrives before they escape, the game ends.
#
# Disabled by default for direct (paid-per-game) mode since players paid for
# a full block window. Useful for free and credits modes.
# Master toggle
EARLY_ENTRY_ENABLED=true
# Allow early entry in free mode?
EARLY_ENTRY_FREE_MODE=true
# Allow early entry in credits mode?
EARLY_ENTRY_CREDITS_MODE=true
# =============================================================================
# SECTION 7: PAYOUT SETTINGS
# =============================================================================
# Controls how winnings are sent back to players.
# Payouts are batched for efficiency and to reduce transaction fees.
# Master payout switch (disable to accept payments but never pay out - EVIL!)
PAYOUTS_ENABLED=true
# Minimum payout amount (atomic units). Prevents dust transactions.
# 10000000000 = 0.1 WOW
PAYOUT_MIN_AMOUNT=10000000000
# Maximum payout per single game (safety limit)
# 10000000000000 = 100 WOW
PAYOUT_MAX_PER_GAME=10000000000000
# How often to process payout queue (seconds). 300 = every 5 minutes.
PAYOUT_BATCH_INTERVAL=300
# Retry failed payouts this many times before marking as permanently failed.
# Failed payouts are logged with error details. Check logs or query:
# SELECT * FROM payouts WHERE status = 'failed';
# To retry a permanently failed payout, reset it with:
# UPDATE payouts SET status = 'pending', retry_count = 0 WHERE id = <id>;
PAYOUT_MAX_RETRIES=3
# Max payouts to send in one batch (wallet RPC can timeout on large batches)
MAX_PAYOUT_BATCH_SIZE=50
# How often to retry failed/stuck payouts (milliseconds). Default: 300000 (5 min)
# PAYOUT_RETRY_INTERVAL_MS=300000
# =============================================================================
# SECTION 8: ANTI-ABUSE LIMITS
# =============================================================================
# Protect against bots and abuse
MAX_GAMES_PER_HOUR=60 # Max games one user can play per hour
MAX_PAYOUTS_PER_DAY=100 # Max payout transactions per user per day
MAX_CREDIT_PURCHASE_PER_DAY=100000000000000 # Max credit purchase (1000 WOW)
GAME_COOLDOWN_SECONDS=5 # Minimum seconds between games
# =============================================================================
# SECTION 9: WALLET RPC - REQUIRED FOR PAID MODES
# =============================================================================
# The wallet RPC handles:
# 1. Generating unique payment addresses for each user
# 2. Checking for incoming payments
# 3. Sending payouts to winners
#
# You must run wownero-wallet-rpc (or monero-wallet-rpc) separately:
#
# wownero-wallet-rpc \
# --wallet-file /path/to/wallet \
# --password "wallet-password" \
# --rpc-bind-port 34570 \
# --rpc-login user:password \
# --disable-rpc-login # OR use rpc-login for security
#
# The wallet must be synced with a daemon to see incoming transactions.
PRIMARY_WALLET_ENDPOINT=http://127.0.0.1:34570
# If your wallet-rpc uses --rpc-login, set credentials here:
# WALLET_RPC_USER=user
# WALLET_RPC_PASSWORD=password
# =============================================================================
# SECTION 10: BLOCKCHAIN DAEMON RPC
# =============================================================================
# Used to get current block height for game synchronization.
# This is the daemon (wownerod/monerod), NOT the wallet.
#
# The game syncs to block timing - each block triggers game state checks.
PRIMARY_RPC_ENDPOINT=http://127.0.0.1:34568
FALLBACK_RPC_ENDPOINT=http://127.0.0.1:34568
# How often to poll for new blocks (milliseconds)
RPC_POLL_INTERVAL=2000
# =============================================================================
# SECTION 11: ADMIN API
# =============================================================================
# Secure endpoints for refunds, credit adjustments, and user management.
# Generate a strong random key and keep it secret!
# Generate with: openssl rand -hex 32
# ADMIN_API_KEY=your-64-character-hex-key-here
# Use in requests:
# curl -H "X-Admin-Key: your-key" http://localhost:3000/api/admin/...
# Allowed origins for admin API CORS (comma-separated, only needed for cross-origin)
# Same-origin requests are always allowed. Leave unset if admin panel is on same host.
# ALLOWED_ORIGINS=https://admin.example.com
# Set to 'true' ONLY when running behind a trusted reverse proxy (Nginx/Caddy). When
# true, the real client IP is read from the X-Forwarded-For header for rate limiting and
# req.ip. Leave unset/false for direct exposure, otherwise clients could spoof their IP.
# TRUST_PROXY=true
# Anti-instant-death grace (ms) before a block-timeout can end a game. This is NOT a
# fairness floor — random block timing is the core mechanic. It only prevents the
# degenerate case where a block lands the instant a game starts (before the dungeon
# renders / before the first move is possible). Default 2000 (2s); set 0 to disable.
# GAME_START_GRACE_MS=2000
# =============================================================================
# SECTION 12: SERVER & MULTI-INSTANCE
# =============================================================================
# Port for HTTP server (default 3000)
# Use different ports for multiple instances (e.g., 3000 for WOW, 3001 for XMR)
PORT=3000
# =============================================================================
# SECTION 13: ADMIN ALERTS (Resend)
# =============================================================================
# Email alerts for low wallet balance and critical issues.
# Sign up at https://resend.com to get an API key.
# Resend API key
# RESEND_API_KEY=re_xxxxxxxxxxxxx
# Admin email to receive alerts
# ADMIN_EMAIL=admin@example.com
# From address for alert emails (must be verified in Resend)
# Default: alerts@monerogue.app
# ALERT_FROM_EMAIL=noreply@yourdomain.com
# -------------------------
# BALANCE THRESHOLDS
# -------------------------
# Two-tier alert system for wallet balance monitoring:
#
# BALANCE_WARN: Sends email alert when balance drops below this level
# You should top up the wallet soon.
#
# BALANCE_CRITICAL: Sends email AND HALTS NEW GAMES when balance drops below
# Users will see a modal: "Sorry, the house balance is too low..."
# Games resume automatically when balance is restored above this threshold.
#
# All values in atomic units:
# WOW: 1 WOW = 100000000000 (10^11)
# XMR: 1 XMR = 1000000000000 (10^12)
# Warning threshold - sends email alert (default: 0.1 WOW/XMR)
BALANCE_WARN=100000000000
# Critical threshold - halts new games (default: 0.01 WOW/XMR)
BALANCE_CRITICAL=10000000000
# Legacy alias (superseded by BALANCE_WARN, kept for backwards compatibility)
# LOW_BALANCE_THRESHOLD=100000000000
# -------------------------
# OTHER ALERT SETTINGS
# -------------------------
# Alert when pending/failed payouts exceed this count
HIGH_PENDING_THRESHOLD=10
# Alert cooldown in milliseconds (avoid spam)
# Default: 3600000 (1 hour)
ALERT_COOLDOWN_MS=3600000
# =============================================================================
# SECTION 14: SMIRK WALLET INTEGRATION
# =============================================================================
# Smirk is a browser extension wallet for Wownero/Monero.
# https://smirk.network
#
# Enable this for Wownero mainnet. Disable for Monero stagenet
# (Smirk doesn't support stagenet addresses).
# Enable Smirk wallet connection button (default: true)
# Set to false for Monero stagenet deployments
SMIRK_ENABLED=true
# =============================================================================
# SECTION 14b: BLOCK EXPLORER
# =============================================================================
# URL prefix for linking transaction hashes to a block explorer.
# Include the trailing slash. TX hash will be appended directly.
# Leave unset to disable clickable TX links in transaction history.
#
# Wownero: https://explore.wowne.ro/tx/
# Monero: https://xmrchain.net/tx/
EXPLORER_TX_URL=https://explore.wowne.ro/tx/
# =============================================================================
# SECTION 15: DEVELOPMENT/DEBUG OPTIONS
# =============================================================================
# These are for testing only. Don't use in production with real money!
# Block source: daemon (real blockchain) | simulated (fake blocks for testing)
# Paid modes always use daemon unless FORCE_SIMULATED_BLOCKS=true
BLOCK_SOURCE=daemon
# Force simulated blocks even in paid mode (DANGEROUS - for testing only!)
# FORCE_SIMULATED_BLOCKS=false
# =============================================================================
# LEGACY OPTIONS (deprecated - use PAYMENT_MODES instead)
# =============================================================================
# GAME_MODE: FREE | PAID_SINGLE | PAID_CREDITS
# Kept for backward compatibility. New setups should use PAYMENT_MODES.
# GAME_MODE=
CRYPTO_TYPE=WOW
# =============================================================================
# Tavern & Multiplayer modes (optional)
# =============================================================================
# Tavern: a walkable social room (chat, avatars, spectator camera).
TAVERN_ENABLED=false
# Server-tick / snapshot broadcast cadence in ms (default 100 = 10 Hz)
# TAVERN_TICK_MS=100
# =============================================================================
# Match mode (multiplayer races)
# =============================================================================
# Enable real-time multiplayer races. Solo/Tavern are unaffected.
MATCH_ENABLED=false
# Max players per race (2–32). Default 4.
# MATCH_MAX_PLAYERS=4
# Server tick interval in ms (default 250 = 4 Hz).
# MATCH_TICK_MS=250
# Minimum race duration in ms before the next block can end it (default 20s).
# MATCH_MIN_DURATION_MS=20000
# Hard ceiling in ms after which a race is force-ended (default 4m).
# MATCH_HARD_CEILING_MS=240000
# Credits cost for prestige-only races (default 1).
# MATCH_CREDITS_COST=1
# Crypto race house fee percent (default 5).
# MATCH_HOUSE_FEE_PERCENT=5
# Optional override for the crypto-race per-player entry fee in atomic units.
# If unset, the existing per-game price (DIRECT_GAME_PRICE / SINGLE_GAME_PRICE) is used.
# MATCH_ENTRY_FEE_ATOMIC=5000000000
# Enable crypto race-entry ticket economy (requires paid instance + ticket products).
# MATCH_CRYPTO_RACE_ENABLED=false