Skip to content

Fix Seamoth movement sync timing#2753

Open
mindless2831 wants to merge 1 commit into
SubnauticaNitrox:masterfrom
mindless2831:seamoth-sync-timing-fix
Open

Fix Seamoth movement sync timing#2753
mindless2831 wants to merge 1 commit into
SubnauticaNitrox:masterfrom
mindless2831:seamoth-sync-timing-fix

Conversation

@mindless2831
Copy link
Copy Markdown
Contributor

Summary

Fixes a Seamoth / remote vehicle movement desync where Seamoth movement could fail to appear on other clients, appear only intermittently, or replay later as delayed/catch-up movement.

During testing, the original behavior was broader than a simple one-way sync issue:

  • In the earliest tests, neither client could reliably see the other client's Seamoth movement.
  • Movement was sometimes not visible at all on the observing client.
  • In some cases, the observer would later see delayed/catch-up Seamoth movement after the driver exited.
  • After initial narrowing/debugging changes, the failure became more deterministic: the player who logged in second could usually be seen driving, while the player who logged in first could not be seen correctly by the later-login client.
  • Reversing login order reversed which player could be seen driving.

The final fix resolves both the broader Seamoth movement visibility failure and the later narrowed login-order-dependent failure.

Related issue:

Root cause

The vehicle movement path was mixing time bases and retaining stale interpolation state across ownership transitions.

Debugging showed that the issue was not primarily caused by server-side ownership denial:

  • The server granted EXCLUSIVE ownership to both drivers when requested.
  • The server broadcast ownership changes.
  • The observer client received ownership changes.
  • The observer client had the correct SeamothMovementReplicator.
  • Vehicle movement packets reached the observer client.

The remaining failure was in client-side movement timing and interpolation.

The vehicle broadcaster and replicator were using Nitrox TimeManager.RealTimeElapsed, while received vehicle snapshots were being scheduled in a way that could leave the observer client's interpolation buffer out of phase with the sender's movement stream.

As testing progressed, this created several visible failure modes:

  • Remote Seamoth movement not appearing.
  • Remote Seamoth movement appearing only in one direction.
  • Behavior changing based on login order.
  • Large stale interpolation buffers.
  • Delayed/catch-up movement replay after the driver exited.

Changes

1. Use Unity realtime for local vehicle movement broadcast cadence

MovementBroadcaster.Update() now uses Time.realtimeSinceStartup instead of TimeManager.RealTimeElapsed.

This keeps the local vehicle broadcast cadence tied to Unity realtime.

2. Use Unity realtime for movement replication

MovementReplicator.CurrentTime now uses Time.realtimeSinceStartup instead of TimeManager.RealTimeElapsed.

This keeps the receiver-side interpolation path on the same local Unity realtime basis as the broadcaster.

3. Schedule received snapshots relative to receiver time

Received snapshots are now scheduled with:

float occurrenceTime = currentTime + INTERPOLATION_TIME;

instead of scheduling from the packet timestamp plus measured latency.

This avoids treating another client's movement timestamp as directly comparable to the receiving client's local interpolation time.

4. Trim stale interpolation backlog

If the movement buffer grows beyond one broadcast-frequency window, the replicator drops stale snapshots and keeps only the latest small interpolation window.

This prevents remote vehicles from replaying a long backlog of stale movement after the receiver falls behind.

5. Clear stale vehicle buffer on remote ownership transition

When a vehicle becomes remote-owned on a client and the client already has a MovementReplicator, the existing interpolation buffer is cleared.

This prevents snapshots from the previous ownership phase from being mixed with the new driver's movement stream.

Testing

Tested with two clients and one Seamoth.

Before the final fix

Initial testing showed the Seamoth movement path was broadly unreliable:

  • Player 1 driving was not reliably visible to Player 2.
  • Player 2 driving was not reliably visible to Player 1.
  • Movement could appear delayed or replay later instead of applying live.
  • In some tests, there was no visible Seamoth motion on the observing client even though packets were being sent and received.

After initial debugging and partial timing changes, the behavior narrowed into a deterministic login-order-dependent failure:

  1. Player 1 logged in first.
  2. Player 2 logged in second.
  3. Player 2 could drive and be seen by Player 1.
  4. Player 1 could drive locally, but Player 2 would not see the Seamoth move correctly.
  5. Both players logged out.
  6. Players logged back in using the reverse order.
  7. The working direction reversed as well.

This confirmed the issue was not tied to a specific player account and was not random. It was related to client timing, interpolation state, and ownership transition handling.

Debug confirmation

