# OpenSpec Change: enterprise-gaming-api-plugin-matchmaking - **Change ID**: enterprise-gaming-api-plugin-matchmaking - **Package**: `@suaveplan/api-plugin-matchmaking` - **Category**: gaming/server - **Target path**: `gaming/server/api-plugin-matchmaking` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/api-plugin-matchmaking.md` --- ## Summary Deliver `@suaveplan/api-plugin-matchmaking` on the genesis branch from scratch at enterprise quality. The package wraps a `MatchmakingQueue` in a Hono HTTP plugin. The queue accepts skill-rated players, sorts by rating, and builds balanced teams using alternating-rank assignment with a configurable skill gap that widens over wait time. HTTP routes expose enqueue, dequeue, status, and queue stats. Genesis adds Zod player schemas, structured errors, OTel queue-depth 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:^`) | `MatchmakingError`; `MM.*` codes | | `@suaveplan/types` (`workspace:^`) | `Clock` | | `@suaveplan/events` (`workspace:^`) | `mm:enqueued`, `mm:match_found`, `mm:dequeued` events | | `zod` (`catalog:forms`) | `QueuedPlayer`, `MatchResult` schemas | | `@opentelemetry/api` (`catalog:observability`) | Queue depth gauge, match counter | --- ## HTTP Routes | Method | Path | Description | |---|---|---| | `POST` | `/queue` | Enqueue player | | `DELETE` | `/queue/:playerId` | Dequeue player | | `GET` | `/queue/:playerId/status` | Queue position | | `POST` | `/queue/tick` | Advance matchmaking (server-side tick) | | `GET` | `/queue/stats` | Queue depth, avg wait time | --- ## Core logic ```ts interface QueuedPlayer { playerId: string displayName: string skillRating: number gameMode: string enqueuedAt: number } interface MatchResult { matchId: string team1: QueuedPlayer[] team2: QueuedPlayer[] avgSkillGap: number createdAt: number } function createMatchmakingQueue(options: MatchmakingOptions): MatchmakingQueue interface MatchmakingOptions { teamSize?: number // players per team, default 5 baseSkillGap?: number // initial acceptable gap, default 200 expandGapPerSecond?: number // gap expansion rate, default 10/sec clock?: Clock } interface MatchmakingQueue { enqueue(player: QueuedPlayer): void dequeue(playerId: string): void findMatches(now: number): MatchResult[] getStatus(playerId: string): QueueStatus | undefined getStats(): QueueStats } ``` ### Error codes ```ts type MatchmakingErrorCode = | 'MM_PLAYER_NOT_FOUND' | 'MM_ALREADY_QUEUED' | 'MM_INVALID_GAME_MODE' | 'MM_QUEUE_FULL' ``` --- ## File layout ```text gaming/server/api-plugin-matchmaking/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts types/common.ts types/common.md core/ matchmaking-queue/ matchmaking-queue.ts matchmaking-queue.md matchmaking-queue.types.ts matchmaking-queue.test.ts index.ts routes/ queue.ts queue.md plugin.ts plugin.md tests/ integration.test.ts ``` --- ## Impact **Downstream**: `@suaveplan/api-plugin-game-server` creates sessions for matched groups. Prerequisites: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`.