Lifecycle and recovery

Use Plot.onLifecycleChange() to pause during an interruption and rebuild from current state when Plot is connected. A connected callback runs only after Party, Game State, every player's Player Data, and synchronized time are ready to read together. Register it before Plot.ready() to handle startup as well as later recovery.

import Plot from '@plot.game/sdk'

const gameState = Plot.gameState.as<{ score?: number }>()

function renderCurrentGame() {
  const party = Plot.party.get()
  renderGame({
    party,
    game: gameState.get(),
    players: party.players.map((player) => ({
      playerId: player.playerId,
      data: Plot.playerData.get(player.playerId),
    })),
  })
}

Plot.onLifecycleChange((status) => {
  setPaused(status === 'disconnected')
  if (status !== 'connected') return

  clearTransientAnimations()
  renderCurrentGame()
})

// Lifecycle handles startup and recovery. Focused listeners handle live changes.
gameState.onChanged(renderCurrentGame)
Plot.playerData.onChanged(renderCurrentGame)
Plot.party.onPlayerAdded(renderCurrentGame)
Plot.party.onPlayerChanged(renderCurrentGame)

await Plot.ready()

The callback receives only 'connected' or 'disconnected'. Registering before readiness does not start Plot or emit an initial disconnected notification; its first callback is connected after the complete initial state is installed, before ready() resolves. Registering after startup immediately calls the listener with the established current status. The method returns an unsubscribe function that is safe to call more than once.

Rebuild the state your message handlers depend on synchronously in the connected callback. Plot does not await a returned promise before continuing with retained messages. A listener error is reported without stopping other listeners.

Lifecycle notifications do not describe ordinary data changes. Another player's arrival or departure, a state write, or a latest-channel outage does not itself notify this listener. Keep using focused onChanged() and party events for live changes. Complete per-domain subscribe() methods also remain available when a view needs that domain's initial, change, and recovery snapshots.

Recover before replaying events

During an interruption, Plot reports disconnected and keeps the last readable state, which may be stale. Recovery installs all current synchronized state, calls pre-registered per-domain recovery subscribers, then reports connected through pre-registered lifecycle listeners. A listener registered inside another callback still receives the current status immediately. Retained reliable messages follow these callbacks in accepted order, before live traffic resumes. Missed state changes become one current snapshot; Plot does not replay every intermediate state revision.

Replayed events are historical relative to that snapshot. If Game State already includes a solved puzzle, a replayed puzzle-solved event must not award its points again. Render facts from synchronized state and use channels for moments. Store player choices in Player Data and shared results in Game State.

Recovery in a surviving page is automatic and does not need another Plot.ready() call. A replacement iframe is a new page and calls ready() as part of its normal startup. Browser-hosted game logic still pauses while that host's browser is suspended; Plot restores data, not code that never ran.

Optional recovery diagnostics

Plot.connection.status and isConnected provide current status without a listener. Plot.connection.recovery is null before the first complete state installation. After installation it is an immutable { kind, replayGap } value, updated before the connected lifecycle callback and retained during a later disconnect.

kindMeaning
liveThe installed state came from an authority that has not restored this game from storage. Browser reconnects still restore current state.
planned-update-restoredA completed update save restored accepted state and unexpired reliable history.
checkpoint-restoredCrash recovery restored a saved state snapshot; progress may move backward and transient history may be missing.

For example, a game with an event-only activity log can discard an incomplete log after reconnecting:

const recovery = Plot.connection.recovery
if (recovery?.replayGap || recovery?.kind === 'checkpoint-restored') {
  clearEventOnlyHistory()
}

Ordinary data writes do not change the recovery kind. A newly reported replay gap updates the diagnostics without creating a lifecycle notification. Games that maintain event-only history can also check this value while processing channel messages. These diagnostics describe the installed recovery state; they are not a stream of gameplay changes.

Five-minute reliable replay

An activated player can temporarily disconnect, switch apps, or reload and recover current state plus unreceived reliable events retained for five minutes. Retention is measured from each event's admission. The buffer has finite message and byte limits. Plot applies backpressure before accepting traffic that would require dropping an unexpired event; unlimited message rates are not supported. Latest channels are lossy and never replayed.

After expiry, or when a crash snapshot identifies lost transient history, Plot.connection.recovery.replayGap describes the known missing delivery range:

{ afterDeliverySequence: 12, throughDeliverySequence: 19 }

This means deliveries 13 through 19 are unavailable. These numbers are diagnostic positions, not game action IDs. A gap does not undo the installed synchronized state. Rebuild event-only presentation from current state, and avoid showing a complete event history when Plot reported a gap.

Messages admitted after the last crash snapshot may leave no saved delivery position. A checkpoint-restored recovery therefore signals possible history loss even when replayGap is null.

Service updates and unexpected crashes

Normal accepted writes become authoritative immediately. Plot periodically saves a complete state snapshot for crash recovery; each write does not wait for a disk save. Before a planned service update, Plot saves all accepted state and unexpired reliable history, then restores the same game after restart.

An unexpected crash may restore an older state snapshot and lose transient channel events. A newly started game can be lost if it has not been snapshotted yet. Render the restored state rather than insisting that an old local score, revision, or phase must remain current. Plot permits checkpoint rollback only at an explicitly validated recovery boundary; a late ordinary response cannot overwrite newer state.

Checkpoint recovery also discards queued outgoing channel messages from before the crash. Plot does not submit those stale effects against rolled-back state. Ordinary reconnects and completed planned updates preserve queued reliable work.

An uncertain old write may reject with operation-superseded when its exact outcome is no longer available. The installed state remains authoritative. Inspect state and any game-owned action receipts before submitting another non-idempotent action; Plot does not rerun the old updater to guess what happened.