v0.5.0
Added
-
atlas runCLI facade (issue #3): adotnet tool(packagePixnop.Atlas.Cli, command
atlas) that executes the Atlas scenarios of a compiled test assembly without VSTest,
through the same in-process xunit runner the engine's own nested E2E tests use. One process,
sequential, embedded server booted exactly as underdotnet test.atlas run path/to/Scenarios.dllstreams per-scenario PASS/FAIL lines with durations and a summary,
and exits non-zero on any failure (an empty run counts as a failure, so a typo'd filter
cannot go green in CI);--filter <substring>selects scenarios by display name (ordinal,
case-insensitive);--listprints the discovered scenarios without booting anything.
VINTAGE_STORYis validated up front with the same check as the engine's boot, so a missing
install fails fast at the CLI boundary. Building block for future multi-process
parallelization (issue #1). -
Prebuilt world saves:
[AtlasWorld(SaveFile = "fixtures/myworld.vcdbs")](or
WorldOptions.SaveFile) boots the scenario class against a copy of the given save instead of
generating a fresh world. Any file name works (the copy is renamed to the engine's pinned save
name), the fixture itself is never written to, and each test class gets its own pristine copy,
so tests cannot corrupt the fixture or each other.Seed,WorldTypeandPlayStyleare
ignored when a save is supplied; the savegame carries its own world configuration. A missing
fixture fails the boot with anAtlasSetupExceptionnaming the path. -
ITestPlayer.IsConnected: first-class "was the player dropped by the server" signal.false
once the server has removed the player (kick, ban); test players never leave on their own, so
afalsevalue always means the server ended the connection. Kicks issued from a background
thread settle a few ticks late (see the zombie-kick fix below), so wait with
await world.Until(() => !player.IsConnected)rather than asserting right after the kick.
Fixed
-
Kicked test players no longer linger as zombies (kick-on-join left the player in
AllOnlinePlayerswithConnectionState == Admitted, a still-ticking half-despawned entity,
and per-tick "Exception thrown while calculating near heat source strength" warnings). Root
cause: mods that kick from a thread-pool thread (e.g. after an HTTP check inside a PlayerJoin
handler, the Nimbus.ServerMod pattern) crash the engine's own teardown -
ServerMain.FrameProfileris[ThreadStatic], so off the game threadDespawnEntitydies on
aNullReferenceExceptionafter the PlayerDisconnect event fired but before the client and
entity registries were cleaned - and the kicking mod's owncatchusually swallows the crash.
A real TCP client self-heals because its socket close re-runs the teardown on the game thread;
Atlas's dummy socket has no close semantics, so Atlas now supplies that second run itself
(KickedPlayerCleanup): when a dropped test player is still registered, the teardown is
re-run on the game thread, the player's TCP socket slot is released, and the joined-name claim
is freed so the scenario can rejoin under the same name. -
Missing-pdb preflight: a
VintagestoryAPI.dllcopied into the test output without its
VintagestoryAPI.pdbused to kill every scenario at server boot with an opaque
TypeInitializationException(NullReferenceExceptioninLoggerBase..cctor- the game's
logger derives source paths from pdb debug info in its static constructor). The boot now fails
fast with anAtlasSetupExceptionnaming the directory and the fix, andAtlas.E2E.targets
additionally warns at build time when the dll lands in the output without its pdb. -
Pre-boot data path seeding:
[AtlasDataFiles(...)](assembly- or class-level, repeatable)
copies fixture files or directory trees into the embedded server's scratch data path before
ServerMainlaunches, so mods that read their config once inStartServerSidevia
api.LoadModConfigsee the seeded file instead of booting unconfigured. Point a fixture folder
at a data-path subfolder ([AtlasDataFiles("fixtures/ModConfig", TargetPath = "ModConfig")])
or lay the fixture tree out like the data path and overlay it onto the root
([AtlasDataFiles("fixtures/serverdata")]). Assembly-level seeds apply first, then
class-level, so class-level files win on a name collision; missing sources and target paths
escaping the data path fail the boot withAtlasSetupException.samples/SampleConfigModplus
ConfigScenariosinsamples/Sample.Scenariosdemonstrate the end-to-end pattern.