“By avoiding a future arrow from hitting your present self, you ensure your past self will dodge the present arrow.”
Suppose you are designing a networking system for a multiplayer game. One of the biggest factors in the quality of networking design is how smooth the game feels for its players. People are not happy when they see snapping, jittering or other unexpected non-smooth behaviours. Such things occur because of the latency over the internet, and games have to try to predict things that are unpredictable by nature (e.g. player movement). Each time the prediction is wrong, the user sees an unpleasant correction.
One way to reduce such negative effects is to try to mask the corrections, by using smoothing.
I propose another approach. We can try to rely less on information that is uncertain, and rely on what is known. The latency is still there, but we can work around it.
Imagine an internet game server with multiple players, where everyone has less than 100 ms round-trip latency (and under 50 ms single-way latency). Those are good internet conditions, but not too unrealistic for today.
That means each player can know exactly all other players have been 100 ms ago, without having to use prediction. Let’s render them there. The local player still sees himself at present time, but he sees all other players 100 ms in the past, 100% smoothly (ignoring dropped packets).
We want to let players aim at enemies, and not where they think enemies are due to their current latency (ala Quake). So the server performs hit collisions with regard to what the player shooting saw. Bullets travel in present time, but they collide with players’ positions 100 ms in the past.
All the above has been done before (see Half-Life 1). But here’s the kicker of this post.
What if you try to dodge a bullet by doing something within 100 ms of it hitting you. With the above system, you physically can’t. No action, however drastic, within 100 ms of the bullet hitting you can save you, since the bullet will still hit where you were 100 ms ago (before you took said action).
It’s not that big a deal for bullets, since they travel quite fast so there’s little you can do in 100 ms to change anything. We can accept that as is. But can we do better?
What if, instead of a bullet, we have a slower moving projectile like an arrow. The player might want to jump out of the harm’s way just before it hits them, just to spite their opponent. With the above system, they will see themselves clearing the arrow, yet it still hits their 100 ms in the past alter-ego and the player quits in frustration.
Yes, we can do better. We take advantage of the principle that player movement is not easily predictable, but arrow projectiles are (all you need are the initial conditions). That’s why we render other players 100 ms in the past, but we can try to render other player’s projectiles 100 ms in the future.
Now, when you see an arrow just about to hit your face, and you duck at the last millisecond, you’re actually ducking 100 ms before the real arrow will have hit you. By avoiding a future arrow from hitting your present self, you ensure your past self will dodge the present arrow.
- It’s hard to render the arrow when it is first fired, as we have to wait to receive the information about its initial conditions. But a long firing wind-up animation helps mask this.
- You’re less likely to dodge arrows that are shot from point-blank range, or bullets that are fast. That’s why it’s okay if you don’t see the bullet before it hits you.
- If you think the above is a problem, consider the real-life fact that if you hear the shot that was aimed at you, nothing you do from that point on (even if you have instantaneous reaction time) will help you, as that means the bullet has already hit you. Bullets travel faster than sound, just like they do faster than UDP internet packets.