82 lines
4.8 KiB
Markdown
82 lines
4.8 KiB
Markdown
# game-server-engine-core — GameEngine + GameLoop + PluginManager
|
||
|
||
**Package**: `@suaveplan/game-server`
|
||
**Status**: PROPOSED
|
||
**Type**: feature (Wave B-1 migration slice)
|
||
|
||
- **Design**: see this file (no separate design.md required for a thin orchestrator slice)
|
||
- **Spec delta**: ./specs/game-server/spec.md
|
||
|
||
## Why
|
||
|
||
Wave A landed the foundations (`errors`, `types`, `engine/di-container`). The
|
||
broader package still has no engine top-level — there is no `GameEngine` to
|
||
orchestrate services, no `GameLoop` to drive deterministic ticks, and no
|
||
`PluginManager` to wire third-party extensions. Without these the package is
|
||
unusable for actual game work.
|
||
|
||
This slice (Wave B-1) ships the three engine primitives in dependency order
|
||
so Wave B-2 (sessions/player-manager) and later state/network/matchmaking
|
||
slices have a stable top-level to bind to. The migration is verbatim in
|
||
intent (preserve upstream lifecycle + statistics surface) but recast at
|
||
genesis bar — injectable `Clock`/`TimersLike`/`IdGenerator`, typed
|
||
`GameServerError` codes, Zod-validated boundaries, OTel counters and
|
||
histograms with real `MeterProvider` assertions, kebab-case `<feature>/`
|
||
layout, 100/100/100/100 coverage.
|
||
|
||
## What changes
|
||
|
||
- ADDED: `GameEngine` (in `./engine/game-engine`) — top-level orchestrator wiring DI-injected services with `start`/`stop`/`tick`/`dispose` lifecycle.
|
||
- ADDED: `GameLoop` (in `./engine/game-loop`) — deterministic fixed-step loop with `Clock`/`TimersLike` injection, `tick-based`/`event-driven`/`hybrid` modes, pause/resume, `tick()` manual driver.
|
||
- ADDED: `PluginManager` (in `./engine/plugin-manager`) — typed `EnginePlugin` registry with `install`/`uninstall`/`enable`/`disable`/`get` + lifecycle-hook execution wrapped in `PLUGIN_LIFECYCLE_FAILED`.
|
||
- ADDED: `GAME_SERVER.*` codes — `ENGINE_NOT_STARTED`, `ENGINE_ALREADY_STARTED`, `ENGINE_DISPOSED`, `INVALID_ENGINE_OPTIONS`, `INVALID_GAME_LOOP_OPTIONS`, `GAME_LOOP_NOT_RUNNING`, `GAME_LOOP_ALREADY_RUNNING`, `PLUGIN_NOT_FOUND`, `DUPLICATE_PLUGIN`, `PLUGIN_LIFECYCLE_FAILED`.
|
||
- ADDED: subpath exports `./engine/game-engine`, `./engine/game-loop`, `./engine/plugin-manager`.
|
||
|
||
## Out of scope
|
||
|
||
- `SessionManager`, `PlayerManager`, `SessionTree` (Wave B-2 — orchestrator consumes them as opaque structural service interfaces only).
|
||
- `StateManager`, `NetworkAdapter`, `MatchmakingEngine`, `QueueManager` (later waves).
|
||
- Game-logic strategy concretes (`AbstractGameLogic`) — the engine accepts an opaque `IGameLogicLike` slot per upstream contract.
|
||
|
||
## Source mapping
|
||
|
||
This change derives from upstream files on `/mnt/s/web/suaveplan`:
|
||
|
||
| Requirement | Source lines | Notes |
|
||
|---|---|---|
|
||
| GameEngine orchestrator | `packages/universal/game-server/src/engine/GameEngine.ts` L60–L497 | Recast — drops session/state/network constructor wiring (Wave B-2+ scope); preserves DI-service registration + start/stop/dispose lifecycle. |
|
||
| GameLoop deterministic ticks | `packages/universal/game-server/src/engine/GameLoop.ts` L36–L358 | Recast — `Date.now()` replaced by injectable `Clock`; `setInterval/clearInterval` replaced by `TimersLike`. |
|
||
| PluginManager lifecycle | `packages/universal/game-server/src/engine/PluginManager.ts` L27–L355 | Recast — plain `Error` → `GameServerError`; dependency-aware topo sort preserved. |
|
||
|
||
### Requirements with no source line
|
||
|
||
| Requirement | Justification |
|
||
|---|---|
|
||
| OTel counters + histograms | Genesis bar — upstream had no OTel; this slice adds `game_server.engine.lifecycle_total`, `game_server.loop.tick_duration_ms`, `game_server.plugin.lifecycle_total` to match the package quality bar (mirrors `@suaveplan/ecs`). |
|
||
| Zod-validated option boundaries | Genesis bar — every public option boundary parses through Zod before reaching state. |
|
||
| Typed `GameServerError` everywhere | Genesis bar — upstream threw plain `Error` for lifecycle violations. |
|
||
|
||
## Verification approach
|
||
|
||
- `src/engine/game-engine/game-engine.test.ts` — covers GameEngine lifecycle, DI wiring, plugin handoff, OTel emission.
|
||
- `src/engine/game-loop/game-loop.test.ts` — covers loop modes, deterministic ticking via injected Clock/TimersLike, pause/resume, error propagation.
|
||
- `src/engine/plugin-manager/plugin-manager.test.ts` — covers install/uninstall/enable/disable, duplicate detection, dependency topo sort, hook execution.
|
||
- `src/errors/game-server-error.test.ts` — extended to assert every new code.
|
||
|
||
## Impact
|
||
|
||
### Affected dependents
|
||
|
||
Run `bunx turbo ls --filter=...@suaveplan/game-server` to enumerate. The
|
||
package has no shipped dependents on genesis at the time of this change
|
||
(verified: `bunx turbo ls --filter=...@suaveplan/game-server` returns only
|
||
the package itself).
|
||
|
||
- No dependents — additive surface only.
|
||
|
||
## Migration impact
|
||
|
||
None — additive only. Existing `./errors`, `./types`, `./engine/di-container`
|
||
surfaces are unchanged. The `GAME_SERVER_CODES` registry is **extended**;
|
||
the existing 4 codes are preserved.
|