Cricket Manager
IPL franchise simulation

A 0.98 correlation should have been a celebration.

In T20 cricket it is a bug. The deepest possible simulation of running an IPL franchise, playable live with friends — and the number that told me it was still too tidy.


In short

Problem
How do you make a simulated cricketer behave like the real one — and how would you know if you had failed?
Constraint
T20 is the most chaotic format in the sport, and the engine has to be perfectly deterministic.
Decided
Adapt the rating model from the Earth Similarity Index. Abandon min-max normalisation for reference-value deviation. Make Spearman correlation the acceptance criterion rather than eyeballing. Event-source the auction.
Rejected
Keeping the min-max version because it was already built and its outputs looked reasonable — and reporting ρ = 0.98 as the headline validation result.
Hardest
Proving single-writer correctness before trusting the event log, and getting net run rate exactly right.
Outcome
2.07M real deliveries modelled. Direction proved, magnitude not. Nobody but me has played it.

Question

How do you make a simulated cricketer behave like the real one — and how would you know if you had failed?

The first half of that is a modelling problem and the second half is the harder one. Any management simulation can produce plausible-looking scorecards. Plausible is the trap: a scorecard that reads correctly tells you nothing about whether the engine underneath is reproducing the sport or merely imitating its output format.

So the project is really two things. A game — you own an IPL franchise, build a squad through a live auction, and watch a season play out ball by ball against other real owners. And a validation problem, which is where nearly all the interesting work went.

The reference points are Football Manager and OOTP Baseball rather than anything on a phone: for people who would rather agonise over whether to burn an RTM card on a death-overs specialist than bowl the yorker themselves.

Constraint

T20 is the most chaotic format in the sport, and the engine has to be perfectly deterministic.

Those pull in opposite directions, and holding both is the entire technical problem.

Determinism is non-negotiable for a multiplayer simulation: the same inputs must produce the same match everywhere, forever, or an auction result cannot be trusted and a bug cannot be reproduced. But the sport being modelled is one where a single over flips a match, and a system built for reproducibility will drift toward tidiness if nobody is watching the right number.

The second constraint is data volume, which is a gift with a bill attached. Roughly 797MB of ball-by-ball Cricsheet data — 11,190 match files across 13 T20 competitions, about 2.07 million individual deliveries — is enough to calibrate against reality rather than taste. It is also enough that "it looks about right" stops being an acceptable answer, because you now have the means to check.

Squad data covers all 10 IPL franchises and 248 active players with batting and bowling statistics each.

Decision

Ten decisions. The last one is the only one I would call judgment rather than engineering.

Decision 01

Steal the rating model from astronomy

Player ratings are built on an adaptation of the Earth Similarity Index — the astronomy metric that scores how closely an exoplanet resembles Earth on a 0-to-1 scale. Swap the planet for a cricketer and Earth for an ideal statistical profile, and you have a rating system with a defensible shape rather than a set of numbers someone felt were about right.

Batting Similarity Index and its bowling counterpart are the result. The appeal is not novelty; it is that the model forces you to state what an ideal player is, numerically and per format, before you can score anyone against it. A hand-tuned rating hides that judgment. This one has to publish it.

Decision 02

Abandon min-max normalisation for reference-value deviation

The first formulation was a min-max normalised weighted sum — each statistic scaled against its historical range, weighted, averaged per format, combined across formats, then re-normalised across players. The batting basket was broad: innings, not-outs, runs, average, high score, strike rate, hundreds, fifties, ducks, fours, sixes and batting position.

It works, and it has a flaw that matters. Min-max normalisation scores a player against the population, so a rating silently moves when the population changes, and a "90" means nothing more specific than "near the top of whoever else is in the dataset."

The refined model scores against a fixed ideal instead:

Component_BSI = 1 − |Reference − Player| / Reference

— capped at 1 where a player exceeds the reference favourably. Format-level score is the mean of its components; overall score is a weighted blend across formats; the final rating is that value times 100.

Traded away: automatic recalibration. A reference-value model has to be revisited by hand as the sport changes, where a min-max model drifts along with it for free.

Rejected: keeping the min-max version because it was already built and its outputs looked reasonable. Reasonable-looking output from a model whose scale has no fixed meaning is exactly the failure this project is about.

Decision 03

Publish the model's own weaknesses alongside it

The design documents state the limitations rather than leaving them to be discovered. The reference values and weights are subjective and high-leverage — small changes there move every rating in the system. The model collapses a multidimensional career into one number. It ignores opposition quality, conditions and era entirely. And it is most valid comparing players of similar role and vintage, which is a real restriction on a game whose auction puts a 2012 specialist next to a 2026 one.

Three extensions are specified and deliberately not built: a longevity factor for long careers, a peak-performance index derived from best-innings and best-match bowling figures, and a combined all-rounder index blending the batting and bowling scores.

The full record — decisions 04–10, the rating model, what each phase built The complete record · about 1,800 words

Decision 04

Make Spearman correlation the acceptance criterion, not eyeballing

Calibration against the ball-by-ball corpus and real auction data produced three separate rating models — Ability, Market Value and Form — validated at Spearman correlations of 0.65 to 0.66 against real-world outcomes.

