* docs: plan NLL preseason simulator * Add NLL Season + Playoffs Monte Carlo simulator 14-team regular season (18 games) projects top-8 via Elo + decaying projectedWins prior (adjusts for games already played). Playoff bracket: QF single-game (1v8, 2v7, 3v6, 4v5), SF and Finals best-of-3. Three runtime modes: bracket-aware → known-seed → regular-season projection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
11 KiB
NLL Box Lacrosse Simulator Implementation Plan
Goal
Implement National Lacrosse League (NLL) box lacrosse as a preseason-draftable sport. Brackt leagues should be able to draft NLL teams before the regular season starts, while the simulator projects both:
- the 18-game regular season and top-eight playoff qualification; and
- the NLL playoff bracket: single-elimination Quarterfinals followed by best-of-three Semifinals and best-of-three NLL Finals.
This is not a playoff-only MVP. The simulator must support all NLL teams in preseason and in-season states, then converge naturally to bracket-only behavior once the playoff field is known.
Current NLL format assumptions
Use these assumptions until the league changes format or an admin overrides season config:
- NLL uses unified single-table standings.
- The league has 14 franchises.
- Each team plays an 18-game regular season.
- Top eight teams qualify for the playoffs.
- Quarterfinal matchups are
1 vs 8,2 vs 7,3 vs 6, and4 vs 5. - Quarterfinals are single elimination.
- Semifinals and NLL Finals are best-of-three series.
- Bracket arms should be fixed as
(1 vs 8) winner vs (4 vs 5) winnerand(2 vs 7) winner vs (3 vs 6) winner, matching the published NLL bracket layout.
References checked May 12, 2026:
- Official NLL playoff bracket page: https://www.nll.com/playoff-bracket/
- Official 2026 playoff schedule/format announcement: https://www.nll.com/news/national-lacrosse-league-unveils-2026-playoff-schedule-and-quarterfinal-matchups/
- Official unified standings/playoff structure announcement: https://www.nll.com/news/nll-adopts-unified-standings-format-and-updated-playoff-structure-for-the-2023-24-season/
- Official NLL standings page: https://www.nll.com/standings/
Architecture fit
Follow the simulator guide:
- Add a dedicated
nll_bracketsimulatorType. - Keep mutable season inputs in
season_participant_simulator_inputs; do not hardcode current teams, current standings, odds, seeds, or ratings in the simulator. - Query simulator config and inputs through the simulator model/runner pathways already used by admin routes.
- Use the existing input-policy system so direct Elo can be derived from projected wins or futures odds.
- Return the standard top-eight probability columns used by expected values.
Data model and config changes
Simulator type
Add nll_bracket in all simulator type locations:
database/schema.tssimulatorTypeEnumapp/services/simulations/registry.tsSIMULATOR_TYPESapp/services/simulations/registry.tsregistry entryapp/services/simulations/manifest.tsprofile mapapp/services/simulations/simulator-config.tsprojected-wins config
Generate the enum migration with npm run db:generate; never hand-write migration SQL.
Manifest profile
Recommended profile:
nll_bracket: {
defaultConfig: {
iterations: 50_000,
seasonGames: 18,
parityFactor: 400,
bracketSize: 8,
playoffTeams: 8,
regularSeasonTeamCount: 14,
regularSeasonMode: "project_remaining_games",
regularSeasonNoise: 0.9,
homeFieldElo: 0,
},
requiredInputs: ["sourceElo"],
optionalInputs: ["projectedWins", "sourceOdds", "seed"],
derivableInputs: { sourceElo: ["projectedWins", "sourceOdds"] },
setupSections: ["participants", "eloRatings", "regularStandings", "bracket"],
}
projectedWins is the preferred preseason admin input. Direct sourceElo should override it, and futures odds can remain a fallback/alternative.
Simulator config
Add nll_bracket to app/services/simulations/simulator-config.ts:
nll_bracket: {
seasonGames: 18,
parityFactor: 400,
averageOpponentElo: 1500,
}
This enables generic projected-wins-to-Elo handling in admin setup.
Simulator algorithm
Create app/services/simulations/nll-simulator.ts with an NLLSimulator implements Simulator.
Inputs loaded per run
Load:
- all
seasonParticipantsfor the sports season; - resolved
sourceElovalues from simulator inputs / EV compatibility bridge; - optional
projectedWinsfor preseason regular-season projection; - optional
seedvalues for known playoff seeds; - regular-season standings from the existing regular standings table when available;
- playoff bracket event/matches/games when available.
Fail readiness through the manifest/input policy if required Elo cannot be resolved for every participant that can affect the playoff field. Do not silently assign Elo unless the season config explicitly enables an input-policy fallback.
Mode selection
The simulator should choose the most complete available state:
- Bracket-aware mode: if a populated NLL playoff bracket exists, honor it.
- Use completed match winners/losers.
- Use completed game rows inside best-of-three matches when present.
- Simulate unresolved games/matches.
- Known-seed mode: if no bracket exists but eight teams have seeds 1-8, simulate the playoff bracket directly from those seeds.
- Regular-season projection mode: otherwise, simulate the full regular season each iteration, derive top-eight seeds, then simulate playoffs.
Regular-season projection mode is required for preseason draftability.
Regular-season projection mode
For each Monte Carlo iteration:
- Start each team from current standings if present:
wins,losses, andgamesPlayedfrom the regular standings table;- if standings are absent, start all teams at
0-0.
- Determine remaining games:
remainingGames = max(0, seasonGames - gamesPlayed).
- Convert Elo to game win probability against an average opponent:
p = 1 / (1 + 10 ** ((averageOpponentElo - teamElo) / parityFactor)).
- Project additional wins:
- preseason/default option: sample
remainingGamesBernoulli trials atp; - if
projectedWinsexists, calibrate the team's mean final wins toward that projection by blending Elo-derived win rate with projected-win rate; - preserve current wins as a floor, so projected final wins cannot fall below already-earned wins.
- preseason/default option: sample
- Rank all teams by simulated final wins, with random tiebreaker for teams tied on wins.
- Take the top eight as seeds 1-8.
- Simulate the playoff bracket for those seeds.
Recommended projection blend for phase one:
projectedRate = projectedWins / seasonGames
eloRate = eloGameWinProbability(teamElo, averageOpponentElo, parityFactor)
regularSeasonWinRate = projectedWins == null
? eloRate
: 0.65 * projectedRate + 0.35 * eloRate
Keep 0.65 configurable as projectedWinsWeight if added to defaultConfig.
Playoff simulation
For playoff games, use team-vs-team Elo probability:
pA = 1 / (1 + 10 ** ((eloB - eloA) / parityFactor))
Then simulate:
- Quarterfinals: one game.
- Semifinals: best of three, first team to two wins.
- Finals: best of three, first team to two wins.
Export small helpers for testability:
nllGameWinProbability(eloA, eloB, parityFactor)simulateNllGame(teamA, teamB, parityFactor)simulateBestOfThree(teamA, teamB, parityFactor, existingWins?)buildNllBracketFromSeeds(seedEntries)simulateNllPlayoffs(seedEntries, options)simulateRegularSeasonSeeds(teams, options)
Completed results and partial series
When a playoff bracket exists:
- A completed match with
winnerIdshould be deterministic. - For best-of-three matches, completed
playoff_match_gamesrows should set current series wins. - If a team already has two series wins, treat the series as complete even if
playoffMatches.isCompletehas not yet been toggled. - If only one game is complete, simulate the remaining game(s) from the current 1-0 state.
Probability mapping
Map each iteration to Brackt's standard top-eight shape:
- champion:
probFirst - finalist:
probSecond - two semifinal losers: split evenly across
probThirdandprobFourth - four quarterfinal losers: split evenly across
probFifth,probSixth,probSeventh,probEighth - non-playoff teams in a given iteration receive no placement count for that iteration
Across all participants:
probFirstsums to 1;probSecondsums to 1;probThirdandprobFourtheach sum to 1;probFifththroughprobEightheach sum to 1.
Admin and data setup
Preseason setup workflow
- Create the NLL sport with
simulatorType = "nll_bracket". - Create the NLL sports season with all 14 teams as participants.
- Import preseason
projectedWinsfor every team through the generic simulator CSV importer. - Optionally import championship futures odds.
- Run the simulator before draft rooms open; all 14 teams should receive EV based on playoff qualification and bracket outcomes.
In-season workflow
- Sync or manually update regular standings.
- Re-run the simulator as standings change.
projectedWinscan remain a preseason prior, but current standings should increasingly dominate as games are played.
Playoff workflow
- Once seeds are final, enter seeds or create the bracket.
- If bracket exists, the simulator should stop projecting regular-season qualification and use bracket-aware mode.
- As playoff games complete, update match/game results and re-run the simulator.
Testing plan
Add app/services/simulations/__tests__/nll-simulator.test.ts.
Required tests:
- projected-wins-to-Elo readiness works for
nll_bracket; - regular-season projection can run from a preseason
0-0state with 14 teams; - higher projected wins increase playoff qualification and title probability;
- only eight teams qualify in each simulated iteration;
- seed ordering builds fixed bracket arms correctly:
(1,8)/(4,5)and(2,7)/(3,6); - Quarterfinals are single-game;
- Semifinals and Finals are best-of-three;
- partial best-of-three state is honored;
- completed matches are deterministic;
- all output probability columns sum correctly;
- manifest, registry, and schema enum stay in sync.
Run at minimum:
npm run typecheck
npm run lint
npm run test:run
Implementation task breakdown
- Add schema enum and migration for
nll_bracket. - Add manifest/config/registry entries.
- Implement pure NLL simulator helpers.
- Implement regular-season projection mode.
- Implement known-seed playoff mode.
- Implement bracket-aware mode with completed match/game support.
- Add unit tests and readiness/manifest regression coverage.
- Seed/create admin data for the NLL sport and current season outside of production code.
- Run required checks.
- Verify
/admin/simulatorsshows actionable setup state and can run preseason NLL seasons.
Explicit non-goals
- Do not hardcode current NLL standings, team Elo values, futures odds, or playoff seeds into production simulator code.
- Do not make NLL playoff-only; preseason draftability requires regular-season simulation.
- Do not add route-level Drizzle queries for simulator setup; use model/service layers.
- Do not create migration SQL manually.