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,106 @@
# Enterprise migration: `@suaveplan/game-server` (core slice)
**Package**: `@suaveplan/game-server`
**Status**: PROPOSED
**Type**: new-package
## Why
`@suaveplan/game-server` on the reference branch
`claude/review-game-engine-arch-Kaykr` (main repo
`packages/universal/game-server/`) is a 192-file framework with session
lifecycle, plugin chain, networking, state-sync, matchmaking, and an
"enhanced" variant (anti-cheat / lag-compensation / interest management /
replay / tournament / persistence / leaderboard / state-sync / timing).
The branch's last commit (`2fb25cdb35`) made the engine generic by
removing all game-specific adapters (bingo / tic-tac-toe / gem-crush).
Genesis needs that engine, but porting all 192 files in one slice would
either skip the genesis quality gates (100% test coverage, sibling `.md`
per `.ts`, kebab-case + 5-file layout, typed errors, Zod boundaries,
OTel) or land partially-built code. Six required workspace dependencies
(`controller`, `keys`, `network-webrtc`, `network-websockets`,
`storage-browser`, `workflow`) do not yet exist on genesis, blocking the
`network/`, `plugins/`, and parts of `state/`.
This change ships the **dependency-light foundation** that every later
slice depends on, behind the `@suaveplan/game-server` name at `0.1.0`,
and documents the remaining scope as explicitly deferred follow-on
changes. The package is fully usable today by downstream code that wants
typed errors, Zod-validated boundary types, or the DI container.
## What changes
- ADDED: `@suaveplan/game-server` package at
`packages/gaming/games/game-server/` (universal tier, `sideEffects:
false`, version `0.1.0`).
- ADDED: `GameServerError` + `GAME_SERVER_CODES` registry via
`defineCodes` — four initial codes:
`SERVICE_NOT_REGISTERED`, `INVALID_GAME_CONFIG`,
`INVALID_SESSION_METADATA`, `INVALID_PLAYER_INFO`.
- ADDED: `GameConfig`, `SessionMetadata`, `PlayerInfo`,
`MoveValidationResult`, `GameResult`, `SessionType`, `DeepReadonly`
with Zod schemas (`GameConfigSchema`, `SessionMetadataSchema`,
`PlayerInfoSchema`, `MoveValidationResultSchema`, `GameResultSchema`,
`SessionTypeSchema`) and validators (`parseGameConfig`,
`parseSessionMetadata`, `parsePlayerInfo`,
`tryParseMoveValidationResult`, `tryParseGameResult`).
- ADDED: `DIContainer` with singleton/transient lifecycles, typed error
on miss, and **re-register-invalidates-cache** semantics (genesis
improvement over upstream).
- ADDED: Subpath exports `./errors`, `./types`, `./engine/di-container`
for tree-shaking.
## Out of scope (deferred)
| Surface area | Why deferred | Follow-up change |
|---|---|---|
| `core/{AbstractGameLogic, interfaces/*, events/*}` | Each contract file needs its own 5-file layout + tests + docs; bundling all of them in one slice would force this PR over 30 files. | `enterprise-game-server-core-contracts` |
| `engine/{GameEngine 487, GameLoop 355, PlayerManager 345, PluginManager 340, SessionLifecycle 295, SessionNode 173, SessionStore 128, SessionTree 429}` | Six of eight files exceed the 300-line genesis hard limit and require splitting. Each needs Zod boundary schemas, OTel instruments, typed `GAME_SERVER.*` codes, 100% coverage, ≥200-word sibling `.md`. | `enterprise-game-server-engine` |
| `state/{DeltaCompressor 221, StateDiffer 280, StateManager 316, StateSync 375}` | StateManager and StateSync exceed the 300-line limit; StateSync also reaches into networking which is blocked. | `enterprise-game-server-state` |
| `matchmaking/{MatchmakingEngine 152, QueueManager 169, SpectatorManager 122, skill-matcher 40, winner-stays-on 56}` | Dependency-clean but still requires the engine/state slices to be useful end-to-end. | `enterprise-game-server-matchmaking` |
| `performance/{NetworkTierAdapter, ObjectPool, SessionPool}` → `telemetry/` | Folds into engine slice (`SessionPool` consumes `Session` from engine). | `enterprise-game-server-engine` |
| `network/{NetworkAdapter, ReconnectionManager, WebRTCTransport, WebSocketTransport}` | BLOCKED on missing genesis deps `@suaveplan/network-webrtc` and `@suaveplan/network-websockets`. | After transport packages land. |
| `plugins/{ABTest, Analytics, BasePlugin, FeatureFlag, Logging, Persistence, Rules, Workflow}` | BLOCKED on missing genesis deps `@suaveplan/workflow` and `@suaveplan/keys`. | After workflow/keys packages land. |
| `enhanced/` (73 files: anti-cheat, components, interest-management, lag-compensation, leaderboard, persistence, replay, service-discovery, state-sync, timing, tournament) | Priority 2 per migration brief; the enhanced variant sits above a stable engine. Several enhanced features have transitive deps on the blocked packages. | `enterprise-game-server-enhanced` |
| `integrations/components/game-lobby/*` | Priority 3 — React-coupled UI that belongs in a `react-game-lobby` package, not a universal-tier engine. | `enterprise-react-game-lobby` |
| `integrations/enhanced-server/*` | Bundles every enhanced feature; depends on `enterprise-game-server-enhanced`. | `enterprise-game-server-enhanced` |
| `stories/*`, `e2e/*`, `benchmarks/*` | Storybook/Playwright/bench tooling lands with the surfaces they exercise. | per-slice. |
## Source mapping
This change derives from `../suaveplan/enterprise/game-server.md` (not
present in the worktree's parent repo — the reference is the branch
`claude/review-game-engine-arch-Kaykr` itself, since the package was
under active redesign).
| Requirement | Source lines | Notes |
|---|---|---|
| Typed error registry `GAME_SERVER.*` | `src/engine/DIContainer.ts:30` (only throw site in the migrated slice; upstream used plain `Error`) | reworded — typed error replaces plain throw. |
| `GameConfig`/`SessionMetadata`/`PlayerInfo`/`MoveValidationResult`/`GameResult` types | `src/core/types/index.ts:1-47` | extended — added Zod schemas + validators (no source line). |
| `DIContainer` singleton/transient resolver | `src/engine/DIContainer.ts:1-61` | extended — re-register-invalidates-cache is new in genesis. |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| Zod schemas for every boundary type | Genesis rule: every public boundary is Zod-validated. |
| Typed `GameServerError` instead of plain `Error` | Genesis rule: every package throws via its own `defineCodes` registry. |
| `readonly` modifiers on every interface field | Genesis lint profile rejects `Record<string, any>` and prefers immutable shapes. |
| Re-register-invalidates-cache on `DIContainer` | Bug-class fix: upstream silently kept stale singletons after re-register. |
## Verification approach
- `src/errors/game-server-error.test.ts` — registry shape, error
construction, code discrimination.
- `src/types/common.test.ts` — every schema accept-path, reject-path,
and validator throw-path with the matching code.
- `src/engine/di-container/di-container.test.ts` — singleton, transient,
symbol identifiers, fluent chaining, miss-throws-typed-error,
has/unregister/clear/getRegisteredServices, re-register cache
invalidation.
## Migration impact
None — additive only. No consumer on genesis currently imports
`@suaveplan/game-server`.

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.

