# Capability Spec Delta: game-server (enterprise-game-server-core) **Package**: `@suaveplan/game-server` **Status**: PROPOSED ## ADDED Requirements ### Requirement: Typed error namespace The package SHALL expose a single `GameServerError` class extending `SuaveplanError` and a `GAME_SERVER_CODES` registry produced by `defineCodes("GAME_SERVER", [...])`. The registry SHALL enumerate only codes thrown by an actual production code path. #### Scenario: registry only contains codes thrown by production paths - **WHEN** the test suite inspects the registered codes - **THEN** the registry contains exactly `SERVICE_NOT_REGISTERED`, `INVALID_GAME_CONFIG`, `INVALID_SESSION_METADATA`, and `INVALID_PLAYER_INFO`, each fully-qualified as `GAME_SERVER.`. #### Scenario: errors carry code, message, cause, and context - **WHEN** a caller constructs `new GameServerError(code, message, { cause, context })` - **THEN** the resulting instance is an `instanceof SuaveplanError`, has `.name === "GameServerError"`, exposes the typed `.code`, and forwards `.cause` and `.context` to the standard logging/OTel adapters. **Verified by:** `src/errors/game-server-error.test.ts::"registers all expected codes under GAME_SERVER namespace"` AND `src/errors/game-server-error.test.ts::"constructs an error with code, message, cause, and context"` AND `src/errors/game-server-error.test.ts::"constructs without optional cause/context"` AND `src/errors/game-server-error.test.ts::"supports discriminated access via the code union"`. --- ### Requirement: Zod-validated boundary types The package SHALL expose Zod schemas (`GameConfigSchema`, `SessionMetadataSchema`, `PlayerInfoSchema`, `MoveValidationResultSchema`, `GameResultSchema`, `SessionTypeSchema`) and throwing validators (`parseGameConfig`, `parseSessionMetadata`, `parsePlayerInfo`) plus non-throwing probes (`tryParseMoveValidationResult`, `tryParseGameResult`) that turn untrusted input into the corresponding typed value or raise a typed `GameServerError` with the matching `GAME_SERVER.INVALID_*` code. #### Scenario: GameConfig enforces maxPlayers >= minPlayers - **WHEN** `parseGameConfig` is given `{ gameType: "x", maxPlayers: 1, minPlayers: 4, rules: {} }` - **THEN** it throws `GameServerError` with code `GAME_SERVER.INVALID_GAME_CONFIG` and a `cause` set to the underlying ZodError. #### Scenario: SessionMetadata accepts passthrough extras with non-negative timestamps - **WHEN** `parseSessionMetadata` is given a record with `createdAt`/`updatedAt` non-negative integers plus arbitrary extra keys - **THEN** the result preserves the extras and validates the timestamps. #### Scenario: MoveValidationResult and GameResult reject extra keys - **WHEN** an off-spec object includes additional keys not declared in the strict schema - **THEN** the non-throwing probe returns `null`. **Verified by:** `src/types/common.test.ts::"rejects maxPlayers < minPlayers"` AND `src/types/common.test.ts::"throws GameServerError with INVALID_GAME_CONFIG"` AND `src/types/common.test.ts::"accepts a valid timestamp pair with passthrough extras"` AND `src/types/common.test.ts::"throws GameServerError with INVALID_SESSION_METADATA"` AND `src/types/common.test.ts::"throws GameServerError with INVALID_PLAYER_INFO on bad input"` AND `src/types/common.test.ts::"returns null on off-spec input"` (twice). --- ### Requirement: Dependency-injection container The package SHALL expose a `DIContainer` class with `register`, `resolve`, `has`, `unregister`, `clear`, and `getRegisteredServices` methods. `register` SHALL support `"singleton"` (default) and `"transient"` lifecycles, return `this` for chaining, and discard any cached singleton instance when an identifier is re-registered. `resolve` SHALL throw `GameServerError` with code `GAME_SERVER.SERVICE_NOT_REGISTERED` for unknown identifiers. #### Scenario: singleton factory invoked exactly once - **WHEN** an identifier registered as `"singleton"` is resolved repeatedly - **THEN** the factory runs once and every resolution returns the same instance. #### Scenario: transient factory invoked on every resolve - **WHEN** an identifier registered as `"transient"` is resolved repeatedly - **THEN** the factory runs once per resolution and instances are not cached. #### Scenario: unknown identifier throws typed error - **WHEN** `resolve` is called with an identifier that was never registered - **THEN** a `GameServerError` is thrown with code `GAME_SERVER.SERVICE_NOT_REGISTERED`, the message contains the stringified identifier, and `.context.identifier` matches. #### Scenario: re-register invalidates cached singleton - **WHEN** an identifier is registered twice with different factories - **THEN** the next resolve returns an instance produced by the second factory, not the cached first instance. **Verified by:** `src/engine/di-container/di-container.test.ts::"registers and resolves a singleton (default lifecycle)"` AND `src/engine/di-container/di-container.test.ts::"registers and resolves a transient service"` AND `src/engine/di-container/di-container.test.ts::"throws GameServerError(SERVICE_NOT_REGISTERED) for unknown identifier (string)"` AND `src/engine/di-container/di-container.test.ts::"throws with the symbol stringified for unknown symbol identifier"` AND `src/engine/di-container/di-container.test.ts::"re-register invalidates the cached singleton"` AND `src/engine/di-container/di-container.test.ts::"unregister returns true for known, false for unknown, and clears cache"` AND `src/engine/di-container/di-container.test.ts::"clear() removes registrations and singleton cache"` AND `src/engine/di-container/di-container.test.ts::"getRegisteredServices() returns the registered identifiers"` AND `src/engine/di-container/di-container.test.ts::"supports symbol identifiers with typed generics"` AND `src/engine/di-container/di-container.test.ts::"supports fluent chained registration"` AND `src/engine/di-container/di-container.test.ts::"has() reflects registration state"`. > Deferred (2026-05-23): the broader `core/{AbstractGameLogic, > interfaces/*, events/*}`, `engine/{GameEngine, GameLoop, > PlayerManager, PluginManager, SessionLifecycle, SessionNode, > SessionStore, SessionTree}`, `state/*`, `matchmaking/*`, > `performance/*` → `telemetry/*`, `network/*`, `plugins/*`, > `enhanced/*`, and `integrations/*` surfaces from the reference branch > `claude/review-game-engine-arch-Kaykr` are tracked as follow-on > changes per the deferral table in `proposal.md`. Six of those > dependencies (`controller`, `keys`, `network-webrtc`, > `network-websockets`, `storage-browser`, `workflow`) are not yet > present on genesis and block the corresponding migration slices.