@codehz/ecs 0.7.4 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -30,25 +30,25 @@ import { World, component } from "@codehz/ecs";
30
30
  type Position = { x: number; y: number };
31
31
  type Velocity = { x: number; y: number };
32
32
 
33
- // Define component IDs (auto-assigned)
34
- const PositionId = component<Position>();
35
- const VelocityId = component<Velocity>();
33
+ // Define components (auto-assigned)
34
+ const Position = component<Position>();
35
+ const Velocity = component<Velocity>();
36
36
 
37
37
  // Create world
38
38
  const world = new World();
39
39
 
40
40
  // Create entity and set components (all changes buffered until sync())
41
41
  const entity = world.new();
42
- world.set(entity, PositionId, { x: 0, y: 0 });
43
- world.set(entity, VelocityId, { x: 1, y: 0.5 });
42
+ world.set(entity, Position, { x: 0, y: 0 });
43
+ world.set(entity, Velocity, { x: 1, y: 0.5 });
44
44
  world.sync();
45
45
 
46
46
  // Create reusable query
47
- const query = world.createQuery([PositionId, VelocityId]);
47
+ const query = world.createQuery([Position, Velocity]);
48
48
 
49
49
  // Update loop
50
50
  const deltaTime = 1.0 / 60.0;
51
- query.forEach([PositionId, VelocityId], (entity, position, velocity) => {
51
+ query.forEach([Position, Velocity], (entity, position, velocity) => {
52
52
  position.x += velocity.x * deltaTime;
53
53
  position.y += velocity.y * deltaTime;
54
54
  });
@@ -87,7 +87,7 @@ const ChildOf = component({ exclusive: true, name: "ChildOf" });
87
87
 
88
88
  ```typescript
89
89
  // Returns an unlisten function
90
- const unhook = world.hook([PositionId, VelocityId], {
90
+ const unhook = world.hook([Position, Velocity], {
91
91
  on_init: (entityId, position, velocity) => {
92
92
  // Called for every entity that already matches when the hook is registered
93
93
  },
@@ -105,7 +105,7 @@ unhook();
105
105
  A shorthand callback form is also supported:
106
106
 
107
107
  ```typescript
108
- const unhook = world.hook([PositionId, VelocityId], (type, entityId, position, velocity) => {
108
+ const unhook = world.hook([Position, Velocity], (type, entityId, position, velocity) => {
109
109
  if (type === "init") console.log("init");
110
110
  if (type === "set") console.log("set");
111
111
  if (type === "remove") console.log("remove");
@@ -116,7 +116,7 @@ Optional components and filters:
116
116
 
117
117
  ```typescript
118
118
  // Optional component: the hook fires even if Velocity is absent
119
- world.hook([PositionId, { optional: VelocityId }], {
119
+ world.hook([Position, { optional: Velocity }], {
120
120
  on_set: (entityId, position, velocity) => {
121
121
  if (velocity !== undefined) {
122
122
  console.log("has velocity and position");
@@ -127,14 +127,14 @@ world.hook([PositionId, { optional: VelocityId }], {
127
127
  });
128
128
 
129
129
  // Filter: exclude entities with specified negative components
130
- const DisabledId = component<void>();
130
+ const Disabled = component<void>();
131
131
  world.hook(
132
- [PositionId, VelocityId],
132
+ [Position, Velocity],
133
133
  {
134
134
  on_set: (entityId, position, velocity) => console.log("entered matching set"),
135
135
  on_remove: (entityId, position, velocity) => console.log("exited matching set"),
136
136
  },
137
- { negativeComponentTypes: [DisabledId] },
137
+ { negativeComponentTypes: [Disabled] },
138
138
  );
139
139
  ```
140
140
 
@@ -164,10 +164,10 @@ console.log(world.has(child, relation(ChildOf, parent2))); // true
164
164
 
165
165
  ```typescript
166
166
  import { World, component, relation } from "@codehz/ecs";
167
- const PositionId = component<Position>();
167
+ const Position = component<Position>();
168
168
 
169
169
  const world = new World();
170
- const wildcardPos = relation(PositionId, "*");
170
+ const wildcardPos = relation(Position, "*");
171
171
 
172
172
  // Listen for changes to all relations of this type
173
173
  world.hook([wildcardPos], {
@@ -362,11 +362,11 @@ import { pipeline } from "@codehz/pipeline";
362
362
  import { World, component } from "@codehz/ecs";
363
363
 
364
364
  const world = new World();
365
- const movementQuery = world.createQuery([PositionId, VelocityId]);
365
+ const movementQuery = world.createQuery([Position, Velocity]);
366
366
 
367
367
  const gameLoop = pipeline<{ deltaTime: number }>()
368
368
  .addPass((env) => {
369
- movementQuery.forEach([PositionId, VelocityId], (entity, position, velocity) => {
369
+ movementQuery.forEach([Position, Velocity], (entity, position, velocity) => {
370
370
  position.x += velocity.x * env.deltaTime;
371
371
  position.y += velocity.y * env.deltaTime;
372
372
  });
package/README.md CHANGED
@@ -31,24 +31,24 @@ type Position = { x: number; y: number };
31
31
  type Velocity = { x: number; y: number };
32
32
 
33
33
  // 定义组件 ID(自动分配)
34
- const PositionId = component<Position>();
35
- const VelocityId = component<Velocity>();
34
+ const Position = component<Position>();
35
+ const Velocity = component<Velocity>();
36
36
 
37
37
  // 创建世界
38
38
  const world = new World();
39
39
 
40
40
  // 创建实体并设置组件(所有更改缓冲到 sync() 时应用)
41
41
  const entity = world.new();
42
- world.set(entity, PositionId, { x: 0, y: 0 });
43
- world.set(entity, VelocityId, { x: 1, y: 0.5 });
42
+ world.set(entity, Position, { x: 0, y: 0 });
43
+ world.set(entity, Velocity, { x: 1, y: 0.5 });
44
44
  world.sync();
45
45
 
46
46
  // 创建可重用的查询
47
- const query = world.createQuery([PositionId, VelocityId]);
47
+ const query = world.createQuery([Position, Velocity]);
48
48
 
49
49
  // 更新循环
50
50
  const deltaTime = 1.0 / 60.0;
51
- query.forEach([PositionId, VelocityId], (entity, position, velocity) => {
51
+ query.forEach([Position, Velocity], (entity, position, velocity) => {
52
52
  position.x += velocity.x * deltaTime;
53
53
  position.y += velocity.y * deltaTime;
54
54
  });
@@ -87,7 +87,7 @@ const ChildOf = component({ exclusive: true, name: "ChildOf" });
87
87
 
88
88
  ```typescript
89
89
  // 返回卸载函数
90
- const unhook = world.hook([PositionId, VelocityId], {
90
+ const unhook = world.hook([Position, Velocity], {
91
91
  on_init: (entityId, position, velocity) => {
92
92
  // 钩子注册时,为每个已同时满足条件的实体调用
93
93
  },
@@ -105,7 +105,7 @@ unhook();
105
105
  也支持回调简写形式:
106
106
 
107
107
  ```typescript
108
- const unhook = world.hook([PositionId, VelocityId], (type, entityId, position, velocity) => {
108
+ const unhook = world.hook([Position, Velocity], (type, entityId, position, velocity) => {
109
109
  if (type === "init") console.log("初始化");
110
110
  if (type === "set") console.log("设置");
111
111
  if (type === "remove") console.log("移除");
@@ -116,7 +116,7 @@ const unhook = world.hook([PositionId, VelocityId], (type, entityId, position, v
116
116
 
117
117
  ```typescript
118
118
  // 可选组件:即使 Velocity 不存在也会触发钩子
119
- world.hook([PositionId, { optional: VelocityId }], {
119
+ world.hook([Position, { optional: Velocity }], {
120
120
  on_set: (entityId, position, velocity) => {
121
121
  if (velocity !== undefined) {
122
122
  console.log("拥有速度和位置");
@@ -127,14 +127,14 @@ world.hook([PositionId, { optional: VelocityId }], {
127
127
  });
128
128
 
129
129
  // 过滤器:排除带有指定负面组件的实体
130
- const DisabledId = component<void>();
130
+ const Disabled = component<void>();
131
131
  world.hook(
132
- [PositionId, VelocityId],
132
+ [Position, Velocity],
133
133
  {
134
134
  on_set: (entityId, position, velocity) => console.log("进入匹配集合"),
135
135
  on_remove: (entityId, position, velocity) => console.log("退出匹配集合"),
136
136
  },
137
- { negativeComponentTypes: [DisabledId] },
137
+ { negativeComponentTypes: [Disabled] },
138
138
  );
139
139
  ```
140
140
 
@@ -164,10 +164,10 @@ console.log(world.has(child, relation(ChildOf, parent2))); // true
164
164
 
165
165
  ```typescript
166
166
  import { World, component, relation } from "@codehz/ecs";
167
- const PositionId = component<Position>();
167
+ const Position = component<Position>();
168
168
 
169
169
  const world = new World();
170
- const wildcardPos = relation(PositionId, "*");
170
+ const wildcardPos = relation(Position, "*");
171
171
 
172
172
  // 监听所有该类型关系的变动
173
173
  world.hook([wildcardPos], {
@@ -362,11 +362,11 @@ import { pipeline } from "@codehz/pipeline";
362
362
  import { World, component } from "@codehz/ecs";
363
363
 
364
364
  const world = new World();
365
- const movementQuery = world.createQuery([PositionId, VelocityId]);
365
+ const movementQuery = world.createQuery([Position, Velocity]);
366
366
 
367
367
  const gameLoop = pipeline<{ deltaTime: number }>()
368
368
  .addPass((env) => {
369
- movementQuery.forEach([PositionId, VelocityId], (entity, position, velocity) => {
369
+ movementQuery.forEach([Position, Velocity], (entity, position, velocity) => {
370
370
  position.x += velocity.x * env.deltaTime;
371
371
  position.y += velocity.y * env.deltaTime;
372
372
  });
@@ -24,9 +24,9 @@ declare const __entityIdTypeTag: unique symbol;
24
24
  * @template T - The data type associated with this ID
25
25
  * @template U - Discriminant for the ID kind (e.g. `"component"`, `"entity-relation"`)
26
26
  */
27
- type EntityId<T = unknown, U$1 = unknown> = number & {
27
+ type EntityId<T = unknown, U = unknown> = number & {
28
28
  readonly [__componentTypeMarker]: T;
29
- readonly [__entityIdTypeTag]: U$1;
29
+ readonly [__entityIdTypeTag]: U;
30
30
  };
31
31
  /**
32
32
  * Component identifier. Valid values are `1` through `1023`.
package/dist/index.mjs CHANGED
@@ -1,3 +1,2 @@
1
1
  import { a as getComponentIdByName, c as isWildcardRelationId, d as isEntityId, f as isRelationId, i as component, l as relation, n as Query, o as getComponentNameById, r as EntityBuilder, s as decodeRelationId, t as World, u as isComponentId } from "./world.mjs";
2
-
3
- export { EntityBuilder, Query, World, component, decodeRelationId, getComponentIdByName, getComponentNameById, isComponentId, isEntityId, isRelationId, isWildcardRelationId, relation };
2
+ export { EntityBuilder, Query, World, component, decodeRelationId, getComponentIdByName, getComponentNameById, isComponentId, isEntityId, isRelationId, isWildcardRelationId, relation };
@@ -1,7 +1,6 @@
1
1
  import { S as EntityId, T as WildcardRelationId, b as ComponentId, c as LifecycleHook, i as Query, m as component, n as EntityBuilder, r as World, s as LifecycleCallback, t as ComponentDef, w as RelationId, y as relation } from "./builder.mjs";
2
2
 
3
3
  //#region src/testing/index.d.ts
4
-
5
4
  /**
6
5
  * Snapshot of a single entity's component state
7
6
  */
package/dist/testing.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import { c as isWildcardRelationId, i as component, l as relation, r as EntityBuilder, t as World } from "./world.mjs";
2
-
3
2
  //#region src/testing/index.ts
4
3
  /**
5
4
  * A test fixture that manages a World instance and provides convenient
@@ -135,16 +134,28 @@ var WorldFixture = class {
135
134
  * ```
136
135
  */
137
136
  const Assertions = {
137
+ /**
138
+ * Check if an entity has a specific component
139
+ */
138
140
  hasComponent(world, entity, componentId) {
139
141
  return world.exists(entity) && world.has(entity, componentId);
140
142
  },
143
+ /**
144
+ * Check if an entity does not have a specific component
145
+ */
141
146
  lacksComponent(world, entity, componentId) {
142
147
  return !world.exists(entity) || !world.has(entity, componentId);
143
148
  },
149
+ /**
150
+ * Get a component value (returns undefined if entity doesn't exist or doesn't have the component)
151
+ */
144
152
  getComponent(world, entity, componentId) {
145
153
  if (!world.exists(entity) || !world.has(entity, componentId)) return;
146
154
  return world.get(entity, componentId);
147
155
  },
156
+ /**
157
+ * Get all relation instances for a wildcard relation
158
+ */
148
159
  getRelations(world, entity, componentId) {
149
160
  if (!world.exists(entity)) return;
150
161
  const wildcardId = relation(componentId, "*");
@@ -154,48 +165,84 @@ const Assertions = {
154
165
  return [];
155
166
  }
156
167
  },
168
+ /**
169
+ * Check if an entity has a relation to a specific target
170
+ */
157
171
  hasRelation(world, entity, componentId, targetEntity) {
158
172
  if (!world.exists(entity)) return false;
159
173
  const relationId = relation(componentId, targetEntity);
160
174
  return world.has(entity, relationId);
161
175
  },
176
+ /**
177
+ * Check if an entity exists in the world
178
+ */
162
179
  entityExists(world, entity) {
163
180
  return world.exists(entity);
164
181
  },
182
+ /**
183
+ * Check if a query contains specific entities
184
+ */
165
185
  queryContains(query, ...entities) {
166
186
  const queryEntities = query.getEntities();
167
187
  return entities.every((e) => queryEntities.includes(e));
168
188
  },
189
+ /**
190
+ * Check if a query contains exactly the specified entities (no more, no less)
191
+ */
169
192
  queryContainsExactly(query, ...entities) {
170
193
  const queryEntities = query.getEntities();
171
194
  if (queryEntities.length !== entities.length) return false;
172
195
  return entities.every((e) => queryEntities.includes(e));
173
196
  },
197
+ /**
198
+ * Get the count of entities in a query
199
+ */
174
200
  queryCount(query) {
175
201
  return query.getEntities().length;
176
202
  },
203
+ /**
204
+ * Assert that an entity has a component (throws if not)
205
+ */
177
206
  assertHasComponent(world, entity, componentId) {
178
207
  if (!world.exists(entity)) throw new AssertionError(`Entity ${entity} does not exist`);
179
208
  if (!world.has(entity, componentId)) throw new AssertionError(`Entity ${entity} does not have component ${componentId}`);
180
209
  },
210
+ /**
211
+ * Assert that an entity lacks a component (throws if it has the component)
212
+ */
181
213
  assertLacksComponent(world, entity, componentId) {
182
214
  if (world.exists(entity) && world.has(entity, componentId)) throw new AssertionError(`Entity ${entity} unexpectedly has component ${componentId}`);
183
215
  },
216
+ /**
217
+ * Assert that a component equals an expected value (throws if not)
218
+ */
184
219
  assertComponentEquals(world, entity, componentId, expected) {
185
220
  this.assertHasComponent(world, entity, componentId);
186
221
  const actual = world.get(entity, componentId);
187
222
  if (!deepEquals(actual, expected)) throw new AssertionError(`Component ${componentId} on entity ${entity} does not match expected value.\nExpected: ${JSON.stringify(expected)}\nActual: ${JSON.stringify(actual)}`);
188
223
  },
224
+ /**
225
+ * Assert that an entity exists (throws if not)
226
+ */
189
227
  assertEntityExists(world, entity) {
190
228
  if (!world.exists(entity)) throw new AssertionError(`Entity ${entity} does not exist`);
191
229
  },
230
+ /**
231
+ * Assert that an entity does not exist (throws if it exists)
232
+ */
192
233
  assertEntityNotExists(world, entity) {
193
234
  if (world.exists(entity)) throw new AssertionError(`Entity ${entity} unexpectedly exists`);
194
235
  },
236
+ /**
237
+ * Assert that a query contains specific entities (throws if not)
238
+ */
195
239
  assertQueryContains(query, ...entities) {
196
240
  const queryEntities = query.getEntities();
197
241
  for (const entity of entities) if (!queryEntities.includes(entity)) throw new AssertionError(`Query does not contain entity ${entity}.\nQuery entities: [${queryEntities.join(", ")}]`);
198
242
  },
243
+ /**
244
+ * Assert that a query does not contain specific entities (throws if it does)
245
+ */
199
246
  assertQueryNotContains(query, ...entities) {
200
247
  const queryEntities = query.getEntities();
201
248
  for (const entity of entities) if (queryEntities.includes(entity)) throw new AssertionError(`Query unexpectedly contains entity ${entity}.\nQuery entities: [${queryEntities.join(", ")}]`);
@@ -221,6 +268,12 @@ const Assertions = {
221
268
  * ```
222
269
  */
223
270
  const Snapshot = {
271
+ /**
272
+ * Capture a snapshot of specified entities and their components
273
+ * @param world The world to capture from
274
+ * @param entities Entities to include in the snapshot
275
+ * @param componentIds Components to capture for each entity
276
+ */
224
277
  capture(world, entities, componentIds) {
225
278
  const entitySnapshots = [];
226
279
  for (const entity of entities) {
@@ -238,6 +291,11 @@ const Snapshot = {
238
291
  }
239
292
  return { entities: entitySnapshots };
240
293
  },
294
+ /**
295
+ * Compare two snapshots and return the differences
296
+ * @param before The 'before' snapshot
297
+ * @param after The 'after' snapshot
298
+ */
241
299
  compare(before, after) {
242
300
  const beforeEntities = new Set(before.entities.map((e) => e.entity));
243
301
  const afterEntities = new Set(after.entities.map((e) => e.entity));
@@ -285,6 +343,9 @@ const Snapshot = {
285
343
  componentChanges
286
344
  };
287
345
  },
346
+ /**
347
+ * Check if two snapshots are equal
348
+ */
288
349
  equals(a, b) {
289
350
  const diff = this.compare(a, b);
290
351
  return diff.addedEntities.length === 0 && diff.removedEntities.length === 0 && diff.componentChanges.length === 0;
@@ -335,7 +396,7 @@ function deepClone(value) {
335
396
  for (const key of Object.keys(value)) result[key] = deepClone(value[key]);
336
397
  return result;
337
398
  }
338
-
339
399
  //#endregion
340
400
  export { AssertionError, Assertions, EntityBuilder, Snapshot, World, WorldFixture, component, relation };
401
+
341
402
  //# sourceMappingURL=testing.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.mjs","names":["entitySnapshots: EntitySnapshot[]","addedEntities: EntityId[]","removedEntities: EntityId[]","componentChanges: SnapshotDiff[\"componentChanges\"]","result: Record<string, unknown>"],"sources":["../src/testing/index.ts"],"sourcesContent":["/**\n * @module testing\n * Testing utilities for ECS-based game logic\n *\n * This module provides framework-agnostic testing helpers that work with\n * bun:test, vitest, jest, or any other testing framework.\n *\n * @example\n * ```typescript\n * import { describe, expect, it } from \"bun:test\";\n * import { component } from \"@codehz/ecs\";\n * import { WorldFixture, EntityBuilder, Assertions } from \"@codehz/ecs/testing\";\n *\n * const PositionId = component<{ x: number; y: number }>();\n * const VelocityId = component<{ x: number; y: number }>();\n *\n * describe(\"Movement System\", () => {\n * it(\"should update position based on velocity\", () => {\n * const fixture = new WorldFixture();\n * const entity = fixture\n * .spawn()\n * .with(PositionId, { x: 0, y: 0 })\n * .with(VelocityId, { x: 1, y: 2 })\n * .build();\n *\n * // Run your game logic here\n * movementSystem(fixture.world, 1.0);\n *\n * expect(Assertions.hasComponent(fixture.world, entity, PositionId)).toBe(true);\n * expect(Assertions.getComponent(fixture.world, entity, PositionId)).toEqual({ x: 1, y: 2 });\n * });\n * });\n * ```\n */\n\nimport type { ComponentId, EntityId, WildcardRelationId } from \"../entity\";\nimport { isWildcardRelationId, relation } from \"../entity\";\nimport type { Query } from \"../query/query\";\nimport { World } from \"../world/world\";\nexport { EntityBuilder } from \"../world/builder\";\nexport type { ComponentDef } from \"../world/builder\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * A component definition for entity building, supporting both regular components and relations\n */\nimport type { EntityBuilder } from \"../world/builder\";\n\n/**\n * Snapshot of a single entity's component state\n */\nexport interface EntitySnapshot {\n entity: EntityId;\n components: Map<EntityId<any>, unknown>;\n}\n\n/**\n * Snapshot of multiple entities' component state\n */\nexport interface WorldSnapshot {\n entities: EntitySnapshot[];\n}\n\n/**\n * Result of comparing two snapshots\n */\nexport interface SnapshotDiff {\n /** Entities that exist in 'after' but not in 'before' */\n addedEntities: EntityId[];\n /** Entities that exist in 'before' but not in 'after' */\n removedEntities: EntityId[];\n /** Changes to components on existing entities */\n componentChanges: Array<{\n entity: EntityId;\n componentId: EntityId<any>;\n before: unknown | undefined;\n after: unknown | undefined;\n changeType: \"added\" | \"removed\" | \"modified\";\n }>;\n}\n\n// =============================================================================\n// WorldFixture - Test World Factory\n// =============================================================================\n\n/**\n * A test fixture that manages a World instance and provides convenient\n * methods for setting up test scenarios.\n *\n * @example\n * ```typescript\n * const fixture = new WorldFixture();\n *\n * // Spawn entities with fluent API\n * const player = fixture\n * .spawn()\n * .with(PositionId, { x: 0, y: 0 })\n * .with(HealthId, { current: 100, max: 100 })\n * .build();\n *\n * // Access the world for running systems\n * movementSystem(fixture.world);\n *\n * // Clean up (optional - creates a fresh world)\n * fixture.reset();\n * ```\n */\nexport class WorldFixture {\n private _world: World;\n private _queries: Query[] = [];\n\n constructor() {\n this._world = new World();\n }\n\n /**\n * Get the underlying World instance\n */\n get world(): World {\n return this._world;\n }\n\n /**\n * Create a new EntityBuilder for spawning an entity with components\n */\n spawn(): EntityBuilder {\n return this._world.spawn();\n }\n\n /**\n * Spawn multiple entities with the same component configuration\n * @param count Number of entities to spawn\n * @param configure Function to configure each entity builder\n * @returns Array of created entity IDs\n */\n spawnMany(count: number, configure: (builder: EntityBuilder, index: number) => EntityBuilder): EntityId[] {\n return this._world.spawnMany(count, configure);\n }\n\n /**\n * Create a query and track it for automatic cleanup\n * @param componentTypes Component types to query for\n * @returns Query instance\n */\n createQuery(componentTypes: EntityId<any>[]): Query {\n const query = this._world.createQuery(componentTypes);\n this._queries.push(query);\n return query;\n }\n\n /**\n * Execute pending commands (alias for world.sync())\n */\n sync(): void {\n this._world.sync();\n }\n\n /**\n * Reset the fixture with a fresh World instance\n * Disposes all tracked queries\n */\n reset(): void {\n for (const query of this._queries) {\n query.dispose();\n }\n this._queries = [];\n this._world = new World();\n }\n\n /**\n * Capture a snapshot of specified entities and their components\n * @param entities Entities to capture\n * @param componentIds Components to include in the snapshot\n */\n captureSnapshot(entities: EntityId[], componentIds: EntityId<any>[]): WorldSnapshot {\n return Snapshot.capture(this._world, entities, componentIds);\n }\n\n /**\n * Symbol.dispose implementation for automatic resource management\n */\n [Symbol.dispose](): void {\n this.reset();\n }\n}\n\n// =============================================================================\n// EntityBuilder - Fluent Entity Creation\n// =============================================================================\n\n/**\n * Fluent builder for creating entities with components.\n * Supports both regular components and entity relations.\n *\n * @example\n * ```typescript\n * // Basic usage\n * // Note: build() will enqueue component commands but will NOT call world.sync().\n * // You must call world.sync() or fixture.sync() manually to apply commands.\n * const entity = new EntityBuilder(world)\n * .with(PositionId, { x: 10, y: 20 })\n * .with(VelocityId, { x: 1, y: 0 })\n * .build();\n * // Apply pending changes\n * world.sync();\n *\n * // With relations\n * const child = new EntityBuilder(world)\n * .with(PositionId, { x: 0, y: 0 })\n * .withRelation(ParentId, parentEntity, { offset: { x: 5, y: 5 } })\n * .build();\n * world.sync();\n *\n * // Tag component (void type)\n * const tagged = new EntityBuilder(world)\n * .with(PlayerTagId)\n * .build();\n * ```\n */\n// EntityBuilder is exported from world.ts; testing utilities will use world.spawn()\n\n// =============================================================================\n// Assertions - Test Assertion Helpers\n// =============================================================================\n\n/**\n * Test assertion utilities that return boolean values or throw descriptive errors.\n * These work with any testing framework's expect() function.\n *\n * @example\n * ```typescript\n * // With bun:test or vitest\n * expect(Assertions.hasComponent(world, entity, PositionId)).toBe(true);\n * expect(Assertions.getComponent(world, entity, PositionId)).toEqual({ x: 10, y: 20 });\n *\n * // Direct assertion (throws on failure)\n * Assertions.assertHasComponent(world, entity, PositionId);\n * Assertions.assertComponentEquals(world, entity, PositionId, { x: 10, y: 20 });\n * ```\n */\nexport const Assertions = {\n /**\n * Check if an entity has a specific component\n */\n hasComponent<T>(world: World, entity: EntityId, componentId: EntityId<T>): boolean {\n return world.exists(entity) && world.has(entity, componentId);\n },\n\n /**\n * Check if an entity does not have a specific component\n */\n lacksComponent<T>(world: World, entity: EntityId, componentId: EntityId<T>): boolean {\n return !world.exists(entity) || !world.has(entity, componentId);\n },\n\n /**\n * Get a component value (returns undefined if entity doesn't exist or doesn't have the component)\n */\n getComponent<T>(world: World, entity: EntityId, componentId: EntityId<T>): T | undefined {\n if (!world.exists(entity) || !world.has(entity, componentId)) {\n return undefined;\n }\n return world.get(entity, componentId);\n },\n\n /**\n * Get all relation instances for a wildcard relation\n */\n getRelations<T>(world: World, entity: EntityId, componentId: ComponentId<T>): [EntityId<unknown>, T][] | undefined {\n if (!world.exists(entity)) {\n return undefined;\n }\n const wildcardId = relation(componentId, \"*\");\n try {\n return world.get(entity, wildcardId);\n } catch {\n return [];\n }\n },\n\n /**\n * Check if an entity has a relation to a specific target\n */\n hasRelation<T>(world: World, entity: EntityId, componentId: ComponentId<T>, targetEntity: EntityId<any>): boolean {\n if (!world.exists(entity)) {\n return false;\n }\n const relationId = relation(componentId, targetEntity);\n return world.has(entity, relationId);\n },\n\n /**\n * Check if an entity exists in the world\n */\n entityExists(world: World, entity: EntityId): boolean {\n return world.exists(entity);\n },\n\n /**\n * Check if a query contains specific entities\n */\n queryContains(query: Query, ...entities: EntityId[]): boolean {\n const queryEntities = query.getEntities();\n return entities.every((e) => queryEntities.includes(e));\n },\n\n /**\n * Check if a query contains exactly the specified entities (no more, no less)\n */\n queryContainsExactly(query: Query, ...entities: EntityId[]): boolean {\n const queryEntities = query.getEntities();\n if (queryEntities.length !== entities.length) {\n return false;\n }\n return entities.every((e) => queryEntities.includes(e));\n },\n\n /**\n * Get the count of entities in a query\n */\n queryCount(query: Query): number {\n return query.getEntities().length;\n },\n\n // === Throwing assertions ===\n\n /**\n * Assert that an entity has a component (throws if not)\n */\n assertHasComponent<T>(world: World, entity: EntityId, componentId: EntityId<T>): void {\n if (!world.exists(entity)) {\n throw new AssertionError(`Entity ${entity} does not exist`);\n }\n if (!world.has(entity, componentId)) {\n throw new AssertionError(`Entity ${entity} does not have component ${componentId}`);\n }\n },\n\n /**\n * Assert that an entity lacks a component (throws if it has the component)\n */\n assertLacksComponent<T>(world: World, entity: EntityId, componentId: EntityId<T>): void {\n if (world.exists(entity) && world.has(entity, componentId)) {\n throw new AssertionError(`Entity ${entity} unexpectedly has component ${componentId}`);\n }\n },\n\n /**\n * Assert that a component equals an expected value (throws if not)\n */\n assertComponentEquals<T>(world: World, entity: EntityId, componentId: EntityId<T>, expected: T): void {\n this.assertHasComponent(world, entity, componentId);\n const actual = world.get(entity, componentId);\n if (!deepEquals(actual, expected)) {\n throw new AssertionError(\n `Component ${componentId} on entity ${entity} does not match expected value.\\n` +\n `Expected: ${JSON.stringify(expected)}\\n` +\n `Actual: ${JSON.stringify(actual)}`,\n );\n }\n },\n\n /**\n * Assert that an entity exists (throws if not)\n */\n assertEntityExists(world: World, entity: EntityId): void {\n if (!world.exists(entity)) {\n throw new AssertionError(`Entity ${entity} does not exist`);\n }\n },\n\n /**\n * Assert that an entity does not exist (throws if it exists)\n */\n assertEntityNotExists(world: World, entity: EntityId): void {\n if (world.exists(entity)) {\n throw new AssertionError(`Entity ${entity} unexpectedly exists`);\n }\n },\n\n /**\n * Assert that a query contains specific entities (throws if not)\n */\n assertQueryContains(query: Query, ...entities: EntityId[]): void {\n const queryEntities = query.getEntities();\n for (const entity of entities) {\n if (!queryEntities.includes(entity)) {\n throw new AssertionError(\n `Query does not contain entity ${entity}.\\n` + `Query entities: [${queryEntities.join(\", \")}]`,\n );\n }\n }\n },\n\n /**\n * Assert that a query does not contain specific entities (throws if it does)\n */\n assertQueryNotContains(query: Query, ...entities: EntityId[]): void {\n const queryEntities = query.getEntities();\n for (const entity of entities) {\n if (queryEntities.includes(entity)) {\n throw new AssertionError(\n `Query unexpectedly contains entity ${entity}.\\n` + `Query entities: [${queryEntities.join(\", \")}]`,\n );\n }\n }\n },\n};\n\n// =============================================================================\n// Snapshot - State Capture and Comparison\n// =============================================================================\n\n/**\n * Utilities for capturing and comparing world state snapshots.\n * Useful for testing that systems produce expected state changes.\n *\n * @example\n * ```typescript\n * const before = Snapshot.capture(world, [entity], [PositionId, VelocityId]);\n *\n * // Run game logic\n * movementSystem(world, deltaTime);\n * world.sync();\n *\n * const after = Snapshot.capture(world, [entity], [PositionId, VelocityId]);\n * const diff = Snapshot.compare(before, after);\n *\n * expect(diff.componentChanges).toHaveLength(1);\n * expect(diff.componentChanges[0].changeType).toBe(\"modified\");\n * ```\n */\nexport const Snapshot = {\n /**\n * Capture a snapshot of specified entities and their components\n * @param world The world to capture from\n * @param entities Entities to include in the snapshot\n * @param componentIds Components to capture for each entity\n */\n capture(world: World, entities: EntityId[], componentIds: EntityId<any>[]): WorldSnapshot {\n const entitySnapshots: EntitySnapshot[] = [];\n\n for (const entity of entities) {\n if (!world.exists(entity)) {\n continue;\n }\n\n const components = new Map<EntityId<any>, unknown>();\n\n for (const componentId of componentIds) {\n if (isWildcardRelationId(componentId)) {\n // For wildcard relations, capture all relation instances\n try {\n const relations = world.get(entity, componentId as WildcardRelationId<any>);\n if (relations && relations.length > 0) {\n components.set(componentId, deepClone(relations));\n }\n } catch {\n // Entity doesn't have this relation type\n }\n } else if (world.has(entity, componentId)) {\n components.set(componentId, deepClone(world.get(entity, componentId)));\n }\n }\n\n entitySnapshots.push({ entity, components });\n }\n\n return { entities: entitySnapshots };\n },\n\n /**\n * Compare two snapshots and return the differences\n * @param before The 'before' snapshot\n * @param after The 'after' snapshot\n */\n compare(before: WorldSnapshot, after: WorldSnapshot): SnapshotDiff {\n const beforeEntities = new Set(before.entities.map((e) => e.entity));\n const afterEntities = new Set(after.entities.map((e) => e.entity));\n\n const addedEntities: EntityId[] = [];\n const removedEntities: EntityId[] = [];\n const componentChanges: SnapshotDiff[\"componentChanges\"] = [];\n\n // Find added entities\n for (const entity of afterEntities) {\n if (!beforeEntities.has(entity)) {\n addedEntities.push(entity);\n }\n }\n\n // Find removed entities\n for (const entity of beforeEntities) {\n if (!afterEntities.has(entity)) {\n removedEntities.push(entity);\n }\n }\n\n // Find component changes on existing entities\n const beforeMap = new Map(before.entities.map((e) => [e.entity, e]));\n const afterMap = new Map(after.entities.map((e) => [e.entity, e]));\n\n for (const entity of beforeEntities) {\n if (!afterEntities.has(entity)) continue; // Skip removed entities\n\n const beforeEntity = beforeMap.get(entity)!;\n const afterEntity = afterMap.get(entity)!;\n\n // Check for component changes\n const allComponentIds = new Set([...beforeEntity.components.keys(), ...afterEntity.components.keys()]);\n\n for (const componentId of allComponentIds) {\n const beforeValue = beforeEntity.components.get(componentId);\n const afterValue = afterEntity.components.get(componentId);\n\n if (beforeValue === undefined && afterValue !== undefined) {\n componentChanges.push({\n entity,\n componentId,\n before: undefined,\n after: afterValue,\n changeType: \"added\",\n });\n } else if (beforeValue !== undefined && afterValue === undefined) {\n componentChanges.push({\n entity,\n componentId,\n before: beforeValue,\n after: undefined,\n changeType: \"removed\",\n });\n } else if (!deepEquals(beforeValue, afterValue)) {\n componentChanges.push({\n entity,\n componentId,\n before: beforeValue,\n after: afterValue,\n changeType: \"modified\",\n });\n }\n }\n }\n\n return { addedEntities, removedEntities, componentChanges };\n },\n\n /**\n * Check if two snapshots are equal\n */\n equals(a: WorldSnapshot, b: WorldSnapshot): boolean {\n const diff = this.compare(a, b);\n return diff.addedEntities.length === 0 && diff.removedEntities.length === 0 && diff.componentChanges.length === 0;\n },\n};\n\n// =============================================================================\n// Utilities\n// =============================================================================\n\n/**\n * Custom assertion error for testing utilities\n */\nexport class AssertionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AssertionError\";\n }\n}\n\n/**\n * Deep equality check for comparing component values\n */\nfunction deepEquals(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEquals(a[i], b[i])) return false;\n }\n return true;\n }\n\n if (typeof a === \"object\" && typeof b === \"object\") {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n\n if (aKeys.length !== bKeys.length) return false;\n\n for (const key of aKeys) {\n if (!Object.prototype.hasOwnProperty.call(bObj, key)) return false;\n if (!deepEquals(aObj[key], bObj[key])) return false;\n }\n return true;\n }\n\n return false;\n}\n\n/**\n * Deep clone a value for snapshot isolation\n */\nfunction deepClone<T>(value: T): T {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map(deepClone) as T;\n }\n\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n result[key] = deepClone((value as Record<string, unknown>)[key]);\n }\n return result as T;\n}\n\n// =============================================================================\n// Re-exports for convenience\n// =============================================================================\n\nexport { component, relation } from \"../entity\";\nexport type { ComponentId, EntityId, RelationId, WildcardRelationId } from \"../entity\";\nexport type { Query } from \"../query/query\";\nexport type { LifecycleCallback, LifecycleHook } from \"../types\";\nexport { World } from \"../world/world\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"testing.mjs","names":[],"sources":["../src/testing/index.ts"],"sourcesContent":["/**\n * @module testing\n * Testing utilities for ECS-based game logic\n *\n * This module provides framework-agnostic testing helpers that work with\n * bun:test, vitest, jest, or any other testing framework.\n *\n * @example\n * ```typescript\n * import { describe, expect, it } from \"bun:test\";\n * import { component } from \"@codehz/ecs\";\n * import { WorldFixture, EntityBuilder, Assertions } from \"@codehz/ecs/testing\";\n *\n * const PositionId = component<{ x: number; y: number }>();\n * const VelocityId = component<{ x: number; y: number }>();\n *\n * describe(\"Movement System\", () => {\n * it(\"should update position based on velocity\", () => {\n * const fixture = new WorldFixture();\n * const entity = fixture\n * .spawn()\n * .with(PositionId, { x: 0, y: 0 })\n * .with(VelocityId, { x: 1, y: 2 })\n * .build();\n *\n * // Run your game logic here\n * movementSystem(fixture.world, 1.0);\n *\n * expect(Assertions.hasComponent(fixture.world, entity, PositionId)).toBe(true);\n * expect(Assertions.getComponent(fixture.world, entity, PositionId)).toEqual({ x: 1, y: 2 });\n * });\n * });\n * ```\n */\n\nimport type { ComponentId, EntityId, WildcardRelationId } from \"../entity\";\nimport { isWildcardRelationId, relation } from \"../entity\";\nimport type { Query } from \"../query/query\";\nimport { World } from \"../world/world\";\nexport { EntityBuilder } from \"../world/builder\";\nexport type { ComponentDef } from \"../world/builder\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * A component definition for entity building, supporting both regular components and relations\n */\nimport type { EntityBuilder } from \"../world/builder\";\n\n/**\n * Snapshot of a single entity's component state\n */\nexport interface EntitySnapshot {\n entity: EntityId;\n components: Map<EntityId<any>, unknown>;\n}\n\n/**\n * Snapshot of multiple entities' component state\n */\nexport interface WorldSnapshot {\n entities: EntitySnapshot[];\n}\n\n/**\n * Result of comparing two snapshots\n */\nexport interface SnapshotDiff {\n /** Entities that exist in 'after' but not in 'before' */\n addedEntities: EntityId[];\n /** Entities that exist in 'before' but not in 'after' */\n removedEntities: EntityId[];\n /** Changes to components on existing entities */\n componentChanges: Array<{\n entity: EntityId;\n componentId: EntityId<any>;\n before: unknown | undefined;\n after: unknown | undefined;\n changeType: \"added\" | \"removed\" | \"modified\";\n }>;\n}\n\n// =============================================================================\n// WorldFixture - Test World Factory\n// =============================================================================\n\n/**\n * A test fixture that manages a World instance and provides convenient\n * methods for setting up test scenarios.\n *\n * @example\n * ```typescript\n * const fixture = new WorldFixture();\n *\n * // Spawn entities with fluent API\n * const player = fixture\n * .spawn()\n * .with(PositionId, { x: 0, y: 0 })\n * .with(HealthId, { current: 100, max: 100 })\n * .build();\n *\n * // Access the world for running systems\n * movementSystem(fixture.world);\n *\n * // Clean up (optional - creates a fresh world)\n * fixture.reset();\n * ```\n */\nexport class WorldFixture {\n private _world: World;\n private _queries: Query[] = [];\n\n constructor() {\n this._world = new World();\n }\n\n /**\n * Get the underlying World instance\n */\n get world(): World {\n return this._world;\n }\n\n /**\n * Create a new EntityBuilder for spawning an entity with components\n */\n spawn(): EntityBuilder {\n return this._world.spawn();\n }\n\n /**\n * Spawn multiple entities with the same component configuration\n * @param count Number of entities to spawn\n * @param configure Function to configure each entity builder\n * @returns Array of created entity IDs\n */\n spawnMany(count: number, configure: (builder: EntityBuilder, index: number) => EntityBuilder): EntityId[] {\n return this._world.spawnMany(count, configure);\n }\n\n /**\n * Create a query and track it for automatic cleanup\n * @param componentTypes Component types to query for\n * @returns Query instance\n */\n createQuery(componentTypes: EntityId<any>[]): Query {\n const query = this._world.createQuery(componentTypes);\n this._queries.push(query);\n return query;\n }\n\n /**\n * Execute pending commands (alias for world.sync())\n */\n sync(): void {\n this._world.sync();\n }\n\n /**\n * Reset the fixture with a fresh World instance\n * Disposes all tracked queries\n */\n reset(): void {\n for (const query of this._queries) {\n query.dispose();\n }\n this._queries = [];\n this._world = new World();\n }\n\n /**\n * Capture a snapshot of specified entities and their components\n * @param entities Entities to capture\n * @param componentIds Components to include in the snapshot\n */\n captureSnapshot(entities: EntityId[], componentIds: EntityId<any>[]): WorldSnapshot {\n return Snapshot.capture(this._world, entities, componentIds);\n }\n\n /**\n * Symbol.dispose implementation for automatic resource management\n */\n [Symbol.dispose](): void {\n this.reset();\n }\n}\n\n// =============================================================================\n// EntityBuilder - Fluent Entity Creation\n// =============================================================================\n\n/**\n * Fluent builder for creating entities with components.\n * Supports both regular components and entity relations.\n *\n * @example\n * ```typescript\n * // Basic usage\n * // Note: build() will enqueue component commands but will NOT call world.sync().\n * // You must call world.sync() or fixture.sync() manually to apply commands.\n * const entity = new EntityBuilder(world)\n * .with(PositionId, { x: 10, y: 20 })\n * .with(VelocityId, { x: 1, y: 0 })\n * .build();\n * // Apply pending changes\n * world.sync();\n *\n * // With relations\n * const child = new EntityBuilder(world)\n * .with(PositionId, { x: 0, y: 0 })\n * .withRelation(ParentId, parentEntity, { offset: { x: 5, y: 5 } })\n * .build();\n * world.sync();\n *\n * // Tag component (void type)\n * const tagged = new EntityBuilder(world)\n * .with(PlayerTagId)\n * .build();\n * ```\n */\n// EntityBuilder is exported from world.ts; testing utilities will use world.spawn()\n\n// =============================================================================\n// Assertions - Test Assertion Helpers\n// =============================================================================\n\n/**\n * Test assertion utilities that return boolean values or throw descriptive errors.\n * These work with any testing framework's expect() function.\n *\n * @example\n * ```typescript\n * // With bun:test or vitest\n * expect(Assertions.hasComponent(world, entity, PositionId)).toBe(true);\n * expect(Assertions.getComponent(world, entity, PositionId)).toEqual({ x: 10, y: 20 });\n *\n * // Direct assertion (throws on failure)\n * Assertions.assertHasComponent(world, entity, PositionId);\n * Assertions.assertComponentEquals(world, entity, PositionId, { x: 10, y: 20 });\n * ```\n */\nexport const Assertions = {\n /**\n * Check if an entity has a specific component\n */\n hasComponent<T>(world: World, entity: EntityId, componentId: EntityId<T>): boolean {\n return world.exists(entity) && world.has(entity, componentId);\n },\n\n /**\n * Check if an entity does not have a specific component\n */\n lacksComponent<T>(world: World, entity: EntityId, componentId: EntityId<T>): boolean {\n return !world.exists(entity) || !world.has(entity, componentId);\n },\n\n /**\n * Get a component value (returns undefined if entity doesn't exist or doesn't have the component)\n */\n getComponent<T>(world: World, entity: EntityId, componentId: EntityId<T>): T | undefined {\n if (!world.exists(entity) || !world.has(entity, componentId)) {\n return undefined;\n }\n return world.get(entity, componentId);\n },\n\n /**\n * Get all relation instances for a wildcard relation\n */\n getRelations<T>(world: World, entity: EntityId, componentId: ComponentId<T>): [EntityId<unknown>, T][] | undefined {\n if (!world.exists(entity)) {\n return undefined;\n }\n const wildcardId = relation(componentId, \"*\");\n try {\n return world.get(entity, wildcardId);\n } catch {\n return [];\n }\n },\n\n /**\n * Check if an entity has a relation to a specific target\n */\n hasRelation<T>(world: World, entity: EntityId, componentId: ComponentId<T>, targetEntity: EntityId<any>): boolean {\n if (!world.exists(entity)) {\n return false;\n }\n const relationId = relation(componentId, targetEntity);\n return world.has(entity, relationId);\n },\n\n /**\n * Check if an entity exists in the world\n */\n entityExists(world: World, entity: EntityId): boolean {\n return world.exists(entity);\n },\n\n /**\n * Check if a query contains specific entities\n */\n queryContains(query: Query, ...entities: EntityId[]): boolean {\n const queryEntities = query.getEntities();\n return entities.every((e) => queryEntities.includes(e));\n },\n\n /**\n * Check if a query contains exactly the specified entities (no more, no less)\n */\n queryContainsExactly(query: Query, ...entities: EntityId[]): boolean {\n const queryEntities = query.getEntities();\n if (queryEntities.length !== entities.length) {\n return false;\n }\n return entities.every((e) => queryEntities.includes(e));\n },\n\n /**\n * Get the count of entities in a query\n */\n queryCount(query: Query): number {\n return query.getEntities().length;\n },\n\n // === Throwing assertions ===\n\n /**\n * Assert that an entity has a component (throws if not)\n */\n assertHasComponent<T>(world: World, entity: EntityId, componentId: EntityId<T>): void {\n if (!world.exists(entity)) {\n throw new AssertionError(`Entity ${entity} does not exist`);\n }\n if (!world.has(entity, componentId)) {\n throw new AssertionError(`Entity ${entity} does not have component ${componentId}`);\n }\n },\n\n /**\n * Assert that an entity lacks a component (throws if it has the component)\n */\n assertLacksComponent<T>(world: World, entity: EntityId, componentId: EntityId<T>): void {\n if (world.exists(entity) && world.has(entity, componentId)) {\n throw new AssertionError(`Entity ${entity} unexpectedly has component ${componentId}`);\n }\n },\n\n /**\n * Assert that a component equals an expected value (throws if not)\n */\n assertComponentEquals<T>(world: World, entity: EntityId, componentId: EntityId<T>, expected: T): void {\n this.assertHasComponent(world, entity, componentId);\n const actual = world.get(entity, componentId);\n if (!deepEquals(actual, expected)) {\n throw new AssertionError(\n `Component ${componentId} on entity ${entity} does not match expected value.\\n` +\n `Expected: ${JSON.stringify(expected)}\\n` +\n `Actual: ${JSON.stringify(actual)}`,\n );\n }\n },\n\n /**\n * Assert that an entity exists (throws if not)\n */\n assertEntityExists(world: World, entity: EntityId): void {\n if (!world.exists(entity)) {\n throw new AssertionError(`Entity ${entity} does not exist`);\n }\n },\n\n /**\n * Assert that an entity does not exist (throws if it exists)\n */\n assertEntityNotExists(world: World, entity: EntityId): void {\n if (world.exists(entity)) {\n throw new AssertionError(`Entity ${entity} unexpectedly exists`);\n }\n },\n\n /**\n * Assert that a query contains specific entities (throws if not)\n */\n assertQueryContains(query: Query, ...entities: EntityId[]): void {\n const queryEntities = query.getEntities();\n for (const entity of entities) {\n if (!queryEntities.includes(entity)) {\n throw new AssertionError(\n `Query does not contain entity ${entity}.\\n` + `Query entities: [${queryEntities.join(\", \")}]`,\n );\n }\n }\n },\n\n /**\n * Assert that a query does not contain specific entities (throws if it does)\n */\n assertQueryNotContains(query: Query, ...entities: EntityId[]): void {\n const queryEntities = query.getEntities();\n for (const entity of entities) {\n if (queryEntities.includes(entity)) {\n throw new AssertionError(\n `Query unexpectedly contains entity ${entity}.\\n` + `Query entities: [${queryEntities.join(\", \")}]`,\n );\n }\n }\n },\n};\n\n// =============================================================================\n// Snapshot - State Capture and Comparison\n// =============================================================================\n\n/**\n * Utilities for capturing and comparing world state snapshots.\n * Useful for testing that systems produce expected state changes.\n *\n * @example\n * ```typescript\n * const before = Snapshot.capture(world, [entity], [PositionId, VelocityId]);\n *\n * // Run game logic\n * movementSystem(world, deltaTime);\n * world.sync();\n *\n * const after = Snapshot.capture(world, [entity], [PositionId, VelocityId]);\n * const diff = Snapshot.compare(before, after);\n *\n * expect(diff.componentChanges).toHaveLength(1);\n * expect(diff.componentChanges[0].changeType).toBe(\"modified\");\n * ```\n */\nexport const Snapshot = {\n /**\n * Capture a snapshot of specified entities and their components\n * @param world The world to capture from\n * @param entities Entities to include in the snapshot\n * @param componentIds Components to capture for each entity\n */\n capture(world: World, entities: EntityId[], componentIds: EntityId<any>[]): WorldSnapshot {\n const entitySnapshots: EntitySnapshot[] = [];\n\n for (const entity of entities) {\n if (!world.exists(entity)) {\n continue;\n }\n\n const components = new Map<EntityId<any>, unknown>();\n\n for (const componentId of componentIds) {\n if (isWildcardRelationId(componentId)) {\n // For wildcard relations, capture all relation instances\n try {\n const relations = world.get(entity, componentId as WildcardRelationId<any>);\n if (relations && relations.length > 0) {\n components.set(componentId, deepClone(relations));\n }\n } catch {\n // Entity doesn't have this relation type\n }\n } else if (world.has(entity, componentId)) {\n components.set(componentId, deepClone(world.get(entity, componentId)));\n }\n }\n\n entitySnapshots.push({ entity, components });\n }\n\n return { entities: entitySnapshots };\n },\n\n /**\n * Compare two snapshots and return the differences\n * @param before The 'before' snapshot\n * @param after The 'after' snapshot\n */\n compare(before: WorldSnapshot, after: WorldSnapshot): SnapshotDiff {\n const beforeEntities = new Set(before.entities.map((e) => e.entity));\n const afterEntities = new Set(after.entities.map((e) => e.entity));\n\n const addedEntities: EntityId[] = [];\n const removedEntities: EntityId[] = [];\n const componentChanges: SnapshotDiff[\"componentChanges\"] = [];\n\n // Find added entities\n for (const entity of afterEntities) {\n if (!beforeEntities.has(entity)) {\n addedEntities.push(entity);\n }\n }\n\n // Find removed entities\n for (const entity of beforeEntities) {\n if (!afterEntities.has(entity)) {\n removedEntities.push(entity);\n }\n }\n\n // Find component changes on existing entities\n const beforeMap = new Map(before.entities.map((e) => [e.entity, e]));\n const afterMap = new Map(after.entities.map((e) => [e.entity, e]));\n\n for (const entity of beforeEntities) {\n if (!afterEntities.has(entity)) continue; // Skip removed entities\n\n const beforeEntity = beforeMap.get(entity)!;\n const afterEntity = afterMap.get(entity)!;\n\n // Check for component changes\n const allComponentIds = new Set([...beforeEntity.components.keys(), ...afterEntity.components.keys()]);\n\n for (const componentId of allComponentIds) {\n const beforeValue = beforeEntity.components.get(componentId);\n const afterValue = afterEntity.components.get(componentId);\n\n if (beforeValue === undefined && afterValue !== undefined) {\n componentChanges.push({\n entity,\n componentId,\n before: undefined,\n after: afterValue,\n changeType: \"added\",\n });\n } else if (beforeValue !== undefined && afterValue === undefined) {\n componentChanges.push({\n entity,\n componentId,\n before: beforeValue,\n after: undefined,\n changeType: \"removed\",\n });\n } else if (!deepEquals(beforeValue, afterValue)) {\n componentChanges.push({\n entity,\n componentId,\n before: beforeValue,\n after: afterValue,\n changeType: \"modified\",\n });\n }\n }\n }\n\n return { addedEntities, removedEntities, componentChanges };\n },\n\n /**\n * Check if two snapshots are equal\n */\n equals(a: WorldSnapshot, b: WorldSnapshot): boolean {\n const diff = this.compare(a, b);\n return diff.addedEntities.length === 0 && diff.removedEntities.length === 0 && diff.componentChanges.length === 0;\n },\n};\n\n// =============================================================================\n// Utilities\n// =============================================================================\n\n/**\n * Custom assertion error for testing utilities\n */\nexport class AssertionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AssertionError\";\n }\n}\n\n/**\n * Deep equality check for comparing component values\n */\nfunction deepEquals(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEquals(a[i], b[i])) return false;\n }\n return true;\n }\n\n if (typeof a === \"object\" && typeof b === \"object\") {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n\n if (aKeys.length !== bKeys.length) return false;\n\n for (const key of aKeys) {\n if (!Object.prototype.hasOwnProperty.call(bObj, key)) return false;\n if (!deepEquals(aObj[key], bObj[key])) return false;\n }\n return true;\n }\n\n return false;\n}\n\n/**\n * Deep clone a value for snapshot isolation\n */\nfunction deepClone<T>(value: T): T {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map(deepClone) as T;\n }\n\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n result[key] = deepClone((value as Record<string, unknown>)[key]);\n }\n return result as T;\n}\n\n// =============================================================================\n// Re-exports for convenience\n// =============================================================================\n\nexport { component, relation } from \"../entity\";\nexport type { ComponentId, EntityId, RelationId, WildcardRelationId } from \"../entity\";\nexport type { Query } from \"../query/query\";\nexport type { LifecycleCallback, LifecycleHook } from \"../types\";\nexport { World } from \"../world/world\";\n"],"mappings":"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"}