Skip to content

bugfix(crc): Fix spurious mismatches for disconnected players at low CRC intervals#2796

Merged
xezon merged 2 commits into
TheSuperHackers:mainfrom
Caball009:fix_mismatch_disconnected_player
Jun 18, 2026
Merged

bugfix(crc): Fix spurious mismatches for disconnected players at low CRC intervals#2796
xezon merged 2 commits into
TheSuperHackers:mainfrom
Caball009:fix_mismatch_disconnected_player

Conversation

@Caball009

@Caball009 Caball009 commented Jun 14, 2026

Copy link
Copy Markdown

This PR introduces a small to fix to avoid spurious mismatches that can occur if the following conditions are met simultaneously:

  1. Low CRC intervals; e.g. once per frame instead of once per 100 frames (default value).
  2. Runahead decreases, so that multiple frames are executed in one go.
  3. Player disconnects during one of these frames.

As players disconnect they stop sending data to other players to speed up the disconnect process. If that coincides with a decrease in runahead, it's possible that a single execution frame contains e.g. 5 CRC messages per player, but fewer for the disconnecting player. The game only checks a player's most recent CRC value. This would lead to a mismatch because the most recent CRC value for the disconnecting player is not up-to-date with the other (connected) players.

You can see it mismatch here as the player disconnects (this was with a special test build):
https://www.youtube.com/live/RM27jEkTGfw?t=2099s
https://www.youtube.com/live/V_l-q9Y-DmA?t=7576s

Here's a replay for the second match. The mismatch happens at frame 3836 because of player index 6 disconnecting from the game: MM NED.zip

I have no idea how to reproduce this in a test environment, unfortunately.

TODO:

  • Replicate in Generals.

@Caball009 Caball009 added Bug Something is not working right, typically is user facing Minor Severity: Minor < Major < Critical < Blocker Network Anything related to network, servers Gen Relates to Generals ZH Relates to Zero Hour labels Jun 14, 2026
@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a false CRC mismatch triggered when a player disconnects during a frame that processes multiple CRC messages (runahead decrease with low CRC intervals). The disconnecting player's stale CRC was being compared against fresh CRCs from connected players, producing a spurious desync report.

  • Introduces a CachedCRCMap typedef to clean up verbose iterator declarations across both Generals and GeneralsMD code paths.
  • Replaces the old "unconditionally take the first map entry as the reference" pattern with a hasReferenceCRC flag that only ever selects the reference CRC from a confirmed-connected player, and skips all disconnected players during comparison — fixing the stale-validator case where the disconnecting player held the lowest slot index.

Confidence Score: 5/5

Safe to merge; the fix correctly handles all edge cases including disconnecting players in any slot position.

The previous concern about a disconnecting player in the lowest slot contaminating the reference CRC is fully resolved: the new hasReferenceCRC flag defers reference selection until the first connected player is found, so stale CRCs from disconnected players are skipped both when setting the reference and during comparison. The numPlayers guard and the inner loop both call isPlayerConnected in close succession in a single-threaded logic frame and will return consistent results.

