feat(replay): Check CRC messages from all players in replays#2649
feat(replay): Check CRC messages from all players in replays#2649Caball009 wants to merge 6 commits into
Conversation
6b0ab7d to
b2e84cd
Compare
ea2f91d to
2bf436a
Compare
beaa007 to
3a28eae
Compare
|
| Filename | Overview |
|---|---|
| GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp | Core of the change: CRCInfo refactored into separate playback and per-player queues; generateMismatchData() implements multi-player mismatch attribution with correct queue-drain and frame calculation. Logic is sound, including the signed-Byte sentinel values (-1, -2) which work correctly because Byte is typedef'd to char (signed). |
| Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp | onLogicCrc() now routes messages to handlePlaybackCRCMessage or handlePlayerCRCMessage based on isLocalPlayer(); early-exit guards for sawCRCMismatch() preserve the previous "stop after first mismatch" invariant. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | processCommandList() now dispatches to GameLogic::checkForMismatch() (network) or TheRecorder->checkForMismatch() (replay) via the new CRCValidationMode enum; extracted checkForMismatch() is equivalent to the old inline block. |
| GeneralsMD/Code/GameEngine/Include/Common/Recorder.h | CRCInfo API redesigned with MismatchData struct and mode enum; clean separation of playback vs player CRC operations. |
| GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp | Adds -replayCRCMode option with numeric argument (0/1/2 → all-single/all-multi/local); out-of-range values fall through to mode 0 gracefully. |
| GeneralsMD/Code/GameEngine/Include/Common/Player.h | getPlayerDisplayName() gains const qualifier, required for calls on const Player* in the new mismatch reporting path. |
| GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h | m_replayCRCCheckMode field added (UnsignedByte); initialized to 0 in GlobalData.cpp constructor. |
| GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h | m_shouldValidateCRCs (Bool) replaced by m_validationModeCRC (CRCValidationMode enum); checkForMismatch() declared private. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant GL as GameLogic::update()
participant CL as TheCommandList
participant GLD as onLogicCrc()
participant REC as RecorderClass
participant CI as CRCInfo
Note over GL: Every REPLAY_CRC_INTERVAL frames
GL->>CL: appendMessage(MSG_LOGIC_CRC, crc, isPlayback)
Note over CL: Replay file CRCs also in list
GL->>GLD: processCommandList() → logicMessageDispatcher()
alt isLocalPlayer (observer/playback)
GLD->>REC: handlePlaybackCRCMessage(crc)
REC->>CI: pushPlaybackCRC(crc)
Note over CI: Skips first in multiplayer (m_skippedOne)
else non-local player (recorded)
GLD->>REC: handlePlayerCRCMessage(playerIndex, crc)
REC->>CI: pushPlayerCRC(playerIndex, crc)
GLD->>GL: "m_validationModeCRC = CRCMODE_REPLAY"
end
GL->>REC: checkForMismatch() [if CRCMODE_REPLAY]
REC->>CI: generateMismatchData()
loop for each accumulated CRC interval j
CI->>CI: popPlaybackCRC()
loop for each player i
CI->>CI: compare m_playerData[i][j] vs playbackCRC
end
alt single mismatch and MULTIPLEMISMATCH mode
CI-->>CI: ignore, continue
else mismatch confirmed
CI-->>REC: MismatchData(playerIndex, frame, crcs)
end
end
alt mmData.mismatched
REC->>REC: "TheInGameUI->message(GUI:CRCMismatch)"
REC->>REC: "TheInGameUI->message(details with player name)"
REC->>REC: setGamePaused() + setSawCRCMismatch()
end
%%{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 GL as GameLogic::update()
participant CL as TheCommandList
participant GLD as onLogicCrc()
participant REC as RecorderClass
participant CI as CRCInfo
Note over GL: Every REPLAY_CRC_INTERVAL frames
GL->>CL: appendMessage(MSG_LOGIC_CRC, crc, isPlayback)
Note over CL: Replay file CRCs also in list
GL->>GLD: processCommandList() → logicMessageDispatcher()
alt isLocalPlayer (observer/playback)
GLD->>REC: handlePlaybackCRCMessage(crc)
REC->>CI: pushPlaybackCRC(crc)
Note over CI: Skips first in multiplayer (m_skippedOne)
else non-local player (recorded)
GLD->>REC: handlePlayerCRCMessage(playerIndex, crc)
REC->>CI: pushPlayerCRC(playerIndex, crc)
GLD->>GL: "m_validationModeCRC = CRCMODE_REPLAY"
end
GL->>REC: checkForMismatch() [if CRCMODE_REPLAY]
REC->>CI: generateMismatchData()
loop for each accumulated CRC interval j
CI->>CI: popPlaybackCRC()
loop for each player i
CI->>CI: compare m_playerData[i][j] vs playbackCRC
end
alt single mismatch and MULTIPLEMISMATCH mode
CI-->>CI: ignore, continue
else mismatch confirmed
CI-->>REC: MismatchData(playerIndex, frame, crcs)
end
end
alt mmData.mismatched
REC->>REC: "TheInGameUI->message(GUI:CRCMismatch)"
REC->>REC: "TheInGameUI->message(details with player name)"
REC->>REC: setGamePaused() + setSawCRCMismatch()
end
Reviews (10): Last reviewed commit: "Expanded CRC check modes." | Re-trigger Greptile
|
@greptileai re-review this pull request now that the previous review concerns are addressed. |
3a28eae to
8907b92
Compare
| #if RETAIL_COMPATIBLE_CRC | ||
| // TheSuperHackers @tweak Caball009 21/06/2026 Playback argument serves no purpose anymore | ||
| // other than to be able play replays from newer retail compatible builds on older builds or retail. | ||
| const bool isPlayback = (TheRecorder && TheRecorder->isPlaybackMode()); | ||
| msg->appendBooleanArgument(isPlayback); | ||
| #endif |
There was a problem hiding this comment.
Perhaps this deserves special attention during reviewing due to potential issues wrt backward compatibility of the replay format.
f521203 to
eec0f3d
Compare
eec0f3d to
df84dba
Compare
e0738c5 to
5461f5b
Compare
The original behavior wrt replays is that only the CRC messages from the player that recorded the replay are checked. This means that players can experience a 'live' mismatch, but the game won't show the mismatch for their replay if they didn't cause it.
That's not a big deal for regular players but still unexpected behavior. For developers it can be very useful to get the exact frame the mismatch happens, regardless of which player recorded the replay or which player caused the mismatch.
This PR changes the default behavior so that the CRC messages from all players are checked. I also added an opt-out command line
-replayLocalPlayerCRCbecause developers with large replay collections may want to rely on the original behavior. As a small bonus the name of the mismatching player is now displayed on screen if it's possible to determine which player is responsible.See commits for cleaner diff.
Before:
replay_crc_before.mp4
After:
replay_crc_after.mp4
TODO: