Fix darts simulator timeout and world-cup bracket edge case
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 2m26s
🚀 Deploy / ʦ TypeScript (pull_request) Successful in 1m16s
🚀 Deploy / 🔍 Lint (pull_request) Successful in 48s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped

- Add numSimulations constructor param to DartsSimulator (same pattern as
  WorldCupSimulator); drop default from 50k→10k; update manifest to match
- Pass 200 iterations in the slow darts column-sum test to prevent CI timeout
- Fix world-cup "SF losers" test to use 48 participants — with only 8,
  the R32 pool has 6 teams and SF never runs, leaving all placement counts
  at 0 and normalization producing probFirst=probThird=1 for one team
- Add manifest iterations: 10_000 override for darts_bracket

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-05-31 10:34:47 -07:00
parent c3b7c77f09
commit c0f674f648
4 changed files with 14 additions and 9 deletions

View file

@ -325,7 +325,7 @@ describe("DartsSimulator.simulate() — Path A (bracket populated)", () => {
it("each probability column sums to 1.0 across all 128 participants", async () => { it("each probability column sums to 1.0 across all 128 participants", async () => {
mockDb.query.playoffMatches.findMany.mockResolvedValue(makeFullBracket()); mockDb.query.playoffMatches.findMany.mockResolvedValue(makeFullBracket());
const sim = new DartsSimulator(); const sim = new DartsSimulator(200);
const results = await sim.simulate("season-1"); const results = await sim.simulate("season-1");
const keys = [ const keys = [

View file

@ -156,8 +156,7 @@ describe("WorldCupSimulator", () => {
}); });
it("SF losers land in 3rd or 4th, never 1st or 2nd", async () => { it("SF losers land in 3rd or 4th, never 1st or 2nd", async () => {
// Set up 8 participants (small bracket, 2 groups of 4) const participants = makeParticipants(48);
const participants = makeParticipants(8);
mockDb.query.seasonParticipants.findMany.mockResolvedValue(participants); mockDb.query.seasonParticipants.findMany.mockResolvedValue(participants);
mockDb.query.scoringEvents.findFirst.mockResolvedValue(null); mockDb.query.scoringEvents.findFirst.mockResolvedValue(null);
mockDb.query.tournamentGroups.findMany.mockResolvedValue([]); mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);

View file

@ -51,7 +51,7 @@ import type { Simulator, SimulationResult } from "./types";
// ─── Simulation parameters ──────────────────────────────────────────────────── // ─── Simulation parameters ────────────────────────────────────────────────────
const NUM_SIMULATIONS = 50000;
/** /**
* Controls how much Elo gaps affect per-set win probability. * Controls how much Elo gaps affect per-set win probability.
@ -198,6 +198,12 @@ function shuffle<T>(arr: T[]): T[] {
// ─── Simulator ──────────────────────────────────────────────────────────────── // ─── Simulator ────────────────────────────────────────────────────────────────
export class DartsSimulator implements Simulator { export class DartsSimulator implements Simulator {
private readonly numSimulations: number;
constructor(numSimulations = 10_000) {
this.numSimulations = numSimulations;
}
async simulate(sportsSeasonId: string): Promise<SimulationResult[]> { async simulate(sportsSeasonId: string): Promise<SimulationResult[]> {
const db = database(); const db = database();
@ -325,7 +331,7 @@ export class DartsSimulator implements Simulator {
const sfLoserCounts = new Map<string, number>(participantIds.map((id) => [id, 0])); const sfLoserCounts = new Map<string, number>(participantIds.map((id) => [id, 0]));
const qfLoserCounts = new Map<string, number>(participantIds.map((id) => [id, 0])); const qfLoserCounts = new Map<string, number>(participantIds.map((id) => [id, 0]));
for (let s = 0; s < NUM_SIMULATIONS; s++) { for (let s = 0; s < this.numSimulations; s++) {
// R1 (64 matches) // R1 (64 matches)
const r1Winners: string[] = []; const r1Winners: string[] = [];
for (let i = 1; i <= 64; i++) { for (let i = 1; i <= 64; i++) {
@ -433,7 +439,7 @@ export class DartsSimulator implements Simulator {
finalistCounts.set(finalist, (finalistCounts.get(finalist) ?? 0) + 1); finalistCounts.set(finalist, (finalistCounts.get(finalist) ?? 0) + 1);
} }
return buildResults(participantIds, NUM_SIMULATIONS, { return buildResults(participantIds, this.numSimulations, {
championCounts, championCounts,
finalistCounts, finalistCounts,
sfLoserCounts, sfLoserCounts,
@ -514,7 +520,7 @@ export class DartsSimulator implements Simulator {
unseededPool.push(`__bye_${unseededPool.length}`); unseededPool.push(`__bye_${unseededPool.length}`);
} }
for (let s = 0; s < NUM_SIMULATIONS; s++) { for (let s = 0; s < this.numSimulations; s++) {
// Draw: shuffle the unseeded pool — seeded positions are pre-computed. // Draw: shuffle the unseeded pool — seeded positions are pre-computed.
const drawnUnseeded = shuffle([...unseededPool]); const drawnUnseeded = shuffle([...unseededPool]);
@ -582,7 +588,7 @@ export class DartsSimulator implements Simulator {
finalistCounts.set(finalist, (finalistCounts.get(finalist) ?? 0) + 1); finalistCounts.set(finalist, (finalistCounts.get(finalist) ?? 0) + 1);
} }
return buildResults(allParticipantIds, NUM_SIMULATIONS, { return buildResults(allParticipantIds, this.numSimulations, {
championCounts, championCounts,
finalistCounts, finalistCounts,
sfLoserCounts, sfLoserCounts,

View file

@ -163,7 +163,7 @@ const PROFILES: Record<SimulatorType, Omit<SimulatorManifestProfile, "simulatorT
setupSections: ["participants", "eloRatings", "futuresOdds", "events"], setupSections: ["participants", "eloRatings", "futuresOdds", "events"],
}, },
darts_bracket: { darts_bracket: {
defaultConfig: { ...BASE_CONFIG, eloDivisor: 400 }, defaultConfig: { ...BASE_CONFIG, iterations: 10_000, eloDivisor: 400 },
requiredInputs: ["sourceElo", "worldRanking"], requiredInputs: ["sourceElo", "worldRanking"],
optionalInputs: ["seed"], optionalInputs: ["seed"],
setupSections: ["participants", "eloRatings", "rankings"], setupSections: ["participants", "eloRatings", "rankings"],