diff --git a/src/baserunning.lua b/src/baserunning.lua
index 7f26082..ebb9168 100644
--- a/src/baserunning.lua
+++ b/src/baserunning.lua
@@ -264,7 +264,7 @@ function Baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, deltaSecon
         self.secondsSinceLastRunnerMove = 0
         self:updateForcedRunners()
     else
-        self.secondsSinceLastRunnerMove = self.secondsSinceLastRunnerMove + deltaSeconds
+        self.secondsSinceLastRunnerMove = (self.secondsSinceLastRunnerMove or 0) + deltaSeconds
     end
 
     return runnersStillMoving, runnersScored, self.secondsSinceLastRunnerMove
diff --git a/src/main.lua b/src/main.lua
index 467ac82..9da90a6 100644
--- a/src/main.lua
+++ b/src/main.lua
@@ -196,13 +196,14 @@ end
 
 ---@param pitchFlyTimeMs number | nil
 ---@param pitchTypeIndex number | nil
-function Game:pitch(pitchFlyTimeMs, pitchTypeIndex)
+---@param accuracy number The closer to 1.0, the better
+function Game:pitch(pitchFlyTimeMs, pitchTypeIndex, accuracy)
     self.state.ball:markUncatchable()
     self.state.ball.heldBy = nil
     self.state.pitchIsOver = false
     self.state.offenseState = C.Offense.batting
 
-    local current = Pitches[pitchTypeIndex](self.state.ball)
+    local current = Pitches[pitchTypeIndex](accuracy, self.state.ball)
     self.state.ball.xAnimator = current.x
     self.state.ball.yAnimator = current.y or Pitches[1](self.state.ball).y
 
@@ -450,17 +451,17 @@ function Game:returnToPitcher()
 end
 
 ---@param throwFly number
-function Game:userPitch(throwFly)
+function Game:userPitch(throwFly, accuracy)
     local aPressed = playdate.buttonIsPressed(playdate.kButtonA)
     local bPressed = playdate.buttonIsPressed(playdate.kButtonB)
     if not aPressed and not bPressed then
-        self:pitch(throwFly, 1)
+        self:pitch(throwFly, 1, accuracy)
     elseif aPressed and not bPressed then
-        self:pitch(throwFly, 2)
+        self:pitch(throwFly, 2, accuracy)
     elseif not aPressed and bPressed then
-        self:pitch(throwFly, 3)
+        self:pitch(throwFly, 3, accuracy)
     elseif aPressed and bPressed then
-        self:pitch(throwFly, 4)
+        self:pitch(throwFly, 4, accuracy)
     end
 end
 
@@ -519,15 +520,15 @@ function Game:updateGameState()
         self:updateBatting(self.state.batAngleDeg, batSpeed)
 
         -- Walk batter to the plate
-        self.baserunning:updateRunner(self.baserunning.batter, nil, crankLimited, self.state.deltaSeconds)
+        self.baserunning:updateRunner(self.baserunning.batter, nil, userOnOffense and crankLimited or 0, self.state.deltaSeconds)
 
         if pitchTracker.secondsSinceLastPitch > C.PitchAfterSeconds then
             if userOnDefense then
-                local powerRatio, isPerfect = throwMeter:readThrow(crankChange)
+                local powerRatio, accuracy, isPerfect = throwMeter:readThrow(crankChange)
                 if powerRatio then
                     local throwFly = C.PitchFlyMs / powerRatio
                     if throwFly and not self:buttonControlledThrow(throwFly, true) then
-                        self:userPitch(throwFly)
+                        self:userPitch(throwFly, accuracy)
                     end
                 end
             else
@@ -547,7 +548,7 @@ function Game:updateGameState()
         end
 
         if userOnDefense then
-            local powerRatio, isPerfect = throwMeter:readThrow(crankChange)
+            local powerRatio, accuracy, isPerfect = throwMeter:readThrow(crankChange)
             if powerRatio then
                 local throwFly = C.PitchFlyMs / powerRatio
                 if throwFly then
diff --git a/src/npc.lua b/src/npc.lua
index 6c657c3..921c4a4 100644
--- a/src/npc.lua
+++ b/src/npc.lua
@@ -18,6 +18,7 @@ function Npc.new(runners, fielders)
     }, { __index = Npc })
 end
 
+-- TODO: FAR more nuanced NPC batting.
 ---@param ball XyPair
 ---@param pitchIsOver boolean
 ---@param deltaSec number
diff --git a/src/pitching.lua b/src/pitching.lua
index 449aaf2..0ee818f 100644
--- a/src/pitching.lua
+++ b/src/pitching.lua
@@ -4,35 +4,47 @@
 ---@type pd_graphics_lib
 local gfx <const> = playdate.graphics
 
+local StrikeZoneWidth <const> = C.StrikeZoneEndX - C.StrikeZoneStartX
+
+-- TODO? Also degrade speed
+function getPitchMissBy(accuracy)
+    accuracy = accuracy or 1.0
+    local missBy = (1 - accuracy) * StrikeZoneWidth * 3
+    if math.random() > 0.5 then
+        missBy = missBy * -1
+    end
+    return missBy
+end
+
 ---@type Pitch[]
 Pitches = {
     -- Fastball
-    function()
+    function(accuracy)
         return {
-            x = gfx.animator.new(0, C.PitchStart.x, C.PitchStart.x, playdate.easingFunctions.linear),
+            x = gfx.animator.new(0, C.PitchStart.x, getPitchMissBy(accuracy) + C.PitchStart.x, playdate.easingFunctions.linear),
             y = gfx.animator.new(C.PitchFlyMs / 1.3, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
         }
     end,
     -- Curve ball
-    function()
+    function(accuracy)
         return {
-            x = gfx.animator.new(C.PitchFlyMs, C.PitchStart.x + 20, C.PitchStart.x, utils.easingHill),
+            x = gfx.animator.new(C.PitchFlyMs, getPitchMissBy(accuracy) + C.PitchStart.x + 20, C.PitchStart.x, utils.easingHill),
             y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
         }
     end,
     -- Slider
-    function()
+    function(accuracy)
         return {
-            x = gfx.animator.new(C.PitchFlyMs, C.PitchStart.x - 20, C.PitchStart.x, utils.easingHill),
+            x = gfx.animator.new(C.PitchFlyMs, getPitchMissBy(accuracy) + C.PitchStart.x - 20, C.PitchStart.x, utils.easingHill),
             y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
         }
     end,
     -- Wobbbleball
-    function(ball)
+    function(accuracy, ball)
         return {
             x = {
                 currentValue = function()
-                    return C.PitchStart.x + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStart.y) / 10))
+                    return getPitchMissBy(accuracy) + C.PitchStart.x + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStart.y) / 10))
                 end,
                 reset = function() end,
             },
@@ -117,14 +129,20 @@ local crankQueue = {}
 
 --- Returns nil when a throw is NOT requested.
 ---@param chargeAmount number
----@return number | nil powerRatio, boolean isPerfect
+---@return number | nil powerRatio, number | nil accuracy, boolean isPerfect
 function throwMeter:readThrow(chargeAmount)
     local ret = self:applyCharge(chargeAmount)
     if ret then
         local ratio = ret / self.idealPower
-        return ratio, math.abs(ratio - 1) < 0.05
+        local accuracy
+        if ratio >= 1 then
+            accuracy = 1 / ratio / 2
+        else
+            accuracy = 1 -- Accuracy is perfect on slow throws
+        end
+        return ratio, accuracy, math.abs(ratio - 1) < 0.05
     end
-    return nil, false
+    return nil, nil, false
 end
 
 --- If (within approx. a third of a second) the crank has moved more than 45 degrees, call that a throw.