9.3 KiB
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/<plugin>/<pkg> 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 onworld.spawn/world.execute/world.snapshot/world.restore, and an@suaveplan/eventsEventBusfor entity lifecycle events. - ADDED:
class BitSet— fixed-sizeUint32Array-backed bitset withset/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,maxComponentscap, 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 andinvalidate()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 optionalz.ZodTypevalidator wired throughWorld.addComponent/setComponent/restore). - ADDED:
EcsErrorextendingSuaveplanErrorwith stableECS.*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<WorldEventMap>payloads as single objects ({ entity }/{ entity, component }) per the genesis@suaveplan/eventsshape — replaces upstream's[{ entity }]tuple payloads. - ADDED: Public Zod schemas for
WorldSnapshot,ComponentDescriptor,QueryDescriptor, andWorldConfig. - ADDED: OTel instrumentation via
@suaveplan/opentelemetry— a counter for entity spawns/despawns, a histogram forworld.executeduration, and spans onspawn/despawn/execute/snapshot/restore.
Out of scope
@suaveplan/streamsintegration / async tick — upstreamecs.md§ 3 mentionsAbortSignaland 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-flagsrollout 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.migratecodemod — 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<EventMap> 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— everyBitSetoperator 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—defineSystemdefaults, 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 (realMeterProvider), error-code coverage, EventBus payload typing, injectable Clock.src/schema/schema.test.ts—defineComponentfactory with and without Zod validator.src/errors/errors.test.ts— everyECS.*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 nonode:*/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.