An end-to-end networked Werewolf variant built to validate one loop: enter with gold → accuse with gold-weighted bounties → players die and drop loot → thieves and police fight over it → a faction takes the pot → choices convert into fame/karma → persistent UO-style titles. See werewolf-stub-spec_1.md for the design.
- Server (authoritative): Cloudflare Worker + one Durable Object per match (WebSocket hibernation, alarm-driven phase machine, transactional checkpoints). The DO is the single source of truth for all gold, roles, and resolution.
- Client: vanilla TypeScript + Canvas (Vite), served from the same Worker's edge assets.
- Persistence: Cloudflare D1 (account gold / fame / infamy / karma), behind a
ProfileStoreinterface. - Gold: simulated, behind a
Ledgerseam so real tokenization can drop in later.
Monorepo (pnpm workspaces):
| Package | What |
|---|---|
@warewolf/shared |
The contract: WS protocol, tuning constants, roster, titles. Imported by all. |
@warewolf/server |
Pure economy core (src/core) + the DO/Worker/D1 IO shell (src/do, src/io). |
@warewolf/client |
Canvas + DOM browser client. |
@warewolf/bot |
Headless WebSocket bot harness for zero-human matches. |
The riskiest logic (gold economy, night resolution) lives in packages/server/src/core as pure functions with zero Cloudflare imports, fully unit-tested. A gold-conservation invariant is asserted after every resolution.
- Node 22+,
pnpm, and a Cloudflare account (pnpm dlx wrangler login). pnpm install
pnpm test # 28 unit tests: night matrix, day resolution, conservation, titles, redaction
pnpm db:migrate:local # create the local D1 schema (once)
pnpm dev:server # start the Worker + DO locally on :8787
# in another terminal:
BOTS=7 MATCHES=5 pnpm sim # run full headless matches over real WebSocketspnpm sim env vars: BOTS (default 7), MATCHES (1), WAREWOLF_WS (ws://127.0.0.1:8787), TIMEOUT_MS.
Two processes:
pnpm dev:server # terminal 1 — Worker + DO + local D1 on :8787
pnpm dev:client # terminal 2 — Vite (HMR) on :5173, proxies /ws + /api to :8787Open http://localhost:5173. It generates a room and rewrites the URL to /r/CODE; open that URL in more tabs (or share on your LAN) to add players. Append ?fast=1 for short phase timers while testing.
pnpm dlx wrangler login
pnpm dlx wrangler d1 create warewolf # copy the returned database_id...
# ...into packages/server/wrangler.jsonc -> d1_databases[0].database_id
pnpm db:migrate:remote # apply schema to the remote D1
pnpm run deploy # builds the client + deploys the WorkerUse
pnpm run deploy, notpnpm deploy—deployis a built-in pnpm subcommand, so the bare form won't run this script.
wrangler deploy prints your public URL, e.g. https://warewolf.<subdomain>.workers.dev.
Open it, pick a name, and share the resulting /r/CODE link. A match auto-starts once the
floor (4 players) is met, or the host can start it. Matches run ~8–12 min on default timers.
No friends online? The bots are "somewhat smart": the seer hunts wolves, town bandwagons real lynches, the wolf misdirects, the thief loots the richest bodies, and police stake the prime suspect.
In the browser (easiest): as the host, click + Add bot in the lobby to add server-controlled bots, then Start match. The Durable Object drives the bots — nothing else to run. (− Remove bot removes one.)
From the CLI (alternative): fill a room from your terminal.
pnpm fill ABCD # 6 bots (default) into room ABCD on the deployed server
pnpm fill ABCD 4 # or pick how manyJoin the room before running fill so you're seated. Against a local dev
server: WAREWOLF_WS=ws://127.0.0.1:8787 pnpm fill ABCD.
All balance lives in packages/server via @warewolf/shared DEFAULT_CONFIG (and FAST_CONFIG
for tests/bots) in packages/shared/src/constants.ts.