Skip to content

Compatibility

Pixnop edited this page Jul 16, 2026 · 4 revisions

Compatibility

Verdict table

Empirical results from the version compatibility sweep (.github/workflows/compat.yml), which builds Atlas against each game version's own dlls and runs the full E2E suite on a real embedded server:

Version Status Notes
1.22.3 Compatible All E2E scenarios green, tested on every push
1.22.2 Compatible All E2E scenarios green, tested on every push
1.22.1 Compatible All E2E scenarios green, tested on every push
1.22.0 Compatible All E2E scenarios green, tested on every push
1.21.7 Compatible All E2E scenarios green, tested on every push (the CI-verified 1.21 version)
1.20.12 Best effort Full E2E suite green when rebuilt against its dlls; verified on the weekly sweep, not per push
1.19.8 Not supported Rejected at boot: ServerMain.PreLaunch and the ServerProgramArgs boot signature differ beyond what the shim bridges
1.18.15 Not supported Rejected at boot: same as above, plus Vintagestory.API.Common.Func collides with System.Func

Each row is the latest patch of its minor available on the stable CDN; 1.18.0 through 1.18.7 predate the game's .NET migration and ship under a different server archive entirely. The supported floor is Vintage Story 1.21.0 since Atlas 0.9.0 (issue #15): the entire compile-level gap below 1.22 was the server exit lifecycle, now owned by the runtime shim EngineCompat (exit-state field, Stop signature, network version read from the loaded engine's metadata), boot-validated fail-fast on engines whose layout drifted.

Below the floor

For 1.19.x and older, the boot API changes shape beyond what reflection can reasonably bridge (ServerMain.PreLaunch, the ServerProgramArgs signature), so Atlas rejects those versions up front with an AtlasSetupException citing the floor. Running a prebuilt Atlas binary on an engine other than the one it was built against used to carry a caveat here ("designed for but no CI lane yet"); since 0.10.0 that path is auto-staged at launch and proven by a per-push CI lane. See Multi-install runs below.

Multi-install runs: no rebuild needed

Repointing VINTAGE_STORY at a different install (another game version, or a server fork like Stratum) does not require rebuilding the test project. Since 0.10.0 (issue #49) Atlas auto-stages the engine assembly at launch, so a suite built once runs against whichever install the variable points at.

The trap it removes. The test output carries its own VintagestoryAPI.dll copy, and that copy wins assembly probing, so it mixes with the target install's VintagestoryLib: on a same-version fork the boot dies with a cryptic MissingFieldException, and across versions the game thread's own JIT loads VintagestoryLib against the stale copy and kills the whole testhost with a raw TypeLoadException before a single Atlas frame runs. In-process redirection cannot fix it (the default binder prefers the app-path copy for a colliding simple name), so Atlas rewrites the copy itself.

How auto-staging works. A module initializer runs before anything can bind the copy (Atlas.XUnit's fires at xUnit discovery, Atlas's own covers direct ServerHost flows) and rewrites the test-output VintagestoryAPI.dll and its .pdb as a unit from the target install, through same-directory temp files and atomic renames, with a one-line stderr notice. When staging is genuinely impossible (the stale copy already bound because engine types were JITted before any Atlas code ran, a read-only test output, or an install shipping no VintagestoryAPI.pdb), the boot fails fast with an AtlasSetupException naming both file identities and the remedies; an already-bound copy is still re-staged on disk, so a plain re-run recovers without a rebuild. When the copies are already identical (the documented rebuild flow) staging is a no-op and behavior is byte-for-byte unchanged.

The supported pattern: build newest, run older. Direction matters, because of the second file the game provides. The test output's Newtonsoft.Json.dll is the BUILD-time install's game build (13.0.4 on 1.22.x, 13.0.3 on 1.21.x/1.20.x, same 13.0.0.0 assembly identity), and the VSTest host binds it at process start for its own protocol, before any staging trigger could run. So:

  • Build against the newest engine you target and run everywhere older. The newer Newtonsoft build is a superset for the older engine, and the whole matrix runs green: measured, the full engine E2E suite built once on 1.22.3 runs 105/105 on 1.21.7 AND 1.20.12, plus the samples, without a rebuild.
  • The reverse is refused up front. A floor-built output on a newer engine dies because the newer engine binds Newtonsoft members its own build added (JToken.WriteTo(JsonWriter) on 1.22.3 killed every boot in measurement), so the boot preflight fails fast with both file versions and the remedies named, and still re-stages the copy on disk so a plain re-run (or a rebuild) recovers. This is the honest limitation: auto-staging carries you down the version range from one build, not up it.

The per-push prebuilt-cross-install CI lane proves the supported path from one build: the samples built once against 1.22.3 run unmodified (--no-build) on 1.21.7, again on 1.21.7 (idempotence), and back on 1.22.3, with byte-identity asserts on the staged copy. This is what closes the old "no CI lane yet" caveat, and it removes the per-install rebuild that a single-VINTAGE_STORY differential CI used to pay (StratumParity's vanilla-plus-fork CI had doubled its time on it).

The one-shot pattern: atlas stage before a --no-build run. Auto-staging's fail-then-rerun still needs a rerun: on the first run in a process after repointing VINTAGE_STORY, the module initializer re-stages the copy on disk correctly, but if engine types were already JITted before any Atlas code ran, THAT run still fails; only the rerun goes green. A script that runs each install exactly once (a one-shot cross-install script, StratumParity's run-parity.sh case) cannot absorb that cycle. Since 0.11.0, atlas stage <dir-or-dll> (see CLI) runs the identical staging decision explicitly, as its own process, ahead of time:

atlas stage out/ && VINTAGE_STORY=... dotnet test out/ --no-build

By the time dotnet test starts, the copy is already staged, so its module initializer finds a no-op and nothing ever fails: this removes the fail-then-rerun on the first switch to a new install, with no rebuild either.

One boundary still holds: auto-staging swaps the engine assembly, it does not invent engine surface. A scenario body that calls an API member only the newer engine has still fails on the older one with a MissingMethodException, and enum values or field/property shapes that shifted across versions must go through EngineCompat (see Writing Scenarios for the cross-version authoring rules).

How the weekly sweep works

.github/workflows/compat.yml runs on a schedule (cron: "0 6 * * 1", every Monday) across a matrix of Vintage Story versions. For each version:

  1. Download and cache that version's Linux server archive from the Vintage Story CDN.
  2. dotnet build Atlas.slnx -c Release against that version's dlls (continue-on-error: true, so a failure here does not abort the workflow, only that version's leg).
  3. If the build succeeded, run Atlas.Engine.Tests and Sample.Scenarios, both filtered to Category=E2E (also continue-on-error: true).
  4. Compute a verdict per version: compatible (build and both E2E suites passed), incompatible (API) (build failed), or partial (build succeeded but at least one E2E suite failed), with a short note naming which suite failed for partial.
  5. An aggregate job collects every version's verdict artifact and renders a consolidated table into the workflow's step summary.

Running it manually

Trigger a sweep on demand from the Actions tab (workflow_dispatch), or from the CLI:

gh workflow run compat.yml --repo Pixnop/Atlas

workflow_dispatch accepts an optional versions input (space- or comma-separated) to sweep a custom set of versions instead of the default matrix (1.18.15, 1.19.8, 1.20.12, 1.21.7, 1.22.0, 1.22.1, 1.22.2, 1.22.3):

gh workflow run compat.yml --repo Pixnop/Atlas -f versions="1.22.0,1.22.3"

Clone this wiki locally