7.9 KiB
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 interveningdispose()/disconnect() - THEN the second call throws
GameServerErrorwith codeGAME_SERVER.TRANSPORT_ALREADY_INITIALIZED.
Scenario: send before initialise
- WHEN
send(...)is called beforeinitialize(...) - THEN it rejects with code
GAME_SERVER.TRANSPORT_NOT_INITIALIZED.
Scenario: send while transport disconnected
- WHEN the underlying transport never fires
onConnectandsendis invoked - THEN it rejects with code
GAME_SERVER.TRANSPORT_NOT_CONNECTED.
Scenario: outbound message validation
- WHEN a frame with empty
typeis passed tosend - THEN it rejects with code
GAME_SERVER.INVALID_MESSAGE_FORMAT.
Scenario: fan-out and handler isolation
- WHEN multiple subscribers are attached via
onMessageand 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
reconnectcallback resolves on the first try - THEN the manager resets to
{ attempts: 0, isReconnecting: false }and stops scheduling.
Scenario: exponential retry until success
- WHEN
reconnectfails twice and then succeeds - THEN the manager invokes the callback exactly three times, doubling the envelope between attempts.
Scenario: exhaustion
- WHEN
maxAttemptsisnandreconnectalways rejects - THEN the manager emits
network:reconnect-exhaustedafternfailures and stops scheduling further attempts.
Scenario: double-start guard
- WHEN
start()is invoked while a reconnection is already in progress - THEN it throws
GameServerErrorwith codeGAME_SERVER.RECONNECT_IN_PROGRESS.
Scenario: use after dispose
- WHEN
start()orforceReconnect()is invoked afterdispose() - THEN it throws
GameServerErrorwith codeGAME_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 beforeconnect(...) - 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
onErrorhandler receives a typedGameServerErrorwith codeGAME_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)andapplyAnswer(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
GameServerErrorwith codeGAME_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 asGAME_SERVER.<NAME>.
Verified by:
src/errors/game-server-error.test.ts::"registers all expected codes under GAME_SERVER namespace".