engineering-game-backend-architecture
Use when building game servers, WebSocket connections, room management, game tick loops, or server-authoritative architecture with Elysia. Triggers: game server, WebSocket, rooms, tick loop, server architecture, multiplayer backend.
Best use case
engineering-game-backend-architecture is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use when building game servers, WebSocket connections, room management, game tick loops, or server-authoritative architecture with Elysia. Triggers: game server, WebSocket, rooms, tick loop, server architecture, multiplayer backend.
Teams using engineering-game-backend-architecture should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/game-backend-architecture/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How engineering-game-backend-architecture Compares
| Feature / Agent | engineering-game-backend-architecture | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use when building game servers, WebSocket connections, room management, game tick loops, or server-authoritative architecture with Elysia. Triggers: game server, WebSocket, rooms, tick loop, server architecture, multiplayer backend.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Game Backend Architecture
## Purpose
WebSocket + REST patterns, room/session management, game tick loops, genre-agnostic server design using Elysia + Bun. Covers server-authoritative architecture, fixed-timestep simulation, room lifecycle, typed message protocols, and horizontal scaling via Redis pub/sub.
## When to Use
Trigger: game server, backend architecture, WebSocket, room management, game loop, tick system, server-authoritative, real-time, Elysia game server, multiplayer backend, session management, game tick, fixed timestep server
## Prerequisites
- `postgres-game-schema` — database layer for persistent game state
- `redis-game-patterns` — caching, pub/sub, presence, and ephemeral state
## Core Principles
> "I'm not interested in creating a game that does things for the player. I want to create a simulation that responds to the player." — Will Wright
> "A game is a series of interesting decisions." — Sid Meier
The server is a simulation system that responds to player intentions and produces meaningful state changes. Every design decision should preserve determinism, enforce authority, and keep the door open for any genre.
1. **Server-authoritative**: the client sends intentions (actions), the server validates and executes. Never trust the client.
2. **Separate simulation loop from I/O**: the game tick runs on a fixed timestep independent of network or render cycles.
3. **Fixed timestep for deterministic game logic**: same inputs at the same tick always produce the same output, enabling replays, rollback, and offline catchup.
4. **Room-based architecture for multiplayer isolation**: each room encapsulates its own state, tick loop, and player set. Rooms are the unit of scaling.
5. **Stateless REST for CRUD, WebSocket for real-time state**: use REST for durable operations (create account, save inventory, fetch leaderboard) and WebSocket for ephemeral, high-frequency updates (game state, actions, presence).
6. **Event-driven**: game actions produce events, systems consume them. Decouple producers from consumers for testability and extensibility.
7. **Horizontal scaling via Redis pub/sub**: multiple Elysia instances share state through Redis. Any instance can handle any player; cross-server communication uses pub/sub channels.
## Step-by-Step Instructions
### 1. Define the Message Protocol
Start with `templates/message-types.ts`. Define discriminated unions for every client-to-server and server-to-client message. This is the contract between frontend and backend — get it right first.
```typescript
import type { ClientMessage, ServerMessage } from './templates/message-types';
```
### 2. Set Up the Elysia Server
Use `boilerplate/server.ts` as the entry point. It wires together:
- REST routes for CRUD operations (players, sessions, inventory)
- A WebSocket endpoint with JWT authentication
- Message routing to the room manager
```bash
bun run boilerplate/server.ts
```
### 3. Implement Room Management
Use `boilerplate/room-manager.ts`. The room manager handles:
- Creating and destroying rooms
- Joining and leaving players
- Broadcasting messages to room members
- Room lifecycle transitions (created -> active -> closing -> closed)
Each room owns its game state and tick loop instance.
### 4. Configure the Game Loop
Use `boilerplate/game-loop.ts`. The `GameLoop` class provides:
- Fixed-timestep accumulator pattern
- Configurable tick rate (default 20 TPS for most genres)
- Speed multiplier for fast-forward or slow-motion
- Delta time overflow protection (caps accumulated time to prevent spiral of death)
Attach one `GameLoop` per room. The tick callback receives `dt` in seconds and updates room state.
### 5. Wire Room State to WebSocket Broadcasts
After each tick, diff the room state and broadcast deltas to all room members:
```typescript
room.gameLoop.onTick = (dt) => {
room.state = updateGameState(room.state, dt);
const delta = computeDelta(room.previousState, room.state);
if (delta) {
room.broadcast({ type: 'state_update', roomId: room.id, state: delta, tick: room.tick });
}
room.previousState = structuredClone(room.state);
room.tick++;
};
```
### 6. Add Redis Pub/Sub for Scaling
When running multiple Elysia instances behind a load balancer:
```typescript
import { Redis } from 'ioredis';
const pub = new Redis(process.env.REDIS_URL!);
const sub = new Redis(process.env.REDIS_URL!);
sub.subscribe('game:broadcast', 'game:direct');
sub.on('message', (channel, message) => {
const parsed = JSON.parse(message);
if (channel === 'game:broadcast') {
server.publish(parsed.channel, parsed.data);
}
if (channel === 'game:direct') {
const ws = connections.get(parsed.userId);
if (ws) ws.send(parsed.data);
}
});
```
### 7. Implement Presence and Heartbeat
- Client sends `ping` every 30 seconds
- Server responds with `pong` + server timestamp
- If no ping within 90 seconds, mark player offline and clean up room membership
- Store presence in Redis with TTL for automatic expiry
## Code Examples
### Fixed-Timestep Server Loop
```typescript
const TICK_RATE = 20;
const TICK_DURATION_MS = 1000 / TICK_RATE;
const loop = new GameLoop(TICK_RATE, (dt) => {
for (const room of roomManager.getActiveRooms()) {
room.update(dt);
room.broadcastState();
}
});
loop.start();
```
### Server-Authoritative Action Processing
```typescript
const processAction = (
room: Room,
playerId: string,
action: { type: string; payload: Record<string, unknown> },
): ActionResult => {
// 1. Validate: is this action legal in current state?
const validation = validateAction(room.state, playerId, action);
if (!validation.valid) {
return { success: false, error: validation.reason };
}
// 2. Execute: apply to authoritative state
const events = applyAction(room.state, playerId, action);
// 3. Broadcast: notify all room members of resulting events
for (const event of events) {
room.broadcast({
type: 'game_event',
event: event.type,
data: event.data,
tick: room.tick,
});
}
return { success: true, events };
};
```
### Room-Scoped State Update
```typescript
const updateRoomState = (room: Room, dt: number) => {
// Run all registered systems in order
for (const system of room.systems) {
system.update(room.state, dt);
}
// Process queued player actions
while (room.actionQueue.length > 0) {
const { playerId, action } = room.actionQueue.shift()!;
processAction(room, playerId, action);
}
room.tick++;
};
```
### WebSocket Message Routing
```typescript
const handleMessage = (ws: GameWebSocket, data: ClientMessage) => {
const { playerId } = ws.data;
switch (data.type) {
case 'join_room':
roomManager.joinRoom(data.roomId, playerId, ws);
break;
case 'leave_room':
roomManager.leaveRoom(data.roomId, playerId);
break;
case 'action':
roomManager.queueAction(data.roomId, playerId, data.action);
break;
case 'ping':
ws.send(JSON.stringify({ type: 'pong', serverTime: Date.now() }));
break;
}
};
```
### Delta State Synchronization
```typescript
const computeDelta = (
previous: Record<string, unknown>,
current: Record<string, unknown>,
): Record<string, unknown> | null => {
const delta: Record<string, unknown> = {};
let hasChanges = false;
for (const key of Object.keys(current)) {
if (JSON.stringify(previous[key]) !== JSON.stringify(current[key])) {
delta[key] = current[key];
hasChanges = true;
}
}
return hasChanges ? delta : null;
};
```
## Cross-References
- **`redis-game-patterns`** — presence storage, pub/sub for cross-server communication, room state caching, rate limiting
- **`bullmq-game-queues`** — offload heavy computations (matchmaking, leaderboard recalc, scheduled events) to background workers
- **`game-state-sync`** — client-side state reconciliation, interpolation, optimistic updates, rollback
- **`postgres-game-schema`** — persistent storage for player profiles, inventory, match history, room configurations
## Pitfalls & Anti-Patterns
### 1. Client-Authoritative State
**Wrong**: client sends "my position is (100, 200)" and server trusts it.
**Right**: client sends "move north" intention, server validates movement speed, collision, and updates position.
### 2. Variable Timestep for Game Logic
**Wrong**: using wall-clock delta time directly in game formulas.
**Right**: fixed-timestep accumulator — game logic always sees the same `dt`, regardless of how fast the server runs.
### 3. Global State Instead of Room Isolation
**Wrong**: single shared game state object for all players.
**Right**: each room owns its state, tick loop, and player set. Rooms are independent simulation contexts.
### 4. Synchronous Database Writes in the Tick Loop
**Wrong**: `await db.update(players).set(...)` inside the tick callback.
**Right**: batch state changes and persist asynchronously outside the tick loop (e.g., every N ticks or on room close).
### 5. Broadcasting Full State Every Tick
**Wrong**: sending the entire game state to every player on every tick.
**Right**: compute deltas and only send what changed. Use interest management to filter updates per player.
### 6. No Overflow Protection
**Wrong**: accumulator grows unbounded when server lags, causing hundreds of catch-up ticks.
**Right**: cap accumulated time (e.g., 1 second max) to prevent spiral of death.
### 7. Tight Coupling Between Message Types and Game Logic
**Wrong**: game systems import WebSocket types and send messages directly.
**Right**: game systems produce events, a separate broadcast layer translates events to WebSocket messages.
### 8. No Graceful Room Shutdown
**Wrong**: rooms disappear instantly when the last player leaves, losing unsaved state.
**Right**: transition through closing state — persist state, notify players, clean up resources, then destroy.
## Designer Philosophy
**Will Wright's Simulation Thinking**: the server is not a script executor — it is a simulation. Players interact with systems that have emergent behavior. The server defines rules and constraints; the interesting outcomes arise from player interactions within those rules. Design your tick loop as a simulation step, not a sequence of hardcoded events.
**Sid Meier's Interesting Decisions**: every message from the client represents a decision. The server's job is to make that decision meaningful by enforcing constraints (can you afford this action?), producing consequences (what happens as a result?), and communicating outcomes (what changed?). If an action has no validation and no consequence, it is not an interesting decision — remove it or make it matter.
**Practical Implications**:
- Validation is gameplay. Rejection messages should explain *why* an action failed, giving players information for their next decision.
- Events are the language of consequence. When an action produces events that other systems consume, you get emergent complexity without writing combinatorial logic.
- The tick loop is your simulation clock. Everything that "happens" in the game happens during a tick. Between ticks, the game world is frozen and consistent.
## Sources
- Glenn Fiedler, "Fix Your Timestep!" — https://gafferongames.com/post/fix_your_timestep/
- Gabriel Gambetta, "Fast-Paced Multiplayer" — https://www.gabrielgambetta.com/client-server-game-architecture.html
- Elysia documentation — https://elysiajs.com/
- Redis pub/sub documentation — https://redis.io/docs/manual/pubsub/
- Will Wright, GDC talks on simulation design
- Sid Meier, GDC 2012, "Interesting Decisions"Related Skills
game-lore
Adds or updates a single world lore entry (faction, location, NPC, or event) — validates against existing lore for consistency, applies the right template, and updates docs/world-lore.md. Extract the entry type (faction/location/npc/event) and name from the user's message.
game-expand
Adds a new feature to an existing MVP plan — scoped architect interview, scope validation against original plan, and integration into the build sequence. Extract the feature name or description from the user's message. Requires docs/mvp-first-draft.md — run game-architect first.
game-build
Builds a game component from the MVP plan — production TypeScript + Vitest tests + mock dependencies + build registry tracking. Extract the component name from the user's message (or "status" to check progress). Requires docs/mvp-first-draft.md — run game-architect first.
game-balance
Analyzes a game system against design principles — economy health metrics, difficulty curve, reward schedules, and progression cost curves. Flags imbalances with severity levels. Use after creating an MVP plan with game-architect. Extract the analysis scope (economy/difficulty/progression/rewards/all) from the user's message.
game-architect
Comprehensive game MVP interviewer and planner. Interviews you in progressive groups, researches genre conventions and reference games, then produces an actionable MVP First Draft with starter project files. Use when starting a new game project from scratch.
narrative-story-structure-game
Use when designing interactive narratives, branching storylines, player agency systems, non-linear story structures, or act frameworks for games. Triggers: branching narrative, player agency, story arc, interactive story, dialogue.
infrastructure-monitoring-game-ops
Use when implementing game monitoring, structured logging, player telemetry, alerting rules, or performance budgets. Triggers: monitoring, logging, telemetry, alerts, observability, metrics.
infrastructure-claude-code-game-workflow
Use when starting a game project, choosing which skill to read, or navigating the game-dev skill ecosystem. Entry point for AI agents working on game development. Triggers: workflow, which skill, how to start, game project setup.
infrastructure-ci-cd-game
Use when setting up CI/CD pipelines, GitHub Actions workflows, deployment automation, migration safety, or staging environments for game projects. Triggers: CI/CD, GitHub Actions, deployment, pipeline, staging.
engineering-stripe-game-payments
Use when implementing game payments, in-app purchases, subscriptions, Stripe webhooks, or monetization flows. Triggers: payments, IAP, subscription, Stripe, checkout, webhooks, monetization.
engineering-redis-game-patterns
Use when implementing Redis patterns for games — caching, leaderboards, pub/sub messaging, session storage, rate limiting, or ephemeral game state. Triggers: Redis, cache, leaderboard, pub/sub, rate limit, session.
engineering-postgres-game-schema
Use when designing game database schemas, player data models, inventory systems, or working with Drizzle ORM and PostgreSQL for games. Triggers: database, schema, Drizzle, players, inventory, leaderboards, game data.