Movement Modifiers and ServerMove

For the second case, does that involve modifiers as well? I would definitely test turning off move combining (or add logging/breakpoints to see if it’s happening), and if at all possible getting a beefy machine or turning off your server tick clamping to at least rule in/out that framerate is the reason you’re seeing this. Also if you’re not aware know that p.NetCorrections 1 will print/display info for you on the severity of the corrections, p.VisualizeMovement 1 will show a rough in-world representation of acceleration/velocity which can sometimes help pinpointing differences for simulated clients, and you can enable or hook up additional logging for the correction path.

Something critical to realize that isn’t quite “in your face” (that may or may not have anything to do with the issues you’re seeing, but wanted to make clear) is how much different the “ticks” are between clients and server for CharacterMovementComponent. For clients, they get reliable TickComponent() calls where they do their logic. You know that every tick you’ll be consuming input, simulating, sending a ServerMove RPC with “I ticked this long with this acceleration (input) and think I ended up at location X”.

On the server (authority) for a character, they only really do tick/simulation logic when they receive ServerMoves. When receiving a ServerMove with the latest ClientTimestamp, the server says “okay, the ClientTimestamp on this latest move is 0.4 seconds ahead of the last one I received, so I’m going to simulate you from where you’re at now to 0.4 seconds in the future, compare where you end up for me with where you think you are, and then correct if needed”. It’s designed this way so that it naturally handles dropped/out-of-order packets.

A client ticking at 10hz as an easy example will send ServerMove RPCs with timestamps 11.0, 11.1, 11.2, 11.3. The server receives 11.0 and then 11.1, and since there’s 0.1 seconds between them, it’ll tick for 0.1 seconds. This is great because you’re very very likely to get the same result. Now say 11.2 gets dropped and the server receives 11.3. Now it’ll do a 0.2 second tick. If on one of the client ticks it had one gravity value and on the second tick it had a different one, if your gravity-modifying logic isn’t built into saved moves or isn’t Timestamp-based, you’ll be doing a 0.2 second tick with not only the wrong gravity value for half of it, but also with not-perfect acceleration. This usually isn’t significant enough to make a difference, but with heavy latency/packet loss/re-ordering the server can end up diverging significantly.

I also believe that by default we don’t alter substep timing (how long individual “ticks” are for PhysWalking/PhysFalling loops) based on that total tick time, so if you have very fine requirements for “something will be different for 0.3 seconds only” you’ll want to look into making sure those line up.

Again for that the strategy is to structure your movement modifiers in a way that is friendly to this reality - instead of having a GetGravity() function that is based on CharacterMovementComponent property (which can fall apart during move replay on clients or when the server’s receiving of ServerMoves is all over the place due to bad network conditions), have a GetGravity(float TimeStamp) function that both client and server movement code can call to get super accurate simulation.

This might all have nothing to do with the issue you’re seeing, but I did want to clarify it in case you have your modification code not being aware of some of it.

I would definitely print out the different tick times for the physics moves for clients and servers so you can see if there is divergence at that level. Even if the server is going at 10FPS and the client is at 60FPS, I believe the server will still tick the character at whatever rate it receives ServerMoves (so 60FPS) - I haven’t tested this recently.