RPS ARENA DOCS

Back to Game

Technical Documentation

Complete technical reference for RPS Arena - a real-time multiplayer blockchain game built on Base network with USDC payments.

1 System Overview

RPS Arena is a real-time competitive multiplayer game where three players battle using Rock-Paper-Scissors mechanics in a 2D arena. The platform leverages blockchain technology for trustless payments and transparent prize distribution.

30 HzPhysics Tick Rate
20 HzClient Snapshot Rate
3Players Per Match
12Concurrent Lobbies
1 USDCEntry Fee
2.4 USDCWinner Payout

Technology Stack

Backend

RuntimeNode.js >= 18.0.0
HTTP ServerExpress 4.18.2
WebSocketws 8.16.0
DatabaseSQLite (better-sqlite3)
Blockchainethers.js 6.11.0

Frontend

RenderingHTML5 Canvas
FrameworkVanilla JavaScript
Wallet SupportEIP-6963
NetworkWebSocket API
AuthenticationSIWE (EIP-4361)

Blockchain

NetworkBase (Chain ID: 8453)
TokenUSDC
Contract0x833589fCD6...
Confirmations3 blocks minimum
RPC ProvidersAlchemy + 3 Fallbacks

Infrastructure

DeploymentRailway
ArchitectureSingle Instance
MonitoringSentry + Discord
LoggingWinston
BackupsHourly (24 retained)

2 System Architecture

High-Level Architecture

Browser Client

Canvas Renderer
WebSocket Client
EIP-6963 Wallets

↓ WebSocket + HTTPS ↓
Node.js Server

Express HTTP
WebSocket Server
Game Loop (30Hz)

↓↓↓
SQLite Database

Users, Matches
Lobbies, Stats
WAL Mode

Base Network

USDC Payments
Wallet Verification
Prize Distribution

Dual-Port Architecture

The server operates on two ports for separation of concerns:

Port 3000 (Production)

PurposePublic player access
Payment1 USDC required
FeaturesFull game functionality

Port 3001 (Admin)

PurposeTesting & administration
PaymentFree joins allowed
FeaturesBot management, resets, backups

Project Structure

RPS-ARENA/
├── server/ # Backend application
│ ├── index.js # Main entry, HTTP/WS servers, dual-port setup
│ ├── match.js # Match logic, game loop, state persistence
│ ├── physics.js # Movement, swept collision, bouncing
│ ├── lobby.js # Lobby management, mutex locking, timeouts
│ ├── payments.js # Blockchain transactions, retry logic, sweeping
│ ├── auth.js # SIWE authentication
│ ├── wallet.js # HD wallet derivation, AES encryption
│ ├── database.js # SQLite operations, deferred queue, transactions
│ ├── protocol.js # Message validation schemas
│ ├── config.js # Centralized configuration
│ ├── alerts.js # Discord webhooks with retry
│ ├── logger.js # Match event logging, replay/audit trail
│ ├── appLogger.js # Winston logger with file rotation
│ ├── session.js # Session management, token rotation
│ ├── bot.js # Bot players with AI movement
│ └── sentry.js # Error tracking initialization
├── client/ # Frontend application
│ ├── index.html # Main game interface
│ ├── how-to.html # How to play guide
│ ├── docs.html # Technical documentation
│ ├── style.css # Styling
│ └── src/
│ ├── main.js # App initialization
│ ├── network.js # WebSocket client, jittered backoff
│ ├── wallet.js # EIP-6963 multi-wallet detection
│ ├── ui.js # Screen management, modals
│ ├── renderer.js # Canvas rendering, role icons
│ ├── input.js # Keyboard handling, sequence tracking
│ ├── interpolation.js # Position smoothing
│ ├── confetti.js # Victory animation
│ └── lobbyBackground.js # Animated lobby background
├── database/ # Schema files
│ └── schema.sql # SQLite table definitions
├── tests/ # Test suites (security, load, e2e)
├── backups/ # Automated database backups
├── .env.example # Environment variable template
├── package.json # Dependencies and npm scripts
├── README.md # Project documentation
├── nixpacks.toml # Railway build config
└── railway.json # Railway deployment config

Single Instance Constraint

RPS Arena runs as a single instance due to in-memory WebSocket connections and game state. Horizontal scaling would require migration to Redis for session storage and PostgreSQL for distributed state management.

3 Game Mechanics

Core Gameplay

Three players compete in a real-time arena using Rock-Paper-Scissors combat mechanics. Each player is assigned a role (Rock, Paper, or Scissors) and must eliminate opponents by colliding with the role they beat.

Arena Specifications

Width1600 pixels
Height900 pixels
BoundaryClamped (no wrap)

Player Properties

Radius22 pixels
Max Speed450 px/sec
Hits to Eliminate1 hit (instant)

Timing

Physics Tick30 Hz (33ms)
Client Snapshot20 Hz (50ms)
Role Reveal10 seconds
Preview Phase3 seconds

Rock-Paper-Scissors Rules

Rock (Orange)
beats
Scissors (Green)
beats
Paper (Blue)
beats
Rock
Collision TypeResultEffect
Winning collisionAttacker winsDefender eliminated instantly
Neutral collision (showdown)BounceBoth players pushed apart 10px
Losing collisionDefender winsAttacker eliminated instantly

Match Lifecycle

Join Lobby
Wait (3 players)
Role Reveal (10s)
Preview (3s)
Battle Phase
Showdown (2 left)
Winner

Physics Engine

Swept Circle Collision Detection

The physics engine uses swept circle collision detection to handle fast-moving players. This technique checks for collisions along the entire path of movement, preventing players from passing through each other.

// Collision radius = PLAYER_RADIUS * 2 = 44px
// Bounce distance: 10px standard, 25px large
// Max bounce iterations per tick: 2

function checkCollision(p1, p2, deltaTime) {
  // Calculate relative velocity and trajectory
  const relVel = subtract(p1.velocity, p2.velocity);
  // Solve quadratic for intersection time
  // Returns collision point if within frame
}

Movement System

Spawn System

Players spawn at predetermined positions based on the deterministic RNG seed for the match:

Spawn Configuration

AlgorithmTriangle formation
Min Distance200px between players
Arena Padding100px from edges
RandomizationSeeded RNG per match

Default Spawn Points

Player 1Top-center region
Player 2Bottom-left region
Player 3Bottom-right region
// Spawn position calculation
const spawnPoints = [
  { x: ARENA_WIDTH / 2, y: ARENA_HEIGHT * 0.2 },      // Top center
  { x: ARENA_WIDTH * 0.2, y: ARENA_HEIGHT * 0.8 },    // Bottom left
  { x: ARENA_WIDTH * 0.8, y: ARENA_HEIGHT * 0.8 }     // Bottom right
];
// Positions shuffled using seeded RNG for fairness

4 Showdown Mode

Triggered When 2 Players Remain

When only 2 players remain alive, the game transitions to Showdown Mode - a heart collection race that determines the winner.

Showdown Mechanics

Phase 1: Freeze

Duration3 seconds
Display"SHOWDOWN" text
PlayersFrozen in place

Phase 2: Heart Race

Hearts Spawned3 hearts
Hearts to Win2 hearts
Spawn LocationRandom in arena

Collision Behavior

Player CollisionsBounce only
EliminationsDisabled
Heart CaptureProximity-based

Showdown Protocol Messages

MessageDirectionDescription
SHOWDOWN_STARTServerAnnounces showdown mode, includes heart positions
SHOWDOWN_READYServerFreeze period ended, race begins
HEART_CAPTUREDServerHeart captured by player (includes heartId, playerId, score)

Heart Object Structure

{
  "id": "heart-1",
  "x": 800,
  "y": 450,
  "captured": false,
  "capturedBy": null
}

Simultaneous Capture Tiebreaker

If both players reach 2 hearts on the same tick, the winner is determined by random selection to ensure fairness.

Heart Collection Details

Heart Properties

Pickup Radius30 pixels
Visual Size20px radius
Spawn Count3 hearts
Win Threshold2 hearts

Spawn Algorithm

DistributionRandom within arena
Edge Padding100px minimum
Player Distance150px minimum
Heart Spacing100px minimum
// Heart spawn algorithm
function spawnHearts(players) {
  const hearts = [];
  for (let i = 0; i < 3; i++) {
    let pos = findValidPosition({
      minEdgePadding: 100,
      minPlayerDistance: 150,
      minHeartDistance: 100
    });
    hearts.push({ id: `heart-${i}`, x: pos.x, y: pos.y });
  }
  return hearts;
}

5 Real-Time Networking

Network Model

RPS Arena uses an authoritative server model with client-side interpolation:

Server Authority

Game StateServer-authoritative
Tick Rate30 Hz
Snapshot Rate20 Hz
State PersistenceEvery 5 ticks

Client Processing

Input Sampling60 Hz (frame rate)
InterpolationLinear smoothing
Sequence TrackingAnti-cheat

Reconnection System

Grace Period: 30 Seconds

Disconnected players have 30 seconds to reconnect before automatic elimination. Features:

  • Player character freezes in place
  • Other players see "disconnected" indicator with countdown
  • Full state resync via RECONNECT_STATE message on reconnect
  • Session token rotation prevents replay attacks
  • Grace period cleared immediately on reconnect (race condition fix)

Connection Resilience

// Client reconnection with jittered exponential backoff
const baseDelay = 1000;       // 1 second
const maxDelay = 30000;       // 30 seconds
const maxAttempts = 5;

// Jitter prevents "thundering herd" on server restart
const jitter = Math.random() * 0.5 + 0.5;  // 50-100%
const delay = Math.min(baseDelay * Math.pow(2, attempt) * jitter, maxDelay);

Lobby Management

Timeout System

Timeout Duration30 minutes
Timer StartFirst player joins
Timer ResetNever (fixed deadline)
Refund TriggerPlayer requests after timeout

Mutex Locking

Lock ScopePer-lobby
Lock Timeout5 seconds
PurposePrevent race conditions
OperationsJoin, leave, match start

Lobby State Machine

waiting (1-2 players)
ready (3 players)
countdown
in_progress
completed

Timeout Refund Flow

When a lobby times out (30 min since first join without filling to 3 players):

  • Players can click "Request Refund" button
  • Server verifies timeout has passed
  • Full 1 USDC refunded to each player's wallet
  • Lobby reset to empty state
  • Refund transaction hash recorded in database

6 WebSocket API Reference

Connection Parameters

const config = {
  pingInterval: 5000,       // 5 seconds
  maxMessageSize: 16384,    // 16 KB
  connectionTimeout: 10000, // 10 seconds
};

Client to Server Messages

TypeDescriptionPayload
HELLOAuthenticate with session token{ token: string }
JOIN_LOBBYJoin lobby with payment proof{ lobbyId: number, txHash: string }
REQUEST_REFUNDRequest timeout refund{ lobbyId: number }
INPUTMovement input{ dirX: -1|0|1, dirY: -1|0|1, seq: number }
PINGLatency measurement{ timestamp: number }

Server to Client Messages

TypeDescription
WELCOMEAuthentication success with userId and serverTime
LOBBY_LISTList of all available lobbies with status
LOBBY_UPDATESingle lobby state change
MATCH_STARTINGMatch begins, countdown starts
ROLE_ASSIGNMENTYour assigned RPS role
COUNTDOWNCountdown timer (10...0)
PREVIEW_START3-second preview phase begins (players see arena)
GAME_STARTPreview ended, movement enabled
SNAPSHOTGame state update (20 Hz) with all player positions
ELIMINATIONPlayer eliminated event
BOUNCENeutral collision bounce (showdown)
SHOWDOWN_START2 players remain, heart positions sent
SHOWDOWN_READYFreeze ended, race begins
HEART_CAPTUREDPlayer captured a heart
MATCH_ENDMatch complete with winner and payouts
REFUND_PROCESSEDRefund transaction confirmed
PLAYER_DISCONNECTPlayer disconnected (grace period countdown)
PLAYER_RECONNECTPlayer reconnected
RECONNECT_STATEFull state resync after reconnect
TOKEN_UPDATENew session token (rotation)
PONGPing response with server timestamp
ERRORError with code and message

Error Codes

CodeDescription
AUTH_REQUIREDNo valid session token provided
AUTH_FAILEDInvalid or expired session token
LOBBY_FULLLobby already has 3 players
LOBBY_IN_PROGRESSMatch already started in this lobby
PAYMENT_REQUIREDNo valid payment transaction provided
PAYMENT_INVALIDTransaction verification failed
ALREADY_IN_LOBBYPlayer is already in a lobby
RATE_LIMITEDToo many messages sent
INTERNAL_ERRORServer-side error occurred

7 REST API Reference

Public Endpoints

MethodEndpointDescription
GET/api/healthHealth check (DB, game loop, deferred queue)
POST/api/authSIWE authentication
POST/api/logoutInvalidate session
GET/api/lobbiesList all lobbies
GET/api/player/:walletGet player stats
GET/api/player/:wallet/historyMatch history (paginated)
GET/api/leaderboardTop 100 players (all/monthly/weekly)
POST/api/player/usernameSet username (requires 1+ match)
POST/api/player/photoUpload profile photo (max 500KB)
GET/api/dev-modeCheck if server is in development mode

Admin Endpoints (Port 3001 Only)

MethodEndpointDescription
POST/api/bot/addAdd bot player to lobby
POST/api/bot/fillFill lobby with bots
GET/api/bot/listList active bots
POST/api/bot/removeRemove specific bot
POST/api/dev/resetReset lobby state
POST/api/admin/backupCreate database backup
GET/api/admin/backupsList existing backups
POST/api/admin/checkpointManual WAL checkpoint
POST/api/admin/backup/cleanupRemove old backups

8 Database Schema

Configuration

SQLite Settings

EngineSQLite 3
Driverbetter-sqlite3
Journal ModeWAL
Foreign KeysEnabled
SynchronousNORMAL

Core Tables

users

id TEXT PRIMARY KEY, wallet_address TEXT UNIQUE, username TEXT, created_at TEXT, updated_at TEXT

sessions

id TEXT PRIMARY KEY, user_id TEXT, token TEXT UNIQUE, created_at TEXT, expires_at TEXT

lobbies (12 fixed)

id INTEGER PRIMARY KEY (1-12), status TEXT, deposit_address TEXT, deposit_private_key_encrypted TEXT, first_join_at TEXT, timeout_at TEXT, swept_at TEXT, current_match_id TEXT

lobby_players

id TEXT PRIMARY KEY, lobby_id INTEGER, user_id TEXT, payment_tx_hash TEXT, payment_confirmed_at TEXT, refund_tx_hash TEXT, refund_reason TEXT, refunded_at TEXT, joined_at TEXT

matches

id TEXT PRIMARY KEY, lobby_id INTEGER, status TEXT (countdown|running|finished|void), winner_id TEXT, rng_seed TEXT, created_at TEXT, countdown_at TEXT, running_at TEXT, ended_at TEXT, payout_amount INTEGER, payout_tx_hash TEXT

match_players

id TEXT PRIMARY KEY, match_id TEXT, user_id TEXT, role TEXT (rock|paper|scissors), spawn_x REAL, spawn_y REAL, eliminated_at TEXT, eliminated_by TEXT, final_x REAL, final_y REAL

match_state (Crash Recovery)

match_id TEXT PRIMARY KEY, version TEXT, tick INTEGER, status TEXT, state_json TEXT, updated_at TEXT

match_events (Audit)

id TEXT PRIMARY KEY, match_id TEXT, tick INTEGER, event_type TEXT (start|elimination|bounce|disconnect|end), data TEXT (JSON), created_at TEXT

player_stats (Denormalized)

wallet_address TEXT PRIMARY KEY, username TEXT, profile_photo TEXT, total_matches INTEGER, wins INTEGER, losses INTEGER, total_earnings_usdc INTEGER, total_spent_usdc INTEGER, current_win_streak INTEGER, best_win_streak INTEGER, first_match_at TEXT, last_match_at TEXT, updated_at TEXT

payout_attempts (Audit)

id TEXT PRIMARY KEY, match_id TEXT, lobby_id INTEGER, recipient_address TEXT, amount_usdc INTEGER, attempt_number INTEGER, status TEXT (pending|success|failed), tx_hash TEXT, error_message TEXT, error_type TEXT (transient|permanent), source_wallet TEXT, treasury_balance_before INTEGER, created_at TEXT

reserved_usernames

username TEXT PRIMARY KEY, reserved_by TEXT, reserved_at TEXT

paid_wallets (Airdrop Tracking)

wallet_address TEXT PRIMARY KEY, first_payment_at TEXT, last_payment_at TEXT, total_payments INTEGER

Indexes (30+)

Composite indexes optimized for: user lookups, session validation, lobby status queries, leaderboard (wins DESC, earnings DESC), match history (user + date), time-filtered stats (monthly/weekly).

9 Resilience & Error Handling

Database Resilience

Deferred Operation Queue

Max Queue Size100 items
Process Interval5 seconds
TriggerBUSY errors

Error Classification

CONNECTIONDB unreachable
CONSTRAINTUnique/FK violation
BUSYLock contention
CORRUPTData corruption

Critical vs Non-Critical Operations

Critical (fail immediately): User creation, sessions, match creation, payouts
Non-critical (queue on BUSY): Stats updates, event logging, profile changes

Crash Recovery

State Persistence

Save FrequencyEvery 5 ticks (~167ms)
State Versionv1
Max State Age5 minutes

Recovery Behavior

On StartupCheck interrupted matches
Stale StateVoid + refund all
Fresh StateVoid + refund all

RPC Fallback System

// RPC provider priority order:
1. Primary: BASE_RPC_URL (Alchemy/Infura)
2. Fallback: base.publicnode.com
3. Fallback: 1rpc.io/base
4. Fallback: mainnet.base.org

// Health checks every 60 seconds
// Tests: block number retrieval + latency
// Auto-failover on provider failure

Payment Error Handling

Error TypeActionExamples
TransientRetry (3x, exponential backoff)ETIMEDOUT, ECONNRESET, 429, 502-504
PermanentFail immediately, alertInsufficient funds, nonce too low, execution reverted

Game Loop Health Monitoring

10 Blockchain Integration

Network Configuration

Base Network

Chain ID8453
Block Time~2 seconds
Confirmations3 blocks

USDC Token

Contract0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Decimals6
StandardERC-20

Wallet Architecture

// BIP-44 derivation path
const path = "m/44'/60'/0'/0/{index}";

// Treasury wallet: index 0 (from TREASURY_MNEMONIC)
// Lobby wallets: indices 1-12 (from LOBBY_WALLET_SEED)

// Private key encryption: AES-256-GCM
// Format: iv:authTag:ciphertext (hex-encoded)

Payment Verification

Wallet Sweeping

After match completion, lobby wallets are swept to treasury:

Low Balance Alerts

Discord alerts triggered when wallet ETH balance falls below 0.001 ETH.

11 Authentication System

SIWE (Sign-In with Ethereum)

Request Nonce
Generate SIWE Message
Sign with Wallet
Verify Signature
Session Created

Session Management

Session Token

Format64-char hex
Generationcrypto.randomBytes(32)
StoragelocalStorage

Token Lifecycle

Expiry24 hours
RotationOn reconnect
CleanupHourly

Token Rotation Security

Session tokens are rotated on every WebSocket reconnection. Old tokens are immediately invalidated, preventing replay attacks during the reconnection grace period.

Wallet Support (EIP-6963)

Multi-wallet detection supporting: MetaMask, Rabby, Coinbase Wallet, Trust Wallet, and any EIP-6963 compatible wallet. Fallback to window.ethereum for legacy support.

12 Player Profiles

Profile Features

Username

Length3-20 characters
Charactersa-z, A-Z, 0-9, _
Requirement1+ completed match
ReservationPermanent

Profile Photo

FormatBase64 data URL
Max Size500 KB
DefaultGenerated avatar
Requirement1+ completed match

Statistics Tracked

Leaderboards

FilterTime RangeSort Order
All TimeAll matches everWins DESC, Earnings DESC
MonthlyCurrent calendar monthWins DESC, Earnings DESC
WeeklySince last MondayWins DESC, Earnings DESC

13 Security Architecture

Security Layers

Input Validation

All WebSocket messages validated against strict JSON schemas. Whitelist of allowed message types. Coordinate bounds checking. Direction validation (-1, 0, 1 only). Input sequence tracking for anti-cheat.

Rate Limiting

INPUT: 120/sec. Other: 10/sec. Connections: 3/IP. Message size: 16KB. Per-IP tracking with 1-hour cleanup to prevent memory leaks.

Wallet Security

Private keys encrypted at rest (AES-256-GCM). Deterministic HD derivation (BIP-44). No plaintext keys in logs or errors. Signature verification via SIWE.

Session Security

Cryptographically random 64-char tokens. Expiration enforcement. Per-user isolation. Token rotation on reconnect prevents replay attacks.

Blockchain Security

3-block confirmations. Exact amount verification. Hardcoded contract addresses. Nonce management via ethers.js. Gas estimation with buffers.

Sentry Integration

Scrubs sensitive data (mnemonics, keys). 5% sampling in production. Ignores non-critical errors (WebSocket closes, rate limits). Breadcrumb tracking for debugging.

Validation Patterns

// Transaction hash validation
const TX_HASH_REGEX = /^0x[a-fA-F0-9]{64}$/;

// Admin/bot tx patterns (port 3001 only)
const ADMIN_TX_REGEX = /^0x(dev_|bot_tx_)[a-zA-Z0-9_]+$/;

// Username validation
const USERNAME_REGEX = /^[a-zA-Z0-9_]{3,20}$/;

// Wallet address validation (EIP-55 checksummed)
ethers.isAddress(address);

14 Bot System (Admin Only)

Admin Port Only

Bot features are only available on port 3001. They are intended for testing and development, not production gameplay.

Bot Features

Bot Movement AI

// Bot targeting logic
if (showdownMode) {
  target = nearestUncapturedHeart;
} else {
  target = enemyThatBotCanBeat;
}
moveTowardTarget(bot, target);

15 Client Architecture

Module Structure

main.js

App initialization, module orchestration, global state.

network.js

WebSocket client, message handling, jittered exponential backoff reconnection.

wallet.js

EIP-6963 multi-wallet detection, connection, signing, fallback to window.ethereum.

ui.js

Screen management (7 screens), modals, DOM updates, tab navigation.

renderer.js

Canvas 2D rendering, player tokens with role colors, hearts, arena grid, role icons.

input.js

Keyboard event tracking (WASD + Arrows), sequence numbers for anti-cheat.

interpolation.js

Linear position interpolation between server snapshots for smooth 60fps rendering.

confetti.js

Victory celebration particle animation system.

lobbyBackground.js

Animated lobby canvas background with floating RPS icons.

Screen States

Landing
Lobby Browser
Waiting Room
Role Reveal (10s)
Preview (3s)
Game
Results

Player Rendering

16 Testing Infrastructure

Test Suites

tests/
├── security/ # Security vulnerability tests
│ └── security-tests.js # Rate limiting, input validation, etc.
├── load/ # Load and performance testing
│ └── load-test.js # Concurrent connections, message throughput
├── e2e/ # End-to-end game flow tests
│ └── full-game-flow.js # Complete match lifecycle with bots
└── manual/ # Manual testing guides
└── TESTING_CHECKLIST.md # QA checklist

npm Scripts

npm start              # Production server
npm run dev            # Development with nodemon
npm run init-db        # Manual database initialization
npm run test:security  # Security tests (--production flag available)
npm run test:load      # Load testing
npm run test:e2e       # End-to-end testing

17 Deployment Guide

Railway Configuration

Build (nixpacks.toml)

ProvidersNode.js
PackagesPython, GCC
Native Modulesbetter-sqlite3

Deploy (railway.json)

Health Check/api/health
Timeout30 seconds
Restart PolicyON_FAILURE (3x)

Health Check Response

{
  "status": "healthy",
  "database": { "connected": true, "journalMode": "wal" },
  "deferredQueue": { "size": 0 },
  "gameLoop": { "healthy": true, "activeMatches": 2 }
}

Server Initialization Sequence

