Synchronized time
Every device clock differs, and messages do not reach every player at the same
instant. Plot.time gives every game copy one
shared time reference so those differences do not create different game results.
After await Plot.ready(), read game time
synchronously:
Plot.time.now() returns a safe integer
number of milliseconds since this game session's fixed epoch. The epoch is
arbitrary; what matters is that a value such as 34_000 identifies the same
moment for every player in this session.
Build timers around a deadline
For a ten-second round, have the host persist the moment it ends. Do not ask each device to start its own ten-second counter:
Every game copy renders the remaining time from that shared deadline. The snapshot subscription handles initial state, ordinary changes, and recovery:
Plot.gameState.as<T>() creates the
typed shared-state view,
gameState.get() reads the deadline,
gameState.set() persists it, and
gameState.subscribe() rebuilds
the timer after a late join or in-session resynchronization. The
info.affects() matcher
prevents unrelated Plot.gameState
changes from restarting the render loop.
A shared deadline avoids starting a fresh countdown on each message's arrival. Clock synchronization remains an estimate, so small differences are possible. A browser that pauses and resumes computes the current remainder instead of continuing an old local counter.
Use Plot.gameState for a deadline that
should survive supported in-session recovery or reach late joiners. It is still
session state, not permanent saved progress. A
reliable realtime channel can
accompany the state change with a transient sound or animation for players
already present. Persist the deadline first; Plot's channel causal ordering then
ensures a receiver installs that state before its message listener runs.
For audio cues and loops, Toolkit: synced audio handles loading, scheduling against this clock, catching up after delays, and correcting playback drift. Store one shared cue timestamp in Game State and pass it to each local object; the toolkit does not distribute playback commands itself.
Convert timestamps correctly
Most game logic needs only now(). Use the
conversion helpers when interoperating with JavaScript dates or an existing
timestamp:
fromUnixMs() assumes its argument
is already a Unix timestamp from Plot or another trusted source. It does not
apply a local-device correction.
fromLocalClockMs() is for a
timestamp produced by this device's Date.now() clock. It applies the current
localClockOffsetMs before
converting to game time.
toUnixMs() converts in the other
direction and is the inverse of
fromUnixMs() while results remain
inside JavaScript's safe-integer range.
At approximately the same instant:
The localClockOffsetMs
estimate can change after synchronization. Do not cache it; read it when
converting a local timestamp, or use
fromLocalClockMs().
Use client timestamps for cooperative gameplay
A client can submit its own Plot.time.now() value for an answer, reaction, or
speed score. The shared clock makes that value comparable within the session,
which is sufficient for Plot's cooperative game model. It does not prove that
modified client code captured the value at the claimed moment. Validate that a
submitted timestamp is a safe integer in a sensible range for the current round,
but do not build a second clock-sync or anti-cheat protocol into the game.
Guarantees, recovery, and limits
now()andunixNow()never decrease within one running SDK instance. Plot filters noisy samples and applies ordinary corrections gradually.gameTimeEpochUnixMsis fixed for the game session. Game-time values from different sessions are not comparable unless first converted to Unix time.Plot.timecontinues advancing from its last synchronized anchor during a temporary disconnection. Accuracy can drift during a long outage; Plot refreshes it automatically after recovery and when a suspended page becomes visible or returns online. An advancing clock does not prove the connection is healthy; usePlot.connectionfor an optional connection indicator.- Every public timestamp, property value, and conversion result is a safe
integer number of milliseconds. The conversion methods throw
TypeErrorfor a non-safe-integer argument or result. - Calling a
TimeApimethod with valid arguments, or reading either time property before Plot installs the complete ready state, throwsPlotNotReadyError. Internal clock failures use the shared error model. Time is already available inside a pre-registered initial snapshot callback. - Clock values are gameplay references, not event sequence numbers. They do not decide synchronized-state or channel order and are not authority for authentication, purchases, or other platform security decisions.
There is no clock event, refresh method, reconnect method, or uncertainty value
for game code to manage. Plot owns synchronization; games read the current
TimeApi when needed.