@vworlds/vecs 1.0.61 → 1.0.62

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.
@@ -0,0 +1,259 @@
1
+ import { assertMemberType, elementType, formatMemberType, isBigIntType, isNumericType, memberTypesEqual, } from "./member_types.js";
2
+ /**
3
+ * Core component holding a component class's member reflection metadata.
4
+ *
5
+ * `world.component(C)` attaches it to the **component entity** of `C` as soon
6
+ * as `C` or a base class declares a member, through decorators ({@link type},
7
+ * {@link range}, …) or the fluent {@link Component.member} API. Query it like
8
+ * any component:
9
+ *
10
+ * ```ts
11
+ * world.component(Position).get(VecsStruct)?.members.x; // { type: "i32", range: [-100, 100] }
12
+ * ```
13
+ *
14
+ * The definition is per class for the whole process: every world attaches the
15
+ * same frozen instance, so `getMut(VecsStruct, …)` on it throws. Members are
16
+ * inherited, whichever way the base class declared them, and are listed base
17
+ * class first, then in declaration order. A declaration that contradicts the
18
+ * definition throws, naming the class and the member.
19
+ */
20
+ export class VecsStruct {
21
+ constructor() {
22
+ /** Member metadata keyed by member name. */
23
+ this.members = {};
24
+ }
25
+ }
26
+ const definitions = new WeakMap();
27
+ /** Base class → definitions of its subclasses that declare members of their own. */
28
+ const descendants = new WeakMap();
29
+ /** Bumped by every change, so composed caches along any class chain stay correct. */
30
+ let version = 0;
31
+ /**
32
+ * @internal The class and its base classes, base class first.
33
+ */
34
+ export function _classChain(Class) {
35
+ const chain = [];
36
+ for (let current = Class; current !== null && current !== Function.prototype; current = Object.getPrototypeOf(current)) {
37
+ chain.push(current);
38
+ }
39
+ return chain.reverse();
40
+ }
41
+ /**
42
+ * @internal The composed members of `Class`, base class first, or `undefined`
43
+ * when neither it nor a base class declares any. Reads the live definition.
44
+ */
45
+ export function _structMembers(Class) {
46
+ const members = composed(Class);
47
+ return members.size === 0 ? undefined : members;
48
+ }
49
+ /**
50
+ * @internal The frozen `VecsStruct` instance of `Class`, or `undefined` when it
51
+ * has no members.
52
+ */
53
+ export function _structInstance(Class) {
54
+ if (_structMembers(Class) === undefined) {
55
+ return undefined;
56
+ }
57
+ return (definitions.get(Class) ?? createDefinition(Class)).instance;
58
+ }
59
+ /**
60
+ * @internal Merge member declarations into the definition of `Class`.
61
+ *
62
+ * @returns `true` when the definition changed.
63
+ * @throws When a declaration contradicts the definition of `Class`, of a base
64
+ * class, or of a subclass, or is malformed.
65
+ */
66
+ export function _declareMembers(Class, declarations) {
67
+ let changed = false;
68
+ for (const [name, declaration] of declarations) {
69
+ const current = composed(Class).get(name);
70
+ const merged = mergeMember(Class, name, current, declaration);
71
+ if (merged === current) {
72
+ continue;
73
+ }
74
+ for (const descendant of descendants.get(Class) ?? []) {
75
+ const own = descendant.own.get(name);
76
+ if (own !== undefined) {
77
+ mergeMember(descendant.Class, name, merged, own);
78
+ }
79
+ }
80
+ const definition = definitions.get(Class) ?? createDefinition(Class);
81
+ if (definition.own.size === 0) {
82
+ for (const base of _classChain(Class).slice(0, -1)) {
83
+ let set = descendants.get(base);
84
+ if (set === undefined) {
85
+ set = new Set();
86
+ descendants.set(base, set);
87
+ }
88
+ set.add(definition);
89
+ }
90
+ }
91
+ definition.own.set(name, merged);
92
+ version++;
93
+ changed = true;
94
+ }
95
+ return changed;
96
+ }
97
+ function composed(Class) {
98
+ const definition = definitions.get(Class);
99
+ if (definition?.cache?.version === version) {
100
+ return definition.cache.members;
101
+ }
102
+ const members = new Map();
103
+ for (const C of _classChain(Class)) {
104
+ definitions.get(C)?.own.forEach((member, name) => {
105
+ const inherited = members.get(name);
106
+ members.set(name, inherited === undefined ? member : combine(inherited, member));
107
+ });
108
+ }
109
+ if (definition !== undefined) {
110
+ definition.cache = { version, members, view: freezeMembers(members) };
111
+ }
112
+ return members;
113
+ }
114
+ /** Fill a subclass's member with fields a base class declared after it. */
115
+ function combine(inherited, member) {
116
+ const next = { ...member };
117
+ next.range ?? (next.range = inherited.range);
118
+ next.description ?? (next.description = inherited.description);
119
+ next.unit ?? (next.unit = inherited.unit);
120
+ next.values ?? (next.values = inherited.values);
121
+ return membersEqual(next, member) ? member : Object.freeze(next);
122
+ }
123
+ function createDefinition(Class) {
124
+ const instance = Object.create(VecsStruct.prototype);
125
+ const definition = { Class, own: new Map(), instance, cache: undefined };
126
+ definitions.set(Class, definition);
127
+ // One instance per class: worlds that attached it before a later declaration,
128
+ // here or on a base class, read the extended definition through it.
129
+ Object.defineProperty(instance, "members", {
130
+ enumerable: true,
131
+ get: () => {
132
+ composed(Class);
133
+ return definition.cache.view;
134
+ },
135
+ });
136
+ Object.freeze(instance);
137
+ return definition;
138
+ }
139
+ function freezeMembers(members) {
140
+ const view = {};
141
+ for (const [name, member] of members) {
142
+ view[name] = member;
143
+ }
144
+ return Object.freeze(view);
145
+ }
146
+ function mergeMember(Class, name, existing, declaration) {
147
+ const where = `VecsStruct ${Class.name || "anonymous class"}.${name}`;
148
+ const type = declaration.type ?? existing?.type;
149
+ if (type === undefined) {
150
+ throw new TypeError(`${where}: member metadata needs a type`);
151
+ }
152
+ assertMemberType(type, where);
153
+ const next = { ...existing, type };
154
+ if (existing !== undefined && !memberTypesEqual(existing.type, type)) {
155
+ throw contradiction(where, "type", formatMemberType(type), formatMemberType(existing.type));
156
+ }
157
+ if (declaration.range !== undefined) {
158
+ const range = normalizeRange(where, type, declaration.range);
159
+ if (existing?.range !== undefined) {
160
+ if (existing.range[0] !== range[0] || existing.range[1] !== range[1]) {
161
+ throw contradiction(where, "range", formatRange(range), formatRange(existing.range));
162
+ }
163
+ }
164
+ else {
165
+ next.range = range;
166
+ }
167
+ }
168
+ for (const field of ["description", "unit"]) {
169
+ const value = declaration[field];
170
+ if (value === undefined) {
171
+ continue;
172
+ }
173
+ if (typeof value !== "string") {
174
+ throw new TypeError(`${where}: ${field} must be a string`);
175
+ }
176
+ if (existing?.[field] !== undefined && existing[field] !== value) {
177
+ throw contradiction(where, field, JSON.stringify(value), JSON.stringify(existing[field]));
178
+ }
179
+ next[field] = value;
180
+ }
181
+ if (declaration.values !== undefined) {
182
+ const values = normalizeValues(where, type, declaration.values);
183
+ if (existing?.values !== undefined) {
184
+ if (!valuesEqual(existing.values, values)) {
185
+ throw contradiction(where, "values", formatValues(values), formatValues(existing.values));
186
+ }
187
+ }
188
+ else {
189
+ next.values = values;
190
+ }
191
+ }
192
+ if (existing !== undefined && membersEqual(existing, next)) {
193
+ return existing;
194
+ }
195
+ return Object.freeze(next);
196
+ }
197
+ function normalizeRange(where, type, range) {
198
+ const element = elementType(type);
199
+ if (!isNumericType(element)) {
200
+ throw new TypeError(`${where}: range needs a numeric type, not ${formatMemberType(type)}`);
201
+ }
202
+ if (!Array.isArray(range) || range.length !== 2) {
203
+ throw new TypeError(`${where}: range must be [min, max]`);
204
+ }
205
+ let [min, max] = range;
206
+ if (isBigIntType(element)) {
207
+ min = toBigInt(where, min);
208
+ max = toBigInt(where, max);
209
+ }
210
+ else if (typeof min !== "number" || typeof max !== "number" || isNaN(min) || isNaN(max)) {
211
+ throw new TypeError(`${where}: range bounds of a ${formatMemberType(type)} must be numbers`);
212
+ }
213
+ if (min > max) {
214
+ throw new RangeError(`${where}: range min ${min} is above max ${max}`);
215
+ }
216
+ return Object.freeze([min, max]);
217
+ }
218
+ function toBigInt(where, value) {
219
+ if (typeof value === "bigint") {
220
+ return value;
221
+ }
222
+ if (typeof value !== "number" || !Number.isInteger(value)) {
223
+ throw new TypeError(`${where}: range bounds of a 64-bit member must be integers`);
224
+ }
225
+ return BigInt(value);
226
+ }
227
+ function normalizeValues(where, type, values) {
228
+ if (elementType(type) !== "enum") {
229
+ throw new TypeError(`${where}: values needs an enum type, not ${formatMemberType(type)}`);
230
+ }
231
+ if (!Array.isArray(values) || !values.every((value) => Number.isInteger(value))) {
232
+ throw new TypeError(`${where}: enum values must be integers`);
233
+ }
234
+ return Object.freeze([...values]);
235
+ }
236
+ function membersEqual(left, right) {
237
+ return (memberTypesEqual(left.type, right.type) &&
238
+ left.range?.[0] === right.range?.[0] &&
239
+ left.range?.[1] === right.range?.[1] &&
240
+ left.description === right.description &&
241
+ left.unit === right.unit &&
242
+ (left.values === right.values ||
243
+ (left.values !== undefined &&
244
+ right.values !== undefined &&
245
+ valuesEqual(left.values, right.values))));
246
+ }
247
+ function valuesEqual(left, right) {
248
+ return left.length === right.length && left.every((value, i) => value === right[i]);
249
+ }
250
+ function contradiction(where, field, declared, current) {
251
+ return new Error(`${where}: ${field} ${declared} contradicts the existing ${field} ${current}`);
252
+ }
253
+ function formatRange(range) {
254
+ return `[${range[0]}, ${range[1]}]`;
255
+ }
256
+ function formatValues(values) {
257
+ return `[${values.join(", ")}]`;
258
+ }
259
+ //# sourceMappingURL=vecs_struct.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vecs_struct.js","sourceRoot":"","sources":["../../src/reflection/vecs_struct.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
- import { Component, type ComponentClass, type ComponentType } from "../component.js";
1
+ import { Component, type ComponentClass, type ComponentInstance, type ComponentType } from "../component.js";
2
2
  import { type EID } from "../entity/index.js";
3
+ import { type ValidationResult } from "../reflection/defaults.js";
3
4
  import { Entities } from "./world.entities.js";
4
5
  import { type World } from "./world.js";
5
6
  export declare class Components extends Entities {
@@ -15,6 +16,14 @@ export declare class Components extends Entities {
15
16
  * A newly registered definition inherits the current scope parent immediately.
16
17
  * Scopes must be entered outside deferred mode. Existing lookups do not reparent it.
17
18
  *
19
+ * On first registration the class's member decorators ({@link type},
20
+ * {@link range}, …) are read into its {@link VecsStruct} definition, which
21
+ * is attached to the component entity when the class declares any member.
22
+ * Static `vecs_<event>` methods (`vecs_add`, `vecs_set`, `vecs_remove`,
23
+ * `vecs_targetAdd`, `vecs_targetRemove`, `vecs_validate`, `vecs_normalize`)
24
+ * are registered as hooks for `<event>`, ahead of hooks added with `.on(…)`.
25
+ * A class without decorators or statics registers exactly as before.
26
+ *
18
27
  * Pass an explicit numeric `ceid` to pin the component to a server-assigned
19
28
  * stable id instead of auto-allocating one.
20
29
  *
@@ -61,6 +70,48 @@ export declare class Components extends Entities {
61
70
  * @returns The `Component` entity, for chaining.
62
71
  */
63
72
  set<C extends ComponentClass>(this: World, Class: C, props: Partial<InstanceType<C>>): Component<C>;
73
+ /**
74
+ * Check `component` with its class's `validate` hooks.
75
+ *
76
+ * Hooks run in registration order and the first failure is returned. When
77
+ * the class has no explicit hook but declares ranges or integer member
78
+ * types, a default hook is created on first use: it checks each such member
79
+ * against its range and its type's bounds (for example `u8` is 0–255), that
80
+ * integers are whole, and that an `enum` with `values` holds one of them. It
81
+ * reads the live definition, inherited members included. A class with
82
+ * neither ranges nor integer types is valid.
83
+ *
84
+ * Nothing calls this on `set`; call it where your code accepts external data.
85
+ *
86
+ * @param component - An instance of a class registered in this world.
87
+ * @returns `true`, or the violations: one message or a list of them.
88
+ * @throws When `component.constructor` is not registered in this world.
89
+ */
90
+ validate(this: World, component: ComponentInstance): ValidationResult;
91
+ /**
92
+ * Return whether {@link validate} accepts `component`.
93
+ *
94
+ * @throws When `component.constructor` is not registered in this world.
95
+ */
96
+ isValid(this: World, component: ComponentInstance): boolean;
97
+ /**
98
+ * Modify `component` in place with its class's `normalize` hooks, so it
99
+ * becomes valid.
100
+ *
101
+ * Hooks run in registration order, each on the result of the previous one.
102
+ * When the class has no explicit hook but declares ranges or integer member
103
+ * types, a default hook is created on first use: it truncates integers and
104
+ * clamps each such member to its range and its type's bounds. Values of the
105
+ * wrong JavaScript type and `enum` values outside their `values` are left as
106
+ * they are; `NaN` becomes `0` first.
107
+ *
108
+ * @param component - An instance of a class registered in this world.
109
+ * @returns `component`, for chaining.
110
+ * @throws When `component.constructor` is not registered in this world.
111
+ */
112
+ normalize<C extends ComponentInstance>(this: World, component: C): C;
64
113
  /** Declare a group of mutually exclusive components. */
65
114
  setExclusiveComponents(this: World, ...components: ComponentClass[]): void;
115
+ /** Resolve the meta of a component instance's class for the reflection API. */
116
+ private _reflectedMeta;
66
117
  }
@@ -1,7 +1,22 @@
1
- import { Component } from "../component.js";
1
+ import { Component, } from "../component.js";
2
2
  import { ComponentDef, Trait } from "../component_def.js";
3
+ import { Relationship, _slotHandlers } from "../component_meta.js";
4
+ import { _defaultNormalize, _defaultValidate, _hasDefaultChecks, } from "../reflection/defaults.js";
5
+ import { _ownDecoratedMembers } from "../reflection/decorators.js";
6
+ import { _attachVecsStruct } from "../reflection/component_members.js";
7
+ import { _classChain, _declareMembers } from "../reflection/vecs_struct.js";
3
8
  import { Entities } from "./world.entities.js";
4
9
  const RESERVED_EID = 0;
10
+ /** Hook events a class can implement as a static `vecs_<event>` method. */
11
+ const STATIC_HOOK_EVENTS = [
12
+ "add",
13
+ "set",
14
+ "remove",
15
+ "targetAdd",
16
+ "targetRemove",
17
+ "validate",
18
+ "normalize",
19
+ ];
5
20
  export class Components extends Entities {
6
21
  component(ComponentClassOrName, ceid) {
7
22
  if (typeof ComponentClassOrName === "string") {
@@ -41,6 +56,75 @@ export class Components extends Entities {
41
56
  set(Class, props) {
42
57
  return this.component(Class).set(Class, props);
43
58
  }
59
+ /**
60
+ * Check `component` with its class's `validate` hooks.
61
+ *
62
+ * Hooks run in registration order and the first failure is returned. When
63
+ * the class has no explicit hook but declares ranges or integer member
64
+ * types, a default hook is created on first use: it checks each such member
65
+ * against its range and its type's bounds (for example `u8` is 0–255), that
66
+ * integers are whole, and that an `enum` with `values` holds one of them. It
67
+ * reads the live definition, inherited members included. A class with
68
+ * neither ranges nor integer types is valid.
69
+ *
70
+ * Nothing calls this on `set`; call it where your code accepts external data.
71
+ *
72
+ * @param component - An instance of a class registered in this world.
73
+ * @returns `true`, or the violations: one message or a list of them.
74
+ * @throws When `component.constructor` is not registered in this world.
75
+ */
76
+ validate(component) {
77
+ const meta = this._reflectedMeta(component, "validate");
78
+ if (meta._onValidate === undefined) {
79
+ if (!_hasDefaultChecks(meta.Class)) {
80
+ return true;
81
+ }
82
+ meta._onValidate = _defaultValidate(meta.Class);
83
+ meta._validateIsDefault = true;
84
+ }
85
+ for (const hook of _slotHandlers(meta._onValidate)) {
86
+ const result = hook(component);
87
+ if (result !== true) {
88
+ return result;
89
+ }
90
+ }
91
+ return true;
92
+ }
93
+ /**
94
+ * Return whether {@link validate} accepts `component`.
95
+ *
96
+ * @throws When `component.constructor` is not registered in this world.
97
+ */
98
+ isValid(component) {
99
+ return this.validate(component) === true;
100
+ }
101
+ /**
102
+ * Modify `component` in place with its class's `normalize` hooks, so it
103
+ * becomes valid.
104
+ *
105
+ * Hooks run in registration order, each on the result of the previous one.
106
+ * When the class has no explicit hook but declares ranges or integer member
107
+ * types, a default hook is created on first use: it truncates integers and
108
+ * clamps each such member to its range and its type's bounds. Values of the
109
+ * wrong JavaScript type and `enum` values outside their `values` are left as
110
+ * they are; `NaN` becomes `0` first.
111
+ *
112
+ * @param component - An instance of a class registered in this world.
113
+ * @returns `component`, for chaining.
114
+ * @throws When `component.constructor` is not registered in this world.
115
+ */
116
+ normalize(component) {
117
+ const meta = this._reflectedMeta(component, "normalize");
118
+ if (meta._onNormalize === undefined) {
119
+ if (!_hasDefaultChecks(meta.Class)) {
120
+ return component;
121
+ }
122
+ meta._onNormalize = _defaultNormalize(meta.Class);
123
+ meta._normalizeIsDefault = true;
124
+ }
125
+ meta._onNormalize(component);
126
+ return component;
127
+ }
44
128
  /** Declare a group of mutually exclusive components. */
45
129
  setExclusiveComponents(...components) {
46
130
  const componentMetas = components.map((C) => this.entity(C).meta);
@@ -60,6 +144,12 @@ export class Components extends Entities {
60
144
  }
61
145
  throw new Error(`Component class ${ComponentClass.name} is already registered with eid ${existingComponent.eid}`);
62
146
  }
147
+ // Validate declarations before allocating an id, so a throw leaves no
148
+ // half-registered class behind.
149
+ for (const Class of _classChain(ComponentClass)) {
150
+ _declareMembers(Class, _ownDecoratedMembers(Class));
151
+ }
152
+ const staticHooks = staticHooksOf(ComponentClass);
63
153
  if (ceid === undefined) {
64
154
  ceid = pool.alloc();
65
155
  }
@@ -72,12 +162,16 @@ export class Components extends Entities {
72
162
  const component = new Constructor(this, ceid, ComponentClass);
73
163
  this._entities.set(ceid, component);
74
164
  this._classRegistry.set(ComponentClass, component);
165
+ for (const [event, hook] of staticHooks) {
166
+ component.meta.on(event, hook);
167
+ }
75
168
  // Publish the class before resolving core metadata: recursive registrations
76
169
  // find themselves, so bootstrap needs neither special ids nor a second pass.
77
170
  // Both metadata definitions exist before any module can claim their scope.
78
171
  this.component(ComponentDef);
79
172
  this.component(Trait);
80
173
  component.add(ComponentDef);
174
+ _attachVecsStruct(component);
81
175
  const parent = this._scope;
82
176
  if (parent !== undefined) {
83
177
  component.childOf(parent);
@@ -88,5 +182,34 @@ export class Components extends Entities {
88
182
  this._notifyEntityCreated(component);
89
183
  return component;
90
184
  }
185
+ /** Resolve the meta of a component instance's class for the reflection API. */
186
+ _reflectedMeta(component, api) {
187
+ const Class = component?.constructor;
188
+ const registered = Class === undefined ? undefined : this._classRegistry.get(Class);
189
+ if (registered === undefined) {
190
+ throw new Error(`world.${api}: component class ${Class?.name || "<unknown>"} is not registered in this world`);
191
+ }
192
+ return registered.meta;
193
+ }
194
+ }
195
+ /**
196
+ * Collect a class's static `vecs_<event>` methods, bound to the class.
197
+ *
198
+ * @throws When a class that is not a relationship declares a `target*` static.
199
+ */
200
+ function staticHooksOf(Class) {
201
+ const statics = Class;
202
+ const hooks = [];
203
+ for (const event of STATIC_HOOK_EVENTS) {
204
+ const hook = statics[`vecs_${event}`];
205
+ if (typeof hook !== "function") {
206
+ continue;
207
+ }
208
+ if (event.startsWith("target") && !(Class.prototype instanceof Relationship)) {
209
+ throw new Error(`"${event}" hooks can only be registered on a Relationship component`);
210
+ }
211
+ hooks.push([event, hook.bind(Class)]);
212
+ }
213
+ return hooks;
91
214
  }
92
215
  //# sourceMappingURL=world.components.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"world.components.js","sourceRoot":"","sources":["../../src/world/world.components.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"world.components.js","sourceRoot":"","sources":["../../src/world/world.components.ts"],"names":[],"mappings":"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"}
package/docs/README.md CHANGED
@@ -30,6 +30,8 @@ Read in this order the first time through:
30
30
  - [Modules](./modules.md) — packaging features, composition, scopes and module provenance, built-in modules.
31
31
  - [Subtree snapshots](./snapshots.md) — capturing, restoring, and moving an entity forest
32
32
  between two worlds.
33
+ - [Component reflection](./reflection.md) — member types, `VecsStruct`, and the `validate` /
34
+ `normalize` hooks.
33
35
  5. [Design guide](./design-guide.md) — opinionated patterns and anti-patterns distilled from real
34
36
  designs. Read it once you know the machinery.
35
37
  6. [Utilities](./utilities.md) — helper classes exported by `@vworlds/vecs` but not part of the ECS
@@ -60,6 +62,7 @@ after reading the execution-model and systems guides.
60
62
  | [relationships.md](./relationships.md) | Relationships, target creation, projection and ownership, hierarchy, traversal, and cleanup. |
61
63
  | [modules.md](./modules.md) | `Module<Config>`, `world.module`, `world.scope`, hierarchy provenance, built-in modules. |
62
64
  | [snapshots.md](./snapshots.md) | `snapshotSubtree`, `restoreSubtree`, `moveSubtree`, and `OnSnapshot` capture policies. |
65
+ | [reflection.md](./reflection.md) | Member types and metadata, `VecsStruct`, `validate` / `normalize` hooks, `vecs_` static hooks. |
63
66
  | [design-guide.md](./design-guide.md) | Patterns, anti-patterns, and a Phaser render-layer case study. |
64
67
  | [diagrams.md](./diagrams.md) | Mermaid notation and copyable schema, instance, and systems templates for ECS designs. |
65
68
  | [utilities.md](./utilities.md) | Public helper classes: `Events`, `ArrayMap`, `IterableArrayMap`, plus `Bitset` cross-ref. |
@@ -154,6 +154,12 @@ world
154
154
  | `set` | `entity.set(C, props)` applies ordinary data, `entity.attach(instance)` stores an instance, `entity.modified(C)` is called, or `getMut` / `ensure` marks it dirty. |
155
155
  | `remove` | The component is detached (`entity.remove(C)`), or the owning entity is destroyed. |
156
156
 
157
+ Two more events, `validate` and `normalize`, check or fix a component instance. They run only
158
+ through `world.validate` and `world.normalize`, never on a mutation; see
159
+ [Component reflection](./reflection.md#validating-and-normalizing). A class can also declare any hook
160
+ as a static `vecs_<event>` method, which runs ahead of hooks added with `on`; see
161
+ [`vecs_` statics](./reflection.md#vecs_-statics).
162
+
157
163
  Relationship component entities additionally expose the `targetAdd` and `targetRemove` events. Both
158
164
  receive `(source, relationshipInstance, target)` after the edge and reverse index reach their new
159
165
  state. An exclusive replacement fires `targetRemove` for the old edge before `targetAdd` for
@@ -450,17 +456,20 @@ interface markers for polymorphic dispatch — is in the
450
456
  | `get(Class)` | Singleton read: `component(Class).get(Class)`. |
451
457
  | `set(Class, props)` | Singleton write: `component(Class).set(Class, props)`. |
452
458
  | `setExclusiveComponents(...classes)` | Declare one mutually exclusive group. |
459
+ | `validate(c)` / `isValid(c)` | Run the class's `validate` hooks; see [Component reflection](./reflection.md). |
460
+ | `normalize(c)` | Run the class's `normalize` hooks, modifying `c` in place. |
453
461
 
454
462
  ### `Component` entity (`lib/vecs/src/component.ts`)
455
463
 
456
464
  A `Component<T>` is an `Entity` subclass, so the full [entity API](./entities.md#reference)
457
465
  applies. On top of it:
458
466
 
459
- | Member | Description |
460
- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
461
- | `on(event, handler, insert?)` | Register a handler for `add` / `remove` / `set` / `targetAdd` / `targetRemove` (plus the internal `policyAdd` / `policyRemove` / `targetPolicyAdd` / `targetPolicyRemove` events). `insert` defaults to `false` (append); `true` prepends. Rejects `target*` events on non-relationships. Returns the component. |
462
- | `off(event, handler)` | Remove a previously-registered handler by reference. Idempotent. Returns the component. |
463
- | `meta` | The `ComponentMeta` bookkeeping record. |
467
+ | Member | Description |
468
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
469
+ | `member(name, type)` | Declare a member for [reflection](./reflection.md) and return the component; `.range` / `.description` / `.unit` / `.values` then apply to it. |
470
+ | `on(event, handler, insert?)` | Register a handler for `add` / `remove` / `set` / `targetAdd` / `targetRemove` / `validate` / `normalize` (plus the internal `policyAdd` / `policyRemove` / `targetPolicyAdd` / `targetPolicyRemove` events). `insert` defaults to `false` (append); `true` prepends. Rejects `target*` events on non-relationships. Returns the component. |
471
+ | `off(event, handler)` | Remove a previously-registered handler by reference. Idempotent. Returns the component. |
472
+ | `meta` | The `ComponentMeta` bookkeeping record. |
464
473
 
465
474
  ### `ComponentMeta` (`lib/vecs/src/component_meta.ts`)
466
475
 
package/docs/diagrams.md CHANGED
@@ -43,6 +43,19 @@ Quote edge labels containing emoji. Only `ChildOf` uses a dotted relationship ed
43
43
  (`child -. "1️⃣ChildOf" .-> parent`); every other relationship uses a solid edge
44
44
  (`ship -- "1️⃣TowedBy" --> tug`). Do not use dotted edges for other parent-like relationships.
45
45
 
46
+ ### Member types
47
+
48
+ When a design depends on what a component holds, list its members under the component in a
49
+ schema box, one indented line each. Use the [reflection member type](./reflection.md#member-types-libvecssrcreflectionmember_typests)
50
+ names and add a range or unit only when it matters to the design:
51
+
52
+ ```mermaid
53
+ flowchart BT
54
+ ship["<b>Ship</b><br/>📦 Position<br/>&nbsp;&nbsp;x: i32 [-100, 100] m<br/>&nbsp;&nbsp;y: i32 [-100, 100] m<br/>📦 Cargo<br/>&nbsp;&nbsp;items: [u16]<br/>🏷️ Networked"]
55
+ ```
56
+
57
+ Leave members out where the component's name says enough. Tags have no members.
58
+
46
59
  ## Schema template
47
60
 
48
61
  Use a box for each entity kind: a **bold** name, then one line per component. Relationships live