Skip to content

fix(skirmish): Improve determinism for restarted games by resetting slot values#2373

Open
Caball009 wants to merge 4 commits intoTheSuperHackers:mainfrom
Caball009:fix_reset_slot_values_for_logical_seed
Open

fix(skirmish): Improve determinism for restarted games by resetting slot values#2373
Caball009 wants to merge 4 commits intoTheSuperHackers:mainfrom
Caball009:fix_reset_slot_values_for_logical_seed

Conversation

@Caball009
Copy link
Copy Markdown

@Caball009 Caball009 commented Mar 1, 2026

Restarted skirmish games may not start with the same logical seed value as the first start. This depends on whether there are players with a random color / position / faction. Those random values are determined by using and updating the logical seed on the first start, after which the random values are stored. That means that for restarted games the logical seed isn't used or updated for those purposes. This PR resets those values to improve determinism for restarted games.

You can put a breakpoint after GameLogic::startNewGame and compare the values of theGameLogicSeed for the first start and following starts and see how the values for the first start deviate from restarts.

TODO:

  • Address feedback.
  • Replicate in Generals.

@Caball009 Caball009 added Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Fix Is fixing something, but is not user facing labels Mar 1, 2026
@Caball009 Caball009 marked this pull request as ready for review March 19, 2026 13:50
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 19, 2026

Greptile Summary

This PR fixes a determinism regression in skirmish game restarts. When slots are configured with random color/position/faction (-1), the first call to startNewGame() advances the logical seed to resolve those values. Previously, restarts called saveOriginalSetup() again, overwriting the saved originals with the already-resolved values, so the seed was never consumed the same way twice. The fix introduces a one-shot m_saveOriginalSetup latch per GameSlot: the first startNewGame() snapshots the original (random) values and clears the latch; all subsequent calls restore those snapshots before random assignment runs, ensuring the seed is consumed identically every time. The change is correctly mirrored in both the Generals and GeneralsMD (Zero Hour) code paths.

Key changes:

  • GameSlot gains m_saveOriginalSetup (initialised TRUE in reset()), flipped to FALSE after the first saveOriginalSetup() call
  • GameLogic::startNewGame() branches on the latch: save originals on first start, restore them on restart
  • toString(GameMode) helper added to both GameLogic translation units for readable debug assertions
  • DEBUG_ASSERTCRASH guards the restart branch to catch unexpected non-skirmish invocations in debug builds

Confidence Score: 5/5

Safe to merge — the fix is logically sound, correctly mirrored in both Generals and Zero Hour code paths, and no P0/P1 issues were identified.

All changed files receive a 5/5 analysis. The one-shot latch pattern is simple and correct: reset() re-arms it for a fresh game setup, saveOriginalSetup() fires once on the first start, and all subsequent restarts restore the snapshots before random seeding. The DEBUG_ASSERTCRASH guard provides adequate protection against accidental non-skirmish invocations. No rule violations were detected.

No files require special attention.

Important Files Changed

Filename Overview
Core/GameEngine/Include/GameNetwork/GameInfo.h Adds m_saveOriginalSetup bool field and getter to GameSlot, and renames saveOffOriginalInfo() to saveOriginalSetup()
Core/GameEngine/Source/GameNetwork/GameInfo.cpp Initialises m_saveOriginalSetup = TRUE in reset(), sets it to FALSE in saveOriginalSetup() as a one-shot latch, ensuring the flag correctly tracks whether the first-start originals have been captured
Generals/Code/GameEngine/Include/GameLogic/GameLogic.h Adds free-function declaration const char* toString(GameMode mode) to support debug-log messaging in the new restart logic
Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Implements toString(GameMode) and the core determinism fix: on restart restores the slot's original color/position/template before random seeding, so the logical seed progresses identically to the first start
GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h Mirrors the Generals header — adds the same toString(GameMode) declaration for the Zero Hour code path
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Mirrors the Generals implementation — same toString function and identical restart-determinism fix applied to the Zero Hour build

Sequence Diagram

sequenceDiagram
    participant User
    participant GameLogic
    participant GameSlot
    participant Seed as LogicalSeed

    Note over User,Seed: First Game Start
    User->>GameLogic: startNewGame()
    GameLogic->>GameSlot: getSaveOriginalSetup() returns TRUE
    GameLogic->>GameSlot: saveOriginalSetup() saves color and pos and template
    Note over GameSlot: latch set to FALSE
    GameLogic->>Seed: populateRandomSideAndColor()
    GameLogic->>Seed: populateRandomStartPosition()
    Note over Seed: Seed advances and resolves random slots

    Note over User,Seed: Game Restart
    User->>GameLogic: startNewGame()
    GameLogic->>GameSlot: getSaveOriginalSetup() returns FALSE
    GameLogic->>GameSlot: restore original color and pos and template
    GameLogic->>Seed: populateRandomSideAndColor()
    GameLogic->>Seed: populateRandomStartPosition()
    Note over Seed: Seed advances identically to first start
Loading

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

Copy link
Copy Markdown

@xezon xezon left a comment

Choose a reason for hiding this comment

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

OK

Needs to be replicated in Generals

Copy link
Copy Markdown

@xezon xezon left a comment

Choose a reason for hiding this comment

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

Greptile is still complaining about something?

@Caball009
Copy link
Copy Markdown
Author

Greptile is still complaining about something?

Yes, that was a good point. I think it could have affected the randomization of AI players if a game was loaded from inside another game or replay.

m_slot[slot]->setState((SlotState)state, name);
if (isAccepted) m_slot[slot]->setAccept();
m_slot[slot]->setPlayerTemplate(origPlayerTemplate);
m_slot[slot]->setStartPos(origStartPos);
m_slot[slot]->setColor(origColor);
m_slot[slot]->saveOffOriginalInfo();

It should work correctly now because GameSlot::setState sets m_saveOffOriginalInfo back to true before GameSlot::saveOffOriginalInfo is called.


Replication in Generals still needs doing.

@Caball009 Caball009 force-pushed the fix_reset_slot_values_for_logical_seed branch from e5d4698 to 0239428 Compare March 27, 2026 15:20
@Caball009
Copy link
Copy Markdown
Author

I can replicate in Generals unless there are other changes desired.


void saveOffOriginalInfo();
void saveOriginalSetup();
Bool getSaveOriginalSetup() const { return m_saveOriginalSetup; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It appears a bit confusing to read.

Would it make sense to flip the meaning from getSaveOriginalSetup to hasSavedOriginalSetup? Maybe the new boolean value isn't even necessary and the save can be inferred from the saved values:

	m_origPlayerTemplate = -1;
	m_origStartPos = -1;
	m_origColor = -1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

storeSetup() and hasStoredSetup()?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Fix Is fixing something, but is not user facing 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.

3 participants