Game sessions

A GameSession is one run's game processes in their roles: a dedicated server or a hosting client, and named clients. Your scenario receives it and drives the actors through named, bounded steps.

The types are in Valheim.Testing.GameSessions (using Valheim.Testing.GameSessions; beside using Valheim.Testing.Game;). The runner builds the session for you: PinnedServerRun for a dedicated server, its hosted modes for a hosting client, and PinnedServerRun.RunCampaignAsync for a campaign.

A scenario

A scenario is (GameSession session, TPlan plan) => Task. This one comes from the FullLifecycle example:

public static void Run(GameSession session, MarkerPlan plan) =>
    Run(plan, session.Server!.Game, session.Server!, () => session.OpenClient(plan.Client!), session.Report, session.Output, session.Cancellation);

The runner starts the session before it calls the scenario and disposes it afterwards. Disposing closes the clients in reverse order, then stops the server, each as a Cleanup step.

GameSession

Member Does Fails when
Server The ServerActor, or null when a client hosts the world
Host The HostingClientActor, or null when there is a dedicated server
Clients The ClientActors by name
StartAsync() Boots the server and brings every client to its menu at once One actor fails: the others are cancelled, torn down, and the error is rethrown
ServerJoinable() Waits until the server accepts joins The bound expires; the step names itself
Join(client, arrival, protect, lobby) Joins one client, optionally at an arrival point, and protects its player The join is refused or times out
JoinAll(lobby) Joins every client As Join
Rejoin(client, …) Joins a client again after it left As Join
Barrier(name, wait, within) Your own readiness step, bounded by within The bound expires
OpenClient(plan, name, directory) Opens one client through a new ClientActor and returns its ClientSession
OpenClientsAsync(clients) Opens several named clients concurrently, each to its menu One start fails: the others are closed
Logs, AddLog(log) The logs the teardown log scan reads
Report, Output, Cancellation The run's ScenarioReport, output folder and cancellation token

A session has a dedicated server or a hosting client, never both. Each barrier is a Setup step.

ServerActor

ServerActor is the one place a run wires an owned dedicated server from its plan. Each boot gets the plan's environment with a fresh session token, strict pins, test access and its own evidence in the output:

Evidence Holds
boot-N/ The boot's kept logs
boot-N.process.json The process record
connection-N.jsonl Every command sent to that boot
Member Does
Game The current boot's GameActor
Start(), Restart() Starts the server, or stops it and starts it again on the same world
WaitUntilJoinable(server) Waits until the server accepts joins
Lobby(server) Reads a crossplay server's lobby (crossplay)
Host, RuntimeDirectory, WorldDirectory Where the server runs
Logs, LiveLog, Stops, StartedProcesses Its logs and how each boot stopped

Owned server session describes how a boot starts and stops.

ClientActor

A ClientActor is the one open path for a session's client. Its logs, a failed startup's included, go to the teardown scan.

Member Does
ClientActor.OnThisMachine(name, plan, output) A client on this machine, launched or attached as the plan's mode says
Start() Opens it once and returns its ClientSession
Game, Session, Plan, Name The opened client
Logs The logs to scan
Dispose() Closes it

On this machine an owned client runs from a disposable profile. In a campaign, session.OpenClient(plan.Client, "player") starts the client on the host its environment was assigned (campaign clients). The name may be left out when the campaign declares one client. Clients in system tests describes the plan fields and the client's evidence.

HostingClientActor

A HostingClientActor is a client that hosts its own world: one process that is both the server and a player. StartAsync prepares it before any actor starts, then opens it to its menu and hosts the world.

Member Does
HostingClientActor.OnThisMachine(name, plan, output) A hosting client on this machine, from a plan with a hostWorld section
Prepare() Runs the plan's preflight, prepares the save directory and places the fixture world
Host() Hosts the world with the disposable character, protected
WaitUntilJoinable(host) Waits until the host is in its world and its server is open
Restart() Leaves the world, which saves it, and hosts it again; peers are disconnected
Game, World, WorldUid, LiveLog, Logs The host, its placed world and its logs

The fixture world is placed through the journalled placement (hosted worlds). When the run so far passed, the host leaves its world at teardown. The world moves into the evidence once no client can still host it; otherwise the report names it in hostWorldLeftInPlace.

A peer of the host is a client plan with "joinsHost": true and no join, passwordVariable, crossplay or hostWorld. session.Join(name) waits until the host accepts connections, reads its Steam ID with cli_multiplayer_identity, and connects with cli_connect_steam_user <steamId>.

Hosted runs

PinnedServerRun.MainAsync(args, HostedRunOptions<TPlan>) runs a hosting client and your scenario on this machine:

<runner> [--in-place] validate-host|host <hosted-plan.json> <new-output-directory>
Mode Does
validate-host Runs the plan's preflight only
host Hosts the world, runs the scenario, disposes the session, scans the logs and writes result.json and junit.xml

HostedRunOptions<TPlan> names ReadPlan, Host (the plan's hosting client section), Mod, LogScan, Scenario, CheckPlan and Provenance.

To drive a hosting client's rounds yourself, without a session, call HostedClientRounds.Run:

new ClientRounds { Client = plan.Client!, Report = report, Output = output }
    .Run(() => ClientSession.Open(plan.Client!, output), measure: round => { /* your checks */ });

It places the fixture world through the same journalled placement as a session. A killed run's world is listed by valheim-test env status and moved out by valheim-test env recover --run ID. Client rounds and hosted worlds describe the rounds.

Test fakes

FakeGameSession.Create and FakeGameSession.Hosted(report, output, hostPlan, openClient, peers) build the same session over scripted clients, with no game. The FullLifecycle example uses scripted replies the same way.

Limits

  • A hosted world has no password and is never public.
  • A crossplay host's peer join is refused.
  • The runner's hosted modes place the host and no peers on this machine. A host with peers on other machines is a campaign without a dedicated server.
  • The Steam peer join is tested with fakes only.