Skip to content

Client-side data pipeline ​

Goal: handle a continuous stock event stream without unbounded memory, UI jank, or cloning the entire buffer into a Worker.

Modules ​

ModulePathRole
Simulatorhooks/use-simulator-stream.ts~0.5 events/s, spike bursts, crew restock every 60s
WebSockethooks/use-stock-websocket.tsLive feed when NEXT_PUBLIC_WS_URL is set
Command Center synchooks/use-command-center-sync.tsDerives incidents → useEventStore for /
Zone stocklib/zone-stock.tsStock %, tier, idle recovery
ETAlib/estimate-minutes-until-empty.tsHeuristic minutes-to-empty from recent pace
Restock hintlib/suggest-restock.tsSingle donor→needy suggestion
Mock generatormock/mock-event-generator.tsSpike-heavy consumption patterns
Ring bufferlib/ring-buffer.tsO(1) capped FIFO slots
Storestate/telemetry-store.tsPublishes immutable snapshots from the ring
Throughputlib/zone-throughput.tsPure aggregation used by tests + worker
Worker hookhooks/use-analytics-worker.tsDebounced sample → Worker → hotspot summary
Workerworkers/analytics.worker.tsOff-thread computeZoneThroughput

Event type ​

typescript
export type StockEvent = {
  zone: string;
  item: string;
  quantity: number; // negative = consumption
  timestamp: number;
};

Store ​

Everything enters through appendEvent / appendEvents. The module-level RingBuffer owns mutable slots; Zustand stores { events, revision } snapshots for React:

typescript
const MAX_EVENTS = 10_000;
// ring.push is O(1); toArray() materializes oldest→newest for subscribers

Same entry point for simulator, WebSocket, or a future API.

Simulator tuning ​

Built so stock tiers become visible within a short demo:

  • Tick every 2000 ms (~0.5/s)
  • 32% of ticks are spikes (−3 or −5); rest −1 or −2
  • One random zone restocked every 60s (+10–21 units)
  • After 40s idle, a zone recovers +1%/s toward 100%

Derivation ​

  • deriveIncidents — groups by zone over 30s for map anchors
  • deriveZoneSnapshots — stock %, demand trend, heat tier
  • estimateMinutesUntilEmpty — decision aid (documented heuristic limits)
  • Worker — relative hotspots from a trailing window sample only

WebSocket ​

useStockWebSocket(url) connects when NEXT_PUBLIC_WS_URL is set and NEXT_PUBLIC_SIMULATOR_ONLY isn't true. Parsed events call the same appendEvent.

Web Worker ​

Runs windowed throughput / hotspot math off the main thread:

  1. Main thread builds a trailing sample via selectWorkerSample (time window + hard cap).
  2. Worker runs computeZoneThroughput and returns rates / hotspot flags.
  3. /dashboard renders Zone throughput.

Effect cleanups stop intervals, close sockets, and terminate the worker on unmount.

See also Architecture and Benchmarks.