eric-builds-math-models
Self-contained statistical models and the charts built from them — one folder per project, each one runs on its own.
Why this repo exists
The models on this show usually live behind a video and a chart. That is fine for watching and bad for checking. This repo is the checking layer: each project gets one folder, its own README, and code small enough to read in one sitting. The README puts it plainly — some of these models end up on MATH vs. VIBES, the weekly show where a math model meets a professional skeptic, live on camera. The repo is where the model has to survive without the edit.
What's in it today
One project so far: world-cup-2026, an attack/defense Poisson model for
the 2026 World Cup knockout rounds, plus the semifinals chart built from it. The
ratings were frozen before the round of 32, so every number on the chart is a pregame
call — the model never saw a knockout result. That freeze is the whole point of the
exercise: it turns "the model liked France-Spain" from a hindsight claim into a
timestamped one you can audit.
How the model works
Each team gets two ratings, attack and defense, fit on international match results.
Expected goals for team A against team B is
exp(MU + SCALE × (att_A − def_B) + regionOffset), with MU = 0.3112 —
evenly matched teams expect about 1.37 goals each — SCALE = 0.95, and a small
confederation offset applied only to cross-confederation games. A Poisson grid over
both teams' expected goals gives win/draw/loss in 90 minutes; level games go to
penalties with an edge toward the higher combined rating, clamped to [0.38, 0.62].
Every knockout venue is treated as neutral, and the four "ways the final can go"
percentages are just the products of the two semifinal advance calls. No hidden
states, no tuning after the freeze — the entire model is 110 lines of JavaScript.
What's real, and what isn't
The chart's numbers are hardcoded into chart.html, and
model.mjs prints every one of them — so the chart is reproducible by
running one command, not by trusting a screenshot. A second script,
h2h.mjs, verifies the head-to-head strips on the chart against the CSVs
in data/ (the France–Spain and England–Argentina meetings). One honest
convention to know before reading those strips: the source dataset records the
90'/120' score, so shootouts count as draws — 1998 England–Argentina is a draw here
even though Argentina advanced on penalties. Match results come from
martj42/international_results
(CC0), through July 11, 2026.
Run it yourself
Clone the repo and pick a folder. For the World Cup project:
node model.mjs # every probability on the chart node h2h.mjs # head-to-head counts and World Cup meetings ./render.sh # chart.png (needs google-chrome + ImageMagick)
If you can make the numbers say something the chart doesn't, that's a bug report we'd enjoy reading.
The full README, verbatim
eric-builds-math-models
Self-contained statistical models and the charts built from them. One folder per project; each folder has its own README and runs on its own.
Some of these models end up on Math vs Vibes — a weekly show where a math model meets a professional skeptic, live on camera.
| Project | What it is |
|---|---|
| world-cup-2026 | attack/defense Poisson model for the 2026 World Cup knockouts, semifinals chart |
Repo post · repo created 2026-07-14, posted 2026-08-06. The ratings were frozen before the round of 32; this post was not, which is why it gets to mention the final.