/ receipts / eric-builds-math-models
Open source · GitHub

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.

★ View on GitHub Source data (CC0)
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
More write-ups like this live in the rest of the receipts, and the datasets behind the show's episodes — flight records, World Cup results, the lot — are free at /data/.

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.