Skip to content

Convert X Rebirth to the Game Description Language#23536

Open
halgari wants to merge 12 commits into
masterfrom
halgari/games-to-gdl-1
Open

Convert X Rebirth to the Game Description Language#23536
halgari wants to merge 12 commits into
masterfrom
halgari/games-to-gdl-1

Conversation

@halgari

@halgari halgari commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

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/, tracking main).

X Rebirth is now defined by a game.yaml plus src/hooks.ts (content.xml installer and the three health checks), built via gdl build into dist/index.js. This is the first GDL conversion; more games will follow.

Also removes the old game-extension-test harness, 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).

halgari added 11 commits June 16, 2026 10:33
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.
@halgari halgari marked this pull request as ready for review June 17, 2026 19:34
@halgari halgari requested a review from a team as a code owner June 17, 2026 19:34

@erri120 erri120 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.

We moved away from using submodules for our custom libraries, GDL should be published to NPM as @nexusmods/game-description-language or @nexusmods/gdl.

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.

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"]
}

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants