bugfix(crc): Fix spurious mismatches for disconnected players at low CRC intervals#2796
bugfix(crc): Fix spurious mismatches for disconnected players at low CRC intervals#2796Caball009 wants to merge 1 commit into
Conversation
|
| Filename | Overview |
|---|---|
| GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h | Extracts CachedCRCMap typedef from the inline member declaration, enabling use of the type alias in GameLogic.cpp without repeating the full std::map<Int, UnsignedInt> spelling. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | Rewrites the CRC comparison loop to skip disconnected players; uses a hasReferenceCRC flag so the reference value is always taken from the first connected player, correctly handling the edge case where the lowest-indexed slot belongs to the disconnecting player. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[processCommandList called] --> B{m_shouldValidateCRCs &&\nnot sawCRCMismatch?}
B -- No --> Z[Done]
B -- Yes --> C[Count connected players\nnumPlayers]
C --> D{m_cachedCRCs.size\n< numPlayers?}
D -- Yes --> E[sawCRCMismatch = TRUE\nNot enough CRCs!]
D -- No --> F[Iterate m_cachedCRCs]
F --> G{isPlayerConnected\nit->first ?}
G -- No disconnecting player --> F
G -- Yes --> H{hasReferenceCRC?}
H -- No --> I[Set referenceCRC = crc\nhasReferenceCRC = TRUE]
I --> F
H -- Yes --> J{referenceCRC != crc?}
J -- No --> F
J -- Yes --> E
E --> K[TheNetwork::setSawCRCMismatch]
Reviews (5): Last reviewed commit: "bugfix(crc): Fix spurious mismatches for..." | Re-trigger Greptile
ae476f1 to
7fbffaf
Compare
7fbffaf to
e2bd3ef
Compare
e2bd3ef to
872efd3
Compare
872efd3 to
b9e744e
Compare
Mauller
left a comment
There was a problem hiding this comment.
Looks okay to me, i don't think it can be much simpler since zero can be a valid CRC funnily enough for crc32.
| Int validatedCRC = crcIt->second; | ||
| //DEBUG_LOG(("CRC to validate is from player %d: %8.8X", crcIt->first, validatedCRC)); | ||
| if (validatorCRC != validatedCRC) | ||
| // TheSuperHackers @bugfix Caball009 14/06/2026 Check if player is still connected, |
There was a problem hiding this comment.
Does it need to be behind a retail compile guard?
There was a problem hiding this comment.
No, I don't think so. Do you see potential issues?
There was a problem hiding this comment.
I suggest test with a client that has this change and a client that does not in a Network match - if not already done. My first instinct would be to guard it with RETAIL_COMPATIBLE_NETWORK but I do not know.
There was a problem hiding this comment.
I suggest test with a client that has this change and a client that does not in a Network match - if not already done.
I already did testing and I wasn't able to reproduce this issue in a test environment because of the timing issue with the decrease in the runahead value.
My first instinct would be to guard it with RETAIL_COMPATIBLE_NETWORK but I do not know.
It's an issue with and without retail compatibility. I'm trying to avoid this type of 'fake' mismatches, so putting it behind a macro doesn't make sense to me.
xezon
left a comment
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
For a follow up, consider moving the Network test before the above TheNetwork->sawCRCMismatch()
| Int validatedCRC = crcIt->second; | ||
| //DEBUG_LOG(("CRC to validate is from player %d: %8.8X", crcIt->first, validatedCRC)); | ||
| if (validatorCRC != validatedCRC) | ||
| // TheSuperHackers @bugfix Caball009 14/06/2026 Check if player is still connected, |
There was a problem hiding this comment.
I suggest test with a client that has this change and a client that does not in a Network match - if not already done. My first instinct would be to guard it with RETAIL_COMPATIBLE_NETWORK but I do not know.
| if (validatorCRC != validatedCRC) | ||
| // TheSuperHackers @bugfix Caball009 14/06/2026 Check if player is still connected, | ||
| // to avoid spurious mismatches at low CRC intervals, e.g. every frame. | ||
| if (!TheNetwork->isPlayerConnected(it->first)) |
There was a problem hiding this comment.
Is isPlayerConnected guaranteed to return the same result on every client in the same frame? I am asking because this function is in GameNetwork, not in GameLogic.
Can Client A lose connection to Client B, while Client C is still connected to Clients A, B? Is this scenario possible?
I suggest to test this with multiple clients.
There was a problem hiding this comment.
This change assumes that all player disconnects are perfectly in sync. Is this guaranteed?
Is isPlayerConnected guaranteed to return the same result on every client in the same frame?
I think so, because player disconnects are handled by Network::processFrameSynchronizedNetCommand, which has a relevant description:
"This is where network commands that need to be executed on the same frame should be executed."
There was a problem hiding this comment.
GeneralsGameCode/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Lines 2654 to 2664 in 091ab3d
FWIW this original code also strongly suggests that isPlayerConnected is synchronized across clients.
There was a problem hiding this comment.
In theory, players with the fix should carry on playing and not register a mismatch, so their game will continue.
Retail players may register the mismatch and dropout.
I don't entirely see a problem with this as it just replicates how crashes are handled, As TSH clients typically continue playing but retail players drop out.
This PR introduces a small to fix to avoid spurious mismatches that can occur if the following conditions are met simultaneously:
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: