From f0b907aa1b9753e6341f0b7feccdfa9726827615 Mon Sep 17 00:00:00 2001 From: Berk Elmali Date: Thu, 7 May 2026 15:03:29 +0300 Subject: [PATCH 1/2] fix: guard division-by-zero in WinCheck when all tiles have fallout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When numTilesWithoutFallout === 0, the tile-percentage division produces Infinity, and Infinity > 80 is true in JS — falsely declaring a winner. Use an early return guard (numTilesWithoutFallout === 0) to avoid the division entirely. Added tests for both FFA and Team modes. --- src/core/execution/WinCheckExecution.ts | 14 +++++- .../core/executions/WinCheckExecution.test.ts | 47 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/core/execution/WinCheckExecution.ts b/src/core/execution/WinCheckExecution.ts index 8b86643539..9f1ffd5f6c 100644 --- a/src/core/execution/WinCheckExecution.ts +++ b/src/core/execution/WinCheckExecution.ts @@ -67,6 +67,11 @@ export class WinCheckExecution implements Execution { const timeElapsed = this.mg.elapsedGameSeconds(); const numTilesWithoutFallout = this.mg.numLandTiles() - this.mg.numTilesWithFallout(); + + if (numTilesWithoutFallout === 0) { + return; + } + if ( (max.numTilesOwned() / numTilesWithoutFallout) * 100 > this.mg.config().percentageTilesOwnedToWin() || @@ -102,9 +107,14 @@ export class WinCheckExecution implements Execution { const timeElapsed = this.mg.elapsedGameSeconds(); const numTilesWithoutFallout = this.mg.numLandTiles() - this.mg.numTilesWithFallout(); - const percentage = (max[1] / numTilesWithoutFallout) * 100; + + if (numTilesWithoutFallout === 0) { + return; + } + if ( - percentage > this.mg.config().percentageTilesOwnedToWin() || + (max[1] / numTilesWithoutFallout) * 100 > + this.mg.config().percentageTilesOwnedToWin() || (this.mg.config().gameConfig().maxTimerValue !== undefined && timeElapsed - this.mg.config().gameConfig().maxTimerValue! * 60 >= 0) || timeElapsed >= WinCheckExecution.HARD_TIME_LIMIT_SECONDS diff --git a/tests/core/executions/WinCheckExecution.test.ts b/tests/core/executions/WinCheckExecution.test.ts index 6148f598ce..0b1f189942 100644 --- a/tests/core/executions/WinCheckExecution.test.ts +++ b/tests/core/executions/WinCheckExecution.test.ts @@ -83,6 +83,53 @@ describe("WinCheckExecution", () => { it("should return false for activeDuringSpawnPhase", () => { expect(winCheck.activeDuringSpawnPhase()).toBe(false); }); + + it("should not set winner via tile percentage when all land tiles have fallout (FFA)", () => { + const player = { + numTilesOwned: vi.fn(() => 100), + name: vi.fn(() => "P1"), + }; + mg.players = vi.fn(() => [player]); + mg.numLandTiles = vi.fn(() => 100); + mg.numTilesWithFallout = vi.fn(() => 100); + mg.config = vi.fn(() => ({ + gameConfig: vi.fn(() => ({ + gameMode: GameMode.FFA, + maxTimerValue: undefined, + })), + percentageTilesOwnedToWin: vi.fn(() => 80), + numSpawnPhaseTurns: vi.fn(() => 0), + })); + mg.ticks = vi.fn(() => 0); + mg.stats = vi.fn(() => ({ stats: () => ({}) })); + winCheck.init(mg, 0); + winCheck.checkWinnerFFA(); + expect(mg.setWinner).not.toHaveBeenCalled(); + }); + + it("should not set winner via tile percentage when all land tiles have fallout (Team)", () => { + const player = { + numTilesOwned: vi.fn(() => 100), + name: vi.fn(() => "P1"), + team: vi.fn(() => "Red"), + }; + mg.players = vi.fn(() => [player]); + mg.numLandTiles = vi.fn(() => 100); + mg.numTilesWithFallout = vi.fn(() => 100); + mg.config = vi.fn(() => ({ + gameConfig: vi.fn(() => ({ + gameMode: GameMode.Team, + maxTimerValue: undefined, + })), + percentageTilesOwnedToWin: vi.fn(() => 80), + numSpawnPhaseTurns: vi.fn(() => 0), + })); + mg.ticks = vi.fn(() => 0); + mg.stats = vi.fn(() => ({ stats: () => ({}) })); + winCheck.init(mg, 0); + winCheck.checkWinnerTeam(); + expect(mg.setWinner).not.toHaveBeenCalled(); + }); }); describe("WinCheckExecution - Nation Winners", () => { From f7c3e1ac11a1480ddb5f1d9b3124f2b612b54ba1 Mon Sep 17 00:00:00 2001 From: 22314621 Date: Sat, 16 May 2026 16:59:24 +0300 Subject: [PATCH 2/2] fix: use <= 0 instead of === 0 for fallout tile guard More defensive check: while numTilesWithFallout shouldn't exceed numLandTiles, using <= 0 guards against any edge case where it might, preventing both division-by-zero and negative-denominator bugs in both FFA and Team win checks. --- src/core/execution/WinCheckExecution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/execution/WinCheckExecution.ts b/src/core/execution/WinCheckExecution.ts index 9f1ffd5f6c..2995e7af06 100644 --- a/src/core/execution/WinCheckExecution.ts +++ b/src/core/execution/WinCheckExecution.ts @@ -68,7 +68,7 @@ export class WinCheckExecution implements Execution { const numTilesWithoutFallout = this.mg.numLandTiles() - this.mg.numTilesWithFallout(); - if (numTilesWithoutFallout === 0) { + if (numTilesWithoutFallout <= 0) { return; } @@ -108,7 +108,7 @@ export class WinCheckExecution implements Execution { const numTilesWithoutFallout = this.mg.numLandTiles() - this.mg.numTilesWithFallout(); - if (numTilesWithoutFallout === 0) { + if (numTilesWithoutFallout <= 0) { return; }