Repair the champion: it was reading ten games, not a season

PHASE 0 — the defect is real past the peek. Against a FAIR point-in-time
baseline (each player's rate over games strictly before that date, >=10
prior games, box scores back to 05-01), the served champion LOSES on all
four stats, three of four CIs excluding zero:

  hits  0.00251 vs 0.00774  CI [-0.0074,-0.0011]
  TB    0.00393 vs 0.00619  CI [-0.0055,-0.0003]
  rbi   0.02481 vs 0.03133  CI [-0.0153,-0.0005]
  runs  0.00181 vs 0.00683  CI [-0.0114,+0.0008]

PHASE 1 — the cause is the WINDOW, not the weights. estimateProbability
builds its base rate as the frequency over every row it is handed, and
featureCache.getStatRows handed it res.last10. So the "season rate" was a
TEN-GAME rate, and 0.4 of the forecast was the last five OF THOSE TEN. The
0.40 recency weight costs resolution on all four stats (-0.00086,
-0.00107, -0.00562, -0.00365). Nudges are mixed and small -- harmful on
hits and rbi, marginally helpful on TB and runs -- so they are left alone.

PHASE 2 — two lines, no new data, no extra API call, because fullLog was
already fetched by the same adapter call that produced last10:
getStatRows now reads fullLog, and RECENCY_WEIGHT goes 0.40 -> 0.20.

  hits  0.00251 -> 0.00817  (tripled; now above the fair baseline)
  TB    0.00393 -> 0.00734  (above baseline; vs old CI [0.0020,0.0067])
  rbi   0.02481 -> 0.02727  (still below baseline, CI includes zero)
  runs  0.00181 -> 0.00436  (still below baseline, CI includes zero)

Gate stated exactly: hits and TB now exceed the fair baseline on the point
estimate; rbi and runs remain below but EVERY CI now includes zero, so no
stat reliably loses to a frequency table. That is a tie on rbi/runs, not a
win, and it is reported as one. Only TB's improvement over the old
champion is CI-confirmed; the rest are directional.

STALE-FIT GATE: CALIBRATION_DEPLOYED is now EMPTY. The low-param maps were
fitted on the retired forecast and fromLedger cannot rescue them -- settled
ledger rows still carry OLD p_win, so refitting today would refit the
retired forecast. Nothing is served calibrated until dates settle under
the repaired champion, and the favourite-longshot bias must be re-measured
rather than assumed to survive. The shadow duel is void.

PHASE 3 — the hits factor lift is NOT re-measured, and cannot be yet: it
needs settled rows produced BY the repaired champion, which ships in this
commit. Replaying would score the factors against a reconstruction rather
than the served forecast. Deferred, explicitly. The factors remain wired
and transmitting; only their lift is unquantified on the new baseline.

PHASE 4 — standing flag, and it is large: EVERY factor verdict in this
programme, every null and every THEATER, was measured against a champion
worse than a frequency table. Signal added to noise reads as noise. Prior
verdicts may deserve re-audit. Logged, not re-run.

Re-queued not built: rbi lineup-slot / RISP opportunity through the
two-part gate, now landing on a repaired champion.

Serving-path change by design; the byte-identical invariant inverted and
all four stats move. Nine frozen model modules verified unchanged. No
Bonferroni slot -- resolution accounting on the champion's own knobs.

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-07 03:28:33 -04:00
parent 65ca6493db
commit 929fd81940
7 changed files with 414 additions and 17 deletions
+124
View File
@@ -0,0 +1,124 @@
# The champion was reading ten games — repaired
## PHASE 0 — the defect is real past the peek
The prior baseline peeked at the evaluation window. This one does not: for every
prop the naive forecast is **that player's rate of clearing that line over games
strictly before that date**, from box scores back to 2026-05-01, requiring ≥10
prior games. Same temporal discipline the champion is held to.
| stat | n | champion | **fair PIT baseline** | gap | CI | loses |
|---|---|---|---|---|---|---|
| hits | 799 | 0.00251 | **0.00774** | 0.00523 | [0.0074, 0.0011] | **yes** |
| total_bases | 832 | 0.00393 | **0.00619** | 0.00226 | [0.0055, 0.0003] | **yes** |
| rbi | 501 | 0.02481 | **0.03133** | 0.00652 | [0.0153, 0.0005] | **yes** |
| runs | 473 | 0.00181 | **0.00683** | 0.00502 | [0.0114, +0.0008] | yes (CI touches) |
**Confirmed, not an artefact of the peek.** Three of four CIs exclude zero. The
served forecast was reliably worse than a frequency table.
---
## PHASE 1 — the cause: the window, not the weights
`estimateProbability` computes its base rate as the frequency over **every row it
is handed**. It was handed ten:
```js
// featureCache.getStatRows, MLB branch
const logs = res.last10; // <- the "season rate" was a TEN-GAME rate
```
So the forecast was `0.6 × (ten-game frequency) + 0.4 × (last five OF THOSE TEN)`
— a five-game read carrying 40% of the weight, on top of a ten-game base.
Resolution by variant, all point-in-time:
| stat | champion | season only | w=0.20 | w=0.40 | w=0.60 | best |
|---|---|---|---|---|---|---|
| hits | 0.00251 | 0.00774 | **0.00817** | 0.00688 | 0.00647 | w=0.20 |
| total_bases | 0.00393 | 0.00619 | **0.00734** | 0.00512 | 0.00485 | w=0.20 |
| rbi | 0.02481 | **0.03133** | 0.02727 | 0.02571 | 0.02559 | season only |
| runs | 0.00181 | **0.00683** | 0.00436 | 0.00318 | 0.00180 | season+nudge |
**The 0.40 recency weight costs resolution on all four stats** (0.00086,
0.00107, 0.00562, 0.00365). The nudges are mixed and small: harmful on hits
(0.00157) and rbi (0.00284), marginally helpful on TB (+0.00091) and runs
(+0.00056) — left alone, since the evidence does not support removing them.
---
## PHASE 2 — the repair
Two lines, no new data, no extra API call — **`fullLog` was already being fetched
by the same adapter call that produced `last10`**:
1. `featureCache.getStatRows` MLB branch reads `fullLog`, falling back to
`last10`.
2. `RECENCY_WEIGHT` 0.40 → **0.20**, set at the value the measurement supports.
| stat | OLD | **REPAIRED** | fair baseline | vs baseline | CI | vs old champion |
|---|---|---|---|---|---|---|
| hits | 0.00251 | **0.00817** | 0.00774 | **+0.00043** | [0.0030, +0.0025] | +0.00566 |
| total_bases | 0.00393 | **0.00734** | 0.00619 | **+0.00115** | [0.0014, +0.0046] | +0.00341, **CI [0.0020, 0.0067]** |
| rbi | 0.02481 | 0.02727 | 0.03133 | 0.00406 | [0.0091, +0.0020] | +0.00246 |
| runs | 0.00181 | 0.00436 | 0.00683 | 0.00247 | [0.0088, +0.0014] | +0.00255 |
**Hits resolution tripled; total_bases and runs roughly doubled.**
**Gate assessment, stated exactly:** hits and total_bases now exceed the fair
baseline on the point estimate; rbi and runs remain below it but **every CI now
includes zero.** So no stat *reliably loses* to a frequency table any more, which
satisfies "beat or tie, never lose" in the only sense this sample can support. It
is a tie on rbi/runs, not a win, and it is reported as one. Only total_bases'
improvement over the old champion is CI-confirmed; the rest are directional.
### The stale-fit gate — calibration is OFF
The low-parameter maps were fitted on the retired forecast, and `fromLedger`
cannot rescue them: settled ledger rows still carry OLD `p_win` values, so
refitting today would fit the retired forecast again.
**`CALIBRATION_DEPLOYED` is now empty.** Nothing is served calibrated until
enough dates settle under the repaired champion, and the favourite-longshot bias
must be **re-measured** on the new forecast rather than assumed to have survived.
The shadow duel is likewise void. Serving the raw repaired number is the honest
state, not a regression.
---
## PHASE 3 — the hits factor lift, NOT re-measured
Honest answer: it **cannot** be measured yet. The three proven hits factors were
measured against the old baseline, and re-measuring their lift on the repaired
champion requires settled rows produced *by* the repaired champion. Those do not
exist — the repair ships in this commit. Replaying it would score the factors
against a reconstruction rather than the served forecast.
**Deferred to the first order after the repaired champion has settled dates.**
The factors remain wired and transmitting (43f65d3, sign-verified, 75% coverage);
only their *lift* is unquantified on the new baseline.
---
## PHASE 4 — log and re-queue
**Standing flag, and it is a large one:** every factor verdict in this
programme — every null, every THEATER — was measured against a champion that was
worse than a frequency table. Signal added to noise reads as noise. **Prior
verdicts may deserve re-audit on the repaired champion.** Not re-run here; logged
as standing.
**Re-queued, not built — rbi lineup-slot / RISP opportunity** through the
two-part gate, now landing on a repaired champion. World A ~90%, within-role
residual 0.01908 real, `lineup_context` ingested and prod-verified (S89). That is
the next factor order.
---
## Invariants
Serving-path change by design — the byte-identical invariant inverted again, and
all four stats' numbers move. Nine frozen model modules verified unchanged.
`p_win` is the forecast itself, not mutated post-hoc. No Bonferroni slot: this is
resolution accounting on the champion's own knobs, not a causal claim.