GMS 2 Why is my client side interpolation glitching out? This bug is keeping me up night for too long.

Armyk

Member
video:
I have recently dabbled into networking and multiplayer and I just can't figure out why is my client side interpolation glitching out. (the video)

The client sends keyboard inputs to the authoritative game server, the server receives and processes the inputs fine, then it sends the "snapshot" (x and y position of the player) back for the client to process it.

I am storing these snapshots in a ds_list along with the time of receiving.

Code:
//vars
        var client_id = buffer_read(server_buffer,buffer_u8);
        var px = buffer_read(server_buffer,buffer_u16);
        var py = buffer_read(server_buffer,buffer_u16);
        var receive_tick = buffer_read(server_buffer,buffer_u16);
           
        //player
        var player = connected_players[? client_id];
        if (player != undefined) {
            var size = ds_list_size(player.movement_snapshots);
            if (size > 1) {
                var last_snapshot = player.movement_snapshots[| size-1];
                if (receive_tick > last_snapshot[3]) {
                    ds_list_add(player.movement_snapshots,[px,py,current_time,receive_tick]); //[x,y,receive_time,server_tick]
                } else {
                    scrDebugMessage("receive tick ("+string(receive_tick)+") lower than server tick("+string(last_snapshot[3])+")");
                }
            } else {
                ds_list_add(player.movement_snapshots,[px,py,current_time,receive_tick]); //[x,y,receive_time,server_tick]   
            }
        }
After that, the player starts interpolating between these snapshots. It works well, but it glitches out sometime for some reason that I cannot find.
Code:
//vars
var size = ds_list_size(movement_snapshots);
var delay = 100;
var render_time = current_time - delay;

//exit if no snapshot
if (size < 1) return;

//just teleport
if (size == 1) {
    var p = movement_snapshots[| size-1];
    x = p[0];
    y = p[1];
    return;
}

//cleanup old snapshots from the list
var nearest_future_snapshot = movement_snapshots[| 1];
while (size > 2 and render_time > nearest_future_snapshot[2]) {
    ds_list_delete(movement_snapshots,0);
    nearest_future_snapshot = movement_snapshots[| 1];
}

//find nearest past snapshot and nearest future one
var nearest_past = movement_snapshots[| 0];
var nearest_future = movement_snapshots[| 1];

//lerp between the old x and the future x
var lerp_time = min((render_time - nearest_past[2]) / (nearest_future[2] - nearest_past[2]),1); //(render_time - past_time) / (total time between future and past snapshot)
x = lerp(nearest_past[0],nearest_future[0],lerp_time);
y = lerp(nearest_past[1],nearest_future[1],lerp_time);

//debugging
hsp = x-last_x;
last_x = x;
if (last_snapshot != nearest_past) show_debug_message("[" + string(render_time) + "]" + " hsp:" + string(hsp) + " size:" + string(size) + " full time:" + string(nearest_future[2] - nearest_past[2]) + " lerp_time:" + string(lerp_time) + " x:" + string(x) + " last_x:" + string(nearest_past[0]) + " dest_x:" + string(nearest_future[0]));
last_snapshot = nearest_past;
This is the log file I am creating the hsp jumps high somethimes causing the glitch:
Code:
[23918] hsp:3.91 size:5 full time:34 lerp_time:0.50 x:1032.50 last_x:1029 dest_x:1036
[23952] hsp:2.43 size:5 full time:49 lerp_time:0.35 x:1038.43 last_x:1036 dest_x:1043
[23985] hsp:2.35 size:5 full time:34 lerp_time:0.03 x:1043.21 last_x:1043 dest_x:1050
[24034] hsp:7 size:5 full time:16 lerp_time:1 x:1057 last_x:1050 dest_x:1057                  THIS ONE IS CAUSING THE GLITCH
[24051] hsp:2.33 size:5 full time:51 lerp_time:0.33 x:1059.33 last_x:1057 dest_x:1064
Can you please help me? Thank you.
 
Last edited by a moderator:

Armyk

Member
Based on the debug messages, it looks like it happens when one packet arrives earlier than normal, which is weird, because i am sending them at a constant rate (30 times a second)
 

Armyk

Member
TCP/IP does not guarantee packets arriving in same order as they were sent.
Hi,
Thank you for your reply.
I am sending the data through udp and am checking if the last received snapshot server time is greater than the last received one, so it only allows packets in the right order to come through. What is weird is, that sometimes they come earlier
 

chamaeleon

Member
I assume you have noticed that the tick value difference is roughly double for the odd one compared to the previous entry, and the difference between others, indicating you dropped an entry from your list at that point. This seems to throw off your lerp_time calculation, due to the computed value being larger than 1, so 1 is used for it. I can't say what effect dropping an entry from your movement snapshot list has exactly, but maybe you'll think of something. Unfortunately I can't visualize in my mind what your various movement snapshot times and nearest past and future times are, in correlation to current_time and render_time..
 

Armyk

Member
I assume you have noticed that the tick value difference is roughly double for the odd one compared to the previous entry, and the difference between others, indicating you dropped an entry from your list at that point. This seems to throw off your lerp_time calculation, due to the computed value being larger than 1, so 1 is used for it. I can't say what effect dropping an entry from your movement snapshot list has exactly, but maybe you'll think of something. Unfortunately I can't visualize in my mind what your various movement snapshot times and nearest past and future times are, in correlation to current_time and render_time..
Hi, thank you very much for your input. Upon further research, the lerp calculation has nothing to do with it. The position applying is messed up somehow.
Here in this video, the black "shadow" of the player is getting set by the newest server position. As you can see, sometimes the shadow jitters back and forth.

What is even weirder is, that when I am in full screen, it jitters waaay less. I have no idea why does the screen have anything to do with this.
 

chamaeleon

Member
Any chance more than one data point is provided in one async callback and you only read one from the provided data buffer?
 

Padouk

Member
Question.. What is that var delay = 100; of yours? is it 3*33ms = 100ms to include previous, present past entry?
The way I understand your code, you expect server updates at a constant rate. (which is never really going to happen under real circumpstances)

Jittering bug is in your lerp_time. Your code expect the difference between local time (current_time) and your servertime (receive_tick) to be constant which is not always true.

Code:
var lerp_time = min((render_time - nearest_past[2]) / (nearest_future[2] - nearest_past[2]),1); //(render_time - past_time) / (total time between future and past snapshot)
I'm also not quite sure about this one, mixing local time to server time seems weird. Did you mean to use [1] instead? (render_time - nearest_past[1])

I like how your receive the server time in [2] (through receive_tick) It definitivly helps you in some circumstances.
I like how you keep track of local package time in [1] (through current_time) It definitivly helps your troubleshoot.
Something feels wrong as to how you mix and match them.


I might be dead wrong, but at least that would be some numbers to help your troubleshooting your issue:
Based on the debug messages, it looks like it happens when one packet arrives earlier than normal, which is weird, because i am sending them at a constant rate (30 times a second)
There are two wrong statement here.
A) In networking there is no such thing as "Constant Rate". Even if your server send 30 times as second (1000/30 = 33ms) there's no garanty for your package to arrive every 33ms.
B) You mean "late" not "earlier"

According to your log, the glitching package is processed "late"
23918 -> 23952 => 34ms
23952 -> 23985 => 33ms
23985 -> 24034 => 49ms
24034 -> 24051 => 17ms
24051

OK let's forget about latency for now. You are in a cotton-candy world, on your localhost with 0 latency and no network saturation.
Let's assume you are trully sending and receiving your package every 33ms (1000/30 = 33ms)

Let's also assume you your game is running at 60fps so it updates every 16 ms (1000/60 = 16ms)


receive at 0 ms => render at frame 0
receive at 33ms => render at frame 2
receive at 66ms => render at frame 4

..

receive at 462ms => render at frame 28
receive at 495ms => render at frame 30
receive at 528ms => render at frame 33 <- here your current_time - pass receive_tick is disproportionate to usual cases 3 frames instead of 2, which throws off bounds your fixed 100ms
receive at 561ms => render at frame 35
receive at 594ms => render at frame 37

--

Right here you can see some lerp_time will be updated ever 3 frames instead of 2. giving you a wrong delta.
Now .. throw in some latency in the loop and you get the same thing amplified where some packages will be processed 1 or 2 frame lates.


Hope this helps
 

Padouk

Member
that's a good call.
You can receive more than one package at a time in your Network Async event.

in your async_load look at the size attribute.. if it's twice the size you are expecting, you will need to read 2 packets. (run your 4 buffer_read n times)
It's good practice and you should definitivly do it... However based on your symptomes i don't think that's what you are experiencing here. That one would be noticable if you had a lot of packages "lost"
 

chamaeleon

Member
What do you mean by datapoint? Like if I am reading the provided buffer the wrong way?
If you don't have a loop over the buffer in the network async event to read all send packet data, you may miss data. One async event may get the data for more than one sent packet.

Edit: @Padouk explains it more coherently than I.
 

Armyk

Member
Question.. What is that var delay = 100; of yours? is it 3*33ms = 100ms to include previous, present past entry?
The way I understand your code, you expect server updates at a constant rate. (which is never really going to happen under real circumpstances)

Jittering bug is in your lerp_time. Your code expect the difference between local time (current_time) and your servertime (receive_tick) to be constant which is not always true.



I'm also not quite sure about this one, mixing local time to server time seems weird. Did you mean to use [1] instead? (render_time - nearest_past[1])

I like how your receive the server time in [2] (through receive_tick) It definitivly helps you in some circumstances.
I like how you keep track of local package time in [1] (through current_time) It definitivly helps your troubleshoot.
Something feels wrong as to how you mix and match them.


I might be dead wrong, but at least that would be some numbers to help your troubleshooting your issue:
There are two wrong statement here.
A) In networking there is no such thing as "Constant Rate". Even if your server send 30 times as second (1000/30 = 33ms) there's no garanty for your package to arrive every 33ms.
B) You mean "late" not "earlier"

According to your log, the glitching package is processed "late"
23918 -> 23952 => 34ms
23952 -> 23985 => 33ms
23985 -> 24034 => 49ms
24034 -> 24051 => 17ms
24051

OK let's forget about latency for now. You are in a cotton-candy world, on your localhost with 0 latency and no network saturation.
Let's assume you are trully sending and receiving your package every 33ms (1000/30 = 33ms)

Let's also assume you your game is running at 60fps so it updates every 16 ms (1000/60 = 16ms)


receive at 0 ms => render at frame 0
receive at 33ms => render at frame 2
receive at 66ms => render at frame 4

..

receive at 462ms => render at frame 28
receive at 495ms => render at frame 30
receive at 528ms => render at frame 33 <- here your current_time - pass receive_tick is disproportionate to usual cases 3 frames instead of 2, which throws off bounds your fixed 100ms
receive at 561ms => render at frame 35
receive at 594ms => render at frame 37

--

Right here you can see some lerp_time will be updated ever 3 frames instead of 2. giving you a wrong delta.
Now .. throw in some latency in the loop and you get the same thing amplified where some packages will be processed 1 or 2 frame lates.


Hope this helps
Hello, thank you SO much for your help. I probably failed at explaining how "my" system works. (My in double quotes, because the theory is from here: Entity Interpolation - Gabriel Gambetta)
You never know when a packet will arrive, or if it will ever arrive. The delay variable stands for how far in past (in ms) do I want the actual rendering of the points received to render. So it's not actually realtime and the player sees the server's positions - ping - 100 ms delay in the past. Upon further research, I found that the lerp time equation isn't actually the one causing trouble, but it's the jumpiness of the positions being sent. If I had zero interpolation, yes it would be "laggy", which is to be expected, but also jittery (left to right), which is not to be expected.
 

Armyk

Member
The async load buffer reading might actually make sense! I didn't really think that two packets could arrive on it at the same time. That's maybe why it works better in fullscreen, because the computer let's the game have more resources and therefore, it doesn't jitter that much, because it's faster.
I will look at the async size and write you back.
 

Armyk

Member
I have logged the async[? "size"], and the size stays the same all the time.
Here is the video of me showing how the fullscreen affects it:

As you can see, when I am windowed, the black shadow is jittery. But when I am in fullscreen, it isn't jittery at all
 

Armyk

Member
It fixed itself on its own. Still have no idea what caused it, but it was not the networking side of things
 
Top