No files require special attention.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Core fix: replaces unconditional begin()-as-validator with hasReferenceCRC pattern that only compares connected players, correctly skipping stale CRCs from disconnecting players regardless of slot index.
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Identical fix applied to the Zero Hour counterpart; logic is consistent with the Generals version.
Generals/Code/GameEngine/Include/GameLogic/GameLogic.h Adds CachedCRCMap typedef inside the private section, consistent with the existing ControlBarOverrideMap typedef pattern in the same class.
GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h Same typedef addition as the Generals header; no issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[processCommandList called] --> B[Clear m_cachedCRCs
m_shouldValidateCRCs = FALSE]
    B --> C[Dispatch all messages
via logicMessageDispatcher]
    C --> D{m_shouldValidateCRCs
AND no prior mismatch?}
    D -- No --> Z[Return]
    D -- Yes --> E[Count connected players
via isPlayerConnected loop]
    E --> F{m_cachedCRCs.size
< numPlayers?}
    F -- Yes --> G[sawCRCMismatch = TRUE
Not enough CRCs]
    F -- No --> H[hasReferenceCRC = FALSE
referenceCRC = 0]
    H --> I[Iterate m_cachedCRCs]
    I --> J{isPlayerConnected
for this entry?}
    J -- No --> I
    J -- Yes --> K{hasReferenceCRC?}
    K -- No --> L[Set referenceCRC
hasReferenceCRC = TRUE]
    L --> I
    K -- Yes --> M{referenceCRC
== current CRC?}
    M -- Yes --> I
    M -- No --> N[sawCRCMismatch = TRUE]
    N --> I
    G --> O{sawCRCMismatch?}
    I -- done --> O
    O -- Yes --> P[Log mismatch details
TheNetwork setSawCRCMismatch]
    O -- No --> Z
    P --> Z
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"}}}%%
flowchart TD
    A[processCommandList called] --> B[Clear m_cachedCRCs
m_shouldValidateCRCs = FALSE]
    B --> C[Dispatch all messages
via logicMessageDispatcher]
    C --> D{m_shouldValidateCRCs
AND no prior mismatch?}
    D -- No --> Z[Return]
    D -- Yes --> E[Count connected players
via isPlayerConnected loop]
    E --> F{m_cachedCRCs.size
< numPlayers?}
    F -- Yes --> G[sawCRCMismatch = TRUE
Not enough CRCs]
    F -- No --> H[hasReferenceCRC = FALSE
referenceCRC = 0]
    H --> I[Iterate m_cachedCRCs]
    I --> J{isPlayerConnected
for this entry?}
    J -- No --> I
    J -- Yes --> K{hasReferenceCRC?}
    K -- No --> L[Set referenceCRC
hasReferenceCRC = TRUE]
    L --> I
    K -- Yes --> M{referenceCRC
== current CRC?}
    M -- Yes --> I
    M -- No --> N[sawCRCMismatch = TRUE]
    N --> I
    G --> O{sawCRCMismatch?}
    I -- done --> O
    O -- Yes --> P[Log mismatch details
TheNetwork setSawCRCMismatch]
    O -- No --> Z
    P --> Z
Loading

Reviews (6): Last reviewed commit: "Replicated in Generals." | Re-trigger Greptile

@Caball009 Caball009 force-pushed the fix_mismatch_disconnected_player branch from ae476f1 to 7fbffaf Compare June 14, 2026 20:16
@Caball009 Caball009 marked this pull request as draft June 14, 2026 20:17
@Caball009 Caball009 force-pushed the fix_mismatch_disconnected_player branch from 7fbffaf to e2bd3ef Compare June 14, 2026 20:29
@Caball009 Caball009 marked this pull request as ready for review June 14, 2026 20:30
@Caball009 Caball009 force-pushed the fix_mismatch_disconnected_player branch from e2bd3ef to 872efd3 Compare June 15, 2026 17:03
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Outdated
@Caball009 Caball009 force-pushed the fix_mismatch_disconnected_player branch from 872efd3 to b9e744e Compare June 15, 2026 21:14

@Mauller Mauller 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.

Looks okay to me, i don't think it can be much simpler since zero can be a valid CRC funnily enough for crc32.

Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp

@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 change assumes that all player disconnects are perfectly in sync. Is this guaranteed?

Bool sawCRCMismatch = FALSE;
Int numPlayers = 0;
DEBUG_ASSERTCRASH(TheNetwork, ("No Network!"));
if (TheNetwork)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

For a follow up, consider moving the Network test before the above TheNetwork->sawCRCMismatch()

Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
@xezon

xezon commented Jun 18, 2026

Copy link
Copy Markdown

Needs replicate in Generals

@Caball009

Copy link
Copy Markdown
Author

Replicated in Generals.

@xezon xezon merged commit 82f4b85 into TheSuperHackers:main Jun 18, 2026
17 checks passed
@Caball009 Caball009 deleted the fix_mismatch_disconnected_player branch June 18, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something is not working right, typically is user facing Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker Network Anything related to network, servers ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants