Rollback Netcode

completely input based 1v1 “replication”, constant 3 frames of input delay.

for those unaware, rollback is networking model now extremely popular with fighting games
explained in short, long before time had a name, every fighting game used delay based netcode, an unfavourable system prone to inconsistent input delay and stuttering

rollback solves many problems, allowing the 2 clients to go out of sync instead of pausing the game, waiting for a new input.
during this desync, your local client will instead predict your opponent’s input for the next frame(s)
once your client does receive input, it will roll the gamestate back to a previous frame, replay with the proper inputs, and continue

thats rollback in a nutshell, a lot more goes into the actual programming of the gameplay itself and handling gamestates, as you can not use “traditional roblox programming” for this to be possible, as an example, i had play animations trough keyframesequences, with my own interpolator, to make sure i can consistently get per-frame animation poses.

with all the knowledge above, this is my first iteration

(6Flatency from client > server)
(12F latency from client > client)
(3F local Input delay)

i still have some super minor desync issues, but once those are resolved, it really is complete.

thing to note:

  • rollback netcode is originally a peer 2 peer model, to replicate this in roblox, i have to run trough the server, effectively doubling the latency between 2 clients. thats just the way it is

hoping to use this to bring viable, traditional fighting games to the platform!

16 Likes

I’ve got a similar system up and running ahead of unreliable remotes releasing soon.
Are you using a fixed timestep? How are you saving, reloading, and resimulating game state?

1 Like

As per standard, i separated my logic and what happens on screen, each frame, a massive table containing all the data of the active match (what frame of animation, position of the character, velocity, previous inputs, time left, you name it) is being ran trough a bunch of different module scripts in order, each editing their respective part of the gamestate table (for example detect inputs > physics > animation)

the then resulting gamestate, still being a massive table, is saved
because of that, restoring an old gamestate is as simple as grabbing an old table instead.

because the logic and visuals are seperated, i can just repeatedly call my Simulate(gamestate,p1input,p2input) function on a gamestate table to simulate x amounts of frames.

as for fixed timesteps, i wish i could, but because on roblox you have mobile players running on 20fps, and pc players on 240, i cant assume a fixed timestep, instead I am using anti-rifting logic like GGPO to account for the difference in update speeds as much as possible. other than that, its out of my control

1 Like

Interesting. A YouTuber has a series on adding rollback to godot and they have a save and load function on every object, and I just stole that. For the fixed timestep, you don’t hope the player’s fps is 60, you just force it through an accumulator. I thought GGPO also worked off a fixed 60 frames a second but I haven’t really looked through the code much.

what you said is probably exactly what the anti rifting solves, using some math with the latest remote frame and latest local frame itll force the faster stepping machine to slow down to match the slower machine since it cant send out updates any faster

1 Like


Update on the desyncing issues i had, its fixed now!

[ (F1 Correct) , (F2 Correct) , (F3 Incorrect) , (F4 Incorrect) , (F5 Incorrect) ]

i forgot to break the loop that checks for the deepest discrepancy meaning that if it were to loop trough the table above, it would state that the deepest discrepancy was at frame 5 instead of 3, causing the incorrect inputs of F3 and F4 to remain present in the simulation.

Silly mistake but atleast its all finished now, very proud of myself to say that i finally got this fully working

Fascinating. It must’ve been a ton of work to build the deterministic fighting game framework in the first place. I’ve always wondered, how do you get both clients to start on the first frame at the same time? I’d imagine that the server would send a signal to both clients indicating the “start” of the game, but since the packets travel at different speeds and travel different distances, how would you get both clients to start at the same time? Or is there some sort of time variable that’s the same across all machines and you just send a packet containing what time the game started at?

local function IS_TIME_SYNCED()
			remote_frame = lastremoteframe
			remote_frame_advantage = (remote_frame - local_frame) 

			local local_frame_advantage = local_frame - remote_frame 

			local frame_advantage_difference = local_frame_advantage - remote_frame_advantage
			
			return local_frame_advantage < StateWrapper.MAX_ROLLBACK_FRAMES and frame_advantage_difference <= StateWrapper.FRAME_ADVANTAGE_LIMIT 
		end

every frame The following function is repeated until it returns true, so when one client starts ahead of the other, itll eventually get blocked by this function from proceeding. once it returns true, the clients are in sync

1 Like

I’m curious as to what information you pass between players, are you just passing the inputs with a timestamp of when the input was made?

This is super cool, I love this networking sort of stuff

Could you explain like why this is useful though like does it make the game feel snappier for the players and I don’t have the best knowledge about this networking stuff but does this have anything to do with something called a authortative server?

Hi there, the most important improvement here is an unchanging input delay that i have full control over, i set it to 3 frames for extra smoothness, but can set it to literally 0 delay at the cost of extra rollbacks required on average

Furthermore, your inputs take immediate effect, your hits are registered instantly, and your combos will always work

As for how this relates to the server depends very much on your usecase, as im doing 1v1, i practically have no need for the server at all. When scaling this to multiple players, you are going to see an authorative server to distribute the proper gamestate yes. For me the 2 players can figure out the proper gamestate together faster than the server can, so its left out for the most part

1 Like

Yep! Just the inputs and the frame of gamestate it was recorded on. This keeps packet sizes very low too :smile:

Here’s the basics of it:

  • Most fighting games run on a peer to peer network, so instead of relying on the server, the players are paired to eachother
  • To prevent the clients from de-syncing, older fighting games would wait until both players inputs were received before advancing the game every frame. This is what would lead to laggy, unresponsive game play.
  • Rollback netcode solves this by simply not waiting for the other players inputs to be received, it would keep running the client side simulation of the game until it finally received the input, then it would rewind all of the important parts (where both players are, what your health is at, etc) and using the up to date input. This is why it’s called rollback.
  • When the other players inputs haven’t been received yet, the game will just assume the inputs are remaining the same as they were last frame as (quite often) they will.
1 Like

Precisely! By sacrificing accuracy, and doing rollbacks, i can lay the focus on the responsiveness of your inputs


I had been working on something similar, all of my physics are calculated through math, no in-engine physics handling, and I use fixed point numbers to avoid replication issues

I still have issues with client de-sync, so the fact that you were able to solve this shows a lot of promise.

1 Like

@MemzDev @CasuallyCritical
Oh, thanks so much guys for explaining it but that begs the question, how does a game like Rocket League with a max of I think 6-8 players have their game feeling so snappy even when players are at much higher pings, I’m really interested in this network stuff!

I dont exactly know what rocket league does, but i remember it using about the same as Fortnite so ill just assume that for both

So they make use of an authorative server as mentioned, the server emits the gamestate to all the clients, clients send their info to the server for it to get taken up into the next gamestate and do their own local rollbacks to stay in sync to the server

Additionally, the server constantly predicts ahead of what the players will do, and the server’s prediction takes priority, which is when you start lagging on these games, you start getting teleported around to align to the servers predictions

But yeah really important difference is that server authorative systems like this rarely function on inputs, but gameplay info. Clients are sending info about their character, the server is returning info about the entire game. This is because these games allow variable frame rates, (doesnt assume locked 60 fps like fighting games do for gameplay accuracy), and this generally allows for some easy interpolation etc to keep things smoother aswell

1 Like

In order to keep the accuracy in my scenario i for 1 always asume 60 fps and write the game logic around that, so everything in the game uses fixed numbers instead of being tied to Deltatime, then the rollback will enforce that the 2 players are in sync, using the “fixed” framerate as a unit of time i can consistently get the same results on both sides

is it possible to make a peer to peer network in roblox

Not true peer to peer, everything still goes trough the server at some point. But its as close as it gets

1 Like