Real-time Multiplayer Networking: Client-side Prediction

by Chris Dadabo
  games, real-time, networking

When developing a multiplayer competitive game, one of the main concerns is preventing your players from cheating.

Imagine you have an authoritative client, meaning that it updates the game state and sends it to the server, which assumes the clients are trustworthy and pushes their updates to the other players’ clients. With this model, a player could easily spoof a game state (I have full health and infinite ammo) and send it to the server (OK, sounds good! Let’s hook you up), which would send the updated game state to the other (now screwed) players.

A much more secure model is an authoritative server. The client is only allowed to capture player inputs or actions (Move me forward twice). The server then receives that input and figures out how to update the game state (You were there, let’s move you forward twice, now you’re here), which then sends the updated game state to the other clients (Player 1 is here now).

The Problem

While much more resistant to cheating, this model has a few issues. Because the server is authoritative, the client has to wait to get the updated game state from the server to figure out what to do with the user’s input. Let’s assume it takes 250ms for a round-trip between client and server; that means our user tries to move forward, but their character doesn’t begin to move until getting the updated game state from the server 250ms later. In a real-time multiplayer game, this kind of latency is unacceptable.

The Solution

If our game is deterministic — a given input will always produce the same output — we can resolve this issue by simulating the results of the input immediately on the client side as soon as the user tries to do something. Because the game is deterministic, we can take the logic used by the server to update the game state and replicate it in the client, and safely assume that the two will produce the same new state. Now, when the user tries to move forward, they will move forward immediately in their client. When the server responds with the new game state 250ms later, it should be identical to the client’s new state, and the client’s game state will not need to be updated.

A New Problem

If client-server round-trip time is lower than the time it takes to animate the player’s movement, this solution works relatively well. But when that isn’t the case, and the user moves more than once, things can get weird. Let’s assume it takes 50ms for the client to animate the player moving forward one square, and the round-trip time is 150ms:

0ms   - Input: Move forward 1 square
50ms  - Client has moved player to (1, 0)
50ms  - Input: Move forward 1 square
100ms - Client has moved player to (2, 0)
150ms - Server responds, says player is at (1, 0)
200ms - Server responds, says player is at (2, 0)

Because of the delay, the user sees himself move forward as requested, but then he briefly moves backward, and then forward again. The server and client are both right, just not at the same time. We need to do something to account for the lag, but what? We’ll address that in a future Data Dump, Real-time Multiplayer Networking: Client-Server Reconciliation.