# OpenSpec Change: enterprise-ecs - **Change ID**: enterprise-ecs - **Package**: `@suaveplan/ecs` - **Category**: games (universal-tier) - **Target path**: `packages/games/ecs` - **Status**: PROPOSED - **Date**: 2026-05-19 - **Spec delta**: `./specs/ecs/spec.md` - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/ecs.md` This change supersedes the earlier `enterprise-gaming-ecs` proposal (`openspec/changes/enterprise-gaming-ecs/`) which predates the `packages//` folder layout and the source-mapping / `Verified by:` discipline. The superseded proposal will be archived under the same date prefix. --- ## Why `@suaveplan/ecs` is the foundational gameplay runtime — an archetype-based Entity Component System with bitset queries, archetype-graph transitions, system scheduling, and entity-ID recycling. The upstream `../suaveplan/packages/universal/ecs` package (1.0.1, 95.98% coverage) ships the algorithmic core but lacks the genesis enterprise floor: no `SuaveplanError` integration, no Zod-validated component schemas, no OTel instrumentation, no injectable clock, no property-based tests, and event payloads typed as tuples instead of `EventBus`-shaped envelopes. Migrating this package on genesis unblocks the `games`-plugin work (procedural-world systems, fauna behaviour, fruit-machine state) and gives every downstream gameplay package a single, observable, deterministic ECS substrate. ## What changes This is a fresh genesis package — no prior version exists on the branch, so every public symbol is ADDED. - ADDED: `createWorld(config?: WorldConfig)` / `class World` — central container managing entities, archetypes, components, queries, and systems with structured errors, OTel spans on `world.spawn` / `world.execute` / `world.snapshot` / `world.restore`, and an `@suaveplan/events` `EventBus` for entity lifecycle events. - ADDED: `class BitSet` — fixed-size `Uint32Array`-backed bitset with `set` / `unset` / `has` / `containsAll` / `intersects` / `equals` / `count` / `clone` / `clear` / iterator / `toKey`. - ADDED: `class Archetype` — column-oriented (SoA) storage with O(1) swap-remove, mask-keyed identity, and per-component column arrays. - ADDED: `class ComponentRegistry` — stable integer IDs for component schemas with name-uniqueness enforcement, `maxComponents` cap, and lookup by id / schema / name. - ADDED: `class EntityPool` — entity-ID allocator with recycling and per-ID generation counters for stale-reference detection. - ADDED: `class Query` + `defineQuery(include, exclude?)` — compiled bitset queries with cached archetype matching and `invalidate()` for archetype-graph mutations. - ADDED: `class SystemScheduler` + `defineSystem(options)` — phase-ordered (`preUpdate` → `update` → `postUpdate` → `render`), priority-sorted system scheduler. - ADDED: `defineComponent(name, defaults, schema?)` — Zod-validated component schema factory (genesis addition: accepts an optional `z.ZodType` validator wired through `World.addComponent` / `setComponent` / `restore`). - ADDED: `EcsError` extending `SuaveplanError` with stable `ECS.*` codes (`ECS.ENTITY_NOT_ALIVE`, `ECS.COMPONENT_NOT_REGISTERED`, `ECS.COMPONENT_NAME_CONFLICT`, `ECS.COMPONENT_LIMIT_REACHED`, `ECS.COMPONENT_VALIDATION_FAILED`, `ECS.SYSTEM_NAME_CONFLICT`, `ECS.SNAPSHOT_INVALID`). - ADDED: `WorldEvents` — `EventBus` payloads as single objects (`{ entity }` / `{ entity, component }`) per the genesis `@suaveplan/events` shape — replaces upstream's `[{ entity }]` tuple payloads. - ADDED: Public Zod schemas for `WorldSnapshot`, `ComponentDescriptor`, `QueryDescriptor`, and `WorldConfig`. - ADDED: OTel instrumentation via `@suaveplan/opentelemetry` — a counter for entity spawns/despawns, a histogram for `world.execute` duration, and spans on `spawn` / `despawn` / `execute` / `snapshot` / `restore`. ## Out of scope - **`@suaveplan/streams` integration / async tick** — upstream `ecs.md` § 3 mentions `AbortSignal` and streams for "network / IO helper". The ECS core is fully synchronous (a tick is a CPU loop, not IO). No async surface exists in upstream source; adding one without a concrete consumer would be speculative. Will be added when the first async-IO system lands. - **`@suaveplan/feature-flags` rollout gates** — applies only to "risky new behaviour" per the source. The current public surface is a faithful port of an already-shipped 1.0.1 design; no risky branch needs gating. - **`migrate` codemod** — applicable only when the public API changes. Fresh package, no prior consumers on genesis. - **Benchmark suite** — covered by an `__benchmarks__` slot in the file layout but the perf-regression gate is wired in a follow-up change once the benchmark harness graduates across the monorepo. Not blocking enterprise sign-off because the package's foundational hot paths (BitSet ops, archetype lookups) are pinned by deterministic unit tests. ## Source mapping This change derives from `../suaveplan/enterprise/ecs.md` (116 lines) and the upstream implementation at `../suaveplan/packages/universal/ecs/`. | Requirement | Source lines | Notes | |---|---|---| | Universal-tier purity (zero browser/Node/React deps) | `ecs.md` L37 | verbatim ("Zero browser / Node / React dependencies — pure TypeScript, runs in Bun, Node, and browser") | | Zod schemas for public types | `ecs.md` L38 | verbatim ("Zod schemas for all public types, ... versioned evolution") | | Property-based tests (fast-check) | `ecs.md` L39 | verbatim | | Deterministic behaviour / injectable clock | `ecs.md` L40 | verbatim ("no `Math.random()` / `Date.now()` at module scope; injectable clock and RNG") | | OpenTelemetry instrumentation | `ecs.md` L41 | verbatim ("via `@suaveplan/opentelemetry` for all public entrypoints") | | Structured errors with stable codes | `ecs.md` L42 | verbatim ("extending `@suaveplan/error` with stable error codes") | | 100% test coverage | `ecs.md` L50 | verbatim cross-cutting (Istanbul, statement + branch) | | 100% docs coverage (co-located `.md`) | `ecs.md` L51 | verbatim cross-cutting | | Archetype-based ECS with bitset queries | `ecs.md` L12 | one-line summary expanded into the World/Archetype/BitSet/Query requirements via upstream `world.ts`, `archetype.ts`, `bitset.ts`, `query.ts` | | Entity pooling with recycling | upstream `entity-pool.ts` L1-79 | implementation-level evidence for §1 purpose ("entity pooling") — surface preserved | | System scheduling (phases + priorities) | upstream `system.ts` L29-80 | implementation-level evidence for §1 purpose ("system scheduling") — surface preserved | | WorldSnapshot / serialization round-trip | upstream `world.ts` L199-236 | preserved as a stable persistence boundary | ### Requirements with no source line | Requirement | Justification | |---|---| | `EventBus`-shape entity events (`{ entity }` single-object payload) | The genesis `@suaveplan/events` package replaced the upstream `EventEmitter` (which used tuple payloads `[{ entity }]`) with a single-payload `EventBus` shape. This is a forced rewrite, not an additive requirement; payload shape follows the genesis events contract. | | 100% coverage threshold (not the upstream 95.98%) | Genesis-wide policy per `CLAUDE.md` Rules §Quality. | | `@suaveplan/testing/runner` test-import gate | Genesis-wide CI rule per `CLAUDE.md` rule 11 / `openspec/AGENTS.md` §7. | | Explicit-named feature-module barrels (no `export *`) | Genesis-wide policy per `CLAUDE.md` rule 10 / `openspec/AGENTS.md` §6a. | ## Verification approach Every requirement in `specs/ecs/spec.md` carries a `**Verified by:**` line at authoring time. Test layout: - `src/bitset/bitset.test.ts` — every `BitSet` operator and the iterator contract. - `src/bitset/bitset.spec-conformance.test.ts` — universal-tier purity + iterator type pinning. - `src/component-registry/component-registry.test.ts` — registration, ID stability, name-uniqueness, cap enforcement. - `src/entity-pool/entity-pool.test.ts` — allocate/free/recycle behaviour and generation counters. - `src/archetype/archetype.test.ts` — column storage, swap-remove identity, mask integrity. - `src/query/query.test.ts` — include / exclude / cached evaluation / invalidation. - `src/system/system.test.ts` — `defineSystem` defaults, phase ordering, priority sort, name conflict. - `src/world/world.test.ts` — spawn / despawn / addComponent / removeComponent / getComponent / setComponent / hasComponent / query / addSystem / removeSystem / execute / snapshot / restore / clear. - `src/world/world.spec-conformance.test.ts` — OTel emission (real `MeterProvider`), error-code coverage, EventBus payload typing, injectable Clock. - `src/schema/schema.test.ts` — `defineComponent` factory with and without Zod validator. - `src/errors/errors.test.ts` — every `ECS.*` code is thrown from at least one production path. - `tests/property.test.ts` — fast-check: entity count monotonicity, bitset algebra, query consistency vs exhaustive scan, snapshot/restore round-trip. - `tests/purity.test.ts` — built dist references no `node:*` / `Buffer` / `process`. - `tests/dep-graph.test.ts` — runtime deps limited to the declared allowlist. ## Migration impact None — additive only. No genesis package currently depends on `@suaveplan/ecs`. Downstream `games`-plugin consumers (procedural-world, fauna, fruit-machine, dialogue, economy) are not yet on genesis and will adopt the new surface when they migrate.