Fix OOM in CI: make WorldCupSimulator simulation count configurable for tests
Tests now pass numSimulations=500 instead of the production default of 50,000. Six simulator tests × 50k iterations each was exhausting the 4GB heap on GitHub Actions runners. Also reduce simGroupMatch stat tests from 50k to 5k iterations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
54d1c92b09
commit
f85f81673f
2 changed files with 23 additions and 17 deletions
|
|
@ -17,31 +17,31 @@ describe("simGroupMatch", () => {
|
|||
|
||||
it("equal teams draw ≈28% of the time", () => {
|
||||
let draws = 0;
|
||||
const N = 50_000;
|
||||
const N = 5_000;
|
||||
for (let i = 0; i < N; i++) {
|
||||
if (simGroupMatch(1500, 1500) === "draw") draws++;
|
||||
}
|
||||
const drawRate = draws / N;
|
||||
// BASE_DRAW_RATE = 0.28 at eloDiff=0; accept ±3% from sampling noise
|
||||
expect(drawRate).toBeGreaterThan(0.25);
|
||||
expect(drawRate).toBeLessThan(0.31);
|
||||
// BASE_DRAW_RATE = 0.28 at eloDiff=0; accept ±5% from sampling noise at N=5k
|
||||
expect(drawRate).toBeGreaterThan(0.23);
|
||||
expect(drawRate).toBeLessThan(0.33);
|
||||
});
|
||||
|
||||
it("draw rate decays with large Elo gap", () => {
|
||||
let draws = 0;
|
||||
const N = 50_000;
|
||||
const N = 5_000;
|
||||
for (let i = 0; i < N; i++) {
|
||||
if (simGroupMatch(2000, 1400) === "draw") draws++;
|
||||
}
|
||||
const drawRate = draws / N;
|
||||
// eloDiff = 600 → pDraw ≈ 0.28 * exp(-1.2) ≈ 0.084; accept ±3%
|
||||
expect(drawRate).toBeLessThan(0.13);
|
||||
// eloDiff = 600 → pDraw ≈ 0.28 * exp(-1.2) ≈ 0.084; accept ±5%
|
||||
expect(drawRate).toBeLessThan(0.15);
|
||||
});
|
||||
|
||||
it("strong favorite wins more often than underdog", () => {
|
||||
let wins = 0;
|
||||
let losses = 0;
|
||||
const N = 50_000;
|
||||
const N = 5_000;
|
||||
for (let i = 0; i < N; i++) {
|
||||
const r = simGroupMatch(1900, 1600);
|
||||
if (r === "win") wins++;
|
||||
|
|
@ -110,7 +110,7 @@ describe("WorldCupSimulator", () => {
|
|||
mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
|
||||
mockDb.query.playoffMatches.findMany.mockResolvedValue([]);
|
||||
|
||||
const sim = new WorldCupSimulator();
|
||||
const sim = new WorldCupSimulator(500);
|
||||
await expect(sim.simulate("season-1")).rejects.toThrow("No participants found");
|
||||
});
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ describe("WorldCupSimulator", () => {
|
|||
mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
|
||||
mockDb.query.playoffMatches.findMany.mockResolvedValue([]);
|
||||
|
||||
const sim = new WorldCupSimulator();
|
||||
const sim = new WorldCupSimulator(500);
|
||||
const results = await sim.simulate("season-1");
|
||||
expect(results).toHaveLength(48);
|
||||
const ids = new Set(results.map((r) => r.participantId));
|
||||
|
|
@ -137,7 +137,7 @@ describe("WorldCupSimulator", () => {
|
|||
mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
|
||||
mockDb.query.playoffMatches.findMany.mockResolvedValue([]);
|
||||
|
||||
const sim = new WorldCupSimulator();
|
||||
const sim = new WorldCupSimulator(500);
|
||||
const results = await sim.simulate("season-1");
|
||||
|
||||
const sumFirst = results.reduce((s, r) => s + r.probabilities.probFirst, 0);
|
||||
|
|
@ -158,7 +158,7 @@ describe("WorldCupSimulator", () => {
|
|||
mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
|
||||
mockDb.query.playoffMatches.findMany.mockResolvedValue([]);
|
||||
|
||||
const sim = new WorldCupSimulator();
|
||||
const sim = new WorldCupSimulator(500);
|
||||
const results = await sim.simulate("season-1");
|
||||
|
||||
for (const r of results) {
|
||||
|
|
@ -179,7 +179,7 @@ describe("WorldCupSimulator", () => {
|
|||
mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
|
||||
mockDb.query.playoffMatches.findMany.mockResolvedValue([]);
|
||||
|
||||
const sim = new WorldCupSimulator();
|
||||
const sim = new WorldCupSimulator(500);
|
||||
const results = await sim.simulate("season-1");
|
||||
|
||||
// probFirst + probSecond + probThird + probFourth should cover all probability mass
|
||||
|
|
@ -235,7 +235,7 @@ describe("WorldCupSimulator", () => {
|
|||
mockDb.query.tournamentGroups.findMany.mockResolvedValue([group, ...remainingGroups]);
|
||||
mockDb.query.playoffMatches.findMany.mockResolvedValue([]);
|
||||
|
||||
const sim = new WorldCupSimulator();
|
||||
const sim = new WorldCupSimulator(500);
|
||||
const results = await sim.simulate("season-1");
|
||||
|
||||
const p3Result = results.find((r) => r.participantId === "p3");
|
||||
|
|
@ -255,7 +255,7 @@ describe("WorldCupSimulator", () => {
|
|||
mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
|
||||
mockDb.query.playoffMatches.findMany.mockResolvedValue([]);
|
||||
|
||||
const sim = new WorldCupSimulator();
|
||||
const sim = new WorldCupSimulator(500);
|
||||
const results = await sim.simulate("season-1");
|
||||
|
||||
for (const r of results) {
|
||||
|
|
|
|||
|
|
@ -303,6 +303,12 @@ function normalizeProbabilities(
|
|||
// ─── Main simulator ───────────────────────────────────────────────────────────
|
||||
|
||||
export class WorldCupSimulator implements Simulator {
|
||||
private readonly numSimulations: number;
|
||||
|
||||
constructor(numSimulations = NUM_SIMULATIONS) {
|
||||
this.numSimulations = numSimulations;
|
||||
}
|
||||
|
||||
async simulate(sportsSeasonId: string): Promise<SimulationResult[]> {
|
||||
const db = database();
|
||||
|
||||
|
|
@ -465,7 +471,7 @@ export class WorldCupSimulator implements Simulator {
|
|||
counts.qfLoser.set(id, 0);
|
||||
}
|
||||
|
||||
for (let sim = 0; sim < NUM_SIMULATIONS; sim++) {
|
||||
for (let sim = 0; sim < this.numSimulations; sim++) {
|
||||
// ── Group stage ──────────────────────────────────────────────
|
||||
const advancingFromGroup: string[] = []; // group winners + runners-up (24 teams)
|
||||
const thirdPlaceTeams: TeamStats[] = []; // 12 third-place teams
|
||||
|
|
@ -570,7 +576,7 @@ export class WorldCupSimulator implements Simulator {
|
|||
}
|
||||
|
||||
// 8. Convert counts to probabilities
|
||||
const N = NUM_SIMULATIONS;
|
||||
const N = this.numSimulations;
|
||||
const numQfLosers = 4; // 4 QF losers per sim
|
||||
|
||||
const results: SimulationResult[] = participantIds.map((id) => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue