# 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.