Skip to content

Client LocalTime RPC stamp lags behind server tick at execution #4066

Description

@ertugrulsngr

Description

When a client sends an RPC stamped with LocalTime.Tick, the server ServerTime.Tick in the RPC handler is greater than the sent value. The gap grows with Network Simulator packet delay.

NGO docs suggest LocalTime predicts the server tick when an action is processed. We expected:

server_tick_on_handler <= sent_LocalTime.Tick

After steady-state (30s+), we see the opposite. GetCurrentRtt() reports correctly (~2× packet delay), so this does not look like an RTT reporting or slow-adaptation issue.


Reproduce Steps

  1. NGO 2.13 + Multiplayer Tools Network Simulator + UTP
  2. Tick rate 30 Hz, Host + Client
  3. Add a NetworkBehaviour that on each client NetworkTickSystem.Tick sends LocalTime.Tick via ServerRpc; server logs received tick + current ServerTime.Tick
  4. Set Packet Delay to 150ms, wait 30s, collect logs
  5. Repeat with 300ms delay

Actual Outcome

Packet Delay RTT Example (sent → server) Gap
150ms ~320ms 1190 → 1193 +3 ticks
300ms ~625ms 1090 → 1098 +8 ticks

Gap scales with delay. ServerTime.Tick + RTT_ticks at send is ~1 tick off; LocalTime.Tick is ~3–8 ticks off.


Expected Outcome

server_tick_on_rpc_handler <= sent_LocalTime.Tick (ideally ≈ equal, ±1 tick). Gap should not grow linearly with delay at steady-state.


Environment

  • OS: Windows 11
  • Unity: 6000.4.11f1
  • NGO: 2.13.0
  • UTP: 2.7.2
  • Multiplayer Tools: 2.2.9
  • Topology: Client-Server, 30 Hz tick rate

