Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Twenty-Eighty

Play it: twenty-eighty.junkdrawer.works

A baseball front-office game where every player carries the scouts' 20-80 grades. You're the general manager of a made-up club: set the roster, run the farm system, draft, trade, sign free agents and extend your own players, then watch the seasons play out pitch by pitch. Your scouts can be wrong, the owner has goals, and 29 other front offices are doing the same thing you are.

Starting a league: a seed box, the 20-80 scale from 20 Poor to 80 Elite, and the choice of how to play   The front office in late May: the Milwaukee Ironmen 35-20 and first in their division, with the division table and recent games   A 20-year-old shortstop's page: Now 55, Future 65, and each tool's present and future grade as a bar, with a 70 arm

How it plays

  • Two grades for every tool, now and at his peak, on the 20-80 scale. Your scouts see them with an error that shrinks with a bigger scouting budget and more looks.
  • Sim a day, a week, a month or the rest of the season at a pace you can watch, with stops for a losing streak, an injury, a trade offer, the deadline, the All-Star Game or a moment of your own.
  • An All-Star Game and a trophy case: a mid-July break, both leagues' All-Stars in a box score, and each winter an MVP, Cy Young, Rookie and Reliever of the Year, Gold Gloves and Silver Sluggers at every position, and an Executive of the Year (maybe you).
  • A past, a record book and a Hall of Fame: veterans arrive with the careers their service time implies, legends from before the first season hold the records, and moments (no-hitters, cycles, hitting streaks, milestones, records falling) are noticed as they happen.
  • The moves a real GM makes: call-ups and options, the 40-man roster, trades (clubs call you too), the draft, free agency, international signings, arbitration and contract extensions.
  • An owner with goals and a budget, ticket prices and a club's books. Fall far enough short and you're fired, and other clubs call.
  • Easy, Normal or Hard, Basics or Full stats, and a staff who send notes when there's something worth doing.
  • Google Drive, if you want it: keep a copy of your league in your own Drive and pick it up on another device. See Google Drive below.
  • No account and no server. The league saves in your browser, and you can export it to a file. It works offline and installs to a phone's home screen.

Google Drive

In the League office, choose Connect Google Drive. From then on your league also goes to a Twenty-Eighty folder in your Drive: a few seconds after each sim, every half minute after other changes, and when you leave the page. A league is one running simulation, so two copies can't be merged: each device writes its own file per league, such as Milwaukee Ironmen (iPhone).txt, and never touches another device's.

  • On another device, connect the same way (or tap Played on another device? on the title screen). The League office lists every copy with its device and where the league stands, and Continue from this carries on from one. When another device has played further with the league you're in, a note at the top of the page offers its copy. Carrying on from a copy replaces the league on that device; it always asks first.
  • What's in a file: the whole league, compressed (every club, player and season played, your moves and your club's books). Sim speed and the stops stay on each device. A file is a league code that Import save also takes, so you can download one from Drive and load it by hand.
  • A long league's file (past 4 MB, in its fourth season or so) goes up in Google's resumable upload: if the connection drops partway, it asks Google how much arrived and sends the rest. If it still can't finish, the cloud turns red and the next save tries again.
  • Twenty-Eighty asks Google only for drive.file, so it can reach the files it made and nothing else in your Drive.
  • Google signs the page out after an hour. The league keeps saving on the device meanwhile; the cloud in the scoreboard turns red, and one tap signs back in and uploads what waited. The sign-in is kept for the hour under junkdrawer.google in localStorage, the same way as the other junkdrawer.works projects, and it remembers your account for the next one.
  • Stop saving on this device leaves the files in Drive and doesn't revoke Google's permission, which would sign every project out. To take the permission back, remove junkdrawer.works under Third-party apps & services in your Google Account.
  • It works only at https://twenty-eighty.junkdrawer.works (and https://junkdrawer.works, its old address), where Google accepts the shared OAuth client (GOOGLE_CLIENT_ID in web/src/ui/drive.ts). Anywhere else (a local copy, the Artifact copy) the Drive controls don't appear.

Running it

The game is TypeScript: the engine is in src/ and the browser UI (Preact) in web/. GitHub Pages serves a built copy from the repo root (index.html and assets/), so rebuild it after changing the code.

npm install
npm run dev                 # play it locally with live reload (http://localhost:5173)
npm run build:site          # rebuild index.html and assets/ at the repo root: what Pages serves
npm test                    # the engine's test suite (vitest)
npm run test:e2e            # plays the built site in Chromium, then Drive saving against a fake Google (needs Playwright)
npm run screenshots         # redraws docs/*.png and og.png
npm run icons               # redraws the PNG icons from icon.svg
npm run build:artifact      # one page for the Artifact viewer (fonts inlined, no downloads)

The engine also runs from the command line:

npm run sim:season                 # a full season (majors + 4 affiliate levels): standings, playoffs, awards, leaders
npm run sim:season -- --team BOS   # ...plus one club's transaction log and end-of-season organization
npm run sim:game -- DEN BOS        # one game, with a box score
npm run scout -- NYE               # an organization's scouting report on the 20-80 scale
npm run grade-chart                # what each grade means in stats (see below)
npm run calibrate                  # compare a simulated season to real MLB
npm run sim:years -- --years 10    # many seasons, offseasons included, watching for drift
npm run pipeline                   # what one year's draft and international class becomes, vs. a league's age profile

Every universe is generated from a seed (--seed my-league), so the same seed always produces the same players and the same season.

To put it online with GitHub Pages: Settings → Pages → Build and deployment → Deploy from a branch, then pick main and / (root).

Files

  • src/: the engine (players, organizations, the pitch-by-pitch game, seasons, the offseason, scouting, money). See Project layout below.
  • web/: the browser UI. The simulation runs in a Web Worker (web/src/worker/), and the league is saved in IndexedDB.
  • index.html, assets/: the built site, from npm run build:site.
  • web/src/ui/fonts/: Barlow Condensed, IBM Plex Mono and Source Sans 3 (SIL Open Font License), served from here so nothing loads from elsewhere.
  • web/src/ui/drive.ts, web/src/ui/sync.ts: Google Drive saving (sign-in, uploads, listing, opening a copy), adapted from Terraville's.
  • sw.js: keeps a copy for playing offline. On a connection that isn't answering, a page opens from that copy after four seconds. FILES in it is a fingerprint of the files it keeps: change one and npm run test:e2e says what to set it to, so a release never leaves the copy half old.
  • icon.svg, icon-*.png, manifest.webmanifest, og.png: the icon, home-screen install and link preview.
  • tools/: the screenshot and icon scripts. test/: the engine tests, e2e.mjs for the site, and drive.mjs, which plays five devices sharing a fake Google Drive (fake-google.mjs), one with a long league.

Under the hood

A baseball front-office simulation in the spirit of Baseball Mogul: you're the GM (and a little bit the owner), you build the roster and the farm system, and the seasons play out in a simulation.

Two things are different from the late-90s version:

  • Every player is graded the way modern scouts grade them: on the 20-80 scale, as present/future grades (45/60), with a hidden "true" talent underneath.
  • The stats are modern: wOBA, xwOBA, wRC+, OPS+, FIP, xFIP, SIERA, ERA-, exit velocity, barrels, whiff and chase rates, and FanGraphs-style WAR. The constants behind them (wOBA weights, FIP constant, runs per win, park factors) are re-derived every season from the simulated league's own run environment.

Playing in the browser

npm run dev starts the game; npm run build:web builds a static site into web/dist that any web server can host. Pick a seed, a club and how you want to play, and you're the GM:

  • How you want to play (chosen at the start, changeable any time in the League office): a challenge level, how much stat detail to show, and whether your staff sends advice.
    • Easy: your scouts see players almost as they are, other clubs deal at close to fair value (a 2% edge instead of 10%), the budget is 15% bigger, win targets are two games lower, and the owner starts more confident, forgives twice as much and can't fire you in your first three seasons.
    • Normal is the game as designed.
    • Hard: your scouts miss by 30% more and rivals' by 20% less, clubs want a 20% edge in a trade, the budget is 10% smaller, win targets are two games higher, and the owner is quicker to lose patience.
    • Basics stats show the familiar numbers (AVG/OBP/SLG, HR, RBI, SB; W, SV, ERA, WHIP) with a toggle for the rest, and put words beside grades ("60, plus"); Full shows everything. Either way, hovering a column header explains the stat.
  • Staff advice: your assistant GM, scouting director, analytics director and business office leave notes on the Staff page (the latest also show in the front office) when there's something worth doing. They flag a player left on the active roster while hurt or an illegal roster, a minor leaguer who'd help now, the weakest spot on the club a month from the deadline (or which rentals to sell if the race is gone), a unit ranking near the bottom of the league, luck that's due to turn (ERA against FIP, wOBA against xwOBA, once the analytics department is good enough to see it), crowds short of the owner's goal, and spending over budget. In the winter they size up tender decisions, the draft board, free agents who'd fill the biggest hole, and international targets. The advice comes from your own scouts and analysts, so it's only as good as they are.
  • Scoreboard sims: play a day, a week, a month or the rest of the season, then October. A full season with all four affiliates takes about 15-20 seconds, running in a Web Worker so the page stays responsive. Set the speed to Steady (about a month in 12 seconds) or Slow (about a week in 8) and whatever page you're on follows along day by day, so you can watch the standings and stop (the button or Esc) when you see a losing streak; the speed can change mid-sim and is remembered. Stops pause the sim by themselves when your club loses a set number in a row, a big leaguer goes down for injured-list time, a club makes you a trade offer your staff doesn't pass on (or any offer, if you'd rather), it's trade deadline day (with the day still to play), the All-Star Game has been played, one of your players has a moment (a no-hitter, a cycle, a milestone, a record), or your staff has something urgent, and a note says why it stopped. Offers that don't stop the sim still wait on the trade desk.
  • Front office: record, division race, recent games, club leaders, injuries, your top prospects, your moves, and the league's headlines: trades, extensions, big free-agent deals, top picks and top prospects' debuts, injuries that change a season. The full wire (every big-league move, or everything down to minor league shuffles) is a click away. For two weeks after the All-Star Game, its score, MVP and your All-Stars, and after the owner's midseason letter, the letter. Around the league lists the latest moments.
  • Moments, noticed game by game: no-hitters and perfect games (combined ones too; nine innings or more without a hit), cycles, four-homer games, 17-strikeout games, hitting streaks from 25 games on, career milestones (300/400/500 homers, 2,000 hits, 200 wins, 2,500 strikeouts, 300 saves and up), 50-homer and 20-win seasons, and records tied or broken, in October too.
  • The league's past. Veterans arrive with big-league careers from before the first season, as long as their service time says, drawn from their grades as they were at each age (a gentle aging curve back from today) at rates fit to the engine's own seasons (scripts/prior-fit.ts); a player's page shows them as one "before the league" line. Two dozen fictional legends played before that: they hold the records (set just above what the engine's best seasons reach, so one falls now and then) and they're the Hall of Fame's first members.
  • Record book: the ten best single seasons and careers in home runs, RBI, hits, steals, average, WAR and the longest hitting streak, and wins, strikeouts, saves, ERA and WAR, league-wide or for one club, legends and this season (still being played, with an asterisk) included; and every moment, year by year.
  • Hall of Fame: each winter the writers vote on players who retired two winters before with ten or more seasons and a real case. They weigh WAR, MVPs, Cy Youngs and All-Star teams, and the round numbers (500 homers, 3,000 hits, 300 wins, 3,000 strikeouts, saves); 75% gets a player in, under 5% or ten years on the ballot and he's off. The season review names the new class; the Hall shows the ballot and every member's plaque.
  • The All-Star Game: the schedule breaks for four days from July 14 (no series runs across it), and the game is on the second. The fans vote in the starters at each position (first-half WAR, with a thumb on the scale for homers and average), the best of the rest fill out the bench and the staff (seven starters, five relievers), and every club sends someone. It's played at a host club's park, which rotates, as an exhibition: pitchers work an inning or two and the reserves take over in the sixth. The box score, the MVP and who else was named are on their own page; it counts toward no one's stats, wear or injuries, only the record ("2026 Federal League All-Star").
  • My club: the active roster, injured list and every affiliate, with each player's present grades, projected WAR (for big leaguers and anyone close) or future value (for prospects), the overall Now grade (on tables with prospects in them; a table of big leaguers leads with wins), 40-man status, options left and service time. Switch the tables between the scouting report, the club's last 15 games, this season's stats (AVG/OBP/SLG, wRC+, xwOBA, fielding runs and WAR for hitters; ERA, ERA-, FIP, K%, BB%, WHIP and WAR for pitchers) and last season's, sortable, with standout numbers tinted and small samples faded. Every legal roster move (call up, select a contract, option, DFA, injured list, promote or demote within the farm, release) is a click away; moves that aren't allowed say why and where to make room, and a call-up or a designation says how it turned out (where he plays, who claimed him).
  • Depth chart: set the lineup, rotation and bullpen order yourself, or leave it to the manager. Change the lineup or the pitching staff and that part is yours while the other stays his; while someone you placed is hurt or sent down, the manager fills in, and he gets his spot back when he returns. An assistant GM can handle injuries and call-ups until you turn him off.
  • Player pages: the scouting report as present/future 20-80 bars, pitch mix, defense by position, and stats by level.
  • Stats: sortable leaderboards for every level, with qualified/club/position filters and the season's own run environment (wOBA weights, FIP constant, runs per win).
  • Scores and box scores, and standings with the wild-card race.
  • October, game by game: twelve clubs, best-of-3 Wild Card Series, then best-of-5, best-of-7 and the World Series. Play your club's next game, finish a round, or run to the end. Every club carries a 26-man playoff roster from its 40-man (at most 13 pitchers, and a manager's own pick has 12 or 13). The managers pick their rosters and rotations day by day, as players heal, and yours does too until you set your own roster and rotation order. Each series starts from the top with whoever is rested, and the other starters work in relief. Injuries keep healing through October, and a player on the injured list can make the playoff roster: he plays once he's healthy. The October page shows your next game with both probable starters, your series game by game with a line on each (walk-offs, comebacks, shutouts, a starter's gem, a two-homer night) and a box score, the bracket as it fills in, and MVPs for the League Championship Series and the World Series.
  • The offseason, a phase at a time: the season in review (awards, who developed by your scouts' read, who retired), the tender deadline for your arbitration cases (each with the stat line he takes into the hearing, and what his saves added to the award), the draft (pick when you're on the clock; finishing it lets your scouting director make the picks you haven't, and asks first), eight weeks of free agency (make offers; players take the best one that clears their price), international signings against your bonus pool, and spring training. Then the next season starts.
  • Trades at any time before the July 31 deadline or in the winter: pick players from both sides and the other club tells you whether it would say yes (and roughly how much more it wants if not). Until the tender deadline, trades (and the payroll) go by next season's contracts: a veteran whose deal is up can't be traded, and an arbitration case counts at his award and takes his tender to his new club. Clubs call you, too: a contender asks for one of your veterans who fills a hole on its roster, or a seller shops you one of theirs who fills one on yours, each valuing players through its own scouts (so an offer can be a steal or a trap). Interest spreads around: nobody is asked about twice within three weeks (or twice in a winter), and players who've come up before come up less. Offers stand for three days (a week in the winter); accept, decline, or load one into the builder to counter. Either side can be filtered to the 40-man roster (a dot marks who's on it). A deal that would put you over 40 still works: Make room lists your 40-man players, and whoever you mark to designate for assignment (or, in season, option to AAA to clear an active spot) goes when the deal does. An offer that needs room loads into the builder to pick. Every player in a deal shows his line this season (last season's early on). With staff advice on, your staff weighs in on each offer and on the deal you're building: take it, worth a look, or pass, with the value by your read, what it does to this season's race, where the newcomers would play and who fills in, the money, and how well your scouts know the players (so a thin scouting department is less sure, and says so). To start a deal from another club, use Trade with on its page, Trade for on a row of its roster, or Trade for him on a player's page. On the block lists the veterans that clubs out of the race are shopping (in the winter, the weakest clubs), by position, with how many wins each would add over who plays there for you now by your read. What would they want? asks the club for its price: the young players of yours it likes best, up to what it wants for him. That's their ask, not a bargain; it loads into the builder to haggle from, with your staff's take. When your staff says a trade could shore up a spot, it names the best fits on the block and links to them.
  • Extensions: your players' pages show what each would sign for at every length, and what the deal is worth by your front office's read; the Payroll tab lists your best candidates, and your assistant GM brings one up in the winter and in spring. AI clubs deal with each other from late May, busiest in the last three weeks before the deadline, and those trades show up on the wire. Payroll shows every contract, your budget and future commitments; History keeps champions, and each season's awards and All-Star Game.
  • Awards, voted each winter in both leagues. The voters start from WAR and lean the way real ones do: MVP voters like homers, RBI and a contender; Cy Young voters like innings and ERA; Reliever of the Year goes to the saves. A Gold Glove at each fielding position goes to the regular with the most fielding runs (a little reputation helps), a Silver Slugger at each lineup spot (DH too) to the best bat. Executive of the Year goes to the front office that most outran its payroll and last season, with a bonus for October. A player's page folds his honors into one line each ("3× All-Star (2026, 2027, 2029)").
  • Finances: this season's books (gate, concessions, media, sponsorship, national money, postseason gates; payroll, dead money, departments, operations, bonuses), ticket pricing with a projection of crowds and gate at every price, fan interest, year-by-year history and a league-wide table.
  • The owner: goals for the season with live progress, the owner's confidence in you, letters from the owner's office and each winter's review. Get fired and three clubs call.

The game autosaves to the browser (IndexedDB) after every sim and roster move, and a save can be exported to a file and imported again from the League office page. Saves resume exactly: the same seed and the same moves play out the same way, whichever pages are open while the days play.

The 20-80 scale

50 is major-league average, and every 10 points is one standard deviation of MLB talent. The engine works in those standard deviations (z = (grade - 50) / 10), and the UI shows grades. Scouts report in 5-point steps.

Grades are causes in the simulation, not labels pasted on afterward. npm run grade-chart takes a hitter with every tool at 50, changes one tool, and plays 30,000 plate appearances against the league's real pitchers. It's the same kind of translation table scouts carry around:

Grade Hit: AVG / K% Raw power: HR per 600 PA / ISO Eye: BB% Run: BABIP
80 .302 / 13.8% 47 / .328 11.9% .341
70 .284 / 16.5% 34 / .253 11.1% .323
60 .267 / 19.2% 26 / .200 9.6% .307
50 .243 / 23.3% 18 / .143 8.2% .303
40 .223 / 27.6% 11 / .100 6.8% .290
30 .189 / 34.1% 8 / .071 5.2% .279
20 .163 / 40.8% 4 / .046 3.9% .277

The one-number overall grade is a prospect list's tool, though. Once a player is in the majors (or at AAA, or has been up), what a club wants to know is how many wins he's worth, and the overall grade bunches up there: nearly every reliever reads about 50, whether he's a closer or a mop-up man. So big leaguers lead with projected WAR (Proj): wins over a full season in his role, by your own read. It's the same belief as the Now grade (scouts blended with analytics, blurred by how good your departments are) in units that separate players. Prospects (rookie-eligible, under 45 days of big-league service) keep FV, the grade of their projected peak. The tool grades stay on 20-80 for everyone.

Pitchers work the same way. Each pitch in the arsenal gets its own grade (a 70 slider is a whiff pitch). Control is throwing strikes: walk rate runs from about 20% at a 20 to 4% at an 80. Command is hitting spots within the zone: fewer middle-middle mistakes, more painted corners. Stamina sets how deep a pitcher can go.

Hitters have six tools: Hit, (raw) Power, Eye, Run, Field and Arm. Swing path (ground-ball vs. fly-ball) and pull tendency are traits rather than grades. Defense is graded per position from the glove, arm and speed tools, with a penalty for playing out of position.

Organizations

Every club runs a full organization: the 26-man active roster (28 from September 1), a 40-man reserve list, and four affiliates (AAA, AA, High-A, Single-A) of about 26 players each. That comes to roughly 4,000 players, each with present/future grades, an age, MLB service time, option years, and a hidden durability trait. The affiliates play their own 162-game seasons on the same calendar, with their own standings, stats and parks (some Triple-A parks sit at altitude). Minor leaguers are graded on the same major-league scale, so an A-ball prospect might be a 40/60.

The roster rules are the ones a GM has to work around:

  • Only 40-man players can be active; selecting a minor leaguer's contract uses a 40-man spot.
  • Optioning a player burns one of his three option years (once per season). He has to stay down 10 days (15 for pitchers) unless he's replacing an injured player. Veterans with five years of service can't be optioned.
  • Injured list: 10-day (position players), 15-day (pitchers), 60-day (frees a 40-man spot).
  • Designating a player for assignment exposes him to waivers. Other clubs claim him if he beats their weakest 40-man player and his salary fits in their budget; otherwise he's outrighted to AAA, and an arbitration salary or a guaranteed deal stays on your payroll. Your own club claims other clubs' castoffs only while your assistant GM runs the roster.

Injuries strike during games. Pitchers face a per-pitch and per-appearance risk, hitters a per-plate-appearance risk, and both scale with age and the durability trait. Durations come from a catalog that runs from a day-to-day bruise to Tommy John surgery.

In-game substitutions: pinch hitters in close late games (for a platoon or talent edge), pinch runners for slow runners representing the tying or winning run, defensive replacements protecting a late lead, and forced replacements when someone gets hurt.

AI front offices run every club each day. They put injured players on the IL and call up replacements, activate players when they heal (optioning or DFA'ing someone to make room), make weekly swaps between the majors and AAA, and promote players up the farm system every couple of weeks, a starter for a starter and a reliever for a reliever, so every affiliate keeps a rotation. When injuries or call-ups leave an affiliate short of 13 healthy pitchers, a big-league staff, they bring one up from the level below or sign a minor leaguer. They judge players by their grades blended with actual production, so a slumping veteran can lose his job and a hot prospect can force his way up. A typical season has about 530 IL placements, 780 call-ups and 220 designations for assignment (about half of them claimed off waivers), all in a transaction log.

The offseason

Every winter the whole universe moves forward a year:

  • Development and aging. Each tool has its own aging curve: speed peaks around 24 and fades first, contact and power peak around 27 and hold for a few years, plate discipline and command keep improving into the 30s. Growth is front-loaded: young players close most of the gap to their future grades between 19 and 24, so good prospects reach the majors in their early twenties, as real ones do. Projections drift (down on average, since most prospects fall short of their upside), so some prospects break out and more of them stall. Future Value (FV) is the grade of a player's projected peak under that model, an honest forecast rather than a ceiling.
  • Contracts. Players with under three years of service make near the minimum; three to six years go to arbitration each winter; six or more can become free agents. Arbitration reads the back of the baseball card, not WAR: last season's average, homers, RBI and steals, a pitcher's wins, innings, strikeouts and ERA, and a reliever's saves, each worth about 0.06 wins in the hearing though they're worth nothing in WAR. The case is fit to WAR over simulated seasons (scripts/arb-fit.ts) with RBI and wins given the weight arbitration gives them, and totals count, so a player who missed half the year has half a case. The award is about 22%, 38% and 58% of what the case (three parts last season, one part grades) would fetch on the open market. So a 35-save closer costs several million more than the setup man with the same line, and a slugger more than the glove man worth the same wins: think about who closes before his arbitration years. Clubs weigh a reliever's wins up for leverage when they decide whom to tender. A win costs about $8M on the open market, and projected WAR comes from the grades through coefficients measured from the engine. Each club's budget is set by its owner from what the club takes in (see Finances and owners below); a released player's guaranteed money, or the rest of his arbitration salary, stays on the books as dead money (a non-tender owes nothing).
  • Extensions: any time, a club can buy out a player's arbitration years (and some of his free-agent years) with a guaranteed deal of two to eight years, once it covers at least one season past the pre-arbitration scale. He asks, season by season, for what he'd otherwise make: the minimum, arbitration awards, then his free-agent price, less a discount for the security (8% on arbitration years, 2-8% on free-agent years, more the further off free agency is), spread evenly over the deal. His side knows him about as well as his club does, so a deal that sees him as he is gains the club only a little (about 10% of the money); what makes a deal is being right about him, and the club carries the risk if he fades or gets hurt. Players within two years of a deal's end can talk; about a third of players a season from free agency would rather test the market. In season the deal starts at once; once the regular season is over, talks wait for the offseason. AI clubs sign 15-25 extensions each spring, with the players their own scouts like.
  • The draft: ten rounds in reverse order of the standings, high schoolers (18, raw, the most room to grow) and college players (21-22, closer to ready).
  • Free agency: every free agent asks for years and salary from his projected WAR over the deal. Clubs with a hole he fills and budget room bid; his price softens week by week. Leftover veterans take minor league deals or retire. Unless you run your roster yourself, your assistant GM bids for your club too, with the money and 40-man spots your own offers leave, and each week ends with what you signed.
  • International signings: 17-year-olds with bonus pools that are bigger for worse teams. Each asks a bonus from how the market reads him (a league-average scouting department's look), not from what he'll really become, so the price is a second opinion, not an answer.
  • Trades are valued by surplus: projected WAR over the years a club controls a player, priced at $8M a win and discounted 10% a year, minus salary. A cheap young star is worth a fortune; an aging star on a big deal can be worth less than nothing. AI contenders buy veterans from rebuilding clubs with prospects, too, though an AI club doesn't trade a free agent it signed that winter until June 15, or offer him to you or put him on the block before then (your trade desk can still ask for him).
  • Spring training heals most injuries, trims every 40-man roster and adds each club's best prospects to it, sets an Opening Day 26 and sorts each farm system by ability (with age floors), hitters, starters and relievers each against their own kind, so every affiliate opens with a rotation and a bullpen (Triple-A, where call-ups come from, with the farm's extra starters).

A universe has to look the same in year 10 as in year 1, so each winter re-centers the grades (50 stays major-league average) and npm run sim:years plays many seasons to check. The talent pipeline is tuned so that one year's intake (the draft plus an international class), followed through its career, looks like one generated league's cross-section: as many players above the big-league line, in the same age mix, with as many stars (npm run pipeline shows the comparison). Over twelve-year runs the run environment stays at 4.2-4.4 runs a game, home runs near 3% of plate appearances, players 25 and under keep 100-280 big-league jobs (the low point comes in years 6-8, as the generated universe's young players give way to drafted ones), there are 5-10 stars (70+) at a time, and the winter re-centering stays near zero.

Scouting and analytics

No club, including yours, sees a player's true grades. Every club sees each player through its own scouts: the true grade plus an error. That error depends on:

  • the scouting department, five levels from "bare bones" ($2M a year, a typical miss of ±7 grade points on a tool) to "elite" ($13M, ±2.9);
  • familiarity: your own organization is read best (less than half the miss), then other clubs' big leaguers, their upper and lower minors, college draft prospects, and, hardest of all, high schoolers and 17-year-old international amateurs. A player signed out of the draft or the international pool is read as he was on the board until his first season, so his page agrees with the board when he signs;
  • the tool: speed and arm strength are easy to clock, the hit tool and command are the hardest reads, and projecting the future is harder than grading the present;
  • looks: send a scout from a player's page, the draft board or the international list. Each look narrows the report (one by about a fifth, five by about half), and two turn up his medical history. Looks refill weekly, and each winter phase.

The errors come from a stable hash of club, player and tool, so a report doesn't flicker from day to day; part of each error is redrawn every year.

Scouts also know what players like him usually are (for an 18-year-old, for a Double-A regular, for a big leaguer), and a report is their best estimate, not just what they saw: the blurrier the look, the more each grade is pulled toward the usual one. Without that, the top of every draft board would be the players the scouts happened to overrate. The best amateurs in a class grade out around 55-60 FV, the way real prospect lists read, and across a board they turn out about as good as they looked. A few looks let a report stand further from the usual.

The analytics department reads players from what they've done, translated to the major-league scale. Better departments trust better metrics: SIERA and xwOBA, which settle faster and say more about skill, instead of runs allowed and wOBA. Your "Now" grade blends the two, leaning on analytics as the sample grows, up to 90% for the best department. A ▲ or ▼ on a roster table means the numbers have pulled your read noticeably away from the scouts'. The scouts alone project the future.

AI clubs decide the same way, each through its own departments: draft boards, international signings, free-agent bids and trade evaluations all run on beliefs, so clubs disagree and misjudge players, and a player your rivals overrate is one to sell. The simulation itself always runs on the true grades.

Department costs come out of the same budget as payroll, and budgets are set in the winter (or before your first Opening Day).

Finances and owners

Every club keeps books (all money in millions):

  • The gate: every home game draws a crowd from the size of the market, fan interest, how the club is playing (its record, regressed toward .500 early on), the night of the week and the month, and the ticket price, capped by the ballpark (36,000-47,000 seats). Home openers and postseason games sell out; postseason tickets go for 2.2 times the usual price. Each fan also spends $22 on concessions and parking.
  • Media and sponsorship scale with the market and, a little, with interest; the national contracts pay every club $130M.
  • Expenses: payroll and dead money (paid through the season), the scouting and analytics departments, operations (the farm system, the ballpark, travel: about $150-190M, more in bigger markets), and draft and international signing bonuses (a first overall pick gets about $10M; late picks $150K).

Big markets take in about twice what small ones do ($260M against $500M a season). Ticket prices trade crowds for dollars: the business office charges the going rate for the market when the park has room, a bit more when it sells out, and never more than 10% over. Charge more than that and fans notice. Fan interest (1.00 is typical) moves each winter with the season: winning, the postseason and full houses build it; losing and gouging wear it down.

Each winter the owner sets next season's budget (payroll, dead money and the departments): a share of what the club expects to take in after operations and bonuses, eased in from this year's. How big a share depends on the owner: win now (spends it all), balanced, patient builder and frugal. Profit builds a cash reserve; the owner takes anything above $100M, and a club deep in the red gets its budget cut. Nothing stops you from spending past the budget in free agency, but the owner notices.

Your owner sets two or three goals each spring, measured against a preseason projection of your club so a rebuilding team isn't asked for a pennant: win so many games, reach the postseason, turn a profit, draw so many fans, give young players regular big-league work, or stay within budget, depending on the owner's style. After the World Series the owner reviews the season (the goals, wins against the projection, the postseason, spending and big losses) and moves their confidence in you (0-100, starting at 60; high confidence loosens the budget a little, low tightens it). Fall below 20 and you're fired (a first-year GM gets more rope). Three clubs with openings will call, and you pick up the winter with the new one.

How a game is simulated

Every pitch is simulated:

  1. Pitch and location. The pitcher picks a pitch from his arsenal, weighted by the count, then a location from the Statcast "attack regions": heart, shadow (edges), chase, or waste. Control and command shape that distribution; so does the count (behind 3-0 he comes in; ahead 0-2 he expands).
  2. Swing decision. The hitter's Eye governs chasing. The count matters: take on 3-0, protect with two strikes.
  3. Contact. Whiff probability depends on the hitter's Hit tool against the pitch's grade and type. Contact can be fouled off or put in play.
  4. Batted ball. Exit velocity (raw power, squared-up contact, pitch quality, location) and launch angle (swing path, pitch type) produce a batted ball with a spray angle.
  5. Physics-lite fielding. Carry distance and hang time are checked against the park's fences and wall heights, and altitude helps (hello, Denver). Nine fielders with real positions and acceleration-limited range try to make the play, and an uncaught ball becomes a race between the batter and the relay throw.
  6. Everything else. Baserunning decisions, steals against the catcher's arm, wild pitches, errors, double plays, sac flies, and a manager who handles lineups, rest days, a five-man rotation and a bullpen with roles and fatigue.

Because every batted ball comes with probabilities for each outcome, the same model yields expected stats (xwOBA from exit velocity and launch angle) and defensive runs (this fielder vs. a league-average fielder on the same ball).

Calibration

npm run calibrate simulates full seasons and compares the league with rounded 2024-25 MLB numbers. Current results (three simulated seasons, 7,290 games):

Sim MLB Sim MLB
Runs/game 4.42 4.40 Avg exit velo 89.1 88.7
AVG / OBP / SLG .247/.318/.403 .244/.313/.401 Hard-hit % 38.5 38.5
K% / BB% 22.6 / 8.5 22.4 / 8.3 Barrel % 7.5 7.8
HR% of PA 3.03 3.00 GB / LD / FB % 43.6 / 25.1 / 22.5 43 / 24.5 / 24.5
BABIP .296 .291 SB per game / SB% 0.76 / 77% 0.72 / 79%
Pitches per PA 3.99 3.90 Errors per game 0.47 0.52
Swing % / CSW % 48.2 / 27.7 47.3 / 28.2 SP innings per start 5.1 5.3

It also checks the spread, which is what makes the 20-80 scale honest. A typical season has a 50-56 HR leader, a batting champion around .330, a 250-strikeout ace, a top position player around 8-9 WAR, and team records from about 60 to 100 wins.

The wOBA weights the sim derives from its own run environment (BB 0.73, 1B 0.85, 2B 1.25, 3B 1.70, HR 2.11) land close to FanGraphs' real ones.

Modern stats

  • Hitting: AVG/OBP/SLG, ISO, BABIP, K%, BB%, wOBA, xwOBA, wRC+, OPS+, chase%, whiff%, zone contact%, average exit velocity, hard-hit%, barrel%, sweet-spot%.
  • Pitching: ERA, FIP, xFIP, SIERA, ERA-, FIP-, WHIP, K%, BB%, K-BB%, whiff%, CSW%, chase%, zone%, GB%, EV/hard-hit/barrels allowed, xwOBA allowed.
  • WAR: position players get batting runs (park-adjusted wRAA), baserunning (wSB), fielding (range plus errors vs. an average fielder, catcher framing, catcher arm), a positional adjustment, a league adjustment and replacement level. Pitchers get FIP-based WAR with dynamic runs per win. Totals follow the FanGraphs convention of 1,000 WAR per 2,430 games, split 57/43.
  • Run expectancy: a 24-state RE matrix from every plate appearance, which also supplies the linear weights.

Project layout

src/
  core/         seeded RNG, 20-80 grade helpers, math
  players/      player types, generator (value targets + tool mixes), development and aging, names, defense, injuries
  league/       fictional 30-team universe, parks, organization generation and re-centering
  org/          roster rules, AI front office, depth charts, valuation, contracts, trades
  scouting/     each club's view of players: scouting error, analytics reads, beliefs
  finance/      club books, attendance and ticket prices, owners, goals and the GM's job
  advice/       the user's staff: notes and suggestions drawn from the club's own beliefs
  sim/          the engine: pitch model, batted-ball physics, game state machine, substitutions
  season/       schedule, multi-level season runner, standings, pitcher workload, postseason
  stats/        stat lines, run expectancy / linear weights, advanced stats and WAR
  calibration/  MLB targets, league and spread reports, the grade chart
  report/       text renderers for the CLI
  offseason/    the winter: season history, draft, free agency, international signings, spring
  save/         save games (exact resume) and migrations
web/            browser UI: Preact pages, a simulation Web Worker, IndexedDB saves
scripts/        sim:season, sim:game, scout, grade-chart, calibrate, probe
test/           vitest suite

Every tunable number in the engine lives in src/sim/constants.ts (plate discipline, contact, running) or at the top of src/sim/battedBall.ts (fielding and physics).

Roadmap

  1. Pitch-by-pitch engine and calibration harness
  2. Player generator with 20-80 grades, full schedule, standings, stats
  3. Minor leagues, 40-man roster, call-ups and options, injuries, in-game substitutions, AI front offices
  4. A browser UI: sortable stat pages, player cards, roster management, league history
  5. Offseason loop: aging and development toward future grades, contracts (pre-arb, arbitration, free agency), the draft, international signings, AI trades valued by surplus WAR
  6. The GM's-eye view: noisy scouting reports whose accuracy depends on your scouting staff, an analytics department, finances and owner goals (attendance, ticket prices, revenue-driven budgets)

A note on the universe

The clubs, players and parks are fictional (real cities, invented teams). Nothing here uses MLB or MLBPA marks, and names that collide with well-known real players are re-drawn.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages