Skip to content

Stage 5: pack config

Some knobs belong to the broadcaster, not the viewer: how long effects last, how many bots one redeem may spawn, how chat messages are branded. Pack config is the direction-reversed mirror of pack state — the broadcaster edits a value in the Dashboard, and the bridge delivers it to the adapter.

Config is declared as titled sections of fields. Each field is an inline JSON Schema, same shape as an event’s params_schema:

[[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
[[pack.config.section]]
key = "chat"
title = "Chat"
[[pack.config.section.field]]
key = "chat_prefix"
type = "string"
maxLength = 16
default = "[chat]"

Sections are presentation (Dashboard panels with headings); field keys live in one flat namespace across all sections. Values are schema-validated before they ever reach the adapter.

Delivery is latched: right after hello_ack the bridge replays one config frame per key with a known value, and pushes a fresh frame whenever the broadcaster edits one. The adapter keeps a dict of defaults (matching the schema defaults) and overwrites on every frame:

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

Apply frames idempotently — a replay after reconnect delivers values you may already have.

Each config key lands in one handler:

  • max_bots — a broadcaster-side cap that clamps whatever the viewer asked for:

    count = min(int(params["count"]), _config["max_bots"])
  • chat_prefix — branding on every viewer message (run it through the same sanitizer as the message itself — it’s still text headed for a console command):

    prefix = sanitize_chat(_config["chat_prefix"])
    if prefix:
    message = f"{prefix} {message}"

Note the division of labor across the three broadcaster-facing surfaces:

  • Event params_schema — viewer-facing limits (count ≤ 4), rejected before a redeem is paid for.
  • Per-reward params — values that are part of a reward’s identity, like the timed events’ seconds (set in the Cue→Event mapping; two rewards, two durations, two prices).
  • Pack config — Pack-wide policy that applies across all rewards, like max_bots and chat_prefix. That’s the test: if you’d ever want two rewards to differ on it, it’s a param; if it should hold everywhere, it’s config.

Next: put the state on stream.