fix: detect game start for Pack 7+ titles that don't use state: "Gameplay"

Pack 7 games (Quiplash 3, Blather Round) transition room state from
"Lobby" to "Logo" instead of "Gameplay". Even newer titles (Doominate,
Big Survey) have no room entity at all — the only signals are room/lock
and room/exit opcodes.

- parseRoomEntity: detect game started as any non-Lobby state
- handleMessage: add room/lock and room/exit opcode handling
- handleRoomLock: emit game.started as fallback for room-entity-less games
- handleRoomExit: emit game.ended on explicit room close
- Tests: 6 new tests covering Logo state, room/lock, room/exit

Verified against live rooms: quiplash3, blanky-blank, you-ruined-it,
bigsurvey.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-05-03 00:23:32 -04:00
parent 2964cee291
commit c5ffe23404
3 changed files with 151 additions and 2 deletions

View File

@@ -36,7 +36,7 @@ class EcastShardClient {
lobbyState: roomVal.lobbyState ?? null,
gameCanStart: !!roomVal.gameCanStart,
gameIsStarting: !!roomVal.gameIsStarting,
gameStarted: roomVal.state === 'Gameplay',
gameStarted: roomVal.state != null && roomVal.state !== 'Lobby',
gameFinished: !!roomVal.gameFinished,
};
}
@@ -213,6 +213,12 @@ class EcastShardClient {
break;
case 'client/disconnected':
break;
case 'room/lock':
this.handleRoomLock();
break;
case 'room/exit':
this.handleRoomExit(message.result);
break;
case 'error':
this.handleError(message.result);
break;
@@ -363,6 +369,44 @@ class EcastShardClient {
}
}
handleRoomLock() {
if (!this.gameStarted) {
console.log(`[Shard Monitor] Room ${this.roomCode} locked (game starting)`);
this.gameStarted = true;
this.gameState = this.gameState || 'Gameplay';
this.onEvent('game.started', {
sessionId: this.sessionId,
gameId: this.gameId,
roomCode: this.roomCode,
playerCount: this.playerCount,
players: [...this.playerNames],
maxPlayers: this.maxPlayers,
});
}
}
handleRoomExit() {
if (this.gameFinished) return;
console.log(`[Shard Monitor] Room ${this.roomCode} exited`);
this.gameFinished = true;
this.onEvent('game.ended', {
sessionId: this.sessionId,
gameId: this.gameId,
roomCode: this.roomCode,
playerCount: this.playerCount,
players: [...this.playerNames],
});
this.onEvent('room.disconnected', {
sessionId: this.sessionId,
gameId: this.gameId,
roomCode: this.roomCode,
reason: 'room_closed',
finalPlayerCount: this.playerCount,
});
activeShards.delete(`${this.sessionId}-${this.gameId}`);
this.disconnect();
}
handleError(result) {
console.error(`[Shard Monitor] Ecast error ${result?.code}: ${result?.msg}`);
if (result?.code === 2027) {