Werewolf 3D: a server-authoritative party game
A ten-seat social deduction game played around a 3D campfire in the browser, with private lobbies, built-in AI players, text chat and a WebRTC voice path.
The challenge
A Werewolf match needs ten players, and every player must see only what their role allows. Rejoining after a drop must not leak or lose state. A group of friends should be able to play without filling every seat, and without paid AI services.
My contribution
Sole developer: game rules, server, AI players, 3D client, networking protocol, media service and CI.
The approach
A TypeScript monorepo separates a pure rules package, a shared protocol package, a WebSocket game server, a Three.js client and a mediasoup media service. The server is the only authority. It projects a per-recipient view of each match, so hidden roles and night actions never reach clients that should not see them. AI players run inside the server and deliberately have no access to the match's private state — they act only on what they observed. Voice uses a mediasoup SFU, with TURN credentials minted per session from an HMAC of a shared secret.
The outcome
Playable locally and over a LAN with one to ten humans and up to nine AI players, core and starter role presets, reconnect handling with 60-second seat holds, and phone and tablet layouts. Voice is implemented but disabled by default; hardware benchmarks and human playtests are still pending.
Context
A desktop-first browser version of the Werewolf social deduction game. Players sit around a fire and use the mouse to pick seats, targets and votes. There's no walking and there are no hotkeys, so anyone can play, and Tab/Enter work as an accessibility alternative.
Architecture
packages/rules: the match state machine (phases, clock, roles, win conditions) with no I/O, so it can be unit-tested directly.packages/protocol: shared command, view and media message types.apps/server: a WebSocket game server that handles lobbies, sessions with CSRF protection, chat, AI controllers and metrics. It projects each match into per-player views.apps/web: the Three.js scene, with glTF assets optimized using meshoptimizer, plus audio, input and UI layers. It's built with Vite.apps/media: a mediasoup service for voice. Permissions and revocation are controlled by the game server.
A problem I solved: keeping secrets secret
In a hidden-role game the easiest bug is a leak: sending one player data that only another player should see. The server never broadcasts match state. It computes a separate view for each recipient, and media permissions are revoked before a recipient's view changes. The AI players follow the same rules. Their "brain" has no reference to the match object, only to the observations a player in that seat would have.
Validation
CI checks formatting and runs lint, typecheck, build, unit tests, protocol contract tests and integration tests.