Welcome to the pack 🐺
Everything you need to understand, run, and ship Werewolf AI Arena — a browser game where humans and AI bots play Ma Sói (Werewolf) together, live. This is the single source of truth. Read top to bottom on day one.
1Overview — what are we building?
A live, watchable Werewolf arena. Humans coach or play alongside AI bots that argue, lie, and vote in Vietnamese (or English) with real personalities — including VN celebrity voices. Spectators can bet on who the wolf is.
🎭 The hook
AI bots with distinct voices (Đà Nẵng GenZ, Sơn Tùng, Trấn Thành…) debate and deceive each other. It's funny to watch and unpredictable.
👥 Who plays
Host creates a room → humans + spectators join by link → host fills empty seats with bots → match streams live to everyone.
🎯 The vision
Watch-first AI arena (Twitch-style): bet on outcomes, pay to inject chaos. Side-project, not a Wildcats product.
2Quick Start — run it in 5 minutes
Goal: get the game on localhost:3000 and play one match in your terminal. You need Node 18+.
Get the private repo (ask Lucas for GitHub access first).
cd werewolf-ai-arena
Copy
.env.example to .env.local and fill it. See section ⑧ for every key + where to get it. Teammates DM each other the shared keys.
copy .env.example .env.local
Watch 6 dumb bots play a full match in ~5 min. Great for understanding the game loop.
npm run match:claude # force real Claude bots
npx tsc --noEmit to typecheck. A failing typecheck = a broken Vercel deploy.3Repo & Git
| Thing | Value |
|---|---|
| Git remote | git@github.com:imnumber1234/werewolf-ai-arena.git |
| Visibility | PRIVATE — keep it that way. Never make public. |
| Main branch | main — pushing here auto-deploys to production. Be careful. |
| Access over SSH | Use SSH (git@github.com), not HTTPS, to avoid login popups. |
| Local folder | GitHub/Werewolf AI Arena/ in Lucas's workspace |
main is live. For anything risky, branch first (git checkout -b feature/x), open a PR, merge when green. Don't push half-broken code straight to main — Vercel ships it instantly.4Tech Stack
Frontend
- Next.js 16 (React 19) — pages + API in one
- TypeScript — everything is typed
- PixiJS — 2D pixel-art village
- three.js — optional 3D VRM avatars
- Custom CSS (globals.css + landing.css)
Backend / Engine
- Next.js API routes (serverless on Vercel)
- Pure-TS game engine (
src/engine/) — no game library - Vercel AI SDK — talks to all LLM vendors
- Upstash Redis — room + event state
- Clerk — user auth
- msedge-tts — free voice (text-to-speech)
Folder map (where things live)
| Folder | What's inside |
|---|---|
app/ | Pages (landing, lobby, room, join, train) + all API routes + React components |
src/engine/ | The game brain — match loop, roles, win logic, cast, dialects, phrasebank |
src/agent/ | The players — real LLM bots, mock bots, human input, webhook bots, prompt assembly |
src/server/ | Storage + services — rooms, event log, permissions, betting, cost meter |
src/cli/ | Terminal match runner (npm run match) |
worker/ | Optional Cloudflare real-time layer (Phase 1, separate package) |
public/ | Faces, 3D models, audio, memes, demo videos, mockups |
db/ | Postgres schema for future Supabase archival |
docs/ | Design + research docs (see section ⑬) |
5Architecture — the reload-safe design
This is the most important concept. Understand it before touching match code.
Host-only. Runs the entire match server-side, turn by turn, and writes every event to Redis. The host's browser does NOT run the match.
Every viewer (player or spectator) subscribes here. They long-poll for new events and get only what their role is allowed to see.
Redis store of who-is-what. Server-side it filters events: a villager never receives the wolves' night chat.
Every message is one event. Reload? You just re-subscribe and replay the log. No freeze, no lost state.
/start drives, /stream watches) fixed that. Don't reintroduce browser-driven match logic.Vendor fallback chain
When a bot needs to speak, it tries vendors in order and degrades gracefully — so one slow/down provider never kills a match.
// 60s timeout + 1 retry per bot. Hard match deadline: 270s (Vercel cap 300s).
6Database
Today it's all Upstash Redis (serverless key-value, no SQL). A Postgres schema exists for future match archival but isn't live yet.
| Store | What | Lifetime |
|---|---|---|
| Room state | Players, settings, current phase (Redis) | 4-hour TTL |
| Event log | Every match message, per room (Redis list) | 2-hour TTL, max 5000 |
| matchMeta | Roles + permissions per match (Redis) | Per match |
| FUTURE matches / match_players | Finished-match archive — defined in db/schema.sql | Permanent (Supabase Postgres, not wired yet) |
cute-termite-84944.upstash.io. Auth tokens live in .env.local under KV_REST_API_*. There's a read-only token too. Never commit these.docs/PLATFORM-MIGRATION.md.7Hosting & Deployment
Primary — Vercel LIVE
| URL | werewolf-ai-arena.vercel.app |
| Deploy | Auto on push to main |
| Region | Singapore (sin1) |
| Fn timeout | 300s (match deadline 270s) |
Optional — Cloudflare Worker PHASE 1
Real-time WebSocket layer using Durable Objects, in worker/ (its own package). Mock matches work; not yet pointed at production. Config in worker/wrangler.jsonc.
Deploy checklist
npx tsc --noEmit — must passgit commit with a scoped messagegit push origin main → Vercel builds & ships automatically8Keys & Environment Variables
.env.local is gitignored; never commit it or paste keys in chat/Notion.| Key | Purpose | Where to get it |
|---|---|---|
CLERK_SECRET_KEYNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY | User auth (login) | clerk.com — free tier |
KV_REST_API_URLKV_REST_API_TOKENKV_REST_API_READ_ONLY_TOKENREDIS_URL | Redis database | upstash.com — free tier |
ANTHROPIC_API_KEY | Claude bots (default) | console.anthropic.com |
GOOGLE_GENERATIVE_AI_API_KEY | Gemini bots (optional) | aistudio.google.com — free |
DEEPSEEK_API_KEY + DEEPSEEK_BASE_URL | DeepSeek bots (optional) | platform.deepseek.com — funded by Anh |
QWEN_API_KEY + QWEN_BASE_URL | Qwen bots (optional) | dashscope.aliyun.com — funded by Anh |
QSTASH_TOKEN | Upstash Workflow (host-reload fix, future) | Upstash console |
KLIPY_API_KEY | Real GIFs (dormant feature) | Optional, off by default |
npm run match) needs no keys at all — it uses mock bots. Add keys only when you want real LLM bots or the full web app with login.9How a Match Runs
Pacing (tunable in src/config.ts)
| Phase | Time |
|---|---|
| Night (roles act) | 30s |
| Day talk | 60s |
| Day vote | 30s |
| Human turn timeout | 45s |
| Bubble gap (one speaker at a time) | 5s |
| Max turns (hard ceiling) | 12 |
10Roles
Assigned by src/engine/roles.ts based on player count. More players unlock more special roles.
11Content & Cast — the voices
The personality is the product. Each bot stacks: identity → regional dialect (giọng vùng miền) → job/role flavor → strategy → voice samples. Built in src/agent/persona.ts, fed by engine files below.
Generic personas (src/shared/personaTemplates.ts)
Central VN dialect, anxious nervous energy.
Northern dialect, cà khịa (roast) humor.
Southern dialect, dramatic, loves the drama.
Loud debate-club captain, English filler words.
Quote-collector, hunts contradictions.
Silent until ~90% certain, then strikes.
Confident, loud, often wrong.
Trusts cat lovers, suspects fast talkers.
VN celebrity cast (src/engine/cast.ts)
Reserved, soft-spoken, cool detachment.
Ultra-talkative, emotional, empathetic, Southern accent.
Grave, preaches morality, cocky.
Faces in public/faces/, personas TBD.
docs/CHARACTER-VOICE-RESEARCH.md and docs/DIALECT-ENHANCEMENT-PLAN.md.Content source files
| File | Holds |
|---|---|
src/engine/cast.ts | Named VN celebrities + their authored voice tics |
src/engine/regions.ts | VN regional dialects (Bắc / Trung / Nam / Tây) |
src/engine/phrasebank.ts | Job-based slang (bully, therapist, prophet, dealer…) |
src/engine/strategies.ts | Play-styles (aggressive, defensive, deceptive) |
src/agent/persona.ts | Assembles the final ~400-token prompt per turn |
12Assets — images, audio, video
| Asset | Location | Notes |
|---|---|---|
| Character faces | public/faces/[NAME]/ | 5 emotion states each (normal/happy/sad/angry/scared) PNG |
| Portrait fallback | portraitFor() in portrait.ts | DiceBear API when no custom face (free); Puter.js AI portraits = Phase 1b |
| Pixel village | app/components/PixelStage.tsx | SVG-drawn ring of avatars |
| 3D avatars (VRM) | public/3d/*.vrm | Optional three.js models |
| Win music | public/audio/celebration.mp3 | Plays on win + dance button |
| Voice (TTS) | msedge-tts | Microsoft Edge TTS — free, no key |
| Memes / GIFs | public/memes/ + /api/gif | Curated fallback pack; Klipy proxy dormant |
| Demo videos | public/demo.mp4, trailer.mp4 | For landing + promo |
13Docs Index — read these next
Already in the repo. Start with HANDOFF.md, then the build plan.
| Doc | What it covers |
|---|---|
HANDOFF.md ⭐ | Full handoff for the next person — read first |
WEREWOLF_BUILD_PLAN.md | Phased roadmap + shipping log |
AUDIT.md | Security & game-balance audit |
BUGS.md | Known issues + fixes |
FLOW-DESIGN.md / FLOW-RESEARCH.md | UI/UX flow mockups + game-design research |
docs/CHARACTER-VOICE-RESEARCH.md | How/why the persona voice system works |
docs/DIALECT-ENHANCEMENT-PLAN.md | Voice layering deep-dive |
docs/PLATFORM-MIGRATION.md | Phase 2: Workflow + WebSocket + Supabase |
docs/PLAYER-MODES-PLAN.md | Human / spectator / bot modes spec |
worker/README.md | Cloudflare Durable Object setup |
14Status & Known Bugs
| Feature | Status |
|---|---|
| Game loop (turn-based, role logic) | SHIPPED |
| Role-filtered chat streaming | SHIPPED |
| AI bots (Claude/Gemini/OpenAI/DeepSeek/Qwen) | SHIPPED |
| Human players (turn-gated) | SHIPPED |
| Spectator betting (two markets) | SHIPPED |
| Training / XP ladder | SHIPPED |
| AI portraits (DiceBear fallback) | SHIPPED |
| Real GIFs (Klipy) | ENV-GATED — needs key |
| Cloudflare real-time worker | PHASE 1 — mock only |
| Supabase match archival | PLANNED — schema ready |
| Host-reload fix (Upstash Workflow) | DESIGNED — needs QSTASH_TOKEN |
npm run test:smoke) is broken as of 2026-06-23 — see BUGS.md. Don't trust it as a gate yet; verify manually.15Team & Workflow
🧑💻 Engineering (most of you)
- Branch off
main, PR, merge when typecheck passes - Run
npx tsc --noEmitbefore every push - Test with
npm run match(free) before spending API tokens - Never commit
.env.localor any key - Keep VN diacritics intact in all content
📣 Marketing & Business (few of you)
- You don't need to code — focus on the live URL + demo videos
- Cast voices & personas are the marketing hook
- Assets to use:
public/demo.mp4,trailer.mp4, landing page - Vision: watch-first arena, betting, pay-to-inject (Twitch-style)