Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
# Migration: `@suaveplan/game-server` network slice (Wave B-4)
**Package**: `@suaveplan/game-server`
**Status**: PROPOSED
**Type**: extends-package
## Why
`@suaveplan/game-server` shipped its core slice (errors, types,
di-container) on Wave A. The network module is one of the four parallel
B-wave slices that bring the package up to the broader reference surface
from `claude/review-game-engine-arch-Kaykr`. Without a unified
`NetworkAdapter` + reconnection driver + WebSocket/WebRTC transports,
downstream session and matchmaking modules (Waves B-1…B-3, B-5) cannot
wire to a transport at all — they all consume the `Transport` contract
defined here.
The reference branch's network module (`network/NetworkAdapter.ts`,
`ReconnectionManager.ts`, `WebSocketTransport.ts`, `WebRTCTransport.ts`)
mixes runtime concerns (raw `WebSocket` / `RTCPeerConnection`) with
framework concerns (event emitter, logger). Genesis splits those: the
unified adapter contract lives here, and the actual platform-tier
clients live in `@suaveplan/network-transport-websockets` and
`@suaveplan/network-transport-webrtc`. The wrappers in this slice are
the thin glue layer that bridges those two worlds via a structural
boundary so tests can use in-process fakes instead of real sockets.
## What changes
- ADDED: `src/network/network-adapter/` — unified `NetworkAdapter` class
with single-init guard, Zod boundary validation, multi-subscriber
fan-out, isolated handler errors, OTel counters
(`game_server.network.messages_sent_total`,
`…messages_received_total`, `…errors_total`).
- ADDED: `src/network/reconnection-manager/` — exponential-backoff
reconnection driver via `@suaveplan/retry`'s
`createExponentialBackoff` + `applyJitter`, injectable
`Clock` + `TimersLike`, OTel counter + histogram
(`reconnect_attempts_total`, `reconnect_delay_ms`).
- ADDED: `src/network/websocket-transport/` — thin wrapper implementing
`Transport` over a structural `UnderlyingWebSocket` boundary.
- ADDED: `src/network/webrtc-transport/` — thin wrapper implementing
`Transport` over a structural `UnderlyingWebRTC` boundary, plus
pass-through helpers (`addIceCandidate`, `applyAnswer`).
- ADDED: Six new `GAME_SERVER.*` error codes:
`TRANSPORT_ALREADY_INITIALIZED`, `TRANSPORT_NOT_INITIALIZED`,
`TRANSPORT_NOT_CONNECTED`, `INVALID_MESSAGE_FORMAT`,
`RECONNECT_EXHAUSTED`, `RECONNECT_IN_PROGRESS`.
- ADDED: Subpath exports `./network/network-adapter`,
`./network/reconnection-manager`, `./network/websocket-transport`,
`./network/webrtc-transport`.
- ADDED: Workspace deps `@suaveplan/network-transport-websockets`,
`@suaveplan/network-transport-webrtc`, `@suaveplan/retry`.
## Out of scope (deferred to later waves)
None. Network slice ships at full functional completeness for the
`NetworkAdapter` + Reconnection + WebSocket + WebRTC contract pair.
Real-world wiring of the underlying clients to the genesis transport
packages is a downstream concern in each application; the wrappers
themselves are complete.
### Affected dependents
- Downstream session and matchmaking slices (B-1, B-2, B-3, B-5)
consume `Transport` and `NetworkAdapter` exclusively through their
exported names; no additional change needed.
## Source mapping
| Genesis path | Upstream reference |
|---|---|
| `src/network/network-adapter/network-adapter.ts` | `packages/universal/game-server/src/network/NetworkAdapter.ts` (full rewrite into structural `Transport` contract, Zod boundary, OTel) |
| `src/network/reconnection-manager/reconnection-manager.ts` | `packages/universal/game-server/src/network/ReconnectionManager.ts` (rewritten to consume `@suaveplan/retry`; storage persistence dropped — handled by a future state slice) |
| `src/network/websocket-transport/websocket-transport.ts` | `packages/universal/game-server/src/network/WebSocketTransport.ts` (wraps `UnderlyingWebSocket` structural client instead of raw `WebSocket`) |
| `src/network/webrtc-transport/webrtc-transport.ts` | `packages/universal/game-server/src/network/WebRTCTransport.ts` (wraps `UnderlyingWebRTC` structural client instead of raw `RTCPeerConnection`) |

View File

@@ -0,0 +1,212 @@
# 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"`.

View File

@@ -0,0 +1,56 @@
# Tasks — game-server-network (Wave B-4)
## 0. Pre-flight (non-negotiable)
- [x] 0.1 Confirm isolated worktree (`pwd` shows
`.claude/worktrees/agent-ab405575252114357`).
- [x] 0.2 `git worktree list` accounted for; all sibling B-wave agents
operate in their own locked worktrees.
- [x] 0.3 Read reference branch source at
`/mnt/s/web/suaveplan/packages/universal/game-server/src/network/`.
## 1. Spec authoring
- [x] 1.1 Read `openspec/AGENTS.md` and `CLAUDE.md`.
- [x] 1.2 Draft `proposal.md` with Source mapping table.
- [x] 1.3 Draft `specs/game-server/spec.md` with ADDED requirements and
`**Verified by:**` lines naming planned tests.
## 2. Implementation
- [x] 2.1 Extend `errors/game-server-error.ts` with six new codes and
update the registry test.
- [x] 2.2 `src/network/network-adapter/{types,impl,index,md,test}`.
- [x] 2.3 `src/network/reconnection-manager/{types,impl,index,md,test}`.
- [x] 2.4 `src/network/websocket-transport/{types,impl,index,md,test}`.
- [x] 2.5 `src/network/webrtc-transport/{types,impl,index,md,test}`.
- [x] 2.6 Subpath exports in `package.json` and workspace dependencies.
- [x] 2.7 Root `src/index.ts` re-exports the network surface.
## 3. Tests
- [x] 3.1 `network-adapter.test.ts` — 20 cases covering init, send,
receive, fan-out, handler isolation, stats, errors, dispose.
- [x] 3.2 `reconnection-manager.test.ts` — 18 cases covering success,
retry, exhaustion, lifecycle events, config update, stop in
flight.
- [x] 3.3 `websocket-transport.test.ts` — 11 cases covering connect,
send, parse, callbacks.
- [x] 3.4 `webrtc-transport.test.ts` — 14 cases covering connect, send,
parse, signalling helpers.
## 4. Verification
- [x] 4.1 `bunx turbo lint typecheck test --filter=@suaveplan/game-server`
green.
- [x] 4.2 `bunx turbo test:coverage --filter=@suaveplan/game-server`
meets 100/100/100/100 on touched modules.
- [x] 4.3 No `vitest|bun:test` direct imports in `src/`.
- [x] 4.4 No `biome-ignore` / `TODO` / `FIXME` markers in `src/`.
## 5. Fold + archive
- [ ] 5.1 Fold the delta into `openspec/specs/game-server/spec.md` on
merge.
- [ ] 5.2 Move the change folder to
`openspec/archive/<yyyy-mm-dd>-game-server-network/` on merge.