@holoscript/framework 6.0.3
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/ALL-test-results.json +1 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/ROADMAP.md +175 -0
- package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
- package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
- package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
- package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
- package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
- package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
- package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
- package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
- package/dist/agents/index.cjs +4790 -0
- package/dist/agents/index.d.cts +1788 -0
- package/dist/agents/index.d.ts +1788 -0
- package/dist/agents/index.js +4695 -0
- package/dist/ai/index.cjs +5347 -0
- package/dist/ai/index.d.cts +1753 -0
- package/dist/ai/index.d.ts +1753 -0
- package/dist/ai/index.js +5244 -0
- package/dist/behavior.cjs +449 -0
- package/dist/behavior.d.cts +130 -0
- package/dist/behavior.d.ts +130 -0
- package/dist/behavior.js +407 -0
- package/dist/economy/index.cjs +3659 -0
- package/dist/economy/index.d.cts +747 -0
- package/dist/economy/index.d.ts +747 -0
- package/dist/economy/index.js +3617 -0
- package/dist/implementations-D9T3un9D.d.cts +236 -0
- package/dist/implementations-D9T3un9D.d.ts +236 -0
- package/dist/index.cjs +24550 -0
- package/dist/index.d.cts +1729 -0
- package/dist/index.d.ts +1729 -0
- package/dist/index.js +24277 -0
- package/dist/learning/index.cjs +219 -0
- package/dist/learning/index.d.cts +104 -0
- package/dist/learning/index.d.ts +104 -0
- package/dist/learning/index.js +189 -0
- package/dist/negotiation/index.cjs +970 -0
- package/dist/negotiation/index.d.cts +610 -0
- package/dist/negotiation/index.d.ts +610 -0
- package/dist/negotiation/index.js +931 -0
- package/dist/skills/index.cjs +1118 -0
- package/dist/skills/index.d.cts +289 -0
- package/dist/skills/index.d.ts +289 -0
- package/dist/skills/index.js +1079 -0
- package/dist/swarm/index.cjs +5268 -0
- package/dist/swarm/index.d.cts +2433 -0
- package/dist/swarm/index.d.ts +2433 -0
- package/dist/swarm/index.js +5221 -0
- package/dist/training/index.cjs +2745 -0
- package/dist/training/index.d.cts +1734 -0
- package/dist/training/index.d.ts +1734 -0
- package/dist/training/index.js +2687 -0
- package/extract-failures.js +10 -0
- package/package.json +82 -0
- package/src/__tests__/bounty-marketplace.test.ts +374 -0
- package/src/__tests__/delegation.test.ts +144 -0
- package/src/__tests__/distributed-claimer.test.ts +147 -0
- package/src/__tests__/done-log-audit.test.ts +342 -0
- package/src/__tests__/framework.test.ts +865 -0
- package/src/__tests__/goal-synthesizer.test.ts +236 -0
- package/src/__tests__/presence.test.ts +223 -0
- package/src/__tests__/protocol-agent.test.ts +254 -0
- package/src/__tests__/revenue-splitter.test.ts +114 -0
- package/src/__tests__/scenario-driven-todo.test.ts +197 -0
- package/src/__tests__/self-improve.test.ts +349 -0
- package/src/__tests__/service-lifecycle.test.ts +237 -0
- package/src/__tests__/skill-router.test.ts +121 -0
- package/src/agents/AgentManifest.ts +493 -0
- package/src/agents/AgentRegistry.ts +475 -0
- package/src/agents/AgentTypes.ts +585 -0
- package/src/agents/AgentWalletRegistry.ts +83 -0
- package/src/agents/AuthenticatedCRDT.ts +388 -0
- package/src/agents/CapabilityMatcher.ts +453 -0
- package/src/agents/CrossRealityHandoff.ts +305 -0
- package/src/agents/CulturalMemory.ts +454 -0
- package/src/agents/FederatedRegistryAdapter.ts +429 -0
- package/src/agents/NormEngine.ts +450 -0
- package/src/agents/OrchestratorAgent.ts +414 -0
- package/src/agents/SkillWorkflowEngine.ts +472 -0
- package/src/agents/TaskDelegationService.ts +551 -0
- package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
- package/src/agents/__tests__/AgentManifest.test.ts +182 -0
- package/src/agents/__tests__/AgentModule.test.ts +864 -0
- package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
- package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
- package/src/agents/__tests__/AgentTypes.test.ts +534 -0
- package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
- package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
- package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
- package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
- package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
- package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
- package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
- package/src/agents/__tests__/NormEngine.test.ts +276 -0
- package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
- package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
- package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
- package/src/agents/index.ts +107 -0
- package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
- package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
- package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
- package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
- package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
- package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
- package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
- package/src/agents/spatial-comms/index.ts +66 -0
- package/src/ai/AIAdapter.ts +313 -0
- package/src/ai/AICopilot.ts +331 -0
- package/src/ai/AIOutputValidator.ts +203 -0
- package/src/ai/BTNodes.ts +239 -0
- package/src/ai/BehaviorSelector.ts +135 -0
- package/src/ai/BehaviorTree.ts +153 -0
- package/src/ai/Blackboard.ts +165 -0
- package/src/ai/GenerationAnalytics.ts +461 -0
- package/src/ai/GenerationCache.ts +265 -0
- package/src/ai/GoalPlanner.ts +165 -0
- package/src/ai/HoloScriptGenerator.ts +580 -0
- package/src/ai/InfluenceMap.ts +180 -0
- package/src/ai/NavMesh.ts +168 -0
- package/src/ai/PerceptionSystem.ts +178 -0
- package/src/ai/PromptTemplates.ts +453 -0
- package/src/ai/SemanticSearchService.ts +80 -0
- package/src/ai/StateMachine.ts +196 -0
- package/src/ai/SteeringBehavior.ts +150 -0
- package/src/ai/SteeringBehaviors.ts +244 -0
- package/src/ai/TrainingDataGenerator.ts +1082 -0
- package/src/ai/UtilityAI.ts +145 -0
- package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
- package/src/ai/__tests__/AIAdapter.test.ts +109 -0
- package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
- package/src/ai/__tests__/AICopilot.test.ts +178 -0
- package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
- package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
- package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
- package/src/ai/__tests__/BTNodes.test.ts +263 -0
- package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
- package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
- package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
- package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
- package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
- package/src/ai/__tests__/Blackboard.test.ts +183 -0
- package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
- package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
- package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
- package/src/ai/__tests__/GenerationCache.test.ts +171 -0
- package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
- package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
- package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
- package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
- package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
- package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
- package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
- package/src/ai/__tests__/NavMesh.test.ts +159 -0
- package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
- package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
- package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
- package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
- package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
- package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
- package/src/ai/__tests__/StateMachine.test.ts +163 -0
- package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
- package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
- package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
- package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
- package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
- package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
- package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
- package/src/ai/__tests__/UtilityAI.test.ts +155 -0
- package/src/ai/__tests__/adapters.prod.test.ts +263 -0
- package/src/ai/__tests__/adapters.test.ts +320 -0
- package/src/ai/adapters.ts +1585 -0
- package/src/ai/index.ts +130 -0
- package/src/behavior/BehaviorPresets.ts +140 -0
- package/src/behavior/BehaviorTree.ts +236 -0
- package/src/behavior/StateMachine.ts +176 -0
- package/src/behavior/StateTrait.ts +67 -0
- package/src/behavior/index.ts +8 -0
- package/src/behavior.ts +8 -0
- package/src/board/audit.ts +284 -0
- package/src/board/board-ops.ts +336 -0
- package/src/board/board-types.ts +302 -0
- package/src/board/index.ts +69 -0
- package/src/define-agent.ts +46 -0
- package/src/define-team.ts +33 -0
- package/src/delegation.ts +265 -0
- package/src/distributed-claimer.ts +228 -0
- package/src/economy/AgentBudgetEnforcer.ts +464 -0
- package/src/economy/BountyManager.ts +185 -0
- package/src/economy/CreatorRevenueAggregator.ts +460 -0
- package/src/economy/InvisibleWallet.ts +82 -0
- package/src/economy/KnowledgeMarketplace.ts +193 -0
- package/src/economy/PaymentWebhookService.ts +512 -0
- package/src/economy/RevenueSplitter.ts +156 -0
- package/src/economy/SubscriptionManager.ts +546 -0
- package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
- package/src/economy/UsageMeter.ts +440 -0
- package/src/economy/_core-stubs.ts +219 -0
- package/src/economy/index.ts +100 -0
- package/src/economy/x402-facilitator.ts +1978 -0
- package/src/index.ts +348 -0
- package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
- package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
- package/src/knowledge/brain.ts +167 -0
- package/src/knowledge/consolidation.ts +581 -0
- package/src/knowledge/knowledge-consolidator.ts +510 -0
- package/src/knowledge/knowledge-store.ts +616 -0
- package/src/learning/MemoryConsolidator.ts +102 -0
- package/src/learning/MemoryScorer.ts +69 -0
- package/src/learning/ProceduralCompiler.ts +45 -0
- package/src/learning/SemanticClusterer.ts +66 -0
- package/src/learning/index.ts +8 -0
- package/src/llm/llm-adapter.ts +159 -0
- package/src/mesh/index.ts +309 -0
- package/src/negotiation/NegotiationProtocol.ts +694 -0
- package/src/negotiation/NegotiationTypes.ts +473 -0
- package/src/negotiation/VotingMechanisms.ts +691 -0
- package/src/negotiation/index.ts +49 -0
- package/src/protocol/goal-synthesizer.ts +317 -0
- package/src/protocol/implementations.ts +474 -0
- package/src/protocol/micro-phase-decomposer.ts +299 -0
- package/src/protocol/micro-step-decomposer.test.ts +306 -0
- package/src/protocol-agent.test.ts +353 -0
- package/src/protocol-agent.ts +670 -0
- package/src/self-improve/absorb-scanner.ts +252 -0
- package/src/self-improve/evolution-engine.ts +149 -0
- package/src/self-improve/framework-absorber.ts +214 -0
- package/src/self-improve/index.ts +50 -0
- package/src/self-improve/prompt-optimizer.ts +212 -0
- package/src/self-improve/test-generator.ts +175 -0
- package/src/skill-router.ts +186 -0
- package/src/skills/index.ts +5 -0
- package/src/skills/skill-md-bridge.ts +1699 -0
- package/src/swarm/ACOEngine.ts +261 -0
- package/src/swarm/CollectiveIntelligence.ts +383 -0
- package/src/swarm/ContributionSynthesizer.ts +481 -0
- package/src/swarm/LeaderElection.ts +393 -0
- package/src/swarm/PSOEngine.ts +206 -0
- package/src/swarm/QuorumPolicy.ts +173 -0
- package/src/swarm/SwarmCoordinator.ts +335 -0
- package/src/swarm/SwarmManager.ts +442 -0
- package/src/swarm/SwarmMembership.ts +456 -0
- package/src/swarm/VotingRound.ts +255 -0
- package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
- package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
- package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
- package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
- package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
- package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
- package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
- package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
- package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
- package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
- package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
- package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
- package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
- package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
- package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
- package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
- package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
- package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
- package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
- package/src/swarm/__tests__/VotingRound.test.ts +174 -0
- package/src/swarm/analytics/SwarmInspector.ts +476 -0
- package/src/swarm/analytics/SwarmMetrics.ts +449 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
- package/src/swarm/analytics/index.ts +7 -0
- package/src/swarm/index.ts +69 -0
- package/src/swarm/messaging/BroadcastChannel.ts +509 -0
- package/src/swarm/messaging/GossipProtocol.ts +565 -0
- package/src/swarm/messaging/SwarmEventBus.ts +443 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
- package/src/swarm/messaging/index.ts +8 -0
- package/src/swarm/spatial/FlockingBehavior.ts +462 -0
- package/src/swarm/spatial/FormationController.ts +500 -0
- package/src/swarm/spatial/Vector3.ts +170 -0
- package/src/swarm/spatial/ZoneClaiming.ts +509 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
- package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
- package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
- package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
- package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
- package/src/swarm/spatial/index.ts +28 -0
- package/src/team.ts +1245 -0
- package/src/training/LRScheduler.ts +377 -0
- package/src/training/QualityScoringPipeline.ts +139 -0
- package/src/training/SoftDedup.ts +461 -0
- package/src/training/SparsityMonitor.ts +685 -0
- package/src/training/SparsityMonitorTypes.ts +209 -0
- package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
- package/src/training/SpatialTrainingDataTypes.ts +216 -0
- package/src/training/TrainingPipelineConfig.ts +215 -0
- package/src/training/constants.ts +94 -0
- package/src/training/index.ts +138 -0
- package/src/training/schema.ts +147 -0
- package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
- package/src/training/scripts/generate-spatial-dataset.ts +521 -0
- package/src/training/training/data/novel-use-cases.jsonl +153 -0
- package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
- package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
- package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
- package/src/training/trainingmonkey/index.ts +26 -0
- package/src/training/trait-mappings.ts +157 -0
- package/src/types/core-stubs.d.ts +113 -0
- package/src/types.ts +304 -0
- package/test-output.txt +0 -0
- package/test-result.json +1 -0
- package/tsc-errors.txt +4 -0
- package/tsc_output.txt +0 -0
- package/tsconfig.json +14 -0
- package/tsup-learning-esm.config.ts +12 -0
- package/tsup.config.ts +21 -0
- package/typescript-errors-2.txt +0 -0
- package/typescript-errors.txt +22 -0
- package/vitest-log-utf8.txt +268 -0
- package/vitest-log.txt +0 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { GoalPlanner, type WorldState, type PlanAction, type Goal } from '../GoalPlanner';
|
|
3
|
+
|
|
4
|
+
// ─── helpers ────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
function makeAction(
|
|
7
|
+
id: string,
|
|
8
|
+
cost: number,
|
|
9
|
+
pre: Record<string, boolean>,
|
|
10
|
+
effects: Record<string, boolean>,
|
|
11
|
+
execute = vi.fn()
|
|
12
|
+
): PlanAction {
|
|
13
|
+
return {
|
|
14
|
+
id,
|
|
15
|
+
name: id,
|
|
16
|
+
cost,
|
|
17
|
+
preconditions: new Map(Object.entries(pre)),
|
|
18
|
+
effects: new Map(Object.entries(effects)),
|
|
19
|
+
execute,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeGoal(id: string, priority: number, conditions: Record<string, boolean>): Goal {
|
|
24
|
+
return { id, name: id, priority, conditions: new Map(Object.entries(conditions)) };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeState(entries: Record<string, boolean>): WorldState {
|
|
28
|
+
return new Map(Object.entries(entries));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── tests ───────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
describe('GoalPlanner — registration', () => {
|
|
34
|
+
it('getActionCount starts at 0', () => expect(new GoalPlanner().getActionCount()).toBe(0));
|
|
35
|
+
it('getGoalCount starts at 0', () => expect(new GoalPlanner().getGoalCount()).toBe(0));
|
|
36
|
+
it('addAction increments count', () => {
|
|
37
|
+
const gp = new GoalPlanner();
|
|
38
|
+
gp.addAction(makeAction('a', 1, {}, {}));
|
|
39
|
+
expect(gp.getActionCount()).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
it('addGoal increments count', () => {
|
|
42
|
+
const gp = new GoalPlanner();
|
|
43
|
+
gp.addGoal(makeGoal('g', 1, {}));
|
|
44
|
+
expect(gp.getGoalCount()).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
it('removeAction decrements count', () => {
|
|
47
|
+
const gp = new GoalPlanner();
|
|
48
|
+
gp.addAction(makeAction('act1', 1, {}, {}));
|
|
49
|
+
gp.removeAction('act1');
|
|
50
|
+
expect(gp.getActionCount()).toBe(0);
|
|
51
|
+
});
|
|
52
|
+
it('removeGoal decrements count', () => {
|
|
53
|
+
const gp = new GoalPlanner();
|
|
54
|
+
gp.addGoal(makeGoal('g1', 1, {}));
|
|
55
|
+
gp.removeGoal('g1');
|
|
56
|
+
expect(gp.getGoalCount()).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
it('addAction multiple keeps all', () => {
|
|
59
|
+
const gp = new GoalPlanner();
|
|
60
|
+
gp.addAction(makeAction('a', 1, {}, {}));
|
|
61
|
+
gp.addAction(makeAction('b', 1, {}, {}));
|
|
62
|
+
expect(gp.getActionCount()).toBe(2);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('GoalPlanner — plan() no goals/actions', () => {
|
|
67
|
+
it('returns null when no goals', () => {
|
|
68
|
+
const gp = new GoalPlanner();
|
|
69
|
+
expect(gp.plan(makeState({}))).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
it('returns null when goal already satisfied', () => {
|
|
72
|
+
const gp = new GoalPlanner();
|
|
73
|
+
gp.addGoal(makeGoal('g', 10, { done: true }));
|
|
74
|
+
// Already satisfied — returns a plan with 0 actions
|
|
75
|
+
const plan = gp.plan(makeState({ done: true }));
|
|
76
|
+
// Implementation may return a plan (empty) or null — either is valid
|
|
77
|
+
if (plan !== null) {
|
|
78
|
+
expect(plan.actions).toHaveLength(0);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
it('returns null when no path to goal', () => {
|
|
82
|
+
const gp = new GoalPlanner();
|
|
83
|
+
gp.addGoal(makeGoal('g', 10, { impossible: true }));
|
|
84
|
+
// No actions available to achieve it
|
|
85
|
+
expect(gp.plan(makeState({}))).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('GoalPlanner — single-action plan', () => {
|
|
90
|
+
it('finds plan for single-step goal', () => {
|
|
91
|
+
const gp = new GoalPlanner();
|
|
92
|
+
gp.addAction(makeAction('unlock', 1, { hasKey: true }, { doorOpen: true }));
|
|
93
|
+
gp.addGoal(makeGoal('openDoor', 10, { doorOpen: true }));
|
|
94
|
+
const plan = gp.plan(makeState({ hasKey: true, doorOpen: false }));
|
|
95
|
+
expect(plan).not.toBeNull();
|
|
96
|
+
expect(plan!.actions).toHaveLength(1);
|
|
97
|
+
expect(plan!.actions[0].id).toBe('unlock');
|
|
98
|
+
});
|
|
99
|
+
it('plan carries correct goalId', () => {
|
|
100
|
+
const gp = new GoalPlanner();
|
|
101
|
+
gp.addAction(makeAction('act', 1, {}, { x: true }));
|
|
102
|
+
gp.addGoal(makeGoal('myGoal', 5, { x: true }));
|
|
103
|
+
const plan = gp.plan(makeState({}));
|
|
104
|
+
expect(plan!.goalId).toBe('myGoal');
|
|
105
|
+
});
|
|
106
|
+
it('plan carries correct totalCost', () => {
|
|
107
|
+
const gp = new GoalPlanner();
|
|
108
|
+
gp.addAction(makeAction('act', 7, {}, { x: true }));
|
|
109
|
+
gp.addGoal(makeGoal('g', 5, { x: true }));
|
|
110
|
+
const plan = gp.plan(makeState({}));
|
|
111
|
+
expect(plan!.totalCost).toBe(7);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('GoalPlanner — multi-action plan', () => {
|
|
116
|
+
it('chains two actions', () => {
|
|
117
|
+
const gp = new GoalPlanner();
|
|
118
|
+
gp.addAction(makeAction('pickupKey', 1, { near_key: true }, { hasKey: true }));
|
|
119
|
+
gp.addAction(makeAction('openDoor', 1, { hasKey: true }, { doorOpen: true }));
|
|
120
|
+
gp.addGoal(makeGoal('escape', 10, { doorOpen: true }));
|
|
121
|
+
const plan = gp.plan(makeState({ near_key: true }));
|
|
122
|
+
expect(plan).not.toBeNull();
|
|
123
|
+
expect(plan!.actions).toHaveLength(2);
|
|
124
|
+
expect(plan!.actions[0].id).toBe('pickupKey');
|
|
125
|
+
expect(plan!.actions[1].id).toBe('openDoor');
|
|
126
|
+
});
|
|
127
|
+
it('total cost is sum of action costs', () => {
|
|
128
|
+
const gp = new GoalPlanner();
|
|
129
|
+
gp.addAction(makeAction('a1', 3, {}, { mid: true }));
|
|
130
|
+
gp.addAction(makeAction('a2', 5, { mid: true }, { goal: true }));
|
|
131
|
+
gp.addGoal(makeGoal('g', 10, { goal: true }));
|
|
132
|
+
const plan = gp.plan(makeState({}));
|
|
133
|
+
expect(plan!.totalCost).toBe(8);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('GoalPlanner — cost optimization (A*)', () => {
|
|
138
|
+
it('picks cheaper path when two routes exist', () => {
|
|
139
|
+
const gp = new GoalPlanner();
|
|
140
|
+
// Expensive route: single action cost=100
|
|
141
|
+
gp.addAction(makeAction('expensive', 100, {}, { done: true }));
|
|
142
|
+
// Cheap route: two actions, total cost=3
|
|
143
|
+
gp.addAction(makeAction('step1', 1, {}, { mid: true }));
|
|
144
|
+
gp.addAction(makeAction('step2', 2, { mid: true }, { done: true }));
|
|
145
|
+
gp.addGoal(makeGoal('goal', 10, { done: true }));
|
|
146
|
+
const plan = gp.plan(makeState({}));
|
|
147
|
+
expect(plan!.totalCost).toBe(3);
|
|
148
|
+
expect(plan!.actions.some((a) => a.id === 'expensive')).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('GoalPlanner — goal priority', () => {
|
|
153
|
+
it('plans highest priority goal first', () => {
|
|
154
|
+
const gp = new GoalPlanner();
|
|
155
|
+
gp.addAction(makeAction('doA', 1, {}, { goalA: true }));
|
|
156
|
+
gp.addAction(makeAction('doB', 1, {}, { goalB: true }));
|
|
157
|
+
gp.addGoal(makeGoal('lowPriority', 1, { goalA: true }));
|
|
158
|
+
gp.addGoal(makeGoal('highPriority', 100, { goalB: true }));
|
|
159
|
+
const plan = gp.plan(makeState({}));
|
|
160
|
+
expect(plan!.goalId).toBe('highPriority');
|
|
161
|
+
});
|
|
162
|
+
it('falls back to next goal if first is unreachable', () => {
|
|
163
|
+
const gp = new GoalPlanner();
|
|
164
|
+
gp.addAction(makeAction('doB', 1, {}, { goalB: true }));
|
|
165
|
+
gp.addGoal(makeGoal('high', 100, { impossible: true }));
|
|
166
|
+
gp.addGoal(makeGoal('low', 1, { goalB: true }));
|
|
167
|
+
const plan = gp.plan(makeState({}));
|
|
168
|
+
expect(plan!.goalId).toBe('low');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('GoalPlanner — precondition enforcement', () => {
|
|
173
|
+
it('action with unmet precondition is not used', () => {
|
|
174
|
+
const gp = new GoalPlanner();
|
|
175
|
+
gp.addAction(makeAction('guarded', 1, { hasSpecialItem: true }, { done: true }));
|
|
176
|
+
gp.addGoal(makeGoal('g', 10, { done: true }));
|
|
177
|
+
// hasSpecialItem is NOT in world state
|
|
178
|
+
const plan = gp.plan(makeState({ hasSpecialItem: false }));
|
|
179
|
+
expect(plan).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
it('action used when precondition met', () => {
|
|
182
|
+
const gp = new GoalPlanner();
|
|
183
|
+
gp.addAction(makeAction('guarded', 1, { hasSpecialItem: true }, { done: true }));
|
|
184
|
+
gp.addGoal(makeGoal('g', 10, { done: true }));
|
|
185
|
+
const plan = gp.plan(makeState({ hasSpecialItem: true }));
|
|
186
|
+
expect(plan).not.toBeNull();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('GoalPlanner — executePlan', () => {
|
|
191
|
+
it('calls execute on each action in order', () => {
|
|
192
|
+
const calls: string[] = [];
|
|
193
|
+
const gp = new GoalPlanner();
|
|
194
|
+
gp.addAction(
|
|
195
|
+
makeAction(
|
|
196
|
+
'a1',
|
|
197
|
+
1,
|
|
198
|
+
{},
|
|
199
|
+
{ mid: true },
|
|
200
|
+
vi.fn(() => calls.push('a1'))
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
gp.addAction(
|
|
204
|
+
makeAction(
|
|
205
|
+
'a2',
|
|
206
|
+
1,
|
|
207
|
+
{ mid: true },
|
|
208
|
+
{ done: true },
|
|
209
|
+
vi.fn(() => calls.push('a2'))
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
gp.addGoal(makeGoal('g', 10, { done: true }));
|
|
213
|
+
const plan = gp.plan(makeState({}));
|
|
214
|
+
gp.executePlan(plan!);
|
|
215
|
+
expect(calls).toEqual(['a1', 'a2']);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { HoloScriptGenerator, validateBatch } from '../HoloScriptGenerator';
|
|
3
|
+
import type { AIAdapter } from '../AIAdapter';
|
|
4
|
+
|
|
5
|
+
function mockAdapter(): AIAdapter {
|
|
6
|
+
return {
|
|
7
|
+
id: 'mock-gen',
|
|
8
|
+
name: 'Mock Generator Adapter',
|
|
9
|
+
isReady: () => true,
|
|
10
|
+
generateHoloScript: vi.fn(async (prompt: string) => ({
|
|
11
|
+
holoScript: `scene { // ${prompt} }`,
|
|
12
|
+
confidence: 0.8,
|
|
13
|
+
objectCount: 1,
|
|
14
|
+
})),
|
|
15
|
+
chat: vi.fn(async () => 'response'),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('HoloScriptGenerator', () => {
|
|
20
|
+
let gen: HoloScriptGenerator;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
gen = new HoloScriptGenerator();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// --- Session management ---
|
|
26
|
+
it('createSession returns session object', () => {
|
|
27
|
+
const adapter = mockAdapter();
|
|
28
|
+
const session = gen.createSession(adapter);
|
|
29
|
+
expect(session).toBeDefined();
|
|
30
|
+
expect(session.sessionId).toContain('session-');
|
|
31
|
+
expect(session.adapter).toBe(adapter);
|
|
32
|
+
expect(session.history).toEqual([]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('createSession with custom config', () => {
|
|
36
|
+
const session = gen.createSession(mockAdapter(), { maxAttempts: 5 });
|
|
37
|
+
expect(session.config.maxAttempts).toBe(5);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('getCurrentSession returns latest', () => {
|
|
41
|
+
gen.createSession(mockAdapter());
|
|
42
|
+
expect(gen.getCurrentSession()).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('getCurrentSession undefined before createSession', () => {
|
|
46
|
+
expect(gen.getCurrentSession()).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// --- History ---
|
|
50
|
+
it('getHistory returns empty initially', () => {
|
|
51
|
+
const session = gen.createSession(mockAdapter());
|
|
52
|
+
expect(gen.getHistory(session)).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('clearHistory removes entries', async () => {
|
|
56
|
+
const adapter = mockAdapter();
|
|
57
|
+
const session = gen.createSession(adapter);
|
|
58
|
+
// Generate to add to history
|
|
59
|
+
await gen.generate('test prompt', session);
|
|
60
|
+
expect(gen.getHistory(session).length).toBeGreaterThanOrEqual(0);
|
|
61
|
+
gen.clearHistory(session);
|
|
62
|
+
expect(gen.getHistory(session)).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// --- Stats ---
|
|
66
|
+
it('getStats returns stats object', () => {
|
|
67
|
+
const session = gen.createSession(mockAdapter());
|
|
68
|
+
const stats = gen.getStats(session);
|
|
69
|
+
expect(stats).toBeDefined();
|
|
70
|
+
expect(stats!.totalGenerations).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// --- Cache ---
|
|
74
|
+
it('getCacheStats returns stats', () => {
|
|
75
|
+
const stats = gen.getCacheStats();
|
|
76
|
+
expect(stats).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// --- Analytics ---
|
|
80
|
+
it('getAnalytics returns metrics', () => {
|
|
81
|
+
const analytics = gen.getAnalytics();
|
|
82
|
+
expect(analytics).toBeDefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('generateReport returns string', () => {
|
|
86
|
+
const report = gen.generateReport();
|
|
87
|
+
expect(typeof report).toBe('string');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// --- generate (async) ---
|
|
91
|
+
it('generate creates code from prompt', async () => {
|
|
92
|
+
const adapter = mockAdapter();
|
|
93
|
+
const session = gen.createSession(adapter);
|
|
94
|
+
const result = await gen.generate('create a red cube', session);
|
|
95
|
+
expect(result).toBeDefined();
|
|
96
|
+
expect(result.holoScript).toBeDefined();
|
|
97
|
+
expect(result.attempts).toBeGreaterThanOrEqual(1);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('generate handles adapter error gracefully', async () => {
|
|
101
|
+
const adapter = mockAdapter();
|
|
102
|
+
adapter.generateHoloScript = vi.fn(async () => {
|
|
103
|
+
throw new Error('fail');
|
|
104
|
+
});
|
|
105
|
+
const session = gen.createSession(adapter);
|
|
106
|
+
try {
|
|
107
|
+
await gen.generate('test', session);
|
|
108
|
+
} catch (_e) {
|
|
109
|
+
// Expected to throw
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('validateBatch', () => {
|
|
115
|
+
it('validates array of code strings', () => {
|
|
116
|
+
const results = validateBatch(['scene { box {} }', 'invalid!!!']);
|
|
117
|
+
expect(results).toHaveLength(2);
|
|
118
|
+
expect(results[0]).toHaveProperty('valid');
|
|
119
|
+
expect(results[0]).toHaveProperty('errors');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('empty array returns empty', () => {
|
|
123
|
+
expect(validateBatch([])).toEqual([]);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfluenceMap — Production Test Suite
|
|
3
|
+
*
|
|
4
|
+
* Covers: layer management, setInfluence, addInfluence, stampRadius,
|
|
5
|
+
* propagation/decay, getInfluence, getInfluenceAtWorld, getMaxCell,
|
|
6
|
+
* clear, clearAll, bounds checking.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { InfluenceMap } from '../InfluenceMap';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
width: 10,
|
|
13
|
+
height: 10,
|
|
14
|
+
cellSize: 1,
|
|
15
|
+
decayRate: 0.1,
|
|
16
|
+
propagationRate: 0.2,
|
|
17
|
+
maxValue: 100,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe('InfluenceMap — Production', () => {
|
|
21
|
+
// ─── Layer Management ─────────────────────────────────────────────
|
|
22
|
+
it('addLayer creates new layer', () => {
|
|
23
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
24
|
+
map.addLayer('threat');
|
|
25
|
+
expect(map.getLayerNames()).toContain('threat');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('removeLayer removes layer', () => {
|
|
29
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
30
|
+
map.addLayer('temp');
|
|
31
|
+
map.removeLayer('temp');
|
|
32
|
+
expect(map.getLayerNames()).not.toContain('temp');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('multiple layers tracked independently', () => {
|
|
36
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
37
|
+
map.addLayer('threat');
|
|
38
|
+
map.addLayer('resource');
|
|
39
|
+
expect(map.getLayerNames().sort()).toEqual(['resource', 'threat']);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ─── Set / Get Influence ──────────────────────────────────────────
|
|
43
|
+
it('setInfluence + getInfluence', () => {
|
|
44
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
45
|
+
map.addLayer('threat');
|
|
46
|
+
map.setInfluence('threat', 3, 4, 50);
|
|
47
|
+
expect(map.getInfluence('threat', 3, 4)).toBe(50);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('getInfluence returns 0 for empty cell', () => {
|
|
51
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
52
|
+
map.addLayer('threat');
|
|
53
|
+
expect(map.getInfluence('threat', 0, 0)).toBe(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('getInfluence returns 0 for out-of-bounds', () => {
|
|
57
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
58
|
+
map.addLayer('threat');
|
|
59
|
+
expect(map.getInfluence('threat', -1, 0)).toBe(0);
|
|
60
|
+
expect(map.getInfluence('threat', 100, 0)).toBe(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('setInfluence clamps to maxValue', () => {
|
|
64
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
65
|
+
map.addLayer('threat');
|
|
66
|
+
map.setInfluence('threat', 0, 0, 999);
|
|
67
|
+
expect(map.getInfluence('threat', 0, 0)).toBe(100);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ─── Add Influence ────────────────────────────────────────────────
|
|
71
|
+
it('addInfluence accumulates value', () => {
|
|
72
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
73
|
+
map.addLayer('threat');
|
|
74
|
+
map.addInfluence('threat', 5, 5, 30);
|
|
75
|
+
map.addInfluence('threat', 5, 5, 20);
|
|
76
|
+
expect(map.getInfluence('threat', 5, 5)).toBe(50);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ─── stampRadius ──────────────────────────────────────────────────
|
|
80
|
+
it('stampRadius sets influence in radius', () => {
|
|
81
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
82
|
+
map.addLayer('threat');
|
|
83
|
+
map.stampRadius('threat', 5, 5, 2, 80);
|
|
84
|
+
expect(map.getInfluence('threat', 5, 5)).toBeGreaterThan(0);
|
|
85
|
+
expect(map.getInfluence('threat', 4, 5)).toBeGreaterThan(0);
|
|
86
|
+
expect(map.getInfluence('threat', 0, 0)).toBe(0); // outside radius
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ─── Propagation + Decay ──────────────────────────────────────────
|
|
90
|
+
it('update propagates influence to neighbors', () => {
|
|
91
|
+
const map = new InfluenceMap({ ...DEFAULT_CONFIG, decayRate: 0, propagationRate: 0.5 });
|
|
92
|
+
map.addLayer('threat');
|
|
93
|
+
map.setInfluence('threat', 5, 5, 100);
|
|
94
|
+
map.update();
|
|
95
|
+
expect(map.getInfluence('threat', 4, 5)).toBeGreaterThan(0);
|
|
96
|
+
expect(map.getInfluence('threat', 6, 5)).toBeGreaterThan(0);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('update decays influence', () => {
|
|
100
|
+
const map = new InfluenceMap({ ...DEFAULT_CONFIG, decayRate: 0.5, propagationRate: 0 });
|
|
101
|
+
map.addLayer('threat');
|
|
102
|
+
map.setInfluence('threat', 5, 5, 100);
|
|
103
|
+
map.update();
|
|
104
|
+
expect(map.getInfluence('threat', 5, 5)).toBeLessThan(100);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ─── World Coordinates ────────────────────────────────────────────
|
|
108
|
+
it('getInfluenceAtWorld converts world to grid', () => {
|
|
109
|
+
const map = new InfluenceMap({ ...DEFAULT_CONFIG, cellSize: 2 });
|
|
110
|
+
map.addLayer('threat');
|
|
111
|
+
map.setInfluence('threat', 2, 3, 75);
|
|
112
|
+
expect(map.getInfluenceAtWorld('threat', 4, 6)).toBe(75); // wx/cellSize = 2, wy/cellSize = 3
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ─── getMaxCell ───────────────────────────────────────────────────
|
|
116
|
+
it('getMaxCell finds highest influence', () => {
|
|
117
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
118
|
+
map.addLayer('threat');
|
|
119
|
+
map.setInfluence('threat', 3, 7, 90);
|
|
120
|
+
map.setInfluence('threat', 1, 1, 10);
|
|
121
|
+
const max = map.getMaxCell('threat');
|
|
122
|
+
expect(max.x).toBe(3);
|
|
123
|
+
expect(max.y).toBe(7);
|
|
124
|
+
expect(max.value).toBe(90);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ─── Clear ────────────────────────────────────────────────────────
|
|
128
|
+
it('clear resets single layer', () => {
|
|
129
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
130
|
+
map.addLayer('threat');
|
|
131
|
+
map.setInfluence('threat', 5, 5, 50);
|
|
132
|
+
map.clear('threat');
|
|
133
|
+
expect(map.getInfluence('threat', 5, 5)).toBe(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('clearAll resets all layers', () => {
|
|
137
|
+
const map = new InfluenceMap(DEFAULT_CONFIG);
|
|
138
|
+
map.addLayer('a');
|
|
139
|
+
map.addLayer('b');
|
|
140
|
+
map.setInfluence('a', 0, 0, 50);
|
|
141
|
+
map.setInfluence('b', 0, 0, 60);
|
|
142
|
+
map.clearAll();
|
|
143
|
+
expect(map.getInfluence('a', 0, 0)).toBe(0);
|
|
144
|
+
expect(map.getInfluence('b', 0, 0)).toBe(0);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { InfluenceMap } from '../InfluenceMap';
|
|
3
|
+
|
|
4
|
+
describe('InfluenceMap', () => {
|
|
5
|
+
let map: InfluenceMap;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
map = new InfluenceMap({
|
|
9
|
+
width: 10,
|
|
10
|
+
height: 10,
|
|
11
|
+
cellSize: 1,
|
|
12
|
+
decayRate: 0.1,
|
|
13
|
+
propagationRate: 0.2,
|
|
14
|
+
maxValue: 100,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Layer Management
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
it('addLayer creates a named layer', () => {
|
|
23
|
+
map.addLayer('threat');
|
|
24
|
+
expect(map.getLayerNames()).toContain('threat');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('removeLayer deletes a layer', () => {
|
|
28
|
+
map.addLayer('temp');
|
|
29
|
+
map.removeLayer('temp');
|
|
30
|
+
expect(map.getLayerNames()).not.toContain('temp');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('getLayerNames returns all layers', () => {
|
|
34
|
+
map.addLayer('a');
|
|
35
|
+
map.addLayer('b');
|
|
36
|
+
expect(map.getLayerNames()).toEqual(expect.arrayContaining(['a', 'b']));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Influence Modification
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
it('setInfluence stores and getInfluence retrieves', () => {
|
|
44
|
+
map.addLayer('test');
|
|
45
|
+
map.setInfluence('test', 3, 4, 50);
|
|
46
|
+
expect(map.getInfluence('test', 3, 4)).toBe(50);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('addInfluence accumulates values', () => {
|
|
50
|
+
map.addLayer('test');
|
|
51
|
+
map.setInfluence('test', 0, 0, 30);
|
|
52
|
+
map.addInfluence('test', 0, 0, 20);
|
|
53
|
+
expect(map.getInfluence('test', 0, 0)).toBe(50);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('setInfluence clamps to maxValue', () => {
|
|
57
|
+
map.addLayer('test');
|
|
58
|
+
map.setInfluence('test', 0, 0, 200); // maxValue = 100
|
|
59
|
+
expect(map.getInfluence('test', 0, 0)).toBe(100);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('out-of-bounds reads return 0', () => {
|
|
63
|
+
map.addLayer('test');
|
|
64
|
+
expect(map.getInfluence('test', -1, 0)).toBe(0);
|
|
65
|
+
expect(map.getInfluence('test', 100, 0)).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Stamp Radius
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
it('stampRadius affects cells within radius', () => {
|
|
73
|
+
map.addLayer('test');
|
|
74
|
+
map.stampRadius('test', 5, 5, 2, 60);
|
|
75
|
+
expect(map.getInfluence('test', 5, 5)).toBeGreaterThan(0);
|
|
76
|
+
expect(map.getInfluence('test', 5, 4)).toBeGreaterThan(0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('stampRadius does not affect cells outside radius', () => {
|
|
80
|
+
map.addLayer('test');
|
|
81
|
+
map.stampRadius('test', 5, 5, 1, 60);
|
|
82
|
+
expect(map.getInfluence('test', 0, 0)).toBe(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Update (Decay + Propagation)
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
it('update decays values', () => {
|
|
90
|
+
map.addLayer('test');
|
|
91
|
+
map.setInfluence('test', 5, 5, 80);
|
|
92
|
+
map.update();
|
|
93
|
+
expect(map.getInfluence('test', 5, 5)).toBeLessThan(80);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('update propagates to neighbors', () => {
|
|
97
|
+
map.addLayer('test');
|
|
98
|
+
map.setInfluence('test', 5, 5, 80);
|
|
99
|
+
map.update();
|
|
100
|
+
// Neighbors should have picked up some influence
|
|
101
|
+
expect(map.getInfluence('test', 5, 4)).toBeGreaterThan(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// World Coordinates
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
it('getInfluenceAtWorld converts world to grid', () => {
|
|
109
|
+
map.addLayer('test');
|
|
110
|
+
map.setInfluence('test', 3, 4, 42);
|
|
111
|
+
// cellSize = 1, so world (3, 4) → grid (3, 4)
|
|
112
|
+
expect(map.getInfluenceAtWorld('test', 3, 4)).toBe(42);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Queries
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
it('getMaxCell returns position and value of highest cell', () => {
|
|
120
|
+
map.addLayer('test');
|
|
121
|
+
map.setInfluence('test', 2, 3, 10);
|
|
122
|
+
map.setInfluence('test', 7, 8, 90);
|
|
123
|
+
const max = map.getMaxCell('test');
|
|
124
|
+
expect(max.x).toBe(7);
|
|
125
|
+
expect(max.y).toBe(8);
|
|
126
|
+
expect(max.value).toBe(90);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Clear
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
it('clear resets a single layer', () => {
|
|
134
|
+
map.addLayer('test');
|
|
135
|
+
map.setInfluence('test', 0, 0, 50);
|
|
136
|
+
map.clear('test');
|
|
137
|
+
expect(map.getInfluence('test', 0, 0)).toBe(0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('clearAll resets all layers', () => {
|
|
141
|
+
map.addLayer('a');
|
|
142
|
+
map.addLayer('b');
|
|
143
|
+
map.setInfluence('a', 0, 0, 50);
|
|
144
|
+
map.setInfluence('b', 0, 0, 50);
|
|
145
|
+
map.clearAll();
|
|
146
|
+
expect(map.getInfluence('a', 0, 0)).toBe(0);
|
|
147
|
+
expect(map.getInfluence('b', 0, 0)).toBe(0);
|
|
148
|
+
});
|
|
149
|
+
});
|