Skip to content

bugfix(logic): Restore retail compatibility after change to frozen time check#2803

Open
Caball009 wants to merge 1 commit into
TheSuperHackers:mainfrom
Caball009:fix_bug_time_frozen
Open

bugfix(logic): Restore retail compatibility after change to frozen time check#2803
Caball009 wants to merge 1 commit into
TheSuperHackers:mainfrom
Caball009:fix_bug_time_frozen

Conversation

@Caball009

@Caball009 Caball009 commented Jun 18, 2026

Copy link
Copy Markdown

#1528 introduced a small change to the game logic as is shown below.

Implementation before approximately:

TheScriptEngine->update();

if (!TheScriptEngine->isTimeFrozenScript())
{
    TheGameLogic->update();
}

Implementation after approximately:

m_isTimeFrozen = TheScriptEngine->isTimeFrozenScript();

TheScriptEngine->update();

if (!m_isTimeFrozen)
{
    TheGameLogic->update();
}

Replay vc6_replay_scripted_frozen_time.zip on map "[Mod] Football v3" mismatches because of that change. This PR fixes the mismatch.

@Caball009 Caball009 added Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour ThisProject The issue was introduced by this project, or this task is specific to this project Script Is related to Script Engine, SCB labels Jun 18, 2026
@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown

Greptile Summary

This PR restores retail-compatible behavior for frozen-time script interactions by moving the isTimeFrozen check inside GameLogic::update() rather than in GameEngine::update(). PR #1528 had inadvertently cached the frozen flag before TheScriptEngine->UPDATE() ran, causing replays to desync when a script froze time mid-frame.

  • GameEngine::update() (both Generals/ and GeneralsMD/): Removes the !isTimeFrozen() guard from canUpdateLogic and removes the separate canUpdateScript branch that only invoked the script engine on frozen frames.
  • GameLogic::update() (both variants): After TheScriptEngine->UPDATE() runs, re-syncs TheFramePacer's frozen state from TheGameEngine->isTimeFrozen(), then returns early if frozen — skipping terrain, object, and CRC updates exactly as the original retail binary did.

Confidence Score: 5/5

The change is targeted and safe: it removes a stale frozen-time pre-check and replaces it with a post-script-update check inside GameLogic::update(), restoring the original retail execution order.

Both Generals and Zero Hour variants are updated consistently. The frame counter (m_frame++) and CRC computation remain gated behind the new early return, matching what retail skipped on frozen frames. The LatchRestore RAII guard at the top of GameLogic::update() fires correctly on the early path. No pre-existing frozen-state logic is duplicated or contradicted.

No files require special attention.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Adds setTimeFrozen sync + early-return after script update; skips terrain/objects/CRC/frame-increment on frozen frames, matching retail behaviour.
Generals/Code/GameEngine/Source/Common/GameEngine.cpp Removes frozen-time guard from canUpdateLogic and drops the canUpdateScript fallback branch; GameLogic::update() now always runs and handles the frozen check internally.
GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp Identical change to the Zero Hour variant of GameEngine::update() — consistent with the Generals copy.
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Identical change to the Zero Hour variant of GameLogic::update() — consistent with the Generals copy.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant GE as GameEngine::update()
    participant GL as GameLogic::update()
    participant SE as ScriptEngine
    participant FP as FramePacer
    participant World as Terrain/Objects/CRC

    Note over GE: canUpdateLogic = canUpdate && !isGameHalted()<br/>(frozen check removed)
    GE->>GL: UPDATE() [always, unless halted]
    GL->>SE: UPDATE()
    SE-->>FP: (may set frozen state)
    GL->>GE: isTimeFrozen()
    GE-->>GL: frozen?
    GL->>FP: setTimeFrozen(frozen)
    alt time is frozen
        GL-->>GE: return early (skip terrain/objects/CRC/m_frame++)
    else time not frozen
        GL->>World: TerrainLogic, Objects, CRC, m_frame++
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant GE as GameEngine::update()
    participant GL as GameLogic::update()
    participant SE as ScriptEngine
    participant FP as FramePacer
    participant World as Terrain/Objects/CRC

    Note over GE: canUpdateLogic = canUpdate && !isGameHalted()<br/>(frozen check removed)
    GE->>GL: UPDATE() [always, unless halted]
    GL->>SE: UPDATE()
    SE-->>FP: (may set frozen state)
    GL->>GE: isTimeFrozen()
    GE-->>GL: frozen?
    GL->>FP: setTimeFrozen(frozen)
    alt time is frozen
        GL-->>GE: return early (skip terrain/objects/CRC/m_frame++)
    else time not frozen
        GL->>World: TerrainLogic, Objects, CRC, m_frame++
    end
Loading

Reviews (1): Last reviewed commit: "bugfix(logic): Check if time is frozen d..." | Re-trigger Greptile

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unfortunate. Can we fix this somehow without moving the early return back into the Game Logic Update function? It makes the logic more complicated to understand again. It would be good to implement it in simple terms.

@Caball009

Copy link
Copy Markdown
Author

This is unfortunate. Can we fix this somehow without moving the early return back into the Game Logic Update function? It makes the logic more complicated to understand again. It would be good to implement it in simple terms.

I don't see how to avoid the early return in a way that doesn't have major downsides.

@xezon

xezon commented Jun 21, 2026

Copy link
Copy Markdown

What downsides do you see?

@Caball009

Copy link
Copy Markdown
Author

What downsides do you see?

Either code duplication, or a potential risk of game logic changes.

If you want to keep the exact same logic, you'd need to keep these in place:

  1. GameLogic::m_isInUpdate is set to true.
  2. GameClient::m_frame is updated.
  3. setFPMode resets the floating point state.
  4. ScriptEngine::update always runs directly after loading a map.

Only solution I see at the moment to keep the changes as is, or perhaps put the code up to the script engine update in a separate function.

@Caball009 Caball009 added Major Severity: Minor < Major < Critical < Blocker and removed Minor Severity: Minor < Major < Critical < Blocker labels Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker Script Is related to Script Engine, SCB ThisProject The issue was introduced by this project, or this task is specific to this project ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants