Optimize 1v1 Disconnect Grace Period (Refined #3945)#3972
Conversation
game.end() is async but was called without await inside a try/catch. The try/catch only catches synchronous errors. Added .catch() to the returned promise to prevent unhandled promise rejections from crashing the server.
…o security-fixes
WalkthroughThe pull request adds a 1v1 ranked disconnect grace period preventing immediate wins when players disconnect, re-checks server-side winner consensus on disconnect with tightened voting thresholds, converts game end handling to async error logging, and enforces Bearer token authentication on singleplayer game archival. Changes1v1 Ranked Disconnect Grace Period
Server-Side Winner Consensus & Validation
Game Finalization & Singleplayer Authentication
Sequence DiagramssequenceDiagram
participant Game
participant WinCheck as WinCheckExecution
participant Player1 as Player 1
participant Player2 as Player 2
Player1->>WinCheck: connected
Player2->>WinCheck: connected
Player2->>WinCheck: disconnect
WinCheck->>WinCheck: start grace timer (tick 0)
loop tick < 300
Game->>WinCheck: checkWinnerFFA()
WinCheck->>WinCheck: grace elapsed < 300 ticks
WinCheck->>Game: no winner yet
end
loop tick >= 300
Game->>WinCheck: checkWinnerFFA()
WinCheck->>Game: setWinner(Player1)
end
Player2->>WinCheck: reconnect
WinCheck->>WinCheck: reset grace timer
sequenceDiagram
participant WS as WebSocket Client
participant GS as GameServer
participant Archive
WS->>GS: close / disconnect
alt game started
GS->>GS: checkWinnerConsensus()
GS->>GS: scan winnerVotes
GS->>GS: check > 2:1 IP majority
alt threshold met
GS->>GS: set this.winner
GS->>Archive: archive game
GS->>GS: log winner decision
else below threshold
GS->>GS: no action
end
else game not started
GS->>GS: skip consensus check
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ 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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/server/GameServer.ts (1)
1248-1267: 💤 Low valueEdge case: winner declared when no active clients remain.
When all clients disconnect,
activeUniqueIPs.sizebecomes 0. The conditionpotentialWinner.ips.size * 2 > 0is true for any vote count ≥ 1, so a winner is declared based on prior votes.This is probably fine (honoring votes cast before disconnect), but verify this matches intended behavior. If no winner should be declared when zero clients remain, add a guard:
private checkWinnerConsensus() { if (this.winner !== null) { return; } const activeUniqueIPs = new Set(this.activeClients.map((c) => c.ip)); + if (activeUniqueIPs.size === 0) { + return; + } for (const [winnerKey, potentialWinner] of this.winnerVotes.entries()) {🤖 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/server/GameServer.ts` around lines 1248 - 1267, The checkWinnerConsensus method can declare a winner when activeUniqueIPs.size is 0; add a guard at the start of checkWinnerConsensus to return early if there are no active unique IPs to avoid declaring winners after all clients disconnect. Update the function (checkWinnerConsensus) to check activeClients/activeUniqueIPs size and skip the winnerVotes loop (and avoid calling archiveGame or setting this.winner) when activeUniqueIPs.size === 0 so prior votes don't trigger a win in that edge case.
🤖 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.
Nitpick comments:
In `@src/server/GameServer.ts`:
- Around line 1248-1267: The checkWinnerConsensus method can declare a winner
when activeUniqueIPs.size is 0; add a guard at the start of checkWinnerConsensus
to return early if there are no active unique IPs to avoid declaring winners
after all clients disconnect. Update the function (checkWinnerConsensus) to
check activeClients/activeUniqueIPs size and skip the winnerVotes loop (and
avoid calling archiveGame or setting this.winner) when activeUniqueIPs.size ===
0 so prior votes don't trigger a win in that edge case.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8c351041-0cc9-4185-b845-de204531d296
📒 Files selected for processing (5)
src/core/execution/WinCheckExecution.tssrc/server/GameManager.tssrc/server/GameServer.tssrc/server/Worker.tstests/core/executions/WinCheckExecution.test.ts
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.
This is a highly optimized version of PR #3945. It utilizes a zero-allocation single-pass loop in WinCheckExecution.ts to track connected and disconnected humans instead of performing multiple array filters every 10 ticks, significantly reducing Garbage Collection overhead in the core game loop.
Please complete the following:
Please put your Discord username so you can be contacted if a bug or regression is found:
barfires