Files
suaveplan/openspec/changes/enterprise-gaming-lobby/proposal.md
2026-05-27 23:29:53 +01:00

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.