Co-op Overview

In short

Co-op is turn-based: 2 to 4 friends play one run on one map. One player bats until they make an out, then the next player bats. The host referees (whose turn, team score, shared outs pool, round end); every machine keeps its own batter, upgrades, coins and shop. Traffic goes over Steam’s relay or local UDP. Rejoin is not built.

How it works

  1. On the title screen, switch the mode card to CO-OP and press PLAY CO-OP. This opens the lobby (GameState.Lobby). See Lobby and Session.
  2. One player presses HOST A GAME, the others JOIN with the code. The host picks the map and difficulty and presses START RUN.
  3. Every machine builds the same 9-inning run from the host’s seed (see Seeds and Determinism and Run Structure), but with its own batter, coins and upgrades (its own RunState).
  4. The host presses PLAY BALL! (ALL READY). Before the first inning everyone watches the match intro at their own pace; the first turn waits until all are through it (Cutscenes). Turns go round the batting order. The batter plays a normal at-bat (At-Bat Loop) on their own machine; everyone else watches it live (Live Spectating).
  5. When the team reaches the inning goal, the inning is cleared: everyone gets their own coins and opens their own shop, then presses READY. When the shared outs pool runs out, the run is over for everyone. The end-of-game cutscenes (BOSS DOWN!, MAP BEATEN!, GAME OVER) play on every screen from local state. See Co-op Rules and Co-op Run Flow.

What is shared and what is not:

Shared (host decides)Per player (own machine)
Map, difficulty, seed, batting orderBatter, cosmetics, batter level bonus
Team score per inning, outs poolCoins, upgrades, shop offers, rerolls
Whose turn it is, inning cleared or failedStats used while batting (the batter’s own)
Ready state of the shopMeta points, XP, unlocks at run end

Architecture

flowchart LR
    subgraph Host["Host (player 0)"]
        HR["CoopRun (host side)<br/>owns CoopRules: order, outs pool,<br/>team score, round end, READY"]
        HS["NetSession (Host role)<br/>roster, version check, StartRun, relay"]
        HR --> HS
    end
    subgraph Client["Each client (players 1-3)"]
        CR["CoopRun (client side)<br/>own RunState, own shop"]
        AB["AtBatController<br/>bats on its turn,<br/>spectates otherwise"]
        CS["NetSession (Client role)"]
        CR --> AB
        CR --> CS
    end
    NR["NetRuntime<br/>polls once a frame"]
    T["INetTransport<br/>LocalTransport (UDP) or SteamTransport (relay)"]
    HS <--> T
    CS <--> T
    NR -.-> T

The network is a star: clients only talk to the host; the host relays a batter’s messages to the others. See Transports and Net Messages.

Built and not built

FeatureStatus
Lobby: LOCAL / STEAM, host, join by code, COPY / PASTE, 4 seats, batter pick, host map and difficulty pickbuilt
Version check (protocol + Application.version)built, see Protocol Versions
Turns, shared score, shared outs pool, opener rotation, second lifebuilt
Own shops with READY chips; host starts the next inning when all are readybuilt
Live spectating (pose stream, pitches, swings, results)built
Match intro with a team gate (IntroDone / IntroProgress, WAITING FOR NAME), opener’s walk-upbuilt
End-of-game cutscenes on every screen, each at its own pace (no messages)built
Boss balls, arcing pitches and The Phantom’s decoys reach spectatorsbuilt (in AtBatPitch)
Steam overlay invites (INVITE FRIENDS) and “Join game”built (works even if the lobby is on LOCAL)
A player leaving mid-run (turn passes without an out)built
Host leaving ends the run for everyonebuilt
Rejoin into a run in progress (Snapshot)not built
Shop timer (plan: 90 s)not built
Save and resume a co-op runnot built (CanSaveAndQuit is false)
Co-op leaderboardnot built (no leaderboard entry in co-op)
Map vote, rich presence, lobby avatars, real Steam app idnot built
Head-to-Head moderegistered, IsAvailable = false

Numbers

WhatValueWhere to tune
Players2 to 4 (1 allowed in the Editor)NetSession.MaxPlayers = 4; LobbyView START RUN check
Clients per host3LocalTransport.MaxClients, SteamTransport.MaxClients
ProtocolHRD-COOP-7NetSession.Protocol
Steam app id480 (Valve’s Spacewar test app)SteamBootstrap.AppId, steam_appid.txt

In the code

FileWhat it does
Assets/_Project/Scripts/Net/NetRuntime.csMonoBehaviour on GameRoot; owns the transport and NetSession, polls them in Update
Assets/_Project/Scripts/Net/NetSession.csLobby roster, version check, StartRun, routes ids 30+ to the run
Assets/_Project/Scripts/Run/CoopRun.csCoopRun : IRunDriver: this machine’s side of the run, host refereeing, turns, spectating hooks
Assets/_Project/Scripts/Run/CoopRules.csPure referee logic (no Unity objects), tested headless
Assets/_Project/Scripts/Run/IRunDriver.csWhat the map, shop, pause and run-over screens talk to; GameRoot.Run returns ActiveCoop or the solo RunManager
Assets/_Project/Scripts/Modes/GameModes.csCoopMode (IsAvailable = true, description “Two to four batters take turns, one out each, and share one score.“)
Assets/_Project/Scripts/Core/GameRoot.csEnterLobby, HookSession (starts CoopRun on RunStarting), JoinFromPlatform (Steam invites)

Gotchas

  • GameModes.cs’s comment on IGameMode still says Co-op is “registered but not yet playable”. The code below it sets CoopMode.IsAvailable = true; the comment is stale.
  • The README’s folder table calls Net/ “Phases 0-3”; live spectating (phase 4) is also on main.
  • The dev menu’s RUN tools are solo only: in co-op it shows “Co-op runs are refereed by the host, so the run tools are solo only.”
  • Anti-cheat is out of scope: the batter’s machine reports its own result and the host trusts it.