Skip to content

feat(launching): launch the native BGFX client against retail archives - #332

Merged
bobtista merged 22 commits into
developmentfrom
feat/native-launch
Aug 1, 2026
Merged

feat(launching): launch the native BGFX client against retail archives#332
bobtista merged 22 commits into
developmentfrom
feat/native-launch

Conversation

@bobtista

@bobtista bobtista commented Jul 30, 2026

Copy link
Copy Markdown

Summary

Launches the community's native BGFX Zero Hour client against the user's retail data — no Wine. Final PR of the mac stack, after #328 (platform-neutral paths, manifests and Unix workspaces), #329 (macOS host) and #331 (the macOS service registrations the merged state required).

Changes

  • Surface native launch failures and set the loader search path. A launch that dies before drawing a window previously reported success.

  • Reach retail archives via the install-path environment instead of copying them. The engine reads CNC_ZH_INSTALLPATH / CNC_GENERALS_INSTALLPATH on non-Windows and mounts *.big from those roots. Zero Hour needs its own and the base Generals archives — roughly 3 GB — so without this every profile workspace had to materialise all of them.

  • Validate retail archive roots before spawning. Checked before spawn so a misconfigured root fails with the path named, rather than as a generic engine abort the host has to interpret.

    Corrected from this PR's original description, which claimed the engine reaches its main loop with zero content and no error and that nothing was observable. Verified against bobtista/GeneralsGameCode at bobtista/topic/trunk, that is false: a root holding no archives aborts during initialisation with exit 1 and a ReleaseCrashInfo.txt, and an unmountable archive also writes [ggc] ARCHIVE MOUNT FAILED to stderr. Neither reaches the main loop.

    Validating first still earns its place for reasons that hold: exit 1 is generic; the stderr sentinel exists only in StdBIGFileSystem, not Win32BIGFileSystem; ReleaseCrash shows a system-modal dialog before _exit(1), so a host-launched child on Windows hangs rather than dying; and GenHub's own immediate-exit detection is a 500 ms window. An earlier check with an actionable message avoids depending on any of that. Validation runs against the installation's declared paths, not the filtered environment — the environment builder drops a path that does not exist, so validating it alone would skip the stale-installation case this exists to catch.

  • Stop setting DYLD_LIBRARY_PATH / LD_LIBRARY_PATH. Measured unnecessary: the BGFX build declares every dependency as @executable_path/… with a matching rpath and launches with the variable cleared. Two costs outweighed a fallback for a build we do not ship — dyld consults it before @executable_path for leaf-name references, so a same-named library elsewhere silently wins; and the hardened runtime ignores it, so behaviour would change silently once GenHub is signed and notarized (Playwright exclusion, inside-out signing and notarization (blocker for public macOS builds) #322).

  • Capture complete stderr when a launch fails. The buffer was read as soon as the process exited, but the asynchronous handlers had not necessarily delivered their final lines — truncating diagnostics precisely where they explain the failure. Now drains via the parameterless WaitForExit(), which also waits for redirected-output handlers. The buffer treats a null line as end-of-stream rather than content, retains the head as well as the tail so startup context survives alongside the symptom, and bounds line length and total size.

Testing

  • GenHub.Tests.Core: 1427 passed, 0 failed.
  • GenHub.Tests.MacOS composition root: 3 passed.
  • Builds with 0 warnings.

The launch tests pass locally and skip in CI — please read this before trusting a green tick

Six tests exercise the real client: three launch it and assert it is still alive after 14 seconds, three assert a misconfigured launch does not survive. All six pass on a machine with a native install (GENHUB_NATIVE_CLIENT_DIR, 6/6 in 54s).

They skip in CI, which has no native fixture and no retail data. A green CI run on this PR therefore says nothing about whether launching works — it only covers the unit and composition tests. The engine-only CI smoke test that would close this gap is #324, and it is deliberately out of scope here.

Fork dependency worth stating

The install-path mechanism this PR validates is fork-only. CNC_ZH_INSTALLPATH and
CNC_GENERALS_INSTALLPATH are read by bobtista/GeneralsGameCode on bobtista/topic/trunk
and do not exist in TheSuperHackers/GeneralsGameCode@main — upstream resolves install paths
from the Windows registry only.

That is fine, since GenHub launches our client, but it bounds what the validation means:
against an upstream build these variables are ignored and the roots have no effect. The
constants and their rationale live in RetailArchiveConstants so this is discoverable rather
than implicit.

The engine behaviour the validation complements is upstream, though — the INI
filesRead == 0 throw, the unnamed enum that routes it to catch (...), and ReleaseCrash
showing a system-modal dialog before _exit(1) are all in upstream main. So the reasons
for checking before spawn rather than relying on the child's exit hold for any build.

Risks and rollback

Revert-safe; no persisted-data conversion. The archive-root validation stops launches before spawn that would previously have failed only after spawn, with a generic engine crash naming nothing — intended, and the validation message names the offending root.

Related

Part of Alpha 5 (#327).

Closes #315.
Closes #317.
Closes #318.

(One keyword per line: GitHub links only the first reference in Closes #315, #317, #318, which would have left #317 and #318 open after merge.)

Greptile Summary

This PR enables native BGFX clients to launch against retail archive roots and improves startup diagnostics.

  • Centralizes the retail archive environment-variable contract and normalizes configured roots.
  • Validates applicable archive roots before spawning native clients, with Windows excluded from the non-Windows contract.
  • Captures bounded stderr output and reports immediate process failures.
  • Adds unit and opt-in native integration coverage for archive roots, manifests, permissions, and diagnostics.

Confidence Score: 3/5

The PR is not yet safe to merge because native Zero Hour can still be spawned without any validated source of its required base Generals archives.

The current validation skips an absent GeneralsPath, while supported engine-only workspaces contain no archives and rely entirely on environment-provided retail roots; that leaves the previously reported missing-base-content startup failure reachable.

Files Needing Attention: GenHub/GenHub/Features/Launching/GameLauncher.cs; GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs

Important Files Changed

Filename Overview
GenHub/GenHub/Features/Launching/GameLauncher.cs Builds and validates native retail archive environments; the previous missing-Generals-root failure remains reachable.
GenHub/GenHub/Features/GameProfiles/Infrastructure/GameProcessManager.cs Adds Unix execute-permission checks, bounded asynchronous stderr capture, and clearer immediate-exit handling.
GenHub/GenHub.Core/Constants/RetailArchiveConstants.cs Centralizes archive environment names and case-insensitive top-level archive enumeration settings.
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs Covers archive-root validation behavior but codifies acceptance of a Zero Hour launch with no Generals root.
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs Adds opt-in native coverage proving engine-only workspaces rely on both retail archive environment roots.

Sequence Diagram

sequenceDiagram
    participant Launcher as GameLauncher
    participant Install as GameInstallation
    participant Process as GameProcessManager
    participant Engine as Native BGFX Client
    Launcher->>Install: Read Zero Hour / Generals roots
    Launcher->>Launcher: Build normalized environment
    Launcher->>Launcher: Validate declared archive roots
    alt Validation fails
        Launcher-->>Launcher: Return actionable launch error
    else Validation passes
        Launcher->>Process: StartProcessAsync(configuration)
        Process->>Engine: Spawn with redirected stderr
        alt Immediate exit
            Process->>Process: Drain and bound stderr
            Process-->>Launcher: Return failure diagnostics
        else Process survives startup window
            Process-->>Launcher: Return tracked process
        end
    end
Loading

Reviews (10): Last reviewed commit: "chore(launching): reference launch const..." | Re-trigger Greptile

Context used:

bobtista added 12 commits July 30, 2026 03:24
Measured unnecessary: the BGFX build declares every dependency as @executable_path/ with a matching rpath and launches with the variable cleared. Two costs outweighed a fallback for a build we do not ship: dyld consults DYLD_LIBRARY_PATH before @executable_path for leaf-name references, so a same-named library elsewhere silently wins; and the hardened runtime ignores it, so behaviour would change silently once GenHub is signed and notarized.

Closes #317.
…gine

The engine reaches its main loop with zero content and no error when the archive roots are wrong: no exit code, no log line, nothing a host process can observe. Launch success therefore cannot distinguish a working game from an empty one, so the roots are checked before spawn and the launch fails with the offending root named.

Presence of at least one .big archive is the sentinel rather than a specific filename, which varies by localisation and version. An archive that exists but fails to mount still needs a machine-readable failure from the engine.

Closes #315.
The buffer was read as soon as the process exited, but the asynchronous stderr handlers had not necessarily delivered their final lines — truncating diagnostics precisely on the path where they explain the failure. Wait via the parameterless WaitForExit overload, which also waits for redirected-output handlers, before reading.

The buffer now treats a null line as the end-of-stream signal rather than content, retains the head as well as the tail so startup context survives alongside the failure, and bounds line length and total size so a pathological writer cannot exhaust memory. Only stderr is redirected, so there is no stdout stream to drain.

Closes #318.
Adds an end-to-end check that a process failing immediately still reports both the head and tail of its stderr. It does not deterministically pin the end-of-stream drain: removing the WaitForExit() call leaves it passing on macOS and .NET 8, because the handlers complete before the capture is read even at twenty thousand lines. The drain stands on the documented contract instead.

The GameLauncher fixture now uses a real retail root holding an archive. It previously pointed at a Windows path that does not exist off Windows, which the new pre-spawn validation correctly rejected.

Clears the warnings this stack introduced; development itself builds clean. Two remain, both predating these fixes and needing invasive reordering of long files: SA1204 in GameLauncher and SA1202 in WorkspaceStrategyBase.
…alization change

EnsureExecutableAsync became protected when the rethrow was added, which put it after a private helper. Moved it above, and dropped the trailing blank line the earlier reordering left.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Improved non-Windows retail-archive discovery using exported install-root environment variables and a standard archive search pattern.
  • Bug Fixes
    • Added fast non-Windows checks for missing/unexecutable engine files.
    • Added pre-launch validation for required archive roots, failing early when archives are missing or unreadable.
    • Enhanced startup failure diagnostics with more reliable stderr capture and bounded error excerpts.
  • Tests
    • Expanded integration and unit coverage for native client launching, archive discovery/validation, stderr capture timing, and bounded error buffering.

Walkthrough

Adds cross-platform retail archive-root constants and pre-launch validation, bounded stderr diagnostics and Unix permission checks, case-insensitive installation discovery, and native-client integration tests.

Changes

Native launch support

Layer / File(s) Summary
Retail archive contract and validation
GenHub/GenHub.Core/Constants/RetailArchiveConstants.cs, GenHub/GenHub/Features/Launching/GameLauncher.cs, GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/*, GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs
Defines archive variables, propagates and validates non-Windows archive roots, and tests valid, invalid, missing, and engine-only archive scenarios.
Native process diagnostics and bounded stderr
GenHub/GenHub/Features/GameProfiles/Infrastructure/GameProcessManager.cs, GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/*Error*Tests.cs, GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeLaunchDiagnosticsTests.cs
Checks execute permissions, captures bounded stderr, drains output deterministically, and tests truncation, race handling, diagnostics, and deadlock prevention.
Native client discovery and integration
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClient*.cs
Adds optional native-client discovery, serialized launch tests, manifest resolution, runtime filtering, and real-client startup checks.
Installation and validation coverage
GenHub/GenHub/Features/GameProfiles/ViewModels/GameProfileLauncherViewModel.cs, GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ContentManifestPoolTests.cs, GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Workspace/WorkspaceValidatorTests.cs
Uses case-insensitive executable detection and extends manifest-ingestion and workspace test coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

Suggested labels: Enhancement, Testing

Poem

I’m a rabbit with archives tucked tight,
Launching the engine by moonlight.
Stderr trails from tail to head,
Empty roots are caught instead.
Hop, test, and run just right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes implement prelaunch retail-root validation, remove launch-path env vars, and fix stderr draining and buffering as requested.
Out of Scope Changes check ✅ Passed The added tests, constants, and docs all support the launch changes; no unrelated code changes stand out.
Docstring Coverage ✅ Passed Docstring coverage is 86.79% which is sufficient. The required threshold is 50.00%.
Title check ✅ Passed The title uses conventional commit format and accurately summarizes the native BGFX retail-archive launch work.
Description check ✅ Passed The description is clearly related to the changeset and matches the native launch, retail archive, and stderr diagnostics work.
📋 Issue Planner

Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).

View plans for tickets: #315, #317, #318

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/native-launch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Comment thread GenHub/GenHub/Features/Launching/GameLauncher.cs Outdated
Comment thread GenHub/GenHub/Features/Launching/GameLauncher.cs Outdated
Comment thread GenHub/GenHub/Features/Launching/GameLauncher.cs Outdated
…alize profile overrides

Two defects from review, both introduced by the earlier validation change.

Windows never reads CNC_ZH_INSTALLPATH or CNC_GENERALS_INSTALLPATH — it resolves install paths from the registry — so a Windows layout without loose top-level archives is not a misconfiguration. BuildEnvironmentVariables returns before setting them there, but validation reads the installation's declared paths, so it needed the same guard rather than inheriting it. Without it, valid Windows launches were rejected with a misleading message about missing archives.

A profile setting a root explicitly chooses the directory, not whether the trailing separator is applied. AddArchiveRoot returned early on an existing value and never normalized it, so the engine concatenated it with the archive filename to produce paths like '/path/toINIZH.big' and silently mounted nothing — precisely the failure this validation exists to prevent, reachable through the override path.

The variable names are an external contract with the engine, so they move to RetailArchiveConstants alongside the archive search pattern.

The normalization test fails against the previous early-return. The Windows guard asserts only on Windows and is a no-op elsewhere.
@bobtista

Copy link
Copy Markdown
Author

Addressed the Greptile findings. Both P1s were real and both were introduced by the archive-root validation change in this PR, so thanks — these would have shipped.

P1: Windows archive validation blocks launches. Confirmed. BuildEnvironmentVariables returns before setting the variables on Windows, but the validation reads the installation's declared paths rather than the built environment — deliberately, to catch a stale root that the environment builder silently drops — so it did not inherit that guard. On Windows, where install paths come from the registry and these variables are never read, a layout without loose top-level archives would have been rejected with a misleading message. Now guarded explicitly, with the reason recorded at the guard.

P1: profile archive roots remain unnormalized. Confirmed, and it defeated the purpose of the check. AddArchiveRoot returned early when the profile had already set the variable, so it never appended the separator; validation then accepted the value because it only tests existence and archive presence. The engine concatenates the value with the archive filename directly, so /path/to became /path/toINIZH.big and mounted nothing — the exact silent failure this PR exists to prevent, reachable through the override path. A profile override now chooses the directory but not whether the separator is applied.

P2: centralize archive environment constants. Agreed — these names are an external contract with the engine, not local strings. Moved to RetailArchiveConstants with the *.big search pattern, and the rationale for why Windows is excluded lives there too.

Verification: the normalization test fails against the previous early-return, so it pins the fix rather than merely passing. 1423 unit tests pass, 0 warnings. The Windows guard asserts only on Windows and is a no-op elsewhere, so CI's Windows job is what exercises it.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 30, 2026
Two of these asserted that validation rejects a bad root, which stopped being true on Windows once validation was guarded there — the platform guard was added without updating the tests that asserted the previous cross-platform behaviour, and Build Windows caught it.

Rejection is non-Windows behaviour by design; the Windows side is asserted separately by Validate_OnWindows_SkipsEntirely. The guards do not hollow the tests out: on non-Windows they still assert, and three still fail if the validation fix is reverted.
@kilo-code-bot

kilo-code-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (7 files)
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeLaunchDiagnosticsTests.cs - Replaced hardcoded strings with NativeClientFixture.BinaryName constant
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs - Replaced hardcoded environment variable names with RetailArchiveConstants
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs - Updated comment to reflect corrected engine behavior
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs - Updated comment to reflect corrected engine behavior
  • GenHub/GenHub/Features/GameClients/GameClientDetector.cs - Updated comment to be more generic
  • GenHub/GenHub/Features/Launching/GameLauncher.cs - Simplified validation logic by removing unused bool Required field and updated error messages
  • docs/dev/constants.md - Fixed capitalization of executable name in documentation
Previous Review Summaries (7 snapshots, latest commit 3eccb33)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 3eccb33)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • GenHub/GenHub.Core/Constants/RetailArchiveConstants.cs - Added case-insensitive enumeration options for archive pattern matching
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs - Added test for upper-cased archive names
  • GenHub/GenHub/Features/Launching/GameLauncher.cs - Updated to use case-insensitive archive enumeration

Previous review (commit 69460ea)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientFixture.cs - Added binary validation to GENHUB_NATIVE_CLIENT_DIR override and new IsDynamicLibrary method
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientFixtureTests.cs - New unit tests for IsDynamicLibrary covering macOS and Linux library patterns
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientManifestLaunchTests.cs - Updated to use IsDynamicLibrary and added library presence assertion
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs - Updated to use IsDynamicLibrary

Previous review (commit 1da85e5)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • GenHub/GenHub/Features/Launching/GameLauncher.cs - comment-only changes to clarify archive root validation behavior

Previous review (commit 7e6f8ce)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • GenHub/GenHub/Features/Launching/GameLauncher.cs - comment-only changes to clarify archive root validation behavior

Previous review (commit aeaa1c0)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs

Previous review (commit 00a2bc6)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (11 files)
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientFixture.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientLaunchCollection.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientLaunchIntegrationTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientManifestLaunchTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeLaunchDiagnosticsTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ContentManifestPoolTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Workspace/WorkspaceValidatorTests.cs
  • GenHub/GenHub/Features/GameProfiles/Infrastructure/GameProcessManager.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs

Previous review (commit 5ac6b3a)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (13 files)
  • GenHub/GenHub.Core/Constants/RetailArchiveConstants.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/BoundedErrorBufferTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientLaunchCollection.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientLaunchIntegrationTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientManifestLaunchTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeLaunchDiagnosticsTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/StderrCaptureRaceTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs
  • GenHub/GenHub/Features/GameProfiles/Infrastructure/GameProcessManager.cs
  • GenHub/GenHub/Features/GameProfiles/ViewModels/GameProfileLauncherViewModel.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs

Reviewed by glm-4.7 · Input: 53.4K · Output: 4.4K · Cached: 327.9K

@bobtista

Copy link
Copy Markdown
Author

@coderabbitai review

Your previous approval was dismissed by a push, and the re-review hit a rate limit. Since then:

  • Fixed the three Greptile findings (two P1s, both real and both introduced by the archive-root validation in this PR) — commit bdb0b19.
  • Fixed a Windows test regression that Build Windows caught: adding the platform guard to validation made two tests contradict it, since they asserted cross-platform rejection — commit 5ac6b3a.

All checks are green: Build Linux, Build Windows, Build macOS, Build Summary, Detect File Changes, Greptile and Kilo.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

@bobtista: I’ll review the updated changes, including the Windows guard and archive-root normalization fixes.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@bobtista

Copy link
Copy Markdown
Author

@coderabbitai full review

The incremental review reports the commits as already seen, so no new review is produced and the approval dismissed by the push is not restored. Requesting a full re-review of 5ac6b3a so the approval state reflects the current head.

For context, the two commits since your approval of bdb0b19:

  • bdb0b19 — the three Greptile findings (Windows validation guard, profile archive-root normalization, constants extraction)
  • 5ac6b3a — scoping two archive-root rejection tests to non-Windows, which Build Windows caught after the guard was added

All checks are green.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

@bobtista: I’ll perform a full re-review of the current PR head, including the Windows validation guard, archive-root normalization, constants extraction, and the platform-scoped test updates.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot added Enhancement New feature or request Testing Topic related to (unit) tests labels Jul 30, 2026

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
GenHub/GenHub/Features/GameProfiles/Infrastructure/GameProcessManager.cs (1)

304-336: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exit-0 fallback drops the already-captured stderr detail.

When the process exits immediately with code 0 and no spawned replacement is found (line 331-336), capturedErrors was already drained at line 311 but its content is never surfaced — only the non-zero-exit branch (line 318-329) builds and reports detail. This narrows the "complete stderr diagnostics" goal for a case that's still a genuine unexplained failure (e.g. a Windows launcher-stub whose spawned process wasn't found, or any immediate zero-exit anomaly).

🩹 Proposed fix
                     else
                     {
+                        var stderrTail = capturedErrors.ToString();
+                        var suffix = string.IsNullOrWhiteSpace(stderrTail) ? string.Empty : $" {stderrTail}";
                         // If it exits with 0 and we didn't find a spawned child, still fail the launch
                         // because we don't have a valid process to track, preventing the UI from getting stuck in a 'running' state
-                        return OperationResult<GameProcessInfo>.CreateFailure("Process exited immediately after launch.");
+                        return OperationResult<GameProcessInfo>.CreateFailure($"Process exited immediately after launch.{suffix}");
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@GenHub/GenHub/Features/GameProfiles/Infrastructure/GameProcessManager.cs`
around lines 304 - 336, Reuse the captured stderr detail in the exit-code-0
fallback within the immediate-exit handling flow: build the same
`stderrTail`/`detail` value from `capturedErrors` before branching, log it with
the zero-exit failure, and include it in the returned
`OperationResult<GameProcessInfo>` message. Preserve the existing non-zero
behavior and “No output was captured.” fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientLaunchIntegrationTests.cs`:
- Around line 31-59: The native-client fixture discovery logic is duplicated
between two integration test classes. In
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientLaunchIntegrationTests.cs#L31-L59,
extract EnvironmentOverride, BinaryName, and NativeClientDirectory into a shared
static helper near NativeClientLaunchCollection, then have
NativeClientLaunchIntegrationTests use it; in
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientManifestLaunchTests.cs#L32-L57,
remove the duplicate definitions and call the same helper while preserving the
existing discovery behavior.

In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs`:
- Around line 187-193: Update the file filter in StageEngineOnly to include
Linux shared libraries ending in “.so” alongside the existing binary and
“.dylib” matches. Preserve the current top-level enumeration and
case-insensitive extension handling.

In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs`:
- Around line 206-221: Reorder the CreateRoot and BuildEnvironment members so
the static BuildEnvironment method appears before the instance CreateRoot
method, add the required blank line between methods, and remove the extra blank
line before the class closing brace to satisfy StyleCop ordering and spacing
rules.

In `@GenHub/GenHub/Features/Launching/GameLauncher.cs`:
- Around line 531-594: Update ValidateRetailArchiveRoots to accept the launched
GameType and validate only that game's corresponding install-path variable and
declared path; preserve the existing Windows guard and root/archive checks.
Update the call site in the launch flow to pass gameClient.GameType so sibling
paths on the same installation cannot affect the current launch.

---

Outside diff comments:
In `@GenHub/GenHub/Features/GameProfiles/Infrastructure/GameProcessManager.cs`:
- Around line 304-336: Reuse the captured stderr detail in the exit-code-0
fallback within the immediate-exit handling flow: build the same
`stderrTail`/`detail` value from `capturedErrors` before branching, log it with
the zero-exit failure, and include it in the returned
`OperationResult<GameProcessInfo>` message. Preserve the existing non-zero
behavior and “No output was captured.” fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c845348c-4462-49b0-961e-5334deddabde

📥 Commits

Reviewing files that changed from the base of the PR and between 17bc745 and 5ac6b3a.

📒 Files selected for processing (13)
  • GenHub/GenHub.Core/Constants/RetailArchiveConstants.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/BoundedErrorBufferTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientLaunchCollection.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientLaunchIntegrationTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientManifestLaunchTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeLaunchDiagnosticsTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/StderrCaptureRaceTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs
  • GenHub/GenHub/Features/GameProfiles/Infrastructure/GameProcessManager.cs
  • GenHub/GenHub/Features/GameProfiles/ViewModels/GameProfileLauncherViewModel.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs

Comment thread GenHub/GenHub/Features/Launching/GameLauncher.cs
…port zero-exit stderr

Validation checked both games' declared roots regardless of which was launching, so a stale Zero Hour path could block a Generals launch that would have worked. It now checks only the launching game's root, taking the game type from the client.

A process exiting immediately with code 0 and no spawned child returned a bare message while the stderr drained moments earlier went unreported — the one diagnostic available for that case, and contrary to the point of the capture work in this branch.

Test changes: the native-client fixture discovery was duplicated across three classes and is now a shared NativeClientFixture, so they cannot diverge on when to skip; engine-only staging includes .so alongside .dylib, having been macOS-only; and SetUnixFileMode calls are guarded so the platform analyzer can follow them.

Also clears every warning in GenHub.Tests.Core, including five that reached development through #328 because I had only ever built the main project.
@bobtista

Copy link
Copy Markdown
Author

Addressed all five findings from the full review. Four were real; one I widened.

Validate only the launching game's root — real, and an over-reach I introduced. Validation checked both declared paths regardless of which game was launching, so a stale Zero Hour root could block a Generals launch that would have worked fine. Now takes the game type from the client and checks only that root, keeping the Windows guard.

Zero-exit fallback dropped the captured stderr — real. A process exiting immediately with code 0 and no spawned child returned a bare message while the stderr drained moments earlier went unreported. That was the only diagnostic available for the case, and contrary to the point of the capture work in this branch.

Duplicated fixture discovery — real, and in three classes rather than two: RetailArchiveRootTests had it too. Extracted to NativeClientFixture in its own file, so they cannot diverge on when to skip. Divergence there would be quiet and nasty: some would skip while others failed.

.so missing from engine-only staging — real. The filter matched the binary and .dylib only, so the staged workspace was incomplete on Linux.

StyleCop ordering in the validation tests — real, and it led somewhere worse. Fixing it surfaced that GenHub.Tests.Core had warnings I had never seen, because every "0 warnings" check this branch reported was run against GenHub.csproj alone. Five of them had already reached development via #328. All are now cleared, including those five, along with the platform-analyzer warnings on SetUnixFileMode.

Verification: GenHub 0 warnings, GenHub.Tests.Core 0 warnings, 1420 unit tests pass, macOS composition 3/3. Ran with the real-client launch tests excluded — they launch the actual game on the developer's machine, so they are only run deliberately.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 30, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 30, 2026
… record the gap

The previous comment claimed a Zero Hour installation may carry the base archives itself. That was asserted, not verified, and is not the reason. The actual reason is that the engine mounts archives from the working directory as well, so base content may sit in the workspace rather than a retail root — the arrangement this mechanism replaces, but still valid.

Records the residual gap review identified: with no Generals root declared and no base content in the workspace either, Zero Hour starts with nothing to mount and this check cannot tell. Archive filenames are arbitrary, so testing for '*.big' proves nothing about base content; a workspace check was considered and rejected because a Zero Hour workspace always contains archives and it would always pass. Closing it needs the engine to report a failed mount.
@bobtista

Copy link
Copy Markdown
Author

On the "Generals archives escape validation" finding — you were right and my first fix was incomplete. You listed three conditions and I only handled two.

Fixed in aeaa1c0: a declared Generals root that is stale, and one that holds no archives, now both fail a Zero Hour launch. Generals launches still ignore the Zero Hour root, since they do not read it.

Not fixed, and it cannot be from here: the absent case. An absent Generals root is not by itself wrong — the engine mounts archives from the working directory as well, so base content may legitimately sit in the profile workspace rather than a retail root. That is the arrangement the install-path environment replaces, and it remains valid, so failing whenever no root is declared would break it.

Telling the two apart needs to know whether base content is actually reachable, and that is not testable from filenames. A real install holds arbitrary archives beside the retail ones:

!!!HotkeyIDMarkersZHEnglish_v2.big
310_ExpandedLANLobbyMenu.big
340_ControlBarPro720ZH.big

So *.big presence proves nothing about base content specifically. I considered a workspace *.big check and rejected it: a Zero Hour workspace always contains archives, so it would always pass — worse than no check, because it reads as a guard while guaranteeing nothing.

Closing it properly needs the engine to report a failed mount, which is the same signal #315 already notes it needs for the archive-exists-but-fails-to-mount case.

Actions taken rather than leaving it implicit:

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientManifestLaunchTests.cs (1)

57-59: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include and assert Linux shared libraries in this fixture.

The helper only captures .dylib files, so Linux .so dependencies never enter the generated manifest; the current assertion can still pass with only generalszh. Include .so files and assert that at least one dynamic library was captured, otherwise Linux packaging/resolution regressions remain untested.

Proposed fix
+ Assert.Contains(
+     manifest.Variants.Single().Files,
+     file => file.RelativePath.EndsWith(".dylib", StringComparison.OrdinalIgnoreCase)
+         || file.RelativePath.EndsWith(".so", StringComparison.OrdinalIgnoreCase));
+
- if (name != NativeClientFixture.BinaryName && !name.EndsWith(".dylib", StringComparison.OrdinalIgnoreCase))
+ if (name != NativeClientFixture.BinaryName
+     && !name.EndsWith(".dylib", StringComparison.OrdinalIgnoreCase)
+     && !name.EndsWith(".so", StringComparison.OrdinalIgnoreCase))

Based on the PR objective that Linux staging includes .so files.

Also applies to: 124-124

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientManifestLaunchTests.cs`
around lines 57 - 59, Update the fixture’s native-library discovery helper to
include Linux .so files alongside .dylib files, then extend the manifest
assertions in the NativeClientManifestLaunchTests fixture to require at least
one captured dynamic library. Keep the existing executable assertion and verify
the generated manifest contains a matching .so or dynamic-library entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientFixture.cs`:
- Around line 33-37: Update the EnvironmentOverride handling in
NativeClientFixture to return the configured directory only when it contains the
expected native client binary, matching the existing fallback validation;
otherwise return null so optional integration tests are skipped for invalid
overrides.

In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs`:
- Around line 166-171: Update the Linux shared-library condition in the archive
path filter to accept both unversioned `.so` files and versioned artifacts such
as `.so.0` or `.so.0.1.0`, while preserving the existing exact binary-name and
macOS `.dylib` matches. Use the existing name-based filter around
NativeClientFixture.BinaryName so StageEngineOnly stages all Linux
shared-library variants.

---

Outside diff comments:
In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientManifestLaunchTests.cs`:
- Around line 57-59: Update the fixture’s native-library discovery helper to
include Linux .so files alongside .dylib files, then extend the manifest
assertions in the NativeClientManifestLaunchTests fixture to require at least
one captured dynamic library. Keep the existing executable assertion and verify
the generated manifest contains a matching .so or dynamic-library entry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 03858a95-fd34-48a2-9937-f7aeb31e116e

📥 Commits

Reviewing files that changed from the base of the PR and between 5ac6b3a and 7e6f8ce.

📒 Files selected for processing (11)
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientFixture.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientLaunchCollection.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientLaunchIntegrationTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientManifestLaunchTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeLaunchDiagnosticsTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ContentManifestPoolTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Workspace/WorkspaceValidatorTests.cs
  • GenHub/GenHub/Features/GameProfiles/Infrastructure/GameProcessManager.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs

…ot validation

The comments asserted the engine reaches its main loop with zero content and no error, and that no post-spawn check could tell. Verified against bobtista/GeneralsGameCode at bobtista/topic/trunk, that is false for the cases described: a root holding no archives aborts during initialisation with exit 1 and a ReleaseCrashInfo.txt, and an unmountable archive also writes '[ggc] ARCHIVE MOUNT FAILED' to stderr. Neither reaches the main loop.

The claim came from the plan and was repeated here without ever being checked against the engine, which is public.

Validating before spawn still earns its place, for reasons that are actually true: exit 1 is generic, the stderr sentinel exists only in StdBIGFileSystem and not the Win32 twin, and ReleaseCrash shows a system-modal dialog before _exit(1) so a host-launched child on Windows hangs instead of dying. The comments now say that, and bound what the .big sentinel can detect.
…d Linux libraries

Three review findings, all in the integration fixtures.

The GENHUB_NATIVE_CLIENT_DIR override only checked the directory existed, while the discovered default also required the engine binary. A directory without one is a misconfigured override and these tests should skip, not fail; both paths now validate the same way.

The '.so' suffix test missed versioned libraries such as libSDL3.so.0, which is the common shape of a shipped Linux library, and a second copy of the same filter in the manifest fixture had not been updated at all when '.so' was added earlier. Both now use a shared NativeClientFixture.IsDynamicLibrary.

The manifest fixture asserted only that something executable was present, so it would have passed with just the binary and never exercised library handling. It now requires a captured dynamic library.

The predicate is unit-tested because every test that uses it skips without a local engine install, so it would otherwise never run in CI. Writing those tests caught a real bug: matching on '.so' found the coincidental one inside '.sound' and stopped, so 'mylib.sound.so.1' was rejected. It matches on '.so.' now, and that case is pinned.
@bobtista

Copy link
Copy Markdown
Author

All three addressed in 69460ea. All were real, all in the integration fixtures.

Fixture override not validated like the default. GENHUB_NATIVE_CLIENT_DIR only checked the directory existed, while the discovered default also required the engine binary. A directory without one is a misconfigured override, so these tests should skip rather than fail — both paths now validate identically. I introduced this asymmetry when extracting the shared helper: the original had it, and I carried it across without noticing.

Versioned Linux libraries missed. .so as a suffix test misses libSDL3.so.0, which is the common shape of a shipped Linux library. Worth noting the second half of this: a copy of the same filter in NativeClientManifestLaunchTests had never been updated when .so was added earlier, so my previous ".so fixed" was only half done. Both now route through a shared NativeClientFixture.IsDynamicLibrary.

Weak manifest assertion. It required only something executable, so it would have passed with just the binary and never exercised library handling. It now requires a captured dynamic library.

I unit-tested the predicate, because every test that consumes it skips without a local engine install and it would otherwise never execute in CI. That paid for itself immediately — the first implementation matched on ".so", which found the coincidental occurrence inside ".sound" and stopped searching, so mylib.sound.so.1 was rejected. It matches on ".so." now and that case is pinned as a test.

GenHub 0 warnings, GenHub.Tests.Core 0 warnings, 1435 unit tests pass.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 30, 2026

@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 platform limitations.

⚠️ Outside diff range comments (3)
GenHub/GenHub/Features/Launching/GameLauncher.cs (2)

1201-1204: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Unregister the placeholder launch before returning validation failure.

This path returns after the placeholder was registered, leaving a stale active launch that can block profile deletion until another launch happens to clean it up.

Proposed fix
             if (archiveRootError is not null)
             {
                 logger.LogError("[GameLauncher] Retail archive root validation failed: {Error}", archiveRootError);
+                await launchRegistry.UnregisterLaunchAsync(launchId);
                 return LaunchOperationResult<GameLaunchInfo>.CreateFailure(archiveRootError, launchId, profile.Id);
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@GenHub/GenHub/Features/Launching/GameLauncher.cs` around lines 1201 - 1204,
Before returning the validation failure in the archiveRootError branch of
GameLauncher, unregister the placeholder launch that was registered earlier.
Ensure the cleanup occurs before CreateFailure is returned, while preserving the
existing error logging and failure result.

499-510: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove loader-path overrides copied from the profile.

profileEnvironment is cloned unchanged, so a profile containing DYLD_LIBRARY_PATH or LD_LIBRARY_PATH re-enables precisely the native library override this change removes.

Proposed fix
         if (OperatingSystem.IsWindows())
         {
             return environment;
         }

+        environment.Remove("DYLD_LIBRARY_PATH");
+        environment.Remove("LD_LIBRARY_PATH");
         AddRetailArchiveRoots(environment, installation);

As per PR objectives, native launches must remove DYLD_LIBRARY_PATH and LD_LIBRARY_PATH.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@GenHub/GenHub/Features/Launching/GameLauncher.cs` around lines 499 - 510,
Update the environment construction in the launcher method containing
AddRetailArchiveRoots so profileEnvironment cannot reintroduce DYLD_LIBRARY_PATH
or LD_LIBRARY_PATH: remove those keys from the cloned environment before
returning on Windows or adding retail archive roots. Preserve all other profile
variables and existing platform-specific behavior.
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs (1)

103-108: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear archive-root environment variables for the “without roots” launch.

UseShellExecute = false child processes inherit the parent environment, and StartProcessAsync only adds variables from configuration.EnvironmentVariables; it never removes inherited values. This test omits CNC_ZH_INSTALLPATH and CNC_GENERALS_INSTALLPATH, so a developer exporting a retail archive root can still make the engine-only launch reach retail archives. For this negative test, explicitly unset those env vars before Process.Start or start from a clean child environment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs`
around lines 103 - 108, Update the “without roots” launch setup in the test
around GameLaunchConfiguration to explicitly remove or unset CNC_ZH_INSTALLPATH
and CNC_GENERALS_INSTALLPATH in the child process environment before
Process.Start, ensuring inherited developer environment values cannot expose
retail archives while preserving the existing engine-only configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs`:
- Around line 103-108: Update the “without roots” launch setup in the test
around GameLaunchConfiguration to explicitly remove or unset CNC_ZH_INSTALLPATH
and CNC_GENERALS_INSTALLPATH in the child process environment before
Process.Start, ensuring inherited developer environment values cannot expose
retail archives while preserving the existing engine-only configuration.

In `@GenHub/GenHub/Features/Launching/GameLauncher.cs`:
- Around line 1201-1204: Before returning the validation failure in the
archiveRootError branch of GameLauncher, unregister the placeholder launch that
was registered earlier. Ensure the cleanup occurs before CreateFailure is
returned, while preserving the existing error logging and failure result.
- Around line 499-510: Update the environment construction in the launcher
method containing AddRetailArchiveRoots so profileEnvironment cannot reintroduce
DYLD_LIBRARY_PATH or LD_LIBRARY_PATH: remove those keys from the cloned
environment before returning on Windows or adding retail archive roots. Preserve
all other profile variables and existing platform-specific behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: eaf55bdb-a359-4c55-9fce-28e8acd8d6c0

📥 Commits

Reviewing files that changed from the base of the PR and between 7e6f8ce and 3eccb33.

📒 Files selected for processing (7)
  • GenHub/GenHub.Core/Constants/RetailArchiveConstants.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientFixture.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientFixtureTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeClientManifestLaunchTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 30, 2026

@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 platform limitations.

⚠️ Outside diff range comments (1)
GenHub/GenHub/Features/Launching/GameLauncher.cs (1)

1201-1206: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Unregister the placeholder before returning this preflight failure.

Line 1203 returns normally after the placeholder was registered at Line 315, so this path bypasses the cleanup catch and leaves a false active launch record. That can block profile deletion until another launch happens to clean it up.

Proposed fix
 if (archiveRootError is not null)
 {
     logger.LogError("[GameLauncher] Retail archive root validation failed: {Error}", archiveRootError);
+    await launchRegistry.UnregisterLaunchAsync(launchId);
     return LaunchOperationResult<GameLaunchInfo>.CreateFailure(archiveRootError, launchId, profile.Id);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@GenHub/GenHub/Features/Launching/GameLauncher.cs` around lines 1201 - 1206,
Before returning the preflight failure from the Retail archive root validation
block in GameLauncher, explicitly unregister the placeholder created during
launch setup. Ensure the cleanup uses the existing placeholder-unregistration
mechanism before CreateFailure returns, while leaving the validation and failure
result unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@GenHub/GenHub/Features/Launching/GameLauncher.cs`:
- Around line 1201-1206: Before returning the preflight failure from the Retail
archive root validation block in GameLauncher, explicitly unregister the
placeholder created during launch setup. Ensure the cleanup uses the existing
placeholder-unregistration mechanism before CreateFailure returns, while leaving
the validation and failure result unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c0e40181-a81e-42ce-89e3-dcd2bfb845c9

📥 Commits

Reviewing files that changed from the base of the PR and between 3eccb33 and d1adeeb.

📒 Files selected for processing (7)
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/NativeLaunchDiagnosticsTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/RetailArchiveRootTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/RetailArchiveRootValidationTests.cs
  • GenHub/GenHub/Features/GameClients/GameClientDetector.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs
  • docs/dev/constants.md

@bobtista

Copy link
Copy Markdown
Author

Recording decisions settled during the 2026-07-30 fork/upstream investigation, so review does not re-open them:

  • The CNC_* install-path dependency is accepted as-is. Upstream cannot produce a native Unix game binary (its game targets are Windows-only; the advertised "Linux" build cross-compiles the Windows exe for Wine), so no configuration exists where GenHub sets these variables and a real client ignores them — the only native Unix client is the fork, which reads them. On Windows GenHub never sets them and every build resolves installs from the registry, so the stock-retail baseline is unaffected.
  • No client-type detection or branching. One native target exists, and the Windows path already differs by construction.
  • Exact-name generalszh matching stays. RTS_BUILD_OUTPUT_SUFFIX is an empty-by-default CMake cache variable that no preset or CI job sets on either upstream or the fork, applied uniformly to all configs; a suffixed binary requires a manual -D override. The manifest EntryPoint remains the contract.
  • -Fix and -installation are not engine flags (nor GenHub CLI arguments). One is a content-filename fragment, the other a manifest-ID substring; neither reaches any command line, and both engines' flag tables lack them.
  • Trailing-separator normalisation stays for now, but the underlying concatenation is a fork-side engine bug (upstream's equivalent code is separator-safe and never instantiated), tracked for the engine repo; when fixed there, EnsureTrailingSeparator and its contract can be retired.

@undead2146 undead2146 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

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

Labels

Enhancement New feature or request Testing Topic related to (unit) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants