@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,2687 @@
|
|
|
1
|
+
// src/training/SpatialTrainingDataGenerator.ts
|
|
2
|
+
var SeededRandom = class {
|
|
3
|
+
state;
|
|
4
|
+
constructor(seed) {
|
|
5
|
+
this.state = seed;
|
|
6
|
+
}
|
|
7
|
+
/** Returns a float in [0, 1) */
|
|
8
|
+
next() {
|
|
9
|
+
this.state |= 0;
|
|
10
|
+
this.state = this.state + 1831565813 | 0;
|
|
11
|
+
let t = Math.imul(this.state ^ this.state >>> 15, 1 | this.state);
|
|
12
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
13
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
14
|
+
}
|
|
15
|
+
/** Returns an integer in [min, max] inclusive */
|
|
16
|
+
int(min, max) {
|
|
17
|
+
return Math.floor(this.next() * (max - min + 1)) + min;
|
|
18
|
+
}
|
|
19
|
+
/** Returns a float in [min, max) */
|
|
20
|
+
float(min, max) {
|
|
21
|
+
return this.next() * (max - min) + min;
|
|
22
|
+
}
|
|
23
|
+
/** Picks a random element from an array */
|
|
24
|
+
pick(array) {
|
|
25
|
+
return array[this.int(0, array.length - 1)];
|
|
26
|
+
}
|
|
27
|
+
/** Shuffles an array in place */
|
|
28
|
+
shuffle(array) {
|
|
29
|
+
const result = [...array];
|
|
30
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
31
|
+
const j = this.int(0, i);
|
|
32
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var ADJACENT_QUESTION_TEMPLATES = [
|
|
38
|
+
{
|
|
39
|
+
question: (src, tgt, _d, maxDist) => `Is "${src}" within ${maxDist}m of "${tgt}" in this scene?`,
|
|
40
|
+
positiveAnswer: (src, tgt, dist, maxDist) => `Yes. "${src}" is ${dist.toFixed(1)}m from "${tgt}", which is within the ${maxDist}m adjacency constraint.`,
|
|
41
|
+
negativeAnswer: (src, tgt, dist, maxDist) => `No. "${src}" is ${dist.toFixed(1)}m from "${tgt}", which exceeds the ${maxDist}m adjacency constraint.`
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
question: (src, tgt) => `Are "${src}" and "${tgt}" adjacent to each other?`,
|
|
45
|
+
positiveAnswer: (src, tgt, dist) => `Yes, "${src}" and "${tgt}" are adjacent. They are ${dist.toFixed(1)}m apart, satisfying the adjacency constraint.`,
|
|
46
|
+
negativeAnswer: (src, tgt, dist, maxDist) => `No, "${src}" and "${tgt}" are not adjacent. At ${dist.toFixed(1)}m apart, they exceed the ${maxDist}m maximum distance.`
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
question: (src, tgt, _d, maxDist) => `Does the spatial_adjacent constraint between "${src}" and "${tgt}" (maxDistance: ${maxDist}m) pass?`,
|
|
50
|
+
positiveAnswer: (src, tgt, dist, maxDist) => `The constraint passes. The distance between "${src}" and "${tgt}" is ${dist.toFixed(1)}m, within the ${maxDist}m limit.`,
|
|
51
|
+
negativeAnswer: (src, tgt, dist, maxDist) => `The constraint fails. The distance between "${src}" and "${tgt}" is ${dist.toFixed(1)}m, exceeding the ${maxDist}m limit.`
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
question: (src, tgt) => `What is the spatial relationship between "${src}" and "${tgt}"? Are they close enough to interact?`,
|
|
55
|
+
positiveAnswer: (src, tgt, dist) => `"${src}" and "${tgt}" are close enough to interact. They are ${dist.toFixed(1)}m apart, within the adjacency threshold.`,
|
|
56
|
+
negativeAnswer: (src, tgt, dist) => `"${src}" and "${tgt}" are too far apart to interact. At ${dist.toFixed(1)}m apart, they fail the adjacency check.`
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
question: (src, tgt, _d, maxDist) => `Can "${src}" reach "${tgt}" given the ${maxDist}m adjacency requirement?`,
|
|
60
|
+
positiveAnswer: (src, tgt, dist) => `Yes, "${src}" can reach "${tgt}". The current distance of ${dist.toFixed(1)}m satisfies the adjacency requirement.`,
|
|
61
|
+
negativeAnswer: (src, tgt, dist, maxDist) => `No, "${src}" cannot reach "${tgt}". The ${dist.toFixed(1)}m distance exceeds the ${maxDist}m requirement.`
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
question: (src, tgt) => `In this HoloScript composition, evaluate whether "${src}" satisfies its adjacency constraint with "${tgt}".`,
|
|
65
|
+
positiveAnswer: (src, tgt, dist, maxDist) => `The adjacency constraint is satisfied. "${src}" is ${dist.toFixed(1)}m from "${tgt}" (max allowed: ${maxDist}m).`,
|
|
66
|
+
negativeAnswer: (src, tgt, dist, maxDist) => `The adjacency constraint is violated. "${src}" is ${dist.toFixed(1)}m from "${tgt}" but must be within ${maxDist}m.`
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
question: (_src, _tgt, _d, maxDist) => `Which objects in this scene are within ${maxDist}m of each other?`,
|
|
70
|
+
positiveAnswer: (src, tgt, dist) => `"${src}" and "${tgt}" are within range at ${dist.toFixed(1)}m apart.`,
|
|
71
|
+
negativeAnswer: (src, tgt, dist) => `"${src}" and "${tgt}" are NOT within range. They are ${dist.toFixed(1)}m apart.`
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
question: (src, tgt) => `If I move "${src}" to its current position, will it still be adjacent to "${tgt}"?`,
|
|
75
|
+
positiveAnswer: (_src, _tgt, dist, maxDist) => `Yes, at its current position the distance is ${dist.toFixed(1)}m, which maintains adjacency (max: ${maxDist}m).`,
|
|
76
|
+
negativeAnswer: (_src, _tgt, dist, maxDist) => `No, at its current position the distance is ${dist.toFixed(1)}m, which breaks the ${maxDist}m adjacency constraint.`
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
question: (src, tgt, _d, maxDist) => `How far is "${src}" from "${tgt}"? Is it within the ${maxDist}m threshold?`,
|
|
80
|
+
positiveAnswer: (src, tgt, dist, maxDist) => `"${src}" is ${dist.toFixed(1)}m from "${tgt}". This is within the ${maxDist}m threshold, so the adjacency holds.`,
|
|
81
|
+
negativeAnswer: (src, tgt, dist, maxDist) => `"${src}" is ${dist.toFixed(1)}m from "${tgt}". This exceeds the ${maxDist}m threshold, so adjacency is violated.`
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
question: (src, tgt) => `Analyze the spatial_adjacent constraint: is "${src}" properly positioned relative to "${tgt}"?`,
|
|
85
|
+
positiveAnswer: (src, tgt, dist, maxDist) => `"${src}" is properly positioned at ${dist.toFixed(1)}m from "${tgt}", satisfying the ${maxDist}m adjacency constraint.`,
|
|
86
|
+
negativeAnswer: (src, tgt, dist, maxDist) => `"${src}" is improperly positioned at ${dist.toFixed(1)}m from "${tgt}". It should be within ${maxDist}m.`
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
question: (src, tgt) => `Does the scene layout satisfy the proximity requirement between "${src}" and "${tgt}"?`,
|
|
90
|
+
positiveAnswer: (src, tgt, dist) => `Yes, the proximity requirement is satisfied. "${src}" and "${tgt}" are ${dist.toFixed(1)}m apart.`,
|
|
91
|
+
negativeAnswer: (src, tgt, dist, maxDist) => `No, the proximity requirement is not satisfied. "${src}" is ${dist.toFixed(1)}m from "${tgt}" (max: ${maxDist}m).`
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
question: (src, tgt, _d, maxDist) => `Given the HoloScript scene below, would placing "${src}" at its position violate the ${maxDist}m adjacency with "${tgt}"?`,
|
|
95
|
+
positiveAnswer: (_src, _tgt, dist, maxDist) => `No violation. The position results in a ${dist.toFixed(1)}m distance, within the ${maxDist}m limit.`,
|
|
96
|
+
negativeAnswer: (_src, _tgt, dist, maxDist) => `Violation detected. The position results in a ${dist.toFixed(1)}m distance, exceeding the ${maxDist}m limit.`
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
var CONTAINS_QUESTION_TEMPLATES = [
|
|
100
|
+
{
|
|
101
|
+
question: (container, contained) => `Is "${contained}" inside "${container}"?`,
|
|
102
|
+
positiveAnswer: (container, contained) => `Yes, "${contained}" is fully contained within "${container}"'s bounds.`,
|
|
103
|
+
negativeAnswer: (container, contained) => `No, "${contained}" is outside "${container}"'s bounds.`
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
question: (container, contained) => `Does "${container}" contain "${contained}" according to the spatial_contains constraint?`,
|
|
107
|
+
positiveAnswer: (container, contained) => `Yes, the spatial_contains constraint is satisfied. "${contained}" is within "${container}".`,
|
|
108
|
+
negativeAnswer: (container, contained) => `No, the spatial_contains constraint is violated. "${contained}" extends beyond "${container}"'s bounds.`
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
question: (container, contained) => `Verify whether "${contained}" fits entirely within "${container}"'s bounding volume.`,
|
|
112
|
+
positiveAnswer: (container, contained) => `Verified: "${contained}" fits entirely within "${container}"'s bounding volume.`,
|
|
113
|
+
negativeAnswer: (container, contained) => `Verification failed: "${contained}" does not fit within "${container}"'s bounding volume.`
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
question: (container) => `Which objects are inside "${container}" in this scene?`,
|
|
117
|
+
positiveAnswer: (container, contained) => `"${contained}" is inside "${container}", satisfying the containment constraint.`,
|
|
118
|
+
negativeAnswer: (container, contained) => `"${contained}" is NOT inside "${container}". It has moved outside the container bounds.`
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
question: (container, contained) => `If "${container}" has bounds from its declared size, is "${contained}" at its current position within those bounds?`,
|
|
122
|
+
positiveAnswer: (_container, contained) => `Yes, "${contained}" at its current position falls within the container's declared bounds.`,
|
|
123
|
+
negativeAnswer: (_container, contained) => `No, "${contained}" at its current position falls outside the container's declared bounds.`
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
question: (container, contained) => `Evaluate the containment relationship: does "${container}" enclose "${contained}"?`,
|
|
127
|
+
positiveAnswer: (container, contained) => `"${container}" successfully encloses "${contained}". The containment constraint is satisfied.`,
|
|
128
|
+
negativeAnswer: (container, contained) => `"${container}" does not enclose "${contained}". The containment constraint is violated.`
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
question: (container, contained) => `In this HoloScript composition, check if "${contained}" remains within the zone defined by "${container}".`,
|
|
132
|
+
positiveAnswer: (container, contained) => `"${contained}" remains within the zone defined by "${container}".`,
|
|
133
|
+
negativeAnswer: (container, contained) => `"${contained}" has left the zone defined by "${container}".`
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
question: (container, contained) => `Can "${contained}" exist at its current position without violating the containment constraint with "${container}"?`,
|
|
137
|
+
positiveAnswer: (_container, contained) => `Yes, "${contained}" can exist at its current position. It is within the container bounds.`,
|
|
138
|
+
negativeAnswer: (_container, contained) => `No, "${contained}" at its current position violates the containment constraint. It is outside the bounds.`
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
question: (container, contained) => `Does the bounding box of "${container}" fully enclose the position of "${contained}"?`,
|
|
142
|
+
positiveAnswer: (container, contained) => `Yes, "${container}"'s bounding box fully encloses "${contained}"'s position.`,
|
|
143
|
+
negativeAnswer: (container, contained) => `No, "${container}"'s bounding box does not enclose "${contained}"'s position.`
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
question: (container, contained) => `Analyze the spatial hierarchy: is "${contained}" a valid child within "${container}"'s spatial region?`,
|
|
147
|
+
positiveAnswer: (container, contained) => `"${contained}" is a valid child within "${container}"'s spatial region. Containment is satisfied.`,
|
|
148
|
+
negativeAnswer: (container, contained) => `"${contained}" is NOT a valid child within "${container}"'s spatial region. It extends outside the boundaries.`
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
question: (container, contained) => `Given the margin constraint, is "${contained}" properly contained within "${container}"?`,
|
|
152
|
+
positiveAnswer: (container, contained) => `With the margin applied, "${contained}" is properly contained within "${container}".`,
|
|
153
|
+
negativeAnswer: (container, contained) => `With the margin applied, "${contained}" is NOT properly contained within "${container}". It is too close to or beyond the boundary.`
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
question: (container, contained) => `Would moving "${contained}" to its declared position cause it to exit "${container}"?`,
|
|
157
|
+
positiveAnswer: (container, contained) => `No, "${contained}" at its declared position remains inside "${container}".`,
|
|
158
|
+
negativeAnswer: (container, contained) => `Yes, "${contained}" at its declared position would be outside "${container}".`
|
|
159
|
+
}
|
|
160
|
+
];
|
|
161
|
+
var REACHABLE_QUESTION_TEMPLATES = [
|
|
162
|
+
{
|
|
163
|
+
question: (src, tgt) => `Is there a clear path from "${src}" to "${tgt}"?`,
|
|
164
|
+
positiveAnswer: (src, tgt, pathLen) => `Yes, there is a clear path from "${src}" to "${tgt}" with a path length of ${pathLen.toFixed(1)}m.`,
|
|
165
|
+
negativeAnswer: (src, tgt, blocker) => `No, the path from "${src}" to "${tgt}" is blocked by "${blocker}".`
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
question: (src, tgt) => `Can "${src}" reach "${tgt}" without obstruction?`,
|
|
169
|
+
positiveAnswer: (src, tgt, pathLen) => `Yes, "${src}" can reach "${tgt}" unobstructed. The path length is ${pathLen.toFixed(1)}m.`,
|
|
170
|
+
negativeAnswer: (src, tgt, blocker) => `No, "${src}" cannot reach "${tgt}". The obstacle "${blocker}" blocks the path.`
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
question: (src, tgt) => `Does the spatial_reachable constraint between "${src}" and "${tgt}" pass?`,
|
|
174
|
+
positiveAnswer: (src, tgt, pathLen) => `The constraint passes. "${src}" can reach "${tgt}" via a ${pathLen.toFixed(1)}m path.`,
|
|
175
|
+
negativeAnswer: (src, tgt, blocker) => `The constraint fails. "${blocker}" obstructs the path between "${src}" and "${tgt}".`
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
question: (src, tgt, obstacles) => `Given obstacles [${obstacles.map((o) => `"${o}"`).join(", ")}], can "${src}" see "${tgt}"?`,
|
|
179
|
+
positiveAnswer: (src, tgt) => `Yes, despite the obstacles, "${src}" has line of sight to "${tgt}".`,
|
|
180
|
+
negativeAnswer: (src, tgt, blocker) => `No, "${blocker}" blocks the line of sight from "${src}" to "${tgt}".`
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
question: (src, tgt) => `Evaluate whether "${src}" has an unobstructed path to "${tgt}" in this scene.`,
|
|
184
|
+
positiveAnswer: (src, tgt, pathLen) => `"${src}" has an unobstructed path to "${tgt}" measuring ${pathLen.toFixed(1)}m.`,
|
|
185
|
+
negativeAnswer: (src, tgt, blocker) => `"${src}" does NOT have an unobstructed path to "${tgt}". "${blocker}" is in the way.`
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
question: (src, tgt) => `In this HoloScript scene, is "${tgt}" reachable from "${src}"?`,
|
|
189
|
+
positiveAnswer: (_src, tgt, pathLen) => `Yes, "${tgt}" is reachable with a direct path of ${pathLen.toFixed(1)}m.`,
|
|
190
|
+
negativeAnswer: (_src, tgt, blocker) => `No, "${tgt}" is not reachable. An obstacle ("${blocker}") blocks the direct path.`
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
question: (src, tgt) => `Check the reachability constraint: can "${src}" navigate to "${tgt}"?`,
|
|
194
|
+
positiveAnswer: (src, tgt, pathLen) => `Reachability check passed: "${src}" can navigate to "${tgt}" (${pathLen.toFixed(1)}m path).`,
|
|
195
|
+
negativeAnswer: (src, tgt, blocker) => `Reachability check failed: "${src}" cannot navigate to "${tgt}" due to "${blocker}".`
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
question: (src, tgt) => `Is the line of sight between "${src}" and "${tgt}" clear?`,
|
|
199
|
+
positiveAnswer: (src, tgt) => `Yes, line of sight between "${src}" and "${tgt}" is clear.`,
|
|
200
|
+
negativeAnswer: (src, tgt, blocker) => `No, line of sight is blocked. "${blocker}" obstructs the view from "${src}" to "${tgt}".`
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
question: (src, tgt, obstacles) => `Considering ${obstacles.length} obstacle(s) in the scene, determine if "${src}" can reach "${tgt}".`,
|
|
204
|
+
positiveAnswer: (src, tgt, pathLen) => `Despite the obstacles, "${src}" can reach "${tgt}" via a ${pathLen.toFixed(1)}m path that avoids all obstructions.`,
|
|
205
|
+
negativeAnswer: (src, tgt, blocker) => `"${src}" cannot reach "${tgt}". The path is blocked by "${blocker}".`
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
question: (src, tgt) => `Would placing a wall between "${src}" and "${tgt}" affect reachability? Analyze the current state.`,
|
|
209
|
+
positiveAnswer: (src, tgt) => `Currently, "${src}" and "${tgt}" are mutually reachable. Adding a wall could change this.`,
|
|
210
|
+
negativeAnswer: (src, tgt, blocker) => `Currently, "${src}" and "${tgt}" are NOT reachable due to "${blocker}". Adding more walls would not change this.`
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
question: (src, tgt) => `Analyze path connectivity: does an unblocked route exist from "${src}" to "${tgt}"?`,
|
|
214
|
+
positiveAnswer: (src, tgt, pathLen) => `An unblocked route exists from "${src}" to "${tgt}" with a total distance of ${pathLen.toFixed(1)}m.`,
|
|
215
|
+
negativeAnswer: (src, tgt, blocker) => `No unblocked route exists from "${src}" to "${tgt}". "${blocker}" creates a barrier.`
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
question: (src, tgt) => `For NPC pathfinding, can "${src}" walk to "${tgt}" in a straight line?`,
|
|
219
|
+
positiveAnswer: (src, tgt, pathLen) => `Yes, "${src}" can walk directly to "${tgt}" in a straight line (${pathLen.toFixed(1)}m).`,
|
|
220
|
+
negativeAnswer: (src, tgt, blocker) => `No, "${src}" cannot walk straight to "${tgt}". "${blocker}" is obstructing the direct path.`
|
|
221
|
+
}
|
|
222
|
+
];
|
|
223
|
+
var OBJECT_NAMES = [
|
|
224
|
+
"Table",
|
|
225
|
+
"Chair",
|
|
226
|
+
"Lamp",
|
|
227
|
+
"Bookshelf",
|
|
228
|
+
"Desk",
|
|
229
|
+
"Sofa",
|
|
230
|
+
"Cabinet",
|
|
231
|
+
"Mirror",
|
|
232
|
+
"Plant",
|
|
233
|
+
"Clock",
|
|
234
|
+
"Vase",
|
|
235
|
+
"Rug",
|
|
236
|
+
"Painting",
|
|
237
|
+
"Shelf",
|
|
238
|
+
"Stool",
|
|
239
|
+
"Bench",
|
|
240
|
+
"Chest",
|
|
241
|
+
"Barrel",
|
|
242
|
+
"Crate",
|
|
243
|
+
"Box",
|
|
244
|
+
"Pillar",
|
|
245
|
+
"Statue",
|
|
246
|
+
"Crystal",
|
|
247
|
+
"Orb",
|
|
248
|
+
"Pedestal",
|
|
249
|
+
"Platform",
|
|
250
|
+
"Beacon",
|
|
251
|
+
"Terminal",
|
|
252
|
+
"Console",
|
|
253
|
+
"Panel"
|
|
254
|
+
];
|
|
255
|
+
var ZONE_NAMES = [
|
|
256
|
+
"Room",
|
|
257
|
+
"Hall",
|
|
258
|
+
"Chamber",
|
|
259
|
+
"Arena",
|
|
260
|
+
"Gallery",
|
|
261
|
+
"Vault",
|
|
262
|
+
"Atrium",
|
|
263
|
+
"Courtyard",
|
|
264
|
+
"Alcove",
|
|
265
|
+
"Corridor",
|
|
266
|
+
"Laboratory",
|
|
267
|
+
"Workshop"
|
|
268
|
+
];
|
|
269
|
+
var OBSTACLE_NAMES = [
|
|
270
|
+
"Wall",
|
|
271
|
+
"Barrier",
|
|
272
|
+
"Fence",
|
|
273
|
+
"Pillar",
|
|
274
|
+
"Column",
|
|
275
|
+
"Boulder",
|
|
276
|
+
"Shield",
|
|
277
|
+
"Gate",
|
|
278
|
+
"Blockade",
|
|
279
|
+
"Partition",
|
|
280
|
+
"Divider",
|
|
281
|
+
"Screen"
|
|
282
|
+
];
|
|
283
|
+
var NPC_NAMES = [
|
|
284
|
+
"Guard",
|
|
285
|
+
"Merchant",
|
|
286
|
+
"Explorer",
|
|
287
|
+
"Scout",
|
|
288
|
+
"Villager",
|
|
289
|
+
"Artisan",
|
|
290
|
+
"Drone",
|
|
291
|
+
"Robot",
|
|
292
|
+
"Companion",
|
|
293
|
+
"Agent",
|
|
294
|
+
"Sentinel",
|
|
295
|
+
"Worker"
|
|
296
|
+
];
|
|
297
|
+
var GEOMETRY_TYPES = ["cube", "sphere", "cylinder", "torus", "cone", "prism"];
|
|
298
|
+
var COLORS = [
|
|
299
|
+
"#ff0000",
|
|
300
|
+
"#00ff00",
|
|
301
|
+
"#0000ff",
|
|
302
|
+
"#ffff00",
|
|
303
|
+
"#ff00ff",
|
|
304
|
+
"#00ffff",
|
|
305
|
+
"#ff8800",
|
|
306
|
+
"#8800ff",
|
|
307
|
+
"#00ff88",
|
|
308
|
+
"#ff0088",
|
|
309
|
+
"#888888",
|
|
310
|
+
"#ffffff"
|
|
311
|
+
];
|
|
312
|
+
function generateObjectBlock(obj, indent = " ") {
|
|
313
|
+
const lines = [];
|
|
314
|
+
lines.push(`${indent}object "${obj.id}" {`);
|
|
315
|
+
if (obj.geometry) {
|
|
316
|
+
lines.push(`${indent} geometry: "${obj.geometry}"`);
|
|
317
|
+
}
|
|
318
|
+
lines.push(
|
|
319
|
+
`${indent} position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
320
|
+
);
|
|
321
|
+
if (obj.scale.x !== 1 || obj.scale.y !== 1 || obj.scale.z !== 1) {
|
|
322
|
+
lines.push(
|
|
323
|
+
`${indent} scale: [${obj.scale.x.toFixed(1)}, ${obj.scale.y.toFixed(1)}, ${obj.scale.z.toFixed(1)}]`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
if (obj.color) {
|
|
327
|
+
lines.push(`${indent} color: "${obj.color}"`);
|
|
328
|
+
}
|
|
329
|
+
lines.push(`${indent}}`);
|
|
330
|
+
return lines.join("\n");
|
|
331
|
+
}
|
|
332
|
+
function generateZoneBlock(obj, indent = " ") {
|
|
333
|
+
const lines = [];
|
|
334
|
+
lines.push(`${indent}zone "${obj.id}" {`);
|
|
335
|
+
lines.push(`${indent} shape: "box"`);
|
|
336
|
+
const sx = obj.scale.x;
|
|
337
|
+
const sy = obj.scale.y;
|
|
338
|
+
const sz = obj.scale.z;
|
|
339
|
+
lines.push(`${indent} size: [${sx.toFixed(1)}, ${sy.toFixed(1)}, ${sz.toFixed(1)}]`);
|
|
340
|
+
lines.push(
|
|
341
|
+
`${indent} position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
342
|
+
);
|
|
343
|
+
lines.push(`${indent}}`);
|
|
344
|
+
return lines.join("\n");
|
|
345
|
+
}
|
|
346
|
+
function generateAdjacentTrait(targetId, maxDist, axis) {
|
|
347
|
+
const parts = [`target: "${targetId}"`, `maxDistance: ${maxDist.toFixed(1)}m`];
|
|
348
|
+
if (axis && axis !== "xyz") {
|
|
349
|
+
parts.push(`axis: "${axis}"`);
|
|
350
|
+
}
|
|
351
|
+
return `@spatial_adjacent(${parts.join(", ")})`;
|
|
352
|
+
}
|
|
353
|
+
function generateContainsTrait(targetId, margin, strict) {
|
|
354
|
+
const parts = [`target: "${targetId}"`];
|
|
355
|
+
if (margin !== void 0 && margin > 0) {
|
|
356
|
+
parts.push(`margin: ${margin.toFixed(1)}m`);
|
|
357
|
+
}
|
|
358
|
+
if (strict) {
|
|
359
|
+
parts.push("strict: true");
|
|
360
|
+
}
|
|
361
|
+
return `@spatial_contains(${parts.join(", ")})`;
|
|
362
|
+
}
|
|
363
|
+
function generateReachableTrait(targetId, maxPathLength, obstacles, algorithm) {
|
|
364
|
+
const parts = [`target: "${targetId}"`];
|
|
365
|
+
if (maxPathLength !== void 0) {
|
|
366
|
+
parts.push(`maxPathLength: ${maxPathLength.toFixed(0)}m`);
|
|
367
|
+
}
|
|
368
|
+
if (obstacles && obstacles.length > 0) {
|
|
369
|
+
parts.push(`obstacles: [${obstacles.map((o) => `"${o}"`).join(", ")}]`);
|
|
370
|
+
}
|
|
371
|
+
if (algorithm) {
|
|
372
|
+
parts.push(`algorithm: "${algorithm}"`);
|
|
373
|
+
}
|
|
374
|
+
return `@spatial_reachable(${parts.join(", ")})`;
|
|
375
|
+
}
|
|
376
|
+
function computeDistance(a, b) {
|
|
377
|
+
const dx = b.x - a.x;
|
|
378
|
+
const dy = b.y - a.y;
|
|
379
|
+
const dz = b.z - a.z;
|
|
380
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
381
|
+
}
|
|
382
|
+
function isPointInBox(point, bounds, margin = 0) {
|
|
383
|
+
return point.x >= bounds.min.x + margin && point.x <= bounds.max.x - margin && point.y >= bounds.min.y + margin && point.y <= bounds.max.y - margin && point.z >= bounds.min.z + margin && point.z <= bounds.max.z - margin;
|
|
384
|
+
}
|
|
385
|
+
function lineIntersectsBox(a, b, box) {
|
|
386
|
+
const dir = { x: b.x - a.x, y: b.y - a.y, z: b.z - a.z };
|
|
387
|
+
const len = Math.sqrt(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z);
|
|
388
|
+
if (len === 0) return false;
|
|
389
|
+
let tmin = 0;
|
|
390
|
+
let tmax = 1;
|
|
391
|
+
const axes = ["x", "y", "z"];
|
|
392
|
+
for (const axis of axes) {
|
|
393
|
+
const d = dir[axis];
|
|
394
|
+
const o = a[axis];
|
|
395
|
+
const bmin = box.min[axis];
|
|
396
|
+
const bmax = box.max[axis];
|
|
397
|
+
if (Math.abs(d) < 1e-10) {
|
|
398
|
+
if (o < bmin || o > bmax) return false;
|
|
399
|
+
} else {
|
|
400
|
+
let t1 = (bmin - o) / d;
|
|
401
|
+
let t2 = (bmax - o) / d;
|
|
402
|
+
if (t1 > t2) {
|
|
403
|
+
const tmp = t1;
|
|
404
|
+
t1 = t2;
|
|
405
|
+
t2 = tmp;
|
|
406
|
+
}
|
|
407
|
+
tmin = Math.max(tmin, t1);
|
|
408
|
+
tmax = Math.min(tmax, t2);
|
|
409
|
+
if (tmin > tmax) return false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
var SpatialTrainingDataGenerator = class {
|
|
415
|
+
config;
|
|
416
|
+
rng;
|
|
417
|
+
exampleCounter = 0;
|
|
418
|
+
constructor(config = {}) {
|
|
419
|
+
this.config = {
|
|
420
|
+
examplesPerCategory: config.examplesPerCategory ?? 10,
|
|
421
|
+
relationshipTypes: config.relationshipTypes ?? [
|
|
422
|
+
"spatial_adjacent",
|
|
423
|
+
"spatial_contains",
|
|
424
|
+
"spatial_reachable"
|
|
425
|
+
],
|
|
426
|
+
difficultyLevels: config.difficultyLevels ?? ["basic", "intermediate", "advanced"],
|
|
427
|
+
positiveRatio: config.positiveRatio ?? 0.5,
|
|
428
|
+
seed: config.seed ?? Date.now(),
|
|
429
|
+
includeContext: config.includeContext ?? true
|
|
430
|
+
};
|
|
431
|
+
this.rng = new SeededRandom(this.config.seed);
|
|
432
|
+
}
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
// Public API
|
|
435
|
+
// ---------------------------------------------------------------------------
|
|
436
|
+
/**
|
|
437
|
+
* Generate all spatial training examples based on configuration.
|
|
438
|
+
*/
|
|
439
|
+
generate() {
|
|
440
|
+
const examples = [];
|
|
441
|
+
this.exampleCounter = 0;
|
|
442
|
+
for (const relType of this.config.relationshipTypes) {
|
|
443
|
+
for (const difficulty of this.config.difficultyLevels) {
|
|
444
|
+
const count = this.config.examplesPerCategory;
|
|
445
|
+
for (let i = 0; i < count; i++) {
|
|
446
|
+
const isPositive = this.rng.next() < this.config.positiveRatio;
|
|
447
|
+
const scene = this.generateScene(relType, difficulty, isPositive);
|
|
448
|
+
const example = this.generateExample(scene, relType, isPositive, difficulty);
|
|
449
|
+
examples.push(example);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return examples;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Generate examples for a specific relationship type.
|
|
457
|
+
*/
|
|
458
|
+
generateForRelationship(relType) {
|
|
459
|
+
const examples = [];
|
|
460
|
+
for (const difficulty of this.config.difficultyLevels) {
|
|
461
|
+
const count = this.config.examplesPerCategory;
|
|
462
|
+
for (let i = 0; i < count; i++) {
|
|
463
|
+
const isPositive = this.rng.next() < this.config.positiveRatio;
|
|
464
|
+
const scene = this.generateScene(relType, difficulty, isPositive);
|
|
465
|
+
const example = this.generateExample(scene, relType, isPositive, difficulty);
|
|
466
|
+
examples.push(example);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return examples;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Generate examples for a specific difficulty level.
|
|
473
|
+
*/
|
|
474
|
+
generateForDifficulty(difficulty) {
|
|
475
|
+
const examples = [];
|
|
476
|
+
for (const relType of this.config.relationshipTypes) {
|
|
477
|
+
const count = this.config.examplesPerCategory;
|
|
478
|
+
for (let i = 0; i < count; i++) {
|
|
479
|
+
const isPositive = this.rng.next() < this.config.positiveRatio;
|
|
480
|
+
const scene = this.generateScene(relType, difficulty, isPositive);
|
|
481
|
+
const example = this.generateExample(scene, relType, isPositive, difficulty);
|
|
482
|
+
examples.push(example);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return examples;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Export examples as JSONL string (one JSON object per line).
|
|
489
|
+
*/
|
|
490
|
+
exportJSONL(examples) {
|
|
491
|
+
return examples.map((ex) => {
|
|
492
|
+
const entry = {
|
|
493
|
+
instruction: this.config.includeContext ? `${ex.instruction}
|
|
494
|
+
|
|
495
|
+
HoloScript Scene:
|
|
496
|
+
\`\`\`holoscript
|
|
497
|
+
${ex.context}
|
|
498
|
+
\`\`\`` : ex.instruction,
|
|
499
|
+
response: ex.response,
|
|
500
|
+
metadata: {
|
|
501
|
+
id: ex.id,
|
|
502
|
+
relationship_type: ex.relationshipType,
|
|
503
|
+
is_positive: ex.isPositive,
|
|
504
|
+
difficulty: ex.difficulty,
|
|
505
|
+
tags: ex.tags
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
return JSON.stringify(entry);
|
|
509
|
+
}).join("\n");
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Export examples as JSON array string.
|
|
513
|
+
*/
|
|
514
|
+
exportJSON(examples) {
|
|
515
|
+
return JSON.stringify(examples, null, 2);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Get statistics about generated examples.
|
|
519
|
+
*/
|
|
520
|
+
getStats(examples) {
|
|
521
|
+
const byRelationship = {
|
|
522
|
+
spatial_adjacent: 0,
|
|
523
|
+
spatial_contains: 0,
|
|
524
|
+
spatial_reachable: 0
|
|
525
|
+
};
|
|
526
|
+
const byDifficulty = {
|
|
527
|
+
basic: 0,
|
|
528
|
+
intermediate: 0,
|
|
529
|
+
advanced: 0
|
|
530
|
+
};
|
|
531
|
+
let positiveCount = 0;
|
|
532
|
+
let negativeCount = 0;
|
|
533
|
+
const templateIds = /* @__PURE__ */ new Set();
|
|
534
|
+
for (const ex of examples) {
|
|
535
|
+
byRelationship[ex.relationshipType]++;
|
|
536
|
+
byDifficulty[ex.difficulty]++;
|
|
537
|
+
if (ex.isPositive) positiveCount++;
|
|
538
|
+
else negativeCount++;
|
|
539
|
+
const tplTag = ex.tags.find((t) => t.startsWith("template:"));
|
|
540
|
+
if (tplTag) templateIds.add(tplTag);
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
totalExamples: examples.length,
|
|
544
|
+
byRelationship,
|
|
545
|
+
byDifficulty,
|
|
546
|
+
positiveCount,
|
|
547
|
+
negativeCount,
|
|
548
|
+
uniqueTemplatesUsed: templateIds.size
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Reset the generator with a new seed.
|
|
553
|
+
*/
|
|
554
|
+
reseed(seed) {
|
|
555
|
+
this.rng = new SeededRandom(seed);
|
|
556
|
+
this.exampleCounter = 0;
|
|
557
|
+
}
|
|
558
|
+
// ---------------------------------------------------------------------------
|
|
559
|
+
// Scene Generation
|
|
560
|
+
// ---------------------------------------------------------------------------
|
|
561
|
+
/**
|
|
562
|
+
* Generate a spatial scene for the given relationship type and difficulty.
|
|
563
|
+
*/
|
|
564
|
+
generateScene(relType, difficulty, isPositive) {
|
|
565
|
+
switch (relType) {
|
|
566
|
+
case "spatial_adjacent":
|
|
567
|
+
return this.generateAdjacentScene(difficulty, isPositive);
|
|
568
|
+
case "spatial_contains":
|
|
569
|
+
return this.generateContainsScene(difficulty, isPositive);
|
|
570
|
+
case "spatial_reachable":
|
|
571
|
+
return this.generateReachableScene(difficulty, isPositive);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// ---------------------------------------------------------------------------
|
|
575
|
+
// Adjacent Scene Generation
|
|
576
|
+
// ---------------------------------------------------------------------------
|
|
577
|
+
generateAdjacentScene(difficulty, isPositive) {
|
|
578
|
+
const objectCount = this.getObjectCount(difficulty);
|
|
579
|
+
const maxDistance = this.rng.float(1, 5);
|
|
580
|
+
const objects = [];
|
|
581
|
+
const relationships = [];
|
|
582
|
+
const srcName = this.rng.pick(OBJECT_NAMES);
|
|
583
|
+
const srcPos = this.randomPosition(difficulty);
|
|
584
|
+
const srcObj = {
|
|
585
|
+
id: srcName,
|
|
586
|
+
type: "object",
|
|
587
|
+
position: srcPos,
|
|
588
|
+
scale: this.randomScale(),
|
|
589
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
590
|
+
color: this.rng.pick(COLORS)
|
|
591
|
+
};
|
|
592
|
+
objects.push(srcObj);
|
|
593
|
+
const tgtName = this.pickUniqueName(OBJECT_NAMES, [srcName]);
|
|
594
|
+
let tgtPos;
|
|
595
|
+
if (isPositive) {
|
|
596
|
+
const angle = this.rng.float(0, Math.PI * 2);
|
|
597
|
+
const elevation = this.rng.float(-0.5, 0.5);
|
|
598
|
+
const dist = this.rng.float(0.5, maxDistance * 0.9);
|
|
599
|
+
tgtPos = {
|
|
600
|
+
x: srcPos.x + Math.cos(angle) * dist,
|
|
601
|
+
y: srcPos.y + elevation,
|
|
602
|
+
z: srcPos.z + Math.sin(angle) * dist
|
|
603
|
+
};
|
|
604
|
+
} else {
|
|
605
|
+
const angle = this.rng.float(0, Math.PI * 2);
|
|
606
|
+
const dist = this.rng.float(maxDistance * 1.5, maxDistance * 3);
|
|
607
|
+
tgtPos = {
|
|
608
|
+
x: srcPos.x + Math.cos(angle) * dist,
|
|
609
|
+
y: srcPos.y + this.rng.float(-1, 1),
|
|
610
|
+
z: srcPos.z + Math.sin(angle) * dist
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
const tgtObj = {
|
|
614
|
+
id: tgtName,
|
|
615
|
+
type: "object",
|
|
616
|
+
position: tgtPos,
|
|
617
|
+
scale: this.randomScale(),
|
|
618
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
619
|
+
color: this.rng.pick(COLORS)
|
|
620
|
+
};
|
|
621
|
+
objects.push(tgtObj);
|
|
622
|
+
const actualDist = computeDistance(srcPos, tgtPos);
|
|
623
|
+
relationships.push({
|
|
624
|
+
type: "spatial_adjacent",
|
|
625
|
+
sourceId: srcName,
|
|
626
|
+
targetId: tgtName,
|
|
627
|
+
satisfied: actualDist <= maxDistance,
|
|
628
|
+
params: { maxDistance }
|
|
629
|
+
});
|
|
630
|
+
const usedNames = [srcName, tgtName];
|
|
631
|
+
for (let i = 2; i < objectCount; i++) {
|
|
632
|
+
const name = this.pickUniqueName(OBJECT_NAMES, usedNames);
|
|
633
|
+
usedNames.push(name);
|
|
634
|
+
objects.push({
|
|
635
|
+
id: name,
|
|
636
|
+
type: "object",
|
|
637
|
+
position: this.randomPosition(difficulty),
|
|
638
|
+
scale: this.randomScale(),
|
|
639
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
640
|
+
color: this.rng.pick(COLORS)
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
const holoScript = this.buildAdjacentHoloScript(objects, srcName, tgtName, maxDistance);
|
|
644
|
+
return {
|
|
645
|
+
name: `AdjacentScene_${this.exampleCounter}`,
|
|
646
|
+
objects,
|
|
647
|
+
relationships,
|
|
648
|
+
difficulty,
|
|
649
|
+
holoScriptSource: holoScript
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
buildAdjacentHoloScript(objects, srcName, tgtName, maxDistance) {
|
|
653
|
+
const lines = [];
|
|
654
|
+
lines.push(`composition "SpatialScene" {`);
|
|
655
|
+
for (const obj of objects) {
|
|
656
|
+
if (obj.id === srcName) {
|
|
657
|
+
lines.push(` object "${obj.id}" {`);
|
|
658
|
+
if (obj.geometry) lines.push(` geometry: "${obj.geometry}"`);
|
|
659
|
+
lines.push(
|
|
660
|
+
` position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
661
|
+
);
|
|
662
|
+
if (obj.color) lines.push(` color: "${obj.color}"`);
|
|
663
|
+
lines.push(` ${generateAdjacentTrait(tgtName, maxDistance)}`);
|
|
664
|
+
lines.push(" }");
|
|
665
|
+
} else {
|
|
666
|
+
lines.push(generateObjectBlock(obj));
|
|
667
|
+
}
|
|
668
|
+
lines.push("");
|
|
669
|
+
}
|
|
670
|
+
lines.push("}");
|
|
671
|
+
return lines.join("\n");
|
|
672
|
+
}
|
|
673
|
+
// ---------------------------------------------------------------------------
|
|
674
|
+
// Contains Scene Generation
|
|
675
|
+
// ---------------------------------------------------------------------------
|
|
676
|
+
generateContainsScene(difficulty, isPositive) {
|
|
677
|
+
const objects = [];
|
|
678
|
+
const relationships = [];
|
|
679
|
+
const containerName = this.rng.pick(ZONE_NAMES);
|
|
680
|
+
const containerSize = {
|
|
681
|
+
x: this.rng.float(4, 12),
|
|
682
|
+
y: this.rng.float(3, 8),
|
|
683
|
+
z: this.rng.float(4, 12)
|
|
684
|
+
};
|
|
685
|
+
const containerPos = this.randomPosition(difficulty);
|
|
686
|
+
const margin = this.rng.float(0, 0.5);
|
|
687
|
+
const containerBounds = {
|
|
688
|
+
min: {
|
|
689
|
+
x: containerPos.x - containerSize.x / 2,
|
|
690
|
+
y: containerPos.y - containerSize.y / 2,
|
|
691
|
+
z: containerPos.z - containerSize.z / 2
|
|
692
|
+
},
|
|
693
|
+
max: {
|
|
694
|
+
x: containerPos.x + containerSize.x / 2,
|
|
695
|
+
y: containerPos.y + containerSize.y / 2,
|
|
696
|
+
z: containerPos.z + containerSize.z / 2
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
const containerObj = {
|
|
700
|
+
id: containerName,
|
|
701
|
+
type: "zone",
|
|
702
|
+
position: containerPos,
|
|
703
|
+
scale: containerSize,
|
|
704
|
+
bounds: containerBounds
|
|
705
|
+
};
|
|
706
|
+
objects.push(containerObj);
|
|
707
|
+
const objectCount = this.getObjectCount(difficulty);
|
|
708
|
+
const containedName = this.rng.pick(OBJECT_NAMES);
|
|
709
|
+
let containedPos;
|
|
710
|
+
if (isPositive) {
|
|
711
|
+
containedPos = {
|
|
712
|
+
x: this.rng.float(
|
|
713
|
+
containerBounds.min.x + margin + 0.5,
|
|
714
|
+
containerBounds.max.x - margin - 0.5
|
|
715
|
+
),
|
|
716
|
+
y: this.rng.float(
|
|
717
|
+
containerBounds.min.y + margin + 0.5,
|
|
718
|
+
containerBounds.max.y - margin - 0.5
|
|
719
|
+
),
|
|
720
|
+
z: this.rng.float(
|
|
721
|
+
containerBounds.min.z + margin + 0.5,
|
|
722
|
+
containerBounds.max.z - margin - 0.5
|
|
723
|
+
)
|
|
724
|
+
};
|
|
725
|
+
} else {
|
|
726
|
+
const side = this.rng.int(0, 5);
|
|
727
|
+
containedPos = { ...containerPos };
|
|
728
|
+
switch (side) {
|
|
729
|
+
case 0:
|
|
730
|
+
containedPos.x = containerBounds.max.x + this.rng.float(1, 5);
|
|
731
|
+
break;
|
|
732
|
+
case 1:
|
|
733
|
+
containedPos.x = containerBounds.min.x - this.rng.float(1, 5);
|
|
734
|
+
break;
|
|
735
|
+
case 2:
|
|
736
|
+
containedPos.y = containerBounds.max.y + this.rng.float(1, 5);
|
|
737
|
+
break;
|
|
738
|
+
case 3:
|
|
739
|
+
containedPos.y = containerBounds.min.y - this.rng.float(1, 5);
|
|
740
|
+
break;
|
|
741
|
+
case 4:
|
|
742
|
+
containedPos.z = containerBounds.max.z + this.rng.float(1, 5);
|
|
743
|
+
break;
|
|
744
|
+
case 5:
|
|
745
|
+
containedPos.z = containerBounds.min.z - this.rng.float(1, 5);
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
const containedObj = {
|
|
750
|
+
id: containedName,
|
|
751
|
+
type: "object",
|
|
752
|
+
position: containedPos,
|
|
753
|
+
scale: this.randomScale(),
|
|
754
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
755
|
+
color: this.rng.pick(COLORS)
|
|
756
|
+
};
|
|
757
|
+
objects.push(containedObj);
|
|
758
|
+
const actuallyContained = isPointInBox(containedPos, containerBounds, margin);
|
|
759
|
+
relationships.push({
|
|
760
|
+
type: "spatial_contains",
|
|
761
|
+
sourceId: containerName,
|
|
762
|
+
targetId: containedName,
|
|
763
|
+
satisfied: actuallyContained,
|
|
764
|
+
params: { margin: margin > 0 ? margin : void 0 }
|
|
765
|
+
});
|
|
766
|
+
const usedNames = [containerName, containedName];
|
|
767
|
+
if (difficulty === "advanced") {
|
|
768
|
+
const innerContainerName = this.pickUniqueName(ZONE_NAMES, usedNames);
|
|
769
|
+
usedNames.push(innerContainerName);
|
|
770
|
+
const innerSize = {
|
|
771
|
+
x: containerSize.x * 0.4,
|
|
772
|
+
y: containerSize.y * 0.4,
|
|
773
|
+
z: containerSize.z * 0.4
|
|
774
|
+
};
|
|
775
|
+
const innerObj = {
|
|
776
|
+
id: innerContainerName,
|
|
777
|
+
type: "zone",
|
|
778
|
+
position: { ...containerPos },
|
|
779
|
+
scale: innerSize,
|
|
780
|
+
bounds: {
|
|
781
|
+
min: {
|
|
782
|
+
x: containerPos.x - innerSize.x / 2,
|
|
783
|
+
y: containerPos.y - innerSize.y / 2,
|
|
784
|
+
z: containerPos.z - innerSize.z / 2
|
|
785
|
+
},
|
|
786
|
+
max: {
|
|
787
|
+
x: containerPos.x + innerSize.x / 2,
|
|
788
|
+
y: containerPos.y + innerSize.y / 2,
|
|
789
|
+
z: containerPos.z + innerSize.z / 2
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
objects.push(innerObj);
|
|
794
|
+
}
|
|
795
|
+
for (let i = objects.length; i < objectCount; i++) {
|
|
796
|
+
const name = this.pickUniqueName(OBJECT_NAMES, usedNames);
|
|
797
|
+
usedNames.push(name);
|
|
798
|
+
objects.push({
|
|
799
|
+
id: name,
|
|
800
|
+
type: "object",
|
|
801
|
+
position: this.randomPosition(difficulty),
|
|
802
|
+
scale: this.randomScale(),
|
|
803
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
804
|
+
color: this.rng.pick(COLORS)
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
const holoScript = this.buildContainsHoloScript(objects, containerName, containedName, margin);
|
|
808
|
+
return {
|
|
809
|
+
name: `ContainsScene_${this.exampleCounter}`,
|
|
810
|
+
objects,
|
|
811
|
+
relationships,
|
|
812
|
+
difficulty,
|
|
813
|
+
holoScriptSource: holoScript
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
buildContainsHoloScript(objects, containerName, containedName, margin) {
|
|
817
|
+
const lines = [];
|
|
818
|
+
lines.push(`composition "SpatialScene" {`);
|
|
819
|
+
for (const obj of objects) {
|
|
820
|
+
if (obj.type === "zone") {
|
|
821
|
+
if (obj.id === containerName) {
|
|
822
|
+
lines.push(` zone "${obj.id}" {`);
|
|
823
|
+
lines.push(` shape: "box"`);
|
|
824
|
+
lines.push(
|
|
825
|
+
` size: [${obj.scale.x.toFixed(1)}, ${obj.scale.y.toFixed(1)}, ${obj.scale.z.toFixed(1)}]`
|
|
826
|
+
);
|
|
827
|
+
lines.push(
|
|
828
|
+
` position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
829
|
+
);
|
|
830
|
+
lines.push(
|
|
831
|
+
` ${generateContainsTrait(containedName, margin > 0 ? margin : void 0)}`
|
|
832
|
+
);
|
|
833
|
+
lines.push(" }");
|
|
834
|
+
} else {
|
|
835
|
+
lines.push(generateZoneBlock(obj));
|
|
836
|
+
}
|
|
837
|
+
} else {
|
|
838
|
+
lines.push(generateObjectBlock(obj));
|
|
839
|
+
}
|
|
840
|
+
lines.push("");
|
|
841
|
+
}
|
|
842
|
+
lines.push("}");
|
|
843
|
+
return lines.join("\n");
|
|
844
|
+
}
|
|
845
|
+
// ---------------------------------------------------------------------------
|
|
846
|
+
// Reachable Scene Generation
|
|
847
|
+
// ---------------------------------------------------------------------------
|
|
848
|
+
generateReachableScene(difficulty, isPositive) {
|
|
849
|
+
const objectCount = this.getObjectCount(difficulty);
|
|
850
|
+
const objects = [];
|
|
851
|
+
const relationships = [];
|
|
852
|
+
const srcName = this.rng.pick(NPC_NAMES);
|
|
853
|
+
const srcPos = this.randomPosition(difficulty);
|
|
854
|
+
objects.push({
|
|
855
|
+
id: srcName,
|
|
856
|
+
type: "npc",
|
|
857
|
+
position: srcPos,
|
|
858
|
+
scale: { x: 1, y: 1, z: 1 },
|
|
859
|
+
geometry: "sphere",
|
|
860
|
+
color: this.rng.pick(COLORS)
|
|
861
|
+
});
|
|
862
|
+
const tgtName = this.pickUniqueName([...OBJECT_NAMES, ...NPC_NAMES], [srcName]);
|
|
863
|
+
const tgtAngle = this.rng.float(0, Math.PI * 2);
|
|
864
|
+
const tgtDist = this.rng.float(5, 20);
|
|
865
|
+
const tgtPos = {
|
|
866
|
+
x: srcPos.x + Math.cos(tgtAngle) * tgtDist,
|
|
867
|
+
y: srcPos.y + this.rng.float(-1, 1),
|
|
868
|
+
z: srcPos.z + Math.sin(tgtAngle) * tgtDist
|
|
869
|
+
};
|
|
870
|
+
objects.push({
|
|
871
|
+
id: tgtName,
|
|
872
|
+
type: "object",
|
|
873
|
+
position: tgtPos,
|
|
874
|
+
scale: this.randomScale(),
|
|
875
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
876
|
+
color: this.rng.pick(COLORS)
|
|
877
|
+
});
|
|
878
|
+
const obstacleCount = difficulty === "basic" ? 0 : difficulty === "intermediate" ? this.rng.int(1, 2) : this.rng.int(2, 4);
|
|
879
|
+
const obstacleNames = [];
|
|
880
|
+
const usedNames = [srcName, tgtName];
|
|
881
|
+
let blockingObstacle = null;
|
|
882
|
+
for (let i = 0; i < obstacleCount; i++) {
|
|
883
|
+
const obsName = this.pickUniqueName(OBSTACLE_NAMES, usedNames);
|
|
884
|
+
usedNames.push(obsName);
|
|
885
|
+
obstacleNames.push(obsName);
|
|
886
|
+
const obsScale = {
|
|
887
|
+
x: this.rng.float(1.5, 4),
|
|
888
|
+
y: this.rng.float(2, 5),
|
|
889
|
+
z: this.rng.float(1.5, 4)
|
|
890
|
+
};
|
|
891
|
+
let obsPos;
|
|
892
|
+
if (!isPositive && i === 0) {
|
|
893
|
+
const t = this.rng.float(0.3, 0.7);
|
|
894
|
+
obsPos = {
|
|
895
|
+
x: srcPos.x + (tgtPos.x - srcPos.x) * t,
|
|
896
|
+
y: srcPos.y + (tgtPos.y - srcPos.y) * t,
|
|
897
|
+
z: srcPos.z + (tgtPos.z - srcPos.z) * t
|
|
898
|
+
};
|
|
899
|
+
blockingObstacle = obsName;
|
|
900
|
+
} else {
|
|
901
|
+
const offset = this.rng.float(3, 8);
|
|
902
|
+
const side = this.rng.next() > 0.5 ? 1 : -1;
|
|
903
|
+
const midpoint = {
|
|
904
|
+
x: (srcPos.x + tgtPos.x) / 2,
|
|
905
|
+
y: (srcPos.y + tgtPos.y) / 2,
|
|
906
|
+
z: (srcPos.z + tgtPos.z) / 2
|
|
907
|
+
};
|
|
908
|
+
const perpX = -(tgtPos.z - srcPos.z);
|
|
909
|
+
const perpZ = tgtPos.x - srcPos.x;
|
|
910
|
+
const perpLen = Math.sqrt(perpX * perpX + perpZ * perpZ);
|
|
911
|
+
if (perpLen > 0) {
|
|
912
|
+
obsPos = {
|
|
913
|
+
x: midpoint.x + perpX / perpLen * offset * side,
|
|
914
|
+
y: midpoint.y,
|
|
915
|
+
z: midpoint.z + perpZ / perpLen * offset * side
|
|
916
|
+
};
|
|
917
|
+
} else {
|
|
918
|
+
obsPos = {
|
|
919
|
+
x: midpoint.x + offset * side,
|
|
920
|
+
y: midpoint.y,
|
|
921
|
+
z: midpoint.z
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const obsBounds = {
|
|
926
|
+
min: {
|
|
927
|
+
x: obsPos.x - obsScale.x / 2,
|
|
928
|
+
y: obsPos.y - obsScale.y / 2,
|
|
929
|
+
z: obsPos.z - obsScale.z / 2
|
|
930
|
+
},
|
|
931
|
+
max: {
|
|
932
|
+
x: obsPos.x + obsScale.x / 2,
|
|
933
|
+
y: obsPos.y + obsScale.y / 2,
|
|
934
|
+
z: obsPos.z + obsScale.z / 2
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
objects.push({
|
|
938
|
+
id: obsName,
|
|
939
|
+
type: "obstacle",
|
|
940
|
+
position: obsPos,
|
|
941
|
+
scale: obsScale,
|
|
942
|
+
bounds: obsBounds,
|
|
943
|
+
isObstacle: true,
|
|
944
|
+
geometry: "cube",
|
|
945
|
+
color: "#444444"
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
let actuallyReachable = true;
|
|
949
|
+
if (obstacleNames.length > 0) {
|
|
950
|
+
for (const obj of objects) {
|
|
951
|
+
if (obj.isObstacle && obj.bounds) {
|
|
952
|
+
if (lineIntersectsBox(srcPos, tgtPos, obj.bounds)) {
|
|
953
|
+
actuallyReachable = false;
|
|
954
|
+
if (!blockingObstacle) blockingObstacle = obj.id;
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
const maxPathLength = this.rng.float(tgtDist * 1.2, tgtDist * 2);
|
|
961
|
+
relationships.push({
|
|
962
|
+
type: "spatial_reachable",
|
|
963
|
+
sourceId: srcName,
|
|
964
|
+
targetId: tgtName,
|
|
965
|
+
satisfied: actuallyReachable,
|
|
966
|
+
params: {
|
|
967
|
+
maxPathLength,
|
|
968
|
+
obstacleTypes: obstacleNames.length > 0 ? ["obstacle"] : void 0,
|
|
969
|
+
algorithm: "line_of_sight"
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
for (let i = objects.length; i < objectCount; i++) {
|
|
973
|
+
const name = this.pickUniqueName(OBJECT_NAMES, usedNames);
|
|
974
|
+
usedNames.push(name);
|
|
975
|
+
objects.push({
|
|
976
|
+
id: name,
|
|
977
|
+
type: "object",
|
|
978
|
+
position: this.randomPosition(difficulty),
|
|
979
|
+
scale: this.randomScale(),
|
|
980
|
+
geometry: this.rng.pick(GEOMETRY_TYPES),
|
|
981
|
+
color: this.rng.pick(COLORS)
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
const holoScript = this.buildReachableHoloScript(
|
|
985
|
+
objects,
|
|
986
|
+
srcName,
|
|
987
|
+
tgtName,
|
|
988
|
+
maxPathLength,
|
|
989
|
+
obstacleNames
|
|
990
|
+
);
|
|
991
|
+
return {
|
|
992
|
+
name: `ReachableScene_${this.exampleCounter}`,
|
|
993
|
+
objects,
|
|
994
|
+
relationships,
|
|
995
|
+
difficulty,
|
|
996
|
+
holoScriptSource: holoScript
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
buildReachableHoloScript(objects, srcName, tgtName, maxPathLength, obstacleNames) {
|
|
1000
|
+
const lines = [];
|
|
1001
|
+
lines.push(`composition "SpatialScene" {`);
|
|
1002
|
+
for (const obj of objects) {
|
|
1003
|
+
if (obj.id === srcName) {
|
|
1004
|
+
lines.push(` object "${obj.id}" {`);
|
|
1005
|
+
if (obj.geometry) lines.push(` geometry: "${obj.geometry}"`);
|
|
1006
|
+
lines.push(
|
|
1007
|
+
` position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
1008
|
+
);
|
|
1009
|
+
if (obj.color) lines.push(` color: "${obj.color}"`);
|
|
1010
|
+
lines.push(
|
|
1011
|
+
` ${generateReachableTrait(tgtName, maxPathLength, obstacleNames.length > 0 ? ["obstacle"] : void 0, "line_of_sight")}`
|
|
1012
|
+
);
|
|
1013
|
+
lines.push(" }");
|
|
1014
|
+
} else if (obj.isObstacle) {
|
|
1015
|
+
lines.push(` object "${obj.id}" {`);
|
|
1016
|
+
lines.push(` geometry: "cube"`);
|
|
1017
|
+
lines.push(
|
|
1018
|
+
` position: [${obj.position.x.toFixed(1)}, ${obj.position.y.toFixed(1)}, ${obj.position.z.toFixed(1)}]`
|
|
1019
|
+
);
|
|
1020
|
+
lines.push(
|
|
1021
|
+
` scale: [${obj.scale.x.toFixed(1)}, ${obj.scale.y.toFixed(1)}, ${obj.scale.z.toFixed(1)}]`
|
|
1022
|
+
);
|
|
1023
|
+
lines.push(" @static");
|
|
1024
|
+
lines.push(" @collidable");
|
|
1025
|
+
lines.push(" }");
|
|
1026
|
+
} else {
|
|
1027
|
+
lines.push(generateObjectBlock(obj));
|
|
1028
|
+
}
|
|
1029
|
+
lines.push("");
|
|
1030
|
+
}
|
|
1031
|
+
lines.push("}");
|
|
1032
|
+
return lines.join("\n");
|
|
1033
|
+
}
|
|
1034
|
+
// ---------------------------------------------------------------------------
|
|
1035
|
+
// Example Generation (instruction-response pairs)
|
|
1036
|
+
// ---------------------------------------------------------------------------
|
|
1037
|
+
generateExample(scene, relType, isPositive, difficulty) {
|
|
1038
|
+
this.exampleCounter++;
|
|
1039
|
+
const rel = scene.relationships[0];
|
|
1040
|
+
let instruction;
|
|
1041
|
+
let response;
|
|
1042
|
+
let templateIndex;
|
|
1043
|
+
switch (relType) {
|
|
1044
|
+
case "spatial_adjacent": {
|
|
1045
|
+
templateIndex = this.rng.int(0, ADJACENT_QUESTION_TEMPLATES.length - 1);
|
|
1046
|
+
const template = ADJACENT_QUESTION_TEMPLATES[templateIndex];
|
|
1047
|
+
const dist = computeDistance(
|
|
1048
|
+
scene.objects.find((o) => o.id === rel.sourceId).position,
|
|
1049
|
+
scene.objects.find((o) => o.id === rel.targetId).position
|
|
1050
|
+
);
|
|
1051
|
+
instruction = template.question(rel.sourceId, rel.targetId, dist, rel.params.maxDistance);
|
|
1052
|
+
response = isPositive ? template.positiveAnswer(rel.sourceId, rel.targetId, dist, rel.params.maxDistance) : template.negativeAnswer(rel.sourceId, rel.targetId, dist, rel.params.maxDistance);
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
case "spatial_contains": {
|
|
1056
|
+
templateIndex = this.rng.int(0, CONTAINS_QUESTION_TEMPLATES.length - 1);
|
|
1057
|
+
const template = CONTAINS_QUESTION_TEMPLATES[templateIndex];
|
|
1058
|
+
instruction = template.question(rel.sourceId, rel.targetId);
|
|
1059
|
+
response = isPositive ? template.positiveAnswer(rel.sourceId, rel.targetId) : template.negativeAnswer(rel.sourceId, rel.targetId);
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
case "spatial_reachable": {
|
|
1063
|
+
templateIndex = this.rng.int(0, REACHABLE_QUESTION_TEMPLATES.length - 1);
|
|
1064
|
+
const template = REACHABLE_QUESTION_TEMPLATES[templateIndex];
|
|
1065
|
+
const obstacles = scene.objects.filter((o) => o.isObstacle).map((o) => o.id);
|
|
1066
|
+
const pathLen = computeDistance(
|
|
1067
|
+
scene.objects.find((o) => o.id === rel.sourceId).position,
|
|
1068
|
+
scene.objects.find((o) => o.id === rel.targetId).position
|
|
1069
|
+
);
|
|
1070
|
+
const blocker = scene.objects.find((o) => o.isObstacle)?.id ?? "unknown";
|
|
1071
|
+
instruction = template.question(rel.sourceId, rel.targetId, obstacles);
|
|
1072
|
+
response = isPositive ? template.positiveAnswer(rel.sourceId, rel.targetId, pathLen) : template.negativeAnswer(rel.sourceId, rel.targetId, blocker);
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return {
|
|
1077
|
+
id: `spatial-${relType.replace("spatial_", "")}-${difficulty}-${this.exampleCounter}`,
|
|
1078
|
+
instruction,
|
|
1079
|
+
response,
|
|
1080
|
+
context: scene.holoScriptSource,
|
|
1081
|
+
relationshipType: relType,
|
|
1082
|
+
isPositive,
|
|
1083
|
+
difficulty,
|
|
1084
|
+
tags: [
|
|
1085
|
+
relType,
|
|
1086
|
+
difficulty,
|
|
1087
|
+
isPositive ? "positive" : "negative",
|
|
1088
|
+
`template:${relType}-${templateIndex}`
|
|
1089
|
+
]
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
// ---------------------------------------------------------------------------
|
|
1093
|
+
// Utility Methods
|
|
1094
|
+
// ---------------------------------------------------------------------------
|
|
1095
|
+
getObjectCount(difficulty) {
|
|
1096
|
+
switch (difficulty) {
|
|
1097
|
+
case "basic":
|
|
1098
|
+
return 2;
|
|
1099
|
+
case "intermediate":
|
|
1100
|
+
return this.rng.int(3, 5);
|
|
1101
|
+
case "advanced":
|
|
1102
|
+
return this.rng.int(6, 9);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
randomPosition(difficulty) {
|
|
1106
|
+
const range = difficulty === "basic" ? 5 : difficulty === "intermediate" ? 10 : 20;
|
|
1107
|
+
return {
|
|
1108
|
+
x: this.rng.float(-range, range),
|
|
1109
|
+
y: this.rng.float(0, range / 2),
|
|
1110
|
+
z: this.rng.float(-range, range)
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
randomScale() {
|
|
1114
|
+
const uniform = this.rng.next() > 0.5;
|
|
1115
|
+
if (uniform) {
|
|
1116
|
+
const s = this.rng.float(0.3, 2.5);
|
|
1117
|
+
return { x: s, y: s, z: s };
|
|
1118
|
+
}
|
|
1119
|
+
return {
|
|
1120
|
+
x: this.rng.float(0.3, 3),
|
|
1121
|
+
y: this.rng.float(0.3, 3),
|
|
1122
|
+
z: this.rng.float(0.3, 3)
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
pickUniqueName(pool, used) {
|
|
1126
|
+
const available = pool.filter((n) => !used.includes(n));
|
|
1127
|
+
if (available.length === 0) {
|
|
1128
|
+
return `${this.rng.pick(pool)}_${this.rng.int(100, 999)}`;
|
|
1129
|
+
}
|
|
1130
|
+
return this.rng.pick(available);
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
function createSpatialTrainingDataGenerator(config) {
|
|
1134
|
+
return new SpatialTrainingDataGenerator(config);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// src/training/SparsityMonitor.ts
|
|
1138
|
+
var DEFAULT_CONFIG = {
|
|
1139
|
+
sparsityThreshold: 0.93,
|
|
1140
|
+
windowSize: 50,
|
|
1141
|
+
perLayerTracking: true,
|
|
1142
|
+
energyMetricsEnabled: true,
|
|
1143
|
+
avgSynapsesPerNeuron: 100,
|
|
1144
|
+
opsPerSynapse: 2,
|
|
1145
|
+
criticalThreshold: 0.85,
|
|
1146
|
+
maxViolationHistory: 1e3
|
|
1147
|
+
};
|
|
1148
|
+
var SparsityMonitor = class {
|
|
1149
|
+
config;
|
|
1150
|
+
/** Current layer metrics indexed by layerId, latest values per layer */
|
|
1151
|
+
currentLayerMetrics = /* @__PURE__ */ new Map();
|
|
1152
|
+
/** Historical layer metrics: layerId -> array of metrics over time */
|
|
1153
|
+
layerHistory = /* @__PURE__ */ new Map();
|
|
1154
|
+
/** Snapshots taken over time */
|
|
1155
|
+
snapshots = [];
|
|
1156
|
+
/** Detected violations */
|
|
1157
|
+
violations = [];
|
|
1158
|
+
/** Rolling window of aggregate sparsity values for stats */
|
|
1159
|
+
sparsityWindow = [];
|
|
1160
|
+
/** Total timesteps recorded across all layers */
|
|
1161
|
+
totalTimestepsRecorded = 0;
|
|
1162
|
+
constructor(config = {}) {
|
|
1163
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1164
|
+
}
|
|
1165
|
+
// ---------------------------------------------------------------------------
|
|
1166
|
+
// Recording
|
|
1167
|
+
// ---------------------------------------------------------------------------
|
|
1168
|
+
/**
|
|
1169
|
+
* Record activity for a single SNN layer at a given timestep.
|
|
1170
|
+
*
|
|
1171
|
+
* Computes spike rate and activation sparsity from the raw input,
|
|
1172
|
+
* checks for threshold violations, and stores the metrics.
|
|
1173
|
+
*
|
|
1174
|
+
* @param layerId - Unique identifier for the layer
|
|
1175
|
+
* @param input - Raw activity data (neuronCount, spikeCount, timestep)
|
|
1176
|
+
* @returns The computed SNNLayerMetrics for this recording
|
|
1177
|
+
*/
|
|
1178
|
+
recordLayerActivity(layerId, input) {
|
|
1179
|
+
if (input.neuronCount <= 0) {
|
|
1180
|
+
throw new Error(`neuronCount must be positive, got ${input.neuronCount}`);
|
|
1181
|
+
}
|
|
1182
|
+
if (input.spikeCount < 0) {
|
|
1183
|
+
throw new Error(`spikeCount must be non-negative, got ${input.spikeCount}`);
|
|
1184
|
+
}
|
|
1185
|
+
if (input.spikeCount > input.neuronCount) {
|
|
1186
|
+
throw new Error(
|
|
1187
|
+
`spikeCount (${input.spikeCount}) cannot exceed neuronCount (${input.neuronCount})`
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
const spikeRate = input.spikeCount / input.neuronCount;
|
|
1191
|
+
const activationSparsity = 1 - spikeRate;
|
|
1192
|
+
const metrics = {
|
|
1193
|
+
layerId,
|
|
1194
|
+
neuronCount: input.neuronCount,
|
|
1195
|
+
spikeCount: input.spikeCount,
|
|
1196
|
+
spikeRate: roundTo4(spikeRate),
|
|
1197
|
+
activationSparsity: roundTo4(activationSparsity),
|
|
1198
|
+
avgMembranePotential: input.avgMembranePotential,
|
|
1199
|
+
timestep: input.timestep
|
|
1200
|
+
};
|
|
1201
|
+
this.currentLayerMetrics.set(layerId, metrics);
|
|
1202
|
+
if (this.config.perLayerTracking) {
|
|
1203
|
+
if (!this.layerHistory.has(layerId)) {
|
|
1204
|
+
this.layerHistory.set(layerId, []);
|
|
1205
|
+
}
|
|
1206
|
+
this.layerHistory.get(layerId).push(metrics);
|
|
1207
|
+
}
|
|
1208
|
+
this.totalTimestepsRecorded++;
|
|
1209
|
+
this.checkViolation(metrics);
|
|
1210
|
+
return metrics;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Record activity for multiple layers at the same timestep (batch recording).
|
|
1214
|
+
*
|
|
1215
|
+
* @param layerInputs - Map of layerId -> activity input
|
|
1216
|
+
* @returns Array of computed metrics for each layer
|
|
1217
|
+
*/
|
|
1218
|
+
recordBatchActivity(layerInputs) {
|
|
1219
|
+
const entries = layerInputs instanceof Map ? Array.from(layerInputs.entries()) : Object.entries(layerInputs);
|
|
1220
|
+
return entries.map(([layerId, input]) => this.recordLayerActivity(layerId, input));
|
|
1221
|
+
}
|
|
1222
|
+
// ---------------------------------------------------------------------------
|
|
1223
|
+
// Snapshots
|
|
1224
|
+
// ---------------------------------------------------------------------------
|
|
1225
|
+
/**
|
|
1226
|
+
* Take a point-in-time snapshot of all current layer metrics.
|
|
1227
|
+
*
|
|
1228
|
+
* The snapshot captures aggregate statistics across all tracked layers,
|
|
1229
|
+
* computes energy efficiency, and checks for violations.
|
|
1230
|
+
*
|
|
1231
|
+
* @returns The snapshot, or null if no layer metrics have been recorded
|
|
1232
|
+
*/
|
|
1233
|
+
takeSnapshot() {
|
|
1234
|
+
if (this.currentLayerMetrics.size === 0) {
|
|
1235
|
+
return null;
|
|
1236
|
+
}
|
|
1237
|
+
const layers = Array.from(this.currentLayerMetrics.values());
|
|
1238
|
+
const totalNeurons = layers.reduce((sum, l) => sum + l.neuronCount, 0);
|
|
1239
|
+
const totalSpikes = layers.reduce((sum, l) => sum + l.spikeCount, 0);
|
|
1240
|
+
const aggregateSparsity = totalNeurons > 0 ? roundTo4(1 - totalSpikes / totalNeurons) : 1;
|
|
1241
|
+
const aggregateSpikeRate = totalNeurons > 0 ? roundTo4(totalSpikes / totalNeurons) : 0;
|
|
1242
|
+
const energyEfficiency = this.config.energyMetricsEnabled ? this.calculateEnergyEfficiency(layers) : createZeroEnergyMetrics();
|
|
1243
|
+
const violations = this.detectViolationsForLayers(layers);
|
|
1244
|
+
const snapshot = {
|
|
1245
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1246
|
+
layers: layers.map((l) => ({ ...l })),
|
|
1247
|
+
aggregateSparsity,
|
|
1248
|
+
aggregateSpikeRate,
|
|
1249
|
+
totalNeurons,
|
|
1250
|
+
totalSpikes,
|
|
1251
|
+
energyEfficiency,
|
|
1252
|
+
violations
|
|
1253
|
+
};
|
|
1254
|
+
this.snapshots.push(snapshot);
|
|
1255
|
+
this.sparsityWindow.push(aggregateSparsity);
|
|
1256
|
+
if (this.sparsityWindow.length > this.config.windowSize) {
|
|
1257
|
+
this.sparsityWindow.shift();
|
|
1258
|
+
}
|
|
1259
|
+
return snapshot;
|
|
1260
|
+
}
|
|
1261
|
+
// ---------------------------------------------------------------------------
|
|
1262
|
+
// Energy Efficiency
|
|
1263
|
+
// ---------------------------------------------------------------------------
|
|
1264
|
+
/**
|
|
1265
|
+
* Calculate theoretical energy efficiency metrics for the given layers.
|
|
1266
|
+
*
|
|
1267
|
+
* Dense ops = sum of (neuronCount * avgSynapsesPerNeuron * opsPerSynapse) per layer
|
|
1268
|
+
* Sparse ops = sum of (spikeCount * avgSynapsesPerNeuron * opsPerSynapse) per layer
|
|
1269
|
+
*
|
|
1270
|
+
* The ratio of ops saved reflects the theoretical computational advantage
|
|
1271
|
+
* of SNN sparsity over equivalent dense ANN computation.
|
|
1272
|
+
*/
|
|
1273
|
+
calculateEnergyEfficiency(layers) {
|
|
1274
|
+
const { avgSynapsesPerNeuron, opsPerSynapse } = this.config;
|
|
1275
|
+
let denseOps = 0;
|
|
1276
|
+
let sparseOps = 0;
|
|
1277
|
+
for (const layer of layers) {
|
|
1278
|
+
const layerDenseOps = layer.neuronCount * avgSynapsesPerNeuron * opsPerSynapse;
|
|
1279
|
+
const layerSparseOps = layer.spikeCount * avgSynapsesPerNeuron * opsPerSynapse;
|
|
1280
|
+
denseOps += layerDenseOps;
|
|
1281
|
+
sparseOps += layerSparseOps;
|
|
1282
|
+
}
|
|
1283
|
+
const opsSaved = denseOps - sparseOps;
|
|
1284
|
+
const efficiencyRatio = denseOps > 0 ? roundTo4(opsSaved / denseOps) : 0;
|
|
1285
|
+
const energySavingsFactor = denseOps > 0 ? roundTo4(denseOps / Math.max(1, sparseOps)) : 1;
|
|
1286
|
+
return {
|
|
1287
|
+
denseOps,
|
|
1288
|
+
sparseOps,
|
|
1289
|
+
opsSaved,
|
|
1290
|
+
efficiencyRatio,
|
|
1291
|
+
energySavingsFactor
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
// ---------------------------------------------------------------------------
|
|
1295
|
+
// Violation Detection
|
|
1296
|
+
// ---------------------------------------------------------------------------
|
|
1297
|
+
/**
|
|
1298
|
+
* Check a single layer's metrics against the sparsity threshold.
|
|
1299
|
+
* If a violation is detected, it is recorded in the violations history.
|
|
1300
|
+
*/
|
|
1301
|
+
checkViolation(metrics) {
|
|
1302
|
+
if (metrics.activationSparsity >= this.config.sparsityThreshold) {
|
|
1303
|
+
return null;
|
|
1304
|
+
}
|
|
1305
|
+
const deficit = roundTo4(this.config.sparsityThreshold - metrics.activationSparsity);
|
|
1306
|
+
const severity = metrics.activationSparsity < this.config.criticalThreshold ? "critical" : "warning";
|
|
1307
|
+
const violation = {
|
|
1308
|
+
layerId: metrics.layerId,
|
|
1309
|
+
measuredSparsity: metrics.activationSparsity,
|
|
1310
|
+
requiredThreshold: this.config.sparsityThreshold,
|
|
1311
|
+
deficit,
|
|
1312
|
+
severity,
|
|
1313
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1314
|
+
timestep: metrics.timestep
|
|
1315
|
+
};
|
|
1316
|
+
this.violations.push(violation);
|
|
1317
|
+
if (this.violations.length > this.config.maxViolationHistory) {
|
|
1318
|
+
this.violations = this.violations.slice(-this.config.maxViolationHistory);
|
|
1319
|
+
}
|
|
1320
|
+
return violation;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Detect violations for an array of layer metrics (used in snapshots).
|
|
1324
|
+
*/
|
|
1325
|
+
detectViolationsForLayers(layers) {
|
|
1326
|
+
const snapshotViolations = [];
|
|
1327
|
+
for (const layer of layers) {
|
|
1328
|
+
if (layer.activationSparsity < this.config.sparsityThreshold) {
|
|
1329
|
+
const deficit = roundTo4(this.config.sparsityThreshold - layer.activationSparsity);
|
|
1330
|
+
const severity = layer.activationSparsity < this.config.criticalThreshold ? "critical" : "warning";
|
|
1331
|
+
snapshotViolations.push({
|
|
1332
|
+
layerId: layer.layerId,
|
|
1333
|
+
measuredSparsity: layer.activationSparsity,
|
|
1334
|
+
requiredThreshold: this.config.sparsityThreshold,
|
|
1335
|
+
deficit,
|
|
1336
|
+
severity,
|
|
1337
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1338
|
+
timestep: layer.timestep
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
return snapshotViolations;
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Get all violations currently affecting active layers
|
|
1346
|
+
* (the most recent metric per layer that is below threshold).
|
|
1347
|
+
*/
|
|
1348
|
+
getActiveViolations() {
|
|
1349
|
+
const active = [];
|
|
1350
|
+
for (const [, metrics] of this.currentLayerMetrics) {
|
|
1351
|
+
if (metrics.activationSparsity < this.config.sparsityThreshold) {
|
|
1352
|
+
const deficit = roundTo4(this.config.sparsityThreshold - metrics.activationSparsity);
|
|
1353
|
+
const severity = metrics.activationSparsity < this.config.criticalThreshold ? "critical" : "warning";
|
|
1354
|
+
active.push({
|
|
1355
|
+
layerId: metrics.layerId,
|
|
1356
|
+
measuredSparsity: metrics.activationSparsity,
|
|
1357
|
+
requiredThreshold: this.config.sparsityThreshold,
|
|
1358
|
+
deficit,
|
|
1359
|
+
severity,
|
|
1360
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1361
|
+
timestep: metrics.timestep
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return active;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Get all historical violations.
|
|
1369
|
+
*/
|
|
1370
|
+
getViolationHistory() {
|
|
1371
|
+
return [...this.violations];
|
|
1372
|
+
}
|
|
1373
|
+
// ---------------------------------------------------------------------------
|
|
1374
|
+
// Statistics
|
|
1375
|
+
// ---------------------------------------------------------------------------
|
|
1376
|
+
/**
|
|
1377
|
+
* Compute aggregate statistics across all recorded data.
|
|
1378
|
+
*/
|
|
1379
|
+
getStats() {
|
|
1380
|
+
const allSparsities = [];
|
|
1381
|
+
const perLayerSums = {};
|
|
1382
|
+
for (const [layerId, history] of this.layerHistory) {
|
|
1383
|
+
for (const m of history) {
|
|
1384
|
+
allSparsities.push(m.activationSparsity);
|
|
1385
|
+
if (!perLayerSums[layerId]) {
|
|
1386
|
+
perLayerSums[layerId] = { sum: 0, count: 0 };
|
|
1387
|
+
}
|
|
1388
|
+
perLayerSums[layerId].sum += m.activationSparsity;
|
|
1389
|
+
perLayerSums[layerId].count++;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
if (allSparsities.length === 0) {
|
|
1393
|
+
for (const [layerId, m] of this.currentLayerMetrics) {
|
|
1394
|
+
allSparsities.push(m.activationSparsity);
|
|
1395
|
+
perLayerSums[layerId] = { sum: m.activationSparsity, count: 1 };
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
const meanSparsity = allSparsities.length > 0 ? roundTo4(allSparsities.reduce((a, b) => a + b, 0) / allSparsities.length) : 0;
|
|
1399
|
+
const minSparsity = allSparsities.length > 0 ? Math.min(...allSparsities) : 0;
|
|
1400
|
+
const maxSparsity = allSparsities.length > 0 ? Math.max(...allSparsities) : 0;
|
|
1401
|
+
const stdDevSparsity = allSparsities.length > 1 ? roundTo4(standardDeviation(allSparsities)) : 0;
|
|
1402
|
+
const perLayerMeanSparsity = {};
|
|
1403
|
+
for (const [layerId, data] of Object.entries(perLayerSums)) {
|
|
1404
|
+
perLayerMeanSparsity[layerId] = roundTo4(data.sum / data.count);
|
|
1405
|
+
}
|
|
1406
|
+
const warningCount = this.violations.filter((v) => v.severity === "warning").length;
|
|
1407
|
+
const criticalCount = this.violations.filter((v) => v.severity === "critical").length;
|
|
1408
|
+
const efficiencies = this.snapshots.map((s) => s.energyEfficiency.efficiencyRatio).filter((e) => e > 0);
|
|
1409
|
+
const meanEnergyEfficiency = efficiencies.length > 0 ? roundTo4(efficiencies.reduce((a, b) => a + b, 0) / efficiencies.length) : 0;
|
|
1410
|
+
const activeViolations = this.getActiveViolations();
|
|
1411
|
+
return {
|
|
1412
|
+
totalTimesteps: this.totalTimestepsRecorded,
|
|
1413
|
+
totalSnapshots: this.snapshots.length,
|
|
1414
|
+
trackedLayers: this.currentLayerMetrics.size,
|
|
1415
|
+
meanSparsity,
|
|
1416
|
+
minSparsity,
|
|
1417
|
+
maxSparsity,
|
|
1418
|
+
stdDevSparsity,
|
|
1419
|
+
totalViolations: this.violations.length,
|
|
1420
|
+
violationsBySeverity: {
|
|
1421
|
+
warning: warningCount,
|
|
1422
|
+
critical: criticalCount
|
|
1423
|
+
},
|
|
1424
|
+
perLayerMeanSparsity,
|
|
1425
|
+
meanEnergyEfficiency,
|
|
1426
|
+
inCompliance: activeViolations.length === 0
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
// ---------------------------------------------------------------------------
|
|
1430
|
+
// Quality History Integration
|
|
1431
|
+
// ---------------------------------------------------------------------------
|
|
1432
|
+
/**
|
|
1433
|
+
* Generate an entry compatible with the quality-history.json format.
|
|
1434
|
+
*
|
|
1435
|
+
* The composite score is based on the aggregate sparsity relative to
|
|
1436
|
+
* the threshold: score = min(1, aggregateSparsity / threshold).
|
|
1437
|
+
*
|
|
1438
|
+
* @param cycle - The monitoring cycle number
|
|
1439
|
+
* @returns A quality history entry with sparsity-specific metrics
|
|
1440
|
+
*/
|
|
1441
|
+
toQualityHistoryEntry(cycle) {
|
|
1442
|
+
const stats = this.getStats();
|
|
1443
|
+
const latestSnapshot = this.snapshots.length > 0 ? this.snapshots[this.snapshots.length - 1] : null;
|
|
1444
|
+
const aggregateSparsity = latestSnapshot?.aggregateSparsity ?? stats.meanSparsity;
|
|
1445
|
+
const aggregateSpikeRate = latestSnapshot?.aggregateSpikeRate ?? 1 - stats.meanSparsity;
|
|
1446
|
+
const composite = roundTo4(Math.min(1, aggregateSparsity / this.config.sparsityThreshold));
|
|
1447
|
+
const grade = gradeFromComposite(composite);
|
|
1448
|
+
const summary = this.generateSummary(stats, latestSnapshot, composite, grade);
|
|
1449
|
+
return {
|
|
1450
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1451
|
+
cycle,
|
|
1452
|
+
composite,
|
|
1453
|
+
grade,
|
|
1454
|
+
focus: "snn-sparsity",
|
|
1455
|
+
summary,
|
|
1456
|
+
sparsityMetrics: {
|
|
1457
|
+
aggregateSparsity,
|
|
1458
|
+
aggregateSpikeRate: roundTo4(aggregateSpikeRate),
|
|
1459
|
+
energyEfficiencyRatio: stats.meanEnergyEfficiency,
|
|
1460
|
+
violationCount: stats.totalViolations,
|
|
1461
|
+
layerCount: stats.trackedLayers,
|
|
1462
|
+
totalNeurons: latestSnapshot?.totalNeurons ?? 0,
|
|
1463
|
+
inCompliance: stats.inCompliance
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Generate a human-readable summary string for the quality history entry.
|
|
1469
|
+
*/
|
|
1470
|
+
generateSummary(stats, latestSnapshot, composite, grade) {
|
|
1471
|
+
const lines = [];
|
|
1472
|
+
lines.push(`SNN Sparsity Monitor - Grade: ${grade} (${(composite * 100).toFixed(1)}%)`);
|
|
1473
|
+
lines.push(`Layers: ${stats.trackedLayers}, Timesteps: ${stats.totalTimesteps}`);
|
|
1474
|
+
lines.push(
|
|
1475
|
+
`Mean Sparsity: ${(stats.meanSparsity * 100).toFixed(1)}% (threshold: ${(this.config.sparsityThreshold * 100).toFixed(0)}%)`
|
|
1476
|
+
);
|
|
1477
|
+
lines.push(
|
|
1478
|
+
`Range: [${(stats.minSparsity * 100).toFixed(1)}%, ${(stats.maxSparsity * 100).toFixed(1)}%]`
|
|
1479
|
+
);
|
|
1480
|
+
if (latestSnapshot) {
|
|
1481
|
+
const eff = latestSnapshot.energyEfficiency;
|
|
1482
|
+
lines.push(
|
|
1483
|
+
`Energy Efficiency: ${(eff.efficiencyRatio * 100).toFixed(1)}% ops saved (${eff.energySavingsFactor.toFixed(1)}x factor)`
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
if (stats.totalViolations > 0) {
|
|
1487
|
+
lines.push(
|
|
1488
|
+
`Violations: ${stats.totalViolations} (${stats.violationsBySeverity.critical} critical, ${stats.violationsBySeverity.warning} warning)`
|
|
1489
|
+
);
|
|
1490
|
+
} else {
|
|
1491
|
+
lines.push("Compliance: All layers within threshold");
|
|
1492
|
+
}
|
|
1493
|
+
return lines.join("\n");
|
|
1494
|
+
}
|
|
1495
|
+
// ---------------------------------------------------------------------------
|
|
1496
|
+
// Harvester Integration
|
|
1497
|
+
// ---------------------------------------------------------------------------
|
|
1498
|
+
/**
|
|
1499
|
+
* Get metrics formatted for integration with SelfImproveHarvester.
|
|
1500
|
+
*
|
|
1501
|
+
* Returns a record of key metrics that can be attached to harvest records
|
|
1502
|
+
* as additional metadata for training data quality assessment.
|
|
1503
|
+
*/
|
|
1504
|
+
getHarvesterMetrics() {
|
|
1505
|
+
const stats = this.getStats();
|
|
1506
|
+
return {
|
|
1507
|
+
snn_mean_sparsity: stats.meanSparsity,
|
|
1508
|
+
snn_min_sparsity: stats.minSparsity,
|
|
1509
|
+
snn_max_sparsity: stats.maxSparsity,
|
|
1510
|
+
snn_violation_count: stats.totalViolations,
|
|
1511
|
+
snn_energy_efficiency: stats.meanEnergyEfficiency,
|
|
1512
|
+
snn_in_compliance: stats.inCompliance,
|
|
1513
|
+
snn_tracked_layers: stats.trackedLayers,
|
|
1514
|
+
snn_total_timesteps: stats.totalTimesteps
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
// ---------------------------------------------------------------------------
|
|
1518
|
+
// Accessors
|
|
1519
|
+
// ---------------------------------------------------------------------------
|
|
1520
|
+
/**
|
|
1521
|
+
* Get all recorded snapshots.
|
|
1522
|
+
*/
|
|
1523
|
+
getSnapshots() {
|
|
1524
|
+
return [...this.snapshots];
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Get the most recent snapshot, or null if none have been taken.
|
|
1528
|
+
*/
|
|
1529
|
+
getLatestSnapshot() {
|
|
1530
|
+
return this.snapshots.length > 0 ? { ...this.snapshots[this.snapshots.length - 1] } : null;
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Get current layer metrics.
|
|
1534
|
+
*/
|
|
1535
|
+
getCurrentLayerMetrics() {
|
|
1536
|
+
return new Map(this.currentLayerMetrics);
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Get history for a specific layer.
|
|
1540
|
+
*/
|
|
1541
|
+
getLayerHistory(layerId) {
|
|
1542
|
+
return [...this.layerHistory.get(layerId) ?? []];
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Get the current configuration.
|
|
1546
|
+
*/
|
|
1547
|
+
getConfig() {
|
|
1548
|
+
return { ...this.config };
|
|
1549
|
+
}
|
|
1550
|
+
// ---------------------------------------------------------------------------
|
|
1551
|
+
// Reset
|
|
1552
|
+
// ---------------------------------------------------------------------------
|
|
1553
|
+
/**
|
|
1554
|
+
* Reset all recorded data and start fresh.
|
|
1555
|
+
*/
|
|
1556
|
+
reset() {
|
|
1557
|
+
this.currentLayerMetrics.clear();
|
|
1558
|
+
this.layerHistory.clear();
|
|
1559
|
+
this.snapshots = [];
|
|
1560
|
+
this.violations = [];
|
|
1561
|
+
this.sparsityWindow = [];
|
|
1562
|
+
this.totalTimestepsRecorded = 0;
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
function roundTo4(value) {
|
|
1566
|
+
return Math.round(value * 1e4) / 1e4;
|
|
1567
|
+
}
|
|
1568
|
+
function standardDeviation(values) {
|
|
1569
|
+
if (values.length < 2) return 0;
|
|
1570
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
1571
|
+
const squaredDiffs = values.map((v) => (v - mean) ** 2);
|
|
1572
|
+
const variance = squaredDiffs.reduce((a, b) => a + b, 0) / (values.length - 1);
|
|
1573
|
+
return Math.sqrt(variance);
|
|
1574
|
+
}
|
|
1575
|
+
function gradeFromComposite(composite) {
|
|
1576
|
+
if (composite >= 0.95) return "A";
|
|
1577
|
+
if (composite >= 0.85) return "B";
|
|
1578
|
+
if (composite >= 0.7) return "C";
|
|
1579
|
+
if (composite >= 0.5) return "D";
|
|
1580
|
+
return "F";
|
|
1581
|
+
}
|
|
1582
|
+
function createZeroEnergyMetrics() {
|
|
1583
|
+
return {
|
|
1584
|
+
denseOps: 0,
|
|
1585
|
+
sparseOps: 0,
|
|
1586
|
+
opsSaved: 0,
|
|
1587
|
+
efficiencyRatio: 0,
|
|
1588
|
+
energySavingsFactor: 1
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
function createSparsityMonitor(config) {
|
|
1592
|
+
return new SparsityMonitor(config);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
// src/training/SoftDedup.ts
|
|
1596
|
+
var DEFAULT_SOFTDEDUP_CONFIG = {
|
|
1597
|
+
ngramSizes: [3, 5, 7],
|
|
1598
|
+
wordLevel: false,
|
|
1599
|
+
minWeight: 0.1,
|
|
1600
|
+
maxWeight: 1,
|
|
1601
|
+
temperature: 1,
|
|
1602
|
+
commonThresholdPercentile: 0.7
|
|
1603
|
+
};
|
|
1604
|
+
var SoftDedup = class {
|
|
1605
|
+
config;
|
|
1606
|
+
constructor(config = {}) {
|
|
1607
|
+
this.config = { ...DEFAULT_SOFTDEDUP_CONFIG, ...config };
|
|
1608
|
+
this.validateConfig();
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Process a dataset of text examples and compute sampling weights.
|
|
1612
|
+
*
|
|
1613
|
+
* @param examples - Array of text strings (training examples)
|
|
1614
|
+
* @returns Array of SoftDedupResult with sampling weights
|
|
1615
|
+
*/
|
|
1616
|
+
process(examples) {
|
|
1617
|
+
if (examples.length === 0) {
|
|
1618
|
+
return [];
|
|
1619
|
+
}
|
|
1620
|
+
if (examples.length === 1) {
|
|
1621
|
+
return [
|
|
1622
|
+
{
|
|
1623
|
+
index: 0,
|
|
1624
|
+
commonnessScore: 0,
|
|
1625
|
+
samplingWeight: this.config.maxWeight,
|
|
1626
|
+
ngramStats: {
|
|
1627
|
+
totalNgrams: this.extractNgrams(examples[0]).length,
|
|
1628
|
+
commonNgrams: 0,
|
|
1629
|
+
commonRatio: 0
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
];
|
|
1633
|
+
}
|
|
1634
|
+
const corpusFrequencies = this.buildCorpusFrequencies(examples);
|
|
1635
|
+
const threshold = this.computeThreshold(corpusFrequencies);
|
|
1636
|
+
const results = examples.map((example, index) => {
|
|
1637
|
+
const ngrams = this.extractNgrams(example);
|
|
1638
|
+
const totalNgrams = ngrams.length;
|
|
1639
|
+
if (totalNgrams === 0) {
|
|
1640
|
+
return {
|
|
1641
|
+
index,
|
|
1642
|
+
commonnessScore: 0,
|
|
1643
|
+
samplingWeight: this.config.maxWeight,
|
|
1644
|
+
ngramStats: { totalNgrams: 0, commonNgrams: 0, commonRatio: 0 }
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
let commonCount = 0;
|
|
1648
|
+
for (const ngram of ngrams) {
|
|
1649
|
+
const freq = corpusFrequencies.get(ngram) ?? 0;
|
|
1650
|
+
if (freq >= threshold) {
|
|
1651
|
+
commonCount++;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
const commonRatio = commonCount / totalNgrams;
|
|
1655
|
+
const commonnessScore = commonRatio;
|
|
1656
|
+
const samplingWeight = this.commonnessToWeight(commonnessScore);
|
|
1657
|
+
return {
|
|
1658
|
+
index,
|
|
1659
|
+
commonnessScore,
|
|
1660
|
+
samplingWeight,
|
|
1661
|
+
ngramStats: {
|
|
1662
|
+
totalNgrams,
|
|
1663
|
+
commonNgrams: commonCount,
|
|
1664
|
+
commonRatio
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1667
|
+
});
|
|
1668
|
+
return results;
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Compute aggregate statistics for a set of SoftDedup results.
|
|
1672
|
+
*/
|
|
1673
|
+
computeStats(results) {
|
|
1674
|
+
if (results.length === 0) {
|
|
1675
|
+
return {
|
|
1676
|
+
totalExamples: 0,
|
|
1677
|
+
meanWeight: 0,
|
|
1678
|
+
medianWeight: 0,
|
|
1679
|
+
stdWeight: 0,
|
|
1680
|
+
atMinWeight: 0,
|
|
1681
|
+
atMaxWeight: 0,
|
|
1682
|
+
effectiveDatasetSize: 0,
|
|
1683
|
+
reductionRatio: 0,
|
|
1684
|
+
uniqueNgramsInCorpus: 0,
|
|
1685
|
+
commonThresholdFrequency: 0
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
const weights = results.map((r) => r.samplingWeight);
|
|
1689
|
+
const totalExamples = results.length;
|
|
1690
|
+
const sum = weights.reduce((a, b) => a + b, 0);
|
|
1691
|
+
const meanWeight = sum / totalExamples;
|
|
1692
|
+
const sorted = [...weights].sort((a, b) => a - b);
|
|
1693
|
+
const mid = Math.floor(sorted.length / 2);
|
|
1694
|
+
const medianWeight = sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
1695
|
+
const variance = weights.reduce((acc, w) => acc + (w - meanWeight) ** 2, 0) / totalExamples;
|
|
1696
|
+
const stdWeight = Math.sqrt(variance);
|
|
1697
|
+
const epsilon = 1e-9;
|
|
1698
|
+
const atMinWeight = weights.filter((w) => Math.abs(w - this.config.minWeight) < epsilon).length;
|
|
1699
|
+
const atMaxWeight = weights.filter((w) => Math.abs(w - this.config.maxWeight) < epsilon).length;
|
|
1700
|
+
const effectiveDatasetSize = sum;
|
|
1701
|
+
const reductionRatio = 1 - effectiveDatasetSize / totalExamples;
|
|
1702
|
+
return {
|
|
1703
|
+
totalExamples,
|
|
1704
|
+
meanWeight,
|
|
1705
|
+
medianWeight,
|
|
1706
|
+
stdWeight,
|
|
1707
|
+
atMinWeight,
|
|
1708
|
+
atMaxWeight,
|
|
1709
|
+
effectiveDatasetSize,
|
|
1710
|
+
reductionRatio,
|
|
1711
|
+
uniqueNgramsInCorpus: 0,
|
|
1712
|
+
// filled by caller if needed
|
|
1713
|
+
commonThresholdFrequency: 0
|
|
1714
|
+
// filled by caller if needed
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Get the current configuration.
|
|
1719
|
+
*/
|
|
1720
|
+
getConfig() {
|
|
1721
|
+
return { ...this.config };
|
|
1722
|
+
}
|
|
1723
|
+
// ===========================================================================
|
|
1724
|
+
// INTERNAL METHODS
|
|
1725
|
+
// ===========================================================================
|
|
1726
|
+
/**
|
|
1727
|
+
* Extract n-grams from a text string.
|
|
1728
|
+
* Supports both character-level and word-level n-grams.
|
|
1729
|
+
*/
|
|
1730
|
+
extractNgrams(text) {
|
|
1731
|
+
const ngrams = [];
|
|
1732
|
+
for (const n of this.config.ngramSizes) {
|
|
1733
|
+
if (this.config.wordLevel) {
|
|
1734
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
1735
|
+
for (let i = 0; i <= words.length - n; i++) {
|
|
1736
|
+
ngrams.push(words.slice(i, i + n).join(" "));
|
|
1737
|
+
}
|
|
1738
|
+
} else {
|
|
1739
|
+
const normalized = text.toLowerCase();
|
|
1740
|
+
for (let i = 0; i <= normalized.length - n; i++) {
|
|
1741
|
+
ngrams.push(normalized.substring(i, i + n));
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
return ngrams;
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Build a frequency map of all n-grams across the entire corpus.
|
|
1749
|
+
*/
|
|
1750
|
+
buildCorpusFrequencies(examples) {
|
|
1751
|
+
const frequencies = /* @__PURE__ */ new Map();
|
|
1752
|
+
for (const example of examples) {
|
|
1753
|
+
const ngrams = this.extractNgrams(example);
|
|
1754
|
+
for (const ngram of ngrams) {
|
|
1755
|
+
frequencies.set(ngram, (frequencies.get(ngram) ?? 0) + 1);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return frequencies;
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Compute the frequency threshold above which an n-gram is considered "common".
|
|
1762
|
+
* Uses the configured percentile of the frequency distribution.
|
|
1763
|
+
*/
|
|
1764
|
+
computeThreshold(frequencies) {
|
|
1765
|
+
if (frequencies.size === 0) {
|
|
1766
|
+
return 1;
|
|
1767
|
+
}
|
|
1768
|
+
const freqValues = Array.from(frequencies.values()).sort((a, b) => a - b);
|
|
1769
|
+
const percentileIndex = Math.floor(freqValues.length * this.config.commonThresholdPercentile);
|
|
1770
|
+
const clampedIndex = Math.min(percentileIndex, freqValues.length - 1);
|
|
1771
|
+
return Math.max(freqValues[clampedIndex], 2);
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Convert a commonness score (0-1) to a sampling weight.
|
|
1775
|
+
*
|
|
1776
|
+
* Uses exponential decay with temperature scaling:
|
|
1777
|
+
* weight = maxWeight * exp(-commonnessScore / temperature)
|
|
1778
|
+
*
|
|
1779
|
+
* Then clamps to [minWeight, maxWeight].
|
|
1780
|
+
*/
|
|
1781
|
+
commonnessToWeight(commonnessScore) {
|
|
1782
|
+
const { minWeight, maxWeight, temperature } = this.config;
|
|
1783
|
+
const rawWeight = maxWeight * Math.exp(-commonnessScore / temperature);
|
|
1784
|
+
return Math.max(minWeight, Math.min(maxWeight, rawWeight));
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Validate configuration parameters.
|
|
1788
|
+
* @throws Error if configuration is invalid
|
|
1789
|
+
*/
|
|
1790
|
+
validateConfig() {
|
|
1791
|
+
const { minWeight, maxWeight, temperature, commonThresholdPercentile, ngramSizes } = this.config;
|
|
1792
|
+
if (minWeight <= 0 || minWeight > 1) {
|
|
1793
|
+
throw new Error(`SoftDedup: minWeight must be in (0, 1], got ${minWeight}`);
|
|
1794
|
+
}
|
|
1795
|
+
if (maxWeight < minWeight || maxWeight > 1) {
|
|
1796
|
+
throw new Error(`SoftDedup: maxWeight must be in [minWeight, 1], got ${maxWeight}`);
|
|
1797
|
+
}
|
|
1798
|
+
if (temperature <= 0) {
|
|
1799
|
+
throw new Error(`SoftDedup: temperature must be > 0, got ${temperature}`);
|
|
1800
|
+
}
|
|
1801
|
+
if (commonThresholdPercentile < 0 || commonThresholdPercentile > 1) {
|
|
1802
|
+
throw new Error(
|
|
1803
|
+
`SoftDedup: commonThresholdPercentile must be in [0, 1], got ${commonThresholdPercentile}`
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
if (ngramSizes.length === 0) {
|
|
1807
|
+
throw new Error("SoftDedup: ngramSizes must have at least one entry");
|
|
1808
|
+
}
|
|
1809
|
+
for (const n of ngramSizes) {
|
|
1810
|
+
if (n < 1 || !Number.isInteger(n)) {
|
|
1811
|
+
throw new Error(`SoftDedup: each ngramSize must be a positive integer, got ${n}`);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
};
|
|
1816
|
+
function createSoftDedup(config = {}) {
|
|
1817
|
+
return new SoftDedup(config);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// src/training/LRScheduler.ts
|
|
1821
|
+
var DEFAULT_LR_SCHEDULER_CONFIG = {
|
|
1822
|
+
baseLR: 2e-4,
|
|
1823
|
+
totalSteps: 1e3,
|
|
1824
|
+
warmupRatio: 0.1,
|
|
1825
|
+
minLR: 0,
|
|
1826
|
+
numCycles: 1
|
|
1827
|
+
};
|
|
1828
|
+
var GRPO_LR_SCHEDULER_CONFIG = {
|
|
1829
|
+
baseLR: 1e-6,
|
|
1830
|
+
totalSteps: 1e3,
|
|
1831
|
+
warmupRatio: 0.1,
|
|
1832
|
+
minLR: 0,
|
|
1833
|
+
numCycles: 1
|
|
1834
|
+
};
|
|
1835
|
+
var LRScheduler = class {
|
|
1836
|
+
config;
|
|
1837
|
+
warmupSteps;
|
|
1838
|
+
constructor(config = {}) {
|
|
1839
|
+
this.config = { ...DEFAULT_LR_SCHEDULER_CONFIG, ...config };
|
|
1840
|
+
this.validateConfig();
|
|
1841
|
+
this.warmupSteps = Math.floor(this.config.totalSteps * this.config.warmupRatio);
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Get the learning rate at a given training step.
|
|
1845
|
+
*
|
|
1846
|
+
* @param step - Current training step (0-indexed)
|
|
1847
|
+
* @returns The learning rate at this step
|
|
1848
|
+
*/
|
|
1849
|
+
getLR(step) {
|
|
1850
|
+
const { baseLR, totalSteps, minLR, numCycles } = this.config;
|
|
1851
|
+
const clampedStep = Math.max(0, Math.min(step, totalSteps));
|
|
1852
|
+
if (clampedStep < this.warmupSteps) {
|
|
1853
|
+
if (this.warmupSteps === 0) {
|
|
1854
|
+
return baseLR;
|
|
1855
|
+
}
|
|
1856
|
+
return baseLR * (clampedStep / this.warmupSteps);
|
|
1857
|
+
}
|
|
1858
|
+
const decaySteps = totalSteps - this.warmupSteps;
|
|
1859
|
+
if (decaySteps <= 0) {
|
|
1860
|
+
return baseLR;
|
|
1861
|
+
}
|
|
1862
|
+
const decayProgress = (clampedStep - this.warmupSteps) / decaySteps;
|
|
1863
|
+
const cosineArg = Math.PI * numCycles * decayProgress;
|
|
1864
|
+
const cosineValue = (1 + Math.cos(cosineArg)) / 2;
|
|
1865
|
+
return minLR + (baseLR - minLR) * cosineValue;
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Get a detailed snapshot of the scheduler state at a given step.
|
|
1869
|
+
*
|
|
1870
|
+
* @param step - Current training step (0-indexed)
|
|
1871
|
+
* @returns LRSchedulerSnapshot with full state information
|
|
1872
|
+
*/
|
|
1873
|
+
getSnapshot(step) {
|
|
1874
|
+
const { totalSteps } = this.config;
|
|
1875
|
+
const clampedStep = Math.max(0, Math.min(step, totalSteps));
|
|
1876
|
+
const learningRate = this.getLR(step);
|
|
1877
|
+
const isWarmup = clampedStep < this.warmupSteps;
|
|
1878
|
+
const phase = isWarmup ? "warmup" : "decay";
|
|
1879
|
+
let phaseProgress;
|
|
1880
|
+
if (isWarmup) {
|
|
1881
|
+
phaseProgress = this.warmupSteps === 0 ? 1 : clampedStep / this.warmupSteps;
|
|
1882
|
+
} else {
|
|
1883
|
+
const decaySteps = totalSteps - this.warmupSteps;
|
|
1884
|
+
phaseProgress = decaySteps <= 0 ? 1 : (clampedStep - this.warmupSteps) / decaySteps;
|
|
1885
|
+
}
|
|
1886
|
+
const overallProgress = totalSteps === 0 ? 1 : clampedStep / totalSteps;
|
|
1887
|
+
return {
|
|
1888
|
+
step: clampedStep,
|
|
1889
|
+
learningRate,
|
|
1890
|
+
phase,
|
|
1891
|
+
phaseProgress,
|
|
1892
|
+
overallProgress
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Compute summary statistics for the full LR schedule.
|
|
1897
|
+
* Samples every step to compute the average LR.
|
|
1898
|
+
*
|
|
1899
|
+
* For large totalSteps, this samples at most 10000 evenly-spaced points
|
|
1900
|
+
* for efficiency.
|
|
1901
|
+
*/
|
|
1902
|
+
getStats() {
|
|
1903
|
+
const { baseLR, minLR, totalSteps } = this.config;
|
|
1904
|
+
const decaySteps = totalSteps - this.warmupSteps;
|
|
1905
|
+
let sumLR = 0;
|
|
1906
|
+
const sampleCount = Math.min(totalSteps + 1, 1e4);
|
|
1907
|
+
const stepSize = totalSteps / Math.max(sampleCount - 1, 1);
|
|
1908
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
1909
|
+
const step = Math.round(i * stepSize);
|
|
1910
|
+
sumLR += this.getLR(step);
|
|
1911
|
+
}
|
|
1912
|
+
const avgLR = sampleCount > 0 ? sumLR / sampleCount : 0;
|
|
1913
|
+
return {
|
|
1914
|
+
peakLR: baseLR,
|
|
1915
|
+
minLR,
|
|
1916
|
+
warmupSteps: this.warmupSteps,
|
|
1917
|
+
decaySteps: Math.max(0, decaySteps),
|
|
1918
|
+
totalSteps,
|
|
1919
|
+
avgLR
|
|
1920
|
+
};
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Generate the full LR schedule as an array of [step, lr] pairs.
|
|
1924
|
+
* Useful for plotting or debugging.
|
|
1925
|
+
*
|
|
1926
|
+
* @param numPoints - Number of points to sample (default: 100)
|
|
1927
|
+
* @returns Array of [step, learningRate] tuples
|
|
1928
|
+
*/
|
|
1929
|
+
getSchedule(numPoints = 100) {
|
|
1930
|
+
const { totalSteps } = this.config;
|
|
1931
|
+
const points = [];
|
|
1932
|
+
const clampedPoints = Math.max(2, numPoints);
|
|
1933
|
+
for (let i = 0; i < clampedPoints; i++) {
|
|
1934
|
+
const step = Math.round(i / (clampedPoints - 1) * totalSteps);
|
|
1935
|
+
points.push([step, this.getLR(step)]);
|
|
1936
|
+
}
|
|
1937
|
+
return points;
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Get the number of warmup steps.
|
|
1941
|
+
*/
|
|
1942
|
+
getWarmupSteps() {
|
|
1943
|
+
return this.warmupSteps;
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Get the current configuration.
|
|
1947
|
+
*/
|
|
1948
|
+
getConfig() {
|
|
1949
|
+
return { ...this.config };
|
|
1950
|
+
}
|
|
1951
|
+
// ===========================================================================
|
|
1952
|
+
// INTERNAL METHODS
|
|
1953
|
+
// ===========================================================================
|
|
1954
|
+
/**
|
|
1955
|
+
* Validate configuration parameters.
|
|
1956
|
+
* @throws Error if configuration is invalid
|
|
1957
|
+
*/
|
|
1958
|
+
validateConfig() {
|
|
1959
|
+
const { baseLR, totalSteps, warmupRatio, minLR, numCycles } = this.config;
|
|
1960
|
+
if (baseLR <= 0) {
|
|
1961
|
+
throw new Error(`LRScheduler: baseLR must be > 0, got ${baseLR}`);
|
|
1962
|
+
}
|
|
1963
|
+
if (totalSteps < 0 || !Number.isInteger(totalSteps)) {
|
|
1964
|
+
throw new Error(`LRScheduler: totalSteps must be a non-negative integer, got ${totalSteps}`);
|
|
1965
|
+
}
|
|
1966
|
+
if (warmupRatio < 0 || warmupRatio >= 1) {
|
|
1967
|
+
throw new Error(`LRScheduler: warmupRatio must be in [0, 1), got ${warmupRatio}`);
|
|
1968
|
+
}
|
|
1969
|
+
if (minLR < 0 || minLR >= baseLR) {
|
|
1970
|
+
throw new Error(`LRScheduler: minLR must be in [0, baseLR), got ${minLR}`);
|
|
1971
|
+
}
|
|
1972
|
+
if (numCycles < 1 || !Number.isInteger(numCycles)) {
|
|
1973
|
+
throw new Error(`LRScheduler: numCycles must be a positive integer, got ${numCycles}`);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
function createSFTScheduler(config = {}) {
|
|
1978
|
+
return new LRScheduler({ ...DEFAULT_LR_SCHEDULER_CONFIG, ...config });
|
|
1979
|
+
}
|
|
1980
|
+
function createGRPOScheduler(config = {}) {
|
|
1981
|
+
return new LRScheduler({ ...GRPO_LR_SCHEDULER_CONFIG, ...config });
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
// src/training/QualityScoringPipeline.ts
|
|
1985
|
+
var DEFAULT_SCORING_CONFIG = {
|
|
1986
|
+
syntaxWeight: 0.4,
|
|
1987
|
+
schemaWeight: 0.3,
|
|
1988
|
+
semanticWeight: 0.3,
|
|
1989
|
+
minPassScore: 0.7,
|
|
1990
|
+
knownTraits: /* @__PURE__ */ new Set(["Grabbable", "Physics", "Animation", "GaussianSplat", "Tradeable", "NPC"]),
|
|
1991
|
+
knownTypes: /* @__PURE__ */ new Set(["orb", "world", "composition", "template"])
|
|
1992
|
+
};
|
|
1993
|
+
var QualityScoringPipeline = class {
|
|
1994
|
+
config;
|
|
1995
|
+
constructor(config = {}) {
|
|
1996
|
+
this.config = { ...DEFAULT_SCORING_CONFIG, ...config };
|
|
1997
|
+
}
|
|
1998
|
+
score(source) {
|
|
1999
|
+
const details = [];
|
|
2000
|
+
const syntax = this.scoreSyntax(source, details);
|
|
2001
|
+
const schema = this.scoreSchema(source, details);
|
|
2002
|
+
const semantic = this.scoreSemantic(source, details);
|
|
2003
|
+
const composite = syntax * this.config.syntaxWeight + schema * this.config.schemaWeight + semantic * this.config.semanticWeight;
|
|
2004
|
+
return {
|
|
2005
|
+
syntaxValidity: syntax,
|
|
2006
|
+
schemaCompliance: schema,
|
|
2007
|
+
semanticCorrectness: semantic,
|
|
2008
|
+
compositeScore: composite,
|
|
2009
|
+
details
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
passes(score) {
|
|
2013
|
+
return score.compositeScore >= this.config.minPassScore;
|
|
2014
|
+
}
|
|
2015
|
+
scoreSyntax(source, details) {
|
|
2016
|
+
let score = 1;
|
|
2017
|
+
if (!source.trim()) {
|
|
2018
|
+
details.push({ metric: "syntax", score: 0, message: "Empty source" });
|
|
2019
|
+
return 0;
|
|
2020
|
+
}
|
|
2021
|
+
const open = (source.match(/{/g) || []).length;
|
|
2022
|
+
const close = (source.match(/}/g) || []).length;
|
|
2023
|
+
if (open !== close) {
|
|
2024
|
+
score -= 0.5;
|
|
2025
|
+
details.push({
|
|
2026
|
+
metric: "syntax",
|
|
2027
|
+
score: 0.5,
|
|
2028
|
+
message: `Unbalanced braces: ${open} open, ${close} close`
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
if (/composition\s+\w+/.test(source) || /orb\s+\w+/.test(source) || /world\s+\w+/.test(source)) {
|
|
2032
|
+
details.push({ metric: "syntax", score: 1, message: "Valid composition structure" });
|
|
2033
|
+
} else {
|
|
2034
|
+
score -= 0.3;
|
|
2035
|
+
details.push({
|
|
2036
|
+
metric: "syntax",
|
|
2037
|
+
score: 0.7,
|
|
2038
|
+
message: "No recognized top-level declaration"
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
return Math.max(0, score);
|
|
2042
|
+
}
|
|
2043
|
+
scoreSchema(source, details) {
|
|
2044
|
+
let score = 1;
|
|
2045
|
+
const traitPattern = /(\w+)\s*{/g;
|
|
2046
|
+
let match;
|
|
2047
|
+
while ((match = traitPattern.exec(source)) !== null) {
|
|
2048
|
+
const name = match[1];
|
|
2049
|
+
if (this.config.knownTraits.has(name) || this.config.knownTypes.has(name)) continue;
|
|
2050
|
+
if (/^[A-Z]/.test(name) && !this.config.knownTraits.has(name)) {
|
|
2051
|
+
score -= 0.1;
|
|
2052
|
+
details.push({ metric: "schema", score: 0.9, message: `Unknown trait: ${name}` });
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
return Math.max(0, score);
|
|
2056
|
+
}
|
|
2057
|
+
scoreSemantic(source, details) {
|
|
2058
|
+
let score = 1;
|
|
2059
|
+
if (/physics\s*{/.test(source) && !/mass|gravity|friction/.test(source)) {
|
|
2060
|
+
score -= 0.2;
|
|
2061
|
+
details.push({
|
|
2062
|
+
metric: "semantic",
|
|
2063
|
+
score: 0.8,
|
|
2064
|
+
message: "Physics block without mass/gravity/friction"
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
if (/animation\s*{/.test(source) && !/clip|duration|speed/.test(source)) {
|
|
2068
|
+
score -= 0.2;
|
|
2069
|
+
details.push({
|
|
2070
|
+
metric: "semantic",
|
|
2071
|
+
score: 0.8,
|
|
2072
|
+
message: "Animation block without clip/duration"
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
if (source.length < 20) {
|
|
2076
|
+
score -= 0.3;
|
|
2077
|
+
details.push({
|
|
2078
|
+
metric: "semantic",
|
|
2079
|
+
score: 0.7,
|
|
2080
|
+
message: "Source too short for meaningful composition"
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
return Math.max(0, score);
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
|
|
2087
|
+
// src/training/TrainingPipelineConfig.ts
|
|
2088
|
+
var DEFAULT_TRAINING_PIPELINE_CONFIG = {
|
|
2089
|
+
qualityScoring: DEFAULT_SCORING_CONFIG,
|
|
2090
|
+
softDedup: DEFAULT_SOFTDEDUP_CONFIG,
|
|
2091
|
+
lrSchedule: DEFAULT_LR_SCHEDULER_CONFIG,
|
|
2092
|
+
hyperparameters: {
|
|
2093
|
+
learningRate: 2e-4,
|
|
2094
|
+
epochs: 2,
|
|
2095
|
+
optimizer: "paged_adamw_8bit",
|
|
2096
|
+
microBatchSize: 8,
|
|
2097
|
+
gradientAccumulationSteps: 4,
|
|
2098
|
+
maxGradNorm: 1,
|
|
2099
|
+
weightDecay: 0.01
|
|
2100
|
+
},
|
|
2101
|
+
pipeline: {
|
|
2102
|
+
enableQualityFilter: true,
|
|
2103
|
+
enableSoftDedup: true,
|
|
2104
|
+
enableLRSchedule: true
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
function buildTrainingPipelineConfig(overrides = {}) {
|
|
2108
|
+
return {
|
|
2109
|
+
qualityScoring: {
|
|
2110
|
+
...DEFAULT_TRAINING_PIPELINE_CONFIG.qualityScoring,
|
|
2111
|
+
...overrides.qualityScoring
|
|
2112
|
+
},
|
|
2113
|
+
softDedup: {
|
|
2114
|
+
...DEFAULT_TRAINING_PIPELINE_CONFIG.softDedup,
|
|
2115
|
+
...overrides.softDedup
|
|
2116
|
+
},
|
|
2117
|
+
lrSchedule: {
|
|
2118
|
+
...DEFAULT_TRAINING_PIPELINE_CONFIG.lrSchedule,
|
|
2119
|
+
...overrides.lrSchedule
|
|
2120
|
+
},
|
|
2121
|
+
hyperparameters: {
|
|
2122
|
+
...DEFAULT_TRAINING_PIPELINE_CONFIG.hyperparameters,
|
|
2123
|
+
...overrides.hyperparameters
|
|
2124
|
+
},
|
|
2125
|
+
pipeline: {
|
|
2126
|
+
...DEFAULT_TRAINING_PIPELINE_CONFIG.pipeline,
|
|
2127
|
+
...overrides.pipeline
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
function computeTotalSteps(datasetSize, config) {
|
|
2132
|
+
const { microBatchSize, gradientAccumulationSteps, epochs } = config.hyperparameters;
|
|
2133
|
+
const effectiveBatchSize = microBatchSize * gradientAccumulationSteps;
|
|
2134
|
+
const stepsPerEpoch = Math.ceil(datasetSize / effectiveBatchSize);
|
|
2135
|
+
return stepsPerEpoch * epochs;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// src/training/trainingmonkey/TrainingMonkeyTypes.ts
|
|
2139
|
+
var DEFAULT_INTEGRATION_CONFIG = {
|
|
2140
|
+
inputPath: "",
|
|
2141
|
+
outputDir: "",
|
|
2142
|
+
trainRatio: 0.9,
|
|
2143
|
+
seed: 42,
|
|
2144
|
+
enableSoftDedup: true,
|
|
2145
|
+
modelName: "qwen7b",
|
|
2146
|
+
stratify: true
|
|
2147
|
+
};
|
|
2148
|
+
|
|
2149
|
+
// src/training/trainingmonkey/TrainingMonkeyIntegration.ts
|
|
2150
|
+
function createSeededRandom(seed) {
|
|
2151
|
+
let state = seed | 0;
|
|
2152
|
+
return () => {
|
|
2153
|
+
state = state + 1831565813 | 0;
|
|
2154
|
+
let t = Math.imul(state ^ state >>> 15, 1 | state);
|
|
2155
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2156
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
var TrainingMonkeyIntegration = class {
|
|
2160
|
+
config;
|
|
2161
|
+
softDedup;
|
|
2162
|
+
constructor(config = {}) {
|
|
2163
|
+
this.config = { ...DEFAULT_INTEGRATION_CONFIG, ...config };
|
|
2164
|
+
this.softDedup = new SoftDedup({
|
|
2165
|
+
ngramSizes: [3, 5, 7],
|
|
2166
|
+
wordLevel: false,
|
|
2167
|
+
minWeight: 0.1,
|
|
2168
|
+
maxWeight: 1,
|
|
2169
|
+
temperature: 1,
|
|
2170
|
+
commonThresholdPercentile: 0.7
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
// ===========================================================================
|
|
2174
|
+
// PUBLIC API
|
|
2175
|
+
// ===========================================================================
|
|
2176
|
+
/**
|
|
2177
|
+
* Run the full integration pipeline on raw JSONL content.
|
|
2178
|
+
*
|
|
2179
|
+
* @param jsonlContent - Raw JSONL string content from the dataset file
|
|
2180
|
+
* @returns Complete IntegrationResult with split data, config, and serialized output
|
|
2181
|
+
*/
|
|
2182
|
+
process(jsonlContent) {
|
|
2183
|
+
const entries = this.readJsonl(jsonlContent);
|
|
2184
|
+
const alpacaEntries = this.convertToAlpaca(entries);
|
|
2185
|
+
const weightedEntries = this.config.enableSoftDedup ? this.applySoftDedup(alpacaEntries, entries) : alpacaEntries.map(
|
|
2186
|
+
(entry, index) => ({
|
|
2187
|
+
...entry,
|
|
2188
|
+
sampling_weight: 1,
|
|
2189
|
+
metadata: entries[index]?.metadata
|
|
2190
|
+
})
|
|
2191
|
+
);
|
|
2192
|
+
const split = this.splitDataset(weightedEntries);
|
|
2193
|
+
const config = this.generateConfig(split);
|
|
2194
|
+
const trainJsonl = this.serializeJsonl(split.train);
|
|
2195
|
+
const validationJsonl = this.serializeJsonl(split.validation);
|
|
2196
|
+
const configJson = JSON.stringify(config, null, 2);
|
|
2197
|
+
return {
|
|
2198
|
+
split,
|
|
2199
|
+
config,
|
|
2200
|
+
trainJsonl,
|
|
2201
|
+
validationJsonl,
|
|
2202
|
+
configJson
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Parse raw JSONL content into SpatialTrainingJSONLEntry objects.
|
|
2207
|
+
*
|
|
2208
|
+
* @param jsonlContent - Raw JSONL string (one JSON object per line)
|
|
2209
|
+
* @returns Array of parsed entries
|
|
2210
|
+
* @throws Error if a line contains invalid JSON
|
|
2211
|
+
*/
|
|
2212
|
+
readJsonl(jsonlContent) {
|
|
2213
|
+
const lines = jsonlContent.split("\n").filter((line) => line.trim() !== "");
|
|
2214
|
+
const entries = [];
|
|
2215
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2216
|
+
try {
|
|
2217
|
+
const parsed = JSON.parse(lines[i]);
|
|
2218
|
+
if (!parsed.instruction || !parsed.response) {
|
|
2219
|
+
throw new Error(`Missing required fields (instruction/response)`);
|
|
2220
|
+
}
|
|
2221
|
+
entries.push(parsed);
|
|
2222
|
+
} catch (e) {
|
|
2223
|
+
throw new Error(`Failed to parse JSONL line ${i + 1}: ${e.message}`);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
return entries;
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Convert spatial training entries to Alpaca format.
|
|
2230
|
+
*
|
|
2231
|
+
* Mapping:
|
|
2232
|
+
* instruction -> instruction (question/prompt)
|
|
2233
|
+
* input -> extracted HoloScript scene from instruction (if present)
|
|
2234
|
+
* output -> response (answer)
|
|
2235
|
+
*
|
|
2236
|
+
* @param entries - Parsed spatial training entries
|
|
2237
|
+
* @returns Alpaca-formatted entries
|
|
2238
|
+
*/
|
|
2239
|
+
convertToAlpaca(entries) {
|
|
2240
|
+
return entries.map((entry) => {
|
|
2241
|
+
const { questionPart, scenePart } = this.extractSceneFromInstruction(entry.instruction);
|
|
2242
|
+
return {
|
|
2243
|
+
instruction: questionPart,
|
|
2244
|
+
input: scenePart,
|
|
2245
|
+
output: entry.response
|
|
2246
|
+
};
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* Apply SoftDedup (W.008) n-gram reweighting to Alpaca entries.
|
|
2251
|
+
*
|
|
2252
|
+
* Uses the instruction + output text to compute n-gram commonness scores
|
|
2253
|
+
* and assigns sampling weights. Template-generated near-duplicates receive
|
|
2254
|
+
* lower weights (min 0.1), while unique examples keep weight 1.0.
|
|
2255
|
+
*
|
|
2256
|
+
* @param alpacaEntries - Converted Alpaca entries
|
|
2257
|
+
* @param originalEntries - Original JSONL entries (for metadata preservation)
|
|
2258
|
+
* @returns Weighted Alpaca entries with sampling_weight and metadata
|
|
2259
|
+
*/
|
|
2260
|
+
applySoftDedup(alpacaEntries, originalEntries) {
|
|
2261
|
+
const textCorpus = alpacaEntries.map((entry) => `${entry.instruction} ${entry.output}`);
|
|
2262
|
+
const dedupResults = this.softDedup.process(textCorpus);
|
|
2263
|
+
return alpacaEntries.map((entry, index) => ({
|
|
2264
|
+
...entry,
|
|
2265
|
+
sampling_weight: dedupResults[index]?.samplingWeight ?? 1,
|
|
2266
|
+
metadata: originalEntries[index]?.metadata
|
|
2267
|
+
}));
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Split weighted entries into train/validation sets.
|
|
2271
|
+
*
|
|
2272
|
+
* When stratified=true, the split preserves the distribution of
|
|
2273
|
+
* relationship_type and difficulty across both sets.
|
|
2274
|
+
*
|
|
2275
|
+
* @param entries - Weighted Alpaca entries
|
|
2276
|
+
* @returns DatasetSplit with train, validation, and stats
|
|
2277
|
+
*/
|
|
2278
|
+
splitDataset(entries) {
|
|
2279
|
+
if (entries.length === 0) {
|
|
2280
|
+
return {
|
|
2281
|
+
train: [],
|
|
2282
|
+
validation: [],
|
|
2283
|
+
stats: {
|
|
2284
|
+
totalExamples: 0,
|
|
2285
|
+
trainCount: 0,
|
|
2286
|
+
validationCount: 0,
|
|
2287
|
+
trainRatio: 0,
|
|
2288
|
+
validationRatio: 0,
|
|
2289
|
+
stratified: this.config.stratify
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
const rng = createSeededRandom(this.config.seed);
|
|
2294
|
+
let train;
|
|
2295
|
+
let validation;
|
|
2296
|
+
if (this.config.stratify && entries[0]?.metadata) {
|
|
2297
|
+
({ train, validation } = this.stratifiedSplit(entries, rng));
|
|
2298
|
+
} else {
|
|
2299
|
+
({ train, validation } = this.randomSplit(entries, rng));
|
|
2300
|
+
}
|
|
2301
|
+
const stats = {
|
|
2302
|
+
totalExamples: entries.length,
|
|
2303
|
+
trainCount: train.length,
|
|
2304
|
+
validationCount: validation.length,
|
|
2305
|
+
trainRatio: train.length / entries.length,
|
|
2306
|
+
validationRatio: validation.length / entries.length,
|
|
2307
|
+
stratified: this.config.stratify && !!entries[0]?.metadata
|
|
2308
|
+
};
|
|
2309
|
+
return { train, validation, stats };
|
|
2310
|
+
}
|
|
2311
|
+
/**
|
|
2312
|
+
* Generate a TrainingMonkey-compatible training configuration.
|
|
2313
|
+
*
|
|
2314
|
+
* Uses W.006 hyperparameters:
|
|
2315
|
+
* - Learning rate: 2e-4
|
|
2316
|
+
* - Epochs: 2
|
|
2317
|
+
* - Optimizer: paged_adamw_8bit
|
|
2318
|
+
*
|
|
2319
|
+
* Uses W.007 batch sizing:
|
|
2320
|
+
* - Micro-batch: 8
|
|
2321
|
+
* - Gradient accumulation: 4
|
|
2322
|
+
* - Effective batch: 32
|
|
2323
|
+
*
|
|
2324
|
+
* Uses W.009 LR schedule:
|
|
2325
|
+
* - Warmup: 10% of total steps
|
|
2326
|
+
* - Schedule: cosine decay
|
|
2327
|
+
*
|
|
2328
|
+
* @param split - The dataset split result
|
|
2329
|
+
* @returns TrainingMonkey-compatible config
|
|
2330
|
+
*/
|
|
2331
|
+
generateConfig(split) {
|
|
2332
|
+
const microBatchSize = 8;
|
|
2333
|
+
const gradientAccumulationSteps = 4;
|
|
2334
|
+
const epochs = 2;
|
|
2335
|
+
const effectiveBatchSize = microBatchSize * gradientAccumulationSteps;
|
|
2336
|
+
const stepsPerEpoch = Math.ceil(split.stats.trainCount / effectiveBatchSize);
|
|
2337
|
+
const totalSteps = stepsPerEpoch * epochs;
|
|
2338
|
+
const weights = split.train.map((e) => e.sampling_weight);
|
|
2339
|
+
const meanWeight = weights.length > 0 ? weights.reduce((sum, w) => sum + w, 0) / weights.length : 1;
|
|
2340
|
+
const effectiveSize = weights.reduce((sum, w) => sum + w, 0);
|
|
2341
|
+
const reductionRatio = weights.length > 0 ? 1 - effectiveSize / weights.length : 0;
|
|
2342
|
+
return {
|
|
2343
|
+
model: {
|
|
2344
|
+
name: this.config.modelName,
|
|
2345
|
+
maxSeqLength: 2048
|
|
2346
|
+
},
|
|
2347
|
+
hyperparameters: {
|
|
2348
|
+
learningRate: 2e-4,
|
|
2349
|
+
epochs,
|
|
2350
|
+
optimizer: "paged_adamw_8bit",
|
|
2351
|
+
microBatchSize,
|
|
2352
|
+
gradientAccumulationSteps,
|
|
2353
|
+
maxGradNorm: 1,
|
|
2354
|
+
weightDecay: 0.01
|
|
2355
|
+
},
|
|
2356
|
+
lrSchedule: {
|
|
2357
|
+
warmupRatio: 0.1,
|
|
2358
|
+
type: "cosine"
|
|
2359
|
+
},
|
|
2360
|
+
dataset: {
|
|
2361
|
+
trainPath: `${this.config.outputDir}/alpaca-train.jsonl`,
|
|
2362
|
+
validationPath: `${this.config.outputDir}/alpaca-val.jsonl`,
|
|
2363
|
+
trainCount: split.stats.trainCount,
|
|
2364
|
+
validationCount: split.stats.validationCount,
|
|
2365
|
+
totalSteps
|
|
2366
|
+
},
|
|
2367
|
+
softDedup: {
|
|
2368
|
+
applied: this.config.enableSoftDedup,
|
|
2369
|
+
meanWeight,
|
|
2370
|
+
effectiveSize,
|
|
2371
|
+
reductionRatio
|
|
2372
|
+
}
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Serialize weighted Alpaca entries to JSONL string.
|
|
2377
|
+
*
|
|
2378
|
+
* @param entries - Entries to serialize
|
|
2379
|
+
* @returns JSONL string (one JSON object per line)
|
|
2380
|
+
*/
|
|
2381
|
+
serializeJsonl(entries) {
|
|
2382
|
+
return entries.map((entry) => JSON.stringify(entry)).join("\n");
|
|
2383
|
+
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Get the current integration configuration.
|
|
2386
|
+
*/
|
|
2387
|
+
getConfig() {
|
|
2388
|
+
return { ...this.config };
|
|
2389
|
+
}
|
|
2390
|
+
// ===========================================================================
|
|
2391
|
+
// INTERNAL METHODS
|
|
2392
|
+
// ===========================================================================
|
|
2393
|
+
/**
|
|
2394
|
+
* Extract the question part and HoloScript scene from an instruction string.
|
|
2395
|
+
*
|
|
2396
|
+
* The spatial reasoning dataset embeds HoloScript scenes in instructions:
|
|
2397
|
+
* ```
|
|
2398
|
+
* Does the spatial_adjacent constraint pass?
|
|
2399
|
+
*
|
|
2400
|
+
* HoloScript Scene:
|
|
2401
|
+
* ```holoscript
|
|
2402
|
+
* composition "SpatialScene" { ... }
|
|
2403
|
+
* ```
|
|
2404
|
+
* ```
|
|
2405
|
+
*
|
|
2406
|
+
* This method splits the instruction into the question (instruction field)
|
|
2407
|
+
* and the scene source (input field) for the Alpaca format.
|
|
2408
|
+
*/
|
|
2409
|
+
extractSceneFromInstruction(instruction) {
|
|
2410
|
+
const sceneMarkerIndex = instruction.indexOf("HoloScript Scene:");
|
|
2411
|
+
if (sceneMarkerIndex === -1) {
|
|
2412
|
+
return { questionPart: instruction.trim(), scenePart: "" };
|
|
2413
|
+
}
|
|
2414
|
+
const questionPart = instruction.substring(0, sceneMarkerIndex).trim();
|
|
2415
|
+
const scenePart = instruction.substring(sceneMarkerIndex).trim();
|
|
2416
|
+
return { questionPart, scenePart };
|
|
2417
|
+
}
|
|
2418
|
+
/**
|
|
2419
|
+
* Perform a stratified split preserving distribution of metadata fields.
|
|
2420
|
+
* Groups by relationship_type + difficulty and splits each group proportionally.
|
|
2421
|
+
*/
|
|
2422
|
+
stratifiedSplit(entries, rng) {
|
|
2423
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2424
|
+
for (const entry of entries) {
|
|
2425
|
+
const key = entry.metadata ? `${entry.metadata.relationship_type}:${entry.metadata.difficulty}` : "unknown";
|
|
2426
|
+
if (!groups.has(key)) {
|
|
2427
|
+
groups.set(key, []);
|
|
2428
|
+
}
|
|
2429
|
+
groups.get(key).push(entry);
|
|
2430
|
+
}
|
|
2431
|
+
const train = [];
|
|
2432
|
+
const validation = [];
|
|
2433
|
+
for (const [, groupEntries] of groups) {
|
|
2434
|
+
const shuffled = this.fisherYatesShuffle([...groupEntries], rng);
|
|
2435
|
+
const splitIndex = Math.round(shuffled.length * this.config.trainRatio);
|
|
2436
|
+
train.push(...shuffled.slice(0, splitIndex));
|
|
2437
|
+
validation.push(...shuffled.slice(splitIndex));
|
|
2438
|
+
}
|
|
2439
|
+
return { train, validation };
|
|
2440
|
+
}
|
|
2441
|
+
/**
|
|
2442
|
+
* Perform a simple random split.
|
|
2443
|
+
*/
|
|
2444
|
+
randomSplit(entries, rng) {
|
|
2445
|
+
const shuffled = this.fisherYatesShuffle([...entries], rng);
|
|
2446
|
+
const splitIndex = Math.round(shuffled.length * this.config.trainRatio);
|
|
2447
|
+
return {
|
|
2448
|
+
train: shuffled.slice(0, splitIndex),
|
|
2449
|
+
validation: shuffled.slice(splitIndex)
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* Fisher-Yates shuffle with seeded PRNG for deterministic ordering.
|
|
2454
|
+
*/
|
|
2455
|
+
fisherYatesShuffle(array, rng) {
|
|
2456
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
2457
|
+
const j = Math.floor(rng() * (i + 1));
|
|
2458
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
2459
|
+
}
|
|
2460
|
+
return array;
|
|
2461
|
+
}
|
|
2462
|
+
};
|
|
2463
|
+
function createTrainingMonkeyIntegration(config = {}) {
|
|
2464
|
+
return new TrainingMonkeyIntegration(config);
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
// src/training/constants.ts
|
|
2468
|
+
var TRAINING_CATEGORIES = [
|
|
2469
|
+
"vr-interaction",
|
|
2470
|
+
"multiplayer",
|
|
2471
|
+
"physics",
|
|
2472
|
+
"ui-spatial",
|
|
2473
|
+
"ai-agents",
|
|
2474
|
+
"procedural",
|
|
2475
|
+
"audio-spatial",
|
|
2476
|
+
"visual-effects",
|
|
2477
|
+
"game-mechanics"
|
|
2478
|
+
];
|
|
2479
|
+
var DIFFICULTY_LEVELS = ["beginner", "intermediate", "advanced", "production"];
|
|
2480
|
+
var QUALITY_THRESHOLDS = {
|
|
2481
|
+
Excellent: { min: 90, max: 100 },
|
|
2482
|
+
VeryGood: { min: 80, max: 89 },
|
|
2483
|
+
Acceptable: { min: 70, max: 79 }
|
|
2484
|
+
};
|
|
2485
|
+
var DEFAULT_GENERATOR_THRESHOLDS = {
|
|
2486
|
+
min_compression_ratio: 5,
|
|
2487
|
+
max_compression_ratio: 15,
|
|
2488
|
+
max_duplication_rate: 0.05,
|
|
2489
|
+
min_templates_per_difficulty: 10,
|
|
2490
|
+
min_quality_score: 0.7
|
|
2491
|
+
};
|
|
2492
|
+
var RULEFORGE_DOMAINS = [
|
|
2493
|
+
"ai_agents",
|
|
2494
|
+
"physics",
|
|
2495
|
+
"robotics",
|
|
2496
|
+
"audio",
|
|
2497
|
+
"rendering",
|
|
2498
|
+
"interaction",
|
|
2499
|
+
"multiplayer",
|
|
2500
|
+
"vr_ar"
|
|
2501
|
+
];
|
|
2502
|
+
function getQualityTier(score) {
|
|
2503
|
+
if (score >= QUALITY_THRESHOLDS.Excellent.min) return "Excellent";
|
|
2504
|
+
if (score >= QUALITY_THRESHOLDS.VeryGood.min) return "VeryGood";
|
|
2505
|
+
if (score >= QUALITY_THRESHOLDS.Acceptable.min) return "Acceptable";
|
|
2506
|
+
return "BelowAcceptable";
|
|
2507
|
+
}
|
|
2508
|
+
function isValidCategory(category) {
|
|
2509
|
+
return TRAINING_CATEGORIES.includes(category);
|
|
2510
|
+
}
|
|
2511
|
+
function isValidDifficulty(difficulty) {
|
|
2512
|
+
return DIFFICULTY_LEVELS.includes(difficulty);
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// src/training/schema.ts
|
|
2516
|
+
function validateTrainingExample(example) {
|
|
2517
|
+
const errors = [];
|
|
2518
|
+
const warnings = [];
|
|
2519
|
+
if (!example || typeof example !== "object") {
|
|
2520
|
+
return {
|
|
2521
|
+
valid: false,
|
|
2522
|
+
errors: [{ field: "root", message: "Example must be an object", severity: "error" }],
|
|
2523
|
+
warnings
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
const ex = example;
|
|
2527
|
+
if (typeof ex.instruction !== "string" || ex.instruction.length === 0) {
|
|
2528
|
+
errors.push({
|
|
2529
|
+
field: "instruction",
|
|
2530
|
+
message: "instruction must be a non-empty string",
|
|
2531
|
+
severity: "error"
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
if (typeof ex.input !== "string") {
|
|
2535
|
+
errors.push({ field: "input", message: "input must be a string", severity: "error" });
|
|
2536
|
+
}
|
|
2537
|
+
if (typeof ex.output !== "string" || ex.output.length === 0) {
|
|
2538
|
+
errors.push({
|
|
2539
|
+
field: "output",
|
|
2540
|
+
message: "output must be a non-empty string",
|
|
2541
|
+
severity: "error"
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
if (ex.metadata && typeof ex.metadata === "object") {
|
|
2545
|
+
const meta = ex.metadata;
|
|
2546
|
+
if (typeof meta.category !== "string") {
|
|
2547
|
+
errors.push({
|
|
2548
|
+
field: "metadata.category",
|
|
2549
|
+
message: "category must be a string",
|
|
2550
|
+
severity: "error"
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
if (typeof meta.difficulty !== "string") {
|
|
2554
|
+
errors.push({
|
|
2555
|
+
field: "metadata.difficulty",
|
|
2556
|
+
message: "difficulty must be a string",
|
|
2557
|
+
severity: "error"
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
if (!Array.isArray(meta.traits)) {
|
|
2561
|
+
warnings.push("metadata.traits should be an array");
|
|
2562
|
+
}
|
|
2563
|
+
} else {
|
|
2564
|
+
errors.push({ field: "metadata", message: "metadata must be an object", severity: "error" });
|
|
2565
|
+
}
|
|
2566
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
// src/training/trait-mappings.ts
|
|
2570
|
+
var TM_REGISTERED_TRAITS = [
|
|
2571
|
+
// V43 Tier 1 Core AI
|
|
2572
|
+
"llm_agent",
|
|
2573
|
+
"behavior_tree",
|
|
2574
|
+
"goal_oriented",
|
|
2575
|
+
"neural_link",
|
|
2576
|
+
"neural_forge",
|
|
2577
|
+
"spatial_awareness",
|
|
2578
|
+
"shared_world",
|
|
2579
|
+
"eye_tracked",
|
|
2580
|
+
"hand_tracking",
|
|
2581
|
+
"vision",
|
|
2582
|
+
// V43 Tier 2 visionOS
|
|
2583
|
+
"spatial_persona",
|
|
2584
|
+
"shareplay",
|
|
2585
|
+
"object_tracking",
|
|
2586
|
+
"scene_reconstruction",
|
|
2587
|
+
"realitykit_mesh",
|
|
2588
|
+
"room_mesh",
|
|
2589
|
+
"volumetric_window",
|
|
2590
|
+
"spatial_navigation",
|
|
2591
|
+
// V43 Tier 2 AI Generative
|
|
2592
|
+
"stable_diffusion",
|
|
2593
|
+
"controlnet",
|
|
2594
|
+
"ai_texture_gen",
|
|
2595
|
+
"diffusion_realtime",
|
|
2596
|
+
"ai_inpainting",
|
|
2597
|
+
"ai_upscaling",
|
|
2598
|
+
// V5.1 Hololand Exclusive
|
|
2599
|
+
"networked",
|
|
2600
|
+
"render_network",
|
|
2601
|
+
"openxr_hal",
|
|
2602
|
+
"hitl",
|
|
2603
|
+
"zora_coins",
|
|
2604
|
+
"neural_upscaling",
|
|
2605
|
+
"grabbable",
|
|
2606
|
+
"throwable",
|
|
2607
|
+
"pointable",
|
|
2608
|
+
"drawable",
|
|
2609
|
+
"attachable",
|
|
2610
|
+
"socket",
|
|
2611
|
+
"billboard",
|
|
2612
|
+
"ui_panel",
|
|
2613
|
+
"hud",
|
|
2614
|
+
"glowing",
|
|
2615
|
+
"physics",
|
|
2616
|
+
"persistent",
|
|
2617
|
+
"tool",
|
|
2618
|
+
// Physics Patterns
|
|
2619
|
+
"conversion_tracking",
|
|
2620
|
+
"impact_physics",
|
|
2621
|
+
"fragment_conversion",
|
|
2622
|
+
"damage_falloff",
|
|
2623
|
+
"cross_system_integration"
|
|
2624
|
+
];
|
|
2625
|
+
function validateTraitName(traitName, validTraits) {
|
|
2626
|
+
const normalized = traitName.toLowerCase().replace(/@/g, "").trim();
|
|
2627
|
+
const traitSet = validTraits instanceof Set ? validTraits : new Set(validTraits);
|
|
2628
|
+
if (traitSet.has(normalized)) return normalized;
|
|
2629
|
+
if (traitSet.has(traitName)) return traitName;
|
|
2630
|
+
return null;
|
|
2631
|
+
}
|
|
2632
|
+
function generateValidationReport(tmTraits, hsTraits, deprecatedTraits) {
|
|
2633
|
+
const hsSet = hsTraits instanceof Set ? hsTraits : new Set(hsTraits);
|
|
2634
|
+
const depSet = deprecatedTraits ?? /* @__PURE__ */ new Set();
|
|
2635
|
+
const details = [];
|
|
2636
|
+
for (const tm of tmTraits) {
|
|
2637
|
+
const normalized = tm.toLowerCase().replace(/@/g, "").trim();
|
|
2638
|
+
if (depSet.has(normalized)) {
|
|
2639
|
+
details.push({ tmName: tm, hsName: normalized, status: "deprecated" });
|
|
2640
|
+
} else if (hsSet.has(normalized)) {
|
|
2641
|
+
details.push({ tmName: tm, hsName: normalized, status: "matched" });
|
|
2642
|
+
} else {
|
|
2643
|
+
details.push({ tmName: tm, hsName: null, status: "unmatched" });
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
return {
|
|
2647
|
+
matched: details.filter((d) => d.status === "matched").length,
|
|
2648
|
+
unmatched: details.filter((d) => d.status === "unmatched").length,
|
|
2649
|
+
deprecated: details.filter((d) => d.status === "deprecated").length,
|
|
2650
|
+
total: details.length,
|
|
2651
|
+
details
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
export {
|
|
2655
|
+
DEFAULT_GENERATOR_THRESHOLDS,
|
|
2656
|
+
DEFAULT_INTEGRATION_CONFIG,
|
|
2657
|
+
DEFAULT_LR_SCHEDULER_CONFIG,
|
|
2658
|
+
DEFAULT_SCORING_CONFIG,
|
|
2659
|
+
DEFAULT_SOFTDEDUP_CONFIG,
|
|
2660
|
+
DEFAULT_TRAINING_PIPELINE_CONFIG,
|
|
2661
|
+
DIFFICULTY_LEVELS,
|
|
2662
|
+
GRPO_LR_SCHEDULER_CONFIG,
|
|
2663
|
+
LRScheduler,
|
|
2664
|
+
QUALITY_THRESHOLDS,
|
|
2665
|
+
QualityScoringPipeline,
|
|
2666
|
+
RULEFORGE_DOMAINS,
|
|
2667
|
+
SoftDedup,
|
|
2668
|
+
SparsityMonitor,
|
|
2669
|
+
SpatialTrainingDataGenerator,
|
|
2670
|
+
TM_REGISTERED_TRAITS,
|
|
2671
|
+
TRAINING_CATEGORIES,
|
|
2672
|
+
TrainingMonkeyIntegration,
|
|
2673
|
+
buildTrainingPipelineConfig,
|
|
2674
|
+
computeTotalSteps,
|
|
2675
|
+
createGRPOScheduler,
|
|
2676
|
+
createSFTScheduler,
|
|
2677
|
+
createSoftDedup,
|
|
2678
|
+
createSparsityMonitor,
|
|
2679
|
+
createSpatialTrainingDataGenerator,
|
|
2680
|
+
createTrainingMonkeyIntegration,
|
|
2681
|
+
generateValidationReport,
|
|
2682
|
+
getQualityTier,
|
|
2683
|
+
isValidCategory,
|
|
2684
|
+
isValidDifficulty,
|
|
2685
|
+
validateTrainingExample,
|
|
2686
|
+
validateTraitName
|
|
2687
|
+
};
|