In this post I’m going to discuss what I’ve recently learnt on the topic of determinism in simulations and games. I’m going to cover ActionScript as well as C/C++, as much of the more in-depth information I found was for C/++ developers (it remains by far the most popular langauge for such applications).
Part 1: Overview of Determinism
Q: What is determinism?
A: Determinism is the proposition where every event is causally determined by an unbroken chain of prior occurrences. Similarly, Wikipedia defines a deterministic algorithm (or system) as one where, given a particular input, the same output will always be produced, and the underlying machine will always pass through the same sequence of states in producing that output.
NOTE: In the remainder of this article, I will use the term system interchangeably with engine or algorithm since some areas discussed are more general, and thus pertain to more than just engines.
Q: What are the advantages of a deterministic physics system in game and simulation development?
A: Very often, the physics engine will be at the core of how your game or simulation operates, i.e. the physical world is of fundamental importance. The ability to move to any point along a timeline and replay physics accurately means that implementations which reside on top of your deterministic physics system can be debugged more reliably, for example:
- Networking implementation
- Rendering
- Extraneous (non-physics) game logic
…because, if we know that the underlying physics are correct, then any problems must lie with the layer we are currently working on, eg. the presentation layer.
Let us imagine using a perfectly deterministic engine. You might play a deathmatch on this engine with your friends, and save a recording of a battle. Because the actions of human players (the only uncertain thing) have already been recorded, and because your engine uses those recorded human inputs to produce predictable outcomes on every game cycle using it’s own perfectly deterministic physics model, the end result of this recording will always be the same (what would be the point of a recording if it changed every time you played it back?!). Ultimately, using determinism for debugging is a huge benefit. If you can reliably specify any point in time in the operation of your simulation, with no variability, you can very quickly determine the source of any bugs in other systems which work along with your physics. Without determinism, it can be very difficult to determine whether a bug is in your physical model of the world or in your game logic (as considered discretely).
Another useful aspect of game and sim development for which this can be useful is the ability to perform replays, or to actually allow the player to be able to affect time, eg. The Turtles of Time.
Part 2: Implementation considerations
Some common beliefs about so-called deterministic (physics) engines are that such a system:
- is deterministic to infinity in either direction along the timeline
- is deterministic no matter what hardware you run it on
- is deterministic no matter what else is running on that system
- must use fixed point instead of floating point math to be truly deterministic, because loss of accuracy due to computers’ limited storage space (16/32/64 bit) for floating point numbers necessitates truncations in results of mathematical operations (eg. in division and multiplication).
- It depends. Some (many?) deterministic systems are deterministic only up to a “reasonable” point. This means they are not truly deterministic. This can be a problem in the system’s own implementation, or in the way the system is being used by a client (e.g. allowing random or pseudo-random values to “pollute” input values). However, for many intents and purposes these “pseudo-deterministic” systems are acceptable. One commonly cited case is in network synchronization: Instead of trying to enforce determinism and thus synchronisation in the underlying system (which can be incredibly difficult), frequent updates are used to synchronise all out-of-sync systems (eg. according to the server’s determination of what the system state should be). But indeed, in a true deterministic system, determinism should be infinite in both directions along the timeline. This leads us to the next two points to be addressed.
- False. There is no implied continuity between systems running different architectures (eg. Mac / Solaris / x86). Floating point operations are handled differently between different architectures/platforms, and sometimes even between floating point implementations within the same platform family (x87 vs SSE). Secondarily, although a standard exists (IEEE754) it is often either not supported, incompletely supported, or has discrepancies in how it is supported across different platforms. However, at least in the case of languages with lower level access to hardware, this can be overcome in more ways than one. For low level languages, compiler settings and the math library in use will also have an effect.
- It depends. Particularly noticeable in Flash is the variable frame rate, and, underlying that, the completely unreliable event-based timers. This is in itself can causes errors to creep in, depending on how you implement your timestepping (see this post and read the comments for more on this). No computer program is exempt from the underlying system’s turbulence, including but not limited to process switching, hardware access wait times, etc. In Flash, what the rest of your system is doing has a major impact on how much time passes between timer events being fired. An example of just how irregular the timers are can be seen here.
- False. Fixed point makes no difference to determinism, and in fact is a great deal harder to implement. The key point here is that the same set of inputs must always give the same set of results. If those results are truncated, no problem. The next time the operation is performed the same way, it will have the same results, providing the floating point implementation (typically in hardware) is consistent between the two operations.
On the fixed-vs-floating issue, why then have engine maintainers implemented fixed point deterministic physics even up till very recently (eg. Box2D)? In fact this has nothing to do with speed and not determinism. On most modern workstations, floating point calculation speed is not a concern as typically there either is an FPU dedicated to handling this, or the instruction set handles it natively (SSE, SSE2). However, for the most part mobile devices cannot presently do efficient floating point ops, which means that fixed point ops are still relevant in getting the most out of the processor.
Part 3: Conclusions
In a high-level language like Flash, you cannot natively* have true cross-platform determinism. If your game is a multiplayer Flash game, you will have to have to go for the “frequent resynchronisation” approach, or do all calculation on the server. If your game is not multiplayer, but you want deterministic physics eg. for playbacks, you will have to settle for determinism on a platform-by-platform basis, i.e. deterministic within a given platform. Make sure your Mac players don’t get hold of your PC players’ replays, and vice-versa!
In languages with lower-level access to hardware, you will have greater control over your choices, however the road will not be easy. In the links in Part 2, I’ve provided a couple of solutions I’ve come across for C/++.
Ultimately, no matter what language/platform you are using, you may be wise not to be too idealistic in regards to determinism due to the immense amount of work and understanding required. You may be better off opting instead for a “reasonable” level of determinism, using tricks dependent on your needs, eg.:
- limiting the duration of a simulation so as to limit derivative “drift” in physics values
- using resynchronisation methods for multiuser/networked scenarios
- storing a full backlog of player/entity inputs rather than trusting the derived (i.e. current) physics state as being an accurate input.
* By natively, I mean that there may be non-proprietary solutions but they would either be very slow to implement, very slow to execute, or would require an external shared library in a lower level language like C, but this would only be a possibility if you were writing a desktop game — as of this writing MDM Zinc supports .dll access for Windows, but not .dylib for Mac or .so for Linux, while AIR supports none of these.