Pitching

In short

PitcherAI.CreatePitch decides everything about a pitch the moment the windup starts: type, strike or ball, plate location, speed, wobble, loop direction, special balls and decoys. All rolls come from the round’s seed, so the same seed throws the same pitches. See Seeds and Determinism. PitchData.PositionAt(time) is the one source of the ball’s path. The bat, timing ring, replay and spectators all read it.

How it works

Choosing a pitch (CreatePitch)

  1. Type. A weighted pick from the pitcher’s pitches list (WeightedPitch.weight). If the park’s or the pitcher’s special balls ride only some pitch types, 80% of picks come from those types (FavouredShare), so you actually see the special balls. The same type is never thrown more than twice in a row, or three times for a favoured type (if the pitcher has another).
  2. Strike or ball. A strike with chance strikeRate. A strike lands anywhere inside the zone. A ball lands just off one edge (left, right, low or high, picked at random) by 0.08 to 0.25 m.
  3. Speed. type.speedMph x pitcher.speedMultiplier x random(0.97, 1.03).
  4. Special balls. Two slots, each rolled on the separate ball stream: the pitcher’s own ball (bossEffect), then the park’s ball (effect). They stack. A Super Fast ball in either slot multiplies the speed by 1.15. See Special Balls.
  5. Flight time. releasePoint.z / (mph x 0.44704) / speedScale. speedScale slows real pitches down to something playable with a mouse.
  6. Wobble. type.wobble x random(-1, 1) (knuckleball flutter).
  7. In zone. StrikeZone.Contains(location), decided now.
  8. Loop direction. Curveballs and changeups (arcSideways) loop any way from left (-90) through up (0) to right (+90), rolled on their own stream; other pitches loop up.
  9. Decoys. A pitcher with decoys (The Phantom) throws that many fake balls with a pitch, with chance decoyChance x ballChanceScale, on their own stream.

The release point comes from the pitcher figure (human or bean) when there is one, otherwise PitcherAI.DefaultReleasePoint (-0.3, 1.8, 17) m.

The path (PitchData.PositionAt)

Let t = share of the flight time (0 at release, 1 at the plate).

  • Before release: the release point. In play, the ball sits in the pitcher’s hand until then.
  • During flight: Lerp(release, plate - break, t) + break x t^2, plus wobble x sin(3 pi t) on x, plus a loop of arc x 4t(1 - t) bowing toward arcAngle. Break grows with t squared, so the pitch bends late. Wobble and loop are zero at both ends, so the pitch still crosses the plate exactly at plateLocation.
  • After the plate: keeps going toward the catcher at its final speed.
  • Break is (horizontalBreak, verticalBreak, 0) from the pitch type. Negative vertical break is a drop.
  • Special balls change the path here: Anti-Gravity flips the drop (and the loop) upside down, Sticky / Headwind / Tailwind change the pace late, Wiffle swerves, Ghost fades. See Special Balls.

Decoys

Each decoy ends 0.25 to 0.42 m from the real ball at the plate, spread around it and kept near the zone (x +/-0.55 m, height 0.2 to 1.4 m). It follows the real path until 30% of the flight, then eases onto its own, and vanishes at 97%. Decoys can’t be hit and cast no shadow: the shadow is the tell. See Pitchers and Bosses.

The strike zone

A box on the plate plane (z = 0): x from -0.22 to 0.22 m, height 0.5 to 1.1 m. Contains adds the ball radius (0.037 m) on every side, so a pitch that clips the edge is a strike. The zone frame is drawn as a white outline at the plate.

Hit power

Each pitch type also sets how hard it comes off the bat by contact zone (hitPowerPerfect, hitPowerNice, hitPowerSour, default 1). Values per type: Numbers and Pitch Types.

Numbers

WhatValueWhere to tune
Same pitch in a row, max2 (3 for a favoured special-ball pitch)PitcherAI.MaxSamePitchInARow, MaxFavouredInARow
Favoured share0.8 (comment: every pitcher lands about 67 to 72% overall)PitcherAI.FavouredShare
Speed jitterx0.97 to x1.03PitcherAI.CreatePitch
Default release point(-0.3, 1.8, 17) mPitcherAI.DefaultReleasePoint
Pitch speed scale (all runs and the cage)0.6RunConfig.pitchSpeedScale
Difficulty pitch speedx1 Normal, x1.08 Medium, x1.15 Hard (multiplies the scale)Difficulties.PitchSpeed
Loop heightfastball 0.12, sinker 0.15, slider 0.2, changeup 0.35, knuckleball 0.35, curveball 0.6 mPitchTypeDefinition.arc (DefaultContent)
Decoy distance at the plate0.25 to 0.42 mPitcherAI.DecoyMinOffset, DecoyMaxOffset
Decoy split / vanish0.3 / 0.97 of the flightPitchData.DecoySplit, DecoyFade
Strike zonehalf width 0.22 m, bottom 0.5 m, top 1.1 mStrikeZone
Ball miss distance0.08 to 0.25 m off an edgeStrikeZone.RandomOutside
Pitcher defaultsspeedMultiplier 1, strikeRate 0.75, timeBetweenPitches 1.2 s, decoyChance 1PitcherDefinition
Pitch type defaults90 mph, spin 2200 rpm (visual only), hit power 1PitchTypeDefinition
Random streamsballs 0x0BA11, decoys 0xDEC0, loop 0xA2CPitcherAI
Batting cage machinestrike rate 0.85, 0.8 s between pitchesBattingCage

Worked example (default release point, Normal): a 90 mph pitch takes 17 / (90 x 0.44704) / 0.6 = about 0.70 s.

Per-type speeds and breaks: Pitch Types. Each pitcher’s mix, strike rate, speed, own balls and decoys: Pitchers and Bosses.

In the code

FileWhat it does
Assets/_Project/Scripts/Pitching/PitcherAI.csCreatePitch, ChoosePitchType, FavouredPitches, Roll, MakeDecoys; dev overrides DevForcedPitch, DevForcedEffect
Assets/_Project/Scripts/Pitching/PitchData.csOne pitch: PositionAt, ArrivalTime, Break, OpacityAt, WiffleOffset, Has, Primary, DecoyPositionAt
Assets/_Project/Scripts/Pitching/StrikeZone.csZone size, Contains, RandomInside, RandomOutside
Assets/_Project/Scripts/Data/PitchTypeDefinition.csSpeed, breaks, wobble, arc, arcSideways, spin, hit power, trail colour
Assets/_Project/Scripts/Data/PitcherDefinition.csPitch mix, strike rate, speed, pace, boss batter modifiers, own balls, decoys, DescribeBalls
Assets/_Project/Scripts/Batting/DecoyBalls.csDraws the decoys

Gotchas

  • The mph shown on the HUD is speedMph (type, pitcher and Super Fast). It does not include pitchSpeedScale or the difficulty speed, which only shorten the flight time.
  • The order of random draws on the main stream matters for determinism: the favoured roll (when there are favoured pitches) and type, strike roll, location, speed jitter, wobble. Adding a draw in the middle changes every later pitch for a given seed. Special balls, decoys and loops have their own streams.
  • DevForcedEffect replaces only the park slot; a boss’s own ball can still roll. See Dev Menu and Shortcuts.
  • The dev overrides still make the normal rolls, so turning them off leaves the rest of the sequence unchanged.
  • PitcherAI has no game-state smarts yet (no pitching around the count). Its class comment says that behaviour belongs here.
  • The trail is tinted by PitchTypeDefinition.color so breaking balls are readable, or by the special ball’s colour.