I’ve added much better client prediction to OpenGBH, although I’m going to change it again because of some new ideas people gave me.
Now the client prediction correctly assigns predicted frame sequence number, which means when that sequence actually arrives from server, it is possible to check if the prediction was correct. And that is how it works, here’s the complete sourcecode of client prediction as it is right now:
//Prediction history
for (uint i = PredictHistory.AllocCount-1; i >= 1; i--) {
memcpy(PredictHistory[i]->LocalPed,
PredictHistory[i-1]->LocalPed,
sizeof(Ped));
PredictHistory[i]->Sequence = PredictHistory[i-1]->Sequence;
}
//Find reliable frame
frame_entry* reliableFrame = Frames.LastFrame;
//Find how far into future we should predict (in frames)
uint lagFrames = (uint)(0.5f+(Network.Statistics.Latency) / (1.0f/ServerTimer.FPSLimit));
lagFrames = clamp(lagFrames,0,History.AllocCount-2);
//Predict next frame
PredictHistory[0]->LocalPed->controlClient = BAD_ID;
PredictHistory[0]->LocalPed->handleMovement(Input.keyPressed);
PredictHistory[0]->LocalPed->Live();
PredictHistory[0]->Sequence = reliableFrame->Sequence+lagFrames;
//This prediction corresponds to sequence from future
//Find reliable pedestrian
Ped* reliablePed = reliableFrame->getLocalPed();
if (!reliablePed) return;
//Find corresponding predicted pedestrian
//He was predicted sometime in the past for this frame
Ped* predictedPed = 0;
uint predictedFrame = BAD_ID;
//Attempt to find predicted frame that matches this one
for (uint i = 0; i < PredictHistory.AllocCount; i++) {
if (PredictHistory[i]->Sequence == reliableFrame->Sequence) {
predictedPed = PredictHistory[i]->LocalPed;
predictedFrame = i;
}
}
//If this frame doesnt correspond to any predicted frame, then just ignore all error checks
if ((curtime() > PredictionErrorCheckStart) && (predictedPed)) {
//Calculate prediction error
Vector3f deltaPosition = predictedPed->Position-reliablePed->Position;
Vector3f deltaVelocity = predictedPed->Velocity-reliablePed->Velocity;
float deltaHeading = predictedPed->Heading-reliablePed->Heading;
//How much the whole "snake" of future states should be moved to compensate
//the prediction error
Vector3f shiftPosition = Vector3f(0,0,0);
Vector3f shiftVelocity = Vector3f(0,0,0);
float shiftHeading = 0;
Statistics.PredictionError = deltaPosition.Length();
//Correct position error by moving snake a bit
float positionErrorMargin = Convar.GetFloat("p_poserr",0.5f);
float positionCorrection = Convar.GetFloat("p_poscorr",0.05f);
if (deltaPosition.Length() > positionErrorMargin) {
//Error too large, reset latest state
predictedPed->Position = reliablePed->Position;
predictedPed->Velocity = reliablePed->Velocity;
predictedPed->Heading = reliablePed->Heading;
//Reset future states as well
for (uint i = 0; i < predictedFrame; i++) {
memcpy(PredictHistory[i]->LocalPed,
PredictHistory[predictedFrame]->LocalPed,
sizeof(Ped));
}
//Reset prediction so you don't get stuck in infinite error loop
PredictionErrorCheckStart = curtime() + 1.0f;
} else {
//Small interpolation error, slightly move all future players into right position
shiftPosition = (reliablePed->Position-predictedPed->Position)*positionCorrection;
//Do not move over Z coordinate
shiftPosition.z = 0;
}
//Velocity error is not corrected
//
//Correct heading error
float headingCorrection = Convar.GetFloat("p_hdgcorr",0.1f);
shiftHeading = (reliablePed->Heading - predictedPed->Heading)*headingCorrection;
if (PredictHistory[0]->LocalPed->Velocity.Length() < 0.1f) {
shiftPosition = Vector3f(0,0,0);
}
//Shift all future states
for (uint i = 0; i <= predictedFrame; i++) {
PredictHistory[i]->LocalPed->Position += shiftPosition;
PredictHistory[i]->LocalPed->Velocity += shiftVelocity;
PredictHistory[i]->LocalPed->Heading += shiftHeading;
}
} else {
for (uint i = 1; i < PredictHistory.AllocCount; i++) {
PredictHistory[i]->Sequence = PredictHistory[0]->Sequence-i;
}
}
//Render predicted frame
PredictedRendered = PredictHistory[0];
This is not up-to-date anymore though, because I’m going to use client backlog timed to all server frames for much more precise input handling instead. This means that the client tells the server all keys he pressed, and at what time he presses them, and the server properly calculates time he pressed them on, and simulates that input.
From servers point of view, client input packets arrive marked with future time, so it takes some time until they actually happen.











