pack.toml reference
This page is the canonical reference for pack.toml. For a guided tour, see
the Quickstart.
Top-level keys
Section titled “Top-level keys”| Key | Type | Required | Notes |
|---|---|---|---|
manifest | int | yes | Manifest schema version. Currently 1. |
id | string | yes | Reverse-DNS Pack ID, e.g. tv.mobrule.hello-world. |
name | string | yes | Human-readable Pack name. |
version | string | yes | SemVer-style version. |
mpp | string | yes | MPP version requirement (SemVer range). |
authors | array | no | List of author names / handles. |
homepage | string | no | URL shown as the Pack’s homepage link. |
description | string | no | Short human description of the Pack. |
license | string | no | SPDX license identifier, e.g. "MIT". |
The Pack’s manifest_hash is a deterministic SHA-256 over the canonicalised
Manifest, formatted as sha256:<64-hex>. Equal content in, equal hash out,
across Linux and Windows. Cosmetic edits (whitespace, comments, key
reordering) do not change the hash; semantic edits (adding or renaming Events,
changing a schema body, changing version) do. mobrule pack print-hash <pack-dir> prints exactly sha256:<64-hex> on stdout; add --json to get a
single-line {pack_id, version, manifest_hash} envelope instead.
[adapter]
Section titled “[adapter]”Tells the bridge how to launch the Adapter process.
[adapter]command = "python3"args = ["-u", "adapter.py"]restart = "on_crash"| Key | Type | Required | Notes |
|---|---|---|---|
command | string | yes | Executable to spawn. May use ${paths.<key>} (see [[adapter.path]] below). |
args | array of string | no | Arguments, in order. May use ${paths.<key>}. |
env | table of string | no | Extra environment variables for the Adapter process. Values may use ${paths.<key>}. Don’t put secrets here: manifest values are hashed and visible. |
working_dir | string | no | Working directory for the process. Defaults to the pack directory, so a relative command (a script beside pack.toml) resolves. |
restart | string | no | "on_crash" (default) or "never". Use "never" when the Adapter supervises another process itself (say, it hosts the game server) and an automatic restart would collide with it. |
[[adapter.path]]
Section titled “[[adapter.path]]”Optional. Packs that drive an external program (typically your game) declare
the machine-local paths they need here, e.g. the game executable and its data
file.
Each declaration gets a key; the bridge substitutes it into command,
args, and env as ${paths.<key>} when it launches the Adapter.
[adapter]command = "python3"args = ["-u", "adapter.py"]
[adapter.env]GAME_SERVER = "${paths.game}"GAME_DATA = "${paths.data}"
[[adapter.path]]key = "game"label = "Game executable"extensions = ["exe"]default = "game/game.exe"
[[adapter.path]]key = "data"label = "Game data file"extensions = ["wad"]| Key | Type | Required | Notes |
|---|---|---|---|
key | string | yes | Identifier ([a-z0-9_]). Substituted as ${paths.<key>}. Must be unique within the [adapter]. |
label | string | yes | Human label shown in the operator’s Launcher panel file picker. |
extensions | array of string | no | File-dialog filter hints (extensions without the dot), e.g. ["exe"]. |
default | string | no | A path relative to the pack directory, used when the operator hasn’t set this key on their machine. Lets a bundle ship the game binary/assets beside the pack and run with zero setup. |
The operator sets these paths once per machine in the
Launcher panel; those
values are stored per-machine and are not part of the pack. When a key has no
machine value, the bridge falls back to default resolved against the pack
directory. A machine value, when set, always wins over default. default
must be relative: machine-specific absolute paths belong in the operator’s
per-machine config, not the shipped pack, so an absolute default is rejected
when the pack loads.
[queues.<name>]
Section titled “[queues.<name>]”Every Pack must declare at least one queue. Within a queue, the bridge dispatches Invocations strictly one at a time; different queues are independent, so an Adapter can work N queues concurrently.
[queues.default]ready_after = "applied"| Key | Type | Required | Notes |
|---|---|---|---|
ready_after | string | no | "applied" (default) or "done": when the queue is free to dispatch the next Invocation. See Dispatch semantics. |
Use ready_after = "done" for queues whose effects must fully complete
in-game before the next Invocation begins.
[events.<name>]
Section titled “[events.<name>]”Each Event the Pack exposes. Required sub-keys:
summary: short human description.queue: the declared[queues.<name>]this Event dispatches through.params_schema: a JSON Schema object describing the Invocation params.
Each property in params_schema.properties may be marked
"x-mobrule-viewer-override" = true to let chat-time inputs override the
default value at Cue-fire time.
Optional sub-keys:
| Key | Type | Notes |
|---|---|---|
title | string | Human-friendly display name shown instead of the raw event name. |
kind | string | "composite" marks a broadcaster-configured weighted roll over member Events. See Composite Events. |
members | array | Whitelist of event names a composite Event’s faces may choose from. See Composite Events. |
reveal | table | Resolves a candidate option set from a live pack-state key at dispatch time. See Reveal. |
viewer_input | table | Splits the viewer’s redemption text across several params. See Defining Events. |
[state.<key>]
Section titled “[state.<key>]”Optional. Declares a Pack-state key the Adapter (or platform) can write at runtime, e.g. a counter an overlay widget subscribes to.
[state.play_count]writer = "adapter"[state.play_count.params_schema]type = "integer"minimum = 0| Key | Type | Required | Notes |
|---|---|---|---|
writer | string | yes | Who may write the key: "adapter", "dispatcher", or "platform". "adapter" lets your Adapter send state_write frames for it; the other two let the dispatcher or platform update it. |
persist | bool | no | true to store the last value durably and replay it to the Adapter on reconnect. Default false. |
params_schema | table | no | JSON Schema the written value must validate against. |
[data.<name>]
Section titled “[data.<name>]”Optional. Declares a named option dataset a param can source its choices from
(via x-mobrule-source), instead of inlining the list. One of two backends:
path (a pack-shipped file, served over /pack-assets/*) or options (an
inline list expanded onto referencing params at load). Either way the choices are
content-hashed into manifest_hash. See
Option datasets.
[data.items]path = "data/items.json"
[data.target_side]options = [ { value = 0, label = "Player", color = "#86efac" }, { value = 1, label = "Opponent", color = "#ff7a7a" },]| Key | Type | Required | Notes |
|---|---|---|---|
path | string | one of path/options | Pack-relative path to the JSON file (must stay inside the pack). |
options | array | one of path/options | Inline choice list ({ value, label, color? }); expanded into x-mobrule-options on referencing params. |
See Option datasets.
[[pack.config.section]]
Section titled “[[pack.config.section]]”Optional. Broadcaster-configurable settings are declared as an array of
[[pack.config.section]] tables, each holding its own fields. The
Pack Config page covers the schema in full.
[overlay] / [overlay.data]
Section titled “[overlay] / [overlay.data]”Optional. Declares your Pack’s stream overlay: where its built JS bundle lives
(dir, with a fallback resolver if omitted) and which [state.<key>] entries
it subscribes to (data.reads). See Overlays for
the full contract.
[input_vote]
Section titled “[input_vote]”Optional. Present ⇒ the Pack opts in to Chat-Plays: a chat-vote vocabulary (token → pad bit) plus a tally window and press duration. Absent ⇒ the Pack has no Chat-Plays at all. See Chat-Plays.
[[default_redeem]]
Section titled “[[default_redeem]]”Optional. An array of { cue, event } pairs (plus an optional description)
that materialise a Reward per entry when the Pack is installed, so a Pack
works out of the box with zero Dashboard setup. See the
Doom tutorial’s polish step for a worked example.
Full example
Section titled “Full example”manifest = 1id = "tv.mobrule.hello-world"name = "Hello World"version = "0.1.0"mpp = "2"authors = ["mobrule"]
[adapter]command = "python3"args = ["-u", "adapter.py"]restart = "on_crash"
[queues.default]ready_after = "applied"
[state.play_count]writer = "adapter"[state.play_count.params_schema]type = "integer"minimum = 0
[events.play_sound]summary = "Log a play-sound notification."queue = "default"[events.play_sound.params_schema]type = "object"additionalProperties = falserequired = ["sound_id"][events.play_sound.params_schema.properties.sound_id]type = "string"maxLength = 64"x-mobrule-viewer-override" = trueSee also
Section titled “See also”- Quickstart: the 15-minute walkthrough.
- Defining Events: params_schema patterns.
- MPP protocol: what the bridge does with these definitions.
- Overlays: the
[overlay]/[overlay.data]tables in full. - Pack state & the data plane: the
[state.<key>]tables in full. - Reveal: the
[events.<name>.reveal]block in full. - Composite Events:
kind = "composite"andmembersin full.