@alife-sdk/core 0.1.0 → 0.3.0

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.
@@ -12,9 +12,15 @@
12
12
  */
13
13
  export class StateMachine {
14
14
  constructor(entity, registry, initialState) {
15
+ this.previousStateId = null;
16
+ this.enterListeners = new Map();
17
+ this.exitListeners = new Map();
18
+ this.changeListeners = new Set();
19
+ this.historyLog = [];
15
20
  this.entity = entity;
16
21
  this.registry = registry;
17
22
  this.currentStateId = initialState;
23
+ this.stateEnterTime = Date.now();
18
24
  const definition = this.registry.get(this.currentStateId);
19
25
  definition.handler.enter(this.entity);
20
26
  }
@@ -25,6 +31,68 @@ export class StateMachine {
25
31
  get state() {
26
32
  return this.currentStateId;
27
33
  }
34
+ /** Previous state identifier, or `null` if no transition has occurred yet. */
35
+ get previous() {
36
+ return this.previousStateId;
37
+ }
38
+ /** Milliseconds elapsed since entering the current state. */
39
+ get currentStateDuration() {
40
+ return Date.now() - this.stateEnterTime;
41
+ }
42
+ // -----------------------------------------------------------------------
43
+ // Tag queries
44
+ // -----------------------------------------------------------------------
45
+ /** Returns `true` if the current state has the given tag. */
46
+ hasTag(tag) {
47
+ const def = this.registry.tryGet(this.currentStateId);
48
+ return def?.tags?.includes(tag) ?? false;
49
+ }
50
+ /** Returns the metadata object of the current state, or `undefined`. */
51
+ get metadata() {
52
+ return this.registry.tryGet(this.currentStateId)?.metadata;
53
+ }
54
+ // -----------------------------------------------------------------------
55
+ // Event subscriptions
56
+ // -----------------------------------------------------------------------
57
+ /**
58
+ * Subscribe to the moment the FSM enters `state`.
59
+ * @returns Unsubscribe function.
60
+ */
61
+ onEnter(state, callback) {
62
+ if (!this.enterListeners.has(state))
63
+ this.enterListeners.set(state, new Set());
64
+ this.enterListeners.get(state).add(callback);
65
+ return () => this.enterListeners.get(state)?.delete(callback);
66
+ }
67
+ /**
68
+ * Subscribe to the moment the FSM exits `state`.
69
+ * @returns Unsubscribe function.
70
+ */
71
+ onExit(state, callback) {
72
+ if (!this.exitListeners.has(state))
73
+ this.exitListeners.set(state, new Set());
74
+ this.exitListeners.get(state).add(callback);
75
+ return () => this.exitListeners.get(state)?.delete(callback);
76
+ }
77
+ /**
78
+ * Subscribe to any state change.
79
+ * @returns Unsubscribe function.
80
+ */
81
+ onChange(callback) {
82
+ this.changeListeners.add(callback);
83
+ return () => this.changeListeners.delete(callback);
84
+ }
85
+ // -----------------------------------------------------------------------
86
+ // History
87
+ // -----------------------------------------------------------------------
88
+ /** Returns a snapshot of the transition history (oldest first). */
89
+ getHistory() {
90
+ return [...this.historyLog];
91
+ }
92
+ /** Clears the transition history. */
93
+ clearHistory() {
94
+ this.historyLog.length = 0;
95
+ }
28
96
  // -----------------------------------------------------------------------
29
97
  // Transitions
30
98
  // -----------------------------------------------------------------------
@@ -46,9 +114,21 @@ export class StateMachine {
46
114
  return { success: false, reason: 'exit_guard' };
47
115
  if (newDefinition.canEnter?.(this.entity, this.currentStateId) === false)
48
116
  return { success: false, reason: 'enter_guard' };
117
+ const from = this.currentStateId;
118
+ // Exit
49
119
  oldDefinition.handler.exit(this.entity);
120
+ this.exitListeners.get(from)?.forEach(cb => cb(newState));
121
+ // Advance state
122
+ this.previousStateId = from;
50
123
  this.currentStateId = newState;
124
+ this.stateEnterTime = Date.now();
125
+ // Record history
126
+ this.historyLog.push({ from, to: newState, timestamp: this.stateEnterTime });
127
+ // Notify change listeners
128
+ this.changeListeners.forEach(cb => cb(from, newState));
129
+ // Enter
51
130
  newDefinition.handler.enter(this.entity);
131
+ this.enterListeners.get(newState)?.forEach(cb => cb(from));
52
132
  return { success: true };
53
133
  }
54
134
  // -----------------------------------------------------------------------
@@ -1 +1 @@
1
- {"version":3,"file":"StateMachine.js","sourceRoot":"","sources":["../../src/ai/StateMachine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,MAAM,OAAO,YAAY;IAKvB,YAAY,MAAe,EAAE,QAAyB,EAAE,YAAoB;QAC1E,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,cAAc,GAAG,YAAY,CAAC;QAEnC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC1D,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAED,0EAA0E;IAC1E,YAAY;IACZ,0EAA0E;IAE1E,uCAAuC;IACvC,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED,0EAA0E;IAC1E,cAAc;IACd,0EAA0E;IAE1E;;;;;;OAMG;IACH,UAAU,CAAC,QAAgB;QACzB,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC7D,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAElD,oFAAoF;QACpF,IAAI,aAAa,CAAC,kBAAkB,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7F,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;QACnD,CAAC;QAED,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,KAAK;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;QAC9G,IAAI,aAAa,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,CAAC,KAAK,KAAK;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;QAE3H,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC;QAC/B,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED,0EAA0E;IAC1E,SAAS;IACT,0EAA0E;IAE1E;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAa;QAClB,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC1D,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAE9C,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CACjD,IAAI,CAAC,cAAc,EACnB,IAAI,CAAC,MAAM,CACZ,CAAC;QAEF,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,0EAA0E;IAC1E,UAAU;IACV,0EAA0E;IAE1E,iEAAiE;IACjE,OAAO;QACL,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC1D,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;CACF"}
1
+ {"version":3,"file":"StateMachine.js","sourceRoot":"","sources":["../../src/ai/StateMachine.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  export { StateMachine } from './StateMachine';
2
- export type { TransitionResult } from './StateMachine';
2
+ export type { TransitionResult, StateTransitionEvent } from './StateMachine';
3
3
  export { MemoryBank, MemoryChannel } from './MemorySystem';
4
4
  export type { MemoryRecord, IMemoryBankConfig, IMemoryInput } from './MemorySystem';
5
5
  export { DangerManager, DangerType } from './DangerManager';
@@ -7,4 +7,6 @@ export type { IDangerEntry } from './DangerManager';
7
7
  export { WorldState } from './WorldState';
8
8
  export { GOAPPlanner } from './GOAPPlanner';
9
9
  export { GOAPAction, ActionStatus } from './GOAPAction';
10
+ export { Blackboard, Task, Condition, Sequence, Selector, Parallel, Inverter, AlwaysSucceed, AlwaysFail, Repeater, Cooldown } from './BehaviorTree';
11
+ export type { TaskStatus, ITreeNode, ParallelPolicy } from './BehaviorTree';
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC3D,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC3D,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACpJ,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/ai/index.js CHANGED
@@ -5,4 +5,5 @@ export { DangerManager, DangerType } from './DangerManager';
5
5
  export { WorldState } from './WorldState';
6
6
  export { GOAPPlanner } from './GOAPPlanner';
7
7
  export { GOAPAction, ActionStatus } from './GOAPAction';
8
+ export { Blackboard, Task, Condition, Sequence, Selector, Parallel, Inverter, AlwaysSucceed, AlwaysFail, Repeater, Cooldown } from './BehaviorTree';
8
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * ReactiveQuery — observe when entities enter and exit a filtered set.
3
+ *
4
+ * Instead of polling the entire entity set every frame, a ReactiveQuery
5
+ * maintains a stable "matched" set and fires change notifications only when
6
+ * entities enter or leave the query (i.e. when the predicate result changes).
7
+ *
8
+ * Usage pattern:
9
+ * 1. Create a query with a predicate.
10
+ * 2. Subscribe to `onChange` to react to structural changes.
11
+ * 3. Call `update(allEntities)` each tick to re-evaluate.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const hostileQuery = new ReactiveQuery<IEntity>(
16
+ * (e) => e.isAlive && e.hasComponent('hostile')
17
+ * );
18
+ *
19
+ * hostileQuery.onChange(({ added, removed }) => {
20
+ * added.forEach(e => combatSystem.track(e));
21
+ * removed.forEach(e => combatSystem.untrack(e));
22
+ * });
23
+ *
24
+ * // Each tick:
25
+ * hostileQuery.update(world.entities());
26
+ * ```
27
+ */
28
+ /** Changes detected since the last `update()` call. */
29
+ export interface QueryChanges<T> {
30
+ /** Entities that newly matched the predicate this update. */
31
+ readonly added: readonly T[];
32
+ /** Entities that no longer match the predicate this update. */
33
+ readonly removed: readonly T[];
34
+ /** All entities currently matching the predicate after this update. */
35
+ readonly current: readonly T[];
36
+ }
37
+ /** Callback invoked when any entities are added or removed from the query. */
38
+ export type QueryChangeListener<T> = (changes: QueryChanges<T>) => void;
39
+ /**
40
+ * Tracks which entities satisfy a predicate and fires change events when
41
+ * the matched set changes.
42
+ */
43
+ export declare class ReactiveQuery<T> {
44
+ private readonly predicate;
45
+ private readonly matched;
46
+ private readonly listeners;
47
+ constructor(predicate: (entity: T) => boolean);
48
+ /**
49
+ * Re-evaluate the predicate against `allEntities`.
50
+ *
51
+ * Fires `onChange` listeners if the matched set changed.
52
+ * Call this once per tick from the owning system.
53
+ */
54
+ update(allEntities: Iterable<T>): void;
55
+ /**
56
+ * Subscribe to change events. Called whenever entities enter or exit the
57
+ * matched set.
58
+ *
59
+ * @returns Unsubscribe function.
60
+ */
61
+ onChange(listener: QueryChangeListener<T>): () => void;
62
+ /** All entities currently matching the predicate (stable snapshot). */
63
+ get current(): readonly T[];
64
+ /** Number of currently matched entities. */
65
+ get size(): number;
66
+ /** Return `true` if the entity is currently in the matched set. */
67
+ has(entity: T): boolean;
68
+ /**
69
+ * Manually add an entity to the matched set without re-evaluating the
70
+ * predicate. Fires `onChange` with the single addition.
71
+ *
72
+ * Useful when external code creates entities and knows they should match.
73
+ */
74
+ track(entity: T): void;
75
+ /**
76
+ * Manually remove an entity from the matched set.
77
+ * Fires `onChange` with the single removal.
78
+ *
79
+ * Useful when entities are destroyed mid-tick.
80
+ */
81
+ untrack(entity: T): void;
82
+ /** Remove all entities and clear all listeners. */
83
+ dispose(): void;
84
+ }
85
+ //# sourceMappingURL=ReactiveQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactiveQuery.d.ts","sourceRoot":"","sources":["../../src/core/ReactiveQuery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH,uDAAuD;AACvD,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;CAChC;AAED,8EAA8E;AAC9E,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAMxE;;;GAGG;AACH,qBAAa,aAAa,CAAC,CAAC;IAId,OAAO,CAAC,QAAQ,CAAC,SAAS;IAHtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;gBAElC,SAAS,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO;IAM9D;;;;;OAKG;IACH,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;IAwCtC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAStD,uEAAuE;IACvE,IAAI,OAAO,IAAI,SAAS,CAAC,EAAE,CAE1B;IAED,4CAA4C;IAC5C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,mEAAmE;IACnE,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,OAAO;IAIvB;;;;;OAKG;IACH,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI;IAatB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI;IAaxB,mDAAmD;IACnD,OAAO,IAAI,IAAI;CAIhB"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * ReactiveQuery — observe when entities enter and exit a filtered set.
3
+ *
4
+ * Instead of polling the entire entity set every frame, a ReactiveQuery
5
+ * maintains a stable "matched" set and fires change notifications only when
6
+ * entities enter or leave the query (i.e. when the predicate result changes).
7
+ *
8
+ * Usage pattern:
9
+ * 1. Create a query with a predicate.
10
+ * 2. Subscribe to `onChange` to react to structural changes.
11
+ * 3. Call `update(allEntities)` each tick to re-evaluate.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const hostileQuery = new ReactiveQuery<IEntity>(
16
+ * (e) => e.isAlive && e.hasComponent('hostile')
17
+ * );
18
+ *
19
+ * hostileQuery.onChange(({ added, removed }) => {
20
+ * added.forEach(e => combatSystem.track(e));
21
+ * removed.forEach(e => combatSystem.untrack(e));
22
+ * });
23
+ *
24
+ * // Each tick:
25
+ * hostileQuery.update(world.entities());
26
+ * ```
27
+ */
28
+ // ---------------------------------------------------------------------------
29
+ // ReactiveQuery
30
+ // ---------------------------------------------------------------------------
31
+ /**
32
+ * Tracks which entities satisfy a predicate and fires change events when
33
+ * the matched set changes.
34
+ */
35
+ export class ReactiveQuery {
36
+ constructor(predicate) {
37
+ this.predicate = predicate;
38
+ this.matched = new Set();
39
+ this.listeners = new Set();
40
+ }
41
+ // -------------------------------------------------------------------------
42
+ // Update
43
+ // -------------------------------------------------------------------------
44
+ /**
45
+ * Re-evaluate the predicate against `allEntities`.
46
+ *
47
+ * Fires `onChange` listeners if the matched set changed.
48
+ * Call this once per tick from the owning system.
49
+ */
50
+ update(allEntities) {
51
+ const added = [];
52
+ const removed = [];
53
+ const nextMatched = new Set();
54
+ for (const entity of allEntities) {
55
+ if (this.predicate(entity)) {
56
+ nextMatched.add(entity);
57
+ if (!this.matched.has(entity)) {
58
+ added.push(entity);
59
+ }
60
+ }
61
+ }
62
+ for (const entity of this.matched) {
63
+ if (!nextMatched.has(entity)) {
64
+ removed.push(entity);
65
+ }
66
+ }
67
+ // Commit new matched set
68
+ this.matched.clear();
69
+ for (const e of nextMatched)
70
+ this.matched.add(e);
71
+ if (added.length > 0 || removed.length > 0) {
72
+ const changes = {
73
+ added,
74
+ removed,
75
+ current: [...this.matched],
76
+ };
77
+ for (const listener of this.listeners) {
78
+ listener(changes);
79
+ }
80
+ }
81
+ }
82
+ // -------------------------------------------------------------------------
83
+ // Subscriptions
84
+ // -------------------------------------------------------------------------
85
+ /**
86
+ * Subscribe to change events. Called whenever entities enter or exit the
87
+ * matched set.
88
+ *
89
+ * @returns Unsubscribe function.
90
+ */
91
+ onChange(listener) {
92
+ this.listeners.add(listener);
93
+ return () => this.listeners.delete(listener);
94
+ }
95
+ // -------------------------------------------------------------------------
96
+ // Accessors
97
+ // -------------------------------------------------------------------------
98
+ /** All entities currently matching the predicate (stable snapshot). */
99
+ get current() {
100
+ return [...this.matched];
101
+ }
102
+ /** Number of currently matched entities. */
103
+ get size() {
104
+ return this.matched.size;
105
+ }
106
+ /** Return `true` if the entity is currently in the matched set. */
107
+ has(entity) {
108
+ return this.matched.has(entity);
109
+ }
110
+ /**
111
+ * Manually add an entity to the matched set without re-evaluating the
112
+ * predicate. Fires `onChange` with the single addition.
113
+ *
114
+ * Useful when external code creates entities and knows they should match.
115
+ */
116
+ track(entity) {
117
+ if (this.matched.has(entity))
118
+ return;
119
+ this.matched.add(entity);
120
+ const changes = {
121
+ added: [entity],
122
+ removed: [],
123
+ current: [...this.matched],
124
+ };
125
+ for (const listener of this.listeners) {
126
+ listener(changes);
127
+ }
128
+ }
129
+ /**
130
+ * Manually remove an entity from the matched set.
131
+ * Fires `onChange` with the single removal.
132
+ *
133
+ * Useful when entities are destroyed mid-tick.
134
+ */
135
+ untrack(entity) {
136
+ if (!this.matched.has(entity))
137
+ return;
138
+ this.matched.delete(entity);
139
+ const changes = {
140
+ added: [],
141
+ removed: [entity],
142
+ current: [...this.matched],
143
+ };
144
+ for (const listener of this.listeners) {
145
+ listener(changes);
146
+ }
147
+ }
148
+ /** Remove all entities and clear all listeners. */
149
+ dispose() {
150
+ this.matched.clear();
151
+ this.listeners.clear();
152
+ }
153
+ }
154
+ //# sourceMappingURL=ReactiveQuery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactiveQuery.js","sourceRoot":"","sources":["../../src/core/ReactiveQuery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * EntityHandle — versioned entity references with use-after-free protection.
3
+ *
4
+ * Instead of holding raw entity references that can become dangling after an
5
+ * entity is destroyed, systems hold an EntityHandle. The handle encodes both
6
+ * the entity's slot index and a generation counter. When a slot is reused for
7
+ * a new entity the generation is bumped, making all old handles stale.
8
+ *
9
+ * Bit layout (fits in a JavaScript safe integer):
10
+ * [47..20] generation (28 bits, up to ~268 M versions per slot)
11
+ * [19.. 0] index (20 bits, up to ~1 M concurrent slots)
12
+ *
13
+ * Usage:
14
+ * const manager = new EntityHandleManager();
15
+ * const handle = manager.alloc('wolf-1');
16
+ * manager.resolve(handle); // → 'wolf-1'
17
+ * manager.free(handle);
18
+ * manager.resolve(handle); // → null (stale)
19
+ */
20
+ /** Opaque numeric type that encodes (generation, index). */
21
+ export type EntityHandle = number & {
22
+ readonly __brand: 'EntityHandle';
23
+ };
24
+ /** Sentinel value for an absent or invalid handle. */
25
+ export declare const NULL_HANDLE: EntityHandle;
26
+ /** Pack (index, generation) into a single handle. */
27
+ export declare function makeHandle(index: number, generation: number): EntityHandle;
28
+ /** Extract the slot index from a handle. */
29
+ export declare function indexOf(handle: EntityHandle): number;
30
+ /** Extract the generation counter from a handle. */
31
+ export declare function genOf(handle: EntityHandle): number;
32
+ /** Return `true` if the handle is not the null sentinel. */
33
+ export declare function isValidHandle(handle: EntityHandle): boolean;
34
+ /** Human-readable description for logging / debugging. */
35
+ export declare function handleToString(handle: EntityHandle): string;
36
+ /**
37
+ * Central registry that owns the slot → entity-id mapping.
38
+ *
39
+ * Recycles freed slots so slot count stays bounded. Old handles pointing at
40
+ * recycled slots resolve to `null` because their stored generation no longer
41
+ * matches the slot's current generation.
42
+ */
43
+ export declare class EntityHandleManager<TId = string> {
44
+ /** Generation counter per slot (index = slot index). */
45
+ private readonly generations;
46
+ /** Entity ID stored in each live slot. `null` = free. */
47
+ private readonly ids;
48
+ /** Slot indices available for reuse. */
49
+ private readonly freeList;
50
+ /** Next slot to allocate when freeList is empty. */
51
+ private nextSlot;
52
+ /**
53
+ * Allocate a new handle for the given entity id.
54
+ * @throws if the slot limit is exhausted.
55
+ */
56
+ alloc(id: TId): EntityHandle;
57
+ /**
58
+ * Release a handle, incrementing the slot's generation.
59
+ * All existing handles pointing at this slot become stale.
60
+ *
61
+ * Does nothing (and does not throw) if the handle is already stale.
62
+ */
63
+ free(handle: EntityHandle): void;
64
+ /**
65
+ * Resolve a handle to its entity id.
66
+ * Returns `null` if the handle is stale (entity was freed) or null.
67
+ */
68
+ resolve(handle: EntityHandle): TId | null;
69
+ /**
70
+ * Return `true` if the handle points to a currently-live slot.
71
+ */
72
+ isAlive(handle: EntityHandle): boolean;
73
+ /** Number of currently-live slots. */
74
+ get size(): number;
75
+ }
76
+ //# sourceMappingURL=EntityHandle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityHandle.d.ts","sourceRoot":"","sources":["../../src/entity/EntityHandle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAgBH,4DAA4D;AAC5D,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;CAAE,CAAC;AAEzE,sDAAsD;AACtD,eAAO,MAAM,WAAW,EAAQ,YAAY,CAAC;AAE7C,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CAG1E;AAED,4CAA4C;AAC5C,wBAAgB,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAEpD;AAED,oDAAoD;AACpD,wBAAgB,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAElD;AAED,4DAA4D;AAC5D,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAE3D;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAG3D;AAMD;;;;;;GAMG;AACH,qBAAa,mBAAmB,CAAC,GAAG,GAAG,MAAM;IAC3C,wDAAwD;IACxD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,yDAAyD;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAsB;IAC1C,wCAAwC;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,oDAAoD;IACpD,OAAO,CAAC,QAAQ,CAAK;IAErB;;;OAGG;IACH,KAAK,CAAC,EAAE,EAAE,GAAG,GAAG,YAAY;IAiB5B;;;;;OAKG;IACH,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAQhC;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,GAAG,GAAG,IAAI;IAOzC;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO;IAMtC,sCAAsC;IACtC,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * EntityHandle — versioned entity references with use-after-free protection.
3
+ *
4
+ * Instead of holding raw entity references that can become dangling after an
5
+ * entity is destroyed, systems hold an EntityHandle. The handle encodes both
6
+ * the entity's slot index and a generation counter. When a slot is reused for
7
+ * a new entity the generation is bumped, making all old handles stale.
8
+ *
9
+ * Bit layout (fits in a JavaScript safe integer):
10
+ * [47..20] generation (28 bits, up to ~268 M versions per slot)
11
+ * [19.. 0] index (20 bits, up to ~1 M concurrent slots)
12
+ *
13
+ * Usage:
14
+ * const manager = new EntityHandleManager();
15
+ * const handle = manager.alloc('wolf-1');
16
+ * manager.resolve(handle); // → 'wolf-1'
17
+ * manager.free(handle);
18
+ * manager.resolve(handle); // → null (stale)
19
+ */
20
+ // ---------------------------------------------------------------------------
21
+ // Bit-layout constants
22
+ // ---------------------------------------------------------------------------
23
+ const INDEX_BITS = 20;
24
+ const GEN_BITS = 28;
25
+ const INDEX_MASK = (1 << INDEX_BITS) - 1; // 0x000FFFFF — 1 048 575 max slots
26
+ const GEN_MASK = (1 << GEN_BITS) - 1; // 0x0FFFFFFF — 268 435 455 max gen
27
+ const MAX_SLOTS = 1 << INDEX_BITS;
28
+ /** Sentinel value for an absent or invalid handle. */
29
+ export const NULL_HANDLE = 0;
30
+ /** Pack (index, generation) into a single handle. */
31
+ export function makeHandle(index, generation) {
32
+ // Use multiplication instead of bit-shift to avoid 32-bit truncation.
33
+ return ((generation & GEN_MASK) * MAX_SLOTS + (index & INDEX_MASK));
34
+ }
35
+ /** Extract the slot index from a handle. */
36
+ export function indexOf(handle) {
37
+ return handle & INDEX_MASK;
38
+ }
39
+ /** Extract the generation counter from a handle. */
40
+ export function genOf(handle) {
41
+ return Math.floor(handle / MAX_SLOTS) & GEN_MASK;
42
+ }
43
+ /** Return `true` if the handle is not the null sentinel. */
44
+ export function isValidHandle(handle) {
45
+ return handle !== NULL_HANDLE;
46
+ }
47
+ /** Human-readable description for logging / debugging. */
48
+ export function handleToString(handle) {
49
+ if (!isValidHandle(handle))
50
+ return 'Entity(NULL)';
51
+ return `Entity(idx=${indexOf(handle)}, gen=${genOf(handle)})`;
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // EntityHandleManager
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Central registry that owns the slot → entity-id mapping.
58
+ *
59
+ * Recycles freed slots so slot count stays bounded. Old handles pointing at
60
+ * recycled slots resolve to `null` because their stored generation no longer
61
+ * matches the slot's current generation.
62
+ */
63
+ export class EntityHandleManager {
64
+ constructor() {
65
+ /** Generation counter per slot (index = slot index). */
66
+ this.generations = [];
67
+ /** Entity ID stored in each live slot. `null` = free. */
68
+ this.ids = [];
69
+ /** Slot indices available for reuse. */
70
+ this.freeList = [];
71
+ /** Next slot to allocate when freeList is empty. */
72
+ this.nextSlot = 0;
73
+ }
74
+ /**
75
+ * Allocate a new handle for the given entity id.
76
+ * @throws if the slot limit is exhausted.
77
+ */
78
+ alloc(id) {
79
+ let index;
80
+ if (this.freeList.length > 0) {
81
+ index = this.freeList.pop();
82
+ }
83
+ else {
84
+ if (this.nextSlot >= MAX_SLOTS) {
85
+ throw new Error(`EntityHandleManager: slot limit (${MAX_SLOTS}) reached`);
86
+ }
87
+ index = this.nextSlot++;
88
+ this.generations[index] = 1;
89
+ }
90
+ this.ids[index] = id;
91
+ return makeHandle(index, this.generations[index]);
92
+ }
93
+ /**
94
+ * Release a handle, incrementing the slot's generation.
95
+ * All existing handles pointing at this slot become stale.
96
+ *
97
+ * Does nothing (and does not throw) if the handle is already stale.
98
+ */
99
+ free(handle) {
100
+ if (!this.isAlive(handle))
101
+ return;
102
+ const index = indexOf(handle);
103
+ this.ids[index] = null;
104
+ this.generations[index] = (this.generations[index] + 1) & GEN_MASK || 1; // skip 0
105
+ this.freeList.push(index);
106
+ }
107
+ /**
108
+ * Resolve a handle to its entity id.
109
+ * Returns `null` if the handle is stale (entity was freed) or null.
110
+ */
111
+ resolve(handle) {
112
+ if (!isValidHandle(handle))
113
+ return null;
114
+ const index = indexOf(handle);
115
+ if (this.generations[index] !== genOf(handle))
116
+ return null;
117
+ return this.ids[index] ?? null;
118
+ }
119
+ /**
120
+ * Return `true` if the handle points to a currently-live slot.
121
+ */
122
+ isAlive(handle) {
123
+ if (!isValidHandle(handle))
124
+ return false;
125
+ const index = indexOf(handle);
126
+ return this.generations[index] === genOf(handle) && this.ids[index] !== null;
127
+ }
128
+ /** Number of currently-live slots. */
129
+ get size() {
130
+ return this.nextSlot - this.freeList.length;
131
+ }
132
+ }
133
+ //# sourceMappingURL=EntityHandle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityHandle.js","sourceRoot":"","sources":["../../src/entity/EntityHandle.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
1
  export type { IEntity } from './IEntity';
2
2
  export type { IComponent } from './IComponent';
3
+ export type { EntityHandle } from './EntityHandle';
4
+ export { NULL_HANDLE, makeHandle, indexOf, genOf, isValidHandle, handleToString, EntityHandleManager, } from './EntityHandle';
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/entity/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/entity/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EACL,WAAW,EACX,UAAU,EACV,OAAO,EACP,KAAK,EACL,aAAa,EACb,cAAc,EACd,mBAAmB,GACpB,MAAM,gBAAgB,CAAC"}
@@ -1,2 +1,2 @@
1
- export {};
1
+ export { NULL_HANDLE, makeHandle, indexOf, genOf, isValidHandle, handleToString, EntityHandleManager, } from './EntityHandle';
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/entity/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/entity/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,WAAW,EACX,UAAU,EACV,OAAO,EACP,KAAK,EACL,aAAa,EACb,cAAc,EACd,mBAAmB,GACpB,MAAM,gBAAgB,CAAC"}