Summary
With PreferredTickingPolicy=Fixed, the client frame counter (FFixedTickState::PendingFrame) is one per world, while the server instance of a newly spawned pawn starts from -1. The first input packet for the new pawn arrives with the current client frame number (for example 31058). The server input buffer (32 entries) overflows and the server keeps 31 inputs queued instead of the usual 2-8. About 25 of those 31 slots were never received: they are never written and keep the default-constructed input (no movement).
The extra queue (about 23 frames of depth, 23 x 16.667 ms = 383 ms at 60 Hz) is then drained only through time dilation on the client. Dilation is computed as 1 + 0.01 x (queue - 2) x decay and capped by np.TimeDilation.Max (1.1). It stays at the cap only while the queue is above about 22, then decreases with the queue, so draining takes 7-10 s.
During that time:
- the client simulation runs slower, at about 91% at the start and about 95% on average;
- the server applies the player’s inputs about 380 ms later than normal at the start, and less and less afterwards, which comes back to the client as larger corrections.
What Type of Bug are you experiencing?
Networking
Steps to Reproduce
- Project with a Mover character replicated through Network Prediction, PreferredTickingPolicy=Fixed, FixedTickFrameRate=60.
- PIE, Run Under One Process, 2 clients, dedicated server. Network emulation on clients: 50-75 ms each way, 2% loss.
- After several minutes, spawn a new pawn on the server and possess it with a client (for example a respawn or a pawn class change).
- On that client, once per second, log PendingFrame - ConfirmedFrame (prediction depth) and the current time dilation, read from UNetworkPredictionWorldManager::GetFixedTickState().
Expected Result
A new pawn starts with a normal input queue on the server (a few frames), as at the start of the session, without needing time dilation to drain it.
Observed Result
- Depth jumps from 12 to 35 frames (50-75 ms profile) and from 20 to 44 frames (100-150 ms, 5% loss), then returns to normal in 7-10 s.
- In the 50-75 ms case, time dilation stays at 1.09-1.10 for about 1 s, then decreases (1.08, 1.06, 1.05, 1.04, 1.03, 1.02) and is back to 1.0 after about 8 s.
- The log shows the input buffer overflow line right after the first input packet.
This happens once per new pawn, with no hitch, but after every respawn the player’s movement is visibly slowed on the client for several seconds, and the server lags behind the player’s inputs by up to about 380 ms.
Affects Versions
5.8
Platform(s)
Windows
Additional Notes
Relevant code (paths relative to Engine/Plugins/Runtime/NetworkPrediction/Source/NetworkPrediction/)
- Fixed tick frame counters: NetworkPredictionTickState.h, FFixedTickState::PendingFrame (line 57) and ConfirmedFrame (line 60)
- Server receive data, input buffer of 32: Public/Services/NetworkPredictionInstanceData.h, TServerRecvData_Fixed, lines 113-128 (InputBuffer(32) at line 127; LastConsumedFrame and LastRecvFrame start at INDEX_NONE, lines 120-122)
- First input mapping on the server: Public/NetworkPredictionSerialization.h, TFixedTickReplicator_Server::NetRecv, lines 129-185
- New instance starting from -1 and first input overflowing the buffer (31 inputs queued): Public/Services/NetworkPredictionService_Input.inl, TRemoteInputService::ProduceInput, lines 135-138
- Dilation computed on the server: Public/NetworkPredictionSerialization.h, TFixedTickReplicator_AP::NetSend, lines 522-524 (block 504-525); received on the client in TFixedTickReplicator_AP::NetRecv, lines 421-431
- Dilation cap np.TimeDilation.Max: Private/NetworkPredictionSerialization.cpp, line 36
- Dilation applied only on the client: Private/NetworkPredictionWorldManager.cpp, UNetworkPredictionWorldManager::OnWorldPreTick_Internal, lines 103-106
Plugins: NetworkPrediction, Mover