Stop sending full state blobs if you want your multiplayer game

ZoeDev Intermediate 50m ago 216 views 13 likes 3 min read

I've been looking into how high-concurrency browser games handle state synchronization without melting the server, and it's a massive departure from the "send everything every time" approach. If you're building a strategy game where a session might stay open for days, you can't just blast the entire world state over a websocket every few seconds. You need a delta encoding strategy that is actually efficient for a server broadcasting to thousands of clients simultaneously.

Most people think delta encoding means doing a byte-level diff between two versions of a file. That works for Git, but it's a nightmare for a game server. If you have 5,000 players, and you try to calculate a unique byte-diff for every single connection based on what each specific client "last saw," your CPU usage will skyrocket. You'd be spending more resources calculating the diffs than actually running the game logic.

The trick is to move away from "what changed in the bytes" to "what facts have changed in the world."

Designing a broadcast-friendly delta

Instead of a diff, you want a message that describes events or state updates. The key is to design the payload so the server can send the exact same message to every socket without knowing what that socket currently holds.

Here is a practical example of how a structured WorldDelta interface looks 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;
}

Notice the dirtySectors field. This is a huge optimization. Instead of sending the entire updated map data for a sector, the server just sends a list of coordinates that are now "stale." The client sees that a sector is dirty and then fetches the specific data it needs. This keeps the heavy lifting off the main broadcast loop and prevents the marketplace or massive map updates from clogging every single 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.

If you send a message saying score: +10, and that message gets delivered twice due to a network hiccup, the client's state is now corrupted. If the message never arrives, the client is permanently out of sync.

Instead, always send the absolute new total. If a player's score changes, the delta should contain the new score, not 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);

When you use absolute values, the merge is just a simple replacement keyed by an ID. If a message is lost, the very next update that arrives for that entity will contain the "full truth," effectively repairing the state automatically. This is a lifesaver for 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 is that a "single event" in the game engine often needs to result in different deltas for different people. In a strategy game, if a player builds a massive fleet, that's a single event, but the data sent to a rival player must be different from the data sent to the owner.

You have to implement a "scrubbing" layer in your deployment pipeline. Before a delta hits the broadcast room, it passes through a function that strips out 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)

C
ChrisCat Intermediate 45m ago
true. also delta compression helps a ton when you're dealing with high tick rates.
0 Reply
R
Riley97 Advanced 43m ago
tried this approach for my last project and the delta logic caused massive desync issues. way too much overhead.
0 Reply
R
Riley82 Advanced 39m ago
I’ve had better luck using bitmasking for small property changes to keep the packet size tiny.
0 Reply

Write a Reply

Markdown supported