Additional debug logging confirmed:

  • The driver client was sending vehicle movement packets.
  • The server received and relayed vehicle movement packets.
  • The observer client received movement packets.
  • The observer client routed them to SeamothMovementReplicator.
  • Server-side EXCLUSIVE ownership transfer worked for both players.
  • The remaining failure was in client-side application/interpolation of received movement.

After the final fix

Tested:

  1. Player 1 logged in first.
  2. Player 2 logged in second.
  3. Player 1 drove while Player 2 watched.
  4. Player 2 saw the Seamoth movement correctly.
  5. Player 1 exited.
  6. Player 2 drove while Player 1 watched.
  7. Player 1 saw the Seamoth movement correctly.
  8. Both clients logged out.
  9. Clients logged back in using the reverse order.
  10. Both driving directions still synced correctly.

Result:

  • Seamoth movement synced correctly in both directions.
  • Reversing login order no longer changed which player could be seen driving.
  • The delayed/catch-up replay behavior did not return in the tested scenarios.
  • The fix restored reliable live Seamoth movement visibility between clients.

Why this is necessary

Seamoths are central to normal Subnautica progression and multiplayer exploration. When this bug occurs, one or more clients can see a driver move locally while other clients see no correct vehicle movement, delayed movement, or old-position replay.

That causes practical multiplayer desync:

  • players disagree about where the Seamoth is,
  • the driver's position appears incorrect to observers,
  • remote players cannot reliably follow or interact around the moving vehicle,
  • and vehicle movement appears broken despite the server ownership path working.

This PR fixes the client-side timing/interpolation path that caused those failures.

Notes

This PR intentionally does not change server-side simulation ownership rules.

Server-side debugging showed ownership transfer was working. The fix is limited to client-side vehicle movement timing, interpolation backlog handling, and stale receiver-state cleanup across ownership changes.

@github-actions
Copy link
Copy Markdown

Test Results

255 tests  ±0   252 ✅ ±0   12s ⏱️ -11s
  1 suites ±0     3 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit cb03ba6. ± Comparison against base commit a89227d.

Copy link
Copy Markdown
Collaborator

@Measurity Measurity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the time of day a few times introduces movement lag to the seamoth. Sometimes event hangs for ~5 to 10 seconds before picking up movement again.

I think the TimeManger is necessary to sync time correctly but the other changes look good.

@mindless2831
Copy link
Copy Markdown
Contributor Author

Changing the time of day a few times introduces movement lag to the seamoth. Sometimes event hangs for ~5 to 10 seconds before picking up movement again.

I think the TimeManger is necessary to sync time correctly but the other changes look good.

I tested restoring TimeManager.RealTimeElapsed for both MovementBroadcaster and MovementReplicator, but that regressed the Seamoth movement fix completely: neither client could reliably see the other client's Seamoth movement again.

As I expected, this is part of the fix for this movement path. The original bug appears tied to using the synchronized/game-time path for movement interpolation timing, especially when ownership changes and remote snapshots are buffered.

I agree that time-of-day changes are important and should not introduce vehicle lag. However, reverting the movement clock back to TimeManager reintroduces the core desync. I am going to investigate whether the TimeManager/time-of-day issue needs a separate mitigation, but the Seamoth sync fix currently requires the movement broadcaster/replicator to use Unity realtime for local interpolation scheduling.

I will say this, I don't see you r average user jumping through the time of day with admin commands and immediately jumping into the seamoth while having another player watching them, but I could be wrong lol. I am still going to try to make that not happen of course, but changing these back does not appear to be possible while making the Seamoth still sync properly with the way things are currently written.

@mindless2831
Copy link
Copy Markdown
Contributor Author

Update: I also specifically tested the time-jump edge case mentioned above.

Using the current version of this PR, I rapidly cycled time through day/night roughly 10 times, then immediately entered and drove the Seamoth. I did not see any lag, delayed interpolation, catch-up movement, or “makeup” movement afterward. Seamoth movement continued to sync normally and behaved as expected. I repeated this several times, with both players.

So based on this test, using Time.realtimeSinceStartup for the vehicle movement broadcast/interpolation path does not appear to introduce the time-of-day jump issue. In contrast, when I tested reverting this path back to TimeManager.RealTimeElapsed, the Seamoth sync issue fully regressed and movement broke again for both clients as it is on of the integral pieces..

@mindless2831 mindless2831 requested a review from Measurity May 21, 2026 03:22
@mindless2831
Copy link
Copy Markdown
Contributor Author

I also wanted to add that I test with 200ms, 400ms, and 200ms to 800ms jitter with LiteNetLib and it worked better there was no issues at all. Interpolation actually made the jitter that was noticeable on the character, completely unnoticeable.

Copy link
Copy Markdown
Collaborator

@Measurity Measurity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Stutter as time goes on is gone now, I must have mixed up a test before.

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