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
Entitysingleton (exportedconst) with internal state:_nextId— monotonic ID counter, starts at1._registry—Map<number, any>keyed by entity ID, holds the registered object reference.
- Public surface:
countgetter — returns_registry.size.create(type, obj)— assignsobj.eidandobj._entityType, registers the object, returns the new ID.get(id)— returns the registered object ornullif absent.remove(id)— deletes the entry from_registry.has(eid)— boolean existence check.clear()— wipes_registryand resets_nextIdto1.
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
eidand_entityType.
PUSHES TO
- The supplied entity object — mutates
obj.eid(number) andobj._entityType(string) insidecreate. - The internal
_registrymap — everycreateinsert, everyremovedelete, full purge onclear.
DOES NOT
- Does not allocate the entity object — callers construct their own objects and hand them in.
- Does not pool or recycle IDs —
_nextIdis 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;
removeonly drops the registry entry. - Does not validate that
idexists onremove— silently no-ops on unknown IDs. - Does not enforce uniqueness of the
typestring.
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 assignedeid.Entity.get(id)— used wherever code holds aneidand 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 fromstartNewGameinloop.ts(alongsideModifiers.clear()andSig.clear()) to reset all entity state at the start of a new run. This also resets_nextIdto1, 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.
_registryvalues areany. The registry treats every entity as an opaque object; structural typing is the caller’s responsibility. The only fields the registry writes areeidand_entityType. - Monotonic IDs within a run.
_nextId++guarantees uniqueness for the life of a run. Becauseclearresets the counter to1, IDs are stable within a run but not across runs — never persist aneidacrossstartNewGame. - 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.removeitself only frees the map slot. Callers that need cascade behaviour must invoke the appropriate subsystem cleanup explicitly. getreturnsnull, notundefined. The|| nullcoalesce normalizesMap.get’sundefinedmiss tonull, so downstream code can use a single=== nullcheck.- Singleton pattern. The module exports a single
const Entityobject rather than a class. There is exactly one registry per process. - Type tag is informational.
_entityTypeis set on the object but never read inside this module — it exists for downstream debugging, telemetry, and type-dispatch by other systems.