Developer Diaries

The diaries we keep while building MKA Engine: what we built, why, and what we measured.

The diaries we keep while building MKA Engine. Each diary covers one decision, the reason behind it and the result we measured. For the engine itself, see the MKA Engine page.

Developer Diaries

Dev Diary #2: Deterministic lockstep and desyncs

· Open separately

In short: Multiplayer in MKA Engine runs on deterministic lockstep: only the players' commands travel over the network, not the world, and every computer computes the same simulation itself. For this the engine produces the same result everywhere; a 64-bit fingerprint of the world is compared between computers, so a desync is caught the moment it happens.

Anyone who has played multiplayer strategy knows this screen: the game says "out of sync" and the session drops. In this diary we gathered everything about multiplayer and consistency in MKA Engine: how two computers calculate the same world, which rules we follow for that, how a difference is caught if one appears, and what happens if someone drops out. At the end are the results of the tests we re-ran on 15 October 2026.

First an honest note: all of the following works in the engine's headless tests and its headless server. The front end that draws the screen has not been written yet, so today there is no playable multiplayer game.

Two ways: send the world, or send the move

There are two basic ways to build a multiplayer game.

In the first, a server calculates the world and keeps sending players the parts of it that change. Most shooters and action games work like this (Unreal networking (opens in a new tab)). With a few dozen objects on screen it is a good way.

The second is like playing chess by post: both players have their own board in front of them and write each other only their moves. If the boards start the same and the same moves are played by the same rules, the two boards always stay the same. This is called lockstep. Age of Empires kept hundreds of units in sync over slow modem connections this way (1500 Archers on a 28.8 (opens in a new tab)).

CriterionSend the worldSend the move (lockstep)
What travels over the networkState of the objects that changedOnly player commands
Who calculatesThe serverEvery player's computer
Network load as the world growsGrowsStays the same
Biggest requirementA fast, powerful serverEvery computer producing the exact same result, bit for bit
Comparing the two ways

In a grand strategy game 30,000 locations, 250 countries and 2 million population units change every day; sending that to every player every day is not possible. The commands players give ("march this army there", "raise taxes") are a few bytes. That is why MKA Engine is lockstep. The cost is in the last row of the table: every computer must get the exact same world, bit for bit, from the same commands.

How does lockstep work?

Think of the game as a group hike: the group waits for the slowest walker. In the engine each step (a day or a turn) goes like this:

  1. When a player gives a command it is not applied at once; it is put into a packet for two steps later and sent to the other players. These two steps give the command time to reach everyone over the network.
  2. A step runs only when the packet for that step has arrived from every player. If one player's packet is missing, everyone waits for it.
  3. Packets are merged in the same order everywhere (by player and sequence number), so everyone applies the same commands in the same order.

Even pausing the game is a command: the pause decision goes through the command log, so every computer stops at the same step.

Connections form a star: when a player sends a message to another player, it goes through the host. What travels on the wire is limited:

TravelsDoes not travel
The command packet of a stepThe state of the world
The world's fingerprint at regular intervalsSingle values like population, money or armies
Join, leave and seat messagesScreen images, frame data
A save file for someone joining laterDice results
What travels on the wire and what does not

Dice results do not travel either, because, as explained in Diary #1, a die comes out the same on every computer from its seed and address.

Which rules do we follow for the same result?

The "bit for bit the same" goal does not hold with a single measure; the engine core follows a set of rules. Each one closes a known reason why different computers get different results:

RuleWhat it prevents
No floating point, only fixed-point numbersTiny deviations from compiler and processor differences
Dice only from the seed and addressThe order of rolls changing the result
Time is only the game step; clocks, files and the network cannot enter the coreThe computer's speed or clock leaking into the game
No hash tables; data is always walked in the same orderThe same data being processed in a different order on another computer
Only the scheduler starts threads; results are merged in chunk orderWhich core finishes first changing the result
Entities are added and removed only at step boundariesThe world changing in the middle of a step
The rules of the core

Diary #1 covers the first three in detail. The other three are the job of the engine's data store and scheduler; we will cover them in their own diaries.

The fingerprint: catching a difference

Even when we follow the rules, a fault can appear: an unexpected behaviour in a mod, or a mistake in the engine. What matters is catching the difference the moment it appears. For that we take the world's fingerprint: a short 64-bit number calculated from the whole state. If a single value in the world changes, the fingerprint changes too.

The fingerprint is built like a tree: the rows of each table are split into chunks of 16,384, each chunk has its own print, each table a root print, and the whole world a single root print. In the headless server the computers send each other the world's root print every 30 steps. If the prints do not match, the engine raises a "desync" event.

Then it finds where the difference is. Think of looking for a lost book in a library: first you check which hall, then which shelf, last which position. The engine compares the two computers' prints first table by table, then chunk by chunk, last row by row, and reports the first difference as "this table, this row, this entity". During this search only prints travel over the network; the world itself is never sent.

