Skip to content

Reveal

The core chaos loop is suspense: a viewer pays, the stream holds its breath while a reel spins, and then it lands on you. “Pick one of N, build tension, apply” recurs everywhere: a random live monster gets enraged, a random spawn point erupts, in co-op a random marine gets cursed.

Reveal is the platform primitive for that moment. The bridge does the roll in its trusted process, holds for a declared duration while a pack-agnostic overlay animates toward the result, then dispatches the outcome. You get an on-stream reveal without hand-writing the timed hold and result-plumbing in your Adapter, and because the roll is bridge-side, every paid outcome is auditable and defensible under a refund dispute.

Reveal has two sources, depending on where the candidates come from:

  • authored: the candidates are a wheel of your events, composed by the broadcaster. This is a Composite event.
  • state: the candidates are resolved at redeem time from live game state (e.g. the marines currently in the co-op lobby, the live spawn points). Declared on the event, described below.

Both share the same machinery and the same overlay.

When the target set only exists at dispatch time, declare a reveal block on the event pointing at a pack-state key:

[events.curse.reveal]
source = "coop_players" # pack-state key to roll over
options = "marines" # array path within that key's value
label = "name" # per-element field used as the reel label
filter = "non-null" # drop empty slots; roll only over live marines
chosen_param = "target" # param to stamp the rolled index into
revealMs = 2500

At redeem the dispatcher:

  1. reads source from its live state store and resolves the options array path;
  2. applies filter (non-null drops empty/absent entries, so a half-full lobby never rolls an empty slot);
  3. rolls uniform over the survivors;
  4. holds revealMs, labelling the reel from each element’s label field;
  5. re-dispatches the same event with chosen_param stamped with the rolled 0-based index.

The contrast with authored: a composite dispatches a different (member) event; a state reveal dispatches the same event, having filled in one target parameter. Your Adapter receives curse with target already chosen and just applies it.

source must reference a declared state key, filter must be non-null, and a reveal block is rejected on a kind = "composite" event (composites own their authored reveal). All of source/options/label/filter/chosen_param are part of the manifest hash.

Whichever source rolled, the dispatcher writes one self-describing, read-only pack-state key the overlay renders:

{
"invocationId": 412,
"event": "curse",
"options": [{ "id": "0", "label": "RIPNTEAR" }, { "id": "1", "label": "DOOMGUY" }],
"chosenIndex": 1,
"revealMs": 2500,
"status": "revealing"
}

Because the key carries its own option labels and chosenIndex, one pack-agnostic overlay renders every reveal: the bridge learns no Pack semantics. options[].label is optional: the state source fills it (the marine name); the authored source omits it and the overlay falls back to the id. status moves "revealing""revealed". The overlay never writes back; it is a pure renderer (see Overlays and the read-only data plane).