Realtime channels
Use Plot.realtime.channels for
transient events and high-frequency values: effects, pointer movement, aim, and
position. Use
Plot.gameState for synchronized shared
state and Plot.playerData for
synchronized session data owned by one player.
Create, receive, and send
Choose a descriptive name and one payload type:
This is a transient effect: it is not reconstructed for a late joiner, and a browser crash near delivery can replay it within the retained window. Keep important progress in synchronized state.
channel<T>() returns a typed
Channel<T>. Its generic is an unchecked
compile-time assertion for autocomplete; it does not validate your
game-specific shape. If you omit the type, sends accept an unknown value and
received payloads use Plot's JsonValue type. Every send is checked against
Plot's supported JSON value model, and a receiver should still validate the
fields, discriminants, IDs, and ranges it uses before applying the payload.
broadcast() sends to every player
who can receive game messages, including the sender. sendTo()
targets one stable PlayerId. A received
ChannelMessage contains the trusted
fromPlayerId and a deeply
immutable payload. “Trusted”
here means Plot supplies the authenticated sender's session-scoped ID; it does
not make a player-authored score, answer, timestamp, or other gameplay claim
authoritative. Plot games normally use those claims under the cooperative
gameplay model.
subscribe() returns an idempotent
Unsubscribe function. Register reliable
listeners during startup, before yielding to unrelated asynchronous work.
Choose a delivery mode
ChannelOptions.delivery affects
outgoing messages from that channel view. It does not filter subscriptions.
ChannelDelivery.Reliableis the default for transient game events and control messages. Plot preserves accepted per-recipient order and retains bounded in-memory history for five minutes through temporary interruptions. A surviving page suppresses replay attempts it has already handled. A browser crash after listener execution but before acknowledgement can still redeliver a message, so make duplicate-sensitive handlers idempotent with an action ID.ChannelDelivery.Latestsuits pointer movement, aim, position, and other continuously replaced samples. Plot may replace an older undelivered sample with a newer one for the same sender, channel, and route. Samples may be dropped and are not replayed by recovery. A reloaded game starts a fresh sample stream. Game code does not add transport sequence numbers or reconnect identifiers.
For replaceable position data, send the newest complete sample:
Broadcast and targeted sends are separate routes. If several independently changing values must coexist, include their current values in one payload or give each stream a distinct name.
Both broadcast() and
sendTo() are synchronous, return
void, and deliberately expose no delivery receipt. They validate arguments
before returning. Until Plot installs the complete ready state, they throw
PlotNotReadyError and do not start readiness. Sends are already available
inside pre-registered initial snapshot callbacks, which run just before
ready() resolves. After a valid send returns, Plot owns submission
and recovery; game code does not await it or implement a retry loop. A return
without an error is not evidence that another game copy already ran its
listener. If a send detects that Plot cannot safely retain a reliable message,
it throws a terminal internal-error synchronously. A hold deadline can instead
expire after the send returned. Either failure stops this SDK instance; later
valid party and state reads, state writes, and channel sends report the retained
error. Reload or exit instead of retrying on the same page. Once this player's
participation or the session has ended, a later valid send likewise throws the
retained error instead of accepting new traffic.
Reliable sends invoked by one game copy retain invocation order. On receipt, Plot also waits until any synchronized-state commit the sender had already observed is locally installed before invoking the channel listener. Clock values do not define this order.
Reloads, disconnects, and new players
Plot retains admitted reliable messages for five minutes, subject to finite message and byte budgets. It applies backpressure before accepting a send that would require discarding an unexpired event. A returning participation receives one complete current world before retained events are replayed, even when the whole page was reloaded. Because a browser crash can happen after a listener runs but before receipt is recorded, make duplicate-sensitive actions idempotent when replay matters. A stable event ID can suppress repeated effects, but it does not make a later asynchronous write part of channel delivery. Keep player choices and shared results in synchronized state when they must survive refresh.
Messages expire five minutes after admission. Completed planned service updates
save unexpired history, but an unexpected service crash may lose it. The
Plot.connection.recovery diagnostics expose known
missing ranges through replayGap before replaying what remains. Recovery reports
connected only after all current state is installed. A checkpoint-restored recovery
can also lose messages admitted after the last snapshot without a saved gap
position, so it signals possible history loss even when replayGap is null.
The snapshot is already current:
replayed events must not apply a state effect for a second time. Latest messages
are never replayed.
Checkpoint recovery discards outgoing messages queued against the previous world. Ordinary reconnects and planned updates preserve queued reliable work.
If Plot cannot submit retained reliable traffic before its five-minute local hold
deadline, it ends this SDK runtime rather than silently dropping a message. That
deadline can expire after the original send returned; later valid party and
state reads, state writes, and channel sends then report the retained
internal-error.
A player whose state is
PartyPlayerState.Disconnected remains
a valid sendTo() target. Plot retains
reliable targeted traffic within the five-minute retention window; latest traffic
may still be replaced or dropped. A player who has left is no longer a valid
target.
A Joining player cannot receive game messages yet. Wait for their
onPlayerChanged() transition to Connected before targeting them. You do not
need a separate ready message or Player Data flag for this SDK-level check.
Register reliable listeners during startup, before ready(). A channel is not
a mailbox that a feature can inspect later; if a message has no listener when
Plot delivers it, the feature cannot read it afterward.
latest samples may be coalesced or
dropped while disconnected and are never a
reload-recovery mechanism. If later code or a later joiner needs a value, store
the synchronized shared result in
Plot.gameState, or in
Plot.playerData when it genuinely
belongs to one player.
A broadcast is not retroactive. A new player starts receiving broadcasts after
their onPlayerChanged() transition to Connected. onPlayerAdded() happens
earlier, when the stable party slot and initial Player Data appear in Joining
state. Messages accepted after the transition are ordered behind that party
change, so the receiver never handles one against an older party snapshot.
Channel identity and naming
The name passed to channel<T>()
is the subscription identity. Selecting the same name and delivery mode again
returns the same channel object. Selecting that name with a conflicting
delivery throws TypeError
instead of creating two mailboxes that could silently disagree.
Names must be strings from 1 through 128 characters whose JSON encoding fits 130 UTF-8 bytes. Names made from letters, digits, dots, underscores, and hyphens fit at the full 128-character length. Plot does not assign meaning to them. Prefer lowercase dotted names grouped by game concept:
game.phasepuzzle.guessplayer.positiondrawing.strokechapter.study.clock.answer
Payloads, immutability, and validation
A channel payload is a JsonValue: a
JsonPrimitive (null, a boolean, a finite
number, or a string), a dense ordinary
JsonArray, or a plain
JsonObject whose own properties are
enumerable, string-keyed data properties. Received values use the deeply
readonly ImmutableJson<T> shape.
undefined, Date, bigint, Map, Set, typed arrays, functions, symbols,
class instances, sparse or decorated arrays, non-finite numbers, accessors,
objects with symbol-keyed or non-enumerable properties, and cyclic values are
rejected. Convert a date to an ISO string and a big integer to a decimal string
before sending it.
Payload validation is bounded to 32 nested levels, 10,000 value nodes, and 262,144 combined key and string characters. Keep high-frequency payloads far smaller for mobile bandwidth, allocation, and battery use.
Latest payloads also have a negotiated UTF-8 JSON byte limit. Read
Plot.realtime.latest.get().maxPayloadBytes after readiness, or subscribe to
Plot.realtime.latest before readiness to receive its initial status and changes.
The current production limit is 512 bytes. An oversized latest send throws
TypeError before submission. When available is false, latest samples are
dropped; synchronized state and reliable messages can remain connected. Use this
status to pause cosmetic high-frequency sending without pausing the whole game.
Reliable listener completion acknowledges delivery, including a listener that throws. It does not wait for an asynchronous game-state write. For a choice whose effect must survive a refresh, represent it in synchronized state.
Transport freshness is not the same as gameplay relevance. A pointer sample can still be well-formed and current for its sender but belong to a round that your game has already finished. Include a round, scene, puzzle, or action identity when game rules need that check, and reject payloads that do not match current synchronized state. Plot handles delivery order and ignores messages from a superseded game page.
channel<T>() throws TypeError
for an invalid name, options object, or delivery value.
broadcast() throws TypeError for
an invalid payload. sendTo() also
throws for an invalid player ID, an unknown or still-joining player, or a player
who has left.
subscribe() throws when its listener
is not a function. A valid send before the complete ready state is installed
throws PlotNotReadyError. Once this
player's participation or the session has ended—or the SDK has entered a
terminal state after a fatal failure—a later valid send throws the retained
PlotOperationError. A reliable send
also throws that error if it detects that Plot cannot safely retain the message.
For exact types, see RealtimeApi,
ChannelsApi,
ChannelOptions,
Channel,
ChannelMessage, and the
ChannelDelivery type.