Skip to content

Client-side automatic reconnect for in-game disconnects#10466

Open
MostCromulent wants to merge 1 commit intoCard-Forge:masterfrom
MostCromulent:NetworkPlay/auto-reconnect
Open

Client-side automatic reconnect for in-game disconnects#10466
MostCromulent wants to merge 1 commit intoCard-Forge:masterfrom
MostCromulent:NetworkPlay/auto-reconnect

Conversation

@MostCromulent
Copy link
Copy Markdown
Contributor

@MostCromulent MostCromulent commented Apr 20, 2026

Screenshot 2026-04-20 192221 Screenshot 2026-04-20 192929

Summary

Previously, any mid-match client disconnect forced the player to exit back to the main menu and manually rejoin the server. This PR adds automatic rejoin on disconnect: the client attempts to reconnect in the background for a short window, and the match resumes where it left off if the connection comes back.

Architecture

Symmetric heartbeat

The client already sent a HeartbeatEvent after 15 seconds of no outbound traffic, so the server could tell it was still alive. This PR makes that symmetrical: the server now echoes the heartbeat back. Both sides treat 45 seconds without any inbound traffic as "connection dead," so a network drop is detected whether the client→server or server→client direction has broken.

Client reconnect loop (FGameClient)

When the connection dies (either side's 45s timer fires, or the OS delivers a FIN), the client enters a RECONNECTING state and attempts to reconnect on a fixed backoff schedule (1s, 5s, 15s, 45s, 60s — five attempts) in the background. On a successful reconnect the server replays the game-start protocol plus the current prompt and the match continues. If all five attempts fail the state moves to FAILED; if the server explicitly says the seat is gone, it moves to SEAT_LOST.

Server side (FServerManager.LobbyInputHandler)

A LoginEvent for a user on the disconnectedClients list swaps the channel into the existing RemoteClient and calls resumeAndResync — the same path that used to run when a player manually reconnected.

A LoginEvent during an active match for a user not on the disconnected list (window expired or never in this match) gets a new SeatLostEvent and a close. This replaces the prior heuristic of inferring seat-loss from any LobbyUpdateEvent during RECONNECTING, which fired spuriously on the lobby update that normally accompanies a successful resume.

Proactive old-channel close

When the client detects the dead connection, it closes its own channel before scheduling the first reconnect attempt, so a FIN reaches the server ASAP. Without this, the reconnect LoginEvent can arrive before the server has cleaned up the dying session and get rejected as a duplicate login.

Player experience (ReconnectModals, both platforms)

When the connection drops, a dialog appears over the match showing a live countdown to the next reconnect attempt. The player can either wait — if reconnect succeeds the dialog closes automatically and the match resumes — or click View Battlefield to dismiss the dialog and let the reconnect loop continue in the background, or Return to Main Menu to give up.

If all five attempts fail the dialog changes to a Connection Lost state with Attempt Rejoin (which restarts the backoff from attempt 1) and Return to Main Menu. If the server tells the client its seat is gone, the dialog changes to Seat Lost with only Return to Main Menu. Return-to-Main fully tears down the network session so the player lands on a clean home screen rather than a stale lobby.

Test hook

The existing client-side /simulatedisconnect chat command (intercepted in FNetOverlay when the sender is an FGameClient) is updated to take a <mode> argument so both detection paths are exercisable:

  • OUTBOUND — drops outbound writes and disables the client's own idle timer, so only the server's 45s timer fires. Simulates a hung client / client→server break.
  • INBOUND — drops inbound reads but lets idle events through, so the client's own 45s timer fires. Simulates a hung server / server→client break.

🤖 Generated with Claude Code

On channelInactive the client now enters a RECONNECTING state and
retries on a fixed backoff (1s, 5s, 15s, 45s, 60s) with a live modal.
On successful resume the server replays the match; on exhausted
backoff the modal switches to FAILED (manual rejoin). The server emits
a new SeatLostEvent when a reconnect LoginEvent finds no matching
slot, replacing the prior "infer from any LobbyUpdateEvent" heuristic.

Also adds:
- Reconnect modals for desktop and mobile (cancel / rejoin / return-
  to-main), with proper lobby-state teardown on both platforms
- /simulatedisconnect test hook (OUTBOUND/INBOUND) to exercise both
  detection paths
- Server echoes HeartbeatEvent to keep the client's READER_IDLE alive

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants