PURPOSE

Object pool for Set<any> instances used as bullet hit-tracking collections. Eliminates per-bullet new Set() allocations by recycling cleared Sets between bullet lifetimes. Targets GC pressure caused by Set’s internal hash-table allocations, which are more expensive than plain-object nursery churn at the engine’s bullet throughput.

OWNS

  • _pool: module-level Set<any>[] array holding released, cleared Sets ready for reuse.
  • Hard cap of 500 entries on _pool.length to prevent unbounded growth when bullet population permanently shrinks.
  • The “guaranteed empty on acquire” invariant: callers always receive a Set with no entries.

READS FROM

  • _pool.length to decide whether to pop a recycled Set or allocate a new one.
  • The set argument passed to releaseSet (null/undefined-tolerant).

PUSHES TO

  • _pool (push on release, pop on acquire).
  • Returned Set reference to the caller (assigned to bullet.hits at every call site).

DOES NOT

  • Does not track which Sets are currently in use (no leak detection).
  • Does not validate that a released Set is not still referenced elsewhere.
  • Does not pool any collection type other than Set.
  • Does not warm the pool — first acquires always allocate.
  • Does not shrink the pool below the cap once it has grown.
  • Does not log, telemetry-report, or expose pool statistics.
  • Does not clear the Set on acquire — relies on releaseSet having cleared it before return.

Signals

None. Pure synchronous functions with no event emission, no store writes, no telemetry hooks.

Entry points

  • acquireSet(): Set<any> — returns either _pool.pop() or a freshly constructed new Set().
  • releaseSet(set: Set<any> | null | undefined): void — calls set.clear() then pushes to _pool if under the cap; no-op when set is falsy.

Pattern notes

  • Callers are bullet-creation sites in engine/weapons/bullets.ts, engine/weapons/weapons.ts, and engine/world/artifacts.ts, each assigning the acquired Set to bullet.hits.
  • Release happens in engine/bridge.ts at the bullet-cleanup site when a bullet dies, calling releaseSet(b.hits).
  • Acquire/release must be paired per bullet lifetime. Holding a released Set reference is a contract violation — the same Set may be handed to another bullet on the next acquireSet call.
  • The 500-entry cap is a one-way ratchet: it bounds peak retained memory but never releases pooled Sets back to GC once allocated.
  • LIFO (stack) ordering via push/pop is intentional — keeps recently used Sets warm in CPU cache.
  • Typed as Set<any> rather than a generic parameter; bullet hit-tracking stores enemy IDs but the pool is type-erased at the boundary.