6.6 KiB
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<GameServerCode> 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, andINVALID_PLAYER_INFO, each fully-qualified asGAME_SERVER.<NAME>.
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.causeand.contextto 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
parseGameConfigis given{ gameType: "x", maxPlayers: 1, minPlayers: 4, rules: {} } - THEN it throws
GameServerErrorwith codeGAME_SERVER.INVALID_GAME_CONFIGand acauseset to the underlying ZodError.
Scenario: SessionMetadata accepts passthrough extras with non-negative timestamps
- WHEN
parseSessionMetadatais given a record withcreatedAt/updatedAtnon-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
resolveis called with an identifier that was never registered - THEN a
GameServerErroris thrown with codeGAME_SERVER.SERVICE_NOT_REGISTERED, the message contains the stringified identifier, and.context.identifiermatches.
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/*, andintegrations/*surfaces from the reference branchclaude/review-game-engine-arch-Kaykrare tracked as follow-on changes per the deferral table inproposal.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.