Skip to content

Conversation

@chris-ruecker
Copy link

Hi, i am currently creating a game, and am using netfox for netcode.

During testing, i noticed that the player connecting as client to the game saw the remote player's movement very smoothly.
But, on the hosts side, the movement of the clients character was slightly jittery. I asked claude to check if he could find the issue, and he came up with the solution below, which seems to have fixed the issue for me.

I don't quite understand the code of netfox, but maybe you can determine if this is a valid issue/fix.

Problem

When using TickInterpolator with RollbackSynchronizer, remote player movement appears jittery on the host, even with constant input (e.g., holding
a direction key). The client observing the host sees smooth movement, but the host observing the client sees stuttery/jittery motion as if discrete
tick updates are visible.

Root Cause

Both TickInterpolator and NetworkRollback connect to NetworkTime.after_tick_loop:

  • TickInterpolator._after_tick_loop() records state snapshots for interpolation
  • NetworkRollback._rollback() runs the rollback loop, which triggers RollbackSynchronizer._after_rollback_loop() → apply_display_state()

The execution order depends on signal connection timing. If TickInterpolator runs first:

  1. after_tick_loop fires
  2. TickInterpolator records state (position X)
  3. NetworkRollback._rollback() runs
  4. RollbackSynchronizer applies display state (position Y)
  5. During _process(): interpolates toward X, but actual position is Y → jitter

Why host→client is jittery but client→host is smooth:

  • Client viewing host: Client doesn't simulate the host - it receives authoritative state directly. The received state IS the display state.
  • Host viewing client: Host simulates client based on inputs, then TickInterpolator records state, then RollbackSynchronizer may apply a different
    display state. Mismatch = jitter.

Solution

Connect TickInterpolator to NetworkRollback.after_loop instead of NetworkTime.after_tick_loop when rollback is enabled:

func _connect_signals() -> void:
NetworkTime.before_tick_loop.connect(_before_tick_loop)
if NetworkRollback.enabled:
NetworkRollback.after_loop.connect(_after_tick_loop)
else:
NetworkTime.after_tick_loop.connect(_after_tick_loop)

This ensures state is recorded after the rollback loop completes and display state is applied.

Backwards Compatibility

Falls back to original after_tick_loop behavior when rollback is disabled, maintaining compatibility for non-rollback use cases.

…y state

Connect TickInterpolator to NetworkRollback.after_loop instead of
NetworkTime.after_tick_loop when rollback is enabled. This ensures
interpolation state is recorded AFTER RollbackSynchronizer applies
display state, fixing jittery remote player movement on the host.

The issue occurs because both TickInterpolator and NetworkRollback connect
to after_tick_loop. If TickInterpolator runs first, it records state before
the rollback loop completes and display state is applied. This causes the
interpolation target to be stale, resulting in visual jitter for remote
players observed on the host.

Falls back to original after_tick_loop behavior when rollback is disabled.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant