@vworlds/vecs 1.0.25 → 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 -4
  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 -4
@@ -0,0 +1,175 @@
1
+ # Entities
2
+
3
+ Create, look up, mutate, and destroy entities, and understand how the entity API behaves under
4
+ deferred mutation.
5
+
6
+ ## What an entity is
7
+
8
+ An `Entity` (`lib/vecs/src/entity/entity.ts`) is a unique numeric id (`eid`) owned by a `World`,
9
+ with an arbitrary set of component instances attached. Never instantiate `Entity` directly —
10
+ create entities through the world.
11
+
12
+ ## Creating and looking up entities
13
+
14
+ ```ts
15
+ // New entity with an auto-assigned id (from the "entity" pool, 1000+ by default):
16
+ const e = world.entity();
17
+
18
+ // Look up by id (throws if not found):
19
+ const found = world.entity(42);
20
+
21
+ // Optional lookup — returns undefined instead of throwing:
22
+ const maybe = world.getEntity(42);
23
+
24
+ // Externally assigned id (e.g. from a server); creates the entity if absent:
25
+ const net = world.getOrCreateEntity(serverId, (newEntity) => {
26
+ tracked.add(newEntity);
27
+ });
28
+
29
+ // Named entities, via the built-in Identity component:
30
+ world.entity("player"); // looks up by name, creating the entity if absent
31
+ world.entity("player").name === "player"; // true
32
+ world.getEntity("player"); // lookup only — undefined when absent
33
+ ```
34
+
35
+ `world.entity(ref)` also accepts a registered component class (returning its component entity) or
36
+ an `Entity` (validating it belongs to this world). Names are maintained by `IdentityModule`:
37
+ setting `entity.name` writes the `Identity` component, and duplicate names are last-writer-wins.
38
+
39
+ `world.entities` is a read-only view of every live entity keyed by id, and `world.clearAllEntities()`
40
+ destroys **every** entity in the world — ordinary entities first, then component entities once
41
+ their carriers are gone. It is a full wipe (systems and phases are backed by entities too); use it
42
+ for end-of-session teardown, not selective resets.
43
+
44
+ ## The component API
45
+
46
+ ```ts
47
+ e.add(Position); // attach with default field values (idempotent)
48
+ e.set(Position, { x: 100 }); // attach if needed, assign props, fire onSet/update
49
+ e.get(Position); // Readonly<Position> | undefined
50
+ e.has(Position); // boolean
51
+ e.remove(Position); // detach; fires exit callbacks, then onRemove
52
+ e.modified(Position); // mark changed after in-place mutation
53
+ e.destroy(); // remove everything and unregister the entity
54
+ ```
55
+
56
+ All mutators return the entity for chaining (`remove` and `destroy` return `void`). Component
57
+ references can be a registered class, a numeric component id, or an entity used as a component
58
+ key.
59
+
60
+ ### `set` vs `add` vs `modified`
61
+
62
+ - `add(C)` attaches with defaults and fires `onAdd` only. It is idempotent and does **not** fire
63
+ `onSet`.
64
+ - `set(C, props)` attaches if needed (firing `onAdd`), copies `props` onto the instance, and fires
65
+ `onSet` plus `update` callbacks.
66
+ - `modified(C)` fires `onSet`/`update` for data you already mutated in place. Repeated calls are
67
+ coalesced until the world routes the notification.
68
+
69
+ ### `getMut`: mutating in place
70
+
71
+ `get` returns a `Readonly` view. To mutate, use `getMut`, which marks the component modified for
72
+ you:
73
+
74
+ ```ts
75
+ // Inside a system / forEach / defer block (deferred mode) — returns the mutable instance:
76
+ const vel = e.getMut(Velocity)!;
77
+ vel.vx += 1;
78
+
79
+ // Outside deferred mode you must use the callback form, so the modified
80
+ // notification fires after your mutation:
81
+ e.getMut(Velocity, (vel) => {
82
+ vel.vx += 1;
83
+ });
84
+ ```
85
+
86
+ Calling `getMut(C)` without a callback outside deferred mode throws. `getMut` also throws for
87
+ relationship components (retarget with `set` instead — see
88
+ [Relationships](./relationships.md#retargeting)) and for `Implements` interface aliases.
89
+
90
+ ### `attach`: storing an existing instance
91
+
92
+ `attach` stores the exact object you pass, replacing any previous instance of that component
93
+ class, and fires events as a `set` would:
94
+
95
+ ```ts
96
+ const shared = new Position();
97
+ e.attach(shared);
98
+ e.get(Position) === shared; // true
99
+
100
+ // Store under a base class key instead of the instance's own constructor:
101
+ e.attach(BaseShape, concreteShape);
102
+ ```
103
+
104
+ The resolved class must already be registered in the entity's world. Use `attach` when component
105
+ ownership is intentionally shared with caller code or another object graph; code receiving the
106
+ component through vecs callbacks should use the entity vecs passes, not assume a 1:1 mapping.
107
+
108
+ ### Deferred semantics
109
+
110
+ Inside a system body, a `forEach`, or a `world.defer` block, all of the mutators above enqueue
111
+ commands instead of applying immediately, and reads return the pre-mutation state until the queue
112
+ drains. The exact read-after-write rules are tabulated in
113
+ [Execution model — deferred mutation](./execution-model.md#deferred-mutation-and-flush-boundaries).
114
+
115
+ Deferred entity creation is idempotent within one deferred scope: if `world.entity()` or
116
+ `getOrCreateEntity(eid)` creates a new entity, `getEntity(eid)`, `entity(eid)`, and later
117
+ `getOrCreateEntity(eid)` calls before the drain return that same pending entity. Collection-style
118
+ reads such as `world.entities` and queries still do not see the entity until the queued creation
119
+ drains.
120
+
121
+ ## Destroying entities
122
+
123
+ `e.destroy()` removes every component (firing each `onRemove` hook and query `exit`), emits the
124
+ entity's `"destroy"` event just before teardown, unregisters the entity from the world, and
125
+ applies incoming relationship cleanup policies (see
126
+ [Relationships](./relationships.md#cleanup-when-a-target-dies)). After destruction the entity must
127
+ not be used.
128
+
129
+ If the entity is itself in use as a component key, `destroy` honors `meta.onDelete` — by default
130
+ it throws while carriers remain ([Components — cleanup policies](./components.md#cleanup-policies-destroying-a-component-entity)).
131
+
132
+ ## Entities as component keys
133
+
134
+ Any entity can serve as a component key: `other.add(keyEntity)` attaches a marker component whose
135
+ 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.
138
+
139
+ ## Relationships, in brief
140
+
141
+ `e.parent(Rel)` returns the target of a relationship component, `e.children(Rel)` the set of
142
+ entities targeting `e` through it, and `e.childOf(parent)` is shorthand for
143
+ `e.set(ChildOf, { target: parent })`. The full model — retargeting, cleanup cascades, traversal —
144
+ is in [Relationships](./relationships.md).
145
+
146
+ ## Reference
147
+
148
+ `lib/vecs/src/entity/` (split across `entity.base.ts`, `entity.components.ts`,
149
+ `entity.lifecycle.ts`, `entity.relationships.ts`):
150
+
151
+ | Property / Method | Description |
152
+ | ---------------------------------------------- | --------------------------------------------------------------------------------------------------- |
153
+ | `eid` | Unique numeric entity id. |
154
+ | `world` | The `World` that owns this entity. |
155
+ | `name` | Identity name (getter/setter backed by the `Identity` component); `undefined` when unnamed. |
156
+ | `componentBitmask` | `Bitset` of component type ids attached. Used for archetype matching. |
157
+ | `components` | Read-only `ArrayMap` view of attached instances keyed by type id (`forEach`, `get`, `has`, `size`). |
158
+ | `empty` | `true` when no components are attached. |
159
+ | `properties` | Free-form `Map<string, any>` for module-level bookkeeping. |
160
+ | `meta` | `ComponentMeta` for using this entity as a component key (created lazily). |
161
+ | `instanceCount()` | Number of entities currently carrying this entity's eid as a component. |
162
+ | `add(ref)` | Attach a component with default values (idempotent). Returns `this`. |
163
+ | `attach(instance)` / `attach(Class, instance)` | Store an existing instance, replacing any previous one; fires set-side events. Returns `this`. |
164
+ | `set(ref, props)` | Attach if needed and assign `props`; fires `onSet` and `update` callbacks. Returns `this`. |
165
+ | `get(ref)` | Read-only component lookup, or `undefined`. |
166
+ | `getMut(ref)` / `getMut(ref, callback)` | Mutable lookup that marks the component modified. Callback form required outside deferred mode. |
167
+ | `has(ref)` | `true` when the component is attached. |
168
+ | `modified(ref)` | Queue an `onSet`/`update` notification; coalesced while pending. Returns `this`. |
169
+ | `remove(ref)` | Detach a component; routes `exit`, then fires `onRemove`. |
170
+ | `destroy()` | Remove all components, unregister, and apply incoming relationship cleanup. |
171
+ | `parent(rel)` | Target entity of relationship `rel`, or `undefined`. |
172
+ | `children(rel)` | `ReadonlySet<Entity>` of entities targeting this one through `rel`. |
173
+ | `childOf(parent)` | Shorthand for `set(ChildOf, { target: parent })`. Returns `this`. |
174
+ | `onAdd/onSet/onRemove(handler)` | Register hooks for this entity used as a component key. Return `this`. |
175
+ | `toString()` | Returns `"EntityN"`. |
@@ -0,0 +1,173 @@
1
+ # Execution Model
2
+
3
+ How `world.progress` runs your systems: the phase spine, ordering guarantees, deferred mutation,
4
+ and reactive event routing. Every design trick in vecs follows from the rules on this page.
5
+
6
+ ## One tick, anatomically
7
+
8
+ `world.progress(now, delta)` (`lib/vecs/src/world/world.pipeline.ts`) does, in order:
9
+
10
+ 1. **`beginFrame(delta)`** — flush any pending top-level commands, rebuild the flat pipeline if
11
+ the pipeline query changed (this is when newly created systems are inserted), and evaluate
12
+ every registered tick source once (see [Systems — cadence](./systems.md#cadence-interval-rate-and-tick-sources)).
13
+ 2. **For each system in pipeline order:** run the system, then `flush()` the world's command
14
+ queue.
15
+ 3. **`endFrame()`** — close the frame.
16
+
17
+ `now` and `delta` are both milliseconds. You can also call `beginFrame` / `endFrame` yourself and
18
+ run systems manually, but `progress` is the normal driver.
19
+
20
+ ## Phases: the pipeline spine
21
+
22
+ Systems are grouped by **phase**, and phases run in a fixed order. The eight built-in phases are
23
+ plain entities, created by `PipelineModule` and chained with the `DependsOn` relationship
24
+ (`lib/vecs/src/modules/pipeline.ts`). The exported constants are their entity names:
25
+
26
+ | Constant | Entity name | Use it for |
27
+ | ------------- | ------------------ | ----------------------------------------------------------------------------------- |
28
+ | `ON_LOAD` | `vecs::OnLoad` | Load data _into_ the ECS: inputs, network snapshots, external reads. |
29
+ | `POST_LOAD` | `vecs::PostLoad` | Process raw loaded data: turn key presses into high-level actions. |
30
+ | `PRE_UPDATE` | `vecs::PreUpdate` | Final prep before game logic: clean up last frame, prepare state gameplay will use. |
31
+ | `ON_UPDATE` | `vecs::OnUpdate` | **The default phase.** Main gameplay/simulation logic. |
32
+ | `ON_VALIDATE` | `vecs::OnValidate` | Validate state after updates: collision detection, constraint checks. |
33
+ | `POST_UPDATE` | `vecs::PostUpdate` | Apply corrections from validation: resolve collisions, fix up state. |
34
+ | `PRE_STORE` | `vecs::PreStore` | Prepare data for output: compute render state once all logic is done. |
35
+ | `ON_STORE` | `vecs::OnStore` | Store/emit the final frame: rendering, writing output, sending snapshots. |
36
+
37
+ `BUILTIN_PIPELINE_PHASES` exports the eight names in order. Map your work onto this taxonomy
38
+ rather than inventing phases: input belongs in `ON_LOAD`/`POST_LOAD`, gameplay in `ON_UPDATE`,
39
+ validation and correction in `ON_VALIDATE`/`POST_UPDATE`, output in `PRE_STORE`/`ON_STORE`.
40
+
41
+ ### Assigning a system to a phase
42
+
43
+ Every system defaults to `ON_UPDATE`. Call `.phase(...)` only to run elsewhere:
44
+
45
+ ```ts
46
+ import { ON_STORE } from "@vworlds/vecs";
47
+
48
+ world.system("Render").phase(ON_STORE).with(Sprite, Position).each([Sprite, Position], draw);
49
+ ```
50
+
51
+ `.phase(anchor)` accepts a phase name or an entity. It sets `DependsOn` on the system's backing
52
+ entity (replacing any previous target) and tags the anchor with `Phase`, creating the anchor
53
+ entity if the name is new. **A system lives in exactly one phase.** If a concern needs work in two
54
+ phases, split it into two systems.
55
+
56
+ ### Ordering guarantees
57
+
58
+ 1. **Across phases, order is guaranteed by the phase spine.** A system in `PRE_STORE` always runs
59
+ before any system in `ON_STORE`, regardless of when either was created.
60
+ 2. **Within a phase, systems run in creation order** (entity-id order — the order `world.system`
61
+ was called). An umbrella module controls this by the order it loads sub-modules.
62
+ 3. **Never rely on registration order for cross-module correctness.** When two independent
63
+ features must order against each other, express it with phases.
64
+
65
+ ### The pipeline is a query
66
+
67
+ The pipeline itself is an ordered query over system entities. The default, installed by
68
+ `PipelineModule`:
69
+
70
+ ```ts
71
+ world.setPipeline({
72
+ with: { all: [System, { target: [DependsOn, Phase] }] },
73
+ cascade: DependsOn,
74
+ });
75
+ ```
76
+
77
+ It matches every `System` whose backing entity `DependsOn` a `Phase`, ordered depth-first along
78
+ the `DependsOn` cascade. `world.setPipeline(spec)` replaces it; the world always appends `System`
79
+ to the spec so the result flattens to systems.
80
+
81
+ ### Custom phases: `insertPhaseAfter`
82
+
83
+ `insertPhaseAfter(phase, anchor)` splices an existing `Phase` entity into the spine directly after
84
+ `anchor`, retargeting the anchor's downstream phases onto the new one:
85
+
86
+ ```ts
87
+ import { insertPhaseAfter, ON_UPDATE, Phase } from "@vworlds/vecs";
88
+
89
+ const physicsStep = world.entity("physics-step").add(Phase);
90
+ insertPhaseAfter(physicsStep, world.entity(ON_UPDATE));
91
+ ```
92
+
93
+ Reserve custom phases for engine-level modules that genuinely need their own spine (the physics
94
+ engine inserts its step phases after `ON_UPDATE`). Application and render systems should use the
95
+ built-in taxonomy.
96
+
97
+ ## Deferred mutation and flush boundaries
98
+
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.
103
+
104
+ For the pipeline this means: **a structural change made by system A is applied in the flush
105
+ immediately after A finishes, so it is visible to every later system in the same tick** — later in
106
+ the same phase or in a later phase — but not to A itself mid-iteration. This single fact enables
107
+ teardown-before-create ordering, the companion-component lifecycle, and same-tick reconciliation
108
+ (see the [Design guide](./design-guide.md)).
109
+
110
+ ### What reads see while deferred
111
+
112
+ While a mutation is queued but not yet applied, reads return the pre-mutation state:
113
+
114
+ | After calling… | `entity.get(C)` / `entity.getMut(C)` returns… |
115
+ | ------------------------- | ---------------------------------------------------------- |
116
+ | `entity.add(C)` | `undefined` — no instance has been created yet. |
117
+ | `entity.attach(instance)` | `undefined`, if `C` was absent before. |
118
+ | `entity.set(C, props)` | The **previous** value (or `undefined` if `C` was absent). |
119
+ | `entity.remove(C)` | Still the component instance. |
120
+
121
+ Outside any deferred scope (top-level user code), the same calls execute inline and effects are
122
+ visible immediately.
123
+
124
+ ### Controlling deferral yourself
125
+
126
+ ```ts
127
+ world.deferred; // true while the world is in deferred mode
128
+ world.defer(() => { ... }); // run a block in deferred mode, drain on exit
129
+ world.beginDefer(); // open a scope manually…
130
+ world.endDefer(); // …close it (drains at the outermost scope)
131
+ world.flush(); // drain queued top-level commands now
132
+ ```
133
+
134
+ Scopes nest: a `Filter.forEach` inside a system inherits the system's scope and does not drain on
135
+ exit. `flush()` is a no-op while any scope is open; it also builds queries created since the last
136
+ flush.
137
+
138
+ ## Reactive routing
139
+
140
+ When the world applies a command, it routes events to everything that watches the affected
141
+ component:
142
+
143
+ - **Hooks** (`onAdd` / `onSet` / `onRemove`) fire synchronously at the moment the command is
144
+ applied — inline outside deferred mode, or during the drain.
145
+ - **Standalone `Query` callbacks** (`enter` / `update` / `exit`) also fire synchronously during
146
+ routing.
147
+ - **Systems** queue events into an ordered **inbox** instead, and replay the inbox at the top of
148
+ their next run (`lib/vecs/src/system.ts`). So systems react to change without scanning, and they
149
+ react _in their own phase slot_, in event arrival order.
150
+
151
+ Three routing rules worth memorizing:
152
+
153
+ - **`update(C)` fires on entry.** When an entity enters a system, the system pushes an `update`
154
+ event for every watched component already present. A single `.update(C, ...)` therefore
155
+ initializes on entry _and_ reacts to changes — don't write a duplicate `enter`.
156
+ - **`exit` reads a snapshot.** Components injected into an `exit` callback are captured when the
157
+ exit is routed, so they are still resolvable even though they are being (or have been) removed.
158
+ - **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.
160
+
161
+ ## Why the patterns work
162
+
163
+ Putting the rules together:
164
+
165
+ - _Teardown before create:_ an `exit`-driven teardown system in `PRE_STORE` runs before an
166
+ `enter`-driven creation system in `ON_STORE`; the teardown's `remove` is flushed between them,
167
+ so a type swap tears down the old companion and builds the new one in the same tick.
168
+ - _React, don't scan:_ `update(C)` touches nothing on idle entities, where `each` would visit all
169
+ of them every tick.
170
+ - _Same-tick reconciliation:_ a system that writes a component early in the tick is guaranteed
171
+ that later phases see the new value.
172
+
173
+ The [Design guide](./design-guide.md) develops each of these into a full pattern.
@@ -0,0 +1,215 @@
1
+ # Getting Started
2
+
3
+ Build a small simulation from scratch: create a world, register components, write two systems, and
4
+ drive the loop with `world.progress`.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install @vworlds/vecs
10
+ ```
11
+
12
+ The package ships TypeScript types; no extra tooling is required.
13
+
14
+ ## 1. Create a world
15
+
16
+ A `World` owns every entity, registered component, query, system, and the update pipeline. Create
17
+ one per game session:
18
+
19
+ ```ts
20
+ import { World } from "@vworlds/vecs";
21
+
22
+ const world = new World();
23
+ ```
24
+
25
+ The constructor installs the built-in modules — identity names, interface aliases, singletons,
26
+ relationships, and the eight-phase pipeline — so the world is ready to use immediately (see
27
+ [Modules](./modules.md)).
28
+
29
+ ## 2. Define and register components
30
+
31
+ Components are plain data classes: field initializers, a no-argument constructor, and no behavior.
32
+ Register each class with `world.component` before using it:
33
+
34
+ ```ts
35
+ class Position {
36
+ x = 0;
37
+ y = 0;
38
+ }
39
+
40
+ class Velocity {
41
+ vx = 0;
42
+ vy = 0;
43
+ }
44
+
45
+ class Health {
46
+ hp = 100;
47
+ }
48
+
49
+ world.component(Position);
50
+ world.component(Velocity);
51
+ world.component(Health);
52
+ ```
53
+
54
+ There is no automatic registration — using an unregistered class as a component throws. See
55
+ [Components](./components.md) for ids, hooks, and policies.
56
+
57
+ ## 3. Add a system
58
+
59
+ Systems declare which entities they care about with `.with(...)` and attach behavior. `each` runs
60
+ once per matched entity on every tick:
61
+
62
+ ```ts
63
+ world
64
+ .system("Move")
65
+ .with(Position, Velocity)
66
+ .each([Position, Velocity], (e, [pos, vel]) => {
67
+ pos.x += vel.vx;
68
+ pos.y += vel.vy;
69
+ e.modified(Position); // notify watchers that Position changed
70
+ });
71
+ ```
72
+
73
+ Two details to internalize early:
74
+
75
+ - The components listed in `.with` are guaranteed present, so `pos` and `vel` are non-nullable in
76
+ the callback tuple.
77
+ - Mutating fields directly does not notify anyone. Call `e.modified(Position)` so hooks, `update`
78
+ callbacks, and reactive queries see the change. See [Systems](./systems.md).
79
+
80
+ ## 4. Add a reactive system in a later phase
81
+
82
+ Systems run in the world's phase pipeline and default to the `vecs::OnUpdate` phase. Place
83
+ follow-up work in a later phase — here, despawning dead entities during validation:
84
+
85
+ ```ts
86
+ import { ON_VALIDATE } from "@vworlds/vecs";
87
+
88
+ world
89
+ .system("Reap")
90
+ .phase(ON_VALIDATE)
91
+ .with(Health)
92
+ .update(Health, (entity, health) => {
93
+ if (health.hp <= 0) {
94
+ entity.destroy();
95
+ }
96
+ });
97
+ ```
98
+
99
+ `update(Health, ...)` fires only when `Health` is set or marked modified on a tracked entity — and
100
+ once on entry for each watched component already present — so this system does no work for idle
101
+ entities. The phase taxonomy and ordering rules are in
102
+ [Execution model](./execution-model.md).
103
+
104
+ ## 5. Observe lifecycle with hooks
105
+
106
+ For a per-component callback that is not worth a whole system, register hooks on the component
107
+ entity:
108
+
109
+ ```ts
110
+ world
111
+ .component(Health)
112
+ .onAdd((entity, h) => console.log(`entity ${entity.eid} spawned with hp=${h.hp}`))
113
+ .onRemove((entity) => console.log(`entity ${entity.eid} died`));
114
+ ```
115
+
116
+ `onAdd` fires when the component is first attached, `onRemove` when it is detached or its entity is
117
+ destroyed, and `onSet` whenever its data is set or marked modified.
118
+
119
+ ## 6. Spawn entities
120
+
121
+ `world.entity()` creates an entity; `set` attaches a component and assigns data in one call.
122
+ Calls chain:
123
+
124
+ ```ts
125
+ world.entity().set(Position, { x: 0, y: 0 }).set(Velocity, { vx: 5, vy: 0 }).set(Health, { hp: 3 });
126
+ ```
127
+
128
+ ## 7. Drive the loop
129
+
130
+ `world.progress(now, delta)` runs one tick: every system in phase order, with structural changes
131
+ flushed after each system. Both arguments are milliseconds — `now` is your absolute clock, `delta`
132
+ the time since the previous tick:
133
+
134
+ ```ts
135
+ let now = 0;
136
+ for (let tick = 0; tick < 5; tick++) {
137
+ now += 16;
138
+ world.progress(now, 16);
139
+ }
140
+ ```
141
+
142
+ In a real game, call `world.progress` from your frame loop (`requestAnimationFrame`, a fixed-step
143
+ server loop, etc.).
144
+
145
+ ## Complete program
146
+
147
+ ```ts
148
+ import { ON_VALIDATE, World } from "@vworlds/vecs";
149
+
150
+ class Position {
151
+ x = 0;
152
+ y = 0;
153
+ }
154
+
155
+ class Velocity {
156
+ vx = 0;
157
+ vy = 0;
158
+ }
159
+
160
+ class Health {
161
+ hp = 100;
162
+ }
163
+
164
+ const world = new World();
165
+
166
+ world.component(Position);
167
+ world.component(Velocity);
168
+ world
169
+ .component(Health)
170
+ .onAdd((entity, h) => console.log(`entity ${entity.eid} spawned with hp=${h.hp}`))
171
+ .onRemove((entity) => console.log(`entity ${entity.eid} died`));
172
+
173
+ world
174
+ .system("Move")
175
+ .with(Position, Velocity)
176
+ .each([Position, Velocity], (e, [pos, vel]) => {
177
+ pos.x += vel.vx;
178
+ pos.y += vel.vy;
179
+ e.modified(Position);
180
+ });
181
+
182
+ world
183
+ .system("Drain")
184
+ .with(Health)
185
+ .each([Health], (e, [health]) => {
186
+ health.hp -= 1;
187
+ e.modified(Health);
188
+ });
189
+
190
+ world
191
+ .system("Reap")
192
+ .phase(ON_VALIDATE)
193
+ .with(Health)
194
+ .update(Health, (entity, health) => {
195
+ if (health.hp <= 0) {
196
+ entity.destroy();
197
+ }
198
+ });
199
+
200
+ world.entity().set(Position, { x: 0, y: 0 }).set(Velocity, { vx: 5, vy: 0 }).set(Health, { hp: 3 });
201
+
202
+ let now = 0;
203
+ for (let tick = 0; tick < 5; tick++) {
204
+ now += 16;
205
+ world.progress(now, 16);
206
+ }
207
+ ```
208
+
209
+ Run it and watch the entity spawn, drain, and die after three ticks.
210
+
211
+ ## Where to go next
212
+
213
+ - [Concepts](./concepts.md) — the mental model behind what you just wrote.
214
+ - [Execution model](./execution-model.md) — why the phase placement and `modified` calls work.
215
+ - [Design guide](./design-guide.md) — the idioms to reach for as your design grows.
@@ -0,0 +1,113 @@
1
+ # Glossary
2
+
3
+ ECS and vecs terminology, in alphabetical order. Each entry links to the guide that develops it.
4
+
5
+ **Archetype** — the set of component type ids attached to an entity, represented as the entity's
6
+ `componentBitmask` (a [`Bitset`](./utilities.md#bitset)). Query matching tests
7
+ archetypes with fast bitwise subset checks.
8
+
9
+ **Backfill** — when a tracked query or system is built, entities that already match are routed
10
+ through `enter` so the tracked set starts complete.
11
+ [Queries and filters](./queries-and-filters.md#lifecycle-build-backfill-destroy).
12
+
13
+ **Bitset** — a compact, growable set of non-negative integers backed by 32-bit words; used for
14
+ archetypes and watchlists, and exported for bit-flag component fields.
15
+ [Utilities](./utilities.md#bitset).
16
+
17
+ **Cascade (query)** — depth-first ordering of a query's members along a `Traversable`
18
+ relationship, via `{ with: ..., cascade: Rel }`.
19
+ [Relationships](./relationships.md#traversal-order-traversable-and-cascade).
20
+
21
+ **Cleanup policy** — what happens to dependent entities on destruction: `onDelete` (component
22
+ entity destroyed → carriers throw/strip/die) and `onDeleteTarget` (relationship target destroyed →
23
+ sources strip/die). [Components](./components.md#cleanup-policies-destroying-a-component-entity),
24
+ [Relationships](./relationships.md#cleanup-when-a-target-dies).
25
+
26
+ **Companion (wrapper) component** — a local, non-networked component holding a non-ECS resource
27
+ (render object, WASM handle) so the entity's component set drives the resource lifecycle.
28
+ [Components](./components.md#companion-components-for-non-ecs-resources).
29
+
30
+ **Component** — a registered plain data class with a no-argument constructor; pure data, no
31
+ behavior. [Components](./components.md).
32
+
33
+ **Component entity** — the entity backing a registered component class; returned by
34
+ `world.component(C)` and carrying the class's hooks and `ComponentMeta`.
35
+ [Components](./components.md#registration-worldcomponent).
36
+
37
+ **Deferred mode / flush** — inside systems, `forEach`, and `defer` blocks, entity mutations are
38
+ queued as commands; `flush` drains the queue at the scope boundary (after each system in the
39
+ pipeline). [Execution model](./execution-model.md#deferred-mutation-and-flush-boundaries).
40
+
41
+ **Entity** — a unique numeric id (`eid`) owned by a world, with a set of components attached.
42
+ [Entities](./entities.md).
43
+
44
+ **Exclusive components** — a declared group where at most one member may exist on an entity;
45
+ setting one removes the others. [Components](./components.md#exclusive-component-groups).
46
+
47
+ **Events** — a minimal typed event emitter helper exported for package-level events. It is not an
48
+ entity lifecycle API. [Utilities](./utilities.md#events).
49
+
50
+ **Filter** — a non-reactive, one-shot predicate that scans world entities on each `forEach`.
51
+ [Queries and filters](./queries-and-filters.md#filters-one-shot-scans).
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).
55
+
56
+ **Implements / interface alias** — a declaration that a concrete component is queryable through
57
+ abstract/interface component types; aliases are read-only views of the same instance.
58
+ [Components](./components.md#interface-aliases-with-implements).
59
+
60
+ **Inbox** — a system's ordered queue of routed `enter` / `exit` / `update` events, replayed at the
61
+ top of the system's run. [Execution model](./execution-model.md#reactive-routing).
62
+
63
+ **Injection** — resolving component instances per entity into a typed callback tuple, including
64
+ `target` (relationship target's components) and `down` (fan-out over children) markers.
65
+ [Systems](./systems.md#component-injection).
66
+
67
+ **Iter** — an opt-in reusable cursor passed to callbacks instead of the bare entity, exposing the
68
+ visited entity and the source entity of each injected slot.
69
+ [Systems](./systems.md#the-iter-cursor).
70
+
71
+ **Module** — a packaging unit (`class M extends Module<Config>`) whose `init()` registers
72
+ components, systems, and policy once per world. [Modules](./modules.md).
73
+
74
+ **Phase** — an entity used as a pipeline ordering anchor (tagged with the `Phase` component);
75
+ systems attach to exactly one phase via `DependsOn`.
76
+ [Execution model](./execution-model.md#phases-the-pipeline-spine).
77
+
78
+ **Pipeline** — the flat, ordered list of systems `world.progress` runs each tick; defined by an
79
+ ordered query over system entities. [Execution model](./execution-model.md#the-pipeline-is-a-query).
80
+
81
+ **Query** — a reactive, always-up-to-date set of entities matching a DSL predicate, with
82
+ `enter` / `exit` / `update` callbacks. [Queries and filters](./queries-and-filters.md).
83
+
84
+ **Query DSL** — the composable predicate language (`all` / `any` / `not` / `only` / `target` /
85
+ `source` / `parent` / `children` / `test`).
86
+ [Queries and filters](./queries-and-filters.md#the-query-dsl).
87
+
88
+ **Relationship** — a component with a `target: Entity` field linking its source entity to a target,
89
+ with reverse lookup (`children`) and cleanup cascades. [Relationships](./relationships.md).
90
+
91
+ **Silence domain / `ignoreSource`** — a query configuration that drops `update` events originating
92
+ from systems matching another query, breaking write feedback loops.
93
+ [Queries and filters](./queries-and-filters.md#silencing-feedback-ignoresource).
94
+
95
+ **Singleton** — a component class constrained to at most one carrier entity; commonly stored on
96
+ its own component entity via `world.set` / `world.get`.
97
+ [Components](./components.md#singletons-and-world-global-data).
98
+
99
+ **System** — a query plus per-tick behavior (`run`, `each`) and inbox-replayed reactive callbacks,
100
+ assigned to one phase. [Systems](./systems.md).
101
+
102
+ **Tick source** — a clock (`IntervalTickSource`, `RateTickSource`, or another system) that gates
103
+ how often a system fires. [Systems](./systems.md#cadence-interval-rate-and-tick-sources).
104
+
105
+ **Watchlist** — the set of component types a query or system reacts to via `update`, stored as a
106
+ bitmask. [Systems](./systems.md#reactive-callbacks-enter-update-exit).
107
+
108
+ **Wire type** — a field-level encoding declaration (`@type` from `@vworlds/vecs-wire`, commonly
109
+ imported as `wireType`) used by the networking packages to serialize components. Networking is
110
+ documented with `@vworlds/vecs-wire` / `@vworlds/vecs-protocol`, not here.
111
+
112
+ **World** — the central container owning entities, component registrations, queries, systems, and
113
+ the pipeline; driven by `world.progress(now, delta)`. [Concepts](./concepts.md).