Lag Compensation Attempt
Inspiration/Idea: Source Multiplayer Networking: Lag Compensation
A top down Action RPG is a bit different than any Source Engine game, and I can't yet program like anyone at valve, but nonetheless I've implemented a version of lag compensation specific to this game that appears to work.
The player can attack an NPC on his/her own screen by moving close and taking a slash. Unfortunately, by the time the player's attack is heard by the server the NPC has moved a bit. On LAN or with < 50 ping this is no problem. However depending on the speed of the NPC and the latency of the player's connection it may appear to the server that the player has missed the NPC despite standing right ontop of it. This problem is worse on slow connections or with fast NPCs.
More Specifics
In the case of my prototype the lag becomes evident when trying to chase an NPC at ~300 ping. Despite sticking right behind the target, the player's character won't take a swing until practically standing on the target. And in my fancy "throttle the updates" cases the perceived position of characters gets very far from their server positions and the player won't attack at all.
Attempted Compensation, w/ pseudocode
(image: purple box collides with the red box denoting valid attack range)
The key to lag compensation is for the server to validate the attack (or other interaction) based on what the player was looking at -- something slightly in the server's past. How far in the past exactly? Well it is at least the length of a ping. In the case of this prototype it is approximately the length of a ping plus however much throttling is occurring. If the ping is 300 ms, and updates are sent lazily every 400 ms (hypothetical bad lag scenario), then after entity interpolation the player is seeing objects at least 700 ms behind their server position. Here's how I make and apply that estimation***
// how far behind the player is in milliseconds var totalDelay = player.ping + player.networkTickLength // convert totalDelay from milliseconds to serverTicks var ticksAgo = floor(totalDelay / serverTickLength) // the remainder of the above division, as // a fraction (e.g. 0.87 ticks) var portion = (totalDelay % serverTickLength) / serverTickLength // calculate position of the NPC in the past, by // interpolating between two past positions // position of the NPC @ ticksAgo - 1 var positionA = gameState.getHistory( currentTick - ticksAgo - 1, npc.id ) // position of the NPC @ ticksAgo var positionB = gameState.getHistory( currentTick - ticksAgo, npc.id ) var xy = lerp(positionA, positionB, portion)
Now xy is a guess of where the NPC was right when the player attacked/interacted/etc. The real version of the code then goes on to assess whether the player's attackHitBox intersects the NPC's collisionHitBox, and you can imagine the rest.
*** take with a mountain of salt, I'm making this up as I go... but so far it does appear to extend the range of latencies with which the game feels playable
Suggestions / critique appreciated














