111 lines
7.5 KiB
Markdown
111 lines
7.5 KiB
Markdown
<!-- markdownlint-disable MD024 -->
|
||
|
||
# Capability Spec Delta: bitpacker-network (bitpacker-stub-completion)
|
||
|
||
**Package**: `@suaveplan/bitpacker-network`
|
||
**Status**: PROPOSED
|
||
|
||
> Every requirement below carries a `**Verified by:**` line at authoring time.
|
||
|
||
## ADDED Requirements
|
||
|
||
### Requirement: NetworkSimulator class with full upstream feature surface
|
||
|
||
The package SHALL expose a `NetworkSimulator extends EventEmitter` class implementing bandwidth-limited delivery, configurable latency baseline + jitter, packet loss probability, packet duplication probability, packet corruption probability, in-flight reordering, burstiness scheduling, and random-disconnect scheduling. Public API matches upstream `network-simulation.ts` (L15-L593): `send(packet)`, `start()`, `stop()`, `disconnect(durationMs)`, `reconnect()`, `getStats()`, `clearStats()`, `setOptions(partial)`.
|
||
|
||
#### Scenario: bandwidth limit honoured across a 1-second window
|
||
|
||
- **WHEN** `bandwidthBytesPerSecond` is 1000 and 5000 bytes are submitted in 100ms
|
||
- **THEN** the simulator emits exactly 1000 bytes' worth of `delivered` events in the first second; the remaining 4000 bytes queue and deliver across the next 4 seconds
|
||
|
||
#### Scenario: latency + jitter inside configured bounds
|
||
|
||
- **WHEN** `latencyMs` is 50 and `jitterMs` is 10
|
||
- **THEN** every delivered packet's measured delivery delay is within `[40, 60]` ms
|
||
|
||
#### Scenario: packet-loss probability hits empirical bucket
|
||
|
||
- **WHEN** `lossProbability` is 0.25 and 1000 packets are sent through a deterministic `Rng`
|
||
- **THEN** the recorded `stats.packetsLost` falls within `[230, 270]` (25% ± 2.5 σ for n=1000)
|
||
|
||
#### Scenario: duplication, corruption, reordering surface as events
|
||
|
||
- **WHEN** the corresponding probabilities are non-zero
|
||
- **THEN** `duplicate`, `corrupt`, and `reorder` events fire with the affected `PacketInfo` payload AND `stats.packetsDuplicated` / `stats.packetsCorrupted` / `stats.packetsReordered` increment accordingly
|
||
|
||
#### Scenario: burstiness scheduler activates burst then ends it
|
||
|
||
- **WHEN** `enableBurstiness: true` with `burstIntervalMs: 100`, `burstDurationMs: 50`, `burstLossMultiplier: 4`
|
||
- **THEN** during the burst window, effective `lossProbability` is 4× the configured value, and after `burstDurationMs` it returns to baseline
|
||
|
||
#### Scenario: disconnect / reconnect lifecycle
|
||
|
||
- **WHEN** `disconnect(200)` is called
|
||
- **THEN** `connected` becomes false, all pending sends emit `dropped`, and after 200ms `connected` becomes true again with a `reconnected` event
|
||
|
||
**Verified by:** `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"honours bandwidth limit across a 1-second window"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"applies latency + jitter inside configured bounds"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"loss probability hits empirical bucket over 1000 packets"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"emits duplicate / corrupt / reorder events with PacketInfo"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"burstiness scheduler activates and deactivates the loss multiplier"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"disconnect drops in-flight, reconnect restores delivery"`
|
||
|
||
---
|
||
|
||
### Requirement: NETWORK_CONDITIONS preset bindings
|
||
|
||
The package SHALL export a `NETWORK_CONDITIONS` map binding each of `IDEAL`, `HOME`, `MOBILE_4G`, `MOBILE_3G`, `SATELLITE`, `LOSSY_WIFI`, `CUSTOM` to a `NetworkSimulationOptions` object. The values SHALL match upstream constants byte-for-byte (cited in `proposal.md` source mapping).
|
||
|
||
#### Scenario: preset selection populates options
|
||
|
||
- **WHEN** a `NetworkSimulator` is constructed with `{ condition: "MOBILE_3G" }`
|
||
- **THEN** the resulting options match `NETWORK_CONDITIONS.MOBILE_3G` for `latencyMs`, `jitterMs`, `bandwidthBytesPerSecond`, `lossProbability`
|
||
|
||
#### Scenario: explicit fields override preset
|
||
|
||
- **WHEN** `{ condition: "MOBILE_3G", lossProbability: 0 }` is passed
|
||
- **THEN** the resulting options take `lossProbability: 0` and inherit the rest from the preset
|
||
|
||
**Verified by:** `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"NETWORK_CONDITIONS presets bind to options as documented"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"explicit options override preset values"`
|
||
|
||
---
|
||
|
||
### Requirement: Deterministic Rng and Clock injection
|
||
|
||
The package SHALL accept an optional `Rng` and `Clock` from `@suaveplan/types`. Default `Rng` is the `crypto.getRandomValues`-backed `Rng.crypto()`. The package SHALL NOT call `Math.random()` or `Date.now()` at module scope or in production code paths.
|
||
|
||
#### Scenario: deterministic Rng yields reproducible loss sequence
|
||
|
||
- **WHEN** a seeded `Rng` is injected and the same 100-packet input is replayed
|
||
- **THEN** the set of dropped packet indexes is byte-identical between runs
|
||
|
||
#### Scenario: deterministic Clock advances burst windows
|
||
|
||
- **WHEN** a fake clock is injected and ticked manually
|
||
- **THEN** burst start / end fire at exactly the configured offsets
|
||
|
||
#### Scenario: no module-scope `Math.random()` or `Date.now()`
|
||
|
||
- **WHEN** `grep -E "Math\.random\(\)|Date\.now\(\)" packages/bitpacker/bitpacker-network/src/` is run
|
||
- **THEN** there are zero matches in implementation files
|
||
|
||
**Verified by:** `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"seeded Rng yields reproducible loss sequence"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"fake Clock advances burst windows deterministically"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"no module-scope Math.random or Date.now in implementation files"`
|
||
|
||
---
|
||
|
||
### Requirement: Stat counters bounded; overflow signalled
|
||
|
||
The simulator SHALL track `packetsSent`, `packetsReceived`, `packetsLost`, `packetsDuplicated`, `packetsCorrupted`, `packetsReordered`, `bytesSent`, `bytesReceived`. When any counter would exceed `Number.MAX_SAFE_INTEGER`, the simulator SHALL emit `BitpackerError` with code `BITPACKER.NETWORK_STAT_OVERFLOW` and `context.counter` naming the affected field; the counter SHALL freeze at `MAX_SAFE_INTEGER`.
|
||
|
||
#### Scenario: stat overflow throws coded error
|
||
|
||
- **WHEN** `packetsSent` is one below `MAX_SAFE_INTEGER` and one more packet is sent
|
||
- **THEN** a `BitpackerError` with code `BITPACKER.NETWORK_STAT_OVERFLOW` is emitted as a `stat-overflow` event AND `stats.packetsSent === Number.MAX_SAFE_INTEGER`
|
||
|
||
**Verified by:** `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"emits NETWORK_STAT_OVERFLOW when a counter would exceed MAX_SAFE_INTEGER"`
|
||
|
||
## REMOVED Requirements
|
||
|
||
### Requirement: 48-line `Math.random()`-based stub at `bitpacker-network/src/index.ts`
|
||
|
||
**Reason:** Stub used module-scope `Math.random()` (CLAUDE.md determinism rule violation) and exposed only three free functions; the real 622-line implementation lived (misplaced) at `bitpacker-game/src/network/network-simulation.ts`.
|
||
|
||
**Migration:** The three existing free functions (`simulateLatency`, `shouldDropPacket`, `applyNetworkConditions`) are preserved as thin wrappers around the new simulator. Existing call sites compile unchanged. The misplaced copy at `bitpacker-game/src/network/network-simulation.ts` is deleted; a one-release shim re-exports the simulator from `@suaveplan/bitpacker-network` so any consumer importing it via the game package's deep path keeps compiling.
|
||
|
||
<!-- markdownlint-enable MD024 -->
|