Fix oxlint violations in LLWS simulator

- Replace non-null assertions with null-safe access (! → ?? / optional chaining)
- Replace .sort() with .toSorted() per linting rules
- Promote bump() to simulate() scope and pass it into simulateSideBracket,
  removing the need to pass counts into that function

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-04-08 20:47:42 +00:00
parent 971c475cb6
commit 31b03e19a8
2 changed files with 17 additions and 16 deletions

View file

@ -153,9 +153,9 @@ describe("LLWSSimulator", () => {
setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS, { includeOdds: true })); setupMockDb(defaultParticipants(), makeEvRows(ALL_IDS, { includeOdds: true }));
const results = await new LLWSSimulator().simulate("season-1"); const results = await new LLWSSimulator().simulate("season-1");
const byId = new Map(results.map((r) => [r.participantId, r])); const byId = new Map(results.map((r) => [r.participantId, r]));
expect(byId.get("us-1")!.probabilities.probFirst).toBeGreaterThan( const us1prob = byId.get("us-1")?.probabilities.probFirst ?? 0;
byId.get("us-10")!.probabilities.probFirst const us10prob = byId.get("us-10")?.probabilities.probFirst ?? 0;
); expect(us1prob).toBeGreaterThan(us10prob);
}); });
it("works when no odds are entered (all 50/50 fallback)", async () => { it("works when no odds are entered (all 50/50 fallback)", async () => {

View file

@ -147,7 +147,7 @@ function simulatePoolPlay(pool: Team[]): [Team, Team] {
// the comparator is consistent (Math.random() inside a comparator is a bug — the // the comparator is consistent (Math.random() inside a comparator is a bug — the
// engine may call it multiple times per pair and get contradictory results). // engine may call it multiple times per pair and get contradictory results).
const tiebreaker = new Map(pool.map((t) => [t.participantId, Math.random()])); const tiebreaker = new Map(pool.map((t) => [t.participantId, Math.random()]));
const ranked = [...pool].sort((a, b) => { const ranked = pool.toSorted((a, b) => {
const diff = (wins.get(b.participantId) ?? 0) - (wins.get(a.participantId) ?? 0); const diff = (wins.get(b.participantId) ?? 0) - (wins.get(a.participantId) ?? 0);
return diff !== 0 ? diff : (tiebreaker.get(a.participantId) ?? 0) - (tiebreaker.get(b.participantId) ?? 0); return diff !== 0 ? diff : (tiebreaker.get(a.participantId) ?? 0) - (tiebreaker.get(b.participantId) ?? 0);
}); });
@ -178,12 +178,8 @@ function simulateSideBracket(
poolA2: Team, poolA2: Team,
poolB1: Team, poolB1: Team,
poolB2: Team, poolB2: Team,
counts: Map<string, PlacementCounts> bump: (id: string, key: keyof PlacementCounts) => void
): { sideChampion: Team; sideLoser: Team } { ): { sideChampion: Team; sideLoser: Team } {
const bump = (id: string, key: keyof PlacementCounts) => {
const entry = counts.get(id);
if (entry) entry[key]++;
};
// Winners bracket // Winners bracket
const g1 = simGame(poolA1, poolB2); const g1 = simGame(poolA1, poolB2);
@ -331,6 +327,10 @@ export class LLWSSimulator implements Simulator {
const counts = new Map<string, PlacementCounts>( const counts = new Map<string, PlacementCounts>(
allIds.map((id) => [id, { champion: 0, finalist: 0, thirdPlace: 0, fourthPlace: 0, bracketLoser: 0 }]) allIds.map((id) => [id, { champion: 0, finalist: 0, thirdPlace: 0, fourthPlace: 0, bracketLoser: 0 }])
); );
const bump = (id: string, key: keyof PlacementCounts) => {
const entry = counts.get(id);
if (entry) entry[key]++;
};
// 6. Run Monte Carlo simulations. // 6. Run Monte Carlo simulations.
for (let s = 0; s < NUM_SIMULATIONS; s++) { for (let s = 0; s < NUM_SIMULATIONS; s++) {
@ -346,19 +346,19 @@ export class LLWSSimulator implements Simulator {
// Double-elimination bracket per side. // Double-elimination bracket per side.
const { sideChampion: usChamp, sideLoser: usLose } = const { sideChampion: usChamp, sideLoser: usLose } =
simulateSideBracket(usA1, usA2, usB1, usB2, counts); simulateSideBracket(usA1, usA2, usB1, usB2, bump);
const { sideChampion: intlChamp, sideLoser: intlLose } = const { sideChampion: intlChamp, sideLoser: intlLose } =
simulateSideBracket(intlA1, intlA2, intlB1, intlB2, counts); simulateSideBracket(intlA1, intlA2, intlB1, intlB2, bump);
// Consolation game: 3rd / 4th place. // Consolation game: 3rd / 4th place.
const consolation = simGame(usLose, intlLose); const consolation = simGame(usLose, intlLose);
counts.get(consolation.winner.participantId)!.thirdPlace++; bump(consolation.winner.participantId, "thirdPlace");
counts.get(consolation.loser.participantId)!.fourthPlace++; bump(consolation.loser.participantId, "fourthPlace");
// World Series: 1st / 2nd place. // World Series: 1st / 2nd place.
const ws = simGame(usChamp, intlChamp); const ws = simGame(usChamp, intlChamp);
counts.get(ws.winner.participantId)!.champion++; bump(ws.winner.participantId, "champion");
counts.get(ws.loser.participantId)!.finalist++; bump(ws.loser.participantId, "finalist");
} }
// 7. Convert counts to probability distributions. // 7. Convert counts to probability distributions.
@ -366,8 +366,9 @@ export class LLWSSimulator implements Simulator {
const bracketLosersPerSim = 4; const bracketLosersPerSim = 4;
const bracketDivisor = bracketLosersPerSim * NUM_SIMULATIONS; const bracketDivisor = bracketLosersPerSim * NUM_SIMULATIONS;
const zeroCounts: PlacementCounts = { champion: 0, finalist: 0, thirdPlace: 0, fourthPlace: 0, bracketLoser: 0 };
return allIds.map((id) => { return allIds.map((id) => {
const c = counts.get(id)!; const c = counts.get(id) ?? zeroCounts;
const bracketProb = c.bracketLoser / bracketDivisor; const bracketProb = c.bracketLoser / bracketDivisor;
return { return {
participantId: id, participantId: id,