Testland
Browse all skills & agents

multiplayer-state-machine-coverage

Build a coverage matrix for a networked-game state machine that exercises connect / authority-handoff / disconnect / reconnect / host-migration paths across Unity Netcode for GameObjects, Unreal Engine replication, and Mirror Networking. Workflow: enumerate the engine's connection states + ownership states + replicated-property update rules, cross them against latency / loss / out-of-order packet injection, encode each combination as a test fixture, and emit a go / no-go gate. Use before submitting a multiplayer title to platform cert - Microsoft's cert guide lists 'Multiplayer does not work as expected' as one of the most common Hold reasons, and Xbox XR-067 (MPSD session state) is failed by uncovered state-machine paths.

Install with skills.sh (any agent)

npx skills add testland/qa --skill multiplayer-state-machine-coverage
View source

multiplayer-state-machine-coverage

Overview

Networked games are state machines: each player connection, each replicated entity, and each authority handoff transitions through a documented set of states. Cert failures and on-the-wire bugs overwhelmingly happen at transition edges - connect, host migration, disconnect-mid-action, reconnect-after-network-loss - not in the steady-state gameplay loop.

This skill is a build-an-X workflow: produce a state-machine coverage matrix that enumerates every connection + ownership + replication state in your engine, crosses it with the network fault matrix (latency / loss / out-of-order / disconnect), and emits a fixture list + a go / no-go gate.

Composes with:

  • game-test-categories-reference - multiplayer testing is a cross-axis over functional / compliance / compatibility / performance.
  • platform-cert-overview-reference - the matrix maps onto Xbox XR-067 (MPSD session state), XR-064 (joinable sessions), XR-115 (controller / user add and remove), and XR-074 (loss of connectivity), all cited inline below from the Xbox Requirements page (opens in new window).
  • unity-test-framework, unreal-automation-system, godot-gut-tests - the per-engine test frameworks the fixtures run in.

When to use

  • Building the multiplayer test plan for a title that ships to Xbox / PlayStation / Switch - cert pass risk concentrates here.
  • Triaging a recurring "host migration drops players" / "join fails after suspend" / "save corrupted on rejoin" bug class.
  • Bringing up netcode-driven gameplay (Unity NGO, Unreal replication, Mirror) for the first time on a project - set the coverage floor before launch.

Microsoft's Certification step-by-step guide (opens in new window) explicitly cites "Multiplayer does not work as expected" as one of the three most common reasons titles are placed on Hold. Holds are calendar-week delays - coverage authored ahead of cert is the cheapest mitigation.

Inputs

Before walking the workflow, gather:

InputWhereWhy
Engine + netcode stackProject - Unity NGO / Unreal replication / MirrorDetermines the state vocabulary you enumerate
Topology - host / dedicated / listenGame design docListen-server has different states than dedicated server (see Unreal section below)
Max concurrent players (MaxPlayers)Backend config / multiplayer serviceCaps the fixture matrix
Persistence model - does the session resume after host-migration / suspend?Game design + platform cert requirementsXR-067 requires the title maintain MPSD session state
Platform target - Xbox / PSN / Switch / SteamCert planDrives the XR / TRC / Lotcheck clauses to cover

Workflow

Step 1 - Enumerate connection states

Per engine docs, list the states the engine exposes. The matrix is fixed by the framework - you cannot add or remove states, only choose which to cover. The full per-engine connection-state tables (Unity NGO, Unreal replication, Mirror) - each state with its trigger and observability hook - are in references/engine-states.md.

Step 2 - Enumerate ownership states

Authority handoff is where most "ghost item" / "ability use after death" bugs live. The per-engine authority-state enumeration (NGO OwnerClientId / IsOwner; Unreal ROLE_*; Mirror isServer / isOwned) is in references/engine-states.md. Authority transitions to cover:

  • Spawn → owner assignment - does the right OnStartAuthority / OnGainedOwnership callback fire?
  • Authority handoff mid-action - player picks up an item that another player owns; does the prior owner stop sending RPCs?
  • Authority loss on disconnect - does authority return to server or transfer to another client?
  • Authority on host migration - see Step 4 below.

Step 3 - Enumerate replicated-property transitions

For every replicated property (NGO NetworkVariable<T>, Unreal UPROPERTY(Replicated) / RepNotify, Mirror [SyncVar]), identify:

  1. The set of legal values it can hold.
  2. The set of legal transitions between values (some pairs should never be observed).
  3. The callback fired on remote when it changes (Unreal: OnRep_<PropName> per Networking Overview (opens in new window): "Replicated Using Properties: State that triggers a callback function upon replication"; Mirror: [SyncVar(hook=...)]; NGO: NetworkVariable<T>.OnValueChanged).

Each OnRep_ / hook handler is a transition edge that needs at least one fixture exercising it.

Step 4 - Cross with the network fault matrix

The engine state machines are deterministic on a perfect network. Real networks are not perfect. Cross-product each state from Step 1 with:

FaultHow to inject
Latency 50 / 200 / 500 msOS-level traffic shaper (tc qdisc add dev eth0 root netem delay 200ms); Unity Multiplayer Tools' Network Simulator; Mirror's LatencySimulation component
Packet loss 1 / 5 / 20 %tc qdisc … loss 5 %; engine-specific simulator
Reorderingtc qdisc … delay 50ms reorder 25 %
Connection dropDetach NIC / kill UDP socket / engine-specific Disconnect()
Host suspend (console only)Platform-specific suspend → resume
Host migration (where supported)Force-quit host; verify new host election

The full Cartesian product is too big - sample by risk-weighted buckets:

  • High - every fault × every transition edge.
  • Medium - steady-state gameplay × every fault.
  • Low - steady-state gameplay × baseline (no fault).

Step 5 - Encode each combination as a fixture

For Unity NGO, the fixture is a UTF [UnityTest] PlayMode test (see unity-test-framework):

[UnityTest]
public IEnumerator HostMigration_TransfersAuthority_OnHostDisconnect()
{
    // Arrange - host + 2 clients
    var host = StartHost();
    yield return new WaitForSeconds(1f);
    var c1 = StartClient(); yield return new WaitForSeconds(0.5f);
    var c2 = StartClient(); yield return new WaitForSeconds(0.5f);

    // Spawn an authority-bearing object owned by host
    var npc = host.SpawnNpc();
    yield return new WaitForSeconds(0.5f);
    Assert.AreEqual(host.LocalClientId, npc.OwnerClientId);

    // Act - kill the host
    host.Shutdown();

    // Assert - surviving client becomes new host within
    // <= 5 s and reassigns NPC authority.
    yield return new WaitForSeconds(5f);
    Assert.IsTrue(c1.IsHost || c2.IsHost);
    Assert.IsNotNull(NetworkManager.Singleton.SpawnManager.SpawnedObjects[npc.NetworkObjectId]);
}

For Unreal, a unreal-automation-system spec wrapping IAutomationDriverModule doesn't drive netcode directly - instead, drive a multi-process test harness (UE 5.x's "Multi-User Editor" / Multi-Process PIE) and use specs to observe the resulting OnRep_ invocations.

For Mirror, the fixture is a Unity NGO-style UTF test plus Mirror's built-in network simulation transports.

Step 6 - Wire to platform-cert clauses

Map every fixture to a specific cert clause it covers - e.g. graceful disconnect on network loss → XR-074, MPSD session retained across host migration → XR-067, joinable via the Xbox shell → XR-064, privilege check before joining → XR-045. The full fixture→XR mapping table (and the Sony TRC / Nintendo Lotcheck NDA stable-ID convention) is in references/cert-clauses.md.

Step 7 - Emit the go / no-go gate

Aggregate the matrix into a coverage report:

Multiplayer state-machine coverage - MyGame v1.4.2
====================================================
Connection states enumerated:          7 / 7 ✓
Ownership transitions enumerated:      5 / 5 ✓
Replicated-property edges enumerated: 23 / 23 ✓
Fault-matrix coverage:
  High-risk bucket:  18 / 18 fixtures ✓
  Medium bucket:      9 / 12 fixtures (75 %) ⚠
  Low bucket:         3 /  4 fixtures (75 %)
Cert-clause coverage:
  XR-067 MPSD session state              ✓
  XR-074 Service connectivity loss        ✓
  XR-064 Joinable via shell               ✓
  XR-045 Privilege checks                 ✓
  XR-015 Comm-privacy                     ⚠ (CommunicateUsingVoice path uncovered)
  XR-115 Controller add/remove mid-MP     ✓
  XR-130 Save roams across SKUs           ✓

VERDICT: NO-GO (XR-015 voice-privacy path uncovered;
              medium-risk bucket below 80 % threshold)

The gate refuses to advance to platform-cert submission until every cert-mapped clause is covered and the high-risk bucket is at 100 %.

Worked example - Unity NGO host migration

Inputs:

  • Engine + netcode: Unity 6.0, Netcode for GameObjects v2.11.
  • Topology: Host (one client doubles as server).
  • Max players: 8.
  • Persistence: Session must survive host migration; saves per-client.
  • Platform target: Xbox + PSN + Switch + Steam.

Step 1 - connection states from the NGO v2.11 manual (opens in new window): Disconnected, Connecting, Connected (Approved), Connected (Pending Spawn), Connected (Spawned), Disconnecting, Host.

Step 2 - ownership transitions: spawn → owner assigned; host quits → ownership re-elected; client picks up host-owned item.

Step 3 - replicated properties under coverage: currentHealth (NetworkVariable<float>), inventoryHash (NetworkVariable<int>), questFlags (NetworkVariable<NetworkSerializableQuestState>).

Step 4 - fault matrix selection: high-risk = HostConnected (other client takes over) under each of {200 ms latency, 5 % loss, host-kill, host-suspend (Xbox)}.

Step 5 - encode each combination as a UTF PlayMode [UnityTest] (see code sample in Step 5 above).

Step 6 - map fixtures to cert clauses (per the XR list (opens in new window)):

  • Host-kill + reconvene → XR-067 (MPSD session retained).
  • Host-suspend on Xbox → XR-074 (graceful service loss).
  • New host accepts joins via shell → XR-064.
  • Voice chat after host migration respects mute → XR-015.

Step 7 - emit gate. Failing fixture: voice mute is reapplied after host migration only 4 / 5 runs (flake). Verdict: NO-GO, flake on the XR-015 voice path; needs a deterministic re-application path before cert.

Anti-patterns

Anti-patternWhy it failsFix
Testing only the happy pathCert findings concentrate on transition edgesCover every state × fault combination in the high-risk bucket
LAN-only multiplayer testingSubmission fails under WAN latency / lossInject latency + loss with tc qdisc or engine simulator
No host-migration coverage on titles that claim to support itXR-067 fails mid-sessionAt least one fixture per supported migration path
Ignoring IsOwned / authority flags in testsFalse positives (test passes because client mirrors authority anyway)Per Mirror docs (opens in new window), assert isOwned / IsOwner explicitly
Replication-property hook coverage by inspection onlyOnRep_ doesn't fire if value unchanged - silent contractsTests that explicitly mutate the property and assert the hook ran
Coverage matrix only on engine states, not cert clausesPasses internal QA, fails certStep 6 mapping is mandatory
Trusting "host migration works" without a deterministic election testElection timing is racyBound the election window (e.g., new host elected within 5 s) and assert on it
Using [ClientRpc] for all communicationBandwidth hog; non-reliable RPCs preferred for frequent calls per Networking Overview (opens in new window)Replicated properties for state; RPCs only for events
Voice chat covered only with text chatPer XR-015 permission table (opens in new window), CommunicateUsingText and CommunicateUsingVoice are separate privilegesTest both paths independently

Limitations

  • Multi-process test harnesses are flaky on CI. Spinning up multiple game instances (host + clients) in a single CI job often hits port-collision or timing-dependent flake. Run multiplayer fixtures on a dedicated multi-VM tier, not the same CI runner as unit tests.
  • Engine-specific simulators differ. Unity's Network Simulator is config-driven; Mirror exposes a LatencySimulation component; Unreal uses Net PktLag / Net PktLoss console commands. The matrix must use whichever the engine ships - no generic abstraction works across all three.
  • Cert clauses change. XR identifiers churn release-to-release (the XR v16.1 May 2026 release notes (opens in new window) retired XR-134) - re-verify the mapping in Step 6 against the current XR document.
  • NDA-only platform clauses. Sony TRC / Nintendo Lotcheck exact multiplayer clauses are NDA - cite by stable ID and tag fixtures with portal clause numbers.
  • State-machine enumeration is engine-version-coupled. A NGO update may add states (e.g., a "Reconnecting" intermediate); re-enumerate when bumping the package version.
  • Coverage matrix does not catch desync bugs by itself. It exercises transitions; for desync detection between authority and remote-mirrored state, add property-equality assertions inside fixtures (especially on the OnRep_ / hook paths).

Fixture-to-cert-clause mapping - reference

View source (opens in new window)

Fixture-to-cert-clause mapping - reference

Map every fixture to the specific cert clause it covers, for multiplayer-state-machine-coverage Step 6. Examples from the Xbox Requirements page (opens in new window):

