From f2edb9df784dad7a6f801dd7eb1f9ab3185d9473 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 20:46:36 -0800 Subject: [PATCH 01/19] Decouple pitch/roll rate damping from render update --- .../GameEngine/Source/GameClient/Drawable.cpp | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 5071dcfa290..d3fcfd4873a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1786,11 +1786,17 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI // the ground can only push back if we're touching it if (overlapped || m_locoInfo->m_overlapZ <= 0.0f) { - m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper + // TheSuperHackers @tweak The pitch/roll rate damping is now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + + m_locoInfo->m_pitchRate += timeScale * ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper if (m_locoInfo->m_pitchRate > 0.0f) - m_locoInfo->m_pitchRate *= 0.5f; + { + const Real pitchDamp = 1.0f - (1.0f - 0.5f) * timeScale; + m_locoInfo->m_pitchRate *= pitchDamp; + } - m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper + m_locoInfo->m_rollRate += timeScale * ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; @@ -2013,11 +2019,17 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI // the ground can only push back if we're touching it if (!airborne) { - m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper + // TheSuperHackers @tweak The pitch/roll rate damping is now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + + m_locoInfo->m_pitchRate += timeScale * ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper if (m_locoInfo->m_pitchRate > 0.0f) - m_locoInfo->m_pitchRate *= 0.5f; + { + const Real pitchDamp = 1.0f - (1.0f - 0.5f) * timeScale; + m_locoInfo->m_pitchRate *= pitchDamp; + } - m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper + m_locoInfo->m_rollRate += timeScale * ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; From aea61c854da5218175e6caed552698954bff8bed Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 20:46:36 -0800 Subject: [PATCH 02/19] Replicate to generals --- .../GameEngine/Source/GameClient/Drawable.cpp | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index 4d322225af4..1270669a217 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1640,11 +1640,17 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI // the ground can only push back if we're touching it if (overlapped || m_locoInfo->m_overlapZ <= 0.0f) { - m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper + // TheSuperHackers @tweak The pitch/roll rate damping is now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + + m_locoInfo->m_pitchRate += timeScale * ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper if (m_locoInfo->m_pitchRate > 0.0f) - m_locoInfo->m_pitchRate *= 0.5f; + { + const Real pitchDamp = 1.0f - (1.0f - 0.5f) * timeScale; + m_locoInfo->m_pitchRate *= pitchDamp; + } - m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper + m_locoInfo->m_rollRate += timeScale * ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; @@ -1866,11 +1872,17 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI // the ground can only push back if we're touching it if (!airborne) { - m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper + // TheSuperHackers @tweak The pitch/roll rate damping is now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + + m_locoInfo->m_pitchRate += timeScale * ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper if (m_locoInfo->m_pitchRate > 0.0f) - m_locoInfo->m_pitchRate *= 0.5f; + { + const Real pitchDamp = 1.0f - (1.0f - 0.5f) * timeScale; + m_locoInfo->m_pitchRate *= pitchDamp; + } - m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper + m_locoInfo->m_rollRate += timeScale * ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; From 0abdcc56cbf546863c02258a49866d9394f115e2 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 17:23:59 -0800 Subject: [PATCH 03/19] Decouple wheel suspension offset from render update --- .../GameEngine/Source/GameClient/Drawable.cpp | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index d3fcfd4873a..ccc650ce5fb 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1960,15 +1960,20 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI // Wheels extend when airborne. m_locoInfo->m_wheelInfo.m_framesAirborne = 0; m_locoInfo->m_wheelInfo.m_framesAirborneCounter++; + + // TheSuperHackers @tweak Wheel suspension offset is now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real suspensionFactor = 0.5f * timeScale; + if (pos->z - hheight > -MAX_SUSPENSION_EXTENSION) { - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f; - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) * suspensionFactor; + m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset) * suspensionFactor; } else { - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f; - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) * suspensionFactor; + m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset) * suspensionFactor; } } // Calculate suspension info. @@ -2130,27 +2135,31 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI newInfo.m_rearLeftHeightOffset += SPRING_FACTOR*(rollHeight/3+rollHeight/2); newInfo.m_frontLeftHeightOffset += SPRING_FACTOR*(rollHeight/3+rollHeight/2); } + // TheSuperHackers @tweak Wheel compression dampening is now decoupled from the render update. + const Real compressionTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real compressionFactor = 0.5f * compressionTimeScale; + if (newInfo.m_frontLeftHeightOffset < m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset) { // If it's going down, dampen the movement a bit - m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset += (newInfo.m_frontLeftHeightOffset - m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset += (newInfo.m_frontLeftHeightOffset - m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset) * compressionFactor; } else { m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset = newInfo.m_frontLeftHeightOffset; } if (newInfo.m_frontRightHeightOffset < m_locoInfo->m_wheelInfo.m_frontRightHeightOffset) { // If it's going down, dampen the movement a bit - m_locoInfo->m_wheelInfo.m_frontRightHeightOffset += (newInfo.m_frontRightHeightOffset - m_locoInfo->m_wheelInfo.m_frontRightHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_frontRightHeightOffset += (newInfo.m_frontRightHeightOffset - m_locoInfo->m_wheelInfo.m_frontRightHeightOffset) * compressionFactor; } else { m_locoInfo->m_wheelInfo.m_frontRightHeightOffset = newInfo.m_frontRightHeightOffset; } if (newInfo.m_rearLeftHeightOffset < m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) { // If it's going down, dampen the movement a bit - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (newInfo.m_rearLeftHeightOffset - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (newInfo.m_rearLeftHeightOffset - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) * compressionFactor; } else { m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset = newInfo.m_rearLeftHeightOffset; } if (newInfo.m_rearRightHeightOffset < m_locoInfo->m_wheelInfo.m_rearRightHeightOffset) { // If it's going down, dampen the movement a bit - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (newInfo.m_rearRightHeightOffset - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (newInfo.m_rearRightHeightOffset - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset) * compressionFactor; } else { m_locoInfo->m_wheelInfo.m_rearRightHeightOffset = newInfo.m_rearRightHeightOffset; } @@ -2261,14 +2270,19 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf // Wheels extend when airborne. m_locoInfo->m_wheelInfo.m_framesAirborne = 0; m_locoInfo->m_wheelInfo.m_framesAirborneCounter++; + + // TheSuperHackers @tweak Wheel suspension offset is now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real suspensionFactor = 0.5f * timeScale; + if (pos->z - hheight > -MAX_SUSPENSION_EXTENSION) { - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) * suspensionFactor; m_locoInfo->m_wheelInfo.m_rearRightHeightOffset = m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset; } else { - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) * suspensionFactor; m_locoInfo->m_wheelInfo.m_rearRightHeightOffset = m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset; } } @@ -2440,10 +2454,14 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf newInfo.m_rearLeftHeightOffset += SPRING_FACTOR*(rollHeight/3+rollHeight/2); } */ + // TheSuperHackers @tweak Wheel compression dampening is now decoupled from the render update. + const Real compressionTimeScale2 = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real compressionFactor2 = 0.5f * compressionTimeScale2; + if (newInfo.m_frontLeftHeightOffset < m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset) { // If it's going down, dampen the movement a bit - m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset += (newInfo.m_frontLeftHeightOffset - m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset += (newInfo.m_frontLeftHeightOffset - m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset) * compressionFactor2; m_locoInfo->m_wheelInfo.m_frontRightHeightOffset = m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset; } else @@ -2454,7 +2472,7 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf if (newInfo.m_rearLeftHeightOffset < m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) { // If it's going down, dampen the movement a bit - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (newInfo.m_rearLeftHeightOffset - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (newInfo.m_rearLeftHeightOffset - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) * compressionFactor2; m_locoInfo->m_wheelInfo.m_rearRightHeightOffset = m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset; } else From 2497b93b704832f7dd52a8cf5a6a0a71de86984c Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 18:47:56 -0800 Subject: [PATCH 04/19] Replicate to generals --- .../GameEngine/Source/GameClient/Drawable.cpp | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index 1270669a217..67a44f19167 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1813,15 +1813,20 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI // Wheels extend when airborne. m_locoInfo->m_wheelInfo.m_framesAirborne = 0; m_locoInfo->m_wheelInfo.m_framesAirborneCounter++; + + // TheSuperHackers @tweak Wheel suspension offset is now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real suspensionFactor = 0.5f * timeScale; + if (pos->z - hheight > -MAX_SUSPENSION_EXTENSION) { - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f; - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) * suspensionFactor; + m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset) * suspensionFactor; } else { - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f; - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) * suspensionFactor; + m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset) * suspensionFactor; } } // Calculate suspension info. @@ -1983,27 +1988,31 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI newInfo.m_rearLeftHeightOffset += SPRING_FACTOR*(rollHeight/3+rollHeight/2); newInfo.m_frontLeftHeightOffset += SPRING_FACTOR*(rollHeight/3+rollHeight/2); } + // TheSuperHackers @tweak Wheel compression dampening is now decoupled from the render update. + const Real compressionTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real compressionFactor = 0.5f * compressionTimeScale; + if (newInfo.m_frontLeftHeightOffset < m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset) { // If it's going down, dampen the movement a bit - m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset += (newInfo.m_frontLeftHeightOffset - m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset += (newInfo.m_frontLeftHeightOffset - m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset) * compressionFactor; } else { m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset = newInfo.m_frontLeftHeightOffset; } if (newInfo.m_frontRightHeightOffset < m_locoInfo->m_wheelInfo.m_frontRightHeightOffset) { // If it's going down, dampen the movement a bit - m_locoInfo->m_wheelInfo.m_frontRightHeightOffset += (newInfo.m_frontRightHeightOffset - m_locoInfo->m_wheelInfo.m_frontRightHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_frontRightHeightOffset += (newInfo.m_frontRightHeightOffset - m_locoInfo->m_wheelInfo.m_frontRightHeightOffset) * compressionFactor; } else { m_locoInfo->m_wheelInfo.m_frontRightHeightOffset = newInfo.m_frontRightHeightOffset; } if (newInfo.m_rearLeftHeightOffset < m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) { // If it's going down, dampen the movement a bit - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (newInfo.m_rearLeftHeightOffset - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (newInfo.m_rearLeftHeightOffset - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) * compressionFactor; } else { m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset = newInfo.m_rearLeftHeightOffset; } if (newInfo.m_rearRightHeightOffset < m_locoInfo->m_wheelInfo.m_rearRightHeightOffset) { // If it's going down, dampen the movement a bit - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (newInfo.m_rearRightHeightOffset - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset)/2.0f; + m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (newInfo.m_rearRightHeightOffset - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset) * compressionFactor; } else { m_locoInfo->m_wheelInfo.m_rearRightHeightOffset = newInfo.m_rearRightHeightOffset; } From d0133126288a94946cfb97b6c92611b65f8849e4 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 19:28:32 -0800 Subject: [PATCH 05/19] Decouple missile wobble and thrust roll from render update --- .../GameEngine/Source/GameClient/Drawable.cpp | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index ccc650ce5fb..05afb481476 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1449,6 +1449,11 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI Real MAX_WOBBLE = locomotor->getMaxWobble(); Real MIN_WOBBLE = locomotor->getMinWobble(); + // TheSuperHackers @tweak Wobble and thrust roll rates are now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real scaledWobbleRate = WOBBLE_RATE * timeScale; + const Real scaledThrustRoll = THRUST_ROLL * timeScale; + // // this is a kind of quick thrust implementation cause we need scud missiles to wobble *now*, // we deal with just adjusting pitch, yaw, and roll just a little bit @@ -1463,15 +1468,15 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI if( m_locoInfo->m_pitch < MAX_WOBBLE - WOBBLE_RATE * 2 ) { - m_locoInfo->m_pitch += WOBBLE_RATE; - m_locoInfo->m_yaw += WOBBLE_RATE; + m_locoInfo->m_pitch += scaledWobbleRate; + m_locoInfo->m_yaw += scaledWobbleRate; } else { - m_locoInfo->m_pitch += (WOBBLE_RATE / 2.0f); - m_locoInfo->m_yaw += (WOBBLE_RATE / 2.0f); + m_locoInfo->m_pitch += (scaledWobbleRate / 2.0f); + m_locoInfo->m_yaw += (scaledWobbleRate / 2.0f); } @@ -1485,15 +1490,15 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI if( m_locoInfo->m_pitch >= MIN_WOBBLE + WOBBLE_RATE * 2.0f ) { - m_locoInfo->m_pitch -= WOBBLE_RATE; - m_locoInfo->m_yaw -= WOBBLE_RATE; + m_locoInfo->m_pitch -= scaledWobbleRate; + m_locoInfo->m_yaw -= scaledWobbleRate; } else { - m_locoInfo->m_pitch -= (WOBBLE_RATE / 2.0f); - m_locoInfo->m_yaw -= (WOBBLE_RATE / 2.0f); + m_locoInfo->m_pitch -= (scaledWobbleRate / 2.0f); + m_locoInfo->m_yaw -= (scaledWobbleRate / 2.0f); } if( m_locoInfo->m_pitch <= MIN_WOBBLE ) @@ -1509,7 +1514,7 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI if( THRUST_ROLL ) { - m_locoInfo->m_roll += THRUST_ROLL; + m_locoInfo->m_roll += scaledThrustRoll; info.m_totalRoll = m_locoInfo->m_roll; } From fc84481ffd8ba1e1134967d77a297445af22ba2c Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 19:34:05 -0800 Subject: [PATCH 06/19] Replicate to generals --- .../GameEngine/Source/GameClient/Drawable.cpp | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index 67a44f19167..73414d0da69 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1317,6 +1317,11 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI Real MAX_WOBBLE = locomotor->getMaxWobble(); Real MIN_WOBBLE = locomotor->getMinWobble(); + // TheSuperHackers @tweak Wobble and thrust roll rates are now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real scaledWobbleRate = WOBBLE_RATE * timeScale; + const Real scaledThrustRoll = THRUST_ROLL * timeScale; + // // this is a kind of quick thrust implementation cause we need scud missiles to wobble *now*, // we deal with just adjusting pitch, yaw, and roll just a little bit @@ -1331,15 +1336,15 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI if( m_locoInfo->m_pitch < MAX_WOBBLE - WOBBLE_RATE * 2 ) { - m_locoInfo->m_pitch += WOBBLE_RATE; - m_locoInfo->m_yaw += WOBBLE_RATE; + m_locoInfo->m_pitch += scaledWobbleRate; + m_locoInfo->m_yaw += scaledWobbleRate; } else { - m_locoInfo->m_pitch += (WOBBLE_RATE / 2.0f); - m_locoInfo->m_yaw += (WOBBLE_RATE / 2.0f); + m_locoInfo->m_pitch += (scaledWobbleRate / 2.0f); + m_locoInfo->m_yaw += (scaledWobbleRate / 2.0f); } @@ -1353,15 +1358,15 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI if( m_locoInfo->m_pitch >= MIN_WOBBLE + WOBBLE_RATE * 2.0f ) { - m_locoInfo->m_pitch -= WOBBLE_RATE; - m_locoInfo->m_yaw -= WOBBLE_RATE; + m_locoInfo->m_pitch -= scaledWobbleRate; + m_locoInfo->m_yaw -= scaledWobbleRate; } else { - m_locoInfo->m_pitch -= (WOBBLE_RATE / 2.0f); - m_locoInfo->m_yaw -= (WOBBLE_RATE / 2.0f); + m_locoInfo->m_pitch -= (scaledWobbleRate / 2.0f); + m_locoInfo->m_yaw -= (scaledWobbleRate / 2.0f); } if( m_locoInfo->m_pitch <= MIN_WOBBLE ) @@ -1377,7 +1382,7 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI if( THRUST_ROLL ) { - m_locoInfo->m_roll += THRUST_ROLL; + m_locoInfo->m_roll += scaledThrustRoll; info.m_totalRoll = m_locoInfo->m_roll; } From 2673ff90e41dfae89b441fab9447d28898b98a35 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 19:47:04 -0800 Subject: [PATCH 07/19] Decouple hover/wings spring-damper physics from render update --- .../GameEngine/Source/GameClient/Drawable.cpp | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 05afb481476..49221126df0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1562,19 +1562,22 @@ void Drawable::calcPhysicsXformHoverOrWings( const Locomotor *locomotor, Physics const Coord3D* accel = physics->getAcceleration(); const Coord3D* vel = physics->getVelocity(); - m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper - m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper + // TheSuperHackers @tweak Spring-damper physics are now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)) * timeScale; // spring/damper + m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)) * timeScale; // spring/damper + + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; // process chassis acceleration dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -1588,23 +1591,23 @@ void Drawable::calcPhysicsXformHoverOrWings( const Locomotor *locomotor, Physics if (fabs(vel->z) > TINY_DZ) { Real pitch = atan2(vel->z, sqrt(sqr(vel->x)+sqr(vel->y))); - m_locoInfo->m_pitch -= Z_VEL_PITCH_COEFF * pitch; + m_locoInfo->m_pitch -= Z_VEL_PITCH_COEFF * pitch * timeScale; } } // cause the chassis to pitch & roll in reaction to current speed Real forwardVel = dir->x * vel->x + dir->y * vel->y; - m_locoInfo->m_pitch += -(FORWARD_VEL_COEFF * forwardVel); + m_locoInfo->m_pitch += -(FORWARD_VEL_COEFF * forwardVel) * timeScale; Real lateralVel = -dir->y * vel->x + dir->x * vel->y; - m_locoInfo->m_roll += -(LATERAL_VEL_COEFF * lateralVel); + m_locoInfo->m_roll += -(LATERAL_VEL_COEFF * lateralVel) * timeScale; // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; } // limit acceleration pitch and roll From f38a2adf55fdea72cdebaa36de1d9ee713cb1b2e Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 19:50:29 -0800 Subject: [PATCH 08/19] Replicate to generals --- .../GameEngine/Source/GameClient/Drawable.cpp | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index 73414d0da69..9674a7a8fc0 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1428,19 +1428,22 @@ void Drawable::calcPhysicsXformHoverOrWings( const Locomotor *locomotor, Physics const Coord3D* accel = physics->getAcceleration(); const Coord3D* vel = physics->getVelocity(); - m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper - m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper + // TheSuperHackers @tweak Spring-damper physics are now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)) * timeScale; // spring/damper + m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)) * timeScale; // spring/damper + + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; // process chassis acceleration dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -1454,23 +1457,23 @@ void Drawable::calcPhysicsXformHoverOrWings( const Locomotor *locomotor, Physics if (fabs(vel->z) > TINY_DZ) { Real pitch = atan2(vel->z, sqrt(sqr(vel->x)+sqr(vel->y))); - m_locoInfo->m_pitch -= Z_VEL_PITCH_COEFF * pitch; + m_locoInfo->m_pitch -= Z_VEL_PITCH_COEFF * pitch * timeScale; } } // cause the chassis to pitch & roll in reaction to current speed Real forwardVel = dir->x * vel->x + dir->y * vel->y; - m_locoInfo->m_pitch += -(FORWARD_VEL_COEFF * forwardVel); + m_locoInfo->m_pitch += -(FORWARD_VEL_COEFF * forwardVel) * timeScale; Real lateralVel = -dir->y * vel->x + dir->x * vel->y; - m_locoInfo->m_roll += -(LATERAL_VEL_COEFF * lateralVel); + m_locoInfo->m_roll += -(LATERAL_VEL_COEFF * lateralVel) * timeScale; // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; } // limit acceleration pitch and roll From a11801c1c440a4e276677110dba9655135dd1358 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 20:08:43 -0800 Subject: [PATCH 09/19] Decouple remaining Drawable physics from render update --- .../GameEngine/Source/GameClient/Drawable.cpp | 83 +++++++++++-------- 1 file changed, 50 insertions(+), 33 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 49221126df0..626f91c2a4b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1778,7 +1778,11 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI // if we had an overlap last frame, and we're now in the air, give a // kick to the pitch for effect if (physics->getPreviousOverlap() != INVALID_ID && m_locoInfo->m_overlapZ > 0.0f) - m_locoInfo->m_pitchRate += LEAVE_OVERLAP_PITCH_KICK; + { + // TheSuperHackers @tweak Leave overlap pitch kick is now decoupled from the render update. + const Real overlapTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_pitchRate += LEAVE_OVERLAP_PITCH_KICK * overlapTimeScale; + } } @@ -1853,8 +1857,10 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI Real recoil = PI/16.0f * GameClientRandomValueReal( 0.5f, 1.0f ); - m_locoInfo->m_accelerationPitchRate -= recoil * forward; - m_locoInfo->m_accelerationRollRate -= recoil * lateral; + // TheSuperHackers @tweak Hit recoil is now decoupled from the render update. + const Real hitRecoilTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_accelerationPitchRate -= recoil * forward * hitRecoilTimeScale; + m_locoInfo->m_accelerationRollRate -= recoil * lateral * hitRecoilTimeScale; } m_lastDamageTimestamp = obj->getBodyModule()->getLastDamageTimestamp(); @@ -2002,24 +2008,27 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI Real factor = curSpeed/maxSpeed; if (fabs(m_locoInfo->m_pitchRate)m_rollRate)getActualLogicTimeScaleOverFpsRatio(); + const Real scaledBounceKick = BOUNCE_ANGLE_KICK * factor * bounceTimeScale; // do the bouncy. switch (GameClientRandomValue(0,3)) { case 0: - m_locoInfo->m_pitchRate -= BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate -= BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate -= scaledBounceKick; + m_locoInfo->m_rollRate -= scaledBounceKick/2; break; case 1: - m_locoInfo->m_pitchRate += BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate -= BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate += scaledBounceKick; + m_locoInfo->m_rollRate -= scaledBounceKick/2; break; case 2: - m_locoInfo->m_pitchRate -= BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate += BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate -= scaledBounceKick; + m_locoInfo->m_rollRate += scaledBounceKick/2; break; case 3: - m_locoInfo->m_pitchRate += BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate += BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate += scaledBounceKick; + m_locoInfo->m_rollRate += scaledBounceKick/2; break; } } @@ -2312,24 +2321,27 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf Real factor = curSpeed/maxSpeed; if (fabs(m_locoInfo->m_pitchRate)m_rollRate)getActualLogicTimeScaleOverFpsRatio(); + const Real scaledBounceKick = BOUNCE_ANGLE_KICK * factor * bounceTimeScale; // do the bouncy. switch (GameClientRandomValue(0,3)) { case 0: - m_locoInfo->m_pitchRate -= BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate -= BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate -= scaledBounceKick; + m_locoInfo->m_rollRate -= scaledBounceKick/2; break; case 1: - m_locoInfo->m_pitchRate += BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate -= BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate += scaledBounceKick; + m_locoInfo->m_rollRate -= scaledBounceKick/2; break; case 2: - m_locoInfo->m_pitchRate -= BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate += BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate -= scaledBounceKick; + m_locoInfo->m_rollRate += scaledBounceKick/2; break; case 3: - m_locoInfo->m_pitchRate += BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate += BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate += scaledBounceKick; + m_locoInfo->m_rollRate += scaledBounceKick/2; break; } } @@ -2339,29 +2351,32 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf // process chassis suspension dynamics - damp back towards groundPitch + // TheSuperHackers @tweak Spring-damper physics are now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + // the ground can only push back if we're touching it if (!airborne) { - m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper - m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper + m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)) * timeScale; // spring/damper + m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)) * timeScale; // spring/damper } else { //Autolevel - m_locoInfo->m_pitchRate += ( (-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate) ); // spring/damper - m_locoInfo->m_rollRate += ( (-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate) ); // spring/damper + m_locoInfo->m_pitchRate += ( (-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate) ) * timeScale; // spring/damper + m_locoInfo->m_rollRate += ( (-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate) ) * timeScale; // spring/damper } - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; // process chassis acceleration dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -2380,10 +2395,10 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf { // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; } // limit acceleration pitch and roll @@ -4285,8 +4300,10 @@ Bool Drawable::handleWeaponFireFX(WeaponSlotType wslot, Int specificBarrelToUse, recoilAngle += PI; if (m_locoInfo) { - m_locoInfo->m_accelerationPitchRate += recoilAmount * Cos(recoilAngle); - m_locoInfo->m_accelerationRollRate += recoilAmount * Sin(recoilAngle); + // TheSuperHackers @tweak Weapon recoil is now decoupled from the render update. + const Real recoilTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_accelerationPitchRate += recoilAmount * Cos(recoilAngle) * recoilTimeScale; + m_locoInfo->m_accelerationRollRate += recoilAmount * Sin(recoilAngle) * recoilTimeScale; } } From bd1d1224d58b7917a9d7b44bb0c04caded81d73d Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 20:10:55 -0800 Subject: [PATCH 10/19] Replicate to generals --- .../GameEngine/Source/GameClient/Drawable.cpp | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index 9674a7a8fc0..da952b8cbb2 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1632,7 +1632,11 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI // if we had an overlap last frame, and we're now in the air, give a // kick to the pitch for effect if (physics->getPreviousOverlap() != INVALID_ID && m_locoInfo->m_overlapZ > 0.0f) - m_locoInfo->m_pitchRate += LEAVE_OVERLAP_PITCH_KICK; + { + // TheSuperHackers @tweak Leave overlap pitch kick is now decoupled from the render update. + const Real overlapTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_pitchRate += LEAVE_OVERLAP_PITCH_KICK * overlapTimeScale; + } } @@ -1707,8 +1711,10 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI Real recoil = PI/16.0f * GameClientRandomValueReal( 0.5f, 1.0f ); - m_locoInfo->m_accelerationPitchRate -= recoil * forward; - m_locoInfo->m_accelerationRollRate -= recoil * lateral; + // TheSuperHackers @tweak Hit recoil is now decoupled from the render update. + const Real hitRecoilTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_accelerationPitchRate -= recoil * forward * hitRecoilTimeScale; + m_locoInfo->m_accelerationRollRate -= recoil * lateral * hitRecoilTimeScale; } m_lastDamageTimestamp = obj->getBodyModule()->getLastDamageTimestamp(); @@ -1855,24 +1861,27 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI Real factor = curSpeed/maxSpeed; if (fabs(m_locoInfo->m_pitchRate)m_rollRate)getActualLogicTimeScaleOverFpsRatio(); + const Real scaledBounceKick = BOUNCE_ANGLE_KICK * factor * bounceTimeScale; // do the bouncy. switch (GameClientRandomValue(0,3)) { case 0: - m_locoInfo->m_pitchRate -= BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate -= BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate -= scaledBounceKick; + m_locoInfo->m_rollRate -= scaledBounceKick/2; break; case 1: - m_locoInfo->m_pitchRate += BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate -= BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate += scaledBounceKick; + m_locoInfo->m_rollRate -= scaledBounceKick/2; break; case 2: - m_locoInfo->m_pitchRate -= BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate += BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate -= scaledBounceKick; + m_locoInfo->m_rollRate += scaledBounceKick/2; break; case 3: - m_locoInfo->m_pitchRate += BOUNCE_ANGLE_KICK*factor; - m_locoInfo->m_rollRate += BOUNCE_ANGLE_KICK*factor/2; + m_locoInfo->m_pitchRate += scaledBounceKick; + m_locoInfo->m_rollRate += scaledBounceKick/2; break; } } @@ -3750,8 +3759,10 @@ Bool Drawable::handleWeaponFireFX(WeaponSlotType wslot, Int specificBarrelToUse, recoilAngle += PI; if (m_locoInfo) { - m_locoInfo->m_accelerationPitchRate += recoilAmount * Cos(recoilAngle); - m_locoInfo->m_accelerationRollRate += recoilAmount * Sin(recoilAngle); + // TheSuperHackers @tweak Weapon recoil is now decoupled from the render update. + const Real recoilTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_accelerationPitchRate += recoilAmount * Cos(recoilAngle) * recoilTimeScale; + m_locoInfo->m_accelerationRollRate += recoilAmount * Sin(recoilAngle) * recoilTimeScale; } } From 6a5542a816b3779407f3f34c718ceb49531c71aa Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 20:37:07 -0800 Subject: [PATCH 11/19] Fix additional missed physics timing issues --- .../GameEngine/Source/GameClient/Drawable.cpp | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 626f91c2a4b..1c8b097b028 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1629,8 +1629,10 @@ void Drawable::calcPhysicsXformHoverOrWings( const Locomotor *locomotor, Physics const Real ELEVATOR_CORRECTION_DEGREE = locomotor->getElevatorCorrectionDegree(); const Real ELEVATOR_CORRECTION_RATE = locomotor->getElevatorCorrectionRate(); - info.m_totalYaw = RUDDER_CORRECTION_DEGREE * sin( m_locoInfo->m_yawModulator += RUDDER_CORRECTION_RATE ); - info.m_totalPitch += ELEVATOR_CORRECTION_DEGREE * cos( m_locoInfo->m_pitchModulator += ELEVATOR_CORRECTION_RATE ); + // TheSuperHackers @tweak Rudder/elevator correction rates are now decoupled from the render update. + const Real correctionTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + info.m_totalYaw = RUDDER_CORRECTION_DEGREE * sin( m_locoInfo->m_yawModulator += RUDDER_CORRECTION_RATE * correctionTimeScale ); + info.m_totalPitch += ELEVATOR_CORRECTION_DEGREE * cos( m_locoInfo->m_pitchModulator += ELEVATOR_CORRECTION_RATE * correctionTimeScale ); info.m_totalZ = 0.0f; @@ -1795,12 +1797,12 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI // process chassis suspension dynamics - damp back towards groundPitch + // TheSuperHackers @tweak The physics are now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + // the ground can only push back if we're touching it if (overlapped || m_locoInfo->m_overlapZ <= 0.0f) { - // TheSuperHackers @tweak The pitch/roll rate damping is now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - m_locoInfo->m_pitchRate += timeScale * ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper if (m_locoInfo->m_pitchRate > 0.0f) { @@ -1811,16 +1813,16 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI m_locoInfo->m_rollRate += timeScale * ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; // process chassis recoil dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -1830,10 +1832,10 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI { // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; } #ifdef RECOIL_FROM_BEING_DAMAGED @@ -1892,10 +1894,12 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI Real ztmp = m_locoInfo->m_overlapZ/2.0f; // do fake Z physics + // TheSuperHackers @tweak Overlap Z physics is now decoupled from the render update. + const Real overlapTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); if (m_locoInfo->m_overlapZ > 0.0f) { - m_locoInfo->m_overlapZVel -= 0.2f; - m_locoInfo->m_overlapZ += m_locoInfo->m_overlapZVel; + m_locoInfo->m_overlapZVel -= 0.2f * overlapTimeScale; + m_locoInfo->m_overlapZ += m_locoInfo->m_overlapZVel * overlapTimeScale; } if (m_locoInfo->m_overlapZ <= 0.0f) @@ -2038,12 +2042,12 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI // process chassis suspension dynamics - damp back towards groundPitch + // TheSuperHackers @tweak The physics are now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + // the ground can only push back if we're touching it if (!airborne) { - // TheSuperHackers @tweak The pitch/roll rate damping is now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - m_locoInfo->m_pitchRate += timeScale * ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper if (m_locoInfo->m_pitchRate > 0.0f) { @@ -2054,16 +2058,16 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI m_locoInfo->m_rollRate += timeScale * ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; // process chassis acceleration dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -2073,10 +2077,10 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI { // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; } // limit acceleration pitch and roll @@ -2126,8 +2130,10 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI // etc, this smaller angle we'll be adding covers the constant wheel shifting // left and right when moving in a relatively straight line // + // TheSuperHackers @tweak Wheel angle smoothing is now decoupled from the render update. #define WHEEL_SMOOTHNESS 10.0f // higher numbers add smaller angles, make it more "smooth" - m_locoInfo->m_wheelInfo.m_wheelAngle += (newInfo.m_wheelAngle - m_locoInfo->m_wheelInfo.m_wheelAngle)/WHEEL_SMOOTHNESS; + const Real wheelAngleTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_wheelInfo.m_wheelAngle += (newInfo.m_wheelAngle - m_locoInfo->m_wheelInfo.m_wheelAngle)/WHEEL_SMOOTHNESS * wheelAngleTimeScale; const Real SPRING_FACTOR = 0.9f; if (pitchHeight<0) { // Front raising up @@ -2448,8 +2454,10 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf // etc, this smaller angle we'll be adding covers the constant wheel shifting // left and right when moving in a relatively straight line // + // TheSuperHackers @tweak Wheel angle smoothing is now decoupled from the render update. #define WHEEL_SMOOTHNESS 10.0f // higher numbers add smaller angles, make it more "smooth" - m_locoInfo->m_wheelInfo.m_wheelAngle += (newInfo.m_wheelAngle - m_locoInfo->m_wheelInfo.m_wheelAngle)/WHEEL_SMOOTHNESS; + const Real wheelAngleTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_wheelInfo.m_wheelAngle += (newInfo.m_wheelAngle - m_locoInfo->m_wheelInfo.m_wheelAngle)/WHEEL_SMOOTHNESS * wheelAngleTimeScale; const Real SPRING_FACTOR = 0.9f; if (pitchHeight<0) From 38d8237c8555af9985cd89279a2073444dd212b6 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 20:39:09 -0800 Subject: [PATCH 12/19] Replicate to generals --- .../GameEngine/Source/GameClient/Drawable.cpp | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index da952b8cbb2..0eb0427270f 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1649,12 +1649,12 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI // process chassis suspension dynamics - damp back towards groundPitch + // TheSuperHackers @tweak The physics are now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + // the ground can only push back if we're touching it if (overlapped || m_locoInfo->m_overlapZ <= 0.0f) { - // TheSuperHackers @tweak The pitch/roll rate damping is now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - m_locoInfo->m_pitchRate += timeScale * ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper if (m_locoInfo->m_pitchRate > 0.0f) { @@ -1665,16 +1665,16 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI m_locoInfo->m_rollRate += timeScale * ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; // process chassis recoil dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -1684,10 +1684,10 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI { // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; } #ifdef RECOIL_FROM_BEING_DAMAGED @@ -1746,10 +1746,12 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI Real ztmp = m_locoInfo->m_overlapZ/2.0f; // do fake Z physics + // TheSuperHackers @tweak Overlap Z physics is now decoupled from the render update. + const Real overlapTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); if (m_locoInfo->m_overlapZ > 0.0f) { - m_locoInfo->m_overlapZVel -= 0.2f; - m_locoInfo->m_overlapZ += m_locoInfo->m_overlapZVel; + m_locoInfo->m_overlapZVel -= 0.2f * overlapTimeScale; + m_locoInfo->m_overlapZ += m_locoInfo->m_overlapZVel * overlapTimeScale; } if (m_locoInfo->m_overlapZ <= 0.0f) @@ -1891,12 +1893,12 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI // process chassis suspension dynamics - damp back towards groundPitch + // TheSuperHackers @tweak The physics are now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + // the ground can only push back if we're touching it if (!airborne) { - // TheSuperHackers @tweak The pitch/roll rate damping is now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - m_locoInfo->m_pitchRate += timeScale * ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper if (m_locoInfo->m_pitchRate > 0.0f) { @@ -1907,16 +1909,16 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI m_locoInfo->m_rollRate += timeScale * ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; // process chassis acceleration dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -1926,10 +1928,10 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI { // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; } // limit acceleration pitch and roll @@ -1979,8 +1981,10 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI // etc, this smaller angle we'll be adding covers the constant wheel shifting // left and right when moving in a relatively straight line // + // TheSuperHackers @tweak Wheel angle smoothing is now decoupled from the render update. #define WHEEL_SMOOTHNESS 10.0f // higher numbers add smaller angles, make it more "smooth" - m_locoInfo->m_wheelInfo.m_wheelAngle += (newInfo.m_wheelAngle - m_locoInfo->m_wheelInfo.m_wheelAngle)/WHEEL_SMOOTHNESS; + const Real wheelAngleTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_wheelInfo.m_wheelAngle += (newInfo.m_wheelAngle - m_locoInfo->m_wheelInfo.m_wheelAngle)/WHEEL_SMOOTHNESS * wheelAngleTimeScale; const Real SPRING_FACTOR = 0.9f; if (pitchHeight<0) { // Front raising up From 8a8bb9965170b9f2ab9addf3fff3e85ab2f12f4f Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 20:51:51 -0800 Subject: [PATCH 13/19] Decouple decal opacity fade from render update --- GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 1c8b097b028..5ffff730cc5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1188,7 +1188,9 @@ void Drawable::updateDrawable( void ) { //LERP (*dm)->setTerrainDecalOpacity(m_decalOpacity); - m_decalOpacity += m_decalOpacityFadeRate; + // TheSuperHackers @tweak Decal opacity fade is now decoupled from the render update. + const Real decalFadeTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_decalOpacity += m_decalOpacityFadeRate * decalFadeTimeScale; } //--------------- From 91e6d98daf2e962444c5e22648ea870d4425f912 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 20:52:12 -0800 Subject: [PATCH 14/19] Replicate to generals --- Generals/Code/GameEngine/Source/GameClient/Drawable.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index 0eb0427270f..e27e9b68c39 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1113,7 +1113,9 @@ void Drawable::updateDrawable( void ) { //LERP (*dm)->setTerrainDecalOpacity(m_decalOpacity); - m_decalOpacity += m_decalOpacityFadeRate; + // TheSuperHackers @tweak Decal opacity fade is now decoupled from the render update. + const Real decalFadeTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_decalOpacity += m_decalOpacityFadeRate * decalFadeTimeScale; } //--------------- From 1b271859c5c7b5f09f3c86f49a866265d7ee7782 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 21:10:54 -0800 Subject: [PATCH 15/19] Decouple drawable fade timing from render update --- .../GameEngine/Include/GameClient/Drawable.h | 2 +- .../GameEngine/Source/GameClient/Drawable.cpp | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index b66eb7dc96a..65bdbfb6ea1 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h @@ -691,7 +691,7 @@ class Drawable : public Thing, FADING_OUT }; FadingMode m_fadeMode; - UnsignedInt m_timeElapsedFade; ///< for how many frames have i been fading + Real m_timeElapsedFade; ///< for how long have i been fading (in 30fps-equivalent frames) UnsignedInt m_timeToFade; ///< how slowly am I fading UnsignedInt m_shroudClearFrame; ///< Last frame the local player saw this drawable "OBJECTSHROUD_CLEAR" diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 5ffff730cc5..1b4910f32c2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1170,7 +1170,9 @@ void Drawable::updateDrawable( void ) Real numer = (m_fadeMode == FADING_IN) ? (m_timeElapsedFade) : (m_timeToFade-m_timeElapsedFade); setDrawableOpacity(numer/(Real)m_timeToFade); - ++m_timeElapsedFade; + // TheSuperHackers @tweak Drawable fade is now decoupled from the render update. + const Real fadeTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_timeElapsedFade += fadeTimeScale; if (m_timeElapsedFade > m_timeToFade) m_fadeMode = FADING_NONE; @@ -4993,7 +4995,7 @@ void Drawable::xfer( Xfer *xfer ) #if RETAIL_COMPATIBLE_XFER_SAVE const XferVersion currentVersion = 7; #else - const XferVersion currentVersion = 8; + const XferVersion currentVersion = 9; #endif XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); @@ -5168,7 +5170,17 @@ void Drawable::xfer( Xfer *xfer ) xfer->xferUser( &m_fadeMode, sizeof( FadingMode ) ); // time elapsed fade - xfer->xferUnsignedInt( &m_timeElapsedFade ); + // TheSuperHackers @tweak Changed from UnsignedInt to Real for frame-rate independent fading. + if (version >= 9) + { + xfer->xferReal( &m_timeElapsedFade ); + } + else + { + UnsignedInt timeElapsedFadeFrames = static_cast(m_timeElapsedFade); + xfer->xferUnsignedInt( &timeElapsedFadeFrames ); + m_timeElapsedFade = static_cast(timeElapsedFadeFrames); + } // time to fade xfer->xferUnsignedInt( &m_timeToFade ); From ea6756c95c5f1f458c9960f0d06d6f238ddeaf3d Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 21:11:03 -0800 Subject: [PATCH 16/19] Replicate to generals --- .../GameEngine/Include/GameClient/Drawable.h | 2 +- .../GameEngine/Source/GameClient/Drawable.cpp | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameClient/Drawable.h b/Generals/Code/GameEngine/Include/GameClient/Drawable.h index 8d4c158d7c4..b23bb044911 100644 --- a/Generals/Code/GameEngine/Include/GameClient/Drawable.h +++ b/Generals/Code/GameEngine/Include/GameClient/Drawable.h @@ -653,7 +653,7 @@ class Drawable : public Thing, FADING_OUT }; FadingMode m_fadeMode; - UnsignedInt m_timeElapsedFade; ///< for how many frames have i been fading + Real m_timeElapsedFade; ///< for how long have i been fading (in 30fps-equivalent frames) UnsignedInt m_timeToFade; ///< how slowly am I fading UnsignedInt m_shroudClearFrame; ///< Last frame the local player saw this drawable "OBJECTSHROUD_CLEAR" diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index e27e9b68c39..5127d08c0de 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1095,7 +1095,9 @@ void Drawable::updateDrawable( void ) Real numer = (m_fadeMode == FADING_IN) ? (m_timeElapsedFade) : (m_timeToFade-m_timeElapsedFade); setDrawableOpacity(numer/(Real)m_timeToFade); - ++m_timeElapsedFade; + // TheSuperHackers @tweak Drawable fade is now decoupled from the render update. + const Real fadeTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_timeElapsedFade += fadeTimeScale; if (m_timeElapsedFade > m_timeToFade) m_fadeMode = FADING_NONE; @@ -4314,7 +4316,7 @@ void Drawable::xfer( Xfer *xfer ) #if RETAIL_COMPATIBLE_XFER_SAVE const XferVersion currentVersion = 5; #else - const XferVersion currentVersion = 6; + const XferVersion currentVersion = 7; #endif XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); @@ -4489,7 +4491,17 @@ void Drawable::xfer( Xfer *xfer ) xfer->xferUser( &m_fadeMode, sizeof( FadingMode ) ); // time elapsed fade - xfer->xferUnsignedInt( &m_timeElapsedFade ); + // TheSuperHackers @tweak Changed from UnsignedInt to Real for frame-rate independent fading. + if (version >= 7) + { + xfer->xferReal( &m_timeElapsedFade ); + } + else + { + UnsignedInt timeElapsedFadeFrames = static_cast(m_timeElapsedFade); + xfer->xferUnsignedInt( &timeElapsedFadeFrames ); + m_timeElapsedFade = static_cast(timeElapsedFadeFrames); + } // time to fade xfer->xferUnsignedInt( &m_timeToFade ); From e6be53431e325c8cfe67ac90e1d3a08e872aadaa Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 21:36:29 -0800 Subject: [PATCH 17/19] Decouple airborne frame counter from render update --- .../GameEngine/Include/GameClient/Drawable.h | 4 ++-- .../GameEngine/Source/GameClient/Drawable.cpp | 23 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index 65bdbfb6ea1..0eae61a9130 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h @@ -128,8 +128,8 @@ struct TWheelInfo Real m_rearLeftHeightOffset; Real m_rearRightHeightOffset; Real m_wheelAngle; ///< Wheel angle. 0 = straight, >0 left, <0 right. - Int m_framesAirborneCounter; ///< Counter. - Int m_framesAirborne; ///< How many frames it was in the air. + Real m_framesAirborneCounter; ///< Counter (in 30fps-equivalent frames). + Real m_framesAirborne; ///< How long it was in the air (in 30fps-equivalent frames). }; //----------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 1b4910f32c2..20cb9048a93 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1981,10 +1981,9 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI { // Wheels extend when airborne. m_locoInfo->m_wheelInfo.m_framesAirborne = 0; - m_locoInfo->m_wheelInfo.m_framesAirborneCounter++; - // TheSuperHackers @tweak Wheel suspension offset is now decoupled from the render update. const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_wheelInfo.m_framesAirborneCounter += timeScale; const Real suspensionFactor = 0.5f * timeScale; if (pos->z - hheight > -MAX_SUSPENSION_EXTENSION) @@ -2296,10 +2295,9 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf { // Wheels extend when airborne. m_locoInfo->m_wheelInfo.m_framesAirborne = 0; - m_locoInfo->m_wheelInfo.m_framesAirborneCounter++; - // TheSuperHackers @tweak Wheel suspension offset is now decoupled from the render update. const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_wheelInfo.m_framesAirborneCounter += timeScale; const Real suspensionFactor = 0.5f * timeScale; if (pos->z - hheight > -MAX_SUSPENSION_EXTENSION) @@ -5234,8 +5232,21 @@ void Drawable::xfer( Xfer *xfer ) xfer->xferReal( &m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset ); xfer->xferReal( &m_locoInfo->m_wheelInfo.m_rearRightHeightOffset ); xfer->xferReal( &m_locoInfo->m_wheelInfo.m_wheelAngle ); - xfer->xferInt( &m_locoInfo->m_wheelInfo.m_framesAirborneCounter ); - xfer->xferInt( &m_locoInfo->m_wheelInfo.m_framesAirborne ); + // TheSuperHackers @tweak Changed from Int to Real for frame-rate independent airborne tracking. + if (version >= 9) + { + xfer->xferReal( &m_locoInfo->m_wheelInfo.m_framesAirborneCounter ); + xfer->xferReal( &m_locoInfo->m_wheelInfo.m_framesAirborne ); + } + else + { + Int framesAirborneCounter = static_cast(m_locoInfo->m_wheelInfo.m_framesAirborneCounter); + Int framesAirborne = static_cast(m_locoInfo->m_wheelInfo.m_framesAirborne); + xfer->xferInt( &framesAirborneCounter ); + xfer->xferInt( &framesAirborne ); + m_locoInfo->m_wheelInfo.m_framesAirborneCounter = static_cast(framesAirborneCounter); + m_locoInfo->m_wheelInfo.m_framesAirborne = static_cast(framesAirborne); + } } // modules From fe043d07c2f15e2d2676e1336ad0aa203c348f69 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 21:37:31 -0800 Subject: [PATCH 18/19] Replicate to generals --- .../GameEngine/Include/GameClient/Drawable.h | 4 ++-- .../GameEngine/Source/GameClient/Drawable.cpp | 20 +++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameClient/Drawable.h b/Generals/Code/GameEngine/Include/GameClient/Drawable.h index b23bb044911..1f15eeede68 100644 --- a/Generals/Code/GameEngine/Include/GameClient/Drawable.h +++ b/Generals/Code/GameEngine/Include/GameClient/Drawable.h @@ -127,8 +127,8 @@ struct TWheelInfo Real m_rearLeftHeightOffset; Real m_rearRightHeightOffset; Real m_wheelAngle; ///< Wheel angle. 0 = straight, >0 left, <0 right. - Int m_framesAirborneCounter; ///< Counter. - Int m_framesAirborne; ///< How many frames it was in the air. + Real m_framesAirborneCounter; ///< Counter (in 30fps-equivalent frames). + Real m_framesAirborne; ///< How long it was in the air (in 30fps-equivalent frames). }; //----------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index 5127d08c0de..ea9f452d77e 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1832,10 +1832,9 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI { // Wheels extend when airborne. m_locoInfo->m_wheelInfo.m_framesAirborne = 0; - m_locoInfo->m_wheelInfo.m_framesAirborneCounter++; - // TheSuperHackers @tweak Wheel suspension offset is now decoupled from the render update. const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_locoInfo->m_wheelInfo.m_framesAirborneCounter += timeScale; const Real suspensionFactor = 0.5f * timeScale; if (pos->z - hheight > -MAX_SUSPENSION_EXTENSION) @@ -4555,8 +4554,21 @@ void Drawable::xfer( Xfer *xfer ) xfer->xferReal( &m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset ); xfer->xferReal( &m_locoInfo->m_wheelInfo.m_rearRightHeightOffset ); xfer->xferReal( &m_locoInfo->m_wheelInfo.m_wheelAngle ); - xfer->xferInt( &m_locoInfo->m_wheelInfo.m_framesAirborneCounter ); - xfer->xferInt( &m_locoInfo->m_wheelInfo.m_framesAirborne ); + // TheSuperHackers @tweak Changed from Int to Real for frame-rate independent airborne tracking. + if (version >= 7) + { + xfer->xferReal( &m_locoInfo->m_wheelInfo.m_framesAirborneCounter ); + xfer->xferReal( &m_locoInfo->m_wheelInfo.m_framesAirborne ); + } + else + { + Int framesAirborneCounter = static_cast(m_locoInfo->m_wheelInfo.m_framesAirborneCounter); + Int framesAirborne = static_cast(m_locoInfo->m_wheelInfo.m_framesAirborne); + xfer->xferInt( &framesAirborneCounter ); + xfer->xferInt( &framesAirborne ); + m_locoInfo->m_wheelInfo.m_framesAirborneCounter = static_cast(framesAirborneCounter); + m_locoInfo->m_wheelInfo.m_framesAirborne = static_cast(framesAirborne); + } } // modules From 2bb1cb5be3b468fb30ded09f7e75e10005dfb573 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 5 Jan 2026 16:59:35 -0800 Subject: [PATCH 19/19] Calculate physics at fixed logic rate with interpolation to restore wobble --- .../GameEngine/Include/GameClient/Drawable.h | 8 +- .../GameEngine/Source/GameClient/Drawable.cpp | 202 ++++++++---------- 2 files changed, 99 insertions(+), 111 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index 0eae61a9130..17d179074a0 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h @@ -626,7 +626,13 @@ class Drawable : public Thing, Real m_totalYaw; ///< Current total yaw for this frame Real m_totalZ; - PhysicsXformInfo() : m_totalPitch(0), m_totalRoll(0), m_totalYaw(0), m_totalZ(0) { } + Real m_prevTotalPitch; + Real m_prevTotalRoll; + Real m_prevTotalYaw; + Real m_prevTotalZ; + + PhysicsXformInfo() : m_totalPitch(0), m_totalRoll(0), m_totalYaw(0), m_totalZ(0), + m_prevTotalPitch(0), m_prevTotalRoll(0), m_prevTotalYaw(0), m_prevTotalZ(0) { } }; Bool calcPhysicsXform(PhysicsXformInfo& info); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 20cb9048a93..99c8addafd4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1376,17 +1376,34 @@ void Drawable::applyPhysicsXform(Matrix3D* mtx) { if (m_physicsXform != NULL) { - // TheSuperHackers @tweak Update the physics transform on every WW Sync only. - // All calculations are originally catered to a 30 fps logic step. + // TheSuperHackers @tweak Run physics on logic frames only, interpolate for rendering. + // This provides stable physics at any framerate without numerical integration issues. if (WW3D::Get_Sync_Frame_Time() != 0) { + // Save previous state before calculating new physics + m_physicsXform->m_prevTotalPitch = m_physicsXform->m_totalPitch; + m_physicsXform->m_prevTotalRoll = m_physicsXform->m_totalRoll; + m_physicsXform->m_prevTotalYaw = m_physicsXform->m_totalYaw; + m_physicsXform->m_prevTotalZ = m_physicsXform->m_totalZ; + calcPhysicsXform(*m_physicsXform); } - mtx->Translate(0.0f, 0.0f, m_physicsXform->m_totalZ); - mtx->Rotate_Y( m_physicsXform->m_totalPitch ); - mtx->Rotate_X( -m_physicsXform->m_totalRoll ); - mtx->Rotate_Z( m_physicsXform->m_totalYaw ); + // Interpolate between previous and current state based on fractional sync time. + // Logic runs at ~33.3ms per frame (30fps), fractional time accumulates between logic frames. + const Real LOGIC_FRAME_MS = TheFramePacer->getLogicTimeStepMilliseconds(); + const Real fractionalMs = (Real)WW3D::Get_Fractional_Sync_Milliseconds(); + const Real t = (LOGIC_FRAME_MS > 0.0f) ? clamp(0.0f, fractionalMs / LOGIC_FRAME_MS, 1.0f) : 0.0f; + + const Real interpPitch = m_physicsXform->m_prevTotalPitch + t * (m_physicsXform->m_totalPitch - m_physicsXform->m_prevTotalPitch); + const Real interpRoll = m_physicsXform->m_prevTotalRoll + t * (m_physicsXform->m_totalRoll - m_physicsXform->m_prevTotalRoll); + const Real interpYaw = m_physicsXform->m_prevTotalYaw + t * (m_physicsXform->m_totalYaw - m_physicsXform->m_prevTotalYaw); + const Real interpZ = m_physicsXform->m_prevTotalZ + t * (m_physicsXform->m_totalZ - m_physicsXform->m_prevTotalZ); + + mtx->Translate(0.0f, 0.0f, interpZ); + mtx->Rotate_Y( interpPitch ); + mtx->Rotate_X( -interpRoll ); + mtx->Rotate_Z( interpYaw ); } } @@ -1453,11 +1470,6 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI Real MAX_WOBBLE = locomotor->getMaxWobble(); Real MIN_WOBBLE = locomotor->getMinWobble(); - // TheSuperHackers @tweak Wobble and thrust roll rates are now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - const Real scaledWobbleRate = WOBBLE_RATE * timeScale; - const Real scaledThrustRoll = THRUST_ROLL * timeScale; - // // this is a kind of quick thrust implementation cause we need scud missiles to wobble *now*, // we deal with just adjusting pitch, yaw, and roll just a little bit @@ -1472,15 +1484,15 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI if( m_locoInfo->m_pitch < MAX_WOBBLE - WOBBLE_RATE * 2 ) { - m_locoInfo->m_pitch += scaledWobbleRate; - m_locoInfo->m_yaw += scaledWobbleRate; + m_locoInfo->m_pitch += WOBBLE_RATE; + m_locoInfo->m_yaw += WOBBLE_RATE; } else { - m_locoInfo->m_pitch += (scaledWobbleRate / 2.0f); - m_locoInfo->m_yaw += (scaledWobbleRate / 2.0f); + m_locoInfo->m_pitch += (WOBBLE_RATE / 2.0f); + m_locoInfo->m_yaw += (WOBBLE_RATE / 2.0f); } @@ -1494,15 +1506,15 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI if( m_locoInfo->m_pitch >= MIN_WOBBLE + WOBBLE_RATE * 2.0f ) { - m_locoInfo->m_pitch -= scaledWobbleRate; - m_locoInfo->m_yaw -= scaledWobbleRate; + m_locoInfo->m_pitch -= WOBBLE_RATE; + m_locoInfo->m_yaw -= WOBBLE_RATE; } else { - m_locoInfo->m_pitch -= (scaledWobbleRate / 2.0f); - m_locoInfo->m_yaw -= (scaledWobbleRate / 2.0f); + m_locoInfo->m_pitch -= (WOBBLE_RATE / 2.0f); + m_locoInfo->m_yaw -= (WOBBLE_RATE / 2.0f); } if( m_locoInfo->m_pitch <= MIN_WOBBLE ) @@ -1518,7 +1530,7 @@ void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformI if( THRUST_ROLL ) { - m_locoInfo->m_roll += scaledThrustRoll; + m_locoInfo->m_roll += THRUST_ROLL; info.m_totalRoll = m_locoInfo->m_roll; } @@ -1566,22 +1578,19 @@ void Drawable::calcPhysicsXformHoverOrWings( const Locomotor *locomotor, Physics const Coord3D* accel = physics->getAcceleration(); const Coord3D* vel = physics->getVelocity(); - // TheSuperHackers @tweak Spring-damper physics are now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - - m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)) * timeScale; // spring/damper - m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)) * timeScale; // spring/damper + m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper + m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; // process chassis acceleration dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -1595,23 +1604,23 @@ void Drawable::calcPhysicsXformHoverOrWings( const Locomotor *locomotor, Physics if (fabs(vel->z) > TINY_DZ) { Real pitch = atan2(vel->z, sqrt(sqr(vel->x)+sqr(vel->y))); - m_locoInfo->m_pitch -= Z_VEL_PITCH_COEFF * pitch * timeScale; + m_locoInfo->m_pitch -= Z_VEL_PITCH_COEFF * pitch; } } // cause the chassis to pitch & roll in reaction to current speed Real forwardVel = dir->x * vel->x + dir->y * vel->y; - m_locoInfo->m_pitch += -(FORWARD_VEL_COEFF * forwardVel) * timeScale; + m_locoInfo->m_pitch += -(FORWARD_VEL_COEFF * forwardVel); Real lateralVel = -dir->y * vel->x + dir->x * vel->y; - m_locoInfo->m_roll += -(LATERAL_VEL_COEFF * lateralVel) * timeScale; + m_locoInfo->m_roll += -(LATERAL_VEL_COEFF * lateralVel); // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); } // limit acceleration pitch and roll @@ -1633,10 +1642,8 @@ void Drawable::calcPhysicsXformHoverOrWings( const Locomotor *locomotor, Physics const Real ELEVATOR_CORRECTION_DEGREE = locomotor->getElevatorCorrectionDegree(); const Real ELEVATOR_CORRECTION_RATE = locomotor->getElevatorCorrectionRate(); - // TheSuperHackers @tweak Rudder/elevator correction rates are now decoupled from the render update. - const Real correctionTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - info.m_totalYaw = RUDDER_CORRECTION_DEGREE * sin( m_locoInfo->m_yawModulator += RUDDER_CORRECTION_RATE * correctionTimeScale ); - info.m_totalPitch += ELEVATOR_CORRECTION_DEGREE * cos( m_locoInfo->m_pitchModulator += ELEVATOR_CORRECTION_RATE * correctionTimeScale ); + info.m_totalYaw = RUDDER_CORRECTION_DEGREE * sin( m_locoInfo->m_yawModulator += RUDDER_CORRECTION_RATE ); + info.m_totalPitch += ELEVATOR_CORRECTION_DEGREE * cos( m_locoInfo->m_pitchModulator += ELEVATOR_CORRECTION_RATE ); info.m_totalZ = 0.0f; @@ -1785,9 +1792,7 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI // kick to the pitch for effect if (physics->getPreviousOverlap() != INVALID_ID && m_locoInfo->m_overlapZ > 0.0f) { - // TheSuperHackers @tweak Leave overlap pitch kick is now decoupled from the render update. - const Real overlapTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - m_locoInfo->m_pitchRate += LEAVE_OVERLAP_PITCH_KICK * overlapTimeScale; + m_locoInfo->m_pitchRate += LEAVE_OVERLAP_PITCH_KICK; } } @@ -1801,32 +1806,28 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI // process chassis suspension dynamics - damp back towards groundPitch - // TheSuperHackers @tweak The physics are now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - // the ground can only push back if we're touching it if (overlapped || m_locoInfo->m_overlapZ <= 0.0f) { - m_locoInfo->m_pitchRate += timeScale * ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper + m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper if (m_locoInfo->m_pitchRate > 0.0f) { - const Real pitchDamp = 1.0f - (1.0f - 0.5f) * timeScale; - m_locoInfo->m_pitchRate *= pitchDamp; + m_locoInfo->m_pitchRate *= 0.5f; } - m_locoInfo->m_rollRate += timeScale * ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper + m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; // process chassis recoil dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -1836,10 +1837,10 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI { // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); } #ifdef RECOIL_FROM_BEING_DAMAGED @@ -1863,10 +1864,8 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI Real recoil = PI/16.0f * GameClientRandomValueReal( 0.5f, 1.0f ); - // TheSuperHackers @tweak Hit recoil is now decoupled from the render update. - const Real hitRecoilTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - m_locoInfo->m_accelerationPitchRate -= recoil * forward * hitRecoilTimeScale; - m_locoInfo->m_accelerationRollRate -= recoil * lateral * hitRecoilTimeScale; + m_locoInfo->m_accelerationPitchRate -= recoil * forward; + m_locoInfo->m_accelerationRollRate -= recoil * lateral; } m_lastDamageTimestamp = obj->getBodyModule()->getLastDamageTimestamp(); @@ -1898,12 +1897,10 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI Real ztmp = m_locoInfo->m_overlapZ/2.0f; // do fake Z physics - // TheSuperHackers @tweak Overlap Z physics is now decoupled from the render update. - const Real overlapTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); if (m_locoInfo->m_overlapZ > 0.0f) { - m_locoInfo->m_overlapZVel -= 0.2f * overlapTimeScale; - m_locoInfo->m_overlapZ += m_locoInfo->m_overlapZVel * overlapTimeScale; + m_locoInfo->m_overlapZVel -= 0.2f; + m_locoInfo->m_overlapZ += m_locoInfo->m_overlapZVel; } if (m_locoInfo->m_overlapZ <= 0.0f) @@ -1981,10 +1978,8 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI { // Wheels extend when airborne. m_locoInfo->m_wheelInfo.m_framesAirborne = 0; - // TheSuperHackers @tweak Wheel suspension offset is now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - m_locoInfo->m_wheelInfo.m_framesAirborneCounter += timeScale; - const Real suspensionFactor = 0.5f * timeScale; + m_locoInfo->m_wheelInfo.m_framesAirborneCounter += 1.0f; + const Real suspensionFactor = 0.5f; if (pos->z - hheight > -MAX_SUSPENSION_EXTENSION) { @@ -2015,9 +2010,7 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI Real factor = curSpeed/maxSpeed; if (fabs(m_locoInfo->m_pitchRate)m_rollRate)getActualLogicTimeScaleOverFpsRatio(); - const Real scaledBounceKick = BOUNCE_ANGLE_KICK * factor * bounceTimeScale; + const Real scaledBounceKick = BOUNCE_ANGLE_KICK * factor; // do the bouncy. switch (GameClientRandomValue(0,3)) { @@ -2045,32 +2038,28 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI // process chassis suspension dynamics - damp back towards groundPitch - // TheSuperHackers @tweak The physics are now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - // the ground can only push back if we're touching it if (!airborne) { - m_locoInfo->m_pitchRate += timeScale * ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper + m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper if (m_locoInfo->m_pitchRate > 0.0f) { - const Real pitchDamp = 1.0f - (1.0f - 0.5f) * timeScale; - m_locoInfo->m_pitchRate *= pitchDamp; + m_locoInfo->m_pitchRate *= 0.5f; } - m_locoInfo->m_rollRate += timeScale * ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper + m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; // process chassis acceleration dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -2080,10 +2069,10 @@ void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformI { // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); } // limit acceleration pitch and roll @@ -2295,10 +2284,8 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf { // Wheels extend when airborne. m_locoInfo->m_wheelInfo.m_framesAirborne = 0; - // TheSuperHackers @tweak Wheel suspension offset is now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - m_locoInfo->m_wheelInfo.m_framesAirborneCounter += timeScale; - const Real suspensionFactor = 0.5f * timeScale; + m_locoInfo->m_wheelInfo.m_framesAirborneCounter += 1.0f; + const Real suspensionFactor = 0.5f; if (pos->z - hheight > -MAX_SUSPENSION_EXTENSION) { @@ -2329,9 +2316,7 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf Real factor = curSpeed/maxSpeed; if (fabs(m_locoInfo->m_pitchRate)m_rollRate)getActualLogicTimeScaleOverFpsRatio(); - const Real scaledBounceKick = BOUNCE_ANGLE_KICK * factor * bounceTimeScale; + const Real scaledBounceKick = BOUNCE_ANGLE_KICK * factor; // do the bouncy. switch (GameClientRandomValue(0,3)) { @@ -2359,32 +2344,29 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf // process chassis suspension dynamics - damp back towards groundPitch - // TheSuperHackers @tweak Spring-damper physics are now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - // the ground can only push back if we're touching it if (!airborne) { - m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)) * timeScale; // spring/damper - m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)) * timeScale; // spring/damper + m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper + m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper } else { //Autolevel - m_locoInfo->m_pitchRate += ( (-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate) ) * timeScale; // spring/damper - m_locoInfo->m_rollRate += ( (-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate) ) * timeScale; // spring/damper + m_locoInfo->m_pitchRate += ( (-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate) ); // spring/damper + m_locoInfo->m_rollRate += ( (-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate) ); // spring/damper } - m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING * timeScale; - m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING * timeScale; + m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING; + m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING; // process chassis acceleration dynamics - damp back towards zero - m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)) * timeScale; // spring/damper - m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate * timeScale; + m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper + m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate; - m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)) * timeScale; // spring/damper - m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate * timeScale; + m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper + m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate; // compute total pitch and roll of tank info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch; @@ -2403,10 +2385,10 @@ void Drawable::calcPhysicsXformMotorcycle( const Locomotor *locomotor, PhysicsXf { // cause the chassis to pitch & roll in reaction to acceleration/deceleration Real forwardAccel = dir->x * accel->x + dir->y * accel->y; - m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel) * timeScale; + m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel); Real lateralAccel = -dir->y * accel->x + dir->x * accel->y; - m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel) * timeScale; + m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel); } // limit acceleration pitch and roll