Docs: Spionen i PROTOCOL.md og README

Faser, tider, stikordenes og gættets regler, afstemning, to spioner, point
og priser, handlinger og fejlkoder, hvad der er hemmeligt hvor, og et
snapshot. README nævner spillet, mappen, bots og dev.html.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
agw
2026-10-20 21:59:57 +02:00
co-authored by Claude Opus 5.5
parent 78fa557da4
commit 3ed6f252ec
2 changed files with 70 additions and 2 deletions
+3 -2
View File
@@ -1,6 +1,6 @@
# Weirsøe Party Protocol
Dansk party-spil i stil med Jackbox. Spillet vises på en storskærm, og spillerne er med fra deres telefoner. Første spil er **Fup og Fakta**: alle finder på en troværdig løgn til et kuriøst faktum, og så skal man finde sandheden blandt løgnene. I **Tegn og Gæt** går kæder af ord, tegninger og gæt bordet rundt, og til sidst ser alle, hvad der blev af det første ord. Værten vælger spil i menuen på storskærmen (piletaster/fjernbetjening, Enter og mus) og kan skifte til et andet spil i samme rum efter podiet, uden at telefonerne skal joine igen.
Dansk party-spil i stil med Jackbox. Spillet vises på en storskærm, og spillerne er med fra deres telefoner. Første spil er **Fup og Fakta**: alle finder på en troværdig løgn til et kuriøst faktum, og så skal man finde sandheden blandt løgnene. I **Tegn og Gæt** går kæder af ord, tegninger og gæt bordet rundt, og til sidst ser alle, hvad der blev af det første ord. I **Spionen** kender alle det hemmelige ord undtagen spionen; alle giver ét stikord, snakker og stemmer om, hvem der lyver. Værten vælger spil i menuen på storskærmen (piletaster/fjernbetjening, Enter og mus) og kan skifte til et andet spil i samme rum efter podiet, uden at telefonerne skal joine igen.
- Spillere: `https://party.weircon.dk` og indtast koden fra skærmen (eller scan QR-koden).
- Vært: `https://party.weircon.dk/host/` (Authentik, grupperne Familie og authentik Admins).
@@ -14,6 +14,7 @@ Serveren er autoritativ. Hver skærm har én WebSocket, sender handlinger og få
| --- | --- |
| `fupogfakta/` | Spilmotor, modeller for kategorier og spørgsmål, spørgsmålsbank i `data/*.json` |
| `tegnoggaet/` | Spilmotor for Tegn og Gæt og speakerens replikker (temaerne er fælles: `party/data/themes.json`) |
| `spionen/` | Spilmotor for Spionen og speakerens replikker (samme temaer) |
| `party/` | Rum, WebSocket-consumers, sider, Authentik-header-login, templates og JS/CSS; spilregistret i `games.py` |
| `partyhub/` | Django-projekt (settings, ASGI) |
| `deploy/` | systemd-unit, miljøfil-skabelon og `deploy.sh` |
@@ -34,7 +35,7 @@ export DJANGO_DEBUG=true DEV_HOST_USER=dev
Én uvicorn-proces (`--workers 1`) bag en reverse proxy med TLS. Proxyen beskytter `/host/`, `/ws/host/` og `/admin/` med Authentik forward-auth og sender `X-authentik-username` og `X-authentik-groups` videre; på alle andre stier skal den blanke de headere. Appen stoler kun på dem fra adresserne i `TRUSTED_PROXIES`, så appens port bør kun kunne nås fra proxyen. Se `deploy/wpp.env.example` for miljøvariabler.
`deploy/bots.py` spiller et helt spil mod en kørende server (`--game tegn` for Tegn og Gæt) og kan også fylde et spil op med bots (`--code`). Alle skærme kan ses uden server i `party/static/party/dev.html` (`?game=tegn&phase=draw` for Tegn og Gæt, `?game=menu` for menuen og introerne).
`deploy/bots.py` spiller et helt spil mod en kørende server (`--game tegn` for Tegn og Gæt, `--game spion` for Spionen) og kan også fylde et spil op med bots (`--code`). Alle skærme kan ses uden server i `party/static/party/dev.html` (`?game=tegn&phase=draw` for Tegn og Gæt, `?game=spion&phase=vote` for Spionen, `?game=menu` for menuen og introerne).
**Speakeren** (ElevenLabs) laver replikker med navne live. `manage.py tts_warm` forvarmer de faste replikker og, under `--budget`, menuens intro, de generiske replikker uden navne og resten. Med `WPP_TTS_LIVE=off` bruges kun forvarmede klip; med `auto` (standard) siges en forvarmet generisk replik, når en replik ikke kan laves live (ingen nøgle, afvist, timeout eller lav saldo). Spørgsmålenes oplæsning og spillernes egne ord har ingen generisk udgave og er tavse, når de ikke er forvarmet. Et nyt spils generiske replikker står i dets `narrator/generic.json`, nøglet på begivenheden.
+67
View File
@@ -314,3 +314,70 @@ Point: +100 pr. hjerte til den, der lavede bidraget, og +500 til ejeren af en k
"Bedste tegning" og "Bedste gæt" (flest hjerter, ved lighed det første i afsløringen), "Kæden der overlevede" og
"Største forvandling" (mindst lighed mellem første ord og sidste gæt). Temaerne står i `party/data/themes.json`
og speakerens replikker i `tegnoggaet/narrator/lines.json`.
## Spionen
Oprettes med `game=spion` på `/host/new` med `rounds` = `3` | `4` | `5` (standard 4) og `rules` = `1` | `0`.
Samme endpoints, samme `join`/`resume`/`ping`/`react`, og samme `joined`/`state`/`error`. Snapshot har `"game": "spion"`.
4-10 spillere; dem der joiner efter start, bliver publikum. Temaerne er fælles med Tegn og Gæt (`party/data/themes.json`);
Spionen bruger kun de konkrete navneord på mindst tre bogstaver (`party/themes.py`, `spy_themes()`).
En runde: alle undtagen spionen (to spioner fra otte spillere) ser et hemmeligt ord; spionen ser kun temaet. Ingen er
spion to runder i træk, hvis det kan undgås. Faser: `lobby → rules` (kun rummets første spil) `→ clue` (40 s) `→ clues`
(stikordene ét ad gangen på storskærmen, tilfældig rækkefølge) `→ talk` (60 s) `→ vote` (30 s) `→ verdict` (7 s)
`→ guess` (30 s, kun hvis den anklagede er spion) `→ result` (10 s), så næste runde eller `final`. `clue` og `vote`
slutter, når alle forbundne spillere er færdige; alle tidsfaser kan springes over af værten. Spillet hænger aldrig:
manglende stikord og stemmer løber ud, går den anklagede spion under gættet, slutter runden, og er der færre end fire
spillere tilbage, slutter spillet efter runden.
**Stikord:** ét ord, højst 20 tegn, ikke temaet. For dem, der kender ordet, heller ikke ordet selv, en bøjning af det
(en/et/er/erne/ene/e/s, også med fordoblet konsonant og uden slut-e), et ord, hvor det står inde i (`hundehus`), eller et
stikord, en anden har skrevet i runden. Spionens stikord holdes hverken op mod ordet eller de andres (en afvisning ville
røbe dem). Store/små bogstaver, mellemrum og bindestreger tæller ikke.
**Afstemning:** ikke på sig selv; flest stemmer bliver anklaget; lighed eller ingen stemmer = ingen anklages, og
spionerne slipper. Publikum kan stemme for sjov (`audience` i `votes`, tæller ikke). Med to spioner er den anklagede
fanget og får gættet; den anden slap. **Spionens gæt** er rigtigt, hvis det er ordet med de samme regler som stikord,
plus små stavefejl (ingen under fire bogstaver, én op til syv, to derover).
**Point:** +100 for at stemme på en spion; +300 til en spion, der slap; +200 til en fanget spion, der gætter ordet.
Priser: Bedste løgner (flest point som spion), Skarpeste øje (flest rigtige stemmer), Mest mistænkt (flest stemmer).
**Spiller (`/ws/play/`)**
| action | felter | hvornår |
| --- | --- | --- |
| `start` | | VIP'en i lobbyen |
| `submit_clue` | `text` (ét ord, 1-20 tegn) | fase `clue`; må skiftes, til fasen slutter |
| `vote` | `player_id` | fase `vote`; må skiftes; publikum også |
| `spy_guess` | `text` (1-30 tegn) | fase `guess`, kun den anklagede spion |
| `react`, `play_again` | | som i Tegn og Gæt |
Fejlkoder ud over Fup og Fakta: `clue_invalid`, `clue_is_word`, `clue_is_theme`, `clue_taken`, `bad_vote`,
`not_in_round`, `guess_invalid`.
**Hemmeligheden:** ordet står kun i `me.card` til dem, der kender det, aldrig til spionen, publikum eller storskærmen før
`result`. Hvem der er spion står ingen steder før dommen: ikke i spillerlisten, ikke i rækkefølger eller tider.
`votes.accused_is_spy` får storskærmen under `verdict` (den afslører det i fasens sidste sekunder); telefonerne først i
`guess`. Telefonerne får ingen stikord i `clues` (de kigger på storskærmen). Speakeren siger aldrig ordet eller spionen
før `result`; dommens replikker spørger kun.
```jsonc
{
"game": "spion",
"phase": "vote", // lobby | rules | clue | clues | talk | vote | verdict | guess | result | final
"round": {"number": 2, "of": 4, "name": "Runde 2 af 4"},
"players": [{"id": "p1", "name": "Asger", "color": "#ff4d6d", "score": 100, "connected": true, "vip": true,
"done": false, "in_round": true}], // done = har skrevet stikord (clue) / stemt (vote)
"theme": {"name": "Transport", "icon": "🚂"},
"spies": 1, // hvor mange, ikke hvem
"clues": {"items": [{"player_id": "p3", "text": "vinger"}, {"player_id": "p1", "text": null}]},
// fase clues (kun storskærmen har items): {"started_at", "intro_ms", "clue_ms", "items"}
"votes": {"counts": [{"player_id": "p2", "votes": 4}], "audience": [{"player_id": "p2", "votes": 1}],
"accused": "p2", "accused_is_spy": true, "by": []}, // verdict/guess/result; by (hvem stemte på hvem) i result
"result": {"spies": ["p2"], "spy_names": ["Sus"], "word": "flyvemaskine", "caught": "p2",
"guess": {"text": "helikopter", "correct": false}, "spy_won": false, "deltas": {"p1": 100}},
"me": {"id": "p1", "role": "player", "card": {"spy": false, "theme": "Transport", "word": "flyvemaskine"},
"clue": "vinger", "vote": "p2", "can_guess": false}
}
```