Skip to content

fix(core): guard against division by zero on 100% fallout coverage in WinCheckExecution - #4891

Open
berkelmali wants to merge 2 commits into
openfrontio:mainfrom
berkelmali:fix/wincheck-fallout-division-guard
Open

fix(core): guard against division by zero on 100% fallout coverage in WinCheckExecution#4891
berkelmali wants to merge 2 commits into
openfrontio:mainfrom
berkelmali:fix/wincheck-fallout-division-guard

Conversation

@berkelmali

Copy link
Copy Markdown
Contributor

PR 3: fix(core): guard against division by zero on 100% fallout coverage in WinCheckExecution

Description:

In WinCheckExecution.ts, both checkWinnerFFA() and checkWinnerTeam() calculate non-fallout land tiles using:

const numTilesWithoutFallout = this.mg.numLandTiles() - this.mg.numTilesWithFallout();

If 100% of the land tiles become covered in fallout (e.g. during heavy late-game nuclear strikes), numTilesWithoutFallout becomes 0. This caused (max.numTilesOwned() / numTilesWithoutFallout) * 100 to evaluate to Infinity, triggering an instant win condition for the leading player regardless of actual tile percentage owned.

This PR guards numTilesWithoutFallout with Math.max(1, ...) in both FFA and Team win check methods to prevent division by zero (Infinity).

Please complete the following:

  • I have added screenshots for all UI updates (N/A — Win check execution logic)
  • I process any text displayed to the user through translateText() and I've added it to the en.json file (N/A — No user-facing text)
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

barfires

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

FFA and team territory-win checks now require at least one non-fallout land tile before calculating ownership percentages. Regression tests cover all-fallout maps in both modes.

Changes

Territory Win Guard

Layer / File(s) Summary
Guard territory-win evaluation
src/core/execution/WinCheckExecution.ts, tests/core/executions/WinCheckExecution.test.ts
FFA and team checks skip territory-win evaluation when no non-fallout land tiles exist. Tests verify that no winner is declared on all-fallout maps.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Suggested reviewers: evanpelle

Poem

Fallout covers every tile,
No territory earns a smile.
FFA waits, teams wait too,
Guarded checks know what to do.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the division-by-zero fix for 100% fallout coverage in WinCheckExecution.
Description check ✅ Passed The description accurately explains the fallout coverage issue, the fix in both win checks, and the added regression tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the small-fix Small fix (≤ 50 lines) — auto-applied by PR gate label Aug 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/execution/WinCheckExecution.ts`:
- Around line 107-110: Update the denominator handling in checkWinnerTeam() and
the corresponding FFA win-check path to retain the actual non-fallout tile count
instead of clamping it with Math.max. Guard each percentage-based territory
condition so it is evaluated only when that denominator is positive, while
keeping timer conditions independent; add FFA and team coverage for zero
non-fallout tiles with positive ownership.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 84cb042c-36ac-46b2-88f1-ac55008059bc

📥 Commits

Reviewing files that changed from the base of the PR and between 5ed8bff and 9ada16a.

📒 Files selected for processing (1)
  • src/core/execution/WinCheckExecution.ts

Comment thread src/core/execution/WinCheckExecution.ts Outdated
@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management Aug 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/execution/WinCheckExecution.ts`:
- Around line 107-112: Update the territory-win comparisons in the current
execution method and checkWinnerTeam() to avoid floating-point division: compare
scaled integer tile counts using a fixed-point percentage scale, converting
percentageTilesOwnedToWin() to that scale first when fractional values are
supported. Preserve the existing positive-denominator guard and apply the
equivalent scaled comparison to max[1].

In `@tests/core/executions/WinCheckExecution.test.ts`:
- Around line 85-95: Rewrite both regression tests around checkWinnerFFA to use
setup() and the real game state instead of partial mg mocks and vi.fn methods.
Configure the simulation to retain zero non-fallout tiles while a player has
positive territory ownership, invoke the core winner check, and assert that the
real game state has no winner.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8acbe31f-bec6-427f-bb34-30c0281a67c3

📥 Commits

Reviewing files that changed from the base of the PR and between 9ada16a and e6663c9.

📒 Files selected for processing (2)
  • src/core/execution/WinCheckExecution.ts
  • tests/core/executions/WinCheckExecution.test.ts

Comment on lines 107 to +112
const numTilesWithoutFallout =
this.mg.numLandTiles() - this.mg.numTilesWithFallout();
if (
const isTerritoryWin =
numTilesWithoutFallout > 0 &&
(max.numTilesOwned() / numTilesWithoutFallout) * 100 >
this.mg.config().percentageTilesOwnedToWin() ||
this.mg.config().percentageTilesOwnedToWin();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the territory comparison in fixed-point arithmetic.

The positive-denominator guard is correct. Both changed conditions still use floating-point division. Compare scaled integer tile counts instead. If percentageTilesOwnedToWin() supports fractional percentages, store the threshold in a fixed integer scale first.

Suggested direction
-    const isTerritoryWin =
-      numTilesWithoutFallout > 0 &&
-      (max.numTilesOwned() / numTilesWithoutFallout) * 100 >
-        this.mg.config().percentageTilesOwnedToWin();
+    const percentageThreshold =
+      this.mg.config().percentageTilesOwnedToWin();
+    const isTerritoryWin =
+      numTilesWithoutFallout > 0 &&
+      max.numTilesOwned() * 100 >
+        numTilesWithoutFallout * percentageThreshold;

Apply the equivalent comparison to max[1] in checkWinnerTeam().

As per coding guidelines, src/core/**/*.ts must avoid floating-point math and keep the simulation deterministic.

Also applies to: 168-173

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/execution/WinCheckExecution.ts` around lines 107 - 112, Update the
territory-win comparisons in the current execution method and checkWinnerTeam()
to avoid floating-point division: compare scaled integer tile counts using a
fixed-point percentage scale, converting percentageTilesOwnedToWin() to that
scale first when fractional values are supported. Preserve the existing
positive-denominator guard and apply the equivalent scaled comparison to max[1].

Source: Coding guidelines

Comment on lines +85 to +95
it("should not set territory winner in FFA when non-fallout tiles is zero", () => {
const player = {
numTilesOwned: vi.fn(() => 10),
name: vi.fn(() => "P1"),
};
mg.players = vi.fn(() => [player]);
mg.numLandTiles = vi.fn(() => 100);
mg.numTilesWithFallout = vi.fn(() => 100);
winCheck.checkWinnerFFA();
expect(mg.setWinner).not.toHaveBeenCalled();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Use setup() and real game state for both regression tests.

These tests replace mg methods with vi.fn() and use a partial mock. Rewrite them with setup() and drive the core simulation directly. Keep the zero non-fallout tile and positive ownership scenario, then assert the real game has no winner.

As per coding guidelines, tests under tests/**/*.test.ts must use setup() and exercise the core simulation directly instead of using mocks.

Also applies to: 97-115

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/core/executions/WinCheckExecution.test.ts` around lines 85 - 95,
Rewrite both regression tests around checkWinnerFFA to use setup() and the real
game state instead of partial mg mocks and vi.fn methods. Configure the
simulation to retain zero non-fallout tiles while a player has positive
territory ownership, invoke the core winner check, and assert that the real game
state has no winner.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

small-fix Small fix (≤ 50 lines) — auto-applied by PR gate

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

1 participant