Skip to content

feat(cli): Add -saveatframe and -saveto options - #3135

Draft
bobtista wants to merge 7 commits into
TheSuperHackers:mainfrom
bobtista:bobtista/feat/saveatframe-cli
Draft

bobtista wants to merge 7 commits into
TheSuperHackers:mainfrom
bobtista:bobtista/feat/saveatframe-cli

Conversation

@bobtista

@bobtista bobtista commented Aug 14, 2026 •

Copy link
Copy Markdown

Fixes #3134. Follow-up to #3001.

There is no command-line way to write a save, so save/load testing still needs the in-game menu.

Now -saveatframe <frame> saves before logic updates at or after the requested frame, then quits. It waits while player input is disabled. -saveto <filename> sets the filename, defaulting to commandline.sav. Both options are debug-only and reject missing or invalid arguments.

Zero Hour also rejects multiplayer and replay saves, and returns a nonzero exit status if saving fails.

Earlier verification on MD_CHI01: a save requested at frame 1780 deferred to 1781 while input was disabled, then loaded through -loadsave.

The follow-up changes pass focused C++98 tests for argument validation, save deferral, game modes, save failures and exit-status propagation through cleanup. No new full game build or in-game save/load test was run.

Note

Add -saveatframe and -saveto debug command-line options

  • Adds -saveatframe (positive frame number) and -saveto (filename, .sav appended if missing) parsers in CommandLine.cpp and CommandLine.cpp; invalid values terminate startup
  • Stores the target frame and filename in RTS_DEBUG-only GlobalData members, defaulting to commandline.sav and frame zero (disabled)
  • When the target frame is reached in an interactive debug game, GameLogic::update writes a normal save before script and object updates, then requests shutdown; the save is deferred while in-game input is disabled
  • Adds process exit code support (GameEngine getter/setter and GameMain uses it after normal execution); in GeneralsMD, a failed save exits with 1, a successful save exits with 0, and multiplayer/replay sessions exit with 1 without saving
  • Behavioral Change: normal game execution now returns the engine-set exit status instead of always 0; the feature only exists in debug (RTS_DEBUG) builds

Macroscope summarized 968003c. (Automatic summaries will resume when PR exits draft mode or review begins).

@bobtista
bobtista force-pushed the bobtista/feat/saveatframe-cli branch 2 times, most recently from 6933dd3 to 351db69 Compare August 14, 2026 01:28
@bobtista bobtista self-assigned this Aug 14, 2026
@bobtista bobtista added Enhancement Is new feature or request Debug Is mostly debug functionality labels Aug 14, 2026
@bobtista
bobtista force-pushed the bobtista/feat/saveatframe-cli branch from 351db69 to c89d8fc Compare August 16, 2026 23:02
@bobtista
bobtista force-pushed the bobtista/feat/saveatframe-cli branch 3 times, most recently from de4bd76 to dfde359 Compare August 27, 2026 16:30
@bobtista
bobtista force-pushed the bobtista/feat/saveatframe-cli branch from dfde359 to 10d9370 Compare September 14, 2026 21:07
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Debug builds add command-line options to save a game at a requested logic frame and set the output filename. Game logic handles the save request when the frame threshold is reached. GeneralsMD also returns an exit code based on the save outcome or unsupported game mode.

Changes

Command-line frame save

Layer / File(s) Summary
Configure the command-line save
Core/GameEngine/Source/Common/CommandLine.cpp, Generals/Code/GameEngine/.../GlobalData.h, Generals/Code/GameEngine/.../GlobalData.cpp, GeneralsMD/Code/GameEngine/.../GlobalData.h, GeneralsMD/Code/GameEngine/.../GlobalData.cpp
Debug builds add -saveatframe and -saveto. The parser validates the frame and filename, then stores them in debug-only GlobalData fields. The fields default to frame 0 and filename commandline.sav.
Save at the requested frame
Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp, GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h, GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp, GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp, GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
Game logic checks the configured frame before script and object updates. It defers saving while UI input is disabled, or saves and requests shutdown. In GeneralsMD, multiplayer and replay modes exit with code 1; save completion sets code 0 on success or 1 on failure. GameMain returns the engine exit code after execution.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant CommandLine
  participant GlobalData
  participant GameLogic
  participant GameEngine
  participant GameMain
  CommandLine->>GlobalData: Store requested frame and filename
  GameLogic->>GlobalData: Read save settings
  GameLogic->>GameLogic: Check frame and UI input state
  GameLogic->>GameLogic: Write configured save
  GameLogic->>GameEngine: Request shutdown and set exit code
  GameEngine->>GameMain: Return exit code after execution
Loading

Merge Risk: 🟡 Moderate · up to 2561b

Unattended save tests can report success when no usable save was written. Propagate save failures to the process exit status before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 96800

Saving is limited to debug builds and a filename in the save directory, but the two game editions handle unsupported modes and failed saves differently. In Generals, an automated run can report success when its save failed.

Retained concerns

  • Medium · reliability · observed: The new Generals command-line save path quits after a failed save without propagating the failure to the process exit status. An unattended caller can therefore treat a missing or incomplete save as successful.
  • Low · reliability · observed: Unlike Zero Hour, Generals permits the new frame-triggered save in multiplayer and replay games: its interactive-game condition does not exclude those modes. The command-line operation can thus serialize state and request shutdown in modes the other edition treats as unsupported.
Security review details

Security Blast Radius

  • inferred — The independently controllable scope shown is a debug-build game process and its local save destination, not a network or tenant-wide entrypoint. The feature does, however, make that file-writing path available to unattended launches in both editions.

Trust Boundaries and Controls

  • observed — Command-line values cross into mutable GlobalData and then GameState. Numeric and filename validation constrain that transition; Zero Hour additionally checks multiplayer and replay modes before writing. Generals lacks the latter control on its new path.

Resilience and Maintainability Implications

  • observed — Both save implementations return a failure code for handled open or serialization errors, but an interrupted or failed write has no visible file rollback in the inspected function. Zero Hour reports returned failures to its caller; Generals’ new caller does not.

Hardening Proposals

  • proposed — For unattended saves, stage output and replace the destination only after a complete write, so interruption does not leave a partial result at the requested filename. This would strengthen an existing save implementation, not fix a newly introduced writer.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the two main command-line options added by the pull request.
Description check ✅ Passed The description directly explains the new options, validation rules, save behavior, exit statuses, testing, and scope.
Linked Issues check ✅ Passed Issue [#3134] requires unattended command-line saves at a selected logic frame for Generals and Zero Hour. The PR adds debug-only -saveatframe and -saveto options in both game code paths. The impl…
Out of Scope Changes check ✅ Passed The changes support issue [#3134]. Command-line parsing, debug state, frame-based save handling, input deferral, mode checks, and exit-status propagation are required for unattended save and load test…

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 75f0284a-2331-4f9b-911d-fede3ead5fa9

📥 Commits

Reviewing files that changed from the base of the PR and between bfa3bbf and ca559be.

📒 Files selected for processing (10)
  • Core/GameEngine/Source/Common/CommandLine.cpp
  • Generals/Code/GameEngine/Include/Common/GlobalData.h
  • Generals/Code/GameEngine/Source/Common/GlobalData.cpp
  • Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
  • GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h
  • GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h
  • GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp
  • GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp
  • GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.

Comment thread Core/GameEngine/Source/Common/CommandLine.cpp
Comment thread Core/GameEngine/Source/Common/CommandLine.cpp Outdated
Comment thread Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 8a315147-3dc0-4bfd-a8e2-1456dfdd15bb

📥 Commits

Reviewing files that changed from the base of the PR and between ca559be and 968003c.

📒 Files selected for processing (2)
  • Core/GameEngine/Source/Common/CommandLine.cpp
  • Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.

Comment thread Core/GameEngine/Source/Common/CommandLine.cpp

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Propagate failed saveGame results to the Generals process status. · GameLogic.cpp:3220-3232

Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp:3220-3232
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Propagate failed saveGame results to the Generals process status.

-saveatframe is registered for unattended save/load tests. A failed saveGame call in debug Generals is logged, but GameMain still returns its initial zero status. An unattended caller can therefore report success for a failed save. Add the same exit-status path already used by GeneralsMD.

Suggested fix
diff --git a/Generals/Code/GameEngine/Include/Common/GameEngine.h b/Generals/Code/GameEngine/Include/Common/GameEngine.h
@@
 	virtual void setQuitting( Bool quitting );				///< set quitting status
 	virtual Bool getQuitting();						///< is app getting ready to quit.
 
+	Int getExitCode() const { return m_exitCode; }
+	void setExitCode(Int exitCode) { m_exitCode = exitCode; }
+
@@
 	Real m_logicTimeAccumulator; ///< Frame time accumulated towards submitting a new logic frame
 
+	Int m_exitCode; ///< process exit status after engine shutdown
 	Bool m_quitting; ///< true when we need to quit the game
 	Bool m_isActive; ///< app has OS focus.
 };
diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp
@@
 	// initialize to non garbage values
 	m_logicTimeAccumulator = 0.0f;
+	m_exitCode = 0;
 	m_quitting = FALSE;
 	m_isActive = FALSE;
diff --git a/Generals/Code/GameEngine/Source/Common/GameMain.cpp b/Generals/Code/GameEngine/Source/Common/GameMain.cpp
@@
 		// run it
 		TheGameEngine->execute();
+		exitcode = TheGameEngine->getExitCode();
 	}
diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp
@@
-			MAYBE_UNUSED const SaveResult saveResult = TheGameState->saveGame(saveName, UnicodeString(L"Command line save"), SAVE_FILE_TYPE_NORMAL);
+			const SaveResult saveResult = TheGameState->saveGame(saveName, UnicodeString(L"Command line save"), SAVE_FILE_TYPE_NORMAL);
 			DEBUG_LOG(("Command line save to '%s' at frame %d returned %d", saveName.str(), m_frame, (Int)saveResult.saveCode));
 			TheWritableGlobalData->m_saveAtFrame = 0;
+			TheGameEngine->setExitCode(saveResult.saveCode == SC_OK ? 0 : 1);
 			TheGameEngine->setQuitting(TRUE);

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: ee51659e-65f9-4460-9b90-b4ae043c7d02

📥 Commits

Reviewing files that changed from the base of the PR and between 968003c and 2561b02.

📒 Files selected for processing (1)
  • Core/GameEngine/Source/Common/CommandLine.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • Core/GameEngine/Source/Common/CommandLine.cpp

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

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

Labels

Debug Is mostly debug functionality Enhancement Is new feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No command line option to write a save at a chosen point

1 participant