Why We Built MicroLeague Sports Vol. 3
Why Sports Data Is Harder Than Most People Think Building believable cross-era simulations turned out to be less about the engine and more about the data underneath it. Here is what we learned. MicroLeague Dev Blog, Vol. 3 By Eddie Solar When we started building MicroLeague Sports, I assumed the simulation engine would be the hard part. The vision was ambitious enough to justify that assumption. Let fans ask whether the 1996 Bulls beat the 2017 Warriors. Whether the 1985 Bears could slow down Patrick Mahomes. Which Cowboys team was actually the greatest. Teaching software to play those games across eras felt like the mountain. I was wrong about which mountain it was. The engine is hard, but it is a solvable, bounded kind of hard. The data underneath it is a different animal. Like most developers approaching this for the first time, we figured sports data was largely a collection exercise: gather historical teams, player stats, schedules, and box scores, feed it to the model, done. That assumption fell apart almost immediately, and the reason it fell apart is the subject of this article. Sports data is not a collection problem. It is an identity problem. Franchises do not stay the same thing. Players are not one entity. And the historical record does not agree with itself. The Real Problem Is Modeling Identity Over Time Volume 2 covered the era problem: statistics are confounded by the conditions that produced them, so a raw number pulled across decades lies to you. That is a normalization challenge, and it is real. But normalization assumes you already know what you are normalizing. Before you can compare the 1992 Cowboys to the 2023 Chiefs, your system has to have a confident answer to a more basic question: what exactly is a "team," and what exactly is a "player," when your dataset spans a hundred years? Those sound like trivial questions. They are not. They are the questions that ate most of our early engineering time, and getting them wrong quietly corrupts everything built on top. Franchises Are Not Stable Entities The first genuine surprise had nothing to do with simulation. It had to do with history refusing to sit still. Sports franchises move. They rebrand. Sometimes they change identity entirely: St. Louis Rams → Los Angeles Rams (a team that had already been in Los Angeles before, making it a round trip across decades) Houston Oilers → Tennessee Titans Seattle SuperSonics → Oklahoma City Thunder Montreal Expos → Washington Nationals Every one of these forces a modeling decision with no obviously correct answer. Is that one franchise with a changed name, or two distinct entities? Do championships travel with the organization, the city, or the name? If a user searches for the Sonics, should they get Oklahoma City results? Should the URL for a 1996 Seattle roster still resolve in ten years? And then there are the cases that break clean rules entirely. When the Browns organization relocated to Baltimore in 1996, the arrangement kept the Browns name, history, and record book in Cleveland while the relocated organization began as the Ravens. The players moved. The history did not. Any schema that assumes "franchise identity follows the organization" produces wrong answers for that case, and any schema that assumes "identity follows the city" produces wrong answers for the Rams. This is entity resolution, and it is the same class of problem as reconciling customer records across merged systems, except the entities are emotionally loaded and fans will absolutely tell you when you get one wrong. There is no universal rule that resolves every case. What you need is a model expressive enough to represent a franchise as a continuous thread through time, with the name, city, and identity treated as attributes that change along that thread rather than as the thing that defines it. Get that abstraction right and a user can pull up any team from any season and the history holds together. Get it wrong and you spend years patching special cases. Players Are Versioned Entities The bigger realization, and the one that reshaped our schema most, is that a player is not one thing either. When someone says they want to use Michael Jordan, which Michael Jordan do they mean? The 1988 version who had just become the league's best two-way player? The 1996 version anchoring a 72-win team? Those are different players in every way that matters to a simulation. Tom Brady in 2001 is not Tom Brady in 2007. LeBron James has rebuilt his game several times across two decades. Shohei Ohtani is not even one kind of player in a given season; he is a hitter and a pitcher simultaneously, which quietly breaks any schema that assumes a player occupies exactly one role. Every season carries its own experience level, coaching, supporting cast, competition, rule environment, and physical peak. So we model player-seasons rather than players. Each season is its own versioned entity with its own profile, linked to a canonical player identity that ties the career together. The alternative, representing a player by career averages, is the trap. It feels efficient and it destroys the signal. Averaging a career flattens the peak season into the decline years and produces a player who never actually existed on any field. For a product whose entire value proposition is putting specific versions of specific teams against each other, that is fatal. The specificity is the product. This is temporal data modeling, and it is the reason our data layer looks less like a stats table and more like a versioned history. The Rules Keep Moving Underneath the Schema Volume 2 covered why era changes make comparison hard. What I want to add here is why they make the schema hard. The NFL protects quarterbacks in ways it did not in 1985 and plays a spread passing game that barely existed then. The NBA reorganized itself around the three-point line and pace. Baseball has run through the dead-ball era, expansion, the designated hitter, the steroid era, and now a pitch clock. Soccer has changed the back-pass rule and added VAR. Each of these is not just a different environment. Each one changes which statistics exist, how they are defined, and whether they are even collected. That means the schema cannot assume a fixed set of fields across all of history. Some metrics simply do not exist for older seasons. Some are defined differently depending on when they were recorded. A model that expects uniform columns across a century will either reject most of history or silently fill it with garbage. Neither is acceptable, so the rule environment of a season has to be first-class data in its own right, not an assumption baked into the schema. The Historical Record Does Not Agree With Itself Here is the lesson I genuinely did not expect: historical sports data is messy, and not in a way that a good parser fixes. Different providers disagree with each other on the same events. Older seasons carry incomplete information. Advanced metrics that are standard today were never computed for most of history and cannot be reconstructed from what was recorded. Player names are inconsistent across sources, across eras, and sometimes within the same source. Statistics were recorded under different definitions depending on the decade. Which means the hardest engineering problem here is often not acquiring data. It is deciding which version of the truth to trust. That question does not have a technical answer you can look up. It requires deliberate reconciliation policy: source hierarchies, conflict resolution rules, canonical identity mapping so that a name spelled three ways resolves to one player, validation passes that catch impossible records, and explicit handling for gaps rather than silent imputation. It is unglamorous work. It is also the work that determines whether anything above it can be believed. Building a reliable sports platform is not downloading datasets. It is validation, reconciliation, normalization, and a lot of hours cleaning history. The Data Has to Support Variance One design principle shapes almost every decision we make, and it is a data principle before it is a product one. Imagine simulating the 1985 Bears against the 2023 Chiefs. If the Bears win 99 times out of 100, most fans will not believe it. If the Chiefs win 99 out of 100, they will not believe that either. Sports do not behave that way. Real games between good teams are decided by injuries, weather, a bad bounce, a called-back touchdown. So the target is not a single deterministic answer. The target is an outcome distribution with a defensible shape: a favorite that wins more often, an underdog with a real path, and enough variance to make the matchup worth running twice. That has a direct consequence for the data layer. A distribution needs inputs rich enough to generate meaningful variance. Point averages alone give you a point estimate and nothing else. Producing believable spread requires modeling the things that actually create uncertainty in sports, which means the data has to carry far more than season totals. Fans do not want mathematical certainty. If a simulation always returns the same answer, the argument is over, and the argument is the entire reason anyone is here. We Are Building a Sports Data Platform Somewhere in all of this, the shape of what we are building changed. We are not building a simulation engine with a database attached. We are building a sports data platform that understands historical franchises, season-specific player profiles, league evolution, rule changes, coaching eras, team identity over time, and cross-era context. The simulation engine is one consumer of that platform. That distinction matters because the same foundation powers everything else on the roadmap: tournaments, predictions, rankings, creator tools, community discussion, and game modes we have not even announced yet. If the foundation only supported simulations, every one of those would be a rewrite. Engineering for Ideas You Have Not Had Yet A thing I have come to believe building software is that good architecture is less about solving today's problem and more about not foreclosing tomorrow's. Every roster we normalize, every franchise thread we connect across decades, every conflicting historical record we reconcile is an investment in features that do not exist yet. Fan-created tournaments. Cross-era fantasy leagues. Franchise simulations. Career comparisons. Ways of exploring sports history that have not been possible before. None of that works without the foundation. All of it becomes straightforward with it. Looking Ahead The engine is what users notice first. The data platform is what makes the engine possible. In Volume 4, I will get into the architecture itself: how we model franchises, seasons, players, and historical context to support multiple leagues, a century of history, millions of player-seasons, and effectively unlimited cross-era matchups. Less philosophy, more schema. Final Thoughts The most rewarding part of building MicroLeague has been discovering how much history sits underneath every game. Every roster tells a story. Every season reflects an era. Every statistic carries context that the number alone does not show. Building software that respects that history has been the hardest part of this and the most motivating. Could the 1985 Bears stop Patrick Mahomes? Would the 1996 Bulls beat the 2017 Warriors? Which Cowboys team was truly the greatest? We are building MicroLeague to answer those questions, not because we think there is one definitive answer, but because sports are at their best when they start an argument worth having. Follow the Journey MicroLeague Sports is an ongoing experiment at the intersection of sports, data, simulation technology, Web2 communities, AI, and Web3 rewards. We are building in public and documenting the wins, the mistakes, the architecture decisions, and the lessons along the way. Explore MicroLeague 🏀 Create matchups and simulations: microleaguesports.com 🪙 Learn about MicroLeague Coin (MLC): coin.microleaguesports.com Try It Yourself Create your first cross-era matchup, run a simulation, make a prediction, and see who wins: microleaguesports.com Connect If you work in or care about sports technology, data engineering, simulation, AI systems, Web3 infrastructure, or product development, I would genuinely like to hear your feedback. Especially if you have solved entity-versioning problems like these somewhere else. I want to hear how you approached it. Follow the Series Volume 1: Why We Built MicroLeague Sports Volume 2: Building a Time Machine for Sports Volume 3: Why Sports Data Is Harder Than Most People Think Volume 4: Designing a Sports Data Platform (coming soon) Volume 5: From Simulations to Predictions, Rewards, and Community (coming soon) Connect If you work in or care about sports technology, data engineering, simulation, AI systems, Web3 infrastructure, or product development, I would genuinely like to hear your feedback. Discord Instagram X The best sports debates never end. We are building the platform where they begin. MicroLeague SimNation Built on data. Powered by the engine. Owned by the fans.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to