Co-op Rules

In short

One shared score and one shared outs pool per inning. Players bat in lobby order; a turn lasts until the batter makes an out (each turn costs the pool exactly one out). The pool is max(solo outs per inning, players still in), so 3 on Normal and Medium, 2 on Hard, but never fewer than the number of players. Reach the goal and the inning is cleared at once; empty the pool first and the run is over.

How it works

CoopRules is the referee. It lives only on the host, has no Unity objects and is tested headless.

  1. Order. Player ids in lobby order, fixed when the run starts (StartRun).
  2. Opener rotation. Inning r (0-based) opens with seat r mod players. If that player has left, the next seat still in opens. So the first batter moves one seat on each inning.
  3. Pool. At the start of each inning: OutsPool = max(soloOuts, ActiveCount), where soloOuts is RunManager.OutsPerInning(config, difficulty) and ActiveCount is the players still in.
  4. A turn. The batter’s own machine plays a mini-round starting from the team score (see Co-op Run Flow). When it ends, the host gets the score the turn added (CompleteTurn(player, scoreGained)):
    • Team score += max(0, scoreGained).
    • If team score ≥ goal: the inning is Cleared (the turn ended on the clearing hit, not an out). No out is charged.
    • Otherwise one out comes off the pool. If the pool is now empty: Failed (run over). Else the turn passes to the next seat still in, and TurnIndex goes up by one.
  5. Second life. CoopRules.OutsForTurn(extraOuts): a batter whose stats give Extra Outs > 0 gets 2 outs on their own turn, otherwise 1. Their first out neither ends the turn nor costs the pool. Never more than one extra life, however many Extra Outs they own. The HUD shows SECOND LIFE.
  6. Drops. RemovePlayer(player): the player never bats again this run. If it was their turn, the turn passes without costing an out. If nobody is left, the inning is Failed. In the shop, a leaver is removed from the READY wait.
  7. Wrong batter. CompleteTurn throws if the player is not the current batter; CoopRun ignores stale TurnEnds from a player who already left.
stateDiagram-v2
    [*] --> NotStarted
    NotStarted --> Playing: StartRound(index, goal)
    Playing --> Playing: CompleteTurn below goal, pool left → next batter
    Playing --> Playing: batter leaves → next batter, no out
    Playing --> Cleared: team score ≥ goal
    Playing --> Failed: pool empty, or nobody left
    Cleared --> Playing: next StartRound

Coins (per player, computed on each machine)

When an inning ends, every machine pays its own player with RunManager.InningCoins:

coins = myHomeRuns × (coinsPerHomeRun + CoinBonus stat) + my $$$ bonus coins
if cleared: coins += coinsPerInning + teamOutsLeft × coinsPerLeftoverOut + (boss inning ? bossBonusCoins : 0)
  • Own: home runs you hit this inning (over all your turns), and $$$ special-ball coins you hit out.
  • Shared: the clear pay, the leftover-out pay (from the team pool) and the boss bonus go to every player equally.
  • A failed inning pays only your own home runs (and then the run ends).

Score

  • The team score starts at 0 each inning. node.score and RunState.TotalScore on every machine get the team score, so the run total is the team’s total.
  • Your machine also counts your own HomeRuns and LongestFt for the run summary and XP.

Numbers

WhatValueWhere to tune
Solo outs per inning3RunConfig.outsPerInning
Hard difficulty−1 out (so 2)Difficulties.OutsDelta
Outs per inning (never below)1RunManager.OutsPerInning
Poolmax(soloOuts, players still in)CoopRules.PoolFor
Pool, 2-3 players, Normal / Medium3
Pool, 4 players, any difficulty4
Pool, 2 players, Hard2
Outs on your turn1, or 2 with Extra Outs > 0CoopRules.OutsForTurn
Outs a turn costs the pool1 (0 if the turn clears the goal or the batter leaves)CoopRules.CompleteTurn
Coins per home run1 + CoinBonusRunConfig.coinsPerHomeRun
Inning clear pay (everyone)4RunConfig.coinsPerInning
Per leftover team out (everyone)1RunConfig.coinsPerLeftoverOut
Boss inning bonus (everyone)5RunConfig.bossBonusCoins
Starting coins5RunConfig.startingCoins

In the code

FileWhat it does
Assets/_Project/Scripts/Run/CoopRules.csStartRound, CompleteTurn, RemovePlayer, PoolFor, OutsForTurn, CurrentBatter, TurnIndex, RoundPhase
Assets/_Project/Scripts/Run/CoopRun.csHost: PlayCurrentRound, HostAnnounceTurn, HostTurnEnded, HostEndRound, OnPlayerLeft. Everyone: OnRoundEnd (coins), BeginMyTurn (outs for the turn)
Assets/_Project/Scripts/Run/RunManager.csOutsPerInning, InningCoins (shared with solo)
Assets/_Project/Editor/CoopRulesTest.csTools > Home Run Derby > Net > Run Co-op Rules Test: pool size, opener rotation, turns on outs, clearing mid-turn, empty pool, drops on and off turn, everyone leaving, second life, wrong batter

Gotchas

The plan's "7 outs" is stale

docs/MULTIPLAYER-PLAN.md (6a) says a solo round has 7 outs, so the pool is max(7, players). Since the 9-inning runs, RunConfig.outsPerInning is 3, and CoopRules takes it from RunManager.OutsPerInning. The code wins: the pool is max(3, players), or max(2, players) on Hard. CoopRulesTest still builds its rules with SoloOuts = 7; that only tests the formula, not the live value.

  • The second life is handled by the batter’s own at-bat (it plays a mini-round with 2 outs). CoopRules only ever sees one finished turn = one out.
  • No boss bonus to the pool (solo has none either).
  • The leftover-out bonus uses the team pool’s outs left, so a big pool with 4 players can pay more than solo.
  • The pool size uses players still in at the start of the inning; it does not shrink if someone leaves mid-inning.
  • In inning 1 the host holds the first TurnStart until everyone is through the match intro (or a deadline passes). The rules above are unchanged by it. See Cutscenes.