@holoscript/core 1.0.0-alpha.1 → 2.0.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/package.json +10 -9
- package/src/HoloScript2DParser.js +227 -0
- package/src/HoloScript2DParser.ts +5 -0
- package/src/HoloScriptCodeParser.js +1102 -0
- package/src/HoloScriptCodeParser.ts +145 -20
- package/src/HoloScriptDebugger.js +458 -0
- package/src/HoloScriptParser.js +338 -0
- package/src/HoloScriptPlusParser.js +371 -0
- package/src/HoloScriptPlusParser.ts +543 -0
- package/src/HoloScriptRuntime.js +1399 -0
- package/src/HoloScriptRuntime.test.js +351 -0
- package/src/HoloScriptRuntime.ts +257 -3
- package/src/HoloScriptTypeChecker.js +356 -0
- package/src/__tests__/GraphicsServices.test.js +357 -0
- package/src/__tests__/GraphicsServices.test.ts +427 -0
- package/src/__tests__/HoloScriptPlusParser.test.js +317 -0
- package/src/__tests__/HoloScriptPlusParser.test.ts +392 -0
- package/src/__tests__/integration.test.js +336 -0
- package/src/__tests__/performance.bench.js +218 -0
- package/src/__tests__/type-checker.test.js +60 -0
- package/src/__tests__/type-checker.test.ts +73 -0
- package/src/index.js +217 -0
- package/src/index.ts +158 -18
- package/src/interop/Interoperability.js +413 -0
- package/src/interop/Interoperability.ts +494 -0
- package/src/logger.js +42 -0
- package/src/parser/EnhancedParser.js +205 -0
- package/src/parser/EnhancedParser.ts +251 -0
- package/src/parser/HoloScriptPlusParser.js +928 -0
- package/src/parser/HoloScriptPlusParser.ts +1089 -0
- package/src/runtime/HoloScriptPlusRuntime.js +674 -0
- package/src/runtime/HoloScriptPlusRuntime.ts +861 -0
- package/src/runtime/PerformanceTelemetry.js +323 -0
- package/src/runtime/PerformanceTelemetry.ts +467 -0
- package/src/runtime/RuntimeOptimization.js +361 -0
- package/src/runtime/RuntimeOptimization.ts +416 -0
- package/src/services/HololandGraphicsPipelineService.js +506 -0
- package/src/services/HololandGraphicsPipelineService.ts +662 -0
- package/src/services/PlatformPerformanceOptimizer.js +356 -0
- package/src/services/PlatformPerformanceOptimizer.ts +503 -0
- package/src/state/ReactiveState.js +427 -0
- package/src/state/ReactiveState.ts +572 -0
- package/src/tools/DeveloperExperience.js +376 -0
- package/src/tools/DeveloperExperience.ts +438 -0
- package/src/traits/AIDriverTrait.js +322 -0
- package/src/traits/AIDriverTrait.test.js +329 -0
- package/src/traits/AIDriverTrait.test.ts +357 -0
- package/src/traits/AIDriverTrait.ts +474 -0
- package/src/traits/LightingTrait.js +313 -0
- package/src/traits/LightingTrait.test.js +410 -0
- package/src/traits/LightingTrait.test.ts +462 -0
- package/src/traits/LightingTrait.ts +505 -0
- package/src/traits/MaterialTrait.js +194 -0
- package/src/traits/MaterialTrait.test.js +286 -0
- package/src/traits/MaterialTrait.test.ts +329 -0
- package/src/traits/MaterialTrait.ts +324 -0
- package/src/traits/RenderingTrait.js +356 -0
- package/src/traits/RenderingTrait.test.js +363 -0
- package/src/traits/RenderingTrait.test.ts +427 -0
- package/src/traits/RenderingTrait.ts +555 -0
- package/src/traits/VRTraitSystem.js +740 -0
- package/src/traits/VRTraitSystem.ts +1040 -0
- package/src/traits/VoiceInputTrait.js +284 -0
- package/src/traits/VoiceInputTrait.test.js +226 -0
- package/src/traits/VoiceInputTrait.test.ts +252 -0
- package/src/traits/VoiceInputTrait.ts +401 -0
- package/src/types/AdvancedTypeSystem.js +226 -0
- package/src/types/AdvancedTypeSystem.ts +494 -0
- package/src/types/HoloScriptPlus.d.ts +853 -0
- package/src/types.js +6 -0
- package/src/types.ts +96 -1
- package/tsconfig.json +1 -1
- package/tsup.config.d.ts +2 -0
- package/tsup.config.js +18 -0
- package/LICENSE +0 -21
- package/dist/chunk-3X2EGU7Z.cjs +0 -52
- package/dist/chunk-3X2EGU7Z.cjs.map +0 -1
- package/dist/chunk-723TPVHD.js +0 -1074
- package/dist/chunk-723TPVHD.js.map +0 -1
- package/dist/chunk-EOKNAVDO.cjs +0 -424
- package/dist/chunk-EOKNAVDO.cjs.map +0 -1
- package/dist/chunk-HQZ3HUMY.js +0 -1087
- package/dist/chunk-HQZ3HUMY.js.map +0 -1
- package/dist/chunk-KWYIVRIH.js +0 -344
- package/dist/chunk-KWYIVRIH.js.map +0 -1
- package/dist/chunk-LKH4ZAN6.js +0 -421
- package/dist/chunk-LKH4ZAN6.js.map +0 -1
- package/dist/chunk-SATNCODL.js +0 -45
- package/dist/chunk-SATNCODL.js.map +0 -1
- package/dist/chunk-VMZN4EVR.cjs +0 -347
- package/dist/chunk-VMZN4EVR.cjs.map +0 -1
- package/dist/chunk-VV3UUUYP.cjs +0 -1089
- package/dist/chunk-VV3UUUYP.cjs.map +0 -1
- package/dist/chunk-XRYTSQHZ.cjs +0 -1076
- package/dist/chunk-XRYTSQHZ.cjs.map +0 -1
- package/dist/debugger.cjs +0 -19
- package/dist/debugger.cjs.map +0 -1
- package/dist/debugger.d.cts +0 -171
- package/dist/debugger.d.ts +0 -171
- package/dist/debugger.js +0 -6
- package/dist/debugger.js.map +0 -1
- package/dist/index.cjs +0 -755
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -169
- package/dist/index.d.ts +0 -169
- package/dist/index.js +0 -699
- package/dist/index.js.map +0 -1
- package/dist/parser.cjs +0 -13
- package/dist/parser.cjs.map +0 -1
- package/dist/parser.d.cts +0 -154
- package/dist/parser.d.ts +0 -154
- package/dist/parser.js +0 -4
- package/dist/parser.js.map +0 -1
- package/dist/runtime.cjs +0 -13
- package/dist/runtime.cjs.map +0 -1
- package/dist/runtime.d.cts +0 -147
- package/dist/runtime.d.ts +0 -147
- package/dist/runtime.js +0 -4
- package/dist/runtime.js.map +0 -1
- package/dist/type-checker.cjs +0 -16
- package/dist/type-checker.cjs.map +0 -1
- package/dist/type-checker.d.cts +0 -105
- package/dist/type-checker.d.ts +0 -105
- package/dist/type-checker.js +0 -3
- package/dist/type-checker.js.map +0 -1
- package/dist/types-WQSk1Qs2.d.cts +0 -238
- package/dist/types-WQSk1Qs2.d.ts +0 -238
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @holoscript/core AI-Driven NPC Trait
|
|
3
|
+
*
|
|
4
|
+
* Enables intelligent NPC behaviors using behavior trees and goal planning
|
|
5
|
+
* Integrates with uaa2-service for agent-based decision making
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Behavior tree runner
|
|
9
|
+
*/
|
|
10
|
+
export class BehaviorTreeRunner {
|
|
11
|
+
constructor(rootNode) {
|
|
12
|
+
this.rootNode = rootNode;
|
|
13
|
+
}
|
|
14
|
+
async tick(context) {
|
|
15
|
+
return this.executeNode(this.rootNode, context);
|
|
16
|
+
}
|
|
17
|
+
async executeNode(node, context) {
|
|
18
|
+
if (node.type === 'action') {
|
|
19
|
+
if (node.action) {
|
|
20
|
+
try {
|
|
21
|
+
return await node.action(context);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error(`Action failed: ${node.id}`, error);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (node.type === 'condition') {
|
|
31
|
+
return node.condition ? node.condition(context) : true;
|
|
32
|
+
}
|
|
33
|
+
if (node.type === 'sequence') {
|
|
34
|
+
for (const child of node.children || []) {
|
|
35
|
+
const result = await this.executeNode(child, context);
|
|
36
|
+
if (!result)
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (node.type === 'selector') {
|
|
42
|
+
for (const child of node.children || []) {
|
|
43
|
+
const result = await this.executeNode(child, context);
|
|
44
|
+
if (result)
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (node.type === 'parallel') {
|
|
50
|
+
const results = await Promise.all((node.children || []).map((child) => this.executeNode(child, context)));
|
|
51
|
+
return results.every((r) => r);
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Goal-Oriented Action Planning (GOAP)
|
|
58
|
+
*/
|
|
59
|
+
export class GOAPPlanner {
|
|
60
|
+
constructor(goals) {
|
|
61
|
+
this.goals = goals.sort((a, b) => b.priority - a.priority);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Plan a sequence of actions to reach goal
|
|
65
|
+
*/
|
|
66
|
+
planGoal(currentState, _goal) {
|
|
67
|
+
// Simple greedy planner: select highest-priority achievable goal
|
|
68
|
+
for (const g of this.goals) {
|
|
69
|
+
if (this.canAchieve(currentState, g)) {
|
|
70
|
+
return [g];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
canAchieve(currentState, goal) {
|
|
76
|
+
for (const [key, value] of goal.preconditions) {
|
|
77
|
+
if (currentState.get(key) !== value) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* AIDriverTrait - Enables intelligent NPC behaviors
|
|
86
|
+
*/
|
|
87
|
+
export class AIDriverTrait {
|
|
88
|
+
constructor(config) {
|
|
89
|
+
this.behaviorRunner = null;
|
|
90
|
+
this.goapPlanner = null;
|
|
91
|
+
this.updateInterval = null;
|
|
92
|
+
this.learningModel = new Map();
|
|
93
|
+
this.config = {
|
|
94
|
+
decisionMode: 'hybrid',
|
|
95
|
+
personality: {
|
|
96
|
+
sociability: 0.5,
|
|
97
|
+
aggression: 0.3,
|
|
98
|
+
curiosity: 0.6,
|
|
99
|
+
loyalty: 0.7,
|
|
100
|
+
},
|
|
101
|
+
stimuliThresholds: {
|
|
102
|
+
hearing: 50,
|
|
103
|
+
sight: 100,
|
|
104
|
+
touch: 5,
|
|
105
|
+
},
|
|
106
|
+
enableLearning: true,
|
|
107
|
+
learningRate: 0.1,
|
|
108
|
+
...config,
|
|
109
|
+
};
|
|
110
|
+
this.context = {
|
|
111
|
+
npcId: config.npcId,
|
|
112
|
+
position: [0, 0, 0],
|
|
113
|
+
rotation: [0, 0, 0],
|
|
114
|
+
memory: new Map(),
|
|
115
|
+
state: 'idle',
|
|
116
|
+
energy: 1.0,
|
|
117
|
+
mood: 0,
|
|
118
|
+
perception: {
|
|
119
|
+
nearbyEntities: [],
|
|
120
|
+
visibleEntities: [],
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
if (config.behaviorTree) {
|
|
124
|
+
this.behaviorRunner = new BehaviorTreeRunner(config.behaviorTree);
|
|
125
|
+
}
|
|
126
|
+
if (config.goals && config.goals.length > 0) {
|
|
127
|
+
this.goapPlanner = new GOAPPlanner(config.goals);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Start NPC AI loop
|
|
132
|
+
*/
|
|
133
|
+
startAI() {
|
|
134
|
+
if (this.updateInterval)
|
|
135
|
+
return;
|
|
136
|
+
this.updateInterval = setInterval(() => {
|
|
137
|
+
this.tick();
|
|
138
|
+
}, 100); // 10 Hz update rate
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Stop NPC AI loop
|
|
142
|
+
*/
|
|
143
|
+
stopAI() {
|
|
144
|
+
if (this.updateInterval) {
|
|
145
|
+
clearInterval(this.updateInterval);
|
|
146
|
+
this.updateInterval = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Main AI tick
|
|
151
|
+
*/
|
|
152
|
+
async tick() {
|
|
153
|
+
// Update energy (decreases over time)
|
|
154
|
+
this.context.energy = Math.max(0, this.context.energy - 0.001);
|
|
155
|
+
// Stress/mood changes
|
|
156
|
+
if (this.context.perception.visibleEntities.length > 0) {
|
|
157
|
+
this.context.mood += 0.1 * (Math.random() - 0.5);
|
|
158
|
+
}
|
|
159
|
+
// Execute appropriate decision mode
|
|
160
|
+
switch (this.config.decisionMode) {
|
|
161
|
+
case 'reactive':
|
|
162
|
+
await this.reactiveDecision();
|
|
163
|
+
break;
|
|
164
|
+
case 'goal-driven':
|
|
165
|
+
await this.goalDrivenDecision();
|
|
166
|
+
break;
|
|
167
|
+
case 'learning':
|
|
168
|
+
await this.learningDecision();
|
|
169
|
+
break;
|
|
170
|
+
case 'hybrid':
|
|
171
|
+
await this.hybridDecision();
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Reactive decision: immediate response to stimuli
|
|
177
|
+
*/
|
|
178
|
+
async reactiveDecision() {
|
|
179
|
+
if (this.behaviorRunner) {
|
|
180
|
+
await this.behaviorRunner.tick(this.context);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Goal-driven decision: plan towards objectives
|
|
185
|
+
*/
|
|
186
|
+
async goalDrivenDecision() {
|
|
187
|
+
if (!this.goapPlanner)
|
|
188
|
+
return;
|
|
189
|
+
const worldState = this.buildWorldState();
|
|
190
|
+
// Select highest priority goal
|
|
191
|
+
const plan = this.goapPlanner.planGoal(worldState, this.config.goals?.[0] || { id: 'idle', name: 'Idle', priority: 0, preconditions: new Map(), effects: new Map(), cost: 0 });
|
|
192
|
+
if (plan.length > 0) {
|
|
193
|
+
// Execute plan
|
|
194
|
+
this.context.state = 'moving';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Learning decision: adapt behavior from experience
|
|
199
|
+
*/
|
|
200
|
+
async learningDecision() {
|
|
201
|
+
// Composite reactive + learning
|
|
202
|
+
await this.reactiveDecision();
|
|
203
|
+
// Learn from interactions
|
|
204
|
+
if (this.config.enableLearning) {
|
|
205
|
+
this.updateLearningModel();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Hybrid decision: combination of reactive and goal-driven
|
|
210
|
+
*/
|
|
211
|
+
async hybridDecision() {
|
|
212
|
+
// Execute behavior tree (reactive)
|
|
213
|
+
if (this.behaviorRunner) {
|
|
214
|
+
const treeResult = await this.behaviorRunner.tick(this.context);
|
|
215
|
+
// If no immediate action, pursue goals
|
|
216
|
+
if (!treeResult && this.goapPlanner) {
|
|
217
|
+
await this.goalDrivenDecision();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Build world state for planning
|
|
223
|
+
*/
|
|
224
|
+
buildWorldState() {
|
|
225
|
+
const state = new Map();
|
|
226
|
+
state.set('position', this.context.position);
|
|
227
|
+
state.set('energy', this.context.energy);
|
|
228
|
+
state.set('mood', this.context.mood);
|
|
229
|
+
state.set('nearbyEntities', this.context.perception.nearbyEntities.length);
|
|
230
|
+
return state;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Update learning model from interactions
|
|
234
|
+
*/
|
|
235
|
+
updateLearningModel() {
|
|
236
|
+
// Simple Q-learning-like update
|
|
237
|
+
const currentReward = this.calculateReward();
|
|
238
|
+
const learningRate = this.config.learningRate || 0.1;
|
|
239
|
+
// Update learned value estimates
|
|
240
|
+
const stateKey = `state_${this.context.state}`;
|
|
241
|
+
const currentValue = this.learningModel.get(stateKey) || 0;
|
|
242
|
+
const newValue = currentValue + learningRate * (currentReward - currentValue);
|
|
243
|
+
this.learningModel.set(stateKey, newValue);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Calculate immediate reward
|
|
247
|
+
*/
|
|
248
|
+
calculateReward() {
|
|
249
|
+
let reward = 0;
|
|
250
|
+
// Reward based on energy maintenance
|
|
251
|
+
if (this.context.energy > 0.5)
|
|
252
|
+
reward += 1;
|
|
253
|
+
// Reward based on social interaction (if sociable)
|
|
254
|
+
if (this.config.personality?.sociability || 0 > 0.5 &&
|
|
255
|
+
this.context.perception.nearbyEntities.length > 0) {
|
|
256
|
+
reward += 1;
|
|
257
|
+
}
|
|
258
|
+
// Reward based on goal progress
|
|
259
|
+
if (this.context.state !== 'idle')
|
|
260
|
+
reward += 0.5;
|
|
261
|
+
return reward;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Set NPC position
|
|
265
|
+
*/
|
|
266
|
+
setPosition(position) {
|
|
267
|
+
this.context.position = position;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Update perception (nearby entities, visible targets)
|
|
271
|
+
*/
|
|
272
|
+
updatePerception(nearbyEntities, visibleEntities) {
|
|
273
|
+
this.context.perception.nearbyEntities = nearbyEntities;
|
|
274
|
+
this.context.perception.visibleEntities = visibleEntities;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Add dialogue to conversation history
|
|
278
|
+
*/
|
|
279
|
+
speak(text) {
|
|
280
|
+
if (!this.context.dialogue) {
|
|
281
|
+
this.context.dialogue = { conversationHistory: [] };
|
|
282
|
+
}
|
|
283
|
+
this.context.dialogue.lastSaid = text;
|
|
284
|
+
this.context.dialogue.conversationHistory.push({
|
|
285
|
+
speaker: this.config.npcId,
|
|
286
|
+
text,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Receive dialogue from another entity
|
|
291
|
+
*/
|
|
292
|
+
hear(speaker, text) {
|
|
293
|
+
if (!this.context.dialogue) {
|
|
294
|
+
this.context.dialogue = { conversationHistory: [] };
|
|
295
|
+
}
|
|
296
|
+
this.context.dialogue.lastHeard = text;
|
|
297
|
+
this.context.dialogue.conversationHistory.push({
|
|
298
|
+
speaker,
|
|
299
|
+
text,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Get current NPC context
|
|
304
|
+
*/
|
|
305
|
+
getContext() {
|
|
306
|
+
return { ...this.context };
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Dispose and cleanup
|
|
310
|
+
*/
|
|
311
|
+
dispose() {
|
|
312
|
+
this.stopAI();
|
|
313
|
+
this.context.memory.clear();
|
|
314
|
+
this.learningModel.clear();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* HoloScript+ @ai_driven trait factory
|
|
319
|
+
*/
|
|
320
|
+
export function createAIDriverTrait(config) {
|
|
321
|
+
return new AIDriverTrait(config);
|
|
322
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIDriverTrait Tests
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive tests for AI-driven NPC behavior
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
7
|
+
import { AIDriverTrait } from '../traits/AIDriverTrait';
|
|
8
|
+
describe('AIDriverTrait', () => {
|
|
9
|
+
let trait;
|
|
10
|
+
let config;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
config = {
|
|
13
|
+
npcId: 'npc-001',
|
|
14
|
+
decisionMode: 'hybrid',
|
|
15
|
+
personality: {
|
|
16
|
+
sociability: 0.8,
|
|
17
|
+
aggression: 0.2,
|
|
18
|
+
curiosity: 0.9,
|
|
19
|
+
loyalty: 0.7,
|
|
20
|
+
},
|
|
21
|
+
stimuliThresholds: {
|
|
22
|
+
hearing: 50,
|
|
23
|
+
sight: 100,
|
|
24
|
+
touch: 5,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
trait = new AIDriverTrait(config);
|
|
28
|
+
});
|
|
29
|
+
describe('Initialization', () => {
|
|
30
|
+
it('should initialize with config', () => {
|
|
31
|
+
expect(trait).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
it('should set NPC ID', () => {
|
|
34
|
+
expect(trait).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
it('should accept decision modes', () => {
|
|
37
|
+
const modes = ['reactive', 'goal-driven', 'learning', 'hybrid'];
|
|
38
|
+
for (const mode of modes) {
|
|
39
|
+
const modeConfig = { ...config, decisionMode: mode };
|
|
40
|
+
const modeTrait = new AIDriverTrait(modeConfig);
|
|
41
|
+
expect(modeTrait).toBeDefined();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('Personality Configuration', () => {
|
|
46
|
+
it('should accept personality traits', () => {
|
|
47
|
+
const personalityConfig = {
|
|
48
|
+
npcId: 'npc-friendly',
|
|
49
|
+
decisionMode: 'hybrid',
|
|
50
|
+
personality: {
|
|
51
|
+
sociability: 1.0,
|
|
52
|
+
aggression: 0.0,
|
|
53
|
+
curiosity: 0.5,
|
|
54
|
+
loyalty: 1.0,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
const friendlyNPC = new AIDriverTrait(personalityConfig);
|
|
58
|
+
expect(friendlyNPC).toBeDefined();
|
|
59
|
+
});
|
|
60
|
+
it('should have default personality if not specified', () => {
|
|
61
|
+
const minimalConfig = {
|
|
62
|
+
npcId: 'npc-default',
|
|
63
|
+
decisionMode: 'reactive',
|
|
64
|
+
};
|
|
65
|
+
const defaultNPC = new AIDriverTrait(minimalConfig);
|
|
66
|
+
expect(defaultNPC).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
it('should validate personality values 0-1', () => {
|
|
69
|
+
const validConfig = {
|
|
70
|
+
npcId: 'npc-valid',
|
|
71
|
+
decisionMode: 'hybrid',
|
|
72
|
+
personality: {
|
|
73
|
+
sociability: 0.5,
|
|
74
|
+
aggression: 0.5,
|
|
75
|
+
curiosity: 0.5,
|
|
76
|
+
loyalty: 0.5,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
const validNPC = new AIDriverTrait(validConfig);
|
|
80
|
+
expect(validNPC).toBeDefined();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('Stimuli Perception', () => {
|
|
84
|
+
it('should support custom stimulus thresholds', () => {
|
|
85
|
+
const customConfig = {
|
|
86
|
+
npcId: 'npc-sharp-senses',
|
|
87
|
+
decisionMode: 'reactive',
|
|
88
|
+
stimuliThresholds: {
|
|
89
|
+
hearing: 200,
|
|
90
|
+
sight: 300,
|
|
91
|
+
touch: 10,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
const sensitivNPC = new AIDriverTrait(customConfig);
|
|
95
|
+
expect(sensitivNPC).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
it('should have default thresholds if not specified', () => {
|
|
98
|
+
const minimalConfig = {
|
|
99
|
+
npcId: 'npc-minimal',
|
|
100
|
+
decisionMode: 'reactive',
|
|
101
|
+
};
|
|
102
|
+
const defaultNPC = new AIDriverTrait(minimalConfig);
|
|
103
|
+
expect(defaultNPC).toBeDefined();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('Behavior Trees', () => {
|
|
107
|
+
it('should accept behavior tree configuration', () => {
|
|
108
|
+
const behaviorTree = {
|
|
109
|
+
id: 'root',
|
|
110
|
+
type: 'selector',
|
|
111
|
+
children: [
|
|
112
|
+
{
|
|
113
|
+
id: 'idle',
|
|
114
|
+
type: 'action',
|
|
115
|
+
action: async () => true,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
const behaviorConfig = {
|
|
120
|
+
npcId: 'npc-behavioral',
|
|
121
|
+
decisionMode: 'reactive',
|
|
122
|
+
behaviorTree,
|
|
123
|
+
};
|
|
124
|
+
const behaviorNPC = new AIDriverTrait(behaviorConfig);
|
|
125
|
+
expect(behaviorNPC).toBeDefined();
|
|
126
|
+
});
|
|
127
|
+
it('should support sequence nodes', () => {
|
|
128
|
+
const sequenceTree = {
|
|
129
|
+
id: 'sequence',
|
|
130
|
+
type: 'sequence',
|
|
131
|
+
children: [
|
|
132
|
+
{ id: 'step1', type: 'action', action: async () => true },
|
|
133
|
+
{ id: 'step2', type: 'action', action: async () => true },
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
expect(sequenceTree).toBeDefined();
|
|
137
|
+
});
|
|
138
|
+
it('should support selector nodes', () => {
|
|
139
|
+
const selectorTree = {
|
|
140
|
+
id: 'selector',
|
|
141
|
+
type: 'selector',
|
|
142
|
+
children: [
|
|
143
|
+
{ id: 'option1', type: 'action', action: async () => false },
|
|
144
|
+
{ id: 'option2', type: 'action', action: async () => true },
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
expect(selectorTree).toBeDefined();
|
|
148
|
+
});
|
|
149
|
+
it('should support parallel nodes', () => {
|
|
150
|
+
const parallelTree = {
|
|
151
|
+
id: 'parallel',
|
|
152
|
+
type: 'parallel',
|
|
153
|
+
children: [
|
|
154
|
+
{ id: 'task1', type: 'action', action: async () => true },
|
|
155
|
+
{ id: 'task2', type: 'action', action: async () => true },
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
expect(parallelTree).toBeDefined();
|
|
159
|
+
});
|
|
160
|
+
it('should support condition nodes', () => {
|
|
161
|
+
const conditionTree = {
|
|
162
|
+
id: 'condition',
|
|
163
|
+
type: 'condition',
|
|
164
|
+
condition: () => true,
|
|
165
|
+
};
|
|
166
|
+
expect(conditionTree).toBeDefined();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe('Goal Planning (GOAP)', () => {
|
|
170
|
+
it('should accept goals configuration', () => {
|
|
171
|
+
const goals = [
|
|
172
|
+
{
|
|
173
|
+
id: 'explore',
|
|
174
|
+
name: 'Explore Area',
|
|
175
|
+
priority: 0.5,
|
|
176
|
+
preconditions: new Map([['energy', 50]]),
|
|
177
|
+
effects: new Map([['discovered', true]]),
|
|
178
|
+
cost: 10,
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
const goalConfig = {
|
|
182
|
+
npcId: 'npc-planner',
|
|
183
|
+
decisionMode: 'goal-driven',
|
|
184
|
+
goals,
|
|
185
|
+
};
|
|
186
|
+
const plannerNPC = new AIDriverTrait(goalConfig);
|
|
187
|
+
expect(plannerNPC).toBeDefined();
|
|
188
|
+
});
|
|
189
|
+
it('should support multiple goals with priorities', () => {
|
|
190
|
+
const goals = [
|
|
191
|
+
{
|
|
192
|
+
id: 'survive',
|
|
193
|
+
name: 'Survive',
|
|
194
|
+
priority: 1.0,
|
|
195
|
+
preconditions: new Map(),
|
|
196
|
+
effects: new Map([['alive', true]]),
|
|
197
|
+
cost: 5,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'succeed',
|
|
201
|
+
name: 'Succeed',
|
|
202
|
+
priority: 0.8,
|
|
203
|
+
preconditions: new Map([['alive', true]]),
|
|
204
|
+
effects: new Map([['succeeded', true]]),
|
|
205
|
+
cost: 20,
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
expect(goals.length).toBe(2);
|
|
209
|
+
});
|
|
210
|
+
it('should support goal timeouts', () => {
|
|
211
|
+
const timedGoal = {
|
|
212
|
+
id: 'escape',
|
|
213
|
+
name: 'Escape Danger',
|
|
214
|
+
priority: 0.9,
|
|
215
|
+
preconditions: new Map([['threatened', true]]),
|
|
216
|
+
effects: new Map([['safe', true]]),
|
|
217
|
+
cost: 15,
|
|
218
|
+
timeoutMs: 5000,
|
|
219
|
+
};
|
|
220
|
+
expect(timedGoal.timeoutMs).toBe(5000);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
describe('Learning Configuration', () => {
|
|
224
|
+
it('should support learning mode', () => {
|
|
225
|
+
const learningConfig = {
|
|
226
|
+
npcId: 'npc-learner',
|
|
227
|
+
decisionMode: 'learning',
|
|
228
|
+
enableLearning: true,
|
|
229
|
+
learningRate: 0.1,
|
|
230
|
+
};
|
|
231
|
+
const learnerNPC = new AIDriverTrait(learningConfig);
|
|
232
|
+
expect(learnerNPC).toBeDefined();
|
|
233
|
+
});
|
|
234
|
+
it('should allow disabling learning', () => {
|
|
235
|
+
const noLearningConfig = {
|
|
236
|
+
npcId: 'npc-fixed',
|
|
237
|
+
decisionMode: 'reactive',
|
|
238
|
+
enableLearning: false,
|
|
239
|
+
};
|
|
240
|
+
const staticNPC = new AIDriverTrait(noLearningConfig);
|
|
241
|
+
expect(staticNPC).toBeDefined();
|
|
242
|
+
});
|
|
243
|
+
it('should accept custom learning rate', () => {
|
|
244
|
+
const fastLearning = {
|
|
245
|
+
npcId: 'npc-fast',
|
|
246
|
+
decisionMode: 'learning',
|
|
247
|
+
enableLearning: true,
|
|
248
|
+
learningRate: 0.5,
|
|
249
|
+
};
|
|
250
|
+
const slowLearning = {
|
|
251
|
+
npcId: 'npc-slow',
|
|
252
|
+
decisionMode: 'learning',
|
|
253
|
+
enableLearning: true,
|
|
254
|
+
learningRate: 0.01,
|
|
255
|
+
};
|
|
256
|
+
expect(fastLearning.learningRate).toBeGreaterThan(slowLearning.learningRate);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
describe('uaa2 Agent Integration', () => {
|
|
260
|
+
it('should accept uaa2 agent ID', () => {
|
|
261
|
+
const uaa2Config = {
|
|
262
|
+
npcId: 'npc-integrated',
|
|
263
|
+
decisionMode: 'hybrid',
|
|
264
|
+
agentId: 'agent-001',
|
|
265
|
+
};
|
|
266
|
+
const integratedNPC = new AIDriverTrait(uaa2Config);
|
|
267
|
+
expect(integratedNPC).toBeDefined();
|
|
268
|
+
});
|
|
269
|
+
it('should work without uaa2 integration', () => {
|
|
270
|
+
const standaloneConfig = {
|
|
271
|
+
npcId: 'npc-standalone',
|
|
272
|
+
decisionMode: 'reactive',
|
|
273
|
+
};
|
|
274
|
+
const standaloneNPC = new AIDriverTrait(standaloneConfig);
|
|
275
|
+
expect(standaloneNPC).toBeDefined();
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
describe('Decision Modes', () => {
|
|
279
|
+
it('should support reactive decision making', () => {
|
|
280
|
+
const reactiveConfig = {
|
|
281
|
+
npcId: 'npc-reactive',
|
|
282
|
+
decisionMode: 'reactive',
|
|
283
|
+
};
|
|
284
|
+
const reactiveNPC = new AIDriverTrait(reactiveConfig);
|
|
285
|
+
expect(reactiveNPC).toBeDefined();
|
|
286
|
+
});
|
|
287
|
+
it('should support goal-driven decision making', () => {
|
|
288
|
+
const goalConfig = {
|
|
289
|
+
npcId: 'npc-goal',
|
|
290
|
+
decisionMode: 'goal-driven',
|
|
291
|
+
goals: [
|
|
292
|
+
{
|
|
293
|
+
id: 'goal1',
|
|
294
|
+
name: 'Goal 1',
|
|
295
|
+
priority: 0.8,
|
|
296
|
+
preconditions: new Map(),
|
|
297
|
+
effects: new Map(),
|
|
298
|
+
cost: 10,
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
const goalNPC = new AIDriverTrait(goalConfig);
|
|
303
|
+
expect(goalNPC).toBeDefined();
|
|
304
|
+
});
|
|
305
|
+
it('should support learning decision making', () => {
|
|
306
|
+
const learningConfig = {
|
|
307
|
+
npcId: 'npc-learning',
|
|
308
|
+
decisionMode: 'learning',
|
|
309
|
+
enableLearning: true,
|
|
310
|
+
};
|
|
311
|
+
const learningNPC = new AIDriverTrait(learningConfig);
|
|
312
|
+
expect(learningNPC).toBeDefined();
|
|
313
|
+
});
|
|
314
|
+
it('should support hybrid decision making', () => {
|
|
315
|
+
const hybridConfig = {
|
|
316
|
+
npcId: 'npc-hybrid',
|
|
317
|
+
decisionMode: 'hybrid',
|
|
318
|
+
behaviorTree: {
|
|
319
|
+
id: 'root',
|
|
320
|
+
type: 'selector',
|
|
321
|
+
},
|
|
322
|
+
goals: [],
|
|
323
|
+
enableLearning: true,
|
|
324
|
+
};
|
|
325
|
+
const hybridNPC = new AIDriverTrait(hybridConfig);
|
|
326
|
+
expect(hybridNPC).toBeDefined();
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
});
|