@vworlds/vecs 1.0.24 → 1.0.26

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 (76) hide show
  1. package/README.md +29 -778
  2. package/dist/component.d.ts +1 -1
  3. package/dist/component.js +1 -1
  4. package/dist/component.js.map +1 -1
  5. package/dist/component_meta.d.ts +5 -1
  6. package/dist/component_meta.js +10 -0
  7. package/dist/component_meta.js.map +1 -1
  8. package/dist/dsl.d.ts +2 -2
  9. package/dist/entity/entity.base.d.ts +2 -18
  10. package/dist/entity/entity.base.js +2 -20
  11. package/dist/entity/entity.base.js.map +1 -1
  12. package/dist/entity/entity.components.d.ts +1 -0
  13. package/dist/entity/entity.components.js +50 -0
  14. package/dist/entity/entity.components.js.map +1 -1
  15. package/dist/entity/entity.lifecycle.d.ts +3 -3
  16. package/dist/entity/entity.lifecycle.js +6 -9
  17. package/dist/entity/entity.lifecycle.js.map +1 -1
  18. package/dist/entity/entity.relationships.js +2 -2
  19. package/dist/entity/entity.relationships.js.map +1 -1
  20. package/dist/filter.d.ts +1 -0
  21. package/dist/filter.js +3 -2
  22. package/dist/filter.js.map +1 -1
  23. package/dist/index.d.ts +4 -2
  24. package/dist/index.js +4 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/inject.d.ts +3 -2
  27. package/dist/inject.js.map +1 -1
  28. package/dist/modules/implements.d.ts +14 -0
  29. package/dist/modules/implements.js +98 -0
  30. package/dist/modules/implements.js.map +1 -0
  31. package/dist/package.json +1 -8
  32. package/dist/query/callbacks.d.ts +6 -2
  33. package/dist/query/callbacks.js +5 -2
  34. package/dist/query/callbacks.js.map +1 -1
  35. package/dist/query/query.d.ts +14 -1
  36. package/dist/query/query.js +26 -15
  37. package/dist/query/query.js.map +1 -1
  38. package/dist/system.d.ts +5 -4
  39. package/dist/system.js +17 -6
  40. package/dist/system.js.map +1 -1
  41. package/dist/util/array_map.d.ts +70 -12
  42. package/dist/util/array_map.js +113 -26
  43. package/dist/util/array_map.js.map +1 -1
  44. package/dist/util/bitset.js +0 -17
  45. package/dist/util/bitset.js.map +1 -1
  46. package/dist/util/events.d.ts +42 -12
  47. package/dist/util/events.js +94 -43
  48. package/dist/util/events.js.map +1 -1
  49. package/dist/util/ordered_set.js +43 -19
  50. package/dist/util/ordered_set.js.map +1 -1
  51. package/dist/world/world.deferred.js +2 -0
  52. package/dist/world/world.deferred.js.map +1 -1
  53. package/dist/world/world.entities.d.ts +8 -1
  54. package/dist/world/world.entities.js +25 -6
  55. package/dist/world/world.entities.js.map +1 -1
  56. package/dist/world/world.js +8 -1
  57. package/dist/world/world.js.map +1 -1
  58. package/dist/world/world.queries.js +6 -1
  59. package/dist/world/world.queries.js.map +1 -1
  60. package/dist/world/world.storage.d.ts +2 -2
  61. package/dist/world/world.storage.js +6 -3
  62. package/dist/world/world.storage.js.map +1 -1
  63. package/docs/README.md +50 -0
  64. package/docs/components.md +267 -0
  65. package/docs/concepts.md +86 -0
  66. package/docs/design-guide.md +506 -0
  67. package/docs/entities.md +175 -0
  68. package/docs/execution-model.md +173 -0
  69. package/docs/getting-started.md +215 -0
  70. package/docs/glossary.md +113 -0
  71. package/docs/modules.md +108 -0
  72. package/docs/queries-and-filters.md +187 -0
  73. package/docs/relationships.md +148 -0
  74. package/docs/systems.md +311 -0
  75. package/docs/utilities.md +139 -0
  76. package/package.json +1 -8
@@ -0,0 +1,108 @@
1
+ # Modules
2
+
3
+ Package components, systems, and policy into reusable units, compose them, and meet the built-in
4
+ modules every world installs.
5
+
6
+ ## What a module is
7
+
8
+ A `Module` (`lib/vecs/src/module.ts`) is a component entity whose `init()` method runs once when
9
+ the module is registered. It is the unit of packaging for a feature: registering components,
10
+ declaring systems, setting exclusivity and cleanup policy, and (rarely) inserting phases.
11
+
12
+ ```ts
13
+ import { Module, PRE_STORE } from "@vworlds/vecs";
14
+
15
+ export class PoseSyncModule extends Module {
16
+ public init(): void {
17
+ const world = this.world;
18
+ world.component(PhysicsPosition);
19
+ world.component(RenderPosition);
20
+ world
21
+ .system("sync.position")
22
+ .with(PhysicsPosition, RenderPosition)
23
+ .phase(PRE_STORE)
24
+ .update(PhysicsPosition, (e, p) => e.set(RenderPosition, { x: p.x, y: p.y }));
25
+ }
26
+ }
27
+
28
+ world.module(PoseSyncModule);
29
+ ```
30
+
31
+ `world.module(ModuleClass, config?, eid?)` (`lib/vecs/src/world/world.modules.ts`) registers the
32
+ module on first call — drawing its entity id from the `module` pool (900–999 by default, or pass
33
+ an explicit `eid`) — and invokes `init(config)` exactly once. Subsequent calls return the existing
34
+ module instance; passing a config again after the first registration throws.
35
+
36
+ ## Configuration
37
+
38
+ Type the config through the `Module<Config>` parameter; it flows from `world.module(M, config)`
39
+ into `init(config)`:
40
+
41
+ ```ts
42
+ export class RenderModule extends Module<{ scene: Scene; pixelsPerMeter?: number }> {
43
+ public init(cfg: { scene: Scene; pixelsPerMeter?: number }): void {
44
+ const ppm = cfg.pixelsPerMeter ?? 32;
45
+ // ...
46
+ }
47
+ }
48
+
49
+ world.module(RenderModule, { scene });
50
+ ```
51
+
52
+ For `Module<undefined>` (the default), the config argument is omitted. Keep configs minimal;
53
+ prefer zero-config when a sensible default exists.
54
+
55
+ ## Composition and umbrella modules
56
+
57
+ A module's `init` can register sub-modules with `this.world.module(Sub, subConfig)`. A big feature
58
+ is often one module per item plus a thin umbrella that composes them:
59
+
60
+ ```ts
61
+ export class PhaserRenderModule extends Module<{ scene: Scene }> {
62
+ public init(cfg: { scene: Scene }): void {
63
+ this.world.component(VGameObject).onRemove((_e, vgo) => vgo.obj.destroy());
64
+ this.world.module(ArcModule, { scene: cfg.scene });
65
+ this.world.module(RectangleModule, { scene: cfg.scene });
66
+ // … shape modules first …
67
+ this.world.module(TransformSyncModule, {}); // … then sync, so create precedes sync within a phase
68
+ }
69
+ }
70
+ ```
71
+
72
+ **Load order sets same-phase order.** Systems in the same phase run in creation order, so the
73
+ order an umbrella loads sub-modules is the order their systems run within a shared phase. Use
74
+ that for intra-feature ordering — but express **cross-module** ordering with phases, never with
75
+ load order (see [Execution model](./execution-model.md#ordering-guarantees)).
76
+
77
+ Guidelines:
78
+
79
+ - **Declare systems inline in `init`.** A `registerFooSystem(world)` helper per system is a layer
80
+ for nothing.
81
+ - **Modules own cross-cutting policy** — `setExclusiveComponents`, cleanup policies, phase
82
+ placement, component registration. Keep that out of application code.
83
+ - **One module per cohesive thing.** Don't pile unrelated features into one `init`.
84
+
85
+ ## Built-in modules
86
+
87
+ Every `World` constructor installs these (`lib/vecs/src/world/world.ts`); you interact with their
88
+ _components_, not with the module objects:
89
+
90
+ | Module | Provides |
91
+ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
92
+ | `IdentityModule` | The `Identity` name component and the name → entity index behind `world.entity("name")` / `entity.name`. |
93
+ | `ImplementsModule` | Maintains [`Implements` interface aliases](./components.md#interface-aliases-with-implements) and their transitive closure. |
94
+ | `SingletonModule` | Enforces the [`Singleton`](./components.md#singletons-and-world-global-data) one-carrier constraint. |
95
+ | `RelationshipsModule` | Registers `ChildOf` (with the `Delete` cascade) and maintains [`Traversable` depth tracking](./relationships.md#traversal-order-traversable-and-cascade). |
96
+ | `PipelineModule` | Creates the eight [built-in phases](./execution-model.md#phases-the-pipeline-spine) and installs the default pipeline query. |
97
+
98
+ All are exported, so a custom world setup can reuse them.
99
+
100
+ ## Reference
101
+
102
+ | API | Description |
103
+ | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
104
+ | `class M extends Module<Config>` | Declare a module; `Config` defaults to `undefined`. |
105
+ | `init(config)` | One-time setup hook; called by the world on first registration. |
106
+ | `world.module(M)` / `world.module(M, config)` / `world.module(M, config, eid)` | Register (first call) or retrieve (later calls) the module. Config after first registration throws. |
107
+ | `this.world` | Inside `init`: the owning world (a module is an entity). |
108
+ | `ModuleConfig<T>` | Utility type extracting a module's config type. |
@@ -0,0 +1,187 @@
1
+ # Queries and Filters
2
+
3
+ Express entity predicates with the query DSL, track them live with `Query`, scan once with
4
+ `Filter`, index with `groupBy`, and reach for the `Bitset` utility that powers matching.
5
+
6
+ ## The query DSL
7
+
8
+ A `QueryDSL` (`lib/vecs/src/dsl.ts`) is a composable expression describing which entities match.
9
+ It is accepted by `query().with`, `system().with`, and `world.filter`:
10
+
11
+ | Operator | Meaning |
12
+ | ---------------------- | ------------------------------------------------------------------- |
13
+ | a component class/id | Entity has that component |
14
+ | an array `[A, B]` | Entity has all of `A` and `B` (empty array matches all) |
15
+ | `true` / `false` | Match all / match no entities |
16
+ | `{ all: [q1, q2] }` | Every sub-expression matches |
17
+ | `{ any: [q1, q2] }` | At least one sub-expression matches |
18
+ | `{ not: q }` | Sub-expression must not match |
19
+ | `{ only: [A, B] }` | Entity has exactly `A` and `B`, nothing else |
20
+ | `{ target: [R, q] }` | Relationship `R`'s target matches `q` |
21
+ | `{ source: [R, q] }` | Filter-only: some entity targeting this one through `R` matches `q` |
22
+ | `{ parent: q }` | Built-in `ChildOf` target matches `q` |
23
+ | `{ children: q }` | Filter-only: some `ChildOf` child matches `q` |
24
+ | `{ test: fn, watch? }` | Custom predicate; rechecked when a `watch`-listed component changes |
25
+
26
+ Operators nest arbitrarily:
27
+
28
+ ```ts
29
+ world.system("render").with({
30
+ all: [Position, { any: [Sprite, Container] }],
31
+ });
32
+ ```
33
+
34
+ Class-valued terms are components — register them first; an unregistered class throws. Use
35
+ `{ test: fn }` for arbitrary logic the operators can't express, with `watch` naming the components
36
+ whose changes should re-evaluate it.
37
+
38
+ `target` and `source` take tuple form only: `{ target: [RelationshipClass, innerDSL] }`. The first
39
+ element must extend `Relationship` (a plain component throws). `target` evaluates the inner DSL
40
+ against the relationship's target; `source` evaluates it against each entity pointing at the
41
+ candidate and matches when at least one does. `parent` / `children` are the same operators
42
+ specialized to `ChildOf`. **`source` and `children` are non-reactive**: exact for each filter
43
+ scan, but rejected by tracked queries and systems. `target` / `parent` are fully reactive — see
44
+ [Relationships](./relationships.md#querying-through-relationships).
45
+
46
+ ### `QuerySpec`: adding `cascade` and `hint`
47
+
48
+ Anywhere a DSL is accepted, you can pass the object form
49
+ `{ with: dsl, cascade?: Rel, hint?: [C...] }`:
50
+
51
+ - `cascade` orders the tracked set depth-first along a
52
+ [`Traversable` relationship](./relationships.md#traversal-order-traversable-and-cascade).
53
+ - `hint` declares components as guaranteed-present for **type inference only** (not validated at
54
+ runtime). Use it when the DSL shape (`any`, `not`, `test`, numeric ids) hides a guarantee the
55
+ type extractor can't see:
56
+
57
+ ```ts
58
+ world
59
+ .filter({ with: { any: [Position, Velocity] }, hint: [Position] })
60
+ .forEach([Position], (e, [pos]) => pos.x);
61
+ ```
62
+
63
+ Without a hint, only plain classes, arrays, `only`, and `all`-chains of those count as guaranteed,
64
+ and everything else injects as `T | undefined`.
65
+
66
+ ## Live queries: `Query`
67
+
68
+ `world.query(name)` returns a standalone reactive entity set (`lib/vecs/src/query/query.ts`),
69
+ configured through the same fluent builder as systems but without pipeline integration:
70
+
71
+ ```ts
72
+ const projectiles = world
73
+ .query("Projectiles")
74
+ .with(Position, Velocity)
75
+ .enter((e) => console.log("spawned", e.eid))
76
+ .exit((e) => console.log("gone", e.eid));
77
+
78
+ projectiles.count; // kept up to date automatically
79
+ projectiles.has(e);
80
+ for (const e of projectiles) { ... }
81
+ projectiles.forEach([Position], (e, [pos]) => { ... });
82
+ ```
83
+
84
+ The crucial difference from systems: **standalone query callbacks fire synchronously** while the
85
+ world routes a command, not in a phase slot. Mutations made inside one callback are themselves
86
+ observed immediately by other queries. Systems instead queue events into an inbox replayed on
87
+ their tick (see [Execution model](./execution-model.md#reactive-routing)).
88
+
89
+ ### Lifecycle: build, backfill, destroy
90
+
91
+ A query is **built** — its predicate materialized and registered — at the next `world.flush()` /
92
+ `progress()`, or immediately via `query.build()` outside deferred mode. Tracked queries (and any
93
+ query with callbacks) **backfill**: entities that already match are routed through `enter` at
94
+ build time. Builder methods throw once the query is built; `built` reports the state.
95
+
96
+ `query.destroy()` permanently removes the query: entity references are purged silently (no `exit`
97
+ fires), the tracked set is cleared, and further use of the object is undefined behavior. Use a
98
+ standalone query when you need a reactive set you can destroy mid-session.
99
+
100
+ ### Reading a query
101
+
102
+ | Member | Description |
103
+ | --------------------------- | -------------------------------------------------------------------------------------------------------- |
104
+ | `count` | Number of currently tracked entities (0 unless tracking). |
105
+ | `has(e)` | `true` when `e` is currently tracked. |
106
+ | `belongs(e)` | Evaluate the predicate against `e` right now (works regardless of tracking). |
107
+ | `[Symbol.iterator]` | Iterate tracked entities. |
108
+ | `forEach(...)` | Iterate with optional [component injection](./systems.md#component-injection); runs in a deferred scope. |
109
+ | `changed` | `true` when membership or watched data changed since the last `forEach` reset it. |
110
+ | `name` / `entity` / `world` | Display name; the backing entity; the owning world. |
111
+
112
+ `orderBy`, `track`, `enter`, `exit`, `update`, `with`, and `without` behave exactly as on systems
113
+ — see [Systems](./systems.md) — except that `update` callbacks fire synchronously here. A plain
114
+ `Query.update` callback can therefore re-enter during command routing; snapshot reused `Iter` /
115
+ tuple values before triggering further mutations from inside it.
116
+
117
+ ### Silencing feedback: `ignoreSource`
118
+
119
+ `query.ignoreSource(dsl | query)` makes the query drop `update` notifications whose originating
120
+ system matches the given query — the standard cure for write feedback loops (A updates B, B's
121
+ system writes back, A reacts again). The relationship is stored as the built-in `SilenceSource`
122
+ component on the query's backing entity. Entry-time initialization bypasses the silence filter, so
123
+ newly entered entities still initialize.
124
+
125
+ ## Grouped queries: `groupBy`
126
+
127
+ `groupBy` partitions a query's members into live buckets you can look up by key — spatial grids,
128
+ interest management, any "find members in bucket K" — without re-scanning:
129
+
130
+ ```ts
131
+ const ballsByCell = world
132
+ .query("ByCell")
133
+ .with(Ball, Position)
134
+ .groupBy([Position], (_e, [p]) => cellIndex(p)); // re-buckets reactively as Position changes
135
+
136
+ ballsByCell.group(key)?.forEach([Position], (e, [p]) => { ... });
137
+ ballsByCell.group(key)?.count;
138
+ for (const group of ballsByCell.groups()) { ... }
139
+ ```
140
+
141
+ Two forms (`lib/vecs/src/query/grouped_query.ts`):
142
+
143
+ - `groupBy([components], keyFn)` — bucket by a computed key; injected components are watched, so
144
+ changing them re-keys the entity.
145
+ - `groupBy(RelationshipClass)` — bucket by the relationship's target entity. Moving between
146
+ buckets is a retarget, a plain data change (see
147
+ [Relationships](./relationships.md#retargeting)).
148
+
149
+ The result is a `GroupedQuery<K, R>` adding `group(key)`, `groups()`, `groupKeys()`, `groupCount`,
150
+ and `onGroupEnter` / `onGroupExit` callbacks for bucket creation and disposal. Each `Group` exposes
151
+ `key`, `count`, `has`, iteration, and injected `forEach`. Grouping implies tracking. Systems do
152
+ not support `groupBy` — keep the grouped query standalone and read it from a system.
153
+
154
+ ## Filters: one-shot scans
155
+
156
+ `world.filter(spec)` returns a `Filter` (`lib/vecs/src/filter.ts`): a non-reactive predicate that
157
+ walks entities on each `forEach` call. It accepts the same DSL (including the filter-only
158
+ `source` / `children` operators):
159
+
160
+ ```ts
161
+ // Entity only:
162
+ world.filter([Position]).forEach((e) => console.log(e.eid));
163
+
164
+ // With component injection (auto-deduced non-null types):
165
+ world.filter([Position, Velocity]).forEach([Position, Velocity], (e, [pos, vel]) => {
166
+ pos.x += vel.vx;
167
+ });
168
+
169
+ // Manual hint where the extractor can't see through the DSL:
170
+ world
171
+ .filter({ with: { any: [Position, Velocity] }, hint: [Position] })
172
+ .forEach([Position], (e, [pos]) => pos.x);
173
+ ```
174
+
175
+ A `Filter` holds no tracked set, registers nothing, and needs no `destroy()` — create it anywhere
176
+ and discard freely. `forEach` runs inside a deferred scope, so mutations made by the callback are
177
+ batched and visible after iteration; nested inside an already-deferred block it inherits the outer
178
+ scope. When the world already maintains a live term for the same predicate, the filter iterates
179
+ that term instead of scanning every entity.
180
+
181
+ Use a `Filter` for occasional or one-off scans; use a `Query`/`System` when you need continuous
182
+ reactivity.
183
+
184
+ ## `Bitset`
185
+
186
+ `Bitset` is the compact integer-set type that powers entity archetypes and query watchlists.
187
+ It is documented in full in [Utilities](./utilities.md#bitset).
@@ -0,0 +1,148 @@
1
+ # Relationships
2
+
3
+ Link entities to entities with relationship components: hierarchy, reverse lookup, retargeting,
4
+ cleanup cascades, and depth-ordered traversal.
5
+
6
+ ## What a relationship is
7
+
8
+ A relationship is a component whose `target` field points at another entity. Define one by
9
+ extending the abstract `Relationship` base (`lib/vecs/src/component_meta.ts`) and registering it
10
+ like any component:
11
+
12
+ ```ts
13
+ import { Relationship } from "@vworlds/vecs";
14
+
15
+ class EquippedBy extends Relationship {}
16
+
17
+ world.component(EquippedBy);
18
+
19
+ item.set(EquippedBy, { target: player });
20
+ item.parent(EquippedBy); // player
21
+ player.children(EquippedBy).has(item); // true
22
+ ```
23
+
24
+ The **source** entity stores the relationship component; the **target** can enumerate its sources
25
+ with `children(ref)` through a reverse index the world maintains automatically. An entity holds at
26
+ most one instance of a given relationship class (like any component), so multi-target links are
27
+ modeled with one relationship class per meaning, or child entities.
28
+
29
+ Because `target` is an `Entity` reference, **relationships are not wire-serializable — don't try
30
+ to network them**. Replicate a plain id or tag instead, or keep the relationship server-local.
31
+
32
+ ## Built-in relationships
33
+
34
+ | Class | Purpose |
35
+ | --------------- | -------------------------------------------------------------------------------------------------------------------------- |
36
+ | `ChildOf` | General parent/child hierarchy. `onDeleteTarget = Delete`: destroying a parent destroys its `ChildOf` children. |
37
+ | `DependsOn` | Pipeline ordering: phases chain to each other and systems attach to phases through it. |
38
+ | `SilenceSource` | Marks a query's silence domain; configured via [`ignoreSource`](./queries-and-filters.md#silencing-feedback-ignoresource). |
39
+
40
+ `entity.childOf(parent)` is shorthand for `entity.set(ChildOf, { target: parent })`.
41
+
42
+ ## Retargeting
43
+
44
+ Point a relationship somewhere else with a plain `set`:
45
+
46
+ ```ts
47
+ unit.set(InCell, { target: newCell }); // reverse index updates automatically
48
+ ```
49
+
50
+ Retargeting is a **data change, not an archetype change** — no component is added or removed, so
51
+ no `enter`/`exit` churn. Prefer retargeting a relationship over swapping marker components to
52
+ express "which bucket this entity is in", and pair it with
53
+ [`groupBy(Relationship)`](./queries-and-filters.md#grouped-queries-groupby) for O(1) bucket
54
+ lookups.
55
+
56
+ `getMut` throws for relationship components: in-place target mutation would bypass the reverse
57
+ index used by `children()` and the cleanup cascades. Always retarget with `set`.
58
+
59
+ ## Cleanup when a target dies
60
+
61
+ What happens to sources when their target entity is destroyed is governed by the relationship's
62
+ `meta.onDeleteTarget`:
63
+
64
+ | Policy | Meaning |
65
+ | ---------------------- | ----------------------------------------------------------------------------------------- |
66
+ | `CleanupPolicy.Remove` | Default. The relationship component is removed from each source. |
67
+ | `CleanupPolicy.Delete` | Each source entity targeting the destroyed entity through this relationship is destroyed. |
68
+
69
+ ```ts
70
+ import { CleanupPolicy } from "@vworlds/vecs";
71
+
72
+ world.component(MyRel).meta.onDeleteTarget = CleanupPolicy.Delete; // cascade like ChildOf
73
+ ```
74
+
75
+ `ChildOf` ships with `Delete`, so destroying a parent recursively destroys its subtree:
76
+
77
+ ```ts
78
+ child.set(ChildOf, { target: parent });
79
+ parent.destroy(); // child (and its own ChildOf children) are destroyed too
80
+ ```
81
+
82
+ `onDeleteTarget` is independent from `onDelete`: `onDeleteTarget` runs when a relationship
83
+ **target** is destroyed; `onDelete` runs when the relationship **component entity itself** is
84
+ destroyed (see
85
+ [Components — cleanup policies](./components.md#cleanup-policies-destroying-a-component-entity)).
86
+
87
+ ## Querying through relationships
88
+
89
+ The DSL follows relationships in both directions
90
+ ([operator table](./queries-and-filters.md#the-query-dsl)):
91
+
92
+ ```ts
93
+ // Reactive: candidates whose FriendOf target has Position and Velocity.
94
+ world.query("body-friends").with({ all: [Body, { target: [FriendOf, [Position, Velocity]] }] });
95
+
96
+ // Reactive: candidates whose ChildOf parent has Body.
97
+ world.query("positioned-children").with({ all: [Position, { parent: Body }] });
98
+
99
+ // Filter-only: parents with at least one ChildOf child that has Position.
100
+ world.filter({ all: [Body, { children: Position }] }).forEach((parent) => { ... });
101
+ ```
102
+
103
+ `target` / `parent` queries are **live on both sides**: adding or removing the relationship on the
104
+ candidate re-routes it normally, and changing the _target's_ components refreshes every candidate
105
+ pointing at it — across multiple nested `target` hops. `source` / `children` look down the reverse
106
+ index and are **non-reactive**: exact for each `Filter` scan but rejected by tracked queries and
107
+ systems.
108
+
109
+ To pull a target's or children's components into callbacks, use `target` / `down` injection — see
110
+ [Systems — relationship injection](./systems.md#relationship-injection-target-and-down).
111
+
112
+ ## Traversal order: `Traversable` and `cascade`
113
+
114
+ Tag a relationship component entity with `Traversable` to enable automatic depth tracking: every
115
+ entity carrying the relationship gets a depth (hops to its root), maintained reactively by
116
+ `RelationshipsModule` (`lib/vecs/src/modules/relationships.ts`):
117
+
118
+ ```ts
119
+ import { Traversable } from "@vworlds/vecs";
120
+
121
+ world.component(InCell).add(Traversable);
122
+ ```
123
+
124
+ A query can then order its members depth-first along the relationship with `cascade`:
125
+
126
+ ```ts
127
+ world.query("tree").with({ with: Node, cascade: ChildOf });
128
+ ```
129
+
130
+ Parents sort before children. Retargeting updates relationship depths, and cascade-ordered queries
131
+ refresh their order before the next iteration. `ChildOf` and `DependsOn` are `Traversable` out of
132
+ the box — the world's default pipeline is itself a `cascade: DependsOn` query (see
133
+ [Execution model](./execution-model.md#the-pipeline-is-a-query)).
134
+
135
+ ## Reference
136
+
137
+ | API | Description |
138
+ | --------------------------------------------------- | -------------------------------------------------------------------------------------- |
139
+ | `class R extends Relationship {}` | Declare a relationship component with a `target: Entity` field. |
140
+ | `e.set(R, { target })` | Create or retarget the relationship. |
141
+ | `e.parent(R)` | The target entity, or `undefined`. |
142
+ | `e.children(R)` | `ReadonlySet<Entity>` of sources targeting `e` through `R`. |
143
+ | `e.childOf(parent)` | Shorthand for `e.set(ChildOf, { target: parent })`. |
144
+ | `meta.onDeleteTarget` | `Remove` (default) or `Delete` cascade when a target is destroyed. |
145
+ | `world.component(R).add(Traversable)` | Enable depth tracking for `cascade` ordering. |
146
+ | `{ target: [R, q] }` / `{ parent: q }` | Reactive DSL operators through the relationship. |
147
+ | `{ source: [R, q] }` / `{ children: q }` | Filter-only DSL operators down the reverse index. |
148
+ | `{ target: [R, [C...]] }` / `{ down: [R, [C...]] }` | Injection markers; see [Systems](./systems.md#relationship-injection-target-and-down). |