GameStack has over 30 multiplayer games, and we built most of them with the same pipeline. A game goes from “that would be fun” to live and playable in a single day. This isn’t a flex — it’s an architecture decision.
The plugin contract
Every game in GameStack is a plugin. A plugin is a directory with a fixed set of files:
- types.ts — the game’s state shape, actions, settings, and player data
- constants.ts — player counts, timers, phase names, scoring rules
- data.ts — card decks, prompt pools, question banks, category lists
- logic.ts — the state machine: how actions transform state
- bot.ts — a classic bot that plays the game using heuristics
- llm-bot.ts — an AI bot that plays using an LLM
- index.ts — the plugin registration entry point
The platform handles everything else: rooms, WebSocket connections, authentication, reconnection, spectating, turn timers, and the lobby UI. The game plugin never thinks about networking.
Why this works
The key insight is that every party game, no matter how different it feels, follows the same lifecycle: players join, a round starts, players take actions, the round resolves, scores update, repeat until someone wins. The differences are in what the actions are and how resolution works.
Bluff (our Fibbage clone) and Fuse (our Hanabi clone) feel nothing alike to play. But structurally, they’re both a loop of “collect simultaneous submissions, resolve, advance.” The plugin contract captures that structure.
The build pipeline
When we decide to build a new game, the process looks like this:
- Write a PRD — one page describing the game, its mechanics, player count, and how scoring works
- Define the types — what does the game state look like? What actions can a player take?
- Write the logic — a pure function from
(state, action) → state - Build the bots — classic bots use heuristics, LLM bots get a prompt describing the game state
- Write tests — every game gets 50+ tests covering setup, actions, edge cases, and bot play
- Build the frontend — a React component that reads state and dispatches actions
- Register — add the game to the plugin registry and game catalog
Each step feeds the next. The types constrain the logic. The logic constrains the tests. The tests validate the bots. By the time we’re writing the frontend, the game is already working.
What we’ve learned
Game logic should be pure. No side effects, no randomness that isn’t seeded, no dependencies on wall clock time. This makes every game deterministic and testable. Our test suite runs 4,400+ tests in seconds because every test is just state = reduce(state, action).
Bots are the best QA. If a bot can play the game from start to finish without crashing, the game logic is probably correct. We run bot-vs-bot simulations as part of the test suite. They’ve caught more bugs than manual testing ever did.
LLM bots are surprisingly good game designers. When we describe a game to an LLM and ask it to play, its mistakes often reveal ambiguities in our rules. If the LLM misunderstands a mechanic, players probably will too.
The pipeline isn’t magic. It’s just a contract strict enough that the interesting decisions happen in the game design, not in the plumbing.