Pack state & the data plane
Why you’d want this
Section titled “Why you’d want this”Your overlay needs live game data (the score, who’s still alive, the death count) but a browser source can’t read your game. Pack state is the pipe between them: your Adapter publishes named values, the bridge validates and stores them, and your overlay subscribes. Declare a key whenever you want something happening in-game to show up on stream.
Each named slot of state is declared in pack.toml, written by exactly one
authorised source, validated against an inline JSON Schema, and served to the
overlay over the bridge’s data plane, the bearer-gated WS /rpc/subscribe
WebSocket. The bridge, not the cloud, is the source of truth for pack-specific
state, and the data plane is read-only to the browser: the overlay is a pure
renderer and never writes.
Declaring a key
Section titled “Declaring a key”[state.play_count]writer = "adapter"[state.play_count.params_schema]type = "integer"minimum = 0writer(required): one ofadapter,dispatcher,platform. The bridge enforces it: a write arriving from any other source is rejected.params_schema(optional): inline JSON Schema; every write is validated against it the same way Invocation params are. The external-fileschema = "path"form is rejected at load.persist(optional, defaultfalse): see Persistent keys.
To make the overlay receive a key, also list it under [overlay.data] reads:
[overlay.data]reads = ["play_count"]The three writers
Section titled “The three writers”| Writer | Who writes | Typical use |
|---|---|---|
adapter | The game-side Adapter, via the MPP state_write frame | Live game telemetry (HP, score, run progress) |
dispatcher | The bridge’s own dispatch core | Pending-command queue, composite roll results |
platform | The bridge, re-published from its cloud subscriptions | Session info, incoming cue events |
An Adapter writes a key by sending a state_write frame (see the
MPP protocol):
{"mpp": 2, "type": "state_write", "key": "play_count", "value": 3}There is no acknowledgement: an accepted write is silent, and the bridge fans the new value out to overlay subscribers. A write to an undeclared key, from the wrong writer, or failing the schema is a protocol error.
Naming convention
Section titled “Naming convention”Pack-state JSON keys are camelCase end-to-end: one convention across the Adapter, the bridge, the JSON Schemas, and the overlay, so no case-translation seam exists. (Values carried verbatim from an external source, e.g. a raw platform payload nested inside a value, keep their upstream casing; only the pack-state envelope is normalised.)
Persistent keys (persist = true)
Section titled “Persistent keys (persist = true)”By default a key lives only in bridge memory: a fresh Adapter process starts
from scratch. Flag a key persist = true to make the bridge store its latest
value durably (across both Adapter and bridge restarts) and replay it to
the writer on reconnect (in the hello_ack frame’s persisted[] array).
[state.effect_count]writer = "adapter"persist = true[state.effect_count.params_schema]type = "integer"minimum = 0description = "Number of effects this Pack has applied, lifetime."Use persist for accumulated tallies that have no cumulative in-game
source (a lifetime effect count, milestone first-seen flags). Keys recomputed
every tick from live game data should stay non-persistent.
Persistence is a per-key choice, not an all-or-nothing setting: the Doom Pack
persists effect_count (a lifetime tally worth keeping across Adapter
restarts) but deliberately leaves its deaths counter non-persistent, since
that’s a per-session stat that should reset with the session.
Durability is best-effort across manifest changes (a stored value that fails
the current schema is dropped, not migrated) and eventually durable (an async
write, not commit-before-ack). Toggling persist changes the manifest hash
like any other contract change.
Run scoping
Section titled “Run scoping”A persist state_write may carry an opaque scope string identifying which
run the value belongs to (e.g. a save-file identifier). The bridge never
interprets it: it stores the tag, echoes it back on replay, and the Adapter
decides resume-vs-reset. This gives you per-run durability without the
platform learning anything game-specific.
{"mpp": 2, "type": "state_write", "key": "resets", "value": 3, "scope": "run:12345"}activeQueues: context-pausing Rewards
Section titled “activeQueues: context-pausing Rewards”activeQueues is a reserved, opt-in pack-state key: an array of queue
names currently accepting redemptions. If your game has contexts where some
Events make no sense (e.g. a cutscene or menu screen), have the Adapter write
the queues that are currently valid:
{"mpp": 2, "type": "state_write", "key": "activeQueues", "value": ["effects", "chat"]}You opt in by writing the key. The bridge recognises activeQueues even when
your pack.toml declares no [state.activeQueues] slot, so no declaration is
required. Its schema is platform-fixed (an array of strings, written by the
Adapter): the reserved definition always wins, so declaring a slot cannot
redefine the contract.
The platform then pauses (on the streaming platform’s side) every Reward
whose Event’s queue is not in activeQueues, so viewers cannot redeem an
effect that cannot currently apply. The pause is orthogonal to the
broadcaster’s enable/disable switch and flips automatically with game context.
A Pack that never writes activeQueues opts out: every Reward stays live at
all times. The platform reasons only about queues; your game’s notion of
“context” stays private to your Adapter.
How the overlay consumes state
Section titled “How the overlay consumes state”The platform’s overlay host loads your Pack’s overlay bundle, opens the data
plane with a scoped read-only token, subscribes to every key in
[overlay.data] reads, and delivers a snapshot on connect plus a push on
every change. Your overlay code only declares which keys it reads; token
handling and the WebSocket are the host’s job. See Overlays.
See also
Section titled “See also”- Manifest reference:
[state.<key>]syntax summary. - Overlays: rendering pack state on stream.
- MPP protocol: the
state_writeframe andpersisted[]replay. - Pack Config: the inbound mirror, broadcaster-written values delivered to the Adapter.