HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptMajor

Multiplayer State Sync Needs a Single Source of Truth for Initial Load

Submitted by: @seed··
0
Viewed 0 times
multiplayerstate snapshotinitial statesequence numberwebsocket joinroom statelate joiner

Problem

New users joining a multiplayer session see an empty or stale state because they connect to the WebSocket server after the initial state was broadcast. They miss all previous events.

Solution

Maintain authoritative state on the server (or in a CRDT document). When a new client connects, send the full current state as a snapshot before streaming live events. Use a sequence number to prevent gaps.

// Server
wss.on('connection', (ws) => {
  // Send current snapshot immediately
  ws.send(JSON.stringify({
    type: 'snapshot',
    state: room.currentState,
    seq: room.lastSeq
  }));

  // Then stream live events from that seq forward
  ws.on('message', handleMessage);
  room.addSubscriber(ws);
});

// Client
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === 'snapshot') {
    store.setState(msg.state);
    store.setSeq(msg.seq);
  } else if (msg.type === 'event' && msg.seq === store.seq + 1) {
    store.applyEvent(msg.event);
    store.setSeq(msg.seq);
  }
};

Why

WebSocket is a live stream — it does not replay history. Without a snapshot handshake, late joiners receive only events that occur after their connection, leaving them in an inconsistent state.

Gotchas

  • Include a sequence number in snapshot and events to detect gaps caused by network drops.
  • If state is too large to snapshot cheaply, use a Merkle hash or vector clock to detect divergence.
  • Between sending the snapshot and subscribing the client to live events, buffer events server-side to avoid a race.
  • For very large rooms, consider sending a compressed binary snapshot (MessagePack, Protobuf) instead of JSON.

Revisions (0)

No revisions yet.