Skip to content

Commit 36bade2

Browse files
fix: Derive NetworkTransform interpolation time from the server clock
A NetworkTransform state's SentTime comes from its NetworkTick, which is a server tick, but the render time the interpolators were given was derived from LocalTime. That mixes two clocks. LocalTime leads ServerTime, so subtracting the tick latency from it lands the render time back at approximately ServerTime rather than a whole tick latency behind it, and a state's SentTime is floored to a tick boundary on top of that. The render time therefore sat at or ahead of the newest state that could exist and the interpolator had nothing to interpolate towards. Measuring from ServerTime makes the offset the whole tick latency instead of whatever is left of it, and is self correcting: as the round trip time grows the tick latency grows and the render time moves further back with it. This also matches the rest of the component, which already resets the interpolators using ServerTime. This is a no-op on a host or server, where the two clocks are the same, so it only affects clients. GetTickLatencyInSeconds returns an absolute time rather than a duration and had the same defect, so it now derives from ServerTime as well. GetTickLatency is left alone because it returns a tick count rather than a point in time.
1 parent 94e1953 commit 36bade2

2 files changed

Lines changed: 24 additions & 16 deletions

File tree

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,22 @@ Additional documentation and release notes are available at [Multiplayer Documen
1919
- `Unity.Netcode.Editor.PackageChecker``Unity.Netcode.GameObjects.Editor.PackageChecker`
2020
- `Unity.Netcode.Editor.Tests``Unity.Netcode.GameObjects.Editor.Tests`
2121

22-
23-
2422
### Deprecated
2523

26-
2724
### Removed
2825

29-
3026
### Fixed
3127

28+
- Issue where non-authority `NetworkTransform` instances derived their interpolation time from the local clock instead of the server clock that state updates are stamped on, which starved the interpolator and reduced interpolation to snapping between state updates. (#TBD)
29+
- Issue where `NetworkTransform.GetTickLatencyInSeconds` returned a time derived from the local clock, which did not match the time the interpolators actually use. (#TBD)
3230
- Issue with not being able to spawn initially disabled in-scene placed objects. (#4093)
3331
- Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093)
3432
- Issue where a user could spawn runtime created `NetworkObject` that has a GlobalObjectIdHash of zero. These are not valid instances and will no longer be allowed to spawn. (#4093)
3533

36-
3734
### Security
3835

39-
4036
### Obsolete
4137

42-
4338
## [2.13.1] - 2026-07-19
4439

4540
### Added

com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4259,14 +4259,27 @@ internal BufferedLinearInterpolatorQuaternion GetRotationInterpolator()
42594259
// Non-Authority
42604260
private void UpdateInterpolation()
42614261
{
4262-
// Use the local time because:
4263-
// Client-Server:
4264-
// Local time is server time on a host or server.
4265-
// Local time on clients takes latency into consideration.
4266-
// Distributed authority:
4267-
// Local time is used by the authority.
4268-
// Local time on non-authority takes latency into consid]eration.
4269-
var timeSystem = m_CachedNetworkManager.LocalTime;
4262+
// Use the server time, because that is the clock the measurements being interpolated between are
4263+
// stamped on: a state's SentTime is derived from its NetworkTick, which is a server tick.
4264+
//
4265+
// Deriving the render time from LocalTime instead mixes two clocks. LocalTime leads ServerTime by
4266+
// roughly the tick latency, so subtracting the tick latency from it lands the render time back at
4267+
// (approximately) ServerTime rather than behind it. "Approximately" is the problem: the lead is
4268+
// fractional while the subtraction is a whole number of ticks, and a state's SentTime is floored to
4269+
// a tick boundary on top of that. The render time therefore ends up at or slightly ahead of the
4270+
// newest state that can exist, leaving the interpolator with nothing to interpolate towards. A
4271+
// measured session had the render time ahead of ServerTime on 100% of frames, with the interpolator
4272+
// never holding more than one measurement.
4273+
//
4274+
// Measuring from ServerTime instead makes the offset the whole tick latency rather than whatever is
4275+
// left of it, which is self correcting: as the round trip time grows, NetworkTimeSystem.TickLatency
4276+
// grows and the render time moves further back with it.
4277+
//
4278+
// Note this is a no-op on a host or server, where LocalTime and ServerTime are the same.
4279+
// TODO-JIRA-TICKET:
4280+
// Confirm the distributed authority case. Authority instances interpolate nothing, so this should
4281+
// not reach them, but ServerTime's meaning under a CMB service session should be verified.
4282+
var timeSystem = m_CachedNetworkManager.ServerTime;
42704283
var currentTime = timeSystem.Time;
42714284
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
42724285
var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime;
@@ -4730,7 +4743,7 @@ internal static float GetTickLatencyInSeconds(NetworkManager networkManager)
47304743
{
47314744
if (networkManager.IsListening)
47324745
{
4733-
return (float)networkManager.LocalTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset).Time;
4746+
return (float)networkManager.ServerTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset).Time;
47344747
}
47354748
return 0f;
47364749
}

0 commit comments

Comments
 (0)