The seeded, bivariate Poisson Monte Carlo engine behind the World Cup 2026 final model on touchline.watch. The whole thing, the maths and the calibration, in about 550 lines of dependency-free TypeScript. Change a number, re-run, watch the probabilities move.
If the model is useful or interesting, star the repo so more people find it.
Touchline turns a fixture into a brief you can read, a model you can run, and a set of receipts you can check. This repo is the model: the exact engine that produced the numbers on the Spain v Argentina final page, lifted out of the site and made to run on its own.
It is not an AI, not a betting tip, and not a black box. It is classical statistics, the same family the analytics world uses, with a fixed seed so the same inputs always give the same result. You can read every line, run it yourself, and move the assumptions to see the numbers shift. That is the whole idea.
- 90 minutes. Each side's goals come from a Poisson draw at its expected-goals rate, plus a small shared component (the bivariate Poisson piece,
cov, default 0.10, the Karlis–Ntzoufras club-football estimate). The shared piece adds the positive goal correlation a pure independent-Poisson model lacks, open games swing both ways. - Extra time. If it is level after 90, extra time runs at roughly a third of the 90-minute rates, tilted by a fatigue term (
etTilt) toward the fresher side. - Penalties. Level after 120, and a shootout is drawn, weighted by
penArgfor pedigree. - Scorers. Each goal is assigned to a player by a blend: half the verified tournament goal share, half a pre-tournament prior. Seven games is too small a sample to trust raw shares outright.
- Live mode. Given the minute, the current score, and who is on top, the engine simulates only the remainder of the match, with a chase adjustment (trailing teams open up, leaders counter).
npm install
npm start # the base case, 100,000 runs
npm start -- 500000 # a different iteration countOutput (identical every time, that is the point):
Base case (the site's read) (n=100,000)
Spain to lift 55.5%
Argentina to lift 44.5%
Extra time 29.1%
Penalties 15.7%
Over 2.5 goals 42.3%
Both teams score 49.0%
Likely scores 1–0 14.3%, 0–1 11.6%, 2–1 11.3%, 1–2 8.8%
Likely scorers Messi 35.5%, Oyarzabal 35.4%, Yamal 18.0%, Lautaro 17.3%
Everything you would want to change lives at the top of src/engine.ts:
export const DEFAULT_PARAMS: SimParams = {
lamEsp: 1.3, // Spain expected goals, 90 min
lamArg: 1.1, // Argentina expected goals, 90 min
penArg: 0.56, // P(Argentina win a shootout)
etTilt: 0.1, // extra-time fatigue tilt toward Spain
cov: 0.1, // shared goal component (0 = independent Poisson)
n: 20000,
seed: 20260719,
};Push Spain's rate up, drop the shared component to zero, hand the shootout to whoever you fancy, and re-run. The PRESETS array holds the calibrations for the markets and the published models, so you can line the base case up against Polymarket and Opta:
Presets, 100,000 runs each: Spain to lift
The site's read 55.5%
Polymarket, live 59.8%
Opta supercomputer 59.4%
La Roja masterclass 70.2%
The stoppage-time script 37.4%
The base case sits about four points under the markets and Opta on purpose: the model thinks money and machine both underprice how Argentina steal finals late (all four of their knockout wins were sealed at 90+2 minutes or later). Whether that read was right is on the receipts, kept either way.
On touchline.watch, this machinery is copy-adapted per match, each fixture gets its own calibrated rates and scorer weights, and the same engine drives the live win probability, the sensitivity table, and the post-match receipts. The full method, with the interactive version, is at touchline.watch/how-it-works. The journal takes the pieces apart one at a time:
- Bivariate Poisson, explained without the maths
- Why the draw is the hardest bet in football
- Why your team's supercomputer odds keep changing
Analysis to argue with, not betting advice. The output is a probability and the working behind it, never a recommendation and never a guarantee. Football is played, not solved.
MIT, see LICENSE. If you build something with it, a link back to touchline.watch is appreciated but not required.