Activity

  1. NoelStephensUnity commented on Jul 7, 2026

    @NoelStephensUnity
    Member

    Hi @ertugrulsngr ,

    There are a few things here that could be potentially misleading.

    • NetworkManager.ServerTime:
      • This is really the "network time" that is synchronized to clients. This time should be the most accurate/synchronized time between server/session owner and the rest of the connected clients. You can actually key motion from this time (i.e. a sinusoidal motion) that will look synchronized on all connected clients.
      • For comparing half or full roundtrip times, you want to use this value.
    • NetworkManager.LocalTime:
      • Runs slightly ahead of server time, but can

    Take the below screenshots into consideration:

    Image _When comparing times from console output, you want to find the transition point where the server's tick increments and then line up the clients' console log output to match their `ServerTime.Tick` increment. Once you have these, you can see why LocalTime would not be a good choice to determine latency._
    • The Host (left side):
      • This shows local and server times being the same (as it should).
    • The Clients (right side top and bottom):
      • This shows each client's local times have a larger delta between the two where one client is still on tick 524 (about 1-2 ticks ahead of server time) while another is on tick 525 (about 2-3 ticks ahead of server).
      • The client's ServerTimes are a closer match to each other while their LocalTimes are not so much.

    Which to use?

    • If you were to send the LocalTime values, then there would be larger deltas.
    • If you were to send the ServerTime values, then there would be smaller deltas.

    I would recommend using ServerTime.

    NGO docs suggest LocalTime predicts the server tick when an action is processed. We expected:

    Could you send me the link to the section of our documentation that describes this?
    I think the context is going to be when the Server is sending to the Client

    If the server were to send an RPC to a client using its Server/Local time, the client's local time would be more accurate than if you send a client's local time to the server since the client's local time can be between 2 to 3+ ticks ahead (increases as latency increases)... which is the behavior you are describing.

    When comparing time between clients and server, you need to first think about the context... are you trying to determine the latency between a server and client or a client to a client? Most of the time, using ServerTime will yield a more accurate delta than using LocalTime.

    Server time is synchronized by the server (or for distributed authority it is the service that provides the ServerTime) on a regular basis.
    Local time runs ahead of the server time relative to continually running calculations based off of a client's latency to the server and the frequently updated ServerTime.

    Using the below script:

        [Rpc(SendTo.NotMe)]
        private void ServerPingsClientsRpc(float serverTime, int tick)
        {
            var serverTimeDelta = NetworkManager.ServerTime.TimeAsFloat - serverTime;
            var serverTickDelta = NetworkManager.ServerTime.Tick - tick;
            var localTimeDelta = NetworkManager.LocalTime.TimeAsFloat - serverTime;
            var localTickDelta = NetworkManager.LocalTime.Tick - tick;
    
            Debug.Log($"[Server to Clients][ServerTime] Client-{NetworkManager.LocalClientId} has a {serverTimeDelta * 1000}ms latency and {serverTickDelta} delta from the server ");
            Debug.Log($"[Server to Clients][LocalTime] Client-{NetworkManager.LocalClientId} has a {localTimeDelta * 1000}ms latency and {localTickDelta} delta from the server ");
        } 

    Using the above RPC, I ran a session where the host sends the client an RPC containing its ServerTime time as float and its current tick.

    Image

    You can see the client's server time lags behind the server by roughly 49ms while the client's local time runs about 54ms ahead. The respective deltas show the client's server time tick value is about one tick behind while the client's local time is about one tick ahead.

    Let me know if this information helps clarify the differences between the two?
    If you still think there is an issue or have more questions please feel free to post here.

  2. added
    stat:awaiting-responseAwaiting response from author. This label should be added manually.
    and removed on Jul 7, 2026
  3. ertugrulsngr commented on Jul 8, 2026

    @ertugrulsngr
    Author

    Hi @NoelStephensUnity ,

    Thanks for the detailed explanation! Looking back at my initial post, I realize my explanation was a bit superficial, so I apologize for that. Let me provide a more precise breakdown of the issue and what I am trying to achieve.

    To answer your question regarding the documentation, the behavior I expected is directly based on the official Unity Netcode for GameObjects documentation at Netcode Time and Ticks:

    https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.4/manual/advanced-topics/networktime-ticks.html

    which explicitly states:

    "LocalTime on a client is ahead of the server. If a server RPC is sent at LocalTime from a client it will roughly arrive at ServerTime on the server."

    Based on this exact quote, I expected that the server tick on the RPC handler would be roughly equal to the sent LocalTime tick. However, as latency increases, my logs show the opposite behavior: the sent LocalTime tick lags behind the server's execution tick, indicating that the current documentation might be incorrect or misleading.

    To understand the underlying mechanics, I did a deep dive into the NGO source code to see how time synchronization is handled inside TimeSyncMessage.cs:

    public void Handle(ref NetworkContext context)
    {
        var networkManager = (NetworkManager)context.SystemOwner;
        var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, Tick);
        networkManager.NetworkTimeSystem.SyncCount++;
        networkManager.NetworkTimeSystem.Sync(
            time.Time,
            networkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(context.SenderId) / 1000d
        );
    }

    When a time sync message arrives, these values are set immediately, and the system continues to extrapolate the time on every update. However, during this sync call, the system uses the raw server tick without offsetting it by the packet's one-way travel latency (such as adding half of the RTT to capture the actual server time at the exact moment of packet arrival).

    Because of this design choice, ServerTime on the client inherently lags behind the actual server. Since NGO does not expose a public event or hook when this sync message is handled, it is currently impossible for me to intercept it and construct a more precise, latency-compensated timeline manually.

    To give you some context on why I need this precision, I am developing a client-side prediction movement system. The client needs to predict the future and stamp its inputs with the exact tick they are expected to execute upon arriving at the server.

    I would highly appreciate your professional feedback or advice on whether this input stamping methodology is the correct approach in NGO, or if there is a better practice you recommend from your experience.

    As a workaround, I experimented with manually offsetting ServerTime by adding the current full RTT in ticks to it. In practice, this empirical approach works successfully and gives us the desired behavior. However, it feels theoretically incorrect.

    Because sync messages arrive roughly once per second, the transport RTT can fluctuate between sync points. Attempting to compensate by adding a fluctuating RTT to an already shifted, extrapolated timeline, without knowing the exact baseline RTT at the moment the sync packet originally arrived, lacks a solid theoretical foundation.

    I would highly appreciate your architectural insights on how I should handle input prediction stamping and time precision in NGO given these constraints.

  4. added and removed
    stat:awaiting-responseAwaiting response from author. This label should be added manually.
    on Jul 8, 2026
  5. NoelStephensUnity commented on Jul 15, 2026

    @NoelStephensUnity
    Member

    Hey @ertugrulsngr,

    Thank you for further clarifying what you are wanting to accomplish along with the additional details. :godmode: 🥇

    To give you some context on why I need this precision, I am developing a client-side prediction movement system. The client needs to predict the future and stamp its inputs with the exact tick they are expected to execute upon arriving at the server.

    When you say "inputs" are these player movement and/or interaction type inputs?
    If so, then it sounds like you want to have server-side input reconciliation where (typically) the reconciliation period is several ticks (at least more than one) and the server timestamps the inputs based on the local server time (when received while also taking the client's latency into consideration). When the reconciliation time is up. the server will:

    • Order the received inputs from clients (if needed) based on the time received (the timestamp already subtracts the average client's latency).
    • Processes the queue it has accumulated.
    • Begin the next reconciliation period.

    Where the reconciliation period is at least 2 network ticks or more.
    So you really end up with a network tick and reconciliation tick where the later isn't typically synchronized with clients.

    Now, if you want true prediction where the client goes ahead and begins to act upon the action before the server has reconciled then you would want the ability to rollback on the client side. Do you plan on having a form of rollback on the client side?

  6. added
    stat:awaiting-responseAwaiting response from author. This label should be added manually.
    and removed on Jul 15, 2026
  7. github-actions commented on Jul 30, 2026

    @github-actions

    This issue has been automatically marked as stale because it has been awaiting response for over 2 weeks without any activity.
    Please update the issue with any new information or it may be closed in 2 weeks.

  8. added
    StaleAdded after 30 days since stat:awaiting response was added (if it's still present)
    on Jul 30, 2026
  9. github-actions commented on Aug 14, 2026

    @github-actions

    This issue has been automatically closed because it has been stale for 2 weeks without any activity. Feel free to reopen if you have new information to add.

  10. removed
    stat:awaiting-responseAwaiting response from author. This label should be added manually.
    stat:awaiting-triageStatus - Awaiting triage from the Netcode team.
    stat:InvestigatingIssue is currently being investigated
    StaleAdded after 30 days since stat:awaiting response was added (if it's still present)
    on Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions