From 95de571eb56b4f85c577c053022a3a41df6ecef1 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 7 Sep 2025 18:45:24 +0300 Subject: [PATCH] netcode progress, improved handling of missing frames --- .../EngineSettings/GraphicsSettings.json | 4 +- .../EngineSettings/InputSettings.json | 30 ++++- .../EngineSettings/PhysicsSettings.json | 3 +- Source/Game/Camera/CameraRender.cs | 4 +- Source/Game/GameMode/NetworkManager_Client.cs | 9 +- Source/Game/GameMode/NetworkManager_Server.cs | 7 +- Source/Game/GameMode/World.cs | 127 ++++++++++++++---- Source/Game/Player/PlayerInput2.cs | 23 +++- Source/Game/Player/PlayerMovement.cs | 98 +++++++++++++- 9 files changed, 259 insertions(+), 46 deletions(-) diff --git a/Content/Settings/EngineSettings/GraphicsSettings.json b/Content/Settings/EngineSettings/GraphicsSettings.json index 2805e81..8eb69be 100644 --- a/Content/Settings/EngineSettings/GraphicsSettings.json +++ b/Content/Settings/EngineSettings/GraphicsSettings.json @@ -6,8 +6,8 @@ "UseVSync": false, "AAQuality": 3, "SSRQuality": 3, - "SSAOQuality": 0, - "VolumetricFogQuality": 3, + "SSAOQuality": 3, + "VolumetricFogQuality": 0, "ShadowsQuality": 3, "ShadowMapsQuality": 3, "AllowCSMBlending": true, diff --git a/Content/Settings/EngineSettings/InputSettings.json b/Content/Settings/EngineSettings/InputSettings.json index fe15c5a..f0e8084 100644 --- a/Content/Settings/EngineSettings/InputSettings.json +++ b/Content/Settings/EngineSettings/InputSettings.json @@ -1,7 +1,7 @@ { "ID": "8ec53dba4c238bfbea1d62922e612a4d", "TypeName": "FlaxEditor.Content.Settings.InputSettings", - "EngineBuild": 6335, + "EngineBuild": 6705, "Data": { "ActionMappings": [ { @@ -84,6 +84,8 @@ "Gamepad": 0, "PositiveButton": 0, "NegativeButton": 0, + "GamepadPositiveButton": 0, + "GamepadNegativeButton": 0, "DeadZone": 0.0, "Sensitivity": 0.11, "Gravity": 1.0, @@ -96,6 +98,8 @@ "Gamepad": 0, "PositiveButton": 0, "NegativeButton": 0, + "GamepadPositiveButton": 0, + "GamepadNegativeButton": 0, "DeadZone": 0.0, "Sensitivity": 0.11, "Gravity": 1.0, @@ -108,9 +112,11 @@ "Gamepad": 0, "PositiveButton": 68, "NegativeButton": 65, + "GamepadPositiveButton": 0, + "GamepadNegativeButton": 0, "DeadZone": 0.01, - "Sensitivity": 5.0, - "Gravity": 5.0, + "Sensitivity": 0.0, + "Gravity": 0.0, "Scale": 1.0, "Snap": true }, @@ -120,9 +126,11 @@ "Gamepad": 0, "PositiveButton": 87, "NegativeButton": 83, + "GamepadPositiveButton": 0, + "GamepadNegativeButton": 0, "DeadZone": 0.01, - "Sensitivity": 5.0, - "Gravity": 5.0, + "Sensitivity": 0.0, + "Gravity": 0.0, "Scale": 1.0, "Snap": true }, @@ -132,6 +140,8 @@ "Gamepad": 0, "PositiveButton": 39, "NegativeButton": 37, + "GamepadPositiveButton": 0, + "GamepadNegativeButton": 0, "DeadZone": 0.0, "Sensitivity": 1.0, "Gravity": 800.0, @@ -144,6 +154,8 @@ "Gamepad": 0, "PositiveButton": 38, "NegativeButton": 40, + "GamepadPositiveButton": 0, + "GamepadNegativeButton": 0, "DeadZone": 0.0, "Sensitivity": 1.0, "Gravity": 800.0, @@ -156,6 +168,8 @@ "Gamepad": 0, "PositiveButton": 0, "NegativeButton": 0, + "GamepadPositiveButton": 0, + "GamepadNegativeButton": 0, "DeadZone": 0.19, "Sensitivity": 1.0, "Gravity": 1.0, @@ -168,6 +182,8 @@ "Gamepad": 0, "PositiveButton": 0, "NegativeButton": 0, + "GamepadPositiveButton": 0, + "GamepadNegativeButton": 0, "DeadZone": 0.19, "Sensitivity": 1.0, "Gravity": 1.0, @@ -180,6 +196,8 @@ "Gamepad": 0, "PositiveButton": 0, "NegativeButton": 0, + "GamepadPositiveButton": 0, + "GamepadNegativeButton": 0, "DeadZone": 0.19, "Sensitivity": 1.0, "Gravity": 1.0, @@ -192,6 +210,8 @@ "Gamepad": 0, "PositiveButton": 0, "NegativeButton": 0, + "GamepadPositiveButton": 0, + "GamepadNegativeButton": 0, "DeadZone": 0.19, "Sensitivity": 1.0, "Gravity": 1.0, diff --git a/Content/Settings/EngineSettings/PhysicsSettings.json b/Content/Settings/EngineSettings/PhysicsSettings.json index e5001c7..61e3ba3 100644 --- a/Content/Settings/EngineSettings/PhysicsSettings.json +++ b/Content/Settings/EngineSettings/PhysicsSettings.json @@ -1,7 +1,7 @@ { "ID": "4bd8a4cc460399b5f1975fbe0a668e3f", "TypeName": "FlaxEditor.Content.Settings.PhysicsSettings", - "EngineBuild": 6510, + "EngineBuild": 6705, "Data": { "DefaultGravity": { "X": 0.0, @@ -13,6 +13,7 @@ "RestitutionCombineMode": 0, "DisableCCD": false, "BroadPhaseType": 3, + "EnableEnhancedDeterminism": false, "SolverType": 0, "MaxDeltaTime": 0.1, "EnableSubstepping": false, diff --git a/Source/Game/Camera/CameraRender.cs b/Source/Game/Camera/CameraRender.cs index 3185167..08e6bf3 100644 --- a/Source/Game/Camera/CameraRender.cs +++ b/Source/Game/Camera/CameraRender.cs @@ -137,7 +137,7 @@ public class CameraRender : PostProcessEffect private bool lastRescale = false; public override void OnUpdate() { -#if FLAX_EDITOR +/*#if FLAX_EDITOR if (Input.GetKeyDown(KeyboardKeys.F7)) { PhysicsSettings physicsSettings = GameSettings.Load(); @@ -145,7 +145,7 @@ public class CameraRender : PostProcessEffect GameSettings.Save(physicsSettings); //GameSettings.Apply(); } -#endif +#endif*/ if (lastEnabled != camera.IsActive) { diff --git a/Source/Game/GameMode/NetworkManager_Client.cs b/Source/Game/GameMode/NetworkManager_Client.cs index e2ff3ed..4db8a57 100644 --- a/Source/Game/GameMode/NetworkManager_Client.cs +++ b/Source/Game/GameMode/NetworkManager_Client.cs @@ -24,14 +24,17 @@ public static partial class NetworkManager //var driver = Object.New(typeof(ENetDriver)); //ClientNetworkDriver = null; - NetworkLagDriver driver = Object.New(); - driver.Lag = 0f; + + //INetworkDriver driver = Object.New(); + INetworkDriver driver = Object.New(); + if (driver is NetworkLagDriver networkLagDriver) + networkLagDriver.Lag = 50.0f; ClientNetworkDriver = driver; client = NetworkPeer.CreatePeer(new NetworkConfig { - NetworkDriver = driver, + NetworkDriver = (Object)driver, ConnectionsLimit = MaximumClients, MessagePoolSize = 2048, MessageSize = MTU, diff --git a/Source/Game/GameMode/NetworkManager_Server.cs b/Source/Game/GameMode/NetworkManager_Server.cs index 85605cf..b572607 100644 --- a/Source/Game/GameMode/NetworkManager_Server.cs +++ b/Source/Game/GameMode/NetworkManager_Server.cs @@ -22,14 +22,15 @@ public static partial class NetworkManager public static bool StartServer(bool listenServer = true) { + Time.Synchronize(); Cleanup(); ConnectedClients = new List(MaximumClients); - //INetworkDriver driver = Object.New(); - NetworkLagDriver driver = Object.New(); + INetworkDriver driver = Object.New(); + //NetworkLagDriver driver = Object.New(); if (driver is NetworkLagDriver networkLagDriver) - networkLagDriver.Lag = 200f; + networkLagDriver.Lag = 50.0f;//200f; ServerNetworkDriver = driver; diff --git a/Source/Game/GameMode/World.cs b/Source/Game/GameMode/World.cs index cdf9281..f6165a2 100644 --- a/Source/Game/GameMode/World.cs +++ b/Source/Game/GameMode/World.cs @@ -24,7 +24,7 @@ public enum GameModeMessageType2 : byte public class PlayerFrame { - public ulong frame; + public ulong frame => inputState.Frame; public Float3 position; public PlayerInputState2 inputState; public PlayerMovementState movementState; @@ -40,7 +40,7 @@ public interface IClientInfo public class World { public bool IsServer => this is ServerWorld; - public bool IsClient => !IsServer; + public bool IsClient => this is ClientWorld; public ulong Frame { get; protected set; } public ulong ServerFrame { get; protected set; } // Last received frame from server public float GameTime { get; protected set; } // The join time @@ -102,17 +102,17 @@ public class World void OnUpdate() { - Console.Print("server Update"); + //Console.Print("server Update"); } void OnUpdateLate() { - Console.Print("server LateUpdate"); + //Console.Print("server LateUpdate"); } void OnFixedUpdate() { - Console.Print("server FixedUpdate"); + //Console.Print("server FixedUpdate"); } protected void CreateScene(string sceneNamePrefix, string sceneGuid) @@ -224,14 +224,17 @@ public class World virtual public bool IsLocalPlayer(uint playerId) => false; + virtual public ulong GetLastProcessedFrame(uint playerId) => 0; + + virtual public ulong GetLastReceivedFrame(uint playerId) => 0; + public PlayerFrame GetPlayerFrame(uint playerId, ulong frame) { IClientInfo player = GetClient(playerId); PlayerFrame playerFrame = player.FrameHistory[frame % 120]; - if (playerFrame.frame != frame) - return null; - - return playerFrame; + if (playerFrame.inputState.Frame == frame || playerFrame.movementState.frame == frame) + return playerFrame; + return null; } public bool HasPlayerFrame(uint playerId, ulong frame) => GetPlayerFrame(playerId, frame) != null; @@ -248,8 +251,9 @@ public class World IClientInfo player = GetClient(playerId); PlayerFrame playerFrame = player.FrameHistory[frame % 120]; - if (playerFrame.frame == frame) + /*if (playerFrame.frame == frame) { + var old = playerFrame.inputState with { Frame = inputState.Frame }; playerFrame.inputState = inputState with { ViewDelta = playerFrame.inputState.ViewDelta + inputState.ViewDelta, @@ -258,11 +262,23 @@ public class World Attack = playerFrame.inputState.Attack || inputState.Attack, Jump = playerFrame.inputState.Jump || inputState.Jump, }; + + if (old != playerFrame.inputState) + old = old; } - else + else*/ playerFrame.inputState = inputState; + playerFrame.inputState.Frame = frame; + //playerFrame.movementState = movementState; + //playerFrame.frame = frame; + } + + virtual public void UpdatePlayerState(uint playerId, ulong frame, PlayerMovementState movementState) + { + IClientInfo player = GetClient(playerId); + PlayerFrame playerFrame = player.FrameHistory[frame % 120]; playerFrame.movementState = movementState; - playerFrame.frame = frame; + playerFrame.movementState.frame = frame; } } @@ -275,12 +291,17 @@ file class ServerWorld : World public PlayerFrame[] FrameHistory { get; set; } = new PlayerFrame[120]; public ulong LastReceivedFrame = 0; public ulong LastSentDeltaFrame = 0; // TODO: Accumulate deltas since this frame + public ulong LastProcessedFrame = 0; // In case player is lagged behind, these frames should be caught up during next simulation public int SendRate = 0; // How many updates the client wants to receive per second public ClientInfo() { for (int i = 0; i < FrameHistory.Length; i++) + { FrameHistory[i] = new PlayerFrame(); + FrameHistory[i].inputState.Frame = ulong.MaxValue; + FrameHistory[i].movementState.frame = ulong.MaxValue; + } } } @@ -306,8 +327,14 @@ file class ServerWorld : World protected override void OnLateUpdate() { - Console.Print("server LateFixedUpdate"); + //Console.Print("server LateFixedUpdate"); base.OnLateUpdate(); + + foreach (var client in _clients.Values) + { + // We assume all the scripts have processed the frames during this cycle + client.LastProcessedFrame = client.LastReceivedFrame; + } } public bool OnMessage(ref NetworkEvent networkEvent) @@ -369,9 +396,10 @@ file class ServerWorld : World { PlayerInputState2 inputState; //? - ulong receivedFrame = networkEvent.Message.ReadUInt64(); - uint reportedPlayerId = networkEvent.Message.ReadUInt32(); - inputState.Frame = receivedFrame; + ulong receivedFrame = inputState.Frame = networkEvent.Message.ReadUInt64(); + uint playerId = networkEvent.Message.ReadUInt32(); + float time = networkEvent.Message.ReadSingle(); + float delay = Time.TimeSinceStartup - time; inputState.ViewDelta.X = networkEvent.Message.ReadSingle(); inputState.ViewDelta.Y = networkEvent.Message.ReadSingle(); inputState.MoveForward = networkEvent.Message.ReadSingle(); @@ -379,22 +407,42 @@ file class ServerWorld : World inputState.Attack = networkEvent.Message.ReadBoolean(); inputState.Jump = networkEvent.Message.ReadBoolean(); - Console.Print($"server receive client frame {receivedFrame}, current server frame {Frame}"); + //Console.Print("recv: " + receivedFrame.ToString()); + + ClientInfo player = GetClient(playerId) as ClientInfo; + if (receivedFrame <= player.LastReceivedFrame) + { + if (receivedFrame < player.LastProcessedFrame) + { + Console.PrintWarning($"Dropping late frame from client: {receivedFrame}, last processed: {player.LastProcessedFrame}"); + break; + } + else + { + //Console.PrintWarning($"Out-of-order frame received from client: {receivedFrame}, latest: {player.LastReceivedFrame}"); + } + } + player.LastReceivedFrame = Math.Max(receivedFrame, player.LastReceivedFrame); + //if (player.LastProcessedFrame == 0) + // player.LastProcessedFrame = receivedFrame - 1; + + //Console.Print($"server receive client frame {receivedFrame}, current server frame {Frame}"); var asdf = receivedFrame; - receivedFrame = Frame; + //receivedFrame = Frame; // TODO: receive all frames from players, simulate all unprocessed frames in next simulation cycle? if (inputState.MoveForward != 0) receivedFrame = receivedFrame; - + //Console.Print($"delay: {delay * 1000}ms"); + // Sanity check - if (GetPlayerInputState(reportedPlayerId, receivedFrame, out var prevInputState)) + if (GetPlayerInputState(playerId, receivedFrame, out var prevInputState)) { - Console.PrintWarning($"Duplicate frame received from client: {asdf}"); + Console.PrintWarning($"Duplicate frame received from client: {asdf}, delay: {delay*1000}ms"); + //break; } - PlayerMovementState movementState = default; - UpdatePlayerInputState(reportedPlayerId, receivedFrame, inputState, movementState); + UpdatePlayerInputState(playerId, receivedFrame, inputState, default); break; } default: @@ -454,12 +502,24 @@ file class ServerWorld : World } } + public override ulong GetLastProcessedFrame(uint playerId) + { + ClientInfo player = GetClient(playerId) as ClientInfo; + return player.LastProcessedFrame; + } + + public override ulong GetLastReceivedFrame(uint playerId) + { + ClientInfo player = GetClient(playerId) as ClientInfo; + return player.LastReceivedFrame; + } + public override void UpdatePlayerInputState(uint playerId, ulong frame, PlayerInputState2 inputState, PlayerMovementState movementState) { base.UpdatePlayerInputState(playerId, frame, inputState, movementState); - ClientInfo player = GetClient(playerId) as ClientInfo; - player.LastReceivedFrame = frame; // Dropped frames should be ignored? + //ClientInfo player = GetClient(playerId) as ClientInfo; + //player.LastReceivedFrame = frame; // Dropped frames should be ignored? } protected override PlayerActor SpawnPlayer(uint playerId, Float3 position, Vector3 eulerAngles) @@ -526,7 +586,7 @@ file class ClientWorld : World public bool Jump;*/ - Console.Print($"client send {Frame}"); + //Console.Print($"client send {Frame}"); UpdatePlayerInputState(player.PlayerId, Frame, inputState, movementState); //Console.Print($"cframe: {Frame}"); @@ -534,12 +594,15 @@ file class ClientWorld : World message.WriteByte((byte)GameModeMessageType2.PlayerInput); message.WriteUInt64(Frame); message.WriteUInt32(player.PlayerId); + message.WriteSingle(Time.TimeSinceStartup); message.WriteVector2(inputState.ViewDelta); message.WriteSingle(inputState.MoveForward); message.WriteSingle(inputState.MoveRight); message.WriteBoolean(inputState.Attack); message.WriteBoolean(inputState.Jump); NetworkManager.ClientEndSendMessage(ref message); + + //Console.Print("send: " + Frame.ToString()); } base.OnLateUpdate(); } @@ -644,6 +707,18 @@ file class ClientWorld : World public override bool IsLocalPlayer(uint playerId) => playerId == _localPlayerId; + public override ulong GetLastProcessedFrame(uint playerId) + { + //ClientInfo player = GetClient(playerId) as ClientInfo; + return 0; + } + + public override ulong GetLastReceivedFrame(uint playerId) + { + //ClientInfo player = GetClient(playerId) as ClientInfo; + return 0; + } + public void OnClientConnected(NetworkConnection connection) { uint playerId = connection.ConnectionId; diff --git a/Source/Game/Player/PlayerInput2.cs b/Source/Game/Player/PlayerInput2.cs index 3865d15..c74bcad 100644 --- a/Source/Game/Player/PlayerInput2.cs +++ b/Source/Game/Player/PlayerInput2.cs @@ -1,15 +1,17 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using FlaxEngine; namespace Game; [StructLayout(LayoutKind.Sequential)] -public struct PlayerInputState2 +public struct PlayerInputState2 : IEquatable { public ulong Frame; public Float2 ViewDelta; @@ -17,6 +19,22 @@ public struct PlayerInputState2 public float MoveRight; public bool Attack; public bool Jump; + + /*public override bool Equals([NotNullWhen(true)] object obj) + { + if (obj is not PlayerInputState2 other) + return false; + return Frame == other.Frame && ViewDelta == other.ViewDelta && MoveForward == other.MoveForward && MoveRight == other.MoveRight && Attack == other.Attack && Jump == other.Jump; + }*/ + + public bool Equals(PlayerInputState2 other) + { + return Frame == other.Frame && ViewDelta == other.ViewDelta && MoveForward == other.MoveForward && MoveRight == other.MoveRight && Attack == other.Attack && Jump == other.Jump; + } + + public static bool operator ==(PlayerInputState2 left, PlayerInputState2 right) => left.Equals(right); + + public static bool operator !=(PlayerInputState2 left, PlayerInputState2 right) => !left.Equals(right); } public interface IPlayerInput @@ -80,6 +98,9 @@ public class PlayerInput2 : IPlayerInput _recordState.Attack |= Input.GetAction("Attack"); _recordState.Jump |= Input.GetAction("Jump"); _recordState.Frame = _frame; + + if (Input.GetKeyDown(KeyboardKeys.F)) + Thread.Sleep(20); } public PlayerInputState2 GetState() => _recordState; diff --git a/Source/Game/Player/PlayerMovement.cs b/Source/Game/Player/PlayerMovement.cs index 3e5f964..b6a9931 100644 --- a/Source/Game/Player/PlayerMovement.cs +++ b/Source/Game/Player/PlayerMovement.cs @@ -74,6 +74,7 @@ public struct PlayerMovementState public int numJumps; public bool jumped; public bool onGround; + public ulong frame; public PlayerMovementState() @@ -250,9 +251,20 @@ public class PlayerMovement : Script viewAnglesLastFrame = viewAngles; } + private static int framedropped = 0; public override void OnUpdate() { - Console.Print("playerMovement OnUpdate"); + if (World.IsServer && Mathf.Abs(Time.DeltaTime - (1.0f / Time.UpdateFPS)) > (1.0f / Time.UpdateFPS) * 0.999f) + { + Console.Print($"drop: {Time.DeltaTime*1000.0f}ms"); + framedropped = 3; + } + else if (World.IsServer && framedropped > 0) + { + Console.Print("dropping..."); + framedropped--; + } + //Console.Print($"{(World.IsClient ? "[cl] " : "[sv] ") + World.Frame.ToString()} playerMovement OnUpdate"); //input.OnUpdate(); @@ -315,7 +327,7 @@ public class PlayerMovement : Script public override void OnFixedUpdate() { - Console.Print("playerMovement OnFixedUpdate"); + //Console.Print($"{(World.IsClient ? "[cl] " : "[sv] ") + World.Frame.ToString()} playerMovement OnFixedUpdate"); float timeDeltaDiff = Time.DeltaTime - 1.0f / Time.PhysicsFPS; if (Time.PhysicsFPS > 0 && Math.Abs(timeDeltaDiff) > 0.0001f) Console.Print("Time.DeltaTime is not stable: " + timeDeltaDiff); @@ -413,13 +425,93 @@ public class PlayerMovement : Script } } - if (!predict) + if (World.IsServer) + { + ulong currentFrame = World.GetLastProcessedFrame(PlayerId); + { + // Rewind to last confirmed state + var frameInfo = World.GetPlayerFrame(PlayerId, currentFrame); + if (frameInfo != null && frameInfo.movementState.frame == currentFrame) + { + // Reset all state to latest confirmed state from server + movementState.position = frameInfo.movementState.position; + movementState.currentVelocity = frameInfo.movementState.currentVelocity; + movementState.lastJumped = frameInfo.movementState.lastJumped; + movementState.numJumps = frameInfo.movementState.numJumps; + movementState.jumped = frameInfo.movementState.jumped; + Actor.Position = movementState.position; + SetCameraEulerAngles(frameInfo.movementState.viewAngles, true); + //ApplyInputToCamera(frameInfo.inputState, true); + } + else if (frameInfo == null) + { + // First frame, update the known state + movementState.viewAngles = viewAngles; + World.UpdatePlayerState(PlayerId, currentFrame, movementState); + } + currentFrame++; + } + + //Console.Print("before:" + viewAngles.ToString()); + + ulong lastFrame = World.GetLastReceivedFrame(PlayerId); + PlayerFrame lastFrameInfo = null; + for (; currentFrame < lastFrame+1; currentFrame++) + { + var frameInfo = World.GetPlayerFrame(PlayerId, currentFrame); + if (frameInfo == null || frameInfo.inputState.Frame != currentFrame) + //if (!Input.GetState(currentFrame, out var pastInputState, out var pastActorState)) + { + Console.Print($"frame {currentFrame} missing"); + frameInfo = lastFrameInfo; // Use last known input + + if (frameInfo == null) + { + // FIXME + frameInfo = new PlayerFrame() + { + inputState = + { + Frame = currentFrame, + }, + movementState = movementState, + }; + frameInfo.movementState.frame = currentFrame; + } + //continue; + //Console.Print($"not predicting"); + //predict = false; + //break; + } + else + { + Input.SetFrame(currentFrame); + Input.UpdateState(); + + ApplyInputToCamera(frameInfo.inputState, true); + lastFrameInfo = frameInfo; + } + SimulatePlayerMovement(frameInfo.inputState, currentFrame); + movementState.viewAngles = viewAngles; + + //Console.Print("tick: " + viewAngles.ToString()); + + //if (frameInfo.movementState.frame != currentFrame) + World.UpdatePlayerState(PlayerId, currentFrame, movementState); + //else + // currentFrame = currentFrame; + } + } + else if (!predict) { SetCameraEulerAngles(viewAnglesLastFrame, true); ApplyInputToCamera(inputState, true); SimulatePlayerMovement(inputState, World.Frame); // MAYBE? } + //if (!movementState.currentVelocity.IsZero) + // Console.Print($"{(World.IsClient ? "client" : "server")} {World.Frame} vel: {movementState.currentVelocity}, pos: {movementState.position}"); + viewAnglesLastFrame = viewAngles; #if false