Splitting the rating into three is the substantive decision. A single number cannot simultaneously answer "how good is this player", "what will he cost" and "how is he playing right now", and collapsing them is how simulations end up with a squad of statistically excellent players who lose. Market value has to be able to disagree with ability — that gap is where the auction game actually lives.

0.65 is the honest number, not a flattering one. Checking ratings against real auction prices and real match outcomes means a 90-rated player has to have actually played like one, and that is a bar you can fail publicly.

Decision 05

Treat determinism as sacred in the engine

The ball-by-ball engine is fully deterministic, and the rules protecting that are absolute: a seeded PRNG (xmur3 for seeding, mulberry32 for generation), no Math.random() anywhere, and no I/O inside the engine at all.

Outcome resolution happens multiplicatively in log-space, which keeps the compounding of many small probability adjustments numerically stable rather than letting rounding accumulate across a full innings.

Guarding it is a golden-master test suite pinned to a specific reproducible result — 143 for 8. Any change that shifts that scoreline is a behavioural change, whether or not it was meant to be one. Aggregate statistical output was validated to within ±0.2 percentage points of real-world tendencies.

Decision 06

Event-source the auction

The auction core is deterministic and event-sourced: an AuctionSequencer processes bids as an ordered event log rather than mutating shared state. For a live room where several people bid within the same second, the log is the truth — replayable, auditable, and impossible to disagree about after the fact.

It carries the real mechanics rather than an abstraction of them: retentions before the open market, RTM cards, the capped, uncapped and overseas categories, and a hard budget cap so every rupee spent on one player is one you cannot spend on the next. Stress-tested with 8-bidder race scenarios.

Decision 07

Damp the AI bidders on purpose

AI franchises run a SATURATION_DAMP mechanism built specifically to stop them hoarding. Without it, bots with a budget and an objective function buy everything worth buying, and the market stops behaving like a market.

The intended effect is that prices rise on genuine scarcity rather than because a bot got greedy — which matters because the auction is the marquee moment of the product. An unrealistic auction does not just play badly; it invalidates every squad decision that follows from it.

Decision 08

Get net run rate exactly right

The league engine uses circle-method round-robin scheduling and an IPL-style playoff bracket — Qualifier 1, Qualifier 2, Eliminator, Final. It also implements the net run rate rule that most implementations get wrong: a side bowled out counts as having faced its full quota of overs, not the number it actually used.

That single rule decides qualification in real seasons. Getting it wrong produces a league table that looks right all year and sends the wrong team to the playoffs.

Decision 09

Prove single-writer correctness before trusting the log

The architecture is a modular monolith with BullMQ-orchestrated jobs and the auction sequencer behind repository-port abstractions, with in-memory adapters for testability. Wiring it to real infrastructure — Postgres 16, Redis 7, Socket.IO, with Prisma adapters replacing the in-memory ones — meant the event log's central guarantee had to be proved rather than assumed.

It is enforced by a unique index on (streamType, streamId, version) and validated by a test firing 8 simultaneous append attempts. If two bids can occupy the same version of the same stream, event sourcing has bought nothing.

Authentication in the live room follows the same posture: argon2id password hashing, Redis-backed sessions on httpOnly and sameSite cookies, double-submit CSRF protection, an authenticated guard, and server-derived identity — client-asserted user IDs are never trusted. In a room where identity determines who owns a bid, accepting the client's word for who it is would be the whole vulnerability.

Decision 10

Treat a suspiciously good score as a defect

The season-sanity probe compared simulated season outcomes against real-world plausibility by Spearman correlation and returned 0.885 to 0.981.

That is a very strong result, and it is the finding I acted on. In most simulations a 0.98 would close the ticket. In T20 it means the model is too orderly: a format famous for a single over flipping a match should not produce seasons that correlate that cleanly with expectation. Real T20 seasons contain results nobody would have ranked correctly beforehand.

So the open problem is named precisely: engine tilt-magnitude calibration. Earlier work proved the simulation is monotonic — better players do produce better outcomes, in the right direction. It never proved the magnitude of that tilt is realistic. Direction was verified; amplitude was not, and the season-sanity numbers are what turned that gap from a theoretical note into a concrete worry.

Rejected: reporting 0.98 as the headline validation result. It is the most flattering number the project has produced and it is evidence of a problem.

The rating model, in full

The reference values are the argument. Everything else is arithmetic.

From dataset to acceptance criterion in the simulation's calibration chain A corpus of 2.07 million real T20 deliveries feeds a rating model built on reference-value deviation, adapted from the Earth Similarity Index after min-max normalisation was abandoned. The model drives a deterministic ball-by-ball engine, whose output is judged against a Spearman rank correlation threshold agreed in advance rather than by inspection. The resulting 0.98 correlation was treated as evidence of a defect rather than as a validation result. 2.07Mreal T20 deliveriesRATING MODELreference-value deviationadapted from the Earth Similarity Index;min-max normalisation abandonedENGINEdeterministic, ball-by-ballSPEARMAN ρacceptance criterionnot eyeballing — a threshold agreedbefore the result was knownρ = 0.98 came back. It was read as evidence of a leak rather than as a validation, and never published as the headline.Direction was proved. Magnitude was not.
Fig. 01 — The acceptance criterion was set before the number arrived. That is the only reason the number could be rejected.

Because a reference-value model makes its assumptions explicit, they can be printed. These are the ideal-player anchors each real player is scored against.

Batting reference values by format
FormatAverageStrike rateMilestoneConversion / boundary
Test55.00.20 centuries/innings1.00 conversion
ODI55.0100.00.15 centuries/innings0.75 conversion
T20I40.0150.01 fifty per 3 innings60% boundary
Bowling reference values — T20I
ComponentReferenceNote
Bowling average20.0Runs conceded per wicket
Economy rate7.0The component that separates T20 bowlers from the rest
Strike rate18.0Balls per wicket
Wicket-haul frequency0.10Four-wicket hauls, not five — a deliberate concession to the shorter format

Format weights blend the three into one overall score — Test 0.40, ODI 0.35, T20I 0.25 in the worked example. The four-wicket-haul substitution is the kind of detail that decides whether a model is a translation of the sport or an imposition on it: five-wicket hauls are rare enough in T20 that scoring against them would flatten every bowler in the game to approximately zero on that component.

What each phase built

Design documents precede implementation for anything touching the attribute-to-outcome mapping. That rule produced the order below.

Phase history and what each one proved
PhaseBuiltProved
RatingsBSI/BoSI calibrated against the ball-by-ball corpus and real auction data; Ability, Market Value and Form modelsSpearman 0.65–0.66 vs real outcomes
Match engineDeterministic ball-by-ball simulation, seeded PRNG, log-space resolutionMonotonicity; aggregates within ±0.2pp; golden master at 143/8
AuctionEvent-sourced sequencer, retentions, RTM, AI bidding with saturation dampingCorrect under 8-bidder race conditions
LeagueCircle-method round-robin, exact NRR, IPL playoff bracketSeason sanity at Spearman 0.885–0.981 — and the flag it raised
InfrastructureModular monolith, BullMQ jobs, repository ports, in-memory adaptersTestability without real infrastructure
PersistencePostgres 16, Redis 7, Socket.IO, Prisma adaptersSingle-writer correctness under 8 concurrent appends
Auth & roomsargon2id, Redis sessions, CSRF, authenticated guard, read-model projections, live auction roomServer-derived identity end to end
StatsLeaderboards with qualification thresholds built on the same CareerLine shape as the rating models; value-for-money analytics, system-state document, technical debt ledgerPoints per crore as an auction-strategy measure

How it is built

A monorepo on Next.js and NestJS with PostgreSQL for persistence, Redis for caching, sessions and pub/sub, Socket.IO for real-time multiplayer, BullMQ for job orchestration and Prisma as the ORM.

Development runs through Claude Code against a persistent context file holding stable project information, with task-scoped prompts issued per phase — a deliberate discipline for keeping long-running, multi-session AI-assisted development coherent rather than letting each session re-derive the architecture. Every phase closes with a summary and an open-items review before the next one opens. The tilt-magnitude problem stayed visible across three phases precisely because that review exists.

What it looks like

The interface is a control room rather than an arcade: dark, data-dense, purposeful, closer in spirit to Cricket Captain than to anything designed for a thirty-second session. It is built to be lived in for a whole season, and the visual design follows from that rather than from what screenshots well.

Outcome

  • 2.07MDeliveries calibrated against
  • 11,190Match files, 13 competitions
  • 0.65Spearman, ratings vs real outcomes
  • ±0.2ppAggregate output vs reality
  • 248Players, 10 franchises
  • 8Concurrent appends, log correct

Every system described above was built and tested: the rating models against real outcomes, the engine against real aggregates, the auction under race conditions, the league against real qualification rules, the event log under concurrency, and the auction room under an authenticated multiplayer session.

What is not settled is the one thing the strongest number exposed. Tilt magnitude remains open, and it is the current focus of refinement rather than a missing feature — the seasons are too well-behaved for the sport they model.

Nobody but me has played it. The build evidence here is unusually complete and the usage evidence is nil — no seasons completed outside testing, no live auction room with other people in it. Whether it becomes something other people play is still open.

Why a simulation is worth building at all

This is the least commercial thing on this site and the most useful thing I have built for thinking. A simulation is systems design with nowhere to hide: you are not shipping features, you are building a small economy with its own internal logic, and if any part of it is unbalanced players find the exploit within hours.

Every subsystem turns out to be a familiar problem wearing a cricket shirt. The auction is a market. The rating system is a model, with the same bias and edge-case failures as any other model. Scheduling is constraint satisfaction. The domain changes; the thinking does not — which is the argument for building outside your field rather than a hobby that needs excusing.

Direction was proved. Magnitude was not. A simulation that gets the ranking right and the chaos wrong is a spreadsheet wearing a scorecard.

next.js · nestjs · postgresql 16 · redis 7 · socket.io · bullmq · prisma · event sourcing · xmur3 · mulberry32 · argon2id · spearman validation · cricsheet · monorepo

Next