Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
# 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`, and
`INVALID_PLAYER_INFO`, each fully-qualified as `GAME_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
`.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.