Skip to content

feat(replay): Check CRC messages from all players in replays#2649

Open
Caball009 wants to merge 5 commits into
TheSuperHackers:mainfrom
Caball009:feat_replay_check_all_crc
Open

feat(replay): Check CRC messages from all players in replays#2649
Caball009 wants to merge 5 commits into
TheSuperHackers:mainfrom
Caball009:feat_replay_check_all_crc

Conversation

@Caball009

@Caball009 Caball009 commented Apr 23, 2026

Copy link
Copy Markdown

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 the CRC messages from all players are checked. I also added an opt-out command line -replayLocalPlayerCRC because 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:

  • Replicate in Generals.
  • Do another large replay test before merging.
  • Create separate commit for the deprecation of the CRC playback argument.
  • Move changes from last commit to other commits.
  • Split into smaller commits.
  • Add comments

@Caball009 Caball009 added Enhancement Is new feature or request Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour labels Apr 23, 2026
@Caball009 Caball009 force-pushed the feat_replay_check_all_crc branch 4 times, most recently from 6b0ab7d to b2e84cd Compare April 24, 2026 19:28
@Caball009 Caball009 force-pushed the feat_replay_check_all_crc branch 4 times, most recently from ea2f91d to 2bf436a Compare June 4, 2026 14:22
@Caball009 Caball009 force-pushed the feat_replay_check_all_crc branch 2 times, most recently from 94b7715 to 9526f3f Compare June 21, 2026 14:00
@Caball009 Caball009 force-pushed the feat_replay_check_all_crc branch 2 times, most recently from beaa007 to 3a28eae Compare June 21, 2026 16:50
@Caball009 Caball009 marked this pull request as ready for review June 21, 2026 17:02
@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR changes replay CRC mismatch detection to check CRC messages from all recorded players instead of only the player who recorded the replay, making it possible to pinpoint which player caused a desync regardless of who recorded. A new -replayLocalPlayerCRC command-line flag is added to opt back into the old per-recorder-only behaviour.

  • CRCInfo is refactored from a single m_data list into separate m_playbackData (live observer CRCs) and m_playerData[MAX_PLAYER_COUNT] (per-player recorded CRCs) queues; generateMismatchData() iterates both to attribute the mismatch to a specific player, to the playback observer, or marks it unknown when multiple players diverge simultaneously.
  • GameLogic::m_shouldValidateCRCs (Bool) is replaced with a CRCValidationMode enum (NONE/NETWORK/REPLAY), and the network mismatch check is extracted into GameLogic::checkForMismatch(); replay mismatch checking is dispatched to TheRecorder->checkForMismatch() after all CRC messages in a frame are collected.
  • The mismatch display is improved to show the diverging player's name, the per-player and playback CRC values, and the exact frame.

Confidence Score: 5/5

Safe to merge pending the outstanding TODOs (replication in Generals and a final large-replay smoke test).

The routing split (local-player → playback queue, non-local → per-player queue) is correct given the confirmed replay invariant that all replay-file messages have isLocalPlayer()==false. The Byte sentinel values (-1, -2) work correctly because Byte is typedef'd as signed char. getNthPlayer safely returns nullptr for negative indices, and all callsites handle that. The generateMismatchData cleanup loop correctly drains the playback queue on mismatch and clears m_playerData. No logic errors were found.

No files require special attention. The two outstanding TODOs (Generals replication and final replay test) are tracked in the PR checklist and are not code defects in the changed files.

Important Files Changed

Filename Overview
GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp Core of the feature: splits CRCInfo into separate playback and per-player queues, adds generateMismatchData() to detect and attribute mismatches across all players; logic appears correct.
GeneralsMD/Code/GameEngine/Include/Common/Recorder.h Adds MismatchData struct with Byte-backed sentinel values (PLAYER_PLAYBACK=-1, PLAYER_UNKNOWN=-2); replaces single queue API with separate handlePlaybackCRCMessage / handlePlayerCRCMessage / checkForMismatch; well-designed interface.
Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp Routes local-player CRCs to handlePlaybackCRCMessage and non-local to handlePlayerCRCMessage, introduces CRCMODE_REPLAY, adds early-exit guard for already-detected mismatches; correct under the confirmed invariant that all replay-file messages have isLocalPlayer()==false.
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Replaces Bool m_shouldValidateCRCs with a CRCValidationMode enum (NONE/NETWORK/REPLAY), extracts checkForMismatch() for the network path, and dispatches to TheRecorder->checkForMismatch() for replays; clean refactor.
GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp Adds -replayLocalPlayerCRC flag that sets m_replayLocalPlayerCRC; straightforward opt-out for developers who rely on old per-recorder-player behaviour.
GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h Adds Bool m_replayLocalPlayerCRC field; correctly initialised to FALSE in GlobalData.cpp.
GeneralsMD/Code/GameEngine/Include/Common/Player.h Makes getPlayerDisplayName() const-qualified to allow calling from const Player* contexts; safe, non-functional change.
GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h Adds CRCValidationMode enum and checkForMismatch() declaration; replaces Bool flag with typed enum for clearer semantics.
GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp Initialises m_replayLocalPlayerCRC to FALSE; trivial, correct change.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant GL as GameLogic::update()
    participant CL as TheCommandList
    participant D as onLogicCrc()
    participant R as TheRecorder (CRCInfo)
    participant UI as TheInGameUI

    GL->>CL: "appendMessage(MSG_LOGIC_CRC, liveCRC, isPlayback=true)"
    Note over CL: Replay file messages also in list

    GL->>GL: processCommandList()
    loop Each MSG_LOGIC_CRC in list
        GL->>D: logicMessageDispatcher(msg)
        alt msgPlayer.isLocalPlayer() — live observer CRC
            D->>R: handlePlaybackCRCMessage(liveCRC)
            Note over R: pushes to m_playbackData
        else recorded player CRC from replay file
            D->>R: handlePlayerCRCMessage(playerIndex, recordedCRC)
            Note over R: pushes to m_playerData[playerIndex]
            D->>GL: "set m_validationModeCRC = CRCMODE_REPLAY"
        end
    end

    alt "m_validationModeCRC == CRCMODE_REPLAY"
        GL->>R: checkForMismatch()
        R->>R: generateMismatchData()
        Note over R: pops m_playbackData, compares vs each m_playerData[i]
        alt mismatch detected
            R->>UI: message(GUI:CRCMismatch)
            R->>UI: message(details: frame, playbackCRC, playerCRC, playerName)
            R->>GL: setGamePaused()
            R->>R: setSawCRCMismatch()
        end
    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 GL as GameLogic::update()
    participant CL as TheCommandList
    participant D as onLogicCrc()
    participant R as TheRecorder (CRCInfo)
    participant UI as TheInGameUI

    GL->>CL: "appendMessage(MSG_LOGIC_CRC, liveCRC, isPlayback=true)"
    Note over CL: Replay file messages also in list

    GL->>GL: processCommandList()
    loop Each MSG_LOGIC_CRC in list
        GL->>D: logicMessageDispatcher(msg)
        alt msgPlayer.isLocalPlayer() — live observer CRC
            D->>R: handlePlaybackCRCMessage(liveCRC)
            Note over R: pushes to m_playbackData
        else recorded player CRC from replay file
            D->>R: handlePlayerCRCMessage(playerIndex, recordedCRC)
            Note over R: pushes to m_playerData[playerIndex]
            D->>GL: "set m_validationModeCRC = CRCMODE_REPLAY"
        end
    end

    alt "m_validationModeCRC == CRCMODE_REPLAY"
        GL->>R: checkForMismatch()
        R->>R: generateMismatchData()
        Note over R: pops m_playbackData, compares vs each m_playerData[i]
        alt mismatch detected
            R->>UI: message(GUI:CRCMismatch)
            R->>UI: message(details: frame, playbackCRC, playerCRC, playerName)
            R->>GL: setGamePaused()
            R->>R: setSawCRCMismatch()
        end
    end
Loading

Reviews (8): Last reviewed commit: "Added '-replayLocalPlayerCRC' command li..." | Re-trigger Greptile

Comment thread Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
Comment thread GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp Outdated
@Caball009 Caball009 marked this pull request as draft June 21, 2026 17:38
@Caball009 Caball009 marked this pull request as ready for review June 21, 2026 17:41
@Caball009

Copy link
Copy Markdown
Author

@greptileai re-review this pull request now that the previous review concerns are addressed.

@Caball009 Caball009 force-pushed the feat_replay_check_all_crc branch from 3a28eae to 8907b92 Compare June 21, 2026 18:57
Comment on lines +3779 to +3784
#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

@Caball009 Caball009 Jun 21, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Perhaps this deserves special attention during reviewing due to potential issues wrt backward compatibility of the replay format.

@Caball009 Caball009 force-pushed the feat_replay_check_all_crc branch 3 times, most recently from cbdbfa6 to f521203 Compare June 22, 2026 14:31
@Caball009 Caball009 force-pushed the feat_replay_check_all_crc branch from f521203 to eec0f3d Compare June 23, 2026 00:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Is new feature or request Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant