A Node.js server that verifies game replays using deterministic simulation. Designed to prevent leaderboard cheating by replaying entire game sessions server-side using recorded player inputs.
Client sends a replay payload after each game:
- Timestamped input events (jump, duck, etc)
- RNG seed used for that session
- Claimed score and duration
The server reconstructs the exact game state by:
- Initializing identical RNG with the provided seed
- Spawning obstacles at precise intervals using deterministic patterns
- Simulating physics and player movement frame-by-frame
- Processing inputs at their exact timestamps
- Running AABB collision detection
If the replay reaches the claimed score without collisions, it's valid. Otherwise, rejected.
Deterministic RNG: Custom seeded random number generator that matches the client implementation. Both use the seedrandom library but wrap it to ensure identical state management and reset behavior.
Frame simulation: Runs at fixed 60 FPS (16.67ms per frame). Time-based calculations use the same constants as the client to ensure pixel-perfect matching.
Obstacle patterns: Deterministic spawn logic based on game time and difficulty curves. Patterns are defined in obstaclePatternLogic.js and include multiple obstacle types with varying speeds and positions.
Collision detection: Axis-aligned bounding box (AABB) checks using hitbox coordinates. Implemented in collision.js with the same tolerance values as client.
Physics simulation: Server maintains full game state including player position, velocity, gravity, ground friction, and jump mechanics. Player physics in server/trex.js mirrors client behavior exactly.
- Koa - Web framework with middleware for routing and body parsing
- koa-router - RESTful endpoint routing
- koa-bodyparser - JSON payload handling
- Supabase - PostgreSQL database for leaderboard persistence
- seedrandom - Deterministic PRNG
- crypto - HMAC signature verification for Shopify app proxy
server/
├── index.js # Koa server, routes, and API endpoints
├── verifyReplay.js # Core replay verification (self-contained)
├── collision.js # AABB collision detection helper
├── seededRandom.js # Seeded PRNG wrapper (matches client)
├── spawner.js # Collectible spawn position logic
├── obstaclePatternLogic.js # Deterministic obstacle pattern definitions
└── testVerify.js # Verification test suite
POST /api/session - Generate new game session with RNG seed
{ "sessionToken": "uuid", "seed": 12345, "startTime": 1702123456789 }POST /api/submit - Submit replay for verification
{
"sessionToken": "uuid",
"username": "player",
"inputs": [{"type": "input", "action": "jump", "timestamp": 1000}],
"score": 1500,
"duration": 30000
}GET /api/leaderboard - Fetch top scores
GET /api/user-score/:username - Get specific player's best score
Install dependencies:
npm installEnvironment variables (.env):
SUPABASE_URL=your_project_url
SUPABASE_ANON_KEY=your_anon_key
SHOPIFY_API_SECRET=your_hmac_secret
PORT=3000
Start:
npm startServer runs on http://localhost:3000
Client-side validation is trivially bypassed. Players can modify JavaScript, intercept network requests, or manipulate browser memory to fake high scores.
Server-side replay verification makes cheating significantly harder. Even if a player modifies their client to report false inputs, those inputs must still produce a valid game when replayed server-side with proper physics and collision detection. They'd need to solve the inverse problem of finding inputs that reach a target score, which is computationally expensive and game-specific.
The approach trades server CPU for security. Each submission costs ~10-50ms of CPU time depending on game duration, which is acceptable for leaderboard submissions that happen once per game rather than continuously.