Initial Commit
This commit is contained in:
105
openspec/archive/2026-05-24-game-server-core/proposal.md
Normal file
105
openspec/archive/2026-05-24-game-server-core/proposal.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Wave A — `@suaveplan/game-server` core foundation
|
||||
|
||||
**Package**: `@suaveplan/game-server`
|
||||
**Status**: PROPOSED
|
||||
**Type**: extends existing genesis package (0.1.0)
|
||||
**Design**: ./design.md (inline below)
|
||||
**Spec delta**: ./specs/game-server/spec.md
|
||||
|
||||
## Why
|
||||
|
||||
Wave A ships the core foundation of `@suaveplan/game-server` that every
|
||||
later wave (engine, session, state, matchmaking, transport) depends on:
|
||||
the four core interfaces (`IGameLogic`, `IPlayerManager`,
|
||||
`ISessionManager`, `IStateManager`) with runtime conformance harnesses,
|
||||
the `GameEvents` typed payload map plus its `GAME_EVENT_KEYS` registry,
|
||||
the `GameEventEmitter` typed pub/sub with ergonomic helpers
|
||||
(`safeEmit`, `waitFor`, `onN`, `onMultiple`), and the
|
||||
`AbstractGameLogic` Template-Method base class for concrete games. The
|
||||
package already shipped `DIContainer`, the `GameServerError` registry,
|
||||
and the Zod-validated core types in the prior change
|
||||
`enterprise-game-server-core`; Wave A is an additive extension that
|
||||
fills the interfaces / events / abstract-base layer behind a single
|
||||
0.1.0 publish.
|
||||
|
||||
Without this Wave the downstream waves (B: session manager + player
|
||||
manager, C: state manager + replay, D: matchmaking + transport plugins)
|
||||
have no contract to implement against. Shipping it as one isolated wave
|
||||
keeps the surface reviewable, the coverage gate enforceable on the
|
||||
whole package, and the dependent slices unblocked.
|
||||
|
||||
## What changes
|
||||
|
||||
- ADDED: `core/types/core.types.ts` — `ConnectionStatus`,
|
||||
`PlayerConnection`, `SessionNode`, `SessionData`, `StateChangeEvent`,
|
||||
`GameEvents` map.
|
||||
- ADDED: `core/interfaces/i-game-logic/` — `IGameLogic<TState, TMove>`
|
||||
interface and `runIGameLogicConformance` harness.
|
||||
- ADDED: `core/interfaces/i-player-manager/` — `IPlayerManager` and
|
||||
`runIPlayerManagerConformance`.
|
||||
- ADDED: `core/interfaces/i-session-manager/` — `ISessionManager` and
|
||||
`runISessionManagerConformance`.
|
||||
- ADDED: `core/interfaces/i-state-manager/` — `IStateManager<TState>`
|
||||
and `runIStateManagerConformance`.
|
||||
- ADDED: `core/events/game-events/` — `GAME_EVENT_KEYS` array +
|
||||
`isGameEventKey` predicate.
|
||||
- ADDED: `core/events/game-event-emitter/` — `GameEventEmitter` typed
|
||||
pub/sub with `safeEmit` / `waitFor` / `onN` / `onMultiple` / per-key
|
||||
listener cap.
|
||||
- ADDED: `core/abstract-game-logic/` — `AbstractGameLogic<TState, TMove>`
|
||||
Template-Method base class with injected `Clock` + `IdGenerator` and
|
||||
OTel instrumentation.
|
||||
- EXTENDED: `errors/game-server-error.ts` — six new codes
|
||||
(`INVALID_MOVE`, `PLAYER_ALREADY_EXISTS`, `PLAYER_NOT_FOUND`,
|
||||
`LISTENER_LIMIT_EXCEEDED`, `INVALID_LISTENER_COUNT`,
|
||||
`EVENT_WAIT_TIMEOUT`) raised by the new core surface.
|
||||
- EXTENDED: root `src/index.ts` — additive named re-exports for the new
|
||||
Wave A surface.
|
||||
- EXTENDED: `package.json` exports map — 8 new subpath entries, one per
|
||||
feature module.
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
`bunx turbo ls --filter=...@suaveplan/game-server` enumerates dependents
|
||||
of this package. As of Wave A landing, no genesis package consumes
|
||||
`@suaveplan/game-server` yet — this is a foundation wave for
|
||||
`@suaveplan/game-loop`, `@suaveplan/game-state-schemas`, and the
|
||||
`api-plugin-game-server` family that will land in Waves B–D.
|
||||
|
||||
- `@suaveplan/game-server` (self) — [x] tests run / [x] docs reviewed /
|
||||
[ ] no action needed.
|
||||
- No external dependents — verified by `bunx turbo ls
|
||||
--filter=...@suaveplan/game-server`.
|
||||
|
||||
## Parity Checklist
|
||||
|
||||
| Capability | Upstream | Migrated | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `IGameLogic` interface | YES | YES | Genesis tightens `any` → generic `TState`/`TMove`. |
|
||||
| Inline `AbstractGameLogic` in `IGameLogic.ts` | YES | UP | Genesis splits to its own feature dir. |
|
||||
| `IPlayerManager` interface | YES | YES | Returns `ReadonlyArray<PlayerConnection>`. |
|
||||
| `ISessionManager` interface | YES | YES | Returns `ReadonlyArray<SessionNode>`. |
|
||||
| `IStateManager` interface | YES | YES | `Readonly<TState>` instead of `DeepReadonly`. |
|
||||
| `GameEvents` map | YES | UP | Variadic tuples collapsed to single-payload objects. |
|
||||
| `GameEventEmitter` extending Node `EventEmitter` | YES | UP | Self-contained typed emitter; same surface +typed errors. |
|
||||
| Wildcard `[key: string]: any[]` index signature | YES | NO | Scoped out: closed map keeps lint/type safety; downstream needing wildcard analytics can subscribe to `GAME_EVENT_KEYS` directly. |
|
||||
| `AbstractGameLogic` Template Method | YES | UP | Adds injected `Clock` + `IdGenerator`, typed errors, OTel instruments. |
|
||||
| `cloneState` JSON round-trip | YES | YES | |
|
||||
| Hook methods (`beforeMoveProcessing`, etc.) | YES | YES | |
|
||||
| Player roster (`addPlayer` / `removePlayer`) | YES | UP | Throws `GameServerError` not plain `Error`. |
|
||||
| Conformance harnesses | NO | UP | Genesis-original — makes interfaces runtime-testable. |
|
||||
|
||||
Rationale for the single scope-out (`wildcard analytics:*`): the
|
||||
upstream signature `[key: symbol]: any[]` defeats type safety and is
|
||||
unused inside the `core/` slice; downstream analytics packages can
|
||||
extend `GameEvents` via module augmentation if needed.
|
||||
|
||||
## Migration
|
||||
|
||||
Wave A is additive — no existing public-surface item is renamed or
|
||||
removed. Consumers of the prior `0.1.0` surface (`DIContainer`,
|
||||
`GameServerError`, Zod parsers, types) keep working unchanged. The new
|
||||
error codes extend the registry; pattern-match consumers should not
|
||||
care because none of the new codes were thrown previously.
|
||||
Reference in New Issue
Block a user