@vworlds/vecs 1.0.44 → 1.0.46

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.
Files changed (136) hide show
  1. package/README.md +7 -1
  2. package/dist/component.d.ts +57 -51
  3. package/dist/component.js +25 -9
  4. package/dist/component.js.map +1 -1
  5. package/dist/component_meta.d.ts +71 -66
  6. package/dist/component_meta.js +331 -34
  7. package/dist/component_meta.js.map +1 -1
  8. package/dist/dsl.js +1 -1
  9. package/dist/dsl.js.map +1 -1
  10. package/dist/entity/entity.base.js +4 -0
  11. package/dist/entity/entity.base.js.map +1 -1
  12. package/dist/entity/entity.components.d.ts +39 -27
  13. package/dist/entity/entity.components.js +258 -74
  14. package/dist/entity/entity.components.js.map +1 -1
  15. package/dist/entity/entity.d.ts +1 -2
  16. package/dist/entity/entity.js +2 -3
  17. package/dist/entity/entity.js.map +1 -1
  18. package/dist/entity/entity.lifecycle.d.ts +7 -1
  19. package/dist/entity/entity.lifecycle.js +40 -53
  20. package/dist/entity/entity.lifecycle.js.map +1 -1
  21. package/dist/entity/entity.queries.js +30 -0
  22. package/dist/entity/entity.queries.js.map +1 -1
  23. package/dist/entity/entity.relationships.d.ts +4 -0
  24. package/dist/entity/entity.relationships.js +18 -10
  25. package/dist/entity/entity.relationships.js.map +1 -1
  26. package/dist/index.d.ts +7 -4
  27. package/dist/index.js +4 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/inject.d.ts +63 -14
  30. package/dist/inject.js +165 -89
  31. package/dist/inject.js.map +1 -1
  32. package/dist/modules/identity.js +2 -2
  33. package/dist/modules/identity.js.map +1 -1
  34. package/dist/modules/implements.js +2 -2
  35. package/dist/modules/implements.js.map +1 -1
  36. package/dist/modules/pipeline.js +5 -5
  37. package/dist/modules/pipeline.js.map +1 -1
  38. package/dist/modules/relationship_types.d.ts +70 -3
  39. package/dist/modules/relationship_types.js +70 -3
  40. package/dist/modules/relationship_types.js.map +1 -1
  41. package/dist/modules/relationships.d.ts +31 -8
  42. package/dist/modules/relationships.js +120 -30
  43. package/dist/modules/relationships.js.map +1 -1
  44. package/dist/modules/singleton.js +2 -2
  45. package/dist/modules/singleton.js.map +1 -1
  46. package/dist/modules/snapshot.d.ts +26 -0
  47. package/dist/modules/snapshot.js +45 -0
  48. package/dist/modules/snapshot.js.map +1 -0
  49. package/dist/modules/snapshot_types.d.ts +85 -0
  50. package/dist/modules/snapshot_types.js +130 -0
  51. package/dist/modules/snapshot_types.js.map +1 -0
  52. package/dist/package.json +2 -2
  53. package/dist/query/callbacks.d.ts +9 -7
  54. package/dist/query/callbacks.js +27 -41
  55. package/dist/query/callbacks.js.map +1 -1
  56. package/dist/query/grouped_query.js +7 -7
  57. package/dist/query/grouped_query.js.map +1 -1
  58. package/dist/query/grouping.d.ts +6 -2
  59. package/dist/query/grouping.js +84 -15
  60. package/dist/query/grouping.js.map +1 -1
  61. package/dist/query/query.d.ts +4 -4
  62. package/dist/query/query.js +37 -14
  63. package/dist/query/query.js.map +1 -1
  64. package/dist/snapshot/capture.d.ts +1 -0
  65. package/dist/snapshot/capture.js +212 -0
  66. package/dist/snapshot/capture.js.map +1 -0
  67. package/dist/snapshot/clone.d.ts +1 -0
  68. package/dist/snapshot/clone.js +107 -0
  69. package/dist/snapshot/clone.js.map +1 -0
  70. package/dist/snapshot/index.d.ts +5 -0
  71. package/dist/snapshot/index.js +21 -0
  72. package/dist/snapshot/index.js.map +1 -0
  73. package/dist/snapshot/preflight.d.ts +1 -0
  74. package/dist/snapshot/preflight.js +40 -0
  75. package/dist/snapshot/preflight.js.map +1 -0
  76. package/dist/snapshot/restore.d.ts +1 -0
  77. package/dist/snapshot/restore.js +111 -0
  78. package/dist/snapshot/restore.js.map +1 -0
  79. package/dist/snapshot/types.d.ts +135 -0
  80. package/dist/snapshot/types.js +26 -0
  81. package/dist/snapshot/types.js.map +1 -0
  82. package/dist/system.d.ts +3 -1
  83. package/dist/system.js +33 -23
  84. package/dist/system.js.map +1 -1
  85. package/dist/terms/all_term.js +19 -0
  86. package/dist/terms/all_term.js.map +1 -1
  87. package/dist/terms/any_term.js +19 -0
  88. package/dist/terms/any_term.js.map +1 -1
  89. package/dist/terms/not_term.js +4 -0
  90. package/dist/terms/not_term.js.map +1 -1
  91. package/dist/terms/target_term.d.ts +1 -2
  92. package/dist/terms/target_term.js +2 -4
  93. package/dist/terms/target_term.js.map +1 -1
  94. package/dist/terms/term.d.ts +2 -0
  95. package/dist/terms/term.js +59 -10
  96. package/dist/terms/term.js.map +1 -1
  97. package/dist/util/array_map.d.ts +43 -12
  98. package/dist/util/array_map.js +90 -41
  99. package/dist/util/array_map.js.map +1 -1
  100. package/dist/util/dense_set.js +26 -0
  101. package/dist/util/dense_set.js.map +1 -1
  102. package/dist/world/world.base.js +12 -0
  103. package/dist/world/world.base.js.map +1 -1
  104. package/dist/world/world.d.ts +7 -0
  105. package/dist/world/world.deferred.js +17 -0
  106. package/dist/world/world.deferred.js.map +1 -1
  107. package/dist/world/world.entities.d.ts +13 -2
  108. package/dist/world/world.entities.js +16 -2
  109. package/dist/world/world.entities.js.map +1 -1
  110. package/dist/world/world.js +15 -3
  111. package/dist/world/world.js.map +1 -1
  112. package/dist/world/world.pipeline.d.ts +2 -2
  113. package/dist/world/world.pipeline.js +4 -2
  114. package/dist/world/world.pipeline.js.map +1 -1
  115. package/dist/world/world.queries.d.ts +7 -0
  116. package/dist/world/world.queries.js +52 -0
  117. package/dist/world/world.queries.js.map +1 -1
  118. package/dist/world/world.transfers.d.ts +137 -0
  119. package/dist/world/world.transfers.js +188 -0
  120. package/dist/world/world.transfers.js.map +1 -0
  121. package/docs/README.md +18 -6
  122. package/docs/components.md +91 -48
  123. package/docs/concepts.md +21 -17
  124. package/docs/design-guide.md +125 -89
  125. package/docs/entities.md +62 -72
  126. package/docs/execution-model.md +26 -10
  127. package/docs/getting-started.md +6 -6
  128. package/docs/glossary.md +19 -10
  129. package/docs/modules.md +8 -8
  130. package/docs/queries-and-filters.md +15 -10
  131. package/docs/relationships.md +250 -115
  132. package/docs/snapshots.md +181 -0
  133. package/docs/systems.md +64 -21
  134. package/docs/utilities.md +10 -6
  135. package/package.json +2 -2
  136. package/skills/vecs/SKILL.md +5 -4
