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,110 @@
<!-- 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 -->