Initial Commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# SUPERSEDED
|
||||
|
||||
This change proposal was authored 2026-04-24 against a now-defunct folder layout (`gaming/engine/ecs/`) and lacked the source-mapping and `Verified by:` discipline that `openspec/AGENTS.md` §13/§14 now require.
|
||||
|
||||
It is **superseded** by `openspec/archive/2026-05-19-enterprise-ecs/`, which:
|
||||
|
||||
- Targets the current `packages/games/ecs/` path per `scripts/folder-reorg/assignment.yml`.
|
||||
- Faithfully ports the upstream `@suaveplan/ecs` design (`Uint32Array` BitSet, archetype-based) rather than redesigning to BigInt masks.
|
||||
- Adds the genesis enterprise floor per `../suaveplan/enterprise/ecs.md` (Zod, OTel, structured errors, fast-check property tests).
|
||||
- Carries a complete `## Source mapping` table and `**Verified by:**` lines on every requirement.
|
||||
|
||||
No code was ever produced against this proposal.
|
||||
193
openspec/archive/2026-05-19-enterprise-gaming-ecs/proposal.md
Normal file
193
openspec/archive/2026-05-19-enterprise-gaming-ecs/proposal.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# 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<T>(entity: Entity, descriptor: ComponentDescriptor<T>, value: T): void
|
||||
removeComponent(entity: Entity, type: ComponentType): void
|
||||
hasComponent(entity: Entity, type: ComponentType): boolean
|
||||
getComponent<T>(entity: Entity, descriptor: ComponentDescriptor<T>): 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<T>(name: string, schema: ZodType<T>): ComponentDescriptor<T>
|
||||
|
||||
interface ComponentDescriptor<T> {
|
||||
readonly type: ComponentType
|
||||
readonly name: string
|
||||
readonly schema: ZodType<T>
|
||||
}
|
||||
|
||||
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.
|
||||
64
openspec/archive/2026-05-19-enterprise-gaming-ecs/tasks.md
Normal file
64
openspec/archive/2026-05-19-enterprise-gaming-ecs/tasks.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Tasks: enterprise-gaming-ecs
|
||||
|
||||
## Phase 1 — Scaffold
|
||||
|
||||
- [ ] Create `gaming/engine/ecs/` with `package.json` (`@suaveplan/ecs`, version `0.1.0`, `sideEffects: false`)
|
||||
- [ ] Add `tsconfig.json` extending `@suaveplan/typescript-config/library.json`
|
||||
- [ ] Add `vitest.config.ts` using `createTestConfig()`
|
||||
- [ ] Wire `bunx turbo lint typecheck test` in turbo pipeline
|
||||
|
||||
## Phase 2 — Error codes & constants
|
||||
|
||||
- [ ] Implement `src/error-codes.ts` — `EcsError extends SuaveplanError`, `defineCodes` for `ECS.*` codes
|
||||
- [ ] Implement `src/constants/index.ts` — `DEFAULT_MAX_ENTITIES`, `ROOT_ARCHETYPE_MASK`
|
||||
|
||||
## Phase 3 — Types
|
||||
|
||||
- [ ] Implement `src/types/common.ts` — `Entity`, `ComponentType`, `ComponentDescriptor<T>`, `QueryMask`, `QueryResult`, `System`, `WorldOptions`, `Archetype`
|
||||
- [ ] Write `src/types/common.md`
|
||||
|
||||
## Phase 4 — Component registry
|
||||
|
||||
- [ ] Implement `src/core/component/component.ts` — `defineComponent<T>()`, auto-incrementing type IDs
|
||||
- [ ] Write tests and docs
|
||||
|
||||
## Phase 5 — Archetype store
|
||||
|
||||
- [ ] Implement `src/core/archetype/archetype.ts` — `bigint` mask, entity roster, typed array storage for numeric components
|
||||
- [ ] Write tests and docs
|
||||
|
||||
## Phase 6 — Query engine
|
||||
|
||||
- [ ] Implement `src/core/query/query.ts` — `buildQueryMask()`, `matchArchetype()`, `QueryResult` with `forEach`
|
||||
- [ ] Property-based test: query result = exhaustive entity scan filtered by component membership
|
||||
|
||||
## Phase 7 — System registry
|
||||
|
||||
- [ ] Implement `src/core/system/system.ts` — `SystemRegistry` with ordered update loop, `registerSystem`, `unregisterSystem`
|
||||
- [ ] Write tests and docs
|
||||
|
||||
## Phase 8 — World
|
||||
|
||||
- [ ] Implement `src/core/world/world.ts` — `createWorld()`, spawn/despawn, component add/remove, tick with AbortSignal, OTel spans
|
||||
- [ ] Wire `@suaveplan/events` entity lifecycle events
|
||||
- [ ] Write tests and docs
|
||||
|
||||
## Phase 9 — Utils
|
||||
|
||||
- [ ] Implement `src/utils/bitset.ts` — `maskFromTypes()`, `maskHasAll()`, `maskHasNone()`, `maskHasAny()`
|
||||
- [ ] Write tests
|
||||
|
||||
## Phase 10 — Integration & property tests
|
||||
|
||||
- [ ] `tests/integration.test.ts` — full ECS loop: spawn entities, add components, query, system update, despawn
|
||||
- [ ] `tests/property.test.ts` — fast-check: entity count monotonicity, archetype mask algebra, query consistency
|
||||
|
||||
## Phase 11 — Benchmark
|
||||
|
||||
- [ ] `src/__benchmarks__/ecs.bench.ts` — 10k spawn, 100k query iteration, 60fps tick with 10 systems
|
||||
|
||||
## Phase 12 — Pre-publish gate
|
||||
|
||||
- [ ] `bunx turbo lint typecheck test --filter=@suaveplan/ecs` green
|
||||
- [ ] `bunx turbo test:coverage --filter=@suaveplan/ecs` — 100% on all touched modules
|
||||
- [ ] Publish: `bun scripts/publish.ts --package @suaveplan/ecs`
|
||||
Reference in New Issue
Block a user