3.5 KiB
OpenSpec Change: enterprise-gaming-lobby
- Change ID: enterprise-gaming-lobby
- Package:
@suaveplan/lobby - Category: gaming/multiplayer
- Target path:
gaming/multiplayer/lobby - Status: PROPOSED
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/lobby.md
Summary
Deliver @suaveplan/lobby on the genesis branch from scratch at enterprise quality. The package provides multiplayer lobby management with matchmaking integration and countdown. A LobbyManager creates/destroys typed Lobby objects; a Lobby tracks member roster, ready state per member, configurable minPlayers/maxPlayers, game mode settings, and a CountdownTimer; when all members are ready, the countdown starts — on expiry the lobby fires lobby:match_ready and hands off to @suaveplan/game-server. Genesis adds Zod lobby config schemas, structured errors, OTel lobby-count gauge, injectable clock, and 100% coverage.
Current-state audit (genesis)
Package does not exist on genesis yet — fresh build.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/error (workspace:^) |
LobbyError; LOBBY.* codes |
@suaveplan/types (workspace:^) |
Clock, IdGenerator |
@suaveplan/events (workspace:^) |
lobby:joined, lobby:left, lobby:ready, lobby:match_ready events |
zod (catalog:forms) |
LobbyConfig, LobbyMember schemas |
@opentelemetry/api (catalog:observability) |
Active lobby gauge, match-ready counter |
Public API surface
interface LobbyConfig {
gameMode: string
minPlayers: number
maxPlayers: number
countdownSeconds?: number // default 5
settings?: Record<string, unknown>
}
interface LobbyMember {
playerId: string
displayName: string
isReady: boolean
joinedAt: number
isHost: boolean
}
function createLobbyManager(options?: { clock?: Clock }): LobbyManager
interface LobbyManager {
createLobby(config: LobbyConfig): Lobby
getLobby(lobbyId: string): Lobby | undefined
destroyLobby(lobbyId: string): void
listLobbies(filter?: LobbyFilter): Lobby[]
readonly count: number
}
interface LobbyFilter {
gameMode?: string
hasOpenSlots?: boolean
}
interface Lobby {
readonly id: string
readonly config: LobbyConfig
readonly members: ReadonlyMap<string, LobbyMember>
readonly isCountingDown: boolean
readonly countdownRemaining: number
join(playerId: string, displayName: string): void
leave(playerId: string): void
setReady(playerId: string, ready: boolean): void
transferHost(toPlayerId: string): void
kick(playerId: string): void
tick(dt: number): void
}
Error codes
type LobbyErrorCode =
| 'LOBBY_NOT_FOUND'
| 'LOBBY_FULL'
| 'LOBBY_MEMBER_NOT_FOUND'
| 'LOBBY_ALREADY_JOINED'
| 'LOBBY_NOT_ENOUGH_PLAYERS'
| 'LOBBY_PERMISSION_DENIED'
File layout
gaming/multiplayer/lobby/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/index.ts
types/common.ts
types/common.md
core/
lobby/
lobby.ts
lobby.md
lobby.types.ts
lobby.test.ts
index.ts
lobby-manager/
lobby-manager.ts
lobby-manager.md
lobby-manager.types.ts
lobby-manager.test.ts
index.ts
tests/
integration.test.ts
Impact
Downstream: @suaveplan/party shares member management patterns; @suaveplan/api-plugin-matchmaking creates lobbies when a match is found. Prerequisites: @suaveplan/error, @suaveplan/types, @suaveplan/events.