Skip to content

Conversation

@MostCromulent
Copy link
Contributor

@MostCromulent MostCromulent commented Feb 10, 2026

Summary

THIS PR HAS BEEN SPLIT FROM #9642.

Network reconnection support: when a remote client disconnects mid-game, the server pauses the game and waits for them to reconnect. If they don't return in time, AI takes over their seat. Host can skip reconnect and pass to AI immediately, or skip timeout and give indefinite time for client to reconnect.

Details

Features

  • Pause on disconnect — when a remote client disconnects mid-game, the server pauses the game for that player and waits up to 5 minutes for them to return.
  • Countdown timer — broadcasts remaining time to chat every 30 seconds so all players know the status.
  • Seamless resume — when the client reconnects, game state and the current prompt are replayed so the game continues where it left off.
  • AI takeover on timeout — if the client doesn't return in time, AI takes over their seat and the game continues
  • Host chat commands:
    • /skipreconnect [name] — skip the reconnection window and immediately replace the disconnected player with AI.
    • /skiptimeout [name] — cancel the countdown timer and wait indefinitely for the player to reconnect.
    • The [name] argument is optional when only one player is disconnected.

Dependency

This branch targets master and is independent from #9642 (delta sync). Both branches modify FServerManager, NetGuiGame, and RemoteClient, so depending on merge order there will be merge conflicts in the other — but the features are architecturally separate and either can merge first.

Technical Implementation

The reconnection flow has three phases:

  1. Disconnect (DeregisterClientHandler.channelInactive): Remove client from active clients map, pause its NetGuiGame (sends become no-ops), cancel pending ReplyPool replies to unblock the game thread, store in disconnectedClients map keyed by username, start countdown timer.
  2. Reconnect (LobbyInputHandler.channelRead on LoginEvent): Match incoming login username against disconnectedClients to identify the returning player. Once identified, use the original RemoteClient's slot index for all game-state operations: swap the Netty channel, re-register in the clients map, then call resumeAndResync() which finds the player's NetGuiGame by slot index and replays setGameView → openView → current prompt (matching normal game-start message ordering).
  3. Timeout (if no reconnect): Timer fires handleReconnectTimeout() which finds the player by slot index, replaces their controller with PlayerControllerAi, and clears the input queue.

Username is used only for reconnection identity (matching a new connection to a previously disconnected client). All game-state operations (pausing, resuming, AI conversion) use slot index via NetGuiGame.getSlotIndex(). This is theoretically fragile to multiple clients with identical usernames, but unlikely to emerge in real play.

GameClientHandler captures the GameView synchronously on the IO thread (pendingGameView) to handle the EDT timing race where setGameView is queued but hasn't executed when openView arrives.

Slash commands are host-only — remote client messages starting with / are suppressed at the server.

Files Changed

File Module Description
FServerManager.java forge-gui Reconnection orchestration, countdown timer, host commands
NetGuiGame.java forge-gui Pause/resume, slot index tracking, null-safe paused returns
RemoteClient.java forge-gui Channel swap for reconnect, slot validation
ReplyPool.java forge-gui Cancel pending replies on disconnect
GameClientHandler.java forge-gui EDT timing race fix for reconnect
HostedMatch.java forge-gui Player-to-GUI lookup accessor
GameLobby.java forge-gui HostedMatch accessor
NetConnectUtil.java forge-gui Host slash command routing

Testing

  • Manual testing of disconnect and reconnect during active games (priority pass, combat, stack resolution)
  • Verified reconnection replays game state and current prompt correctly
  • Verified AI takeover on timeout unblocks the game thread
  • Verified /skipreconnect and /skiptimeout host commands
  • Verified slash commands are host-only (suppressed from remote clients)

🤖 Code authored by Claude (Opus 4.6) under human direction.

MostCromulent and others added 9 commits February 8, 2026 20:13
…Client slot tracking)

Port changes from NetworkPlay/main that prevent server deadlock on
client disconnect:
- ReplyPool.cancelAll(): completes all pending futures with null
- RemoteClient.cancelPendingReplies(): calls cancelAll on disconnect
- RemoteClient.UNASSIGNED_SLOT / hasValidSlot(): slot assignment tracking

These are correctness fixes for the existing disconnect path, not
reconnection-specific. Already in use on main via
DeregisterClientHandler.channelInactive().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… takeover)

When a remote client disconnects mid-game, the server now pauses the game
and waits up to 120 seconds for the player to rejoin. If the same username
reconnects, the channel is swapped on the original RemoteClient, game state
is resynced, and the current prompt is replayed. If the timeout expires,
the player is converted to AI and the game continues.

Key changes:
- RemoteClient: volatile channel/replies, swapChannel() for reconnection
- NetGuiGame: pause/resume (sends become no-ops), null-safe primitive returns
  to prevent NPE from cancelAll()/timeout null values
- FServerManager: reconnection flow in DeregisterClientHandler/LobbyInputHandler,
  helper methods for pause/resume/resync/AI conversion, ConcurrentHashMap for
  thread-safe client tracking
- GameLobby: getHostedMatch() accessor for server reconnection logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GameClientHandler: capture GameView synchronously in beforeCall(setGameView)
  as pendingGameView fallback for when gui.getGameView() is null during
  createMatch() (EDT hasn't processed setGameView yet when openView arrives)
- FServerManager.resumeAndResync: send updateGameView() before openView() to
  match normal HostedMatch.startGame() message ordering
- FServerManager: fix same-username loop in resumeAndResync/pauseNetGuiGame —
  continue searching when name matches but GUI is CMatchUI (host), not NetGuiGame
- HostedMatch: add getGuiForPlayer() and dumpGuis() for authoritative GUI lookup
- Add diagnostic logging to trace guis map contents during reconnect

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The game engine deduplicates identical player names (e.g. "2nd MostCromulent")
so name-based matching in pauseNetGuiGame/resumeAndResync never finds the
remote player. Store slotIndex on NetGuiGame and match by that instead.

- NetGuiGame: add slotIndex field and getter
- pauseNetGuiGame/resumeAndResync: match by netGui.getSlotIndex() == slotIndex
- Remove unused findGuiForPlayer() and dumpGuis() diagnostic methods

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ommands

Replace single-fire 300s timer with periodic 30s countdown that broadcasts
M:SS formatted messages. Add /skipreconnect (force AI takeover) and
/skiptimeout (disable timer, wait indefinitely) host commands. Suppress
slash commands from remote clients. Fix convertToAI to match by slot index
instead of player name. Intercept host commands in NetConnectUtil before
broadcasting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrate upstream changes: version exchange warning (Card-Forge#9698),
host/client differentiated lobby messages, and (Host) chat indicator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove unused disconnected flag from RemoteClient (pause on NetGuiGame
handles send suppression, clients map removal prevents broadcasts).
Fix inline comments: remove session-specific labels and case-specific
examples per comment durability guideline.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant