@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.
Files changed (329) hide show
  1. package/ALL-test-results.json +1 -0
  2. package/CHANGELOG.md +8 -0
  3. package/LICENSE +21 -0
  4. package/ROADMAP.md +175 -0
  5. package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
  6. package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
  7. package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
  8. package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
  9. package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
  10. package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
  11. package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
  12. package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
  13. package/dist/agents/index.cjs +4790 -0
  14. package/dist/agents/index.d.cts +1788 -0
  15. package/dist/agents/index.d.ts +1788 -0
  16. package/dist/agents/index.js +4695 -0
  17. package/dist/ai/index.cjs +5347 -0
  18. package/dist/ai/index.d.cts +1753 -0
  19. package/dist/ai/index.d.ts +1753 -0
  20. package/dist/ai/index.js +5244 -0
  21. package/dist/behavior.cjs +449 -0
  22. package/dist/behavior.d.cts +130 -0
  23. package/dist/behavior.d.ts +130 -0
  24. package/dist/behavior.js +407 -0
  25. package/dist/economy/index.cjs +3659 -0
  26. package/dist/economy/index.d.cts +747 -0
  27. package/dist/economy/index.d.ts +747 -0
  28. package/dist/economy/index.js +3617 -0
  29. package/dist/implementations-D9T3un9D.d.cts +236 -0
  30. package/dist/implementations-D9T3un9D.d.ts +236 -0
  31. package/dist/index.cjs +24550 -0
  32. package/dist/index.d.cts +1729 -0
  33. package/dist/index.d.ts +1729 -0
  34. package/dist/index.js +24277 -0
  35. package/dist/learning/index.cjs +219 -0
  36. package/dist/learning/index.d.cts +104 -0
  37. package/dist/learning/index.d.ts +104 -0
  38. package/dist/learning/index.js +189 -0
  39. package/dist/negotiation/index.cjs +970 -0
  40. package/dist/negotiation/index.d.cts +610 -0
  41. package/dist/negotiation/index.d.ts +610 -0
  42. package/dist/negotiation/index.js +931 -0
  43. package/dist/skills/index.cjs +1118 -0
  44. package/dist/skills/index.d.cts +289 -0
  45. package/dist/skills/index.d.ts +289 -0
  46. package/dist/skills/index.js +1079 -0
  47. package/dist/swarm/index.cjs +5268 -0
  48. package/dist/swarm/index.d.cts +2433 -0
  49. package/dist/swarm/index.d.ts +2433 -0
  50. package/dist/swarm/index.js +5221 -0
  51. package/dist/training/index.cjs +2745 -0
  52. package/dist/training/index.d.cts +1734 -0
  53. package/dist/training/index.d.ts +1734 -0
  54. package/dist/training/index.js +2687 -0
  55. package/extract-failures.js +10 -0
  56. package/package.json +82 -0
  57. package/src/__tests__/bounty-marketplace.test.ts +374 -0
  58. package/src/__tests__/delegation.test.ts +144 -0
  59. package/src/__tests__/distributed-claimer.test.ts +147 -0
  60. package/src/__tests__/done-log-audit.test.ts +342 -0
  61. package/src/__tests__/framework.test.ts +865 -0
  62. package/src/__tests__/goal-synthesizer.test.ts +236 -0
  63. package/src/__tests__/presence.test.ts +223 -0
  64. package/src/__tests__/protocol-agent.test.ts +254 -0
  65. package/src/__tests__/revenue-splitter.test.ts +114 -0
  66. package/src/__tests__/scenario-driven-todo.test.ts +197 -0
  67. package/src/__tests__/self-improve.test.ts +349 -0
  68. package/src/__tests__/service-lifecycle.test.ts +237 -0
  69. package/src/__tests__/skill-router.test.ts +121 -0
  70. package/src/agents/AgentManifest.ts +493 -0
  71. package/src/agents/AgentRegistry.ts +475 -0
  72. package/src/agents/AgentTypes.ts +585 -0
  73. package/src/agents/AgentWalletRegistry.ts +83 -0
  74. package/src/agents/AuthenticatedCRDT.ts +388 -0
  75. package/src/agents/CapabilityMatcher.ts +453 -0
  76. package/src/agents/CrossRealityHandoff.ts +305 -0
  77. package/src/agents/CulturalMemory.ts +454 -0
  78. package/src/agents/FederatedRegistryAdapter.ts +429 -0
  79. package/src/agents/NormEngine.ts +450 -0
  80. package/src/agents/OrchestratorAgent.ts +414 -0
  81. package/src/agents/SkillWorkflowEngine.ts +472 -0
  82. package/src/agents/TaskDelegationService.ts +551 -0
  83. package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
  84. package/src/agents/__tests__/AgentManifest.test.ts +182 -0
  85. package/src/agents/__tests__/AgentModule.test.ts +864 -0
  86. package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
  87. package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
  88. package/src/agents/__tests__/AgentTypes.test.ts +534 -0
  89. package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
  90. package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
  91. package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
  92. package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
  93. package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
  94. package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
  95. package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
  96. package/src/agents/__tests__/NormEngine.test.ts +276 -0
  97. package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
  98. package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
  99. package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
  100. package/src/agents/index.ts +107 -0
  101. package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
  102. package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
  103. package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
  104. package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
  105. package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
  106. package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
  107. package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
  108. package/src/agents/spatial-comms/index.ts +66 -0
  109. package/src/ai/AIAdapter.ts +313 -0
  110. package/src/ai/AICopilot.ts +331 -0
  111. package/src/ai/AIOutputValidator.ts +203 -0
  112. package/src/ai/BTNodes.ts +239 -0
  113. package/src/ai/BehaviorSelector.ts +135 -0
  114. package/src/ai/BehaviorTree.ts +153 -0
  115. package/src/ai/Blackboard.ts +165 -0
  116. package/src/ai/GenerationAnalytics.ts +461 -0
  117. package/src/ai/GenerationCache.ts +265 -0
  118. package/src/ai/GoalPlanner.ts +165 -0
  119. package/src/ai/HoloScriptGenerator.ts +580 -0
  120. package/src/ai/InfluenceMap.ts +180 -0
  121. package/src/ai/NavMesh.ts +168 -0
  122. package/src/ai/PerceptionSystem.ts +178 -0
  123. package/src/ai/PromptTemplates.ts +453 -0
  124. package/src/ai/SemanticSearchService.ts +80 -0
  125. package/src/ai/StateMachine.ts +196 -0
  126. package/src/ai/SteeringBehavior.ts +150 -0
  127. package/src/ai/SteeringBehaviors.ts +244 -0
  128. package/src/ai/TrainingDataGenerator.ts +1082 -0
  129. package/src/ai/UtilityAI.ts +145 -0
  130. package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
  131. package/src/ai/__tests__/AIAdapter.test.ts +109 -0
  132. package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
  133. package/src/ai/__tests__/AICopilot.test.ts +178 -0
  134. package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
  135. package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
  136. package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
  137. package/src/ai/__tests__/BTNodes.test.ts +263 -0
  138. package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
  139. package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
  140. package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
  141. package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
  142. package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
  143. package/src/ai/__tests__/Blackboard.test.ts +183 -0
  144. package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
  145. package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
  146. package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
  147. package/src/ai/__tests__/GenerationCache.test.ts +171 -0
  148. package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
  149. package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
  150. package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
  151. package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
  152. package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
  153. package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
  154. package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
  155. package/src/ai/__tests__/NavMesh.test.ts +159 -0
  156. package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
  157. package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
  158. package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
  159. package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
  160. package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
  161. package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
  162. package/src/ai/__tests__/StateMachine.test.ts +163 -0
  163. package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
  164. package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
  165. package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
  166. package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
  167. package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
  168. package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
  169. package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
  170. package/src/ai/__tests__/UtilityAI.test.ts +155 -0
  171. package/src/ai/__tests__/adapters.prod.test.ts +263 -0
  172. package/src/ai/__tests__/adapters.test.ts +320 -0
  173. package/src/ai/adapters.ts +1585 -0
  174. package/src/ai/index.ts +130 -0
  175. package/src/behavior/BehaviorPresets.ts +140 -0
  176. package/src/behavior/BehaviorTree.ts +236 -0
  177. package/src/behavior/StateMachine.ts +176 -0
  178. package/src/behavior/StateTrait.ts +67 -0
  179. package/src/behavior/index.ts +8 -0
  180. package/src/behavior.ts +8 -0
  181. package/src/board/audit.ts +284 -0
  182. package/src/board/board-ops.ts +336 -0
  183. package/src/board/board-types.ts +302 -0
  184. package/src/board/index.ts +69 -0
  185. package/src/define-agent.ts +46 -0
  186. package/src/define-team.ts +33 -0
  187. package/src/delegation.ts +265 -0
  188. package/src/distributed-claimer.ts +228 -0
  189. package/src/economy/AgentBudgetEnforcer.ts +464 -0
  190. package/src/economy/BountyManager.ts +185 -0
  191. package/src/economy/CreatorRevenueAggregator.ts +460 -0
  192. package/src/economy/InvisibleWallet.ts +82 -0
  193. package/src/economy/KnowledgeMarketplace.ts +193 -0
  194. package/src/economy/PaymentWebhookService.ts +512 -0
  195. package/src/economy/RevenueSplitter.ts +156 -0
  196. package/src/economy/SubscriptionManager.ts +546 -0
  197. package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
  198. package/src/economy/UsageMeter.ts +440 -0
  199. package/src/economy/_core-stubs.ts +219 -0
  200. package/src/economy/index.ts +100 -0
  201. package/src/economy/x402-facilitator.ts +1978 -0
  202. package/src/index.ts +348 -0
  203. package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
  204. package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
  205. package/src/knowledge/brain.ts +167 -0
  206. package/src/knowledge/consolidation.ts +581 -0
  207. package/src/knowledge/knowledge-consolidator.ts +510 -0
  208. package/src/knowledge/knowledge-store.ts +616 -0
  209. package/src/learning/MemoryConsolidator.ts +102 -0
  210. package/src/learning/MemoryScorer.ts +69 -0
  211. package/src/learning/ProceduralCompiler.ts +45 -0
  212. package/src/learning/SemanticClusterer.ts +66 -0
  213. package/src/learning/index.ts +8 -0
  214. package/src/llm/llm-adapter.ts +159 -0
  215. package/src/mesh/index.ts +309 -0
  216. package/src/negotiation/NegotiationProtocol.ts +694 -0
  217. package/src/negotiation/NegotiationTypes.ts +473 -0
  218. package/src/negotiation/VotingMechanisms.ts +691 -0
  219. package/src/negotiation/index.ts +49 -0
  220. package/src/protocol/goal-synthesizer.ts +317 -0
  221. package/src/protocol/implementations.ts +474 -0
  222. package/src/protocol/micro-phase-decomposer.ts +299 -0
  223. package/src/protocol/micro-step-decomposer.test.ts +306 -0
  224. package/src/protocol-agent.test.ts +353 -0
  225. package/src/protocol-agent.ts +670 -0
  226. package/src/self-improve/absorb-scanner.ts +252 -0
  227. package/src/self-improve/evolution-engine.ts +149 -0
  228. package/src/self-improve/framework-absorber.ts +214 -0
  229. package/src/self-improve/index.ts +50 -0
  230. package/src/self-improve/prompt-optimizer.ts +212 -0
  231. package/src/self-improve/test-generator.ts +175 -0
  232. package/src/skill-router.ts +186 -0
  233. package/src/skills/index.ts +5 -0
  234. package/src/skills/skill-md-bridge.ts +1699 -0
  235. package/src/swarm/ACOEngine.ts +261 -0
  236. package/src/swarm/CollectiveIntelligence.ts +383 -0
  237. package/src/swarm/ContributionSynthesizer.ts +481 -0
  238. package/src/swarm/LeaderElection.ts +393 -0
  239. package/src/swarm/PSOEngine.ts +206 -0
  240. package/src/swarm/QuorumPolicy.ts +173 -0
  241. package/src/swarm/SwarmCoordinator.ts +335 -0
  242. package/src/swarm/SwarmManager.ts +442 -0
  243. package/src/swarm/SwarmMembership.ts +456 -0
  244. package/src/swarm/VotingRound.ts +255 -0
  245. package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
  246. package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
  247. package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
  248. package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
  249. package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
  250. package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
  251. package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
  252. package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
  253. package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
  254. package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
  255. package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
  256. package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
  257. package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
  258. package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
  259. package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
  260. package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
  261. package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
  262. package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
  263. package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
  264. package/src/swarm/__tests__/VotingRound.test.ts +174 -0
  265. package/src/swarm/analytics/SwarmInspector.ts +476 -0
  266. package/src/swarm/analytics/SwarmMetrics.ts +449 -0
  267. package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
  268. package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
  269. package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
  270. package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
  271. package/src/swarm/analytics/index.ts +7 -0
  272. package/src/swarm/index.ts +69 -0
  273. package/src/swarm/messaging/BroadcastChannel.ts +509 -0
  274. package/src/swarm/messaging/GossipProtocol.ts +565 -0
  275. package/src/swarm/messaging/SwarmEventBus.ts +443 -0
  276. package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
  277. package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
  278. package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
  279. package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
  280. package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
  281. package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
  282. package/src/swarm/messaging/index.ts +8 -0
  283. package/src/swarm/spatial/FlockingBehavior.ts +462 -0
  284. package/src/swarm/spatial/FormationController.ts +500 -0
  285. package/src/swarm/spatial/Vector3.ts +170 -0
  286. package/src/swarm/spatial/ZoneClaiming.ts +509 -0
  287. package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
  288. package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
  289. package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
  290. package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
  291. package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
  292. package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
  293. package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
  294. package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
  295. package/src/swarm/spatial/index.ts +28 -0
  296. package/src/team.ts +1245 -0
  297. package/src/training/LRScheduler.ts +377 -0
  298. package/src/training/QualityScoringPipeline.ts +139 -0
  299. package/src/training/SoftDedup.ts +461 -0
  300. package/src/training/SparsityMonitor.ts +685 -0
  301. package/src/training/SparsityMonitorTypes.ts +209 -0
  302. package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
  303. package/src/training/SpatialTrainingDataTypes.ts +216 -0
  304. package/src/training/TrainingPipelineConfig.ts +215 -0
  305. package/src/training/constants.ts +94 -0
  306. package/src/training/index.ts +138 -0
  307. package/src/training/schema.ts +147 -0
  308. package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
  309. package/src/training/scripts/generate-spatial-dataset.ts +521 -0
  310. package/src/training/training/data/novel-use-cases.jsonl +153 -0
  311. package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
  312. package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
  313. package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
  314. package/src/training/trainingmonkey/index.ts +26 -0
  315. package/src/training/trait-mappings.ts +157 -0
  316. package/src/types/core-stubs.d.ts +113 -0
  317. package/src/types.ts +304 -0
  318. package/test-output.txt +0 -0
  319. package/test-result.json +1 -0
  320. package/tsc-errors.txt +4 -0
  321. package/tsc_output.txt +0 -0
  322. package/tsconfig.json +14 -0
  323. package/tsup-learning-esm.config.ts +12 -0
  324. package/tsup.config.ts +21 -0
  325. package/typescript-errors-2.txt +0 -0
  326. package/typescript-errors.txt +22 -0
  327. package/vitest-log-utf8.txt +268 -0
  328. package/vitest-log.txt +0 -0
  329. package/vitest.config.ts +8 -0
@@ -0,0 +1,635 @@
1
+ /**
2
+ * UnifiedBudgetOptimizer — Bridges Economy and Rendering Budgets
3
+ *
4
+ * Oracle Cycle 6 identified that AgentBudgetEnforcer (1D USDC, hard stop)
5
+ * and ResourceBudgetAnalyzer (10D resources, graceful LOD degradation)
6
+ * solve the same optimization problem with zero shared abstractions.
7
+ *
8
+ * This module provides:
9
+ * 1. Trait Utility Function — value per resource cost (enables optimization)
10
+ * 2. Equimarginal LOD Allocation — drop by lowest value/cost ratio, not greedy
11
+ * 3. Resource Cost Floor Pricing — prevents economic denial-of-rendering
12
+ * 4. Unified budget query — single view of both economy + rendering pressure
13
+ *
14
+ * @version 1.0.0
15
+ */
16
+
17
+ import {
18
+ ResourceCategory,
19
+ TRAIT_RESOURCE_COSTS,
20
+ PLATFORM_BUDGETS,
21
+ type ResourceUsageNode,
22
+ } from './_core-stubs';
23
+
24
+ // =============================================================================
25
+ // TYPES
26
+ // =============================================================================
27
+
28
+ /**
29
+ * Trait utility entry — encodes how much value a trait provides per unit cost.
30
+ * Higher utility = keep this trait when budgets are tight.
31
+ */
32
+ export interface TraitUtility {
33
+ /** Trait name (e.g., '@particle', '@gaussian') */
34
+ trait: string;
35
+ /** Base utility score (0-100, higher = more valuable to the experience) */
36
+ baseUtility: number;
37
+ /** Category for grouping (visual, physics, audio, network, ai) */
38
+ category: TraitCategory;
39
+ /** Whether this trait is required (cannot be dropped by LOD) */
40
+ required: boolean;
41
+ /** LOD level at which this trait can start being dropped (0 = never) */
42
+ minLODLevel: number;
43
+ }
44
+
45
+ export type TraitCategory = 'visual' | 'physics' | 'audio' | 'network' | 'ai' | 'interaction';
46
+
47
+ /**
48
+ * Allocation decision for a single trait.
49
+ */
50
+ export interface TraitAllocation {
51
+ /** Trait name */
52
+ trait: string;
53
+ /** Whether to include this trait at current budget level */
54
+ included: boolean;
55
+ /** LOD level applied (0 = full detail) */
56
+ lodLevel: number;
57
+ /** Resource cost at current LOD */
58
+ resourceCost: Partial<Record<ResourceCategory, number>>;
59
+ /** Economic cost (USDC base units, 0 if no marketplace price) */
60
+ economicCost: number;
61
+ /** Computed utility score */
62
+ utility: number;
63
+ /** Value/cost ratio used for allocation decisions */
64
+ valueCostRatio: number;
65
+ /** Reason for exclusion if not included */
66
+ excludeReason?: string;
67
+ }
68
+
69
+ /**
70
+ * Resource cost floor for marketplace pricing.
71
+ * Prevents a 1-credit trait from consuming 10K gaussians.
72
+ */
73
+ export interface ResourceCostFloor {
74
+ /** Minimum price per unit of resource consumed */
75
+ perGaussian: number;
76
+ /** Minimum price per draw call */
77
+ perDrawCall: number;
78
+ /** Minimum price per MB memory */
79
+ perMemoryMB: number;
80
+ /** Minimum price per particle */
81
+ perParticle: number;
82
+ /** Minimum price per physics body */
83
+ perPhysicsBody: number;
84
+ /** Base platform fee (USDC base units) */
85
+ baseFee: number;
86
+ }
87
+
88
+ /**
89
+ * Unified budget state — single view across economy + rendering.
90
+ */
91
+ export interface UnifiedBudgetState {
92
+ /** Agent ID */
93
+ agentId: string;
94
+ /** Economic pressure (0-1, where 1 = budget exhausted) */
95
+ economicPressure: number;
96
+ /** Resource pressure per category (0-1) */
97
+ resourcePressure: Partial<Record<ResourceCategory, number>>;
98
+ /** Overall pressure (max of economic and worst resource) */
99
+ overallPressure: number;
100
+ /** Suggested LOD level based on combined pressure */
101
+ suggestedLOD: number;
102
+ /** Whether any hard limit is breached */
103
+ hardLimitBreached: boolean;
104
+ /** Traits that should be shed to relieve pressure */
105
+ shedCandidates: TraitAllocation[];
106
+ }
107
+
108
+ /**
109
+ * Configuration for the unified optimizer.
110
+ */
111
+ export interface UnifiedOptimizerConfig {
112
+ /** Target platform for resource limits */
113
+ platform: string;
114
+ /** Resource cost floor for marketplace pricing */
115
+ costFloor: ResourceCostFloor;
116
+ /** Custom trait utilities (override defaults) */
117
+ traitUtilities?: Map<string, TraitUtility>;
118
+ /** LOD scaling factors — how much resource cost reduces per LOD level */
119
+ lodScaling?: number[];
120
+ /** Economic budget limit (USDC base units, 0 = no limit) */
121
+ economicBudget?: number;
122
+ /** Economic spend so far (USDC base units) */
123
+ economicSpent?: number;
124
+ }
125
+
126
+ // =============================================================================
127
+ // DEFAULT TRAIT UTILITIES
128
+ // =============================================================================
129
+
130
+ /**
131
+ * Default utility scores for common traits.
132
+ * These encode "how much does this trait contribute to the experience?"
133
+ *
134
+ * Scoring guide:
135
+ * - 90-100: Core experience (mesh, material, physics) — never drop
136
+ * - 70-89: Important visual/audio quality (lighting, particles)
137
+ * - 50-69: Enhancement (volumetric, VFX, spatial audio)
138
+ * - 30-49: Nice-to-have (reflections, subsurface, ambisonics)
139
+ * - 10-29: Luxury (ray tracing, global illumination, neural rendering)
140
+ */
141
+ export const DEFAULT_TRAIT_UTILITIES: Record<string, Omit<TraitUtility, 'trait'>> = {
142
+ // ── Core (required, high utility) ──
143
+ '@mesh': { baseUtility: 100, category: 'visual', required: true, minLODLevel: 0 },
144
+ '@material': { baseUtility: 95, category: 'visual', required: true, minLODLevel: 0 },
145
+ '@physics': { baseUtility: 90, category: 'physics', required: true, minLODLevel: 0 },
146
+ '@rigidbody': { baseUtility: 90, category: 'physics', required: true, minLODLevel: 0 },
147
+ '@collider': { baseUtility: 88, category: 'physics', required: true, minLODLevel: 0 },
148
+ '@rendering': { baseUtility: 95, category: 'visual', required: true, minLODLevel: 0 },
149
+ '@character': { baseUtility: 92, category: 'visual', required: true, minLODLevel: 0 },
150
+ '@networked': { baseUtility: 85, category: 'network', required: true, minLODLevel: 0 },
151
+ // C6 Layer 2 re-score: @agent was hand-assigned at 85 (required). Data-derived
152
+ // analysis shows agents are a luxury in most rendering scenes — only ~35% of
153
+ // compositions use @agent, and it consumes 5MB memory. Downgraded to non-required
154
+ // enhancement. Scenes that genuinely need agents can override via custom utilities.
155
+ '@agent': { baseUtility: 35, category: 'ai', required: false, minLODLevel: 2 },
156
+
157
+ // ── Important quality (droppable at high LOD) ──
158
+ '@light': { baseUtility: 80, category: 'visual', required: false, minLODLevel: 3 },
159
+ '@lighting': { baseUtility: 78, category: 'visual', required: false, minLODLevel: 3 },
160
+ '@shader': { baseUtility: 75, category: 'visual', required: false, minLODLevel: 2 },
161
+ '@advanced_pbr': { baseUtility: 72, category: 'visual', required: false, minLODLevel: 2 },
162
+ '@particle': { baseUtility: 70, category: 'visual', required: false, minLODLevel: 2 },
163
+ '@audio': { baseUtility: 75, category: 'audio', required: false, minLODLevel: 3 },
164
+ '@spatial_audio': { baseUtility: 72, category: 'audio', required: false, minLODLevel: 2 },
165
+ '@animation': { baseUtility: 78, category: 'visual', required: false, minLODLevel: 3 },
166
+ '@skeleton': { baseUtility: 76, category: 'visual', required: false, minLODLevel: 3 },
167
+
168
+ // ── Enhancement (drop at medium LOD) ──
169
+ '@vfx': { baseUtility: 60, category: 'visual', required: false, minLODLevel: 2 },
170
+ '@volumetric': { baseUtility: 55, category: 'visual', required: false, minLODLevel: 2 },
171
+ '@advanced_lighting': { baseUtility: 58, category: 'visual', required: false, minLODLevel: 2 },
172
+ '@advanced_texturing': { baseUtility: 56, category: 'visual', required: false, minLODLevel: 2 },
173
+ '@screen_space_effects': { baseUtility: 52, category: 'visual', required: false, minLODLevel: 2 },
174
+ '@environmental_audio': { baseUtility: 55, category: 'audio', required: false, minLODLevel: 2 },
175
+ '@npc': { baseUtility: 65, category: 'ai', required: false, minLODLevel: 3 },
176
+ '@npc_ai': { baseUtility: 62, category: 'ai', required: false, minLODLevel: 3 },
177
+ '@fluid_simulation': { baseUtility: 50, category: 'physics', required: false, minLODLevel: 1 },
178
+ '@advanced_cloth': { baseUtility: 48, category: 'physics', required: false, minLODLevel: 1 },
179
+ '@joint': { baseUtility: 65, category: 'physics', required: false, minLODLevel: 3 },
180
+
181
+ // ── Nice-to-have (drop early) ──
182
+ '@subsurface_scattering': {
183
+ baseUtility: 38,
184
+ category: 'visual',
185
+ required: false,
186
+ minLODLevel: 1,
187
+ },
188
+ '@ambisonics': { baseUtility: 35, category: 'audio', required: false, minLODLevel: 1 },
189
+ '@voronoi_fracture': { baseUtility: 40, category: 'physics', required: false, minLODLevel: 1 },
190
+ '@granular_material': { baseUtility: 38, category: 'physics', required: false, minLODLevel: 1 },
191
+ '@lip_sync': { baseUtility: 42, category: 'visual', required: false, minLODLevel: 1 },
192
+
193
+ // ── Luxury (drop first) ──
194
+ '@ray_tracing': { baseUtility: 20, category: 'visual', required: false, minLODLevel: 1 },
195
+ '@global_illumination': { baseUtility: 22, category: 'visual', required: false, minLODLevel: 1 },
196
+ '@nerf': { baseUtility: 18, category: 'visual', required: false, minLODLevel: 1 },
197
+ '@multiview_gaussian_renderer': {
198
+ baseUtility: 25,
199
+ category: 'visual',
200
+ required: false,
201
+ minLODLevel: 1,
202
+ },
203
+ // C6 Layer 2 re-score: @gaussian/@gaussian_splat were hand-assigned at 65.
204
+ // Data-derived analysis: 100K gaussians on Quest 3 = 55% of total GPU budget,
205
+ // making the value/cost ratio much lower than hand-scores suggested.
206
+ // Downgraded by 45 points to reflect actual GPU cost relative to utility.
207
+ '@gaussian': { baseUtility: 20, category: 'visual', required: false, minLODLevel: 1 },
208
+ '@gaussian_splat': { baseUtility: 20, category: 'visual', required: false, minLODLevel: 1 },
209
+ '@stable_diffusion': { baseUtility: 15, category: 'ai', required: false, minLODLevel: 1 },
210
+ '@diffusion_realtime': { baseUtility: 18, category: 'ai', required: false, minLODLevel: 1 },
211
+ '@local_llm': { baseUtility: 30, category: 'ai', required: false, minLODLevel: 1 },
212
+ };
213
+
214
+ /**
215
+ * Default resource cost floor pricing.
216
+ * Prevents marketplace traits from being priced below their GPU cost.
217
+ * Prices in USDC base units (6 decimals, so 1_000_000 = $1.00).
218
+ */
219
+ /**
220
+ * Platform-specific LOD scaling curves.
221
+ *
222
+ * C6 Layer 2 finding: platform exchange rates are wildly non-uniform (25x
223
+ * price difference for a single @particle between Mobile AR and Desktop VR).
224
+ * A uniform LOD curve means constrained platforms over-degrade while powerful
225
+ * platforms under-utilize. Each platform gets its own perceptual curve.
226
+ *
227
+ * Constrained platforms (mobile_ar, quest3) degrade faster at LOD 1-2.
228
+ * Powerful platforms (desktop-vr) degrade slowly, preserving quality.
229
+ */
230
+ export const PLATFORM_LOD_SCALING: Record<string, number[]> = {
231
+ 'quest3': [1.0, 0.6, 0.3, 0.12, 0.04], // Aggressive — tight budget
232
+ 'mobile-ar': [1.0, 0.5, 0.2, 0.08, 0.02], // Most aggressive — tightest budget
233
+ 'webgpu': [1.0, 0.7, 0.4, 0.18, 0.06], // Moderate — matches default
234
+ 'desktop-vr': [1.0, 0.85, 0.6, 0.3, 0.1], // Gentle — plenty of headroom
235
+ 'visionos': [1.0, 0.8, 0.5, 0.25, 0.08], // Moderate-gentle — good hardware
236
+ };
237
+
238
+ /**
239
+ * Default resource cost floor pricing.
240
+ * Prevents marketplace traits from being priced below their GPU cost.
241
+ * Prices in USDC base units (6 decimals, so 1_000_000 = $1.00).
242
+ */
243
+ export const DEFAULT_COST_FLOOR: ResourceCostFloor = {
244
+ perGaussian: 0.01, // 10K gaussians = $0.10 minimum
245
+ perDrawCall: 1000, // $0.001 per draw call
246
+ perMemoryMB: 5000, // $0.005 per MB
247
+ perParticle: 0.1, // 1000 particles = $0.10 minimum
248
+ perPhysicsBody: 500, // $0.0005 per body
249
+ baseFee: 100_000, // $0.10 base fee on all marketplace traits
250
+ };
251
+
252
+ /**
253
+ * Default LOD scaling: how much resource usage decreases per LOD level.
254
+ *
255
+ * Previous geometric scaling [1.0, 0.5, 0.25, 0.1, 0.05] over-degrades
256
+ * the visible mid-range (LOD 1-2). Human perception follows Weber-Fechner
257
+ * (logarithmic), meaning a 50% reduction at LOD 1 is far more noticeable
258
+ * than a 50% reduction at LOD 3.
259
+ *
260
+ * Perceptual curve: each step reduces quality by an amount proportional
261
+ * to the *perceived* difference, not the geometric ratio.
262
+ * LOD 0 = 1.0 (full), LOD 1 = 0.7, LOD 2 = 0.4, LOD 3 = 0.18, LOD 4 = 0.06
263
+ *
264
+ * This preserves more detail in the visually sensitive mid-range while
265
+ * still achieving aggressive reduction at far distances.
266
+ */
267
+ export const DEFAULT_LOD_SCALING = [1.0, 0.7, 0.4, 0.18, 0.06];
268
+
269
+ // =============================================================================
270
+ // UNIFIED BUDGET OPTIMIZER
271
+ // =============================================================================
272
+
273
+ export class UnifiedBudgetOptimizer {
274
+ private platform: string;
275
+ private costFloor: ResourceCostFloor;
276
+ private traitUtilities: Map<string, TraitUtility>;
277
+ private lodScaling: number[];
278
+ private economicBudget: number;
279
+ private economicSpent: number;
280
+
281
+ constructor(config: UnifiedOptimizerConfig) {
282
+ this.platform = config.platform;
283
+ this.costFloor = config.costFloor;
284
+ // Use platform-specific LOD scaling if available, fall back to default perceptual curve
285
+ this.lodScaling = config.lodScaling
286
+ ?? PLATFORM_LOD_SCALING[config.platform]
287
+ ?? DEFAULT_LOD_SCALING;
288
+ this.economicBudget = config.economicBudget ?? 0;
289
+ this.economicSpent = config.economicSpent ?? 0;
290
+
291
+ // Build utility map from defaults + overrides
292
+ this.traitUtilities = new Map();
293
+ for (const [trait, util] of Object.entries(DEFAULT_TRAIT_UTILITIES)) {
294
+ this.traitUtilities.set(trait, { trait, ...util });
295
+ }
296
+ if (config.traitUtilities) {
297
+ for (const [trait, util] of config.traitUtilities) {
298
+ this.traitUtilities.set(trait, util);
299
+ }
300
+ }
301
+ }
302
+
303
+ // ===========================================================================
304
+ // CORE: Equimarginal Allocation
305
+ // ===========================================================================
306
+
307
+ /**
308
+ * Allocate traits across a resource budget using the equimarginal principle.
309
+ * Instead of greedily dropping the deepest LOD first, this sorts by value/cost
310
+ * ratio and drops traits with the lowest marginal utility first.
311
+ *
312
+ * @param nodes - Resource usage nodes (traits + counts)
313
+ * @param maxLOD - Maximum LOD level to consider (default: 4)
314
+ * @returns Allocation decisions for each trait
315
+ */
316
+ allocate(nodes: ResourceUsageNode[], maxLOD: number = 4): TraitAllocation[] {
317
+ const limits = PLATFORM_BUDGETS[this.platform];
318
+ if (!limits) {
319
+ // No limits = include everything at LOD 0
320
+ return this.flattenToAllocations(nodes, 0);
321
+ }
322
+
323
+ // Start with everything at LOD 0
324
+ let allocations = this.flattenToAllocations(nodes, 0);
325
+
326
+ // Check if we're within budget
327
+ let pressure = this.computeResourcePressure(allocations, limits);
328
+ if (pressure.maxPressure <= 1.0) {
329
+ return allocations; // Everything fits
330
+ }
331
+
332
+ // Equimarginal: sort by value/cost ratio ascending, shed lowest-value first
333
+ for (let lod = 1; lod <= maxLOD && pressure.maxPressure > 1.0; lod++) {
334
+ // Get droppable traits at this LOD level, sorted by value/cost ratio
335
+ const candidates = allocations
336
+ .filter((a) => a.included && !this.isRequired(a.trait) && this.canDropAtLOD(a.trait, lod))
337
+ .sort((a, b) => a.valueCostRatio - b.valueCostRatio);
338
+
339
+ for (const candidate of candidates) {
340
+ if (pressure.maxPressure <= 1.0) break;
341
+
342
+ // Degrade this trait to current LOD level
343
+ const idx = allocations.findIndex((a) => a.trait === candidate.trait);
344
+ if (idx >= 0) {
345
+ allocations[idx] = this.buildAllocation(candidate.trait, lod, 1);
346
+ }
347
+
348
+ pressure = this.computeResourcePressure(allocations, limits);
349
+ }
350
+ }
351
+
352
+ // If still over budget, start excluding non-required traits (lowest ratio first)
353
+ if (pressure.maxPressure > 1.0) {
354
+ const excludeable = allocations
355
+ .filter((a) => a.included && !this.isRequired(a.trait))
356
+ .sort((a, b) => a.valueCostRatio - b.valueCostRatio);
357
+
358
+ for (const candidate of excludeable) {
359
+ if (pressure.maxPressure <= 1.0) break;
360
+
361
+ const idx = allocations.findIndex((a) => a.trait === candidate.trait);
362
+ if (idx >= 0) {
363
+ allocations[idx] = {
364
+ ...allocations[idx],
365
+ included: false,
366
+ excludeReason: `Excluded to fit ${this.platform} budget (value/cost ratio: ${candidate.valueCostRatio.toFixed(2)})`,
367
+ };
368
+ }
369
+
370
+ pressure = this.computeResourcePressure(allocations, limits);
371
+ }
372
+ }
373
+
374
+ return allocations;
375
+ }
376
+
377
+ // ===========================================================================
378
+ // RESOURCE COST FLOOR PRICING
379
+ // ===========================================================================
380
+
381
+ /**
382
+ * Calculate the minimum marketplace price for a trait based on its resource cost.
383
+ * Prevents economic denial-of-rendering attacks where a cheap marketplace trait
384
+ * consumes massive GPU resources.
385
+ *
386
+ * @param traitName - The trait to price
387
+ * @param instanceCount - How many instances (default: 1)
388
+ * @returns Minimum price in USDC base units (6 decimals)
389
+ */
390
+ calculateCostFloor(traitName: string, instanceCount: number = 1): number {
391
+ const normalized = traitName.startsWith('@') ? traitName : `@${traitName}`;
392
+ const costs = TRAIT_RESOURCE_COSTS[normalized];
393
+ if (!costs) return this.costFloor.baseFee;
394
+
395
+ let floor = this.costFloor.baseFee;
396
+
397
+ if (costs.gaussians) floor += costs.gaussians * instanceCount * this.costFloor.perGaussian;
398
+ if (costs.gpuDrawCalls)
399
+ floor += costs.gpuDrawCalls * instanceCount * this.costFloor.perDrawCall;
400
+ if (costs.memoryMB) floor += costs.memoryMB * instanceCount * this.costFloor.perMemoryMB;
401
+ if (costs.particles) floor += costs.particles * instanceCount * this.costFloor.perParticle;
402
+ if (costs.physicsBodies)
403
+ floor += costs.physicsBodies * instanceCount * this.costFloor.perPhysicsBody;
404
+
405
+ return Math.ceil(floor);
406
+ }
407
+
408
+ /**
409
+ * Validate that a marketplace listing price meets the resource cost floor.
410
+ *
411
+ * @param traitName - The trait being listed
412
+ * @param listPrice - Proposed listing price (USDC base units)
413
+ * @param instanceCount - Expected instance count
414
+ * @returns Validation result
415
+ */
416
+ validateMarketplacePrice(
417
+ traitName: string,
418
+ listPrice: number,
419
+ instanceCount: number = 1
420
+ ): { valid: boolean; floor: number; deficit: number; message: string } {
421
+ const floor = this.calculateCostFloor(traitName, instanceCount);
422
+ const deficit = Math.max(0, floor - listPrice);
423
+
424
+ if (listPrice >= floor) {
425
+ return {
426
+ valid: true,
427
+ floor,
428
+ deficit: 0,
429
+ message: `Price ${listPrice} meets resource cost floor of ${floor}`,
430
+ };
431
+ }
432
+
433
+ return {
434
+ valid: false,
435
+ floor,
436
+ deficit,
437
+ message: `Price ${listPrice} is below resource cost floor of ${floor}. The trait's GPU/memory cost exceeds its economic price by ${deficit} USDC base units.`,
438
+ };
439
+ }
440
+
441
+ // ===========================================================================
442
+ // UNIFIED BUDGET STATE
443
+ // ===========================================================================
444
+
445
+ /**
446
+ * Get a unified view of budget pressure across economy + rendering.
447
+ *
448
+ * @param agentId - Agent identifier
449
+ * @param nodes - Current resource usage nodes
450
+ * @param economicSpent - Current economic spend (USDC base units)
451
+ * @param economicLimit - Economic budget limit (USDC base units)
452
+ * @returns Unified budget state
453
+ */
454
+ getUnifiedState(
455
+ agentId: string,
456
+ nodes: ResourceUsageNode[],
457
+ economicSpent?: number,
458
+ economicLimit?: number
459
+ ): UnifiedBudgetState {
460
+ const spent = economicSpent ?? this.economicSpent;
461
+ const limit = economicLimit ?? this.economicBudget;
462
+ const economicPressure = limit > 0 ? spent / limit : 0;
463
+
464
+ const allocations = this.flattenToAllocations(nodes, 0);
465
+ const limits = PLATFORM_BUDGETS[this.platform] ?? {};
466
+ const pressure = this.computeResourcePressure(allocations, limits);
467
+
468
+ const overallPressure = Math.max(economicPressure, pressure.maxPressure);
469
+
470
+ // Suggest LOD based on overall pressure
471
+ let suggestedLOD = 0;
472
+ if (overallPressure > 0.95) suggestedLOD = 3;
473
+ else if (overallPressure > 0.8) suggestedLOD = 2;
474
+ else if (overallPressure > 0.6) suggestedLOD = 1;
475
+
476
+ // Find shed candidates (sorted by value/cost ratio ascending)
477
+ const shedCandidates = allocations
478
+ .filter((a) => a.included && !this.isRequired(a.trait))
479
+ .sort((a, b) => a.valueCostRatio - b.valueCostRatio)
480
+ .slice(0, 10);
481
+
482
+ return {
483
+ agentId,
484
+ economicPressure: Math.min(1, economicPressure),
485
+ resourcePressure: pressure.perCategory,
486
+ overallPressure: Math.min(1, overallPressure),
487
+ suggestedLOD,
488
+ hardLimitBreached: pressure.maxPressure > 1.0 || economicPressure > 1.0,
489
+ shedCandidates,
490
+ };
491
+ }
492
+
493
+ // ===========================================================================
494
+ // UTILITY QUERIES
495
+ // ===========================================================================
496
+
497
+ /**
498
+ * Get the utility score for a trait.
499
+ */
500
+ getUtility(traitName: string): TraitUtility | undefined {
501
+ const normalized = traitName.startsWith('@') ? traitName : `@${traitName}`;
502
+ return this.traitUtilities.get(normalized);
503
+ }
504
+
505
+ /**
506
+ * Set custom utility for a trait.
507
+ */
508
+ setUtility(utility: TraitUtility): void {
509
+ this.traitUtilities.set(utility.trait, utility);
510
+ }
511
+
512
+ /**
513
+ * Get the total weighted resource cost of a trait at a given LOD level.
514
+ * Collapses multi-dimensional resource cost into a single scalar
515
+ * using platform limits as normalization weights.
516
+ */
517
+ getWeightedCost(traitName: string, lodLevel: number = 0, instanceCount: number = 1): number {
518
+ const normalized = traitName.startsWith('@') ? traitName : `@${traitName}`;
519
+ const costs = TRAIT_RESOURCE_COSTS[normalized];
520
+ if (!costs) return 0;
521
+
522
+ const limits = PLATFORM_BUDGETS[this.platform] ?? {};
523
+ const scale = this.lodScaling[Math.min(lodLevel, this.lodScaling.length - 1)] ?? 0.05;
524
+
525
+ let weighted = 0;
526
+ for (const [cat, cost] of Object.entries(costs)) {
527
+ const limit = limits[cat as ResourceCategory];
528
+ if (limit && limit > 0) {
529
+ // Normalize cost by platform limit so each dimension contributes proportionally
530
+ weighted += ((cost as number) * instanceCount * scale) / limit;
531
+ }
532
+ }
533
+
534
+ return weighted;
535
+ }
536
+
537
+ /**
538
+ * Compute value/cost ratio for a trait at a given LOD level.
539
+ * Higher = more efficient use of resources.
540
+ */
541
+ getValueCostRatio(traitName: string, lodLevel: number = 0, instanceCount: number = 1): number {
542
+ const normalized = traitName.startsWith('@') ? traitName : `@${traitName}`;
543
+ const util = this.traitUtilities.get(normalized);
544
+ const utility = util?.baseUtility ?? 50; // Default utility for unknown traits
545
+ const cost = this.getWeightedCost(normalized, lodLevel, instanceCount);
546
+ if (cost === 0) return utility * 100; // Free trait = infinite value (capped)
547
+ return utility / cost;
548
+ }
549
+
550
+ // ===========================================================================
551
+ // INTERNALS
552
+ // ===========================================================================
553
+
554
+ private flattenToAllocations(nodes: ResourceUsageNode[], lodLevel: number): TraitAllocation[] {
555
+ const allocations: TraitAllocation[] = [];
556
+ const seen = new Set<string>();
557
+
558
+ for (const node of nodes) {
559
+ for (const trait of node.traits) {
560
+ const normalized = trait.startsWith('@') ? trait : `@${trait}`;
561
+ if (seen.has(normalized)) continue;
562
+ seen.add(normalized);
563
+
564
+ allocations.push(this.buildAllocation(normalized, lodLevel, node.count || 1));
565
+ }
566
+ }
567
+
568
+ return allocations;
569
+ }
570
+
571
+ private buildAllocation(trait: string, lodLevel: number, instanceCount: number): TraitAllocation {
572
+ const normalized = trait.startsWith('@') ? trait : `@${trait}`;
573
+ const costs = TRAIT_RESOURCE_COSTS[normalized] ?? {};
574
+ const scale = this.lodScaling[Math.min(lodLevel, this.lodScaling.length - 1)] ?? 0.05;
575
+
576
+ // Scale resource costs by LOD level
577
+ const scaledCosts: Partial<Record<ResourceCategory, number>> = {};
578
+ for (const [cat, cost] of Object.entries(costs)) {
579
+ scaledCosts[cat as ResourceCategory] = Math.ceil((cost as number) * instanceCount * scale);
580
+ }
581
+
582
+ const utility = this.traitUtilities.get(normalized)?.baseUtility ?? 50;
583
+ const weightedCost = this.getWeightedCost(normalized, lodLevel, instanceCount);
584
+ const valueCostRatio = weightedCost > 0 ? utility / weightedCost : utility * 100;
585
+
586
+ return {
587
+ trait: normalized,
588
+ included: true,
589
+ lodLevel,
590
+ resourceCost: scaledCosts,
591
+ economicCost: 0, // Set externally if marketplace pricing applies
592
+ utility,
593
+ valueCostRatio,
594
+ };
595
+ }
596
+
597
+ private isRequired(trait: string): boolean {
598
+ const normalized = trait.startsWith('@') ? trait : `@${trait}`;
599
+ return this.traitUtilities.get(normalized)?.required ?? false;
600
+ }
601
+
602
+ private canDropAtLOD(trait: string, lodLevel: number): boolean {
603
+ const normalized = trait.startsWith('@') ? trait : `@${trait}`;
604
+ const util = this.traitUtilities.get(normalized);
605
+ if (!util) return lodLevel >= 2; // Unknown traits droppable at LOD 2+
606
+ return lodLevel >= util.minLODLevel;
607
+ }
608
+
609
+ private computeResourcePressure(
610
+ allocations: TraitAllocation[],
611
+ limits: Partial<Record<ResourceCategory, number>>
612
+ ): { maxPressure: number; perCategory: Partial<Record<ResourceCategory, number>> } {
613
+ // Sum resource costs from all included allocations
614
+ const totals: Record<string, number> = {};
615
+ for (const alloc of allocations) {
616
+ if (!alloc.included) continue;
617
+ for (const [cat, cost] of Object.entries(alloc.resourceCost)) {
618
+ totals[cat] = (totals[cat] || 0) + (cost as number);
619
+ }
620
+ }
621
+
622
+ // Compute pressure per category
623
+ const perCategory: Partial<Record<ResourceCategory, number>> = {};
624
+ let maxPressure = 0;
625
+
626
+ for (const [cat, limit] of Object.entries(limits)) {
627
+ const used = totals[cat] || 0;
628
+ const pressure = limit ? used / (limit as number) : 0;
629
+ perCategory[cat as ResourceCategory] = pressure;
630
+ if (pressure > maxPressure) maxPressure = pressure;
631
+ }
632
+
633
+ return { maxPressure, perCategory };
634
+ }
635
+ }