# 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 ```ts interface LobbyConfig { gameMode: string minPlayers: number maxPlayers: number countdownSeconds?: number // default 5 settings?: Record } 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 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 ```ts type LobbyErrorCode = | 'LOBBY_NOT_FOUND' | 'LOBBY_FULL' | 'LOBBY_MEMBER_NOT_FOUND' | 'LOBBY_ALREADY_JOINED' | 'LOBBY_NOT_ENOUGH_PLAYERS' | 'LOBBY_PERMISSION_DENIED' ``` --- ## File layout ```text 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`.