Fix simulator correctness bugs and reduce test iteration counts #61
4 changed files with 14 additions and 9 deletions
|
|
@ -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 = [
|
||||||
|
|
|
||||||
|
|
@ -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([]);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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"],
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue