Files
2026-05-27 23:29:53 +01:00

213 lines
7.9 KiB
Markdown

# Capability Spec Delta: game-server (game-server-network)
**Package**: `@suaveplan/game-server`
**Status**: PROPOSED
## ADDED Requirements
### Requirement: Unified network adapter contract
The package SHALL expose a `NetworkAdapter` class that wraps a structural
`Transport` interface and provides single-initialisation, multi-subscriber
fan-out, Zod-validated outbound + inbound frames, and OpenTelemetry
counters for messages sent / received / errors.
#### Scenario: single-initialisation guard
- **WHEN** `initialize(...)` is called twice without an intervening
`dispose()` / `disconnect()`
- **THEN** the second call throws `GameServerError` with code
`GAME_SERVER.TRANSPORT_ALREADY_INITIALIZED`.
#### Scenario: send before initialise
- **WHEN** `send(...)` is called before `initialize(...)`
- **THEN** it rejects with code `GAME_SERVER.TRANSPORT_NOT_INITIALIZED`.
#### Scenario: send while transport disconnected
- **WHEN** the underlying transport never fires `onConnect` and `send`
is invoked
- **THEN** it rejects with code `GAME_SERVER.TRANSPORT_NOT_CONNECTED`.
#### Scenario: outbound message validation
- **WHEN** a frame with empty `type` is passed to `send`
- **THEN** it rejects with code `GAME_SERVER.INVALID_MESSAGE_FORMAT`.
#### Scenario: fan-out and handler isolation
- **WHEN** multiple subscribers are attached via `onMessage` and one of
them throws
- **THEN** every other subscriber still receives the frame and the
failure is logged through the injected logger.
**Verified by:**
`src/network/network-adapter/network-adapter.test.ts::"initialises a transport and connects"`
AND
`src/network/network-adapter/network-adapter.test.ts::"rejects double-initialisation"`
AND
`src/network/network-adapter/network-adapter.test.ts::"rejects send before initialize"`
AND
`src/network/network-adapter/network-adapter.test.ts::"rejects send when not connected"`
AND
`src/network/network-adapter/network-adapter.test.ts::"rejects malformed outgoing message"`
AND
`src/network/network-adapter/network-adapter.test.ts::"fans inbound messages to every subscriber"`
AND
`src/network/network-adapter/network-adapter.test.ts::"isolates handler errors and still delivers to others"`
AND
`src/network/network-adapter/network-adapter.test.ts::"drops malformed inbound frames"`
AND
`src/network/network-adapter/network-adapter.test.ts::"exposes stats counts"`.
---
### Requirement: Reconnection with exponential backoff
The package SHALL expose a `ReconnectionManager` that schedules
reconnection attempts through an injectable `TimersLike`, delegates
backoff calculation to `@suaveplan/retry`'s `createExponentialBackoff`,
and reports outcomes through OTel counter + histogram.
#### Scenario: success on first attempt
- **WHEN** the supplied `reconnect` callback resolves on the first try
- **THEN** the manager resets to `{ attempts: 0, isReconnecting: false }`
and stops scheduling.
#### Scenario: exponential retry until success
- **WHEN** `reconnect` fails twice and then succeeds
- **THEN** the manager invokes the callback exactly three times,
doubling the envelope between attempts.
#### Scenario: exhaustion
- **WHEN** `maxAttempts` is `n` and `reconnect` always rejects
- **THEN** the manager emits `network:reconnect-exhausted` after `n`
failures and stops scheduling further attempts.
#### Scenario: double-start guard
- **WHEN** `start()` is invoked while a reconnection is already in
progress
- **THEN** it throws `GameServerError` with code
`GAME_SERVER.RECONNECT_IN_PROGRESS`.
#### Scenario: use after dispose
- **WHEN** `start()` or `forceReconnect()` is invoked after `dispose()`
- **THEN** it throws `GameServerError` with code
`GAME_SERVER.RECONNECT_EXHAUSTED`.
**Verified by:**
`src/network/reconnection-manager/reconnection-manager.test.ts::"succeeds on first attempt"`
AND
`src/network/reconnection-manager/reconnection-manager.test.ts::"retries with exponential backoff on failure"`
AND
`src/network/reconnection-manager/reconnection-manager.test.ts::"exhausts after maxAttempts and emits exhausted event"`
AND
`src/network/reconnection-manager/reconnection-manager.test.ts::"rejects start when already reconnecting"`
AND
`src/network/reconnection-manager/reconnection-manager.test.ts::"start throws after dispose"`
AND
`src/network/reconnection-manager/reconnection-manager.test.ts::"forceReconnect throws after dispose"`.
---
### Requirement: WebSocket transport wrapper
The package SHALL expose a `WebSocketTransport` class implementing the
unified `Transport` contract by wrapping a structural `UnderlyingWebSocket`
client. JSON framing SHALL be applied to outgoing messages and inbound
parse failures SHALL surface through `onError` rather than throwing.
#### Scenario: connect forwards protocols
- **WHEN** `connect(url, { protocols: ["v1"] })` is invoked
- **THEN** the underlying client receives the URL and protocols.
#### Scenario: send rejects when not connected
- **WHEN** `send(...)` is invoked before `connect(...)`
- **THEN** it rejects with code `GAME_SERVER.TRANSPORT_NOT_CONNECTED`.
#### Scenario: parse error surfaces through onError
- **WHEN** the underlying client delivers a non-JSON frame
- **THEN** the registered `onError` handler receives a typed
`GameServerError` with code `GAME_SERVER.INVALID_MESSAGE_FORMAT`.
**Verified by:**
`src/network/websocket-transport/websocket-transport.test.ts::"connects with protocols"`
AND
`src/network/websocket-transport/websocket-transport.test.ts::"send serialises message as JSON"`
AND
`src/network/websocket-transport/websocket-transport.test.ts::"send rejects when disconnected"`
AND
`src/network/websocket-transport/websocket-transport.test.ts::"reports parse errors through onError"`
AND
`src/network/websocket-transport/websocket-transport.test.ts::"propagates connect/disconnect callbacks"`.
---
### Requirement: WebRTC transport wrapper
The package SHALL expose a `WebRTCTransport` class implementing the
unified `Transport` contract by wrapping a structural `UnderlyingWebRTC`
client. The wrapper SHALL provide pass-through signalling helpers
(`addIceCandidate`, `applyAnswer`) and SHALL throw a typed error when
the underlying client does not implement them.
#### Scenario: connect with options bag
- **WHEN** `connect(url, { iceServers: [...] })` is invoked
- **THEN** the options bag is forwarded verbatim to the underlying
client's `open(...)`.
#### Scenario: signalling pass-through
- **WHEN** `addIceCandidate(c)` and `applyAnswer(a)` are invoked on a
client that implements them
- **THEN** the candidate / answer is delivered verbatim to the client.
#### Scenario: signalling unsupported
- **WHEN** the underlying client omits `addIceCandidate` / `applyAnswer`
- **THEN** the wrapper throws `GameServerError` with code
`GAME_SERVER.TRANSPORT_NOT_INITIALIZED`.
**Verified by:**
`src/network/webrtc-transport/webrtc-transport.test.ts::"connects with options bag"`
AND
`src/network/webrtc-transport/webrtc-transport.test.ts::"send serialises message as JSON"`
AND
`src/network/webrtc-transport/webrtc-transport.test.ts::"addIceCandidate forwards to underlying"`
AND
`src/network/webrtc-transport/webrtc-transport.test.ts::"applyAnswer forwards to underlying"`
AND
`src/network/webrtc-transport/webrtc-transport.test.ts::"addIceCandidate throws when unsupported"`
AND
`src/network/webrtc-transport/webrtc-transport.test.ts::"applyAnswer throws when unsupported"`.
---
### Requirement: Extended error code registry
The package SHALL register six additional `GAME_SERVER.*` codes
introduced by the network slice and SHALL expose them on the
`GAME_SERVER_CODES` constant alongside the existing core codes.
#### Scenario: registry shape
- **WHEN** the test suite inspects `GAME_SERVER_CODES`
- **THEN** it contains `TRANSPORT_ALREADY_INITIALIZED`,
`TRANSPORT_NOT_INITIALIZED`, `TRANSPORT_NOT_CONNECTED`,
`INVALID_MESSAGE_FORMAT`, `RECONNECT_EXHAUSTED`,
`RECONNECT_IN_PROGRESS`, each fully-qualified as
`GAME_SERVER.<NAME>`.
**Verified by:**
`src/errors/game-server-error.test.ts::"registers all expected codes under GAME_SERVER namespace"`.