Table of Contents

Namespace Valheim.Testing.GameSessions

Classes

CampaignPreflightActor

One statically selected actor. Host eligibility does not claim that the host is currently ready.

CampaignPreflightProblem

One independent setup problem found before a host is contacted or a fixture is copied.

CampaignPreflightReport

A deterministic, read-only review of the locally selected actors and fixture.

CliTunnel

A local loopback endpoint for a ValheimCLI that listens on its host's loopback only. For a remote host it is an ssh local forward this process started; disposing stops that process, and only that one. Otherwise nothing was started.

ClientActor

One named game client of a run, from its open to its close. It is the one client open path (#258): Start() opens the client to its strictly pinned menu through its placement, once, and that open's kept logs, a failed startup's included, are the actor's (Logs) for the teardown scan. Where the client runs is its placement: this machine (OnThisMachine(string, ClientRunPlan, string, CancellationToken): launched, or attached to an operator's client, as the plan's mode says), or the host a campaign assigned it, in that host's desktop session or, for a local macOS host, this runner's GUI session, on its leased Steam identity (OpenClient(ClientRunPlan, string?, string?)). Game is the open client's in-game handle. Disposing it closes the client: it stops only a process it started, and detaches from an operator's.

ContainerGameHost

A running container on this machine's Docker daemon, driven with docker exec -i. Scripts run as the given user in the container's shell. A Docker error, or a shell the container lacks, without the host's exit report is a transport failure. The container's ValheimCLI is reachable only when the container shares this machine's network (--network host, Linux): a published port reaches the container's own interface, never its loopback, and the CLI must not listen anywhere else.

CrossplayLibraries

Whether a Linux runtime can load crossplay. The game's PlayFab Party library (libparty.so in the runtime's Plugins folder) needs system libraries a bare Linux host may lack, libpulse0 and libpulse-mainloop-glib0 among them. Without one, Unity logs Failed to open plugin, the game's first PlayFab call fails with DllNotFoundException: libParty.so, and a -crossplay server retries its network every 30 s and never opens a lobby. The check asks the host's own loader (ldd, with the launch's library path) before anything starts, and a refusal names each missing library and, where known, the Debian or Ubuntu package that provides it.

EnvironmentRuns

What an EnvironmentInventory does across its hosts (valheim-test env status|recover|teardown and a bare env preflight's journal check): reads each host's run journal, recovers or tears down what one run left, releases the leases of a run whose machine is gone, removes one unjournalled copy. Also where a session's actors and a standalone run's server are placed on the inventory's environments. The inventory itself only describes the hosts and environments.

FetchedDirectory

A host directory extracted locally. Sha256 and Bytes describe the archive, checked on both ends.

GameSession

One run's game processes in their roles (#258, decided on #289): an owned dedicated server (Server) or a client that hosts its own world (Host, #258 step 8), and named clients (Clients), each with its in-game handle .Game. A scenario is a function of the session and its plan, (GameSession session, TPlan plan) => Task, and coordinates the actors only through the barriers, each one named Setup step with a bounded wait that names itself when it times out:

Disposing it closes the clients in reverse order, then stops the server, each a Cleanup step: clients before their host. A hosting client is closed after its peers, then its placed world is moved into the evidence only once no client can still host it.

HostInstall

Reads game installs and their profiles on a host (never copying an install), so a run on another machine keeps the same pins as one here: the runtime manifest, the game build, loader and patchers (InstallPins) and the server's execute bit are all checked against what the host holds, not against a local copy.

HostListing

The files of a directory on a host as ListAsync(IGameHost, string, TimeSpan, IReadOnlyList<string>?, CancellationToken) found them: every regular file's SHA256 by its relative path with / separators (the same manifest Manifest(string) records for a local directory) and, on a bash host, which game executables at the root carry the user-execute bit.

HostLock

An exclusive lock on one host, held by one run. Dispose releases it; a release that cannot be proven throws HostLockException, because a failed teardown is a failure to report. Call ReleaseAsync(CancellationToken) to inspect it instead.

HostLockException

A lock could not be taken or released: another run holds it (HeldByOther) or the outcome is unknown.

HostLockResult
HostLogResult

The end of a host log wait: the matching line, or the last line seen before the deadline.

HostOperationException

A host operation did not succeed. Outcome tells a script failure from a transport failure and an unknown outcome.

HostResult

One host script run. Output has carriage returns removed; ExitCode is set only when Outcome is Exited.

HostServer

Starts an owned dedicated server on a Linux/bash, macOS/bash or Windows/pwsh host and returns it identified by process ID and start time, the identity an owned session checks against its adapter's reported process ID. Linux uses a setsid recorder, macOS a detached supervisor, and both keep stdout and stderr in the boot directory. Windows uses a short-lived headless task to start the server independently of SSH, then removes that task.

HostServerProcess

A dedicated server HostServer started, identified by process ID and start time on its host. It is an IOwnedProcess, so an OwnedServerSession launches, checks and stops it like a local one. Stopping touches only that process, and only while its start time still matches (a process ID reused by another program is never touched): a clean stop (StopCleanly(TimeSpan, TimeSpan)) sends it SIGINT, on which the game saves, retires its PlayFab lobby and quits, and kills it only if it has not exited in time. Then it keeps the boot's logs in its boot directory and, when an evidence directory was given, fetches that directory here. Disposing kills it if it was not stopped yet.

HostShell

The shell a host runs scripts in, found on the host's PATH (or an absolute path without spaces): Pwsh (PowerShell 7) on a Windows host, Bash on a macOS or Linux host. Nothing else runs host scripts.

HostedCampaignCharacter

One registered, disposable local character for one named campaign client.

HostedCampaignManifest

Private setup input for one dedicated server (or none, when one of the clients hosts the world) and named clients: the campaign's actor declaration. The inventory supplies hosts, source installs and the Steam lease location; each role has its own dependency lock so server-only mods never appear on a client by accident. Paths to local inputs may be relative to this manifest. No code or dependencies are downloaded implicitly.

HostedCampaignPreparation

One command's setup half: reviewed inputs become pinned, separate disposable server/client installs.

HostedCampaignRole

One process's reviewed dependency lock and optional, explicit config/script/asset files.

HostedClientRounds

A hosting client's ClientRounds on this machine, driven by the caller (#829). The fixture world is placed through the hosting layer's one journalled site, as start's, a session's and a campaign's are: journalled in this machine's run journal before anything reaches the client's worlds_local, and moved out after into <Output>/<runId>/host-world (fetched into host-world beside it), so after a killed run valheim-test env status lists the world and valheim-test env recover --run moves it out.

HostedRunOptions<TPlan>

What a mod's runner supplies for a hosted run (MainAsync<TPlan>(string[], HostedRunOptions<TPlan>)): one client hosts a pinned fixture world from its menu, with no dedicated server (#258 step 8).

HostedRuntimeFile

A reviewed local file copied to one path in a disposable host runtime.

HostingClientActor

The client of a run that hosts its own world (#258 step 8, Q2): one game process with both capabilities, a client with a local player and the server of its world (a listen server). Its plan is a ClientRunPlan with a HostWorld section; the inventory has no role for it. Its lifecycle:

  1. Prepare(): the plan's static preflight (Preflight(IEnumerable<string>) with HostedRounds), the native client's save directory, then the fixture world placed in its local worlds through its journalled site (HostedWorld; on this machine journalled in this machine's run journal). StartAsync() records each part as its own step, before any actor starts.
  2. Start(): the client to its strictly pinned menu, through its placement, once (ClientActor).
  3. Host(): it hosts the placed world (Start(GameActor, ClientRunPlan, string, TimeSpan, CancellationToken, bool)), protected unless ProtectPlayer is false, and an owned host's disposable character gets test access.

As the session's IOwnedServer, WaitUntilJoinable(GameActor) waits until its world is open to peers and Restart() re-hosts it: the host leaves its world (the game saves it), is re-pinned at its menu and hosts it again. Game is its in-game handle, as server and as client. A peer joins it through the session (Join(string, HeightExpectation?, bool, CrossplayLobby?); JoinHost(ClientRunPlan, GameActor, string, bool, CancellationToken)). Closing it stops only a process it started, or detaches from an operator's; the placed world is moved into the evidence only once no client can still host it (#332): it never opened, its owned process stopped, or it left its world. Otherwise the world stays in place (World is not collected; a session names it in the report).

InteractiveClient

Starts a game client inside a host's existing desktop session, where its display, GPU and signed-in Steam client are. A process started over SSH or from a service has none of these, so ClientSession cannot start a client on another machine.

  • Windows (a PowerShell host): a scheduled task registered for the host user with "Run only when user is logged on" (an interactive token, never a stored password), under a unique name for this launch. Starting it runs a small launcher in the user's desktop session, which starts the game and records its process ID and start time. The task is always removed again, whatever happened.
  • macOS: an SSH host bootstraps a run-owned LaunchAgent into its unlocked console user's GUI session; the agent starts the selected native or Rosetta slice and is removed after the exact client process stops.

A Linux host runs the dedicated server only; a Linux client is refused (System.PlatformNotSupportedException). The host refuses before anything starts when the user has no desktop session there (on Windows, also when it has more than one), when no Steam client runs as the user in it, when a required install file is missing or when the launch directory exists.

InteractiveClientProcess

A game client InteractiveClient started, identified by process ID and start time on its host: the identity an owned session checks against its adapter's reported process ID. It implements IOwnedProcess, so it can be given to the ClientSession launch that takes its process, connection and readiness as functions. Stopping it stops only that process, and only while its start time still matches: a process ID reused by another program is never touched. Disposing stops it if it was not stopped yet.

InteractiveSessionException

A host could not start a client: there is no desktop session for the host user, or no Steam client in it. Nothing was started.

LocalGameHost

This machine. Scripts run in a local shell process (pwsh on Windows, bash elsewhere), so the same lock, ship, log and fetch scripts run as on a remote host. The only transport failure is a shell that cannot be started.

ModDeclaration

What a mod declares to the runner once (#258): the read-only session capability its test adapter serves (for example my.mod.testing/session) and the environment variable that passes the owned session token to it, and, optionally, the Harmony patches it applies. With HarmonyCapability, Owner and Patches, a GameSession checks at runtime-ready, as the Setup step "server: the mod's Harmony patches are applied", that every declared patch is applied on its owned server (RequirePatchesApplied(GameActor)) when the server's pins load the mod (Plugin pinned to a build), so no scenario runs on a server where the mod is loaded but not patched in. Clients are not checked by the session yet.

OneShotServerContext

The strict plans and fixture identity prepared for a mod-owned one-shot server scenario.

OwnedCopies

Finds and removes the copies runs leave behind. A copy is recognised only by what WorldFixture writes: a directory named valheim-test- and 32 hex digits holding fixture-provenance.json, the manifest of what was copied into it. Nothing else under a root is ever listed or removed, and links are never followed. Find(string) only reads. Remove(string, bool) takes one copy at a time by path, refuses one a running process uses and refuses a world copy (a run's save) unless asked, and keeps what the run changed in the copy beside it (Retire(string, long, long)).

OwnedCopy

A copy a run left behind: where it is, what it holds, its size, when it was made, which running processes use it, and the result of the run it belongs to (the nearest result.json), when there is one.

PinnedServerRun

The lifecycle of a mod's owned dedicated-server test runner, so the mod supplies only its plan fields, modes and scenarios. Usage: <runner> [--inventory <environments.json>] validate|run <plan.json> <new-output-directory>.

  1. Refuses an existing output directory (evidence is never overwritten) and one inside a pinned source.
  2. Reads the plan and places its server: on this machine, from the install the plan's runtime names, or with --inventory on a host (below); checks the host before writing anything there.
  3. Records provenance: plan, runner and toolkit hashes, mode, platform, crossplay, the profile, the world copy and their input hashes.
  4. Takes the host's lock, checks every runtime or plugin pin the plan supplies, stages the server's disposable loader profile from the install (which is never changed), and binds omitted pins from that profile. It copies the world, checking a supplied manifest or recording its hashes as found, reads a world UID from the fixture only when the plan supplied neither worlduid nor world, places the copy beside the profile, and checks the profile's executable and its game build, loader, patchers, required plugins and staged plugin set before launch.
  5. validate stops there. Otherwise: checks the CLI port is free, starts the GameSession's owned server (ServerActor) from the profile with per-boot logs (boot-N.*) and recorded commands (connection-N.jsonl), waits on the dedicated startup events, establishes test access and runs the scenario with the session and the plan.
  6. Always disposes the session (clients the scenario left open, then only the owned server), records the server's PIDs, scans the session's logs (Logs: every boot's and client's; ScanLogs(IEnumerable<RunLog>, IReadOnlyDictionary<string, LogClassification>?), with the plan's LogScan), writes result.json and junit.xml, and prints PASS (only for run), VALIDATED or FAIL. Ctrl+C and SIGTERM cancel the run.

A standalone plan with omitted pins enforces the values it observed from the staged profile and fixture and records their PinOrigins as observed.

Without --inventory the server's host is this machine (a local host whose one environment is the plan's install); with --inventory <environments.json> it is a host of the private environment inventory instead: the first server environment in inventory order whose host (Linux with bash over SSH, in a container or this machine, or Windows with PowerShell) and ports fit the plan. Either way its choice and reason are recorded as serverEnvironment. The runner takes that host's lock for the run, checks the host's install against the plan's runtime manifest and RuntimePins, stages a disposable loader profile of it in a new run directory there (the install's own mods on the pinned loader and ValheimCLI set, the install never changed) and binds the plan to that profile, ships the verified world copy and verifies it there, makes the executable and pin checks on the profile, checks the CLI port on the host, reaches ValheimCLI only through a loopback tunnel (OpenCliTunnelAsync(int, TimeSpan, int, CancellationToken)), starts each boot with HostServer and waits for its listening line in the host's log, and at teardown stops only the process it started, fetches each boot's logs (boot-N/) and the world copy (host-world/), removes the profile, closes the tunnel and releases the lock. Clients open on this machine; remote clients and several actors are a campaign's (RunCampaignAsync<TPlan>(string, TPlan, Func<TPlan, IReadOnlyDictionary<string, ClientRunPlan>>, string, PinnedServerRunOptions<TPlan>)), where OpenClient(ClientRunPlan, string?, string?) starts each in its host's desktop session on its leased, observed Steam identity. A host operation whose outcome is unknown (a lost reply, a transport failure, an unproven lock or lease release), when nothing else failed for certain, prints UNKNOWN and returns 3: neither a pass nor a failure.

Returns the process exit code: 0 when every step passed, 1 on failure, 2 on bad usage, 3 when the outcome is unknown.

PinnedServerRunOptions<TPlan>

What a mod's system-test runner supplies to PinnedServerRun.

PreparedHostedCampaign

Every role prepared: the resolved environment, in memory, names only the new immutable installs and the observed Steam identities. RunCampaignAsync<TPlan>(string, TPlan, Func<TPlan, IReadOnlyDictionary<string, ClientRunPlan>>, string, PinnedServerRunOptions<TPlan>) runs a plan on it and retires those installs after its processes stop. Run the scenario while this object is alive.

ReviewCapture

Makes a reproducible still view with a protected, arrived player. It restores the client's exact starting safety, weather, debug time, mist and camera mode on success or failure. A failed restore fails the capture even if a complete PNG was written. The default ValheimCLI plugin also restores on unload when the client is still alive.

ReviewCapturePlan

One human-review screenshot at a declared site. The host directory must be a fresh, private directory belonging to this run; the local evidence directory must not exist. The default ValheimCLI plugin provides the paired review-begin/review-restore commands. Images are evidence for a person, never an automated pass.

ReviewCaptureReceipt

The fetched PNG, its metadata and digest. No visual verdict is implied. Evidence links the metadata sidecar, which records the image's SHA-256 with the world, build, pins and conditions, for Attach(EvidenceReference) (kind review-still, site = the capture id).

ReviewClip

Capture only after an owned, pinned client has entered its intended world; remove partial local evidence on any failure.

ReviewClipPlan

A short, opt-in world-only motion sample. Output is a directory of hashed PNG frames for human inspection, never a verdict.

ReviewClipReceipt

One local frame directory with provenance: frame-NNN.png, the game's frames.csv (index, elapsed milliseconds, bytes and SHA-256 of every frame) and the JSON sidecar. ManifestSha256 is the SHA-256 of frames.csv, which in turn lists every frame's digest. Visual correctness remains for a person to judge.

RunCancellation

Cancels a console-owned setup or run on Ctrl+C or SIGTERM. Dispose after cleanup so another run in the same process does not inherit the handler. Cleanup never runs on the cancelled token: BeginCleanup(TimeSpan?) gives it a fresh one, bounded by a budget, which only a further signal during cleanup (the escape from a cleanup that hangs) or the budget itself cancels; Abandoned then says why, and the run is left for valheim-test env recover. Nothing is ever killed by the escape itself.

ScriptedGameHost

The shared implementation: every operation is a fixed script run through the host's shell, so local, SSH and container hosts behave alike and differ only in how the shell is started and how the ValheimCLI port is reached.

A script travels on standard input, never on a command line: a small fixed wrapper on the host reads it, writes it to a temporary file, runs it and reports its exit code on its own line of stderr. That report, not the transport's exit status, is the script's exit code, so a Windows host whose login shell is PowerShell (which reports only 0 or 1) still returns the real one. Without the report the outcome is a transport failure or unknown.

ServerActor

One owned dedicated server, from its plan, through every boot it starts, to its stop. It is the one place a run's owned server is wired (#258): each boot gets the plan's environment with the session token, its kept logs (Logs, boot-N), a boot-N.process.json record and recorded commands (connection-N.jsonl) in the output, waits on the plan's startup events, is strictly pinned (ExpectCommand), and has test access established before it is handed over (EnsureTestAccess), the first boot and each Restart() alike. A stop asks the server to quit and kills it only after the plan's QuitSeconds. Where the boots run is its placement: this machine or a host of the environment, from the disposable profile PinnedServerRun stages there. Game is the current boot's in-game handle. Disposing it stops only the server it started.

Shipment

Files extracted into a new directory on the host. Sha256 is the archive's hash, checked on both ends; Commit and Tree are set for a git revision.

SshGameHost

A machine reached through OpenSSH (ssh from PATH; on Windows the inbox client, or Git for Windows' client when the runner itself is in an SSH session) with key or agent authentication only: BatchMode is always on, so ssh never prompts, and nothing here accepts a password. The destination is user@host, ssh://user@host:port or an ssh-config alias; the host key must already be known (or pass StrictHostKeyChecking=accept-new). A bash host needs a POSIX login shell; a Windows host's pwsh works under any login shell, including Windows' cmd.exe. ssh exit 255 without the host's exit report is a transport failure.

StagedArm

One arm staged into the disposable install and preflighted: the strict client plan the runner hands to ClientRounds, and the run manifest.

SteamAccountHold

One campaign client's Steam account for a run (Valheim.Testing.GameSessions.ResolvedEnvironment.SteamAccounts): the lease on it and the optional signed-in check. AcquireAsync(ResolvedEnvironment, string, string, IGameHost, string, TimeSpan?, CancellationToken) leases the account the client names, or the first free one for its host, and refuses at once when another run holds it, naming that holder run; nothing waits. The lease never lapses on a timer (#257): it is held until ReleaseAsync(), which belongs after the client's teardown, or until valheim-test env recover|teardown --run releases it once its client is proven stopped. A release that finds the lease no longer this run's, or cannot be proven, fails. Its text names the account, never a credential or a SteamID.

SteamAccountLeaseException

A lease could not be taken or released. Accounts lists the holders when none was free; never a credential.

SteamAccountStatus

An account's lease: who holds it, and for which run when its claim names one, or why it could not be taken. A held lease ends only when it is released (#257). Never a credential.

SteamSignedInException

The signed-in check refused a client before it started. The message names the account and the host, never a SteamID.

TargetedRegression

Stages and preflights a targeted native regression from its RegressionInputs on the inventory's client environment, without the game, then hands one arm to the existing strict-pinned ClientRounds.

  1. The fixture root must hold exactly one world with the manifest's UID (FixtureLayout).
  2. Every staged file must be its pinned SHA256; the manifest records every arm's source hash and the chosen arm is staged.
  3. The shared hosted-runtime stage makes a fresh disposable loader profile per arm with BepInEx/plugins, patchers, config and scripts rebuilt from the allowlist only: the default ValheimCLI plugin, the mod plugins, the probe and one arm.
  4. Every hard [BepInDependency] any staged plugin declares must be met by a staged plugin's own [BepInPlugin] (with its minimum version), no [BepInIncompatibility] may be staged, every plugin must load in the client process, and every assembly reference must resolve to the game, BepInEx or a staged DLL. File names count for nothing.
  5. The plan's own Validate(params string[]) and Preflight(IEnumerable<string>) run on the staged install, including the static ValheimCLI capability check against Manifest when set.

Nothing launches. Run(string, string, string, IReadOnlyList<string>, Action<ClientRound>, CancellationToken, Action<ScenarioReport>?) stages a fresh profile for one arm, refuses any file outside the allowlist right before the launch, and keeps ClientRounds' live capability check and strict per-command pins as the second gate.

WorldDump

The one owner of "a ValheimCLI world dump becomes a pinned offline input". CaptureAsync(GameActor, IGameHost, string, string, int, (float X, float Z, float Radius)?, string, string, TimeSpan, CancellationToken) runs the dump through a pinned GameActor, checks the game's reply against the file it fetched, and writes <name>.csv and its WorldDumpManifest as <name>.json. Offline tests load the pair with ReadManifest(string) and Verify() or Load(IEnumerable<WorldDumpManifest>, string), which check the file against the manifest; the reply grammar is read only here.

Interfaces

IGameHost

A machine or container that runs a game server or client for a test run. Every operation takes an explicit deadline. A remote operation that times out or loses its reply reports an unknown outcome, never a pass or a failure, and is never retried here: a mutation must not be repeated blindly.

IOneShotHostedScenario

A mod-owned assertion for valheim-test start, where the owned client hosts the fixture world. The toolkit prepares the client, character, world, pins, report, and cleanup. The scenario receives the joined client and its hosting actor for one round; it should record each assertion with Step(string, Action).

IOneShotScenarioPreflight

Optional input check for a one-shot server or hosted-client scenario. The toolkit calls this once before it stages a fixture or starts a game, then passes the exact returned value to the scenario. Read settings and local files here; do not change a game install, save or shared state. Throw an actionable exception when an input is missing or invalid.

IOneShotServerScenario

A mod-owned assertion that runs inside one strictly pinned valheim-test server-load campaign. The toolkit prepares the server, optional clean client, fixture, dependencies, plan and evidence before calling this. Implement this interface in a .NET 10 test assembly and pass that assembly with --scenario.

Enums

GameHostKind
HostLockState

What a claim, release or check of a host lock established.

HostLogOutcome
HostOutcome

What is known about a host script once its command returned.

HostServerStop

What stopping a server on a host found: it was killed, had gone already, or quit by itself when asked (SIGINT).

HostShellKind
InteractiveRefusal

Why a host refused to start a client in its desktop session.

InteractiveStop

What stopping an interactive client found: it was killed, had gone already, or quit by itself when asked.

OwnedCopyKind

What an owned copy holds, from its contents.

SteamAccountLeaseState
SteamAccountState
SteamSignedInState

What the optional signed-in check found on a client's host.