@holoscript/core 8.0.8 → 8.0.10

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 (463) hide show
  1. package/LICENSE +21 -0
  2. package/bin/holoscript.cjs +5 -1
  3. package/dist/GLTFPipeline-L76YTXKA.js +15 -0
  4. package/dist/GLTFPipeline-ZWJN7GI6.cjs +36 -0
  5. package/dist/HoloCompositionParser-K76KTEEE.js +12 -0
  6. package/dist/HoloCompositionParser-UZH6Y4HO.cjs +45 -0
  7. package/dist/HoloScriptCodeParser-2D4ERFUZ.js +6 -0
  8. package/dist/HoloScriptCodeParser-UDRLDA7I.cjs +23 -0
  9. package/dist/HoloScriptPlusParser-FTMCGWXD.js +13 -0
  10. package/dist/HoloScriptPlusParser-LQR5GIKF.cjs +30 -0
  11. package/dist/HoloScriptRuntime-UTRLJI5L.js +12 -0
  12. package/dist/HoloScriptRuntime-YBYXJHJN.cjs +21 -0
  13. package/dist/SceneIRCompiler-63KA3M2F.js +17 -0
  14. package/dist/SceneIRCompiler-KWUFHZ4O.cjs +42 -0
  15. package/dist/{USDZExporter-AUHZODOF.cjs → USDZExporter-ML4UMIGC.cjs} +3 -3
  16. package/dist/{USDZExporter-GXB4AUSG.js → USDZExporter-ZFKLQ76G.js} +3 -3
  17. package/dist/agents/index.cjs +1 -1
  18. package/dist/agents/index.js +1 -1
  19. package/dist/audit/AuditLogger.d.ts +106 -0
  20. package/dist/audit/AuditQueryBuilder.d.ts +79 -0
  21. package/dist/audit/ComplianceReporter.d.ts +90 -0
  22. package/dist/audit/index.d.ts +13 -0
  23. package/dist/{chunk-FGMDMJM7.js → chunk-2BYAUHBO.js} +3 -3
  24. package/dist/{chunk-EDPYCLRC.cjs → chunk-2E6PP5UE.cjs} +83 -59
  25. package/dist/{chunk-GASCWJK5.js → chunk-2EGGAZT3.js} +65 -287
  26. package/dist/{chunk-SIEXCILW.js → chunk-2I3XAGMF.js} +3 -3
  27. package/dist/{chunk-OS533P33.cjs → chunk-2KKOYZYI.cjs} +1074 -1655
  28. package/dist/chunk-2MQKNDNH.js +51 -0
  29. package/dist/{chunk-7DMMMBCY.cjs → chunk-2X3IPCBK.cjs} +10 -10
  30. package/dist/chunk-2ZOTBO25.cjs +202 -0
  31. package/dist/chunk-356N4NPM.cjs +152 -0
  32. package/dist/{chunk-X3GJPRVK.cjs → chunk-36NPLOI4.cjs} +92 -81
  33. package/dist/{chunk-SLU6UPJI.cjs → chunk-37HHY7RH.cjs} +5 -5
  34. package/dist/{chunk-YDPEHCCI.cjs → chunk-3CDDRUER.cjs} +3 -118
  35. package/dist/chunk-3F7ZZWIN.js +1651 -0
  36. package/dist/chunk-3ILD7TKP.cjs +187 -0
  37. package/dist/{chunk-X27QRG4C.cjs → chunk-3IUHTGDD.cjs} +153 -41
  38. package/dist/{chunk-PXNZ6TME.js → chunk-4376VHNJ.js} +60 -36
  39. package/dist/{chunk-YKGPCZEE.cjs → chunk-4FIBOJW6.cjs} +10 -10
  40. package/dist/chunk-4Q7RKQQI.cjs +14195 -0
  41. package/dist/{chunk-YCM6QQRT.cjs → chunk-566ZB4LZ.cjs} +180 -8
  42. package/dist/{chunk-2Y3V22SG.js → chunk-5CURIYGT.js} +3 -3
  43. package/dist/{chunk-QBWNJXNR.js → chunk-5FYMCVBY.js} +5 -5
  44. package/dist/{chunk-ZA34AN3R.js → chunk-5LVFTGKH.js} +841 -12
  45. package/dist/{chunk-Z6PMDS4G.js → chunk-5OUOMBKK.js} +1090 -53
  46. package/dist/chunk-63AUAIL6.js +48 -0
  47. package/dist/chunk-655FATJU.js +110 -0
  48. package/dist/{chunk-TQ377OLB.cjs → chunk-65S62L4D.cjs} +28 -22
  49. package/dist/chunk-6NTAZN5Z.cjs +502 -0
  50. package/dist/chunk-6TRX6AR2.js +9376 -0
  51. package/dist/chunk-6ZCCLKS2.cjs +51 -0
  52. package/dist/{chunk-MDR235RZ.js → chunk-734OB5SK.js} +721 -8
  53. package/dist/{chunk-Q2Q34NF3.js → chunk-77QI552M.js} +6 -6
  54. package/dist/{chunk-WKX5AMOY.js → chunk-7I74JWPW.js} +3 -3
  55. package/dist/{chunk-5FSKKMR7.cjs → chunk-7IDJDMPU.cjs} +232 -4
  56. package/dist/chunk-7VBKRGIZ.cjs +847 -0
  57. package/dist/{chunk-KCPHASCO.js → chunk-AATYYLYK.js} +5 -5
  58. package/dist/{chunk-DTGBOSEM.cjs → chunk-AWXFEAQL.cjs} +12 -12
  59. package/dist/{chunk-THCK6ECW.js → chunk-B3CWTCK2.js} +231 -3
  60. package/dist/chunk-BCP4E7MD.cjs +997 -0
  61. package/dist/chunk-BG7RPYJP.js +91 -0
  62. package/dist/{chunk-VF257YKO.js → chunk-BP4KTMLY.js} +117 -5
  63. package/dist/{chunk-YN6IETRZ.js → chunk-BQJ3YBF3.js} +6424 -987
  64. package/dist/{chunk-7X5PNIQJ.js → chunk-BXSOEQI3.js} +27 -25
  65. package/dist/{chunk-WFKVS2K4.cjs → chunk-C65C5VED.cjs} +1101 -55
  66. package/dist/{chunk-KOUAQL2B.cjs → chunk-CANAL6CT.cjs} +1569 -168
  67. package/dist/{chunk-FDSGHPAK.js → chunk-CBSDJF3B.js} +3 -3
  68. package/dist/{chunk-FEWUHQNM.cjs → chunk-CFVK26P4.cjs} +4 -4
  69. package/dist/{chunk-M7K4LE66.cjs → chunk-CI33NINL.cjs} +4 -4
  70. package/dist/{chunk-2OWDB4FB.js → chunk-CKV5NYAE.js} +6 -6
  71. package/dist/{chunk-LPS5AMQD.cjs → chunk-CM4NHXVO.cjs} +24 -24
  72. package/dist/chunk-D3HSRXYB.js +115 -0
  73. package/dist/{chunk-FNHJRI32.js → chunk-DBH3D2MU.js} +1561 -160
  74. package/dist/{chunk-GW3LKD5C.js → chunk-DJNH4HFW.js} +3 -3
  75. package/dist/{chunk-2GCNOI2U.cjs → chunk-DN6F4DHH.cjs} +9 -3
  76. package/dist/chunk-DQRJ5RJG.js +432 -0
  77. package/dist/{chunk-C7ZBSV2Q.js → chunk-DUG3OQAS.js} +106 -43
  78. package/dist/chunk-ECUYJOJ2.cjs +199 -0
  79. package/dist/chunk-EEMTCAK7.js +182 -0
  80. package/dist/{chunk-3SKLAUDD.cjs → chunk-EI3J6LTW.cjs} +359 -581
  81. package/dist/{chunk-SSSN7VAM.js → chunk-EM2OGPYE.js} +4 -4
  82. package/dist/{chunk-56VOWURB.cjs → chunk-ENZCIQZC.cjs} +522 -453
  83. package/dist/chunk-ESBXUYOC.cjs +836 -0
  84. package/dist/{chunk-L5U4W6JC.js → chunk-ESSEF6U2.js} +4 -4
  85. package/dist/chunk-ET3AN4W3.js +824 -0
  86. package/dist/chunk-EXKSFGSX.cjs +14 -14
  87. package/dist/{chunk-I76GIKN7.cjs → chunk-F3YT6GUF.cjs} +17 -17
  88. package/dist/{chunk-WRWEX5ZF.js → chunk-F75DZBBI.js} +3 -3
  89. package/dist/{chunk-4JGRWHZJ.js → chunk-FG6PYGIZ.js} +12 -6
  90. package/dist/chunk-FGGJQRLZ.js +14 -14
  91. package/dist/{chunk-SXOSDZD6.js → chunk-FWCHPCD4.js} +128 -10
  92. package/dist/{chunk-XW6IXEQH.cjs → chunk-G35CAQ7I.cjs} +5 -5
  93. package/dist/{chunk-7VMGP2FW.js → chunk-G6KACNMO.js} +132 -188
  94. package/dist/{chunk-FJPADH77.cjs → chunk-GAWJJAGT.cjs} +6 -6
  95. package/dist/chunk-GGR7QUKK.cjs +4724 -0
  96. package/dist/chunk-GIBF4BHH.js +14098 -0
  97. package/dist/{chunk-AWB7QYY6.cjs → chunk-GILYQ5VH.cjs} +6 -6
  98. package/dist/{chunk-FWVYAMNW.cjs → chunk-GKFCHGCM.cjs} +4 -4
  99. package/dist/{chunk-Y3ZFVUUU.js → chunk-GLEJ2TLS.js} +3 -3
  100. package/dist/{chunk-3GWWJT7L.js → chunk-GP3OKSKT.js} +13 -13
  101. package/dist/chunk-GTIPOEHN.js +196 -0
  102. package/dist/{chunk-LMJ5EBV7.cjs → chunk-GZPUA2TL.cjs} +16 -15
  103. package/dist/{chunk-IELX2OKI.js → chunk-H4LJ4HEA.js} +3 -115
  104. package/dist/{chunk-ACVWFGBI.cjs → chunk-HCX4SMFK.cjs} +3 -3
  105. package/dist/{chunk-HYNTS5CP.js → chunk-HHRZ6STP.js} +24 -2
  106. package/dist/chunk-HIDEIEFG.js +48 -0
  107. package/dist/{chunk-W6RW64BB.cjs → chunk-HSWBIDZM.cjs} +228 -5
  108. package/dist/{chunk-GSOUNWEL.js → chunk-HVX7QQYK.js} +1035 -1616
  109. package/dist/{chunk-TRCGG42I.cjs → chunk-IFW5IW4L.cjs} +6502 -1019
  110. package/dist/{chunk-F5LKJ6F5.cjs → chunk-IJU6AS67.cjs} +32 -28
  111. package/dist/{chunk-TVANHNJ7.js → chunk-IK6Z2HNC.js} +173 -3
  112. package/dist/{chunk-QGMUTVVH.cjs → chunk-IS7NTKY5.cjs} +20 -20
  113. package/dist/{chunk-YFVQWSNJ.js → chunk-IXWGYWH6.js} +3 -3
  114. package/dist/chunk-J2DAS2PL.js +148 -0
  115. package/dist/chunk-JBQXZIWB.js +148 -0
  116. package/dist/chunk-JBYBMBMW.js +261 -0
  117. package/dist/{chunk-LWUOUCYL.js → chunk-JEJ7TT54.js} +5 -5
  118. package/dist/{chunk-DUCB6LGL.js → chunk-JOTCTHN5.js} +211 -5
  119. package/dist/{chunk-47RUFYKR.js → chunk-K56UVEXV.js} +3 -3
  120. package/dist/{chunk-XSFNHP2E.js → chunk-KB26H447.js} +3 -3
  121. package/dist/{chunk-3NGDZZR4.js → chunk-KEYG7HGY.js} +430 -37
  122. package/dist/{chunk-DY72BMU5.js → chunk-KFVNCW4Q.js} +4 -4
  123. package/dist/chunk-KPGC7SK2.js +192 -0
  124. package/dist/chunk-KROXXBK5.cjs +53 -0
  125. package/dist/{chunk-ZPZAVHXL.cjs → chunk-LB3ZZMKT.cjs} +137 -19
  126. package/dist/chunk-LDYZWNJW.cjs +1654 -0
  127. package/dist/{chunk-UGXHYIWT.cjs → chunk-LHNHO27U.cjs} +28 -28
  128. package/dist/{chunk-ULMSSHCJ.cjs → chunk-LMB555QB.cjs} +405 -38
  129. package/dist/{chunk-CBJY23VL.js → chunk-MD4IVS42.js} +3 -3
  130. package/dist/{chunk-VMLKSV26.js → chunk-MDDWEBCX.js} +226 -3
  131. package/dist/chunk-MUWH4QK5.cjs +94 -0
  132. package/dist/{chunk-T6W3Z3SH.cjs → chunk-N2ILK7BF.cjs} +4 -4
  133. package/dist/{chunk-6EX3I5P4.cjs → chunk-N74QRJGP.cjs} +6 -6
  134. package/dist/chunk-NFFHLX37.cjs +132 -0
  135. package/dist/chunk-NJSYWX7H.js +190 -0
  136. package/dist/{chunk-SH5PPVK7.cjs → chunk-NQN5LFN4.cjs} +165 -221
  137. package/dist/chunk-NTQRFOPP.cjs +56 -0
  138. package/dist/{chunk-YFWRJW4B.js → chunk-NWMJPTVL.js} +51 -55
  139. package/dist/{chunk-63ZGNR2T.cjs → chunk-OC6GV5QH.cjs} +42 -42
  140. package/dist/{chunk-7E7GCQCK.js → chunk-OG3MBHM3.js} +9 -3
  141. package/dist/chunk-OMNHTCS4.cjs +264 -0
  142. package/dist/chunk-OQXB6NNC.cjs +117 -0
  143. package/dist/{chunk-Y57W2CT5.cjs → chunk-OV46CCA2.cjs} +51 -55
  144. package/dist/{chunk-6PRZIHGL.cjs → chunk-OV6HHKLE.cjs} +21 -266
  145. package/dist/{chunk-C37BHB7N.cjs → chunk-P26YYPOO.cjs} +5 -5
  146. package/dist/{chunk-2BZHWUSR.cjs → chunk-P7JEKJTZ.cjs} +16 -16
  147. package/dist/{chunk-NGBXXZLL.cjs → chunk-PDXX5UI4.cjs} +45 -45
  148. package/dist/{chunk-FHQFPYV4.js → chunk-PJFTTSKE.js} +8 -7
  149. package/dist/{chunk-GVFJ6T4S.cjs → chunk-PVODJEJ7.cjs} +721 -8
  150. package/dist/{chunk-ZZ7RO2UQ.js → chunk-Q265VZJ4.js} +3 -3
  151. package/dist/{chunk-E7JP3BS3.cjs → chunk-Q47JEE2C.cjs} +4 -4
  152. package/dist/chunk-QBY7LRN2.js +281 -0
  153. package/dist/{chunk-GH6C2QGW.cjs → chunk-QEDMOGF3.cjs} +857 -16
  154. package/dist/{chunk-5WUNN6ZN.cjs → chunk-QHSAVOOL.cjs} +24 -2
  155. package/dist/chunk-QJNCDVYM.cjs +137 -0
  156. package/dist/chunk-QM7FJB5W.js +4717 -0
  157. package/dist/chunk-QQFB4RT2.cjs +448 -0
  158. package/dist/chunk-QSYBHQAD.cjs +196 -0
  159. package/dist/{chunk-2YSNA2F2.cjs → chunk-QVJMDVYZ.cjs} +5 -5
  160. package/dist/chunk-RAJMAX6D.js +131 -0
  161. package/dist/{chunk-VVKVQZI6.js → chunk-RAN3ECAS.js} +3 -3
  162. package/dist/chunk-RKGUZBOC.js +79 -0
  163. package/dist/{chunk-HLFDZ35S.cjs → chunk-RRQIAJLQ.cjs} +8 -8
  164. package/dist/{chunk-PABONWKI.js → chunk-RXQNRZ5F.js} +3 -3
  165. package/dist/{chunk-7ZHYEITD.cjs → chunk-S4O7CFHQ.cjs} +10 -11
  166. package/dist/chunk-SE7ANBGN.js +128 -0
  167. package/dist/{chunk-X2AVEAZM.cjs → chunk-SJIRCTIS.cjs} +176 -2
  168. package/dist/{chunk-WXQ57LAO.js → chunk-SQXAZHDG.js} +4 -4
  169. package/dist/chunk-SR73L5ZP.cjs +84 -0
  170. package/dist/{chunk-OABEO6HH.js → chunk-SZOLTWWD.js} +2349 -214
  171. package/dist/{chunk-5JEU52DW.js → chunk-TPZ3BN2D.js} +3 -3
  172. package/dist/{chunk-QFU53KRH.js → chunk-TSTFYGGX.js} +4 -5
  173. package/dist/{chunk-EBS32AJI.cjs → chunk-TZCFKZ6Z.cjs} +5 -5
  174. package/dist/chunk-UCVZXQ63.js +724 -0
  175. package/dist/chunk-UGQ3QNV6.js +992 -0
  176. package/dist/chunk-UNMVGZVW.js +142 -0
  177. package/dist/{chunk-TEVX443G.js → chunk-UTUD6GJI.js} +178 -6
  178. package/dist/chunk-UXBYA7DV.cjs +155 -0
  179. package/dist/chunk-V6GXFSK7.cjs +443 -0
  180. package/dist/chunk-VHORVGCG.cjs +761 -0
  181. package/dist/{chunk-B4EKV6HQ.js → chunk-VKCTYLDN.js} +500 -442
  182. package/dist/{chunk-O3MGZDCT.cjs → chunk-VTEXJOSD.cjs} +63 -63
  183. package/dist/chunk-VVAAZITA.cjs +287 -0
  184. package/dist/{chunk-V7AKE5VT.cjs → chunk-W2GA3EQB.cjs} +435 -42
  185. package/dist/chunk-W4GABFCR.js +37 -0
  186. package/dist/chunk-WKEK4NCA.js +490 -0
  187. package/dist/{chunk-5A4TCHOC.cjs → chunk-WNYXT4TE.cjs} +4 -4
  188. package/dist/{chunk-S762UEV5.js → chunk-WTH23ISI.js} +6 -6
  189. package/dist/{chunk-BTKZGHAV.js → chunk-WWNCM473.js} +10 -6
  190. package/dist/{chunk-4YNYPHH3.cjs → chunk-X75H75UY.cjs} +2351 -213
  191. package/dist/{chunk-LYJJI7ZK.cjs → chunk-XCUDDFKC.cjs} +216 -9
  192. package/dist/chunk-YAO4HPJL.cjs +120 -0
  193. package/dist/chunk-YKY2JBER.cjs +9443 -0
  194. package/dist/chunk-YP5CHEYU.cjs +154 -0
  195. package/dist/{chunk-WRNEH5MO.js → chunk-YW3I2FQL.js} +18 -7
  196. package/dist/{chunk-BRNUPDHJ.js → chunk-Z557DVYN.js} +386 -19
  197. package/dist/{chunk-VSACG2AR.js → chunk-Z6LZUYZF.js} +21 -264
  198. package/dist/chunk-ZHPMP447.cjs +43 -0
  199. package/dist/{chunk-LODTHGST.cjs → chunk-ZK7KX5CD.cjs} +133 -70
  200. package/dist/{chunk-CCPSNT6B.cjs → chunk-ZRPALBQB.cjs} +5 -5
  201. package/dist/chunk-ZVFHPMC5.js +824 -0
  202. package/dist/chunk-ZVQMKUGJ.js +443 -0
  203. package/dist/cli/holoscript-runner.cjs +86 -48
  204. package/dist/cli/holoscript-runner.js +54 -16
  205. package/dist/codebase/index.cjs +1 -1
  206. package/dist/codebase/index.js +1 -1
  207. package/dist/compiler/agent-inference.cjs +11 -11
  208. package/dist/compiler/agent-inference.js +9 -6
  209. package/dist/compiler/android-xr.cjs +13 -12
  210. package/dist/compiler/android-xr.js +11 -10
  211. package/dist/compiler/android.cjs +12 -12
  212. package/dist/compiler/android.js +10 -10
  213. package/dist/compiler/business-quest.cjs +10 -10
  214. package/dist/compiler/business-quest.js +2 -2
  215. package/dist/compiler/coco.cjs +1 -1
  216. package/dist/compiler/coco.js +1 -1
  217. package/dist/compiler/context.cjs +9 -9
  218. package/dist/compiler/context.js +6 -6
  219. package/dist/compiler/daimon-seed.cjs +36 -0
  220. package/dist/compiler/daimon-seed.js +12 -0
  221. package/dist/compiler/domain-block-utils.cjs +205 -156
  222. package/dist/compiler/domain-block-utils.js +8 -7
  223. package/dist/compiler/dtdl.cjs +10 -10
  224. package/dist/compiler/dtdl.js +7 -7
  225. package/dist/compiler/gltf-pipeline.cjs +9 -9
  226. package/dist/compiler/gltf-pipeline.js +2 -2
  227. package/dist/compiler/godot.cjs +10 -9
  228. package/dist/compiler/godot.js +9 -8
  229. package/dist/compiler/incremental.cjs +11 -11
  230. package/dist/compiler/incremental.js +6 -6
  231. package/dist/compiler/index.cjs +2585 -3273
  232. package/dist/compiler/index.d.ts +149 -35
  233. package/dist/compiler/index.js +2197 -3102
  234. package/dist/compiler/ios.cjs +11 -11
  235. package/dist/compiler/ios.js +9 -9
  236. package/dist/compiler/llm-provider-capabilities.cjs +9 -9
  237. package/dist/compiler/llm-provider-capabilities.js +6 -6
  238. package/dist/compiler/nodetoy.cjs +1 -1
  239. package/dist/compiler/nodetoy.js +1 -1
  240. package/dist/compiler/openxr.cjs +11 -10
  241. package/dist/compiler/openxr.js +10 -9
  242. package/dist/compiler/remotion.cjs +1 -1
  243. package/dist/compiler/remotion.js +1 -1
  244. package/dist/compiler/reproducibility.cjs +1 -1
  245. package/dist/compiler/reproducibility.js +1 -1
  246. package/dist/compiler/sdf.cjs +11 -10
  247. package/dist/compiler/sdf.js +9 -8
  248. package/dist/compiler/semantic-scene.cjs +7 -7
  249. package/dist/compiler/semantic-scene.js +2 -2
  250. package/dist/compiler/state.cjs +9 -9
  251. package/dist/compiler/state.js +7 -7
  252. package/dist/compiler/trait-composition.cjs +11 -11
  253. package/dist/compiler/trait-composition.js +7 -7
  254. package/dist/compiler/traits/ProvenanceSemiring.d.ts +167 -0
  255. package/dist/compiler/traits/Semiring.d.ts +26 -0
  256. package/dist/compiler/unity.cjs +11 -10
  257. package/dist/compiler/unity.js +10 -9
  258. package/dist/compiler/unreal.cjs +12 -11
  259. package/dist/compiler/unreal.js +10 -9
  260. package/dist/compiler/urdf.cjs +18 -17
  261. package/dist/compiler/urdf.js +9 -8
  262. package/dist/compiler/usd-physics.cjs +11 -10
  263. package/dist/compiler/usd-physics.js +8 -7
  264. package/dist/compiler/visionos.cjs +11 -10
  265. package/dist/compiler/visionos.js +10 -9
  266. package/dist/compiler/vrchat.cjs +11 -10
  267. package/dist/compiler/vrchat.js +9 -8
  268. package/dist/compiler/wasm.cjs +14 -13
  269. package/dist/compiler/wasm.js +9 -8
  270. package/dist/compiler/webgpu.cjs +10 -9
  271. package/dist/compiler/webgpu.js +9 -8
  272. package/dist/constants.cjs +398 -0
  273. package/dist/constants.js +9 -0
  274. package/dist/coordinators/index.cjs +4 -1
  275. package/dist/coordinators/index.js +4 -1
  276. package/dist/debugger.cjs +11 -9
  277. package/dist/debugger.js +9 -7
  278. package/dist/{dist-ACXG6IH5.cjs → dist-GRMOP2UL.cjs} +341 -65
  279. package/dist/{dist-VD4L6V7Q.js → dist-ZBUKU2JG.js} +333 -63
  280. package/dist/entries/interop.cjs +23 -23
  281. package/dist/entries/interop.js +3 -3
  282. package/dist/entries/scripting.cjs +12 -11
  283. package/dist/entries/scripting.js +8 -7
  284. package/dist/evolution/index.cjs +66 -0
  285. package/dist/evolution/index.d.ts +162 -0
  286. package/dist/evolution/index.js +17 -0
  287. package/dist/hololand/index.cjs +41 -37
  288. package/dist/hololand/index.js +3 -3
  289. package/dist/index.cjs +12394 -3224
  290. package/dist/index.d.ts +260 -18
  291. package/dist/index.js +10755 -2182
  292. package/dist/keyword-registry/PluginKeywordRegistry.d.ts +138 -0
  293. package/dist/keyword-registry/index.cjs +29 -0
  294. package/dist/keyword-registry/index.d.ts +1 -0
  295. package/dist/keyword-registry/index.js +4 -0
  296. package/dist/math/tropical-spmv.cjs +11 -11
  297. package/dist/math/tropical-spmv.d.ts +1 -0
  298. package/dist/math/tropical-spmv.js +2 -2
  299. package/dist/math/tropicalSpmv.d.ts +43 -0
  300. package/dist/math/vec3.cjs +1 -1
  301. package/dist/math/vec3.js +1 -1
  302. package/dist/matter/StagedMatter.d.ts +82 -0
  303. package/dist/matter/index.cjs +17 -0
  304. package/dist/matter/index.d.ts +1 -0
  305. package/dist/matter/index.js +4 -0
  306. package/dist/{ml-dsa-Z7JDKW4L.js → ml-dsa-J6VKVT4K.js} +3 -3
  307. package/dist/{ml-dsa-FSKBTE3W.cjs → ml-dsa-MTIK4ZUQ.cjs} +23 -23
  308. package/dist/paper-0c-spike/index.cjs +1 -1
  309. package/dist/paper-0c-spike/index.js +1 -1
  310. package/dist/parameter-envelope/ParameterEnvelope.d.ts +128 -0
  311. package/dist/parameter-envelope/index.cjs +17 -0
  312. package/dist/parameter-envelope/index.d.ts +2 -0
  313. package/dist/parameter-envelope/index.js +4 -0
  314. package/dist/parser/HoloCompositionTypes.d.ts +2013 -0
  315. package/dist/parser/PipelineParser.d.ts +162 -0
  316. package/dist/parser.cjs +36 -20
  317. package/dist/parser.js +13 -9
  318. package/dist/plugin-manifest/ManifestAutoLoader.d.ts +35 -0
  319. package/dist/plugin-manifest/PluginManifest.d.ts +156 -0
  320. package/dist/plugin-manifest/index.cjs +25 -0
  321. package/dist/plugin-manifest/index.d.ts +2 -0
  322. package/dist/plugin-manifest/index.js +4 -0
  323. package/dist/plugin-solver-contract/PluginSolverContract.d.ts +288 -0
  324. package/dist/plugin-solver-contract/index.cjs +41 -0
  325. package/dist/plugin-solver-contract/index.d.ts +1 -0
  326. package/dist/plugin-solver-contract/index.js +4 -0
  327. package/dist/policy/ContentPolicyGate.d.ts +51 -0
  328. package/dist/policy/PolicyPack.d.ts +120 -0
  329. package/dist/policy/blocklist.d.ts +32 -0
  330. package/dist/policy/defaults.d.ts +44 -0
  331. package/dist/policy/index.cjs +106 -0
  332. package/dist/policy/index.d.ts +17 -0
  333. package/dist/policy/index.js +5 -0
  334. package/dist/policy/jsonLogic.d.ts +31 -0
  335. package/dist/policy/types.d.ts +174 -0
  336. package/dist/proof-composition/ProofCompositionLaw.d.ts +93 -0
  337. package/dist/proof-composition/index.cjs +25 -0
  338. package/dist/proof-composition/index.d.ts +1 -0
  339. package/dist/proof-composition/index.js +4 -0
  340. package/dist/receipts/hash-policy.d.ts +85 -0
  341. package/dist/reconstruction/index.cjs +75 -47
  342. package/dist/reconstruction/index.js +5 -5
  343. package/dist/runtime/plugin-trait-registrar.d.ts +36 -0
  344. package/dist/runtime.cjs +81 -79
  345. package/dist/runtime.js +9 -7
  346. package/dist/scene-presets-EDKYZ5GW.js +4 -0
  347. package/dist/scene-presets-NCW2CFRS.cjs +17 -0
  348. package/dist/self-improvement/index.cjs +1 -1
  349. package/dist/self-improvement/index.js +1 -1
  350. package/dist/{src-PT2FERDV.cjs → src-G3D7ZATQ.cjs} +545 -21
  351. package/dist/{src-J6CM4AUR.js → src-UXYA7HM6.js} +539 -16
  352. package/dist/storage/index.cjs +10 -10
  353. package/dist/storage/index.js +3 -3
  354. package/dist/testing.cjs +1 -1
  355. package/dist/testing.js +1 -1
  356. package/dist/tools/index.cjs +3631 -0
  357. package/dist/tools/index.js +3581 -0
  358. package/dist/traitDocs/traitDocs.cjs +9 -9
  359. package/dist/traitDocs/traitDocs.d.ts +1 -0
  360. package/dist/traitDocs/traitDocs.js +3 -3
  361. package/dist/traits/ControlLoopTrait.d.ts +154 -0
  362. package/dist/traits/KinematicChainTrait.d.ts +116 -0
  363. package/dist/traits/SensorSamplingTrait.d.ts +154 -0
  364. package/dist/traits/TraitTypes.d.ts +270 -0
  365. package/dist/traits/TransactionTrait.d.ts +121 -0
  366. package/dist/traits/botanical-lotus.cjs +54 -50
  367. package/dist/traits/botanical-lotus.js +2 -5
  368. package/dist/traits/control-loop.cjs +21 -0
  369. package/dist/traits/control-loop.d.ts +1 -0
  370. package/dist/traits/control-loop.js +4 -0
  371. package/dist/traits/engines/cloth-verlet.d.ts +65 -0
  372. package/dist/traits/engines/index.cjs +19 -19
  373. package/dist/traits/engines/index.d.ts +15 -275
  374. package/dist/traits/engines/index.js +3 -3
  375. package/dist/traits/engines/motion-data-schema.d.ts +119 -0
  376. package/dist/traits/engines/motion-matching.d.ts +102 -0
  377. package/dist/traits/engines/noise.d.ts +25 -0
  378. package/dist/traits/engines/onnx-adapter.d.ts +173 -0
  379. package/dist/traits/engines/onnx-motion-matching.d.ts +163 -0
  380. package/dist/traits/engines/pfnn-network.d.ts +92 -0
  381. package/dist/traits/engines/synthetic-walk-cycle.d.ts +38 -0
  382. package/dist/traits/engines/tensor-ops.d.ts +15 -0
  383. package/dist/traits/index.cjs +2789 -1259
  384. package/dist/traits/index.d.ts +35 -0
  385. package/dist/traits/index.js +1692 -411
  386. package/dist/traits/kinematic-chain.cjs +25 -0
  387. package/dist/traits/kinematic-chain.d.ts +1 -0
  388. package/dist/traits/kinematic-chain.js +4 -0
  389. package/dist/traits/sensor-sampling.cjs +33 -0
  390. package/dist/traits/sensor-sampling.d.ts +1 -0
  391. package/dist/traits/sensor-sampling.js +4 -0
  392. package/dist/traits/simulation-solver-factory.cjs +1 -1
  393. package/dist/traits/simulation-solver-factory.js +1 -1
  394. package/dist/traits/transaction.cjs +29 -0
  395. package/dist/traits/transaction.d.ts +1 -0
  396. package/dist/traits/transaction.js +4 -0
  397. package/dist/traits/webcam-gaze.cjs +1 -1
  398. package/dist/traits/webcam-gaze.js +1 -1
  399. package/dist/type-checker.cjs +7 -6
  400. package/dist/type-checker.js +5 -4
  401. package/dist/types/AdvancedTypeSystem.d.ts +489 -0
  402. package/dist/types/HoloScriptPlus.d.ts +810 -0
  403. package/dist/types/base.d.ts +248 -0
  404. package/dist/types.d.ts +799 -0
  405. package/dist/world/index.cjs +3 -4
  406. package/dist/world/index.js +3 -4
  407. package/dist/world-model/index.cjs +1 -1
  408. package/dist/world-model/index.js +1 -1
  409. package/dist/wot/index.cjs +1 -1
  410. package/dist/wot/index.js +1 -1
  411. package/package.json +135 -69
  412. package/dist/GLTFPipeline-3EJLL3TS.cjs +0 -38
  413. package/dist/GLTFPipeline-3OWJV2VK.js +0 -17
  414. package/dist/HoloScriptPlusParser-ADWRY5OS.cjs +0 -698
  415. package/dist/HoloScriptPlusParser-JLJDXHSC.js +0 -693
  416. package/dist/HoloScriptPlusParser-QCGFPWIL.cjs +0 -27
  417. package/dist/HoloScriptPlusParser-WENAFEFV.js +0 -10
  418. package/dist/HoloScriptRuntime-GGRVEU4T.cjs +0 -19
  419. package/dist/HoloScriptRuntime-GJK6VJPH.js +0 -10
  420. package/dist/chunk-22Q5A6RV.js +0 -979
  421. package/dist/chunk-4C57PR42.js +0 -468
  422. package/dist/chunk-4W5G5QVL.cjs +0 -847
  423. package/dist/chunk-5ET6L4NS.js +0 -487
  424. package/dist/chunk-C4GSLCHE.cjs +0 -7153
  425. package/dist/chunk-F2G6CWFT.js +0 -7089
  426. package/dist/chunk-G6PNEASF.cjs +0 -814
  427. package/dist/chunk-GTA4Q3A3.js +0 -1491
  428. package/dist/chunk-HCCQLAA4.cjs +0 -74
  429. package/dist/chunk-HFSVMKM3.cjs +0 -72
  430. package/dist/chunk-KAIVDEIG.cjs +0 -4539
  431. package/dist/chunk-KQU64CIX.cjs +0 -1508
  432. package/dist/chunk-MSYSQ2AJ.js +0 -4533
  433. package/dist/chunk-NHJLP37F.js +0 -70
  434. package/dist/chunk-NHYRRAGI.cjs +0 -490
  435. package/dist/chunk-OIQYIIO3.cjs +0 -473
  436. package/dist/chunk-OL46QLBJ.js +0 -61
  437. package/dist/chunk-SKZPC5AG.cjs +0 -984
  438. package/dist/chunk-SLHLXUMO.js +0 -445
  439. package/dist/chunk-UOIGFQXC.cjs +0 -9750
  440. package/dist/chunk-UOL6Y5TD.cjs +0 -759
  441. package/dist/chunk-UVFI7XIR.cjs +0 -452
  442. package/dist/chunk-VEPGBETF.js +0 -844
  443. package/dist/chunk-XQ5MWMP2.js +0 -756
  444. package/dist/chunk-XT6JY5W5.js +0 -9684
  445. package/dist/chunk-YRRAUB35.js +0 -811
  446. package/dist/compiler/GasOptimizationAnalyzer.d.ts +0 -73
  447. package/dist/compiler/NFTMarketplaceCompiler.d.ts +0 -23
  448. package/dist/compiler/ar.cjs +0 -23
  449. package/dist/compiler/ar.js +0 -11
  450. package/dist/compiler/babylon.cjs +0 -21
  451. package/dist/compiler/babylon.js +0 -15
  452. package/dist/compiler/multi-layer.cjs +0 -24
  453. package/dist/compiler/multi-layer.js +0 -15
  454. package/dist/compiler/playcanvas.cjs +0 -19
  455. package/dist/compiler/playcanvas.js +0 -13
  456. package/dist/compiler/r3f.cjs +0 -39
  457. package/dist/compiler/r3f.d.ts +0 -22
  458. package/dist/compiler/r3f.js +0 -17
  459. package/dist/compiler/vrr.cjs +0 -24
  460. package/dist/compiler/vrr.js +0 -12
  461. package/dist/parser/NFTMarketplaceTypes.d.ts +0 -191
  462. package/dist/playwright-3AT3AKTV.cjs +0 -98673
  463. package/dist/playwright-VZPDVUEQ.js +0 -98660
@@ -0,0 +1,3581 @@
1
+ import { __require } from '../chunk-W4GABFCR.js';
2
+ import * as readline from 'readline';
3
+ import { hexToRGBA, createDefaultMaterialDef, rgbaToHex } from '@holoscript/engine/rendering/MaterialLibrary';
4
+ export { createDefaultMaterialDef, hexToRGBA, rgbaToHex } from '@holoscript/engine/rendering/MaterialLibrary';
5
+
6
+ var ErrorFormatter = class {
7
+ /**
8
+ * Format error with source context and helpful visual indicators
9
+ *
10
+ * @param error - The error object containing message, location, and suggestion
11
+ * @param sourceCode - Optional source code to show contextual error location
12
+ * @returns Formatted error string with visual cues and source context
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const formatted = ErrorFormatter.formatError({
17
+ * message: "Unexpected token",
18
+ * location: { line: 5, column: 12 },
19
+ * suggestion: "Try adding a semicolon"
20
+ * }, sourceCode);
21
+ * ```
22
+ */
23
+ static formatError(error, sourceCode) {
24
+ const { message, location, suggestion, token: _token } = error;
25
+ let formatted = `
26
+ \u274C Error: ${message}
27
+ `;
28
+ if (location) {
29
+ formatted += ` at line ${location.line}, column ${location.column}
30
+ `;
31
+ if (sourceCode) {
32
+ const lines = sourceCode.split("\n");
33
+ const errorLine = lines[location.line - 1];
34
+ if (errorLine) {
35
+ formatted += `
36
+ ${location.line} | ${errorLine}
37
+ `;
38
+ formatted += ` | ${" ".repeat(location.column - 1)}^
39
+ `;
40
+ }
41
+ }
42
+ }
43
+ if (suggestion) {
44
+ formatted += `
45
+ \u{1F4A1} Suggestion: ${suggestion}
46
+ `;
47
+ }
48
+ return formatted;
49
+ }
50
+ /**
51
+ * Format multiple errors with summary and limit display to first 5
52
+ *
53
+ * @param errors - Array of error objects to format
54
+ * @returns Formatted string with error count summary and individual error details
55
+ */
56
+ static formatErrors(errors) {
57
+ if (errors.length === 0) return "";
58
+ let formatted = `
59
+ \u274C Found ${errors.length} error${errors.length !== 1 ? "s" : ""}:
60
+ `;
61
+ for (let i = 0; i < Math.min(errors.length, 5); i++) {
62
+ formatted += this.formatError(errors[i]);
63
+ }
64
+ if (errors.length > 5) {
65
+ formatted += `
66
+ ... and ${errors.length - 5} more error${errors.length - 5 !== 1 ? "s" : ""}
67
+ `;
68
+ }
69
+ return formatted;
70
+ }
71
+ /**
72
+ * Format success message
73
+ */
74
+ static formatSuccess(message, details) {
75
+ let formatted = `
76
+ \u2705 ${message}
77
+ `;
78
+ if (details) {
79
+ if (typeof details === "object") {
80
+ formatted += JSON.stringify(details, null, 2) + "\n";
81
+ } else {
82
+ formatted += `${details}
83
+ `;
84
+ }
85
+ }
86
+ return formatted;
87
+ }
88
+ /**
89
+ * Format help text
90
+ */
91
+ static formatHelp() {
92
+ return `
93
+ HoloScript+ REPL v1.0.0
94
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
95
+
96
+ Commands:
97
+ help - Show this help message
98
+ clear - Clear the screen
99
+ vars - List all variables
100
+ types - List all types
101
+ profile - Show performance profile
102
+ exit - Exit REPL
103
+
104
+ Shortcuts:
105
+ .create orb#name - Create orb
106
+ .position x y z - Set position
107
+ .property key val - Set property
108
+
109
+ Examples:
110
+ > orb#myOrb { position: [0, 0, 0] }
111
+ > myOrb.position = [1, 1, 1]
112
+ > match state { "idle" => { ... } }
113
+
114
+ For more info, visit: https://github.com/brianonbased-dev/holoscript
115
+ `.trim();
116
+ }
117
+ };
118
+ var HoloScriptREPL = class {
119
+ constructor(parser, runtime) {
120
+ this.variables = /* @__PURE__ */ new Map();
121
+ this.types = /* @__PURE__ */ new Map();
122
+ this.history = [];
123
+ this.parser = parser;
124
+ this.runtime = runtime;
125
+ this.rl = readline.createInterface({
126
+ input: process.stdin,
127
+ output: process.stdout,
128
+ history: this.history,
129
+ historySize: 100
130
+ });
131
+ }
132
+ /**
133
+ * Start REPL
134
+ */
135
+ async start() {
136
+ console.debug("\n\u{1F97D} HoloScript+ REPL v1.0.0");
137
+ console.debug('Type "help" for commands, "exit" to quit\n');
138
+ await this.repl();
139
+ }
140
+ /**
141
+ * Main REPL loop
142
+ */
143
+ async repl() {
144
+ const prompt = () => {
145
+ this.rl.question("> ", async (input) => {
146
+ try {
147
+ const trimmed = input.trim();
148
+ if (!trimmed) {
149
+ prompt();
150
+ return;
151
+ }
152
+ if (trimmed.startsWith(".")) {
153
+ this.handleCommand(trimmed);
154
+ prompt();
155
+ return;
156
+ }
157
+ switch (trimmed.toLowerCase()) {
158
+ case "help":
159
+ console.debug(ErrorFormatter.formatHelp());
160
+ prompt();
161
+ return;
162
+ case "clear":
163
+ console.clear();
164
+ prompt();
165
+ return;
166
+ case "vars":
167
+ this.showVariables();
168
+ prompt();
169
+ return;
170
+ case "types":
171
+ this.showTypes();
172
+ prompt();
173
+ return;
174
+ case "profile":
175
+ this.showProfile();
176
+ prompt();
177
+ return;
178
+ case "exit":
179
+ this.rl.close();
180
+ console.debug("\n\u{1F44B} Goodbye!\n");
181
+ return;
182
+ }
183
+ const result = await this.evaluate(trimmed);
184
+ if (result !== void 0 && result !== null) {
185
+ this.displayResult(result);
186
+ }
187
+ } catch (error) {
188
+ console.error(ErrorFormatter.formatError(error));
189
+ }
190
+ prompt();
191
+ });
192
+ };
193
+ prompt();
194
+ }
195
+ /**
196
+ * Handle dot commands
197
+ */
198
+ handleCommand(command) {
199
+ const parts = command.slice(1).split(" ");
200
+ const cmd = parts[0];
201
+ switch (cmd) {
202
+ case "create":
203
+ console.debug(`Creating: ${parts.slice(1).join(" ")}`);
204
+ break;
205
+ case "position":
206
+ console.debug(`Position: x=${parts[1]}, y=${parts[2]}, z=${parts[3]}`);
207
+ break;
208
+ case "property":
209
+ console.debug(`Property: ${parts[1]} = ${parts[2]}`);
210
+ break;
211
+ default:
212
+ console.debug(`Unknown command: .${cmd}`);
213
+ }
214
+ }
215
+ /**
216
+ * Evaluate HoloScript code
217
+ */
218
+ async evaluate(code) {
219
+ try {
220
+ const parseResult = this.parser.parse(code);
221
+ if (parseResult.errors && parseResult.errors.length > 0) {
222
+ throw {
223
+ message: "Parse error",
224
+ errors: parseResult.errors
225
+ };
226
+ }
227
+ const result = await this.runtime.execute(parseResult.ast);
228
+ return result;
229
+ } catch (error) {
230
+ throw error;
231
+ }
232
+ }
233
+ /**
234
+ * Show variables
235
+ */
236
+ showVariables() {
237
+ if (this.variables.size === 0) {
238
+ console.debug("No variables defined");
239
+ return;
240
+ }
241
+ console.debug("\n\u{1F4E6} Variables:");
242
+ for (const [name, value] of this.variables) {
243
+ console.debug(` ${name}: ${this.formatValue(value)}`);
244
+ }
245
+ console.debug();
246
+ }
247
+ /**
248
+ * Show types
249
+ */
250
+ showTypes() {
251
+ if (this.types.size === 0) {
252
+ console.debug("No custom types defined");
253
+ return;
254
+ }
255
+ console.debug("\n\u{1F3F7}\uFE0F Types:");
256
+ for (const [name, type] of this.types) {
257
+ console.debug(` ${name}: ${JSON.stringify(type)}`);
258
+ }
259
+ console.debug();
260
+ }
261
+ /**
262
+ * Show performance profile
263
+ */
264
+ showProfile() {
265
+ console.debug("\n\u23F1\uFE0F Performance Profile:");
266
+ console.debug(" (Profiling data would be displayed here)");
267
+ console.debug();
268
+ }
269
+ /**
270
+ * Display result
271
+ */
272
+ displayResult(result) {
273
+ const formatted = this.formatValue(result);
274
+ console.debug(`=> ${formatted}`);
275
+ }
276
+ /**
277
+ * Format value for display
278
+ */
279
+ formatValue(value) {
280
+ if (value === null) return "null";
281
+ if (value === void 0) return "undefined";
282
+ if (typeof value === "string") return `"${value}"`;
283
+ if (typeof value === "number") return `${value}`;
284
+ if (typeof value === "boolean") return `${value}`;
285
+ if (Array.isArray(value)) {
286
+ return `[ ${value.map((v) => this.formatValue(v)).join(", ")} ]`;
287
+ }
288
+ if (typeof value === "object" && value !== null) {
289
+ const pairs = Object.entries(value).map(([k, v]) => `${k}: ${this.formatValue(v)}`).join(", ");
290
+ return `{ ${pairs} }`;
291
+ }
292
+ return String(value);
293
+ }
294
+ };
295
+ async function startREPL(parser, runtime) {
296
+ const repl = new HoloScriptREPL(parser, runtime);
297
+ await repl.start();
298
+ }
299
+ var HotReloadWatcher = class {
300
+ constructor() {
301
+ this.watchers = /* @__PURE__ */ new Map();
302
+ }
303
+ /**
304
+ * Watch file for changes
305
+ */
306
+ watch(filePath, callback) {
307
+ const fs = __require("fs");
308
+ fs.watchFile(filePath, { interval: 1e3 }, async () => {
309
+ try {
310
+ console.debug(`
311
+ \u{1F504} Reloading: ${filePath}`);
312
+ await callback();
313
+ console.debug("\u2705 Reload complete\n");
314
+ } catch (error) {
315
+ console.error(ErrorFormatter.formatError(error));
316
+ }
317
+ });
318
+ this.watchers.set(filePath, true);
319
+ }
320
+ /**
321
+ * Stop watching
322
+ */
323
+ unwatch(filePath) {
324
+ const fs = __require("fs");
325
+ fs.unwatchFile(filePath);
326
+ this.watchers.delete(filePath);
327
+ }
328
+ /**
329
+ * Stop all watchers
330
+ */
331
+ clear() {
332
+ const fs = __require("fs");
333
+ for (const filePath of this.watchers.keys()) {
334
+ fs.unwatchFile(filePath);
335
+ }
336
+ this.watchers.clear();
337
+ }
338
+ };
339
+ var SourceMapGenerator = class {
340
+ constructor() {
341
+ this.mappings = [];
342
+ }
343
+ /**
344
+ * Add mapping
345
+ */
346
+ addMapping(generatedLine, generatedColumn, sourceLine, sourceColumn, name) {
347
+ this.mappings.push({
348
+ generatedLine,
349
+ generatedColumn,
350
+ sourceLine,
351
+ sourceColumn,
352
+ name
353
+ });
354
+ }
355
+ /**
356
+ * Generate source map
357
+ */
358
+ generate(sourceFile, generatedFile) {
359
+ return {
360
+ version: 3,
361
+ file: generatedFile,
362
+ sourceRoot: "",
363
+ sources: [sourceFile],
364
+ sourcesContent: [],
365
+ mappings: this.encodeMappings(),
366
+ names: []
367
+ };
368
+ }
369
+ /**
370
+ * Encode mappings in VLQ format
371
+ */
372
+ encodeMappings() {
373
+ return this.mappings.map((m) => `${m.generatedLine}:${m.generatedColumn}->${m.sourceLine}:${m.sourceColumn}`).join(";");
374
+ }
375
+ };
376
+ var _MaterialEditor = class _MaterialEditor {
377
+ constructor(config = {}) {
378
+ this.materials = /* @__PURE__ */ new Map();
379
+ this.currentMaterialId = null;
380
+ this.container = null;
381
+ this.previewCanvas = null;
382
+ this.previewRenderer = null;
383
+ // THREE.WebGLRenderer
384
+ this.previewScene = null;
385
+ // THREE.Scene
386
+ this.previewCamera = null;
387
+ // THREE.Camera
388
+ this.previewSphere = null;
389
+ this.config = {
390
+ container: config.container,
391
+ livePreview: config.livePreview ?? true,
392
+ previewResolution: config.previewResolution ?? 512,
393
+ autoSave: config.autoSave ?? true,
394
+ debug: config.debug ?? false,
395
+ theme: config.theme ?? "dark"
396
+ };
397
+ if (this.config.container) {
398
+ this.initializeUI();
399
+ }
400
+ if (this.config.livePreview) {
401
+ this.initializePreview();
402
+ }
403
+ if (this.config.debug) {
404
+ console.debug("[MaterialEditor] Initialized", this.config);
405
+ }
406
+ }
407
+ /**
408
+ * Initialize editor UI
409
+ */
410
+ initializeUI() {
411
+ if (!this.config.container) return;
412
+ this.container = this.config.container;
413
+ this.container.innerHTML = this.generateEditorHTML();
414
+ this.previewCanvas = this.container.querySelector(
415
+ "#material-preview-canvas"
416
+ );
417
+ this.attachEventListeners();
418
+ this.updatePresetsList();
419
+ if (this.config.debug) {
420
+ console.debug("[MaterialEditor] UI initialized");
421
+ }
422
+ }
423
+ /**
424
+ * Generate editor HTML
425
+ */
426
+ generateEditorHTML() {
427
+ const theme = this.config.theme === "dark" ? "material-editor-dark" : "material-editor-light";
428
+ return `
429
+ <div class="material-editor ${theme}">
430
+ <!-- Preview Panel -->
431
+ <div class="preview-panel">
432
+ <div class="panel-header">
433
+ <h3>Preview</h3>
434
+ <div class="preview-controls">
435
+ <button class="preview-btn" data-action="rotate" title="Rotate">\u{1F504}</button>
436
+ <button class="preview-btn" data-action="reset" title="Reset View">\u21BB</button>
437
+ </div>
438
+ </div>
439
+ <div class="preview-content">
440
+ <canvas id="material-preview-canvas"></canvas>
441
+ </div>
442
+ </div>
443
+
444
+ <!-- Properties Panel -->
445
+ <div class="properties-panel">
446
+ <div class="panel-header">
447
+ <h3>Material Properties</h3>
448
+ </div>
449
+ <div class="properties-content">
450
+ <div class="property-group">
451
+ <label>Material Type</label>
452
+ <select id="material-type" class="property-select">
453
+ <option value="standard">Standard</option>
454
+ <option value="physical">Physical</option>
455
+ <option value="metal">Metal</option>
456
+ <option value="glass">Glass</option>
457
+ <option value="emissive">Emissive</option>
458
+ <option value="toon">Toon</option>
459
+ </select>
460
+ </div>
461
+
462
+ <div class="property-group">
463
+ <label>
464
+ Base Color
465
+ <span class="value-display" id="color-value">#888888</span>
466
+ </label>
467
+ <input type="color" id="base-color" class="color-input" value="#888888">
468
+ </div>
469
+
470
+ <div class="property-group">
471
+ <label>
472
+ Metalness
473
+ <span class="value-display" id="metalness-value">0.5</span>
474
+ </label>
475
+ <input type="range" id="metalness" min="0" max="1" step="0.01" value="0.5" class="property-slider">
476
+ </div>
477
+
478
+ <div class="property-group">
479
+ <label>
480
+ Roughness
481
+ <span class="value-display" id="roughness-value">0.5</span>
482
+ </label>
483
+ <input type="range" id="roughness" min="0" max="1" step="0.01" value="0.5" class="property-slider">
484
+ </div>
485
+
486
+ <div class="property-group">
487
+ <label>
488
+ Opacity
489
+ <span class="value-display" id="opacity-value">1.0</span>
490
+ </label>
491
+ <input type="range" id="opacity" min="0" max="1" step="0.01" value="1.0" class="property-slider">
492
+ </div>
493
+
494
+ <div class="property-group" id="emissive-group" style="display: none;">
495
+ <label>
496
+ Emissive Color
497
+ <span class="value-display" id="emissive-color-value">#00ff00</span>
498
+ </label>
499
+ <input type="color" id="emissive-color" class="color-input" value="#00ff00">
500
+ </div>
501
+
502
+ <div class="property-group" id="emissive-intensity-group" style="display: none;">
503
+ <label>
504
+ Emissive Intensity
505
+ <span class="value-display" id="emissive-intensity-value">1.0</span>
506
+ </label>
507
+ <input type="range" id="emissive-intensity" min="0" max="5" step="0.1" value="1.0" class="property-slider">
508
+ </div>
509
+
510
+ <div class="property-group">
511
+ <button id="save-material-btn" class="action-btn">Save Material</button>
512
+ </div>
513
+ </div>
514
+ </div>
515
+
516
+ <!-- Presets Panel -->
517
+ <div class="presets-panel">
518
+ <div class="panel-header">
519
+ <h3>Material Presets</h3>
520
+ </div>
521
+ <div class="presets-content" id="presets-list">
522
+ <!-- Populated dynamically -->
523
+ </div>
524
+ </div>
525
+ </div>
526
+
527
+ <style>
528
+ .material-editor {
529
+ display: grid;
530
+ grid-template-columns: 1fr 350px;
531
+ grid-template-rows: 1fr 200px;
532
+ gap: 1px;
533
+ width: 100%;
534
+ height: 100%;
535
+ background: rgba(255, 255, 255, 0.05);
536
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
537
+ }
538
+
539
+ .material-editor-dark {
540
+ background: #1a1a1a;
541
+ color: #e0e0e0;
542
+ }
543
+
544
+ .material-editor-light {
545
+ background: #f5f5f5;
546
+ color: #333;
547
+ }
548
+
549
+ .preview-panel {
550
+ grid-column: 1;
551
+ grid-row: 1 / 3;
552
+ display: flex;
553
+ flex-direction: column;
554
+ background: rgba(30, 30, 30, 0.95);
555
+ border: 1px solid rgba(255, 255, 255, 0.1);
556
+ }
557
+
558
+ .properties-panel {
559
+ grid-column: 2;
560
+ grid-row: 1;
561
+ display: flex;
562
+ flex-direction: column;
563
+ background: rgba(30, 30, 30, 0.95);
564
+ border: 1px solid rgba(255, 255, 255, 0.1);
565
+ }
566
+
567
+ .presets-panel {
568
+ grid-column: 2;
569
+ grid-row: 2;
570
+ display: flex;
571
+ flex-direction: column;
572
+ background: rgba(30, 30, 30, 0.95);
573
+ border: 1px solid rgba(255, 255, 255, 0.1);
574
+ }
575
+
576
+ .panel-header {
577
+ display: flex;
578
+ justify-content: space-between;
579
+ align-items: center;
580
+ padding: 10px 15px;
581
+ background: rgba(0, 0, 0, 0.2);
582
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
583
+ }
584
+
585
+ .panel-header h3 {
586
+ margin: 0;
587
+ font-size: 14px;
588
+ font-weight: 600;
589
+ color: #60a5fa;
590
+ }
591
+
592
+ .preview-controls {
593
+ display: flex;
594
+ gap: 5px;
595
+ }
596
+
597
+ .preview-btn {
598
+ padding: 4px 8px;
599
+ background: rgba(96, 165, 250, 0.1);
600
+ border: 1px solid rgba(96, 165, 250, 0.3);
601
+ border-radius: 4px;
602
+ color: #60a5fa;
603
+ cursor: pointer;
604
+ font-size: 14px;
605
+ }
606
+
607
+ .preview-btn:hover {
608
+ background: rgba(96, 165, 250, 0.2);
609
+ }
610
+
611
+ .preview-content {
612
+ flex: 1;
613
+ display: flex;
614
+ align-items: center;
615
+ justify-content: center;
616
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
617
+ padding: 20px;
618
+ }
619
+
620
+ #material-preview-canvas {
621
+ max-width: 100%;
622
+ max-height: 100%;
623
+ border-radius: 8px;
624
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
625
+ }
626
+
627
+ .properties-content {
628
+ flex: 1;
629
+ overflow-y: auto;
630
+ padding: 15px;
631
+ }
632
+
633
+ .property-group {
634
+ margin-bottom: 20px;
635
+ }
636
+
637
+ .property-group label {
638
+ display: block;
639
+ font-size: 13px;
640
+ margin-bottom: 8px;
641
+ color: #cbd5e0;
642
+ font-weight: 500;
643
+ }
644
+
645
+ .value-display {
646
+ float: right;
647
+ color: #60a5fa;
648
+ font-size: 12px;
649
+ font-weight: 600;
650
+ }
651
+
652
+ .property-slider {
653
+ width: 100%;
654
+ height: 6px;
655
+ border-radius: 3px;
656
+ background: rgba(96, 165, 250, 0.2);
657
+ outline: none;
658
+ -webkit-appearance: none;
659
+ }
660
+
661
+ .property-slider::-webkit-slider-thumb {
662
+ -webkit-appearance: none;
663
+ width: 16px;
664
+ height: 16px;
665
+ border-radius: 50%;
666
+ background: #60a5fa;
667
+ cursor: pointer;
668
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
669
+ }
670
+
671
+ .property-select {
672
+ width: 100%;
673
+ padding: 8px;
674
+ background: rgba(0, 0, 0, 0.3);
675
+ border: 1px solid rgba(255, 255, 255, 0.2);
676
+ border-radius: 4px;
677
+ color: #fff;
678
+ font-size: 13px;
679
+ }
680
+
681
+ .color-input {
682
+ width: 100%;
683
+ height: 40px;
684
+ padding: 0;
685
+ border: 1px solid rgba(255, 255, 255, 0.2);
686
+ border-radius: 4px;
687
+ background: transparent;
688
+ cursor: pointer;
689
+ }
690
+
691
+ .action-btn {
692
+ width: 100%;
693
+ padding: 10px;
694
+ background: rgba(34, 197, 94, 0.2);
695
+ border: 1px solid rgba(34, 197, 94, 0.4);
696
+ border-radius: 6px;
697
+ color: #4ade80;
698
+ cursor: pointer;
699
+ font-size: 13px;
700
+ font-weight: 500;
701
+ transition: all 0.2s;
702
+ }
703
+
704
+ .action-btn:hover {
705
+ background: rgba(34, 197, 94, 0.3);
706
+ border-color: rgba(34, 197, 94, 0.6);
707
+ }
708
+
709
+ .presets-content {
710
+ padding: 10px;
711
+ overflow-y: auto;
712
+ }
713
+
714
+ .presets-grid {
715
+ display: grid;
716
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
717
+ gap: 10px;
718
+ }
719
+
720
+ .preset-card {
721
+ padding: 10px;
722
+ background: rgba(0, 0, 0, 0.3);
723
+ border: 1px solid rgba(255, 255, 255, 0.1);
724
+ border-radius: 6px;
725
+ cursor: pointer;
726
+ transition: all 0.2s;
727
+ text-align: center;
728
+ }
729
+
730
+ .preset-card:hover {
731
+ background: rgba(96, 165, 250, 0.1);
732
+ border-color: rgba(96, 165, 250, 0.3);
733
+ }
734
+
735
+ .preset-color {
736
+ width: 100%;
737
+ height: 50px;
738
+ border-radius: 4px;
739
+ margin-bottom: 8px;
740
+ border: 1px solid rgba(255, 255, 255, 0.2);
741
+ }
742
+
743
+ .preset-name {
744
+ font-size: 11px;
745
+ color: #94a3b8;
746
+ }
747
+ </style>
748
+ `;
749
+ }
750
+ /**
751
+ * Initialize 3D preview
752
+ */
753
+ initializePreview() {
754
+ const THREE = globalThis.THREE;
755
+ if (!this.previewCanvas || !THREE) return;
756
+ this.previewScene = new THREE.Scene();
757
+ this.previewScene.background = null;
758
+ this.previewCamera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
759
+ this.previewCamera.position.set(0, 0, 5);
760
+ this.previewRenderer = new THREE.WebGLRenderer({
761
+ canvas: this.previewCanvas,
762
+ alpha: true,
763
+ antialias: true
764
+ });
765
+ this.previewRenderer.setSize(this.config.previewResolution, this.config.previewResolution);
766
+ this.previewRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
767
+ const geometry = new THREE.SphereGeometry(1.5, 64, 64);
768
+ const material = new THREE.MeshStandardMaterial({
769
+ color: 8947848,
770
+ metalness: 0.5,
771
+ roughness: 0.5
772
+ });
773
+ this.previewSphere = new THREE.Mesh(geometry, material);
774
+ this.previewScene.add(this.previewSphere);
775
+ const ambientLight = new THREE.AmbientLight(16777215, 0.4);
776
+ this.previewScene.add(ambientLight);
777
+ const directionalLight = new THREE.DirectionalLight(16777215, 1);
778
+ directionalLight.position.set(5, 5, 5);
779
+ this.previewScene.add(directionalLight);
780
+ const rimLight = new THREE.DirectionalLight(6333946, 0.5);
781
+ rimLight.position.set(-5, 0, -5);
782
+ this.previewScene.add(rimLight);
783
+ this.animatePreview();
784
+ if (this.config.debug) {
785
+ console.debug("[MaterialEditor] Preview initialized");
786
+ }
787
+ }
788
+ /**
789
+ * Animate preview sphere
790
+ */
791
+ animatePreview() {
792
+ if (!this.previewRenderer || !this.previewScene || !this.previewCamera) return;
793
+ const animate = () => {
794
+ requestAnimationFrame(animate);
795
+ if (this.previewSphere) {
796
+ this.previewSphere.rotation[1] += 5e-3;
797
+ }
798
+ this.previewRenderer.render(this.previewScene, this.previewCamera);
799
+ };
800
+ animate();
801
+ }
802
+ /**
803
+ * Attach event listeners
804
+ */
805
+ attachEventListeners() {
806
+ if (!this.container) return;
807
+ const typeSelect = this.container.querySelector("#material-type");
808
+ typeSelect?.addEventListener("change", (e) => {
809
+ const type = e.target.value;
810
+ this.updateMaterialType(type);
811
+ });
812
+ const colorInput = this.container.querySelector("#base-color");
813
+ colorInput?.addEventListener("input", (e) => {
814
+ const color = e.target.value;
815
+ document.getElementById("color-value").textContent = color;
816
+ this.updatePreviewMaterial({ color });
817
+ });
818
+ const metalnessInput = this.container.querySelector("#metalness");
819
+ metalnessInput?.addEventListener("input", (e) => {
820
+ const value = parseFloat(e.target.value);
821
+ document.getElementById("metalness-value").textContent = value.toFixed(2);
822
+ this.updatePreviewMaterial({ metalness: value });
823
+ });
824
+ const roughnessInput = this.container.querySelector("#roughness");
825
+ roughnessInput?.addEventListener("input", (e) => {
826
+ const value = parseFloat(e.target.value);
827
+ document.getElementById("roughness-value").textContent = value.toFixed(2);
828
+ this.updatePreviewMaterial({ roughness: value });
829
+ });
830
+ const opacityInput = this.container.querySelector("#opacity");
831
+ opacityInput?.addEventListener("input", (e) => {
832
+ const value = parseFloat(e.target.value);
833
+ document.getElementById("opacity-value").textContent = value.toFixed(2);
834
+ this.updatePreviewMaterial({ opacity: value, transparent: value < 1 });
835
+ });
836
+ const emissiveColorInput = this.container.querySelector("#emissive-color");
837
+ emissiveColorInput?.addEventListener("input", (e) => {
838
+ const color = e.target.value;
839
+ document.getElementById("emissive-color-value").textContent = color;
840
+ this.updatePreviewMaterial({ emissive: color });
841
+ });
842
+ const emissiveIntensityInput = this.container.querySelector(
843
+ "#emissive-intensity"
844
+ );
845
+ emissiveIntensityInput?.addEventListener("input", (e) => {
846
+ const value = parseFloat(e.target.value);
847
+ document.getElementById("emissive-intensity-value").textContent = value.toFixed(1);
848
+ this.updatePreviewMaterial({ emissiveIntensity: value });
849
+ });
850
+ const saveBtn = this.container.querySelector("#save-material-btn");
851
+ saveBtn?.addEventListener("click", () => {
852
+ this.saveMaterial();
853
+ });
854
+ }
855
+ /**
856
+ * Update material type
857
+ */
858
+ updateMaterialType(type) {
859
+ const emissiveGroup = document.getElementById("emissive-group");
860
+ const emissiveIntensityGroup = document.getElementById("emissive-intensity-group");
861
+ if (type === "emissive") {
862
+ if (emissiveGroup) emissiveGroup.style.display = "block";
863
+ if (emissiveIntensityGroup) emissiveIntensityGroup.style.display = "block";
864
+ } else {
865
+ if (emissiveGroup) emissiveGroup.style.display = "none";
866
+ if (emissiveIntensityGroup) emissiveIntensityGroup.style.display = "none";
867
+ }
868
+ if (this.config.debug) {
869
+ console.debug("[MaterialEditor] Material type changed:", type);
870
+ }
871
+ }
872
+ /**
873
+ * Update preview material (THREE.js sphere).
874
+ * Accepts hex color strings for THREE.js Color.set() compatibility.
875
+ */
876
+ updatePreviewMaterial(params) {
877
+ if (!this.previewSphere) return;
878
+ const material = this.previewSphere.material;
879
+ if (params.color !== void 0) {
880
+ material.color.set(params.color);
881
+ }
882
+ if (params.metalness !== void 0) {
883
+ material.metalness = params.metalness;
884
+ }
885
+ if (params.roughness !== void 0) {
886
+ material.roughness = params.roughness;
887
+ }
888
+ if (params.opacity !== void 0) {
889
+ material.opacity = params.opacity;
890
+ }
891
+ if (params.transparent !== void 0) {
892
+ material.transparent = params.transparent;
893
+ }
894
+ if (params.emissive !== void 0) {
895
+ material.emissive.set(params.emissive);
896
+ }
897
+ if (params.emissiveIntensity !== void 0) {
898
+ material.emissiveIntensity = params.emissiveIntensity;
899
+ }
900
+ material.needsUpdate = true;
901
+ }
902
+ /**
903
+ * Save current material.
904
+ * Returns a fully-formed MaterialDef (the unified PBR type).
905
+ */
906
+ saveMaterial() {
907
+ const id = `material_${Date.now()}`;
908
+ const material = this.getCurrentMaterial();
909
+ material.id = id;
910
+ this.materials.set(id, material);
911
+ if (this.config.debug) {
912
+ console.debug("[MaterialEditor] Material saved:", material);
913
+ }
914
+ return material;
915
+ }
916
+ /**
917
+ * Get current material configuration from the editor UI.
918
+ * Reads hex color values from DOM inputs and converts to RGBA objects
919
+ * for the unified MaterialDef type.
920
+ */
921
+ getCurrentMaterial() {
922
+ const typeSelect = document.getElementById("material-type");
923
+ const colorInput = document.getElementById("base-color");
924
+ const metalnessInput = document.getElementById("metalness");
925
+ const roughnessInput = document.getElementById("roughness");
926
+ const opacityInput = document.getElementById("opacity");
927
+ const emissiveColorInput = document.getElementById("emissive-color");
928
+ const emissiveIntensityInput = document.getElementById(
929
+ "emissive-intensity"
930
+ );
931
+ const materialType = typeSelect?.value || "standard";
932
+ const colorHex = colorInput?.value || "#888888";
933
+ const albedo = hexToRGBA(colorHex);
934
+ const opacity = parseFloat(opacityInput?.value || "1.0");
935
+ albedo.a = opacity;
936
+ const material = createDefaultMaterialDef("", "Custom Material", materialType);
937
+ material.albedo = albedo;
938
+ material.metallic = parseFloat(metalnessInput?.value || "0.5");
939
+ material.roughness = parseFloat(roughnessInput?.value || "0.5");
940
+ if (opacity < 1) {
941
+ material.blendMode = "transparent";
942
+ }
943
+ if (materialType === "emissive") {
944
+ const emissiveHex = emissiveColorInput?.value || "#00ff00";
945
+ const emissiveRgba = hexToRGBA(emissiveHex);
946
+ material.emission = { r: emissiveRgba.r, g: emissiveRgba.g, b: emissiveRgba.b };
947
+ material.emissionStrength = parseFloat(emissiveIntensityInput?.value || "1.0");
948
+ }
949
+ return material;
950
+ }
951
+ /**
952
+ * Load a MaterialDef into the editor UI.
953
+ * Converts RGBA objects back to hex strings for DOM color inputs.
954
+ */
955
+ loadMaterial(material) {
956
+ this.currentMaterialId = material.id;
957
+ const colorHex = rgbaToHex(material.albedo);
958
+ const emissiveHex = rgbaToHex(material.emission);
959
+ const typeSelect = document.getElementById("material-type");
960
+ const colorInput = document.getElementById("base-color");
961
+ const metalnessInput = document.getElementById("metalness");
962
+ const roughnessInput = document.getElementById("roughness");
963
+ const opacityInput = document.getElementById("opacity");
964
+ const emissiveColorInput = document.getElementById("emissive-color");
965
+ const emissiveIntensityInput = document.getElementById(
966
+ "emissive-intensity"
967
+ );
968
+ if (typeSelect) typeSelect.value = material.materialType || "standard";
969
+ if (colorInput) colorInput.value = colorHex;
970
+ if (metalnessInput) metalnessInput.value = material.metallic.toString();
971
+ if (roughnessInput) roughnessInput.value = material.roughness.toString();
972
+ if (opacityInput) opacityInput.value = (material.albedo.a ?? 1).toString();
973
+ if (emissiveColorInput) {
974
+ emissiveColorInput.value = emissiveHex;
975
+ }
976
+ if (emissiveIntensityInput) {
977
+ emissiveIntensityInput.value = material.emissionStrength.toString();
978
+ }
979
+ document.getElementById("color-value").textContent = colorHex;
980
+ document.getElementById("metalness-value").textContent = material.metallic.toFixed(2);
981
+ document.getElementById("roughness-value").textContent = material.roughness.toFixed(2);
982
+ document.getElementById("opacity-value").textContent = (material.albedo.a ?? 1).toFixed(2);
983
+ if (material.emissionStrength > 0) {
984
+ document.getElementById("emissive-color-value").textContent = emissiveHex;
985
+ document.getElementById("emissive-intensity-value").textContent = material.emissionStrength.toFixed(1);
986
+ }
987
+ this.updateMaterialType(material.materialType || "standard");
988
+ this.updatePreviewMaterial({
989
+ color: colorHex,
990
+ metalness: material.metallic,
991
+ roughness: material.roughness,
992
+ opacity: material.albedo.a ?? 1,
993
+ transparent: material.blendMode === "transparent",
994
+ emissive: emissiveHex,
995
+ emissiveIntensity: material.emissionStrength
996
+ });
997
+ if (this.config.debug) {
998
+ console.debug("[MaterialEditor] Material loaded:", material.name);
999
+ }
1000
+ }
1001
+ /**
1002
+ * Load a preset by name.
1003
+ * Merges preset overrides with default MaterialDef values.
1004
+ */
1005
+ loadPreset(presetName) {
1006
+ const preset = _MaterialEditor.PRESETS.find((p) => p.name === presetName);
1007
+ if (!preset) return;
1008
+ const material = {
1009
+ ...createDefaultMaterialDef(
1010
+ `preset_${presetName.toLowerCase()}`,
1011
+ presetName,
1012
+ preset.material.materialType
1013
+ ),
1014
+ ...preset.material,
1015
+ id: `preset_${presetName.toLowerCase()}`,
1016
+ name: presetName
1017
+ };
1018
+ this.loadMaterial(material);
1019
+ if (this.config.debug) {
1020
+ console.debug("[MaterialEditor] Preset loaded:", presetName);
1021
+ }
1022
+ }
1023
+ /**
1024
+ * Update presets list
1025
+ */
1026
+ updatePresetsList() {
1027
+ const presetsList = document.getElementById("presets-list");
1028
+ if (!presetsList) return;
1029
+ let html = '<div class="presets-grid">';
1030
+ for (const preset of _MaterialEditor.PRESETS) {
1031
+ html += `
1032
+ <div class="preset-card" data-preset="${preset.name}">
1033
+ <div class="preset-color" style="background: ${preset.previewColor};"></div>
1034
+ <div class="preset-name">${preset.name}</div>
1035
+ </div>
1036
+ `;
1037
+ }
1038
+ html += "</div>";
1039
+ presetsList.innerHTML = html;
1040
+ presetsList.querySelectorAll(".preset-card").forEach((card) => {
1041
+ card.addEventListener("click", (e) => {
1042
+ const presetName = e.currentTarget.getAttribute("data-preset");
1043
+ if (presetName) {
1044
+ this.loadPreset(presetName);
1045
+ }
1046
+ });
1047
+ });
1048
+ }
1049
+ /**
1050
+ * Get all materials (returns unified MaterialDef objects)
1051
+ */
1052
+ getMaterials() {
1053
+ return Array.from(this.materials.values());
1054
+ }
1055
+ /**
1056
+ * Get presets
1057
+ */
1058
+ static getPresets() {
1059
+ return _MaterialEditor.PRESETS;
1060
+ }
1061
+ /**
1062
+ * Dispose editor
1063
+ */
1064
+ dispose() {
1065
+ if (this.previewRenderer) {
1066
+ this.previewRenderer.dispose();
1067
+ this.previewRenderer = null;
1068
+ }
1069
+ this.materials.clear();
1070
+ if (this.config.debug) {
1071
+ console.debug("[MaterialEditor] Disposed");
1072
+ }
1073
+ }
1074
+ };
1075
+ // THREE.Mesh
1076
+ // Material presets (using unified MaterialDef schema)
1077
+ _MaterialEditor.PRESETS = [
1078
+ {
1079
+ name: "Concrete",
1080
+ category: "Construction",
1081
+ material: {
1082
+ materialType: "standard",
1083
+ albedo: { r: 0.502, g: 0.502, b: 0.502, a: 1 },
1084
+ metallic: 0,
1085
+ roughness: 0.9
1086
+ },
1087
+ previewColor: "#808080"
1088
+ },
1089
+ {
1090
+ name: "Metal",
1091
+ category: "Metal",
1092
+ material: {
1093
+ materialType: "metal",
1094
+ albedo: { r: 0.533, g: 0.533, b: 0.533, a: 1 },
1095
+ metallic: 1,
1096
+ roughness: 0.2
1097
+ },
1098
+ previewColor: "#888888"
1099
+ },
1100
+ {
1101
+ name: "Gold",
1102
+ category: "Metal",
1103
+ material: {
1104
+ materialType: "metal",
1105
+ albedo: { r: 1, g: 0.843, b: 0, a: 1 },
1106
+ metallic: 1,
1107
+ roughness: 0.15
1108
+ },
1109
+ previewColor: "#ffd700"
1110
+ },
1111
+ {
1112
+ name: "Copper",
1113
+ category: "Metal",
1114
+ material: {
1115
+ materialType: "metal",
1116
+ albedo: { r: 0.722, g: 0.451, b: 0.2, a: 1 },
1117
+ metallic: 1,
1118
+ roughness: 0.25
1119
+ },
1120
+ previewColor: "#b87333"
1121
+ },
1122
+ {
1123
+ name: "Chrome",
1124
+ category: "Metal",
1125
+ material: {
1126
+ materialType: "metal",
1127
+ albedo: { r: 0.8, g: 0.8, b: 0.8, a: 1 },
1128
+ metallic: 1,
1129
+ roughness: 0.05
1130
+ },
1131
+ previewColor: "#cccccc"
1132
+ },
1133
+ {
1134
+ name: "Wood",
1135
+ category: "Natural",
1136
+ material: {
1137
+ materialType: "standard",
1138
+ albedo: { r: 0.545, g: 0.435, b: 0.278, a: 1 },
1139
+ metallic: 0,
1140
+ roughness: 0.7
1141
+ },
1142
+ previewColor: "#8b6f47"
1143
+ },
1144
+ {
1145
+ name: "Plastic",
1146
+ category: "Synthetic",
1147
+ material: {
1148
+ materialType: "physical",
1149
+ albedo: { r: 1, g: 0.42, b: 0.42, a: 1 },
1150
+ metallic: 0,
1151
+ roughness: 0.4
1152
+ },
1153
+ previewColor: "#ff6b6b"
1154
+ },
1155
+ {
1156
+ name: "Glass",
1157
+ category: "Transparent",
1158
+ material: {
1159
+ materialType: "glass",
1160
+ albedo: { r: 1, g: 1, b: 1, a: 0.3 },
1161
+ metallic: 0,
1162
+ roughness: 0,
1163
+ blendMode: "transparent",
1164
+ doubleSided: true
1165
+ },
1166
+ previewColor: "rgba(255, 255, 255, 0.3)"
1167
+ },
1168
+ {
1169
+ name: "Rubber",
1170
+ category: "Synthetic",
1171
+ material: {
1172
+ materialType: "standard",
1173
+ albedo: { r: 0.2, g: 0.2, b: 0.2, a: 1 },
1174
+ metallic: 0,
1175
+ roughness: 0.8
1176
+ },
1177
+ previewColor: "#333333"
1178
+ },
1179
+ {
1180
+ name: "Ceramic",
1181
+ category: "Natural",
1182
+ material: {
1183
+ materialType: "physical",
1184
+ albedo: { r: 0.941, g: 0.941, b: 0.941, a: 1 },
1185
+ metallic: 0,
1186
+ roughness: 0.3
1187
+ },
1188
+ previewColor: "#f0f0f0"
1189
+ },
1190
+ {
1191
+ name: "Neon",
1192
+ category: "Emissive",
1193
+ material: {
1194
+ materialType: "emissive",
1195
+ albedo: { r: 0, g: 1, b: 0, a: 1 },
1196
+ emission: { r: 0, g: 1, b: 0 },
1197
+ emissionStrength: 2
1198
+ },
1199
+ previewColor: "#00ff00"
1200
+ },
1201
+ {
1202
+ name: "Glow",
1203
+ category: "Emissive",
1204
+ material: {
1205
+ materialType: "emissive",
1206
+ albedo: { r: 0.376, g: 0.647, b: 0.98, a: 1 },
1207
+ emission: { r: 0.376, g: 0.647, b: 0.98 },
1208
+ emissionStrength: 1.5
1209
+ },
1210
+ previewColor: "#60a5fa"
1211
+ }
1212
+ ];
1213
+ var MaterialEditor = _MaterialEditor;
1214
+
1215
+ // src/tools/materialPresetQuickPick.ts
1216
+ function getMaterialEditorBuiltinPresets() {
1217
+ return MaterialEditor.getPresets();
1218
+ }
1219
+ function listMaterialEditorQuickPickPresetNames() {
1220
+ return MaterialEditor.getPresets().map((p) => p.name);
1221
+ }
1222
+ function listMaterialEditorQuickPickPresetsByCategory() {
1223
+ const m = /* @__PURE__ */ new Map();
1224
+ for (const p of MaterialEditor.getPresets()) {
1225
+ const list = m.get(p.category) ?? [];
1226
+ list.push(p.name);
1227
+ m.set(p.category, list);
1228
+ }
1229
+ return m;
1230
+ }
1231
+
1232
+ // src/tools/SceneInspector.ts
1233
+ var SceneInspector = class {
1234
+ constructor(config = {}) {
1235
+ this.composition = null;
1236
+ this.renderer = null;
1237
+ this.entities = /* @__PURE__ */ new Map();
1238
+ this.selectedEntityId = null;
1239
+ this.performanceHistory = [];
1240
+ this.currentFrame = 0;
1241
+ this.lastUpdateTime = 0;
1242
+ this.physicsVisualization = {
1243
+ showBounds: false,
1244
+ showVelocity: false,
1245
+ showForces: false,
1246
+ showContacts: false,
1247
+ showCenterOfMass: false
1248
+ };
1249
+ this.config = {
1250
+ enabled: config.enabled ?? true,
1251
+ showPerformance: config.showPerformance ?? true,
1252
+ showHierarchy: config.showHierarchy ?? true,
1253
+ showProperties: config.showProperties ?? true,
1254
+ showStatistics: config.showStatistics ?? true,
1255
+ performanceHistorySize: config.performanceHistorySize ?? 300,
1256
+ updateFrequency: config.updateFrequency ?? 10
1257
+ // 10 Hz
1258
+ };
1259
+ }
1260
+ /**
1261
+ * Initialize inspector with composition
1262
+ */
1263
+ initialize(composition, renderer) {
1264
+ this.composition = composition;
1265
+ this.renderer = renderer || null;
1266
+ this.buildEntityHierarchy();
1267
+ console.debug("[SceneInspector] Initialized with", this.entities.size, "entities");
1268
+ }
1269
+ /**
1270
+ * Build entity hierarchy from composition
1271
+ */
1272
+ buildEntityHierarchy() {
1273
+ if (!this.composition) return;
1274
+ this.entities.clear();
1275
+ for (const obj of this.composition.objects || []) {
1276
+ const getProp = (key) => obj.properties?.find((p) => p.key === key)?.value;
1277
+ const pos = getProp("position");
1278
+ const rot = getProp("rotation");
1279
+ const scl = getProp("scale");
1280
+ const inspectorEntity = {
1281
+ id: obj.name,
1282
+ name: obj.name,
1283
+ type: obj.type || "entity",
1284
+ parentId: null,
1285
+ // HoloScript doesn't have hierarchy yet, all root-level
1286
+ childrenIds: [],
1287
+ transform: {
1288
+ // @ts-expect-error
1289
+ position: pos ? [pos[0], pos[1], pos[2]] : [0, 0, 0],
1290
+ // @ts-expect-error
1291
+ rotation: rot ? [rot[0], rot[1], rot[2]] : [0, 0, 0],
1292
+ // @ts-expect-error
1293
+ scale: scl ? [scl[0], scl[1], scl[2]] : [1, 1, 1]
1294
+ },
1295
+ properties: this.extractProperties(obj),
1296
+ // @ts-expect-error
1297
+ traits: obj.traits?.map((t) => t.name) || [],
1298
+ visible: true,
1299
+ active: true
1300
+ };
1301
+ this.entities.set(inspectorEntity.id, inspectorEntity);
1302
+ }
1303
+ }
1304
+ /**
1305
+ * Extract properties from entity
1306
+ */
1307
+ extractProperties(entity) {
1308
+ const props = {};
1309
+ for (const prop of entity.properties || []) {
1310
+ if (["geometry", "material"].includes(prop.key)) {
1311
+ props[prop.key] = prop.value;
1312
+ }
1313
+ }
1314
+ for (const trait of entity.traits || []) {
1315
+ if (trait.config) {
1316
+ props[trait.name] = trait.config;
1317
+ }
1318
+ }
1319
+ return props;
1320
+ }
1321
+ /**
1322
+ * Update inspector (call per frame)
1323
+ */
1324
+ update(deltaTime) {
1325
+ if (!this.config.enabled) return;
1326
+ this.currentFrame++;
1327
+ const now = performance.now();
1328
+ if (now - this.lastUpdateTime >= 1e3 / this.config.updateFrequency) {
1329
+ this.recordPerformanceFrame(deltaTime);
1330
+ this.lastUpdateTime = now;
1331
+ }
1332
+ }
1333
+ /**
1334
+ * Record performance frame
1335
+ */
1336
+ recordPerformanceFrame(deltaTime) {
1337
+ const stats = this.renderer?.getStatistics();
1338
+ const frame = {
1339
+ frameNumber: this.currentFrame,
1340
+ frameTime: deltaTime * 1e3,
1341
+ // Convert to ms
1342
+ fps: stats?.fps || 0,
1343
+ physicsTime: 0,
1344
+ // Would come from physics engine
1345
+ renderTime: deltaTime * 1e3 * 0.6,
1346
+ // Estimate
1347
+ syncTime: deltaTime * 1e3 * 0.1,
1348
+ // Estimate
1349
+ timestamp: performance.now()
1350
+ };
1351
+ this.performanceHistory.push(frame);
1352
+ if (this.performanceHistory.length > this.config.performanceHistorySize) {
1353
+ this.performanceHistory.shift();
1354
+ }
1355
+ }
1356
+ /**
1357
+ * Get entity hierarchy (for tree view)
1358
+ */
1359
+ getEntityHierarchy() {
1360
+ return Array.from(this.entities.values());
1361
+ }
1362
+ /**
1363
+ * Get entity by ID
1364
+ */
1365
+ getEntity(id) {
1366
+ return this.entities.get(id);
1367
+ }
1368
+ /**
1369
+ * Select entity
1370
+ */
1371
+ selectEntity(id) {
1372
+ if (this.entities.has(id)) {
1373
+ this.selectedEntityId = id;
1374
+ console.debug("[SceneInspector] Selected entity:", id);
1375
+ }
1376
+ }
1377
+ /**
1378
+ * Get selected entity
1379
+ */
1380
+ getSelectedEntity() {
1381
+ return this.selectedEntityId ? this.entities.get(this.selectedEntityId) || null : null;
1382
+ }
1383
+ /**
1384
+ * Update entity property (live editing)
1385
+ */
1386
+ updateEntityProperty(id, path, value) {
1387
+ const entity = this.entities.get(id);
1388
+ if (!entity) return false;
1389
+ const parts = path.split(".");
1390
+ let target = entity;
1391
+ for (let i = 0; i < parts.length - 1; i++) {
1392
+ target = target[parts[i]];
1393
+ if (!target) return false;
1394
+ }
1395
+ const lastKey = parts[parts.length - 1];
1396
+ target[lastKey] = value;
1397
+ console.debug("[SceneInspector] Updated", id, path, "=", value);
1398
+ return true;
1399
+ }
1400
+ /**
1401
+ * Get scene statistics
1402
+ */
1403
+ getStatistics() {
1404
+ const rendererStats = this.renderer?.getStatistics();
1405
+ const recentFrames = this.performanceHistory.slice(-60);
1406
+ const fpsList = recentFrames.map((f) => f.fps).filter((fps) => fps > 0);
1407
+ const averageFPS = fpsList.length > 0 ? fpsList.reduce((a, b) => a + b, 0) / fpsList.length : 0;
1408
+ const minFPS = fpsList.length > 0 ? Math.min(...fpsList) : 0;
1409
+ const maxFPS = fpsList.length > 0 ? Math.max(...fpsList) : 0;
1410
+ return {
1411
+ totalEntities: this.entities.size,
1412
+ activeEntities: Array.from(this.entities.values()).filter((e) => e.active).length,
1413
+ visibleEntities: Array.from(this.entities.values()).filter((e) => e.visible).length,
1414
+ totalVertices: 0,
1415
+ // Would come from renderer
1416
+ totalTriangles: rendererStats?.triangles || 0,
1417
+ drawCalls: rendererStats?.drawCalls || 0,
1418
+ textureMemory: (rendererStats?.memoryUsage?.textures || 0) / (1024 * 1024),
1419
+ geometryMemory: (rendererStats?.memoryUsage?.geometries || 0) / (1024 * 1024),
1420
+ currentFPS: rendererStats?.fps || 0,
1421
+ averageFPS: Math.round(averageFPS),
1422
+ minFPS: Math.round(minFPS),
1423
+ maxFPS: Math.round(maxFPS)
1424
+ };
1425
+ }
1426
+ /**
1427
+ * Get performance history
1428
+ */
1429
+ getPerformanceHistory() {
1430
+ return this.performanceHistory;
1431
+ }
1432
+ /**
1433
+ * Toggle physics visualization
1434
+ */
1435
+ togglePhysicsVisualization(type) {
1436
+ this.physicsVisualization[type] = !this.physicsVisualization[type];
1437
+ console.debug(
1438
+ "[SceneInspector] Physics visualization:",
1439
+ type,
1440
+ "=",
1441
+ this.physicsVisualization[type]
1442
+ );
1443
+ }
1444
+ /**
1445
+ * Get physics visualization settings
1446
+ */
1447
+ getPhysicsVisualization() {
1448
+ return { ...this.physicsVisualization };
1449
+ }
1450
+ /**
1451
+ * Generate HTML for inspector UI
1452
+ */
1453
+ generateInspectorHTML() {
1454
+ const stats = this.getStatistics();
1455
+ const selectedEntity = this.getSelectedEntity();
1456
+ return `
1457
+ <div class="scene-inspector" style="position: absolute; top: 0; right: 0; width: 400px; height: 100vh; background: rgba(0, 0, 0, 0.9); color: #fff; font-family: monospace; font-size: 12px; overflow-y: auto; z-index: 1000;">
1458
+
1459
+ <!-- Header -->
1460
+ <div style="padding: 15px; border-bottom: 2px solid #4ecdc4; background: #1a1a2e;">
1461
+ <h2 style="margin: 0; color: #4ecdc4;">\u{1F50D} Scene Inspector</h2>
1462
+ <div style="color: #aaa; margin-top: 5px;">Frame: ${this.currentFrame}</div>
1463
+ </div>
1464
+
1465
+ <!-- Statistics Panel -->
1466
+ ${this.config.showStatistics ? `
1467
+ <div style="padding: 15px; border-bottom: 1px solid #333;">
1468
+ <h3 style="margin: 0 0 10px 0; color: #ffd93d;">\u{1F4CA} Statistics</h3>
1469
+ <div style="line-height: 1.6;">
1470
+ <div>Entities: ${stats.totalEntities} (${stats.activeEntities} active)</div>
1471
+ <div>FPS: ${stats.currentFPS} (avg: ${stats.averageFPS}, min: ${stats.minFPS})</div>
1472
+ <div>Draw Calls: ${stats.drawCalls}</div>
1473
+ <div>Triangles: ${stats.totalTriangles.toLocaleString()}</div>
1474
+ <div>Memory: ${(stats.textureMemory + stats.geometryMemory).toFixed(1)} MB</div>
1475
+ </div>
1476
+ </div>
1477
+ ` : ""}
1478
+
1479
+ <!-- Hierarchy Panel -->
1480
+ ${this.config.showHierarchy ? `
1481
+ <div style="padding: 15px; border-bottom: 1px solid #333;">
1482
+ <h3 style="margin: 0 0 10px 0; color: #95e1d3;">\u{1F333} Hierarchy</h3>
1483
+ <div style="max-height: 300px; overflow-y: auto;">
1484
+ ${this.getEntityHierarchy().map(
1485
+ (entity) => `
1486
+ <div
1487
+ data-entity-id="${entity.id}"
1488
+ style="padding: 5px; margin: 2px 0; background: ${this.selectedEntityId === entity.id ? "#4ecdc4" : "#2a2a3e"}; cursor: pointer; border-radius: 4px;"
1489
+ onclick="window.inspectorSelectEntity('${entity.id}')"
1490
+ >
1491
+ <div style="font-weight: bold;">${entity.name}</div>
1492
+ <div style="color: #aaa; font-size: 10px;">${entity.type} \xB7 ${entity.traits.join(", ")}</div>
1493
+ </div>
1494
+ `
1495
+ ).join("")}
1496
+ </div>
1497
+ </div>
1498
+ ` : ""}
1499
+
1500
+ <!-- Properties Panel -->
1501
+ ${this.config.showProperties && selectedEntity ? `
1502
+ <div style="padding: 15px; border-bottom: 1px solid #333;">
1503
+ <h3 style="margin: 0 0 10px 0; color: #ff6b6b;">\u2699\uFE0F Properties</h3>
1504
+ <div style="line-height: 1.8;">
1505
+ <div style="color: #4ecdc4; margin-bottom: 10px; font-size: 14px; font-weight: bold;">${selectedEntity.name}</div>
1506
+
1507
+ <div style="margin: 10px 0;">
1508
+ <div style="color: #ffd93d;">Transform:</div>
1509
+ <div style="margin-left: 10px;">
1510
+ <div>Position: ${selectedEntity.transform.position.map((v) => v.toFixed(2)).join(", ")}</div>
1511
+ <div>Rotation: ${selectedEntity.transform.rotation.map((v) => v.toFixed(2)).join(", ")}</div>
1512
+ <div>Scale: ${selectedEntity.transform.scale.map((v) => v.toFixed(2)).join(", ")}</div>
1513
+ </div>
1514
+ </div>
1515
+
1516
+ <div style="margin: 10px 0;">
1517
+ <div style="color: #ffd93d;">Traits:</div>
1518
+ <div style="margin-left: 10px;">${selectedEntity.traits.join(", ") || "None"}</div>
1519
+ </div>
1520
+
1521
+ <div style="margin: 10px 0;">
1522
+ <div style="color: #ffd93d;">Properties:</div>
1523
+ <div style="margin-left: 10px;">
1524
+ ${Object.entries(selectedEntity.properties).map(
1525
+ ([key, value]) => `
1526
+ <div>${key}: ${typeof value === "object" ? JSON.stringify(value) : value}</div>
1527
+ `
1528
+ ).join("")}
1529
+ </div>
1530
+ </div>
1531
+ </div>
1532
+ </div>
1533
+ ` : ""}
1534
+
1535
+ <!-- Performance Graph -->
1536
+ ${this.config.showPerformance ? `
1537
+ <div style="padding: 15px;">
1538
+ <h3 style="margin: 0 0 10px 0; color: #95e1d3;">\u26A1 Performance</h3>
1539
+ <canvas id="performance-graph" width="370" height="100" style="background: #1a1a2e; border-radius: 4px;"></canvas>
1540
+ </div>
1541
+ ` : ""}
1542
+
1543
+ </div>
1544
+
1545
+ <script>
1546
+ // Entity selection handler
1547
+ window.inspectorSelectEntity = function(entityId) {
1548
+ console.debug('Selected entity:', entityId);
1549
+ // Would call inspector.selectEntity(entityId) and refresh UI
1550
+ };
1551
+
1552
+ // Render performance graph
1553
+ ${this.config.showPerformance ? `
1554
+ (function() {
1555
+ const canvas = document.getElementById('performance-graph');
1556
+ if (!canvas) return;
1557
+
1558
+ const ctx = canvas.getContext('2d');
1559
+ const history = ${JSON.stringify(this.performanceHistory.slice(-60))};
1560
+
1561
+ // Clear canvas
1562
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1563
+
1564
+ // Draw graph
1565
+ const maxFPS = 60;
1566
+ const width = canvas.width;
1567
+ const height = canvas.height;
1568
+
1569
+ ctx.strokeStyle = '#4ecdc4';
1570
+ ctx.lineWidth = 2;
1571
+ ctx.beginPath();
1572
+
1573
+ history.forEach((frame, i) => {
1574
+ const x = (i / history.length) * width;
1575
+ const y = height - (frame.fps / maxFPS) * height;
1576
+
1577
+ if (i === 0) ctx.moveTo(x, y);
1578
+ else ctx.lineTo(x, y);
1579
+ });
1580
+
1581
+ ctx.stroke();
1582
+
1583
+ // Draw 60 FPS line
1584
+ ctx.strokeStyle = '#ffd93d';
1585
+ ctx.lineWidth = 1;
1586
+ ctx.setLineDash([5, 5]);
1587
+ ctx.beginPath();
1588
+ ctx.moveTo(0, 0);
1589
+ ctx.lineTo(width, 0);
1590
+ ctx.stroke();
1591
+ ctx.setLineDash([]);
1592
+
1593
+ // Draw labels
1594
+ ctx.fillStyle = '#fff';
1595
+ ctx.font = '10px monospace';
1596
+ ctx.fillText('60 FPS', 5, 10);
1597
+ ctx.fillText('0 FPS', 5, height - 5);
1598
+ })();
1599
+ ` : ""}
1600
+ </script>
1601
+ `;
1602
+ }
1603
+ /**
1604
+ * Enable/disable inspector
1605
+ */
1606
+ setEnabled(enabled) {
1607
+ this.config.enabled = enabled;
1608
+ }
1609
+ /**
1610
+ * Get configuration
1611
+ */
1612
+ getConfig() {
1613
+ return { ...this.config };
1614
+ }
1615
+ };
1616
+
1617
+ // src/tools/VisualEditor.ts
1618
+ var VisualEditor = class {
1619
+ constructor(config = {}) {
1620
+ this.entities = /* @__PURE__ */ new Map();
1621
+ this.selectedEntityIds = /* @__PURE__ */ new Set();
1622
+ this.composition = null;
1623
+ this.history = [];
1624
+ this.historyIndex = -1;
1625
+ this.maxHistorySize = 100;
1626
+ this.container = null;
1627
+ this.hierarchyPanel = null;
1628
+ this.propertiesPanel = null;
1629
+ this.viewportPanel = null;
1630
+ this.toolbarPanel = null;
1631
+ this.config = {
1632
+ container: config.container ?? null,
1633
+ autoSave: config.autoSave ?? true,
1634
+ autoSaveInterval: config.autoSaveInterval ?? 3e4,
1635
+ // 30 seconds
1636
+ debug: config.debug ?? false,
1637
+ gridSnap: config.gridSnap ?? true,
1638
+ gridSize: config.gridSize ?? 1,
1639
+ enable3DPreview: config.enable3DPreview ?? true,
1640
+ theme: config.theme ?? "dark"
1641
+ };
1642
+ if (this.config.container) {
1643
+ this.initializeUI();
1644
+ }
1645
+ if (this.config.autoSave) {
1646
+ this.startAutoSave();
1647
+ }
1648
+ if (this.config.debug) {
1649
+ console.debug("[VisualEditor] Initialized", this.config);
1650
+ }
1651
+ }
1652
+ /**
1653
+ * Initialize editor UI
1654
+ */
1655
+ initializeUI() {
1656
+ if (!this.config.container) return;
1657
+ this.container = this.config.container;
1658
+ this.container.innerHTML = this.generateEditorHTML();
1659
+ this.hierarchyPanel = this.container.querySelector(".editor-hierarchy");
1660
+ this.propertiesPanel = this.container.querySelector(".editor-properties");
1661
+ this.viewportPanel = this.container.querySelector(".editor-viewport");
1662
+ this.toolbarPanel = this.container.querySelector(".editor-toolbar");
1663
+ this.attachEventListeners();
1664
+ if (this.config.debug) {
1665
+ console.debug("[VisualEditor] UI initialized");
1666
+ }
1667
+ }
1668
+ /**
1669
+ * Generate editor HTML
1670
+ */
1671
+ generateEditorHTML() {
1672
+ const theme = this.config.theme === "dark" ? "editor-dark" : "editor-light";
1673
+ return `
1674
+ <div class="holostudio-editor ${theme}">
1675
+ <!-- Toolbar -->
1676
+ <div class="editor-toolbar">
1677
+ <div class="toolbar-section">
1678
+ <button class="toolbar-btn" data-action="new" title="New Composition">
1679
+ <span class="icon">\u{1F4C4}</span> New
1680
+ </button>
1681
+ <button class="toolbar-btn" data-action="open" title="Open Composition">
1682
+ <span class="icon">\u{1F4C1}</span> Open
1683
+ </button>
1684
+ <button class="toolbar-btn" data-action="save" title="Save Composition">
1685
+ <span class="icon">\u{1F4BE}</span> Save
1686
+ </button>
1687
+ </div>
1688
+ <div class="toolbar-section">
1689
+ <button class="toolbar-btn" data-action="undo" title="Undo">
1690
+ <span class="icon">\u21B6</span>
1691
+ </button>
1692
+ <button class="toolbar-btn" data-action="redo" title="Redo">
1693
+ <span class="icon">\u21B7</span>
1694
+ </button>
1695
+ </div>
1696
+ <div class="toolbar-section">
1697
+ <button class="toolbar-btn" data-action="play" title="Preview">
1698
+ <span class="icon">\u25B6</span> Preview
1699
+ </button>
1700
+ </div>
1701
+ </div>
1702
+
1703
+ <!-- Main Content -->
1704
+ <div class="editor-content">
1705
+ <!-- Left Sidebar: Hierarchy -->
1706
+ <div class="editor-panel editor-hierarchy">
1707
+ <div class="panel-header">
1708
+ <h3>Hierarchy</h3>
1709
+ <button class="panel-btn" data-action="add-entity" title="Add Entity">+</button>
1710
+ </div>
1711
+ <div class="panel-content" id="hierarchy-content">
1712
+ <!-- Populated dynamically -->
1713
+ </div>
1714
+ </div>
1715
+
1716
+ <!-- Center: Viewport -->
1717
+ <div class="editor-panel editor-viewport">
1718
+ <div class="panel-header">
1719
+ <h3>Viewport</h3>
1720
+ <div class="viewport-controls">
1721
+ <button class="panel-btn" data-action="grid-toggle" title="Toggle Grid">
1722
+ <span class="icon">\u229E</span>
1723
+ </button>
1724
+ <button class="panel-btn" data-action="gizmo-toggle" title="Toggle Gizmo">
1725
+ <span class="icon">\u22B9</span>
1726
+ </button>
1727
+ </div>
1728
+ </div>
1729
+ <div class="panel-content viewport-content" id="viewport-content">
1730
+ <canvas id="editor-canvas"></canvas>
1731
+ <div class="viewport-overlay">
1732
+ <div class="viewport-stats">
1733
+ <div>Entities: <span id="entity-count">0</span></div>
1734
+ <div>Selected: <span id="selected-count">0</span></div>
1735
+ </div>
1736
+ </div>
1737
+ </div>
1738
+ </div>
1739
+
1740
+ <!-- Right Sidebar: Properties -->
1741
+ <div class="editor-panel editor-properties">
1742
+ <div class="panel-header">
1743
+ <h3>Properties</h3>
1744
+ </div>
1745
+ <div class="panel-content" id="properties-content">
1746
+ <div class="no-selection">
1747
+ <p>No entity selected</p>
1748
+ <p class="hint">Select an entity in the Hierarchy or Viewport</p>
1749
+ </div>
1750
+ </div>
1751
+ </div>
1752
+ </div>
1753
+
1754
+ <!-- Bottom Panel: Asset Library & Traits -->
1755
+ <div class="editor-bottom">
1756
+ <div class="panel-header">
1757
+ <h3>Traits & Assets</h3>
1758
+ </div>
1759
+ <div class="panel-content traits-library">
1760
+ <!-- Populated dynamically -->
1761
+ </div>
1762
+ </div>
1763
+ </div>
1764
+
1765
+ <style>
1766
+ .holostudio-editor {
1767
+ width: 100%;
1768
+ height: 100%;
1769
+ display: flex;
1770
+ flex-direction: column;
1771
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
1772
+ overflow: hidden;
1773
+ }
1774
+
1775
+ .editor-dark {
1776
+ background: #1a1a1a;
1777
+ color: #e0e0e0;
1778
+ }
1779
+
1780
+ .editor-light {
1781
+ background: #f5f5f5;
1782
+ color: #333;
1783
+ }
1784
+
1785
+ /* Toolbar */
1786
+ .editor-toolbar {
1787
+ display: flex;
1788
+ gap: 20px;
1789
+ padding: 10px 15px;
1790
+ background: rgba(0, 0, 0, 0.3);
1791
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
1792
+ }
1793
+
1794
+ .toolbar-section {
1795
+ display: flex;
1796
+ gap: 5px;
1797
+ }
1798
+
1799
+ .toolbar-btn {
1800
+ display: flex;
1801
+ align-items: center;
1802
+ gap: 5px;
1803
+ padding: 6px 12px;
1804
+ background: rgba(96, 165, 250, 0.1);
1805
+ border: 1px solid rgba(96, 165, 250, 0.3);
1806
+ border-radius: 6px;
1807
+ color: #60a5fa;
1808
+ cursor: pointer;
1809
+ font-size: 13px;
1810
+ transition: all 0.2s;
1811
+ }
1812
+
1813
+ .toolbar-btn:hover {
1814
+ background: rgba(96, 165, 250, 0.2);
1815
+ border-color: rgba(96, 165, 250, 0.5);
1816
+ }
1817
+
1818
+ .icon {
1819
+ font-size: 16px;
1820
+ }
1821
+
1822
+ /* Main Content */
1823
+ .editor-content {
1824
+ display: flex;
1825
+ flex: 1;
1826
+ gap: 1px;
1827
+ overflow: hidden;
1828
+ background: rgba(255, 255, 255, 0.05);
1829
+ }
1830
+
1831
+ .editor-panel {
1832
+ display: flex;
1833
+ flex-direction: column;
1834
+ background: rgba(30, 30, 30, 0.95);
1835
+ border: 1px solid rgba(255, 255, 255, 0.1);
1836
+ }
1837
+
1838
+ .editor-hierarchy {
1839
+ width: 250px;
1840
+ flex-shrink: 0;
1841
+ }
1842
+
1843
+ .editor-viewport {
1844
+ flex: 1;
1845
+ min-width: 0;
1846
+ }
1847
+
1848
+ .editor-properties {
1849
+ width: 300px;
1850
+ flex-shrink: 0;
1851
+ }
1852
+
1853
+ .panel-header {
1854
+ display: flex;
1855
+ justify-content: space-between;
1856
+ align-items: center;
1857
+ padding: 10px 15px;
1858
+ background: rgba(0, 0, 0, 0.2);
1859
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
1860
+ }
1861
+
1862
+ .panel-header h3 {
1863
+ margin: 0;
1864
+ font-size: 14px;
1865
+ font-weight: 600;
1866
+ color: #60a5fa;
1867
+ }
1868
+
1869
+ .panel-btn {
1870
+ padding: 4px 8px;
1871
+ background: rgba(96, 165, 250, 0.1);
1872
+ border: 1px solid rgba(96, 165, 250, 0.3);
1873
+ border-radius: 4px;
1874
+ color: #60a5fa;
1875
+ cursor: pointer;
1876
+ font-size: 14px;
1877
+ }
1878
+
1879
+ .panel-btn:hover {
1880
+ background: rgba(96, 165, 250, 0.2);
1881
+ }
1882
+
1883
+ .panel-content {
1884
+ flex: 1;
1885
+ overflow-y: auto;
1886
+ padding: 10px;
1887
+ }
1888
+
1889
+ /* Viewport */
1890
+ .viewport-content {
1891
+ position: relative;
1892
+ padding: 0;
1893
+ }
1894
+
1895
+ #editor-canvas {
1896
+ width: 100%;
1897
+ height: 100%;
1898
+ display: block;
1899
+ }
1900
+
1901
+ .viewport-overlay {
1902
+ position: absolute;
1903
+ top: 10px;
1904
+ left: 10px;
1905
+ pointer-events: none;
1906
+ }
1907
+
1908
+ .viewport-stats {
1909
+ background: rgba(0, 0, 0, 0.7);
1910
+ padding: 8px 12px;
1911
+ border-radius: 6px;
1912
+ font-size: 12px;
1913
+ font-family: 'Courier New', monospace;
1914
+ }
1915
+
1916
+ .viewport-stats div {
1917
+ margin-bottom: 3px;
1918
+ }
1919
+
1920
+ .viewport-controls {
1921
+ display: flex;
1922
+ gap: 5px;
1923
+ }
1924
+
1925
+ /* Properties Panel */
1926
+ .no-selection {
1927
+ text-align: center;
1928
+ padding: 40px 20px;
1929
+ color: #888;
1930
+ }
1931
+
1932
+ .no-selection .hint {
1933
+ font-size: 12px;
1934
+ margin-top: 10px;
1935
+ }
1936
+
1937
+ /* Bottom Panel */
1938
+ .editor-bottom {
1939
+ height: 200px;
1940
+ background: rgba(30, 30, 30, 0.95);
1941
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
1942
+ }
1943
+
1944
+ .traits-library {
1945
+ display: grid;
1946
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
1947
+ gap: 10px;
1948
+ }
1949
+ </style>
1950
+ `;
1951
+ }
1952
+ /**
1953
+ * Attach event listeners
1954
+ */
1955
+ attachEventListeners() {
1956
+ if (!this.container) return;
1957
+ this.container.querySelectorAll("[data-action]").forEach((btn) => {
1958
+ btn.addEventListener("click", (e) => {
1959
+ const action = e.currentTarget.getAttribute("data-action");
1960
+ this.handleAction(action);
1961
+ });
1962
+ });
1963
+ document.addEventListener("keydown", (e) => {
1964
+ this.handleKeyboardShortcut(e);
1965
+ });
1966
+ }
1967
+ /**
1968
+ * Handle toolbar action
1969
+ */
1970
+ handleAction(action) {
1971
+ if (!action) return;
1972
+ switch (action) {
1973
+ case "new":
1974
+ this.createNewComposition();
1975
+ break;
1976
+ case "save":
1977
+ this.saveComposition();
1978
+ break;
1979
+ case "undo":
1980
+ this.undo();
1981
+ break;
1982
+ case "redo":
1983
+ this.redo();
1984
+ break;
1985
+ case "add-entity":
1986
+ this.addEntity();
1987
+ break;
1988
+ case "play":
1989
+ this.previewComposition();
1990
+ break;
1991
+ default:
1992
+ if (this.config.debug) {
1993
+ console.debug("[VisualEditor] Unhandled action:", action);
1994
+ }
1995
+ }
1996
+ }
1997
+ /**
1998
+ * Handle keyboard shortcut
1999
+ */
2000
+ handleKeyboardShortcut(e) {
2001
+ const isCtrl = e.ctrlKey || e.metaKey;
2002
+ if (isCtrl && e.key === "s") {
2003
+ e.preventDefault();
2004
+ this.saveComposition();
2005
+ } else if (isCtrl && e.key === "z") {
2006
+ e.preventDefault();
2007
+ if (e.shiftKey) {
2008
+ this.redo();
2009
+ } else {
2010
+ this.undo();
2011
+ }
2012
+ } else if (e.key === "Delete") {
2013
+ e.preventDefault();
2014
+ this.deleteSelectedEntities();
2015
+ }
2016
+ }
2017
+ /**
2018
+ * Create new composition
2019
+ */
2020
+ createNewComposition() {
2021
+ this.composition = {
2022
+ type: "Composition",
2023
+ name: "Untitled",
2024
+ objects: [],
2025
+ templates: [],
2026
+ spatialGroups: [],
2027
+ lights: [],
2028
+ imports: [],
2029
+ timelines: [],
2030
+ audio: [],
2031
+ zones: [],
2032
+ transitions: [],
2033
+ conditionals: [],
2034
+ iterators: [],
2035
+ npcs: [],
2036
+ quests: [],
2037
+ abilities: [],
2038
+ dialogues: [],
2039
+ stateMachines: [],
2040
+ achievements: [],
2041
+ talentTrees: [],
2042
+ shapes: []
2043
+ };
2044
+ this.entities.clear();
2045
+ this.selectedEntityIds.clear();
2046
+ this.history = [];
2047
+ this.historyIndex = -1;
2048
+ this.updateHierarchy();
2049
+ this.updateProperties();
2050
+ if (this.config.debug) {
2051
+ console.debug("[VisualEditor] New composition created");
2052
+ }
2053
+ }
2054
+ /**
2055
+ * Load composition
2056
+ */
2057
+ loadComposition(composition) {
2058
+ this.composition = composition;
2059
+ this.entities.clear();
2060
+ this.selectedEntityIds.clear();
2061
+ let entityIndex = 0;
2062
+ for (const obj of composition.objects || []) {
2063
+ const entity = this.holoObjectToEntity(obj, entityIndex++);
2064
+ this.entities.set(entity.id, entity);
2065
+ }
2066
+ this.updateHierarchy();
2067
+ this.updateProperties();
2068
+ if (this.config.debug) {
2069
+ console.debug("[VisualEditor] Composition loaded:", composition.name);
2070
+ }
2071
+ }
2072
+ /**
2073
+ * Convert HoloObject to EditorEntity
2074
+ */
2075
+ holoObjectToEntity(obj, index) {
2076
+ const getProp = (key) => obj.properties?.find((p) => p.key === key)?.value;
2077
+ const pos = getProp("position");
2078
+ const rot = getProp("rotation");
2079
+ const scl = getProp("scale");
2080
+ return {
2081
+ id: `entity_${index}`,
2082
+ name: obj.name || `Object ${index}`,
2083
+ type: "object",
2084
+ parentId: null,
2085
+ children: [],
2086
+ transform: {
2087
+ position: pos ? [pos[0], pos[1], pos[2]] : [0, 0, 0],
2088
+ rotation: rot ? [rot[0], rot[1], rot[2]] : [0, 0, 0],
2089
+ scale: scl ? [scl[0], scl[1], scl[2]] : [1, 1, 1]
2090
+ },
2091
+ geometry: getProp("geometry"),
2092
+ material: getProp("material"),
2093
+ traits: (obj.traits || []).map((t) => ({
2094
+ name: t.name,
2095
+ properties: t.config,
2096
+ category: this.getTraitCategory(t.name)
2097
+ })),
2098
+ properties: obj.properties?.filter(
2099
+ (p) => !["position", "rotation", "scale", "geometry", "material"].includes(p.key)
2100
+ ).reduce((acc, p) => ({ ...acc, [p.key]: p.value }), {}) || {},
2101
+ visible: true,
2102
+ locked: false,
2103
+ selected: false
2104
+ };
2105
+ }
2106
+ /**
2107
+ * Get trait category
2108
+ */
2109
+ getTraitCategory(traitName) {
2110
+ if (traitName.startsWith("physics")) return "Physics";
2111
+ if (traitName.startsWith("render")) return "Rendering";
2112
+ if (["grabbable", "climbable", "teleport"].includes(traitName)) return "VR";
2113
+ return "General";
2114
+ }
2115
+ /**
2116
+ * Save composition
2117
+ */
2118
+ saveComposition() {
2119
+ if (!this.composition) return null;
2120
+ const objects = [];
2121
+ for (const entity of this.entities.values()) {
2122
+ const properties = [];
2123
+ properties.push({
2124
+ type: "ObjectProperty",
2125
+ key: "position",
2126
+ value: entity.transform.position
2127
+ });
2128
+ properties.push({
2129
+ type: "ObjectProperty",
2130
+ key: "rotation",
2131
+ value: entity.transform.rotation
2132
+ });
2133
+ properties.push({ type: "ObjectProperty", key: "scale", value: entity.transform.scale });
2134
+ if (entity.geometry)
2135
+ properties.push({ type: "ObjectProperty", key: "geometry", value: entity.geometry });
2136
+ if (entity.material)
2137
+ properties.push({
2138
+ type: "ObjectProperty",
2139
+ key: "material",
2140
+ value: entity.material
2141
+ });
2142
+ for (const [k, v] of Object.entries(entity.properties)) {
2143
+ properties.push({ type: "ObjectProperty", key: k, value: v });
2144
+ }
2145
+ const obj = {
2146
+ type: "Object",
2147
+ name: entity.name,
2148
+ properties,
2149
+ traits: entity.traits.map((t) => ({
2150
+ type: "ObjectTrait",
2151
+ name: t.name,
2152
+ config: t.properties || {}
2153
+ })),
2154
+ children: []
2155
+ };
2156
+ objects.push(obj);
2157
+ }
2158
+ this.composition.objects = objects;
2159
+ if (this.config.debug) {
2160
+ console.debug("[VisualEditor] Composition saved:", this.composition);
2161
+ }
2162
+ return this.composition;
2163
+ }
2164
+ /**
2165
+ * Export composition as .holo code
2166
+ */
2167
+ exportToCode() {
2168
+ if (!this.composition) return "";
2169
+ let code = `composition "${this.composition.name}" {
2170
+ `;
2171
+ for (const entity of this.entities.values()) {
2172
+ code += ` object "${entity.name}" {
2173
+ `;
2174
+ for (const trait of entity.traits) {
2175
+ if (trait.properties && Object.keys(trait.properties).length > 0) {
2176
+ const propsStr = Object.entries(trait.properties).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
2177
+ code += ` @${trait.name}(${propsStr})
2178
+ `;
2179
+ } else {
2180
+ code += ` @${trait.name}
2181
+ `;
2182
+ }
2183
+ }
2184
+ if (entity.geometry) {
2185
+ code += ` geometry: "${entity.geometry}"
2186
+ `;
2187
+ }
2188
+ code += ` position: [${entity.transform.position.join(", ")}]
2189
+ `;
2190
+ if (entity.transform.rotation.some((r) => r !== 0)) {
2191
+ code += ` rotation: [${entity.transform.rotation.join(", ")}]
2192
+ `;
2193
+ }
2194
+ if (entity.transform.scale.some((s) => s !== 1)) {
2195
+ code += ` scale: [${entity.transform.scale.join(", ")}]
2196
+ `;
2197
+ }
2198
+ if (entity.material) {
2199
+ code += ` material: ${JSON.stringify(entity.material)}
2200
+ `;
2201
+ }
2202
+ code += ` }
2203
+ `;
2204
+ }
2205
+ code += `}
2206
+ `;
2207
+ return code;
2208
+ }
2209
+ /**
2210
+ * Add new entity
2211
+ */
2212
+ addEntity(type = "box") {
2213
+ const id = `entity_${Date.now()}_${Math.random().toString(36).substring(7)}`;
2214
+ const entity = {
2215
+ id,
2216
+ name: `${type.charAt(0).toUpperCase() + type.slice(1)} ${this.entities.size + 1}`,
2217
+ type: "object",
2218
+ parentId: null,
2219
+ children: [],
2220
+ transform: {
2221
+ position: [0, 0, 0],
2222
+ rotation: [0, 0, 0],
2223
+ scale: [1, 1, 1]
2224
+ },
2225
+ geometry: type,
2226
+ material: {
2227
+ type: "standard",
2228
+ color: "#888888"
2229
+ },
2230
+ traits: [],
2231
+ properties: {},
2232
+ visible: true,
2233
+ locked: false,
2234
+ selected: false
2235
+ };
2236
+ this.entities.set(id, entity);
2237
+ this.addToHistory("create", id, void 0, entity);
2238
+ this.updateHierarchy();
2239
+ if (this.config.debug) {
2240
+ console.debug("[VisualEditor] Entity added:", entity.name);
2241
+ }
2242
+ return entity;
2243
+ }
2244
+ /**
2245
+ * Delete selected entities
2246
+ */
2247
+ deleteSelectedEntities() {
2248
+ for (const id of this.selectedEntityIds) {
2249
+ const entity = this.entities.get(id);
2250
+ if (entity && !entity.locked) {
2251
+ this.addToHistory("delete", id, entity, void 0);
2252
+ this.entities.delete(id);
2253
+ }
2254
+ }
2255
+ this.selectedEntityIds.clear();
2256
+ this.updateHierarchy();
2257
+ this.updateProperties();
2258
+ if (this.config.debug) {
2259
+ console.debug("[VisualEditor] Deleted entities");
2260
+ }
2261
+ }
2262
+ /**
2263
+ * Select entity
2264
+ */
2265
+ selectEntity(id, multiSelect = false) {
2266
+ if (!multiSelect) {
2267
+ for (const entity2 of this.entities.values()) {
2268
+ entity2.selected = false;
2269
+ }
2270
+ this.selectedEntityIds.clear();
2271
+ }
2272
+ const entity = this.entities.get(id);
2273
+ if (entity) {
2274
+ entity.selected = true;
2275
+ this.selectedEntityIds.add(id);
2276
+ this.updateProperties();
2277
+ }
2278
+ }
2279
+ /**
2280
+ * Update entity property
2281
+ */
2282
+ updateEntityProperty(id, path, value) {
2283
+ const entity = this.entities.get(id);
2284
+ if (!entity || entity.locked) return;
2285
+ const previousState = { ...entity };
2286
+ const keys = path.split(".");
2287
+ let target = entity;
2288
+ for (let i = 0; i < keys.length - 1; i++) {
2289
+ target = target[keys[i]];
2290
+ }
2291
+ target[keys[keys.length - 1]] = value;
2292
+ this.addToHistory("modify", id, previousState, entity);
2293
+ this.updateProperties();
2294
+ if (this.config.debug) {
2295
+ console.debug("[VisualEditor] Property updated:", path, "=", value);
2296
+ }
2297
+ }
2298
+ /**
2299
+ * Add history entry
2300
+ */
2301
+ addToHistory(action, entityId, previousState, newState) {
2302
+ this.history = this.history.slice(0, this.historyIndex + 1);
2303
+ this.history.push({
2304
+ action,
2305
+ entityId,
2306
+ previousState,
2307
+ newState,
2308
+ timestamp: Date.now()
2309
+ });
2310
+ this.historyIndex++;
2311
+ if (this.history.length > this.maxHistorySize) {
2312
+ this.history.shift();
2313
+ this.historyIndex--;
2314
+ }
2315
+ }
2316
+ /**
2317
+ * Undo last action
2318
+ */
2319
+ undo() {
2320
+ if (this.historyIndex < 0) return;
2321
+ const entry = this.history[this.historyIndex];
2322
+ if (entry.action === "create") {
2323
+ this.entities.delete(entry.entityId);
2324
+ } else if (entry.action === "delete") {
2325
+ this.entities.set(entry.entityId, entry.previousState);
2326
+ } else if (entry.action === "modify") {
2327
+ this.entities.set(entry.entityId, entry.previousState);
2328
+ }
2329
+ this.historyIndex--;
2330
+ this.updateHierarchy();
2331
+ this.updateProperties();
2332
+ if (this.config.debug) {
2333
+ console.debug("[VisualEditor] Undo:", entry.action);
2334
+ }
2335
+ }
2336
+ /**
2337
+ * Redo last undone action
2338
+ */
2339
+ redo() {
2340
+ if (this.historyIndex >= this.history.length - 1) return;
2341
+ this.historyIndex++;
2342
+ const entry = this.history[this.historyIndex];
2343
+ if (entry.action === "create") {
2344
+ this.entities.set(entry.entityId, entry.newState);
2345
+ } else if (entry.action === "delete") {
2346
+ this.entities.delete(entry.entityId);
2347
+ } else if (entry.action === "modify") {
2348
+ this.entities.set(entry.entityId, entry.newState);
2349
+ }
2350
+ this.updateHierarchy();
2351
+ this.updateProperties();
2352
+ if (this.config.debug) {
2353
+ console.debug("[VisualEditor] Redo:", entry.action);
2354
+ }
2355
+ }
2356
+ /**
2357
+ * Update hierarchy panel
2358
+ */
2359
+ updateHierarchy() {
2360
+ if (!this.hierarchyPanel) return;
2361
+ const content = this.hierarchyPanel.querySelector("#hierarchy-content");
2362
+ if (!content) return;
2363
+ let html = '<div class="hierarchy-list">';
2364
+ for (const entity of this.entities.values()) {
2365
+ const selectedClass = entity.selected ? "selected" : "";
2366
+ const visibleIcon = entity.visible ? "\u{1F441}" : "\u{1F6AB}";
2367
+ const lockedIcon = entity.locked ? "\u{1F512}" : "";
2368
+ html += `
2369
+ <div class="hierarchy-item ${selectedClass}" data-entity-id="${entity.id}">
2370
+ <span class="entity-icon">${this.getEntityIcon(entity.type)}</span>
2371
+ <span class="entity-name">${entity.name}</span>
2372
+ <span class="entity-status">${visibleIcon} ${lockedIcon}</span>
2373
+ </div>
2374
+ `;
2375
+ }
2376
+ html += "</div>";
2377
+ content.innerHTML = html;
2378
+ const entityCount = document.getElementById("entity-count");
2379
+ const selectedCount = document.getElementById("selected-count");
2380
+ if (entityCount) entityCount.textContent = this.entities.size.toString();
2381
+ if (selectedCount) selectedCount.textContent = this.selectedEntityIds.size.toString();
2382
+ }
2383
+ /**
2384
+ * Get entity icon
2385
+ */
2386
+ getEntityIcon(type) {
2387
+ const icons = {
2388
+ object: "\u{1F4E6}",
2389
+ light: "\u{1F4A1}",
2390
+ camera: "\u{1F4F7}",
2391
+ group: "\u{1F4C1}"
2392
+ };
2393
+ return icons[type] || "\u{1F4E6}";
2394
+ }
2395
+ /**
2396
+ * Update properties panel
2397
+ */
2398
+ updateProperties() {
2399
+ if (!this.propertiesPanel) return;
2400
+ const content = this.propertiesPanel.querySelector("#properties-content");
2401
+ if (!content) return;
2402
+ if (this.selectedEntityIds.size === 0) {
2403
+ content.innerHTML = `
2404
+ <div class="no-selection">
2405
+ <p>No entity selected</p>
2406
+ <p class="hint">Select an entity in the Hierarchy or Viewport</p>
2407
+ </div>
2408
+ `;
2409
+ return;
2410
+ }
2411
+ const entityId = Array.from(this.selectedEntityIds)[0];
2412
+ const entity = this.entities.get(entityId);
2413
+ if (!entity) return;
2414
+ const html = `
2415
+ <div class="properties-inspector">
2416
+ <div class="property-group">
2417
+ <h4>Transform</h4>
2418
+ <div class="property-row">
2419
+ <label>Position</label>
2420
+ <div class="vector3-input">
2421
+ <input type="number" value="${entity.transform.position[0]}" step="0.1" data-property="transform.position.0">
2422
+ <input type="number" value="${entity.transform.position[1]}" step="0.1" data-property="transform.position.1">
2423
+ <input type="number" value="${entity.transform.position[2]}" step="0.1" data-property="transform.position.2">
2424
+ </div>
2425
+ </div>
2426
+ <div class="property-row">
2427
+ <label>Rotation</label>
2428
+ <div class="vector3-input">
2429
+ <input type="number" value="${entity.transform.rotation[0]}" step="1" data-property="transform.rotation.0">
2430
+ <input type="number" value="${entity.transform.rotation[1]}" step="1" data-property="transform.rotation.1">
2431
+ <input type="number" value="${entity.transform.rotation[2]}" step="1" data-property="transform.rotation.2">
2432
+ </div>
2433
+ </div>
2434
+ <div class="property-row">
2435
+ <label>Scale</label>
2436
+ <div class="vector3-input">
2437
+ <input type="number" value="${entity.transform.scale[0]}" step="0.1" data-property="transform.scale.0">
2438
+ <input type="number" value="${entity.transform.scale[1]}" step="0.1" data-property="transform.scale.1">
2439
+ <input type="number" value="${entity.transform.scale[2]}" step="0.1" data-property="transform.scale.2">
2440
+ </div>
2441
+ </div>
2442
+ </div>
2443
+
2444
+ <div class="property-group">
2445
+ <h4>Traits</h4>
2446
+ <div class="traits-list">
2447
+ ${entity.traits.map((t) => `<div class="trait-badge">${t.name}</div>`).join("")}
2448
+ </div>
2449
+ <button class="add-trait-btn">+ Add Trait</button>
2450
+ </div>
2451
+ </div>
2452
+
2453
+ <style>
2454
+ .properties-inspector {
2455
+ padding: 10px;
2456
+ }
2457
+
2458
+ .property-group {
2459
+ margin-bottom: 20px;
2460
+ padding: 10px;
2461
+ background: rgba(0, 0, 0, 0.2);
2462
+ border-radius: 6px;
2463
+ }
2464
+
2465
+ .property-group h4 {
2466
+ margin: 0 0 10px 0;
2467
+ font-size: 13px;
2468
+ color: #60a5fa;
2469
+ }
2470
+
2471
+ .property-row {
2472
+ margin-bottom: 10px;
2473
+ }
2474
+
2475
+ .property-row label {
2476
+ display: block;
2477
+ font-size: 12px;
2478
+ margin-bottom: 5px;
2479
+ color: #94a3b8;
2480
+ }
2481
+
2482
+ .vector3-input {
2483
+ display: grid;
2484
+ grid-template-columns: 1fr 1fr 1fr;
2485
+ gap: 5px;
2486
+ }
2487
+
2488
+ .vector3-input input {
2489
+ padding: 5px;
2490
+ background: rgba(0, 0, 0, 0.3);
2491
+ border: 1px solid rgba(255, 255, 255, 0.2);
2492
+ border-radius: 4px;
2493
+ color: #fff;
2494
+ font-size: 12px;
2495
+ }
2496
+
2497
+ .traits-list {
2498
+ display: flex;
2499
+ flex-wrap: wrap;
2500
+ gap: 5px;
2501
+ margin-bottom: 10px;
2502
+ }
2503
+
2504
+ .trait-badge {
2505
+ padding: 4px 8px;
2506
+ background: rgba(96, 165, 250, 0.2);
2507
+ border: 1px solid rgba(96, 165, 250, 0.4);
2508
+ border-radius: 12px;
2509
+ font-size: 11px;
2510
+ color: #60a5fa;
2511
+ }
2512
+
2513
+ .add-trait-btn {
2514
+ width: 100%;
2515
+ padding: 6px;
2516
+ background: rgba(34, 197, 94, 0.1);
2517
+ border: 1px solid rgba(34, 197, 94, 0.3);
2518
+ border-radius: 4px;
2519
+ color: #4ade80;
2520
+ cursor: pointer;
2521
+ font-size: 12px;
2522
+ }
2523
+
2524
+ .add-trait-btn:hover {
2525
+ background: rgba(34, 197, 94, 0.2);
2526
+ }
2527
+
2528
+ .hierarchy-list {
2529
+ padding: 5px;
2530
+ }
2531
+
2532
+ .hierarchy-item {
2533
+ display: flex;
2534
+ align-items: center;
2535
+ gap: 8px;
2536
+ padding: 6px 10px;
2537
+ margin-bottom: 2px;
2538
+ border-radius: 4px;
2539
+ cursor: pointer;
2540
+ font-size: 13px;
2541
+ }
2542
+
2543
+ .hierarchy-item:hover {
2544
+ background: rgba(96, 165, 250, 0.1);
2545
+ }
2546
+
2547
+ .hierarchy-item.selected {
2548
+ background: rgba(96, 165, 250, 0.3);
2549
+ }
2550
+
2551
+ .entity-icon {
2552
+ font-size: 14px;
2553
+ }
2554
+
2555
+ .entity-name {
2556
+ flex: 1;
2557
+ }
2558
+
2559
+ .entity-status {
2560
+ font-size: 11px;
2561
+ }
2562
+ </style>
2563
+ `;
2564
+ content.innerHTML = html;
2565
+ }
2566
+ /**
2567
+ * Preview composition
2568
+ */
2569
+ previewComposition() {
2570
+ const composition = this.saveComposition();
2571
+ if (!composition) return;
2572
+ if (this.config.debug) {
2573
+ console.debug("[VisualEditor] Preview:", composition);
2574
+ }
2575
+ this.container?.dispatchEvent(
2576
+ new CustomEvent("holostudio:preview", {
2577
+ detail: { composition }
2578
+ })
2579
+ );
2580
+ }
2581
+ /**
2582
+ * Start auto-save
2583
+ */
2584
+ startAutoSave() {
2585
+ if (typeof window === "undefined") return;
2586
+ this.autoSaveTimer = window.setInterval(() => {
2587
+ this.saveComposition();
2588
+ if (this.config.debug) {
2589
+ console.debug("[VisualEditor] Auto-saved");
2590
+ }
2591
+ }, this.config.autoSaveInterval);
2592
+ }
2593
+ /**
2594
+ * Stop auto-save
2595
+ */
2596
+ stopAutoSave() {
2597
+ if (this.autoSaveTimer) {
2598
+ clearInterval(this.autoSaveTimer);
2599
+ this.autoSaveTimer = void 0;
2600
+ }
2601
+ }
2602
+ /**
2603
+ * Dispose editor
2604
+ */
2605
+ dispose() {
2606
+ this.stopAutoSave();
2607
+ this.entities.clear();
2608
+ this.selectedEntityIds.clear();
2609
+ this.history = [];
2610
+ if (this.config.debug) {
2611
+ console.debug("[VisualEditor] Disposed");
2612
+ }
2613
+ }
2614
+ /**
2615
+ * Get all entities
2616
+ */
2617
+ getEntities() {
2618
+ return Array.from(this.entities.values());
2619
+ }
2620
+ /**
2621
+ * Get selected entities
2622
+ */
2623
+ getSelectedEntities() {
2624
+ return Array.from(this.selectedEntityIds).map((id) => this.entities.get(id)).filter(Boolean);
2625
+ }
2626
+ /**
2627
+ * Get composition
2628
+ */
2629
+ getComposition() {
2630
+ return this.saveComposition();
2631
+ }
2632
+ };
2633
+
2634
+ // src/tools/MixamoIntegration.ts
2635
+ var MIXAMO_API_BASE = "https://www.mixamo.com/api/v1";
2636
+ var MAX_RETRIES = 5;
2637
+ var INITIAL_BACKOFF_MS = 1e3;
2638
+ var MAX_BACKOFF_MS = 3e4;
2639
+ var SUPPORTED_FORMATS = ["fbx", "glb"];
2640
+ var MixamoError = class extends Error {
2641
+ constructor(message, code) {
2642
+ super(message);
2643
+ this.name = "MixamoError";
2644
+ this.code = code;
2645
+ }
2646
+ };
2647
+ var MixamoRateLimitError = class extends MixamoError {
2648
+ constructor(retryAfterSeconds, attemptNumber) {
2649
+ super(
2650
+ `Mixamo API rate limit exceeded. Retry after ${retryAfterSeconds}s (attempt ${attemptNumber}).`,
2651
+ "RATE_LIMIT_EXCEEDED"
2652
+ );
2653
+ this.name = "MixamoRateLimitError";
2654
+ this.retryAfterSeconds = retryAfterSeconds;
2655
+ this.attemptNumber = attemptNumber;
2656
+ }
2657
+ };
2658
+ var MixamoTopologyError = class extends MixamoError {
2659
+ constructor(issues) {
2660
+ const errorIssues = issues.filter((i) => i.severity === "error");
2661
+ const summary = errorIssues.map((i) => i.message).join("; ");
2662
+ super(`Mesh topology unsupported for auto-rigging: ${summary}`, "UNSUPPORTED_TOPOLOGY");
2663
+ this.name = "MixamoTopologyError";
2664
+ this.issues = issues;
2665
+ }
2666
+ };
2667
+ var MixamoFormatError = class extends MixamoError {
2668
+ constructor(providedFormat) {
2669
+ super(
2670
+ `Unsupported mesh format "${providedFormat}". Supported formats: ${SUPPORTED_FORMATS.join(", ")}.`,
2671
+ "UNSUPPORTED_FORMAT"
2672
+ );
2673
+ this.name = "MixamoFormatError";
2674
+ this.providedFormat = providedFormat;
2675
+ this.supportedFormats = SUPPORTED_FORMATS;
2676
+ }
2677
+ };
2678
+ var MixamoAPI = class {
2679
+ constructor(config) {
2680
+ this.config = {
2681
+ credentials: config.credentials,
2682
+ maxRetries: config.maxRetries ?? MAX_RETRIES,
2683
+ initialBackoffMs: config.initialBackoffMs ?? INITIAL_BACKOFF_MS,
2684
+ debug: config.debug ?? false,
2685
+ timeoutMs: config.timeoutMs ?? 6e4
2686
+ };
2687
+ }
2688
+ // -----------------------------------------------------------------------
2689
+ // Public API
2690
+ // -----------------------------------------------------------------------
2691
+ /**
2692
+ * Upload a character mesh to Mixamo for rigging.
2693
+ *
2694
+ * Supports FBX and GLB formats. Validates mesh topology before upload
2695
+ * and throws `MixamoTopologyError` for unsupported geometries.
2696
+ *
2697
+ * @param data - Raw mesh file data as Buffer or ArrayBuffer.
2698
+ * @param filename - Original filename (used to detect format from extension).
2699
+ * @returns Upload result with character ID and mesh metadata.
2700
+ *
2701
+ * @throws {MixamoFormatError} If the file format is not FBX or GLB.
2702
+ * @throws {MixamoTopologyError} If the mesh has unsupported topology.
2703
+ * @throws {MixamoRateLimitError} If the API rate limit is exceeded after max retries.
2704
+ * @throws {MixamoError} For other API errors.
2705
+ */
2706
+ async uploadCharacter(data, filename) {
2707
+ const format = this.detectFormat(filename);
2708
+ const topologyIssues = this.validateTopology(data, format);
2709
+ const errors = topologyIssues.filter((i) => i.severity === "error");
2710
+ if (errors.length > 0) {
2711
+ throw new MixamoTopologyError(topologyIssues);
2712
+ }
2713
+ if (topologyIssues.length > 0 && this.config.debug) {
2714
+ console.warn(
2715
+ "[MixamoAPI] Topology warnings:",
2716
+ topologyIssues.map((i) => i.message)
2717
+ );
2718
+ }
2719
+ const formData = this.createFormData(data, filename, format);
2720
+ const response = await this.requestWithRetry("POST", "/characters/upload", formData, {
2721
+ isFormData: true
2722
+ });
2723
+ const result = {
2724
+ characterId: response.character_id,
2725
+ filename,
2726
+ format,
2727
+ vertexCount: response.vertex_count,
2728
+ polygonCount: response.polygon_count,
2729
+ topologyValid: response.topology_valid,
2730
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
2731
+ };
2732
+ if (this.config.debug) {
2733
+ console.debug("[MixamoAPI] Character uploaded:", result.characterId);
2734
+ }
2735
+ return result;
2736
+ }
2737
+ /**
2738
+ * Auto-rig an uploaded character.
2739
+ *
2740
+ * Triggers Mixamo's server-side auto-rigging pipeline which detects
2741
+ * joint positions and generates a humanoid skeleton.
2742
+ *
2743
+ * @param characterId - The character ID from a previous upload.
2744
+ * @param rigType - Rig type to generate. Default: 'humanoid'.
2745
+ * @returns Rig result with bone count and quality score.
2746
+ *
2747
+ * @throws {MixamoRateLimitError} If the API rate limit is exceeded after max retries.
2748
+ * @throws {MixamoError} For other API errors (e.g. character not found).
2749
+ */
2750
+ async autoRig(characterId, rigType = "humanoid") {
2751
+ const response = await this.requestWithRetry("POST", `/characters/${encodeURIComponent(characterId)}/rig`, {
2752
+ rig_type: rigType
2753
+ });
2754
+ const result = {
2755
+ characterId: response.character_id,
2756
+ rigType: response.rig_type,
2757
+ boneCount: response.bone_count,
2758
+ qualityScore: response.quality_score,
2759
+ success: response.success,
2760
+ warnings: response.warnings ?? [],
2761
+ riggedAt: (/* @__PURE__ */ new Date()).toISOString()
2762
+ };
2763
+ if (this.config.debug) {
2764
+ console.debug(
2765
+ `[MixamoAPI] Character rigged: ${result.characterId} (${result.boneCount} bones, quality: ${result.qualityScore})`
2766
+ );
2767
+ }
2768
+ return result;
2769
+ }
2770
+ /**
2771
+ * List available animations from the Mixamo library.
2772
+ *
2773
+ * Supports pagination, category filtering, and text search.
2774
+ *
2775
+ * @param options - Listing options (page, limit, category, query, motionType).
2776
+ * @returns Paginated list of animations.
2777
+ *
2778
+ * @throws {MixamoRateLimitError} If the API rate limit is exceeded after max retries.
2779
+ * @throws {MixamoError} For other API errors.
2780
+ */
2781
+ async listAnimations(options = {}) {
2782
+ const params = new URLSearchParams();
2783
+ if (options.page) params.set("page", String(options.page));
2784
+ if (options.limit) params.set("limit", String(options.limit));
2785
+ if (options.category) params.set("category", options.category);
2786
+ if (options.query) params.set("query", options.query);
2787
+ if (options.motionType) params.set("motion_type", options.motionType);
2788
+ const queryString = params.toString();
2789
+ const endpoint = `/animations${queryString ? `?${queryString}` : ""}`;
2790
+ const response = await this.requestWithRetry("GET", endpoint);
2791
+ const result = {
2792
+ animations: response.animations.map((a) => ({
2793
+ id: a.id,
2794
+ name: a.name,
2795
+ category: a.category,
2796
+ duration: a.duration,
2797
+ loopable: a.loopable,
2798
+ motionType: a.motion_type,
2799
+ thumbnailUrl: a.thumbnail_url
2800
+ })),
2801
+ total: response.total,
2802
+ page: response.page,
2803
+ limit: response.limit,
2804
+ hasMore: response.has_more
2805
+ };
2806
+ if (this.config.debug) {
2807
+ console.debug(
2808
+ `[MixamoAPI] Listed ${result.animations.length} animations (${result.total} total)`
2809
+ );
2810
+ }
2811
+ return result;
2812
+ }
2813
+ /**
2814
+ * Download an animation applied to a specific character.
2815
+ *
2816
+ * The animation is retargeted to the character's skeleton and returned
2817
+ * as raw file data in the requested format (FBX or GLB).
2818
+ *
2819
+ * @param options - Download options (characterId, animationId, format, etc.)
2820
+ * @returns Download result with raw file data and metadata.
2821
+ *
2822
+ * @throws {MixamoFormatError} If the requested format is not supported.
2823
+ * @throws {MixamoRateLimitError} If the API rate limit is exceeded after max retries.
2824
+ * @throws {MixamoError} For other API errors.
2825
+ */
2826
+ async downloadAnimation(options) {
2827
+ const format = options.format ?? "fbx";
2828
+ if (!SUPPORTED_FORMATS.includes(format)) {
2829
+ throw new MixamoFormatError(format);
2830
+ }
2831
+ const response = await this.requestWithRetry(
2832
+ "POST",
2833
+ `/characters/${encodeURIComponent(options.characterId)}/animations/${encodeURIComponent(options.animationId)}/download`,
2834
+ {
2835
+ format,
2836
+ include_skin: options.includeSkin ?? false,
2837
+ fps: options.fps ?? 30,
2838
+ keyframe_reduction: options.keyframeReduction ?? 0
2839
+ },
2840
+ { binary: true }
2841
+ );
2842
+ const suggestedFilename = `${options.characterId}_${options.animationId}.${format}`;
2843
+ const result = {
2844
+ data: response,
2845
+ contentType: format === "fbx" ? "application/octet-stream" : "model/gltf-binary",
2846
+ filename: suggestedFilename,
2847
+ sizeBytes: response.byteLength,
2848
+ format
2849
+ };
2850
+ if (this.config.debug) {
2851
+ console.debug(
2852
+ `[MixamoAPI] Animation downloaded: ${result.filename} (${result.sizeBytes} bytes)`
2853
+ );
2854
+ }
2855
+ return result;
2856
+ }
2857
+ // -----------------------------------------------------------------------
2858
+ // Format Detection & Validation
2859
+ // -----------------------------------------------------------------------
2860
+ /**
2861
+ * Detect mesh format from filename extension.
2862
+ *
2863
+ * @param filename - The mesh filename.
2864
+ * @returns The detected format.
2865
+ * @throws {MixamoFormatError} If the extension is not recognized.
2866
+ */
2867
+ detectFormat(filename) {
2868
+ const ext = filename.split(".").pop()?.toLowerCase();
2869
+ if (ext === "fbx") return "fbx";
2870
+ if (ext === "glb" || ext === "gltf") return "glb";
2871
+ throw new MixamoFormatError(ext ?? "unknown");
2872
+ }
2873
+ /**
2874
+ * Validate mesh topology for auto-rigging compatibility.
2875
+ *
2876
+ * Performs pre-flight checks on the mesh data to detect issues that
2877
+ * would cause the auto-rigging pipeline to fail or produce poor results.
2878
+ *
2879
+ * @param data - Raw mesh file data.
2880
+ * @param format - The mesh format.
2881
+ * @returns Array of topology issues found (may be empty if mesh is clean).
2882
+ */
2883
+ validateTopology(data, format) {
2884
+ const issues = [];
2885
+ const byteLength = data instanceof ArrayBuffer ? data.byteLength : data.length;
2886
+ if (byteLength < 100) {
2887
+ issues.push({
2888
+ code: "DEGENERATE_FACES",
2889
+ message: "File is too small to contain valid mesh data.",
2890
+ severity: "error"
2891
+ });
2892
+ return issues;
2893
+ }
2894
+ if (format === "fbx") {
2895
+ const header = new Uint8Array(
2896
+ data instanceof ArrayBuffer ? data : data.buffer,
2897
+ data instanceof ArrayBuffer ? 0 : data.byteOffset,
2898
+ Math.min(23, byteLength)
2899
+ );
2900
+ const magic = String.fromCharCode(...header.slice(0, 20));
2901
+ if (!magic.startsWith("Kaydara FBX Binary")) {
2902
+ if (byteLength < 1e3) {
2903
+ issues.push({
2904
+ code: "DEGENERATE_FACES",
2905
+ message: "FBX file does not contain a valid binary header. ASCII FBX may have limited support.",
2906
+ severity: "warning"
2907
+ });
2908
+ }
2909
+ }
2910
+ }
2911
+ if (format === "glb") {
2912
+ const header = new Uint8Array(
2913
+ data instanceof ArrayBuffer ? data : data.buffer,
2914
+ data instanceof ArrayBuffer ? 0 : data.byteOffset,
2915
+ Math.min(4, byteLength)
2916
+ );
2917
+ const glbMagic = header[0] === 103 && header[1] === 108 && header[2] === 84 && header[3] === 70;
2918
+ if (!glbMagic) {
2919
+ issues.push({
2920
+ code: "DEGENERATE_FACES",
2921
+ message: "GLB file does not contain a valid glTF binary header.",
2922
+ severity: "error"
2923
+ });
2924
+ }
2925
+ }
2926
+ const estimatedPolygons = byteLength / 50;
2927
+ if (estimatedPolygons > 5e5) {
2928
+ issues.push({
2929
+ code: "EXCESSIVE_POLYGONS",
2930
+ message: `Estimated polygon count (~${Math.round(estimatedPolygons).toLocaleString()}) exceeds recommended limit of 500,000 for auto-rigging. Consider decimating the mesh.`,
2931
+ severity: "warning"
2932
+ });
2933
+ }
2934
+ if (estimatedPolygons > 2e6) {
2935
+ issues.push({
2936
+ code: "EXCESSIVE_POLYGONS",
2937
+ message: `Estimated polygon count (~${Math.round(estimatedPolygons).toLocaleString()}) far exceeds the maximum of 2,000,000 for auto-rigging.`,
2938
+ severity: "error"
2939
+ });
2940
+ }
2941
+ return issues;
2942
+ }
2943
+ // -----------------------------------------------------------------------
2944
+ // HTTP Utilities
2945
+ // -----------------------------------------------------------------------
2946
+ /**
2947
+ * Create form data for file upload.
2948
+ */
2949
+ createFormData(data, filename, format) {
2950
+ return {
2951
+ file: data,
2952
+ filename,
2953
+ format,
2954
+ type: "character"
2955
+ };
2956
+ }
2957
+ /**
2958
+ * Execute an HTTP request with exponential backoff retry for rate limits.
2959
+ *
2960
+ * @param method - HTTP method.
2961
+ * @param endpoint - API endpoint path (appended to base URL).
2962
+ * @param body - Request body (JSON or form data).
2963
+ * @param options - Additional request options.
2964
+ * @returns Parsed response data.
2965
+ */
2966
+ async requestWithRetry(method, endpoint, body, options) {
2967
+ const baseUrl = this.config.credentials.apiBase ?? MIXAMO_API_BASE;
2968
+ const url = `${baseUrl}${endpoint}`;
2969
+ let lastError = null;
2970
+ for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
2971
+ try {
2972
+ const headers = {
2973
+ Authorization: `Bearer ${this.config.credentials.accessToken}`,
2974
+ Accept: options?.binary ? "application/octet-stream" : "application/json"
2975
+ };
2976
+ const fetchOptions = {
2977
+ method,
2978
+ headers,
2979
+ signal: AbortSignal.timeout(this.config.timeoutMs)
2980
+ };
2981
+ if (body && method !== "GET") {
2982
+ if (options?.isFormData) {
2983
+ headers["Content-Type"] = "multipart/form-data";
2984
+ fetchOptions.body = JSON.stringify(body);
2985
+ } else {
2986
+ headers["Content-Type"] = "application/json";
2987
+ fetchOptions.body = JSON.stringify(body);
2988
+ }
2989
+ }
2990
+ const response = await fetch(url, fetchOptions);
2991
+ if (response.status === 429) {
2992
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0", 10);
2993
+ const backoffMs = Math.min(
2994
+ retryAfter > 0 ? retryAfter * 1e3 : this.config.initialBackoffMs * Math.pow(2, attempt),
2995
+ MAX_BACKOFF_MS
2996
+ );
2997
+ if (this.config.debug) {
2998
+ console.warn(
2999
+ `[MixamoAPI] Rate limited (attempt ${attempt + 1}/${this.config.maxRetries}). Retrying in ${backoffMs}ms...`
3000
+ );
3001
+ }
3002
+ if (attempt === this.config.maxRetries - 1) {
3003
+ throw new MixamoRateLimitError(Math.ceil(backoffMs / 1e3), attempt + 1);
3004
+ }
3005
+ await this.sleep(backoffMs);
3006
+ continue;
3007
+ }
3008
+ if (!response.ok) {
3009
+ let errorMessage = `Mixamo API error: ${response.status} ${response.statusText}`;
3010
+ try {
3011
+ const errorBody = await response.json();
3012
+ if (errorBody.message) {
3013
+ errorMessage = errorBody.message;
3014
+ }
3015
+ if (errorBody.error) {
3016
+ errorMessage = errorBody.error;
3017
+ }
3018
+ } catch {
3019
+ }
3020
+ throw new MixamoError(errorMessage, `HTTP_${response.status}`);
3021
+ }
3022
+ if (options?.binary) {
3023
+ return await response.arrayBuffer();
3024
+ }
3025
+ return await response.json();
3026
+ } catch (error) {
3027
+ if (error instanceof MixamoRateLimitError || error instanceof MixamoTopologyError || error instanceof MixamoFormatError) {
3028
+ throw error;
3029
+ }
3030
+ lastError = error instanceof Error ? error : new Error(String(error));
3031
+ if (attempt < this.config.maxRetries - 1) {
3032
+ const backoffMs = Math.min(
3033
+ this.config.initialBackoffMs * Math.pow(2, attempt),
3034
+ MAX_BACKOFF_MS
3035
+ );
3036
+ if (this.config.debug) {
3037
+ console.warn(
3038
+ `[MixamoAPI] Request failed (attempt ${attempt + 1}/${this.config.maxRetries}): ${lastError.message}. Retrying in ${backoffMs}ms...`
3039
+ );
3040
+ }
3041
+ await this.sleep(backoffMs);
3042
+ continue;
3043
+ }
3044
+ }
3045
+ }
3046
+ throw new MixamoError(
3047
+ `Request failed after ${this.config.maxRetries} attempts: ${lastError?.message ?? "Unknown error"}`,
3048
+ "MAX_RETRIES_EXCEEDED"
3049
+ );
3050
+ }
3051
+ /**
3052
+ * Sleep for a given number of milliseconds.
3053
+ */
3054
+ sleep(ms) {
3055
+ return new Promise((resolve) => setTimeout(resolve, ms));
3056
+ }
3057
+ };
3058
+ var MixamoPresetMapper = class {
3059
+ constructor() {
3060
+ this.mappings = /* @__PURE__ */ new Map();
3061
+ this.reverseIndex = /* @__PURE__ */ new Map();
3062
+ this.loadDefaultMappings();
3063
+ }
3064
+ /**
3065
+ * Get the Mixamo animation mapping for a preset name.
3066
+ *
3067
+ * @param presetName - HoloScript animation preset name (e.g. 'walk', 'idle').
3068
+ * @returns The mapping, or undefined if no mapping exists.
3069
+ */
3070
+ getMixamoId(presetName) {
3071
+ return this.mappings.get(presetName.toLowerCase());
3072
+ }
3073
+ /**
3074
+ * Get the HoloScript preset name for a Mixamo animation ID.
3075
+ *
3076
+ * @param mixamoId - Mixamo animation ID.
3077
+ * @returns The preset name, or undefined if no reverse mapping exists.
3078
+ */
3079
+ getPresetName(mixamoId) {
3080
+ return this.reverseIndex.get(mixamoId);
3081
+ }
3082
+ /**
3083
+ * Get all preset-to-Mixamo mappings.
3084
+ *
3085
+ * @returns Array of all mappings.
3086
+ */
3087
+ getAllMappings() {
3088
+ return Array.from(this.mappings.values());
3089
+ }
3090
+ /**
3091
+ * Check if a mapping exists for a given preset name.
3092
+ *
3093
+ * @param presetName - HoloScript animation preset name.
3094
+ */
3095
+ hasMapping(presetName) {
3096
+ return this.mappings.has(presetName.toLowerCase());
3097
+ }
3098
+ /**
3099
+ * Register a custom preset-to-Mixamo mapping.
3100
+ *
3101
+ * @param mapping - The mapping to register.
3102
+ */
3103
+ registerMapping(mapping) {
3104
+ const key = mapping.presetName.toLowerCase();
3105
+ this.mappings.set(key, mapping);
3106
+ this.reverseIndex.set(mapping.mixamoAnimationId, key);
3107
+ for (const altId of mapping.alternativeIds) {
3108
+ this.reverseIndex.set(altId, key);
3109
+ }
3110
+ }
3111
+ /**
3112
+ * Search for mappings by Mixamo clip name (partial, case-insensitive).
3113
+ *
3114
+ * @param query - Search query string.
3115
+ * @returns Array of matching mappings.
3116
+ */
3117
+ searchByClipName(query) {
3118
+ const q = query.toLowerCase();
3119
+ const results = [];
3120
+ for (const mapping of this.mappings.values()) {
3121
+ if (mapping.mixamoClipName.toLowerCase().includes(q)) {
3122
+ results.push(mapping);
3123
+ }
3124
+ }
3125
+ return results;
3126
+ }
3127
+ /**
3128
+ * Load the default preset-to-Mixamo mappings for all 15 canonical presets.
3129
+ *
3130
+ * Mixamo animation IDs are based on the Mixamo REST API's internal
3131
+ * identifiers. These map to the canonical clip names defined in
3132
+ * @holoscript/animation-presets.
3133
+ */
3134
+ loadDefaultMappings() {
3135
+ const defaults = [
3136
+ {
3137
+ presetName: "walk",
3138
+ mixamoAnimationId: "14667",
3139
+ mixamoClipName: "Walking",
3140
+ alternativeIds: ["14668", "14670", "14671", "14672"],
3141
+ speedAdjustment: 1
3142
+ },
3143
+ {
3144
+ presetName: "run",
3145
+ mixamoAnimationId: "14524",
3146
+ mixamoClipName: "Running",
3147
+ alternativeIds: ["14525", "14526", "14527", "14528"],
3148
+ speedAdjustment: 1
3149
+ },
3150
+ {
3151
+ presetName: "idle",
3152
+ mixamoAnimationId: "14330",
3153
+ mixamoClipName: "Idle",
3154
+ alternativeIds: ["14331", "14332", "14333"],
3155
+ speedAdjustment: 1
3156
+ },
3157
+ {
3158
+ presetName: "jump",
3159
+ mixamoAnimationId: "14340",
3160
+ mixamoClipName: "Jump",
3161
+ alternativeIds: ["14341", "14342", "14343", "14344"],
3162
+ speedAdjustment: 1
3163
+ },
3164
+ {
3165
+ presetName: "attack",
3166
+ mixamoAnimationId: "14084",
3167
+ mixamoClipName: "Sword And Shield Attack",
3168
+ alternativeIds: ["14085", "14086", "14088", "14090"],
3169
+ speedAdjustment: 1
3170
+ },
3171
+ {
3172
+ presetName: "dance",
3173
+ mixamoAnimationId: "14189",
3174
+ mixamoClipName: "Hip Hop Dancing",
3175
+ alternativeIds: ["14190", "14191", "14192", "14193"],
3176
+ speedAdjustment: 1
3177
+ },
3178
+ {
3179
+ presetName: "wave",
3180
+ mixamoAnimationId: "14660",
3181
+ mixamoClipName: "Waving",
3182
+ alternativeIds: ["14661"],
3183
+ speedAdjustment: 1
3184
+ },
3185
+ {
3186
+ presetName: "speak",
3187
+ mixamoAnimationId: "14591",
3188
+ mixamoClipName: "Talking",
3189
+ alternativeIds: ["14592", "14593"],
3190
+ speedAdjustment: 1
3191
+ },
3192
+ {
3193
+ presetName: "sit",
3194
+ mixamoAnimationId: "14564",
3195
+ mixamoClipName: "Sitting",
3196
+ alternativeIds: ["14565", "14566"],
3197
+ speedAdjustment: 1
3198
+ },
3199
+ {
3200
+ presetName: "sleep",
3201
+ mixamoAnimationId: "14572",
3202
+ mixamoClipName: "Sleeping Idle",
3203
+ alternativeIds: ["14573"],
3204
+ speedAdjustment: 1
3205
+ },
3206
+ {
3207
+ presetName: "crouch",
3208
+ mixamoAnimationId: "14180",
3209
+ mixamoClipName: "Crouching Idle",
3210
+ alternativeIds: ["14181", "14182", "14183"],
3211
+ speedAdjustment: 1
3212
+ },
3213
+ {
3214
+ presetName: "swim",
3215
+ mixamoAnimationId: "14610",
3216
+ mixamoClipName: "Swimming",
3217
+ alternativeIds: ["14611", "14612", "14613"],
3218
+ speedAdjustment: 0.9
3219
+ },
3220
+ {
3221
+ presetName: "fly",
3222
+ mixamoAnimationId: "14250",
3223
+ mixamoClipName: "Flying",
3224
+ alternativeIds: ["14251", "14252", "14253"],
3225
+ speedAdjustment: 0.8
3226
+ },
3227
+ {
3228
+ presetName: "climb",
3229
+ mixamoAnimationId: "14160",
3230
+ mixamoClipName: "Climbing",
3231
+ alternativeIds: ["14161", "14162", "14163"],
3232
+ speedAdjustment: 0.8
3233
+ },
3234
+ {
3235
+ presetName: "emote",
3236
+ mixamoAnimationId: "14220",
3237
+ mixamoClipName: "Cheering",
3238
+ alternativeIds: ["14221", "14222", "14223"],
3239
+ speedAdjustment: 1
3240
+ }
3241
+ ];
3242
+ for (const mapping of defaults) {
3243
+ this.registerMapping(mapping);
3244
+ }
3245
+ }
3246
+ };
3247
+ var CharacterTemplateRegistry = class {
3248
+ /**
3249
+ * Creates a new CharacterTemplateRegistry pre-loaded with default templates.
3250
+ * Pass `false` to create an empty registry.
3251
+ */
3252
+ constructor(loadDefaults = true) {
3253
+ this.templates = /* @__PURE__ */ new Map();
3254
+ if (loadDefaults) {
3255
+ this.loadDefaultTemplates();
3256
+ }
3257
+ }
3258
+ // -----------------------------------------------------------------------
3259
+ // Lookup
3260
+ // -----------------------------------------------------------------------
3261
+ /**
3262
+ * Get a template by its unique identifier.
3263
+ *
3264
+ * @param id - Template ID.
3265
+ * @returns The template, or undefined if not found.
3266
+ */
3267
+ get(id) {
3268
+ return this.templates.get(id);
3269
+ }
3270
+ /**
3271
+ * Check if a template exists.
3272
+ *
3273
+ * @param id - Template ID.
3274
+ */
3275
+ has(id) {
3276
+ return this.templates.has(id);
3277
+ }
3278
+ // -----------------------------------------------------------------------
3279
+ // Filtering
3280
+ // -----------------------------------------------------------------------
3281
+ /**
3282
+ * Get all templates matching a specific archetype.
3283
+ *
3284
+ * @param archetype - The character archetype to filter by.
3285
+ * @returns Array of matching templates.
3286
+ */
3287
+ getByArchetype(archetype) {
3288
+ const results = [];
3289
+ for (const template of this.templates.values()) {
3290
+ if (template.archetype === archetype) {
3291
+ results.push(template);
3292
+ }
3293
+ }
3294
+ return results;
3295
+ }
3296
+ /**
3297
+ * Get all templates matching a specific rig type.
3298
+ *
3299
+ * @param rigType - Rig type (e.g. "humanoid", "quadruped").
3300
+ * @returns Array of matching templates.
3301
+ */
3302
+ getByRigType(rigType) {
3303
+ const results = [];
3304
+ for (const template of this.templates.values()) {
3305
+ if (template.rigType === rigType) {
3306
+ results.push(template);
3307
+ }
3308
+ }
3309
+ return results;
3310
+ }
3311
+ /**
3312
+ * Search templates by name, description, or tags.
3313
+ *
3314
+ * @param query - Search query (case-insensitive substring match).
3315
+ * @returns Array of matching templates.
3316
+ */
3317
+ search(query) {
3318
+ const q = query.toLowerCase();
3319
+ const results = [];
3320
+ for (const template of this.templates.values()) {
3321
+ const matchesName = template.name.toLowerCase().includes(q);
3322
+ const matchesDescription = template.description.toLowerCase().includes(q);
3323
+ const matchesTags = template.tags.some((t) => t.toLowerCase().includes(q));
3324
+ if (matchesName || matchesDescription || matchesTags) {
3325
+ results.push(template);
3326
+ }
3327
+ }
3328
+ return results;
3329
+ }
3330
+ // -----------------------------------------------------------------------
3331
+ // Enumeration
3332
+ // -----------------------------------------------------------------------
3333
+ /**
3334
+ * Get all registered templates.
3335
+ */
3336
+ getAll() {
3337
+ return Array.from(this.templates.values());
3338
+ }
3339
+ /**
3340
+ * Get all available archetypes that have at least one template.
3341
+ */
3342
+ getAvailableArchetypes() {
3343
+ const archetypes = /* @__PURE__ */ new Set();
3344
+ for (const template of this.templates.values()) {
3345
+ archetypes.add(template.archetype);
3346
+ }
3347
+ return Array.from(archetypes);
3348
+ }
3349
+ /**
3350
+ * Get the total number of registered templates.
3351
+ */
3352
+ get size() {
3353
+ return this.templates.size;
3354
+ }
3355
+ // -----------------------------------------------------------------------
3356
+ // Registration
3357
+ // -----------------------------------------------------------------------
3358
+ /**
3359
+ * Register a custom character template.
3360
+ *
3361
+ * @param template - The template to register.
3362
+ */
3363
+ register(template) {
3364
+ this.templates.set(template.id, template);
3365
+ }
3366
+ /**
3367
+ * Remove a template from the registry.
3368
+ *
3369
+ * @param id - Template ID to remove.
3370
+ * @returns `true` if the template was found and removed.
3371
+ */
3372
+ unregister(id) {
3373
+ return this.templates.delete(id);
3374
+ }
3375
+ // -----------------------------------------------------------------------
3376
+ // Default Templates
3377
+ // -----------------------------------------------------------------------
3378
+ /**
3379
+ * Load the 12 default pre-rigged character templates.
3380
+ *
3381
+ * These cover common humanoid archetypes used in spatial computing,
3382
+ * VR, game development, and animation workflows.
3383
+ */
3384
+ loadDefaultTemplates() {
3385
+ const defaults = [
3386
+ // ---- Male Adult Archetypes ----
3387
+ {
3388
+ id: "xbot",
3389
+ name: "X Bot",
3390
+ archetype: "male-adult",
3391
+ description: "Mixamo default male humanoid. Clean topology, production-ready skeleton with 65 bones. Ideal for prototyping and general-purpose character animation.",
3392
+ mixamoCharacterId: "mixamo-xbot-v1",
3393
+ rigType: "humanoid",
3394
+ boneCount: 65,
3395
+ polygonCount: 12456,
3396
+ availableFormats: ["fbx", "glb"],
3397
+ defaultAnimations: ["idle", "walk", "run", "jump"],
3398
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/xbot.png",
3399
+ tags: ["male", "humanoid", "default", "robot", "prototype"],
3400
+ isDefault: true
3401
+ },
3402
+ {
3403
+ id: "brute",
3404
+ name: "Brute",
3405
+ archetype: "male-adult",
3406
+ description: "Muscular male character with heavy build. Suitable for warrior, fighter, and action archetypes. Enhanced upper body deformation.",
3407
+ mixamoCharacterId: "mixamo-brute-v1",
3408
+ rigType: "humanoid",
3409
+ boneCount: 65,
3410
+ polygonCount: 18234,
3411
+ availableFormats: ["fbx", "glb"],
3412
+ defaultAnimations: ["idle", "walk", "attack"],
3413
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/brute.png",
3414
+ tags: ["male", "humanoid", "muscular", "warrior", "heavy"],
3415
+ isDefault: true
3416
+ },
3417
+ // ---- Female Adult Archetypes ----
3418
+ {
3419
+ id: "ybot",
3420
+ name: "Y Bot",
3421
+ archetype: "female-adult",
3422
+ description: "Mixamo default female humanoid. Clean topology, production-ready skeleton with 65 bones. Ideal for prototyping and general-purpose character animation.",
3423
+ mixamoCharacterId: "mixamo-ybot-v1",
3424
+ rigType: "humanoid",
3425
+ boneCount: 65,
3426
+ polygonCount: 11892,
3427
+ availableFormats: ["fbx", "glb"],
3428
+ defaultAnimations: ["idle", "walk", "run", "dance"],
3429
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/ybot.png",
3430
+ tags: ["female", "humanoid", "default", "robot", "prototype"],
3431
+ isDefault: true
3432
+ },
3433
+ {
3434
+ id: "maria",
3435
+ name: "Maria",
3436
+ archetype: "female-adult",
3437
+ description: "Realistic female character with detailed facial blend shapes. Suitable for cinematic sequences and social VR.",
3438
+ mixamoCharacterId: "mixamo-maria-v1",
3439
+ rigType: "humanoid",
3440
+ boneCount: 67,
3441
+ polygonCount: 22340,
3442
+ availableFormats: ["fbx", "glb"],
3443
+ defaultAnimations: ["idle", "walk", "speak", "wave"],
3444
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/maria.png",
3445
+ tags: ["female", "humanoid", "realistic", "facial", "cinematic"],
3446
+ isDefault: true
3447
+ },
3448
+ // ---- Child Archetypes ----
3449
+ {
3450
+ id: "ty",
3451
+ name: "Ty",
3452
+ archetype: "male-child",
3453
+ description: "Young male character with child proportions. Adjusted bone hierarchy for accurate child movement and shorter stride length.",
3454
+ mixamoCharacterId: "mixamo-ty-v1",
3455
+ rigType: "humanoid",
3456
+ boneCount: 63,
3457
+ polygonCount: 9876,
3458
+ availableFormats: ["fbx", "glb"],
3459
+ defaultAnimations: ["idle", "walk", "run", "jump"],
3460
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/ty.png",
3461
+ tags: ["child", "male", "humanoid", "young", "small"],
3462
+ isDefault: true
3463
+ },
3464
+ {
3465
+ id: "lily",
3466
+ name: "Lily",
3467
+ archetype: "female-child",
3468
+ description: "Young female character with child proportions. Suitable for educational, family-friendly, and social VR applications.",
3469
+ mixamoCharacterId: "mixamo-lily-v1",
3470
+ rigType: "humanoid",
3471
+ boneCount: 63,
3472
+ polygonCount: 10234,
3473
+ availableFormats: ["fbx", "glb"],
3474
+ defaultAnimations: ["idle", "walk", "dance", "wave"],
3475
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/lily.png",
3476
+ tags: ["child", "female", "humanoid", "young", "small"],
3477
+ isDefault: true
3478
+ },
3479
+ // ---- Robot Archetypes ----
3480
+ {
3481
+ id: "android",
3482
+ name: "Android Unit",
3483
+ archetype: "robot-humanoid",
3484
+ description: "Humanoid robot with mechanical joint articulation. Rigid body segments connected by revolute joints. Suitable for sci-fi and robotics simulations.",
3485
+ mixamoCharacterId: "mixamo-android-v1",
3486
+ rigType: "humanoid",
3487
+ boneCount: 60,
3488
+ polygonCount: 15678,
3489
+ availableFormats: ["fbx", "glb"],
3490
+ defaultAnimations: ["idle", "walk", "run"],
3491
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/android.png",
3492
+ tags: ["robot", "humanoid", "mechanical", "sci-fi", "android"],
3493
+ isDefault: true
3494
+ },
3495
+ {
3496
+ id: "mech-worker",
3497
+ name: "Mech Worker",
3498
+ archetype: "robot-mechanical",
3499
+ description: "Industrial mechanical robot with hydraulic limbs. Heavy-duty construction archetype with simplified joint hierarchy for mechanical animation.",
3500
+ mixamoCharacterId: "mixamo-mechworker-v1",
3501
+ rigType: "humanoid",
3502
+ boneCount: 48,
3503
+ polygonCount: 20456,
3504
+ availableFormats: ["fbx", "glb"],
3505
+ defaultAnimations: ["idle", "walk", "attack"],
3506
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/mechworker.png",
3507
+ tags: ["robot", "mechanical", "industrial", "heavy", "hydraulic"],
3508
+ isDefault: true
3509
+ },
3510
+ // ---- Creature Archetypes ----
3511
+ {
3512
+ id: "drake",
3513
+ name: "Drake",
3514
+ archetype: "creature-bipedal",
3515
+ description: "Bipedal dragon-like creature with wing attachments. Modified humanoid rig with additional wing and tail bone chains.",
3516
+ mixamoCharacterId: "mixamo-drake-v1",
3517
+ rigType: "humanoid",
3518
+ boneCount: 78,
3519
+ polygonCount: 24890,
3520
+ availableFormats: ["fbx", "glb"],
3521
+ defaultAnimations: ["idle", "walk", "attack", "fly"],
3522
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/drake.png",
3523
+ tags: ["creature", "bipedal", "dragon", "fantasy", "wings", "tail"],
3524
+ isDefault: true
3525
+ },
3526
+ {
3527
+ id: "wolf",
3528
+ name: "Wolf",
3529
+ archetype: "creature-quadruped",
3530
+ description: "Quadruped wolf with full locomotion rig. Four-legged skeleton hierarchy with spine flexibility and tail chain.",
3531
+ mixamoCharacterId: "mixamo-wolf-v1",
3532
+ rigType: "quadruped",
3533
+ boneCount: 52,
3534
+ polygonCount: 14567,
3535
+ availableFormats: ["fbx", "glb"],
3536
+ defaultAnimations: ["idle", "walk", "run"],
3537
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/wolf.png",
3538
+ tags: ["creature", "quadruped", "wolf", "animal", "four-legged"],
3539
+ isDefault: true
3540
+ },
3541
+ // ---- Stylized Archetypes ----
3542
+ {
3543
+ id: "paladin",
3544
+ name: "Paladin",
3545
+ archetype: "armored-knight",
3546
+ description: "Armored knight with plate armor and cape physics. Heavy character suitable for medieval and fantasy combat scenarios.",
3547
+ mixamoCharacterId: "mixamo-paladin-v1",
3548
+ rigType: "humanoid",
3549
+ boneCount: 68,
3550
+ polygonCount: 28456,
3551
+ availableFormats: ["fbx", "glb"],
3552
+ defaultAnimations: ["idle", "walk", "attack", "crouch"],
3553
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/paladin.png",
3554
+ tags: ["humanoid", "armored", "knight", "medieval", "fantasy", "heavy"],
3555
+ isDefault: true
3556
+ },
3557
+ {
3558
+ id: "trooper",
3559
+ name: "Trooper",
3560
+ archetype: "sci-fi-soldier",
3561
+ description: "Sci-fi soldier with tactical gear and visor. Modern military archetype suitable for FPS, action, and tactical simulations.",
3562
+ mixamoCharacterId: "mixamo-trooper-v1",
3563
+ rigType: "humanoid",
3564
+ boneCount: 65,
3565
+ polygonCount: 19234,
3566
+ availableFormats: ["fbx", "glb"],
3567
+ defaultAnimations: ["idle", "walk", "run", "crouch", "attack"],
3568
+ thumbnailUrl: "https://www.mixamo.com/thumbnails/trooper.png",
3569
+ tags: ["humanoid", "soldier", "sci-fi", "military", "tactical", "fps"],
3570
+ isDefault: true
3571
+ }
3572
+ ];
3573
+ for (const template of defaults) {
3574
+ this.templates.set(template.id, template);
3575
+ }
3576
+ }
3577
+ };
3578
+
3579
+ export { CharacterTemplateRegistry, ErrorFormatter, HoloScriptREPL, HotReloadWatcher, MaterialEditor, MixamoAPI, MixamoError, MixamoFormatError, MixamoPresetMapper, MixamoRateLimitError, MixamoTopologyError, SceneInspector, SourceMapGenerator, VisualEditor, getMaterialEditorBuiltinPresets, listMaterialEditorQuickPickPresetNames, listMaterialEditorQuickPickPresetsByCategory, startREPL };
3580
+ //# sourceMappingURL=index.js.map
3581
+ //# sourceMappingURL=index.js.map