@@ -5,8 +5,9 @@ exclusivity, cleanup policies, interface aliases, and singletons.
5
5
 
6
6
  ## Components are plain data classes
7
7
 
8
- A component is an ordinary class with field initializers and a no-argument constructor. It does
9
- not inherit from a vecs base class — vecs instantiates it with `new ComponentClass()`:
8
+ An ordinary component is a class with field initializers and a no-argument constructor. Relationship
9
+ components are the exception: they extend `Relationship` so vecs can recognize their private edge
10
+ storage. Vecs instantiates either form with `new ComponentClass()`:
10
11
 
11
12
  ```ts
12
13
  class Position {
@@ -71,27 +72,30 @@ Pass `idPools` to the `World` constructor to change ranges (each entry is an `Id
71
72
  released this frame is not immediately recycled. `world.peekNextEid(poolName)` returns the next id
72
73
  a pool would allocate without reserving it. Id 0 is reserved.
73
74
 
74
- ## Hooks: `onAdd`, `onSet`, `onRemove`, `onRetarget`
75
+ ## Hooks: component and relationship edges
75
76
 
76
77
  The `Component` entity exposes lifecycle hooks fired for every entity carrying the component:
77
78
 
78
79
  ```ts
79
80
  world
80
81
  .component(Sprite)
81
- .onAdd((entity, sprite) => sprite.initialize(scene, entity))
82
- .onSet((entity, sprite) => sprite.syncToScene(entity))
83
- .onRemove((entity, sprite) => sprite.destroy(scene, entity));
82
+ .on("add", (entity, sprite) => sprite.initialize(scene, entity))
83
+ .on("set", (entity, sprite) => sprite.syncToScene(entity))
84
+ .on("remove", (entity, sprite) => sprite.destroy(scene, entity));
84
85
  ```
85
86
 
86
- | Hook | Fires when… |
87
- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
88
- | `onAdd` | The component is first attached (`add`, or `set` on an entity that lacked it). |
89
- | `onSet` | `entity.set(C, props)` applies data, `entity.attach(instance)` stores an instance, `entity.modified(C)` is called, or `getMut` / `ensure` marks it dirty. |
90
- | `onRemove` | The component is detached (`entity.remove(C)`), or the owning entity is destroyed. |
87
+ | Hook | Fires when… |
88
+ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
89
+ | `add` | The component is first attached (`add`, or `set` on an entity that lacked an ordinary component). |
90
+ | `set` | `entity.set(C, props)` applies ordinary data, `entity.attach(instance)` stores an instance, `entity.modified(C)` is called, or `getMut` / `ensure` marks it dirty. |
91
+ | `remove` | The component is detached (`entity.remove(C)`), or the owning entity is destroyed. |
91
92
 
92
- Relationship component entities additionally expose `onRetarget`. It fires after an existing
93
- relationship changes to a different target, once `target` and `children` already reflect the new
94
- reverse-index state. Initial assignment, same-target `set`, and removal do not fire it.
93
+ Relationship component entities additionally expose the `targetAdd` and `targetRemove` events. Both
94
+ receive `(source, relationshipInstance, target)` after the edge and reverse index reach their new
95
+ state. An exclusive replacement fires `targetRemove` for the old edge before `targetAdd` for
96
+ the new edge. Adding or removing edges never fires `set`; it is reserved for ordinary fields on
97
+ an existing relationship. See [Relationships — edge hooks](./relationships.md#edge-hooks) for the
98
+ full ordering.
95
99
 
96
100
  Handlers receive `(entity, componentInstance)` — component instances do not carry entity
97
101
  references, so the entity is passed explicitly. Hooks fire synchronously when the corresponding
@@ -99,6 +103,11 @@ command is applied (see [Execution model](./execution-model.md#reactive-routing)
99
103
  resource management and cross-cutting reactions that aren't worth a system; use systems when the
100
104
  reaction belongs to a phase.
101
105
 
106
+ Multiple handlers may be registered for the same event: they fire in registration
107
+ order. Call `on(event, handler)` to append, `on(event, handler, true)` to
108
+ prepend, and `off(event, handler)` to remove a handler by reference. All three
109
+ return the component entity for chaining.
110
+
102
111
  ## Component traits: policy on component entities
103
112
 
104
113
  Registered components are themselves entities. Attach a configuration component to a component
@@ -107,24 +116,30 @@ components **component traits**:
107
116
 
108
117
  ```ts
109
118
  world.component(Gravity).add(Singleton);
110
- world.component(Inventory).add(OwnedTarget);
111
- world.component(ChildOf).add(Traversable);
119
+ world.component(Inventory).add(Exclusive).add(OwnedTarget);
120
+ world.component(ChildOf).add(Traversable); // ChildOf is already Exclusive
112
121
  world.component(Orange).set(Implements, { interfaces: [Fruit] });
113
122
  ```
114
123
 
115
- | Trait/declaration | Applies to | Effect |
116
- | ----------------- | ---------------------------------- | -------------------------------------------------------------------- |
117
- | `Singleton` | Concrete components | Allows at most one carrier entity |
118
- | `Implements` | Non-relationship components | Exposes concrete data through abstract aliases |
119
- | `Traversable` | Relationships | Maintains hierarchy depth for cascade ordering |
120
- | `OwnedTarget` | Relationships other than `ChildOf` | Owns the active target through `ChildOf` and destructive replacement |
124
+ | Trait/declaration | Applies to | Effect |
125
+ | ----------------- | -------------------------------------------- | ---------------------------------------------------------------------------- |
126
+ | `Singleton` | Concrete components | Allows at most one carrier entity |
127
+ | `Implements` | Non-relationship components | Exposes concrete data through abstract aliases |
128
+ | `Exclusive` | Relationships | Restricts the relationship to one target with atomic replacement |
129
+ | `Traversable` | Exclusive relationships | Maintains hierarchy depth for cascade ordering |
130
+ | `OwnedTarget` | Exclusive relationships other than `ChildOf` | Owns each explicit target through `ChildOf` and destructive edge replacement |
131
+ | `OneOf` | Relationships | Constrains each target to be a direct `ChildOf` child of a declared parent |
121
132
 
122
133
  Traits declare policy; their built-in modules install the hooks and maintain the metadata that
123
134
  enforce it. Configure traits in feature-module setup rather than opportunistically inside systems.
124
135
  Do not assume all traits have the same lifecycle:
125
136
 
126
137
  - Add `Singleton` before component instances exist.
127
- - Add `OwnedTarget` before relationship instances exist; it does not backfill.
138
+ - Add `Exclusive` before relationship instances exist. `OwnedTarget` and `Traversable` require it.
139
+ - Add `OwnedTarget` before relationship instances exist; it never creates a target implicitly and
140
+ does not replace public edge hooks.
141
+ - Add `OneOf` before relationship instances exist; it rejects every target that is not a direct
142
+ `ChildOf` child of the declared parent.
128
143
  - `Traversable` backfills existing relationship instances.
129
144
  - Setting or removing `Implements` recomputes interface aliases.
130
145
 
@@ -139,8 +154,10 @@ class Audited {}
139
154
 
140
155
  class AuditModule extends Module {
141
156
  public init(): void {
142
- this.world.component(Audited).onAdd((component) => {
143
- component.onSet((entity) => console.log("changed", component.meta.Class.name, entity.eid));
157
+ this.world.component(Audited).on("add", (component) => {
158
+ component.on("set", (entity) =>
159
+ console.log("changed", component.meta.Class.name, entity.eid)
160
+ );
144
161
  });
145
162
  }
146
163
  }
@@ -171,17 +188,22 @@ at any time. Exclusivity is how you model "exactly one of N" (one renderable per
171
188
  physics shape per body) — and it makes type swaps clean, because replacing the component fires the
172
189
  old one's `exit`/`onRemove` automatically.
173
190
 
191
+ This is distinct from the `Exclusive` relationship trait. `setExclusiveComponents(A, B)` chooses
192
+ which **component class** may exist; `world.component(R).add(Exclusive)` allows one instance of
193
+ relationship `R` as usual but limits it to one **target edge**, replacing that edge atomically. See
194
+ [Relationships — exclusive one-target relationships](./relationships.md#exclusive-one-target-relationships).
195
+
174
196
  ## Cleanup policies: destroying a component entity
175
197
 
176
198
  Every component key is backed by a component entity. What happens when you destroy that entity
177
199
  while other entities still carry the component is controlled by `meta.onDelete`
178
200
  (`CleanupPolicy` in `lib/vecs/src/component_meta.ts`):
179
201
 
180
- | Policy | Meaning |
181
- | ---------------------- | -------------------------------------------------------------------------------------------------- |
182
- | `CleanupPolicy.Throw` | Default. Destroying the component entity while other entities carry it throws and changes nothing. |
183
- | `CleanupPolicy.Remove` | Remove the component from every carrier, then destroy the component entity. |
184
- | `CleanupPolicy.Delete` | Destroy every carrier entity, then destroy the component entity. |
202
+ | Policy | Meaning |
203
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
204
+ | `CleanupPolicy.Throw` | Default. Destroying the component entity while other entities carry it throws. The synchronous `destroy()` call throws before mutating anything, so the world stays usable; a `Throw` policy reached during a deferred drain or a relationship cascade throws and leaves the world partial — discard it. |
205
+ | `CleanupPolicy.Remove` | Remove the component from every carrier, then destroy the component entity. |
206
+ | `CleanupPolicy.Delete` | Destroy every carrier entity, then destroy the component entity. |
185
207
 
186
208
  ```ts
187
209
  import { CleanupPolicy } from "@vworlds/vecs";
@@ -191,7 +213,29 @@ world.component(Temporary).destroy(); // strips Temporary from all carriers
191
213
  ```
192
214
 
193
215
  The `Throw` default also applies to plain entities used as component keys (`entity.add(keyEntity)`).
194
- A failed destroy leaves the component entity, its carriers, and the usage count untouched.
216
+
217
+ ## Snapshot policies: what a component may cross worlds with
218
+
219
+ A component type also declares how
220
+ [subtree snapshots](./snapshots.md) capture it, through `world.component(Type).snapshotPolicy(...)`.
221
+ The default — no declaration — deep-copies the instance's own enumerable fields, and **fails
222
+ loudly at capture** on a nested value that is not plain data, so a component carrying a class
223
+ instance must declare a policy saying how that instance is detached. `DontSnapshot` marks a
224
+ type as world-bound (an engine handle, a socket, a live `Query`), and `CustomSnapshot` with
225
+ `capture` / `materialize` callbacks is how a type that mixes declarative and runtime state hands over
226
+ only the declarative half:
227
+
228
+ ```ts
229
+ world.component(PhysicsEngine).snapshotPolicy(DontSnapshot);
230
+ ```
231
+
232
+ A policy is declared by the owning module and may change at any time: a capture binds the
233
+ materializer its policy resolved to, so an existing snapshot always restores the way it was
234
+ captured.
235
+ A synchronous `destroy()` throw (component still in use) leaves the world usable. A throw during
236
+ deferred drain or a cascade is irrecoverable — the component entity, its carriers, and the usage
237
+ count are in a partial state and the world must be discarded. Configure cleanup policies so cascades
238
+ are consistent; treat a mid-destroy throw as a programming error.
195
239
 
196
240
  Relationship components have a second, independent policy — `meta.onDeleteTarget` — that governs
197
241
  what happens to _sources_ when a relationship **target** is destroyed. See
@@ -279,7 +323,7 @@ the live object itself as a component** with `entity.attach(obj)` instead of kee
279
323
  const obj = scene.add.arc(0, 0); // a Phaser GameObject
280
324
  entity.attach(obj); // the GameObject is now a component, keyed by its class
281
325
 
282
- world.component(Phaser.GameObjects.Arc).onRemove((_entity, obj) => obj.destroy());
326
+ world.component(Phaser.GameObjects.Arc).on("remove", (_entity, obj) => obj.destroy());
283
327
  ```
284
328
 
285
329
  The entity's component set then _is_ the resource lifecycle: queries drive create/destroy, and
@@ -306,22 +350,21 @@ interface markers for polymorphic dispatch — is in the
306
350
  A `Component<T>` is an `Entity` subclass, so the full [entity API](./entities.md#reference)
307
351
  applies. On top of it:
308
352
 
309
- | Member | Description |
310
- | --------------------- | -------------------------------------------------------------------- |
311
- | `onAdd(handler)` | Register an add hook. Returns the component entity for chaining. |
312
- | `onRemove(handler)` | Register a remove hook. |
313
- | `onRetarget(handler)` | Register a relationship retarget hook. Throws for non-relationships. |
314
- | `onSet(handler)` | Register a set/modified hook. |
315
- | `meta` | The `ComponentMeta` bookkeeping record. |
353
+ | Member | Description |
354
+ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
355
+ | `on(event, handler, insert?)` | Register a handler for `add` / `remove` / `set` / `targetAdd` / `targetRemove` (plus the internal `policyAdd` / `policyRemove` / `targetPolicyAdd` / `targetPolicyRemove` events). `insert` defaults to `false` (append); `true` prepends. Rejects `target*` events on non-relationships. Returns the component. |
356
+ | `off(event, handler)` | Remove a previously-registered handler by reference. Idempotent. Returns the component. |
357
+ | `meta` | The `ComponentMeta` bookkeeping record. |
316
358
 
317
359
  ### `ComponentMeta` (`lib/vecs/src/component_meta.ts`)
318
360
 
319
- | Field | Description |
320
- | ---------------- | --------------------------------------------------------------------------------------------- |
321
- | `Class` | The component class constructor. |
322
- | `eid` | Component entity id assigned at registration. |
323
- | `bitPtr` | Pre-computed bit pointer into entity archetype bitsets. |
324
- | `isRelationship` | `true` when the class extends `Relationship`. |
325
- | `onDelete` | `CleanupPolicy` applied to carriers when the component entity is destroyed. Default `Throw`. |
326
- | `onDeleteTarget` | `CleanupPolicy` applied to sources when a relationship target is destroyed. Default `Remove`. |
327
- | `usageCount` | Number of entities currently carrying this component. |
361
+ | Field | Description |
362
+ | ----------------------- | --------------------------------------------------------------------------------------------- |
363
+ | `Class` | The component class constructor. |
364
+ | `eid` | Component entity id assigned at registration. |
365
+ | `bitPtr` | Pre-computed bit pointer into entity archetype bitsets. |
366
+ | `isRelationship` | `true` when the class extends `Relationship`. |
367
+ | `relationshipExclusive` | `true` when the relationship carries the `Exclusive` trait. |
368
+ | `onDelete` | `CleanupPolicy` applied to carriers when the component entity is destroyed. Default `Throw`. |
369
+ | `onDeleteTarget` | `CleanupPolicy` applied to sources when a relationship target is destroyed. Default `Remove`. |
370
+ | `usageCount` | Number of entities currently carrying this component. |
package/docs/concepts.md CHANGED
@@ -12,21 +12,22 @@ every idiom in the [Design guide](./design-guide.md) is a consequence of taking
12
12
 
13
13
  ## The pieces
14
14
 
15
- | Concept | What it is |
16
- | ------------------------ | ----------------------------------------------------------------------------------------------------------- |
17
- | **World** | Central container. Owns every entity, registered component, query, system, and the flat system pipeline. |
18
- | **Entity** | A numeric id (`eid`) with a set of components attached. Created via the world. |
19
- | **Component** | A registered plain data class with a no-argument constructor. No behavior. |
20
- | **System** | A `Query` plus per-tick logic (`update`, `each`, `run`), assigned to one pipeline phase. |
21
- | **Query** | A reactive, always-up-to-date set of entities matching a predicate, with `enter`/`exit`/`update` callbacks. |
22
- | **Filter** | A non-reactive, one-shot scan: walks all world entities on each `forEach` call. |
23
- | **Hook** | Lightweight component callbacks (`onAdd` / `onSet` / `onRemove`, plus relationship `onRetarget`). |
24
- | **Phase** | Marker component for entities used as pipeline ordering anchors. |
25
- | **Relationship** | A component whose `target` field points at another entity, with reverse lookup and cleanup cascades. |
26
- | **Component trait** | Policy attached to a component entity, configuring every use of that component type. |
27
- | **Module** | A unit of packaging: a class whose `init()` registers components, systems, phases, and policy. |
28
- | **Exclusive components** | A group of components where at most one may exist on any entity at a time. |
29
- | **Implements** | Declares abstract/interface component aliases for a concrete component, for polymorphic queries. |
15
+ | Concept | What it is |
16
+ | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
17
+ | **World** | Central container. Owns every entity, registered component, query, system, and the flat system pipeline. |
18
+ | **Entity** | A numeric id (`eid`) with a set of components attached. Created via the world. |
19
+ | **Component** | A registered plain data class with a no-argument constructor. No behavior. |
20
+ | **System** | A `Query` plus per-tick logic (`update`, `each`, `run`), assigned to one pipeline phase. |
21
+ | **Query** | A reactive, always-up-to-date set of entities matching a predicate, with `enter`/`exit`/`update` callbacks. |
22
+ | **Filter** | A non-reactive, one-shot scan: walks all world entities on each `forEach` call. |
23
+ | **Hook** | Lightweight component callbacks (`onAdd` / `onSet` / `onRemove`, plus relationship edge hooks). |
24
+ | **Phase** | Marker component for entities used as pipeline ordering anchors. |
25
+ | **Relationship** | A component holding explicit edges to one or more target entities, with reverse lookup and cleanup cascades. |
26
+ | **Component trait** | Policy attached to a component entity, configuring every use of that component type. |
27
+ | **Module** | A unit of packaging: a class whose `init()` registers components, systems, phases, and policy. |
28
+ | **Exclusive components** | A group of components where at most one may exist on any entity at a time. |
29
+ | **Exclusive relationship** | A relationship carrying `Exclusive`, restricting each source to at most one target. |
30
+ | **Implements** | Declares abstract/interface component aliases for a concrete component, for polymorphic queries. |
30
31
 
31
32
  ## Data vs. behavior
32
33
 
@@ -46,8 +47,11 @@ right components, and adding a capability to an entity is just adding a componen
46
47
 
47
48
  Because a registered component is also an entity, it can itself carry components. Built-in
48
49
  **component traits** use that meta-ECS layer to declare reusable policy: `Singleton` constrains a
49
- component, `Implements` declares aliases, and `Traversable` / `OwnedTarget` configure relationships.
50
- The trait remains data; a module owns the hooks and metadata behavior it activates.
50
+ component, `Implements` declares aliases, `Exclusive` constrains relationship cardinality, and
51
+ `Traversable` / `OwnedTarget` configure exclusive relationships. The trait remains data; a module
52
+ owns the hooks and metadata behavior it activates. Relationship `Exclusive` is unrelated to an
53
+ exclusive component group: one limits edge count, while the other limits coexisting component
54
+ types.
51
55
 
52
56
  Prefer many small, single-purpose components over one large one. An entity that never rotates
53
57
  simply omits `Rotation`; absence encodes "not applicable". See