Watch feature was half-implemented since day one and stayed broken through
the typed-protobuf migration:
1. Server (WatchGameHandler): only pushed the WatchGameSuccessResponse ack.
A mid-game watcher joined a blank scene because the server never told
them who the players were or what moves had been played.
2. Client: had a Watch button that sent the request but no event bus
handler for GAME_WATCH_SUCCESSFUL, so nothing happened visually.
Server fix:
- On successful watch, if room.status == STARTING, bootstrap the watcher
with a synthesised GameStartingResponse (player ids, nicknames, board
size) and replay every move in room.getMoveHistory() as individual
GameMoveSuccessResponse messages on that channel.
- Black/white player lookup uses room.getBlackPlayerId/getWhitePlayerId
against the clientSideMap so we don't reassign roles.
- Move replay resolves playerNickname from the same map.
Client fix:
- menu-ui.js: new GAME_WATCH_SUCCESSFUL handler flips gameState.isSpectating
= true. The subsequent GameStartingResponse flows through the existing
menu-scene handler (transitions to GameScene) and the existing
game-state-service handler (populates room state, resets moves).
- Move replay events propagate through the global GAME_MOVE_SUCCESS handler
in game-state-service before GameScene.create() runs, so GameScene's
existing rejoin/spectate loop at create() renders every stone.
After the typed-protobuf migration the event bus delivers the full
ShowRoomsResponse message to the handler — a {rooms: [...]} object, not
the raw array the old JSON envelope used to unwrap. showRoomList was
still doing `Array.isArray(rooms) ? rooms : []`, so the guard always
failed and the UI rendered "No rooms available" even when rooms existed
server-side.
Repro: create a PVP room from tab A, open Join Room from tab B — empty list.
Fix: read payload.rooms (defensively default to [] so the empty-state row
still shows when protobufjs strips the empty repeated field).
Phase 04 of the WebSocket protobuf migration:
- Add protobufjs@7.5.4 runtime dep and protobufjs-cli@1.1.3 devDep
- Add proto:gen npm script that runs pbjs + pbts against server/src/main/proto/
- Commit static-module output at client/src/generated/protocol.{js,d.ts}
- Rewrite connection-service.js:
* Set binaryType='arraybuffer' and default URL to ws://localhost:1999/ratel
* Replace string-keyed send(code, data) with 13 typed send helpers
(sendNickname, sendGameMove, sendCreatePveRoom, ...)
* Decode incoming BinaryWebSocketFrame via Response.decode and map
each oneof case to a ClientEventCode for the event bus
- Update 14 call sites across menu-ui, game-ui, game-scene
- game-state-service CLIENT_CONNECT now reads data.clientId
- menu-ui NICKNAME_SET toast only triggers when invalidLength > 0
- Drop ServerEventCode enum from protocol-constants.js