@vworlds/vecs 1.0.43 → 1.0.45

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 (38) hide show
  1. package/README.md +2 -2
  2. package/dist/component.d.ts +27 -17
  3. package/dist/component.js +9 -0
  4. package/dist/component.js.map +1 -1
  5. package/dist/component_meta.d.ts +40 -13
  6. package/dist/component_meta.js +22 -6
  7. package/dist/component_meta.js.map +1 -1
  8. package/dist/entity/entity.components.d.ts +57 -4
  9. package/dist/entity/entity.components.js +121 -22
  10. package/dist/entity/entity.components.js.map +1 -1
  11. package/dist/entity/entity.d.ts +4 -2
  12. package/dist/entity/entity.js +4 -2
  13. package/dist/entity/entity.js.map +1 -1
  14. package/dist/entity/entity.lifecycle.js +1 -4
  15. package/dist/entity/entity.lifecycle.js.map +1 -1
  16. package/dist/entity/entity.relationships.js +3 -0
  17. package/dist/entity/entity.relationships.js.map +1 -1
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/modules/relationship_types.d.ts +18 -0
  22. package/dist/modules/relationship_types.js +18 -0
  23. package/dist/modules/relationship_types.js.map +1 -1
  24. package/dist/modules/relationships.d.ts +8 -2
  25. package/dist/modules/relationships.js +41 -3
  26. package/dist/modules/relationships.js.map +1 -1
  27. package/dist/package.json +1 -1
  28. package/docs/README.md +6 -6
  29. package/docs/components.md +77 -13
  30. package/docs/concepts.md +8 -2
  31. package/docs/design-guide.md +135 -1
  32. package/docs/entities.md +62 -6
  33. package/docs/execution-model.md +8 -4
  34. package/docs/glossary.md +11 -2
  35. package/docs/modules.md +7 -7
  36. package/docs/relationships.md +81 -0
  37. package/package.json +1 -1
  38. package/skills/vecs/SKILL.md +81 -0
@@ -152,7 +152,33 @@ world.component(Phaser.GameObjects.Arc).onRemove((_entity, obj) => obj.destroy()
152
152
 
153
153
  Use them for resource management and cross-cutting reactions that aren't worth a system. (Note
154
154
  the difference from systems: hooks are per-component-instance and run at the mutation site;
155
- systems are queries that run in a phase.)
155
+ systems are queries that run in a phase.) Each event has one handler slot: setting another handler
156
+ replaces it, calling the hook method with no argument reads it, and passing `undefined` removes it.
157
+
158
+ ### Component traits: policy on component entities
159
+
160
+ A registered component is also an entity. Components attached to that component entity can declare
161
+ policy for every use of the component type; these configuration components are **component traits**:
162
+
163
+ ```ts
164
+ world.component(Gravity).add(Singleton);
165
+ world.component(Inventory).add(OwnedTarget);
166
+ world.component(ChildOf).add(Traversable);
167
+ world.component(Orange).set(Implements, { interfaces: [Fruit] });
168
+ ```
169
+
170
+ | Trait/declaration | Reach for it when |
171
+ | ----------------- | -------------------------------------------------------------------------- |
172
+ | `Singleton` | At most one entity may carry the component |
173
+ | `Implements` | Concrete components should be queryable through abstract aliases |
174
+ | `Traversable` | A relationship hierarchy needs depth or cascade ordering |
175
+ | `OwnedTarget` | A relationship is an exclusively owned, zero-or-one entity-valued property |
176
+
177
+ Traits declare policy; modules implement it through hooks and metadata. Configure them in module
178
+ setup, not inside systems. Lifecycle differs by trait: `OwnedTarget` and `Singleton` are setup-time
179
+ policy, `Traversable` backfills, and `Implements` recomputes declarations. Do not assume traits are
180
+ dynamically removable. See the [Components guide](./components.md#component-traits-policy-on-component-entities)
181
+ for the full model and custom-trait guidance.
156
182
 
157
183
  ### Exclusive component groups
158
184
 
@@ -178,6 +204,21 @@ entity.target(InCell); // the target entity
178
204
  world.query("ByCell").with(Networked, Position).groupBy(InCell); // index by target
179
205
  ```
180
206
 
207
+ Use `entity.ensureTarget(InCell)` when an absent relationship should default to a new independent
208
+ entity. For aggregate parts whose lifetime should remain bound to the source once assigned, attach
209
+ `OwnedTarget` during setup:
210
+
211
+ ```ts
212
+ world.component(Inventory).add(OwnedTarget);
213
+ const inventory = player.ensureTarget(Inventory); // inventory is ChildOf player
214
+ player.add(Inventory); // also creates an owned target
215
+ ```
216
+
217
+ `OwnedTarget` creates a target when the relationship is added, destroys displaced targets on
218
+ retarget, and destroys the current target when the relationship is removed, keeping aggregate
219
+ cleanup aligned with the active relationship. Configure the relationship owner's lifecycle hooks
220
+ before attaching the trait; it wraps those hooks and runs ownership first.
221
+
181
222
  - **`Relationship.target` is an `Entity` — not directly serialisable, but
182
223
  relationships can be networked** when listed in `networkComponents`.
183
224
  `vecs-protocol` registers `Relationship.prototype.target` as an `"entity"`
@@ -543,6 +584,87 @@ or when the callback is intentionally "future changes only."
543
584
  Bucket entities by a relationship target (or a derived key) and look up buckets in O(1) instead of
544
585
  scanning. Moving between buckets is a retarget (data change), not a component swap.
545
586
 
587
+ ### Owned target entities (entity-valued properties)
588
+
589
+ Before adding a nested object to a component or a side map from owner eid to state, ask whether the
590
+ value is really one ECS sub-entity. Use an owned target when all of these are true:
591
+
592
+ - The owner has zero or one value for the concern.
593
+ - The value needs its own optional components, systems, or query identity.
594
+ - The value is exclusively owned and never shared.
595
+ - Replacement, relationship removal, and owner destruction should destroy it.
596
+
597
+ ```ts
598
+ class Inventory extends Relationship {}
599
+ class InventoryState {}
600
+ class Capacity {
601
+ slots = 0;
602
+ }
603
+ class Weight {
604
+ current = 0;
605
+ }
606
+
607
+ world.component(Inventory).add(OwnedTarget);
608
+ world.component(InventoryState);
609
+ world.component(Capacity);
610
+ world.component(Weight);
611
+
612
+ const inventory = player.ensureTarget(Inventory);
613
+ inventory.add(InventoryState).set(Capacity, { slots: 20 }).set(Weight, { current: 0 });
614
+ ```
615
+
616
+ `Inventory` is an entity-valued property of `player`, but it remains entirely inside the ECS.
617
+ Inventory systems operate on inventory entities without adding inventory-specific state to every
618
+ player archetype:
619
+
620
+ ```ts
621
+ world
622
+ .system("inventory.weight")
623
+ .with(InventoryState, Weight)
624
+ .each([Weight], (_inventory, [weight]) => {
625
+ void weight.current;
626
+ });
627
+ ```
628
+
629
+ The lifecycle follows from the relationship and trait:
630
+
631
+ | Operation | Result |
632
+ | -------------------------------------- | ---------------------------------------- |
633
+ | `player.add(Inventory)` | Creates the inventory entity |
634
+ | First `player.ensureTarget(Inventory)` | Creates and returns the inventory entity |
635
+ | Later `ensureTarget(Inventory)` | Returns the same target |
636
+ | Retarget `Inventory` | Destroys the displaced inventory |
637
+ | Remove `Inventory` | Destroys the current inventory |
638
+ | Destroy `player` | `ChildOf` destroys the inventory |
639
+
640
+ Inside deferred execution, mutate the entity returned by `ensureTarget`; do not immediately re-read
641
+ the relationship:
642
+
643
+ ```ts
644
+ world.defer(() => {
645
+ const inventory = player.ensureTarget(Inventory);
646
+ inventory.set(Capacity, { slots: 20 }); // correct: pending entity is usable
647
+ player.target(Inventory); // still undefined until flush
648
+ });
649
+ ```
650
+
651
+ Use the narrowest model that fits:
652
+
653
+ | Need | Model |
654
+ | --------------------------------------------------- | ------------------------------------------ |
655
+ | A few fields sharing the owner's identity/lifecycle | Components directly on the owner |
656
+ | One composable, exclusively owned sub-entity | `ensureTarget` + `OwnedTarget` |
657
+ | One shared or independently living linked entity | Relationship without `OwnedTarget` |
658
+ | Zero-to-many subobjects | Child entities with identifying components |
659
+ | A non-ECS runtime object | `attach(instance)` |
660
+ | World-global state | Singleton/component entity |
661
+
662
+ Good fits include an inventory aggregate, equipment loadout, character progression sheet, AI
663
+ blackboard, quest journal, ability book, or unique vehicle subsystem. Individual inventory items,
664
+ abilities, objectives, and other collections should remain separate entities. Networking remains
665
+ explicit: register the relationship and replicate the target's required components; `OwnedTarget`
666
+ itself is local lifecycle policy.
667
+
546
668
  ### Initialize via opt-in `update`-on-entry
547
669
 
548
670
  When an update callback owns both initialization and later changes, pass `{ onEnter: true }`. The
@@ -559,6 +681,10 @@ system after whatever produces the entity/companion.
559
681
  | An imperative runtime object with `start()/stop()/track/enable` | A **module** + the world's own lifecycle (`clearAllEntities()`, closing the world). Systems live on the world; they don't need a wrapper to turn on. |
560
682
  | Builder/factory helpers that _create entities_ (`createArc(world, …)`) | Let callers `world.entity().add(Networked).set(Arc, …).set(FillStyle, …)`. Components are small and optional; the caller adds exactly what it wants. |
561
683
  | A god component with many optional fields | Split into small single-purpose components; absence encodes "not applicable." |
684
+ | A large nested `inventory`, `brain`, or progression object | Use an owned target entity when the subobject needs its own components, systems, and lifecycle. |
685
+ | A side `Map<ownerEid, SubsystemState>` | Use `owner.ensureTarget(Rel)`; add `OwnedTarget` only for exclusive destructive ownership. |
686
+ | `OwnedTarget` on `CurrentEnemy`, `InCell`, `Team`, or shared state | Use a normal relationship; retargeting or removal of an owned relationship destroys its target. |
687
+ | One owned target used as a collection | Use multiple child/item entities; owned targets model zero-or-one aggregates. |
562
688
  | Duplicated `enter` + `update` doing the same thing | One `update({ watch: C, onEnter: true })` when update owns initialization. |
563
689
  | One system trying to act in two phases | Split into two systems, one per phase. |
564
690
  | Custom phases for app/render logic | Use the standard taxonomy; only engine modules insert phases. |
@@ -588,6 +714,14 @@ system after whatever produces the entity/companion.
588
714
  `vecs-protocol` registers it as an `"entity"` wire field (u32 eid) at module
589
715
  load, so `Relationship` subclasses listed in `networkComponents` serialise
590
716
  correctly.
717
+ - **Component traits have trait-specific setup semantics.** In particular, configure `OwnedTarget`
718
+ before creating relationship instances; it does not backfill.
719
+ - **`OwnedTarget` is destructive and exclusive.** Retargeting destroys the old target, removing the
720
+ relationship destroys the current target, and owner destruction cascades through `ChildOf`.
721
+ Never use it for shared teams, cells, enemies, definitions, or other world entities.
722
+ - **Owned targets are zero-or-one.** Use child entities for collections, and during deferred
723
+ execution mutate the entity returned by `ensureTarget` instead of re-reading `target(R)` before
724
+ flush.
591
725
  - **`world.module(M)` runs `init` once.** Re-registering with a different config throws.
592
726
 
593
727
  ---
package/docs/entities.md CHANGED
@@ -47,6 +47,8 @@ for end-of-session teardown, not selective resets.
47
47
  e.add(Position); // attach with default field values (idempotent)
48
48
  e.set(Position, { x: 100 }); // attach if needed, assign props, fire onSet/update
49
49
  e.get(Position); // Readonly<Position> | undefined
50
+ e.ensure(Position, (pos) => (pos.x += 1)); // create if needed, then mutate and notify
51
+ e.ensureTarget(FriendOf); // get or create an independent relationship target
50
52
  e.has(Position); // boolean
51
53
  e.remove(Position); // detach; fires exit callbacks, then onRemove
52
54
  e.modified(Position); // mark changed after in-place mutation
@@ -63,10 +65,12 @@ key.
63
65
  `onSet`.
64
66
  - `set(C, props)` attaches if needed (firing `onAdd`), copies `props` onto the instance, and fires
65
67
  `onSet` plus `update` callbacks.
68
+ - `ensure(C, callback)` creates the component if missing, then lets you mutate it and fires
69
+ `onAdd` followed by one `onSet`. When the component already exists, it behaves like `getMut`.
66
70
  - `modified(C)` fires `onSet`/`update` for data you already mutated in place. Repeated calls are
67
71
  coalesced until the world routes the notification.
68
72
 
69
- ### `getMut`: mutating in place
73
+ ### `getMut` and `ensure`: mutating in place
70
74
 
71
75
  `get` returns a `Readonly` view. To mutate, use `getMut`, which marks the component modified for
72
76
  you:
@@ -83,8 +87,53 @@ e.getMut(Velocity, (vel) => {
83
87
  });
84
88
  ```
85
89
 
86
- Calling `getMut(C)` without a callback outside deferred mode throws. `getMut` also throws for
87
- relationship components (retarget with `set` instead — see
90
+ Use `ensure` when absence should mean "create with defaults, then mutate":
91
+
92
+ ```ts
93
+ e.ensure(Velocity, (vel) => {
94
+ vel.vx = 2;
95
+ });
96
+ ```
97
+
98
+ `ensure` always returns a component for a live entity. Calling it on a destroyed entity throws.
99
+ When several calls ensure the same missing component during one deferred scope, they return the
100
+ same pending instance. The instance remains hidden from `get` and queries until the queue drains,
101
+ but every ensure callback mutates that shared pending value.
102
+
103
+ Use `ensureTarget` for the corresponding get-or-create operation on a registered relationship:
104
+
105
+ ```ts
106
+ world.component(FriendOf);
107
+
108
+ const friend = entity.ensureTarget(FriendOf);
109
+ entity.ensureTarget(FriendOf) === friend; // true
110
+ entity.target(FriendOf) === friend; // true after deferred changes flush
111
+ ```
112
+
113
+ When the relationship is absent, `ensureTarget` creates an independent entity and points the
114
+ relationship at it. It does not add `ChildOf`; destroying the source does not destroy the generated
115
+ target. Repeated calls during one deferred scope return the same pending target, while `target`
116
+ continues to expose only committed state until the queue drains.
117
+
118
+ Mark the relationship with `OwnedTarget` during world setup when its targets should instead be
119
+ owned by their sources:
120
+
121
+ ```ts
122
+ world.component(Inventory).add(OwnedTarget);
123
+
124
+ const inventory = player.ensureTarget(Inventory);
125
+ inventory.parent === player; // true after deferred changes flush
126
+
127
+ player.add(Inventory); // also creates an owned target
128
+ ```
129
+
130
+ The relationship module creates a target when an owned relationship is added, adds `ChildOf`
131
+ whenever `Inventory` is assigned, destroys displaced targets on retarget, and destroys the current
132
+ target when the relationship is removed. Destroying `player` therefore also destroys `inventory`.
133
+ See [Relationships](./relationships.md#owned-targets).
134
+
135
+ Calling `getMut(C)` or `ensure(C)` without a callback outside deferred mode throws. Both methods
136
+ also throw for relationship components (retarget with `set` instead — see
88
137
  [Relationships](./relationships.md#retargeting)) and for `Implements` interface aliases.
89
138
 
90
139
  ### `attach`: storing an existing instance
@@ -111,6 +160,8 @@ Inside a system body, a `forEach`, or a `world.defer` block, all of the mutators
111
160
  commands instead of applying immediately, and reads return the pre-mutation state until the queue
112
161
  drains. The exact read-after-write rules are tabulated in
113
162
  [Execution model — deferred mutation](./execution-model.md#deferred-mutation-and-flush-boundaries).
163
+ `ensure(C)` is the exception for mutable access: it returns a mutable component immediately, but a
164
+ newly created component does not appear in `get(C)` until the queue drains.
114
165
 
115
166
  Deferred entity creation is idempotent within one deferred scope: if `world.entity()` or
116
167
  `getOrCreateEntity(eid)` creates a new entity, `getEntity(eid)`, `entity(eid)`, and later
@@ -133,8 +184,10 @@ it throws while carriers remain ([Components — cleanup policies](./components.
133
184
 
134
185
  Any entity can serve as a component key: `other.add(keyEntity)` attaches a marker component whose
135
186
  type id is the key entity's eid. The key entity's `meta` (created lazily) carries the same hook
136
- and policy surface as a registered class, and `keyEntity.onAdd/onSet/onRemove(...)` register hooks
137
- directly. This is how tag-like, runtime-created component types work without declaring a class.
187
+ and policy surface as a registered class, and `keyEntity.onAdd/onSet/onRemove(...)` get, set, or
188
+ remove its single lifecycle handlers directly. This is how tag-like, runtime-created component
189
+ types work without declaring a class. Registered relationship component entities additionally
190
+ expose `onRetarget(...)`.
138
191
 
139
192
  ## Relationships, in brief
140
193
 
@@ -164,6 +217,8 @@ is in [Relationships](./relationships.md).
164
217
  | `set(ref, props)` | Attach if needed and assign `props`; fires `onSet` and `update` callbacks. Returns `this`. |
165
218
  | `get(ref)` | Read-only component lookup, or `undefined`. |
166
219
  | `getMut(ref)` / `getMut(ref, callback)` | Mutable lookup that marks the component modified. Callback form required outside deferred mode. |
220
+ | `ensure(ref)` / `ensure(ref, callback)` | Mutable lookup that creates when missing. Callback form required outside deferred mode. |
221
+ | `ensureTarget(Relationship)` | Return a relationship target, creating an independent target when absent. |
167
222
  | `has(ref)` | `true` when the component is attached. |
168
223
  | `modified(ref)` | Queue an `onSet`/`update` notification; coalesced while pending. Returns `this`. |
169
224
  | `remove(ref)` | Detach a component; routes `exit`, then fires `onRemove`. |
@@ -173,5 +228,6 @@ is in [Relationships](./relationships.md).
173
228
  | `childOf(parent)` | Shorthand for `set(ChildOf, { target: parent })`. Returns `this`. |
174
229
  | `parent` / `parent = p` | Getter/setter for the built-in `ChildOf` target; assigning `undefined` removes `ChildOf`. |
175
230
  | `destroyed` / `alive` | `true` when the entity has been / has not been destroyed (marked for deletion). |
176
- | `onAdd/onSet/onRemove(handler)` | Register hooks for this entity used as a component key. Return `this`. |
231
+ | `onAdd/onSet/onRemove(handler?)` | Get, set, or remove this component key's single hook. Setters return `this`. |
232
+ | `onRetarget(handler?)` | Get, set, or remove a relationship component entity's single retarget hook. |
177
233
  | `toString()` | Returns `"EntityN"`. |
@@ -97,9 +97,9 @@ built-in taxonomy.
97
97
  ## Deferred mutation and flush boundaries
98
98
 
99
99
  Inside a system body, a `Query.forEach` / `Filter.forEach` iteration, or any `world.defer(...)`
100
- block, the world is in **deferred mode**: entity mutations (`add` / `attach` / `set` / `remove` /
101
- `destroy` / `modified`, and `world.entity()` creation) are queued as commands instead of applied
102
- inline. The queue drains when the scope that opened deferral ends.
100
+ block, the world is in **deferred mode**: entity mutations (`add` / `attach` / `set` / `ensure` /
101
+ `ensureTarget` / `remove` / `destroy` / `modified`, and `world.entity()` creation) are queued as
102
+ commands instead of applied inline. The queue drains when the scope that opened deferral ends.
103
103
 
104
104
  For the pipeline this means: **a structural change made by system A is applied in the flush
105
105
  immediately after A finishes, so it is visible to every later system in the same tick** — later in
@@ -116,8 +116,12 @@ While a mutation is queued but not yet applied, reads return the pre-mutation st
116
116
  | `entity.add(C)` | `undefined` — no instance has been created yet. |
117
117
  | `entity.attach(instance)` | `undefined`, if `C` was absent before. |
118
118
  | `entity.set(C, props)` | The **previous** value (or `undefined` if `C` was absent). |
119
+ | `entity.ensure(C)` | A mutable component, even when newly created. |
119
120
  | `entity.remove(C)` | Still the component instance. |
120
121
 
122
+ `entity.ensureTarget(R)` similarly returns one shared pending target while `entity.target(R)`
123
+ continues to return the previously committed target until flush.
124
+
121
125
  Outside any deferred scope (top-level user code), the same calls execute inline and effects are
122
126
  visible immediately.
123
127
 
@@ -156,7 +160,7 @@ Three routing rules worth memorizing:
156
160
  - **`exit` reads a snapshot.** Components injected into an `exit` callback are captured when the
157
161
  exit is routed, so they are still resolvable even though they are being (or have been) removed.
158
162
  - **In-place mutation needs `modified`.** Assigning to component fields directly notifies no one.
159
- Call `entity.modified(C)` (or use `set` / `getMut`) to route the change.
163
+ Call `entity.modified(C)` (or use `set` / `getMut` / `ensure`) to route the change.
160
164
 
161
165
  ## Why the patterns work
162
166
 
package/docs/glossary.md CHANGED
@@ -50,8 +50,9 @@ entity lifecycle API. [Utilities](./utilities.md#events).
50
50
  **Filter** — a non-reactive, one-shot predicate that scans world entities on each `forEach`.
51
51
  [Queries and filters](./queries-and-filters.md#filters-one-shot-scans).
52
52
 
53
- **Hook** — an `onAdd` / `onSet` / `onRemove` callback registered per component class, fired
54
- synchronously at the mutation site. [Components](./components.md#hooks-onadd-onset-onremove).
53
+ **Hook** — an `onAdd` / `onSet` / `onRemove` callback registered per component class, or an
54
+ `onRetarget` callback for relationships, fired synchronously at the mutation site.
55
+ [Components](./components.md#hooks-onadd-onset-onremove-onretarget).
55
56
 
56
57
  **Implements / interface alias** — a declaration that a concrete component is queryable through
57
58
  abstract/interface component types; aliases are read-only views of the same instance.
@@ -71,6 +72,10 @@ visited entity and the source entity of each injected slot.
71
72
  **Module** — a packaging unit (`class M extends Module<Config>`) whose `init()` registers
72
73
  components, systems, and policy once per world. [Modules](./modules.md).
73
74
 
75
+ **Owned target entity** — a zero-or-one aggregate entity targeted through a relationship carrying
76
+ the `OwnedTarget` trait. It is parented to the source and destroyed when displaced, when the
77
+ relationship is removed, or when the source dies. [Relationships](./relationships.md#owned-targets).
78
+
74
79
  **Phase** — an entity used as a pipeline ordering anchor (tagged with the `Phase` component);
75
80
  systems attach to exactly one phase via `DependsOn`.
76
81
  [Execution model](./execution-model.md#phases-the-pipeline-spine).
@@ -102,6 +107,10 @@ assigned to one phase. [Systems](./systems.md).
102
107
  **Tick source** — a clock (`IntervalTickSource`, `RateTickSource`, or another system) that gates
103
108
  how often a system fires. [Systems](./systems.md#cadence-interval-rate-and-tick-sources).
104
109
 
110
+ **Trait (component trait)** — policy attached as a component to a registered component entity,
111
+ configuring every use of that component type. Built-ins include `Singleton`, `Implements`,
112
+ `Traversable`, and `OwnedTarget`. [Components](./components.md#component-traits-policy-on-component-entities).
113
+
105
114
  **Watchlist** — the set of component types a query or system reacts to via `update`, stored as a
106
115
  bitmask. [Systems](./systems.md#reactive-callbacks-enter-update-exit).
107
116
 
package/docs/modules.md CHANGED
@@ -89,13 +89,13 @@ Guidelines:
89
89
  Every `World` constructor installs these (`lib/vecs/src/world/world.ts`); you interact with their
90
90
  _components_, not with the module objects:
91
91
 
92
- | Module | Provides |
93
- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
94
- | `IdentityModule` | The `Identity` name component and the name → entity index behind `world.entity("name")` / `entity.name`. |
95
- | `ImplementsModule` | Maintains [`Implements` interface aliases](./components.md#interface-aliases-with-implements) and their transitive closure. |
96
- | `SingletonModule` | Enforces the [`Singleton`](./components.md#singletons-and-world-global-data) one-carrier constraint. |
97
- | `RelationshipsModule` | Registers `ChildOf` (with the `Delete` cascade) and maintains [`Traversable` depth tracking](./relationships.md#traversal-order-traversable-and-cascade). |
98
- | `PipelineModule` | Creates the eight [built-in phases](./execution-model.md#phases-the-pipeline-spine) and installs the default pipeline query. |
92
+ | Module | Provides |
93
+ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
94
+ | `IdentityModule` | The `Identity` name component and the name → entity index behind `world.entity("name")` / `entity.name`. |
95
+ | `ImplementsModule` | Maintains [`Implements` interface aliases](./components.md#interface-aliases-with-implements) and their transitive closure. |
96
+ | `SingletonModule` | Enforces the [`Singleton`](./components.md#singletons-and-world-global-data) one-carrier constraint. |
97
+ | `RelationshipsModule` | Registers `ChildOf`, installs [`OwnedTarget`](./relationships.md#owned-targets) ownership, and maintains [`Traversable` depth tracking](./relationships.md#traversal-order-traversable-and-cascade). |
98
+ | `PipelineModule` | Creates the eight [built-in phases](./execution-model.md#phases-the-pipeline-spine) and installs the default pipeline query. |
99
99
 
100
100
  All are exported, so a custom world setup can reuse them.
101
101
 
@@ -43,6 +43,70 @@ subclasses) explicitly in your `networkComponents` array to replicate parent-chi
43
43
 
44
44
  `entity.childOf(parent)` is shorthand for `entity.set(ChildOf, { target: parent })`.
45
45
 
46
+ ## Ensuring a target
47
+
48
+ Use `ensureTarget` when a registered relationship should always have some target and a new,
49
+ independent entity is the correct default:
50
+
51
+ ```ts
52
+ world.component(FriendOf);
53
+
54
+ const friend = entity.ensureTarget(FriendOf);
55
+ entity.ensureTarget(FriendOf) === friend; // true
56
+ entity.target(FriendOf) === friend; // true
57
+ ```
58
+
59
+ The method returns the existing target when present. Otherwise it creates an entity, initializes
60
+ the relationship with that target, and attaches the relationship. During deferred execution,
61
+ repeated calls share one pending relationship and return the same target; `target(FriendOf)` still
62
+ returns `undefined` until flush.
63
+
64
+ The generated target is independent: `ensureTarget` does not add `ChildOf`, and destroying the
65
+ source does not destroy the target. Add ownership explicitly when your model requires it. The
66
+ relationship class must be registered before calling `ensureTarget`.
67
+
68
+ ## Owned targets
69
+
70
+ Mark a relationship component with `OwnedTarget` during world setup when every target assigned
71
+ through that relationship should become a `ChildOf` child of its source:
72
+
73
+ ```ts
74
+ import { OwnedTarget, Relationship } from "@vworlds/vecs";
75
+
76
+ class Inventory extends Relationship {}
77
+
78
+ world.component(Inventory).add(OwnedTarget);
79
+
80
+ const inventory = player.ensureTarget(Inventory);
81
+ inventory.parent === player; // true
82
+
83
+ player.destroy();
84
+ inventory.destroyed === true; // ChildOf cascades deletion
85
+ ```
86
+
87
+ Use `add` when the relationship itself represents a feature and initialization belongs in an
88
+ `onAdd` hook or system `enter` callback:
89
+
90
+ ```ts
91
+ world.component(Inventory).add(OwnedTarget);
92
+
93
+ player.add(Inventory);
94
+ const inventory = player.target(Inventory)!; // target was created by OwnedTarget
95
+ ```
96
+
97
+ `OwnedTarget` wraps `onAdd`, `onSet`, `onRetarget`, and `onRemove` on `Inventory`. Adding the
98
+ relationship creates a target when needed. New targets become `ChildOf` children, displaced targets
99
+ are destroyed on retarget, and the current target is destroyed when the relationship is removed
100
+ (including source destruction). Ownership behavior runs before the relationship owner's existing
101
+ hook, so owner hooks and query entry observe an initialized target. During deferred execution, these
102
+ changes apply while the command queue drains.
103
+
104
+ Configure the trait before creating relationship instances; it does not backfill existing targets.
105
+ Configure owner lifecycle hooks before attaching the trait, because later hook assignment replaces
106
+ the wrapper and its ownership behavior.
107
+ Do not share one owned target between several sources: retargeting or removing any owning
108
+ relationship can destroy it. `OwnedTarget` rejects ordinary components and `ChildOf` itself.
109
+
46
110
  ## Retargeting
47
111
 
48
112
  Point a relationship somewhere else with a plain `set`:
@@ -60,6 +124,20 @@ lookups.
60
124
  `getMut` throws for relationship components: in-place target mutation would bypass the reverse
61
125
  index used by `children()` and the cleanup cascades. Always retarget with `set`.
62
126
 
127
+ Register `onRetarget` when behavior needs both targets:
128
+
129
+ ```ts
130
+ world.component(InCell).onRetarget((entity, oldCell, newCell) => {
131
+ // entity.target(InCell) is newCell here.
132
+ // oldCell.children(InCell) no longer contains entity.
133
+ // newCell.children(InCell) already contains entity.
134
+ });
135
+ ```
136
+
137
+ It fires only when an existing relationship moves to a different existing target, after reverse
138
+ indexes update and before `onSet`. Initial assignment, same-target writes, and removal do not fire
139
+ `onRetarget`; use `onSet` and `onRemove` for those lifecycle points.
140
+
63
141
  ## Cleanup when a target dies
64
142
 
65
143
  What happens to sources when their target entity is destroyed is governed by the relationship's
@@ -145,6 +223,9 @@ the box — the world's default pipeline is itself a `cascade: DependsOn` query
145
223
  | `e.target(R)` | The target entity through relationship `R`, or `undefined`. |
146
224
  | `e.children(R)` | `ReadonlySet<Entity>` of sources targeting `e` through `R`. |
147
225
  | `e.childOf(parent)` | Shorthand for `e.set(ChildOf, { target: parent })`. |
226
+ | `e.ensureTarget(R)` | Return `R`'s target, creating an independent entity when absent. |
227
+ | `world.component(R).add(OwnedTarget)` | Create and own `R` targets as `ChildOf` children of their sources. |
228
+ | `world.component(R).onRetarget(handler?)` | Get, set, or remove the single retarget handler. |
148
229
  | `e.parent` / `e.parent = p` | Getter/setter for the built-in `ChildOf` target; setting `undefined` removes `ChildOf`. |
149
230
  | `meta.onDeleteTarget` | `Remove` (default) or `Delete` cascade when a target is destroyed. |
150
231
  | `world.component(R).add(Traversable)` | Enable depth tracking for `cascade` ordering. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vworlds/vecs",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: vecs
3
+ description: "Designs, implements, reviews, and debugs games or applications using @vworlds/vecs. Use whenever a task creates or changes vecs components, entities, systems, queries, relationships, modules, phases, lifecycle, networked components, or ECS architecture."
4
+ ---
5
+
6
+ # vecs Application Development
7
+
8
+ Use the documentation shipped with the consuming project's installed `@vworlds/vecs` package.
9
+ Those docs match the API version the project actually uses and are the source of truth for vecs
10
+ design and behavior.
11
+
12
+ ## Before Working
13
+
14
+ 1. Locate `node_modules/@vworlds/vecs/docs/`. In a workspace or non-npm install, resolve the
15
+ installed `@vworlds/vecs` package and use its adjacent `docs/` directory.
16
+ 2. Read `docs/design-guide.md` sections 1, 2, 8, and 9 before designing or changing ECS behavior.
17
+ 3. Read the task-specific sections of that design guide:
18
+ - Components or replicated state: section 3.
19
+ - Systems or queries: section 4.
20
+ - Ordering or timing: section 5.
21
+ - Feature architecture: section 6.
22
+ - Lifecycle and synchronization patterns: section 7.
23
+ 4. Use `docs/README.md` to select any detailed subsystem guides needed by the task. Read
24
+ `docs/concepts.md` and `docs/execution-model.md` when the design depends on unfamiliar vecs
25
+ behavior.
26
+ 5. Inspect the consuming project's existing vecs modules and conventions before proposing a new
27
+ pattern.
28
+
29
+ Do not rely on memory or a different vecs checkout when installed documentation is available.
30
+ Confirm exact names and signatures against the installed declarations under
31
+ `node_modules/@vworlds/vecs/dist/`.
32
+
33
+ ## Design From ECS Semantics
34
+
35
+ Start from the vecs golden rule: components are data, systems are behavior, and component presence
36
+ and change drive lifecycle.
37
+
38
+ Before implementing, identify:
39
+
40
+ - The state represented by each component and whether absence has meaning.
41
+ - Which component transition creates, updates, or removes related state or resources.
42
+ - Whether the work should react through `enter`, `update`, or `exit` rather than scan every frame.
43
+ - Which phase owns the work and what earlier or later phase it must order against.
44
+ - Whether an owned target, relationship, trait, grouped query, or attached resource expresses the
45
+ lifecycle better than imperative bookkeeping.
46
+ - Whether the feature belongs in a `Module` that owns registration, systems, phases, and policy.
47
+
48
+ Prefer the idioms in the installed design guide over generic ECS patterns. vecs execution details,
49
+ including per-system flushes and reactive inbox routing, are part of the design model.
50
+
51
+ ## Avoid Generic Workarounds
52
+
53
+ Check the design guide before introducing:
54
+
55
+ - Side maps keyed by entity id.
56
+ - Components containing behavior or broad god-components with optional sentinel fields.
57
+ - Per-frame scans for changes that vecs can route reactively.
58
+ - Manual lifecycle bookkeeping that duplicates component presence.
59
+ - System registration order as a substitute for explicit cross-feature phase ordering.
60
+ - Parallel object graphs that duplicate relationships already represented in the ECS.
61
+ - Wrapper abstractions that hide normal vecs entities, components, systems, or modules without a
62
+ concrete need.
63
+
64
+ If the requested design conflicts with a documented vecs invariant, explain the conflict and use
65
+ the documented pattern unless the user explicitly chooses otherwise.
66
+
67
+ ## Implement And Verify
68
+
69
+ 1. Reuse existing components and modules where their semantics match; do not reuse them merely
70
+ because their fields look similar.
71
+ 2. Keep components small data classes and put behavior in systems or narrowly scoped lifecycle
72
+ hooks as directed by the guide.
73
+ 3. Express cross-feature ordering with phases. Treat same-phase registration order as a local
74
+ implementation detail unless the installed docs explicitly make it part of the design.
75
+ 4. Test lifecycle transitions, not only steady-state values: entry, mutation, removal, retargeting,
76
+ destruction, and same-tick visibility where relevant.
77
+ 5. Run the consuming project's typecheck and focused tests. Resolve API uncertainty from the
78
+ installed declarations and docs rather than guessing.
79
+
80
+ For networking, physics, Phaser, client, or server integration, also inspect the README and
81
+ declarations shipped with the corresponding `@vworlds/vecs-*` package.