The fingerprint is for catching a change, not for encryption. Its method is frozen: saves, replays and the network check all compare the same prints, so changing the method would break all of them.

The proof: we re-ran it on 15 October

While writing this diary we re-ran four tests. All ran headless on our development computer; the times were taken on a computer busy with other work, so here the result matters, not the time.

  • 10 years scale world; daily fingerprints identical on 1, 8 and 16 threads
  • End of year 5 a saved game loads into the same game
  • 4 peers, 100 years over a network, always the same prints
Tests of 15 October 2026

The number of cores does not matter. We ran the scale world (30,000 locations, 250 countries, 2 million population units) for 10 game years three times: on one thread, on 8 threads and on 16 threads. All three sets of daily fingerprints came out identical. So a 4-core computer and a 16-core computer can meet in the same game.

Save and load continue the same game. In the same world we saved the game at the end of year 5, loaded the save and ran 5 more years. The result was the same as the game that never stopped. A player who joins later enters this way.

Four computers, a hundred years. In the network test four peers run 100 game years: once as four separate programs over a real network connection (TCP), once inside a single program. One of the peers gets an artificial delay. Every peer meets the same fingerprint at every check, and none gets ahead of the others by more than the input delay (two steps). Both runs passed.

Different processors give the same result. We ran the tests of the engine's core packages in three separate environments: our development computer's processor (x86_64), an ARM processor (aarch64, emulated inside Docker) and WebAssembly (with wasmtime). All three gave the same results. So the three environments produced the same numbers, dice and dates.

Starting the same game: the lobby

A world that is the same bit for bit starts from the same beginning. When a player joins the lobby, the host compares the network protocol version, the engine version, a digest of the installed mods and the mods' load order. If one does not match, the join is refused and both sides see their own value; a problem like "I have this mod and you do not" shows up before the game starts. The save file also carries the mod digest, so a save cannot be opened with a different mod set.

When the game starts every computer receives the same package: the seed, the starting step, the seats (which player has which country) and the game rules. If two players want the same country, the one who joined first gets it.

If someone drops out

Internet connections drop and computers freeze. The engine's answers:

SituationWhat happens
A player dropsThe game pauses; after a set time (0, 30, 60 or 120 seconds) the AI takes over that country
The dropped player returnsThey reconnect with their seat's token; their country is given back
Someone new joinsThe host sends them the last save and the commands since; the save goes in 64 KB pieces and its print is checked
The host leavesEveryone stops at the last step; among those left, the smallest player number continues the game from its own save
PausingThree rule options: anyone pauses, only the host pauses, or only the host can lift a pause the host made
Two human players at warThe host can put a cap on game speed
Unusual situations

We also have a headless server program: a host that plays no country and only hosts the game. Observers can join too; an observer's commands are refused.

Limits

  • No playable game: All of this works in headless tests and the server; there is no screen or interface yet.
  • The slowest decides: In lockstep the game moves at the speed of the slowest computer and the slowest connection.
  • Two-step delay: A command is applied two steps after the step it was given in.
  • Changing host is not seamless: When the host leaves the game stops briefly and continues from a save.
  • Everything goes through the host: In a star layout the host's connection affects everyone's connection.
  • Not cheat protection: Because every computer calculates the whole world, a modified client can read information it should not normally see. Lockstep does not prevent this on its own.
  • No matchmaking: Finding players and setting up connections over the internet is the job of the platform the game ships on; it has not been written yet.

Frequently asked questions

What is lockstep?

A multiplayer method in which every computer runs the same simulation and only player commands travel over the network. In strategy games with large worlds it keeps network traffic independent of the size of the world.

Why do desyncs happen?

When two computers compute a different world from the same commands. Typical causes are floating-point differences, tables with an undefined order, code that depends on the clock or the environment, and racing threads.

How does MKA Engine catch a desync?

A 64-bit fingerprint is computed from the whole state of the world and compared between players. If it differs, the game reports it right away.

Can a dropped player come back?

Yes. A dropped player reconnects with the token of their seat and gets their country back. A newcomer receives the latest save and the following commands from the host. Today this runs in the engine's headless server.

Sources and records

External sources:

Our internal records (the engine repository is closed, so we cannot link them):

  • Determinism decision and the lobby/handoff decision.
  • The multiplayer layer document: wire protocol, lobby, pausing, handoff by save, desync localisation and the headless server.
  • Tests of 15 October 2026: scale world for 10 years, 1/8/16 threads and daily fingerprints; save-load comparison at the end of year 5; 100-year network test with four peers (two runs); cross-target test (x86_64, aarch64, WebAssembly).

In the next diary we go inside the engine: how are 2 million population units held in memory?

If you have questions or ideas about this diary, write to us from the contact page. For an overview of the engine see the MKA Engine page, and for all diaries the Developer Diaries list.