Test fixtureXbox XR covered
Client gracefully disconnects on Xbox network lossXR-074: "Titles must gracefully handle errors with Xbox and partner services connectivity."
MPSD session state retains member list across host migrationXR-067: "titles with online multiplayer functionality must maintain session-state information on the Xbox network … through the Xbox Multiplayer Session Directory (MPSD)"
Joining via Xbox shell launches into multiplayer sessionXR-064: "titles that offer joinable game sessions must enable joinability through the Xbox shell interface"
Privilege check before joining MP sessionXR-045: XPRIVILEGE_MULTIPLAYER_SESSIONS (ID 254) per the XR-045 privilege table (opens in new window)
Player communication respects privacy settingsXR-015: CommunicateUsingText / CommunicateUsingVoice privilege checks per the XR-015 permissions table (opens in new window)
Save roams across console types within a generationXR-130: "Ensure that saved games work across console types within the generation"
Cross-network play visual identificationXR-007: "Titles must visually identify Xbox network users when they're playing with players from non-Xbox gaming networks"
Controller disconnect mid-multiplayerXR-115: re-establish active controller; see XR-115 (opens in new window)

For Sony TRC and Nintendo Lotcheck, the analogous clauses are NDA - cite by stable ID per platform-cert-overview-reference and tag the fixture with the partner-portal clause number.

Networked engine state and ownership tables - reference

View source (opens in new window)

Networked engine state and ownership tables - reference

Per-engine connection-state and authority-state enumerations for multiplayer-state-machine-coverage Steps 1-2. The matrix is fixed by each framework - you cannot add or remove states, only choose which to cover.

Connection states

Unity Netcode for GameObjects (per the v2.11 manual (opens in new window)):

StateTriggerObservability
DisconnectedInitial / after disconnectNetworkManager.IsConnectedClient == false
ConnectingNetworkManager.StartClient() invokedBetween request and approval
Connected (Approved)Server accepts clientOnClientConnectedCallback
Connected (Pending Spawn)Approved but player object not yet spawnedWait for OnNetworkSpawn
Connected (Spawned)NetworkObject.IsSpawned == trueGameplay-ready
DisconnectingShutdown() / link lossOnClientDisconnectCallback fires next
HostSame process is both server + clientNetworkManager.IsHost

Unreal Engine replication (per the Networking Overview (opens in new window)):

StateTriggerObservability
NM_StandaloneSingle-playerWorld->GetNetMode()
NM_DedicatedServer"Separate machine with no local players"IsRunningDedicatedServer()
NM_ListenServer"Host machine where the server operator also plays locally"IsRunningListenServer()
NM_ClientConnected as remote clientWorld->IsClient()
LoginAGameModeBase::PreLoginLoginPostLoginOverride PostLogin
Travel (seamless / hard)ServerTravel to new mapPlayerController->bIsClientReplicationPausedForFrame
LogoutLogout() callbackOverride on GameModeBase

Mirror Networking (per the Mirror docs on NetworkBehaviour (opens in new window)):

StateTriggerObservability
OnStartServer"called on server when a game object spawns on the server"NetworkBehaviour override
OnStartClient"called on clients when the game object spawns on the client"NetworkBehaviour override
OnStartLocalPlayerLocal player only, after OnStartClientNetworkBehaviour override
OnStartAuthority / OnStopAuthority"Called when ownership changes"NetworkBehaviour override
OnStopServer / OnStopClient"Cleanup when objects are destroyed"NetworkBehaviour override

Ownership / authority states

EngineAuthority states
Unity NGOOwnerClientId (per NetworkObject); IsOwner, IsServer, IsHost flags
UnrealROLE_Authority (server), ROLE_AutonomousProxy (owning client), ROLE_SimulatedProxy (other clients), ROLE_None
MirrorisServer, isClient, isLocalPlayer, isOwned per Mirror NetworkBehaviour docs (opens in new window) - isOwned "Returns true on the client if this client has authority over this game object"

Related skills

game-perf-profiling

Profiles game builds against frame-time, memory, GPU draw-call, and GC-spike budgets using Unity Profiler + Profile Analyzer + Performance Testing package and Unreal Insights + stat commands. Establishes pass/fail thresholds (16.6 ms at 60 fps, 33.3 ms at 30 fps), writes automated performance regression tests that run in CI, and emits a structured budget report per SKU. Use when a title must hit a declared frame-time or memory budget before a milestone gate or platform-cert submission, or when a recent change needs a performance regression check.

game-test-categories-reference

Pure-reference catalog of the testing categories that apply to a video-game build before it ships. Defines the six canonical buckets the industry tests against - functional / compliance / compatibility / performance / localization / accessibility - plus the multiplayer and content-rating sub-axes. Cross-references each bucket to the platform-holder vocabulary that drives it (Microsoft Xbox Requirements / XR test cases, Sony TRC, Nintendo Lotcheck, Steam Direct review). Use as the taxonomy lookup when planning a game test pass, scoping QA effort, mapping platform-cert findings back to internal test categories, preparing a submission checklist, reviewing first-party certification requirements, or triaging cert testing failures against internal categories.

gameplay-recording-replay

Build a deterministic gameplay record/replay test artefact for Unity, Unreal, or Godot - record a player session, save it to disk, replay it bit-for-bit, and assert that the resulting game state matches the original. Covers Unity Input System's InputEventTrace API (Enable / Disable / WriteTo / ReadFrom / Replay) for input-level capture, Unreal's Replay System (DemoRec / DemoPlay / DemoStop console commands plus DemoNetDriver + NetworkReplayStreamer, default storage at %LOCALAPPDATA%/{Project}/Saved/Demos) for replication-stream capture, and Godot's community-pattern deterministic-RNG + input-script replay since Godot ships no first-party replay system. Use when authoring a regression-test artefact for player-recorded sessions, building a netcode replay for spectator / esports, or producing reproducible bug repros for cert teams.

godot-gut-tests

Author and run GUT (Godot Unit Test) - the community-canonical GDScript test framework at github.com/bitwes/Gut and gut.readthedocs.io. Covers install (Godot Asset Library or manual `addons/gut/` copy + plugin enable), GUT panel inside the editor, writing tests that extend GutTest with `test_` prefix methods, the assertion family (assert_eq / assert_almost_eq / assert_true / assert_signal_emitted), lifecycle hooks (before_each / after_each / before_all / after_all), inner classes for grouping, parameterized tests via `params=[...]`, doubles / stubs / spies, async / coroutine tests, the command-line runner (`-d -s addons/gut/gut_cmdln.gd -gdir=res://test -gjunit_xml_file=... -gexit`), JUnit XML export, and CI integration. Godot 4.x uses GUT 9.x (current main branch supports 4.6.x; godot_4_7 branch for 4.7.x); Godot 3.x uses GUT 7.x. Use when the unit under test is GDScript code in a Godot project.

platform-cert-overview-reference

Pure-reference catalog of the four platform-holder certification regimes a multi-platform title submits to before release: Microsoft Xbox Requirements (XR) / Xbox certification on learn.microsoft.com, Sony Technical Requirements Checklist (TRC) on the gated PlayStation DevNet portal, Nintendo Lotcheck on the gated Nintendo Developer Portal, and Steam Direct review on partner.steamgames.com. Documents the submission workflow, severity / pass-fail vocabulary, test-bench configurations, and known SLAs for each platform. Cites public sources inline; cites gated NDA portals by stable ID per PLUGIN_AUTHORING.md Step 4 fallback. Use when planning a cert calendar, mapping internal QA findings to the platform's vocabulary, or sequencing submissions across platforms.

unity-test-framework

Author and run the Unity game-engine Test Framework (`com.unity.test-framework`, currently v1.8). Distinct from the ThrowTheSwitch Unity C testing library at throwtheswitch.org/unity - the two tools share only a name. Covers package install via Package Manager, the EditMode vs PlayMode split, the [Test] / [UnityTest] / [SetUp] / [TearDown] / [UnityPlatform] attributes, assembly-definition setup (Editor folder vs asmdef with `includePlatforms` / `optionalUnityReferences: [TestAssemblies]`), Test Runner window, command-line batch invocation with `-runTests` / `-testPlatform` / `-testResults` / `-testFilter` / `-testCategory`, NUnit 3.5 assertion API, and CI integration. Use when the unit under test is C# Unity code that needs to exercise the Unity runtime or editor.

unreal-automation-system

Author and run Unreal Engine's Automation Test Framework - Epic's C++ test framework for UE 4.x / 5.x, documented at dev.epicgames.com/documentation/en-us/unreal-engine. Covers the five test categories Epic defines (Unit / Feature / Smoke / Content Stress / Screenshot Comparison), the IMPLEMENT_SIMPLE_AUTOMATION_TEST and IMPLEMENT_COMPLEX_AUTOMATION_TEST macros, the BDD-style Automation Spec API (DEFINE_SPEC / BEGIN_DEFINE_SPEC / Describe / It / BeforeEach / LatentIt / xIt), latent commands (ADD_LATENT_AUTOMATION_COMMAND), the Automation Driver for UI input simulation (IAutomationDriverModule::Get().CreateDriver(), By::Id / By::Path locators), running via Session Frontend (Window > Test Automation) and command line (-ExecCmds="Automation RunTests …"), and CI integration. Use when the unit under test is C++ Unreal code that needs the UE runtime, editor, or UMG UI surface.