-
Notifications
You must be signed in to change notification settings - Fork 1
Mod Staging
ModStager accepts exactly what the game's own mod loader accepts:
-
Folder: a directory laid out like a mod (with
modinfo.json), copied recursively into the staging directory. -
Zip: a
.ziparchive, copied as-is; the game's mod loader unpacks it like any other zip mod. - Dll: a compiled mod assembly, copied as-is.
Paths can point at any of the three; Atlas resolves each one, verifies it exists, and copies it into a scratch staging folder that the embedded server loads mods from.
Paths declared via [assembly: AtlasMods(...)] or [AtlasWorld(Mods = ...)] are resolved
relative to the test assembly's own output directory, not the source tree. If your mod
lives at samples/SampleMod and your test assembly builds to
samples/Sample.Scenarios/bin/Debug/net10.0/, the path is "../../../../SampleMod" (up
through net10.0, the configuration folder, bin, and the project folder, then down into
the mod).
If a path does not resolve to an existing folder, .zip, or .dll, ModStager.Stage throws
AtlasSetupException naming every missing path. See Troubleshooting.
Hand-writing a relative path gets fragile as soon as build configuration or output layout
changes. If your mod-under-test is a project in the same solution, tag the ProjectReference
instead:
<ProjectReference Include="..\MyMod\MyMod.csproj">
<AtlasMod>true</AtlasMod>
</ProjectReference>Pixnop.Atlas.XUnit ships a buildTransitive MSBuild target (WriteAtlasModManifest,
defined in build/Atlas.E2E.targets) that runs automatically on every build of every package
consumer. It reads _ResolvedProjectReferencePaths (the item MSBuild produces after
resolving each ProjectReference to its actual build output) filtered to entries whose
AtlasMod metadata is true, and writes the resolved output path of each into
atlas-mods.generated.txt, one absolute path per line, directly in $(TargetDir) next to the
test assembly.
AttributeMapper.Map reads atlas-mods.generated.txt when present and appends its paths
after any paths declared via [assembly: AtlasMods(...)] or [AtlasWorld(Mods = ...)].
Both sources are staged the same way; the ordering only matters if your mod loader cares
about dependency-sort input order, which in practice it does not since the game's own
dependency resolution reorders mods regardless.
The manifest is written with WriteLinesToFile(Overwrite=true), so re-builds do not append
stale entries. If no ProjectReference carries AtlasMod=true in the current build (for
example, after removing the metadata), any leftover manifest from a previous build is deleted
rather than left behind with outdated content. This always-run write/delete pair is
deliberate: with no Inputs/Outputs declared on the target, there is no up-to-date skip, so
the stale-manifest cleanup is reliable on incremental builds. The cost is a cheap no-op (one
item transform, one Exists check) when no reference is tagged, which is the common case for
most consumers of the package.
The target is gated on '$(TargetFramework)' != '' so it only runs on the inner per-TFM
passes of a multi-targeting build, where $(TargetDir) is actually meaningful.
AtlasBridge.dll (the harness ModSystem that captures ICoreServerAPI and hands it to the
engine, see Architecture) is staged through a separate code path, ModStager.StageBridge,
rather than being folded into the general Stage call used for mods-under-test:
- It is always exactly one file, known at build time, never user-supplied, so it does not need the folder/zip/dll dispatch that user mod paths do.
- Its staging failures are diagnostically distinct from a missing mod path: a failed bridge
copy means Atlas itself cannot function (no way to reach
ICoreServerAPI), not that the user mistyped a path.StageBridgewraps file system failures (locked file, permissions, missing source) inAtlasSetupException, naming both the source and destination and carrying the file system error as the inner exception, so the failure reads as a setup problem rather than an opaque host crash.