View File

@@ -0,0 +1,113 @@
# Tasks — enterprise-game-server-core
## 0. Pre-flight (non-negotiable)
- [x] 0.1 Confirm clean working tree at slice open (verified via `git status` and `git worktree list`: worktree
branched from `25dbec9c8b`, no in-flight uncommitted work).
- [x] 0.2 Account for `.claude/worktrees/`: this slice runs in
`agent-a0756387a9185f112` and is the only active worktree.
- [x] 0.3 `MANIFEST.md` will be regenerated as part of archive (task 7.3).
## 1. Spec authoring
- [x] 1.1 Read reference branch source for the migrated scope.
- [x] 1.2 No existing `openspec/specs/game-server/spec.md` — fresh capability.
- [x] 1.3 Read `openspec/AGENTS.md` §13/§14 hard rules.
- [x] 1.4 Draft `proposal.md`.
- [x] 1.5 `## Source mapping` table populated with line ranges /
no-source justifications.
- [x] 1.6 Draft `specs/game-server/spec.md` with ADDED requirements.
- [x] 1.7 Every requirement carries a `**Verified by:**` line naming
concrete `it(...)` titles.
## 2. Implementation
- [x] 2.1 Package scaffold: `package.json`, `tsconfig.lib.json`,
`vite.config.ts`, `vitest.config.ts`, `tests/setup/polyfills.ts`,
`README.md`.
- [x] 2.2 `src/errors/game-server-error.ts` + barrel — `GameServerError`,
`GAME_SERVER_CODES`, `GameServerCode`.
- [x] 2.3 `src/types/common.types.ts` (interfaces) + `src/types/common.ts`
(Zod schemas + validators) + barrel.
- [x] 2.4 `src/engine/di-container/{di-container.ts, .types.ts, index.ts}`.
- [x] 2.5 Root `src/index.ts` re-exports the full Priority-1 surface.
## 3. Tests
- [x] 3.1 `src/errors/game-server-error.test.ts` — registry shape, error
shape, code-union discrimination.
- [x] 3.2 `src/types/common.test.ts` — every schema accept + reject + the
thrower path with the matching `GAME_SERVER.*` code.
- [x] 3.3 `src/engine/di-container/di-container.test.ts` — 11 `it()`
cases covering singleton, transient, symbol identifier, fluent
chaining, miss-throws-typed-error (string + symbol),
has/unregister/clear/getRegisteredServices, re-register cache
invalidation.
- [x] 3.4 Every `it(...)` carries `{ timeout: 5000 }`.
## 4. Documentation
- [x] 4.1 Sibling `.md` ≥200 words for each non-exempt `.ts`:
`game-server-error.md`, `common.md`, `di-container.md`.
- [x] 4.2 `README.md` lists each `.md` under `## Modules` and tracks
deferred surfaces.
- [x] 4.3 No CLAUDE.md package-listing edit required — the rewrite of
`enterprise-gaming-game-server` into this slice plus deferred
follow-ups will be captured when the listing is next refreshed.
## 5. Pre-archive verification (the 5-gate triple)
- [ ] 5.1 `bun run check` — Biome 0/0/0 across the package. **NOT RUN**
in this session: scope-management constraint per migration brief.
- [ ] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/game-server`
— green. **NOT RUN** in this session.
- [ ] 5.3 `bunx turbo test:coverage --filter=@suaveplan/game-server` —
100/100/100/100. **NOT RUN** in this session.
- [ ] 5.4 `bun scripts/openspec-conformance.ts @suaveplan/game-server` —
exit 0. **NOT RUN** in this session.
- [ ] 5.5 `bunx turbo build --filter=@suaveplan/game-server` — green.
**NOT RUN** in this session.
> The 5 boxes above remain unchecked because this slice was authored
> under a single-session migration brief and the pre-archive gates
> require a live Bun/Turbo invocation against the working tree. They
> MUST be run and made green before this change is archived (task 7).
## 6. Pre-archive 7-row checklist (AGENTS.md §13)
- [x] 6.1 Generics threaded — `ServiceIdentifier<T>` carries through
`register`/`resolve`; schema validator return types preserve
readonly modifiers via explicit cast (canonical Zod↔TS bridge).
- [x] 6.2 Zod schemas wired at every declared boundary
(`GameConfig`/`SessionMetadata`/`PlayerInfo`/`MoveValidationResult`/`GameResult`).
- [x] 6.3 Every code in `error-codes.ts` thrown by at least one
production path: `SERVICE_NOT_REGISTERED` (di-container.ts:39),
`INVALID_GAME_CONFIG` (common.ts:88), `INVALID_SESSION_METADATA`
(common.ts:103), `INVALID_PLAYER_INFO` (common.ts:118).
- [x] 6.4 Strict-mode behaviors tested both ways — `MoveValidationResult`
strict-extra rejection, `GameResult` strict-extra rejection,
`SessionMetadata` passthrough acceptance, `GameConfig` passthrough
acceptance.
- [ ] 6.5 OTel instruments not introduced in this slice — Priority-1
DI container has no metric-emitting paths. Deferred to the engine
slice (`enterprise-game-server-engine`) which lands `Session`
tick-duration histograms and `SessionManager` lifecycle counters.
- [ ] 6.6 Lifecycle dedup — not applicable to the four artifacts in this
slice. Deferred to the engine slice where `SessionStore` /
`SessionLifecycle` introduce idempotent lifecycle ops.
- [x] 6.7 No retroactive ticks — every box above reflects work done in
this slice; un-runnable gates and not-applicable rows are
unchecked with an explicit reason.
## 7. Archive
- [ ] 7.1 Fold `specs/game-server/spec.md` into
`openspec/specs/game-server/spec.md` (currently absent — first
authoring of the capability).
- [ ] 7.2 Move `openspec/changes/enterprise-game-server-core/` →
`openspec/archive/2026-05-23-enterprise-game-server-core/`.
- [ ] 7.3 `bun scripts/folder-reorg/generate-manifest.ts` to refresh
`MANIFEST.md`.
> Tasks 5.x and 7.x will run as the next session's first action once the
> 5-gate triple goes green.