Skip to content

fix: add 30-second grace period for disconnects in 1v1 games (BUG-05)#3945

Open
berkelmali wants to merge 2 commits into
openfrontio:mainfrom
berkelmali:fix/bug05-1v1-disconnect-grace-period
Open

fix: add 30-second grace period for disconnects in 1v1 games (BUG-05)#3945
berkelmali wants to merge 2 commits into
openfrontio:mainfrom
berkelmali:fix/bug05-1v1-disconnect-grace-period

Conversation

@berkelmali
Copy link
Copy Markdown
Contributor

@berkelmali berkelmali commented May 16, 2026

Description:

Add a 30-second grace period before declaring a disconnect winner in 1v1 ranked games. Previously, a momentary WiFi blip would instantly declare the opponent as winner. Now the system waits 300 ticks (30s) and resets the timer if the player reconnects.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • 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

Previously, a momentary WiFi blip would instantly declare the opponent
as winner in 1v1 ranked games. WinCheckExecution checked
isDisconnected() every ~1 second, with no reconnect window.

Changes:
- Track disconnectGraceTick when only 1 of 2 humans is connected
- Winner declared only after 300 ticks (30 seconds) elapse
- Grace timer resets if the disconnected player reconnects
- Both-disconnected edge case handled (most tiles wins after grace)

4 new tests cover: immediate, expired, reconnect, both-disconnect
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54c270d5-d5a8-4e98-8589-6c846f0151cf

📥 Commits

Reviewing files that changed from the base of the PR and between 3808b22 and fdfd4ed.

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

Walkthrough

WinCheckExecution replaces immediate winner declaration in 1v1 ranked mode with a 300-tick disconnect grace period. When one human disconnects the timer starts and the win is awarded only after the grace elapses; reconnection resets the timer. Tests exercise start, expiration, reconnect reset, both-disconnect, and single-human edge cases.

Changes

1v1 Disconnect Grace Period

Layer / File(s) Summary
Grace period state and configuration
src/core/execution/WinCheckExecution.ts
Adds DISCONNECT_GRACE_TICKS (300) and a nullable field to track when the disconnect grace period begins.
Grace period state machine implementation
src/core/execution/WinCheckExecution.ts
Replaces immediate "one connected human = winner" logic with a state machine: starts grace when one of two humans disconnects, awards the connected player only after grace elapses, handles both-disconnected by awarding winner by tile ownership after grace, and resets/clears grace when both reconnect.
Grace period test scenarios
tests/core/executions/WinCheckExecution.test.ts
Replaces and adds tests to assert no immediate winner when a human disconnects, winner after advancing past grace ticks, grace timer reset on reconnect, both-disconnect resolution by tile ownership after grace, multiple-human no-win behavior, and single-human/no-op expectations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A pause of three hundred ticks, a breath between the fray,
A chance to come back home again before the game gives way.
If both fall silent, tiles now tell the tale,
But step back in quick — and balance will prevail. 🎮⏳

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a 30-second grace period for disconnects in 1v1 ranked games, which is the primary objective of the PR.
Description check ✅ Passed The description is directly related to the changeset, explaining the grace period feature, its purpose (preventing instant wins from momentary disconnects), and how it works (300 ticks with timer reset on reconnect).
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/core/executions/WinCheckExecution.test.ts (1)

572-603: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a post-grace assertion for simultaneous disconnects.

This test only checks “not immediate winner.” Please also advance beyond grace and assert winner-by-tiles, so this contract is locked by tests.

Suggested test extension
   test("should not set winner immediately when both players disconnect", async () => {
@@
     const human1 = game.player("Player1");
     const human2 = game.player("Player2");

+    // Make winner deterministic for post-grace check
+    let assigned = 0;
+    game.map().forEachTile((tile) => {
+      if (!game.map().isLand(tile)) return;
+      if (assigned < 1) {
+        human1.conquer(tile);
+        assigned++;
+      }
+    });
+
     human1.markDisconnected(true);
     human2.markDisconnected(true);
@@
     winCheck.checkWinnerFFA();

     // No grace timer was started (both disconnected simultaneously)
     expect(setWinnerSpy).not.toHaveBeenCalled();
     expect(winCheck.isActive()).toBe(true);
+
+    game.endSpawnPhase();
+    for (let i = 0; i < 310; i++) {
+      game.executeNextTick();
+    }
+    winCheck.checkWinnerFFA();
+    expect(setWinnerSpy).toHaveBeenCalledWith(human1, expect.anything());
+    expect(winCheck.isActive()).toBe(false);
   });
🤖 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 572 - 603,
Extend the test to also verify the post-grace outcome: after the initial
assertions, advance the test timer/passage of time beyond the grace period (or
call the WinCheckExecution method that processes end-of-grace), then invoke the
win-checking logic again (e.g., call winCheck.checkWinnerFFA() or the
appropriate tick/resolve method on WinCheckExecution) and assert that
game.setWinner (setWinnerSpy) is called with the expected winner reason
(winner-by-tiles) and that winCheck.isActive() is false; reference
WinCheckExecution, winCheck.checkWinnerFFA(), winCheck.isActive(), and the
setWinnerSpy assigned to game.setWinner when adding these steps.
🤖 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 87-103: When both players are disconnected (connectedHumans.length
=== 0 && allHumans.length === 2) the code returns without initializing the grace
timer if this.disconnectGraceTick is null; update the branch to set
this.disconnectGraceTick = this.mg.ticks() when disconnectGraceTick is null so
the DISCONNECT_GRACE_TICKS countdown begins, then return; keep the existing
logic that checks elapsed >= WinCheckExecution.DISCONNECT_GRACE_TICKS to pick
the winner (using allHumans[0]) and call this.mg.setWinner(...) when grace
expires.

---

Outside diff comments:
In `@tests/core/executions/WinCheckExecution.test.ts`:
- Around line 572-603: Extend the test to also verify the post-grace outcome:
after the initial assertions, advance the test timer/passage of time beyond the
grace period (or call the WinCheckExecution method that processes end-of-grace),
then invoke the win-checking logic again (e.g., call winCheck.checkWinnerFFA()
or the appropriate tick/resolve method on WinCheckExecution) and assert that
game.setWinner (setWinnerSpy) is called with the expected winner reason
(winner-by-tiles) and that winCheck.isActive() is false; reference
WinCheckExecution, winCheck.checkWinnerFFA(), winCheck.isActive(), and the
setWinnerSpy assigned to game.setWinner when adding these steps.
🪄 Autofix (Beta)

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

Run ID: 8de81f51-5127-48e1-b85b-099443c325aa

📥 Commits

Reviewing files that changed from the base of the PR and between b813792 and 3808b22.

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

Comment thread src/core/execution/WinCheckExecution.ts
@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

1 participant