Run Structure

In short

A run is 9 innings at one ballpark, at one difficulty. Each inning (a RunNode) has a pitcher and a score goal. Innings 3, 6 and 9 are boss innings. Reach the goal to clear the inning; run out of outs first and the run ends. Everything is rebuilt from the run seed, so a checkpoint only stores your choices and totals.

How it works

  1. RunState is created with a batter, park, difficulty and seed. The batter’s starting upgrades are added and Coins starts at RunConfig.startingCoins.
  2. RunMapGenerator.Generate builds the innings from new SeededRandom(seed) (the only thing that uses the raw seed). For each inning i (0-based):
    • Boss inning if (i + 1) % bossEvery == 0, or it is the last inning and the park has a finalBossId.
    • Last inning with a park boss: that pitcher (the park’s finalBossId). It is never drawn earlier.
    • Other boss innings: a random boss not used yet in this run whose minRound <= i, never one that is any park’s own final boss (park bosses only ever pitch at their park). If none fit, the rules relax step by step (ignore minRound, then allow repeats, then any boss, then any pitcher).
    • Normal innings: a random non-boss pitcher with minRound <= i.
    • isFinalBoss is set on a boss inning that is also the last one.
    • targetScore comes from RunState.InningGoal.
  3. Each inning, RunManager.PlayCurrentRound copies the run’s stats, adds the park’s and the pitcher’s batterModifiers, and sets outs to OutsPerInning + ExtraOuts. See Upgrades and Synergies for stats.
  4. The inning ends when the score reaches targetScore (cleared) or outs hit 0 (run over). A cleared inning pays coins, moves CurrentIndex on and opens the shop; clearing the last one beats the map.
  5. Cutscenes (Cutscenes): inning 1 opens with the match intro (once per run); a cleared boss inning that is not the last plays BOSS DOWN! before the shop; the run’s end plays MAP BEATEN! or GAME OVER before the results.

Inning goal (RunState.InningGoal):

goal = inningGoal x inningGoalGrowth^inning x (boss ? bossGoalMultiplier : 1)
       x mapGoalGrowth^(map's place in the unlock order) x difficulty goal multiplier
rounded to the nearest 25, never below 25

Seeds. Each system derives its own stream with SeededRandom.Derive, so nothing you do changes the pitches: innings use the raw seed, pitches use stream 1 (RoundSeed = Derive(Derive(seed, 1), inning index)), shop visits use stream 2. Normal runs get Random.Range(1, int.MaxValue); the daily uses the date. See Seeds and Determinism.

Checkpoint and resume. RunManager.Checkpoint writes run.json (RunSaveData) every time the between-innings screen opens and every time the shop opens (after a boss inning that is before BOSS DOWN! starts, so quitting mid-cutscene resumes in the shop). CONTINUE on the title screen rebuilds the run from the seed, then restores inning, coins, score, home runs, longest hit, cleared inning scores and owned upgrades. A checkpoint taken in the shop reopens the shop with the same offers. Quitting mid-inning resumes before that inning, which plays the same pitches again.

Numbers

WhatValueWhere to tune
Innings per run9RunConfig.inningsPerRun
Boss every3 inningsRunConfig.bossEvery
Outs per inning3RunConfig.outsPerInning
First goal500RunConfig.inningGoal
Growth per inningx1.25RunConfig.inningGoalGrowth
Boss inningx1.25RunConfig.bossGoalMultiplier
Per map along the unlock orderx1.15RunConfig.mapGoalGrowth
Goal roundingnearest 25, minimum 25RunState.InningGoal
Checkpoint version2 (older saves are dropped)RunSaveData.CurrentVersion

Goals on the first map (Sandlot Field), Normal, worked out from the formula (the editor test checks 500, 625 and 975):

Inning123 (boss)456 (boss)789 (boss)
Goal50062597597512251900190023753725

With the default pitchers (Pitchers and Bosses): inning 1 is always Coach Pete (the only non-boss with minRound 0); inning 3 is always The Ace and inning 6 always Knuckle Sandwich (the only two bosses that are not a park’s own). Inning 9 is always the park’s own final boss:

ParkInning 9 boss (finalBossId)
Sandlot FieldBackyard Benny (benny)
FarmyardFarmer Fitz (farmer)
Windy City YardThe Gale (gale)
Midnight YardThe Phantom (phantom)
Moon StadiumThe Alien (alien)

In the code

FileWhat it does
Assets/_Project/Scripts/Run/RunState.csRun state, InningGoal, RoundSeed, TurnSeed, CreateShopRng, PriceOf, AddUpgrade, RecalculateStats
Assets/_Project/Scripts/Run/RunMapGenerator.csGenerate: pitchers, bosses and goals per inning
Assets/_Project/Scripts/Run/RunNode.csOne inning: pitcher, isBoss, isFinalBoss, targetScore, cleared, score
Assets/_Project/Scripts/Run/RunManager.csOutsPerInning, InningContext, Checkpoint, ContinueRun, CanResume
Assets/_Project/Scripts/Progression/RunSaveData.csThe checkpoint fields
Assets/_Project/Scripts/UI/RunMapView.csThe between-innings line score (BOSS / FINAL BOSS cells, GOAL per inning) and briefing
Assets/_Project/Editor/RunStructureTest.csChecks 9 innings, bosses at 3/6/9, goals, unlock order, coins and outs

Gotchas

  • A checkpoint is dropped (and deleted) if its version is not 2, its batter or park no longer exists, its difficulty is out of range, its inning is past the end, or it is a daily from another UTC day (CanResume).
  • The batting cage uses targetScore = int.MaxValue, so its round never clears. See Batting Cage.
  • Every run map now sets finalBossId. A park without one would fall back to a random boss in inning 9.
  • DefaultContent’s comment on The Phantom says “Elsewhere he can turn up as a late boss (inning 6 on)“. The code wins: RunMapGenerator keeps every park’s own boss out of the random pool, so he only pitches at Midnight Yard.
  • The level perks are fixed at run start (RunState.LevelBonus); XP only arrives when the run ends.
  • Dev menu tools (DevJumpToInning, DevOpenShop, DevEndRun) mark skipped innings as cleared at exactly their goals. See Dev Menu and Shortcuts.