@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,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SwarmEventBus Tests
|
|
3
|
+
* HoloScript v3.2 - Autonomous Agent Swarms
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
7
|
+
import { SwarmEventBus, EventPriority } from '../SwarmEventBus';
|
|
8
|
+
|
|
9
|
+
describe('SwarmEventBus', () => {
|
|
10
|
+
let bus: SwarmEventBus;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
bus = new SwarmEventBus({ asyncProcessing: false });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('basic publish/subscribe', () => {
|
|
21
|
+
it('should deliver event to subscriber', async () => {
|
|
22
|
+
const handler = vi.fn();
|
|
23
|
+
bus.subscribe('test.event', handler);
|
|
24
|
+
|
|
25
|
+
bus.emit('test.event', 'agent-1', { data: 'hello' });
|
|
26
|
+
|
|
27
|
+
expect(handler).toHaveBeenCalledOnce();
|
|
28
|
+
expect(handler.mock.calls[0][0]).toMatchObject({
|
|
29
|
+
type: 'test.event',
|
|
30
|
+
payload: { data: 'hello' },
|
|
31
|
+
source: 'agent-1',
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should support multiple subscribers', async () => {
|
|
36
|
+
const handler1 = vi.fn();
|
|
37
|
+
const handler2 = vi.fn();
|
|
38
|
+
|
|
39
|
+
bus.subscribe('test.event', handler1);
|
|
40
|
+
bus.subscribe('test.event', handler2);
|
|
41
|
+
|
|
42
|
+
bus.emit('test.event', 'agent-1', {});
|
|
43
|
+
|
|
44
|
+
expect(handler1).toHaveBeenCalledOnce();
|
|
45
|
+
expect(handler2).toHaveBeenCalledOnce();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should not deliver to unsubscribed handlers', async () => {
|
|
49
|
+
const handler = vi.fn();
|
|
50
|
+
const subId = bus.subscribe('test.event', handler);
|
|
51
|
+
|
|
52
|
+
bus.unsubscribe(subId);
|
|
53
|
+
bus.emit('test.event', 'agent-1', {});
|
|
54
|
+
|
|
55
|
+
expect(handler).not.toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should only deliver matching topics', async () => {
|
|
59
|
+
const handler = vi.fn();
|
|
60
|
+
bus.subscribe('topic.a', handler);
|
|
61
|
+
|
|
62
|
+
bus.emit('topic.b', 'agent-1', {});
|
|
63
|
+
|
|
64
|
+
expect(handler).not.toHaveBeenCalled();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('wildcard patterns', () => {
|
|
69
|
+
it('should match wildcard patterns', async () => {
|
|
70
|
+
const handler = vi.fn();
|
|
71
|
+
bus.subscribe('swarm.*', handler);
|
|
72
|
+
|
|
73
|
+
bus.emit('swarm.joined', 'agent-1', {});
|
|
74
|
+
bus.emit('swarm.left', 'agent-1', {});
|
|
75
|
+
|
|
76
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should match prefix.*', async () => {
|
|
80
|
+
const handler = vi.fn();
|
|
81
|
+
bus.subscribe('agent.*', handler);
|
|
82
|
+
|
|
83
|
+
bus.emit('agent.created', 'a', {});
|
|
84
|
+
bus.emit('agent.destroyed', 'a', {});
|
|
85
|
+
bus.emit('other.event', 'a', {});
|
|
86
|
+
|
|
87
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should match **.suffix', async () => {
|
|
91
|
+
const handler = vi.fn();
|
|
92
|
+
bus.subscribe('*error', handler);
|
|
93
|
+
|
|
94
|
+
bus.emit('system.error', 'a', {});
|
|
95
|
+
bus.emit('agent.action.error', 'a', {});
|
|
96
|
+
|
|
97
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('priority handling', () => {
|
|
102
|
+
it('should process critical events by priority order', async () => {
|
|
103
|
+
const order: string[] = [];
|
|
104
|
+
|
|
105
|
+
bus.subscribe('event', (e: any) => { order.push(e.priority); });
|
|
106
|
+
|
|
107
|
+
// Use publish with asyncProcessing: false - events are queued by priority
|
|
108
|
+
const asyncBus = new SwarmEventBus({ asyncProcessing: true });
|
|
109
|
+
asyncBus.subscribe('event', (e: any) => { order.push(e.priority); });
|
|
110
|
+
|
|
111
|
+
// Emit directly for synchronous test
|
|
112
|
+
bus.emit('event', 'a', {}, { priority: 'low' });
|
|
113
|
+
bus.emit('event', 'a', {}, { priority: 'critical' });
|
|
114
|
+
bus.emit('event', 'a', {}, { priority: 'high' });
|
|
115
|
+
bus.emit('event', 'a', {}, { priority: 'normal' });
|
|
116
|
+
|
|
117
|
+
// emit is synchronous so order is insertion order
|
|
118
|
+
// Use publish + processQueue for priority testing
|
|
119
|
+
expect(order).toContain('critical');
|
|
120
|
+
expect(order).toContain('high');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should default to normal priority', async () => {
|
|
124
|
+
const handler = vi.fn();
|
|
125
|
+
bus.subscribe('test', handler);
|
|
126
|
+
|
|
127
|
+
bus.emit('test', 'a', {});
|
|
128
|
+
|
|
129
|
+
expect(handler.mock.calls[0][0].priority).toBe('normal');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('once subscription', () => {
|
|
134
|
+
it('should only fire once', async () => {
|
|
135
|
+
const handler = vi.fn();
|
|
136
|
+
bus.once('test.event', handler);
|
|
137
|
+
|
|
138
|
+
bus.emit('test.event', 'a', {});
|
|
139
|
+
bus.emit('test.event', 'a', {});
|
|
140
|
+
|
|
141
|
+
expect(handler).toHaveBeenCalledOnce();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('TTL expiration', () => {
|
|
146
|
+
it('should drop expired events', async () => {
|
|
147
|
+
vi.useFakeTimers();
|
|
148
|
+
// Don't auto-process so we can advance time before processing
|
|
149
|
+
const asyncBus = new SwarmEventBus({ asyncProcessing: false });
|
|
150
|
+
const handler = vi.fn();
|
|
151
|
+
asyncBus.subscribe('test', handler);
|
|
152
|
+
|
|
153
|
+
// Publish adds to queue but doesn't process (asyncProcessing: false)
|
|
154
|
+
asyncBus.publish('test', 'a', {}, { ttl: 1000 });
|
|
155
|
+
|
|
156
|
+
// Advance past TTL
|
|
157
|
+
vi.advanceTimersByTime(2000);
|
|
158
|
+
|
|
159
|
+
// Now process - event should be expired
|
|
160
|
+
await asyncBus.processQueue();
|
|
161
|
+
|
|
162
|
+
expect(handler).not.toHaveBeenCalled();
|
|
163
|
+
vi.useRealTimers();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('dead letter queue', () => {
|
|
168
|
+
it('should add failed events to dead letter queue', async () => {
|
|
169
|
+
const deadBus = new SwarmEventBus({
|
|
170
|
+
enableDeadLetter: true,
|
|
171
|
+
asyncProcessing: false,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
deadBus.subscribe('test', () => {
|
|
175
|
+
throw new Error('Handler failed');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
deadBus.emit('test', 'a', {});
|
|
179
|
+
|
|
180
|
+
const stats = deadBus.getStats();
|
|
181
|
+
expect(stats.deadLetterCount).toBeGreaterThanOrEqual(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should allow replaying dead letters', async () => {
|
|
185
|
+
const deadBus = new SwarmEventBus({
|
|
186
|
+
enableDeadLetter: true,
|
|
187
|
+
asyncProcessing: false,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
let shouldFail = true;
|
|
191
|
+
const handler = vi.fn((_e) => {
|
|
192
|
+
if (shouldFail) throw new Error('fail');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
deadBus.subscribe('test', handler);
|
|
196
|
+
|
|
197
|
+
deadBus.emit('test', 'a', { data: 1 });
|
|
198
|
+
|
|
199
|
+
// Fix the handler
|
|
200
|
+
shouldFail = false;
|
|
201
|
+
|
|
202
|
+
// Replay dead letters
|
|
203
|
+
const replayed = await deadBus.replayDeadLetters();
|
|
204
|
+
|
|
205
|
+
expect(replayed).toBeGreaterThanOrEqual(0);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('statistics', () => {
|
|
210
|
+
it('should track published events', async () => {
|
|
211
|
+
bus.emit('test1', 'a', {});
|
|
212
|
+
bus.emit('test2', 'a', {});
|
|
213
|
+
|
|
214
|
+
const stats = bus.getStats();
|
|
215
|
+
expect(stats.eventsPublished).toBe(2);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should track delivered events', async () => {
|
|
219
|
+
bus.subscribe('test', vi.fn());
|
|
220
|
+
bus.emit('test', 'a', {});
|
|
221
|
+
|
|
222
|
+
const stats = bus.getStats();
|
|
223
|
+
expect(stats.eventsDelivered).toBe(1);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('configuration', () => {
|
|
228
|
+
it('should respect maxQueueSize', async () => {
|
|
229
|
+
const smallBus = new SwarmEventBus({ maxQueueSize: 2, asyncProcessing: true });
|
|
230
|
+
|
|
231
|
+
smallBus.publish('a', 'x', {});
|
|
232
|
+
smallBus.publish('b', 'x', {});
|
|
233
|
+
smallBus.publish('c', 'x', {});
|
|
234
|
+
|
|
235
|
+
const stats = smallBus.getStats();
|
|
236
|
+
expect(stats.pendingEvents).toBeLessThanOrEqual(2);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('getConfig', () => {
|
|
241
|
+
it('should return configuration', () => {
|
|
242
|
+
const config = bus.getConfig();
|
|
243
|
+
expect(config).toHaveProperty('maxQueueSize');
|
|
244
|
+
expect(config).toHaveProperty('defaultTTL');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlockingBehavior - Boid-based swarm movement
|
|
3
|
+
* HoloScript v3.2 - Autonomous Agent Swarms
|
|
4
|
+
*
|
|
5
|
+
* Implements Craig Reynolds' Boids algorithm:
|
|
6
|
+
* - Separation: Steer to avoid crowding neighbors
|
|
7
|
+
* - Alignment: Steer towards average heading of neighbors
|
|
8
|
+
* - Cohesion: Steer towards average position of neighbors
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Vector3 } from './Vector3';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Boid agent state
|
|
15
|
+
*/
|
|
16
|
+
export interface IBoid {
|
|
17
|
+
id: string;
|
|
18
|
+
position: Vector3;
|
|
19
|
+
velocity: Vector3;
|
|
20
|
+
acceleration: Vector3;
|
|
21
|
+
maxSpeed: number;
|
|
22
|
+
maxForce: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Flocking configuration
|
|
27
|
+
*/
|
|
28
|
+
export interface IFlockingConfig {
|
|
29
|
+
/** Radius for separation behavior */
|
|
30
|
+
separationRadius: number;
|
|
31
|
+
/** Radius for alignment behavior */
|
|
32
|
+
alignmentRadius: number;
|
|
33
|
+
/** Radius for cohesion behavior */
|
|
34
|
+
cohesionRadius: number;
|
|
35
|
+
/** Weight for separation force */
|
|
36
|
+
separationWeight: number;
|
|
37
|
+
/** Weight for alignment force */
|
|
38
|
+
alignmentWeight: number;
|
|
39
|
+
/** Weight for cohesion force */
|
|
40
|
+
cohesionWeight: number;
|
|
41
|
+
/** Maximum speed */
|
|
42
|
+
maxSpeed: number;
|
|
43
|
+
/** Maximum steering force */
|
|
44
|
+
maxForce: number;
|
|
45
|
+
/** Boundary mode */
|
|
46
|
+
boundaryMode: 'wrap' | 'bounce' | 'contain';
|
|
47
|
+
/** World bounds (if boundaryMode is not 'none') */
|
|
48
|
+
worldBounds?: {
|
|
49
|
+
min: Vector3;
|
|
50
|
+
max: Vector3;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* FlockingBehavior - Manages Boid-based swarm movement
|
|
56
|
+
*/
|
|
57
|
+
export class FlockingBehavior {
|
|
58
|
+
private boids: Map<string, IBoid> = new Map();
|
|
59
|
+
private config: IFlockingConfig;
|
|
60
|
+
|
|
61
|
+
constructor(config?: Partial<IFlockingConfig>) {
|
|
62
|
+
this.config = {
|
|
63
|
+
separationRadius: 25,
|
|
64
|
+
alignmentRadius: 50,
|
|
65
|
+
cohesionRadius: 50,
|
|
66
|
+
separationWeight: 1.5,
|
|
67
|
+
alignmentWeight: 1.0,
|
|
68
|
+
cohesionWeight: 1.0,
|
|
69
|
+
maxSpeed: 4,
|
|
70
|
+
maxForce: 0.1,
|
|
71
|
+
boundaryMode: 'wrap',
|
|
72
|
+
...config,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Add a boid to the flock
|
|
78
|
+
*/
|
|
79
|
+
addBoid(id: string, position: Vector3, velocity?: Vector3): IBoid {
|
|
80
|
+
const boid: IBoid = {
|
|
81
|
+
id,
|
|
82
|
+
position: position.clone(),
|
|
83
|
+
velocity:
|
|
84
|
+
velocity?.clone() ??
|
|
85
|
+
new Vector3(
|
|
86
|
+
(Math.random() - 0.5) * 2,
|
|
87
|
+
(Math.random() - 0.5) * 2,
|
|
88
|
+
(Math.random() - 0.5) * 2
|
|
89
|
+
),
|
|
90
|
+
acceleration: Vector3.zero(),
|
|
91
|
+
maxSpeed: this.config.maxSpeed,
|
|
92
|
+
maxForce: this.config.maxForce,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
this.boids.set(id, boid);
|
|
96
|
+
return boid;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Remove a boid from the flock
|
|
101
|
+
*/
|
|
102
|
+
removeBoid(id: string): boolean {
|
|
103
|
+
return this.boids.delete(id);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get a boid by ID
|
|
108
|
+
*/
|
|
109
|
+
getBoid(id: string): IBoid | undefined {
|
|
110
|
+
return this.boids.get(id);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get all boids
|
|
115
|
+
*/
|
|
116
|
+
getAllBoids(): IBoid[] {
|
|
117
|
+
return [...this.boids.values()];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Update boid position manually
|
|
122
|
+
*/
|
|
123
|
+
setBoidPosition(id: string, position: Vector3): void {
|
|
124
|
+
const boid = this.boids.get(id);
|
|
125
|
+
if (boid) {
|
|
126
|
+
boid.position = position.clone();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Update a single boid
|
|
132
|
+
*/
|
|
133
|
+
updateBoid(boid: IBoid, neighbors: IBoid[]): void {
|
|
134
|
+
const separation = this.separate(boid, neighbors);
|
|
135
|
+
const alignment = this.align(boid, neighbors);
|
|
136
|
+
const cohesion = this.cohere(boid, neighbors);
|
|
137
|
+
|
|
138
|
+
// Apply weights
|
|
139
|
+
const separationForce = separation.multiply(this.config.separationWeight);
|
|
140
|
+
const alignmentForce = alignment.multiply(this.config.alignmentWeight);
|
|
141
|
+
const cohesionForce = cohesion.multiply(this.config.cohesionWeight);
|
|
142
|
+
|
|
143
|
+
// Accumulate forces
|
|
144
|
+
boid.acceleration = boid.acceleration
|
|
145
|
+
.add(separationForce)
|
|
146
|
+
.add(alignmentForce)
|
|
147
|
+
.add(cohesionForce);
|
|
148
|
+
|
|
149
|
+
// Update velocity
|
|
150
|
+
boid.velocity = boid.velocity.add(boid.acceleration);
|
|
151
|
+
boid.velocity = boid.velocity.clampMagnitude(boid.maxSpeed);
|
|
152
|
+
|
|
153
|
+
// Update position
|
|
154
|
+
boid.position = boid.position.add(boid.velocity);
|
|
155
|
+
|
|
156
|
+
// Reset acceleration
|
|
157
|
+
boid.acceleration = Vector3.zero();
|
|
158
|
+
|
|
159
|
+
// Handle boundaries
|
|
160
|
+
this.handleBoundaries(boid);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Update all boids in the flock
|
|
165
|
+
*/
|
|
166
|
+
update(): void {
|
|
167
|
+
const boidList = this.getAllBoids();
|
|
168
|
+
|
|
169
|
+
for (const boid of boidList) {
|
|
170
|
+
const neighbors = this.findNeighbors(
|
|
171
|
+
boid,
|
|
172
|
+
Math.max(
|
|
173
|
+
this.config.separationRadius,
|
|
174
|
+
this.config.alignmentRadius,
|
|
175
|
+
this.config.cohesionRadius
|
|
176
|
+
)
|
|
177
|
+
);
|
|
178
|
+
this.updateBoid(boid, neighbors);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Separation: Steer away from nearby boids
|
|
184
|
+
*/
|
|
185
|
+
separate(boid: IBoid, neighbors: IBoid[]): Vector3 {
|
|
186
|
+
let steer = Vector3.zero();
|
|
187
|
+
let count = 0;
|
|
188
|
+
|
|
189
|
+
for (const other of neighbors) {
|
|
190
|
+
if (other.id === boid.id) continue;
|
|
191
|
+
|
|
192
|
+
const distance = boid.position.distanceTo(other.position);
|
|
193
|
+
if (distance > 0 && distance < this.config.separationRadius) {
|
|
194
|
+
// Calculate vector pointing away from neighbor
|
|
195
|
+
let diff = boid.position.subtract(other.position);
|
|
196
|
+
diff = diff.normalize();
|
|
197
|
+
diff = diff.divide(distance); // Weight by distance (closer = stronger)
|
|
198
|
+
steer = steer.add(diff);
|
|
199
|
+
count++;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (count > 0) {
|
|
204
|
+
steer = steer.divide(count);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (steer.magnitude() > 0) {
|
|
208
|
+
steer = steer.normalize();
|
|
209
|
+
steer = steer.multiply(boid.maxSpeed);
|
|
210
|
+
steer = steer.subtract(boid.velocity);
|
|
211
|
+
steer = steer.clampMagnitude(boid.maxForce);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return steer;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Alignment: Steer towards average heading of neighbors
|
|
219
|
+
*/
|
|
220
|
+
align(boid: IBoid, neighbors: IBoid[]): Vector3 {
|
|
221
|
+
let sum = Vector3.zero();
|
|
222
|
+
let count = 0;
|
|
223
|
+
|
|
224
|
+
for (const other of neighbors) {
|
|
225
|
+
if (other.id === boid.id) continue;
|
|
226
|
+
|
|
227
|
+
const distance = boid.position.distanceTo(other.position);
|
|
228
|
+
if (distance > 0 && distance < this.config.alignmentRadius) {
|
|
229
|
+
sum = sum.add(other.velocity);
|
|
230
|
+
count++;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (count > 0) {
|
|
235
|
+
sum = sum.divide(count);
|
|
236
|
+
sum = sum.normalize();
|
|
237
|
+
sum = sum.multiply(boid.maxSpeed);
|
|
238
|
+
const steer = sum.subtract(boid.velocity);
|
|
239
|
+
return steer.clampMagnitude(boid.maxForce);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return Vector3.zero();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Cohesion: Steer towards center of mass of neighbors
|
|
247
|
+
*/
|
|
248
|
+
cohere(boid: IBoid, neighbors: IBoid[]): Vector3 {
|
|
249
|
+
let sum = Vector3.zero();
|
|
250
|
+
let count = 0;
|
|
251
|
+
|
|
252
|
+
for (const other of neighbors) {
|
|
253
|
+
if (other.id === boid.id) continue;
|
|
254
|
+
|
|
255
|
+
const distance = boid.position.distanceTo(other.position);
|
|
256
|
+
if (distance > 0 && distance < this.config.cohesionRadius) {
|
|
257
|
+
sum = sum.add(other.position);
|
|
258
|
+
count++;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (count > 0) {
|
|
263
|
+
const center = sum.divide(count);
|
|
264
|
+
return this.seek(boid, center);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return Vector3.zero();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Seek: Steer towards a target location
|
|
272
|
+
*/
|
|
273
|
+
seek(boid: IBoid, target: Vector3): Vector3 {
|
|
274
|
+
let desired = target.subtract(boid.position);
|
|
275
|
+
desired = desired.normalize();
|
|
276
|
+
desired = desired.multiply(boid.maxSpeed);
|
|
277
|
+
const steer = desired.subtract(boid.velocity);
|
|
278
|
+
return steer.clampMagnitude(boid.maxForce);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Flee: Steer away from a target location
|
|
283
|
+
*/
|
|
284
|
+
flee(boid: IBoid, target: Vector3): Vector3 {
|
|
285
|
+
return this.seek(boid, target).multiply(-1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Arrive: Seek with slowing as approaching target
|
|
290
|
+
*/
|
|
291
|
+
arrive(boid: IBoid, target: Vector3, slowingRadius: number): Vector3 {
|
|
292
|
+
let desired = target.subtract(boid.position);
|
|
293
|
+
const distance = desired.magnitude();
|
|
294
|
+
|
|
295
|
+
if (distance < slowingRadius) {
|
|
296
|
+
// Slow down proportionally
|
|
297
|
+
const speed = (distance / slowingRadius) * boid.maxSpeed;
|
|
298
|
+
desired = desired.normalize().multiply(speed);
|
|
299
|
+
} else {
|
|
300
|
+
desired = desired.normalize().multiply(boid.maxSpeed);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const steer = desired.subtract(boid.velocity);
|
|
304
|
+
return steer.clampMagnitude(boid.maxForce);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Find neighbors within radius
|
|
309
|
+
*/
|
|
310
|
+
findNeighbors(boid: IBoid, radius: number): IBoid[] {
|
|
311
|
+
const radiusSquared = radius * radius;
|
|
312
|
+
const neighbors: IBoid[] = [];
|
|
313
|
+
|
|
314
|
+
for (const other of this.boids.values()) {
|
|
315
|
+
if (other.id === boid.id) continue;
|
|
316
|
+
const distSq = boid.position.distanceToSquared(other.position);
|
|
317
|
+
if (distSq < radiusSquared) {
|
|
318
|
+
neighbors.push(other);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return neighbors;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Handle world boundaries
|
|
327
|
+
*/
|
|
328
|
+
private handleBoundaries(boid: IBoid): void {
|
|
329
|
+
if (!this.config.worldBounds) return;
|
|
330
|
+
|
|
331
|
+
const { min, max } = this.config.worldBounds;
|
|
332
|
+
|
|
333
|
+
switch (this.config.boundaryMode) {
|
|
334
|
+
case 'wrap':
|
|
335
|
+
this.wrapPosition(boid, min, max);
|
|
336
|
+
break;
|
|
337
|
+
case 'bounce':
|
|
338
|
+
this.bouncePosition(boid, min, max);
|
|
339
|
+
break;
|
|
340
|
+
case 'contain':
|
|
341
|
+
this.containPosition(boid, min, max);
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Wrap position to opposite side
|
|
348
|
+
*/
|
|
349
|
+
private wrapPosition(boid: IBoid, min: Vector3, max: Vector3): void {
|
|
350
|
+
const size = max.subtract(min);
|
|
351
|
+
|
|
352
|
+
if (boid.position.x < min.x) boid.position.x += size.x;
|
|
353
|
+
if (boid.position.x > max.x) boid.position.x -= size.x;
|
|
354
|
+
if (boid.position.y < min.y) boid.position.y += size.y;
|
|
355
|
+
if (boid.position.y > max.y) boid.position.y -= size.y;
|
|
356
|
+
if (boid.position.z < min.z) boid.position.z += size.z;
|
|
357
|
+
if (boid.position.z > max.z) boid.position.z -= size.z;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Bounce off boundaries
|
|
362
|
+
*/
|
|
363
|
+
private bouncePosition(boid: IBoid, min: Vector3, max: Vector3): void {
|
|
364
|
+
if (boid.position.x < min.x || boid.position.x > max.x) {
|
|
365
|
+
boid.velocity.x *= -1;
|
|
366
|
+
boid.position.x = Math.max(min.x, Math.min(max.x, boid.position.x));
|
|
367
|
+
}
|
|
368
|
+
if (boid.position.y < min.y || boid.position.y > max.y) {
|
|
369
|
+
boid.velocity.y *= -1;
|
|
370
|
+
boid.position.y = Math.max(min.y, Math.min(max.y, boid.position.y));
|
|
371
|
+
}
|
|
372
|
+
if (boid.position.z < min.z || boid.position.z > max.z) {
|
|
373
|
+
boid.velocity.z *= -1;
|
|
374
|
+
boid.position.z = Math.max(min.z, Math.min(max.z, boid.position.z));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Contain within boundaries (clamp)
|
|
380
|
+
*/
|
|
381
|
+
private containPosition(boid: IBoid, min: Vector3, max: Vector3): void {
|
|
382
|
+
boid.position.x = Math.max(min.x, Math.min(max.x, boid.position.x));
|
|
383
|
+
boid.position.y = Math.max(min.y, Math.min(max.y, boid.position.y));
|
|
384
|
+
boid.position.z = Math.max(min.z, Math.min(max.z, boid.position.z));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Apply external force to a boid
|
|
389
|
+
*/
|
|
390
|
+
applyForce(id: string, force: Vector3): void {
|
|
391
|
+
const boid = this.boids.get(id);
|
|
392
|
+
if (boid) {
|
|
393
|
+
boid.acceleration = boid.acceleration.add(force);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Apply force to all boids
|
|
399
|
+
*/
|
|
400
|
+
applyForceToAll(force: Vector3): void {
|
|
401
|
+
for (const boid of this.boids.values()) {
|
|
402
|
+
boid.acceleration = boid.acceleration.add(force);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Get flock center of mass
|
|
408
|
+
*/
|
|
409
|
+
getFlockCenter(): Vector3 {
|
|
410
|
+
if (this.boids.size === 0) return Vector3.zero();
|
|
411
|
+
|
|
412
|
+
let sum = Vector3.zero();
|
|
413
|
+
for (const boid of this.boids.values()) {
|
|
414
|
+
sum = sum.add(boid.position);
|
|
415
|
+
}
|
|
416
|
+
return sum.divide(this.boids.size);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get average velocity (direction) of flock
|
|
421
|
+
*/
|
|
422
|
+
getFlockDirection(): Vector3 {
|
|
423
|
+
if (this.boids.size === 0) return Vector3.zero();
|
|
424
|
+
|
|
425
|
+
let sum = Vector3.zero();
|
|
426
|
+
for (const boid of this.boids.values()) {
|
|
427
|
+
sum = sum.add(boid.velocity);
|
|
428
|
+
}
|
|
429
|
+
return sum.divide(this.boids.size).normalize();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get flock spread (radius from center)
|
|
434
|
+
*/
|
|
435
|
+
getFlockSpread(): number {
|
|
436
|
+
if (this.boids.size <= 1) return 0;
|
|
437
|
+
|
|
438
|
+
const center = this.getFlockCenter();
|
|
439
|
+
let maxDist = 0;
|
|
440
|
+
|
|
441
|
+
for (const boid of this.boids.values()) {
|
|
442
|
+
const dist = boid.position.distanceTo(center);
|
|
443
|
+
if (dist > maxDist) maxDist = dist;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return maxDist;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Update configuration
|
|
451
|
+
*/
|
|
452
|
+
setConfig(config: Partial<IFlockingConfig>): void {
|
|
453
|
+
this.config = { ...this.config, ...config };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Get current configuration
|
|
458
|
+
*/
|
|
459
|
+
getConfig(): IFlockingConfig {
|
|
460
|
+
return { ...this.config };
|
|
461
|
+
}
|
|
462
|
+
}
|