Skip to content

Pack Config

Pack Config lets a broadcaster tune your Pack without touching its code. Limits, labels, default toggles: anything the streamer should be able to set once and have stick. You declare the settings in pack.toml, the platform renders an editor in the Dashboard, stores the values per broadcaster, and delivers them to your Adapter.

Reach for Pack Config when a value belongs to the broadcaster, not to each redeem. A bot-spawn cap, a chat prefix, an asset path: the streamer sets these once and every Invocation inherits them.

These are two different inputs, and picking the wrong one frustrates everyone.

  • Config is broadcaster-wide. One value per broadcaster, set in the Dashboard, applies to every Invocation. Declare it under [[pack.config.section]]. Read it in the Adapter with whatever you store the latched config frames into.
  • Params are per-Invocation. A fresh input on each redeem, validated against the event’s params_schema, carried in the invocation frame.

Rule of thumb: if a viewer supplies it at redeem time, it is a param. If the streamer sets it in their dashboard and forgets it, it is config.

Config is an ordered array of titled sections. Each section owns an ordered array of fields.

[[pack.config.section]]
key = "effects"
title = "Effects"
subtitle = "How hard viewers can grief"
[[pack.config.section.field]]
key = "max_bots"
type = "integer"
minimum = 1
maximum = 8
default = 4
description = "Cap on bots a single redeem can spawn."
[[pack.config.section]]
key = "chat"
title = "Chat"
[[pack.config.section.field]]
key = "chat_prefix"
type = "string"
maxLength = 16
default = "[chat]"
description = "Prefix prepended to viewer messages in game chat."

The Dashboard renders one panel per section, with the section title and subtitle as headings, and one editor widget per field.

A [[pack.config.section]] table:

KeyRequiredMeaning
keyyesSection identifier, matches ^[a-z][a-z0-9_]*$. Stable handle for the section.
titlenoPanel heading shown in the Dashboard.
subtitlenoSub-heading shown under the title.

A [[pack.config.section.field]] table is an inline JSON Schema once you strip its two meta keys:

KeyRequiredMeaning
keyyesField identifier, matches ^[a-z][a-z0-9_]*$. This is the storage key the value is delivered under.
requirednoIf true, the broadcaster must set a value before they can save.

Every other key in the field table is JSON Schema (type, minimum, maxLength, enum, default, description, x-mobrule-* annotations). Set a default so the field arrives with a sensible value before the broadcaster touches it.

Schemas are inline only. There is no external schema = "path" form.

Section order and field order are significant. They ride the canonical manifest_hash, so reordering sections or fields re-pins the hash (the same way the order of events does). Declare them in the order you want them to appear, and treat that order as part of your Pack’s identity.

Field keys are flat across every section, because the field key is the storage key. The same key in two sections is a DuplicateConfigFieldKey error at load time. max_bots in the effects section and max_bots in another section will not load. Keep field keys unique Pack-wide.

The bridge latches config values and replays them to your Adapter as config frames right after hello_ack, then pushes a fresh frame on every Dashboard edit. Each frame carries one key and its value.

Store the frames as they arrive and read from your store wherever you need the setting. The Doom Pack keeps a dict seeded with the same defaults as the schema, applies each frame on receipt, and reads with a small getter:

_config = {"max_bots": 4, "chat_prefix": "[chat]"}
def config_set(key, value):
if key in _config:
_config[key] = value
# ... in the MPP read loop:
elif msg.get("type") == "config":
config_set(msg.get("key"), msg.get("value"))

Then a handler reads the latched value:

count = min(int(params["count"]), _config["max_bots"])

Seed your Adapter’s defaults to match the schema defaults. The defaults cover the window before the first frame arrives, and the broadcaster’s saved values cover everything after.

  • Manifest reference: [[pack.config.section]] lives alongside [events.*].
  • Events: per-Invocation params, the other half of the config/params split.
  • UI hints: x-mobrule-* annotations that affect how the config editor renders fields.