rollback netcode in Godot 4 — anyone actually done this or is it just theory on reddit

265 views 11 replies

Been trying to implement proper rollback netcode for a 2D fighting game prototype in Godot 4 and I'm at that stage where I've read everything there is to read and written almost nothing that actually works.

The concept is clear enough: capture inputs every frame, send them to the peer with the frame number attached, predict forward if their packet hasn't arrived yet, roll back and resimulate when it does, compare state hashes to detect divergence. That part makes sense. The implementation is where things fall apart.

The core issue is determinism. For rollback to work correctly, two machines running the same input sequence from the same starting state have to produce bit-identical results every frame. Godot 4's physics engine gives you no such guarantee. Floating point behavior varies by platform, physics steps accumulate drift, and if you're using any built-in 2D physics for movement or collision you're probably already desyncing before round one starts.

The options I've identified so far:

  • Go fully deterministic: fixed-point math for all game-state values, no physics engine, custom AABB collision. Painful but the only real guarantee.
  • Keep Godot's physics, accept desyncs, implement aggressive state reconciliation and hope for the best.
  • Move the simulation layer into C# and rely on explicit determinism controls there, using GDScript only for rendering.

For a simple fighting game where I can avoid the physics engine entirely, option 1 seems feasible. Fixed-point integers in GDScript aren't pretty but they work. I've been using a FP class with a scaled integer representation and it's manageable for a small action set. The concern is rollback budget: how many frames can I realistically resimulate per _process call before there's visible hitching? At 60fps you have ~16ms and a full resim of even a simple frame takes a non-trivial chunk of that.

There's at least one Godot rollback addon that takes a hash-verification approach: peers exchange state hashes each frame, detect divergence, and rollback further when it happens. Doesn't solve determinism, just catches when it breaks. Useful, but it's treating the symptom.

Has anyone actually shipped or seriously prototyped rollback in Godot 4? How did you handle the determinism problem? Did you end up writing your own physics layer? And how deep did your rollback window actually need to go in practice?

Replying to StormFox: not just theory. there's a Godot addon called Godot Rollback Netcode by snopek-g...

yeah the snopek addon is solid as a starting point. biggest thing i learned: your _save_state() and _load_state() implementations need to be exhaustive. missed one exported var on my player node and spent a week wondering why replays were subtly wrong. there's no safety net. it just silently desyncs if you forget something. no error, no warning, just wrongness creeping in over several seconds.

also watch out for nodes that reference scene-level singletons in their state. anything non-deterministic in there will eventually ruin your day.

detective magnifying glass finds nothing
Replying to ZenithStone: the determinism audit is the part nobody warns you about. once rollback is basic...

The RNG seeding issue is sneakier than it looks. In Godot 4 you can end up with multiple RandomNumberGenerator instances scattered across nodes, and any constructed with defaults will seed from OS time. What worked for us: wrap every RNG call behind a single global seeded deterministically at match start, then grep the whole project for direct RandomNumberGenerator.new() and bare randf() calls outside that wrapper. Found three independent instances, including one inside a particle effect that didn't touch gameplay state but was causing divergence in our debug state-comparison tool and took an embarrassingly long time to isolate.

Float accumulation is harder to fully solve. Once you're calling trig or engine physics functions you're largely at Godot's mercy on precision. We ended up accepting small drift and leaning on periodic full-state syncs to compensate rather than trying to eliminate it entirely.

not just theory. there's a Godot addon called Godot Rollback Netcode by snopek-games that handles a lot of the heavy lifting. it manages serializing/deserializing state and the rollback window; you implement _save_state() and _load_state() on your nodes and it handles the rest. works over ENet or WebRTC. for a 2D fighter it's probably the closest to a production-ready starting point that exists for Godot 4 right now.

the mechanism itself isn't the hard part though. it's making your game state fully deterministic. physics queries, RNG, anything engine-side you don't explicitly control becomes a landmine. that's where most of my attempts fell apart before i found the addon and actually read about what determinism requires in practice.

debugging at 2am

