Skip to content

Chat-Plays

Channel-point Rewards are one viewer paying for one effect. Chat-Plays flips that: your whole chat drives the game together, for free. Viewers type a button name (up, a, start, …), the bridge tallies the votes over a window, and the winning button is pressed. No redemption, no cost, just volume. High-spam is the point. Reach for it when the fun is collective input rather than individual purchases. It works with any chat source.

Chat-Plays is strictly opt-in via the [input_vote] table in pack.toml. A Pack that omits it has no Chat-Plays at all: no state keys, no Dashboard control, no input delivery.

[input_vote]
window_ms = 500 # vote-tally window
press_duration_ms = 250 # momentary-press length
[input_vote.vocabulary]
a = 0
b = 1
start = 6
up = 8
down = 9
left = 10
right = 11
  • vocabulary (required, non-empty) maps a chat token to a pad bit index (0..=31). The token is what a viewer types; the bit index is the position set in the pad mask when that token wins. What a bit means physically is your Adapter’s business: the platform never learns it. Each token must be a lowercase identifier ([a-z][a-z0-9_]*); duplicate bit indices are allowed (two tokens can map to the same physical button).
  • window_ms (> 0): votes arriving within one window are tallied together; at window close the winner is selected and pressed. A shorter window feels more responsive to chat but has fewer votes to aggregate, so a handful of fast typers can decide it; a longer window is more democratic but laggier. 500 ms is a good starting point: short enough to feel live, long enough to gather a real crowd of votes.
  • press_duration_ms (> 0): a winning vote is a momentary press. The winning bit is latched into the pad mask, then auto-released to 0 after this many milliseconds, so a tap never becomes a permanent hold. Choose a duration long enough for your game’s per-frame input poll to register (250 ms ≈ 15 frames at 60 fps).
  1. A viewer types a chat message. The whole trimmed, case-folded text must equal a vocabulary token: "UP" counts as up; "go up" counts as nothing. Every matching line counts; there is no per-viewer dedup (spam control is the broadcaster’s lever: slow mode, mods).
  2. The cloud relays chat lines pack-blind: it never sees your vocabulary and never tallies. Matching and tallying happen on the bridge, generically.
  3. At window close, one button is selected (see modes below) and its bit is delivered to the Adapter.

Votes are only counted while Chat-Plays is active (chat_plays_active, below).

The broadcaster picks the selection mode via the platform chat_plays_mode setting (not Pack data; your Pack declares vocabulary and timing only):

  • democracy (default): the single most-voted button wins; ties break randomly among the top-voted tokens.
  • anarchy: one random cast vote wins, weighted by vote count.

Both modes press exactly one button per window.

The effective on/off, chat_plays_active, has two sources, combined by the platform:

  • chat_plays_enabled: a broadcaster toggle (manual on/off in the Dashboard).
  • A viewer Reward that opens a timed window. Declare an Event whose params carry chat_plays_window_ms and bind it to a Reward; redeeming it opens Chat-Plays for that long, independent of the broadcaster toggle:
[events.chat_plays_window]
summary = "Open a timed Chat-Plays window."
title = "Chat Plays: open a window"
queue = "default"
[events.chat_plays_window.params_schema]
type = "object"
required = ["chat_plays_window_ms"]
additionalProperties = false
[events.chat_plays_window.params_schema.properties.chat_plays_window_ms]
type = "integer"
minimum = 1000
maximum = 600000
description = "How long (ms) the Chat-Plays window stays open."

chat_plays_active = chat_plays_enabled OR window-open.

The bridge delivers two latched values over the same config frame channel as Pack Config (replayed on every Adapter connect, pushed on change):

  • chat_plays_active (boolean): whether Chat-Plays is currently live. Use it to gate/lock out local pad input if desired.
  • chat_plays_pad_mask (integer): the current pad bit mask. OR it into your game’s joypad input each frame while active. Delivery is vote-cadence latched; there is no per-frame push, so your game loop samples the latched mask.