docs/VAMPIRE_VILLAGE.md
Rama main · réplica de lectura del repositorio original.
# Vampire Village
A hidden-role social deduction game for a voice room (spec:
`projectdetails/13_VAMPIRE_VILLAGE_PLUGIN.md`, roadmap Aşama 7). Vampires hide
among the villagers; every night they bite someone, every day the village
talks and votes to hang a suspect. 5–12 players, played inside the lobby's
activities hub like Hushle.
| | |
|---|---|
| Plugin id | `vampire-village` (package `@lobbyforge/vampire-village`, `plugins/vampire-village`) |
| Players | 5–12; late joiners and the dead watch as spectators |
| Languages | English, Turkish (`locales/*.json`) |
| Where | Any voice or stage channel, once a server admin installs and enables the app (Admin → Apps) |
## How to start
1. A server admin installs **Vampire Village** in *Admin → Apps* (or
`POST /api/servers/{id}/apps {"pluginId":"vampire-village","enabled":true}`).
2. In the lobby, someone with the *Start activity* permission opens the
activities hub of a voice channel (*Play together*, or *Activities in this
room* from the voice view) and starts **Vampire Village**. They are the host.
3. Everyone who wants to play opens the hub, creates a character (a name
and a card colour) and presses **I’m ready**.
4. With at least 5 players, all ready, the host presses **Start the game**.
## A round
| Phase | What happens | Timer | Ends |
|---|---|---|---|
| Lobby | Characters, readiness, the host sets the timers | — | Host starts |
| Roles | Everyone sees their own role card; vampires meet their pack | 10 s | Timer or host |
| Night *n* | Night roles act in secret; vampires pick a victim together | 30 s (15–180) | Everyone with a choice has made it, the timer, or the host |
| Dawn | The village learns who died, how, and what role they had | 5 s | Timer or host |
| Day *n* · discussion | Talk (voice or the village chat) | 90 s (30–600) | Timer or host |
| Day *n* · vote | One vote per living player, changeable | 30 s (15–180) | Everyone living has voted, the timer, or the host |
| Verdict | Who was hanged (and their role), or nobody | 5 s | Timer or host |
| Game over | The winner, every role, the whole story | — | Host: *Play again* |
Timers are deadlines stored in the state (`phaseEndsAt`). Every panel counts
down to the same moment. **Only living vampires see the night clock** (spec
§13) — everyone else sees *Night…*; the night length is still a public
setting, so this is presentation, not a secret.
## Roles
The MVP roles (spec §23). Everyone starts with their own role card; no one
else's role is shown until that player dies.
| Role | Team | At night | Notes |
|---|---|---|---|
| Vampire | Vampires | Bites one player, together with the pack | A **majority of the living pack** must pick the same target (1 of 1, 2 of 2, 2 of 3). Cannot bite a vampire. Sees the pack and the pack chat. |
| Villager | Village | — | Voice and vote. |
| Seer | Village | Looks into one other player | Learns their exact role **at dawn**. |
| Doctor | Village | Protects one player (themself allowed) | Not the same player two nights in a row. Stops a bite or a shot. |
| Hunter | Village | From night 2, may shoot someone (2 bullets) | If the shot kills a **village-team** player, the hunter dies of remorse. Shooting a vampire or a neutral is free. |
| Survivor | Neutral | Raises a shield, or not (3 shields) | A raised shield is spent even on a quiet night; it stops any attack on the survivor. Wins by being alive at the end, whoever wins. |
| Jester | Neutral | — | Wins if the village **hangs** them (the game goes on). Dying any other way is a loss. |
### Who gets which role
Spec §8, limited to the MVP roles; seats the table gives to Phase 2 roles
(witch, detective, arsonist, gossip, Eros, mayor, fool) are plain villagers.
| Players | Vampires | Village team | Neutral |
|---|---|---|---|
| 5 | 1 | seer, doctor, 1 villager | survivor |
| 6 | 1 | seer, doctor, hunter, 1 villager | survivor |
| 7 | 1 | seer, doctor, hunter, 1 villager | survivor, jester |
| 8 | 2 | seer, doctor, hunter, 1 villager | survivor, jester |
| 9 | 2 | seer, doctor, hunter, 2 villagers | survivor, jester |
| 10 | 3 | seer, doctor, hunter, 2 villagers | survivor, jester |
| 11 | 3 | seer, doctor, hunter, 3 villagers | survivor, jester |
| 12 | 3 | seer, doctor, hunter, 4 villagers | survivor, jester |
Roles are shuffled on the server (`crypto.getRandomValues`) when the host
starts; the start action carries nothing a client could steer.
## The night, resolved
When the night ends, the choices resolve in the spec §10 order:
1. **Information** — the seer's result.
2. **Protection** — the doctor's patient; the survivor's shield (if raised).
3. **Attacks** — the hunter's shot, then the pack's bite. A protected or
shielded target survives; the first attack that lands names the cause.
4. **Results** — the hunter's remorse, the private notes, the public log.
Night 1 is quiet for the hunter (spec §9: the first night is for
information; the vampires still bite). Choices can be changed until the
night ends; the night ends early once every living vampire majority, seer,
doctor, hunter (with bullets, from night 2) and survivor (with shields) has
decided. "Skip tonight" / "Hold fire" / "Stay unguarded" count as decisions.
At dawn everyone reads who died, how (*bitten*, *shot*, *died of remorse*),
and their role. If an attack was stopped, the village hears that **an attack
was stopped** — not whom it was aimed at; the saved player and the doctor
learn it privately.
## The vote
- One vote per living player, for another living player or for **no one**;
it can be changed until the vote closes.
- **A majority of the living hangs** (more than half: 3 of 5, 4 of 6). No
majority — nobody is hanged. With a strict majority a tie cannot happen.
- The vote closes when everyone living has voted, when the timer runs out,
or when the host closes it.
- Votes are public while they are cast (spec §14): each row shows who voted.
## Winning (spec §12)
- **Village**: every vampire is dead.
- **Vampires**: the living vampires are at least as many as the living
**village team** (neutrals do not count).
- If the last vampire and the last village-team player fall together, no team
wins.
- **Survivor**: alive when the game ends — wins alongside whoever won.
- **Jester**: hanged at any point — has already won.
- Everyone on the winning team wins, dead or alive.
The end screen shows the winner, every player's role and fate, a
night-by-night timeline (every seer look, protection, shot, shield and the
votes) and what the pack whispered.
## Talking
- **Village chat** — living players, by day (dawn, discussion, vote,
verdict); read-only at night; everyone who played may talk after the game.
10 messages per player per phase; the last 60 are kept.
- **Pack chat** — living vampires, during the role reveal and at night.
12 whispers per vampire per phase; the last 40 are kept. Only vampires ever
receive it (until the game ends).
- The voice room itself is not muted by the game in this MVP: the panel asks
players to keep quiet at night and the dead to give no hints.
## What each seat sees
The server projects the state separately for every viewer
(`projectActivityState` in `packages/core/src/activity-projection.ts`, used by
the REST routes and the realtime gateway alike). Everything secret lives
under `state.secret` and is removed for everyone while the game runs; each
viewer gets only their own slice as `me`.
| Information | Living player | Living vampire | Dead player | Spectator | After the game |
|---|---|---|---|---|---|
| Own role, own notes, own resources | ✅ | ✅ | ✅ | — | ✅ |
| Own choice tonight | ✅ | ✅ | — | — | ✅ |
| Fellow vampires, the pack's votes, pack chat | — | ✅ | — | — | ✅ |
| Another player's night choice or private result | — | — | — | — | ✅ |
| Roles of the dead, causes of death | ✅ | ✅ | ✅ | ✅ | ✅ |
| Living players' roles | — | fellow vampires only | — | — | ✅ |
| Votes, the village chat, the event log | ✅ | ✅ | ✅ | ✅ | ✅ |
The host sees exactly what their own seat sees — host controls never reveal
a role (anti-cheat: the projection is applied to the host too).
## Host controls
In the lobby: the timers, removing a player, **Start the game**. During the
game: move on (*Start the night*, *End the night*, *Start the day*, *Start the
vote*, *Close the vote*, *Continue*), **Pause/Resume** (actions wait while
paused), **+30 s**, the timers for the next phases, **Remove from the game**,
and **End the game** (no winner; everything is revealed). After the game:
**Play again** (same village, fresh lobby, same timers).
## Leaving and joining
- Leaving the lobby frees the seat; the host can remove players.
- A living player who leaves mid-game (or is removed by the host) counts as
dead — "left the village" — and their role is revealed. Their vote and any
vote or night choice aimed at them are dropped; the win check runs.
- Anyone who joins after the roles are dealt (or when the lobby is full)
watches as a spectator.
- The game declares `requiresVoiceRoom`: joining, readying, night choices,
votes and chat need the activity's voice room (403 `voice_required` from
outside it); leaving works from anywhere (`allowOutsideVoice`), and
watching never needs voice. A host who leaves the voice room hands the
game over after 60 s to the longest-present participant there; see
[PLUGIN_SDK.md → Voice, hosting and play again](PLUGIN_SDK.md#voice-hosting-and-play-again).
## Rule decisions
Where the spec was silent, offered options, or contradicted itself:
- **Vote rule: majority of the living** (the design's rule: "A majority of the
living is needed. Ties mean no one leaves today."), instead of the spec §11
plurality with a run-off on a tie. A strict majority makes the run-off
unnecessary; plurality + run-off is a possible follow-up setting.
- **Roles are revealed on death**, night deaths included — spec §13 (the dead
card shows the role) and the design, over the §14 table that hides a night
victim's role from the living.
- **Saved players are not named publicly** at dawn (the spec says the dawn
shows "who was saved"): naming them would tell the vampires where the doctor
was, and the doctor cannot protect them again the next night.
- **The vote closes early** once every living player has voted.
- **Doctor**: may protect themself (the spec only forbids the same target on
consecutive nights).
- **Survivor**: raising a shield spends it, attacked or not.
- **Hunter**: remorse applies when the shot lands on a village-team player,
even if the vampires bit the same player.
- **Jester**: the win is recorded when hanged; the game continues.
- **Night 1**: only the hunter sits out (killing roles other than the pack
wait for night 2); the doctor and the survivor act.
- **Text mode only** (the spec's MVP): no server-side voice muting yet.
- **No lobby chat**: the voice room and the channel chat cover the lobby.
- **Character**: a name (unique in the village, 24 characters) and one of 8
card colours; no custom icon or bio.
- **Fixed short phases**: role reveal 10 s, dawn 5 s, verdict 5 s (spec §9);
night, discussion and vote are the host's to set.
## Not in the MVP (spec Phase 2)
Voice + text mode with server-side muting, the witch, detective, gossip,
arsonist, Eros, mayor and fool, last words, the jester's revenge, ambient
sound, role-use effects, statistics/MVP on the end screen, manual role
assignment, a dead-only chat, reconnect timers and pause-on-disconnect
(today a leaver simply counts as dead).
## For developers
```
plugins/vampire-village/
src/state.ts types, limits, initial state, migrateVillageState (v1)
src/rules.ts role deal, teams, majority, fixed timers
src/reducer.ts the reducer (server clock + CSPRNG injected)
src/validate.ts validateAction — shape checks before dispatch
src/view.ts the projected VillageView + the panel's pure helpers
src/renderClient.tsx the panel root ('use client')
src/ui/ phase views, pieces, palette, key maps
locales/en.json, tr.json
```
- **State**: public fields at the top level, every secret under
`state.secret` (roles, tonight's choices, private notes, resources, pack
chat, night history). A new secret goes under `secret` and is hidden by
default. `version: 1`; anything else migrates to a fresh lobby.
- **No side doors**: a secret write never touches a public field — the pack
chat numbers its messages with `secret.packSeq`, not the public `seq`, so
public log/chat ids have no gaps to count whispers by. One host-level
channel remains: the realtime bus announces every committed action, so a
watcher can tell *that* something happened at night (never who or what).
Closing it would take the host publishing only when a viewer's projection
changes. The audit log is not a side door either: player actions are
`member` actions, which the host never writes there.
- **Actions**: player actions are `member` with `actorFields: ['playerId']` —
the reducer keeps its own roster and checks seat, life, role and phase;
table controls are `host`. Every refused action returns the same state
object.
| Action | Who | |
|---|---|---|
| `join` `{ name, color? }` | member | Take a seat (lobby) or watch (running) |
| `leave`, `set-ready {ready}` | member | |
| `night-target {targetId \| null}` | member | Bite / look / protect / shoot; `null` = skip or take back |
| `night-shield {raise}` | member | Survivor |
| `vote {targetId \| null}` | member | `null` = no one |
| `chat {text}`, `pack-chat {text}` | member | |
| `timeout {phaseId}` | member | Accepted once the server clock passes the deadline (1 s grace) |
| `configure {settings}`, `start`, `kick {targetId}`, `advance {phaseId}`, `pause`, `resume`, `extend {seconds}`, `play-again`, `end-game` | host | |
- **Timers**: the server has no scheduler, so clients report a deadline. The
host's panel reports 0.4 s after it; seat *n* waits 2.5 s + 0.9 s·*n*;
spectators 15 s — so normally one request moves the game on, and a closed
host tab never stalls it.
- **Rate limit**: the activity action route allows 30 actions a minute per
client address (all players behind one address share it, so configure
`LOBBYFORGE_TRUSTED_PROXY` behind a proxy). A full 6-player round is about
25 actions.
- **Tests**: `pnpm --filter @lobbyforge/vampire-village test` (reducer, rules,
validation, view helpers, locales, a server render of every phase);
hidden-information rules in
`packages/core/src/__tests__/activity-projection.vampire.test.ts`; a
six-browser game in `apps/web/e2e/activity-vampire-village.spec.ts`
(compose stack).