Build the pitcher engine, and find the cap was eating the whole board

Strikeouts are NOT proven -- n=57 against a bar of 500. But the finding that
matters is not a correlation.

THE CAP. Measured on the live slate via the refusal diagnostic: 1,244 unique
gradeable props exist, the 500 cap graded about 334, and because dedupeProps
takes first-row-wins in FEED ORDER, what survives is decided by feed position
rather than value. Pitchers are 2.6% of a batter-dominated feed, so we were
grading SIX strikeout props a slate against 32 available -- putting n>=500
three months away for every pitcher stat. Pitcher props were never being
refused (graded 5, refused 0, suppressed 0); it was truncation.

Raised 500 -> 1500 on measured cost: 721ms per prop at concurrency 5 is about
179 seconds for the full board, against a cron that runs five times a day and
a fire-and-forget caller that never holds an HTTP response. statsapi is free
and unlimited. Concurrency stays at 5 -- one variable at a time. This unblocks
every n-blocked stat in the programme, not just pitchers.

THE ENGINE. pitcherEngine.js is its own engine, not the batter engine pointed
at pitchers: the batter model asks whether contact becomes a hit and reads
contact quality, the pitcher model asks whether the plate appearance ends
without contact at all and reads stuff. Archetypes are FLAME (whiff-led),
SCALPEL (chase-led), SINKER (pitches to contact) and DEFAULT, and a test
asserts the weight keys are not the batter engine's. The projection is K% by
log5 against THIS lineup, times batters faced, through a binomial. An
unclassifiable arm gets the balanced map, never a guessed archetype.

THE MEASUREMENT, at n=57 and contaminated. Four solo features clear the 0.15
effect bar and fail only on sample: arm angle at -0.250 -- the largest
correlation measured anywhere in this programme -- then whiff +0.213, k rate
+0.206, chase +0.195. The batter cluster's best was 0.135. Head to head,
pitch-v1 resolves 0.1285 against the counter's -0.0639, delta +0.192 with a CI
spanning zero.

That negative is the interesting number. The counter is ANTI-PREDICTIVE on
strikeouts: counting a pitcher's recent Ks is worse than useless, because his
recent totals track which lineups he drew and how long he was left in rather
than his skill. It is the one stat where the incumbent has no defensible edge.

A bug caught on the way. resolveTeam wants an abbreviation and the game log
supplies full team names, so the roster join silently resolved nothing and the
first run reported 0% lineup coverage -- the theorized stuff x lineup carrier
was never being tested, not failing. Fixed; coverage is now 94.7%. The carrier
still shows no incremental signal over whiff alone, and adding the lineup term
lowered head-to-head resolution, which is recorded rather than dropped.

Calibration was not reached: nothing passed the first bar. The batter model
and the counter are byte-identical, verified by diff.

4,221 tests green (335 suites); web build exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1sivYNqY2TS5ftykmHBU9
This commit is contained in:
Kev
2026-08-03 18:43:32 -04:00
parent c0621e7aa2
commit 843c8c6d4b
7 changed files with 924 additions and 1 deletions
+24
View File
@@ -3,6 +3,30 @@
## Last Updated
2026-08-03
## Session 84 (2026-08-03) — Pitcher engine built; the cap was eating the board ✅
Spec: `specs/pitcher-engine-strikeouts.md`. 4,221 tests / 335 suites green, build
exit 0. Batter model + counter byte-identical (verified by diff).
- **THE REAL FIND: the grade cap, not pitcher data.** 1,244 unique gradeable
props/slate; the 500 cap graded ~334, and first-row-wins-in-feed-order gave
pitchers 6 props a slate. Raised 500 -> 1500 on measured cost (~179s for the
full board at concurrency 5, cron 5x/day). Unblocks EVERY n-blocked stat.
Pitcher props were never being refused (graded 5, refused 0, suppressed 0).
- **`pitcherEngine.js` — own archetypes (FLAME/SCALPEL/SINKER/DEFAULT), own
inputs (stuff), own projection** (log5 K% vs THIS lineup x batters faced).
Test asserts its weight keys differ from the batter engine's. 17 tests.
- **Strikeouts NOT proven** (n=57 vs 500): pitch-v1 0.1285 vs counter 0.0639,
delta +0.192 CI [0.098,+0.509]. Four solo features clear the |r|>=0.15 bar and
fail only on n — arm_angle 0.250 (largest in the programme), whiff +0.213,
k_pct +0.206, chase +0.195.
- **The counter is ANTI-PREDICTIVE on Ks (0.064)** — recent K counts track
opponent and workload, not skill.
- **Bug caught:** `resolveTeam` needs an abbreviation; the game log gives names,
so lineup coverage was 0% and the theorized carrier was never tested. Fixed via
NAME_TO_ABBR → 94.7%. The carrier still shows no incremental signal (n=54).
- **Calibration not reached** — nothing passed BAR 1.
- **Next:** let the cap accrue (~2 weeks to n>=500), re-run with a point-in-time
window from statcast_history; give arm_angle a registry entry + mechanism.
## Session 83 (2026-08-03) — Batter cluster measured; the proven set is EMPTY ✅
Spec: `specs/batter-cluster-prove.md`. 4,204 tests / 334 suites green, build exit
0. skillProjection byte-identical (TB frozen, verified by diff); counter untouched.