entity.ts

PURPOSE

Central entity registry for the engine. Allocates unique numeric entity IDs (eid), tags every registered object with its type string, and provides O(1) lookup, removal, existence check, and full-clear operations. Every ship, enemy, projectile, pickup, and other simulation object that needs identity flows through here. Ported from the legacy 06-systems.js Entity block.

OWNS

  • Entity singleton (exported const) with internal state:
    • _nextId — monotonic ID counter, starts at 1.
    • _registryMap<number, any> keyed by entity ID, holds the registered object reference.
  • Public surface:
    • count getter — returns _registry.size.
    • create(type, obj) — assigns obj.eid and obj._entityType, registers the object, returns the new ID.
    • get(id) — returns the registered object or null if absent.
    • remove(id) — deletes the entry from _registry.
    • has(eid) — boolean existence check.
    • clear() — wipes _registry and resets _nextId to 1.

READS FROM

  • Nothing. Module is self-contained: no imports, no external state.
  • Callers pass in the type string and object reference; the module never inspects object fields beyond writing eid and _entityType.

PUSHES TO

  • The supplied entity object — mutates obj.eid (number) and obj._entityType (string) inside create.
  • The internal _registry map — every create insert, every remove delete, full purge on clear.

DOES NOT

  • Does not allocate the entity object — callers construct their own objects and hand them in.
  • Does not pool or recycle IDs — _nextId is monotonic; an ID is never reissued within a run.
  • Does not emit signals on create or remove.
  • Does not iterate or tick entities — no per-frame logic lives here. Per-type lists (ships, enemies, projectiles, etc.) are owned by their respective subsystems and gameplay arrays in state.ts.
  • Does not cascade-cleanup associated state. The header comment notes that Modifiers and FX systems clean up on entity removal by their own mechanisms; remove only drops the registry entry.
  • Does not validate that id exists on remove — silently no-ops on unknown IDs.
  • Does not enforce uniqueness of the type string.

Signals

None. This module neither subscribes to nor emits any signals.

Entry points

  • Entity.create(type, obj) — called by spawn paths whenever a new simulation object needs a stable identity. Returns the assigned eid.
  • Entity.get(id) — used wherever code holds an eid and needs to resolve back to the live object (modifier targets, signal payloads, lookup-by-id flows).
  • Entity.has(eid) — guard before dereferencing a possibly-stale ID.
  • Entity.remove(id) — called by death/despawn paths once subsystems have done their own teardown.
  • Entity.clear() — called from startNewGame in loop.ts (alongside Modifiers.clear() and Sig.clear()) to reset all entity state at the start of a new run. This also resets _nextId to 1, so IDs from the previous run are not reused but the counter restarts.
  • Entity.count — used for diagnostics and dump output.

Pattern notes

  • Untyped registry. _registry values are any. The registry treats every entity as an opaque object; structural typing is the caller’s responsibility. The only fields the registry writes are eid and _entityType.
  • Monotonic IDs within a run. _nextId++ guarantees uniqueness for the life of a run. Because clear resets the counter to 1, IDs are stable within a run but not across runs — never persist an eid across startNewGame.
  • No cascade on remove. The cleanup-on-removal model is pull-based: subsystems (Modifiers, FX) are responsible for noticing or being told the entity is gone. Entity.remove itself only frees the map slot. Callers that need cascade behaviour must invoke the appropriate subsystem cleanup explicitly.
  • get returns null, not undefined. The || null coalesce normalizes Map.get’s undefined miss to null, so downstream code can use a single === null check.
  • Singleton pattern. The module exports a single const Entity object rather than a class. There is exactly one registry per process.
  • Type tag is informational. _entityType is set on the object but never read inside this module — it exists for downstream debugging, telemetry, and type-dispatch by other systems.