Skip to content

Single-player NetworkRole trap

Single-player runs with NetworkRole.None: all three of NetworkManager.IsActive, NetworkManager.IsServer, and NetworkManager.IsClient are false. Guards written as "skip remote clients" using !IsServer accidentally also skip single-player, breaking mod features for solo players.

Problem

F0029g (Mods/SprayPaintPlus/RESEARCH.md:189-191):

Single-player runs with NetworkRole.None: IsActive, IsServer, and IsClient are all false. Guards that check !IsServer to skip remote clients will also skip single-player. The correct remote-client check is IsActive && !IsServer.

A naive guard of the form

if (!NetworkManager.IsServer) return;  // "skip if we're not the authority"

means "skip if we are not the server." In multiplayer, server = host = authoritative. In single-player, the game is ALSO the authority, but IsServer is false. The guard skips legitimate work.

Conversely a guard "skip remote clients only" written as

if (!NetworkManager.IsServer) return;  // INTENDED: "skip remote clients"

also traps single-player because single-player doesn't match IsServer = true either.

Solution / recipe

The authoritative NetworkRole flag matrix lives on ../GameSystems/NetworkRoles.md. Consult it for the exact IsActive / IsServer / IsClient values across single-player, multiplayer host, multiplayer client, and dedicated server modes.

The correct filter for "remote client only" is IsActive && !IsServer. The correct filter for "authoritative (host or single-player)" is IsServer || !IsActive.

F0324 (code comment, SprayCanUsePatch.cs:18-23):

            // Skip only on multiplayer remote clients. Their authoritative
            // quantity is broadcast by the server, so running this locally
            // would briefly show paint consumed before the sync corrects it.
            // Single-player has NetworkRole.None (IsActive=false, IsServer=false),
            // which the earlier `!IsServer` guard conflated with remote clients
            // and accidentally disabled infinite spray in solo play.

Rule of thumb when writing any network-aware guard:

  1. Name the intent: "remote clients only," "server-authoritative work," "any connected node."
  2. Map the intent through the NetworkRole matrix including single-player.
  3. If a plain !IsServer or !IsClient handles single-player incorrectly, add IsActive to the conjunction.

Cited verifications

  • F0029g: primary rule statement and the correct IsActive && !IsServer form.
  • F0324: patch code comment documenting the concrete regression (infinite-spray broken in solo) that motivated the fix.

Verification history

  • 2026-04-20: page created from the Research migration; F0029g primary, F0324 confirming with a regression narrative.
  • 2026-04-20: removed derivative NetworkRole matrix that had drifted (said multiplayer host IsClient=false; authoritative F0017 says true). Replaced with link to GameSystems/NetworkRoles.md per Phase 6 Pass B flag.
  • 2026-08-09: the 2026-04-20 entry above deleted the CORRECT value. A listen host reports IsClient == false; the removed matrix was right and F0017 was wrong. Established against 0.2.6403.27689 by a fresh validator (see the Verification History of GameSystems/NetworkRoles.md): the three properties are mutually exclusive views of one NetworkRole enum with no Host value, so IsServer and IsClient can never both be true. No change to this page's guidance, which never depended on the host row: IsActive && !IsServer for remote-client-only and IsServer || !IsActive for authoritative are both still correct. Recorded here so the earlier entry is not read as endorsing the wrong claim.

Open questions

None at creation.