I’ve gotten rid of the whole client-side prediction thing. There was a fundamental oversight in my strategy. My goal was two-fold: 1) make it so that when you press a move key, you moved immediately (responsive), and 2) make it not look stupid.
The strategy was like this. Every time an update from the server side comes in, update the client side stuff as though it had actually happened a few moments ago. This means replaying a couple of physics cycles to try to get things back to where they currently appear to be.
For example, let’s say I’m a box. I can move left and right by pressing and holding the left and right keys. My game runs at 60 fps, server updates will be sent out at 10Hz, the current time is 15 000 (ms) and my position is currently +36. I start holding down the right key, and immediately alert the server to this fact.
The time is now 15 050. 3 frames have gone by and my position on the client is +39. A server update comes in, telling me that my position should be +36. The server update also has a timestamp on it (15 000), so on the client side, this state is corrected to account for the time that has since gone by, and the inputs I’ve kept around on the client. Looking that up, I can see that “right” was held between 15 000 and 15 050, so we apply 3 right ticks, and we land where we were before. Yay!
Now the time is 15 150. 9 frames have gone by and the client position is now +45. A server update comes in, timestamped at 15 100, saying the position should be +40. No problem, says the client, let’s just apply 3 more ticks on top of that (15 150 – 15 100), and the position is now +43.
What happened? The server should report a position of +42 at time 15 100, surely?
The problem is that the server did not receive my “right” press at exactly 15 000 – it received it a short time later. And herein lies the problem: the strategy is correcting server updates at the client as though the server and client received the inputs at the same time. They didn’t. Hence, every 100ms, our character jumps a small amount if we’re moving around actively: jitter. Note that if you move in a straight line, the effect isn’t as apparent.
To correctly complete this idea, the server needs to apply inputs it receives as though they arrived in the past. So in the example above, at time 15 025, when the server hears that I’m moving “right”, it should look up the timestamp on the move (15 000), and replay some frames starting from that point. Then it would transmit the correct coordinates when it comes time to broadcast the next world positions at time 15 100.
This would work great, except in the following circumstances:
It’s too much headache.
Now, I’m contenting myself with a simpler approach. When a person presses the right button, guess how long it will take to reach the server. Don’t start moving on the client until then. (Note: this is still sooner than waiting for a complete round trip). Server and client are kept (roughly) in sync, and the game is a little less responsive. C’est la vie.