@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 +23 -4
- package/dist/ai/BehaviorTree.d.ts +172 -0
- package/dist/ai/BehaviorTree.d.ts.map +1 -0
- package/dist/ai/BehaviorTree.js +275 -0
- package/dist/ai/BehaviorTree.js.map +1 -0
- package/dist/ai/StateMachine.d.ts +38 -0
- package/dist/ai/StateMachine.d.ts.map +1 -1
- package/dist/ai/StateMachine.js +80 -0
- package/dist/ai/StateMachine.js.map +1 -1
- package/dist/ai/index.d.ts +3 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +1 -0
- package/dist/ai/index.js.map +1 -1
- package/dist/core/ReactiveQuery.d.ts +85 -0
- package/dist/core/ReactiveQuery.d.ts.map +1 -0
- package/dist/core/ReactiveQuery.js +154 -0
- package/dist/core/ReactiveQuery.js.map +1 -0
- package/dist/entity/EntityHandle.d.ts +76 -0
- package/dist/entity/EntityHandle.d.ts.map +1 -0
- package/dist/entity/EntityHandle.js +133 -0
- package/dist/entity/EntityHandle.js.map +1 -0
- package/dist/entity/index.d.ts +2 -0
- package/dist/entity/index.d.ts.map +1 -1
- package/dist/entity/index.js +1 -1
- package/dist/entity/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/registry/AIStateRegistry.d.ts +4 -0
- package/dist/registry/AIStateRegistry.d.ts.map +1 -1
- package/dist/registry/AIStateRegistry.js.map +1 -1
- package/dist/time/TimeManager.d.ts.map +1 -1
- package/dist/time/TimeManager.js +1 -2
- package/dist/time/TimeManager.js.map +1 -1
- package/package.json +1 -1
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,
|
|
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
|
|
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
|
-
|
|
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;
|
|
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"}
|