@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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Framework-agnostic A-Life simulation and AI decision-making system.
4
4
 
5
- Zero external dependencies. Works with Phaser, Unity, Node.js, or any other runtime.
5
+ Zero external dependencies. Works with Phaser, PixiJS, Node.js, or any JavaScript runtime.
6
6
 
7
7
  ```
8
8
  npm install @alife-sdk/core
@@ -16,7 +16,7 @@ npm install @alife-sdk/core
16
16
 
17
17
  - **NPC AI** — finite state machines, GOAP planners, memory and perception systems
18
18
  - **A-Life simulation** — offline NPC brains, terrain selection, spawn cooldowns, faction diplomacy
19
- - **Game clock** — accelerated in-game time, day/night cycle, hour events
19
+ - **Game clock** — accelerated in-game time with configurable day/night cycle and time-change callbacks (HOUR_CHANGED, DAY_NIGHT_CHANGED events)
20
20
  - **World graph** — waypoint graph with A* pathfinding for offline NPC movement
21
21
  - **Plugin system** — extend the kernel with your own domain features
22
22
 
@@ -52,6 +52,8 @@ kernel.provide(Ports.PlayerPosition, { getPlayerPosition: () => ({ x: player.x,
52
52
  fullPreset(kernel);
53
53
 
54
54
  // 4. Register game data (before init)
55
+ // Plugins is a set of typed plugin tokens — alternative to string IDs
56
+ // e.g. kernel.getPlugin(Plugins.FACTIONS) is equivalent to kernel.getPlugin('factions')
55
57
  const factions = kernel.getPlugin(Plugins.FACTIONS).factions;
56
58
  factions.register('stalker', { name: 'Stalker', baseRelations: { bandit: -80 } });
57
59
  factions.register('bandit', { name: 'Bandit', baseRelations: { stalker: -80 } });
@@ -83,7 +85,7 @@ Each module has its own import path for optimal tree-shaking:
83
85
 
84
86
  | Import path | What's inside | Module docs |
85
87
  |-------------|--------------|-------------|
86
- | `@alife-sdk/core` | `ALifeKernel`, `Clock`, `SpatialGrid`, `Ports` | [core/](src/core/) |
88
+ | `@alife-sdk/core` | `ALifeKernel`, `Clock`, `SpatialGrid`, `Ports`, `PortRegistry`, `Vec2`, `createPortToken` | [core/](src/core/) |
87
89
  | `@alife-sdk/core/ai` | `StateMachine`, `MemoryBank`, `DangerManager`, `GOAPPlanner` | [ai/](src/ai/README.md) |
88
90
  | `@alife-sdk/core/combat` | `DamageInstance`, `MoraleTracker`, `ImmunityProfile` | [combat/](src/combat/README.md) |
89
91
  | `@alife-sdk/core/config` | `createDefaultConfig`, `IALifeConfig` | [config/](src/config/README.md) |
@@ -187,7 +189,7 @@ kernel.events.on(ALifeEvents.NPC_DIED, ({ npcId, killedBy }) => {
187
189
  });
188
190
  ```
189
191
 
190
- 38 typed events across 9 categories: A-Life, AI, Surge, Anomaly, Squad,
192
+ 41 typed events across 9 categories: A-Life, AI, Surge, Anomaly, Squad,
191
193
  Faction, Time, Social, Monster. See [`events/README.md`](src/events/README.md).
192
194
 
193
195
  ### AI — StateMachine + GOAP
@@ -231,6 +233,23 @@ kernel.restoreState() ← restore from save
231
233
  kernel.destroy() ← cleanup, call plugin.destroy() in reverse order
232
234
  ```
233
235
 
236
+ ### Save versioning and migrations
237
+
238
+ The kernel supports versioned saves. When a save file was created by an older
239
+ version of your game, you can register migration functions to transform the
240
+ state forward. Migrations are applied automatically during `restoreState()`.
241
+
242
+ ```ts
243
+ // Register a migration that upgrades state from version 0 → 1
244
+ kernel.registerMigration(0, (state) => {
245
+ // transform state as needed
246
+ return { ...state, version: 1, newField: 'default' };
247
+ });
248
+
249
+ // Later, restoreState() applies all needed migrations automatically
250
+ kernel.restoreState(oldSave);
251
+ ```
252
+
234
253
  ---
235
254
 
236
255
  ## Testing
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Behavior Tree — hierarchical AI task composition.
3
+ *
4
+ * Pairs well with GOAP: GOAP decides *what* goal to pursue, the BT decides
5
+ * *how* to execute it step by step. The BT is driven externally by calling
6
+ * `tree.tick(blackboard)` each frame or simulation step.
7
+ *
8
+ * Node types:
9
+ * Composites — Sequence, Selector, Parallel
10
+ * Decorators — Inverter, Repeater, AlwaysSucceed, AlwaysFail, Cooldown
11
+ * Leaves — Task (action), Condition
12
+ *
13
+ * Blackboard:
14
+ * Typed key-value store shared across all nodes in one tick. Nodes read
15
+ * perception data from it and write intermediate results to it.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const bb = new Blackboard({ canSeeTarget: false, ammoCount: 10 });
20
+ *
21
+ * const tree = new Selector([
22
+ * new Sequence([
23
+ * new Condition((bb) => bb.get('canSeeTarget')),
24
+ * new Condition((bb) => bb.get('ammoCount') > 0),
25
+ * new Task((bb) => { shoot(); return 'success'; }),
26
+ * ]),
27
+ * new Task(() => { patrol(); return 'running'; }),
28
+ * ]);
29
+ *
30
+ * // Each frame:
31
+ * tree.tick(bb);
32
+ * ```
33
+ */
34
+ /** Result returned by every node's `tick()`. */
35
+ export type TaskStatus = 'success' | 'failure' | 'running';
36
+ /**
37
+ * Typed key-value store shared across all nodes during a single tick.
38
+ *
39
+ * Initialize it with a plain object; the keys become the allowed set of keys
40
+ * via the generic type parameter.
41
+ */
42
+ export declare class Blackboard<T extends Record<string, unknown> = Record<string, unknown>> {
43
+ private readonly data;
44
+ constructor(initial?: Partial<T>);
45
+ get<K extends keyof T>(key: K): T[K] | undefined;
46
+ set<K extends keyof T>(key: K, value: T[K]): void;
47
+ has(key: keyof T): boolean;
48
+ delete(key: keyof T): void;
49
+ }
50
+ /** Every node in the tree implements this single interface. */
51
+ export interface ITreeNode<TBB extends Blackboard = Blackboard> {
52
+ tick(blackboard: TBB): TaskStatus;
53
+ }
54
+ /**
55
+ * Task (action leaf).
56
+ * Runs an arbitrary callback; the callback returns the status directly.
57
+ */
58
+ export declare class Task<TBB extends Blackboard = Blackboard> implements ITreeNode<TBB> {
59
+ private readonly action;
60
+ constructor(action: (bb: TBB) => TaskStatus);
61
+ tick(bb: TBB): TaskStatus;
62
+ }
63
+ /**
64
+ * Condition (boolean leaf).
65
+ * Returns 'success' when the predicate is true, 'failure' otherwise.
66
+ */
67
+ export declare class Condition<TBB extends Blackboard = Blackboard> implements ITreeNode<TBB> {
68
+ private readonly predicate;
69
+ constructor(predicate: (bb: TBB) => boolean);
70
+ tick(bb: TBB): TaskStatus;
71
+ }
72
+ /**
73
+ * Sequence — AND gate.
74
+ * Ticks children left-to-right. Returns 'failure' on the first failing child,
75
+ * 'running' on the first running child, 'success' when all succeed.
76
+ */
77
+ export declare class Sequence<TBB extends Blackboard = Blackboard> implements ITreeNode<TBB> {
78
+ private readonly children;
79
+ constructor(children: ITreeNode<TBB>[]);
80
+ tick(bb: TBB): TaskStatus;
81
+ }
82
+ /**
83
+ * Selector — OR gate.
84
+ * Ticks children left-to-right. Returns 'success' on the first succeeding
85
+ * child, 'running' on the first running child, 'failure' when all fail.
86
+ */
87
+ export declare class Selector<TBB extends Blackboard = Blackboard> implements ITreeNode<TBB> {
88
+ private readonly children;
89
+ constructor(children: ITreeNode<TBB>[]);
90
+ tick(bb: TBB): TaskStatus;
91
+ }
92
+ /** Policy for the Parallel node. */
93
+ export type ParallelPolicy = 'require-all' | 'require-one';
94
+ /**
95
+ * Parallel — ticks ALL children every tick simultaneously.
96
+ *
97
+ * `require-all` (default — AND semantics):
98
+ * - Success: all children succeed.
99
+ * - Failure: any child fails.
100
+ * - Running: otherwise.
101
+ *
102
+ * `require-one` (OR semantics):
103
+ * - Success: at least one child succeeds.
104
+ * - Failure: all children fail.
105
+ * - Running: otherwise (some running, none succeeded yet).
106
+ */
107
+ export declare class Parallel<TBB extends Blackboard = Blackboard> implements ITreeNode<TBB> {
108
+ private readonly children;
109
+ private readonly successPolicy;
110
+ constructor(children: ITreeNode<TBB>[], successPolicy?: ParallelPolicy);
111
+ tick(bb: TBB): TaskStatus;
112
+ }
113
+ /**
114
+ * Inverter — flips 'success' ↔ 'failure'; passes 'running' unchanged.
115
+ */
116
+ export declare class Inverter<TBB extends Blackboard = Blackboard> implements ITreeNode<TBB> {
117
+ private readonly child;
118
+ constructor(child: ITreeNode<TBB>);
119
+ tick(bb: TBB): TaskStatus;
120
+ }
121
+ /**
122
+ * AlwaysSucceed — maps any child result to 'success'.
123
+ */
124
+ export declare class AlwaysSucceed<TBB extends Blackboard = Blackboard> implements ITreeNode<TBB> {
125
+ private readonly child;
126
+ constructor(child: ITreeNode<TBB>);
127
+ tick(bb: TBB): TaskStatus;
128
+ }
129
+ /**
130
+ * AlwaysFail — maps any child result to 'failure'.
131
+ */
132
+ export declare class AlwaysFail<TBB extends Blackboard = Blackboard> implements ITreeNode<TBB> {
133
+ private readonly child;
134
+ constructor(child: ITreeNode<TBB>);
135
+ tick(bb: TBB): TaskStatus;
136
+ }
137
+ /**
138
+ * Repeater — ticks its child N times, returning 'success' after all iterations.
139
+ * If the child returns 'failure' the repeater short-circuits with 'failure'.
140
+ * Pass `Infinity` for an endless loop (returns 'running' every tick).
141
+ */
142
+ export declare class Repeater<TBB extends Blackboard = Blackboard> implements ITreeNode<TBB> {
143
+ private readonly child;
144
+ private readonly times;
145
+ private remaining;
146
+ constructor(child: ITreeNode<TBB>, times: number);
147
+ tick(bb: TBB): TaskStatus;
148
+ /** Reset the repeat counter to allow reuse. */
149
+ reset(): void;
150
+ }
151
+ /**
152
+ * Cooldown — blocks its child while a cooldown timer is active.
153
+ *
154
+ * When the cooldown is inactive: ticks the child normally. If the child
155
+ * succeeds, starts the cooldown and returns 'success'. While the cooldown
156
+ * is active: immediately returns 'failure' (the action is on cooldown).
157
+ *
158
+ * `now` defaults to `Date.now` — inject a custom clock for deterministic tests.
159
+ */
160
+ export declare class Cooldown<TBB extends Blackboard = Blackboard> implements ITreeNode<TBB> {
161
+ private readonly child;
162
+ private readonly durationMs;
163
+ private readonly clock;
164
+ private readyAt;
165
+ constructor(child: ITreeNode<TBB>, durationMs: number, clock?: () => number);
166
+ tick(bb: TBB): TaskStatus;
167
+ /** Force the cooldown to expire immediately. */
168
+ reset(): void;
169
+ /** `true` while the cooldown timer is active. */
170
+ get isOnCooldown(): boolean;
171
+ }
172
+ //# sourceMappingURL=BehaviorTree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BehaviorTree.d.ts","sourceRoot":"","sources":["../../src/ai/BehaviorTree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Behavior Tree — hierarchical AI task composition.
3
+ *
4
+ * Pairs well with GOAP: GOAP decides *what* goal to pursue, the BT decides
5
+ * *how* to execute it step by step. The BT is driven externally by calling
6
+ * `tree.tick(blackboard)` each frame or simulation step.
7
+ *
8
+ * Node types:
9
+ * Composites — Sequence, Selector, Parallel
10
+ * Decorators — Inverter, Repeater, AlwaysSucceed, AlwaysFail, Cooldown
11
+ * Leaves — Task (action), Condition
12
+ *
13
+ * Blackboard:
14
+ * Typed key-value store shared across all nodes in one tick. Nodes read
15
+ * perception data from it and write intermediate results to it.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const bb = new Blackboard({ canSeeTarget: false, ammoCount: 10 });
20
+ *
21
+ * const tree = new Selector([
22
+ * new Sequence([
23
+ * new Condition((bb) => bb.get('canSeeTarget')),
24
+ * new Condition((bb) => bb.get('ammoCount') > 0),
25
+ * new Task((bb) => { shoot(); return 'success'; }),
26
+ * ]),
27
+ * new Task(() => { patrol(); return 'running'; }),
28
+ * ]);
29
+ *
30
+ * // Each frame:
31
+ * tree.tick(bb);
32
+ * ```
33
+ */
34
+ // ---------------------------------------------------------------------------
35
+ // Blackboard
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * Typed key-value store shared across all nodes during a single tick.
39
+ *
40
+ * Initialize it with a plain object; the keys become the allowed set of keys
41
+ * via the generic type parameter.
42
+ */
43
+ export class Blackboard {
44
+ constructor(initial = {}) {
45
+ this.data = new Map(Object.entries(initial));
46
+ }
47
+ get(key) {
48
+ return this.data.get(key);
49
+ }
50
+ set(key, value) {
51
+ this.data.set(key, value);
52
+ }
53
+ has(key) {
54
+ return this.data.has(key);
55
+ }
56
+ delete(key) {
57
+ this.data.delete(key);
58
+ }
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // Leaf nodes
62
+ // ---------------------------------------------------------------------------
63
+ /**
64
+ * Task (action leaf).
65
+ * Runs an arbitrary callback; the callback returns the status directly.
66
+ */
67
+ export class Task {
68
+ constructor(action) {
69
+ this.action = action;
70
+ }
71
+ tick(bb) {
72
+ return this.action(bb);
73
+ }
74
+ }
75
+ /**
76
+ * Condition (boolean leaf).
77
+ * Returns 'success' when the predicate is true, 'failure' otherwise.
78
+ */
79
+ export class Condition {
80
+ constructor(predicate) {
81
+ this.predicate = predicate;
82
+ }
83
+ tick(bb) {
84
+ return this.predicate(bb) ? 'success' : 'failure';
85
+ }
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Composite nodes
89
+ // ---------------------------------------------------------------------------
90
+ /**
91
+ * Sequence — AND gate.
92
+ * Ticks children left-to-right. Returns 'failure' on the first failing child,
93
+ * 'running' on the first running child, 'success' when all succeed.
94
+ */
95
+ export class Sequence {
96
+ constructor(children) {
97
+ this.children = children;
98
+ }
99
+ tick(bb) {
100
+ for (const child of this.children) {
101
+ const status = child.tick(bb);
102
+ if (status !== 'success')
103
+ return status;
104
+ }
105
+ return 'success';
106
+ }
107
+ }
108
+ /**
109
+ * Selector — OR gate.
110
+ * Ticks children left-to-right. Returns 'success' on the first succeeding
111
+ * child, 'running' on the first running child, 'failure' when all fail.
112
+ */
113
+ export class Selector {
114
+ constructor(children) {
115
+ this.children = children;
116
+ }
117
+ tick(bb) {
118
+ for (const child of this.children) {
119
+ const status = child.tick(bb);
120
+ if (status !== 'failure')
121
+ return status;
122
+ }
123
+ return 'failure';
124
+ }
125
+ }
126
+ /**
127
+ * Parallel — ticks ALL children every tick simultaneously.
128
+ *
129
+ * `require-all` (default — AND semantics):
130
+ * - Success: all children succeed.
131
+ * - Failure: any child fails.
132
+ * - Running: otherwise.
133
+ *
134
+ * `require-one` (OR semantics):
135
+ * - Success: at least one child succeeds.
136
+ * - Failure: all children fail.
137
+ * - Running: otherwise (some running, none succeeded yet).
138
+ */
139
+ export class Parallel {
140
+ constructor(children, successPolicy = 'require-all') {
141
+ this.children = children;
142
+ this.successPolicy = successPolicy;
143
+ }
144
+ tick(bb) {
145
+ let successCount = 0;
146
+ let failureCount = 0;
147
+ for (const child of this.children) {
148
+ const status = child.tick(bb);
149
+ if (status === 'success')
150
+ successCount++;
151
+ else if (status === 'failure')
152
+ failureCount++;
153
+ }
154
+ if (this.successPolicy === 'require-all') {
155
+ if (failureCount > 0)
156
+ return 'failure';
157
+ if (successCount === this.children.length)
158
+ return 'success';
159
+ }
160
+ else {
161
+ if (successCount > 0)
162
+ return 'success';
163
+ if (failureCount === this.children.length)
164
+ return 'failure';
165
+ }
166
+ return 'running';
167
+ }
168
+ }
169
+ // ---------------------------------------------------------------------------
170
+ // Decorator nodes
171
+ // ---------------------------------------------------------------------------
172
+ /**
173
+ * Inverter — flips 'success' ↔ 'failure'; passes 'running' unchanged.
174
+ */
175
+ export class Inverter {
176
+ constructor(child) {
177
+ this.child = child;
178
+ }
179
+ tick(bb) {
180
+ const status = this.child.tick(bb);
181
+ if (status === 'success')
182
+ return 'failure';
183
+ if (status === 'failure')
184
+ return 'success';
185
+ return 'running';
186
+ }
187
+ }
188
+ /**
189
+ * AlwaysSucceed — maps any child result to 'success'.
190
+ */
191
+ export class AlwaysSucceed {
192
+ constructor(child) {
193
+ this.child = child;
194
+ }
195
+ tick(bb) {
196
+ this.child.tick(bb);
197
+ return 'success';
198
+ }
199
+ }
200
+ /**
201
+ * AlwaysFail — maps any child result to 'failure'.
202
+ */
203
+ export class AlwaysFail {
204
+ constructor(child) {
205
+ this.child = child;
206
+ }
207
+ tick(bb) {
208
+ this.child.tick(bb);
209
+ return 'failure';
210
+ }
211
+ }
212
+ /**
213
+ * Repeater — ticks its child N times, returning 'success' after all iterations.
214
+ * If the child returns 'failure' the repeater short-circuits with 'failure'.
215
+ * Pass `Infinity` for an endless loop (returns 'running' every tick).
216
+ */
217
+ export class Repeater {
218
+ constructor(child, times) {
219
+ this.child = child;
220
+ this.times = times;
221
+ this.remaining = times;
222
+ }
223
+ tick(bb) {
224
+ if (this.remaining <= 0)
225
+ return 'success';
226
+ const status = this.child.tick(bb);
227
+ if (status === 'failure')
228
+ return 'failure';
229
+ if (status === 'success') {
230
+ this.remaining--;
231
+ if (this.remaining <= 0)
232
+ return 'success';
233
+ }
234
+ return 'running';
235
+ }
236
+ /** Reset the repeat counter to allow reuse. */
237
+ reset() {
238
+ this.remaining = this.times;
239
+ }
240
+ }
241
+ /**
242
+ * Cooldown — blocks its child while a cooldown timer is active.
243
+ *
244
+ * When the cooldown is inactive: ticks the child normally. If the child
245
+ * succeeds, starts the cooldown and returns 'success'. While the cooldown
246
+ * is active: immediately returns 'failure' (the action is on cooldown).
247
+ *
248
+ * `now` defaults to `Date.now` — inject a custom clock for deterministic tests.
249
+ */
250
+ export class Cooldown {
251
+ constructor(child, durationMs, clock = Date.now) {
252
+ this.child = child;
253
+ this.durationMs = durationMs;
254
+ this.clock = clock;
255
+ this.readyAt = 0;
256
+ }
257
+ tick(bb) {
258
+ if (this.clock() < this.readyAt)
259
+ return 'failure';
260
+ const status = this.child.tick(bb);
261
+ if (status === 'success') {
262
+ this.readyAt = this.clock() + this.durationMs;
263
+ }
264
+ return status;
265
+ }
266
+ /** Force the cooldown to expire immediately. */
267
+ reset() {
268
+ this.readyAt = 0;
269
+ }
270
+ /** `true` while the cooldown timer is active. */
271
+ get isOnCooldown() {
272
+ return this.clock() < this.readyAt;
273
+ }
274
+ }
275
+ //# sourceMappingURL=BehaviorTree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BehaviorTree.js","sourceRoot":"","sources":["../../src/ai/BehaviorTree.ts"],"names":[],"mappings":"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"}
@@ -18,13 +18,51 @@ export type TransitionResult = {
18
18
  readonly success: false;
19
19
  readonly reason: 'not_allowed' | 'exit_guard' | 'enter_guard';
20
20
  };
21
+ export interface StateTransitionEvent {
22
+ readonly from: string;
23
+ readonly to: string;
24
+ readonly timestamp: number;
25
+ }
21
26
  export declare class StateMachine {
22
27
  private currentStateId;
28
+ private previousStateId;
29
+ private stateEnterTime;
23
30
  private readonly registry;
24
31
  private readonly entity;
32
+ private readonly enterListeners;
33
+ private readonly exitListeners;
34
+ private readonly changeListeners;
35
+ private readonly historyLog;
25
36
  constructor(entity: IEntity, registry: AIStateRegistry, initialState: string);
26
37
  /** Current active state identifier. */
27
38
  get state(): string;
39
+ /** Previous state identifier, or `null` if no transition has occurred yet. */
40
+ get previous(): string | null;
41
+ /** Milliseconds elapsed since entering the current state. */
42
+ get currentStateDuration(): number;
43
+ /** Returns `true` if the current state has the given tag. */
44
+ hasTag(tag: string): boolean;
45
+ /** Returns the metadata object of the current state, or `undefined`. */
46
+ get metadata(): Readonly<Record<string, unknown>> | undefined;
47
+ /**
48
+ * Subscribe to the moment the FSM enters `state`.
49
+ * @returns Unsubscribe function.
50
+ */
51
+ onEnter(state: string, callback: (from: string | null) => void): () => void;
52
+ /**
53
+ * Subscribe to the moment the FSM exits `state`.
54
+ * @returns Unsubscribe function.
55
+ */
56
+ onExit(state: string, callback: (to: string) => void): () => void;
57
+ /**
58
+ * Subscribe to any state change.
59
+ * @returns Unsubscribe function.
60
+ */
61
+ onChange(callback: (from: string, to: string) => void): () => void;
62
+ /** Returns a snapshot of the transition history (oldest first). */
63
+ getHistory(): readonly StateTransitionEvent[];
64
+ /** Clears the transition history. */
65
+ clearHistory(): void;
28
66
  /**
29
67
  * Force transition to a new state.
30
68
  *
@@ -1 +1 @@
1
- {"version":3,"file":"StateMachine.d.ts","sourceRoot":"","sources":["../../src/ai/StateMachine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,YAAY,GAAG,aAAa,CAAA;CAAE,CAAC;AAE/F,qBAAa,YAAY;IACvB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;gBAErB,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM;IAa5E,uCAAuC;IACvC,IAAI,KAAK,IAAI,MAAM,CAElB;IAMD;;;;;;OAMG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB;IAsB9C;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAkB3B,iEAAiE;IACjE,OAAO,IAAI,IAAI;CAIhB"}
1
+ {"version":3,"file":"StateMachine.d.ts","sourceRoot":"","sources":["../../src/ai/StateMachine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,YAAY,GAAG,aAAa,CAAA;CAAE,CAAC;AAE/F,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IAEjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyD;IACxF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgD;IAC9E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiD;IACjF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA8B;gBAE7C,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM;IAc5E,uCAAuC;IACvC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,8EAA8E;IAC9E,IAAI,QAAQ,IAAI,MAAM,GAAG,IAAI,CAE5B;IAED,6DAA6D;IAC7D,IAAI,oBAAoB,IAAI,MAAM,CAEjC;IAMD,6DAA6D;IAC7D,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAK5B,wEAAwE;IACxE,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAE5D;IAMD;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI;IAM3E;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAMjE;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IASlE,mEAAmE;IACnE,UAAU,IAAI,SAAS,oBAAoB,EAAE;IAI7C,qCAAqC;IACrC,YAAY,IAAI,IAAI;IAQpB;;;;;;OAMG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB;IAwC9C;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAkB3B,iEAAiE;IACjE,OAAO,IAAI,IAAI;CAIhB"}