ECS v2

Entity-Component-System architecture. Entities are IDs, components are data, systems are functions. No inheritance, no scene graph — just fast, flat, data-oriented design.

Quick Example

import { createWorld, createEntity, stepWorld } from './engine/ecs/world/World.js';
import { registerSystem } from './engine/ecs/systems/SystemRegistry.js';
import { setComponent, getComponent } from './engine/ecs/storage/ArchetypeStorage.js';

// 1. Create a world
const world = createWorld({ name: 'MyGame' });

// 2. Create entities (just numeric IDs)
const player = createEntity(world);
const enemy  = createEntity(world);

// 3. Attach components (plain data)
setComponent(world, player, 'Transform', {
  position: [0, 1, 0],
  rotation: [0, 0, 0, 1],
  scale: [1, 1, 1]
});

// 4. Register systems (functions that run each tick)
registerSystem(world, {
  name: 'GravitySystem',
  phase: 'physics',
  update(world, dt) {
    // Query and update entities with PhysicsBody components
  }
});

// 5. Step the simulation
stepWorld(world, 1/60);

Core Concepts

Worlds

A World is a container for entities, components, and systems. Most apps use a single world, but you can create multiple for isolation (e.g., a UI world separate from gameplay).

const world = createWorld({
  name: 'GameWorld',
  fixedDelta: 1/60,
  phases: ['prePhysics', 'physics', 'postPhysics', 'render', 'lateUpdate']
});

The world tracks:

Entities

An entity is a generational ID — a 32-bit integer encoding an index and a generation counter. This prevents dangling references: if entity slot 5 is destroyed and reused, the old ID (generation 1) won't match the new occupant (generation 2).

const id = createEntity(world);   // → 1048577 (index=1, gen=1)
destroyEntity(world, id);           // Frees the slot
isEntityAlive(world, id);           // → false

const newId = createEntity(world);  // Reuses slot 1, but gen=2
isEntityAlive(world, id);           // → false (old gen doesn't match)
isEntityAlive(world, newId);        // → true

ID encoding: Lower 20 bits = entity index (max ~1M entities). Upper bits = generation counter. Decoded via decodeEntityId(id){ index, generation }.

Components

Components are plain data objects attached to entities by name. The engine defines a standard schema in EntitySchema.js with normalization and validation:

ComponentKey FieldsPurpose
Transformposition, rotation, scaleSpatial placement
PhysicsBodyvelocity, mass, typeRigid body dynamics
Collidershape, size, offsetCollision shapes
RenderablemeshId, materialId, visibleVisual representation
Lighttype, color, intensity, rangeLight sources
Camerafov, near, far, projectionView configuration
ParticleEmitterpreset, rate, lifetimeParticle spawning
NavAgentspeed, radius, destinationAI pathfinding
NetReplicatedownerId, priorityNetwork sync
// Set a component
setComponent(world, entityId, 'Light', {
  type: 'point',
  color: [1, 0.9, 0.7],
  intensity: 2.5,
  range: 15
});

// Get a component
const transform = getComponent(world, entityId, 'Transform');
console.log(transform.position); // → [0, 1, 0]

// Remove a component
removeComponent(world, entityId, 'Light');

Systems

Systems are registered functions that execute each frame in a defined phase order. They process entities by querying for required components.

registerSystem(world, {
  name: 'MovementSystem',
  phase: 'physics',           // Which phase to run in
  order: 10,                  // Priority within phase (lower = earlier)
  updateKind: 'tick',          // 'tick' (fixed), 'frame' (variable), or 'both'
  after: ['InputSystem'],      // Dependency ordering
  before: ['CollisionSystem'], // Must run before these

  init(world, system) {
    // Called once when registered
    system.state = { moveSpeed: 5.0 };
  },

  update(world, dt, system) {
    // Called every tick — do your work here
    // Query entities, read/write components
  },

  teardown(world, system) {
    // Called when system is unregistered
  }
});

Execution Phases

Systems are grouped into phases that execute in order. The default phases are:

PhasePurposeTypical Systems
prePhysicsInput processing, AI decisionsInputSystem, AISystem
physicsPhysics simulation, movementPhysicsSystem, MovementSystem
postPhysicsCollision response, constraintsCollisionSystem, ConstraintSystem
renderPrepare render dataCameraSystem, LightSystem
lateUpdateCleanup, UI syncAnimationSystem, UISync

Two step functions exist:

Archetype Storage

Components are stored in archetype-based storage (ArchetypeStorage.js). Entities with the same set of components are grouped together for cache-friendly iteration. The storage handles:

Component Healing

ComponentHealer.js validates and repairs component data using the schemas in EntitySchema.js. It normalizes vectors, clamps values, and fills missing fields with defaults. This makes save/load robust — corrupted or outdated save data is automatically healed.

Entity Lifecycle

// 1. Create
const id = createEntity(world);

// 2. Add components
setComponent(world, id, 'Transform', { position: [0,0,0] });
setComponent(world, id, 'Renderable', { meshId: 'cube' });

// 3. Systems process it each frame automatically

// 4. Delete with full cleanup (GPU buffers, selections, etc.)
deleteEntity({
  ecsWorld: world,
  entityId: id,
  spawnedEntities,
  uniformBuffers,
  bindGroups
});

Prefabs

The PrefabRegistry.js defines reusable entity templates (spawnables) with pre-configured components and SDF collision shapes:

// Spawning a prefab
const entity = spawnPrefab(world, 'torch', {
  position: [5, 0, 3],
  scale: [0.5, 0.5, 0.5]
});
// Automatically gets: Transform, Renderable, Light, ParticleEmitter, Collider
// Plus SDF collision shape (sphere/box/cylinder) for particle interaction

World Snapshots

Capture and restore world state for save/load, rewind, or debugging:

// Capture
const snapshot = captureWorldSnapshot(world, { maxEntities: 1000 });

// Restore
restoreWorldFromSnapshot(world, snapshot);

Key Files

FilePurpose
ecs/world/World.jscreateWorld, createEntity, stepWorld, stepWorldFrame
ecs/systems/SystemRegistry.jsregisterSystem, phase scheduling, dependency ordering
ecs/storage/ArchetypeStorage.jsComponent storage, queries, sparse-set indexing
ecs/EntitySchema.jsComponent schemas with types and normalization
ecs/ComponentHealer.jsAuto-repair invalid component data
ecs/EntityManager.jsHigh-level entity deletion with GPU cleanup
ecs/prefabs/PrefabRegistry.jsSpawnable entity templates with SDF shapes

See Also

Architecture

See how ECS fits into the full engine layer diagram.

Architecture →

Rendering

How ECS components drive the rendering pipeline.

Rendering Guide →

Particle System

ECS entity→SDF collision for particle interactions.

Particles Guide →