Skip to content

feat: Durable spawn tick for synchronizer nodes - #620

Merged
elementbound merged 8 commits into
foxssake:mainfrom
aromancev:fix/past-spawn
Jul 7, 2026
Merged

feat: Durable spawn tick for synchronizer nodes#620
elementbound merged 8 commits into
foxssake:mainfrom
aromancev:fix/past-spawn

Conversation

@aromancev

@aromancev aromancev commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Currently, a node spawned in the past does not simulate correctly which leads to desync between different clients.

Failure mode

  1. Client fires a projectile at tick T.
  2. Server receives client input for tick T while the current tick is T + 1. Fires the projectile at tick T + 1.
  3. The projectile on the server is alive a tick later than on the client and it simulates incorrectly (it should've been already moving for one tick).

This failure has two components:

  1. It's ok to spawn the projectile later, but we need to set the correct spawn tick for the liveness check + run resim from that tick.
  2. If we only do [1], the simulation will corrupt the initial subject state. Network history will not revert it because it doesn't exist for ticks before subject was registered. This will lead to the projectile "skipping ahead" on some clients and hitting earlier than it's supposed to.

This change effectively does two things:

  • Ensures the spawned node is alive and resimulated from the correct tick.
  • Seeds network history for the spawn tick with unmodified values to prevent state corruption.

Note: spawn_tick is exposed on both synchronizers to control the exact spawn tick. It defaults to NetworkRollback.tick so simply spawning the node during the rollback simulation will work correctly. But if the consumer doesn't want to spawn the node during rollback, they can set spawn_tick to whatever tick they consider as the first alive tick when spawning the node (before the deferred process_settings executes).

Comment thread addons/netfox/rollback/predictive-synchronizer.gd Outdated
@aromancev
aromancev force-pushed the fix/past-spawn branch 3 times, most recently from 86810c7 to adc2962 Compare June 28, 2026 09:43
@elementbound elementbound changed the title fix: state sync for subjects spawned in the past feat: Durable spawn tick for synchronizer nodes Jul 4, 2026
@elementbound
elementbound merged commit b4041af into foxssake:main Jul 7, 2026
4 checks passed
elementbound added a commit that referenced this pull request Jul 28, 2026
Since the spawn fix #620, when something is spawned it becomes active on
the same tick by default. That means we have to resimulate the same tick
with that node already participating. This is ok from the correctness
point but forces an unnecessary resimulation.

Even in single player without any peers connected this will double the
work. In real network conditions, it just adds one extra simulation tick
of load.

The fix is to make spawned objects live on the next tick. That also
exposes another problem which is that the correct state seeded before
gets rewritten on subsequent `spawn` calls even if they provide the
correct spawn tick.

So the second part of the fix to maintain a consistent state when
`spawn` is called again:
* Align `spawn_tick` with the new spawn tick (strictly not necessary but
is more logical).
* Clear previous despawn as it may no longer be relevant or worse be
earlier than the new spawn.
* (critical) push the new initial state again for the new spawn tick
since it will be erased by resimulation from the previously seeded state
that may no longer be correct.

Specifically, in my game I observed a stale player position being
retained and hence leading to a wrong projectile initial position.
Updated player position would arrive on the next tick and should be able
to correct the previous one.

---------

Co-authored-by: Tamás Gálffy <ezittgtx@gmail.com>
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.

2 participants