v0.10.0
Added
-
atlas diff baseline.trx candidate.trx: first-class differential comparison of two TRX
runs (issue #88, 0.10.0 roadmap, from the StratumParity field pattern: the same suite runs
against vanilla and against a fork, and the outcome comparison was hand-rolled scripts
until now). Comparison is keyed by test name exactly as the TRX reports it (theory rows
carry their arguments in the name, so every row diffs on its own; duplicate names, one per
rerun attempt, merge worst-outcome-first) and buckets every change into new failures
(failed in candidate; passed, skipped or absent in baseline), fixed (failed to passed),
vanished (present to absent), new tests, still failing, and notable duration shifts
between two passing runs (conservative on purpose: at least 2x AND at least 500 ms apart,
both directions reported, informational only). The console report is a summary line plus
compact per-category listings (empty categories print nothing);--jsonreplaces it with
a stable machine shape versioned like the worker protocol (v: 1first, additive
evolution, category keys always present). Exit codes gate differential CI directly: 0 no
regressions, 1 at least one regression (a new failure or a vanished test, nothing else), 2
usage or unreadable input. Works on the TRX Atlas writes (atlas run --parallel --trx)
and tolerates any spec-conforming TRX including plaindotnet test --logger trx; no
server, assembly orVINTAGE_STORYinvolved. Full contract in
docs/specs/2026-07-14-diff-command.md. -
IWorldSession.EntitySimulationTicks, a monotonic counter of the embedded server's
real entity-simulation ticks, so entity-tick-frequency probes can assert exact counts
instead of ratios (issue #79, from the StratumParity field report: a counting
EntityBehavior on spawned straw dummies observed non-constant ratios between
World.Ticks(n)and actual entity ticks, about half on some runs and 100 percent on
others, forcing the suite onto ratio assertions). The investigation (decompiles of
1.20.12/1.21.7/1.22.3 plus the Stratum patches, and instrumented live runs on both
flavors, written up in docs/specs/2026-07-14-tick-contract.md) found the variance was
never pump pacing: one Atlas tick is one fire of a 1ms game-tick listener, at most one
perServerMain.Process()pass, and at the engine's default 33.33ms pacing, passes,
Atlas ticks and entity-simulation ticks (a separate 20ms-stride server system) all run
1:1 on both vanilla and Stratum. The half-counts were Stratum's distance-band entity
throttle keyed on the engine's randomized new-player spawn (spawnRadius, 50 blocks on
default playstyles): a probe anchored to world spawn lands at a random 0-55 blocks from
the anchor player and straddles the fork's 32-block near/mid band boundary run by run
(measured: 25.5 blocks -> 150/150 ticks, 50.8 blocks -> 75/150). The new counter reads
the engine's own record of the entity-simulation system's last tick (the public
millisecondsSinceStartstamp of theServerSystemEntitySimulationentry in the
internalServerMain.Systemsarray, symbols verified identical on 1.20.12, 1.21.7 and
1.22.3 and untouched by Stratum), sampled by the pump once per pass, which observes
every fire exactly once (systems tick at most once per pass and every fire strictly
advances the stamp). Reads run on the game thread in the same turn as probe reads, so
Assert.Equal(counterDelta, probeTicks)is exact for an unthrottled entity on every
supported engine, and it holds on Stratum too once the probe is anchored to
player.Entity.Posinstead of world spawn (both proven in E2E on vanilla and the
fork). On an engine whose tick machinery drifted the counter degrades at boot behind a
one-time warning and only reading the property throws, with the drifted symbols named.
The spec also pins, for the first time, whatawait World.Ticks(n)guarantees and does
not;Ticks(n)semantics are deliberately unchanged. -
Engine-assembly auto-staging at launch (issue #49, option 2): a prebuilt test assembly now
runs against whichever installVINTAGE_STORYpoints at, without a rebuild. The 0.8.0
preflight fail-fasted on a diverged test-outputVintagestoryAPI.dll, and cross-VERSION
mixes never even reached it: the game thread's own JIT loadsVintagestoryLibagainst the
stale copy and kills the whole testhost with a rawTypeLoadException(measured: a
1.22.3-built output pointed at 1.21.7 dies onServerMain.PlaySoundAtwith no Atlas frame
executed). In-process redirection is measurably impossible, not just late:
AssemblyLoadContext.Default.LoadFromAssemblyPath(installCopy)defers to the default
binder, which prefers the app-path copy for a colliding simple name (measured on .NET 10:
it returns the test-output assembly). So Atlas now rewrites the test-output copy itself,
dll AND pdb as a unit, through same-directory temp files and atomic renames, from module
initializers that run before anything can bind it: Atlas.XUnit's fires at xUnit DISCOVERY
(instantiating the[AtlasScenario]discoverer, measured to precede the first engine-type
JIT), Atlas's own covers directServerHostflows, and both stage the assembly's own
directory too, which is the scenario directory underatlas run(where the app base is
the CLI's own bin, so the CLI needed no change). The boot preflight moved from the game
thread toStartAsync, on a thread that can still surface an error before that JIT, and
now verifies instead of failing: staging already happened, was a no-op (identical copies,
the documented rebuild flow, byte-for-byte unchanged behavior), or the boot throws an
AtlasSetupExceptionnaming both file identities and the remedies for the genuinely
impossible cases: a diverged copy already bound because engine types were JITted before
any Atlas code ran (the output is still re-staged so a plain re-run recovers), an
unwritable test output, an install shipping noVintagestoryAPI.pdb. Field motivation
from the issue #49 thread: StratumParity's differential CI (vanilla and the Stratum fork
on every push) rebuilt the test assembly once per install under the single-VINTAGE_STORY
constraint, doubling its CI time; auto-staging removes the per-install rebuild. The staging
set covers the second game-provided file too, direction-aware: the output's
Newtonsoft.Json.dll(the BUILD-time install's game build) is left alone when it is the
same-or-newer build than the target's (the measured-green mix: the newer build is a
superset for the older engine), and an OLDER build, which the newer engine cannot run (it
binds members its own build added:JToken.WriteTo(JsonWriter)on 1.22.3 killed every boot
in measurement, 90/105 scenarios plus all samples), is refused up front with both file
versions and the remedies named, plus the same on-disk re-stage, because the VSTest host
binds that assembly at process start, before any trigger could ever swap it in-process. So
the supported prebuilt pattern is: build against the newest engine you target, run
everywhere older; the full engine E2E suite built once on 1.22.3 runs 105/105 on 1.21.7 AND
1.20.12 without a rebuild, and the per-pushprebuilt-cross-installCI lane proves the
samples crossing 1.22.3 -> 1.21.7 -> 1.21.7 (idempotence) -> 1.22.3 from one build, with
byte-identity asserts on the staged copy. The pure decision core (EngineStaging) and the
thin IO/reflection shell (EngineStager) replaceVsInstall.VerifyApiCopyMatchesInstall;
ApiCopySynckeeps the shared identity/compare primitives.
Fixed
-
EngineCompatnow resolvesEnumClientState.Playingfrom the loaded engine by name instead
of comparing joined clients against the compiled-in value: 1.22 insertedAdmittedinto the
enum, shiftingPlayingfrom 3 (1.20.x/1.21.x) to 4, and the C# compiler bakes enum values
into the referencing assembly's IL exactly like theGameVersionconsts the shim already
reads from metadata at run time. A prebuilt Atlas therefore misread the join lifecycle on the
other engine line: on the first issue #49 cross-install engine run (1.22.3-built suite on
1.21.7), all 33 join-dependent scenarios timed out inWaitForPlaying, comparing that
engine'sQueuedagainst Playing, while every join-free scenario passed (72/105). The value
is boot-validated like every shim member (a fork that renames the state fails fast with the
symbol and game version named), and the compat source rule gains its corollary: engine enum
members whose positions shift across supported versions are compile-time constants too, and
must go throughEngineCompat. Same-version runs are byte-for-byte unaffected. -
WorldSession.SpawnEntitynow reachesEntity.Pos/ServerPosthroughEngineCompat
instead of direct member access: 1.22 turned both FIELDS into properties
(ServerPos => Pos, one shared instance), so the same source compiles on every supported
version but the emitted IL binds to exactly one shape, and a prebuilt binary dies with
MissingMethodException: get_ServerPos()on the other engine line (the four failures left
on the issue #49 cross-install engine run after the enum fix took it from 33 down to 4).
EngineCompat.ServerPosOf/PosOfresolve the member shape once per process (property
preferred, public-field fallback, boot-validated fail-fast with the missing member and game
version named).SidedPos, a property on every supported version, stays the documented
surface for SPAWNED entities (the one engine-test assert readingEntity.Posmoved to it);
the new accessors exist for the pre-registration window whereSidedPosis unusable (it
dereferencesentity.World, unset untilSpawnEntity, on pre-1.22 engines).