Smoothly Reconciling Prediction Corrections

Started by
2 comments, last by sufficientreason 8 years ago

Hi again. Thanks for all the help thus far!

I'm going back over my clientside prediction code and trying to remove some jerky snapping artifacts on corrections. I'm getting in a situation where it looks like I need to introduce multiple layers of smoothing and I'm wondering how to proceed.

So, in the naive "show whatever the server sends us" model with a dejitter buffer, my approach looks like this:

rJ7dMJ4.png

I store all states (which I receive every two ticks from the server) in a buffer, and try to stay around 2-3 states behind the newest received in order to allow time for states to arrive out of order and be properly arranged. I call the most recent state out of the dejitter range the "latest" state, with an immediate prior state. When displaying this information on screen, I smoothly interpolate between the prior state and the latest state to produce a position from some t based on the current FPS delta time. (I can extrapolate too but it's basically the same process with t > 1.)

When doing prediction, the process uses the same data, like this:

vmsh4CE.png

Every tick on the client I apply the most recent received authoritative state (which normally would be waiting for dejitter) and produce one predicted state for every unacknowledged input message sent to the server. When displaying this information on screen, I interpolate as above, only this time I use the last two predicted states as my endpoints.

My question then comes up when I receive the next authoritative state from the server while predicting:

d5txLSp.png

Here we get the authoritative state for tick 56, which disagrees with our previously predicted state for tick 56. Right now I just trash 56', apply 56, and resimulate, but this creates a noticeable snap because of the following situation:

4ndF3wl.png

I'd like to smooth over this, but I'm struggling with how to implement it because of two reasons:

1) I'm already smoothing on display between the latest and prior states.

2) This may happen every two ticks (or whatever the server send rate is), meaning I could be constantly trying to smoothly integrate corrections. I know I need to somehow set aside 56' and roll it into the correction, but I don't know what to do when I'm smoothing over a 56-56' correction and then 58 arrives.

Assuming I have the power to call lerp(t) on any two states, any ideas how I can implement this second, continuous layer of smoothing? Have you dealt with something like this before?

Thanks!

EDIT: Just to clarify, I'm asking because I'd like to smooth the corrections over several ticks, possibly more than the send rate. So I could be correcting 56-56' while 58 comes in, and could in theory have their corrections overlap.

Advertisement

Instead of adding an additional layer you can simply cap your first interpolation to a maximum speed. An example to clarify: lets say you can move at 5 units per second and your simulation runs on 10hz. If you have a 100 frames per second and all goes well, you move at 0.05 units per frame. If you've mispredicted two steps, the distance you're off could be as big as 2 units. In your current model, if you'd interpolate from the erroneous position to the correct position you'd be moving at 0.2 units per frame, which is 4 times as fast. Now instead of moving at 4 times the speed, you can cap that to a maximum of two or the three times the speed. Note that you don't want to cap it one time the speed, because that would mean you'd never be able to catch up! This means a higher cap corrects faster at the cost of not looking as smooth.

Once you have that in place and feel like you need to do better, you can play around with manipulating the cap, e.g. once you have some momentum in the correct direction you can start increasing the cap so that you can have faster correction.

If you check out EPIC (entity position interpolation code) it shows one possible solution:
http://www.mindcontrol.org/~hplus/epic/

The basic idea is to:
Whenever you receive a new packet, calculate where the entity will be some time in YOUR future, based on that packet.
Then set the entity direction/velocity such that it will reach that position at that point in the future.
Don't directly update the displayed entity position, just update the velocity/direction, and let general movement take care of the rest (however you do that in your engine.)

How far into your future you calculate the position will set both the amount of acceptable jitter you can handle, and the amount of latency in entity updates, so it's a bit of a balancing act.
When the entity reaches that position, if you haven't yet gotten another update, you have two options:
- keep going for a little while
- stop the entity movement until you receive the next packet

Which you choose depends on your specific gameplay needs.
enum Bool { True, False, FileNotFound };

Instead of adding an additional layer you can simply cap your first interpolation to a maximum speed. An example to clarify: lets say you can move at 5 units per second and your simulation runs on 10hz. If you have a 100 frames per second and all goes well, you move at 0.05 units per frame. If you've mispredicted two steps, the distance you're off could be as big as 2 units. In your current model, if you'd interpolate from the erroneous position to the correct position you'd be moving at 0.2 units per frame, which is 4 times as fast. Now instead of moving at 4 times the speed, you can cap that to a maximum of two or the three times the speed. Note that you don't want to cap it one time the speed, because that would mean you'd never be able to catch up! This means a higher cap corrects faster at the cost of not looking as smooth.

Once you have that in place and feel like you need to do better, you can play around with manipulating the cap, e.g. once you have some momentum in the correct direction you can start increasing the cap so that you can have faster correction.

I've been experimenting with this approach and it seems to work fairly well. Unity has a built-in SmoothDamp function that will interpolate an object's position towards a given point within a certain time span. It isn't ideal, but I think it will work for me for now. Thanks!

This topic is closed to new replies.

Advertisement