Skip to content

Alpha 5: native macOS host and native BGFX client — scope and tracking #327

Description

@bobtista

Proposal

Alpha 5 delivers first-class macOS support and launches the community's native BGFX Zero Hour client against the user's retail data — no Wine. It lands the four-phase feat/macos-hostfeat/native-launch stack plus the hardening items from its external review.

Notarization (#322) is not being pursued. It requires a Developer ID certificate,
which requires a paid Apple Developer Program membership, and that is not being
purchased. #354 fixes the half that needed no certificate: GenHub clears
com.apple.quarantine from the game executables it materializes, so the propagation
failure — GenHub opens, the game silently will not launch — no longer happens. What
remains is one documented step for the app bundle itself
(xattr -dr com.apple.quarantine /Applications/GenHub.app).

macOS is therefore installable with one manual step, not distributable in the
Gatekeeper sense. The signing pipeline is built and verified under an ad-hoc identity
on feat/macos-signing-notarization, unmerged, ready if that decision changes.

Scope

Definition of done: the three PRs land and #354 lands — at that point macOS is
installable with the one documented step. Notarization is explicitly out of scope; see
#322. Priority order; "PR" is which stack PR
closes it (see The mac stack below).

# Issue Closed by
1 #314 — Reject multi-variant manifests at ingestion PR A
2 #315 — Validate retail archive roots before launch PR C
3 #316 — Materialize workspace executables atomically PR A
4 #317 — Remove DYLD_LIBRARY_PATH / LD_LIBRARY_PATH PR C
5 #318 — Fix stderr capture EOF race PR C
6 #319 — macOS detector must distinguish TCC denial from "none found" PR B
7 #320ValidateScopes shrink-only ratchet own PR
8 #322 — signing and notarization dropped — needs a Developer ID; #354 covers the usable-unsigned path
9 #323 — Launch receipt + cheap revalidation own PR
10 #324 — Engine-only launch smoke on macos-15 CI own PR
11 #325 — Magic-byte executable classification own PR
12 #330 — Detect installations by archive presence, not executable name own PR
13 #333 — Zero Hour can launch with no base Generals content blocked on engine
14 #334 — Post-spawn archive-failure detection: 500 ms window, unused [ggc] sentinel own PR

Current status — updated 2026-08-03

The three stack PRs have all landed. PR A merged as #328, PR B as #329 (plus
#331 for the service registrations it needed), and PR C as #332. The remaining
definition-of-done item is #322 notarization.

State Issues
Closed #314, #315, #316, #317, #318, #319, #320, #325
PR open #323 (#341), #334 (#340)
PR pending #324, #330 — branches rebased onto development, PRs to follow
Not started #322 — no branch; the blocker for public macOS distribution
Blocked #333 — needs an engine-side signal, see below

#315, #317 and #318 shipped inside #332 exactly as the note below predicts, and
#316, #320 and #325 shipped as #338, #337 and #339. None auto-closed, so all six
were closed manually on 2026-08-03 with the merged PR and file evidence recorded on
each issue.

#333 is the one item no amount of GenHub work can close. The engine skips the
base-Generals load silently in release builds — DEBUG_ASSERTCRASH is debug-only,
and the in-tree @todo says it ought to be a release crash. GenHub cannot detect
the case from the filesystem: a Zero Hour workspace always contains *.big, so a
workspace archive check would always pass and guarantee nothing. It needs either the
engine to make the empty-installPath case observable, or a mount-count signal
GenHub can act on. Until one exists, #333 stays open regardless of milestone
progress.

#314#319 are review findings on code that has not shipped. Their fixes are
folded into the owning branch before the PR opens, so each will close without a
visible "fixes #NNN" diff — the code reads as originally written. Each issue
carries a comment saying which PR closes it. They are kept as issues rather than PR
checklist items because they describe user-visible behaviour worth finding later.

Moved out of this milestone:

Dependencies worth knowing

The mac stack

Restructured from four sequential PRs into two independent PRs plus one dependent
one
, and rebased onto development @ 8e07339. All three have since merged.

PR Branch Commits Base Merged as
A feat/cross-platform-fixes 7 development #328
B feat/macos-host 4 development #329, plus #331
C feat/native-launch 16 (B + A + 4 + wiring) A and B #332

A and B were reviewed in parallel as intended, and C followed them.

Why this beats the original four-deep chain

The original plan chained macos-hostplatform-neutral-model
unix-filesystemnative-launch. Measured against this repo's rules, that chain
is expensive: squash-merging a parent orphans its child (it keeps the parent's
unsquashed commits and goes CONFLICTING, needing a manual retarget and rebase),
and dismiss_stale_reviews_on_push: true means every such rebase drops the
approvals. A four-deep stack pays that three times. We hit exactly this with
#305/#309 during the Alpha 4 blockers.

The two middle phases fix real Linux and Windows bugs — silent hard-link copy,
Windows paths on Linux, updater installing the wrong platform, symlink reachability
— and are not macOS work. Checking the actual coupling: of 43 files they touch,
only two tie them to the macOS layer, and the types they introduce
(MacOSGamePathProvider, UnixFileOperationsService, UnixSymlinkCapabilityProvider)
all live in the shared project, not GenHub.MacOS. The only genuinely
macOS-dependent parts are three DI registration lines plus two entries in a shared
test-assertion helper that the macOS branch creates.

Those five lines moved into a single wiring commit at the head of C, which is the
first point where both the types and the module exist. That makes A fully
standalone: 7 commits, zero GenHub.MacOS files, 1384 tests green directly on
development
. C's tree is byte-identical to the original four-deep stack, so
nothing was lost or gained — 1395 tests green.

The practical win: the cross-platform bug fixes stop being hostage to macOS review,
and two of the three PRs can be reviewed at the same time.

Landing notes

  1. The stack must be rebased once more after chore: alpha 4 release #229 merges. main requires
    linear history, so chore: alpha 4 release #229 lands as a squash or rebase — both mint new SHAs, and
    8e07339 will not be an ancestor of main. The rebase already done resolved
    both collisions against current code, so the second pass should be mechanical.

    Two collisions were found and are already resolved in the branches:

  2. Fold the review fixes into the owning branch before opening PRs
    (Reject multi-variant manifests at ingestion until .Files consumers are migrated #314macOS installation detector must distinguish TCC denial from no installations found #319 map to PRs as noted in the priority table above).

  3. Open A and B together; open C after both land.

Say in A's description that it fixes Linux and Windows bugs and stands on its own
merits — it is not macOS-only work.

Engine-repo track

Tracked separately in the engine repository (bobtista/GeneralsGameCode, topic/trunk, upstream TheSuperHackers):

  • CNC_ZH_USERDATAPATH for per-profile Options.ini
  • Normalize install-path roots in GlobalData.cpp to always append the separator
  • Machine-readable failure (exit code or sentinel log line) when required archives fail to mount

The third is the engine-side half of #315 and would simplify every GenHub-side mitigation.

Status

Alpha 5 opens once Alpha 4 ships. Alpha 4 readiness is tracked in #307.

The stack, as it actually landed

PR Scope State
#328 A — cross-platform paths, manifests, Unix workspaces merged
#329 B — macOS host, installation detection, CI merged
#331 macOS service registrations merged
#332 C — native BGFX launch against retail archives open, approved

#331 was not planned. #328 made IGamePathProvider a required dependency and #329
added the macOS DI module, but the macOS registrations reference types from the first and a
module from the second, so they could only compile once both were in. Each PR was green in
isolation and the merged state was not: GenHub.app aborted at container build and
development went red. #331 repaired it and now runs GenHub.Tests.MacOS before publish,
where the assertion had existed but sat after the smoke test and never executed.

The lesson for the remaining independent PRs: two green branches can merge into a broken
state, and no per-branch CI can see it.

Why #314, #316 and #319 are still open

Their fixes are merged — #328 carries #314 and #316, #329 carries #319 — and the Fixes
links are recorded. GitHub only auto-closes on merge to the default branch, and these
merged into development, so they will close when development reaches main at release.
They are done, not forgotten.

#332 will close #315, #317 and #318 on the same terms.

Scope added after this issue was written

A premise correction worth recording. #315, #324 and this issue were written on the
belief that the engine reaches its main loop with zero content and reports nothing. Verified
against bobtista/GeneralsGameCode at bobtista/topic/trunk, that is false — a bad root
aborts during initialisation with exit 1 and a ReleaseCrashInfo.txt, and an unmountable
archive also writes to stderr. Neither reaches the main loop. #324's rationale should be
re-read in that light: it is still worth doing, but as an assertion on exit code and
sentinel rather than on the process merely staying alive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions