Advertisement

Server-side client command extrapolation and reconciliation

Started by October 14, 2023 10:33 PM
3 comments, last by hplus0603 1 year, 2 months ago

I'm working on an authoritative multiplayer fps in Unity.

The client is sending commands to the server designated for specific ticks and the server sends back an offset to control how far ahead/behind the client's command clock should be based on how late/early the commands are arriving.

I found a GDC talk that describes adding server-side client command extrapolation and reconciliation.

In other words, when the server goes to execute tick 100 if the client's command for tick 100 has not arrived the server will reuse the last received command from the client assuming their input has not changed much. This is pretty normal from my understanding, but it goes further by rewinding/resimulating server ticks that had used old/extrapolated input when the actual (previously missing) command from the client does arrive. The period of time that they allow to be reconciled like this is limited to 50ms. Once it exceeds 50ms the ‘extrapolated’ commands become authoritative and will not be resimulated if they do later arrive.

https://www.gdcvault.com/play/1026833/Handling-Network-Latency-Variation-in​ (timestamp 9:10)

we want to avoid running out of input to simulate

there are several options the server can use

the server can extrapolate client input by taking the last known input from that client and executing it and taking the position and orientation and committing that to the world state and the antilag buffers that when clients are looking at that client moving around they see and can shoot at the client effectively. it is not committed in this case to the player's view of themselves, because committing that extrapolated state to the player's view and then sending that back to the player would result in the player's own view of themselves hitching because they would not have simulated that extrapolated movement. they are not experiencing their own network issue. and when the actual input does arrive from the player with extrapolation you unwind or discard that extrapolation and you apply the real input

They imply in the presentation that they even extrapolation/prediction over buffering to keep the latency low and the tick offset always on the bleeding edge. But what they don't cover is what limits are imposed during the extrapolation/reconciliation periods.

Approach A:

Limit extrapolation to movement only. Could result in other players being killed during the reconciliation which might feel bad? But should favor the shooter apply here?

extrapolate:

  • move (last received move input)

reconcile:

  • move (actual move input)
  • fire (actual fire input)

Approach B:

Allow movement + gunfire during extrapolation. Reconcile move input but ignore actual fire input. Keep fire latency down for remote players.

extrapolate:

  • move (last received move input)
  • fire (last received fire input)

reconcile:

  • move (actual move input)

Issues

And that's not to mention the other issues that could arise from the reconciliation (e.g. delta compression against snapshots that were already sent but have now been resimulated… possibly bringing dead players back to life if the resimulated input would have moved the extrapolated player out of the way of danger, etc.)

I'm deep into this rabbit hole and looking for any feedback/thoughts about the practicalities of implementing something like this and what/if any limits should be imposed.

In general, you'll want to hide those kinds of problems with gameplay design, and delayed effects.

The classic illustration of this is a hit scan weapon is fired. The firing players hears the bang and sees the muzzle flash, but the illustration of the remote player doesn't actually show blood spray for a hit until the server has told them they hit. Eg, the interaction is split in two: the “I triggered the command” interaction which is immediate, and the “the command had an effect” interaction which is generated by the server.

And, if you extrapolate those effects, and then decide you want to undo that, you might delay displaying the actual taking-effect until the latency window has closed. This is the “most correct” way to do it, but of course it adds latency – there's no way around that. The alternative is, as you say, to let players “come back alive” based on delayed input. Which is totally doable, and also totally a thing. Network jitter is rare, and if a particular player has a lot of jitter, they will typically get a long initial clock delay, rather than a long recovery buffer.

enum Bool { True, False, FileNotFound };
Advertisement

@hplus0603 I think you're talking about client-side extrapolation/prediction. What I'm looking at is server-side extrapolation and reconciliation of client commands (which I haven't seen discussed outside of this GDC talk).

This is a shorter video that touches on the highlights of what I'm talking about (timestamped).

Most authoritative client-server architectures I've seen look something like this:

  1. Clients send their inputs to the server for tick 100
  2. Server processes tick 100
  3. If server did not receive input from client for tick 100 it will use last received input from client
  4. Server receives late input from client for tick 100. Server discards late input and tells client to adjust tick offset.
  5. That's it. If you did not get you input to the server in time for tick 100 that input is discarded

But in this model it looks like:

  1. Clients send their inputs to the server for tick 100
  2. Server processes tick 100
  3. If server did not receive input from client for tick 100 it will use last received input from client
  4. Server receives late input from client for tick 100. Server will resimulate tick 100 - current tick using actual input.
  5. That way even if your input is late it still has a change to get processed. Favoring a bit of error/correction in exchange for less latency.

All of this is server-side, nothing client side. Hope that helps explain what I'm trying to do better!

I'm talking about both, because they work together. The splitting of interactions in two parts will hide whatever the re-processing delay is for late arrivals on the server. Or another way to think about it: the delay to commit, could also be used as extra queuing delay before you commit; exactly where you put it, determines what the user will see in different scenarios.

One mechanism that takes this to the extreme, and just re-simulates everything all the time, is the fighting game approach for GGPO: https://www.ggpo.net/​​ This works well when simulation is cheap. The game design in a fighter typically has “wind-up” animations, which can serve to mask the latency (although there's an argument that an opponent seeing less of the wind-up gives them less time to react and thus it's not perfect.)

enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement