Contents
In short: MKA Engine runs a game day in the same order every time: commands, timers, systems, end-of-day work, the world's fingerprint and the calendar's advance. Systems declare in advance which tables they read and write; the engine builds stages from that and shares the work of each stage across threads. The result is the same whatever the number of threads.
When the game clock advances, one day passes on screen. Behind it hundreds of thousands of rows are computed: population grows, treasuries change, timers ring, commands are applied. In what order, by which rules and on how many cores is the engine's job. This diary follows one game day. In earlier diaries we saw the calendar and the world in memory; now we look at the wheel that turns on top of them.
Like a factory shift
Think of the factory in the picture. A shift begins with the morning instructions, then the jobs with alarms are done, then the workshops work, and at the end the ledger is closed. Workers at separate benches can work at the same time, but two workers cannot reach for the same bench; one has to wait. The engine's game day works exactly like this. The hard part is knowing which job is at which bench and making sure the result is the same whatever order the work happens in.
The seven stops of a day
Every day goes in the same order. We wrote this order down in advance, because in multiplayer every computer must follow the same one (Diary #2).
| Order | Stop | What happens |
|---|---|---|
| 1 | Commands | The commands players gave for that day are applied by player and sequence number |
| 2 | Timers | Timers that are due ring, by due time and sequence number; a new timer can be set for the next day at the earliest |
| 3 | Systems | The rules of the game run stage by stage; at the end of each stage the messages left behind are applied |
| 4 | End of day | Removal requests, then addition requests are applied; archive and removal rules work; indexes are updated |
| 5 | Expired | Script variables whose time is over are removed |
| 6 | Fingerprint | The world's digest is refreshed and the day's fingerprint is taken |
| 7 | Advance | The step counter and the date move one forward |
Events and hooks enter this order at their own defined moments; the basic skeleton is the seven stops above. The order has small but important consequences. Because a timer's handler can set a new timer for the next day at the earliest, a handler cannot disturb the world the same day's systems will see. Doing additions and removals together at the end of the day also means, as we described in Diary #3, that all systems of a day see the same world.
How are systems arranged into stages?
Each rule of the game is a system: "population grows", "country income is computed". Each system declares which columns it reads and which it writes. The engine builds a plan from this, and the rule is simple: a system is put one stage after the latest earlier system it conflicts with, or into the first stage if it conflicts with none. Two systems conflict when one reads what the other writes, or both write to the same thing.
Systems in the same stage do not touch each other's data, so the result is the same in whatever order they run. The plan is made once when the game loads; nothing is computed during the day.
If a system wants to tell another row something, it leaves a message; messages are applied at the end of the stage in order of system, chunk and write. So the system that writes a message cannot see its effect in the same stage; readers wait for the next stage.
How do threads work?
To share out work that does not wait at the same bench, the engine opens a fixed number of threads when the game starts and does not close them during the game. The work of a stage is cut into numbered pieces; each thread takes the next number, does it, takes the next. The calling thread works too, so 8 threads means 8 workers. That way the cost of opening and closing threads is not paid every day, and nobody waits idle because of one slow piece.
Whichever thread takes whichever piece, the result does not change: the results of the pieces are merged in piece order. That is why the same game is played with 1 thread or 16 threads.
We measured it
With the headless runner we ran 12 game years (4,380 days) in a simple world of 30,000 locations, 250 countries and 2 million population units. The world has four systems; because they conflict with each other they fell into four separate stages. So the speed here comes not from running systems side by side but from splitting the rows of each system across threads.
One game day of year 12, by number of threads (ms)
| Label | Time |
|---|---|
| 1 | 4.58 |
| 2 | 2.79 |
| 4 | 1.40 |
| 8 | 0.90 |
| 16 | 0.94 |
Going from one thread to eight made the day about 5 times faster (from 4.58 ms to 0.90 ms). After that it is flat: 16 threads are no faster than eight. The reason is simple: part of the work cannot be split. At eight threads part of the day goes to small stages and the fingerprint; they are short, so splitting them gains nothing. This is the known limit of parallel work (Amdahl's law (opens in a new tab)): the part that cannot be split keeps the time above a floor, however many workers are added.
At all five thread counts (1, 2, 4, 8, 16) the fingerprint at the end of the 12 years was the same: the same game was played.
| Part | Time (ms) |
|---|---|
| Stage 1: population update | 0.60 |
| Stage 2: location population | 0.08 |
| Stage 3: location daily | 0.21 |
| Stage 4: country daily | 0.06 |
| Fingerprint | 0.12 |
In the first stage the system's work added up to about 3.95 ms over all threads but showed on the wall clock as 0.60 ms: we split the work about 6.6 times (a calculation, not a measurement). The small stages are not big enough to split anyway.
Not everything runs every day
Not every rule of a game runs every day: tax is collected monthly, the population census is monthly, some events are checked yearly. Each system declares its frequency: every step, daily, weekly, monthly or yearly.
If a monthly job were done on a single day at the start of the month, that day would take much longer than the others and the game would hitch once a month. We spread monthly work over 30 days: the rows are cut into 30 equal slices and one slice is handled each day. When the month ends every row has been handled exactly once, and no day carries the whole job.
- 2 systems spread over 30 days (population update, monthly census)
- 1.001 ratio of month-start days to other days (8 threads, 144 month starts)
- 1.153 ratio of the slowest day to the fastest in the 30-day cycle
Month-start days are not measurably slower than the other days of the month. So monthly systems do not create a jump between days.
More than one turn a day
The engine keeps the calendar day as its base unit, but when a game loads it can choose the length of a step: N turns a day, daily, weekly or monthly. Games with fine-grained systems such as combat may want several turns a day. We opened this option up front because adding it later would have meant touching the timer and the save format again. Random numbers and saves are kept by step number instead of day; so the determinism does not break when the number of turns changes.
Limits
- Parallelism depends on how systems conflict: If many systems write to the same table, the number of stages grows and stages wait for each other. Splitting rows makes up for some of it, but not every job can be split.
- The part that cannot be split: Small stages and the fingerprint limit the speed at many threads. In our measurement nothing was gained beyond 8 threads.
- Scope of the measurement: A simple world with four systems. The hundreds of systems of a real game will change the number and spread of stages; the numbers in this diary do not show that game's speed.
- The slowest day of the month: Spreading removes the month-start jump, but small differences between days remain (the slowest day is 15 percent slower than the fastest).
- Delay of spread work: When monthly work is spread over 30 days, a row is handled on some other day of the month. Rule design must know this: there is no assumption that everybody is handled at the same moment at the start of the month.
Frequently asked questions
How is parallel work made safe in a game engine?
Each system declares in advance which tables it reads and writes; the engine puts conflicting systems into separate stages, and systems in the same stage do not touch each other's data. This check is made when the game loads, and nothing is checked during the day.
Does the number of threads change the result of the game?
No. The results of the pieces are merged in piece order and random numbers are computed independently of order, so the same game is played with 1 thread or 16 threads. In our measurement the same fingerprint came out at five thread counts.
In what order do systems run?
Systems are split into stages; stages run in order, and systems in the same stage are independent of each other. The general order of a day is commands, timers, systems, end-of-day work, the fingerprint and the calendar's advance.
Why is monthly work spread over 30 days?
If it were done in one day at the start of the month, that day would take much longer than the others and the game would hitch. Cut into 30 slices, a thirtieth of the job is done each day. In our measurement month-start days were as fast as the other days.
Sources and records
External sources:
- Wikipedia, Amdahl's law (the limit of parallel speed-up) (opens in a new tab)
- Wikipedia, Thread pool (a pool of long-lived threads) (opens in a new tab)
Our internal records (the engine repository is closed, so we cannot link them):
- Step phase order decision: the seven stops of a day and the places of events and hooks.
- Step length decision: N turns a day, daily, weekly, monthly steps; system frequencies.
- Thread measurement: 16 October 2026, 12 game years, 1, 2, 4, 8 and 16 threads, 3 runs each, frozen code copy, computer busy.
In the next diary we will cover the language in which we write the game's content: how are tables, rules and numbers introduced to the engine?
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.
Sources and related docs
You may also like
- How is a strategy game's world map made?
For MKA Engine we built a 27,357-location world map of 1337 from real data: how terrain, rivers, straits, population and location borders were made.
- The road to a game engine
How we started MKA Engine: reading four games, treating determinism as law, moving phase by phase with measurements, and where we are now.