chaining-v1: the portable chain, and the gate that blocks the parlay surface

The order's own prerequisite for the hit-parlay surface was to verify the hit
probability is calibrated. It is not, and the failure is exactly the shape that
destroys a parlay.

Measured on 972 settled hits props: the model is monotonically over-confident
at the top and flat above 0.70. Predicted 0.911 comes back 0.630. Predicted
0.844 comes back 0.630. Predicted 0.747 comes back 0.605. There is no
discrimination at all in the range a parlay is built from, and the error runs
in the flattering direction. Four "91%" legs are 0.686 by the model and 0.157
in fact -- a 4.4x overstatement that compounds with every leg added.

Single props survive a calibration error of that size. A parlay multiplies it.
So chainAcross REFUSES to compound atoms not marked calibrated, and refusing is
the feature rather than a limitation: a ticket built on these numbers would be
confidently wrong in the direction the user pays for.

calibration.js provides the reliability table, the gate (tolerance 0.05,
weighted to the high end because that is where tickets live) and an isotonic
fit. Isotonic is the honest repair here because it is monotone: the model's
ordering survives untouched while the numbers move to what actually happened.
The fitted map says 0.65 -> 0.594, 0.85 -> 0.639, 0.91 -> 0.639.

chain.js is the portable core -- base events plus context, through a chain
function, into a PLUGGABLE aggregator: across players for a compound ticket, up
to the team for expected scoring. The sport-specific parts are inputs rather
than code paths, so basketball plugs in as content. The archetype
redistribution hook is there now, dormant in baseball because a nine-run lead
does not change who bats next, and live in basketball where a blowout fades the
star and feeds the bench.

Two judgement calls worth naming. Treating same-game legs as independent errs
in the FLATTERING direction, since they share pitcher, park and weather -- so
correlation shifts the compound toward the weakest leg, bounded, and is labelled
an approximation rather than a joint distribution. And market divergence does
NOT downgrade confidence: it flags a contested script whose props are either the
best or the worst on the board, and which one is unknown until settled.
Internal inconsistency does downgrade it, because per-entity reads failing to
sum to the team read means one of them is wrong and we do not know which.

Not built: the independent game-script projection. It needs proven team-level
atoms and out-of-sample validation against actual margins, and no atom has
passed the gate yet. Building it now would produce something plausible rather
than something proven, which is the failure mode this whole programme exists to
avoid.

4,269 tests green (339 suites); web build exit 0; counter and frozen clusters
byte-identical.

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-04 17:17:14 -04:00
parent a80a775fa0
commit 9c5b968351
5 changed files with 608 additions and 0 deletions
+38
View File
@@ -1586,6 +1586,44 @@ phased plan in the Session-57 conversation / BUILD-STATE Next section).
- DRIVER/CATALYST pre-registered theories are now INPUT-READY (were
input-blocked); they are sample-blocked from here. Ingesting is not proving.
## chaining-v1 + the calibration gate (Session 90 — non-obvious)
- **THE HIT-PARLAY SURFACE IS BLOCKED, and the block is structural.** Measured on
972 settled hits props: the model is monotonically over-confident exactly where
a parlay stacks — predicted **0.911 → actual 0.630** (err 0.281), and it is
FLAT at ~63% for everything above 0.70 (no discrimination there at all).
A 4-leg "91%" ticket: model 0.686, reality 0.157 — a **4.4x overstatement that
compounds with every leg**. `chain.chainAcross` REFUSES atoms not marked
`calibrated: true`. Refusing is the feature.
- **Calibration ≠ resolution, and chaining cares about calibration.** A model can
rank fine and be useless compounded. Single props survive a calibration error;
a parlay multiplies it. Never stack a probability that has not passed
`calibration.isCalibrated`.
- **`calibration.fitIsotonic` is the honest repair** — monotone (pool-adjacent-
violators), so the model's ORDERING survives untouched while the NUMBERS move
to what actually happened. Real map: 0.65→0.594, 0.85→0.639, **0.91→0.639**.
Fit it POINT-IN-TIME (outcomes preceding the prop) or it has seen the answer.
- **`src/services/model/chain.js` is the portable core:**
`base_events + context → chainFn → aggregator`, aggregator pluggable —
ACROSS = compound ticket, UP = team score. Sport parts are INPUTS, not code
paths, so basketball is content not a rebuild.
- **`redistribute` is the archetype hook** — DORMANT in baseball (a nine-run lead
doesn't change who bats next), LIVE in basketball (blowout fades the star,
feeds the bench). It exists now so the machine doesn't need rewriting later.
- **Treating same-game legs as independent errs in the FLATTERING direction** —
they share pitcher/park/weather, so the joint is likelier than the product.
`chainAcross` applies a bounded shift toward the weakest leg and labels it an
approximation, not a joint distribution. Prefer cross-game legs.
- **An unreadable atom is DROPPED, never p=0** — a single zero leg would zero an
entire ticket.
- **Market divergence is NOT an error signal** and must not downgrade confidence:
it flags a contested game script (its props are the best or the worst on the
board, unknown which). **Internal inconsistency IS** — per-entity reads not
summing to the team read means one is wrong and we don't know which, so the
honest output is LOW CONFIDENCE, not a correction.
- **`propagate` is shrinkage-weighted by sample** — one game moves a 400-obs atom
barely and a 4-obs atom a lot. That gap is the difference between learning and
noise-chasing.
## Active Skills
- vyndr-voice (all user-facing output)
- prop-analysis (grading methodology)