Repository: turanszkij/WickedEngine (language: cpp)

Issue:
IK seems to be framerate dependent and bugs out on higher framerates
See the attached video. With 60 FPS, the IK work as expected, with 165 FPS, there seem to be some rounding errors or something, making the IK very erratic (it's much more noticable at 165 than in this 60 FPS video, it seems like every second frame has the leg stretched instead of bent)

https://github.com/turanszkij/WickedEngine/assets/541637/4ecf61f6-f7ec-47b3-8ee1-5a3ca9d7250a




Discussion / hints:
Upon further testing, IK still looks ok at 120 Hz, but at a 144 Hz refresh rate, the bug appears, I can't select more granular refresh rates, and I can't find an option to limit FPS to a fixed value besides activating VSync and setting the monitor refresh rate, so I can't say when exactly it starts to fail.

Fix patch:
diff --git a/Content/scripts/character_controller/character_controller.lua b/Content/scripts/character_controller/character_controller.lua
index d963748f92..f0beb80fd2 100644
--- a/Content/scripts/character_controller/character_controller.lua
+++ b/Content/scripts/character_controller/character_controller.lua
@@ -329,6 +329,7 @@ local function Character(model_name, start_position, face, controllable)
 		position = Vector(),
 		controllable = true,
 		fixed_update_remain = 0,
+		timestep_occured = false,
 		root_bone_offset = 0,
 		foot_placed_left = false,
 		foot_placed_right = false,
@@ -708,8 +709,10 @@ local function Character(model_name, start_position, face, controllable)
 			self.fixed_update_remain = self.fixed_update_remain + dt
 			local fixed_update_fps = 120
 			local fixed_dt = 1.0 / fixed_update_fps
+			self.timestep_occured = false;
 
 			while self.fixed_update_remain >= fixed_dt do
+				self.timestep_occured = true;
 				self.fixed_update_remain = self.fixed_update_remain - fixed_dt
 				
 				capsulepos = vector.Add(capsulepos, vector.Multiply(self.velocity, fixed_dt))
@@ -813,6 +816,11 @@ local function Character(model_name, start_position, face, controllable)
 		end,
 
 		Update_IK = function(self)
+			-- Make sure we only update IK as often as we check for ground collisions etc.
+			-- in Update. Otherwise, when the framerate is higher than the step rate, 
+			-- self.velocity is unreliable
+			if not self.timestep_occured then return end;
+
 			-- IK foot placement:
 			local base_y = self.position.GetY()
 			local ik_foot = INVALID_ENTITY


Test patch: