Skip to content

About

A werewolf game where every villager is the Laya decision model

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

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
convaiinnovations/laya

Moonrise Village

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.

How to play

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:

  1. The wolf eliminates one villager at night.
  2. The morning clue points at the wolf 70% of the time and at an innocent player 30% of the time.
  3. The village has two talk rounds.
  4. Every living player votes. A tie triggers one revote. A second tie means no elimination.
  5. 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.

How Laya drives a bot

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.

Project structure

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

Limits

  • 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.

Run locally

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.py

The 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

Configuration

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.

Deploy

The GitHub Actions workflow runs fast tests, checks that the app imports without loading checkpoints, then pushes main to the configured Hugging Face Space.

  1. Create or open the Gradio Space CallSohail/werewolf-village.
  2. Set the Space variable LAYA_DEVICE=cpu.
  3. In the GitHub repository, add the Actions secret HF_TOKEN with write access to the Space.
  4. Add the Actions variable HF_SPACE=CallSohail/werewolf-village.
  5. Push to main. The deploy job 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.

License

MIT, Muhammad Sohail.

About

A werewolf game where every villager is the Laya decision model

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages