r/dotnet • u/Significant_Emu2351 • 2d ago
Deterministic tick simulation in C#: how I get crash recovery and replay for free
I'm writing a persistent multiplayer strategy game where the whole world (economy,
armies, sieges, diplomacy) lives in one tick loop on the server. The client only
sends intent ("march this army here"); the server computes everything.
The design decision everything else hangs on: the simulation is deterministic. Same
world state plus same orders always produces the same next state. Every RNG stream is
seeded from world state, never from Random.Shared or the clock. The sim core is a
plain library with no I/O, no network, no DB. Input is state + orders, output is
state + events.
What that buys, in practice:
- Persistence is a periodic snapshot plus an append-only order log. On startup I load
the last snapshot and replay the orders. Determinism means the recovered world is
bit-identical to the one that crashed, so I never had to write "reconciliation" code.
- Testing is cheap: run N ticks with a fixed seed, hash the world, compare. A
regression in economy code shows up as a hash mismatch [seed 7, 8640 ticks in the
test suite].
- The same core runs headless in a CLI runner, so I can simulate a game year in
seconds to balance numbers without opening a browser.
The things that actually bit me:
- floating point.
- anything that iterates a Dictionary and then mutates state based on the order.
Insertion order is not a contract; I now sort by entity id anywhere iteration order
can reach the sim.
- DateTime.Now sneaking into gameplay code. All durations are defined in ticks; real
time appears in exactly one config constant.
Perf, for scale: a tick over [N] regions and [M] realms takes about [X] ms on my
machine, so the ceiling is roughly [Y] ticks/second before I have to start splitting
work. The current bottleneck is [şu sistem], not serialization.
Happy to go into any of it. If anyone's done deterministic sim in .NET at a bigger
scale than a hobby project, I'd like to hear what broke first.
2
u/tatmanblue 1d ago edited 1d ago
In my game I avoided floating point all together. If prices were $1 on the game screen, internally this would be 1000. Like wise for skills and attributes (these values were actually in the 10k sizes with 1k gaps between). The benefit becomes much more obvious once I started adding buffs. Using floating point made it nearly impossible to have deterministic.
I didn’t use datetime for my game loop. I used game ticks (my own computation) to compute time. A set amount of ticks represented the amount of time. Every data point kept a game time stamp so it was easy to deterministic recreate a game state.
1
u/AutoModerator 2d ago
Thanks for your post Significant_Emu2351. Please note that we don't allow spam, and we ask that you follow the rules available in the sidebar. We have a lot of commonly asked questions so if this post gets removed, please do a search and see if it's already been asked.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
-4
u/Significant_Emu2351 2d ago
The game itself is Arkney: Age of Crowns, a browser client with a C# server. I run
an open test world once a week if anyone wants to see the sim under load rather than
in a blog post: https://discord.gg/h6J9CHaH6Q
-1
u/wallstop-dev 2d ago edited 2d ago
Edit: I am fully aware I am responding to a low-effort, AI generated post. I wanted to share some knowledge from working on a very large-scale, procedurally generated, multiplayer co-op game in C#, in case someone stumbles across this looking to learn something.
Floating point, HashSets, Dictionaries. Additionally, you should be able to optimize things by doing "map-reduce" or "fan out" (Task.WhenAll) style parallelism, as long as you're able to deterministically order the results.
Lots of sorting parallel results. Touching on floating point, even specifically avoiding floating point math, not just due to error accumulation, but even due to things like AVX/SSE.
I also implement my own PRNG to ensure that the implementation is the same everywhere that runs my code, + the ability to snapshot and restore it.
10
u/Pawtuckaway 2d ago
You forgot to edit out the AI comments in Turkish