From 95357c3286a698c7f3bdf336be99e5fca744ff59 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 21:46:45 -0800 Subject: [PATCH 1/7] Decouple GUI transition and world animation timing from render update --- Core/GameEngine/Include/GameClient/GameWindowTransitions.h | 2 +- .../Source/GameClient/GUI/GameWindowTransitions.cpp | 5 ++++- GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/GameWindowTransitions.h b/Core/GameEngine/Include/GameClient/GameWindowTransitions.h index a95e4c3603d..5812db830c0 100644 --- a/Core/GameEngine/Include/GameClient/GameWindowTransitions.h +++ b/Core/GameEngine/Include/GameClient/GameWindowTransitions.h @@ -647,7 +647,7 @@ class TransitionGroup typedef std::list TransitionWindowList; TransitionWindowList m_transitionWindowList; Int m_directionMultiplier; - Int m_currentFrame; ///< maintain how long we've spent on this transition; + Real m_currentFrame; ///< maintain how long we've spent on this transition (in 30fps-equivalent frames); AsciiString m_name; }; diff --git a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp index cd2205a44a2..26102e01e79 100644 --- a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp @@ -55,6 +55,7 @@ #include "GameClient/GameWindowTransitions.h" #include "GameClient/GameWindow.h" #include "GameClient/GameWindowManager.h" +#include "Common/FramePacer.h" //----------------------------------------------------------------------------- // DEFINES //////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- @@ -270,7 +271,9 @@ void TransitionGroup::init( void ) void TransitionGroup::update( void ) { - m_currentFrame += m_directionMultiplier; // we go forward or backwards depending. + // TheSuperHackers @tweak GUI transition timing is now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + m_currentFrame += m_directionMultiplier * timeScale; // we go forward or backwards depending. TransitionWindowList::iterator it = m_transitionWindowList.begin(); while (it != m_transitionWindowList.end()) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index 4cc38ed6569..da258d6fdf1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -5707,8 +5707,12 @@ void InGameUI::updateAndDrawWorldAnimations( void ) } // update the Z value + // TheSuperHackers @tweak World animation Z-rise is now decoupled from the render update. if( wad->m_zRisePerSecond ) - wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND; + { + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND * timeScale; + } } From cff9563fe8a99fff6af896fd47aa6c074aadae12 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 3 Jan 2026 21:49:58 -0800 Subject: [PATCH 2/7] Replicate to generals --- Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp index 4a90702f645..b76291bcb37 100644 --- a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -5534,8 +5534,12 @@ void InGameUI::updateAndDrawWorldAnimations( void ) } // update the Z value + // TheSuperHackers @tweak World animation Z-rise is now decoupled from the render update. if( wad->m_zRisePerSecond ) - wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND; + { + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND * timeScale; + } } From e6cd525736d61a33642ace30557d61462c9c8cdd Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 3 Feb 2026 16:23:03 -0600 Subject: [PATCH 3/7] Use getBaseOverUpdateFpsRatio() for frame-rate independent timing in all game states --- .../Source/GameClient/GUI/GameWindowTransitions.cpp | 4 ++-- Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp | 4 ++-- GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp index 26102e01e79..c61400d0f53 100644 --- a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp @@ -271,8 +271,8 @@ void TransitionGroup::init( void ) void TransitionGroup::update( void ) { - // TheSuperHackers @tweak GUI transition timing is now decoupled from the render update. - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + // TheSuperHackers @tweak bobtista GUI transition timing is now decoupled from the render update. + const Real timeScale = TheFramePacer->getBaseOverUpdateFpsRatio(); m_currentFrame += m_directionMultiplier * timeScale; // we go forward or backwards depending. TransitionWindowList::iterator it = m_transitionWindowList.begin(); while (it != m_transitionWindowList.end()) diff --git a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp index b76291bcb37..242fe4669ff 100644 --- a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -5534,10 +5534,10 @@ void InGameUI::updateAndDrawWorldAnimations( void ) } // update the Z value - // TheSuperHackers @tweak World animation Z-rise is now decoupled from the render update. + // TheSuperHackers @tweak bobtista World animation Z-rise is now decoupled from the render update. if( wad->m_zRisePerSecond ) { - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real timeScale = TheFramePacer->getBaseOverUpdateFpsRatio(); wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND * timeScale; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index da258d6fdf1..f6a5f052998 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -5707,10 +5707,10 @@ void InGameUI::updateAndDrawWorldAnimations( void ) } // update the Z value - // TheSuperHackers @tweak World animation Z-rise is now decoupled from the render update. + // TheSuperHackers @tweak bobtista World animation Z-rise is now decoupled from the render update. if( wad->m_zRisePerSecond ) { - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real timeScale = TheFramePacer->getBaseOverUpdateFpsRatio(); wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND * timeScale; } From 901bd6ca42c0abd2f0e630841a287976821142ae Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 24 Mar 2026 12:43:36 -0400 Subject: [PATCH 4/7] style: Use explicit float assignments for m_currentFrame --- .../Source/GameClient/GUI/GameWindowTransitions.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp index c61400d0f53..9f6d8caae70 100644 --- a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp @@ -240,7 +240,7 @@ Int TransitionWindow::getTotalFrames( void ) //----------------------------------------------------------------------------- TransitionGroup::TransitionGroup( void ) { - m_currentFrame = 0; + m_currentFrame = 0.0f; m_fireOnce = FALSE; } @@ -257,7 +257,7 @@ TransitionGroup::~TransitionGroup( void ) void TransitionGroup::init( void ) { - m_currentFrame = 0; + m_currentFrame = 0.0f; m_directionMultiplier = 1; TransitionWindowList::iterator it = m_transitionWindowList.begin(); while (it != m_transitionWindowList.end()) @@ -318,7 +318,7 @@ void TransitionGroup::reverse( void ) tWin->reverse(totalFrames); it++; } - m_currentFrame = totalFrames; + m_currentFrame = (Real)totalFrames; // m_currentFrame ++; } From 5e51f645bbaf6f12d1121d38e3c438e923ef01f3 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 18 Apr 2026 12:33:55 -0400 Subject: [PATCH 5/7] fix(gui): Use logic time scale for world animation Z-rise --- GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index f6a5f052998..652e326d90f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -5710,7 +5710,7 @@ void InGameUI::updateAndDrawWorldAnimations( void ) // TheSuperHackers @tweak bobtista World animation Z-rise is now decoupled from the render update. if( wad->m_zRisePerSecond ) { - const Real timeScale = TheFramePacer->getBaseOverUpdateFpsRatio(); + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND * timeScale; } From e5c8d4594c8c1a1199cb6c05758f1c160e5f4056 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 18 Apr 2026 12:33:55 -0400 Subject: [PATCH 6/7] fix(gui): Replicate to Generals --- Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp index 242fe4669ff..c5bb87e7943 100644 --- a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -5537,7 +5537,7 @@ void InGameUI::updateAndDrawWorldAnimations( void ) // TheSuperHackers @tweak bobtista World animation Z-rise is now decoupled from the render update. if( wad->m_zRisePerSecond ) { - const Real timeScale = TheFramePacer->getBaseOverUpdateFpsRatio(); + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND * timeScale; } From c714914422a81d4168716f8b678f23e28757ff0b Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 4 May 2026 23:36:11 +1000 Subject: [PATCH 7/7] fix(gui): Step every integer transition frame to prevent stuck menu --- .../GameClient/GUI/GameWindowTransitions.cpp | 31 +++++++++++++++---- .../GameEngine/Source/GameClient/InGameUI.cpp | 7 +++-- .../GameEngine/Source/GameClient/InGameUI.cpp | 7 +++-- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp index 9f6d8caae70..6e424bd59a4 100644 --- a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp @@ -272,14 +272,33 @@ void TransitionGroup::init( void ) void TransitionGroup::update( void ) { // TheSuperHackers @tweak bobtista GUI transition timing is now decoupled from the render update. + // Step every integer frame between the old and new accumulator value so discrete-state-machine + // transitions cannot skip a state when the render frame rate dips below the base rate. const Real timeScale = TheFramePacer->getBaseOverUpdateFpsRatio(); - m_currentFrame += m_directionMultiplier * timeScale; // we go forward or backwards depending. - TransitionWindowList::iterator it = m_transitionWindowList.begin(); - while (it != m_transitionWindowList.end()) + const Int prevFrame = (Int)m_currentFrame; + m_currentFrame += m_directionMultiplier * timeScale; + const Int newFrame = (Int)m_currentFrame; + + if( newFrame == prevFrame ) { - TransitionWindow *tWin = *it; - tWin->update(m_currentFrame); - it++; + return; + } + + const Int step = (newFrame > prevFrame) ? 1 : -1; + for( Int frame = prevFrame + step; frame != newFrame + step; frame += step ) + { + TransitionWindowList::iterator it = m_transitionWindowList.begin(); + while (it != m_transitionWindowList.end()) + { + TransitionWindow *tWin = *it; + tWin->update(frame); + it++; + } + + if( isFinished() ) + { + break; + } } } diff --git a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp index c5bb87e7943..6d5cdeb2c02 100644 --- a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -5504,6 +5504,9 @@ static const UnsignedInt FRAMES_BEFORE_EXPIRE_TO_FADE = LOGICFRAMES_PER_SECOND * // ------------------------------------------------------------------------------------------------ void InGameUI::updateAndDrawWorldAnimations( void ) { + // TheSuperHackers @tweak bobtista World animation Z-rise is now decoupled from the render update. + const Real zRiseTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + // go through all animations for( WorldAnimationListIterator it = m_worldAnimationList.begin(); it != m_worldAnimationList.end(); /*empty*/ ) @@ -5534,11 +5537,9 @@ void InGameUI::updateAndDrawWorldAnimations( void ) } // update the Z value - // TheSuperHackers @tweak bobtista World animation Z-rise is now decoupled from the render update. if( wad->m_zRisePerSecond ) { - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND * timeScale; + wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND * zRiseTimeScale; } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index 652e326d90f..2e2ee9ab69a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -5677,6 +5677,9 @@ static const UnsignedInt FRAMES_BEFORE_EXPIRE_TO_FADE = LOGICFRAMES_PER_SECOND * // ------------------------------------------------------------------------------------------------ void InGameUI::updateAndDrawWorldAnimations( void ) { + // TheSuperHackers @tweak bobtista World animation Z-rise is now decoupled from the render update. + const Real zRiseTimeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + // go through all animations for( WorldAnimationListIterator it = m_worldAnimationList.begin(); it != m_worldAnimationList.end(); /*empty*/ ) @@ -5707,11 +5710,9 @@ void InGameUI::updateAndDrawWorldAnimations( void ) } // update the Z value - // TheSuperHackers @tweak bobtista World animation Z-rise is now decoupled from the render update. if( wad->m_zRisePerSecond ) { - const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND * timeScale; + wad->m_worldPos.z += wad->m_zRisePerSecond / LOGICFRAMES_PER_SECOND * zRiseTimeScale; } }