Files
2026-05-27 23:29:53 +01:00

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 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<WorldEventMap> 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<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 — 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.