Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -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.

View 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.

View 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`