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

111 lines
7.5 KiB
Markdown
Raw Permalink 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.
<!-- 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 -->