Co-op Run Flow
In short
CoopRunis the co-opIRunDriver. Every machine builds the same map from the host’s seed and keeps its ownRunState. The host starts each inning and announces each turn; the batter plays a mini-round on their own machine (1 out, 2 with a second life, starting from the team score) and reports the score it added. After a cleared inning everyone shops on their own and presses READY. The run ends for all when the pool runs out or inning 9 is cleared.
How it works
- Start. Host presses START RUN in the lobby (Lobby and Session). Every machine runs
CoopRun.Start: it buildsRunStatefrom the seed, the host’s map (RunParkId) and difficulty, its own batter (from the roster, else the selected batter) and its batter level bonus.ShopSalt = LocalId + 1gives each player different shop offers from the same seed. The host also createsCoopRules. Everyone lands on the run map. - Inning start. Only the host can play (
CanPlayRound); its button reads PLAY BALL! (ALL READY). Clients see “Waiting for {host} to press PLAY BALL.” The host sendsRoundStart(inning, pool) and thenTurnStart.- Inning 1 only: the match intro. On
RoundStartevery machine plays the intro with the whole lineup (cards built from the roster; everyone’s build is still their starting kit). The host does not send the firstTurnStartyet: it makes anIntroGateof every player and waits. Each player who finishes, skips (Esc) or has cutscenes off counts as done (client →IntroDone; host → everyoneIntroProgresswith who is still reading). Players who are done hold on the face-off with WAITING FOR NAME (or ALEX & SAM, 3 PLAYERS (1/4), WAITING FOR TEAM…). When nobody is left reading, or after the intro’s camera time + 90 s, the host sends the firstTurnStart. A player who leaves counts as done. - The first turn opens with the walk-up: the batter’s machine walks up under the intro’s tracking camera, then
YOUR TURN! and the 3-2-1 come after the camera lands. Spectators keep the face-off until the batter’s
AtBatBeginarrives, then watch the same walk-up. See Cutscenes.
- Inning 1 only: the match intro. On
- A turn. On
TurnStart:- The batter’s machine (
BeginMyTurn): stats = own stats + the park’s and pitcher’s batter modifiers; outs =OutsForTurn(ExtraOuts); pitch seed =RunState.TurnSeed(node, turnIndex);startingScore= team score;isTurn = true; a 3 s pre-roll for YOUR TURN! and a 3-2-1. It playsAtBatController.BeginRoundlike a solo inning and sendsAtBatBegin, then streams the at-bat (Live Spectating). Each finished play goes out as aPlayFeedline. When the mini-round ends (“TURN OVER”, or the goal is reached) it sendsTurnEndwith the score added, or, on the host, applies it directly. - Everyone else switches to
GameState.CoopWatchand spectates.
- The batter’s machine (
- Next turn or inning end. The host feeds
TurnEndtoCoopRules.CompleteTurn(Co-op Rules). Still playing → nextTurnStart. Cleared or failed →RoundEnd(cleared, team score, outs left). - Inning end (
OnRoundEnd, every machine): pays this player’s coins, records the team score on the node and the run total, shows INNING n CLEARED! or OUT OF OUTS…. Failed → run over. Cleared and it was the last inning → run won. A cleared boss inning plays BOSS DOWN! first (the phase is already Shop and READY is cleared, so the ready gate waits for everyone’s cutscene). Otherwise the shop opens. - Shop. Each player shops with their own coins and offers. The button is READY (then READY! while waiting).
The shop lists each player as READY or SHOPPING. Pressing it sends
ShopReady; the host broadcastsReadyState. When everyone still in is ready, all go back to the map (“Everyone is ready.”). - Run over (
EndRun, every machine on its own): the phase becomes Over, everything is awarded and saved (see “Meta, unlocks and XP” below), then MAP BEATEN! or GAME OVER plays, then the results. The results screen has only MAIN MENU. - Outros in co-op need no messages: each machine builds its own
OutroCardand clicks through at its own pace. The figure at the plate is whoever batted last (their batter, cosmetics, and the bat seen in theirAtBatBegin). The winning hit is replayed only if this machine saw it (it batted or watched it live since the turn began).
sequenceDiagram participant H as Host (CoopRules) participant B as Batter participant S as Spectators H->>B: StartRun (seed, order, map, difficulty) H->>S: StartRun Note over H,S: everyone on the run map H->>B: RoundStart (inning, pool) H->>S: RoundStart Note over H,S: inning 1: match intro on every screen B->>H: IntroDone S->>H: IntroDone H->>B: IntroProgress (done, total, still reading) H->>S: IntroProgress loop each turn H->>B: TurnStart (batter, turn, team score, outs left, pool) H->>S: TurnStart B->>H: AtBatBegin, AtBatPose..., AtBatPitch, AtBatSwing, AtBatResult H->>S: relayed live at-bat B->>H: PlayFeed (each play) H->>S: PlayFeed (relayed) B->>H: TurnEnd (score gained) H->>H: CompleteTurn end H->>B: RoundEnd (cleared, score, outs left) H->>S: RoundEnd Note over B,S: own coins, own shop B->>H: ShopReady S->>H: ShopReady H->>B: ReadyState (all ready → map) H->>S: ReadyState
Meta, unlocks and XP in co-op
CoopRun.EndRun runs on every machine for its own player:
| Award | How | Differs from solo |
|---|---|---|
| Map progress | Meta.RecordMap(park, difficulty, won, TotalScore) | Uses the team total; a team win counts toward everyone’s unlocks |
| Meta points | Meta.AwardRun(summary, countsForBest: false): (inningsCleared × 10 + score / 500 + (won ? 50 : 0)) × difficulty (1 / 1.5 / 2.5) | Does not touch your solo best score |
| XP | Meta.AwardXp(summary): (inningsCleared × 25 + homeRuns × 5 + (won ? 150 : 0)) × difficulty | homeRuns is your own, not the team’s |
| Leaderboard | none | Solo submits to the local board; co-op does not |
| Run save | none | CanSaveAndQuit is false; the solo run.json is never touched |
See Meta Progression and XP and Levels.
Leaving
- LEAVE GAME is on the run map, the pause menu (instead of ABANDON RUN) and the watch screen. It sends
Leaveand returns to the title. - A client leaving: everyone sees “{NAME} LEFT THE GAME” in the feed; the host drops them from the order (their turn passes without an out).
- The host leaving: every client’s session ends with “The host left the game” and goes to the title. If the run is already over, clients stay on the results.
-
Host leaving mid-run = no rewards
CoopRun.OnSessionEndedcallsStop()withoutAwardRun/AwardXp, so clients get no meta points or XP for a run the host abandons. Only a run that reaches its end pays out. - A host leaving during MAP BEATEN! or GAME OVER changes nothing: the run is already Over and paid, so clients finish the cutscene and stay on the results. A host leaving during BOSS DOWN! (phase Shop) still ends the run for everyone.
Numbers
| What | Value | Where to tune |
|---|---|---|
| YOUR TURN! pre-roll | 3 s | CoopRun.TurnPreRoll |
| Pose stream rate | 30 per second | CoopRun.PoseInterval = 1/30 |
| Feed lines kept | 8 | CoopRun.MaxFeed |
| Messages handled per frame (max) | 256 | CoopRun.Tick guard |
| Big cheer on a watched home run | ≥ 450 ft | CoopRun.OnPlayFeed |
| Intro gate deadline | intro camera time + 90 s | CoopRun.IntroReadingAllowance |
| Innings per run | 9 | RunConfig.inningsPerRun |
In the code
| File | What it does |
|---|---|
Assets/_Project/Scripts/Run/CoopRun.cs | Start, PlayCurrentRound, LeaveShop, AbandonRun, OnMessage, OnRoundStart, OnTurnStart, BeginMyTurn, OnMyPlay, OnMyTurnFinished, OnRoundEnd, OnReadyState, EndRun, Watch; intro: PlayMatchIntro, IntroFinishedHere, HostIntroProgress, ShowIntroProgress; outros: PlayOutro; CoopTicker drains the message queue and checks the intro gate each frame |
Assets/_Project/Scripts/Intro/IntroGate.cs | Host: who is still reading the intro; Ready(now) |
Assets/_Project/Scripts/Run/IRunDriver.cs | IsCoop, CanPlayRound, WaitingFor, CanSaveAndQuit, LeaveShop (co-op: ready), AbandonRun (co-op: leave) |
Assets/_Project/Scripts/UI/RunMapView.cs | PLAY BALL! (ALL READY), LEAVE GAME |
Assets/_Project/Scripts/UI/ShopView.cs | READY / READY!, per-player READY / SHOPPING chips |
Assets/_Project/Scripts/UI/HudView.cs | Co-op strip: CO-OP TEAM POOL n OUTS LEFT NEXT UP: name, SECOND LIFE, YOUR TURN! 3-2-1 |
Assets/_Project/Scripts/UI/CoopWatchView.cs | Watch screen (see Live Spectating) |
Gotchas
- Messages are queued and run on the next frame (
Defer), including the host’s messages to itself, so nothing runs re-entrantly inside the at-bat coroutine or the network poll. - The batter’s machine decides its own plays; the host only adds up
scoreGained.TurnEndalso carries home runs and longest hit, but the host ignores them. - The host’s
PlayFeedrelay overwrites the batter id with the sender’s id. MULTIPLAYER-PLAN.mdmentions a 90 s shop timer and a host map vote; neither is built. The map is a straight line of 9 innings, so there is no path to pick.- The intro gate holds only the first
TurnStart:RoundStarthas already gone out, so the inning’s pool is set. - Settings > CUTSCENES off on one machine just marks that player done at once; the others still watch.
- Alt-tabbing pauses a window that is batting (
UIRoot.OnApplicationFocus).NetRuntimekeeps polling while paused and turns onApplication.runInBackgroundwhen a session starts.
Related
- Co-op Rules: the referee’s rules and coin split
- Live Spectating: what spectators see during a turn
- Net Messages: every message in the diagram
- Run Structure: the 9-inning map
- At-Bat Loop: the mini-round each turn reuses
- Seeds and Determinism: run seed, turn seeds, per-player shop salt
- Game States and Screens: RunMap, AtBat, CoopWatch, Shop, RunOver
- Meta Progression and XP and Levels: what a co-op run awards
- Cutscenes: the intro, walk-up and outros