Server Side extrapolation Techniques

Greetings all!

I’ve been looking through the forums and the interwebs at large to see if I can gain some insight into improving upon the interpolation/extrapolation techniques found in the GraduallyUpdateState.cs file found with the networking example.

I am currently building a test scene to learn the networking ropes and there are some vague comments in the script. Specifically I am talking about the extrapolation comment that just says You can do clever stuff with predicting what should happen. Has anyone found anything particularly clever that helps with extrapolation? How about latency masking using interpolation?

I am not so much looking for code as I am trying to spur a discussion on what works and what doesn’t. That and I need to learn the theory behind the techniques so I can implement them myself.

Article links are also welcome even if they do not pertain to unity.

GraduallyUpdateState already is doing interpolation to mask latency, that’s the whole point of the script. It isn’t doing any extrapolation, though, it’s just using the last known state update during periods of packet loss that exceed interpolationBacktime. If you wanted to extrapolate, you’d have to predict the next position by adding the last velocity to the last known position, times the amount of time that has elapsed. You’d want to clamp that time though, you don’t want to predict more than 500ms or so.

If you want to predict rotation, that gets trickier. Take a look at NetworkRigidbody, they do some basic extrapolation.

else
{
	State latest = m_BufferedState[0];
	
	float extrapolationLength = (float)(interpolationTime - latest.timestamp);
	// Don't extrapolation for more than 500 ms, you would need to do that carefully
	if (extrapolationLength < m_ExtrapolationLimit)
	{
		float axisLength = extrapolationLength * latest.angularVelocity.magnitude * Mathf.Rad2Deg;
		Quaternion angularRotation = Quaternion.AngleAxis(axisLength, latest.angularVelocity);
		
		rigidbody.position = latest.pos + latest.velocity * extrapolationLength;
		rigidbody.rotation = angularRotation * latest.rot;
		rigidbody.velocity = latest.velocity;
		rigidbody.angularVelocity = latest.angularVelocity;
	}
}

Thanks Prime!

The way I currently have it set up is using a Character Controller which to my knowledge doesn’t take into account angular velocity and the velocity field on said controller is read only so I can’t transfer velocity from the client to the server (unless I am thinking about this entirely wrong).

I am currently using the GraduallyUpdateState script, which I have modified, with great success however there is still some jerkiness as the client lerps to the servers position. I was thinking I could do the 50/50 interpolation as mentioned in the script comments but I haven’t found a good way to do that yet.

EDIT: that awkward moment when you realize you have two forum accounts that are basically named the same

You don’t need to sync the CharacterController velocity to extrapolate, you could just figure out the last velocity by using the old distance over time on last two known states, like:

var velocity = (bufferedStates[0].pos - bufferedStates[1].pos) / (bufferedStates[0].timestamp - bufferedStates[1].timestamp);
var extrapPos = bufferedStates[0].pos + velocity * extrapolationLength;

The jerkiness you’re referring to is only when the client is correcting; if you run 3 builds you’ll notice client 2 looks perfectly smooth on client 3 because corrections only happen on the owner client. This has more to do with how GraduallyUpdateState handles corrections, it will correct a LOT, and unity isn’t capable of the same rewind/replay corrections that other engines use like Source.

Personally I wouldn’t do authoritative movement with input forwarding like this in Unity, for this very reason. I’d probably just send positions to the server, and have the server send back a correction if that position fails sanity checks (is he moving too fast? is he moving up into the air? is this position somewhere players shouldn’t be?) it will make your life a lot easier. The server will forward all valid and corrected positions to the proxy clients.

You are correct. If I have multiple players all clients that are not the one I’m currently controlling look smooth as butter. This gives me some hope that I am at least on the right track.

I’ve been looking at the articles on the Valve site recently too to try to gain some insight into how to better handle this. I will look into the method you described to see if I can’t make it happen in my test scene.

First I am going to try meeting the server in the middle when a correction is made to see if it smooths the movement any. Currently it is just lerping to the servers position. I am afraid however that by doing that it will make not only the clients movement seem jerky but that of the server as well. I’ll get back to you on that one.

Ok, so I may have to trust the client on this one and just perform sanity checks like you suggested Prime. I have had no luck meeting the server in the middle on the interpolation. I could just be doing it wrong.

I’m not willing to give up just yet though. I need to check my math to see if I’m even in the ball park. So if I am trying to interpolate on the server and the client would I take half of the servers position and half of the clients position, add them together, and then lerp both entities to this new position? or am I just crazy bad at vector math?

Ok, just to update this thread a bit.

I’ve worked out a prediction algorithm that smooths out the jerkiness considerably.

The prediction as it was being handled previously just lerped the transform to the servers position if there was an error that needed to be corrected. This resulted in jerkiness on the client side.

I modified this to basically take half of the new corrected position provided by the server and half of the latest client position then lerp the transform to this position. This has dramatically reduced the jerkiness on the client end. I am not 100% happy with it yet, but it is at least passable for my test scene.

to recap I took this error correction:

Vector3 difference = m_NewPosition - transform.position;
transform.position = Vector3.Lerp(transform.position, m_NewPosition, difference.magnitude);

and changed it to this:

Vector3 predictPos = (m_NewPosition/2)+(m_LocalBufState[0].pos/2);
Vector3 difference = predictPos - transform.position;

transform.position = Vector3.Lerp(transform.position, predictPos, difference.magnitude);

it works well for now, I may have to alter it again once I learn more about the process

EDIT: I will also have to test this thoroughly before giving it a final thumbs up

EDIT2: and just to be clear this is really interpolation on the client side. sorry for the confusing thread title