Stop sending full state blobs when building a multiplayer game

ZoeDev Intermediate 8/30/2026 309 views 13 likes 3 min read

High-concurrency browser games synchronize state differently from the "send everything every time" method. In a strategy game where a session might remain active for days, blasting the entire world state over a websocket every few seconds is not an option. The server needs a genuinely efficient delta encoding strategy when broadcasting to thousands of clients at once.

Many people assume delta encoding is a byte-level comparison between two versions of a file. That approach works for Git, but it becomes a nightmare for a game server. With 5,000 players, calculating a separate byte-diff for every connection according to what each client "last saw" would drive CPU usage through the roof.

The key is to shift the focus from "what changed in the bytes" to "what facts have changed in the world."

Designing a broadcast-friendly delta

Rather than sending a diff, create a message that describes events or state updates. Design the payload so the server can send the exact same message to every socket without knowing what that socket already contains.

Here is a practical example of a structured WorldDelta interface in a TypeScript environment:

interface WorldDelta {
 added?: { players?: Player[] };
 removed?: { playerIds?: string[] };
 updated?: {
 players?: Player[];
 sectors?: Sector[];
 dirtySectors?: SectorCoord[]; // map data here went stale, refetch it
 tradeBoard?: TradeBoardDelta; // the market board moved
 deals?: DealsDelta; // a negotiation moved; only its two parties get this
 };
 serverNow: number;
}

Consider the dirtySectors field. It provides a major optimization. Instead of transmitting all the updated map data for a sector, the server sends only a list of coordinates that have become "stale." The client recognizes that a sector is dirty and fetches the specific data it requires. This removes the heavy work from the main broadcast loop and stops marketplace activity or massive map updates from overwhelming every socket.

The golden rule: Use absolute values, never increments

This is the most important part of a robust AI workflow or game networking architecture: make your updates idempotent.

Suppose a message contains score: +10 and a network hiccup causes that message to be delivered twice. The client's state becomes corrupted. If the message never arrives, the client remains permanently out of sync.

Instead, always send the absolute new total. Whenever a player's score changes, the delta should contain the new score rather than the change.

// How to merge an update without doing math
const incoming = new Map(delta.updated.players.map((p) => [p.id, p]));
this.players = this.players.map((p) => incoming.get(p.id) ?? p);

With absolute values, merging becomes a straightforward replacement keyed by an ID. If a message is lost, the next update received for that entity will contain the "full truth," automatically repairing the state. This is invaluable in browser-based games where tabs go to sleep or users switch from Wi-Fi to 5G.

Handling information hiding and fog of war

Another layer of complexity arises because a "single event" in the game engine often needs to produce different deltas for different people. In a strategy game, a player building a massive fleet represents one event, but the data sent to a rival player must differ from the data sent to the owner.

You need a "scrubbing" layer within the deployment pipeline. Before a delta enters the broadcast room, it passes through a function that removes sensitive data based on the recipient's permissions.

function scrubForBroadcast(player, isOwner) {
 if (isOwner) return player;
 
 // Remove sensitive info for everyone else
 return {
   ...player,
   treasury: 0,
   fleetStacks: [],
   // ... etc
 };
}

By combining absolute value updates, dirty-flagging for heavy assets, and permission-based scrubbing, you can build a real-world multiplayer system that stays performant even as the game world grows massive.

webdevtypescriptAI ProgrammingAI Codinggamedev

All Replies (3)

Want a live back-and-forth? Join the global AI chat room — login to talk.

C
ChrisCat Intermediate 8/30/2026

Delta compression is a lifesaver for high tick rates. Which library handles that best? Design the payload so the server can send the exact same message to every socket without knowing what that socket already contains.

0 Reply
R
Riley97 Advanced 8/30/2026

I've been examining how high-concurrency browser games synchronize state without overwhelming the server. They take a completely different approach from the "send everything every time" method. In a strategy game where a session might remain active for days, blasting the entire world state over a websocket every few seconds is not an option. The server needs a genuinely efficient delta encoding strategy when broadcasting to thousands of clients at once. Many people assume delta encoding is a byte-level comparison between two versions of a file. That approach works for Git, but it becomes a nightmare for a game server. With 5,000 players, calculating a separate byte-diff for every connection according to what each client "last saw" would drive CPU usage through the roof. More resources would go toward computing the diffs than running the game itself. The key is to shift the focus from "what changed in the bytes" to "what facts have changed in the world." ## Designing a broadcast-friendly delta Rather than sending a diff, create a message that describes events or state updates. Design the payload so the server can send the exact same message to every socket without knowing what that socket already contains. Here is a practical example of a structured WorldDelta interface in a TypeScript environment: ```ts interface WorldDelta { added?: { players?: Player[] }; removed?: { playerIds?: string[] }; updated?: { players?: Player[]; sectors?: Sector[]; dirtySectors?: SectorCoord[]; // map data here went stale, refetch it tradeBoard?: TradeBoardDelta; // the market board moved de } }

0 Reply
R
Riley82 Advanced 8/30/2026

Bitmasking kept my packet sizes tiny for small property changes. Have you tried that? One concrete step is to shift the focus from "what changed in the bytes" to "what facts have changed in the world"—design a message that describes events or state updates, so the server can send the exact same payload to every socket without tracking per-client history.

0 Reply

Write a Reply

Markdown supported