Files
suaveplan/openspec/archive/2026-05-24-game-server-engine-core/proposal.md
2026-05-27 23:29:53 +01:00

82 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.