Keep The Ship Running
Multiplayer crisis management sim — keep systems online as failures cascade.
Live Demo
The Problem
Crisis management is a skill you can't practice safely in real life. When the engine room floods or the data center loses cooling, you need to make decisions fast with incomplete information. Bad decisions cascade.
I wanted to build a game that captures that pressure — systems failing, priorities competing, decisions that matter.
The Connection
This experiment draws directly from my experience:
- Engine room emergencies — Fire, flooding, loss of propulsion. Each requires immediate action and coordination.
- Data center outages — Cascading failures when one system takes down others. The UPS fails, generators kick in, you have seconds.
- Shipboard damage control — The Navy taught me systematic response to emergencies. Isolate, contain, repair.
The Approach
Svelte was chosen for its simplicity and performance. The game state updates frequently, and Svelte's compile-time reactivity keeps things smooth.
The game features: - Real-time system simulation (power, cooling, network, life support) - Interconnected failures (losing power cascades to everything) - AI crew members that follow orders but make mistakes under pressure - Multiplayer drop-in where other players can take crew roles
Technical Deep Dive
The simulation runs on Cloudflare Durable Objects. Each game room is a Durable Object that maintains state, processes player actions, and broadcasts updates via WebSocket.
The tick-based simulation runs server-side at 10Hz. Clients receive state updates and render interpolated animations. This keeps the authoritative state on the server while maintaining smooth visuals.
// Durable Object game loop
export class GameRoom {
state: DurableObjectState;
sessions: WebSocket[] = [];
async alarm() {
// Run simulation tick
this.gameState = simulateTick(this.gameState);
// Broadcast to all players
const update = serializeState(this.gameState);
this.sessions.forEach(ws => ws.send(update));
// Schedule next tick (100ms = 10Hz)
this.state.storage.setAlarm(Date.now() + 100);
}
}Durable Object game loop with 10Hz tick rate
What I Learned
What worked well:
What I'd change:
Trade-offs accepted: