The ultimate basketball sandbox simulator. From the blacktop to the league.
Dwarf Fortress × Football Manager × Crusader Kings, but basketball. A living league where players are characters with arcs instead of ratings, where chemistry is the residue of actual interactions, and where the world remembers everything — so season 30 is more interesting than season 1.
Now at v0.8 — "The Levers": a deterministic, seeded league simulation with a terminal UI, persistent universes, a living market fed by circuits that play real games — and a tactics layer, because decisions decide games. The full vision lives in DESIGN.md.
cargo run --release # the universe menu: continue, load, or start anew
cargo run --release -- 1996 # jump straight into seed 1996
cargo run --release -- --headless 50 # sim 50 seasons, print the league audit
cargo run --release -- --lab BOS MEM 500 # game lab: replay one matchup, print the oddsAn XMOB production. A Ben Porter game. (Any key skips the cold open.)
A universe is a file. Since v0.5, every world you touch lives in
universes/ as a single JSON file — every player who ever existed,
every memory and grudge, every banner, and the exact state of the dice,
so a loaded universe continues byte-for-byte where it left off (unit
tested). The menu lists your worlds newest-first with the club you were
running; autosaves land at every season rollover and on quit (quitting
mid-broadcast sounds the buzzer first — what you watched, happened). God-
mode edits are permanent now: the son you added to the squad is part of
history. Save files are versioned and human-readable — crack one open.
Football Manager, but basketball: pick a club to follow, then SPACE is
your continue button.
| Key | Action |
|---|---|
SPACE |
Continue — finish tonight's slate & roll to the next day (offseason: run the draft & start the next season) |
W |
Sim a week — on the upcoming slate: watch that game LIVE |
S |
Sim the rest of the current phase (season / playoffs) |
1–9 / TAB |
Switch tabs: Club · The Wire · Standings · Scores · Teams · Players · Market · History · Hall of Fame |
↑↓ / jk |
Move selection |
ENTER |
Drill in: game → box score → play-by-play; team → roster → player page |
F |
Follow the selected team (Teams tab) / un-follow (Club tab) |
← / → |
Switch category: leaders (PPG…MVP Race) and Hall of Fame (Legacy…Scoring Titles) |
T |
Tactics (v0.8): the levers — pace, three hunting, defensive pressure, hero ball, rotation depth, practice focus — from your Club tab or any team page |
E |
God mode: edit the open player or team (names, attributes, personality, salary, budget…) |
N |
God mode: create a brand-new player on the open team, straight into the editor |
L |
Game lab: replay the open box score's matchup 100× in a sealed sandbox (R = new batch) |
W |
(in the lab) Watch a replay: one possession at a time (SPACE next · R reroll the possession · E call the outcome yourself) |
B |
In any broadcast view: toggle the side panel between the floor and the live box score — the running numbers as of the possession under the clock |
ESC |
Back |
[ / ] |
Scores tab time travel: [ into past nights, ] into upcoming days (ENTER = pregame sim, W = ride forward and watch live) |
Q |
Quit |
The editor exists for debugging, unit-testing hypotheses ("what does a 99-shooting rookie do to the scoring race?"), and the oldest Football Manager tradition: adding your son to the squad. Changes are live in the sim immediately — and session-only until save/load lands.
Brand identity: every franchise has brand colors (rendered across the Club header, team pages, and box scores) and a named home floor — the Beacons defend The Lighthouse, the Soul play at The Juke Joint. All of it is editable: colors cycle through an 18-swatch palette with a live preview, arenas are free text. Rebrand a franchise mid-dynasty if the mood strikes.
Atmosphere (v0.6.1): walking into a team page is arriving in its city. Every franchise gets a generated logo — emblem, block-letter monogram, founding year, all in club colors — a procedural skyline ("Boston after dark": every city its own shape, windows lit in the franchise accent), and The Building: arena capacity, banners in the rafters, the crowd's mood tonight, and the courtside fixture every city has ("every Toronto has its Drake" — a platinum rapper, a retired legend, tech money; same face, same seat, universe after universe).
The Club tab is the FM seat: record, seed, banners, locker-room mood, upcoming fixtures (or your live playoff series), recent results, and the club chronicle — your franchise's slice of the shared timeline. During the playoffs the Standings tab becomes the live bracket: the road to the banner, round by round.
The Hall of Fame tab is the all-time ledger — every career (active and
retired), sortable the way GOAT debates actually get argued: legacy score,
career points, PPG, seasons, rings, MVPs, scoring titles. Open anyone for
the full nba.com-style career page: season-by-season table, trophy case,
and the story he tells himself. The same top-10 prints in every
--headless audit, so career-shape drift is caught alongside stat parity.
Every game in the Scores tab drills to a full broadcast view: the possession log on the left (game clock, running score, shot descriptions, steals, boards, free throws), and the floor on the right — the home arena drawn with its center-court logo in brand colors, a shot chart that fills in as you scrub through the game, and all ten players positioned on the selected play (solid chips are the offense; the assister or pocket-picker is underlined). The situation panel reads like a scoreboard: period, clock, seconds into the shot clock, team fouls. Box scores carry TO and PF columns; every whistle in the trace is attributed to a defender.
The game lab (L from any box score) is the debugging and
min-maxing chamber: the same matchup replayed 100 times in a sealed
sandbox — win split, average score, margin histogram, and every replay
drillable down to its own play-by-play. The league never sees these
games. Edit a player in god mode, hit L again, and watch the win
distribution move.
And it works before the game exists: press ] on the Scores tab
past the latest results and you're browsing the upcoming slate — the
next day's fixtures with records and arenas in the regular season, the
next game of every live series (real 2-2-1-1-1 home court) in the
playoffs. ENTER runs the pregame sim. From the Club tab, L asks the
manager's question directly: can we beat these guys tomorrow? The odds
board exists before the odds do — and when the tactics layer lands,
this is where you'll rehearse a game plan before playing it for real.
Every night has a shape now: slates run in tip-off order — East
arenas at 7:00–8:30, West arenas at 9:00–10:30 — and the Scores tab's
home position is Tonight: the day's board, finals above the line,
tip-offs below it. The night advances game by game: ENTER sims the
selected game and opens its box score, W takes a seat in the arena
instead, L checks the odds first, and SPACE finishes the night. When
the last game goes final the board stays up for review — SPACE rolls to
the next morning, and the next slate is yours to step through the same
way. A stepped night is byte-identical to a bulk-simmed one; granularity
is free. The playoffs work the same way: the board shows the night's
bracket games with series state ("MEM lead 2-1"), each one simmable or
attendable, and when the last game of a round goes final the round turns
on the spot — step your way through a title run and the confetti falls
the moment the Finals end.
And a future game can be attended, not just predicted. Press W on
any upcoming fixture and time runs forward: every earlier day is played,
the earlier tip-offs on that night's slate go final, and then your
game tips off with the league's own dice — the night literally suspends
between tip-off and the final buzzer (the continue button waits). The
broadcast unfolds one possession at a time, no spoilers. The whistle is
yours: R rerolls the possession under the clock (everything before it
stands; everything after re-simulates), E calls the outcome in two
stages — force the two, the splash, the brick, the cough-up, or the trip
to the line, then point at the man: whoever wants it, or any of the
offense's five by their court number — and the rest of the game
recomputes around the ruling. When you hit ESC,
the buzzer sounds and what you watched is what happened: the result
goes into the standings, the stats, and the timeline as the official
game, god calls and all — final on tonight's board, with the rest of
the slate still pending, yours to sim or attend one by one. Watching
without touching anything produces exactly the game a plain sim would
have — attendance is free; interference is a choice.
The same watching-and-whistle controls work inside the game lab on
sandbox replays (W from the replay list) — there, nothing you do ever
reaches the league. Rehearse in the lab; make history in the arena. The same lab runs headless: --lab BOS MEM 500
prints the distribution; --lab BOS MEM 1 prints a full annotated
trace (shot clock, team fouls) for engine debugging.
- 30 fictional teams, two conferences, 82-game seasons in the NBA's real shape (everyone home-and-away, ten conference opponents four times, four rivals three — 1,230 games a year), 16-team playoffs with best-of-7 series and 2-2-1-1-1 home court.
- Possession-level games with NBA structure — four 12-minute quarters
built from eight 6-minute lineup stints (starters open and close, the
bench carries the middle), five on the floor, 5-minute overtimes,
quarter-by-quarter scores. Usage driven by skill and ego, hot streaks,
full box scores — and Clutch (v0.7.2): when it's late, ice rises (a
real make-probability lift, and the ball finds the men who want it)
while nerves shrink and shake. Higher is strictly better, like every
rating should be; the property is unit-tested. The league's statistical fingerprint
is calibrated to NBA 2023-24 averages —
--headlessprints a parity audit (PTS/FG%/3PA/REB/AST/STL/TOV all within ~±2%). - The Levers (v0.8) — every bench has an identity, and the levers
feed the same possession engine for all thirty staffs and for you.
Pace pulls the night's tempo; three hunting tilts the shot
diet (talent still decides if the diet is wise); defensive
pressure gambles — more steals and forced turnovers, more fouls,
cleaner looks when beaten; hero ball sharpens the usage curve so
the stars eat (movement offenses share the ball and the assists);
rotation depth rides eight men or trusts ten (short rotations
mean heavy minutes — and the injury lottery prices minutes);
practice focus tilts what develops, with a conditioning program
that trades growth for durability and fewer injuries. Press
Ton your club and pull them yourself; NPC coaching staffs adapt theirs every offseason — drifting toward their roster's strengths, shaking something up after a lost season — but never touch yours. The Club page names the identity ("playing junkyard-defense ball, 8 deep"), and the whole loop closes in the lab: change a lever, run the odds, watch the win distribution move, then play the night for real. - Players as characters — generated names with cultural depth, hometowns, personality (ego, work ethic, clutch, charisma), latent vs. scouted ability (the world sees grades, not truth), confidence/morale/form that change nightly.
- The chronicle — a significance-scored news feed: 50-point eruptions, earned nicknames, triple-doubles, win streaks, ego clashes at practice, "we're talking about PRACTICE" podium moments, trade-demand whispers, awards, records, retirements with legacy verdicts.
- The shared timeline — one objective, append-only history of events (who/what/where/when + why: causal links). Every witness files his own memory of an event — salience, valence, and attribution — so the 50-point night lives fondly in the scorer's log and bitterly in the defender's. Relationships are never stored: they're derived by folding a player's glosses over the history he shares with someone. Feuds escalate because remembered clashes raise the odds of the next one; grudges decay slower than glory, slower still for big egos. Player pages show "The Story He Tells Himself"; trade demands print their receipts; the headless audit ends with a full dossier of the final MVP (the DeAndre test, v0).
- The Market (v0.6) — free agency, and the pyramid's front door. Everyone outside the league is in the pool: waived vets, the undrafted ("the chip is permanent"), and a steady stream of circuit talent — the G-League grind, overseas clubs, street-circuit legends the scouts argue about. Rosters carry up to 15 men and cuts are legal until the trade deadline; cut players hit the wire, not the void. When bodies go down, front offices make calls: a team short on healthy players signs the best free agent its own scouts can see to an emergency deal — a minimum contract with everything to prove. Produce and the jersey is yours; otherwise it's back to the circuit. Every call-up is a timeline event and a fond memory ("the phone finally rang").
- The Pyramid plays (v0.7) — the circuits are real places now:
22 clubs on 3 floors (the G-League grind: Reno Highline, Fort
Wayne Forge; overseas: Piraeus Tridents, Belgrade Fortress; the
street: Dyckman Nights, The Hole) playing weekly games at a coarse
simulation distance. Every free agent suits up somewhere and builds a
real stat line — the number your scouts weigh next to their grade.
Circuit crowns and scoring kings land on the news wire and the
timeline ("he ran the G-League and everybody knew it"); a 45-point
circuit eruption travels up ("somebody's scout was in the building").
Kids getting real circuit minutes develop faster than bench-rotters.
Internationals arrive already carrying overseas seasons in their
career table (marked
~), and every call-up keeps his circuit lines forever — the whole climb on one page. Arrow through the Market tab to walk the floors: the pool, then standings and leaders per circuit. - Scouting is a budget line — each franchise spends $0.5–12M/yr on eyes for the circuits, and its read on any free agent is fogged in proportion to how little it pays (the Market tab grades the pool through your club's scouts). Big markets see clearly; small markets sign on reputation. Editable in god mode, like everything else.
- The trade market — every player has a market-priced contract; every team a budget by market size. Trade demands actually get resolved: the front office shops the malcontent until the deadline, deals must match on scouted value (busts move at hype prices) and fit both teams' books, and over-budget teams salary-dump onto teams with cap room. Every trade is a timeline event whose why-links cite the demand that forced it; the moved player remembers it as a fresh start or a betrayal.
- The career arc — three movements: a development phase where a player only gets better (work ethic converts potential into ability), a post-peak slide where the body goes first and the jumper last, and an overextension phase past each man's personal farewell age where everything erodes, steeper every borrowed year. Retirement is earned, not scheduled: it weighs last season's minutes and production, pride (stars would rather walk than wilt on a bench), and the medical file — a legend still putting up numbers plays into his late 30s; a man the league stopped calling reads the silence. There is no hard age wall: the 40s are reachable.
- Injuries — the lottery is drawn after every game, with odds shaped by age, minutes, a latent durability the scouts never see, and the weight of the medical file (each entry makes the next likelier and the farewell sooner). Injured men don't dress — rosters grit through shorthanded nights — and severe injuries (the torn Achilles, the ACL) permanently change the player and echo in his memories. Every player carries a full medical file on his page, and the god-mode editor can dial an injury up or heal a man outright.
- The offseason churn — work-ethic-driven development, retirements, and a draft where teams pick on hype, not truth — so busts and steals emerge for free. Every draft class ships with storylines.
- Continuity — 50 simmed seasons hold league size, talent level, and age
curve steady while dynasties, eras, and records accumulate. Same seed,
same universe:
4187always produces the eight-banner San Antonio run and Ambrose "The Encore" Vandeweghe, six-time MVP. - One name, one man — every full name is one-of-one across the whole universe, all-time. American first names are synthesized from living prefixes and cores (Treshawn, Kedrian, D'Angelo, Jamarcus) instead of cycled from a list, surnames hyphenate (~4%) and carry Jr./III, and the Canadian and German pipelines join the draft pool. A true collision after 24 retries becomes a Jr. — lineage as a fallback. The headless audit counts duplicate full names: zero across 2,000+ players and 50 seasons, with 300+ distinct surnames in circulation. Nicknames are one-of-one too: the media never crowns a second "Blacktop Jesus" while the first is remembered.
Trades and free agency with real mechanics, playoff-specific stats, coaches and GMs as characters, relationship graphs with memory, the pundit/GOAT-debate layer, street/career mode (the Street V3 promise), save/load. See the roadmap in DESIGN.md.
- The play trace is derived, never rolled: clocks, shot coordinates, and foul attribution come from a deterministic hash of sim context, not from the game RNG — so capturing a trace can't perturb the sim, and the same seed keeps producing the same universe it always has (verified by diffing full 50-season audits across engine versions).
- Headless-first:
league.rs+game.rsknow nothing about the UI. The TUI (ui.rs, ratatui) is a bolt-on frontend;--headlessis another. - Deterministic: one
ChaCha8RNG seeded from the CLI. A league is a seed. timeline.rsholds the one global event log plus thin per-player gloss overlays — the DESIGN.md storage shape: personal logs are views, not copies. Sim state is still mutated directly (full event-sourcing is the phase-3 refactor); the timeline is the memory system, not yet the store.- ~2,700 lines of Rust, six modules, zero unsafe.