Deterministic games

Hi,

I’ve posted an article on the oddlabs blog that shows how to add playback to your games, allowing more easy debugging of obscure bugs. In the bottom of the article there’s a download with the playback code and the sample Space Invaders game from LWJGL with playback added. The code is BSD licensed, if you find it useful.

http://oddlabs.com/blog/?p=20#more-20

  • elias

Thats a nice elegent logging system there, but you don’t mention how you deal with multi-threading issues?

Also, from personal experience I find the tricky bit of determinism is not the actual implementation, but finding all the inputs to log in the first place (particularly with large, legacy, spaghetti-like codebases which were never written to be deterministic in the first place). Do you do any kind of checksumming to help with this?

We don’t deal with multithreading at all :slight_smile: We only use threads that does read-only access to the game state, for example the music queue thread or a loader. I’ve added a short paragraph of a way to do simple checksums we use.

  • elias

How about replaying at variable speeds? For example you receive a 30min replay and the exception is thrown around 29’. You’d want to get there fast, right? :slight_smile:

Cool! It works but it seems not as deterministic as the name suggests :wink:

I played a game till the end and left 1 enemey to kill me. When I replayed the log I noticed I won instead! (and the enemies didn’t got killed in the order I killed them). However I did see all the moves I did being played back, but clearly at a different interval. A new game started and i could see my ship follow the enemy that had killed me in the game before but wasn’t there now… The events don’t seem to be played back according to the time they occured on… or is this not the intention of the library?

If there is anything absolute-time dependant or Random (without a set seed) / Math.random(), you’re pretty much screwed. Variable time-steps are also breaking determinism.

Ah just read his blog (I always read manuals last ;)):

[quote]Since you can’t effectively log the elapsed_time values, you need to avoid updating any game state and reading any input in render(). It must be a “write-only” function that draws pretty stuff on the screen.
[/quote]
So I guess it’s not his intention to have the events synced with time perse.
Also, I tested a replay with vsync on (noticed I had it disabled before) and it seemed to match. It might be interesting to add recording ticks to time the replay though…

Ok, it seems that I missed a few spots of non-deteminism (System.currentTimeMillis() and Sys.getTime() and isSoundPlaying()(!)). I’ve updated the sample and added a checksum check that can be enabled by uncommenting a few lines in the gameloop. The game should now play back perfectly. To Thijs: I replaced System.currentTimeMillis() - lastTime with a constant elapsed time of 1000/HZ, since the frame time is synced to HZ anyway. As you correctly noted, you can’t practically have game loops that steps according to currentTimeMillis or similar. If you want that, you’ll need to update game state in fixed size steps:


long currentTime = System.currentTimeMillis() ;
long elapsed = currentTime - lastTime;
lastTime = currentTime;
while (elapsed > MILLIS_PER_TICK) {
   updateGameState(MILLIS_PER_TICK);
   elapsed -= MILLIS_PER_TICK;
}

  • elias

Spasi: We implemented “fast-forward” by deliberately querying the keyboard without using Deterministic and check for a magic key sequence, for example CTRL-LEFT/PAGEUP. When one of the keys are pressed, the game fast forwards - in the case of Space Invaders you can count frames and don’t render or Display.sync() for a certain amount of frames or in case of a time-dependent game loop like in my previous post, add time to System.currentTimeMillis. We also have a key sequence for forwarding until end-of-log in case of debugging a crash.

  • elias