| title | Moonrise Village | |
|---|---|---|
| emoji | 🌙 | |
| colorFrom | indigo | |
| colorTo | gray | |
| sdk | gradio | |
| sdk_version | 6.28.0 | |
| python_version | 3.12 | |
| app_file | app.py | |
| pinned | false | |
| license | mit | |
| short_description | A production-ready Werewolf game driven by Laya decisions | |
| models |
|
Moonrise Village is a six-player Werewolf game in which every bot is driven by Laya, an open, non-autoregressive decision model. Laya never writes dialogue. It scores suspicion, selects intentions, votes, and night targets in one batched forward pass per bot. The words you see come from a fixed English and French template bank.
The live Space is configured for CallSohail/werewolf-village, Gradio 6.28.0, CPU inference, and ZeroGPU
compatibility. Game state stays in gr.State. Aggregate session stats stay in gr.BrowserState. The server
does not store games or messages.
Choose a language and one of three modes:
| Mode | Your role | What happens |
|---|---|---|
| Hunt | Villager | A random bot is the wolf. Read clues, question stories, and vote it out. |
| Hide | Werewolf | Pick a victim each night and redirect the village during both talk rounds. |
| Spectate | Observer | Six bots play a complete seeded game while decisions stream into the log. |
Every day follows the same sequence:
- The wolf eliminates one villager at night.
- The morning clue points at the wolf 70% of the time and at an innocent player 30% of the time.
- The village has two talk rounds.
- Every living player votes. A tie triggers one revote. A second tie means no elimination.
- Villagers win by eliminating the wolf. The wolf wins at parity or after day six.
Eliminated roles are revealed. Read minds unlocks the bots' normalized suspicion bars after the first vote and stays visible at game end. The end screen includes role reveals, daily suspicion replay, browser-only stats, and copyable share text.
village/brain.py builds four string fields for each bot: its perspective, the living player list, recent
clues, and a budgeted transcript. Older discussion is summarized by code as one line per round. The state and
all questions for that phase go through one Engine.predict call.
Here is a shortened talk-round question batch:
{
"suspicion_mira": {
"type": "score",
"instructions": "How suspicious is Mira as the werewolf?",
"criteria": [
"clearly innocent", "probably innocent", "unsure",
"probably the wolf", "clearly the wolf"
],
},
"contradiction_mira": {
"type": "noul",
"instructions": "Has Mira contradicted themselves?",
},
"intent": {
"type": "choice",
"instructions": "What conversational move should you make now?",
"criteria": {
"accuse": None, "defend_self": None, "agree": None,
"question": None, "doubt": None, "stay_quiet": None
},
},
}The expected score from each suspicion rubric is normalized to 0 to 1, then combined with numeric personality weights. Mira gives extra weight to clues. Bram leans toward the current majority. Odile is more likely to push an accusation. Tomas is cautious. Nell emphasizes de-escalation. Aster balances clues and group evidence.
A bot wolf never evaluates or selects itself. It frames a suspected villager or a recent accuser, follows the majority when that is safe, and occasionally receives an intentionally over-defensive tell template.
Human messages use a separate, single Laya call with two noul questions: abusive or hateful language, and a
claim of special powers or authority. Abuse above 0.8 is replaced with [the village ignores this]. Authority
claims remain visible but increase bot suspicion as a game mechanic. Input is NFKC-normalized, stripped of
zero-width and control characters, capped at 280 characters, and limited to one message per second.
app.py Gradio wiring, streaming handlers, background loading
village/engine.py Low-memory Router wrapper and checkpoint fallback
village/game.py State machine, roles, voting, wins, input handling
village/brain.py State/question building and Laya-backed bot logic
village/lines.py English and French line templates
village/clues.py Seeded 70/30 clue generator
village/render.py Escaped HTML rendering
village/style.css Responsive day/night interface
tests/ Fast fake-engine tests and opt-in real-model tests
- Laya is a fast decision model, not an oracle. Its probabilities are evidence, not facts.
- Choice accuracy drops with roughly more than 20 options. This board uses at most five.
- The English checkpoint accepts about 512 model tokens and the multilingual checkpoint about 1024. The game uses a conservative 420-token state budget and trims old transcript lines first.
- CPU decisions normally take about 100 to 500 ms after loading, depending on the host.
- Two resident checkpoints need substantial RAM. A low-memory meta-device loader avoids a second random-weight copy during startup.
- Template dialogue is intentionally bounded. The bots decide what to do, not arbitrary prose.
Python 3.12 is recommended.
python -m venv .venv
source .venv/bin/activate
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements-dev.txt
python -m pytest -q
python app.pyThe first model start downloads the selected Laya checkpoints. To validate only the UI and fast game tests:
SWAY_SKIP_ENGINE=1 python -c "import app; print('ok')"Run the real-model checks only when model downloads and several minutes of CPU time are acceptable:
SWAY_MODEL_TESTS=1 LAYA_DEVICE=cpu python -m pytest -q -s tests/test_model.py| Variable | Default | Purpose |
|---|---|---|
LAYA_DEVICE |
automatic | Set to cpu on the Space. |
LAYA_CHECKPOINTS |
english,multilingual |
Comma-separated resident Router checkpoints. |
LAYA_LOW_MEMORY |
1 |
Use the meta-device loader. Set 0 for Laya's stock loader. |
SWAY_SKIP_ENGINE |
unset | Set 1 for CI/import checks that must not load models. |
SWAY_MODEL_TESTS |
unset | Set 1 to enable slow real-model tests. |
MOONRISE_CONCURRENCY |
2 |
Maximum concurrent queued Gradio jobs. |
BROWSER_STATE_SECRET |
local fallback | Secret used to sign browser-stored session statistics. |
GRADIO_SERVER_NAME |
0.0.0.0 |
Bind address. |
GRADIO_SERVER_PORT / PORT |
7860 |
HTTP port. |
The GitHub Actions workflow runs fast tests, checks that the app imports without loading checkpoints, then
pushes main to the configured Hugging Face Space.
- Create or open the Gradio Space
CallSohail/werewolf-village. - Set the Space variable
LAYA_DEVICE=cpu. - In the GitHub repository, add the Actions secret
HF_TOKENwith write access to the Space. - Add the Actions variable
HF_SPACE=CallSohail/werewolf-village. - Push to
main. Thedeployjob mirrors that tested commit to the Space.
The ZeroGPU registration stub at the top of app.py satisfies the Space runtime but is never called. Inference
stays on CPU. Gradio launches with ssr_mode=False.
MIT, Muhammad Sohail.