Skip to content
Work

Browser game · baseball

Live invictobaseball.com

Invicto

Draft a 12-player team from real World Baseball Classic rosters, 2006 to 2026, then simulate the tournament.

Invicto simulates a tournament automatically, from pool play through the quarterfinal, semifinal and final. This lineup wins the title with a 6-1 record.
The tournament plays out after the 12th pick.
Role
Solo: design, code, release
Timeline
Jun 2026 to now
Stack
TypeScript, Vite, Cloudflare Workers, D1, Vitest

Live at invictobaseball.com. Public code on GitHub. Status checked Oct 22, 2026.

By the numbers

3,175

player-edition records: 2,524 players, 23 countries, 6 editions

20,000

simulated tournaments per lineup in the difficulty test

4

languages: Spanish, English, Japanese and Korean

31.6 KB

of JavaScript, gzipped, with no UI framework

Fig. 1 From public data to a seeded tournament

Build time

  1. Two public sources Wikipedia roster pages for 6 editions, and the SABR Lahman database.
  2. scripts/build-data.ts Combines and validates the records, preserving each source URL and retrieval time. If a page yields fewer than 16 teams, the build stops.
  3. 3,175 records One per player and edition: 2,524 players from 23 countries.

In the browser

  1. Draft Pick a 12-player lineup.
  2. Team rating A power mean with p = -4 combines contact, power, speed, defense, clutch and pitching. One weak category limits the whole lineup.
  3. Tournament Each roll uses rngFor(seed, ...path), so daily challenge rolls are reproducible regardless of call order.
Keeping the difficulty consistent src/engine/montecarlo.test.ts The test simulates 20,000 tournaments per lineup. An elite lineup must go 7-0 in 2% to 5% of them. A lineup with one weak category must stay below 2%.

The idea

Invicto turns the World Baseball Classic into a seven-game challenge. Roll a country and tournament edition to fill each of 12 lineup slots, drawing from all six Classics between 2006 and 2026. Then try to go 7-0.

What I built

A data pipeline combines Wikipedia rosters and the SABR Lahman database into 3,175 player-edition records. Each record retains its source URL and retrieval time. The build stops if a roster page yields fewer than 16 teams, and the data identifies two-way players automatically.

Team strength uses a power mean with exponent -4 across contact, power, speed, defense, clutch and pitching. That makes a weak category matter to the whole lineup. The game supports Spanish, English, Japanese and Korean. The same Cloudflare Worker uses D1 to record anonymous daily stats, count duplicate results once and store no IPs or cookies.

The hard parts

  1. 01 Imperfect public data

    Rosters, names, positions and stats arrive in different formats. The pipeline joins them, validates each record and keeps every row’s source. When a player record is wrong, I can trace it back to the original page.

    Where in the code: scripts/build-data.ts (opens in a new tab)

  2. 02 Making 7-0 a challenge

    A strong lineup should have an advantage while still facing a difficult tournament. A Monte Carlo test runs 20,000 tournaments per lineup. An elite lineup must finish 7-0 between 2% and 5% of the time; one weak category must bring that below 2%.

    Where in the code: src/engine/montecarlo.test.ts (opens in a new tab)

  3. 03 Consistent daily rolls

    Each random decision uses a stream identified by the seed and a path of labels. Daily players receive the same rolls regardless of the order they reveal them. Reloading the tab resumes the same seeded run.

    Where in the code: src/engine/rng.ts (opens in a new tab)

Before and after

BeforeAfterSource
First screen on a slow connectionThe title screen waited for 1.33 MB of roster data.The title renders first, and the data loads when a run starts.Commit 465db25, September 2, 2026

What’s next

Refresh the roster data after the next Classic.

Invicto draft, round 5 of 12: a South Korea, WBC 2006 roll above the player list, with 12 lineup slots at the bottom.
Invicto results: a share card for a 6-1 championship run, showing the 12-player lineup and each game result above the Share and Copy card buttons.

Try it

Shared approaches