On startup, the server initializes components in this order:

  1. Sentry: Initialize error tracking (if DSN configured)
  2. Database: Initialize SQLite with WAL mode, run migrations
  3. Lobby Wallets: Derive 12 HD wallets from seed, encrypt private keys
  4. Lobbies: Create/verify 12 lobby records with deposit addresses
  5. Match Recovery: Check for interrupted matches, void stale ones, refund players
  6. HTTP Servers: Start Express on PORT (production) and ADMIN_PORT (admin)
  7. WebSocket Servers: Attach WS handlers to both HTTP servers
  8. RPC Provider: Initialize ethers.js with primary RPC, test connection
  9. Health Monitor: Start game loop health checks (2s interval)
  10. RPC Monitor: Start RPC health checks (60s interval)
  11. Session Cleanup: Schedule hourly expired session cleanup
  12. Backup Scheduler: Start hourly database backup job
  13. Discord Alert: Send SERVER_START notification

Graceful Shutdown

On SIGTERM/SIGINT, the server shuts down in this order:

  1. Stop accepting new HTTP connections
  2. Stop accepting new WebSocket connections
  3. Wait for in-progress payouts to complete (5s max)
  4. Close existing WebSocket connections with code 1001
  5. Send SERVER_SHUTDOWN Discord alert
  6. Flush Sentry events (2s timeout)
  7. WAL checkpoint and close database connection
  8. Exit with code 0

Monitoring

Discord Webhooks

CriticalErrors, failures, low balance
ActivityMatches, joins, completions
Retry3x with exponential backoff

Sentry

ErrorsUncaught exceptions
Performance5% sampling
ScrubbingKeys, mnemonics

Backup System

18 Configuration Reference

Required Environment Variables

VariableDescription
PORTHTTP server port (Railway sets this)
ADMIN_PORTAdmin/testing server port (default: 3001)
NODE_ENVEnvironment mode (production/development)
DATABASE_PATHSQLite file path
BASE_RPC_URLPrimary RPC endpoint (Alchemy/Infura)
CHAIN_IDBlockchain network ID (8453 for Base)
USDC_CONTRACT_ADDRESSUSDC token contract address
TREASURY_MNEMONICTreasury wallet 12-word seed phrase
LOBBY_WALLET_SEEDLobby wallets 12-word seed phrase
WALLET_ENCRYPTION_KEY32-byte hex key for AES-256

Optional Environment Variables

VariableDefaultDescription
SESSION_EXPIRY_HOURS24Session token expiration
GAME_TICK_RATE30Physics ticks per second
GAME_ARENA_WIDTH1600Arena width in pixels
GAME_ARENA_HEIGHT900Arena height in pixels
GAME_MAX_SPEED450Max player speed (px/sec)
GAME_PLAYER_RADIUS22Player collision radius
COUNTDOWN_DURATION10Role reveal countdown (seconds)
BUY_IN_AMOUNT1000000Entry fee (micro-USDC)
WINNER_PAYOUT2400000Winner payout (micro-USDC)
TREASURY_CUT600000Platform fee (micro-USDC)
RATE_LIMIT_INPUT120INPUT messages per second
RATE_LIMIT_OTHER10Other messages per second
MAX_CONNECTIONS_PER_IP3Concurrent connections per IP
RECONNECT_GRACE_PERIOD30Disconnect grace period (seconds)
DISCORD_WEBHOOK_URL-Critical alerts webhook
DISCORD_ACTIVITY_WEBHOOK_URL-Activity logs webhook
SENTRY_DSN-Sentry error tracking DSN
LOG_LEVELinfoWinston log level
BACKUP_INTERVAL_HOURS1Backup frequency
BACKUP_KEEP_COUNT24Backups to retain
DEBUG_PHYSICSfalsePhysics debug logging
DEBUG_MATCHfalseMatch debug logging
LOBBY_TIMEOUT_MINUTES30Lobby timeout before refund available
PREVIEW_DURATION3Preview phase duration (seconds)
SHOWDOWN_FREEZE_DURATION3Showdown freeze duration (seconds)
STATE_PERSIST_INTERVAL5Ticks between state persistence
HEART_PICKUP_RADIUS30Heart collection radius (pixels)
MIN_SPAWN_DISTANCE200Minimum distance between spawn points