Client-Side Prediction and Server Reconciliation

Example gif:

Here is a nice example where you can see the Client-Side Prediction and Server Reconciliation in action: I die to the bomb and respawn on the corner. The respawn on the corner happens only client side so for the server I am still at my last death position. When I respawn a try to move again the reconciliation snaps me back to the server position and moves me from there.

Client-Side Prediction and Server Reconciliation in action

In the Beginning....

there was a past Project for a Uni course where we had the task to transform a local multiplayer bomber man to a client and server architecture. The basics were already done but not really anything to smooth out all the problems that come with multiplayer once you stop having the client and the server on the same device. The Project itself is rather simple : It's basically just Bomberman which is playable with up to 4 players

The Messaging Structure:

The Client can send three types of messages:

The Server has also three types of Messages:

Client Prediction and Server Reconciliation

For the client side prediction all I'm doing is taking the input and applying it instantly on the client. These local inputs are then saved in a queue with a sequence number and the input for the Reconciliation part.

//Client.h
struct PredictedState
{
	float x = 0.f;
	float y = 0.f;
};

PredictedState          m_predictedState;
std::deque<PendingInput> m_pendingInputs;    // unacknowledged inputs
int                     m_lastAckedSeq = 0;
int                     input_sequenceNumber = 0;

For the reconciliation part I check if the last acknowledged Number is bigger then the last reconciled Number. If that is the case the client reconciles, reapplies the inputs and then sets his position again based on what the server told him

//Client.cpp
if (m_lastAckedSeq > m_lastReconciledSeq) 
{
		Reconcile(m_lastAckedSeq, 
		serverSnapshot.playerStates[slot].x, 
		serverSnapshot.playerStates[slot].y);

		m_lastReconciledSeq = m_lastAckedSeq;
		players[slot]->Set(m_predictedState.x, m_predictedState.y);
}

In Reconcile the only thing that happens is that the predicted state is updated to the last acknowledged input and the predicted inputs are reapplied.

Conclusion

Overall I'd say that this was a nice small scale project for me to get a better understanding of how client-side prediction and server reconciliation work. At first the concept seemed complicated but once I understood the order in which things happened it became a lot clearer to me. I am happy with the result, and it's nice to see that the movement and bomb placement of the characters now works pretty smooth even when playing to different devices. And a big shoutout to Gabriel Gambettas Fast-Paced Multiplayer Blog that helped me a lot in understanding these concepts.