# OpenSpec Change: enterprise-gaming-ecs - **Change ID**: enterprise-gaming-ecs - **Package**: `@suaveplan/ecs` - **Category**: gaming/engine - **Target path**: `gaming/engine/ecs` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/ecs.md` --- ## Summary Deliver `@suaveplan/ecs` on the genesis branch from scratch at enterprise quality. The package provides an archetype-based Entity Component System with bitset queries — the foundational runtime for all gameplay systems. Entities are integer IDs; components are plain typed structs stored in contiguous typed arrays per archetype; queries filter archetypes by bitset AND/NOT masks for O(1) iteration. Genesis adds structured errors via `@suaveplan/error`, Zod schemas for all component descriptors, OTel span instrumentation on world tick and query execution, injectable clock and RNG, `AbortSignal` for async tick cancellation, and property-based tests with fast-check. --- ## Current-state audit (genesis) Package does not exist on genesis yet — fresh build. No prior source, no test results, no coverage data. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Base `EcsError`; stable `ECS.*` error codes | | `@suaveplan/types` (`workspace:^`) | `Clock`, `IdGenerator`, `LoggerLike` | | `@suaveplan/events` (`workspace:^`) | Entity lifecycle events (spawn, despawn, component added/removed) | | `zod` (`catalog:forms`) | Component descriptor schemas, query option schemas | | `@opentelemetry/api` (`catalog:observability`) | Spans on `world.tick`, `query.run`, `world.spawn` | --- ## Public API surface ### Core ```ts // World factory function createWorld(options?: WorldOptions): World interface WorldOptions { maxEntities?: number // default 65536 clock?: Clock logger?: LoggerLike } interface World { spawn(components?: ComponentInit[]): Entity despawn(entity: Entity): void addComponent(entity: Entity, descriptor: ComponentDescriptor, value: T): void removeComponent(entity: Entity, type: ComponentType): void hasComponent(entity: Entity, type: ComponentType): boolean getComponent(entity: Entity, descriptor: ComponentDescriptor): T | undefined query(mask: QueryMask): QueryResult tick(dt: number, signal?: AbortSignal): void registerSystem(system: System): void destroy(): void } type Entity = number & { readonly __brand: 'Entity' } // Component descriptor registry function defineComponent(name: string, schema: ZodType): ComponentDescriptor interface ComponentDescriptor { readonly type: ComponentType readonly name: string readonly schema: ZodType } type ComponentType = number & { readonly __brand: 'ComponentType' } // Query API interface QueryMask { all?: ComponentType[] none?: ComponentType[] any?: ComponentType[] } interface QueryResult { entities: readonly Entity[] size: number forEach(cb: (entity: Entity) => void): void } // System API interface System { readonly name: string update(world: World, dt: number): void } // Archetype internals (exposed for advanced consumers) interface Archetype { readonly mask: bigint readonly entities: readonly Entity[] readonly size: number } ``` ### Error codes ```ts type EcsErrorCode = | 'ECS_ENTITY_NOT_FOUND' | 'ECS_COMPONENT_NOT_REGISTERED' | 'ECS_MAX_ENTITIES_EXCEEDED' | 'ECS_INVALID_QUERY' | 'ECS_WORLD_DESTROYED' ``` --- ## New additions for genesis 1. **Bitset queries via BigInt masks** — archetype mask stored as `bigint`; AND/NOT/ANY evaluated with bitwise ops for O(1) archetype filtering. 2. **Typed array component storage** — Float32Array/Int32Array backing per numeric component type; fallback to generic Map for complex structs. 3. **OTel instrumentation** — `ecs.tick` span with attribute `ecs.system_count`; `ecs.query` histogram for query execution time; `ecs.entities_alive` gauge. 4. **Reactive entity events** — `@suaveplan/events` bus fires `ecs:spawn`, `ecs:despawn`, `ecs:component:add`, `ecs:component:remove`. 5. **Property-based tests** — fast-check: entity count monotonicity, query result consistency with exhaustive scan, archetype mask algebra. 6. **Benchmark suite** — `__benchmarks__/ecs.bench.ts`: 10k entity spawn, 100k query iteration, 60fps tick with 10 systems. --- ## File layout ```text gaming/engine/ecs/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/ index.ts types/ common.ts common.md core/ world/ world.ts world.md world.types.ts world.test.ts index.ts archetype/ archetype.ts archetype.md archetype.types.ts archetype.test.ts index.ts component/ component.ts component.md component.types.ts component.test.ts index.ts query/ query.ts query.md query.types.ts query.test.ts index.ts system/ system.ts system.md system.types.ts system.test.ts index.ts utils/ bitset.ts bitset.md bitset.test.ts __benchmarks__/ ecs.bench.ts tests/ integration.test.ts property.test.ts ``` --- ## Impact **Downstream**: `@suaveplan/game-loop` orchestrates world ticks; `@suaveplan/r3f-*` packages read ECS component state for rendering. No existing genesis package currently depends on it. **Breaking changes from main**: none — fresh build. **Prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events` published to Verdaccio.