Glitchy client-side prediction

Hi All,

I’m trying to put together a small project, focusing only on syncing player movement. I’ve decided to go with a server-authorative approach, where the client only submits their inputs, the server updates the game world based on them, and responds with the updated game state.
This works well if you have zero latency, but once you do, movement feels sluggish, because you have to wait out the roundtrip from client to server then back until your character actually starts moving.

To combat this, I’ve decided to implement client-side prediction, and this is where things got messy.
Here’s my implementation, describing what I’ve done and what are its issues: GitHub - elementbound/netwind

Approach
Basically I keep a cache of game state and inputs for each network tick, on both the client and the server.
The client submits their inputs marked with the tick index on each network tick, then simulates the results locally.
The server receives the inputs, updates the game, and responds with the game state for the given tick. If the input comes from the past ( which it does, because of latency ), the server rolls back the game state and simulates from there to the present tick.
The client receives the actual state from the server and saves it into a cache. Since that state almost always is from the past, it rolls back the game state and simulates the local player based on the recorded inputs to the present tick.

Issue
The issue is that even with interpolation, the player jumps around, seemingly due to the server and client arriving at different game states somehow.

As you can see on the right, the client is jumping around, especially when turning. It’s running at a simulated 50ms latency, which I consider a very good case.

So all in all, I’d be grateful for any clues on where I might’ve gone wrong with my implementation. Any other comment/feedback is welcome to the repo as well. I’m planning to update this and keep it open-source, so if I manage to figure out how to solve this, it can potentially be used as a starting point.

This post came out a bit lengthy, so I’m happy to adjust this post if something’s misleading/unclear/missing.

Thanks in advance!

1 Like

Just a quick heads-up, the TickHistoryBuffer implementation had some issues ( it added too many empty entries when setting a new tick ). I’m still experimenting with it, will update the repo once I work it out.
This is where not writing unit tests bites you in the back :slight_smile:

how are you correlating your client side packets / ticks to the server side?

Thanks for checking in! I’m using NetworkManager.LocalTime.Tick to index the updates if that’s what you mean.

In the meantime I’ve found the issue; I’ve documented it in the repo’s README, but here’s a tldr:

  • My TickHistoryBuffer implementation had a few dumb bugs
  • The server was always resimulating up to the current tick, even though it didn’t have inputs for all those frames, while the client did. And even though the client had more info, the server’s speculative states were considered to be the truth.