🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Client-side prediction for physics *without* a fixed step update?

Started by
4 comments, last by frob 4 years, 7 months ago

I am looking to gather opinions to check my assumptions, so I will try to keep my assertions to a minimum. Please challenge me.

We have a physics-based vehicle simulation running in an engine which does not support fixed step updates (neither for the entire game thread or for physics only - nothing!). The approach with which I am most familiar was the one demonstrated in the Overwatch Netcode GDC talk (and referenced by Rocket League's) - clients running ahead of server, input buffering on the server, server can tell clients to speed-up or slow down based on RTT, and clients re-simulate themselves using local input buffer whenever there is an error in their prediction vs the historical server auth state received N frames in the past.

I have been investigating client side prediction, and it seems like every canonical example we seem to uphold (Gabriel Gambetta's articles, Overwatch talks, Rocketleague talk, etc) relies on a fixed-step update so that the client can predictively execute e.g. 5 frames of W+D input, then 2 frames of W+A input, and so on, and have the server execute the same input on the same frame for the same duration, reducing divergence between prediction and the authoritative state received back on the client after RTT+buffer time.

Has anyone seen this done without fixed steps? What does it look like? I know Unreal doesn't use a fixed step and does have CSP, but that's for characters which use a kinematic, hand-written movement system and not a physics engine. That really isn't an option for us.

How do you reason about how long to apply inputs locally vs the server if neither side is on the same page wrt frame length? I am guessing you can send timestamps + duration for each input, and have the server make a best-guess at applying your inputs for the specified duration (issues with lining this up with server frames notwithstanding).

I would appreciate any insight or experiences in this area so that I don't get target-fixated on this strategy and perform unnecessary work retrofitting fixed-step updates into a tech stack which does not have them.

Many thanks.

 

 

 

Advertisement

If you don't have a fixed timestep then it becomes much less practical for a client to receive a server update and know whether or not it had 'predicted' (bad term, but common) correctly on the local machine. You're going to find that there's a mismatch pretty much every time - it's just a case of how large the delta is. It's also much less practical to be able to perform 'reconciliation' when you are sure of a divergence because you have no confidence that you're going to get the same outcome as the server, given that your replayed inputs are likely to get processed differently to how the server did it.

I found myself in a similar situation once and I attempted a crude delta/offset fix, simply measuring the error client side and adjusting the current position by that delta, in the hope that an error of X several frames ago would broadly correspond to an equal error of X now. To cut a long story short, this didn't work and wasn't stable. (e.g. server reports the entity was actually 0.1m lower on the server mid-jump, but this was 100ms ago, and moving the entity down 0.1m now puts it in the ground, so the physics engine ejects it forcefully, and so on.)

Short answer from me... in your situation I'd either:

  • consider hacking in a deterministic fixed-step approach, bearing in mind that a lot of other code such as input handling may need reworking too. Or;
  • consider a more lenient networking model where responses from the server are used to influence the client-side object without a formal reconciliation stage. E.g. the delta/offset model I mentioned above but with more time invested to make it stable, to smooth over the corrections, etc.

You can totally run client prediction without fixed time steps (but I don't envy you.)

There are two ways to think about that, which both end up coming down to the same thing: The client needs to be able to receive time snapshots from the server, and calculate a difference in state based on that time. If the client doesn't have a snapshot at that time, it needs to interpolate between the two snapshots surrounding that. As a simple example, if I have position A (Pa) at time 0.11 and position B (Pb) at time 0.14, and I receive a snapshot at time 0.13, then the interpolated position Pi to compare against would be Pi = Pa + (Pb-Pa)*(0.13-0.11)/(0.14-0.11)

Some events, like, say, driving over a pickup/mine and triggering it, may be spawned by physical interaction (collision / trigger volume detection,) but my recommendation is to actually only detect those on the server and send events that the events were detected to the clients, who can then take appropriate action. If the latency is too high for that to be feasible, then you can perhaps show some visual/audio effects on the client ("smoke plume / bang sound / camera shake") but delaying any "hard" effect (like removing hitpoints or whatever) until the server tells you.

The second way to think about it (if the first is "treat time as continuous and interpolate,") is to treat time as a sequence of very small timesteps. Call a timestep a microsecond, and keep track of them in an int64 or something. Work under the assumption that the physics engine micro-steps through all these time steps, but you only get to see certain points outside the engine. If the client has snapshots for timestep 110000 and 140000, and you receive a state from step 130000, then you're again faced with the question of: what is my baseline here? Again, you will likely end up interpolating, but the approach of thinking of time as lots of small timesteps may be easier (or harder) to express in some particular code/engine.

I'm asssuming your physics engine doesn't have cheap shapshot restore, nor control over the amount of time it advances in any one step. If you have those, then you could restore the world at 0.11, step forward by 0.2 to 0.13, and then compare to what you got from the server.

enum Bool { True, False, FileNotFound };

In my experience, this interpolation is the (relatively) easy part. The problems I found were:

(a) there is almost always an error (because simple linear interpolation doesn't handle forces/acceleration/deceleration well - presumably some calculus here would reduce the error) and (b) performing the necessary correction is non-trivial unless you have granular enough control over the physics to be able to efficiently rewind and replay only the relevant entities, and only to the point that you want to render now (rather than some arbitrary time +/- half a frame, for example). Not sure if this is the case in the OP's engine, but it would be interesting to know.

On 11/20/2019 at 7:07 AM, HateDread said:

We have a physics-based vehicle simulation

 

 
 
 
 
 
 
 
 
On 11/20/2019 at 10:29 AM, hplus0603 said:

As a simple example, if I have position A (Pa) at time 0.11 and position B (Pb) at time 0.14, and I receive a snapshot at time 0.13, then the interpolated position Pi to compare against would be Pi = Pa + (Pb-Pa)*(0.13-0.11)/(0.14-0.11)

And even this breaks down very, very quickly, especially on physics-driven systems.

Consider a vehicle wheel, that is spinning rapidly.  The vehicle wheel is also colliding and running the motion of your physics system.

The difference between position A and position B is calculated, the wheel has turned 75 degrees.  So the networking system updates the position and calls it done. 

Then the vehicle physics system kicks in.  Does this mean the car wheel jumped 75 degrees with no force applied to the vehicle? Does this mean it must apply a 75 degree rotation as a sudden spike of force?  

Or maybe it means the wheel was going rapidly in the other direction, and it needs to apply a -285 degree rotation to get the physical effect?

So to fix it for the wheel you'll need to write some special code to handle all this variability.

Then you'll need to write some special code for every other physics-interactive network-controlled object.

 

Before long you're living a nightmare where every single object has a bunch of special case networking code to handle all the variations between variable time network updates, variable time physics updates, variable time latency, nondeterministic math, and nondeterministic physics simulations.

Physics over an Internet connection in a game engine is not fun. While there are hacks and workarounds, and solutions that work if you don't need interactive rates, solutions do not exist in interactive realtime games where machines are separated by more than a few rooms.

You can fake it, but faking physics-controlled simulations is difficult and error prone. It also suffers terribly as latency increases.  Even at single digit millisecond latencies there is still a constant flow of physics corrections. 

This topic is closed to new replies.

Advertisement