The "Start Game" button in the waiting room is gone — as soon as a
second player joins a PVP room, the server flips to Playing and both
clients transition into the game in lockstep.
Server:
- Proto: remove GameStartingRequest message + oneof field (now unused).
- state/home.go handleJoinRoom: after a successful join that fills the
room, call assignColors, set Status=Playing + CurrentTurn=Black,
close StartCh, broadcast GameStartingResponse, return StateGamePvp
for the joining player. If still only one player, return StateWaiting
as before.
- state/waiting.go: collapse ownerWait + joinerWait into a single
unified loop. Both roles just select on StartCh (auto-start signal)
and CmdCh (for ClientExit). No more GameStartingRequest handling or
PlayerCount checks.
- network/dispatch.go: drop Request_GameStarting from the stateful
routing list.
Client:
- connection-service.js: remove sendGameStarting() helper.
- menu-ui-rooms.js showWaiting: drop the isOwner parameter + Start Game
button + owner/joiner branching. Both views show the same passive
'Waiting for opponent...' message that flips to 'Opponent joined:
<name> - starting game...' when the second player arrives.
- menu-scene.js: update _onRoomCreateSuccess + _onRoomJoinSuccess to
call the simpler showWaiting(ownerNickname, playerCount, onLeave)
signature.
Tests:
- Remove TestWaitingOwnerGameStartingRequiresFullRoom (obsolete).
- Rewrite TestWaitingOwnerStartsWhenFull into TestWaitingOwner
TransitionsOnStartCh: simulate handleJoinRoom closing StartCh,
verify owner transitions to StateGamePvp.
- Add TestFlow_JoinAutoStartsGame: end-to-end drive where owner is in
waitingState, joiner sends JoinRoomRequest from home, and both
goroutines must land in StateGamePvp with colors assigned and
GameStartingResponse broadcast.
Regen Go + JS proto stubs. go vet + go test ./... green.
npm --prefix client run build green.
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.
Rename
- server/database/ → server/lobby/ (17 import sites + qualified references)
- Package was named "database" but there is no database — it's in-memory
hashmaps for players/rooms/spectators. "lobby" accurately names its role:
the multiplayer game lobby state (who is in what room, joining/leaving,
spectating).
Ratel-online legacy cleanup
- Delete server/README.md — 100% Chinese, entirely about ratel card games
(斗地主/跑得快/德州扑克/麻将/骗子酒馆/Uno). Zero relevance to gomoku.
- Delete server/build.sh + server/build.ps1 — multi-platform ratel-server
build scripts with mixed Chinese comments, superseded by Makefile +
Dockerfile.
- Delete server/docs/ — Chinese Docker deployment guide + quickstart,
superseded by root docs/deployment-guide.md and root README.md.
- Delete server/demo.gif — ratel card-game demo screenshot.
Comment fixes: update "database" references in consts/const.go and
game/board.go package docs to point at "lobby" instead.
go vet + go test ./... green.
Protocol type safety
- Add Piece, GameResult, RoomType, RoomStatus proto3 enums; replace
unsafe string fields in GameMoveSuccessResponse, GameOverResponse,
RoomCreateSuccessResponse, RoomSummary, WatchGameSuccessResponse.
- Go compiler now rejects typos like "PVP" or "BLACK". protobufjs
toJSON() serializes enums as UPPERCASE names, so existing client
comparisons keep working.
- Fixes turn-change bug where lowercase "black"/"white" server strings
never matched client 'BLACK'/'WHITE' checks, freezing currentTurn
and blocking the non-starting player's clicks.
- Regenerate Go + JS stubs.
Room-join bug fix
- home.go handleJoinRoom now broadcasts RoomJoinSuccessResponse to all
players in the room (owner + joiner), not just the joiner. Owner
can see the player count flip to 2/2 and the Start Game button
enables. Remove superseded broadcastJoined + GameReadyResponse path.
- client menu-scene _onRoomJoinSuccess: read correct proto camelCase
field names (roomOwner, roomClientCount, clientNickname).
Dead code removal
- Delete GameReadyRequest/Response proto messages entirely; leaveRoom
notifies via ClientExitResponse.
- Delete server/network/{network,wss}.go empty stubs.
- Delete server/pkg/consts/ unused re-export package.
- Slim server/consts/const.go from 117 LOC to 24: drop legacy TCP
constants, duplicated RoomType/RoomStatus enums, Error/NewErr types,
StateJoin/StateCreate=0 type-unsafe aliases, unused GameTypes maps.
- database/player.go: remove unused IP, Mode, Type, Amount, legacy
private state field.
- Fix gopls findings: unused forPlayer, gameOverOnce, runAIMove player,
handleGameReset player params.
go vet + go test ./... green, npm run build green.