- TypeScript 56.8%
- Python 40.1%
- HTML 3.1%
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 |
||
|---|---|---|
| .forgejo/workflows | ||
| docs/img | ||
| overlay | ||
| .gitignore | ||
| adapter.py | ||
| CLAUDE.md | ||
| crowd.toml | ||
| LICENSE | ||
| pack.toml | ||
| README.md | ||
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:
chatandeffectsuseready_after = "applied". Instant effects; a chat message never waits behind a 30-second gravity change.timedusesready_after = "done". The adapter applies the effect and sendsappliedright away, but holdsdoneuntil 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 thehello_ackreplay.active_effect- the currently-running timed effect,""when none.last_effect-{name, seq, by?}written per applied effect.seqdistinguishes back-to-back identical effects,byis 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:
- 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. - Freedoom from https://freedoom.github.io/ (free, BSD-licensed game data), or a commercial
DOOM2.WADyou 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_BEGINCONNECTION → SVRC_SALT → CLRC_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- watcheslast_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.saydeliberately doesn't toast since it's already visible in game chat.deathCounter- compact 💀 n badge fed bydeaths.
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:
- Manual e2e run against real Zandronum (see Status). Gates everything below; ACS work can't be developed blind.
- Ship a
mobrule.pk3with ACS scripts, triggered via RCONpuke <script>. Unlocks effects raw cvars can't do: spawn a specific monster at the player, item rain, screen effects. - 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. active_effectbanner widget, a persistent "MOON GRAVITY ACTIVE" plate while a timed effect runs.
License
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.