@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,1526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spatial Training Data Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates labeled spatial reasoning examples from HoloScript compositions
|
|
5
|
+
* with spatial constraints (spatial_adjacent, spatial_contains, spatial_reachable).
|
|
6
|
+
*
|
|
7
|
+
* Outputs instruction-response pairs in JSONL format suitable for fine-tuning
|
|
8
|
+
* LLMs on spatial reasoning tasks.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - 12+ instruction templates per spatial relationship type (per G.002 mandate)
|
|
12
|
+
* - Randomized scene parameters (object counts, positions, scales, relationships)
|
|
13
|
+
* - Both positive and negative examples for each spatial relationship type
|
|
14
|
+
* - Configurable difficulty levels (basic, intermediate, advanced)
|
|
15
|
+
*
|
|
16
|
+
* @module training/SpatialTrainingDataGenerator
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
SpatialDifficulty,
|
|
21
|
+
SpatialRelationshipType,
|
|
22
|
+
SceneObject,
|
|
23
|
+
SpatialRelationship,
|
|
24
|
+
SpatialScene,
|
|
25
|
+
SpatialTrainingExample,
|
|
26
|
+
SpatialGeneratorConfig,
|
|
27
|
+
SpatialGeneratorStats,
|
|
28
|
+
SpatialTrainingJSONLEntry,
|
|
29
|
+
} from './SpatialTrainingDataTypes';
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// SEEDED RANDOM NUMBER GENERATOR
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Simple seeded PRNG (Mulberry32) for reproducible generation.
|
|
37
|
+
*/
|
|
38
|
+
class SeededRandom {
|
|
39
|
+
private state: number;
|
|
40
|
+
|
|
41
|
+
constructor(seed: number) {
|
|
42
|
+
this.state = seed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Returns a float in [0, 1) */
|
|
46
|
+
next(): number {
|
|
47
|
+
this.state |= 0;
|
|
48
|
+
this.state = (this.state + 0x6d2b79f5) | 0;
|
|
49
|
+
let t = Math.imul(this.state ^ (this.state >>> 15), 1 | this.state);
|
|
50
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
51
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Returns an integer in [min, max] inclusive */
|
|
55
|
+
int(min: number, max: number): number {
|
|
56
|
+
return Math.floor(this.next() * (max - min + 1)) + min;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Returns a float in [min, max) */
|
|
60
|
+
float(min: number, max: number): number {
|
|
61
|
+
return this.next() * (max - min) + min;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Picks a random element from an array */
|
|
65
|
+
pick<T>(array: T[]): T {
|
|
66
|
+
return array[this.int(0, array.length - 1)];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Shuffles an array in place */
|
|
70
|
+
shuffle<T>(array: T[]): T[] {
|
|
71
|
+
const result = [...array];
|
|
72
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
73
|
+
const j = this.int(0, i);
|
|
74
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// TEMPLATE POOLS (12+ per relationship type, per G.002 mandate)
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Instruction templates for spatial_adjacent relationship.
|
|
86
|
+
* Each template has a question and answer format with placeholders.
|
|
87
|
+
*/
|
|
88
|
+
const ADJACENT_QUESTION_TEMPLATES: Array<{
|
|
89
|
+
question: (src: string, tgt: string, dist: number, maxDist: number) => string;
|
|
90
|
+
positiveAnswer: (src: string, tgt: string, dist: number, maxDist: number) => string;
|
|
91
|
+
negativeAnswer: (src: string, tgt: string, dist: number, maxDist: number) => string;
|
|
92
|
+
}> = [
|
|
93
|
+
{
|
|
94
|
+
question: (src, tgt, _d, maxDist) =>
|
|
95
|
+
`Is "${src}" within ${maxDist}m of "${tgt}" in this scene?`,
|
|
96
|
+
positiveAnswer: (src, tgt, dist, maxDist) =>
|
|
97
|
+
`Yes. "${src}" is ${dist.toFixed(1)}m from "${tgt}", which is within the ${maxDist}m adjacency constraint.`,
|
|
98
|
+
negativeAnswer: (src, tgt, dist, maxDist) =>
|
|
99
|
+
`No. "${src}" is ${dist.toFixed(1)}m from "${tgt}", which exceeds the ${maxDist}m adjacency constraint.`,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
question: (src, tgt) => `Are "${src}" and "${tgt}" adjacent to each other?`,
|
|
103
|
+
positiveAnswer: (src, tgt, dist) =>
|
|
104
|
+
`Yes, "${src}" and "${tgt}" are adjacent. They are ${dist.toFixed(1)}m apart, satisfying the adjacency constraint.`,
|
|
105
|
+
negativeAnswer: (src, tgt, dist, maxDist) =>
|
|
106
|
+
`No, "${src}" and "${tgt}" are not adjacent. At ${dist.toFixed(1)}m apart, they exceed the ${maxDist}m maximum distance.`,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
question: (src, tgt, _d, maxDist) =>
|
|
110
|
+
`Does the spatial_adjacent constraint between "${src}" and "${tgt}" (maxDistance: ${maxDist}m) pass?`,
|
|
111
|
+
positiveAnswer: (src, tgt, dist, maxDist) =>
|
|
112
|
+
`The constraint passes. The distance between "${src}" and "${tgt}" is ${dist.toFixed(1)}m, within the ${maxDist}m limit.`,
|
|
113
|
+
negativeAnswer: (src, tgt, dist, maxDist) =>
|
|
114
|
+
`The constraint fails. The distance between "${src}" and "${tgt}" is ${dist.toFixed(1)}m, exceeding the ${maxDist}m limit.`,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
question: (src, tgt) =>
|
|
118
|
+
`What is the spatial relationship between "${src}" and "${tgt}"? Are they close enough to interact?`,
|
|
119
|
+
positiveAnswer: (src, tgt, dist) =>
|
|
120
|
+
`"${src}" and "${tgt}" are close enough to interact. They are ${dist.toFixed(1)}m apart, within the adjacency threshold.`,
|
|
121
|
+
negativeAnswer: (src, tgt, dist) =>
|
|
122
|
+
`"${src}" and "${tgt}" are too far apart to interact. At ${dist.toFixed(1)}m apart, they fail the adjacency check.`,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
question: (src, tgt, _d, maxDist) =>
|
|
126
|
+
`Can "${src}" reach "${tgt}" given the ${maxDist}m adjacency requirement?`,
|
|
127
|
+
positiveAnswer: (src, tgt, dist) =>
|
|
128
|
+
`Yes, "${src}" can reach "${tgt}". The current distance of ${dist.toFixed(1)}m satisfies the adjacency requirement.`,
|
|
129
|
+
negativeAnswer: (src, tgt, dist, maxDist) =>
|
|
130
|
+
`No, "${src}" cannot reach "${tgt}". The ${dist.toFixed(1)}m distance exceeds the ${maxDist}m requirement.`,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
question: (src, tgt) =>
|
|
134
|
+
`In this HoloScript composition, evaluate whether "${src}" satisfies its adjacency constraint with "${tgt}".`,
|
|
135
|
+
positiveAnswer: (src, tgt, dist, maxDist) =>
|
|
136
|
+
`The adjacency constraint is satisfied. "${src}" is ${dist.toFixed(1)}m from "${tgt}" (max allowed: ${maxDist}m).`,
|
|
137
|
+
negativeAnswer: (src, tgt, dist, maxDist) =>
|
|
138
|
+
`The adjacency constraint is violated. "${src}" is ${dist.toFixed(1)}m from "${tgt}" but must be within ${maxDist}m.`,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
question: (_src, _tgt, _d, maxDist) =>
|
|
142
|
+
`Which objects in this scene are within ${maxDist}m of each other?`,
|
|
143
|
+
positiveAnswer: (src, tgt, dist) =>
|
|
144
|
+
`"${src}" and "${tgt}" are within range at ${dist.toFixed(1)}m apart.`,
|
|
145
|
+
negativeAnswer: (src, tgt, dist) =>
|
|
146
|
+
`"${src}" and "${tgt}" are NOT within range. They are ${dist.toFixed(1)}m apart.`,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
question: (src, tgt) =>
|
|
150
|
+
`If I move "${src}" to its current position, will it still be adjacent to "${tgt}"?`,
|
|
151
|
+
positiveAnswer: (_src, _tgt, dist, maxDist) =>
|
|
152
|
+
`Yes, at its current position the distance is ${dist.toFixed(1)}m, which maintains adjacency (max: ${maxDist}m).`,
|
|
153
|
+
negativeAnswer: (_src, _tgt, dist, maxDist) =>
|
|
154
|
+
`No, at its current position the distance is ${dist.toFixed(1)}m, which breaks the ${maxDist}m adjacency constraint.`,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
question: (src, tgt, _d, maxDist) =>
|
|
158
|
+
`How far is "${src}" from "${tgt}"? Is it within the ${maxDist}m threshold?`,
|
|
159
|
+
positiveAnswer: (src, tgt, dist, maxDist) =>
|
|
160
|
+
`"${src}" is ${dist.toFixed(1)}m from "${tgt}". This is within the ${maxDist}m threshold, so the adjacency holds.`,
|
|
161
|
+
negativeAnswer: (src, tgt, dist, maxDist) =>
|
|
162
|
+
`"${src}" is ${dist.toFixed(1)}m from "${tgt}". This exceeds the ${maxDist}m threshold, so adjacency is violated.`,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
question: (src, tgt) =>
|
|
166
|
+
`Analyze the spatial_adjacent constraint: is "${src}" properly positioned relative to "${tgt}"?`,
|
|
167
|
+
positiveAnswer: (src, tgt, dist, maxDist) =>
|
|
168
|
+
`"${src}" is properly positioned at ${dist.toFixed(1)}m from "${tgt}", satisfying the ${maxDist}m adjacency constraint.`,
|
|
169
|
+
negativeAnswer: (src, tgt, dist, maxDist) =>
|
|
170
|
+
`"${src}" is improperly positioned at ${dist.toFixed(1)}m from "${tgt}". It should be within ${maxDist}m.`,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
question: (src, tgt) =>
|
|
174
|
+
`Does the scene layout satisfy the proximity requirement between "${src}" and "${tgt}"?`,
|
|
175
|
+
positiveAnswer: (src, tgt, dist) =>
|
|
176
|
+
`Yes, the proximity requirement is satisfied. "${src}" and "${tgt}" are ${dist.toFixed(1)}m apart.`,
|
|
177
|
+
negativeAnswer: (src, tgt, dist, maxDist) =>
|
|
178
|
+
`No, the proximity requirement is not satisfied. "${src}" is ${dist.toFixed(1)}m from "${tgt}" (max: ${maxDist}m).`,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
question: (src, tgt, _d, maxDist) =>
|
|
182
|
+
`Given the HoloScript scene below, would placing "${src}" at its position violate the ${maxDist}m adjacency with "${tgt}"?`,
|
|
183
|
+
positiveAnswer: (_src, _tgt, dist, maxDist) =>
|
|
184
|
+
`No violation. The position results in a ${dist.toFixed(1)}m distance, within the ${maxDist}m limit.`,
|
|
185
|
+
negativeAnswer: (_src, _tgt, dist, maxDist) =>
|
|
186
|
+
`Violation detected. The position results in a ${dist.toFixed(1)}m distance, exceeding the ${maxDist}m limit.`,
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Instruction templates for spatial_contains relationship.
|
|
192
|
+
*/
|
|
193
|
+
const CONTAINS_QUESTION_TEMPLATES: Array<{
|
|
194
|
+
question: (container: string, contained: string) => string;
|
|
195
|
+
positiveAnswer: (container: string, contained: string) => string;
|
|
196
|
+
negativeAnswer: (container: string, contained: string) => string;
|
|
197
|
+
}> = [
|
|
198
|
+
{
|
|
199
|
+
question: (container, contained) => `Is "${contained}" inside "${container}"?`,
|
|
200
|
+
positiveAnswer: (container, contained) =>
|
|
201
|
+
`Yes, "${contained}" is fully contained within "${container}"'s bounds.`,
|
|
202
|
+
negativeAnswer: (container, contained) =>
|
|
203
|
+
`No, "${contained}" is outside "${container}"'s bounds.`,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
question: (container, contained) =>
|
|
207
|
+
`Does "${container}" contain "${contained}" according to the spatial_contains constraint?`,
|
|
208
|
+
positiveAnswer: (container, contained) =>
|
|
209
|
+
`Yes, the spatial_contains constraint is satisfied. "${contained}" is within "${container}".`,
|
|
210
|
+
negativeAnswer: (container, contained) =>
|
|
211
|
+
`No, the spatial_contains constraint is violated. "${contained}" extends beyond "${container}"'s bounds.`,
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
question: (container, contained) =>
|
|
215
|
+
`Verify whether "${contained}" fits entirely within "${container}"'s bounding volume.`,
|
|
216
|
+
positiveAnswer: (container, contained) =>
|
|
217
|
+
`Verified: "${contained}" fits entirely within "${container}"'s bounding volume.`,
|
|
218
|
+
negativeAnswer: (container, contained) =>
|
|
219
|
+
`Verification failed: "${contained}" does not fit within "${container}"'s bounding volume.`,
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
question: (container) => `Which objects are inside "${container}" in this scene?`,
|
|
223
|
+
positiveAnswer: (container, contained) =>
|
|
224
|
+
`"${contained}" is inside "${container}", satisfying the containment constraint.`,
|
|
225
|
+
negativeAnswer: (container, contained) =>
|
|
226
|
+
`"${contained}" is NOT inside "${container}". It has moved outside the container bounds.`,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
question: (container, contained) =>
|
|
230
|
+
`If "${container}" has bounds from its declared size, is "${contained}" at its current position within those bounds?`,
|
|
231
|
+
positiveAnswer: (_container, contained) =>
|
|
232
|
+
`Yes, "${contained}" at its current position falls within the container's declared bounds.`,
|
|
233
|
+
negativeAnswer: (_container, contained) =>
|
|
234
|
+
`No, "${contained}" at its current position falls outside the container's declared bounds.`,
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
question: (container, contained) =>
|
|
238
|
+
`Evaluate the containment relationship: does "${container}" enclose "${contained}"?`,
|
|
239
|
+
positiveAnswer: (container, contained) =>
|
|
240
|
+
`"${container}" successfully encloses "${contained}". The containment constraint is satisfied.`,
|
|
241
|
+
negativeAnswer: (container, contained) =>
|
|
242
|
+
`"${container}" does not enclose "${contained}". The containment constraint is violated.`,
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
question: (container, contained) =>
|
|
246
|
+
`In this HoloScript composition, check if "${contained}" remains within the zone defined by "${container}".`,
|
|
247
|
+
positiveAnswer: (container, contained) =>
|
|
248
|
+
`"${contained}" remains within the zone defined by "${container}".`,
|
|
249
|
+
negativeAnswer: (container, contained) =>
|
|
250
|
+
`"${contained}" has left the zone defined by "${container}".`,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
question: (container, contained) =>
|
|
254
|
+
`Can "${contained}" exist at its current position without violating the containment constraint with "${container}"?`,
|
|
255
|
+
positiveAnswer: (_container, contained) =>
|
|
256
|
+
`Yes, "${contained}" can exist at its current position. It is within the container bounds.`,
|
|
257
|
+
negativeAnswer: (_container, contained) =>
|
|
258
|
+
`No, "${contained}" at its current position violates the containment constraint. It is outside the bounds.`,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
question: (container, contained) =>
|
|
262
|
+
`Does the bounding box of "${container}" fully enclose the position of "${contained}"?`,
|
|
263
|
+
positiveAnswer: (container, contained) =>
|
|
264
|
+
`Yes, "${container}"'s bounding box fully encloses "${contained}"'s position.`,
|
|
265
|
+
negativeAnswer: (container, contained) =>
|
|
266
|
+
`No, "${container}"'s bounding box does not enclose "${contained}"'s position.`,
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
question: (container, contained) =>
|
|
270
|
+
`Analyze the spatial hierarchy: is "${contained}" a valid child within "${container}"'s spatial region?`,
|
|
271
|
+
positiveAnswer: (container, contained) =>
|
|
272
|
+
`"${contained}" is a valid child within "${container}"'s spatial region. Containment is satisfied.`,
|
|
273
|
+
negativeAnswer: (container, contained) =>
|
|
274
|
+
`"${contained}" is NOT a valid child within "${container}"'s spatial region. It extends outside the boundaries.`,
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
question: (container, contained) =>
|
|
278
|
+
`Given the margin constraint, is "${contained}" properly contained within "${container}"?`,
|
|
279
|
+
positiveAnswer: (container, contained) =>
|
|
280
|
+
`With the margin applied, "${contained}" is properly contained within "${container}".`,
|
|
281
|
+
negativeAnswer: (container, contained) =>
|
|
282
|
+
`With the margin applied, "${contained}" is NOT properly contained within "${container}". It is too close to or beyond the boundary.`,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
question: (container, contained) =>
|
|
286
|
+
`Would moving "${contained}" to its declared position cause it to exit "${container}"?`,
|
|
287
|
+
positiveAnswer: (container, contained) =>
|
|
288
|
+
`No, "${contained}" at its declared position remains inside "${container}".`,
|
|
289
|
+
negativeAnswer: (container, contained) =>
|
|
290
|
+
`Yes, "${contained}" at its declared position would be outside "${container}".`,
|
|
291
|
+
},
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Instruction templates for spatial_reachable relationship.
|
|
296
|
+
*/
|
|
297
|
+
const REACHABLE_QUESTION_TEMPLATES: Array<{
|
|
298
|
+
question: (src: string, tgt: string, obstacles: string[]) => string;
|
|
299
|
+
positiveAnswer: (src: string, tgt: string, pathLen: number) => string;
|
|
300
|
+
negativeAnswer: (src: string, tgt: string, blocker: string) => string;
|
|
301
|
+
}> = [
|
|
302
|
+
{
|
|
303
|
+
question: (src, tgt) => `Is there a clear path from "${src}" to "${tgt}"?`,
|
|
304
|
+
positiveAnswer: (src, tgt, pathLen) =>
|
|
305
|
+
`Yes, there is a clear path from "${src}" to "${tgt}" with a path length of ${pathLen.toFixed(1)}m.`,
|
|
306
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
307
|
+
`No, the path from "${src}" to "${tgt}" is blocked by "${blocker}".`,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
question: (src, tgt) => `Can "${src}" reach "${tgt}" without obstruction?`,
|
|
311
|
+
positiveAnswer: (src, tgt, pathLen) =>
|
|
312
|
+
`Yes, "${src}" can reach "${tgt}" unobstructed. The path length is ${pathLen.toFixed(1)}m.`,
|
|
313
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
314
|
+
`No, "${src}" cannot reach "${tgt}". The obstacle "${blocker}" blocks the path.`,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
question: (src, tgt) =>
|
|
318
|
+
`Does the spatial_reachable constraint between "${src}" and "${tgt}" pass?`,
|
|
319
|
+
positiveAnswer: (src, tgt, pathLen) =>
|
|
320
|
+
`The constraint passes. "${src}" can reach "${tgt}" via a ${pathLen.toFixed(1)}m path.`,
|
|
321
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
322
|
+
`The constraint fails. "${blocker}" obstructs the path between "${src}" and "${tgt}".`,
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
question: (src, tgt, obstacles) =>
|
|
326
|
+
`Given obstacles [${obstacles.map((o) => `"${o}"`).join(', ')}], can "${src}" see "${tgt}"?`,
|
|
327
|
+
positiveAnswer: (src, tgt) =>
|
|
328
|
+
`Yes, despite the obstacles, "${src}" has line of sight to "${tgt}".`,
|
|
329
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
330
|
+
`No, "${blocker}" blocks the line of sight from "${src}" to "${tgt}".`,
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
question: (src, tgt) =>
|
|
334
|
+
`Evaluate whether "${src}" has an unobstructed path to "${tgt}" in this scene.`,
|
|
335
|
+
positiveAnswer: (src, tgt, pathLen) =>
|
|
336
|
+
`"${src}" has an unobstructed path to "${tgt}" measuring ${pathLen.toFixed(1)}m.`,
|
|
337
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
338
|
+
`"${src}" does NOT have an unobstructed path to "${tgt}". "${blocker}" is in the way.`,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
question: (src, tgt) => `In this HoloScript scene, is "${tgt}" reachable from "${src}"?`,
|
|
342
|
+
positiveAnswer: (_src, tgt, pathLen) =>
|
|
343
|
+
`Yes, "${tgt}" is reachable with a direct path of ${pathLen.toFixed(1)}m.`,
|
|
344
|
+
negativeAnswer: (_src, tgt, blocker) =>
|
|
345
|
+
`No, "${tgt}" is not reachable. An obstacle ("${blocker}") blocks the direct path.`,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
question: (src, tgt) => `Check the reachability constraint: can "${src}" navigate to "${tgt}"?`,
|
|
349
|
+
positiveAnswer: (src, tgt, pathLen) =>
|
|
350
|
+
`Reachability check passed: "${src}" can navigate to "${tgt}" (${pathLen.toFixed(1)}m path).`,
|
|
351
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
352
|
+
`Reachability check failed: "${src}" cannot navigate to "${tgt}" due to "${blocker}".`,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
question: (src, tgt) => `Is the line of sight between "${src}" and "${tgt}" clear?`,
|
|
356
|
+
positiveAnswer: (src, tgt) => `Yes, line of sight between "${src}" and "${tgt}" is clear.`,
|
|
357
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
358
|
+
`No, line of sight is blocked. "${blocker}" obstructs the view from "${src}" to "${tgt}".`,
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
question: (src, tgt, obstacles) =>
|
|
362
|
+
`Considering ${obstacles.length} obstacle(s) in the scene, determine if "${src}" can reach "${tgt}".`,
|
|
363
|
+
positiveAnswer: (src, tgt, pathLen) =>
|
|
364
|
+
`Despite the obstacles, "${src}" can reach "${tgt}" via a ${pathLen.toFixed(1)}m path that avoids all obstructions.`,
|
|
365
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
366
|
+
`"${src}" cannot reach "${tgt}". The path is blocked by "${blocker}".`,
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
question: (src, tgt) =>
|
|
370
|
+
`Would placing a wall between "${src}" and "${tgt}" affect reachability? Analyze the current state.`,
|
|
371
|
+
positiveAnswer: (src, tgt) =>
|
|
372
|
+
`Currently, "${src}" and "${tgt}" are mutually reachable. Adding a wall could change this.`,
|
|
373
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
374
|
+
`Currently, "${src}" and "${tgt}" are NOT reachable due to "${blocker}". Adding more walls would not change this.`,
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
question: (src, tgt) =>
|
|
378
|
+
`Analyze path connectivity: does an unblocked route exist from "${src}" to "${tgt}"?`,
|
|
379
|
+
positiveAnswer: (src, tgt, pathLen) =>
|
|
380
|
+
`An unblocked route exists from "${src}" to "${tgt}" with a total distance of ${pathLen.toFixed(1)}m.`,
|
|
381
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
382
|
+
`No unblocked route exists from "${src}" to "${tgt}". "${blocker}" creates a barrier.`,
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
question: (src, tgt) =>
|
|
386
|
+
`For NPC pathfinding, can "${src}" walk to "${tgt}" in a straight line?`,
|
|
387
|
+
positiveAnswer: (src, tgt, pathLen) =>
|
|
388
|
+
`Yes, "${src}" can walk directly to "${tgt}" in a straight line (${pathLen.toFixed(1)}m).`,
|
|
389
|
+
negativeAnswer: (src, tgt, blocker) =>
|
|
390
|
+
`No, "${src}" cannot walk straight to "${tgt}". "${blocker}" is obstructing the direct path.`,
|
|
391
|
+
},
|
|
392
|
+
];
|
|
393
|
+
|
|
394
|
+
// =============================================================================
|
|
395
|
+
// OBJECT NAME POOLS
|
|
396
|
+
// =============================================================================
|
|
397
|
+
|
|
398
|
+
const OBJECT_NAMES = [
|
|
399
|
+
'Table',
|
|
400
|
+
'Chair',
|
|
401
|
+
'Lamp',
|
|
402
|
+
'Bookshelf',
|
|
403
|
+
'Desk',
|
|
404
|
+
'Sofa',
|
|
405
|
+
'Cabinet',
|
|
406
|
+
'Mirror',
|
|
407
|
+
'Plant',
|
|
408
|
+
'Clock',
|
|
409
|
+
'Vase',
|
|
410
|
+
'Rug',
|
|
411
|
+
'Painting',
|
|
412
|
+
'Shelf',
|
|
413
|
+
'Stool',
|
|
414
|
+
'Bench',
|
|
415
|
+
'Chest',
|
|
416
|
+
'Barrel',
|
|
417
|
+
'Crate',
|
|
418
|
+
'Box',
|
|
419
|
+
'Pillar',
|
|
420
|
+
'Statue',
|
|
421
|
+
'Crystal',
|
|
422
|
+
'Orb',
|
|
423
|
+
'Pedestal',
|
|
424
|
+
'Platform',
|
|
425
|
+
'Beacon',
|
|
426
|
+
'Terminal',
|
|
427
|
+
'Console',
|
|
428
|
+
'Panel',
|
|
429
|
+
];
|
|
430
|
+
|
|
431
|
+
const ZONE_NAMES = [
|
|
432
|
+
'Room',
|
|
433
|
+
'Hall',
|
|
434
|
+
'Chamber',
|
|
435
|
+
'Arena',
|
|
436
|
+
'Gallery',
|
|
437
|
+
'Vault',
|
|
438
|
+
'Atrium',
|
|
439
|
+
'Courtyard',
|
|
440
|
+
'Alcove',
|
|
441
|
+
'Corridor',
|
|
442
|
+
'Laboratory',
|
|
443
|
+
'Workshop',
|
|
444
|
+
];
|
|
445
|
+
|
|
446
|
+
const OBSTACLE_NAMES = [
|
|
447
|
+
'Wall',
|
|
448
|
+
'Barrier',
|
|
449
|
+
'Fence',
|
|
450
|
+
'Pillar',
|
|
451
|
+
'Column',
|
|
452
|
+
'Boulder',
|
|
453
|
+
'Shield',
|
|
454
|
+
'Gate',
|
|
455
|
+
'Blockade',
|
|
456
|
+
'Partition',
|
|
457
|
+
'Divider',
|
|
458
|
+
'Screen',
|
|
459
|
+
];
|
|
460
|
+
|
|
461
|
+
const NPC_NAMES = [
|
|
462
|
+
'Guard',
|
|
463
|
+
'Merchant',
|
|
464
|
+
'Explorer',
|
|
465
|
+
'Scout',
|
|
466
|
+
'Villager',
|
|
467
|
+
'Artisan',
|
|
468
|
+
'Drone',
|
|
469
|
+
'Robot',
|
|
470
|
+
'Companion',
|
|
471
|
+
'Agent',
|
|
472
|
+
'Sentinel',
|
|
473
|
+
'Worker',
|
|
474
|
+
];
|
|
475
|
+
|
|
476
|
+
const GEOMETRY_TYPES = ['cube', 'sphere', 'cylinder', 'torus', 'cone', 'prism'];
|
|
477
|
+
|
|
478
|
+
const COLORS = [
|
|
479
|
+
'#ff0000',
|
|
480
|
+
'#00ff00',
|
|
481
|
+
'#0000ff',
|
|
482
|
+
'#ffff00',
|
|
483
|
+
'#ff00ff',
|
|
484
|
+
'#00ffff',
|
|
485
|
+
'#ff8800',
|
|
486
|
+
'#8800ff',
|
|
487
|
+
'#00ff88',
|
|
488
|
+
'#ff0088',
|
|
489
|
+
'#888888',
|
|
490
|
+
'#ffffff',
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
// =============================================================================
|
|
494
|
+
// HOLOSCRIPT GENERATION HELPERS
|
|
495
|
+
// =============================================================================
|
|
496
|
+
|
|
497
|
+
function generateObjectBlock(obj: SceneObject, indent: string = ' '): string {
|
|
498
|
+
const lines: string[] = [];
|
|
499
|
+
lines.push(`${indent}object "${obj.id}" {`);
|
|
500
|
+
if (obj.geometry) {
|
|
501
|
+
lines.push(`${indent} geometry: "${obj.geometry}"`);
|
|
502
|
+
}
|
|
503
|
+
lines.push(
|
|
504
|
+
`${indent} position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
505
|
+
);
|
|
506
|
+
if (obj.scale.x !== 1 || obj.scale.y !== 1 || obj.scale.z !== 1) {
|
|
507
|
+
lines.push(
|
|
508
|
+
`${indent} scale: [${obj.scale.x.toFixed(1)}, ${obj.scale.y.toFixed(1)}, ${obj.scale.z.toFixed(1)}]`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
if (obj.color) {
|
|
512
|
+
lines.push(`${indent} color: "${obj.color}"`);
|
|
513
|
+
}
|
|
514
|
+
lines.push(`${indent}}`);
|
|
515
|
+
return lines.join('\n');
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function generateZoneBlock(obj: SceneObject, indent: string = ' '): string {
|
|
519
|
+
const lines: string[] = [];
|
|
520
|
+
lines.push(`${indent}zone "${obj.id}" {`);
|
|
521
|
+
lines.push(`${indent} shape: "box"`);
|
|
522
|
+
const sx = obj.scale.x;
|
|
523
|
+
const sy = obj.scale.y;
|
|
524
|
+
const sz = obj.scale.z;
|
|
525
|
+
lines.push(`${indent} size: [${sx.toFixed(1)}, ${sy.toFixed(1)}, ${sz.toFixed(1)}]`);
|
|
526
|
+
lines.push(
|
|
527
|
+
`${indent} position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
528
|
+
);
|
|
529
|
+
lines.push(`${indent}}`);
|
|
530
|
+
return lines.join('\n');
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function generateAdjacentTrait(targetId: string, maxDist: number, axis?: string): string {
|
|
534
|
+
const parts = [`target: "${targetId}"`, `maxDistance: ${maxDist.toFixed(1)}m`];
|
|
535
|
+
if (axis && axis !== 'xyz') {
|
|
536
|
+
parts.push(`axis: "${axis}"`);
|
|
537
|
+
}
|
|
538
|
+
return `@spatial_adjacent(${parts.join(', ')})`;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function generateContainsTrait(targetId: string, margin?: number, strict?: boolean): string {
|
|
542
|
+
const parts = [`target: "${targetId}"`];
|
|
543
|
+
if (margin !== undefined && margin > 0) {
|
|
544
|
+
parts.push(`margin: ${margin.toFixed(1)}m`);
|
|
545
|
+
}
|
|
546
|
+
if (strict) {
|
|
547
|
+
parts.push('strict: true');
|
|
548
|
+
}
|
|
549
|
+
return `@spatial_contains(${parts.join(', ')})`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function generateReachableTrait(
|
|
553
|
+
targetId: string,
|
|
554
|
+
maxPathLength?: number,
|
|
555
|
+
obstacles?: string[],
|
|
556
|
+
algorithm?: string
|
|
557
|
+
): string {
|
|
558
|
+
const parts = [`target: "${targetId}"`];
|
|
559
|
+
if (maxPathLength !== undefined) {
|
|
560
|
+
parts.push(`maxPathLength: ${maxPathLength.toFixed(0)}m`);
|
|
561
|
+
}
|
|
562
|
+
if (obstacles && obstacles.length > 0) {
|
|
563
|
+
parts.push(`obstacles: [${obstacles.map((o) => `"${o}"`).join(', ')}]`);
|
|
564
|
+
}
|
|
565
|
+
if (algorithm) {
|
|
566
|
+
parts.push(`algorithm: "${algorithm}"`);
|
|
567
|
+
}
|
|
568
|
+
return `@spatial_reachable(${parts.join(', ')})`;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function computeDistance(
|
|
572
|
+
a: { x: number; y: number; z: number },
|
|
573
|
+
b: { x: number; y: number; z: number }
|
|
574
|
+
): number {
|
|
575
|
+
const dx = b.x - a.x;
|
|
576
|
+
const dy = b.y - a.y;
|
|
577
|
+
const dz = b.z - a.z;
|
|
578
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function isPointInBox(
|
|
582
|
+
point: { x: number; y: number; z: number },
|
|
583
|
+
bounds: { min: { x: number; y: number; z: number }; max: { x: number; y: number; z: number } },
|
|
584
|
+
margin: number = 0
|
|
585
|
+
): boolean {
|
|
586
|
+
return (
|
|
587
|
+
point.x >= bounds.min.x + margin &&
|
|
588
|
+
point.x <= bounds.max.x - margin &&
|
|
589
|
+
point.y >= bounds.min.y + margin &&
|
|
590
|
+
point.y <= bounds.max.y - margin &&
|
|
591
|
+
point.z >= bounds.min.z + margin &&
|
|
592
|
+
point.z <= bounds.max.z - margin
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Simple line-segment vs AABB intersection test.
|
|
598
|
+
* Returns true if the line from `a` to `b` intersects the box.
|
|
599
|
+
*/
|
|
600
|
+
function lineIntersectsBox(
|
|
601
|
+
a: { x: number; y: number; z: number },
|
|
602
|
+
b: { x: number; y: number; z: number },
|
|
603
|
+
box: { min: { x: number; y: number; z: number }; max: { x: number; y: number; z: number } }
|
|
604
|
+
): boolean {
|
|
605
|
+
const dir = { x: b.x - a.x, y: b.y - a.y, z: b.z - a.z };
|
|
606
|
+
const len = Math.sqrt(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z);
|
|
607
|
+
if (len === 0) return false;
|
|
608
|
+
|
|
609
|
+
let tmin = 0;
|
|
610
|
+
let tmax = 1;
|
|
611
|
+
|
|
612
|
+
const axes: Array<'x' | 'y' | 'z'> = ['x', 'y', 'z'];
|
|
613
|
+
for (const axis of axes) {
|
|
614
|
+
const d = dir[axis];
|
|
615
|
+
const o = a[axis];
|
|
616
|
+
const bmin = box.min[axis];
|
|
617
|
+
const bmax = box.max[axis];
|
|
618
|
+
|
|
619
|
+
if (Math.abs(d) < 1e-10) {
|
|
620
|
+
if (o < bmin || o > bmax) return false;
|
|
621
|
+
} else {
|
|
622
|
+
let t1 = (bmin - o) / d;
|
|
623
|
+
let t2 = (bmax - o) / d;
|
|
624
|
+
if (t1 > t2) {
|
|
625
|
+
const tmp = t1;
|
|
626
|
+
t1 = t2;
|
|
627
|
+
t2 = tmp;
|
|
628
|
+
}
|
|
629
|
+
tmin = Math.max(tmin, t1);
|
|
630
|
+
tmax = Math.min(tmax, t2);
|
|
631
|
+
if (tmin > tmax) return false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// =============================================================================
|
|
639
|
+
// SPATIAL TRAINING DATA GENERATOR
|
|
640
|
+
// =============================================================================
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Generates labeled spatial reasoning examples from HoloScript compositions.
|
|
644
|
+
*
|
|
645
|
+
* @example
|
|
646
|
+
* ```typescript
|
|
647
|
+
* const generator = new SpatialTrainingDataGenerator({ seed: 42 });
|
|
648
|
+
* const examples = generator.generate();
|
|
649
|
+
* const jsonl = generator.exportJSONL(examples);
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
export class SpatialTrainingDataGenerator {
|
|
653
|
+
private readonly config: Required<SpatialGeneratorConfig>;
|
|
654
|
+
private rng: SeededRandom;
|
|
655
|
+
private exampleCounter: number = 0;
|
|
656
|
+
|
|
657
|
+
constructor(config: SpatialGeneratorConfig = {}) {
|
|
658
|
+
this.config = {
|
|
659
|
+
examplesPerCategory: config.examplesPerCategory ?? 10,
|
|
660
|
+
relationshipTypes: config.relationshipTypes ?? [
|
|
661
|
+
'spatial_adjacent',
|
|
662
|
+
'spatial_contains',
|
|
663
|
+
'spatial_reachable',
|
|
664
|
+
],
|
|
665
|
+
difficultyLevels: config.difficultyLevels ?? ['basic', 'intermediate', 'advanced'],
|
|
666
|
+
positiveRatio: config.positiveRatio ?? 0.5,
|
|
667
|
+
seed: config.seed ?? Date.now(),
|
|
668
|
+
includeContext: config.includeContext ?? true,
|
|
669
|
+
};
|
|
670
|
+
this.rng = new SeededRandom(this.config.seed);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// ---------------------------------------------------------------------------
|
|
674
|
+
// Public API
|
|
675
|
+
// ---------------------------------------------------------------------------
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Generate all spatial training examples based on configuration.
|
|
679
|
+
*/
|
|
680
|
+
generate(): SpatialTrainingExample[] {
|
|
681
|
+
const examples: SpatialTrainingExample[] = [];
|
|
682
|
+
this.exampleCounter = 0;
|
|
683
|
+
|
|
684
|
+
for (const relType of this.config.relationshipTypes) {
|
|
685
|
+
for (const difficulty of this.config.difficultyLevels) {
|
|
686
|
+
const count = this.config.examplesPerCategory;
|
|
687
|
+
|
|
688
|
+
for (let i = 0; i < count; i++) {
|
|
689
|
+
const isPositive = this.rng.next() < this.config.positiveRatio;
|
|
690
|
+
const scene = this.generateScene(relType, difficulty, isPositive);
|
|
691
|
+
const example = this.generateExample(scene, relType, isPositive, difficulty);
|
|
692
|
+
examples.push(example);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return examples;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Generate examples for a specific relationship type.
|
|
702
|
+
*/
|
|
703
|
+
generateForRelationship(relType: SpatialRelationshipType): SpatialTrainingExample[] {
|
|
704
|
+
const examples: SpatialTrainingExample[] = [];
|
|
705
|
+
|
|
706
|
+
for (const difficulty of this.config.difficultyLevels) {
|
|
707
|
+
const count = this.config.examplesPerCategory;
|
|
708
|
+
|
|
709
|
+
for (let i = 0; i < count; i++) {
|
|
710
|
+
const isPositive = this.rng.next() < this.config.positiveRatio;
|
|
711
|
+
const scene = this.generateScene(relType, difficulty, isPositive);
|
|
712
|
+
const example = this.generateExample(scene, relType, isPositive, difficulty);
|
|
713
|
+
examples.push(example);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return examples;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Generate examples for a specific difficulty level.
|
|
722
|
+
*/
|
|
723
|
+
generateForDifficulty(difficulty: SpatialDifficulty): SpatialTrainingExample[] {
|
|
724
|
+
const examples: SpatialTrainingExample[] = [];
|
|
725
|
+
|
|
726
|
+
for (const relType of this.config.relationshipTypes) {
|
|
727
|
+
const count = this.config.examplesPerCategory;
|
|
728
|
+
|
|
729
|
+
for (let i = 0; i < count; i++) {
|
|
730
|
+
const isPositive = this.rng.next() < this.config.positiveRatio;
|
|
731
|
+
const scene = this.generateScene(relType, difficulty, isPositive);
|
|
732
|
+
const example = this.generateExample(scene, relType, isPositive, difficulty);
|
|
733
|
+
examples.push(example);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return examples;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Export examples as JSONL string (one JSON object per line).
|
|
742
|
+
*/
|
|
743
|
+
exportJSONL(examples: SpatialTrainingExample[]): string {
|
|
744
|
+
return examples
|
|
745
|
+
.map((ex) => {
|
|
746
|
+
const entry: SpatialTrainingJSONLEntry = {
|
|
747
|
+
instruction: this.config.includeContext
|
|
748
|
+
? `${ex.instruction}\n\nHoloScript Scene:\n\`\`\`holoscript\n${ex.context}\n\`\`\``
|
|
749
|
+
: ex.instruction,
|
|
750
|
+
response: ex.response,
|
|
751
|
+
metadata: {
|
|
752
|
+
id: ex.id,
|
|
753
|
+
relationship_type: ex.relationshipType,
|
|
754
|
+
is_positive: ex.isPositive,
|
|
755
|
+
difficulty: ex.difficulty,
|
|
756
|
+
tags: ex.tags,
|
|
757
|
+
},
|
|
758
|
+
};
|
|
759
|
+
return JSON.stringify(entry);
|
|
760
|
+
})
|
|
761
|
+
.join('\n');
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Export examples as JSON array string.
|
|
766
|
+
*/
|
|
767
|
+
exportJSON(examples: SpatialTrainingExample[]): string {
|
|
768
|
+
return JSON.stringify(examples, null, 2);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Get statistics about generated examples.
|
|
773
|
+
*/
|
|
774
|
+
getStats(examples: SpatialTrainingExample[]): SpatialGeneratorStats {
|
|
775
|
+
const byRelationship: Record<SpatialRelationshipType, number> = {
|
|
776
|
+
spatial_adjacent: 0,
|
|
777
|
+
spatial_contains: 0,
|
|
778
|
+
spatial_reachable: 0,
|
|
779
|
+
};
|
|
780
|
+
const byDifficulty: Record<SpatialDifficulty, number> = {
|
|
781
|
+
basic: 0,
|
|
782
|
+
intermediate: 0,
|
|
783
|
+
advanced: 0,
|
|
784
|
+
};
|
|
785
|
+
let positiveCount = 0;
|
|
786
|
+
let negativeCount = 0;
|
|
787
|
+
const templateIds = new Set<string>();
|
|
788
|
+
|
|
789
|
+
for (const ex of examples) {
|
|
790
|
+
byRelationship[ex.relationshipType]++;
|
|
791
|
+
byDifficulty[ex.difficulty]++;
|
|
792
|
+
if (ex.isPositive) positiveCount++;
|
|
793
|
+
else negativeCount++;
|
|
794
|
+
// Extract template hint from tags
|
|
795
|
+
const tplTag = ex.tags.find((t) => t.startsWith('template:'));
|
|
796
|
+
if (tplTag) templateIds.add(tplTag);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return {
|
|
800
|
+
totalExamples: examples.length,
|
|
801
|
+
byRelationship,
|
|
802
|
+
byDifficulty,
|
|
803
|
+
positiveCount,
|
|
804
|
+
negativeCount,
|
|
805
|
+
uniqueTemplatesUsed: templateIds.size,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Reset the generator with a new seed.
|
|
811
|
+
*/
|
|
812
|
+
reseed(seed: number): void {
|
|
813
|
+
this.rng = new SeededRandom(seed);
|
|
814
|
+
this.exampleCounter = 0;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// ---------------------------------------------------------------------------
|
|
818
|
+
// Scene Generation
|
|
819
|
+
// ---------------------------------------------------------------------------
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Generate a spatial scene for the given relationship type and difficulty.
|
|
823
|
+
*/
|
|
824
|
+
generateScene(
|
|
825
|
+
relType: SpatialRelationshipType,
|
|
826
|
+
difficulty: SpatialDifficulty,
|
|
827
|
+
isPositive: boolean
|
|
828
|
+
): SpatialScene {
|
|
829
|
+
switch (relType) {
|
|
830
|
+
case 'spatial_adjacent':
|
|
831
|
+
return this.generateAdjacentScene(difficulty, isPositive);
|
|
832
|
+
case 'spatial_contains':
|
|
833
|
+
return this.generateContainsScene(difficulty, isPositive);
|
|
834
|
+
case 'spatial_reachable':
|
|
835
|
+
return this.generateReachableScene(difficulty, isPositive);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// ---------------------------------------------------------------------------
|
|
840
|
+
// Adjacent Scene Generation
|
|
841
|
+
// ---------------------------------------------------------------------------
|
|
842
|
+
|
|
843
|
+
private generateAdjacentScene(difficulty: SpatialDifficulty, isPositive: boolean): SpatialScene {
|
|
844
|
+
const objectCount = this.getObjectCount(difficulty);
|
|
845
|
+
const maxDistance = this.rng.float(1.0, 5.0);
|
|
846
|
+
const objects: SceneObject[] = [];
|
|
847
|
+
const relationships: SpatialRelationship[] = [];
|
|
848
|
+
|
|
849
|
+
// Generate source object
|
|
850
|
+
const srcName = this.rng.pick(OBJECT_NAMES);
|
|
851
|
+
const srcPos = this.randomPosition(difficulty);
|
|
852
|
+
const srcObj: SceneObject = {
|
|
853
|
+
id: srcName,
|
|
854
|
+
type: 'object',
|
|
855
|
+
position: srcPos,
|
|
856
|
+
scale: this.randomScale(),
|
|
857
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
858
|
+
color: this.rng.pick(COLORS),
|
|
859
|
+
};
|
|
860
|
+
objects.push(srcObj);
|
|
861
|
+
|
|
862
|
+
// Generate target object - position depends on whether positive or negative
|
|
863
|
+
const tgtName = this.pickUniqueName(OBJECT_NAMES, [srcName]);
|
|
864
|
+
let tgtPos;
|
|
865
|
+
if (isPositive) {
|
|
866
|
+
// Place within maxDistance
|
|
867
|
+
const angle = this.rng.float(0, Math.PI * 2);
|
|
868
|
+
const elevation = this.rng.float(-0.5, 0.5);
|
|
869
|
+
const dist = this.rng.float(0.5, maxDistance * 0.9);
|
|
870
|
+
tgtPos = {
|
|
871
|
+
x: srcPos.x + Math.cos(angle) * dist,
|
|
872
|
+
y: srcPos.y + elevation,
|
|
873
|
+
z: srcPos.z + Math.sin(angle) * dist,
|
|
874
|
+
};
|
|
875
|
+
} else {
|
|
876
|
+
// Place beyond maxDistance
|
|
877
|
+
const angle = this.rng.float(0, Math.PI * 2);
|
|
878
|
+
const dist = this.rng.float(maxDistance * 1.5, maxDistance * 3.0);
|
|
879
|
+
tgtPos = {
|
|
880
|
+
x: srcPos.x + Math.cos(angle) * dist,
|
|
881
|
+
y: srcPos.y + this.rng.float(-1, 1),
|
|
882
|
+
z: srcPos.z + Math.sin(angle) * dist,
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const tgtObj: SceneObject = {
|
|
887
|
+
id: tgtName,
|
|
888
|
+
type: 'object',
|
|
889
|
+
position: tgtPos,
|
|
890
|
+
scale: this.randomScale(),
|
|
891
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
892
|
+
color: this.rng.pick(COLORS),
|
|
893
|
+
};
|
|
894
|
+
objects.push(tgtObj);
|
|
895
|
+
|
|
896
|
+
const actualDist = computeDistance(srcPos, tgtPos);
|
|
897
|
+
|
|
898
|
+
relationships.push({
|
|
899
|
+
type: 'spatial_adjacent',
|
|
900
|
+
sourceId: srcName,
|
|
901
|
+
targetId: tgtName,
|
|
902
|
+
satisfied: actualDist <= maxDistance,
|
|
903
|
+
params: { maxDistance },
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
// Add extra objects for intermediate/advanced
|
|
907
|
+
const usedNames = [srcName, tgtName];
|
|
908
|
+
for (let i = 2; i < objectCount; i++) {
|
|
909
|
+
const name = this.pickUniqueName(OBJECT_NAMES, usedNames);
|
|
910
|
+
usedNames.push(name);
|
|
911
|
+
objects.push({
|
|
912
|
+
id: name,
|
|
913
|
+
type: 'object',
|
|
914
|
+
position: this.randomPosition(difficulty),
|
|
915
|
+
scale: this.randomScale(),
|
|
916
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
917
|
+
color: this.rng.pick(COLORS),
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Generate HoloScript source
|
|
922
|
+
const holoScript = this.buildAdjacentHoloScript(objects, srcName, tgtName, maxDistance);
|
|
923
|
+
|
|
924
|
+
return {
|
|
925
|
+
name: `AdjacentScene_${this.exampleCounter}`,
|
|
926
|
+
objects,
|
|
927
|
+
relationships,
|
|
928
|
+
difficulty,
|
|
929
|
+
holoScriptSource: holoScript,
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
private buildAdjacentHoloScript(
|
|
934
|
+
objects: SceneObject[],
|
|
935
|
+
srcName: string,
|
|
936
|
+
tgtName: string,
|
|
937
|
+
maxDistance: number
|
|
938
|
+
): string {
|
|
939
|
+
const lines: string[] = [];
|
|
940
|
+
lines.push(`composition "SpatialScene" {`);
|
|
941
|
+
|
|
942
|
+
for (const obj of objects) {
|
|
943
|
+
if (obj.id === srcName) {
|
|
944
|
+
// Source object gets the adjacent trait
|
|
945
|
+
lines.push(` object "${obj.id}" {`);
|
|
946
|
+
if (obj.geometry) lines.push(` geometry: "${obj.geometry}"`);
|
|
947
|
+
lines.push(
|
|
948
|
+
` position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
949
|
+
);
|
|
950
|
+
if (obj.color) lines.push(` color: "${obj.color}"`);
|
|
951
|
+
lines.push(` ${generateAdjacentTrait(tgtName, maxDistance)}`);
|
|
952
|
+
lines.push(' }');
|
|
953
|
+
} else {
|
|
954
|
+
lines.push(generateObjectBlock(obj));
|
|
955
|
+
}
|
|
956
|
+
lines.push('');
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
lines.push('}');
|
|
960
|
+
return lines.join('\n');
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// ---------------------------------------------------------------------------
|
|
964
|
+
// Contains Scene Generation
|
|
965
|
+
// ---------------------------------------------------------------------------
|
|
966
|
+
|
|
967
|
+
private generateContainsScene(difficulty: SpatialDifficulty, isPositive: boolean): SpatialScene {
|
|
968
|
+
const objects: SceneObject[] = [];
|
|
969
|
+
const relationships: SpatialRelationship[] = [];
|
|
970
|
+
|
|
971
|
+
// Generate container (zone)
|
|
972
|
+
const containerName = this.rng.pick(ZONE_NAMES);
|
|
973
|
+
const containerSize = {
|
|
974
|
+
x: this.rng.float(4, 12),
|
|
975
|
+
y: this.rng.float(3, 8),
|
|
976
|
+
z: this.rng.float(4, 12),
|
|
977
|
+
};
|
|
978
|
+
const containerPos = this.randomPosition(difficulty);
|
|
979
|
+
const margin = this.rng.float(0, 0.5);
|
|
980
|
+
|
|
981
|
+
const containerBounds = {
|
|
982
|
+
min: {
|
|
983
|
+
x: containerPos.x - containerSize.x / 2,
|
|
984
|
+
y: containerPos.y - containerSize.y / 2,
|
|
985
|
+
z: containerPos.z - containerSize.z / 2,
|
|
986
|
+
},
|
|
987
|
+
max: {
|
|
988
|
+
x: containerPos.x + containerSize.x / 2,
|
|
989
|
+
y: containerPos.y + containerSize.y / 2,
|
|
990
|
+
z: containerPos.z + containerSize.z / 2,
|
|
991
|
+
},
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
const containerObj: SceneObject = {
|
|
995
|
+
id: containerName,
|
|
996
|
+
type: 'zone',
|
|
997
|
+
position: containerPos,
|
|
998
|
+
scale: containerSize,
|
|
999
|
+
bounds: containerBounds,
|
|
1000
|
+
};
|
|
1001
|
+
objects.push(containerObj);
|
|
1002
|
+
|
|
1003
|
+
// Generate contained object(s)
|
|
1004
|
+
const objectCount = this.getObjectCount(difficulty);
|
|
1005
|
+
const containedName = this.rng.pick(OBJECT_NAMES);
|
|
1006
|
+
let containedPos;
|
|
1007
|
+
|
|
1008
|
+
if (isPositive) {
|
|
1009
|
+
// Place inside container bounds (with margin)
|
|
1010
|
+
containedPos = {
|
|
1011
|
+
x: this.rng.float(
|
|
1012
|
+
containerBounds.min.x + margin + 0.5,
|
|
1013
|
+
containerBounds.max.x - margin - 0.5
|
|
1014
|
+
),
|
|
1015
|
+
y: this.rng.float(
|
|
1016
|
+
containerBounds.min.y + margin + 0.5,
|
|
1017
|
+
containerBounds.max.y - margin - 0.5
|
|
1018
|
+
),
|
|
1019
|
+
z: this.rng.float(
|
|
1020
|
+
containerBounds.min.z + margin + 0.5,
|
|
1021
|
+
containerBounds.max.z - margin - 0.5
|
|
1022
|
+
),
|
|
1023
|
+
};
|
|
1024
|
+
} else {
|
|
1025
|
+
// Place outside container bounds
|
|
1026
|
+
const side = this.rng.int(0, 5);
|
|
1027
|
+
containedPos = { ...containerPos };
|
|
1028
|
+
switch (side) {
|
|
1029
|
+
case 0:
|
|
1030
|
+
containedPos.x = containerBounds.max.x + this.rng.float(1, 5);
|
|
1031
|
+
break;
|
|
1032
|
+
case 1:
|
|
1033
|
+
containedPos.x = containerBounds.min.x - this.rng.float(1, 5);
|
|
1034
|
+
break;
|
|
1035
|
+
case 2:
|
|
1036
|
+
containedPos.y = containerBounds.max.y + this.rng.float(1, 5);
|
|
1037
|
+
break;
|
|
1038
|
+
case 3:
|
|
1039
|
+
containedPos.y = containerBounds.min.y - this.rng.float(1, 5);
|
|
1040
|
+
break;
|
|
1041
|
+
case 4:
|
|
1042
|
+
containedPos.z = containerBounds.max.z + this.rng.float(1, 5);
|
|
1043
|
+
break;
|
|
1044
|
+
case 5:
|
|
1045
|
+
containedPos.z = containerBounds.min.z - this.rng.float(1, 5);
|
|
1046
|
+
break;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const containedObj: SceneObject = {
|
|
1051
|
+
id: containedName,
|
|
1052
|
+
type: 'object',
|
|
1053
|
+
position: containedPos,
|
|
1054
|
+
scale: this.randomScale(),
|
|
1055
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
1056
|
+
color: this.rng.pick(COLORS),
|
|
1057
|
+
};
|
|
1058
|
+
objects.push(containedObj);
|
|
1059
|
+
|
|
1060
|
+
const actuallyContained = isPointInBox(containedPos, containerBounds, margin);
|
|
1061
|
+
|
|
1062
|
+
relationships.push({
|
|
1063
|
+
type: 'spatial_contains',
|
|
1064
|
+
sourceId: containerName,
|
|
1065
|
+
targetId: containedName,
|
|
1066
|
+
satisfied: actuallyContained,
|
|
1067
|
+
params: { margin: margin > 0 ? margin : undefined },
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
// For advanced: add nested containment
|
|
1071
|
+
const usedNames = [containerName, containedName];
|
|
1072
|
+
if (difficulty === 'advanced') {
|
|
1073
|
+
// Add inner container
|
|
1074
|
+
const innerContainerName = this.pickUniqueName(ZONE_NAMES, usedNames);
|
|
1075
|
+
usedNames.push(innerContainerName);
|
|
1076
|
+
const innerSize = {
|
|
1077
|
+
x: containerSize.x * 0.4,
|
|
1078
|
+
y: containerSize.y * 0.4,
|
|
1079
|
+
z: containerSize.z * 0.4,
|
|
1080
|
+
};
|
|
1081
|
+
const innerObj: SceneObject = {
|
|
1082
|
+
id: innerContainerName,
|
|
1083
|
+
type: 'zone',
|
|
1084
|
+
position: { ...containerPos },
|
|
1085
|
+
scale: innerSize,
|
|
1086
|
+
bounds: {
|
|
1087
|
+
min: {
|
|
1088
|
+
x: containerPos.x - innerSize.x / 2,
|
|
1089
|
+
y: containerPos.y - innerSize.y / 2,
|
|
1090
|
+
z: containerPos.z - innerSize.z / 2,
|
|
1091
|
+
},
|
|
1092
|
+
max: {
|
|
1093
|
+
x: containerPos.x + innerSize.x / 2,
|
|
1094
|
+
y: containerPos.y + innerSize.y / 2,
|
|
1095
|
+
z: containerPos.z + innerSize.z / 2,
|
|
1096
|
+
},
|
|
1097
|
+
},
|
|
1098
|
+
};
|
|
1099
|
+
objects.push(innerObj);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Add extra objects
|
|
1103
|
+
for (let i = objects.length; i < objectCount; i++) {
|
|
1104
|
+
const name = this.pickUniqueName(OBJECT_NAMES, usedNames);
|
|
1105
|
+
usedNames.push(name);
|
|
1106
|
+
objects.push({
|
|
1107
|
+
id: name,
|
|
1108
|
+
type: 'object',
|
|
1109
|
+
position: this.randomPosition(difficulty),
|
|
1110
|
+
scale: this.randomScale(),
|
|
1111
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
1112
|
+
color: this.rng.pick(COLORS),
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const holoScript = this.buildContainsHoloScript(objects, containerName, containedName, margin);
|
|
1117
|
+
|
|
1118
|
+
return {
|
|
1119
|
+
name: `ContainsScene_${this.exampleCounter}`,
|
|
1120
|
+
objects,
|
|
1121
|
+
relationships,
|
|
1122
|
+
difficulty,
|
|
1123
|
+
holoScriptSource: holoScript,
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
private buildContainsHoloScript(
|
|
1128
|
+
objects: SceneObject[],
|
|
1129
|
+
containerName: string,
|
|
1130
|
+
containedName: string,
|
|
1131
|
+
margin: number
|
|
1132
|
+
): string {
|
|
1133
|
+
const lines: string[] = [];
|
|
1134
|
+
lines.push(`composition "SpatialScene" {`);
|
|
1135
|
+
|
|
1136
|
+
for (const obj of objects) {
|
|
1137
|
+
if (obj.type === 'zone') {
|
|
1138
|
+
if (obj.id === containerName) {
|
|
1139
|
+
lines.push(` zone "${obj.id}" {`);
|
|
1140
|
+
lines.push(` shape: "box"`);
|
|
1141
|
+
lines.push(
|
|
1142
|
+
` size: [${obj.scale.x.toFixed(1)}, ${obj.scale.y.toFixed(1)}, ${obj.scale.z.toFixed(1)}]`
|
|
1143
|
+
);
|
|
1144
|
+
lines.push(
|
|
1145
|
+
` position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
1146
|
+
);
|
|
1147
|
+
lines.push(
|
|
1148
|
+
` ${generateContainsTrait(containedName, margin > 0 ? margin : undefined)}`
|
|
1149
|
+
);
|
|
1150
|
+
lines.push(' }');
|
|
1151
|
+
} else {
|
|
1152
|
+
lines.push(generateZoneBlock(obj));
|
|
1153
|
+
}
|
|
1154
|
+
} else {
|
|
1155
|
+
lines.push(generateObjectBlock(obj));
|
|
1156
|
+
}
|
|
1157
|
+
lines.push('');
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
lines.push('}');
|
|
1161
|
+
return lines.join('\n');
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// ---------------------------------------------------------------------------
|
|
1165
|
+
// Reachable Scene Generation
|
|
1166
|
+
// ---------------------------------------------------------------------------
|
|
1167
|
+
|
|
1168
|
+
private generateReachableScene(difficulty: SpatialDifficulty, isPositive: boolean): SpatialScene {
|
|
1169
|
+
const objectCount = this.getObjectCount(difficulty);
|
|
1170
|
+
const objects: SceneObject[] = [];
|
|
1171
|
+
const relationships: SpatialRelationship[] = [];
|
|
1172
|
+
|
|
1173
|
+
// Generate source (NPC/agent)
|
|
1174
|
+
const srcName = this.rng.pick(NPC_NAMES);
|
|
1175
|
+
const srcPos = this.randomPosition(difficulty);
|
|
1176
|
+
objects.push({
|
|
1177
|
+
id: srcName,
|
|
1178
|
+
type: 'npc',
|
|
1179
|
+
position: srcPos,
|
|
1180
|
+
scale: { x: 1, y: 1, z: 1 },
|
|
1181
|
+
geometry: 'sphere',
|
|
1182
|
+
color: this.rng.pick(COLORS),
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
// Generate target
|
|
1186
|
+
const tgtName = this.pickUniqueName([...OBJECT_NAMES, ...NPC_NAMES], [srcName]);
|
|
1187
|
+
const tgtAngle = this.rng.float(0, Math.PI * 2);
|
|
1188
|
+
const tgtDist = this.rng.float(5, 20);
|
|
1189
|
+
const tgtPos = {
|
|
1190
|
+
x: srcPos.x + Math.cos(tgtAngle) * tgtDist,
|
|
1191
|
+
y: srcPos.y + this.rng.float(-1, 1),
|
|
1192
|
+
z: srcPos.z + Math.sin(tgtAngle) * tgtDist,
|
|
1193
|
+
};
|
|
1194
|
+
objects.push({
|
|
1195
|
+
id: tgtName,
|
|
1196
|
+
type: 'object',
|
|
1197
|
+
position: tgtPos,
|
|
1198
|
+
scale: this.randomScale(),
|
|
1199
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
1200
|
+
color: this.rng.pick(COLORS),
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
// Generate obstacles
|
|
1204
|
+
const obstacleCount =
|
|
1205
|
+
difficulty === 'basic'
|
|
1206
|
+
? 0
|
|
1207
|
+
: difficulty === 'intermediate'
|
|
1208
|
+
? this.rng.int(1, 2)
|
|
1209
|
+
: this.rng.int(2, 4);
|
|
1210
|
+
const obstacleNames: string[] = [];
|
|
1211
|
+
const usedNames = [srcName, tgtName];
|
|
1212
|
+
let blockingObstacle: string | null = null;
|
|
1213
|
+
|
|
1214
|
+
for (let i = 0; i < obstacleCount; i++) {
|
|
1215
|
+
const obsName = this.pickUniqueName(OBSTACLE_NAMES, usedNames);
|
|
1216
|
+
usedNames.push(obsName);
|
|
1217
|
+
obstacleNames.push(obsName);
|
|
1218
|
+
|
|
1219
|
+
const obsScale = {
|
|
1220
|
+
x: this.rng.float(1.5, 4),
|
|
1221
|
+
y: this.rng.float(2, 5),
|
|
1222
|
+
z: this.rng.float(1.5, 4),
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
let obsPos;
|
|
1226
|
+
if (!isPositive && i === 0) {
|
|
1227
|
+
// Place first obstacle directly in the path (for negative examples)
|
|
1228
|
+
const t = this.rng.float(0.3, 0.7);
|
|
1229
|
+
obsPos = {
|
|
1230
|
+
x: srcPos.x + (tgtPos.x - srcPos.x) * t,
|
|
1231
|
+
y: srcPos.y + (tgtPos.y - srcPos.y) * t,
|
|
1232
|
+
z: srcPos.z + (tgtPos.z - srcPos.z) * t,
|
|
1233
|
+
};
|
|
1234
|
+
blockingObstacle = obsName;
|
|
1235
|
+
} else {
|
|
1236
|
+
// Place off to the side (doesn't block)
|
|
1237
|
+
const offset = this.rng.float(3, 8);
|
|
1238
|
+
const side = this.rng.next() > 0.5 ? 1 : -1;
|
|
1239
|
+
const midpoint = {
|
|
1240
|
+
x: (srcPos.x + tgtPos.x) / 2,
|
|
1241
|
+
y: (srcPos.y + tgtPos.y) / 2,
|
|
1242
|
+
z: (srcPos.z + tgtPos.z) / 2,
|
|
1243
|
+
};
|
|
1244
|
+
// Perpendicular offset
|
|
1245
|
+
const perpX = -(tgtPos.z - srcPos.z);
|
|
1246
|
+
const perpZ = tgtPos.x - srcPos.x;
|
|
1247
|
+
const perpLen = Math.sqrt(perpX * perpX + perpZ * perpZ);
|
|
1248
|
+
if (perpLen > 0) {
|
|
1249
|
+
obsPos = {
|
|
1250
|
+
x: midpoint.x + (perpX / perpLen) * offset * side,
|
|
1251
|
+
y: midpoint.y,
|
|
1252
|
+
z: midpoint.z + (perpZ / perpLen) * offset * side,
|
|
1253
|
+
};
|
|
1254
|
+
} else {
|
|
1255
|
+
obsPos = {
|
|
1256
|
+
x: midpoint.x + offset * side,
|
|
1257
|
+
y: midpoint.y,
|
|
1258
|
+
z: midpoint.z,
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const obsBounds = {
|
|
1264
|
+
min: {
|
|
1265
|
+
x: obsPos.x - obsScale.x / 2,
|
|
1266
|
+
y: obsPos.y - obsScale.y / 2,
|
|
1267
|
+
z: obsPos.z - obsScale.z / 2,
|
|
1268
|
+
},
|
|
1269
|
+
max: {
|
|
1270
|
+
x: obsPos.x + obsScale.x / 2,
|
|
1271
|
+
y: obsPos.y + obsScale.y / 2,
|
|
1272
|
+
z: obsPos.z + obsScale.z / 2,
|
|
1273
|
+
},
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
objects.push({
|
|
1277
|
+
id: obsName,
|
|
1278
|
+
type: 'obstacle',
|
|
1279
|
+
position: obsPos,
|
|
1280
|
+
scale: obsScale,
|
|
1281
|
+
bounds: obsBounds,
|
|
1282
|
+
isObstacle: true,
|
|
1283
|
+
geometry: 'cube',
|
|
1284
|
+
color: '#444444',
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// Check actual reachability
|
|
1289
|
+
let actuallyReachable = true;
|
|
1290
|
+
if (obstacleNames.length > 0) {
|
|
1291
|
+
for (const obj of objects) {
|
|
1292
|
+
if (obj.isObstacle && obj.bounds) {
|
|
1293
|
+
if (lineIntersectsBox(srcPos, tgtPos, obj.bounds)) {
|
|
1294
|
+
actuallyReachable = false;
|
|
1295
|
+
if (!blockingObstacle) blockingObstacle = obj.id;
|
|
1296
|
+
break;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const maxPathLength = this.rng.float(tgtDist * 1.2, tgtDist * 2.0);
|
|
1303
|
+
|
|
1304
|
+
relationships.push({
|
|
1305
|
+
type: 'spatial_reachable',
|
|
1306
|
+
sourceId: srcName,
|
|
1307
|
+
targetId: tgtName,
|
|
1308
|
+
satisfied: actuallyReachable,
|
|
1309
|
+
params: {
|
|
1310
|
+
maxPathLength,
|
|
1311
|
+
obstacleTypes: obstacleNames.length > 0 ? ['obstacle'] : undefined,
|
|
1312
|
+
algorithm: 'line_of_sight',
|
|
1313
|
+
},
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
// Add extra objects for complexity
|
|
1317
|
+
for (let i = objects.length; i < objectCount; i++) {
|
|
1318
|
+
const name = this.pickUniqueName(OBJECT_NAMES, usedNames);
|
|
1319
|
+
usedNames.push(name);
|
|
1320
|
+
objects.push({
|
|
1321
|
+
id: name,
|
|
1322
|
+
type: 'object',
|
|
1323
|
+
position: this.randomPosition(difficulty),
|
|
1324
|
+
scale: this.randomScale(),
|
|
1325
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
1326
|
+
color: this.rng.pick(COLORS),
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
const holoScript = this.buildReachableHoloScript(
|
|
1331
|
+
objects,
|
|
1332
|
+
srcName,
|
|
1333
|
+
tgtName,
|
|
1334
|
+
maxPathLength,
|
|
1335
|
+
obstacleNames
|
|
1336
|
+
);
|
|
1337
|
+
|
|
1338
|
+
return {
|
|
1339
|
+
name: `ReachableScene_${this.exampleCounter}`,
|
|
1340
|
+
objects,
|
|
1341
|
+
relationships,
|
|
1342
|
+
difficulty,
|
|
1343
|
+
holoScriptSource: holoScript,
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
private buildReachableHoloScript(
|
|
1348
|
+
objects: SceneObject[],
|
|
1349
|
+
srcName: string,
|
|
1350
|
+
tgtName: string,
|
|
1351
|
+
maxPathLength: number,
|
|
1352
|
+
obstacleNames: string[]
|
|
1353
|
+
): string {
|
|
1354
|
+
const lines: string[] = [];
|
|
1355
|
+
lines.push(`composition "SpatialScene" {`);
|
|
1356
|
+
|
|
1357
|
+
for (const obj of objects) {
|
|
1358
|
+
if (obj.id === srcName) {
|
|
1359
|
+
lines.push(` object "${obj.id}" {`);
|
|
1360
|
+
if (obj.geometry) lines.push(` geometry: "${obj.geometry}"`);
|
|
1361
|
+
lines.push(
|
|
1362
|
+
` position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
1363
|
+
);
|
|
1364
|
+
if (obj.color) lines.push(` color: "${obj.color}"`);
|
|
1365
|
+
lines.push(
|
|
1366
|
+
` ${generateReachableTrait(tgtName, maxPathLength, obstacleNames.length > 0 ? ['obstacle'] : undefined, 'line_of_sight')}`
|
|
1367
|
+
);
|
|
1368
|
+
lines.push(' }');
|
|
1369
|
+
} else if (obj.isObstacle) {
|
|
1370
|
+
lines.push(` object "${obj.id}" {`);
|
|
1371
|
+
lines.push(` geometry: "cube"`);
|
|
1372
|
+
lines.push(
|
|
1373
|
+
` position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
1374
|
+
);
|
|
1375
|
+
lines.push(
|
|
1376
|
+
` scale: [${obj.scale.x.toFixed(1)}, ${obj.scale.y.toFixed(1)}, ${obj.scale.z.toFixed(1)}]`
|
|
1377
|
+
);
|
|
1378
|
+
lines.push(' @static');
|
|
1379
|
+
lines.push(' @collidable');
|
|
1380
|
+
lines.push(' }');
|
|
1381
|
+
} else {
|
|
1382
|
+
lines.push(generateObjectBlock(obj));
|
|
1383
|
+
}
|
|
1384
|
+
lines.push('');
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
lines.push('}');
|
|
1388
|
+
return lines.join('\n');
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// ---------------------------------------------------------------------------
|
|
1392
|
+
// Example Generation (instruction-response pairs)
|
|
1393
|
+
// ---------------------------------------------------------------------------
|
|
1394
|
+
|
|
1395
|
+
private generateExample(
|
|
1396
|
+
scene: SpatialScene,
|
|
1397
|
+
relType: SpatialRelationshipType,
|
|
1398
|
+
isPositive: boolean,
|
|
1399
|
+
difficulty: SpatialDifficulty
|
|
1400
|
+
): SpatialTrainingExample {
|
|
1401
|
+
this.exampleCounter++;
|
|
1402
|
+
const rel = scene.relationships[0];
|
|
1403
|
+
|
|
1404
|
+
let instruction: string;
|
|
1405
|
+
let response: string;
|
|
1406
|
+
let templateIndex: number;
|
|
1407
|
+
|
|
1408
|
+
switch (relType) {
|
|
1409
|
+
case 'spatial_adjacent': {
|
|
1410
|
+
templateIndex = this.rng.int(0, ADJACENT_QUESTION_TEMPLATES.length - 1);
|
|
1411
|
+
const template = ADJACENT_QUESTION_TEMPLATES[templateIndex];
|
|
1412
|
+
const dist = computeDistance(
|
|
1413
|
+
scene.objects.find((o) => o.id === rel.sourceId)!.position,
|
|
1414
|
+
scene.objects.find((o) => o.id === rel.targetId)!.position
|
|
1415
|
+
);
|
|
1416
|
+
instruction = template.question(rel.sourceId, rel.targetId, dist, rel.params.maxDistance!);
|
|
1417
|
+
response = isPositive
|
|
1418
|
+
? template.positiveAnswer(rel.sourceId, rel.targetId, dist, rel.params.maxDistance!)
|
|
1419
|
+
: template.negativeAnswer(rel.sourceId, rel.targetId, dist, rel.params.maxDistance!);
|
|
1420
|
+
break;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
case 'spatial_contains': {
|
|
1424
|
+
templateIndex = this.rng.int(0, CONTAINS_QUESTION_TEMPLATES.length - 1);
|
|
1425
|
+
const template = CONTAINS_QUESTION_TEMPLATES[templateIndex];
|
|
1426
|
+
instruction = template.question(rel.sourceId, rel.targetId);
|
|
1427
|
+
response = isPositive
|
|
1428
|
+
? template.positiveAnswer(rel.sourceId, rel.targetId)
|
|
1429
|
+
: template.negativeAnswer(rel.sourceId, rel.targetId);
|
|
1430
|
+
break;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
case 'spatial_reachable': {
|
|
1434
|
+
templateIndex = this.rng.int(0, REACHABLE_QUESTION_TEMPLATES.length - 1);
|
|
1435
|
+
const template = REACHABLE_QUESTION_TEMPLATES[templateIndex];
|
|
1436
|
+
const obstacles = scene.objects.filter((o) => o.isObstacle).map((o) => o.id);
|
|
1437
|
+
const pathLen = computeDistance(
|
|
1438
|
+
scene.objects.find((o) => o.id === rel.sourceId)!.position,
|
|
1439
|
+
scene.objects.find((o) => o.id === rel.targetId)!.position
|
|
1440
|
+
);
|
|
1441
|
+
const blocker = scene.objects.find((o) => o.isObstacle)?.id ?? 'unknown';
|
|
1442
|
+
|
|
1443
|
+
instruction = template.question(rel.sourceId, rel.targetId, obstacles);
|
|
1444
|
+
response = isPositive
|
|
1445
|
+
? template.positiveAnswer(rel.sourceId, rel.targetId, pathLen)
|
|
1446
|
+
: template.negativeAnswer(rel.sourceId, rel.targetId, blocker);
|
|
1447
|
+
break;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
return {
|
|
1452
|
+
id: `spatial-${relType.replace('spatial_', '')}-${difficulty}-${this.exampleCounter}`,
|
|
1453
|
+
instruction,
|
|
1454
|
+
response,
|
|
1455
|
+
context: scene.holoScriptSource,
|
|
1456
|
+
relationshipType: relType,
|
|
1457
|
+
isPositive,
|
|
1458
|
+
difficulty,
|
|
1459
|
+
tags: [
|
|
1460
|
+
relType,
|
|
1461
|
+
difficulty,
|
|
1462
|
+
isPositive ? 'positive' : 'negative',
|
|
1463
|
+
`template:${relType}-${templateIndex}`,
|
|
1464
|
+
],
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// ---------------------------------------------------------------------------
|
|
1469
|
+
// Utility Methods
|
|
1470
|
+
// ---------------------------------------------------------------------------
|
|
1471
|
+
|
|
1472
|
+
private getObjectCount(difficulty: SpatialDifficulty): number {
|
|
1473
|
+
switch (difficulty) {
|
|
1474
|
+
case 'basic':
|
|
1475
|
+
return 2;
|
|
1476
|
+
case 'intermediate':
|
|
1477
|
+
return this.rng.int(3, 5);
|
|
1478
|
+
case 'advanced':
|
|
1479
|
+
return this.rng.int(6, 9);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
private randomPosition(difficulty: SpatialDifficulty): { x: number; y: number; z: number } {
|
|
1484
|
+
const range = difficulty === 'basic' ? 5 : difficulty === 'intermediate' ? 10 : 20;
|
|
1485
|
+
return {
|
|
1486
|
+
x: this.rng.float(-range, range),
|
|
1487
|
+
y: this.rng.float(0, range / 2),
|
|
1488
|
+
z: this.rng.float(-range, range),
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
private randomScale(): { x: number; y: number; z: number } {
|
|
1493
|
+
const uniform = this.rng.next() > 0.5;
|
|
1494
|
+
if (uniform) {
|
|
1495
|
+
const s = this.rng.float(0.3, 2.5);
|
|
1496
|
+
return { x: s, y: s, z: s };
|
|
1497
|
+
}
|
|
1498
|
+
return {
|
|
1499
|
+
x: this.rng.float(0.3, 3.0),
|
|
1500
|
+
y: this.rng.float(0.3, 3.0),
|
|
1501
|
+
z: this.rng.float(0.3, 3.0),
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
private pickUniqueName(pool: string[], used: string[]): string {
|
|
1506
|
+
const available = pool.filter((n) => !used.includes(n));
|
|
1507
|
+
if (available.length === 0) {
|
|
1508
|
+
// Fallback: append a number
|
|
1509
|
+
return `${this.rng.pick(pool)}_${this.rng.int(100, 999)}`;
|
|
1510
|
+
}
|
|
1511
|
+
return this.rng.pick(available);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// =============================================================================
|
|
1516
|
+
// FACTORY FUNCTION
|
|
1517
|
+
// =============================================================================
|
|
1518
|
+
|
|
1519
|
+
/**
|
|
1520
|
+
* Create a new SpatialTrainingDataGenerator with the given configuration.
|
|
1521
|
+
*/
|
|
1522
|
+
export function createSpatialTrainingDataGenerator(
|
|
1523
|
+
config?: SpatialGeneratorConfig
|
|
1524
|
+
): SpatialTrainingDataGenerator {
|
|
1525
|
+
return new SpatialTrainingDataGenerator(config);
|
|
1526
|
+
}
|