No description
  • TypeScript 56.8%
  • Python 40.1%
  • HTML 3.1%
Find a file
A-iko e2c6e9e3ef feat: crowd scenario for the fake-audience sim (stage 10)
Ship a pack-shipped crowd.toml for the mobrule CLI's Crowd Simulator:
weights shaping the chaos mix, a cadence, and canned viewer chat for the
say event. Adds the README section and the stage-10 row in the tutorial
map.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WFTa8XKCzzcidGHCXZeB4y
2026-07-17 14:57:26 +02:00
.forgejo/workflows Run workflow steps under bash (runner default sh rejects pipefail) 2026-07-14 13:43:09 +02:00
docs/img Rewrite README 2026-07-09 01:11:59 +02:00
overlay feat: overlay - effect toasts with viewer credit, death counter (stage 8) 2026-06-12 16:26:20 +02:00
.gitignore feat: overlay - effect toasts with viewer credit, death counter (stage 8) 2026-06-12 16:26:20 +02:00
adapter.py feat(adapter): accept bridge-injected MOBRULE_ADAPTER_TOKEN 2026-06-12 22:01:46 +02:00
CLAUDE.md feat: crowd scenario for the fake-audience sim (stage 10) 2026-07-17 14:57:26 +02:00
crowd.toml feat: crowd scenario for the fake-audience sim (stage 10) 2026-07-17 14:57:26 +02:00
LICENSE chore: align LICENSE with declared MIT license 2026-07-10 11:08:25 +02:00
pack.toml feat: default redeems and full README (stage 9) 2026-06-12 16:26:20 +02:00
README.md feat: crowd scenario for the fake-audience sim (stage 10) 2026-07-17 14:57:26 +02:00

chaos-pack-doom

Doom Chaos, a mobrule pack that lets Twitch viewers wreak havoc on a live Zandronum (Doom) session. Moon gravity, nightmare-speed monsters, hostile bots, and chat taunts, all bought with channel points.

Where pack-hello-world is the smallest possible adapter, this pack shows a real integration: an external game driven over its own remote protocol (RCON), multiple queues, timed effects that revert themselves, and honest failure reporting (read: refunds) when the game is unreachable. The adapter is a single stdlib-only Python file, so it doubles as readable example code. There's a full adapter tutorial built around this pack at https://docs.mobrule.tv.

Install

Grab the zip from the pack-latest release and unzip it into your packs folder (open the Dashboard, go to the pack picker, and click Packs folder to reveal it). Select the pack in the Dashboard, then follow the Game setup section below to get Zandronum ready.

Events

Event Queue Effect
say(message) chat Viewer message in game chat
gravity(level: moon|heavy, seconds) timed Changes sv_gravity for seconds, then reverts
fast_monsters(seconds) timed Nightmare-speed monsters for seconds, then reverts
add_bot(count: 1-4) effects Adds hostile bots

The timed events take a seconds param (5-120, default 30) that the broadcaster sets per reward rather than the viewer, so "Moon Gravity (30s)" and "Moon Gravity (60s)" can be two rewards at different prices.

Queues

Three queues, showing both queue policies:

  • chat and effects use ready_after = "applied". Instant effects; a chat message never waits behind a 30-second gravity change.
  • timed uses ready_after = "done". The adapter applies the effect and sends applied right away, but holds done until the effect elapses and reverts. The bridge serializes the queue on that, so timed effects run one at a time with zero overlap bookkeeping in the adapter.

State and config

Pack state (adapter to platform):

  • effect_count - lifetime chaos tally. persist = true, so it survives adapter restarts via the hello_ack replay.
  • active_effect - the currently-running timed effect, "" when none.
  • last_effect - {name, seq, by?} written per applied effect. seq distinguishes back-to-back identical effects, by is the redeeming viewer when known. Drives the overlay toast.
  • deaths - session death count, parsed from the obituary lines the server pushes to RCON clients. It's a heuristic that matches stock English obituaries; exact counts would need an ACS hook.

Pack config (broadcaster to adapter, delivered as latched config frames): max_bots caps bots per redeem, chat_prefix gets prepended to say messages. The adapter's defaults match the schema defaults and apply until the first frame lands.

Game setup

The pack doesn't ship the game. Install separately:

  1. Zandronum from https://zandronum.com/download (client and zandronum-server). Not bundled on purpose: parts of its codebase carry legacy non-commercial license terms, so we point at upstream instead of redistributing.
  2. Freedoom from https://freedoom.github.io/ (free, BSD-licensed game data), or a commercial DOOM2.WAD you own. Neither is ever distributed here.

Launch from the Dashboard

The pack declares two [[adapter.path]] entries — the Zandronum executable and an IWAD — so the Dashboard's Launcher panel shows a Browse field for each. Pick zandronum.exe and freedoom2.wad, Save, then hit Launch game: the adapter hosts the Zandronum server itself (-host, RCON enabled) and connects to it, all from one button. The paths are stored in <pack>/.local/launcher.toml on this machine only, never in the hashed manifest.

No RCON password to configure: since the adapter hosts the server and connects to it, the password is just a loopback secret between two halves of one process, so the adapter generates an ephemeral one each run. DOOM_RCON_PASSWORD only matters for a manual run against a server you host yourself (below).

Running the adapter manually

Without the Launcher, start a server yourself and point the adapter at it:

zandronum -host -iwad freedoom2.wad \
  +sv_rconpassword 'pick-a-password' +sv_cheats 1

Then run the adapter (leave DOOM_ZANDRONUM unset so it connects to the already-running server instead of hosting its own):

MOBRULE_ADAPTER_TOKEN=<token> DOOM_RCON_PASSWORD='pick-a-password' python3 adapter.py

The adapter connects to the bridge at $BRIDGE_HOST:$BRIDGE_PORT (defaults 127.0.0.1:7777) and to Zandronum RCON at $DOOM_RCON_HOST:$DOOM_RCON_PORT (defaults 127.0.0.1:10666).

If the game server is down, invocations come back as failed frames (the viewer-facing refund path) and the adapter reconnects lazily on the next redeem. The game crashing must never kill the pack.

The wire

Zandronum RCON is protocol v4 (see src/sv_rcon.{h,cpp} in the Zandronum source): UDP datagrams Huffman-coded with the fixed Skulltag tree. The adapter always sends unencoded packets using the protocol's 0xFF escape and only decodes server replies, so it never needs an encoder. Handshake: CLRC_BEGINCONNECTIONSVRC_SALTCLRC_PASSWORD with md5(salt + password)SVRC_LOGGEDIN, with a CLRC_PONG keepalive every 5 seconds.

Viewer text routed into say gets sanitized before it touches a console command: the Zandronum console treats ; as a command separator and " as a string delimiter, so both (plus \ and non-printable ASCII) are stripped to prevent console-command injection.

Overlay

overlay/ ships two Doom-themed widgets (Vite + Solid lib, same shape as pack-hello-world/overlay/, dist/index.js committed):

  • effectToast - watches last_effect; flashes "GRAVITY SHIFTED - doomguy_2026" for 4 seconds per applied effect, then renders nothing. The initial snapshot only arms the watcher, so no stale toast on an OBS refresh. say deliberately doesn't toast since it's already visible in game chat.
  • deathCounter - compact 💀 n badge fed by deaths.

The game is the content, so the widgets stay out of the way: single-line plates, translucent backgrounds, nothing fullscreen. Suggested placement is toast top-center, death badge bottom-left. The dev harness renders both over a fake game frame so the footprint is easy to judge:

cd overlay
npm install     # first time only
npm run dev     # mock data plane streams scripted diffs for ~20s
npm run build   # rebuild the committed dist/index.js

Fake crowd

Want to feel the chaos without a live channel? The mobrule CLI can play a fake audience against your local bridge:

mobrule dev crowd --scenario crowd.toml

crowd.toml ships with the pack: a chat-heavy mix (lots of say, the odd bot drop and gravity flip) plus canned viewer messages. Tweak the weights or add your own lines. Add --seed 42 --record run.jsonl for a replayable run.

Status

The RCON client is verified against a protocol-faithful mock server (handshake, auth, command framing, Huffman decode round-trip), and has not yet been run against a real Zandronum binary. Do one manual end-to-end pass before demoing live.

Ideas / escalation path

In order, each step unlocks the next:

  1. Manual e2e run against real Zandronum (see Status). Gates everything below; ACS work can't be developed blind.
  2. Ship a mobrule.pk3 with ACS scripts, triggered via RCON puke <script>. Unlocks effects raw cvars can't do: spawn a specific monster at the player, item rain, screen effects.
  3. Chat-Plays via [input_vote]. The platform half is nearly free; the game half rides the pk3 from step 2. Mind the fidelity ceiling: RCON can't press buttons, so ACS approximates inputs with shoves and turns. That's "Twitch shoves Doom guy", which is why it's kept out of the tutorial; Chat-Plays' honest home is a pack that feeds real pad input.
  4. active_effect banner widget, a persistent "MOON GRAVITY ACTIVE" plate while a timed effect runs.

License

MIT

Not affiliated with or endorsed by id Software. DOOM is a trademark of id Software LLC. No game data or engine code is distributed with this pack.