the determinism audit is the part nobody warns you about. once rollback is basically working you realize your entire codebase wasn't written with determinism as a constraint. rng seeding, float accumulation across frames, anything that reads real wall time. all of it is a problem. i ended up rewriting more non-netcode code than netcode code, which felt wrong but was definitely correct.

this is fine fire

Replying to BlazeRay: the 'missed one entity' issue nearly made me abandon the whole thing lol. i had ...

The pooled entity gap is basically guaranteed unless you build something that catches it early. What worked for me was a debug-mode assertion that walks the full node tree from a known state root and checks every active node against a registry of objects that must appear in saves. Slow enough that you gate it behind a flag, but running it for a few sessions surfaces all the gaps before they turn into 2am desync mysteries.

The nastier version of this problem is when the missing node is conditionally active. It gets included in saves when active but silently skipped when inactive, so your snapshots are inconsistent depending on game state at rollback time. That one took way longer to diagnose than the straightforward missing-node case.

Replying to ByteHaze: One thing to figure out before going deep on Godot Rollback Netcode: whether you...

for a 2D fighter specifically i'd push hard toward per-tick rollback. predictive interpolation means players can briefly see frames that causally never happened, fine for a racing game, genuinely bad for a fighter where frame-perfect reads are the whole point. if someone whiff-punishes based on a ghost frame that gets corrected a moment later, that's a trust problem with your netcode. rollback keeps both clients in causally valid states at the cost of implementation complexity, and for the genre that tradeoff is almost always correct imo.

Replying to BlazeRay: the 'missed one entity' issue nearly made me abandon the whole thing lol. i had ...

lmao the pooled entity problem is like a rite of passage. i had the exact same thing with a pooled shockwave effect that had its own hitbox as a child node — spent an afternoon convinced the desync was in my input prediction when it was just... that node never getting walked during state serialization. and the bug only showed up 6-8 frames after the missing entity would have mattered, so tracing it back was genuinely miserable. the fix was like 4 lines. the debugging was most of a day.

detective conspiracy board pointing

Something that caught me off guard: the transport layer matters more than I expected once you're dealing with real network conditions. I was using Godot's built-in ENet wrapper and everything looked fine in local testing, but simulated packet loss was masking a bug in my input frame counting. Packets arriving out of order were being fed directly into the rollback system without reordering first.

Adding a sequence number to each input packet and a small receive buffer that sorts before processing fixed it. Obvious in retrospect, but it only surfaced once I added artificial jitter and reorder simulation to my test setup. If you're not running those conditions early, you're probably sitting on something similar.

Replying to NeonShade: yeah the snopek addon is solid as a starting point. biggest thing i learned: you...

the 'missed one entity' issue nearly made me abandon the whole thing lol. i had a pooled bullet that wasn't being included in state saves because i forgot it lived one node level deeper than where i was iterating. rollback would correct every other entity perfectly and the bullet would just... teleport across the screen. took me almost a full day to even identify it as the bullet causing it.

switched to recursively walking the scene tree and collecting every node that implements _save_state() rather than maintaining a manual list. much harder to accidentally miss something that way.

object teleporting unexpectedly game bug
Replying to VelvetMoth: for a 2D fighter specifically i'd push hard toward per-tick rollback. predictive...

yeah for 2D fighters it's basically non-negotiable. the whole genre runs on frame-perfect interactions, and if a player sees a frame that causally never happened, their shoryuken looks like it whiffed when it actually connected and the game feels broken. the snopek addon is implementing the same ideas as GGPO anyway so you're already on the right architecture, just gotta commit fully to determinism and exhaustive per-tick state saves. no shortcuts on that part.

One thing to figure out before going deep on Godot Rollback Netcode: whether your game actually needs per-tick rollback or just predictive interpolation with correction. For a 2D fighter, rollback is the right answer, input windows are tight and mispredictions are very visible. But the implementation cost is real, and if prediction is wrong frequently enough, the visual artifacts can actually be more jarring than a slightly higher input delay with clean interpolation.

Not saying don't do rollback, for a fighting game it's genuinely the correct choice. It's just the hardest solution to a problem that sometimes has easier ones. Make sure you know which one you actually have before committing.

Moonjump
Forum Search Shader Sandbox
Sign In Register