Files
gomoku/server/network
tiennm99 03758b0e90 fix(exit): route ClientExit through state machine, keep WS alive
Bug: after clicking "Leave" on the game-over modal, the client landed
on the nickname entry screen and got stuck — typing a nickname produced
no response for up to 90 seconds.

Root cause chain:
1. Server handleClientExit (stateless) closed SendCh and nilled it but
   did not close the WS connection. Writer goroutine drained + exited
   without closing the conn; reader stayed blocked on ReadMessage; state
   machine stayed blocked on CmdCh. Zombie session.
2. Any subsequent player.Send() returned nil (SendCh was nil), silently
   dropping responses. The setNickname reply never reached the client.
3. Client only unstuck when reader's 90 s readDeadline fired, finally
   closing the WS and triggering the auto-reconnect path.
4. Client MenuScene.create() always called showNicknameScreen() without
   checking gameState.nickname, so even after the reconnect the user
   had to re-type their name.

Fix: "exit" now means "leave current room, return to home lobby" —
not "terminate WS session".

Server:
- dispatch.go: move Request_ClientExit from stateless to stateful path.
- handlers_stateless.go: delete handleClientExit entirely.
- waiting.go leaveRoom: broadcast ClientExitResponse via broadcastResponse
  BEFORE removing the player, so the exiting player also receives their
  own exit confirmation. Removes duplicated self-notification logic.
- waiting/gamePvp/gamePve/gameover/watching: on ClientExitRequest, call
  leaveRoom + return consts.StateHome (was return 0, ErrClientExit).
- home.go: on ClientExitRequest, send bare ClientExitResponse and stay
  in home (no room to leave).
- Update 4 state tests that asserted ErrClientExit → now assert StateHome.
- Rename TestWatching_ClientExitReturnsErrClientExit → ReturnsHome.

Client:
- menu-scene.js create(): if gameState.nickname is set, skip nickname
  screen and call showLobby() directly. Otherwise showNicknameScreen().
- game-scene.js _onClientExit: drop the explicit showLobby() call; let
  MenuScene.create() decide the panel based on nickname state. Remove
  now-unused menu-ui showLobby import.

Result: click Leave → server sends ClientExitResponse, state machine
transitions to StateHome, WS stays open, client transitions to
MenuScene → sees gameState.nickname is set → goes straight to lobby.
No reconnect, no re-typing, no 90-second stall.

go vet + go test ./... green. npm run build green.
2026-04-11 16:51:13 +07:00
..