Convert X Rebirth to the Game Description Language#23536
Open
halgari wants to merge 12 commits into
Open
Conversation
Replace the hand-written X Rebirth game extension with a declarative game.yaml compiled by the GDL toolchain (added as the game-description-language submodule). The extension is now a game.yaml, game art, and a small src/hooks.ts holding the two imperative pieces GDL can't express declaratively: the content.xml installer (parses XML and emits attribute instructions) and the three in-game health checks. This is a 1:1 behavioral replacement. The game-extension-test harness produces identical results against every X Rebirth mod on Nexus (956 passed, 15 skipped) for both the original and converted extension. Also teaches @vortex/game-extension-test to load GDL extensions (detects game.yaml, loads the generated entry through the harness module aliases, and captures health checks registered via registerHealthCheck), so future GDL game conversions are covered by the same regression harness. The GDL submodule tracks feat/vortex-xrebirth-integration, which adds the features this conversion needs (declarative installer predicates, a custom install hook, runtime diagnostics, stop patterns, and Steam app id derivation).
- Make GDL extensions build in a fresh clone / CI. The build now goes through scripts/build-gdl-extension.mjs, which installs and builds the self-contained game-description-language submodule (using its own pinned deps) on first build, since Vortex's install does not reach into it. The previous build.mjs assumed the submodule was already installed and built. - Fix a latent crash: the content.xml installer's lookup was case-sensitive while the `hasFile: "**/content.xml"` predicate that selects it is case-insensitive, so an archive with e.g. Content.XML would pass testSupported and then throw in install. Match case-insensitively, in line with the mod-shape health check. - Make the game-extensions test target depend on build (it loads the generated .gdl-out entry) and include game.yaml in its inputs. Regression unchanged: 956 passed, 15 skipped.
Only vitest.fixtures.config.ts (used by the cli.ts fixture runner) loads GDL extensions and needs the @gdl/runtime alias. The plain vitest.config.ts never resolves that specifier, so the alias was dead.
X Rebirth was the only game that used the harness, and it is now a GDL extension. With the move to GDL, game testing is owned by the GDL toolchain (inline tests and corpus), so the standalone harness is no longer needed. Removes packages/game-extension-test, the X Rebirth opt-in flag and test-descriptor.ts, and the test:game-extensions target. The unit tests in src/hooks.test.ts still cover the content.xml installer and health checks via @vortex/extension-test-mocks, which is kept.
Add `tests: { corpus: nexus }` so `gdl test:corpus --fetch` runs the
declarative installers against real X Rebirth mod manifests from Nexus.
Gitignore the fetched tests/cache.
Note: GDL's corpus runner currently can't execute the content.xml install
hook (it builds plans statically), so content.xml mods route to the dropin
installer in the corpus rather than content-xml, and the Nexus discovery
query is not paginated (samples the first ~20 mods). The corpus is a partial
smoke test of declarative routing until those gaps are addressed in GDL.
Bump game-description-language to pick up corpus pagination, hook attribution, and execution mode (hooks + health checks run against the full Nexus catalog). Add tests.syntheticContent for content.xml so the corpus can drive the content.xml install hook. A full `gdl test:corpus --fetch` now covers 302 mods (was 20): 299 matched with correct installer attribution (259 content-xml via the hook), 3 non-mod uploads unmatched, 0 health-check failures.
Convert the drop-in stopPatterns from regex to globs (GDL converts them to case-insensitive regex at build time), which read more clearly and match the GDL idiom. Drop the redundant lang.dat pattern (subsumed by **/*.dat). Verified behavior-preserving: the full corpus run is unchanged at 299 matched / 3 unmatched / 0 failed (302 mods), same installer attribution, 0 health-check failures.
The mod-shape health check no longer carries its own copy of the drop-in patterns. It reads them from the registered game's details.stopPatterns (which GDL populates from game.yaml's stopPatterns block), so game.yaml is the single source feeding details.stopPatterns, the drop-in installer predicate, and the health check. - hooks.ts: drop the hardcoded XREBIRTH_STOP_PATTERNS; resolve via util.getGame. - extension-test-mocks: add util.getGame + setMockGame so unit tests can supply the game's stop patterns. - bump GDL submodule (mock getGame support for corpus execution mode). Verified: extension unit tests pass, and the full corpus run is unchanged (299 matched / 3 unmatched / 0 failed, 0 health-check failures).
The drop-in installer now matches on inline globs (when/any) instead of the shared stopPatterns block, and the mod-shape health check recognises drop-ins by their xrebirth-dropin modType rather than re-deriving from patterns. That removes the indirection (one block feeding three consumers) for a game with a single real consumer. - game.yaml: inline the drop-in globs; remove the stopPatterns block. - hooks.ts: recognise drop-ins by modType; drop the getGame/stopPatterns lookup. - extension-test-mocks: revert the getGame/setMockGame test helper (unused). - bump GDL submodule (stopPatterns feature removed). Verified: corpus unchanged at 299 matched / 3 unmatched / 0 failed, 0 health failures; extension unit tests pass.
erri120
requested changes
Jun 18, 2026
erri120
left a comment
Member
There was a problem hiding this comment.
We moved away from using submodules for our custom libraries, GDL should be published to NPM as @nexusmods/game-description-language or @nexusmods/gdl.
Member
There was a problem hiding this comment.
Need to update the build nx target config. I recommend adding a new default target to the root nx.json file:
"gdl": [
"{workspaceRoot}/scripts/build-gdl-extension.mjs",
"{projectRoot}/game.yaml"
]And adding gdl to the build inputs:
"build": {
"inputs": ["default", "typescript", "vortex-api", "gdl"]
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Switches the X Rebirth extension over to the Game Description Language (GDL) and brings GDL into the repo as a git submodule (
game-description-language/, trackingmain).X Rebirth is now defined by a
game.yamlplussrc/hooks.ts(content.xml installer and the three health checks), built viagdl buildintodist/index.js. This is the first GDL conversion; more games will follow.Also removes the old
game-extension-testharness, which only X Rebirth used. Its role is now covered by GDL's corpus runner (gdl test:corpus), which loads the built bundle and exercises the real installer and health-check paths against cached Nexus manifests.Verification: GDL suite green, X Rebirth unit tests pass, and the corpus run is 299 matched / 3 unmatched / 0 failed (the unmatched are non-mod uploads).