agentworkshop 0.7.44 → 0.7.47

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 (1880) hide show
  1. package/.AgentWorkShop/prompts/host-tools.json +265 -6
  2. package/.AgentWorkShop/prompts/lead-supervise.md +54 -29
  3. package/.AgentWorkShop/prompts/peer-reply-optional.md +6 -2
  4. package/.AgentWorkShop/prompts/peer-reply-required.md +8 -7
  5. package/.output/.AgentWorkShop/aml-python/__pycache__/amlkit.cpython-312.pyc +0 -0
  6. package/.output/.AgentWorkShop/aml-python/amlkit.py +185 -120
  7. package/.output/.AgentWorkShop/aml-python/requirements.txt +9 -7
  8. package/.output/.AgentWorkShop/aml-python/train-hybrid-example.py +87 -0
  9. package/.output/.AgentWorkShop/prompts/host-tools.json +265 -6
  10. package/.output/.AgentWorkShop/prompts/lead-supervise.md +54 -29
  11. package/.output/.AgentWorkShop/prompts/peer-reply-optional.md +6 -2
  12. package/.output/.AgentWorkShop/prompts/peer-reply-required.md +8 -7
  13. package/.output/.AgentWorkShop/schema.json +12 -0
  14. package/.output/nitro.json +1 -1
  15. package/.output/public/_nuxt/-hB4kEmt.js +1 -0
  16. package/.output/public/_nuxt/1g9IT9Qh2.js +1 -0
  17. package/.output/public/_nuxt/74x7BOWv2.js +1 -0
  18. package/.output/public/_nuxt/9c5GOgbu.js +1 -0
  19. package/.output/public/_nuxt/9zrLAN2o2.js +1 -0
  20. package/.output/public/_nuxt/B1RT9NTO2.js +1 -0
  21. package/.output/public/_nuxt/B2x8SKRO.js +1 -0
  22. package/.output/public/_nuxt/B6Olw0Op.js +1 -0
  23. package/.output/public/_nuxt/B7jlMWsz.js +1 -0
  24. package/.output/public/_nuxt/B8cnjDtt.js +1 -0
  25. package/.output/public/_nuxt/B9rIcCX12.js +1 -0
  26. package/.output/public/_nuxt/BACBBVdw.js +1 -0
  27. package/.output/public/_nuxt/BBhLVYdM.js +1 -0
  28. package/.output/public/_nuxt/BDmNbZ252.js +1 -0
  29. package/.output/public/_nuxt/BFVf8HLR.js +1 -0
  30. package/.output/public/_nuxt/BGKUW7ra.js +1 -0
  31. package/.output/public/_nuxt/BH3obZJ42.js +1 -0
  32. package/.output/public/_nuxt/BHwG4yNs.js +1 -0
  33. package/.output/public/_nuxt/BIOq3xqe2.js +1 -0
  34. package/.output/public/_nuxt/BITZvQBK.js +1 -0
  35. package/.output/public/_nuxt/BL_dbd2v2.js +1 -0
  36. package/.output/public/_nuxt/BMMoqi94.js +1 -0
  37. package/.output/public/_nuxt/BNnnYEYh2.js +1 -0
  38. package/.output/public/_nuxt/BQXCMsXk.js +1 -0
  39. package/.output/public/_nuxt/BS9P_Ies2.js +1 -0
  40. package/.output/public/_nuxt/BWU1pvLL.js +1 -0
  41. package/.output/public/_nuxt/B_9_BM9G.js +3 -0
  42. package/.output/public/_nuxt/B_LcD7qE2.js +1 -0
  43. package/.output/public/_nuxt/BcQXL8pb.js +1 -0
  44. package/.output/public/_nuxt/BdM_Qr752.js +1 -0
  45. package/.output/public/_nuxt/BiOSWsjD.js +2 -0
  46. package/.output/public/_nuxt/BkSViqQd.js +1 -0
  47. package/.output/public/_nuxt/Bl5dYv4m.js +1 -0
  48. package/.output/public/_nuxt/BmiMC_BC.js +2 -0
  49. package/.output/public/_nuxt/BnpY_zNx.js +1 -0
  50. package/.output/public/_nuxt/BqYVLAcm.js +1 -0
  51. package/.output/public/_nuxt/Br3AySNQ.js +1 -0
  52. package/.output/public/_nuxt/Br3MggOL.js +1 -0
  53. package/.output/public/_nuxt/BrRX-l9B2.js +4108 -0
  54. package/.output/public/_nuxt/Bt2ZL5332.js +1 -0
  55. package/.output/public/_nuxt/Bub2P5me.js +28 -0
  56. package/.output/public/_nuxt/BvRk9kiK.js +0 -0
  57. package/.output/public/_nuxt/BvRk9kiK2.js +0 -0
  58. package/.output/public/_nuxt/Bwc6lt2X.js +3 -0
  59. package/.output/public/_nuxt/BxHBIVAg.js +1 -0
  60. package/.output/public/_nuxt/BxYTM0hb.js +1 -0
  61. package/.output/public/_nuxt/ByMj6e7s.js +3 -0
  62. package/.output/public/_nuxt/BzvHUfRR2.js +1 -0
  63. package/.output/public/_nuxt/C2YYOgMe.js +42 -0
  64. package/.output/public/_nuxt/C3twEwTh2.js +1 -0
  65. package/.output/public/_nuxt/C4WhK2wq.js +1 -0
  66. package/.output/public/_nuxt/C50LmoI1.js +0 -0
  67. package/.output/public/_nuxt/CAaSPBqk.js +1 -0
  68. package/.output/public/_nuxt/CBDX7p73.js +1 -0
  69. package/.output/public/_nuxt/CD9lpEz22.js +13 -0
  70. package/.output/public/_nuxt/CGZVhYJ4.js +1 -0
  71. package/.output/public/_nuxt/CJRDL4EG.js +1 -0
  72. package/.output/public/_nuxt/CKQPwwZa2.js +1 -0
  73. package/.output/public/_nuxt/CLySL7mW.js +75 -0
  74. package/.output/public/_nuxt/CMXcOb6W.js +1 -0
  75. package/.output/public/_nuxt/CN1CDAwe.js +1 -0
  76. package/.output/public/_nuxt/CP0I2l-6.js +1 -0
  77. package/.output/public/_nuxt/CP0Nep9S.js +1 -0
  78. package/.output/public/_nuxt/CPeZNE4S2.js +1 -0
  79. package/.output/public/_nuxt/CPmNRTBk2.js +21 -0
  80. package/.output/public/_nuxt/CQabB0oy.js +1 -0
  81. package/.output/public/_nuxt/CT-vTjcG.js +1 -0
  82. package/.output/public/_nuxt/CTVfcnjU.js +3 -0
  83. package/.output/public/_nuxt/CWzp2R6G.js +1 -0
  84. package/.output/public/_nuxt/C_VAXV7I2.js +7 -0
  85. package/.output/public/_nuxt/CavpkQb0.js +1 -0
  86. package/.output/public/_nuxt/CeWnOF3O.js +1 -0
  87. package/.output/public/_nuxt/CfLUvgr32.js +1 -0
  88. package/.output/public/_nuxt/Ch1P2mIf.js +1 -0
  89. package/.output/public/_nuxt/ChoLwR-s.js +1 -0
  90. package/.output/public/_nuxt/Chphw5IA2.js +1 -0
  91. package/.output/public/_nuxt/ChtjzEpK2.js +19 -0
  92. package/.output/public/_nuxt/CiTQY_n32.js +1 -0
  93. package/.output/public/_nuxt/Ck5oMvUk.js +1 -0
  94. package/.output/public/_nuxt/Ckafrjnw2.js +1 -0
  95. package/.output/public/_nuxt/Cm_PTn9t2.js +1 -0
  96. package/.output/public/_nuxt/Cmp8PMrA.js +1 -0
  97. package/.output/public/_nuxt/Cnrmlixo.js +3 -0
  98. package/.output/public/_nuxt/Cr518Gh02.js +1 -0
  99. package/.output/public/_nuxt/Crbir9cT.js +1 -0
  100. package/.output/public/_nuxt/CtPNnATm2.js +31 -0
  101. package/.output/public/_nuxt/Cu8c3oMN2.js +1 -0
  102. package/.output/public/_nuxt/CwLQAKay2.js +1 -0
  103. package/.output/public/_nuxt/CzlUIbK32.js +17 -0
  104. package/.output/public/_nuxt/D6lZftfy.js +1 -0
  105. package/.output/public/_nuxt/D83nVQPl.js +1 -0
  106. package/.output/public/_nuxt/D89BUtx6.js +1 -0
  107. package/.output/public/_nuxt/DCtk3qqr.js +1 -0
  108. package/.output/public/_nuxt/DD6Z0yPN2.js +1 -0
  109. package/.output/public/_nuxt/DGw8juu_.js +2 -0
  110. package/.output/public/_nuxt/DHSDk8Bq2.js +4 -0
  111. package/.output/public/_nuxt/DJdDFnjG2.js +1 -0
  112. package/.output/public/_nuxt/DJyNDnag.js +1 -0
  113. package/.output/public/_nuxt/DK3Fl9T5.js +1 -0
  114. package/.output/public/_nuxt/DLPuRMeG.js +19 -0
  115. package/.output/public/_nuxt/DMYYS5sK.js +1 -0
  116. package/.output/public/_nuxt/DNno23Jf2.js +1 -0
  117. package/.output/public/_nuxt/DNoEoo8t.js +1 -0
  118. package/.output/public/_nuxt/DPUXbTg92.js +1 -0
  119. package/.output/public/_nuxt/DRpFsrno.js +1 -0
  120. package/.output/public/_nuxt/DXIaadqv.js +1 -0
  121. package/.output/public/_nuxt/D_7AzAWe.js +1 -0
  122. package/.output/public/_nuxt/Dbe9TFc9.js +1 -0
  123. package/.output/public/_nuxt/DdNujZN5.js +1 -0
  124. package/.output/public/_nuxt/DdvQ7LRI.js +14 -0
  125. package/.output/public/_nuxt/DhFv8S2Z.js +1 -0
  126. package/.output/public/_nuxt/DkJVtqfc.js +1 -0
  127. package/.output/public/_nuxt/DkLCjrCk2.js +47 -0
  128. package/.output/public/_nuxt/DkpmSnHX.js +1 -0
  129. package/.output/public/_nuxt/DlUQa0wn2.js +1 -0
  130. package/.output/public/_nuxt/DoKpLEqc.js +1 -0
  131. package/.output/public/_nuxt/DpqA8D8_.js +1 -0
  132. package/.output/public/_nuxt/Dq0emNZi2.js +1 -0
  133. package/.output/public/_nuxt/DqtgGFEc.js +1 -0
  134. package/.output/public/_nuxt/DqtnqNgL2.js +1 -0
  135. package/.output/public/_nuxt/Dqz5HiXa.js +2 -0
  136. package/.output/public/_nuxt/DxagrjB8.js +1 -0
  137. package/.output/public/_nuxt/Dy3oXKdn2.js +1 -0
  138. package/.output/public/_nuxt/DzBbYWrw2.js +1 -0
  139. package/.output/public/_nuxt/EhhQmlBV2.js +1 -0
  140. package/.output/public/_nuxt/H2rNs1O82.js +1 -0
  141. package/.output/public/_nuxt/H6pPjo0l.js +1 -0
  142. package/.output/public/_nuxt/JAM4aNC_.js +1 -0
  143. package/.output/public/_nuxt/JgheoP3e.js +1 -0
  144. package/.output/public/_nuxt/LwIFGLgF.js +1 -0
  145. package/.output/public/_nuxt/Nw_6OpQZ2.js +1 -0
  146. package/.output/public/_nuxt/OXtV_ukR.js +1 -0
  147. package/.output/public/_nuxt/OmpTerminalPanel.ComMDrA8.css +1 -0
  148. package/.output/public/_nuxt/PLGqdko0.js +1 -0
  149. package/.output/public/_nuxt/PixDfCSm.js +1 -0
  150. package/.output/public/_nuxt/RAqsw632.js +1 -0
  151. package/.output/public/_nuxt/Sjjmp5OD2.js +1 -0
  152. package/.output/public/_nuxt/TOWR4WpO.js +1 -0
  153. package/.output/public/_nuxt/TownView.BvlfuLtP.css +1 -0
  154. package/.output/public/_nuxt/U8oQxruK.js +1 -0
  155. package/.output/public/_nuxt/WleoCtUX.js +1 -0
  156. package/.output/public/_nuxt/YaroNkJZ2.js +1 -0
  157. package/.output/public/_nuxt/Z-Z6EhXs.js +1 -0
  158. package/.output/public/_nuxt/ZuQtOnfI.js +1 -0
  159. package/.output/public/_nuxt/ZvXovq7N2.js +1 -0
  160. package/.output/public/_nuxt/_id_.BTrFUIHX.css +1 -0
  161. package/.output/public/_nuxt/_id_.Bossmhv1.css +1 -0
  162. package/.output/public/_nuxt/_wsId_.D-u-dbP9.css +1 -0
  163. package/.output/public/_nuxt/agents.Cgm_JG9L.css +1 -0
  164. package/.output/public/_nuxt/aml.Bi5X6_oi.css +1 -0
  165. package/.output/public/_nuxt/bg2adfck.js +1 -0
  166. package/.output/public/_nuxt/bt__BP-I.js +13 -0
  167. package/.output/public/_nuxt/builds/latest.json +1 -1
  168. package/.output/public/_nuxt/builds/meta/1b18aaf2-ccbc-45e0-b9c3-107655b226f9.json +1 -0
  169. package/.output/public/_nuxt/cBI0JqsV2.js +209 -0
  170. package/.output/public/_nuxt/cDcDztgb2.js +1 -0
  171. package/.output/public/_nuxt/cGSl2GZp.js +1 -0
  172. package/.output/public/_nuxt/csitHKnE2.js +1 -0
  173. package/.output/public/_nuxt/d-w5jR8R.js +1 -0
  174. package/.output/public/_nuxt/daq.0uieKcZk.css +1 -0
  175. package/.output/public/_nuxt/dcw.pQRVkAi_.css +1 -0
  176. package/.output/public/_nuxt/default.uao9U50c.css +1 -0
  177. package/.output/public/_nuxt/e7BBCLxr.js +1 -0
  178. package/.output/public/_nuxt/entry.6DEjPWvR.css +1 -0
  179. package/.output/public/_nuxt/eutnXI4F.js +24 -0
  180. package/.output/public/_nuxt/fxWyOzyp2.js +1 -0
  181. package/.output/public/_nuxt/hkMGMTqt.js +1 -0
  182. package/.output/public/_nuxt/ikAUnxx3.js +1 -0
  183. package/.output/public/_nuxt/lWYQbIR3.js +1 -0
  184. package/.output/public/_nuxt/logs.CD0Olr3c.css +1 -0
  185. package/.output/public/_nuxt/monitor.CI2v5Nvl.css +1 -0
  186. package/.output/public/_nuxt/pages.CMhSEhEb.css +1 -0
  187. package/.output/public/_nuxt/permissions.DxuOOIqQ.css +1 -0
  188. package/.output/public/_nuxt/plugins.C50x1qZp.css +1 -0
  189. package/.output/public/_nuxt/qOlYoGLf.js +1 -0
  190. package/.output/public/_nuxt/schedules.BXNJ9q_I.css +1 -0
  191. package/.output/public/_nuxt/settings.Bo5EYkLI.css +1 -0
  192. package/.output/public/_nuxt/teams.sGmx1AdX.css +1 -0
  193. package/.output/public/_nuxt/tokens.DFMFaCYU.css +1 -0
  194. package/.output/public/_nuxt/uI9Ki_XZ.js +9 -0
  195. package/.output/public/_nuxt/users.DBxcKnfg.css +1 -0
  196. package/.output/public/_nuxt/v55S8LnT.js +1 -0
  197. package/.output/public/_nuxt/wDPc4SSR.js +106 -0
  198. package/.output/public/_nuxt/workshop.b4YRXEe0.css +1 -0
  199. package/.output/public/_nuxt/y9kob70X.js +1 -0
  200. package/.output/public/_nuxt/zh-S_7rN.js +1 -0
  201. package/.output/server/chunks/_/LeftOutlined.mjs +1 -1
  202. package/.output/server/chunks/_/SearchOutlined.mjs +1243 -279
  203. package/.output/server/chunks/_/SearchOutlined.mjs.map +1 -1
  204. package/.output/server/chunks/_/binding-authz.mjs +1 -1
  205. package/.output/server/chunks/_/character-asset.repo.mjs +1 -1
  206. package/.output/server/chunks/_/entity.mjs +1 -1
  207. package/.output/server/chunks/_/env-manager.mjs +1 -1
  208. package/.output/server/chunks/_/host-bindings.mjs +1 -1
  209. package/.output/server/chunks/_/nitro.mjs +27006 -19064
  210. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  211. package/.output/server/chunks/_/scene-layout.repo.mjs +1 -1
  212. package/.output/server/chunks/build/Checkbox-4EdGAAl_.mjs +206 -0
  213. package/.output/server/chunks/build/Checkbox-4EdGAAl_.mjs.map +1 -0
  214. package/.output/server/chunks/build/FormItemContext-4ORSzhWv.mjs +72 -0
  215. package/.output/server/chunks/build/FormItemContext-4ORSzhWv.mjs.map +1 -0
  216. package/.output/server/chunks/build/OmpTerminalPanel-EQHrOJbw.mjs +942 -0
  217. package/.output/server/chunks/build/OmpTerminalPanel-EQHrOJbw.mjs.map +1 -0
  218. package/.output/server/chunks/build/PluginSlot-BP_qedpQ.mjs +399 -0
  219. package/.output/server/chunks/build/PluginSlot-BP_qedpQ.mjs.map +1 -0
  220. package/.output/server/chunks/build/TownView-BAR8CPIZ.mjs +6373 -0
  221. package/.output/server/chunks/build/TownView-BAR8CPIZ.mjs.map +1 -0
  222. package/.output/server/chunks/build/_id_-DgMpjM3_.mjs +1354 -0
  223. package/.output/server/chunks/build/_id_-DgMpjM3_.mjs.map +1 -0
  224. package/.output/server/chunks/build/_id_-Dv3BQbYV.mjs +388 -0
  225. package/.output/server/chunks/build/_id_-Dv3BQbYV.mjs.map +1 -0
  226. package/.output/server/chunks/build/_wsId_-BKJvFmyf.mjs +13109 -0
  227. package/.output/server/chunks/build/_wsId_-BKJvFmyf.mjs.map +1 -0
  228. package/.output/server/chunks/build/agents-B2GLPmn2.mjs +1085 -0
  229. package/.output/server/chunks/build/agents-B2GLPmn2.mjs.map +1 -0
  230. package/.output/server/chunks/build/aml-DdWicH3t.mjs +806 -0
  231. package/.output/server/chunks/build/aml-DdWicH3t.mjs.map +1 -0
  232. package/.output/server/chunks/build/api-BY6ji7RC.mjs +50 -0
  233. package/.output/server/chunks/build/api-BY6ji7RC.mjs.map +1 -0
  234. package/.output/server/chunks/build/api-error-BwAQLw44.mjs +47 -0
  235. package/.output/server/chunks/build/api-error-BwAQLw44.mjs.map +1 -0
  236. package/.output/server/chunks/build/apiClient-D281qrK2.mjs +81 -0
  237. package/.output/server/chunks/build/apiClient-D281qrK2.mjs.map +1 -0
  238. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +1 -1
  239. package/.output/server/chunks/build/card-CUSocE9p.mjs +953 -0
  240. package/.output/server/chunks/build/card-CUSocE9p.mjs.map +1 -0
  241. package/.output/server/chunks/build/channel-templates-RPOtkgRf.mjs +846 -0
  242. package/.output/server/chunks/build/channel-templates-RPOtkgRf.mjs.map +1 -0
  243. package/.output/server/chunks/build/checkbox-C4AKj81e.mjs +439 -0
  244. package/.output/server/chunks/build/checkbox-C4AKj81e.mjs.map +1 -0
  245. package/.output/server/chunks/build/client-only-qNA2OVV_.mjs +43 -0
  246. package/.output/server/chunks/build/client-only-qNA2OVV_.mjs.map +1 -0
  247. package/.output/server/chunks/build/constants-HPHJvjne.mjs +139 -0
  248. package/.output/server/chunks/build/constants-HPHJvjne.mjs.map +1 -0
  249. package/.output/server/chunks/build/core-CgTOPS4t.mjs +294 -0
  250. package/.output/server/chunks/build/core-CgTOPS4t.mjs.map +1 -0
  251. package/.output/server/chunks/build/daq-CPtsh0e7.mjs +1253 -0
  252. package/.output/server/chunks/build/daq-CPtsh0e7.mjs.map +1 -0
  253. package/.output/server/chunks/build/dcw-ry5Gyw6Y.mjs +478 -0
  254. package/.output/server/chunks/build/dcw-ry5Gyw6Y.mjs.map +1 -0
  255. package/.output/server/chunks/build/default-DxOE1eB0.mjs +2061 -0
  256. package/.output/server/chunks/build/default-DxOE1eB0.mjs.map +1 -0
  257. package/.output/server/chunks/build/descriptions-CFaUq4y4.mjs +406 -0
  258. package/.output/server/chunks/build/descriptions-CFaUq4y4.mjs.map +1 -0
  259. package/.output/server/chunks/build/drawer-Cfh9_zft.mjs +834 -0
  260. package/.output/server/chunks/build/drawer-Cfh9_zft.mjs.map +1 -0
  261. package/.output/server/chunks/build/dropdown-KdKWCS5K.mjs +556 -0
  262. package/.output/server/chunks/build/dropdown-KdKWCS5K.mjs.map +1 -0
  263. package/.output/server/chunks/build/dropdown-Yev6knaj.mjs +14 -0
  264. package/.output/server/chunks/build/dropdown-Yev6knaj.mjs.map +1 -0
  265. package/.output/server/chunks/build/form-uSlNqIAV.mjs +4711 -0
  266. package/.output/server/chunks/build/form-uSlNqIAV.mjs.map +1 -0
  267. package/.output/server/chunks/build/input-Cr_KFj4T.mjs +1381 -0
  268. package/.output/server/chunks/build/input-Cr_KFj4T.mjs.map +1 -0
  269. package/.output/server/chunks/build/input-number-BRVnxeiA.mjs +1119 -0
  270. package/.output/server/chunks/build/input-number-BRVnxeiA.mjs.map +1 -0
  271. package/.output/server/chunks/build/interval-DvHBIIZh.mjs +9 -0
  272. package/.output/server/chunks/build/interval-DvHBIIZh.mjs.map +1 -0
  273. package/.output/server/chunks/build/logs-CeaiO3y9.mjs +367 -0
  274. package/.output/server/chunks/build/logs-CeaiO3y9.mjs.map +1 -0
  275. package/.output/server/chunks/build/menu-BgkRSSx7.mjs +1949 -0
  276. package/.output/server/chunks/build/menu-BgkRSSx7.mjs.map +1 -0
  277. package/.output/server/chunks/build/monitor-DnuXEIi5.mjs +2245 -0
  278. package/.output/server/chunks/build/monitor-DnuXEIi5.mjs.map +1 -0
  279. package/.output/server/chunks/build/narrow-fetch-CcG-BPxc.mjs +6 -0
  280. package/.output/server/chunks/build/narrow-fetch-CcG-BPxc.mjs.map +1 -0
  281. package/.output/server/chunks/build/pages-Bn90ijKD.mjs +539 -0
  282. package/.output/server/chunks/build/pages-Bn90ijKD.mjs.map +1 -0
  283. package/.output/server/chunks/build/permissions-DL70W1YD.mjs +629 -0
  284. package/.output/server/chunks/build/permissions-DL70W1YD.mjs.map +1 -0
  285. package/.output/server/chunks/build/plugins-DrsZo--K.mjs +246 -0
  286. package/.output/server/chunks/build/plugins-DrsZo--K.mjs.map +1 -0
  287. package/.output/server/chunks/build/popconfirm-Ox-_2V8A.mjs +339 -0
  288. package/.output/server/chunks/build/popconfirm-Ox-_2V8A.mjs.map +1 -0
  289. package/.output/server/chunks/build/schedules-VLzNcjnW.mjs +7164 -0
  290. package/.output/server/chunks/build/schedules-VLzNcjnW.mjs.map +1 -0
  291. package/.output/server/chunks/build/segmented-C61yLHvo.mjs +341 -0
  292. package/.output/server/chunks/build/segmented-C61yLHvo.mjs.map +1 -0
  293. package/.output/server/chunks/build/select-rdBWytVK.mjs +3922 -0
  294. package/.output/server/chunks/build/select-rdBWytVK.mjs.map +1 -0
  295. package/.output/server/chunks/build/settings-DVAUVMsX.mjs +1089 -0
  296. package/.output/server/chunks/build/settings-DVAUVMsX.mjs.map +1 -0
  297. package/.output/server/chunks/build/space-B2uhHJG8.mjs +110 -0
  298. package/.output/server/chunks/build/space-B2uhHJG8.mjs.map +1 -0
  299. package/.output/server/chunks/build/spin-C2cuvGth.mjs +357 -0
  300. package/.output/server/chunks/build/spin-C2cuvGth.mjs.map +1 -0
  301. package/.output/server/chunks/build/switch-DZAUIbIN.mjs +377 -0
  302. package/.output/server/chunks/build/switch-DZAUIbIN.mjs.map +1 -0
  303. package/.output/server/chunks/build/table-DOFAvY-v.mjs +8881 -0
  304. package/.output/server/chunks/build/table-DOFAvY-v.mjs.map +1 -0
  305. package/.output/server/chunks/build/tabs-DTeIuZeP.mjs +1923 -0
  306. package/.output/server/chunks/build/tabs-DTeIuZeP.mjs.map +1 -0
  307. package/.output/server/chunks/build/tag-CHLGvNM6.mjs +1029 -0
  308. package/.output/server/chunks/build/tag-CHLGvNM6.mjs.map +1 -0
  309. package/.output/server/chunks/build/teams-Mc07PYO8.mjs +1195 -0
  310. package/.output/server/chunks/build/teams-Mc07PYO8.mjs.map +1 -0
  311. package/.output/server/chunks/build/tokens-DiwD5qni.mjs +1036 -0
  312. package/.output/server/chunks/build/tokens-DiwD5qni.mjs.map +1 -0
  313. package/.output/server/chunks/build/town-BIn3be5v.mjs +39 -0
  314. package/.output/server/chunks/build/town-BIn3be5v.mjs.map +1 -0
  315. package/.output/server/chunks/build/town-D8VmTCmN.mjs +128 -0
  316. package/.output/server/chunks/build/town-D8VmTCmN.mjs.map +1 -0
  317. package/.output/server/chunks/build/types-D6J7slEa.mjs +5561 -0
  318. package/.output/server/chunks/build/types-D6J7slEa.mjs.map +1 -0
  319. package/.output/server/chunks/build/useAgentHarnessOptions-CPn5dzKe.mjs +121 -0
  320. package/.output/server/chunks/build/useAgentHarnessOptions-CPn5dzKe.mjs.map +1 -0
  321. package/.output/server/chunks/build/useAgentTemplateActions-DQWzVnZ0.mjs +74 -0
  322. package/.output/server/chunks/build/useAgentTemplateActions-DQWzVnZ0.mjs.map +1 -0
  323. package/.output/server/chunks/build/useAgentTemplateDialogState-CZzpu5og.mjs +64 -0
  324. package/.output/server/chunks/build/useAgentTemplateDialogState-CZzpu5og.mjs.map +1 -0
  325. package/.output/server/chunks/build/useAgentTemplateForm-vqyiRPtt.mjs +114 -0
  326. package/.output/server/chunks/build/useAgentTemplateForm-vqyiRPtt.mjs.map +1 -0
  327. package/.output/server/chunks/build/useAgentTemplatesCatalog-DcQbsOik.mjs +130 -0
  328. package/.output/server/chunks/build/useAgentTemplatesCatalog-DcQbsOik.mjs.map +1 -0
  329. package/.output/server/chunks/build/useAmlDatasets-BM0lZu40.mjs +89 -0
  330. package/.output/server/chunks/build/useAmlDatasets-BM0lZu40.mjs.map +1 -0
  331. package/.output/server/chunks/build/useAmlEntities-BjXWvFd_.mjs +83 -0
  332. package/.output/server/chunks/build/useAmlEntities-BjXWvFd_.mjs.map +1 -0
  333. package/.output/server/chunks/build/useAmlEnv-BB7p6x_w.mjs +191 -0
  334. package/.output/server/chunks/build/useAmlEnv-BB7p6x_w.mjs.map +1 -0
  335. package/.output/server/chunks/build/useAmlExperiments-BtyW32PO.mjs +86 -0
  336. package/.output/server/chunks/build/useAmlExperiments-BtyW32PO.mjs.map +1 -0
  337. package/.output/server/chunks/build/useAmlFormat-WrKuRfw2.mjs +79 -0
  338. package/.output/server/chunks/build/useAmlFormat-WrKuRfw2.mjs.map +1 -0
  339. package/.output/server/chunks/build/useAmlJobs-DBuLvWMF.mjs +263 -0
  340. package/.output/server/chunks/build/useAmlJobs-DBuLvWMF.mjs.map +1 -0
  341. package/.output/server/chunks/build/useAmlModels-VurmkBYe.mjs +90 -0
  342. package/.output/server/chunks/build/useAmlModels-VurmkBYe.mjs.map +1 -0
  343. package/.output/server/chunks/build/useAmlNotes-BUdj3X6Y.mjs +83 -0
  344. package/.output/server/chunks/build/useAmlNotes-BUdj3X6Y.mjs.map +1 -0
  345. package/.output/server/chunks/build/useAmlPredict-DFy-Do10.mjs +127 -0
  346. package/.output/server/chunks/build/useAmlPredict-DFy-Do10.mjs.map +1 -0
  347. package/.output/server/chunks/build/useDaqAddNode-kW7uTSMQ.mjs +139 -0
  348. package/.output/server/chunks/build/useDaqAddNode-kW7uTSMQ.mjs.map +1 -0
  349. package/.output/server/chunks/build/useDaqDetailBinding-cpBz4hSL.mjs +67 -0
  350. package/.output/server/chunks/build/useDaqDetailBinding-cpBz4hSL.mjs.map +1 -0
  351. package/.output/server/chunks/build/useDaqDetailFrames-BZGtKKXW.mjs +76 -0
  352. package/.output/server/chunks/build/useDaqDetailFrames-BZGtKKXW.mjs.map +1 -0
  353. package/.output/server/chunks/build/useDaqDetailHistory-C4_hVeqZ.mjs +173 -0
  354. package/.output/server/chunks/build/useDaqDetailHistory-C4_hVeqZ.mjs.map +1 -0
  355. package/.output/server/chunks/build/useDaqDetailNode-Bwid_iA8.mjs +96 -0
  356. package/.output/server/chunks/build/useDaqDetailNode-Bwid_iA8.mjs.map +1 -0
  357. package/.output/server/chunks/build/useDaqDetailParams-JfqHvTxr.mjs +156 -0
  358. package/.output/server/chunks/build/useDaqDetailParams-JfqHvTxr.mjs.map +1 -0
  359. package/.output/server/chunks/build/useDaqFilters-C13836p-.mjs +219 -0
  360. package/.output/server/chunks/build/useDaqFilters-C13836p-.mjs.map +1 -0
  361. package/.output/server/chunks/build/useDaqNodeWindow-1DWj2H5G.mjs +87 -0
  362. package/.output/server/chunks/build/useDaqNodeWindow-1DWj2H5G.mjs.map +1 -0
  363. package/.output/server/chunks/build/useDaqOptimizations-Ct-TSXxy.mjs +133 -0
  364. package/.output/server/chunks/build/useDaqOptimizations-Ct-TSXxy.mjs.map +1 -0
  365. package/.output/server/chunks/build/useDaqStream-wOxZcMDA.mjs +395 -0
  366. package/.output/server/chunks/build/useDaqStream-wOxZcMDA.mjs.map +1 -0
  367. package/.output/server/chunks/build/useDaqTemplates-BqQRdrIm.mjs +184 -0
  368. package/.output/server/chunks/build/useDaqTemplates-BqQRdrIm.mjs.map +1 -0
  369. package/.output/server/chunks/build/useDashboardCharts-DZysNDUX.mjs +351 -0
  370. package/.output/server/chunks/build/useDashboardCharts-DZysNDUX.mjs.map +1 -0
  371. package/.output/server/chunks/build/useDashboardData-Ny_wtbo6.mjs +150 -0
  372. package/.output/server/chunks/build/useDashboardData-Ny_wtbo6.mjs.map +1 -0
  373. package/.output/server/chunks/build/useDcwAddNode-C8nE7geh.mjs +148 -0
  374. package/.output/server/chunks/build/useDcwAddNode-C8nE7geh.mjs.map +1 -0
  375. package/.output/server/chunks/build/useDcwCards-ETEsHvVy.mjs +64 -0
  376. package/.output/server/chunks/build/useDcwCards-ETEsHvVy.mjs.map +1 -0
  377. package/.output/server/chunks/build/useDcwDetailScope-BNrpDzbO.mjs +181 -0
  378. package/.output/server/chunks/build/useDcwDetailScope-BNrpDzbO.mjs.map +1 -0
  379. package/.output/server/chunks/build/useDcwDetailTemplates-IcKAJ37Y.mjs +145 -0
  380. package/.output/server/chunks/build/useDcwDetailTemplates-IcKAJ37Y.mjs.map +1 -0
  381. package/.output/server/chunks/build/useDcwFilter-FfVlDdyS.mjs +68 -0
  382. package/.output/server/chunks/build/useDcwFilter-FfVlDdyS.mjs.map +1 -0
  383. package/.output/server/chunks/build/useDcwLedger-BLoMmcu1.mjs +76 -0
  384. package/.output/server/chunks/build/useDcwLedger-BLoMmcu1.mjs.map +1 -0
  385. package/.output/server/chunks/build/useDcwLineMutations-BMC5BTWU.mjs +134 -0
  386. package/.output/server/chunks/build/useDcwLineMutations-BMC5BTWU.mjs.map +1 -0
  387. package/.output/server/chunks/build/useDcwLineRun-Bpxk4mse.mjs +101 -0
  388. package/.output/server/chunks/build/useDcwLineRun-Bpxk4mse.mjs.map +1 -0
  389. package/.output/server/chunks/build/useDcwProducts-D-zrMaeV.mjs +90 -0
  390. package/.output/server/chunks/build/useDcwProducts-D-zrMaeV.mjs.map +1 -0
  391. package/.output/server/chunks/build/useDcwQuery-1lYHOaU6.mjs +95 -0
  392. package/.output/server/chunks/build/useDcwQuery-1lYHOaU6.mjs.map +1 -0
  393. package/.output/server/chunks/build/useDcwQuickActions-C-tgcXIr.mjs +85 -0
  394. package/.output/server/chunks/build/useDcwQuickActions-C-tgcXIr.mjs.map +1 -0
  395. package/.output/server/chunks/build/useDcwRecipes-BzGOymqz.mjs +237 -0
  396. package/.output/server/chunks/build/useDcwRecipes-BzGOymqz.mjs.map +1 -0
  397. package/.output/server/chunks/build/useDcwStream-D04kCV3J.mjs +435 -0
  398. package/.output/server/chunks/build/useDcwStream-D04kCV3J.mjs.map +1 -0
  399. package/.output/server/chunks/build/useDcwTemplates-Dsjw9u6U.mjs +88 -0
  400. package/.output/server/chunks/build/useDcwTemplates-Dsjw9u6U.mjs.map +1 -0
  401. package/.output/server/chunks/build/useDcwWrites-3RxwN36I.mjs +120 -0
  402. package/.output/server/chunks/build/useDcwWrites-3RxwN36I.mjs.map +1 -0
  403. package/.output/server/chunks/build/useDeviceTwins-CbVad1M3.mjs +128 -0
  404. package/.output/server/chunks/build/useDeviceTwins-CbVad1M3.mjs.map +1 -0
  405. package/.output/server/chunks/build/useLocalTime-9a4B-aHv.mjs +1 -8
  406. package/.output/server/chunks/build/useLocalTime-9a4B-aHv.mjs.map +1 -1
  407. package/.output/server/chunks/build/useLogsFormat-BlwLpyzy.mjs +74 -0
  408. package/.output/server/chunks/build/useLogsFormat-BlwLpyzy.mjs.map +1 -0
  409. package/.output/server/chunks/build/useLogsManual-Deev2Cjk.mjs +81 -0
  410. package/.output/server/chunks/build/useLogsManual-Deev2Cjk.mjs.map +1 -0
  411. package/.output/server/chunks/build/useLogsQuery-CRAm2hmS.mjs +112 -0
  412. package/.output/server/chunks/build/useLogsQuery-CRAm2hmS.mjs.map +1 -0
  413. package/.output/server/chunks/build/useLogsScope-ClNijVj4.mjs +80 -0
  414. package/.output/server/chunks/build/useLogsScope-ClNijVj4.mjs.map +1 -0
  415. package/.output/server/chunks/build/useMonitorColumns-ylxrUNTK.mjs +212 -0
  416. package/.output/server/chunks/build/useMonitorColumns-ylxrUNTK.mjs.map +1 -0
  417. package/.output/server/chunks/build/useMonitorData-CEpKXoD4.mjs +131 -0
  418. package/.output/server/chunks/build/useMonitorData-CEpKXoD4.mjs.map +1 -0
  419. package/.output/server/chunks/build/useMonitorFormat-_7zLapf3.mjs +87 -0
  420. package/.output/server/chunks/build/useMonitorFormat-_7zLapf3.mjs.map +1 -0
  421. package/.output/server/chunks/build/useMonitorTerminal-Dj4QwRNW.mjs +68 -0
  422. package/.output/server/chunks/build/useMonitorTerminal-Dj4QwRNW.mjs.map +1 -0
  423. package/.output/server/chunks/build/useOpsLog-CgnNG7_n.mjs +137 -0
  424. package/.output/server/chunks/build/useOpsLog-CgnNG7_n.mjs.map +1 -0
  425. package/.output/server/chunks/build/useTeamActions-D6sxdpkP.mjs +76 -0
  426. package/.output/server/chunks/build/useTeamActions-D6sxdpkP.mjs.map +1 -0
  427. package/.output/server/chunks/build/useTeamAddMemberDialog-Q-ntQ6Vo.mjs +81 -0
  428. package/.output/server/chunks/build/useTeamAddMemberDialog-Q-ntQ6Vo.mjs.map +1 -0
  429. package/.output/server/chunks/build/useTeamCreateDialog-D7OMfDTM.mjs +94 -0
  430. package/.output/server/chunks/build/useTeamCreateDialog-D7OMfDTM.mjs.map +1 -0
  431. package/.output/server/chunks/build/useTeamDeployDialog-vBRqjiut.mjs +77 -0
  432. package/.output/server/chunks/build/useTeamDeployDialog-vBRqjiut.mjs.map +1 -0
  433. package/.output/server/chunks/build/useTeamDialogState-CFRUQxAf.mjs +79 -0
  434. package/.output/server/chunks/build/useTeamDialogState-CFRUQxAf.mjs.map +1 -0
  435. package/.output/server/chunks/build/useTeamPluginDialog-B8kxewpZ.mjs +100 -0
  436. package/.output/server/chunks/build/useTeamPluginDialog-B8kxewpZ.mjs.map +1 -0
  437. package/.output/server/chunks/build/useTeamsCatalog-xRhk7ezA.mjs +120 -0
  438. package/.output/server/chunks/build/useTeamsCatalog-xRhk7ezA.mjs.map +1 -0
  439. package/.output/server/chunks/build/useTokenClipboard-CwQpoFeP.mjs +69 -0
  440. package/.output/server/chunks/build/useTokenClipboard-CwQpoFeP.mjs.map +1 -0
  441. package/.output/server/chunks/build/useTokenCreate-DpDcF1Vx.mjs +112 -0
  442. package/.output/server/chunks/build/useTokenCreate-DpDcF1Vx.mjs.map +1 -0
  443. package/.output/server/chunks/build/useTokenRename-BRejFrtc.mjs +84 -0
  444. package/.output/server/chunks/build/useTokenRename-BRejFrtc.mjs.map +1 -0
  445. package/.output/server/chunks/build/useTokenReveal-DER2afAS.mjs +115 -0
  446. package/.output/server/chunks/build/useTokenReveal-DER2afAS.mjs.map +1 -0
  447. package/.output/server/chunks/build/useTokensList-DHt65IOY.mjs +89 -0
  448. package/.output/server/chunks/build/useTokensList-DHt65IOY.mjs.map +1 -0
  449. package/.output/server/chunks/build/useWorkbenchAuth-B2j-zILO.mjs +135 -0
  450. package/.output/server/chunks/build/useWorkbenchAuth-B2j-zILO.mjs.map +1 -0
  451. package/.output/server/chunks/build/useWorkbenchWorkspaces-B_2mOxN6.mjs +133 -0
  452. package/.output/server/chunks/build/useWorkbenchWorkspaces-B_2mOxN6.mjs.map +1 -0
  453. package/.output/server/chunks/build/useWorkshopApi-B1B9vd4m.mjs +102 -0
  454. package/.output/server/chunks/build/useWorkshopApi-B1B9vd4m.mjs.map +1 -0
  455. package/.output/server/chunks/build/useWorkshopWs-B-Es2Xg3.mjs +1782 -0
  456. package/.output/server/chunks/build/useWorkshopWs-B-Es2Xg3.mjs.map +1 -0
  457. package/.output/server/chunks/build/useWorkspaceAuth-tQ1ORWXo.mjs +57 -0
  458. package/.output/server/chunks/build/useWorkspaceAuth-tQ1ORWXo.mjs.map +1 -0
  459. package/.output/server/chunks/build/useWorkspaceChannels-BwB6TAWY.mjs +87 -0
  460. package/.output/server/chunks/build/useWorkspaceChannels-BwB6TAWY.mjs.map +1 -0
  461. package/.output/server/chunks/build/useWorkspacePanels-CuAmDpT5.mjs +75 -0
  462. package/.output/server/chunks/build/useWorkspacePanels-CuAmDpT5.mjs.map +1 -0
  463. package/.output/server/chunks/build/useWorkspaceShell-CcQ4JlqO.mjs +165 -0
  464. package/.output/server/chunks/build/useWorkspaceShell-CcQ4JlqO.mjs.map +1 -0
  465. package/.output/server/chunks/build/users-De2HygNt.mjs +561 -0
  466. package/.output/server/chunks/build/users-De2HygNt.mjs.map +1 -0
  467. package/.output/server/chunks/build/workshop-CmgpTwyf.mjs +1018 -0
  468. package/.output/server/chunks/build/workshop-CmgpTwyf.mjs.map +1 -0
  469. package/.output/server/chunks/build/workspaces-DVIEk1Bl.mjs +139 -0
  470. package/.output/server/chunks/build/workspaces-DVIEk1Bl.mjs.map +1 -0
  471. package/.output/server/chunks/routes/api/health.get.mjs +2 -2
  472. package/.output/server/chunks/routes/api/index.get.mjs +2 -2
  473. package/.output/server/chunks/routes/api/index.post.mjs +2 -2
  474. package/.output/server/chunks/routes/api/mcp/workshop.mjs +2 -2
  475. package/.output/server/chunks/routes/api/metrics.mjs +2 -2
  476. package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs +2 -2
  477. package/.output/server/chunks/routes/api/plugins/client/_name_.get.mjs +2 -2
  478. package/.output/server/chunks/routes/api/plugins/i18n.get.mjs +2 -2
  479. package/.output/server/chunks/routes/api/plugins/manifest.get.mjs +2 -2
  480. package/.output/server/chunks/routes/api/system/config-groups/_id_.delete.mjs +2 -2
  481. package/.output/server/chunks/routes/api/system/config-groups/_id_.patch.mjs +2 -2
  482. package/.output/server/chunks/routes/api/system/config.get.mjs +2 -2
  483. package/.output/server/chunks/routes/api/system/index.get.mjs +2 -2
  484. package/.output/server/chunks/routes/api/system/index.patch.mjs +2 -2
  485. package/.output/server/chunks/routes/api/system/index.post.mjs +2 -2
  486. package/.output/server/chunks/routes/api/system/index2.get.mjs +2 -2
  487. package/.output/server/chunks/routes/api/system/monitor/terminal/ws.mjs +35 -4
  488. package/.output/server/chunks/routes/api/system/monitor/terminal/ws.mjs.map +1 -1
  489. package/.output/server/chunks/routes/api/system/monitor/terminate.post.mjs +2 -2
  490. package/.output/server/chunks/routes/api/system/monitor.get.mjs +2 -2
  491. package/.output/server/chunks/routes/api/system/settings/events.get.mjs +2 -2
  492. package/.output/server/chunks/routes/api/system/settings/reset.post.mjs +2 -2
  493. package/.output/server/chunks/routes/api/users/_id_.delete.mjs +2 -2
  494. package/.output/server/chunks/routes/api/users/_id_.get.mjs +2 -2
  495. package/.output/server/chunks/routes/api/users/_id_.put.mjs +2 -2
  496. package/.output/server/chunks/routes/api/users/index.get.mjs +2 -2
  497. package/.output/server/chunks/routes/api/users/index.post.mjs +2 -2
  498. package/.output/server/chunks/routes/api/users/login.post.mjs +2 -2
  499. package/.output/server/chunks/routes/api/users/logout.post.mjs +2 -2
  500. package/.output/server/chunks/routes/api/users/me.get.mjs +2 -2
  501. package/.output/server/chunks/routes/api/users/register.post.mjs +2 -2
  502. package/.output/server/chunks/routes/api/users/setup-status.get.mjs +2 -2
  503. package/.output/server/chunks/routes/api/users/tokens/_id/token.get.mjs +2 -2
  504. package/.output/server/chunks/routes/api/users/tokens/_id_.delete.mjs +2 -2
  505. package/.output/server/chunks/routes/api/users/tokens/_id_.patch.mjs +2 -2
  506. package/.output/server/chunks/routes/api/workshop/_...path_.mjs +2 -2
  507. package/.output/server/chunks/routes/api/workshop/a2a/_agentId/index.get.mjs +2 -2
  508. package/.output/server/chunks/routes/api/workshop/a2a/_agentId/index.post.mjs +2 -2
  509. package/.output/server/chunks/routes/api/workshop/a2a/send.post.mjs +2 -2
  510. package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs +38 -6
  511. package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs.map +1 -1
  512. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.delete.mjs +2 -2
  513. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.patch.mjs +2 -2
  514. package/.output/server/chunks/routes/api/workshop/agent-tools/index.get.mjs +14 -5
  515. package/.output/server/chunks/routes/api/workshop/agent-tools/index.get.mjs.map +1 -1
  516. package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs +2 -2
  517. package/.output/server/chunks/routes/api/workshop/agent-tools/index2.get.mjs +2 -2
  518. package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +2 -2
  519. package/.output/server/chunks/routes/api/workshop/agent-tools/list.get.mjs +2 -2
  520. package/.output/server/chunks/routes/api/workshop/agents/_id_.delete.mjs +2 -2
  521. package/.output/server/chunks/routes/api/workshop/agents/_id_.get.mjs +2 -2
  522. package/.output/server/chunks/routes/api/workshop/agents/_id_.patch.mjs +2 -2
  523. package/.output/server/chunks/routes/api/workshop/agents/subscribe.post.mjs +2 -2
  524. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id/preview.get.mjs +1 -1
  525. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.delete.mjs +2 -2
  526. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs +2 -2
  527. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.patch.mjs +2 -2
  528. package/.output/server/chunks/routes/api/workshop/aml/entities/prune.post.mjs +2 -2
  529. package/.output/server/chunks/routes/api/workshop/aml/env/recheck.post.mjs +2 -2
  530. package/.output/server/chunks/routes/api/workshop/aml/env/uv.post.mjs +2 -2
  531. package/.output/server/chunks/routes/api/workshop/aml/env/venv.post.mjs +2 -2
  532. package/.output/server/chunks/routes/api/workshop/aml/index.get.mjs +2 -2
  533. package/.output/server/chunks/routes/api/workshop/aml/index.post.mjs +2 -2
  534. package/.output/server/chunks/routes/api/workshop/aml/index2.get.mjs +2 -2
  535. package/.output/server/chunks/routes/api/workshop/aml/index2.post.mjs +2 -2
  536. package/.output/server/chunks/routes/api/workshop/aml/index3.get.mjs +2 -2
  537. package/.output/server/chunks/routes/api/workshop/aml/index4.get.mjs +2 -2
  538. package/.output/server/chunks/routes/api/workshop/aml/index5.get.mjs +2 -2
  539. package/.output/server/chunks/routes/api/workshop/aml/index6.get.mjs +2 -2
  540. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/cancel.post.mjs +2 -2
  541. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/logs.get.mjs +2 -2
  542. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/retry.post.mjs +2 -2
  543. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.delete.mjs +2 -2
  544. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.get.mjs +2 -2
  545. package/.output/server/chunks/routes/api/workshop/aml/models/_id/predict.post.mjs +2 -2
  546. package/.output/server/chunks/routes/api/workshop/aml/models/_id/promote.post.mjs +2 -2
  547. package/.output/server/chunks/routes/api/workshop/aml/models/_id_.delete.mjs +2 -2
  548. package/.output/server/chunks/routes/api/workshop/aml/models/_id_.patch.mjs +2 -2
  549. package/.output/server/chunks/routes/api/workshop/approvals/_id/decide.post.mjs +2 -2
  550. package/.output/server/chunks/routes/api/workshop/assets/character/_id_.delete.mjs +1 -1
  551. package/.output/server/chunks/routes/api/workshop/assets/devices/_id_.delete.mjs +1 -1
  552. package/.output/server/chunks/routes/api/workshop/assets/index.get.mjs +2 -2
  553. package/.output/server/chunks/routes/api/workshop/assets/index.post.mjs +1 -1
  554. package/.output/server/chunks/routes/api/workshop/assets/index2.get.mjs +1 -1
  555. package/.output/server/chunks/routes/api/workshop/assets/index2.post.mjs +1 -1
  556. package/.output/server/chunks/routes/api/workshop/audit.get.mjs +2 -2
  557. package/.output/server/chunks/routes/api/workshop/caller.mjs +2 -2
  558. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +10 -5
  559. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs.map +1 -1
  560. package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.delete.mjs +2 -2
  561. package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.get.mjs +2 -2
  562. package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.patch.mjs +2 -2
  563. package/.output/server/chunks/routes/api/workshop/channel-templates/from-channel.post.mjs +2 -2
  564. package/.output/server/chunks/routes/api/workshop/channels/_id/activate.post.mjs +2 -2
  565. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/index.get.mjs +2 -2
  566. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/index.post.mjs +2 -2
  567. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/memories/_memoryId_.delete.mjs +2 -2
  568. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/memories/search.post.mjs +2 -2
  569. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/model.patch.mjs +2 -2
  570. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/position.patch.mjs +2 -2
  571. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/range.patch.mjs +2 -2
  572. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/stop.post.mjs +2 -2
  573. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId_.delete.mjs +2 -2
  574. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId_.patch.mjs +2 -2
  575. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.get.mjs +41 -0
  576. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.get.mjs.map +1 -0
  577. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs +52 -0
  578. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs.map +1 -0
  579. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/messages/_messageId/read.post.mjs +33 -0
  580. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/messages/_messageId/read.post.mjs.map +1 -0
  581. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/permissions.get.mjs +48 -0
  582. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/permissions.get.mjs.map +1 -0
  583. package/.output/server/chunks/routes/api/workshop/channels/_id/events.get.mjs +2 -2
  584. package/.output/server/chunks/routes/api/workshop/channels/_id/index.get.mjs +2 -2
  585. package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +2 -2
  586. package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs +2 -2
  587. package/.output/server/chunks/routes/api/workshop/channels/_id/index2.get.mjs +10 -14
  588. package/.output/server/chunks/routes/api/workshop/channels/_id/index2.get.mjs.map +1 -1
  589. package/.output/server/chunks/routes/api/workshop/channels/_id/index2.post.mjs +2 -2
  590. package/.output/server/chunks/routes/api/workshop/channels/_id/index3.get.mjs +15 -28
  591. package/.output/server/chunks/routes/api/workshop/channels/_id/index3.get.mjs.map +1 -1
  592. package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs +3 -2
  593. package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs.map +1 -1
  594. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs +29 -23
  595. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs.map +1 -1
  596. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs +9 -4
  597. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs.map +1 -1
  598. package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs +22 -10
  599. package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs.map +1 -1
  600. package/.output/server/chunks/routes/api/workshop/channels/_id/index6.get.mjs +34 -0
  601. package/.output/server/chunks/routes/api/workshop/channels/_id/index6.get.mjs.map +1 -0
  602. package/.output/server/chunks/routes/api/workshop/channels/_id/members/_userId/approve.post.mjs +33 -0
  603. package/.output/server/chunks/routes/api/workshop/channels/_id/members/_userId/approve.post.mjs.map +1 -0
  604. package/.output/server/chunks/routes/api/workshop/channels/_id/members/_userId_.delete.mjs +34 -0
  605. package/.output/server/chunks/routes/api/workshop/channels/_id/members/_userId_.delete.mjs.map +1 -0
  606. package/.output/server/chunks/routes/api/workshop/channels/_id/members/join.post.mjs +35 -0
  607. package/.output/server/chunks/routes/api/workshop/channels/_id/members/join.post.mjs.map +1 -0
  608. package/.output/server/chunks/routes/api/workshop/channels/_id/members/leave.post.mjs +33 -0
  609. package/.output/server/chunks/routes/api/workshop/channels/_id/members/leave.post.mjs.map +1 -0
  610. package/.output/server/chunks/routes/api/workshop/channels/_id/memories/_memoryId_.delete.mjs +2 -2
  611. package/.output/server/chunks/routes/api/workshop/channels/_id/queue.get.mjs +2 -2
  612. package/.output/server/chunks/routes/api/workshop/channels/_id/terminals.get.mjs +2 -2
  613. package/.output/server/chunks/routes/api/workshop/channels/_id_.delete.mjs +2 -2
  614. package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +2 -2
  615. package/.output/server/chunks/routes/api/workshop/channels/index.patch.mjs +22 -6
  616. package/.output/server/chunks/routes/api/workshop/channels/index.patch.mjs.map +1 -1
  617. package/.output/server/chunks/routes/api/workshop/channels.get.mjs +21 -3
  618. package/.output/server/chunks/routes/api/workshop/channels.get.mjs.map +1 -1
  619. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +5 -5
  620. package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +2 -2
  621. package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +2 -2
  622. package/.output/server/chunks/routes/api/workshop/daq/_id/frames/content.get.mjs +2 -2
  623. package/.output/server/chunks/routes/api/workshop/daq/_id/frames.get.mjs +2 -2
  624. package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +2 -2
  625. package/.output/server/chunks/routes/api/workshop/daq/_id/test.post.mjs +2 -2
  626. package/.output/server/chunks/routes/api/workshop/daq/_id_.delete.mjs +2 -2
  627. package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +2 -2
  628. package/.output/server/chunks/routes/api/workshop/daq/alarms/_id/ack.post.mjs +2 -2
  629. package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs +2 -2
  630. package/.output/server/chunks/routes/api/workshop/daq/controller.post.mjs +2 -2
  631. package/.output/server/chunks/routes/api/workshop/daq/index.post.mjs +2 -2
  632. package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +2 -2
  633. package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +2 -2
  634. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +2 -2
  635. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +2 -2
  636. package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +2 -2
  637. package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +2 -2
  638. package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +2 -2
  639. package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +2 -2
  640. package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +2 -2
  641. package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +2 -2
  642. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +2 -2
  643. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +2 -2
  644. package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +2 -2
  645. package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +2 -2
  646. package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +2 -2
  647. package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +2 -2
  648. package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +2 -2
  649. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +2 -2
  650. package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +2 -2
  651. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +2 -2
  652. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +2 -2
  653. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +2 -2
  654. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +2 -2
  655. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +2 -2
  656. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +2 -2
  657. package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +2 -2
  658. package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +2 -2
  659. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +2 -2
  660. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +2 -2
  661. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +2 -2
  662. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs +2 -2
  663. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +2 -2
  664. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +2 -2
  665. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +2 -2
  666. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +2 -2
  667. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +2 -2
  668. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +2 -2
  669. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +2 -2
  670. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +2 -2
  671. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +2 -2
  672. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +2 -2
  673. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +2 -2
  674. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +2 -2
  675. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/revert.post.mjs +2 -2
  676. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +2 -2
  677. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs +2 -2
  678. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +2 -2
  679. package/.output/server/chunks/routes/api/workshop/dcw/recipes/index.patch.mjs +2 -2
  680. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +2 -2
  681. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +2 -2
  682. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +2 -2
  683. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +2 -2
  684. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +2 -2
  685. package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +2 -2
  686. package/.output/server/chunks/routes/api/workshop/device-twins/_id/control.post.mjs +2 -2
  687. package/.output/server/chunks/routes/api/workshop/device-twins/_id/telemetry.post.mjs +2 -2
  688. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +2 -2
  689. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.patch.mjs +2 -2
  690. package/.output/server/chunks/routes/api/workshop/fs/dirs.get.mjs +1 -1
  691. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +2 -2
  692. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +2 -2
  693. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +11 -4
  694. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs.map +1 -1
  695. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +28 -36
  696. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs.map +1 -1
  697. package/.output/server/chunks/routes/api/workshop/index.get.mjs +2 -2
  698. package/.output/server/chunks/routes/api/workshop/index.post.mjs +2 -2
  699. package/.output/server/chunks/routes/api/workshop/index.put.mjs +2 -2
  700. package/.output/server/chunks/routes/api/workshop/index10.get.mjs +26 -12
  701. package/.output/server/chunks/routes/api/workshop/index10.get.mjs.map +1 -1
  702. package/.output/server/chunks/routes/api/workshop/index11.get.mjs +12 -7
  703. package/.output/server/chunks/routes/api/workshop/index11.get.mjs.map +1 -1
  704. package/.output/server/chunks/routes/api/workshop/index12.get.mjs +3 -3
  705. package/.output/server/chunks/routes/api/workshop/index12.get.mjs.map +1 -1
  706. package/.output/server/chunks/routes/api/workshop/index13.get.mjs +29 -0
  707. package/.output/server/chunks/routes/api/workshop/index13.get.mjs.map +1 -0
  708. package/.output/server/chunks/routes/api/workshop/index14.get.mjs +28 -0
  709. package/.output/server/chunks/routes/api/workshop/index14.get.mjs.map +1 -0
  710. package/.output/server/chunks/routes/api/workshop/index2.get.mjs +2 -2
  711. package/.output/server/chunks/routes/api/workshop/index2.post.mjs +2 -2
  712. package/.output/server/chunks/routes/api/workshop/index3.get.mjs +2 -2
  713. package/.output/server/chunks/routes/api/workshop/index3.post.mjs +2 -2
  714. package/.output/server/chunks/routes/api/workshop/index4.get.mjs +2 -2
  715. package/.output/server/chunks/routes/api/workshop/index4.post.mjs +2 -2
  716. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +2 -2
  717. package/.output/server/chunks/routes/api/workshop/index5.post.mjs +2 -2
  718. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +2 -2
  719. package/.output/server/chunks/routes/api/workshop/index6.post.mjs +2 -2
  720. package/.output/server/chunks/routes/api/workshop/index7.get.mjs +2 -2
  721. package/.output/server/chunks/routes/api/workshop/index7.post.mjs +14 -16
  722. package/.output/server/chunks/routes/api/workshop/index7.post.mjs.map +1 -1
  723. package/.output/server/chunks/routes/api/workshop/index8.get.mjs +40 -19
  724. package/.output/server/chunks/routes/api/workshop/index8.get.mjs.map +1 -1
  725. package/.output/server/chunks/routes/api/workshop/index8.post.mjs +19 -5
  726. package/.output/server/chunks/routes/api/workshop/index8.post.mjs.map +1 -1
  727. package/.output/server/chunks/routes/api/workshop/index9.get.mjs +20 -24
  728. package/.output/server/chunks/routes/api/workshop/index9.get.mjs.map +1 -1
  729. package/.output/server/chunks/routes/api/workshop/index9.post.mjs +30 -0
  730. package/.output/server/chunks/routes/api/workshop/index9.post.mjs.map +1 -0
  731. package/.output/server/chunks/routes/api/workshop/mailbox/all.get.mjs +2 -2
  732. package/.output/server/chunks/routes/api/workshop/mailbox.get.mjs +2 -2
  733. package/.output/server/chunks/routes/api/workshop/memories/maintenance.post.mjs +2 -2
  734. package/.output/server/chunks/routes/api/workshop/notifications/read.post.mjs +41 -0
  735. package/.output/server/chunks/routes/api/workshop/notifications/read.post.mjs.map +1 -0
  736. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +2 -2
  737. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +2 -2
  738. package/.output/server/chunks/routes/api/workshop/plugins/index.get.mjs +2 -2
  739. package/.output/server/chunks/routes/api/workshop/runtime.get.mjs +2 -2
  740. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +2 -2
  741. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +2 -2
  742. package/.output/server/chunks/routes/api/workshop/scene/layouts.get.mjs +2 -2
  743. package/.output/server/chunks/routes/api/workshop/schedules/_id/run.post.mjs +29 -0
  744. package/.output/server/chunks/routes/api/workshop/schedules/_id/run.post.mjs.map +1 -0
  745. package/.output/server/chunks/routes/api/workshop/schedules/_id/runs.get.mjs +31 -0
  746. package/.output/server/chunks/routes/api/workshop/schedules/_id/runs.get.mjs.map +1 -0
  747. package/.output/server/chunks/routes/api/workshop/schedules/_id_.delete.mjs +30 -0
  748. package/.output/server/chunks/routes/api/workshop/schedules/_id_.delete.mjs.map +1 -0
  749. package/.output/server/chunks/routes/api/workshop/schedules/_id_.get.mjs +29 -0
  750. package/.output/server/chunks/routes/api/workshop/schedules/_id_.get.mjs.map +1 -0
  751. package/.output/server/chunks/routes/api/workshop/schedules/_id_.patch.mjs +40 -0
  752. package/.output/server/chunks/routes/api/workshop/schedules/_id_.patch.mjs.map +1 -0
  753. package/.output/server/chunks/routes/api/workshop/tasks/_id/complete.post.mjs +2 -2
  754. package/.output/server/chunks/routes/api/workshop/tasks/_id/dispatch.post.mjs +2 -2
  755. package/.output/server/chunks/routes/api/workshop/tasks/_id/index.post.mjs +2 -2
  756. package/.output/server/chunks/routes/api/workshop/tasks/_id/reassign.post.mjs +39 -0
  757. package/.output/server/chunks/routes/api/workshop/tasks/_id/reassign.post.mjs.map +1 -0
  758. package/.output/server/chunks/routes/api/workshop/tasks/_id/report.post.mjs +2 -2
  759. package/.output/server/chunks/routes/api/workshop/tasks/_id/retry.post.mjs +2 -2
  760. package/.output/server/chunks/routes/api/workshop/tasks/_id/update.post.mjs +44 -0
  761. package/.output/server/chunks/routes/api/workshop/tasks/_id/update.post.mjs.map +1 -0
  762. package/.output/server/chunks/routes/api/workshop/tasks/_id_.get.mjs +9 -5
  763. package/.output/server/chunks/routes/api/workshop/tasks/_id_.get.mjs.map +1 -1
  764. package/.output/server/chunks/routes/api/workshop/tasks.get.mjs +2 -2
  765. package/.output/server/chunks/routes/api/workshop/teams/_id/deploy.post.mjs +2 -2
  766. package/.output/server/chunks/routes/api/workshop/teams/_id/index.get.mjs +2 -2
  767. package/.output/server/chunks/routes/api/workshop/teams/_id/index.post.mjs +2 -2
  768. package/.output/server/chunks/routes/api/workshop/teams/_id/index.put.mjs +2 -2
  769. package/.output/server/chunks/routes/api/workshop/teams/_id/index2.get.mjs +2 -2
  770. package/.output/server/chunks/routes/api/workshop/teams/_id/members/_agentId_.delete.mjs +2 -2
  771. package/.output/server/chunks/routes/api/workshop/teams/_id_.delete.mjs +2 -2
  772. package/.output/server/chunks/routes/api/workshop/teams/_id_.get.mjs +2 -2
  773. package/.output/server/chunks/routes/api/workshop/teams/_id_.patch.mjs +2 -2
  774. package/.output/server/chunks/routes/api/workshop/users/me.get.mjs +2 -2
  775. package/.output/server/chunks/routes/api/workshop/users/register.post.mjs +2 -2
  776. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +3 -3
  777. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channels/_channelId_.delete.mjs +2 -2
  778. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channels/_channelId_.post.mjs +2 -2
  779. package/.output/server/chunks/routes/api/workshop/workspaces/index.delete.mjs +2 -2
  780. package/.output/server/chunks/routes/renderer.mjs +3 -3
  781. package/.output/server/chunks/virtual/entry.mjs +6055 -1624
  782. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  783. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  784. package/.output/server/index.mjs +15 -5549
  785. package/.output/server/index.mjs.map +1 -1
  786. package/.output/server/package.json +1 -1
  787. package/README-zh.md +102 -48
  788. package/README.md +105 -38
  789. package/app/components/AppHeader.vue +26 -703
  790. package/app/components/aml/AmlDatasetFormModal.vue +279 -0
  791. package/app/components/aml/AmlDatasetTable.vue +319 -0
  792. package/app/components/aml/AmlEnvPanel.vue +230 -0
  793. package/app/components/aml/AmlExperimentsTable.vue +142 -0
  794. package/app/components/aml/AmlInfraBanner.vue +23 -0
  795. package/app/components/aml/AmlJobsTable.vue +248 -0
  796. package/app/components/aml/AmlModelsTable.vue +173 -0
  797. package/app/components/aml/AmlOverviewBar.vue +62 -0
  798. package/app/components/aml/AmlPageHeader.vue +47 -0
  799. package/app/components/aml/AmlPredictConsole.vue +201 -0
  800. package/app/components/app-header/HeaderTools.vue +208 -0
  801. package/app/components/app-header/HitlBell.vue +423 -0
  802. package/app/components/app-header/TrailNav.vue +261 -0
  803. package/app/components/app-header/UserChip.vue +150 -0
  804. package/app/components/daq/DaqAddNodeModal.vue +209 -0
  805. package/app/components/daq/DaqAlarmBar.vue +46 -0
  806. package/app/components/daq/DaqControlCard.vue +159 -0
  807. package/app/components/daq/DaqDetailFramesTile.vue +139 -0
  808. package/app/components/daq/DaqDetailHistoryCard.vue +128 -0
  809. package/app/components/daq/DaqDetailLiveCard.vue +92 -0
  810. package/app/components/daq/DaqDetailParamForm.vue +350 -0
  811. package/app/components/daq/DaqInfraBanner.vue +31 -0
  812. package/app/components/daq/DaqLineBanner.vue +47 -0
  813. package/app/components/daq/DaqNodePanel.vue +396 -0
  814. package/app/components/daq/DaqOptPanel.vue +197 -0
  815. package/app/components/daq/DaqTemplateModal.vue +246 -0
  816. package/app/components/dashboard/DashboardFleetPanel.vue +228 -0
  817. package/app/components/dashboard/DashboardGaugeBand.vue +78 -0
  818. package/app/components/dashboard/DashboardHarnessPanel.vue +149 -0
  819. package/app/components/dashboard/DashboardHero.vue +209 -0
  820. package/app/components/dashboard/DashboardLineStatePanel.vue +78 -0
  821. package/app/components/dashboard/DashboardNodeStatePanel.vue +46 -0
  822. package/app/components/dashboard/DashboardPipelinePanel.vue +46 -0
  823. package/app/components/dashboard/DashboardTrendPanel.vue +83 -0
  824. package/app/components/dashboard/DashboardWritePanel.vue +52 -0
  825. package/app/components/dcw/DcwAddNodeModal.vue +328 -0
  826. package/app/components/dcw/DcwAdoptCard.vue +89 -0
  827. package/app/components/dcw/DcwCreateLineModal.vue +105 -0
  828. package/app/components/dcw/DcwDeleteLineModal.vue +73 -0
  829. package/app/components/dcw/DcwDetailHead.vue +65 -0
  830. package/app/components/dcw/DcwDetailTemplateModal.vue +238 -0
  831. package/app/components/dcw/DcwEditLineModal.vue +108 -0
  832. package/app/components/dcw/DcwGatewayBar.vue +94 -0
  833. package/app/components/dcw/DcwLedgerCard.vue +160 -0
  834. package/app/components/dcw/DcwLineCard.vue +117 -0
  835. package/app/components/dcw/DcwLineRunCard.vue +158 -0
  836. package/app/components/dcw/DcwNodesTable.vue +253 -0
  837. package/app/components/dcw/DcwProductCreateModal.vue +113 -0
  838. package/app/components/dcw/DcwQueryCard.vue +251 -0
  839. package/app/components/dcw/DcwRecipeEditModal.vue +285 -0
  840. package/app/components/dcw/DcwRecipeHistoryModal.vue +165 -0
  841. package/app/components/dcw/DcwRecipePanel.vue +367 -0
  842. package/app/components/dcw/DcwRunDataModal.vue +191 -0
  843. package/app/components/dcw/DcwTemplateModal.vue +227 -0
  844. package/app/components/dcw/DcwWriteHistoryCard.vue +96 -0
  845. package/app/components/logs/LogsEventTable.vue +309 -0
  846. package/app/components/logs/LogsFilterCard.vue +234 -0
  847. package/app/components/logs/LogsManualModal.vue +231 -0
  848. package/app/components/logs/LogsPageHead.vue +71 -0
  849. package/app/components/monitor/MonitorAgentTable.vue +143 -0
  850. package/app/components/monitor/MonitorChannelTable.vue +80 -0
  851. package/app/components/monitor/MonitorLoginGate.vue +24 -0
  852. package/app/components/monitor/MonitorPageHead.vue +92 -0
  853. package/app/components/monitor/MonitorProcessTable.vue +159 -0
  854. package/app/components/monitor/MonitorStatGrid.vue +165 -0
  855. package/app/components/settings/AppearancePane.vue +282 -0
  856. package/app/components/settings/PluginsSection.vue +280 -0
  857. package/app/components/settings/RuntimeFieldRow.vue +189 -0
  858. package/app/components/settings/RuntimeGroupSection.vue +290 -0
  859. package/app/components/settings/RuntimePane.vue +235 -0
  860. package/app/components/settings/SystemPane.vue +83 -0
  861. package/app/components/tokens/TokenCreateModal.vue +42 -0
  862. package/app/components/tokens/TokenOnceModal.vue +164 -0
  863. package/app/components/tokens/TokenRenameModal.vue +40 -0
  864. package/app/components/tokens/TokensAuthGate.vue +47 -0
  865. package/app/components/tokens/TokensPageHead.vue +76 -0
  866. package/app/components/tokens/TokensTable.vue +227 -0
  867. package/app/components/workshop/AgentLanesView.vue +35 -755
  868. package/app/components/workshop/ChannelSessionList.vue +58 -493
  869. package/app/components/workshop/ChatMemberPanel.vue +555 -0
  870. package/app/components/workshop/ChatTimeline.vue +569 -0
  871. package/app/components/workshop/CommandPalette.vue +1 -0
  872. package/app/components/workshop/Composer.vue +224 -511
  873. package/app/components/workshop/DeviceTwinPanel.vue +23 -499
  874. package/app/components/workshop/EventBlock.vue +20 -408
  875. package/app/components/workshop/MemoryPanel.vue +405 -364
  876. package/app/components/workshop/MultiChannelView.vue +189 -189
  877. package/app/components/workshop/NotificationCenter.vue +325 -0
  878. package/app/components/workshop/TaskBoardView.vue +260 -686
  879. package/app/components/workshop/TaskInspectorDrawer.vue +484 -390
  880. package/app/components/workshop/TranscriptTimeline.vue +60 -438
  881. package/app/components/workshop/agents/AgentTemplateEditModal.vue +139 -0
  882. package/app/components/workshop/agents/AgentTemplateTable.vue +269 -0
  883. package/app/components/workshop/agents/AgentTemplateToolbar.vue +67 -0
  884. package/app/components/workshop/blocks/ClusterRoute.vue +1 -1
  885. package/app/components/workshop/blocks/EventBlockAvatar.vue +114 -0
  886. package/app/components/workshop/blocks/EventBlockHead.vue +232 -0
  887. package/app/components/workshop/composer/ComposerToolbar.vue +278 -0
  888. package/app/components/workshop/composer/MentionMenu.vue +135 -0
  889. package/app/components/workshop/composer/ReplyChip.vue +65 -0
  890. package/app/components/workshop/composer/StatusChip.vue +171 -0
  891. package/app/components/workshop/console/CenterPane.vue +94 -0
  892. package/app/components/workshop/console/Dock.vue +71 -0
  893. package/app/components/workshop/console/LeftPane.vue +84 -0
  894. package/app/components/workshop/console/RightPane.vue +95 -0
  895. package/app/components/workshop/console/Topbar.vue +289 -0
  896. package/app/components/workshop/device-twin/DeviceTwinCard.vue +265 -0
  897. package/app/components/workshop/device-twin/DeviceTwinDcwSection.vue +203 -0
  898. package/app/components/workshop/lanes/AddMemberModal.vue +270 -0
  899. package/app/components/workshop/lanes/EditMemberModal.vue +106 -0
  900. package/app/components/workshop/lanes/LaneHeader.vue +432 -0
  901. package/app/components/workshop/lanes/LanesToolbar.vue +97 -0
  902. package/app/components/workshop/session-list/ChannelSettingsModal.vue +329 -0
  903. package/app/components/workshop/session-list/MountChannelModal.vue +124 -0
  904. package/app/components/workshop/session-list/SaveTemplateModal.vue +93 -0
  905. package/app/components/workshop/task-board/Card.vue +172 -0
  906. package/app/components/workshop/task-board/Column.vue +151 -0
  907. package/app/components/workshop/task-board/List.vue +157 -0
  908. package/app/components/workshop/task-board/StatusMenu.vue +88 -0
  909. package/app/components/workshop/teams/TeamAddMemberModal.vue +53 -0
  910. package/app/components/workshop/teams/TeamCard.vue +183 -0
  911. package/app/components/workshop/teams/TeamCreateModal.vue +94 -0
  912. package/app/components/workshop/teams/TeamDeployModal.vue +40 -0
  913. package/app/components/workshop/teams/TeamPluginModal.vue +119 -0
  914. package/app/components/workshop/terminal/OmpTerminalPanel.vue +19 -760
  915. package/app/components/workshop/terminal/TerminalHitlCard.vue +170 -0
  916. package/app/components/workshop/town/TownScene.ts +72 -961
  917. package/app/components/workshop/town/TownScene3D.ts +357 -4045
  918. package/app/components/workshop/town/TownView.vue +461 -7405
  919. package/app/components/workshop/town/components/TownAgentBindings.vue +380 -0
  920. package/app/components/workshop/town/components/TownAgentInspector.vue +546 -0
  921. package/app/components/workshop/town/components/TownAlarmList.vue +147 -0
  922. package/app/components/workshop/town/components/TownBoundaryPanel.vue +350 -0
  923. package/app/components/workshop/town/components/TownCalloutLayer.vue +147 -0
  924. package/app/components/workshop/town/components/TownChannelDock.vue +116 -0
  925. package/app/components/workshop/town/components/TownControlDock.vue +400 -0
  926. package/app/components/workshop/town/components/TownDaqNodeInspector.vue +345 -0
  927. package/app/components/workshop/town/components/TownDaqTree.vue +366 -0
  928. package/app/components/workshop/town/components/TownDcwBindSection.vue +473 -0
  929. package/app/components/workshop/town/components/TownDcwNodeInspector.vue +185 -0
  930. package/app/components/workshop/town/components/TownDcwTree.vue +280 -0
  931. package/app/components/workshop/town/components/TownDeviceBindings.vue +394 -0
  932. package/app/components/workshop/town/components/TownDeviceTransform.vue +248 -0
  933. package/app/components/workshop/town/components/TownEventList.vue +120 -0
  934. package/app/components/workshop/town/components/TownHealthBody.vue +100 -0
  935. package/app/components/workshop/town/components/TownKpiStrip.vue +207 -0
  936. package/app/components/workshop/town/components/TownLeftRail.vue +308 -0
  937. package/app/components/workshop/town/components/TownLoadingMask.vue +61 -0
  938. package/app/components/workshop/town/components/TownNavMap.vue +154 -0
  939. package/app/components/workshop/town/components/TownRightRail.vue +540 -0
  940. package/app/components/workshop/town/components/TownStage.vue +281 -0
  941. package/app/components/workshop/town/components/TownStageTop.vue +238 -0
  942. package/app/components/workshop/town/components/TownStatusBar.vue +108 -0
  943. package/app/components/workshop/town/components/TownTopNav.vue +367 -0
  944. package/app/components/workshop/town/components/TownTrendDock.vue +178 -0
  945. package/app/components/workshop/town/scene/town-scene-agents.ts +254 -0
  946. package/app/components/workshop/town/scene/town-scene-behavior.ts +194 -0
  947. package/app/components/workshop/town/scene/town-scene-bubbles.ts +96 -0
  948. package/app/components/workshop/town/scene/town-scene-core.ts +158 -0
  949. package/app/components/workshop/town/scene/town-scene-models.ts +109 -0
  950. package/app/components/workshop/town/scene/town-scene-visuals.ts +279 -0
  951. package/app/components/workshop/town/scene/town-scene3d-agents.ts +308 -0
  952. package/app/components/workshop/town/scene/town-scene3d-blocks.ts +297 -0
  953. package/app/components/workshop/town/scene/town-scene3d-bubbles.ts +281 -0
  954. package/app/components/workshop/town/scene/town-scene3d-camera.ts +282 -0
  955. package/app/components/workshop/town/scene/town-scene3d-devices.ts +433 -0
  956. package/app/components/workshop/town/scene/town-scene3d-editing.ts +490 -0
  957. package/app/components/workshop/town/scene/town-scene3d-events.ts +173 -0
  958. package/app/components/workshop/town/scene/town-scene3d-factory.ts +412 -0
  959. package/app/components/workshop/town/scene/town-scene3d-hud.ts +110 -0
  960. package/app/components/workshop/town/scene/town-scene3d-lifecycle.ts +144 -0
  961. package/app/components/workshop/town/scene/town-scene3d-links.ts +141 -0
  962. package/app/components/workshop/town/scene/town-scene3d-loop.ts +286 -0
  963. package/app/components/workshop/town/scene/town-scene3d-models.ts +154 -0
  964. package/app/components/workshop/town/scene/town-scene3d-nodes.ts +562 -0
  965. package/app/components/workshop/town/scene/town-scene3d-ranges.ts +273 -0
  966. package/app/components/workshop/town/scene/town-scene3d-renderer.ts +320 -0
  967. package/app/components/workshop/town/scene/town-scene3d-selection.ts +257 -0
  968. package/app/components/workshop/town/scene/town-scene3d-types.ts +264 -0
  969. package/app/components/workshop/transcript/TranscriptBlockList.vue +68 -0
  970. package/app/components/workshop/transcript/TranscriptEarlierPager.vue +81 -0
  971. package/app/components/workshop/transcript/TranscriptEmptyState.vue +153 -0
  972. package/app/components/workshop/transcript/TranscriptFilterBar.vue +126 -0
  973. package/app/components/workshop/workbench/AuthGate.vue +179 -0
  974. package/app/components/workshop/workbench/CreateWorkspaceModal.vue +36 -0
  975. package/app/components/workshop/workbench/WorkbenchHead.vue +129 -0
  976. package/app/components/workshop/workbench/WorkspaceCard.vue +198 -0
  977. package/app/components/workshop/workbench/WorkspaceEmpty.vue +56 -0
  978. package/app/composables/workshop/device-twin-types.ts +29 -0
  979. package/app/composables/workshop/event-blocks/attention.ts +58 -0
  980. package/app/composables/workshop/event-blocks/classify.ts +44 -0
  981. package/app/composables/workshop/event-blocks/clusterer.ts +231 -0
  982. package/app/composables/workshop/event-blocks/constants.ts +48 -0
  983. package/app/composables/workshop/event-blocks/fold-stream.ts +45 -0
  984. package/app/composables/workshop/event-blocks/markdown.ts +81 -0
  985. package/app/composables/workshop/event-blocks/mentions.ts +49 -0
  986. package/app/composables/workshop/event-blocks/text-utils.ts +100 -0
  987. package/app/composables/workshop/event-blocks/types.ts +40 -0
  988. package/app/composables/workshop/town/town-relay-props.ts +226 -0
  989. package/app/composables/workshop/town/town-view-types.ts +145 -0
  990. package/app/composables/workshop/town/useTownAgentBindings.ts +115 -0
  991. package/app/composables/workshop/town/useTownAgentChat.ts +203 -0
  992. package/app/composables/workshop/town/useTownAlarms.ts +36 -0
  993. package/app/composables/workshop/town/useTownCallouts.ts +174 -0
  994. package/app/composables/workshop/town/useTownChannels.ts +220 -0
  995. package/app/composables/workshop/town/useTownHealth.ts +66 -0
  996. package/app/composables/workshop/town/useTownHudTickers.ts +77 -0
  997. package/app/composables/workshop/town/useTownLayout.ts +277 -0
  998. package/app/composables/workshop/town/useTownNavMap.ts +301 -0
  999. package/app/composables/workshop/town/useTownNodeBindings.ts +345 -0
  1000. package/app/composables/workshop/town/useTownPanelDrag.ts +91 -0
  1001. package/app/composables/workshop/town/useTownRailData.ts +421 -0
  1002. package/app/composables/workshop/town/useTownSceneControl.ts +92 -0
  1003. package/app/composables/workshop/town/useTownSceneInput.ts +275 -0
  1004. package/app/composables/workshop/town/useTownSceneRuntime.ts +390 -0
  1005. package/app/composables/workshop/town/useTownSheets.ts +41 -0
  1006. package/app/composables/workshop/town/useTownTrends.ts +93 -0
  1007. package/app/composables/workshop/useChannelList.ts +88 -0
  1008. package/app/composables/workshop/useChannelTerminals.ts +65 -0
  1009. package/app/composables/workshop/useComposerMentions.ts +130 -0
  1010. package/app/composables/workshop/useDeviceTwinDcwWrite.ts +77 -0
  1011. package/app/composables/workshop/useDeviceTwinPanel.ts +56 -0
  1012. package/app/composables/workshop/useEventBlockView.ts +159 -0
  1013. package/app/composables/workshop/useEventBlocks.ts +33 -662
  1014. package/app/composables/workshop/useHitlAnswer.ts +184 -0
  1015. package/app/composables/workshop/useLaneWidths.ts +27 -0
  1016. package/app/composables/workshop/useOmpTerminalSession.ts +424 -0
  1017. package/app/composables/workshop/usePluginAdmin.ts +152 -0
  1018. package/app/composables/workshop/useRuntimeConfigDraft.ts +118 -0
  1019. package/app/composables/workshop/useSettingsGroups.ts +243 -0
  1020. package/app/composables/workshop/useTaskBoardColumns.ts +47 -0
  1021. package/app/composables/workshop/useTaskBoardDrag.ts +78 -0
  1022. package/app/composables/workshop/useTaskBoardMenu.ts +60 -0
  1023. package/app/composables/workshop/useTaskBoardTasks.ts +74 -0
  1024. package/app/composables/workshop/useTerminalRenderer.ts +308 -0
  1025. package/app/composables/workshop/useTranscriptEnterStage.ts +48 -0
  1026. package/app/composables/workshop/useTranscriptHistory.ts +52 -0
  1027. package/app/composables/workshop/useTranscriptScroll.ts +113 -0
  1028. package/app/composables/workshop/useWorkshopApi.ts +85 -2
  1029. package/app/composables/workshop/useWorkshopWs.ts +265 -126
  1030. package/app/config/schema.ts +13 -0
  1031. package/app/layouts/town.vue +21 -16
  1032. package/app/pages/aml/api.ts +6 -0
  1033. package/app/pages/aml/composables/useAmlDatasets.ts +38 -0
  1034. package/app/pages/aml/composables/useAmlEntities.ts +57 -0
  1035. package/app/pages/aml/composables/useAmlEnv.ts +191 -0
  1036. package/app/pages/aml/composables/useAmlExperiments.ts +45 -0
  1037. package/app/pages/aml/composables/useAmlFormat.ts +44 -0
  1038. package/app/pages/aml/composables/useAmlJobs.ts +131 -0
  1039. package/app/pages/aml/composables/useAmlModels.ts +56 -0
  1040. package/app/pages/aml/composables/useAmlNotes.ts +47 -0
  1041. package/app/pages/aml/composables/useAmlPredict.ts +93 -0
  1042. package/app/pages/aml/constants.ts +63 -0
  1043. package/app/pages/aml/index.vue +143 -2596
  1044. package/app/pages/aml/styles.css +420 -0
  1045. package/app/pages/aml/types.ts +189 -0
  1046. package/app/pages/composables/useDashboardCharts.ts +192 -0
  1047. package/app/pages/composables/useDashboardData.ts +144 -0
  1048. package/app/pages/daq/[id].vue +59 -929
  1049. package/app/pages/daq/composables/useDaqAddNode.ts +126 -0
  1050. package/app/pages/daq/composables/useDaqDetailBinding.ts +29 -0
  1051. package/app/pages/daq/composables/useDaqDetailFrames.ts +42 -0
  1052. package/app/pages/daq/composables/useDaqDetailHistory.ts +199 -0
  1053. package/app/pages/daq/composables/useDaqDetailNode.ts +53 -0
  1054. package/app/pages/daq/composables/useDaqDetailParams.ts +138 -0
  1055. package/app/pages/daq/composables/useDaqFilters.ts +187 -0
  1056. package/app/pages/daq/composables/useDaqNodeWindow.ts +86 -0
  1057. package/app/pages/daq/composables/useDaqOptimizations.ts +99 -0
  1058. package/app/pages/daq/composables/useDaqTemplates.ts +152 -0
  1059. package/app/pages/daq/daq-page.css +681 -0
  1060. package/app/pages/daq/index.vue +132 -2188
  1061. package/app/pages/dcw/[id].vue +297 -2615
  1062. package/app/pages/dcw/composables/useDcwAddNode.ts +121 -0
  1063. package/app/pages/dcw/composables/useDcwCards.ts +27 -0
  1064. package/app/pages/dcw/composables/useDcwDetailScope.ts +145 -0
  1065. package/app/pages/dcw/composables/useDcwDetailTemplates.ts +94 -0
  1066. package/app/pages/dcw/composables/useDcwFilter.ts +29 -0
  1067. package/app/pages/dcw/composables/useDcwLedger.ts +34 -0
  1068. package/app/pages/dcw/composables/useDcwLineMutations.ts +115 -0
  1069. package/app/pages/dcw/composables/useDcwLineRun.ts +60 -0
  1070. package/app/pages/dcw/composables/useDcwProducts.ts +54 -0
  1071. package/app/pages/dcw/composables/useDcwQuery.ts +68 -0
  1072. package/app/pages/dcw/composables/useDcwQuickActions.ts +47 -0
  1073. package/app/pages/dcw/composables/useDcwRecipes.ts +239 -0
  1074. package/app/pages/dcw/composables/useDcwTemplates.ts +50 -0
  1075. package/app/pages/dcw/composables/useDcwWrites.ts +81 -0
  1076. package/app/pages/dcw/dcw-page.css +344 -0
  1077. package/app/pages/dcw/index.vue +64 -986
  1078. package/app/pages/dcw/types.ts +9 -0
  1079. package/app/pages/index.vue +64 -925
  1080. package/app/pages/logs/composables/useLogsFormat.ts +43 -0
  1081. package/app/pages/logs/composables/useLogsManual.ts +63 -0
  1082. package/app/pages/logs/composables/useLogsQuery.ts +52 -0
  1083. package/app/pages/logs/composables/useLogsScope.ts +39 -0
  1084. package/app/pages/logs/index.vue +57 -728
  1085. package/app/pages/monitor/composables/useMonitorColumns.ts +69 -0
  1086. package/app/pages/monitor/composables/useMonitorData.ts +124 -0
  1087. package/app/pages/monitor/composables/useMonitorFormat.ts +39 -0
  1088. package/app/pages/monitor/composables/useMonitorTerminal.ts +28 -0
  1089. package/app/pages/monitor/types.ts +61 -0
  1090. package/app/pages/monitor.vue +88 -658
  1091. package/app/pages/permissions/index.vue +3 -2
  1092. package/app/pages/plugins/index.vue +3 -2
  1093. package/app/pages/settings.vue +7 -1403
  1094. package/app/pages/tokens/composables/useTokenClipboard.ts +33 -0
  1095. package/app/pages/tokens/composables/useTokenCreate.ts +93 -0
  1096. package/app/pages/tokens/composables/useTokenRename.ts +54 -0
  1097. package/app/pages/tokens/composables/useTokenReveal.ts +84 -0
  1098. package/app/pages/tokens/composables/useTokensList.ts +60 -0
  1099. package/app/pages/tokens.vue +79 -597
  1100. package/app/pages/users.vue +5 -4
  1101. package/app/pages/workshop/agents.vue +47 -529
  1102. package/app/pages/workshop/composables/useAgentHarnessOptions.ts +59 -0
  1103. package/app/pages/workshop/composables/useAgentTemplateActions.ts +42 -0
  1104. package/app/pages/workshop/composables/useAgentTemplateDialogState.ts +24 -0
  1105. package/app/pages/workshop/composables/useAgentTemplateForm.ts +88 -0
  1106. package/app/pages/workshop/composables/useAgentTemplatesCatalog.ts +92 -0
  1107. package/app/pages/workshop/composables/useTeamActions.ts +44 -0
  1108. package/app/pages/workshop/composables/useTeamAddMemberDialog.ts +49 -0
  1109. package/app/pages/workshop/composables/useTeamCreateDialog.ts +67 -0
  1110. package/app/pages/workshop/composables/useTeamDeployDialog.ts +49 -0
  1111. package/app/pages/workshop/composables/useTeamDialogState.ts +46 -0
  1112. package/app/pages/workshop/composables/useTeamPluginDialog.ts +72 -0
  1113. package/app/pages/workshop/composables/useTeamsCatalog.ts +100 -0
  1114. package/app/pages/workshop/composables/useWorkbenchAuth.ts +125 -0
  1115. package/app/pages/workshop/composables/useWorkbenchWorkspaces.ts +123 -0
  1116. package/app/pages/workshop/composables/useWorkspaceAuth.ts +31 -0
  1117. package/app/pages/workshop/composables/useWorkspaceChannels.ts +62 -0
  1118. package/app/pages/workshop/composables/useWorkspacePanels.ts +56 -0
  1119. package/app/pages/workshop/composables/useWorkspaceShell.ts +136 -0
  1120. package/app/pages/workshop/index.vue +68 -620
  1121. package/app/pages/workshop/schedules.vue +553 -0
  1122. package/app/pages/workshop/teams.vue +58 -568
  1123. package/app/pages/workshop/w/[wsId].vue +113 -596
  1124. package/app/stores/workshop/chat.ts +434 -0
  1125. package/app/stores/workshop/entities.ts +385 -283
  1126. package/app/stores/workshop/events.ts +5 -4
  1127. package/app/stores/workshop/hitl.ts +110 -7
  1128. package/app/stores/workshop/narrow-fetch.ts +23 -0
  1129. package/app/stores/workshop/notifications.ts +288 -0
  1130. package/app/stores/workshop/user.ts +23 -10
  1131. package/app/stores/workshop/workspaces.ts +12 -2
  1132. package/config.yml +7 -1
  1133. package/docs/aml.md +11 -0
  1134. package/docs/cli.en.md +5 -5
  1135. package/docs/cli.md +5 -5
  1136. package/docs/full-test-plan.md +15 -1
  1137. package/docs/plugins.en.md +1 -1
  1138. package/docs/plugins.md +1 -1
  1139. package/docs/sdk.en.md +1 -1
  1140. package/docs/sdk.md +1 -1
  1141. package/i18n/dicts/dcwDetail.json +2 -0
  1142. package/i18n/locales/en.ts +71 -0
  1143. package/i18n/locales/zh-CN.ts +71 -0
  1144. package/package.json +2 -3
  1145. package/scripts/_audit/detached-instance.mjs +112 -0
  1146. package/scripts/_audit/schedules-ui/schedules-page.png +0 -0
  1147. package/scripts/_audit/schedules-ui/workspace-channel-tag.png +0 -0
  1148. package/scripts/_audit/stubs/nuxt-imports.mjs +67 -0
  1149. package/scripts/_audit/ts-register-hook.mjs +12 -0
  1150. package/scripts/_audit/ts-resolve-hook.mjs +25 -5
  1151. package/scripts/_audit/watchdog-recovery-test.mjs +24 -2
  1152. package/scripts/_aw-packaged-start.cjs +22 -0
  1153. package/scripts/_aw0746-stage12.mjs +94 -0
  1154. package/scripts/_aw0746-stage34.mjs +134 -0
  1155. package/scripts/_aw0746-stage5.mjs +106 -0
  1156. package/scripts/_aw0746-stage6-aml.mjs +177 -0
  1157. package/scripts/_aw0746-stage6b-train-sweep.mjs +142 -0
  1158. package/scripts/_aw0746-stage7-twin.mjs +259 -0
  1159. package/scripts/_aw0746-train-strong.py +142 -0
  1160. package/scripts/_dbg-comm-verify.mjs +188 -0
  1161. package/scripts/_dbg-full-feature-e2e.mjs +19 -11
  1162. package/scripts/_dbg-im-e2e.mjs +169 -0
  1163. package/scripts/_dbg-opt2-run.mjs +114 -0
  1164. package/scripts/_dbg-opt2-setup.mjs +95 -0
  1165. package/scripts/_dbg-param-map-e2e.ts +1 -1
  1166. package/scripts/_dbg-param-map-rest-smoke.mjs +1 -1
  1167. package/scripts/_dbg-real-scenario-e2e.mjs +246 -0
  1168. package/scripts/_dbg-schedule-e2e.mjs +262 -0
  1169. package/scripts/_dbg-schedule-ui-verify.mjs +115 -0
  1170. package/scripts/_dbg-team-matrix.mjs +148 -0
  1171. package/scripts/_dbg-ui-fullcrawl.mjs +91 -0
  1172. package/scripts/_dbg-ui-static-audit.mjs +65 -0
  1173. package/scripts/_dbg-write-lock-e2e.mjs +131 -0
  1174. package/scripts/_plc-sim-detached.cjs +22 -0
  1175. package/scripts/_probe-daq-omp-task.mjs +100 -0
  1176. package/scripts/_probe-daq-scope-default.mjs +123 -0
  1177. package/scripts/_probe-density.cjs +15 -0
  1178. package/scripts/_probe-gate-model.mjs +34 -0
  1179. package/scripts/_probe-model-eligibility.cjs +13 -0
  1180. package/scripts/_probe-recipe-switch.mjs +92 -0
  1181. package/scripts/_provision-injection-line.mjs +39 -0
  1182. package/scripts/_run-matrix.ps1 +53 -0
  1183. package/scripts/_run-real-injection-fifo-goals.mjs +367 -0
  1184. package/scripts/acceptance-aml-db.ts +23 -0
  1185. package/scripts/acceptance-aml-hybrid-live.mjs +94 -0
  1186. package/scripts/acceptance-aml-hybrid-twin.ts +93 -0
  1187. package/scripts/acceptance-hybrid-python.py +51 -0
  1188. package/scripts/aml-ui-smoke.mjs +2 -0
  1189. package/scripts/api-live-e2e.mjs +32 -7
  1190. package/scripts/demo-group-chat-all-real.ts +279 -0
  1191. package/scripts/demo-group-chat-omp-cases.ts +210 -0
  1192. package/scripts/demo-group-chat-omp.ts +184 -0
  1193. package/scripts/demo-group-chat-order.ts +197 -0
  1194. package/scripts/e2e-agent-team.mjs +6 -4
  1195. package/scripts/e2e-agentteam-chat.mjs +473 -0
  1196. package/scripts/e2e-agentteam-real-triage.mjs +421 -0
  1197. package/scripts/e2e-agentteam-task-flow-real.ts +443 -0
  1198. package/scripts/e2e-aw-tui-global.mjs +8 -8
  1199. package/scripts/e2e-channel-isolation.ts +5 -1
  1200. package/scripts/e2e-closedloop/cleanup.mjs +27 -0
  1201. package/scripts/e2e-closedloop/lib.mjs +109 -0
  1202. package/scripts/e2e-closedloop/main.mjs +59 -0
  1203. package/scripts/e2e-closedloop/scenarios-accounts.mjs +208 -0
  1204. package/scripts/e2e-closedloop/scenarios-authz.mjs +190 -0
  1205. package/scripts/e2e-closedloop/scenarios-daq-dcw.mjs +219 -0
  1206. package/scripts/e2e-closedloop/scenarios-plugins.mjs +169 -0
  1207. package/scripts/e2e-closedloop/state.mjs +10 -0
  1208. package/scripts/e2e-crash-cycle.mjs +108 -0
  1209. package/scripts/e2e-full-closedloop.mjs +6 -919
  1210. package/scripts/e2e-hil-multi-user.mjs +307 -0
  1211. package/scripts/e2e-hil-restart.mjs +339 -0
  1212. package/scripts/e2e-hitl-live.mjs +336 -323
  1213. package/scripts/e2e-lead-team-mgmt.ts +1 -1
  1214. package/scripts/e2e-memory-system.ts +70 -89
  1215. package/scripts/e2e-multiharness-team.mjs +32 -15
  1216. package/scripts/e2e-omp-workspace.ts +21 -6
  1217. package/scripts/e2e-parallel-execution.mjs +20 -6
  1218. package/scripts/e2e-plc-plugin-closedloop.mjs +96 -65
  1219. package/scripts/e2e-rest-memory.mjs +26 -11
  1220. package/scripts/e2e-resume-crash.mjs +22 -7
  1221. package/scripts/e2e-task-queue.ts +8 -8
  1222. package/scripts/e2e-template-isolation.mjs +4 -2
  1223. package/scripts/proof-omp-real.ts +189 -0
  1224. package/scripts/real-injection-closed-loop.mjs +383 -0
  1225. package/scripts/test-agent-runtime.ts +5 -1
  1226. package/scripts/test-agentteam-guardrails.ts +113 -0
  1227. package/scripts/test-agentteam-task-chat-flow.ts +420 -0
  1228. package/scripts/test-agentteam-workflow.ts +472 -0
  1229. package/scripts/test-collab-e2e.mjs +17 -3
  1230. package/scripts/test-dcw-read.ts +9 -2
  1231. package/scripts/test-event-persistence.mjs +10 -2
  1232. package/scripts/test-full-system.ts +16 -3
  1233. package/scripts/test-group-chat-hardening.ts +265 -0
  1234. package/scripts/test-group-chat-unit/lib.ts +186 -0
  1235. package/scripts/test-group-chat-unit/main.ts +45 -0
  1236. package/scripts/test-group-chat-unit/section-a-migration.ts +75 -0
  1237. package/scripts/test-group-chat-unit/section-b-permission-matrix.ts +88 -0
  1238. package/scripts/test-group-chat-unit/section-c-member-lifecycle.ts +64 -0
  1239. package/scripts/test-group-chat-unit/section-d-mention-resolution.ts +36 -0
  1240. package/scripts/test-group-chat-unit/section-e-chat-facts.ts +97 -0
  1241. package/scripts/test-group-chat-unit/section-f-user-notifications.ts +52 -0
  1242. package/scripts/test-group-chat-unit/section-g-member-projection.ts +44 -0
  1243. package/scripts/test-group-chat-unit/section-h-outbox-consistency.ts +30 -0
  1244. package/scripts/test-group-chat-unit/section-i-read-idempotency.ts +24 -0
  1245. package/scripts/test-group-chat-unit/section-j-permission-scope.ts +150 -0
  1246. package/scripts/test-group-chat-unit.ts +35 -0
  1247. package/scripts/test-hitl-decision.ts +526 -0
  1248. package/scripts/test-hitl-legacy-channel-auth.ts +143 -0
  1249. package/scripts/test-lane-history-live.mjs +30 -7
  1250. package/scripts/test-memory-vector.ts +10 -0
  1251. package/scripts/test-orchestration.ts +7 -2
  1252. package/scripts/test-persistence-lazy.ts +7 -1
  1253. package/scripts/test-prompt-system.mjs +46 -20
  1254. package/scripts/test-route-reason.mjs +9 -2
  1255. package/scripts/test-schedule-runtime.ts +309 -0
  1256. package/scripts/test-scheduler-loop/lib.ts +321 -0
  1257. package/scripts/test-scheduler-loop/main.ts +40 -0
  1258. package/scripts/test-scheduler-loop/scenarios-modes.ts +95 -0
  1259. package/scripts/test-scheduler-loop/scenarios-runtime.ts +58 -0
  1260. package/scripts/test-scheduler-loop/scenarios-supervise.ts +183 -0
  1261. package/scripts/test-scheduler-loop/scenarios-triage.ts +102 -0
  1262. package/scripts/test-scheduler-loop.ts +5 -460
  1263. package/scripts/test-supervision-watchdog.ts +105 -0
  1264. package/scripts/test-task-engine.ts +27 -5
  1265. package/scripts/test-task-lease-fencing.ts +154 -0
  1266. package/scripts/test-terminal-e2e.mjs +9 -3
  1267. package/scripts/three-system-e2e/lib.mjs +79 -0
  1268. package/scripts/three-system-e2e/main.mjs +40 -0
  1269. package/scripts/three-system-e2e/phases/phase-0-user-token.mjs +42 -0
  1270. package/scripts/three-system-e2e/phases/phase-0a2-line-autoselect.mjs +35 -0
  1271. package/scripts/three-system-e2e/phases/phase-0b-fixture-grants.mjs +39 -0
  1272. package/scripts/three-system-e2e/phases/phase-0c-plugin-outbound-auth.mjs +95 -0
  1273. package/scripts/three-system-e2e/phases/stage-1-health.mjs +47 -0
  1274. package/scripts/three-system-e2e/phases/stage-2-5-team-plugins.mjs +84 -0
  1275. package/scripts/three-system-e2e/phases/stage-2-teams-tools-hitl.mjs +184 -0
  1276. package/scripts/three-system-e2e/phases/stage-3-diag.mjs +73 -0
  1277. package/scripts/three-system-e2e/phases/stage-4-plugin-toggle.mjs +53 -0
  1278. package/scripts/three-system-e2e/state.mjs +87 -0
  1279. package/scripts/three-system-e2e.mjs +24 -646
  1280. package/scripts/typecheck.mjs +75 -0
  1281. package/scripts/ui/lib.mjs +14 -2
  1282. package/scripts/ui/smoke-refactor.mjs +258 -0
  1283. package/scripts/verify-a2a-live.mjs +15 -1
  1284. package/scripts/verify-ws-events.mjs +19 -1
  1285. package/server/api/system/monitor/terminal/ws.ts +51 -1
  1286. package/server/api/workshop/agent-tools/approvals/[id]/decide.post.ts +59 -5
  1287. package/server/api/workshop/agent-tools/approvals/index.get.ts +28 -3
  1288. package/server/api/workshop/channel-templates/[id]/instantiate.post.ts +6 -1
  1289. package/server/api/workshop/channels/[id]/chat/messages/[messageId]/read.post.ts +22 -0
  1290. package/server/api/workshop/channels/[id]/chat/messages/index.get.ts +29 -0
  1291. package/server/api/workshop/channels/[id]/chat/messages/index.post.ts +51 -0
  1292. package/server/api/workshop/channels/[id]/chat/permissions.get.ts +35 -0
  1293. package/server/api/workshop/channels/[id]/index.patch.ts +34 -8
  1294. package/server/api/workshop/channels/[id]/members/[userId]/approve.post.ts +20 -0
  1295. package/server/api/workshop/channels/[id]/members/[userId].delete.ts +21 -0
  1296. package/server/api/workshop/channels/[id]/members/index.get.ts +23 -0
  1297. package/server/api/workshop/channels/[id]/members/join.post.ts +30 -0
  1298. package/server/api/workshop/channels/[id]/members/leave.post.ts +20 -0
  1299. package/server/api/workshop/channels/[id]/messages/index.post.ts +3 -0
  1300. package/server/api/workshop/channels/[id]/tasks/index.post.ts +8 -0
  1301. package/server/api/workshop/channels.get.ts +21 -1
  1302. package/server/api/workshop/hitl/pending.get.ts +27 -7
  1303. package/server/api/workshop/hitl/respond.post.ts +86 -89
  1304. package/server/api/workshop/notifications/index.get.ts +60 -0
  1305. package/server/api/workshop/notifications/read.post.ts +31 -0
  1306. package/server/api/workshop/schedules/[id]/run.post.ts +15 -0
  1307. package/server/api/workshop/schedules/[id]/runs.get.ts +15 -0
  1308. package/server/api/workshop/schedules/[id].delete.ts +14 -0
  1309. package/server/api/workshop/schedules/[id].get.ts +13 -0
  1310. package/server/api/workshop/schedules/[id].patch.ts +29 -0
  1311. package/server/api/workshop/schedules/index.get.ts +12 -0
  1312. package/server/api/workshop/schedules/index.post.ts +32 -0
  1313. package/server/api/workshop/tasks/[id]/reassign.post.ts +32 -0
  1314. package/server/api/workshop/tasks/[id]/update.post.ts +35 -0
  1315. package/server/api/workshop/tasks/[id].get.ts +15 -6
  1316. package/server/api/workshop/ws/audience.ts +56 -0
  1317. package/server/api/workshop/ws/db-buffer.ts +68 -0
  1318. package/server/api/workshop/ws/event-mapping.ts +47 -0
  1319. package/server/api/workshop/ws/handlers.ts +132 -0
  1320. package/server/api/workshop/ws/hub.ts +133 -0
  1321. package/server/api/workshop/ws/index.ts +23 -0
  1322. package/server/api/workshop/ws/notification-uplink.ts +77 -0
  1323. package/server/api/workshop/ws/peer-sub.ts +128 -0
  1324. package/server/api/workshop/ws/publish.ts +122 -0
  1325. package/server/api/workshop/ws/shared.ts +94 -0
  1326. package/server/api/workshop/ws/streams.ts +146 -0
  1327. package/server/api/workshop/ws/subscriptions.ts +137 -0
  1328. package/server/plugins/workshop.ts +30 -0
  1329. package/server/plugins-builtin/diag-bridge/host/constants.mjs +25 -0
  1330. package/server/plugins-builtin/diag-bridge/host/diagnosis.mjs +56 -0
  1331. package/server/plugins-builtin/diag-bridge/host/helpers.mjs +126 -0
  1332. package/server/plugins-builtin/diag-bridge/host/index.mjs +15 -0
  1333. package/server/plugins-builtin/diag-bridge/host/plugin.mjs +296 -0
  1334. package/server/plugins-builtin/diag-bridge/host/snapshot.mjs +93 -0
  1335. package/server/plugins-builtin/diag-bridge/host/sweep.mjs +51 -0
  1336. package/server/plugins-builtin/diag-bridge/index.mjs +6 -605
  1337. package/server/repositories/user.repository.ts +13 -0
  1338. package/server/services/system-config/contracts.ts +29 -0
  1339. package/server/services/system-config/dispose.ts +19 -0
  1340. package/server/services/system-config/facade.ts +26 -0
  1341. package/server/services/system-config/groups.ts +108 -0
  1342. package/server/services/system-config/helpers.ts +41 -0
  1343. package/server/services/system-config/index.ts +21 -0
  1344. package/server/services/system-config/init.ts +26 -0
  1345. package/server/services/system-config/mutate.ts +103 -0
  1346. package/server/services/system-config/plugins.ts +104 -0
  1347. package/server/services/system-config/recompute.ts +66 -0
  1348. package/server/services/system-config/snapshot.ts +59 -0
  1349. package/server/services/system-config/state.ts +79 -0
  1350. package/server/services/system-config/types.ts +58 -0
  1351. package/server/services/workshop/agents/adapters/line-spawn.ts +2 -2
  1352. package/server/services/workshop/agents/adapters/omp-rpc-client/api.ts +111 -0
  1353. package/server/services/workshop/agents/adapters/omp-rpc-client/contracts.ts +22 -0
  1354. package/server/services/workshop/agents/adapters/omp-rpc-client/facade.ts +7 -0
  1355. package/server/services/workshop/agents/adapters/omp-rpc-client/frames.ts +111 -0
  1356. package/server/services/workshop/agents/adapters/omp-rpc-client/helpers.ts +38 -0
  1357. package/server/services/workshop/agents/adapters/omp-rpc-client/index.ts +25 -0
  1358. package/server/services/workshop/agents/adapters/omp-rpc-client/start.ts +70 -0
  1359. package/server/services/workshop/agents/adapters/omp-rpc-client/state.ts +65 -0
  1360. package/server/services/workshop/agents/adapters/omp-rpc-client/stdout.ts +92 -0
  1361. package/server/services/workshop/agents/adapters/omp-rpc-client/types.ts +115 -0
  1362. package/server/services/workshop/agents/adapters/omp-rpc-client/usage.ts +47 -0
  1363. package/server/services/workshop/agents/agent-interface.ts +52 -4
  1364. package/server/services/workshop/agents/base-agent.ts +36 -8
  1365. package/server/services/workshop/agents/claude-agent.ts +5 -0
  1366. package/server/services/workshop/agents/codex-agent/client.ts +170 -0
  1367. package/server/services/workshop/agents/codex-agent/core.ts +119 -0
  1368. package/server/services/workshop/agents/codex-agent/facade.ts +8 -0
  1369. package/server/services/workshop/agents/codex-agent/helpers.ts +13 -0
  1370. package/server/services/workshop/agents/codex-agent/hitl.ts +138 -0
  1371. package/server/services/workshop/agents/codex-agent/index.ts +14 -0
  1372. package/server/services/workshop/agents/codex-agent/turn.ts +367 -0
  1373. package/server/services/workshop/agents/codex-agent/types.ts +42 -0
  1374. package/server/services/workshop/agents/dsh-agent.ts +5 -0
  1375. package/server/services/workshop/agents/harness-availability.ts +127 -127
  1376. package/server/services/workshop/agents/harness-terminal/api.ts +197 -0
  1377. package/server/services/workshop/agents/harness-terminal/index.ts +17 -0
  1378. package/server/services/workshop/agents/harness-terminal/io.ts +120 -0
  1379. package/server/services/workshop/agents/harness-terminal/park.ts +55 -0
  1380. package/server/services/workshop/agents/harness-terminal/sanitize.ts +98 -0
  1381. package/server/services/workshop/agents/harness-terminal/session.ts +124 -0
  1382. package/server/services/workshop/agents/harness-terminal/shared.ts +74 -0
  1383. package/server/services/workshop/agents/hermes-agent.ts +5 -0
  1384. package/server/services/workshop/agents/hitl-capabilities.ts +312 -0
  1385. package/server/services/workshop/agents/hitl-decision/answers.ts +27 -0
  1386. package/server/services/workshop/agents/hitl-decision/decide.ts +268 -0
  1387. package/server/services/workshop/agents/hitl-decision/dispatcher.ts +81 -0
  1388. package/server/services/workshop/agents/hitl-decision/index.ts +24 -0
  1389. package/server/services/workshop/agents/hitl-decision/payload.ts +45 -0
  1390. package/server/services/workshop/agents/hitl-decision/reconcile.ts +70 -0
  1391. package/server/services/workshop/agents/hitl-decision/shared.ts +106 -0
  1392. package/server/services/workshop/agents/hitl-decision/visibility.ts +94 -0
  1393. package/server/services/workshop/agents/hitl-registry.ts +403 -7
  1394. package/server/services/workshop/agents/host-tool-bridge/catalog.ts +78 -0
  1395. package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +167 -0
  1396. package/server/services/workshop/agents/host-tool-bridge/index.ts +15 -0
  1397. package/server/services/workshop/agents/host-tool-bridge/session.ts +22 -0
  1398. package/server/services/workshop/agents/host-tool-bridge/tools/industrial.ts +64 -0
  1399. package/server/services/workshop/agents/host-tool-bridge/tools/memory.ts +44 -0
  1400. package/server/services/workshop/agents/host-tool-bridge/tools/messaging.ts +182 -0
  1401. package/server/services/workshop/agents/host-tool-bridge/tools/progress.ts +84 -0
  1402. package/server/services/workshop/agents/host-tool-bridge/tools/tasks.ts +120 -0
  1403. package/server/services/workshop/agents/host-tool-bridge/tools/team.ts +70 -0
  1404. package/server/services/workshop/agents/host-tool-bridge/types.ts +31 -0
  1405. package/server/services/workshop/agents/industrial/aml-dataset-tools.ts +276 -0
  1406. package/server/services/workshop/agents/industrial/aml-job-tools.ts +363 -0
  1407. package/server/services/workshop/agents/industrial/daq-tools.ts +251 -0
  1408. package/server/services/workshop/agents/industrial/dcw-judge-tools.ts +153 -0
  1409. package/server/services/workshop/agents/industrial/dcw-tools.ts +213 -0
  1410. package/server/services/workshop/agents/industrial/index.ts +24 -0
  1411. package/server/services/workshop/agents/industrial/ops-tools.ts +382 -0
  1412. package/server/services/workshop/agents/industrial/param-tools.ts +172 -0
  1413. package/server/services/workshop/agents/industrial/twin-tools.ts +250 -0
  1414. package/server/services/workshop/agents/mock-agent.ts +136 -12
  1415. package/server/services/workshop/agents/omp-agent/client.ts +116 -0
  1416. package/server/services/workshop/agents/omp-agent/context-governance.ts +204 -0
  1417. package/server/services/workshop/agents/omp-agent/core.ts +146 -0
  1418. package/server/services/workshop/agents/omp-agent/event-mapping.ts +142 -0
  1419. package/server/services/workshop/agents/omp-agent/facade.ts +8 -0
  1420. package/server/services/workshop/agents/omp-agent/helpers.ts +37 -0
  1421. package/server/services/workshop/agents/omp-agent/index.ts +16 -0
  1422. package/server/services/workshop/agents/omp-agent/lifecycle.ts +80 -0
  1423. package/server/services/workshop/agents/omp-agent/tools-steer.ts +487 -0
  1424. package/server/services/workshop/agents/omp-agent/types.ts +45 -0
  1425. package/server/services/workshop/agents/opencode-agent/core.ts +77 -0
  1426. package/server/services/workshop/agents/opencode-agent/engine-agnostic.ts +274 -0
  1427. package/server/services/workshop/agents/opencode-agent/facade.ts +8 -0
  1428. package/server/services/workshop/agents/opencode-agent/helpers.ts +29 -0
  1429. package/server/services/workshop/agents/opencode-agent/hitl.ts +113 -0
  1430. package/server/services/workshop/agents/opencode-agent/index.ts +16 -0
  1431. package/server/services/workshop/agents/opencode-agent/lifecycle.ts +44 -0
  1432. package/server/services/workshop/agents/opencode-agent/server.ts +244 -0
  1433. package/server/services/workshop/agents/opencode-agent/turn.ts +278 -0
  1434. package/server/services/workshop/agents/opencode-agent/types.ts +54 -0
  1435. package/server/services/workshop/agents/prompt-builder.ts +16 -6
  1436. package/server/services/workshop/agents/qwen-agent.ts +6 -2
  1437. package/server/services/workshop/agents/registry.ts +32 -2
  1438. package/server/services/workshop/aml/job-orchestrator/broadcast.ts +37 -0
  1439. package/server/services/workshop/aml/job-orchestrator/control.ts +94 -0
  1440. package/server/services/workshop/aml/job-orchestrator/failure.ts +77 -0
  1441. package/server/services/workshop/aml/job-orchestrator/index.ts +22 -0
  1442. package/server/services/workshop/aml/job-orchestrator/process.ts +100 -0
  1443. package/server/services/workshop/aml/job-orchestrator/shared.ts +51 -0
  1444. package/server/services/workshop/aml/job-orchestrator/stages.ts +96 -0
  1445. package/server/services/workshop/aml/job-orchestrator/start-job.ts +140 -0
  1446. package/server/services/workshop/aml/job-orchestrator/status.ts +20 -0
  1447. package/server/services/workshop/aml/job-orchestrator/submit.ts +120 -0
  1448. package/server/services/workshop/aml/job-orchestrator/tick.ts +37 -0
  1449. package/server/services/workshop/aml/python/__pycache__/amlkit.cpython-312.pyc +0 -0
  1450. package/server/services/workshop/aml/python/amlkit.py +185 -120
  1451. package/server/services/workshop/aml/python/requirements.txt +9 -7
  1452. package/server/services/workshop/aml/python/train-hybrid-example.py +87 -0
  1453. package/server/services/workshop/aml/twin/acceptance.ts +68 -0
  1454. package/server/services/workshop/aml/twin/calibration-scheduler.ts +48 -0
  1455. package/server/services/workshop/aml/twin/channel-profile.ts +43 -0
  1456. package/server/services/workshop/aml/twin/contracts.ts +215 -0
  1457. package/server/services/workshop/aml/twin/physics-runtime.ts +165 -0
  1458. package/server/services/workshop/aml/twin/repo.ts +31 -0
  1459. package/server/services/workshop/aml/twin/snapshot-service.ts +81 -0
  1460. package/server/services/workshop/aml/twin/trial-service.ts +89 -0
  1461. package/server/services/workshop/aml/twin/uq-ood.ts +66 -0
  1462. package/server/services/workshop/daq/daq-controller/contracts.ts +39 -0
  1463. package/server/services/workshop/daq/daq-controller/crud.ts +202 -0
  1464. package/server/services/workshop/daq/daq-controller/driver-probe.ts +64 -0
  1465. package/server/services/workshop/daq/daq-controller/facade.ts +28 -0
  1466. package/server/services/workshop/daq/daq-controller/frames.ts +137 -0
  1467. package/server/services/workshop/daq/daq-controller/helpers.ts +52 -0
  1468. package/server/services/workshop/daq/daq-controller/index.ts +22 -0
  1469. package/server/services/workshop/daq/daq-controller/ingest.ts +189 -0
  1470. package/server/services/workshop/daq/daq-controller/runtime-control.ts +72 -0
  1471. package/server/services/workshop/daq/daq-controller/state.ts +194 -0
  1472. package/server/services/workshop/daq/daq-controller/sweep-alarms.ts +164 -0
  1473. package/server/services/workshop/daq/daq-controller/tsdb-twin.ts +138 -0
  1474. package/server/services/workshop/daq/daq-controller/types.ts +68 -0
  1475. package/server/services/workshop/daq/daq-controller/views.ts +84 -0
  1476. package/server/services/workshop/daq/drivers/http.ts +95 -0
  1477. package/server/services/workshop/daq/drivers/index.ts +29 -0
  1478. package/server/services/workshop/daq/drivers/mock.ts +130 -0
  1479. package/server/services/workshop/daq/drivers/modbus-rtu.ts +67 -0
  1480. package/server/services/workshop/daq/drivers/modbus-tcp.ts +307 -0
  1481. package/server/services/workshop/daq/drivers/mqtt.ts +182 -0
  1482. package/server/services/workshop/daq/drivers/opcua.ts +227 -0
  1483. package/server/services/workshop/daq/drivers/registry.ts +144 -0
  1484. package/server/services/workshop/daq/drivers/s7-stub.ts +29 -0
  1485. package/server/services/workshop/daq/drivers/shared.ts +60 -0
  1486. package/server/services/workshop/db/channel-member.repo.ts +122 -0
  1487. package/server/services/workshop/db/channel.repo.ts +93 -8
  1488. package/server/services/workshop/db/chat-message.repo.ts +233 -0
  1489. package/server/services/workshop/db/database/index.ts +18 -0
  1490. package/server/services/workshop/db/database/json.ts +14 -0
  1491. package/server/services/workshop/db/database/migrations.ts +367 -0
  1492. package/server/services/workshop/db/database/open.ts +76 -0
  1493. package/server/services/workshop/db/database/rows.ts +374 -0
  1494. package/server/services/workshop/db/database/schema.ts +651 -0
  1495. package/server/services/workshop/db/database/seed-data.ts +60 -0
  1496. package/server/services/workshop/db/database/seed.ts +145 -0
  1497. package/server/services/workshop/db/hitl-request.repo.ts +221 -0
  1498. package/server/services/workshop/db/notification.repo.ts +199 -0
  1499. package/server/services/workshop/db/outbox.repo.ts +127 -0
  1500. package/server/services/workshop/db/scheduled-task.repo.ts +235 -0
  1501. package/server/services/workshop/db/schema.sql +185 -0
  1502. package/server/services/workshop/db/task.repo.ts +185 -69
  1503. package/server/services/workshop/db/transaction.ts +72 -0
  1504. package/server/services/workshop/dcw/dcw-controller/actuation.ts +127 -0
  1505. package/server/services/workshop/dcw/dcw-controller/bindings.ts +83 -0
  1506. package/server/services/workshop/dcw/dcw-controller/contracts.ts +16 -0
  1507. package/server/services/workshop/dcw/dcw-controller/control.ts +78 -0
  1508. package/server/services/workshop/dcw/dcw-controller/crud.ts +152 -0
  1509. package/server/services/workshop/dcw/dcw-controller/facade.ts +19 -0
  1510. package/server/services/workshop/dcw/dcw-controller/helpers.ts +57 -0
  1511. package/server/services/workshop/dcw/dcw-controller/index.ts +22 -0
  1512. package/server/services/workshop/dcw/dcw-controller/lines.ts +213 -0
  1513. package/server/services/workshop/dcw/dcw-controller/params.ts +97 -0
  1514. package/server/services/workshop/dcw/dcw-controller/products.ts +103 -0
  1515. package/server/services/workshop/dcw/dcw-controller/recipes.ts +137 -0
  1516. package/server/services/workshop/dcw/dcw-controller/state.ts +66 -0
  1517. package/server/services/workshop/dcw/dcw-controller/types.ts +57 -0
  1518. package/server/services/workshop/dcw/dcw-controller/write.ts +147 -0
  1519. package/server/services/workshop/dcw/dcw-node.ts +8 -0
  1520. package/server/services/workshop/dcw/drivers/http.ts +66 -0
  1521. package/server/services/workshop/dcw/drivers/index.ts +26 -0
  1522. package/server/services/workshop/dcw/drivers/mock.ts +44 -0
  1523. package/server/services/workshop/dcw/drivers/modbus-rtu.ts +93 -0
  1524. package/server/services/workshop/dcw/drivers/modbus.ts +139 -0
  1525. package/server/services/workshop/dcw/drivers/mqtt.ts +108 -0
  1526. package/server/services/workshop/dcw/drivers/opcua.ts +110 -0
  1527. package/server/services/workshop/dcw/drivers/registry.ts +118 -0
  1528. package/server/services/workshop/dcw/drivers/shared.ts +113 -0
  1529. package/server/services/workshop/dcw/recipe-rollback-manager/contracts.ts +26 -0
  1530. package/server/services/workshop/dcw/recipe-rollback-manager/facade.ts +13 -0
  1531. package/server/services/workshop/dcw/recipe-rollback-manager/helpers.ts +30 -0
  1532. package/server/services/workshop/dcw/recipe-rollback-manager/index.ts +17 -0
  1533. package/server/services/workshop/dcw/recipe-rollback-manager/internal.ts +33 -0
  1534. package/server/services/workshop/dcw/recipe-rollback-manager/judge.ts +52 -0
  1535. package/server/services/workshop/dcw/recipe-rollback-manager/lock.ts +137 -0
  1536. package/server/services/workshop/dcw/recipe-rollback-manager/rollback.ts +190 -0
  1537. package/server/services/workshop/dcw/recipe-rollback-manager/state.ts +21 -0
  1538. package/server/services/workshop/dcw/recipe-rollback-manager/sweep.ts +77 -0
  1539. package/server/services/workshop/dcw/recipe-rollback-manager/types.ts +3 -0
  1540. package/server/services/workshop/dcw/recipe-rollback-manager/views.ts +214 -0
  1541. package/server/services/workshop/plugins/host/api.mjs +90 -0
  1542. package/server/services/workshop/plugins/host/config.mjs +135 -0
  1543. package/server/services/workshop/plugins/host/index.mjs +23 -0
  1544. package/server/services/workshop/plugins/host/init.mjs +101 -0
  1545. package/server/services/workshop/plugins/host/load.mjs +321 -0
  1546. package/server/services/workshop/plugins/host/reload.mjs +94 -0
  1547. package/server/services/workshop/plugins/host/rollback.mjs +72 -0
  1548. package/server/services/workshop/plugins/host/services.mjs +58 -0
  1549. package/server/services/workshop/plugins/host/state.mjs +54 -0
  1550. package/server/services/workshop/plugins/host/toggle.mjs +64 -0
  1551. package/server/services/workshop/plugins/host.mjs +6 -890
  1552. package/server/services/workshop/runtime/agent-runtime/contracts.ts +25 -0
  1553. package/server/services/workshop/runtime/agent-runtime/facade.ts +7 -0
  1554. package/server/services/workshop/runtime/agent-runtime/helpers.ts +18 -0
  1555. package/server/services/workshop/runtime/agent-runtime/index.ts +19 -0
  1556. package/server/services/workshop/runtime/agent-runtime/lifecycle.ts +119 -0
  1557. package/server/services/workshop/runtime/agent-runtime/message.ts +409 -0
  1558. package/server/services/workshop/runtime/agent-runtime/state.ts +371 -0
  1559. package/server/services/workshop/runtime/agent-runtime/supervise.ts +188 -0
  1560. package/server/services/workshop/runtime/agent-runtime/types.ts +186 -0
  1561. package/server/services/workshop/runtime/channel-runtime.ts +7 -0
  1562. package/server/services/workshop/runtime/chat-mention.ts +164 -0
  1563. package/server/services/workshop/runtime/chat-projection.ts +246 -0
  1564. package/server/services/workshop/runtime/execution-mode.ts +11 -2
  1565. package/server/services/workshop/runtime/manager/a2a.ts +283 -0
  1566. package/server/services/workshop/runtime/manager/access.ts +483 -0
  1567. package/server/services/workshop/runtime/manager/admin-agents.ts +268 -0
  1568. package/server/services/workshop/runtime/manager/admin-channel.ts +276 -0
  1569. package/server/services/workshop/runtime/manager/bus.ts +182 -0
  1570. package/server/services/workshop/runtime/manager/channel-templates.ts +171 -0
  1571. package/server/services/workshop/runtime/manager/chat-reply.ts +181 -0
  1572. package/server/services/workshop/runtime/manager/chat.ts +384 -0
  1573. package/server/services/workshop/runtime/manager/contracts.ts +210 -0
  1574. package/server/services/workshop/runtime/manager/facade.ts +24 -0
  1575. package/server/services/workshop/runtime/manager/helpers.ts +119 -0
  1576. package/server/services/workshop/runtime/manager/host-tools.ts +249 -0
  1577. package/server/services/workshop/runtime/manager/index.ts +42 -0
  1578. package/server/services/workshop/runtime/manager/internal.ts +73 -0
  1579. package/server/services/workshop/runtime/manager/lead-team.ts +161 -0
  1580. package/server/services/workshop/runtime/manager/notifications.ts +108 -0
  1581. package/server/services/workshop/runtime/manager/runtime-observe.ts +300 -0
  1582. package/server/services/workshop/runtime/manager/runtime-wiring.ts +307 -0
  1583. package/server/services/workshop/runtime/manager/schedules.ts +302 -0
  1584. package/server/services/workshop/runtime/manager/state.ts +644 -0
  1585. package/server/services/workshop/runtime/manager/tasks.ts +518 -0
  1586. package/server/services/workshop/runtime/manager/teams.ts +148 -0
  1587. package/server/services/workshop/runtime/manager/types.ts +245 -0
  1588. package/server/services/workshop/runtime/manager/workspace.ts +136 -0
  1589. package/server/services/workshop/runtime/memory/contracts.ts +18 -0
  1590. package/server/services/workshop/runtime/memory/facade.ts +10 -0
  1591. package/server/services/workshop/runtime/memory/governance.ts +184 -0
  1592. package/server/services/workshop/runtime/memory/helpers.ts +196 -0
  1593. package/server/services/workshop/runtime/memory/index.ts +27 -0
  1594. package/server/services/workshop/runtime/memory/recall.ts +216 -0
  1595. package/server/services/workshop/runtime/memory/score.ts +33 -0
  1596. package/server/services/workshop/runtime/memory/state.ts +40 -0
  1597. package/server/services/workshop/runtime/memory/types.ts +44 -0
  1598. package/server/services/workshop/runtime/memory/write.ts +93 -0
  1599. package/server/services/workshop/runtime/permission-scope.ts +81 -0
  1600. package/server/services/workshop/runtime/schedule-runtime.ts +320 -0
  1601. package/server/services/workshop/runtime/scheduler-loop/contracts.ts +29 -0
  1602. package/server/services/workshop/runtime/scheduler-loop/execute.ts +295 -0
  1603. package/server/services/workshop/runtime/scheduler-loop/facade.ts +7 -0
  1604. package/server/services/workshop/runtime/scheduler-loop/helpers.ts +14 -0
  1605. package/server/services/workshop/runtime/scheduler-loop/index.ts +16 -0
  1606. package/server/services/workshop/runtime/scheduler-loop/rules.ts +131 -0
  1607. package/server/services/workshop/runtime/scheduler-loop/snapshot.ts +99 -0
  1608. package/server/services/workshop/runtime/scheduler-loop/state.ts +150 -0
  1609. package/server/services/workshop/runtime/scheduler-loop/tick.ts +157 -0
  1610. package/server/services/workshop/runtime/scheduler-loop/types.ts +50 -0
  1611. package/server/services/workshop/runtime/task-engine/contracts.ts +16 -0
  1612. package/server/services/workshop/runtime/task-engine/create.ts +145 -0
  1613. package/server/services/workshop/runtime/task-engine/facade.ts +7 -0
  1614. package/server/services/workshop/runtime/task-engine/helpers.ts +87 -0
  1615. package/server/services/workshop/runtime/task-engine/index.ts +15 -0
  1616. package/server/services/workshop/runtime/task-engine/lease.ts +79 -0
  1617. package/server/services/workshop/runtime/task-engine/lifecycle.ts +308 -0
  1618. package/server/services/workshop/runtime/task-engine/policy.ts +113 -0
  1619. package/server/services/workshop/runtime/task-engine/transition.ts +146 -0
  1620. package/server/services/workshop/runtime/task-engine/types.ts +3 -0
  1621. package/server/services/workshop/runtime/task-engine/update.ts +116 -0
  1622. package/server/services/workshop/runtime/task-engine/views.ts +169 -0
  1623. package/server/services/workshop/runtime/user-notification-hub.ts +192 -0
  1624. package/server/services/workshop/settings.ts +32 -0
  1625. package/server/services/workshop/types/task.ts +85 -0
  1626. package/server/utils/errors.ts +2 -0
  1627. package/server/utils/mojibake-guard.ts +29 -0
  1628. package/shared/config/schema.json +12 -0
  1629. package/shared/dcw-protocol/catalog.ts +203 -0
  1630. package/shared/dcw-protocol/closed-loop.ts +137 -0
  1631. package/shared/dcw-protocol/index.ts +19 -0
  1632. package/shared/dcw-protocol/line.ts +131 -0
  1633. package/shared/dcw-protocol/recipe.ts +301 -0
  1634. package/shared/workshop-protocol.ts +327 -5
  1635. package/tui/aw-tui.mjs +27 -6
  1636. package/tui/lib/pickers.mjs +21 -2
  1637. package/.output/public/_nuxt/4sIEbgcl.js +0 -42
  1638. package/.output/public/_nuxt/B7gZokg82.js +0 -209
  1639. package/.output/public/_nuxt/B8uy9iRW.js +0 -1
  1640. package/.output/public/_nuxt/BC9wjTJK2.js +0 -4
  1641. package/.output/public/_nuxt/BCGafdXT.js +0 -2
  1642. package/.output/public/_nuxt/BDmh31hl.js +0 -1
  1643. package/.output/public/_nuxt/BFjGV-RV2.js +0 -4108
  1644. package/.output/public/_nuxt/BGTYYQH-.js +0 -1
  1645. package/.output/public/_nuxt/BHA40aHa.js +0 -1
  1646. package/.output/public/_nuxt/BJHKLJJU.js +0 -1
  1647. package/.output/public/_nuxt/BN6iuJEg2.js +0 -1
  1648. package/.output/public/_nuxt/BU83-NnX.js +0 -106
  1649. package/.output/public/_nuxt/BUmHEJ77.js +0 -2
  1650. package/.output/public/_nuxt/BY7YrV_D.js +0 -1
  1651. package/.output/public/_nuxt/BYhEUx8t.js +0 -17
  1652. package/.output/public/_nuxt/BneN7fbu.js +0 -75
  1653. package/.output/public/_nuxt/Bqcgz95s.js +0 -18
  1654. package/.output/public/_nuxt/C3J6FYKa.js +0 -1
  1655. package/.output/public/_nuxt/C3gwHTz0.js +0 -1
  1656. package/.output/public/_nuxt/C4DWUZ6d2.js +0 -1
  1657. package/.output/public/_nuxt/C7UlqPeD.js +0 -1
  1658. package/.output/public/_nuxt/CClOy-Hz2.js +0 -1
  1659. package/.output/public/_nuxt/CFxtLwjp.js +0 -1
  1660. package/.output/public/_nuxt/CS50LB_l.js +0 -1
  1661. package/.output/public/_nuxt/CSkxz2NX.js +0 -3
  1662. package/.output/public/_nuxt/CTmDn-YP.js +0 -1
  1663. package/.output/public/_nuxt/CWoJjWmZ.js +0 -1
  1664. package/.output/public/_nuxt/CYWVXZTS2.js +0 -7
  1665. package/.output/public/_nuxt/C__m4EhR.js +0 -1
  1666. package/.output/public/_nuxt/CoNpEq5X.js +0 -1
  1667. package/.output/public/_nuxt/Crod5z2f.js +0 -1
  1668. package/.output/public/_nuxt/Cs8pVeVG.js +0 -1
  1669. package/.output/public/_nuxt/CssgYAAL2.js +0 -1
  1670. package/.output/public/_nuxt/CzlMDL5H.js +0 -13
  1671. package/.output/public/_nuxt/D0ZBQrN_2.js +0 -1
  1672. package/.output/public/_nuxt/D6uzXueg.js +0 -2
  1673. package/.output/public/_nuxt/D98NH8kG.js +0 -1
  1674. package/.output/public/_nuxt/D9Oidm5I.js +0 -1
  1675. package/.output/public/_nuxt/DBHNsT1Y.js +0 -23
  1676. package/.output/public/_nuxt/DDgJcSET.js +0 -19
  1677. package/.output/public/_nuxt/DGIuvyRN2.js +0 -1
  1678. package/.output/public/_nuxt/DGj-WEBg.js +0 -1
  1679. package/.output/public/_nuxt/DHFrCUxA2.js +0 -13
  1680. package/.output/public/_nuxt/DIaZ2PTo2.js +0 -1
  1681. package/.output/public/_nuxt/DKySmNPI.js +0 -14
  1682. package/.output/public/_nuxt/DPnwL14Y.js +0 -1
  1683. package/.output/public/_nuxt/DR12_WCA.js +0 -3
  1684. package/.output/public/_nuxt/DTFCy6h-.js +0 -1
  1685. package/.output/public/_nuxt/DYr1VMiQ.js +0 -6
  1686. package/.output/public/_nuxt/DZtm4l-P2.js +0 -1
  1687. package/.output/public/_nuxt/DdD9mCHM.js +0 -1
  1688. package/.output/public/_nuxt/DeyBFhKH2.js +0 -4
  1689. package/.output/public/_nuxt/Di0NY_ht.js +0 -1
  1690. package/.output/public/_nuxt/DkGQ7lnb2.js +0 -1
  1691. package/.output/public/_nuxt/DtYHUi5Z.js +0 -1
  1692. package/.output/public/_nuxt/Dz_Q3Lpw2.js +0 -1
  1693. package/.output/public/_nuxt/DzbFpZRk.js +0 -3
  1694. package/.output/public/_nuxt/EHlEMRii.js +0 -1
  1695. package/.output/public/_nuxt/Eke1fs-U.js +0 -1
  1696. package/.output/public/_nuxt/H0BcqZtT.js +0 -9
  1697. package/.output/public/_nuxt/HSYEkkce.js +0 -1
  1698. package/.output/public/_nuxt/IREZ1eC2.js +0 -1
  1699. package/.output/public/_nuxt/J9oU00D1.js +0 -1
  1700. package/.output/public/_nuxt/JCoYF4IJ.js +0 -31
  1701. package/.output/public/_nuxt/JTLM-kqr2.js +0 -1
  1702. package/.output/public/_nuxt/KZQX7xoM.js +0 -3
  1703. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +0 -1
  1704. package/.output/public/_nuxt/RYFtK4qZ2.js +0 -1
  1705. package/.output/public/_nuxt/SSyAPaHc.js +0 -1
  1706. package/.output/public/_nuxt/TownView.li4EMQxK.css +0 -1
  1707. package/.output/public/_nuxt/VdLk4jLE2.js +0 -1
  1708. package/.output/public/_nuxt/YnQxK3eG.js +0 -1
  1709. package/.output/public/_nuxt/_id_.BhsXoEuk.css +0 -1
  1710. package/.output/public/_nuxt/_id_.DQyj-WKN.css +0 -1
  1711. package/.output/public/_nuxt/_wsId_.CTx-BlfF.css +0 -1
  1712. package/.output/public/_nuxt/aLLjF2K-2.js +0 -1
  1713. package/.output/public/_nuxt/aShsv_rG.js +0 -19
  1714. package/.output/public/_nuxt/aWWIh8YL.js +0 -24
  1715. package/.output/public/_nuxt/aesgXKSr.js +0 -4
  1716. package/.output/public/_nuxt/agents.B21nUEUX.css +0 -1
  1717. package/.output/public/_nuxt/aml.ekTjxFkf.css +0 -1
  1718. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +0 -1
  1719. package/.output/public/_nuxt/ca4TJ3Op.js +0 -1
  1720. package/.output/public/_nuxt/daq.BdVmoIfe.css +0 -1
  1721. package/.output/public/_nuxt/dcw.DCgyzpWV.css +0 -1
  1722. package/.output/public/_nuxt/default.Bh3L2HU8.css +0 -1
  1723. package/.output/public/_nuxt/entry.bdm0tv6M.css +0 -1
  1724. package/.output/public/_nuxt/jZsY8yYz.js +0 -1
  1725. package/.output/public/_nuxt/lHAxdSsj.js +0 -1
  1726. package/.output/public/_nuxt/logs.BpKzmKsh.css +0 -1
  1727. package/.output/public/_nuxt/monitor.D9pe7qN6.css +0 -1
  1728. package/.output/public/_nuxt/pages.j9rzRocr.css +0 -1
  1729. package/.output/public/_nuxt/pdk8jLGT.js +0 -1
  1730. package/.output/public/_nuxt/permissions.DwTVlFe6.css +0 -1
  1731. package/.output/public/_nuxt/plugins.DhiZbHU8.css +0 -1
  1732. package/.output/public/_nuxt/settings.BRx9rIDg.css +0 -1
  1733. package/.output/public/_nuxt/teams.BLlOPB_Z.css +0 -1
  1734. package/.output/public/_nuxt/tokens.De8xzszn.css +0 -1
  1735. package/.output/public/_nuxt/town.BLrD9-WH.css +0 -1
  1736. package/.output/public/_nuxt/users.DLgZ1D0h.css +0 -1
  1737. package/.output/public/_nuxt/vRTjoWqd.js +0 -1
  1738. package/.output/public/_nuxt/workshop.D1jFNkrn.css +0 -1
  1739. package/.output/public/_nuxt/xh3_vYcu.js +0 -1
  1740. package/.output/public/_nuxt/zq5t1vyX.js +0 -1
  1741. package/.output/server/chunks/build/Checkbox-eax2M3o-.mjs +0 -207
  1742. package/.output/server/chunks/build/Checkbox-eax2M3o-.mjs.map +0 -1
  1743. package/.output/server/chunks/build/OmpTerminalPanel-CVvxpsF7.mjs +0 -915
  1744. package/.output/server/chunks/build/OmpTerminalPanel-CVvxpsF7.mjs.map +0 -1
  1745. package/.output/server/chunks/build/PluginSlot-CcR4Ys3T.mjs +0 -400
  1746. package/.output/server/chunks/build/PluginSlot-CcR4Ys3T.mjs.map +0 -1
  1747. package/.output/server/chunks/build/TownView-CJ6IsDCc.mjs +0 -2791
  1748. package/.output/server/chunks/build/TownView-CJ6IsDCc.mjs.map +0 -1
  1749. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs +0 -804
  1750. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +0 -1
  1751. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs +0 -381
  1752. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +0 -1
  1753. package/.output/server/chunks/build/_wsId_-DpY3T-md.mjs +0 -10136
  1754. package/.output/server/chunks/build/_wsId_-DpY3T-md.mjs.map +0 -1
  1755. package/.output/server/chunks/build/agents-DsCkuXDV.mjs +0 -1114
  1756. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +0 -1
  1757. package/.output/server/chunks/build/aml-CT0xKGKI.mjs +0 -605
  1758. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +0 -1
  1759. package/.output/server/chunks/build/api-error-dul30BYQ.mjs +0 -47
  1760. package/.output/server/chunks/build/api-error-dul30BYQ.mjs.map +0 -1
  1761. package/.output/server/chunks/build/apiClient-BwnfOQU8.mjs +0 -81
  1762. package/.output/server/chunks/build/apiClient-BwnfOQU8.mjs.map +0 -1
  1763. package/.output/server/chunks/build/button-DEcZFcqN.mjs +0 -1938
  1764. package/.output/server/chunks/build/button-DEcZFcqN.mjs.map +0 -1
  1765. package/.output/server/chunks/build/card-Dipvaykh.mjs +0 -955
  1766. package/.output/server/chunks/build/card-Dipvaykh.mjs.map +0 -1
  1767. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs +0 -847
  1768. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +0 -1
  1769. package/.output/server/chunks/build/checkbox-8fB-wI-T.mjs +0 -439
  1770. package/.output/server/chunks/build/checkbox-8fB-wI-T.mjs.map +0 -1
  1771. package/.output/server/chunks/build/client-only-D5BzSQc6.mjs +0 -43
  1772. package/.output/server/chunks/build/client-only-D5BzSQc6.mjs.map +0 -1
  1773. package/.output/server/chunks/build/daq-DWWSKGAV.mjs +0 -1059
  1774. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +0 -1
  1775. package/.output/server/chunks/build/dcw-CT8k17_E.mjs +0 -250
  1776. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +0 -1
  1777. package/.output/server/chunks/build/default-D3525UFX.mjs +0 -1487
  1778. package/.output/server/chunks/build/default-D3525UFX.mjs.map +0 -1
  1779. package/.output/server/chunks/build/drawer-Cmh90erI.mjs +0 -836
  1780. package/.output/server/chunks/build/drawer-Cmh90erI.mjs.map +0 -1
  1781. package/.output/server/chunks/build/dropdown-CGQ0KAzA.mjs +0 -14
  1782. package/.output/server/chunks/build/dropdown-CGQ0KAzA.mjs.map +0 -1
  1783. package/.output/server/chunks/build/dropdown-vhn1NPFs.mjs +0 -558
  1784. package/.output/server/chunks/build/dropdown-vhn1NPFs.mjs.map +0 -1
  1785. package/.output/server/chunks/build/initDefaultProps-DIbWwP4R.mjs +0 -21
  1786. package/.output/server/chunks/build/initDefaultProps-DIbWwP4R.mjs.map +0 -1
  1787. package/.output/server/chunks/build/input-number-DzpyYB99.mjs +0 -1521
  1788. package/.output/server/chunks/build/input-number-DzpyYB99.mjs.map +0 -1
  1789. package/.output/server/chunks/build/input-sf_BT6o6.mjs +0 -2617
  1790. package/.output/server/chunks/build/input-sf_BT6o6.mjs.map +0 -1
  1791. package/.output/server/chunks/build/logs--dByNyhw.mjs +0 -198
  1792. package/.output/server/chunks/build/logs--dByNyhw.mjs.map +0 -1
  1793. package/.output/server/chunks/build/menu-WCrlpYVh.mjs +0 -1950
  1794. package/.output/server/chunks/build/menu-WCrlpYVh.mjs.map +0 -1
  1795. package/.output/server/chunks/build/modal-yvUquPj5.mjs +0 -1272
  1796. package/.output/server/chunks/build/modal-yvUquPj5.mjs.map +0 -1
  1797. package/.output/server/chunks/build/monitor-Dh2Kvaq5.mjs +0 -2212
  1798. package/.output/server/chunks/build/monitor-Dh2Kvaq5.mjs.map +0 -1
  1799. package/.output/server/chunks/build/pages-BHcSBM2D.mjs +0 -518
  1800. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +0 -1
  1801. package/.output/server/chunks/build/permissions-68SsNjex.mjs +0 -629
  1802. package/.output/server/chunks/build/permissions-68SsNjex.mjs.map +0 -1
  1803. package/.output/server/chunks/build/plugins-BNgIi61W.mjs +0 -246
  1804. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +0 -1
  1805. package/.output/server/chunks/build/popconfirm-CRaHcz5O.mjs +0 -342
  1806. package/.output/server/chunks/build/popconfirm-CRaHcz5O.mjs.map +0 -1
  1807. package/.output/server/chunks/build/segmented-3TrWo0lS.mjs +0 -5047
  1808. package/.output/server/chunks/build/segmented-3TrWo0lS.mjs.map +0 -1
  1809. package/.output/server/chunks/build/select-D-CeE5vY.mjs +0 -3923
  1810. package/.output/server/chunks/build/select-D-CeE5vY.mjs.map +0 -1
  1811. package/.output/server/chunks/build/settings-ipYBDWdn.mjs +0 -1175
  1812. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +0 -1
  1813. package/.output/server/chunks/build/space-CCBYAkXt.mjs +0 -197
  1814. package/.output/server/chunks/build/space-CCBYAkXt.mjs.map +0 -1
  1815. package/.output/server/chunks/build/spin-HVY81X_L.mjs +0 -358
  1816. package/.output/server/chunks/build/spin-HVY81X_L.mjs.map +0 -1
  1817. package/.output/server/chunks/build/switch-CelRXpcy.mjs +0 -377
  1818. package/.output/server/chunks/build/switch-CelRXpcy.mjs.map +0 -1
  1819. package/.output/server/chunks/build/table-BjSWwmic.mjs +0 -8883
  1820. package/.output/server/chunks/build/table-BjSWwmic.mjs.map +0 -1
  1821. package/.output/server/chunks/build/tabs-BM3tAh_J.mjs +0 -1925
  1822. package/.output/server/chunks/build/tabs-BM3tAh_J.mjs.map +0 -1
  1823. package/.output/server/chunks/build/tag-DNE9vXc0.mjs +0 -1029
  1824. package/.output/server/chunks/build/tag-DNE9vXc0.mjs.map +0 -1
  1825. package/.output/server/chunks/build/teams-5lx-CwFa.mjs +0 -1218
  1826. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +0 -1
  1827. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs +0 -908
  1828. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +0 -1
  1829. package/.output/server/chunks/build/town-BUqRobze.mjs +0 -52
  1830. package/.output/server/chunks/build/town-BUqRobze.mjs.map +0 -1
  1831. package/.output/server/chunks/build/town-QJWB5wng.mjs +0 -125
  1832. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +0 -1
  1833. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs +0 -395
  1834. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +0 -1
  1835. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs +0 -435
  1836. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +0 -1
  1837. package/.output/server/chunks/build/useDeviceTwins-Yko6nNPF.mjs +0 -128
  1838. package/.output/server/chunks/build/useDeviceTwins-Yko6nNPF.mjs.map +0 -1
  1839. package/.output/server/chunks/build/useOpsLog-CO_VWh7t.mjs +0 -137
  1840. package/.output/server/chunks/build/useOpsLog-CO_VWh7t.mjs.map +0 -1
  1841. package/.output/server/chunks/build/useWorkshopApi-NyjL0L3j.mjs +0 -90
  1842. package/.output/server/chunks/build/useWorkshopApi-NyjL0L3j.mjs.map +0 -1
  1843. package/.output/server/chunks/build/useWorkshopWs-C_I7q9As.mjs +0 -1319
  1844. package/.output/server/chunks/build/useWorkshopWs-C_I7q9As.mjs.map +0 -1
  1845. package/.output/server/chunks/build/users-Cfd5-l2-.mjs +0 -561
  1846. package/.output/server/chunks/build/users-Cfd5-l2-.mjs.map +0 -1
  1847. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs +0 -931
  1848. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +0 -1
  1849. package/.output/server/chunks/build/workspaces-Bgz5Ekwp.mjs +0 -139
  1850. package/.output/server/chunks/build/workspaces-Bgz5Ekwp.mjs.map +0 -1
  1851. package/scripts/test-agent-brain.ts +0 -135
  1852. package/scripts/test-game-session.ts +0 -160
  1853. package/scripts/test-protocol.ts +0 -156
  1854. package/scripts/verify-game.mjs +0 -477
  1855. package/server/api/workshop/ws.ts +0 -763
  1856. package/server/services/system-config.ts +0 -629
  1857. package/server/services/workshop/agents/adapters/omp-rpc-client.ts +0 -604
  1858. package/server/services/workshop/agents/codex-agent.ts +0 -743
  1859. package/server/services/workshop/agents/harness-terminal.ts +0 -625
  1860. package/server/services/workshop/agents/host-tool-bridge.ts +0 -714
  1861. package/server/services/workshop/agents/industrial-tools.ts +0 -1680
  1862. package/server/services/workshop/agents/omp-agent.ts +0 -1199
  1863. package/server/services/workshop/agents/opencode-agent.ts +0 -960
  1864. package/server/services/workshop/aml/job-orchestrator.ts +0 -639
  1865. package/server/services/workshop/daq/daq-controller.ts +0 -1284
  1866. package/server/services/workshop/daq/drivers.ts +0 -1196
  1867. package/server/services/workshop/db/database.ts +0 -1044
  1868. package/server/services/workshop/dcw/dcw-controller.ts +0 -1176
  1869. package/server/services/workshop/dcw/drivers.ts +0 -731
  1870. package/server/services/workshop/dcw/recipe-rollback-manager.ts +0 -713
  1871. package/server/services/workshop/runtime/agent-runtime.ts +0 -821
  1872. package/server/services/workshop/runtime/manager.ts +0 -3050
  1873. package/server/services/workshop/runtime/memory.ts +0 -681
  1874. package/server/services/workshop/runtime/scheduler-loop.ts +0 -799
  1875. package/server/services/workshop/runtime/task-engine.ts +0 -574
  1876. package/shared/dcw-protocol.ts +0 -747
  1877. /package/.output/public/_nuxt/{BrIJiJSN.js → BrIJiJSN2.js} +0 -0
  1878. /package/.output/public/_nuxt/{CNs_Ozdc2.js → CNs_Ozdc.js} +0 -0
  1879. /package/.output/public/_nuxt/{DIOBYJe3.js → DIOBYJe32.js} +0 -0
  1880. /package/.output/public/_nuxt/{lO5tpDZ5.js → lO5tpDZ52.js} +0 -0
@@ -15,773 +15,167 @@
15
15
  */
16
16
  import * as THREE from 'three'
17
17
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
18
- import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js'
19
- import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
20
- import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
21
- import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
22
- import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'
23
- import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js'
18
+ import type { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
19
+ import type { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
24
20
  import type { AepEnvelope } from '#shared/workshop-protocol'
25
- import { mapEnvelopeToIntent, type TownBubbleKind } from '#shared/town-protocol'
26
- import { parseActionFromEnvelope, stepToward, type ActionKind, type ActionContext } from '#shared/town-behavior'
21
+ import type { TownBubbleKind } from '#shared/town-protocol'
22
+ import type { ActionKind, ActionContext } from '#shared/town-behavior'
27
23
  // 纯几何/色彩/尺度决策层(与测试共用,零渲染依赖)
28
24
  import {
29
- AGENT_SPEED, ARRIVE, BUBBLE_Y, GROUND_Y, SNAP_SIZE, UNITS, WAIT_MS,
30
- WORLD_CX, WORLD_CZ, WORLD_H, WORLD_W,
31
- boundaryPoints, bubbleDisplayMs, channelColorNum, drainDisplayMs,
32
- clampToAgentRange, clampToBoundary, distToRangeBoundary,
33
- normLayout, pointInBoundary, toLocal,
25
+ WORLD_CX, WORLD_CZ,
34
26
  type AgentRangeLayout, type ChannelLayout,
35
27
  } from '#shared/town-scene-math'
36
28
 
37
- export type { AgentRangeLayout, ChannelLayout } from '#shared/town-scene-math'
38
-
39
- /** 场景 → Vue HUD 事件(TownEventMap 与 2D 同构) */
40
- export type TownEventMap = {
41
- ready: boolean
42
- fps: number
43
- agentCount: number
44
- blockCount: number
45
- lastActivity: { channelId: string, agentName: string, text: string, at?: number } | null
46
- behavior: { agentName: string, action: string, targetName: string | null } | null
47
- /** 选中 Agent/设备(供 Vue 弹缩放/旋转滑杆);null 取消选中 */
48
- select: { kind: 'agent' | 'device', id: string, scale: number, rotation: number } | null
49
- /** 选中频道(供 Vue 边界编辑面板);null 取消 */
50
- selectChannel: string | null
51
- /** 频道布局被拖拽/手柄调整(供 Vue 刷新边界面板草稿);null 取消 */
52
- channelResized: { channelId: string, layout: ChannelLayout } | null
53
- /** Agent 活动范围被框选绘制/拖移/手柄调整/清除(供 Vue 刷新对象面板草稿);null 取消 */
54
- agentRangeChanged: { agentId: string } | null
55
- /** 动画状态切换(数据驱动:Agent 在 idle/walk 之间切换时广播);null 表示无 */
56
- motion: { agentName: string, anim: 'idle' | 'walk', at: number } | null
57
- /** 场景保存状态(设备/角色布局持久化进度) */
58
- saveState: { state: 'idle' | 'dirty' | 'saving' | 'saved' | 'error', at: number } | null
59
- }
60
-
61
- /** 编辑 / 浏览模式:浏览只读(相机+点选),编辑可拖拽设备/调整角色落点 */
62
- export type TownScene3DMode = 'browse' | 'edit'
63
-
64
- /** 设备 transform 补丁(拖拽/滑杆结束防抖保存) */
65
- export interface DeviceTransformPatch {
66
- posX?: number
67
- posZ?: number
68
- rotationY?: number
69
- scale?: number
70
- }
71
-
72
- /** syncDevices 输入:与 useDeviceTwins.DeviceTwinView 同构的子集 */
73
- export interface DeviceTwinSync {
74
- id: string
75
- name: string
76
- modelRef: string
77
- /** 实体类别:'device' 常规设备(GLB) / 'daq' 数采节点(程序化网格);后端为宽 string */
78
- kind?: string
79
- state?: 'idle' | 'running' | 'offline' | 'alarm'
80
- telemetry?: Record<string, number | string | boolean>
81
- posX?: number
82
- posZ?: number
83
- rotationY?: number
84
- scale?: number
85
- /** 所属产线(场景光晕分色依据) */
86
- lineId?: string
87
- /** 产线光晕色(Hex;同产线节点同色光环) */
88
- lineColor?: string
89
- }
90
-
91
- /** 场景内可缩放目标(Agent 或设备节点) */
92
- interface ScaledTarget {
93
- kind: 'agent' | 'device'
94
- id: string
95
- /** 用户缩放倍率(1 = 默认归一化尺寸) */
96
- userScale: number
97
- /** 模型子节点(缩放施加于此) */
98
- holder: THREE.Group
99
- }
29
+ // 轨道相机与相机交互自本文件抽出(见 scene/town-scene3d-camera.ts)
30
+ import {
31
+ autoFrameTo, flyToPreset, focusTo, getAutoOrbit, getCameraPose, orbitBy, panBy, panByScreen,
32
+ panWorldBy, resetView, screenToWorld, setAutoOrbit, setViewPreset, worldToScreen, zoomBy,
33
+ } from './scene/town-scene3d-camera'
34
+ // 渲染核心初始化自本文件抽出(见 scene/town-scene3d-renderer.ts)
35
+ import { initRendererCore } from './scene/town-scene3d-renderer'
36
+ // 类型契约 / 面向对象节点(Channel 领地 / 角色 / 数字孪生设备)自本文件抽出,
37
+ // 只通过宿主契约回调场景 —— 细节见 town-scene3d-types.ts 与 town-scene3d-nodes.ts
38
+ // 信息接收器 + 聊天气泡自本文件抽出(见 scene/town-scene3d-bubbles.ts)
39
+ import { drainReceivers, enqueueBubble } from './scene/town-scene3d-bubbles'
40
+ // 数采绑定链路 + 薄膜 web 自本文件抽出(见 scene/town-scene3d-links.ts)
41
+ import { deviceTopY, rebuildFilmWeb, refreshDaqLinks, syncDaqLinks } from './scene/town-scene3d-links'
42
+ // 事件共鸣 + 行为 FSM 入口 + WS 事件入口自本文件抽出(见 scene/town-scene3d-events.ts)
43
+ import { behaviorActionLabel, emitResonance, handleTownEvent, startBehavior } from './scene/town-scene3d-events'
44
+ // 调试 / HUD 数据面自本文件抽出(见 scene/town-scene3d-hud.ts)
45
+ import { getDebugState, getMinimapState, getRecentActivity } from './scene/town-scene3d-hud'
46
+ // 数字孪生设备节点层自本文件抽出(见 scene/town-scene3d-devices.ts)
47
+ import {
48
+ getDeviceModelRef, getDeviceName, getDeviceNodes, persistAllDevices, persistDeviceTransform,
49
+ recreateDeviceNode, removeDevice, renameDevice, renameDeviceSprite, spawnDeviceNode,
50
+ swapDeviceModel, swapDeviceModelSprite, syncDevices, updateDeviceNode,
51
+ } from './scene/town-scene3d-devices'
52
+ // GLB 加载与 PBR 材质增强自本文件抽出(见 scene/town-scene3d-models.ts)
53
+ import { enhancePbrMaterials } from './scene/town-scene3d-models'
54
+ // 角色节点层 + 模型库挂载自本文件抽出(见 scene/town-scene3d-agents.ts)
55
+ import {
56
+ dropModelOnWorld, ensureAgent, getAgentClips, getAgentModel, getAgentName,
57
+ registerModelsFromList, setAnimPref, swapAgentModel,
58
+ } from './scene/town-scene3d-agents'
59
+ // 频道领地放置层自本文件抽出(见 scene/town-scene3d-blocks.ts)
60
+ import {
61
+ applyLayoutToBlock, applySceneLayouts, blockLayout, buildBlocks, dropChannelOnWorld,
62
+ getAllChannelLayouts, getCameraTarget, getChannelLayout, hasChannel, placedChannels,
63
+ removeChannel, trackLayout, updateChannelLayout,
64
+ } from './scene/town-scene3d-blocks'
65
+ // 渲染循环 + 画质阶梯自本文件抽出(见 scene/town-scene3d-loop.ts)
66
+ import { adaptQuality, applyPixelRatio, applyQuality, setQualityMode, startRenderLoop } from './scene/town-scene3d-loop'
67
+ // 拾取 / 选中 / 缩放旋转自本文件抽出(见 scene/town-scene3d-selection.ts)
68
+ import {
69
+ getModelRotation, getSelectedChannel, getSelectedScale, isGizmoBusy, persistScale, pickAt,
70
+ pickBeacon, pickChannel, pickChannelEdge, registerScalable, selectChannel, setExposure,
71
+ setModelRotation, setModelScale, setSelected, setTerritoryOpacity, setTransformMode,
72
+ } from './scene/town-scene3d-selection'
73
+ // 实例资源释放与整场重置自本文件抽出(见 scene/town-scene3d-lifecycle.ts)
74
+ import {
75
+ dispose as disposeScene, disposeAgentAssets, disposeCanvasTextures, disposeDeviceAssets,
76
+ resetAll as resetScene,
77
+ } from './scene/town-scene3d-lifecycle'
78
+ // 编辑/浏览模式 + 场景对象拖拽(设备/角色/频道/边界手柄)自本文件抽出(见 scene/town-scene3d-editing.ts)
79
+ import {
80
+ applyBlockMove, applyResize, boundaryHandlePoints, clampAgentsToBoundary, endPointerDrag, getMode,
81
+ getSnap, isPointerDragging, movePointerDrag, pickResizeHandle, refreshChannelHandles, setMode,
82
+ setSnap, snapWorld, tryStartPointerDrag,
83
+ } from './scene/town-scene3d-editing'
84
+ // Agent 独立活动范围(框选绘制/整框平移/手柄缩放/面板读写)自本文件抽出(见 scene/town-scene3d-ranges.ts)
85
+ import {
86
+ applyAgentRangeResize, cancelRangeDraw, clearAgentRange, commitAgentRange, emitAgentRangeChanged,
87
+ getAgentRange, hitAgentRangeBoundary, isRangeDrawing, pickAgentRangeHandle, rangeLineVisible,
88
+ rangeLineVisibleFor, refreshAgentRangeHandles, refreshAgentRangeLines, setAgentRangeScene,
89
+ startRangeDraw, updateRangeDrawPreview,
90
+ } from './scene/town-scene3d-ranges'
91
+ import type { Agent3D, Block3D, DeviceNode } from './scene/town-scene3d-nodes'
92
+ import type {
93
+ AgentHost, AgentRangeHandle, BlockHost, ChannelMessageReceiver, ChannelResizeHandle, DaqLink,
94
+ DeviceApi, DeviceHost, DeviceTwinSync, ModelInfo, PointerDragState, ScaledTarget,
95
+ SelectedTarget, TownEntityInput, TownEventMap, TownScene3DMode, TownSceneHost,
96
+ } from './scene/town-scene3d-types'
100
97
 
101
- /** 与 2D TownEntityInput 同构的实体基线 */
102
- export interface TownEntityInput {
103
- channelId: string
104
- channelName: string
105
- agents: Array<{
106
- agentId: string
107
- name: string
108
- role: 'lead' | 'worker'
109
- harness: string
110
- state: 'idle' | 'busy' | 'stopped'
111
- currentTaskId?: string | null
112
- currentTaskTitle?: string | null
113
- currentTaskProgress?: number | null
114
- modelRef?: string | null
115
- /** 管理员布局落点(来自 config.homeX/homeZ;缺省 = 领地环形排布) */
116
- homeX?: number | null
117
- homeZ?: number | null
118
- /** 管理员布局活动范围(来自 config.range;缺省 = 沿用频道边界) */
119
- range?: AgentRangeLayout | null
120
- }>
121
- }
98
+ export type { AgentRangeLayout, ChannelLayout } from '#shared/town-scene-math'
99
+ export {
100
+ Block3D, Agent3D, DeviceNode, makeBoundary,
101
+ } from './scene/town-scene3d-nodes'
102
+ export type {
103
+ BehaviorState, BubbleMsg, ChannelMessageReceiver, DeviceTransformPatch,
104
+ DeviceTwinSync, ModelInfo, ScaledTarget, TownEntityInput, TownEventMap, TownScene3DMode,
105
+ } from './scene/town-scene3d-types'
122
106
 
123
107
  /** 频道布局(3D 小镇放置):与共享 AepSceneLayout/useSceneLayouts 同构 —— 定义见 #shared/town-scene-math */
124
108
 
125
109
  /** Agent 独立活动范围(编辑模式框选绘制/手柄调整;经 config.range 持久化)。
126
110
  * 缺省(null)= 未设置,该 Agent 沿用频道边界活动。 —— 定义见 #shared/town-scene-math */
127
111
 
128
- /** 频道领地 3D 实例(面向对象:持有布局/网格/成员,封装移动/边界/成员钳制等行为)。
129
- * 数据来自 scene-layouts 持久化,由场景按数据库元数据实例化,并注入宿主控制器。 */
130
- class Block3D {
131
- channelId!: string
132
- name!: string
133
- x!: number
134
- z!: number
135
- radiusX!: number
136
- radiusZ!: number
137
- shape!: 'ellipse' | 'rect'
138
- rotationY!: number
139
- color!: number
140
- platform!: THREE.Mesh
141
- /** 领地边缘发光环(工业孪生边界告示;随平台移动) */
142
- padRing!: THREE.Mesh
143
- /** 中央信标(HMI 定位销:点击 → 定位频道中心 + 唤醒边界编辑) */
144
- beacon!: THREE.Group
145
- /** 活动边界(编辑高亮) */
146
- boundary!: THREE.LineLoop
147
- label!: THREE.Sprite
148
- /** 本频道的 Agent3D 成员实例(面向对象聚合) */
149
- members: Agent3D[] = []
150
- /** 宿主场景控制器(实例化后注入;提供跨实体服务) */
151
- host!: TownScene3D
152
-
153
- constructor(init: {
154
- channelId: string
155
- name: string
156
- x: number
157
- z: number
158
- radiusX: number
159
- radiusZ: number
160
- shape: 'ellipse' | 'rect'
161
- rotationY: number
162
- color: number
163
- platform: THREE.Mesh
164
- padRing: THREE.Mesh
165
- beacon: THREE.Group
166
- boundary: THREE.LineLoop
167
- label: THREE.Sprite
168
- }) {
169
- Object.assign(this, init)
170
- }
171
-
172
- /** 当前布局(与共享 ChannelLayout 同构;供边界面板/落库/E2E) */
173
- layout(): ChannelLayout {
174
- return { channelId: this.channelId, x: this.x, z: this.z, radiusX: this.radiusX, radiusZ: this.radiusZ, shape: this.shape, rotationY: this.rotationY }
175
- }
176
-
177
- /** 归一化布局(radius 钳制下限) */
178
- normLayout(): ChannelLayout {
179
- return normLayout(this.layout())
180
- }
181
-
182
- /** 边界手柄本地坐标(矩形四角 / 椭圆轴向四点;radius 是半轴或半宽)。 */
183
- handlePoints(): Array<[number, number]> {
184
- if (this.shape === 'rect') return [[this.radiusX, this.radiusZ], [-this.radiusX, this.radiusZ], [-this.radiusX, -this.radiusZ], [this.radiusX, -this.radiusZ]]
185
- return [[this.radiusX, 0], [-this.radiusX, 0], [0, this.radiusZ], [0, -this.radiusZ]]
186
- }
187
-
188
- /** 世界点是否落在领地边界内(点选/命中判定) */
189
- pointIn(x: number, z: number): boolean {
190
- return pointInBoundary(this.layout(), x, z)
191
- }
192
-
193
- /** 整体平移:更新中心、平台/边界/名牌网格,并携带全部成员落点与各自活动范围(行为内聚) */
194
- moveBy(dx: number, dz: number): void {
195
- this.x += dx
196
- this.z += dz
197
- this.platform.position.set(this.x, 0.16, this.z)
198
- this.padRing.position.set(this.x, 0.32, this.z)
199
- this.beacon.position.set(this.x, 0, this.z)
200
- this.boundary.position.set(this.x, 0.3, this.z)
201
- this.boundary.rotation.y = this.rotationY * Math.PI / 180
202
- this.label.position.set(this.x, 30, this.z)
203
- for (const a of this.members) {
204
- a.root.position.x += dx
205
- a.root.position.z += dz
206
- a.homeX += dx
207
- a.homeZ += dz
208
- if (a.range) {
209
- a.range.x += dx
210
- a.range.z += dz
211
- a.renderRangeLine()
212
- }
213
- }
214
- this.host.trackLayout(this)
215
- }
216
-
217
- /** 按补丁更新布局字段(边界编辑;钳制下限由宿主统一保证) */
218
- applyPatch(patch: Partial<ChannelLayout>): void {
219
- if (patch.x !== undefined) this.x = patch.x
220
- if (patch.z !== undefined) this.z = patch.z
221
- if (patch.radiusX !== undefined) this.radiusX = patch.radiusX
222
- if (patch.radiusZ !== undefined) this.radiusZ = patch.radiusZ
223
- if (patch.shape !== undefined) this.shape = patch.shape
224
- if (patch.rotationY !== undefined) this.rotationY = patch.rotationY
225
- }
226
-
227
- /** 把成员落点与各自活动范围收进当前频道边界(边界缩放/整体移动后调用) */
228
- clampMembersAndRanges(): void {
229
- const layout = this.layout()
230
- for (const a of this.members) {
231
- const c = clampToBoundary(layout, a.root.position.x, a.root.position.z, 16)
232
- a.root.position.x = c.x
233
- a.root.position.z = c.z
234
- a.homeX = c.x
235
- a.homeZ = c.z
236
- if (a.range) {
237
- a.renderRangeLine()
238
- const cc = clampToAgentRange(a.range, a.homeX, a.homeZ, 0)
239
- if (cc.x !== a.homeX || cc.z !== a.homeZ) {
240
- a.homeX = cc.x
241
- a.homeZ = cc.z
242
- a.root.position.x = cc.x
243
- a.root.position.z = cc.z
244
- }
245
- }
246
- }
247
- this.host.markDirty()
248
- }
249
- }
250
-
251
- /** 边界几何体:椭圆/矩形线框,弯折朝向 rotationY(度)。
252
- * 轮廓点来自 #shared/town-scene-math.boundaryPoints(纯几何,场景只做渲染)。 */
253
- function makeBoundary(shape: 'ellipse' | 'rect', rx: number, rz: number, color: number): THREE.LineLoop {
254
- const pts = boundaryPoints(shape, rx, rz, 48).map(([x, z]) => new THREE.Vector3(x, 0, z))
255
- const geo = new THREE.BufferGeometry().setFromPoints(pts)
256
- const line = new THREE.LineLoop(geo, new THREE.LineBasicMaterial({ color, transparent: true, opacity: 0.75, depthTest: false }))
257
- return line
258
- }
259
-
260
- /** 归一化边界(radius 钳制下限;rect 用半宽) —— 定义见 #shared/town-scene-math */
261
-
262
- /** 把点钳制到频道边界内 —— 定义见 #shared/town-scene-math */
263
-
264
- /** 点到边界内判定 —— 定义见 #shared/town-scene-math */
265
-
266
- /** 边界/范围的极值点(矩形四角 / 椭圆轴向四点;世界坐标,含 rotationY) —— 定义见 #shared/town-scene-math */
267
-
268
- /** 把 Agent 活动范围整体收进频道边界 —— 定义见 #shared/town-scene-math */
269
-
270
- /** 把点钳制到 Agent 自己活动范围内 —— 定义见 #shared/town-scene-math */
271
-
272
- /** 世界点到活动范围边界线的最近距离 —— 定义见 #shared/town-scene-math */
273
-
274
- /** 角色 3D 实例(面向对象:持有模型/状态/行为,封装漫游 FSM、移动钳制、动画、活动范围)。
275
- * 由场景按 entities/数据库元数据(home/range/modelRef)实例化并注入宿主;动画状态驱动 motion 事件。 */
276
- class Agent3D {
277
- channelId!: string
278
- agentId!: string
279
- name!: string
280
- role!: 'lead' | 'worker'
281
- /** 根 Group(位置=落地点,脚底) */
282
- root!: THREE.Group
283
- /** 模型子节点(换模型时替换) —— 始终存在(空组占位) */
284
- model!: THREE.Group
285
- mixer!: THREE.AnimationMixer | null
286
- /** 当前模型动画 clip(若有) */
287
- clips!: THREE.AnimationClip[]
288
- /** 身份色(频道哈希色;气泡/小地图等取色数据源) */
289
- colorNum!: number
290
- /** 头顶名字 Sprite */
291
- nameSprite!: THREE.Sprite
292
- /** 当前气泡 */
293
- bubble!: THREE.Sprite | null
294
- /** 当前头顶聊天气泡文本(调试/HUD 用;null = 无气泡) */
295
- bubbleText!: string | null
296
- bubbleTimer!: ReturnType<typeof setTimeout> | null
297
- /** 状态/进度/行为 */
298
- state!: 'idle' | 'busy' | 'stopped'
299
- progress!: number | null
300
- /** 行为 FSM */
301
- behavior!: BehaviorState
302
- /** 用户拖动中 */
303
- dragging!: boolean
304
- /** home(行为结束后回归) */
305
- homeX!: number
306
- homeZ!: number
307
- /** 独立活动范围(编辑模式框选/手柄;null = 沿用频道边界) */
308
- range!: AgentRangeLayout | null
309
- /** 活动范围线框(随 range 渲染;清除/重建时移除) */
310
- rangeLine!: THREE.LineLoop | null
311
- textureKey!: string
312
- modelRef!: string
313
- /** 当前动画状态(数据驱动模型:idle/walk;经宿主 motion 事件监听) */
314
- animState: 'idle' | 'walk' = 'idle'
315
- /** 当前 mixer action(crossfade 切换用;null = 未开始) */
316
- activeAction: THREE.AnimationAction | null = null
317
- /** 宿主场景控制器(实例化后注入) */
318
- host!: TownScene3D
319
- /** 本帧是否在移动(update 结束时驱动动画) */
320
- private moving = false
321
- /** 脚下光环组(频道色身份标识;root 子节点,随移动跟随,零每帧定位成本) */
322
- aura: THREE.Group | null = null
323
- /** 光环材质(渲染循环呼吸;userData.baseOp 存基准透明度) */
324
- auraMats: THREE.MeshBasicMaterial[] = []
325
- /** 呼吸相位(每实例随机,避免全场同频闪烁) */
326
- auraPhase = 0
327
-
328
- /** 挂脚下光环(频道色,与领地同源;lead 加外环 —— 角色身份一眼可辨) */
329
- attachAura(color: number, role: 'lead' | 'worker'): void {
330
- const g = new THREE.Group()
331
- const mats: THREE.MeshBasicMaterial[] = []
332
- const mk = (r0: number, r1: number, y: number, op: number): void => {
333
- const m = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: op, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending })
334
- m.userData.baseOp = op
335
- mats.push(m)
336
- const mesh = new THREE.Mesh(new THREE.RingGeometry(r0, r1, 44), m)
337
- mesh.rotation.x = -Math.PI / 2
338
- mesh.position.y = y
339
- g.add(mesh)
340
- }
341
- mk(34, 40, 0.36, 0.26)
342
- if (role === 'lead') mk(46, 50, 0.42, 0.2)
343
- this.root.add(g)
344
- this.aura = g
345
- this.auraMats = mats
346
- this.auraPhase = Math.random() * Math.PI * 2
347
- }
348
-
349
- constructor(init: {
350
- channelId: string
351
- agentId: string
352
- name: string
353
- role: 'lead' | 'worker'
354
- root: THREE.Group
355
- model: THREE.Group
356
- mixer: THREE.AnimationMixer | null
357
- clips: THREE.AnimationClip[]
358
- colorNum: number
359
- nameSprite: THREE.Sprite
360
- bubble: THREE.Sprite | null
361
- bubbleText: string | null
362
- bubbleTimer: ReturnType<typeof setTimeout> | null
363
- state: 'idle' | 'busy' | 'stopped'
364
- progress: number | null
365
- behavior: BehaviorState
366
- dragging: boolean
367
- homeX: number
368
- homeZ: number
369
- range: AgentRangeLayout | null
370
- rangeLine: THREE.LineLoop | null
371
- textureKey: string
372
- modelRef: string
373
- }) {
374
- Object.assign(this, init)
375
- }
376
-
377
- /** 到达判定 */
378
- reached(target: { x: number, z: number }): boolean {
379
- return Math.hypot(this.root.position.x - target.x, this.root.position.z - target.z) <= ARRIVE
380
- }
381
-
382
- /** 朝目标匀速走一步(按自身活动范围/频道边界钳制;驱动朝向与动画) */
383
- driveToward(target: { x: number, z: number }, speed: number, dt: number): void {
384
- this.moving = true
385
- const cur = { x: this.root.position.x, z: this.root.position.z }
386
- const next = stepToward({ x: cur.x, y: cur.z }, { x: target.x, y: target.z }, speed, dt)
387
- let nx = next.x
388
- let nz = next.y
389
- if (this.range) {
390
- const clamped = clampToAgentRange(this.range, nx, nz, 6)
391
- nx = clamped.x
392
- nz = clamped.z
393
- }
394
- else {
395
- const layout = this.host.blockLayoutOf(this.channelId)
396
- if (layout) {
397
- const clamped = clampToBoundary(layout, nx, nz, 6)
398
- nx = clamped.x
399
- nz = clamped.z
400
- }
401
- }
402
- this.root.position.x = nx
403
- this.root.position.z = nz
404
- // 朝向平滑:朝移动方向插值转动(左/右两态,不用瞬间 snap,2.5D 行走更顺)
405
- const dir = next.dir
406
- const targetY = dir === 'left' ? Math.PI : 0
407
- const curY = this.root.rotation.y
408
- // 最短角差插值(0 ↔ π 之间取捷径;步长限速防抖)
409
- let d = targetY - curY
410
- if (d > Math.PI) d -= Math.PI * 2
411
- else if (d < -Math.PI) d += Math.PI * 2
412
- const maxStep = dt * 3.2
413
- this.root.rotation.y = curY + Math.max(-maxStep, Math.min(maxStep, d))
414
- void dt
415
- }
416
-
417
- /** 行为 FSM(idle/roam/approach/wait/returnHome);由渲染循环每帧驱动,动画随移动状态切换 */
418
- update(dt: number): void {
419
- this.moving = false
420
- try {
421
- this.updateBehavior(dt)
422
- }
423
- finally {
424
- this.playWalkAnim(this.moving)
425
- }
426
- }
427
-
428
- private updateBehavior(dt: number): void {
429
- const b = this.behavior
430
- if (b.mode === 'idle' || b.mode === 'roam') {
431
- if (b.engaged || this.dragging) return
432
- b.mode = 'roam'
433
- if (!b.roamTarget) {
434
- // 到达后就地停顿片刻:保持 idle 动画,营造走走停停的闲逛节奏(并产生 motion 事件)
435
- if (performance.now() < b.pauseUntil) return
436
- // 漫游目标:有独立活动范围 → 在自身范围内取点;否则沿用频道边界内取点
437
- const layout = this.host.blockLayoutOf(this.channelId)
438
- let tx: number
439
- let tz: number
440
- if (this.range) {
441
- tx = this.homeX + (Math.random() * 2 - 1) * this.range.radiusX * 0.9
442
- tz = this.homeZ + (Math.random() * 2 - 1) * this.range.radiusZ * 0.9
443
- const clamped = clampToAgentRange(this.range, tx, tz, 8)
444
- tx = clamped.x
445
- tz = clamped.z
446
- }
447
- else {
448
- const range = layout ? Math.min(layout.radiusX, layout.radiusZ) * 0.45 : 80
449
- tx = this.homeX + (Math.random() * 2 - 1) * range
450
- tz = this.homeZ + (Math.random() * 2 - 1) * range * 0.6
451
- if (layout) {
452
- const clamped = clampToBoundary(layout, tx, tz, 16)
453
- tx = clamped.x
454
- tz = clamped.z
455
- }
456
- }
457
- b.roamTarget = { x: tx, z: tz }
458
- }
459
- this.driveToward(b.roamTarget, AGENT_SPEED * 0.5, dt)
460
- if (this.reached(b.roamTarget)) {
461
- b.roamTarget = null
462
- b.pauseUntil = performance.now() + 350 + Math.random() * 900
463
- }
464
- return
465
- }
466
- if (b.mode === 'approach') {
467
- const target = b.targetId ? this.host.getAgent(b.targetId) : undefined
468
- if (!target) {
469
- b.mode = 'idle'
470
- return
471
- }
472
- let pos = { x: target.root.position.x, z: target.root.position.z }
473
- // 目标在自身活动范围外 → 逼近到自身范围边界(送达交接在边界处完成)
474
- if (this.range) {
475
- const c = clampToAgentRange(this.range, pos.x, pos.z, 8)
476
- pos = { x: c.x, z: c.z }
477
- }
478
- this.driveToward(pos, AGENT_SPEED, dt)
479
- if (this.reached(pos)) {
480
- this.host.deliverBehavior(this, target)
481
- if (b.action?.requireReply) {
482
- b.mode = 'wait'
483
- b.waitUntil = performance.now() + WAIT_MS
484
- }
485
- else {
486
- b.mode = 'returnHome'
487
- this.host.releaseEngaged(this)
488
- b.targetId = null
489
- }
490
- }
491
- return
492
- }
493
- if (b.mode === 'wait') {
494
- if (this.dragging) return
495
- const target = b.targetId ? this.host.getAgent(b.targetId) : undefined
496
- if (target && !target.dragging) {
497
- const stand = { x: target.root.position.x + 28, z: target.root.position.z + 8 }
498
- this.driveToward(stand, AGENT_SPEED * 0.6, dt)
499
- }
500
- if (performance.now() >= b.waitUntil) {
501
- b.mode = 'returnHome'
502
- this.host.releaseEngaged(this)
503
- }
504
- return
505
- }
506
- if (b.mode === 'returnHome') {
507
- if (this.dragging) return
508
- const home = { x: this.homeX, z: this.homeZ }
509
- this.driveToward(home, AGENT_SPEED * 0.7, dt)
510
- if (this.reached(home)) {
511
- this.root.position.set(this.homeX, GROUND_Y, this.homeZ)
512
- b.mode = 'idle'
513
- b.targetId = null
514
- b.action = undefined
515
- }
516
- }
517
- }
518
-
519
- /** 动画状态切换(有 clip: idle/walk 动作;无 clip: 上下浮动 bob);切换时经宿主广播 motion 事件 */
520
- playWalkAnim(moving: boolean): void {
521
- const next = moving ? 'walk' : 'idle'
522
- if (this.animState !== next) {
523
- this.animState = next
524
- this.host.notifyMotion(this)
525
- }
526
- const bob = () => {
527
- // 程序化动作绑定(model 局部,色环/名牌保持贴地稳定):
528
- // 待机呼吸浮动 + 行走跳跃颠簸 + 行走左右微摆
529
- const t = performance.now()
530
- const breathe = 1.2 + Math.sin(t * 0.0016) * 1.6
531
- const hop = Math.abs(Math.sin(t * 0.005)) * 6
532
- this.model.position.y = moving ? hop : breathe
533
- const swayTarget = moving ? Math.sin(t * 0.006) * 0.07 : 0
534
- this.model.rotation.z += (swayTarget - this.model.rotation.z) * 0.12
535
- }
536
- if (this.clips.length === 0) {
537
- bob()
538
- return
539
- }
540
- // 有动画 clip:名字感知选择(剪辑命名各异:WALK/Walk、idle/stand/breath...),
541
- // 先按语义匹配,找不到回退索引(idle→0,walk→1);仅含走路剪辑的模型待机时
542
- // 回退程序化 bob,避免原地播放走路。crossfade 平滑过渡(避免双 action 叠加)。
543
- if (!this.mixer) return
544
- const lower = (c: THREE.AnimationClip) => c.name.toLowerCase()
545
- const clip = moving
546
- ? (this.clips.find(c => /walk|run|move/.test(lower(c))) ?? this.clips[Math.min(1, this.clips.length - 1)])
547
- : (this.clips.find(c => /idle|stand|breath/.test(lower(c))) ?? (this.clips.length > 1 ? this.clips[0] : null))
548
- if (!clip) {
549
- if (this.activeAction) {
550
- this.activeAction.fadeOut(0.15)
551
- this.activeAction = null
552
- }
553
- bob()
554
- return
555
- }
556
- const nextAction = this.mixer.clipAction(clip)
557
- if (this.activeAction === nextAction) {
558
- nextAction.timeScale = moving ? 1.3 : 0.9
559
- return
560
- }
561
- nextAction.reset().setEffectiveTimeScale(moving ? 1.3 : 0.9).fadeIn(0.15).play()
562
- if (this.activeAction) this.activeAction.fadeOut(0.15)
563
- this.activeAction = nextAction
564
- }
565
-
566
- /** 更新 home 落点(行为结束后回归点;含拖拽/范围位移后落库路径) */
567
- setHome(x: number, z: number): void {
568
- this.homeX = x
569
- this.homeZ = z
570
- }
571
-
572
- /** 渲染/刷新活动范围线框(数据驱动:range 变更 → 线框重建)+ 可见性 */
573
- renderRangeLine(): void {
574
- if (this.rangeLine) {
575
- this.host.threeScene.remove(this.rangeLine)
576
- this.rangeLine = null
577
- }
578
- if (!this.range) return
579
- const color = 0x41c8f4
580
- const line = makeBoundary(this.range.shape, this.range.radiusX, this.range.radiusZ, color)
581
- // 虚线描边(与频道实线边界区分;2.5D 可读性)
582
- line.material = new THREE.LineDashedMaterial({ color, transparent: true, opacity: 0.75, depthTest: false, dashSize: 14, gapSize: 9 })
583
- line.computeLineDistances()
584
- line.position.set(this.range.x, 0.5, this.range.z)
585
- line.rotation.y = this.range.rotationY * Math.PI / 180
586
- line.visible = this.host.rangeLineVisibleFor(this)
587
- this.host.threeScene.add(line)
588
- this.rangeLine = line
589
- this.host.markDirty()
590
- }
591
-
592
- /** 挂气泡(替换旧气泡;宿主完成纹理渲染) */
593
- attachBubble(sprite: THREE.Sprite, text: string): void {
594
- if (this.bubble) {
595
- this.host.threeScene.remove(this.bubble)
596
- this.bubbleText = null
597
- }
598
- this.bubble = sprite
599
- this.bubbleText = text
600
- }
601
-
602
- /** 移除气泡(宿主负责从场景摘除) */
603
- clearBubble(): void {
604
- if (!this.bubble) return
605
- this.host.threeScene.remove(this.bubble)
606
- this.bubble = null
607
- this.bubbleText = null
608
- }
609
- }
610
-
611
- type BehaviorMode
612
- = 'idle'
613
- | 'roam'
614
- | 'approach'
615
- | 'wait'
616
- | 'returnHome'
617
-
618
- /** 数字孪生设备 3D 实例(面向对象:持有节点网格/状态/遥测,封装讑生同步、状态环、模型重挂、transform 记忆)。
619
- * 由场景按 device-twins 数据库元数据实例化;state/telemetry 数据驱动渲染。 */
620
- class DeviceNode {
621
- twinId!: string
622
- name!: string
623
- modelRef!: string
624
- root!: THREE.Group
625
- /** 模型挂载组(换模型时 clear 重挂;缩放施加于此) */
626
- holder!: THREE.Group
627
- ring!: THREE.Mesh
628
- /** 运行态动效弧(仅 running 可见;渲染循环缓转 —— 语义动效:设备在转 = 在产) */
629
- arc: THREE.Mesh | null = null
630
- label!: THREE.Sprite
631
- state!: 'idle' | 'running' | 'offline' | 'alarm'
632
- telemetry!: Record<string, number | string | boolean>
633
- /** 宿主场景控制器(实例化后注入) */
634
- host!: TownScene3D
635
-
636
- constructor(init: {
637
- twinId: string
638
- name: string
639
- modelRef: string
640
- root: THREE.Group
641
- holder: THREE.Group
642
- ring: THREE.Mesh
643
- label: THREE.Sprite
644
- state: 'idle' | 'running' | 'offline' | 'alarm'
645
- telemetry: Record<string, number | string | boolean>
646
- }) {
647
- Object.assign(this, init)
648
- }
649
-
650
- /** 模型顶面世界高度缓存(HUD/callout 每帧读取;缩放变更时失效) */
651
- topYCache: number | null = null
652
- /** 当前产线光晕色(空 = 未分配) */
653
- lineColor = ''
654
- /** 产线换色回调(recreateDaqNode 装配;applyTwin 检测变化时重 tint) */
655
- applyLine: ((color: string) => void) | null = null
656
-
657
- /** 状态环颜色(数据驱动:状态变化/产线换色时调用,渲染循环不再每帧重刷)。
658
- * 分配了产线的节点(数采)环随产线色 —— 状态生命感已由 LED 呼吸环表达;常规设备环 = 状态色。 */
659
- updateRing(): void {
660
- const mat = this.ring.material as THREE.MeshBasicMaterial
661
- if (this.lineColor) mat.color.set(this.lineColor)
662
- else mat.color.setHex(this.state === 'alarm' ? 0xff6b6b : this.state === 'offline' ? 0x8496a5 : this.state === 'running' ? 0x35e0a0 : 0xf6c453)
663
- if (this.arc) this.arc.visible = this.state === 'running'
664
- }
665
-
666
- /** 与数据库讑生记录收敛(状态/遥测/名称/模型/产线光晕;宿主完成名牌与模型重挂) */
667
- applyTwin(t: DeviceTwinSync): void {
668
- if (t.state) this.state = t.state
669
- if (t.telemetry) this.telemetry = { ...this.telemetry, ...t.telemetry }
670
- if (t.name && t.name !== this.name) this.host.renameDeviceSprite(this, t.name)
671
- if (t.modelRef && t.modelRef !== this.modelRef) this.host.swapDeviceModelSprite(this, t.modelRef)
672
- if ((t.lineColor ?? '') !== this.lineColor) {
673
- this.lineColor = t.lineColor ?? ''
674
- this.applyLine?.(this.lineColor)
675
- }
676
- this.updateRing()
677
- this.host.markDirty()
678
- }
679
-
680
- /** 记录当前 transform(供防抖落库快照) */
681
- rememberTransform(): { posX: number, posZ: number, rotationY: number } {
682
- return {
683
- posX: Math.round(this.root.position.x * 10) / 10,
684
- posZ: Math.round(this.root.position.z * 10) / 10,
685
- rotationY: Math.round(THREE.MathUtils.radToDeg(this.root.rotation.y) * 10) / 10,
686
- }
687
- }
688
- }
689
-
690
- interface BehaviorState {
691
- mode: BehaviorMode
692
- roamTarget: { x: number, z: number } | null
693
- targetId: string | null
694
- waitUntil: number
695
- /** 漫游到达后就地停顿至该时刻(idle 动画;营造走走停停的闲逛节奏,驱动 motion 事件) */
696
- pauseUntil: number
697
- action?: { kind: ActionKind, taskKind?: string, requireReply: boolean, text: string }
698
- engaged: boolean
699
- }
700
-
701
- /** 信息接收器队列中的一条消息(按频道 FIFO 逐条消费) */
702
- interface BubbleMsg {
703
- /** 说话 Agent(null = 频道级/系统气泡) */
704
- agentId: string | null
705
- kind: TownBubbleKind
706
- text: string
707
- ttlMs: number
708
- }
709
-
710
- /** 每个实例化 Channel 的信息接收器:WS 实时信息经 handleTownEvent 入队,FIFO 逐条消费渲染到对应 Agent 头顶 */
711
- interface ChannelMessageReceiver {
712
- channelId: string
713
- queue: BubbleMsg[]
714
- current: BubbleMsg | null
715
- currentUntil: number
716
- }
112
+ // 频道领地 / 角色 / 数字孪生设备节点与类型契约见 ./scene/town-scene3d-nodes.ts 与 ./scene/town-scene3d-types.ts
717
113
 
718
114
  /** 世界尺度/速度/身份色等纯常量与函数集中定义于 #shared/town-scene-math(与 2D/测试共用) */
719
115
 
720
- /** 角色模型来源登记(registerModelsFromList) */
721
- interface ModelInfo { id: string, file: string, name: string, kind?: string, hFactor?: number }
116
+ export class TownScene3D implements TownSceneHost {
117
+ // ================================================================
118
+ // 宿主契约面:下列成员被 scene/*.ts 抽出模块以「窄接口」直接读写
119
+ // (每个模块只声明它真正触达的项,见各模块顶部的 XxxHost 接口)。
120
+ // 为「方法簇 → 模块」的结构性拆分而公开,不改变任何运行时语义。
121
+ // 其中 autoFrameTo / pickChannelEdge / boundaryHandlePoints 由 private 放宽为 public:
122
+ // 原调用点全在本类内部(已随簇迁出),editing / ranges 模块经宿主契约回调,纯属可见性放宽。
123
+ // ================================================================
722
124
 
723
- export class TownScene3D {
724
- private renderer!: THREE.WebGLRenderer
125
+ renderer!: THREE.WebGLRenderer
725
126
  /** 后处理管线:Render → Bloom(夜航辉光)→ Output(tone mapping + sRGB;曝光经 setExposure 仍实时生效) */
726
- private composer!: EffectComposer
727
- private camera!: THREE.PerspectiveCamera
127
+ composer!: EffectComposer
128
+ camera!: THREE.PerspectiveCamera
728
129
  /** 相机注视目标(拖拽平移它,zoom 微调距离) */
729
- private camTarget = new THREE.Vector3(WORLD_CX, 20, WORLD_CZ)
130
+ camTarget = new THREE.Vector3(WORLD_CX, 20, WORLD_CZ)
730
131
  /** 主方向光(阴影相机范围随 dolly 扩,大领地投影不消失) */
731
- private keyLight!: THREE.DirectionalLight
732
- private scene!: THREE.Scene
733
- private ground!: THREE.Mesh
132
+ keyLight!: THREE.DirectionalLight
133
+ scene!: THREE.Scene
134
+ ground!: THREE.Mesh
734
135
  /** 穹顶天幕(随镜头平移,保证无限视野观感) */
735
- private skyDome: THREE.Mesh | null = null
736
- private blocks = new Map<string, Block3D>()
737
- private agents = new Map<string, Agent3D>()
136
+ skyDome: THREE.Mesh | null = null
137
+ blocks = new Map<string, Block3D>()
138
+ agents = new Map<string, Agent3D>()
738
139
  /** 数字孪生设备节点(twinId → node) */
739
- private deviceNodes = new Map<string, DeviceNode>()
140
+ deviceNodes = new Map<string, DeviceNode>()
740
141
  /** 数采节点顶端 LED 环(twinId → 环;渲染循环缓转呼吸) */
741
- private daqLedRings = new Map<string, THREE.Mesh>()
142
+ daqLedRings = new Map<string, THREE.Mesh>()
742
143
  /** 数采→设备 绑定链路(虚线贝塞尔 + 流动脉冲;syncDaqLinks 维护) */
743
- private daqLinkGroup = new THREE.Group()
744
- private daqLinks: Array<{ daqId: string, deviceId: string, line: THREE.Line, pulse: THREE.Mesh, curve: THREE.QuadraticBezierCurve3, pt: number, ptSig: string }> = []
745
-
746
- // ===== 投影/拾取 scratch 单例(热路径零分配) =====
747
- private static readonly _wDir = new THREE.Vector3()
748
- private static readonly _wRel = new THREE.Vector3()
749
- private static readonly _wVec = new THREE.Vector3()
750
- private static readonly _wVec2 = new THREE.Vector2()
751
- private static readonly _groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
752
- private static readonly _raycaster = new THREE.Raycaster()
753
- private daqLinkSig = ''
144
+ daqLinkGroup = new THREE.Group()
145
+ daqLinks: DaqLink[] = []
146
+
147
+ daqLinkSig = ''
754
148
  /** 期望链路(TownView 传入;设备节点晚到时由 syncDevices 末尾重仲裁) */
755
- private daqLinksWanted: Array<{ daqId: string, deviceId: string }> = []
149
+ daqLinksWanted: Array<{ daqId: string, deviceId: string }> = []
756
150
  /** 薄膜 web(产线设备之间的半透明膜;按 X 序连接挤出→流延→MD→TD→收卷) */
757
- private filmWebGroup = new THREE.Group()
758
- private filmWebSig = ''
759
- private filmWebMat: THREE.MeshStandardMaterial | null = null
151
+ filmWebGroup = new THREE.Group()
152
+ filmWebSig = ''
153
+ filmWebMat: THREE.MeshStandardMaterial | null = null
760
154
  /** 服务端已有但模型资产尚未注册的设备孪生,资产到达后补建节点。 */
761
- private pendingDeviceTwins = new Map<string, DeviceTwinSync>()
155
+ pendingDeviceTwins = new Map<string, DeviceTwinSync>()
762
156
  /** 尚未取得服务端 ID 的本地设备节点(临时 ID → 创建请求初始状态)。 */
763
- private pendingDeviceCreates = new Map<string, { name: string, modelRef: string, posX: number, posZ: number }>()
157
+ pendingDeviceCreates = new Map<string, { name: string, modelRef: string, posX: number, posZ: number }>()
764
158
  /** 用户在创建设备完成前删除的临时节点,创建完成后补偿删除服务端记录。 */
765
- private cancelledDeviceCreates = new Set<string>()
159
+ cancelledDeviceCreates = new Set<string>()
766
160
  /** 场景内可缩放目标(Agent 或设备) */
767
- private scalables = new Map<string, ScaledTarget>()
161
+ scalables = new Map<string, ScaledTarget>()
768
162
  /** 当前选中(kind:id) */
769
- private selected: { kind: 'agent' | 'device', id: string } | null = null
163
+ selected: SelectedTarget | null = null
770
164
  /** 已登记模型(id → file) */
771
- private modelsById = new Map<string, ModelInfo>()
165
+ modelsById = new Map<string, ModelInfo>()
772
166
  /** GLTF 缓存(避免重复加载) */
773
- private gltfCache = new Map<string, { file: string, scene: THREE.Group, height: number }>()
774
- private gltfLoader = new GLTFLoader()
775
- private el: HTMLDivElement
776
- private raf = 0
777
- private clock = new THREE.Clock()
778
- private frameCount = 0
779
- private fpsAccum = 0
167
+ gltfCache = new Map<string, { file: string, scene: THREE.Group, height: number }>()
168
+ gltfLoader = new GLTFLoader()
169
+ el: HTMLDivElement
170
+ raf = 0
171
+ clock = new THREE.Clock()
172
+ frameCount = 0
173
+ fpsAccum = 0
780
174
  /** 墙钟 FPS 窗口起点与 rAF 回调计数(徽标/自适应质量的真实输入;原 dt 累计会漂移) */
781
- private fpsWinT0 = 0
782
- private rafCount = 0
175
+ fpsWinT0 = 0
176
+ rafCount = 0
783
177
  /** 帧预算(ms):渲染节流上限;0 = 不限制。数据消费不走此门控(帧到达即入实时缓冲) */
784
- private frameBudgetMs = 1000 / 40
178
+ frameBudgetMs = 1000 / 40
785
179
 
786
180
  /** 用户帧率选择(60/120/0=不限制):只影响渲染节流,与数据消费频率无关 */
787
181
  setFpsCap(fps: number): void {
@@ -789,70 +183,56 @@ export class TownScene3D {
789
183
  this.frameAcc = 0
790
184
  }
791
185
 
792
- private frameAcc = 0
186
+ frameAcc = 0
793
187
  /** 动态分辨率基准(初始 dpr):实测帧率过低时降,富余时回升 */
794
- private baseDpr = Math.min(window.devicePixelRatio, 2)
795
- private dirty = true
188
+ baseDpr = Math.min(window.devicePixelRatio, 2)
189
+ dirty = true
796
190
  /** 频道旗缓存(任务状态) */
797
- private flagBy = new Map<string, THREE.Mesh>()
798
- private disposed = false
191
+ flagBy = new Map<string, THREE.Mesh>()
192
+ disposed = false
799
193
  /** 当前相机缩放(滚轮;作用于 dolly 距离) */
800
- private dolly = 1.0
194
+ dolly = 1.0
801
195
  /** 轨道相机状态机(Blender 规范):yaw 方位角 / pitch 仰角 / radius 半径。
802
196
  * viewTarget 供预设平滑趋近;左键环绕直接改双值(即时跟手)。 */
803
- private autoOrbit = false
804
- private viewCur = { yaw: 0, pitch: 0.7, radius: 1178 }
805
- private viewTarget = { yaw: 0, pitch: 0.7, radius: 1178 }
197
+ autoOrbit = false
198
+ viewCur = { yaw: 0, pitch: 0.7, radius: 1178 }
199
+ viewTarget = { yaw: 0, pitch: 0.7, radius: 1178 }
806
200
  /** Blender 式变换手柄(选中设备;G 移动 / R 旋转 / S 缩放) */
807
- private tControls: TransformControls | null = null
201
+ tControls: TransformControls | null = null
808
202
  /** 舞台尺寸观察器 */
809
- private resizeOb: ResizeObserver | null = null
203
+ resizeOb: ResizeObserver | null = null
810
204
  /** 编辑 / 浏览模式(浏览只读:设备/角色不可拖,仅点选) */
811
- private mode: TownScene3DMode = 'browse'
205
+ mode: TownScene3DMode = 'browse'
812
206
  /** 正在拖曳的场景对象(编辑模式):设备 / 角色落点 / 频道整体 / 边界手柄 / Agent 活动范围 */
813
- private pointerDrag:
814
- | { kind: 'device', id: string }
815
- | { kind: 'agent', id: string }
816
- | { kind: 'channel', id: string, dx: number, dz: number }
817
- | { kind: 'resize', id: string, handle: number }
818
- | { kind: 'channelEdge', id: string, rx0: number, rz0: number, rd0: number }
819
- | { kind: 'rangeDraw', id: string, x0: number, z0: number }
820
- | { kind: 'agentRange', id: string, dx: number, dz: number }
821
- | { kind: 'agentRangeResize', id: string, handle: number }
822
- | null = null
207
+ pointerDrag: PointerDragState = null
823
208
 
824
209
  /** 网格吸附(编辑拖拽落点对齐 16 单位网格) */
825
- private snapEnabled = true
210
+ snapEnabled = true
826
211
  /** 选中高亮环(琥珀色动效环,跟随当前选中设备/角色;渲染循环驱动) */
827
- private selRing: THREE.Mesh | null = null
212
+ selRing: THREE.Mesh | null = null
828
213
  /** 边界缩放手柄(编辑模式选中频道时显示;拖拽手柄调整 radiusX/radiusZ) */
829
- private resizeHandles: Array<{ mesh: THREE.Mesh, cid: string, handle: number }> = []
214
+ readonly resizeHandles: ChannelResizeHandle[] = []
830
215
  /** Agent 活动范围缩放手柄(编辑模式选中带范围角色时显示;拖拽调整该 Agent 范围大小) */
831
- private agentRangeHandles: Array<{ mesh: THREE.Mesh, agentId: string, handle: number }> = []
216
+ readonly agentRangeHandles: AgentRangeHandle[] = []
832
217
  /** 框选绘制预览线框(rangeDraw 模式;松开后变为正式范围) */
833
- private rangeDrawLine: THREE.LineLoop | null = null
218
+ rangeDrawLine: THREE.LineLoop | null = null
834
219
  /** 框选绘制目标 agent(非空 = 编辑模式正在为该角色拉动矩形框) */
835
- private rangeDrawAgent: string | null = null
220
+ rangeDrawAgent: string | null = null
836
221
  /** 框选绘制当前对角(供 endPointerDrag 生成范围) */
837
- private rangeDrawPreviewState: { x0: number, z0: number, x1: number, z1: number } | null = null
222
+ rangeDrawPreviewState: { x0: number, z0: number, x1: number, z1: number } | null = null
838
223
  /** setAgentRangeScene 中因范围收缩而位移的 home(面板提交时一并落库) */
839
- private rangeHomeMoved: { agentId: string, x: number, z: number } | null = null
840
- private lastActivity: { channelId: string, agentName: string, text: string, at?: number } | null = null
841
- private recentActivity: Array<{ channelId: string, agentName: string, text: string, at?: number }> = []
842
- private dbgBubbles: Array<{ text: string, at: number }> = []
224
+ rangeHomeMoved: { agentId: string, x: number, z: number } | null = null
225
+ lastActivity: { channelId: string, agentName: string, text: string, at?: number } | null = null
226
+ recentActivity: Array<{ channelId: string, agentName: string, text: string, at?: number }> = []
227
+ dbgBubbles: Array<{ text: string, at: number }> = []
843
228
  /** 频道信息接收器(channelId → FIFO 队列;实时信息经 handleTownEvent 入队,渲染循环逐条消费) */
844
- private receivers = new Map<string, ChannelMessageReceiver>()
229
+ receivers = new Map<string, ChannelMessageReceiver>()
845
230
  readonly container: HTMLDivElement
846
231
 
847
232
  /** 任务 ID → assignee 反查 */
848
233
  resolveTaskAssignee: ((taskId: string) => string | null) | null = null
849
234
  /** 数字孪生设备 API(由 TownView 注入 useDeviceTwins 适配器;拖 dev 模型进场景时创建设备) */
850
- devices: {
851
- create(input: { name: string, modelRef?: string, kind?: string, controls?: string[], posX?: number, posZ?: number, scale?: number }): Promise<{ id: string }>
852
- update(id: string, patch: DeviceTransformPatch): Promise<unknown>
853
- remove?(id: string): Promise<unknown>
854
- control(id: string, command: string, args?: Record<string, unknown>): Promise<unknown>
855
- } | null = null
235
+ devices: DeviceApi | null = null
856
236
 
857
237
  /** 管理员布局:保存 Agent 落点(由 TownView 注入;经既有 channel_agents.config 持久化) */
858
238
  agentApi: {
@@ -875,363 +255,32 @@ export class TownScene3D {
875
255
  this.buildBlocks(seed)
876
256
  }
877
257
 
258
+ // ================================================================
259
+ // 面向对象节点的宿主契约(Block3D / Agent3D / DeviceNode)
260
+ // 节点只依赖 scene/town-scene3d-types 的最小接口,此处显式暴露宿主能力,
261
+ // 保证「节点所需服务」在编译期可校验(缺一即报错)。
262
+ // ================================================================
263
+
264
+ /** 频道领地节点宿主(Block3D.host) */
265
+ get blockHost(): BlockHost { return this }
266
+
267
+ /** 角色节点宿主(Agent3D.host) */
268
+ get agentHost(): AgentHost { return this }
269
+
270
+ /** 设备节点宿主(DeviceNode.host) */
271
+ get deviceHost(): DeviceHost { return this }
272
+
878
273
  // ================================================================
879
274
  // 初始化(渲染器/相机/灯光/地面)
880
275
  // ================================================================
881
276
 
882
277
  private initRenderer(): void {
883
- this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: 'high-performance' })
884
- this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
885
- this.renderer.setSize(this.el.clientWidth || 1100, this.el.clientHeight || 700)
886
- this.renderer.shadowMap.enabled = true
887
- this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
888
- // 阴影按需重绘:渲染循环里仅场景内容变化(dirty)或角色动画在跑时置 needsUpdate,
889
- // 静态场景(总览/无动画)不再每帧全量重绘阴影贴图(2048² PCFSoft 是最大单项 GPU 开销)
890
- this.renderer.shadowMap.autoUpdate = false
891
- this.renderer.outputColorSpace = THREE.SRGBColorSpace
892
- // 电影级色调映射:高光滚降 + 中间调层次,GLB 材质不再"平板曝光"
893
- this.renderer.toneMapping = THREE.ACESFilmicToneMapping
894
- this.renderer.toneMappingExposure = 1.12
895
- // 统计跨 pass 累计(composer 多 pass 直渲染时 info 每次自动清零,只会读到末 pass 的 1 次调用):
896
- // 每帧渲染前手动 reset,render() 累加,__townStats 拿到的才是整帧真实 drawCalls/triangles
897
- this.renderer.info.autoReset = false
898
- this.el.appendChild(this.renderer.domElement)
899
- // 舞台尺寸跟随宿主(网格布局/抽屉/全屏切换都会改变宿主尺寸)
900
- this.resizeOb = new ResizeObserver(() => {
901
- const w = this.el.clientWidth
902
- const h = this.el.clientHeight
903
- if (w < 2 || h < 2) return
904
- this.renderer.setSize(w, h)
905
- this.composer.setSize(w, h)
906
- this.camera.aspect = w / h
907
- this.camera.updateProjectionMatrix()
908
- this.dirty = true
909
- })
910
- this.resizeOb.observe(this.el)
911
- // 选中:点击 Agent/设备 → 弹缩放/旋转滑杆(经 on('select') 通知 Vue);拖拽释放不触发
912
- // 点击语义守卫:记录按下位置;松手位移大 = 相机平移手势 → 不触发点选(含信标)
913
- let clickDownAt: { x: number, y: number } | null = null
914
- this.renderer.domElement.addEventListener('pointerdown', (e: PointerEvent) => {
915
- clickDownAt = e.button === 0 ? { x: e.clientX, y: e.clientY } : null
916
- })
917
- this.renderer.domElement.addEventListener('pointerup', (e: PointerEvent) => {
918
- if (e.button !== 0 || this.pointerDrag || this.tControls?.dragging) return
919
- // 平移手势(按下后移动超 8px)→ 不作点选
920
- if (clickDownAt && Math.hypot(e.clientX - clickDownAt.x, e.clientY - clickDownAt.y) > 8) {
921
- clickDownAt = null
922
- return
923
- }
924
- clickDownAt = null
925
- const w = this.screenToWorld(e.clientX, e.clientY)
926
- // 频道信标优先(领队驻扎中心与信标重叠;中心 ±34 内点=信标,按住拖=Agent:
927
- // 拖拽手势在 pointerup 时因 pointerDrag 存在提前返回,不会误触信标)
928
- const beacon = this.pickBeacon(w.x, w.z)
929
- if (beacon) {
930
- this.setSelected(null)
931
- this.selectChannel(beacon.cid)
932
- this.focusTo(beacon.x, beacon.z)
933
- return
934
- }
935
- const hit = this.pickAt(w.x, w.z)
936
- if (hit) {
937
- this.setSelected(hit)
938
- // 点中角色/设备时清除频道选中(避免与边界面板混用)
939
- if (this.selectedChannel) this.selectChannel(null)
940
- return
941
- }
942
- // 未命中 agent/设备 → 尝试点选频道领地(打开边界编辑面板)
943
- const cid = this.pickChannel(w.x, w.z)
944
- this.setSelected(null)
945
- if (cid && this.mode === 'edit') this.selectChannel(cid)
946
- })
947
- // 双击 Agent/设备:缓动聚焦(指挥官镜头;不打断选择语义)
948
- this.renderer.domElement.addEventListener('dblclick', (e: MouseEvent) => {
949
- if (this.pointerDrag) return
950
- const w = this.screenToWorld(e.clientX, e.clientY)
951
- const hit = this.pickAt(w.x, w.z)
952
- if (hit?.kind === 'agent') {
953
- const a = this.agents.get(hit.id)
954
- if (a) this.focusTo(a.root.position.x, a.root.position.z)
955
- }
956
- else if (hit?.kind === 'device') {
957
- const d = this.deviceNodes.get(hit.id)
958
- if (d) this.focusTo(d.root.position.x, d.root.position.z)
959
- }
960
- else {
961
- // 双击领地(非 Agent/设备落点)→ 对焦该 Channel 中心并选中
962
- // (浏览模式:边界面板即刻可用调节范围;编辑模式:缩放手柄同时显示,可拖拽控制)
963
- const cid = this.pickChannel(w.x, w.z)
964
- const b = cid ? this.blocks.get(cid) : undefined
965
- if (b) {
966
- this.selectChannel(cid)
967
- this.focusTo(b.x, b.z)
968
- }
969
- }
970
- })
971
-
972
- this.scene = new THREE.Scene()
973
- this.scene.background = new THREE.Color(0x0a0f18)
974
- // 大气纵深:雾把远处柔化进夜色,2.5D 场景立刻有工业孪生的空间感
975
- this.scene.fog = new THREE.Fog(0x1b2836, 1400, 8800)
976
- // PBR 环境光照:PMREM 室内环境 → 金属/粗糙 GLB 材质获得真实反射与 specular 生命;
977
- // 低强度保留夜景工业气氛(只在材质细节里"呼吸",不提亮全场)
978
- try {
979
- const pmrem = new THREE.PMREMGenerator(this.renderer)
980
- this.scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture
981
- this.scene.environmentIntensity = 0.42
982
- pmrem.dispose()
983
- }
984
- catch { /* 环境贴图失败:退回纯灯光方案 */ }
985
-
986
- // 穹顶渐变天幕(深空蓝黑 → 地平线工业暖灰;BackSide 大球,随镜头平移)
987
- this.skyDome = this.makeSkyDome()
988
- this.scene.add(this.skyDome)
989
-
990
- // 边界缩放手柄(编辑模式选中频道时显示;4 个:椭圆轴点 / 矩形角点)
991
- for (let i = 0; i < 4; i++) {
992
- const h = new THREE.Mesh(
993
- new THREE.TorusGeometry(16, 6, 8, 20),
994
- new THREE.MeshBasicMaterial({ color: 0xf6c453, transparent: true, opacity: 0.95, depthTest: false }),
995
- )
996
- h.rotation.x = Math.PI / 2
997
- h.position.y = 1.4
998
- h.visible = false
999
- this.scene.add(h)
1000
- this.resizeHandles.push({ mesh: h, cid: '', handle: i })
1001
- }
1002
- // Agent 活动范围缩放手柄(编辑模式选中带范围角色时显示;4 个:椭圆轴点 / 矩形角点)
1003
- for (let i = 0; i < 4; i++) {
1004
- const h = new THREE.Mesh(
1005
- new THREE.TorusGeometry(10, 4, 8, 16),
1006
- new THREE.MeshBasicMaterial({ color: 0x41c8f4, transparent: true, opacity: 0.95, depthTest: false }),
1007
- )
1008
- h.rotation.x = Math.PI / 2
1009
- h.position.y = 1.2
1010
- h.visible = false
1011
- this.scene.add(h)
1012
- this.agentRangeHandles.push({ mesh: h, agentId: '', handle: i })
1013
- }
1014
-
1015
- // 选中高亮环:单位几何 + 每帧按目标缩放(琥珀 #f6c453,加性发光,慢转活性)
1016
- const selRing = new THREE.Mesh(
1017
- new THREE.RingGeometry(0.92, 1.0, 56),
1018
- new THREE.MeshBasicMaterial({ color: 0xf6c453, transparent: true, opacity: 0.85, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending }),
1019
- )
1020
- selRing.rotation.x = -Math.PI / 2
1021
- selRing.position.y = 0.5
1022
- selRing.visible = false
1023
- selRing.renderOrder = 5
1024
- this.scene.add(selRing)
1025
- this.selRing = selRing
1026
-
1027
- // 相机:斜俯视 2.5D(FOV 55 + 远距框景,整个园区一块入画)
1028
- this.camera = new THREE.PerspectiveCamera(55, (this.el.clientWidth || 1100) / (this.el.clientHeight || 700), 1, 12000)
1029
- this.camera.position.set(WORLD_CX, 760, WORLD_CZ + 900)
1030
- this.camera.lookAt(WORLD_CX, 20, WORLD_CZ)
1031
-
1032
- // Blender 式变换手柄:选中设备时出现(移动 G / 旋转 R / 缩放 S;UI 分段钮 + 键盘同源)
1033
- const tc = new TransformControls(this.camera, this.renderer.domElement)
1034
- tc.size = 0.85
1035
- tc.setMode('translate')
1036
- tc.showY = false
1037
- tc.addEventListener('dragging-changed', (e) => {
1038
- // 手柄拖拽期间压制场景点选/相机平移;松手后设备 transform 落库
1039
- if (!e.value) {
1040
- const sel = this.selected
1041
- if (sel?.kind === 'device' && this.deviceNodes.has(sel.id)) this.persistDeviceTransform(sel.id)
1042
- }
1043
- })
1044
- tc.addEventListener('objectChange', () => {
1045
- const sel = this.selected
1046
- if (!sel || sel.kind !== 'device') return
1047
- const dev = this.deviceNodes.get(sel.id)
1048
- if (!dev) return
1049
- // 缩放转移:gizmo 对 root 的缩放倍率转移到模型 holder(免得环/铭牌跟着变大),root 复位
1050
- if (tc.mode === 'scale' && Math.abs(dev.root.scale.x - 1) > 1e-4) {
1051
- const reg = this.scalables.get(`device:${sel.id}`)
1052
- const cur = reg?.userScale ?? 1
1053
- this.setModelScale(sel.id, Math.min(50, Math.max(0.02, cur * dev.root.scale.x)), 'device')
1054
- dev.root.scale.set(1, 1, 1)
1055
- }
1056
- this.dirty = true
1057
- })
1058
- this.tControls = tc
1059
- this.scene.add(tc.getHelper())
1060
-
1061
- // 灯光:为 ACES 配平(整体提亮补偿 filmic 滚降;normalBias 消除模型自阴影痤疮)
1062
- this.scene.add(new THREE.AmbientLight(0xd0e4ee, 1.6))
1063
- const hemi = new THREE.HemisphereLight(0x9fc7e8, 0x3a2f25, 0.7)
1064
- this.scene.add(hemi)
1065
- const key = new THREE.DirectionalLight(0xfff4e0, 3.2)
1066
- key.position.set(WORLD_CX + 500, 900, WORLD_CZ + 300)
1067
- key.castShadow = true
1068
- this.keyLight = key
1069
- // 高分辨率软影:2048 map + 大 radius(PCSS 观感),贴地阴影是真实感的核心来源
1070
- key.shadow.mapSize.set(2048, 2048)
1071
- key.shadow.camera.left = -1400
1072
- key.shadow.camera.right = 1400
1073
- key.shadow.camera.top = 2000
1074
- key.shadow.camera.bottom = -2000
1075
- key.shadow.normalBias = 2
1076
- key.shadow.bias = -0.0002
1077
- key.shadow.radius = 6
1078
- this.scene.add(key)
1079
- const fill = new THREE.DirectionalLight(0x88bbff, 0.75)
1080
- fill.position.set(WORLD_CX - 1200, 900, WORLD_CZ - 800)
1081
- this.scene.add(fill)
1082
- // 设计稿光系补光:青色轮廓光(金属切边高光)+ 绿色低位补光(孪生绿氛围)
1083
- const rim = new THREE.DirectionalLight(0x41c8f4, 1.4)
1084
- rim.position.set(WORLD_CX - 1400, 620, WORLD_CZ - 1000)
1085
- this.scene.add(rim)
1086
- const green = new THREE.DirectionalLight(0x35e0a0, 0.4)
1087
- green.position.set(WORLD_CX + 700, 380, WORLD_CZ + 900)
1088
- this.scene.add(green)
1089
-
1090
- // 地面(大平面,无限观感:7× 世界尺寸 + 高重复贴图)——夜航深蓝地坪;低反射让网格退为纹理
1091
- const groundMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.62, metalness: 0.3, envMapIntensity: 0.42 })
1092
- this.ground = new THREE.Mesh(new THREE.PlaneGeometry(WORLD_W * 16, WORLD_H * 16), groundMat)
1093
- this.ground.rotation.x = -Math.PI / 2
1094
- this.ground.receiveShadow = true
1095
- this.scene.add(this.ground)
1096
-
1097
- // 绑定链路层 + 薄膜 web 层(syncDaqLinks / 产线设备增删移动时重建)
1098
- this.scene.add(this.daqLinkGroup, this.filmWebGroup)
1099
-
1100
- // 后处理:RenderPass → UnrealBloom(夜航辉光只作用于 HDR 顶端:阈值 3.0 =
1101
- // 本灯光系下漫反射亮面(2~2.5 线性)不起晕,只留指示灯/金属高光;LED 等指示件
1102
- // 已按 ×4.5~6 提为 HDR)→ OutputPass(tone mapping + sRGB 收尾;setExposure
1103
- // 经 OutputPass 透传仍实时生效)。MSAA 目标保住抗锯齿(EffectComposer 默认无 MSAA)。
1104
- const bloomTarget = new THREE.WebGLRenderTarget(1, 1, { type: THREE.HalfFloatType, samples: 4 })
1105
- this.composer = new EffectComposer(this.renderer, bloomTarget)
1106
- this.composer.setPixelRatio(this.renderer.getPixelRatio())
1107
- this.composer.setSize(this.el.clientWidth || 1100, this.el.clientHeight || 700)
1108
- this.composer.addPass(new RenderPass(this.scene, this.camera))
1109
- this.composer.addPass(new UnrealBloomPass(new THREE.Vector2(this.el.clientWidth || 1100, this.el.clientHeight || 700), 0.32, 0.35, 3.0))
1110
- this.composer.addPass(new OutputPass())
1111
-
1112
- this.applyGroundTexture()
1113
-
278
+ initRendererCore(this)
1114
279
  // E2E/性能探针钩子:真值验证用(渲染帧计数/质量档/pixelRatio),dispose 时清除
1115
280
  ;(globalThis as { __townScene3d?: TownScene3D }).__townScene3d = this
1116
281
  this.loop()
1117
282
  }
1118
283
 
1119
- /** 工业孪生地面贴图(程序化 Canvas:深色混凝土地基 + 分块拼缝 + 细网格导引线 + 噪点)。
1120
- * 一次性生成并缓存;SVG 背景贴图仅作历史兼容,优先使用本贴图。 */
1121
- private makeIndustrialGroundTexture(): THREE.CanvasTexture {
1122
- const size = 1024
1123
- const canvas = document.createElement('canvas')
1124
- canvas.width = size
1125
- canvas.height = size
1126
- const ctx = canvas.getContext('2d')!
1127
- // 深色混凝土地基(微渐变)+ 分块拼缝 + 细网格导引线 —— 夜航深蓝基调(设计稿 #131f36 系)
1128
- const grad = ctx.createLinearGradient(0, 0, size, size)
1129
- grad.addColorStop(0, '#151f36')
1130
- grad.addColorStop(0.5, '#101a2d')
1131
- grad.addColorStop(1, '#131c31')
1132
- ctx.fillStyle = grad
1133
- ctx.fillRect(0, 0, size, size)
1134
- // 分块拼缝(工业板格 8×8;低调度 —— 地面是舞台,不是主角)
1135
- ctx.strokeStyle = 'rgba(42,63,102,0.32)'
1136
- ctx.lineWidth = 2
1137
- for (let i = 0; i <= 8; i++) {
1138
- const p = (i / 8) * size
1139
- ctx.beginPath()
1140
- ctx.moveTo(p, 0)
1141
- ctx.lineTo(p, size)
1142
- ctx.stroke()
1143
- ctx.beginPath()
1144
- ctx.moveTo(0, p)
1145
- ctx.lineTo(size, p)
1146
- ctx.stroke()
1147
- }
1148
- // 细网格导引线(设计稿 GridHelper 主色 0x2a3f66;压暗让设备读第一眼)
1149
- ctx.strokeStyle = 'rgba(42,63,102,0.2)'
1150
- ctx.lineWidth = 1
1151
- for (let i = 0; i <= 32; i++) {
1152
- const p = (i / 32) * size
1153
- ctx.beginPath()
1154
- ctx.moveTo(p, 0)
1155
- ctx.lineTo(p, size)
1156
- ctx.stroke()
1157
- ctx.beginPath()
1158
- ctx.moveTo(0, p)
1159
- ctx.lineTo(size, p)
1160
- ctx.stroke()
1161
- }
1162
- // 噪点(亚光粗糙感;伪随机但确定性)
1163
- ctx.fillStyle = 'rgba(255,255,255,0.02)'
1164
- for (let i = 0; i < 900; i++) {
1165
- const x = (i * 733) % size
1166
- const y = (i * 151) % size
1167
- ctx.fillRect(x, y, 2, 2)
1168
- }
1169
- const tex = new THREE.CanvasTexture(canvas)
1170
- tex.colorSpace = THREE.SRGBColorSpace
1171
- tex.wrapS = THREE.RepeatWrapping
1172
- tex.wrapT = THREE.RepeatWrapping
1173
- tex.repeat.set(23, 17)
1174
- tex.anisotropy = Math.min(8, this.renderer.capabilities.getMaxAnisotropy())
1175
- return tex
1176
- }
1177
-
1178
- /** 穹顶天幕:垂直渐变(顶部深空 → 地平线工业暖灰)+ 顶半球星野 + 底部更亮一点,覆盖整球 */
1179
- private makeSkyDome(): THREE.Mesh {
1180
- const canvas = document.createElement('canvas')
1181
- canvas.width = 1024
1182
- canvas.height = 256
1183
- const ctx = canvas.getContext('2d')!
1184
- const grad = ctx.createLinearGradient(0, 0, 0, 256)
1185
- grad.addColorStop(0, '#080d16') // 天顶:近黑夜空
1186
- grad.addColorStop(0.40, '#121b27')
1187
- grad.addColorStop(0.64, '#22303e') // 中段石墨蓝(提前起坡,渐变带更长)
1188
- grad.addColorStop(0.84, '#3b4b5b') // 地平线:钢色微光(与雾同族)
1189
- grad.addColorStop(1, '#2a3644') // 地平线下收暗(下半球)
1190
- ctx.fillStyle = grad
1191
- ctx.fillRect(0, 0, canvas.width, canvas.height)
1192
- // 星野(顶部 45%:确定性伪随机,数据青/冷白微点,越靠天顶越密 —— 夜航纵深,低调度不抢戏)
1193
- let seed = 20260831
1194
- const rnd = (): number => {
1195
- seed = (seed * 1103515245 + 12345) % 2147483648
1196
- return seed / 2147483648
1197
- }
1198
- for (let i = 0; i < 220; i++) {
1199
- const x = rnd() * canvas.width
1200
- const y = rnd() * rnd() * 118
1201
- const r = rnd() < 0.88 ? 1 : 2
1202
- const a = 0.12 + rnd() * 0.5
1203
- ctx.fillStyle = rnd() < 0.24 ? `rgba(160,220,255,${a.toFixed(2)})` : `rgba(232,240,250,${a.toFixed(2)})`
1204
- ctx.beginPath()
1205
- ctx.arc(x, y, r, 0, Math.PI * 2)
1206
- ctx.fill()
1207
- }
1208
- const tex = new THREE.CanvasTexture(canvas)
1209
- tex.colorSpace = THREE.SRGBColorSpace
1210
- const geo = new THREE.SphereGeometry(5600, 24, 16)
1211
- const mat = new THREE.MeshBasicMaterial({ map: tex, side: THREE.BackSide, fog: false, depthWrite: false })
1212
- const dome = new THREE.Mesh(geo, mat)
1213
- dome.position.set(WORLD_CX, 0, WORLD_CZ)
1214
- dome.renderOrder = -10
1215
- return dome
1216
- }
1217
-
1218
- /** 赛博小镇背景贴图 → 地面材质(优先程序化工业贴图;SVG 兼容保留) */
1219
- private applyGroundTexture(): void {
1220
- try {
1221
- const mat = this.ground.material as THREE.MeshStandardMaterial
1222
- // 工业孪生贴图为主(SVG 历史贴图只在程序化不可用时兜底)
1223
- mat.map = this.makeIndustrialGroundTexture()
1224
- mat.color.set(0xffffff)
1225
- mat.roughness = 0.94
1226
- mat.metalness = 0.06
1227
- // 环境反射压低:沥青地面保持亚光,反射生命留给金属设备/角色模型
1228
- mat.envMapIntensity = 0.28
1229
- mat.needsUpdate = true
1230
- this.dirty = true
1231
- }
1232
- catch { /* Canvas 不可用:保持纯色地面 */ }
1233
- }
1234
-
1235
284
  // ================================================================
1236
285
  // 事件总线(与 2D 同构)
1237
286
  // ================================================================
@@ -1241,7 +290,7 @@ export class TownScene3D {
1241
290
  return () => this.busHandlers.delete(event)
1242
291
  }
1243
292
 
1244
- private emit<K extends keyof TownEventMap>(event: K, e: TownEventMap[K]): void {
293
+ emit<K extends keyof TownEventMap>(event: K, e: TownEventMap[K]): void {
1245
294
  const fn = this.busHandlers.get(event)
1246
295
  if (fn) fn(e)
1247
296
  }
@@ -1253,455 +302,52 @@ export class TownScene3D {
1253
302
  /**
1254
303
  * 用频道实体基线同步领地/角色。仅放置已保存布局的频道(布局来自 useSceneLayouts,
1255
304
  * 经 applySceneLayouts 注入);未放置频道只出现在频道坞,不进 3D。
305
+ * 实现见 scene/town-scene3d-blocks.ts。
1256
306
  */
1257
- private buildBlocks(seeds: TownEntityInput[]): void {
1258
- this.entityIndex.clear()
1259
- for (const ch of seeds) {
1260
- this.entityIndex.set(ch.channelId, ch)
1261
- const layout = this.layouts.get(ch.channelId)
1262
- if (!layout) continue
1263
- this.placeChannel(ch, layout)
1264
- }
1265
- this.emit('blockCount', this.blocks.size)
1266
- this.emit('agentCount', this.agents.size)
1267
- }
1268
-
1269
- /** 按布局放置一个频道领地(面向对象:Block3D 实例)+ 在其边界内铺放全部 Agent */
1270
- private placeChannel(ch: TownEntityInput, rawLayout: ChannelLayout): void {
1271
- const layout = normLayout(rawLayout)
1272
- const color = channelColorNum(ch.channelId)
1273
- const pad = this.makeBlock(ch.channelName, color, layout)
1274
- const block = new Block3D({
1275
- channelId: ch.channelId, name: ch.channelName,
1276
- x: layout.x, z: layout.z,
1277
- radiusX: layout.radiusX, radiusZ: layout.radiusZ,
1278
- shape: layout.shape, rotationY: layout.rotationY,
1279
- color,
1280
- platform: pad.platform,
1281
- padRing: pad.padRing,
1282
- beacon: pad.beacon,
1283
- boundary: makeBoundary(layout.shape, layout.radiusX, layout.radiusZ, color),
1284
- label: this.makeLabel(ch.channelName, layout.x, 30, layout.z),
1285
- })
1286
- block.host = this
1287
- block.boundary.position.set(layout.x, 0.3, layout.z)
1288
- block.boundary.rotation.y = layout.rotationY * Math.PI / 180
1289
- this.scene.add(block.boundary)
1290
- this.blocks.set(ch.channelId, block)
1291
- // 在边界内铺放该频道的全部 Agent(lead 居中心,worker 左右展开;钳在边界内)
1292
- this.layoutAgentsInBlock(ch, block)
1293
- // 面向对象聚合:把该频道全部成员挂到 Block3D.members(供整体移动/边界钳制批量处理)
1294
- for (const a of this.agents.values()) {
1295
- if (a.channelId === ch.channelId && !block.members.includes(a)) block.members.push(a)
1296
- }
1297
- }
1298
-
1299
- /** 在频道领地里铺放全部 Agent(按边界内分布;客户自定 home 优先) */
1300
- private layoutAgentsInBlock(ch: TownEntityInput, block: Block3D): void {
1301
- const { x, z, radiusX, radiusZ } = block
1302
- const inner = 0.6 // 内缩比例:agent 铺在边界内侧
1303
- const n = Math.max(1, ch.agents.length)
1304
- ch.agents.forEach((a, i) => {
1305
- // 若该 agent 有 persisted home(且在边界附近)则用之;否则沿长轴均匀铺放
1306
- let px: number
1307
- let pz: number
1308
- if (typeof a.homeX === 'number' && typeof a.homeZ === 'number') {
1309
- px = a.homeX
1310
- pz = a.homeZ
1311
- }
1312
- else {
1313
- const t = n <= 1 ? 0 : (i / (n - 1)) * 2 - 1
1314
- px = x + t * radiusX * inner * 0.8
1315
- pz = z + (a.role === 'lead' ? 0 : (i % 2 === 0 ? -1 : 1) * radiusZ * inner * 0.4)
1316
- }
1317
- this.ensureAgent({ ...a, channelId: ch.channelId }, px, pz, block.color)
1318
- })
1319
- }
1320
-
1321
- /** 领地底盘 = 按真实形状的平台(染色层) + 同形边框环(边界告示);所见即用户设置 */
1322
- private makeBlock(name: string, color: number, layout: ChannelLayout): { platform: THREE.Mesh, padRing: THREE.Mesh, beacon: THREE.Group } {
1323
- const rotY = layout.rotationY * Math.PI / 180
1324
- // 平台:按真实半轴构建(与边框环同一构建路径,无单位几何、无 scale 传递 —— 杜绝缩放残留)
1325
- const platform = new THREE.Mesh(
1326
- this.makePadShapeGeometry(layout.shape, layout.radiusX, layout.radiusZ),
1327
- new THREE.MeshStandardMaterial({
1328
- color, transparent: true, opacity: 0.24, roughness: 0.85, metalness: 0.12,
1329
- emissive: color, emissiveIntensity: 0.12, side: THREE.DoubleSide,
1330
- }),
1331
- )
1332
- // 几何已烘焙放平(XY→XZ 无镜像),网格仅做 Y 旋转(与边界线同语义),不再有任何 scale
1333
- platform.rotation.set(0, rotY, 0)
1334
- platform.position.set(layout.x, 0.16, layout.z)
1335
- platform.receiveShadow = false
1336
- this.scene.add(platform)
1337
- // 边框环:实际尺寸外形挖去内缩内形(恒定 13 单位线宽,随形状变化重建)
1338
- const padRing = new THREE.Mesh(
1339
- this.makePadRingGeometry(layout.shape, layout.radiusX, layout.radiusZ),
1340
- new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.52, side: THREE.DoubleSide, depthWrite: false }),
1341
- )
1342
- padRing.rotation.set(0, rotY, 0)
1343
- padRing.position.set(layout.x, 0.34, layout.z)
1344
- this.scene.add(padRing)
1345
- // 中央信标(HMI 定位销):底座 + 立杆 + 频道色菱形顶标(顶标随渲染循环缓转)
1346
- const beacon = this.makeBeacon(color)
1347
- beacon.position.set(layout.x, 0, layout.z)
1348
- this.scene.add(beacon)
1349
- void name
1350
- return { platform, padRing, beacon }
1351
- }
1352
-
1353
- /** 频道中心信标(HMI 定位销):点击 → pickBeacon 定位中心 + 唤醒边界编辑 */
1354
- private makeBeacon(color: number): THREE.Group {
1355
- const g = new THREE.Group()
1356
- const metal = new THREE.MeshStandardMaterial({ color: 0x2a3542, roughness: 0.45, metalness: 0.65 })
1357
- const base = new THREE.Mesh(new THREE.CylinderGeometry(7, 9, 5, 12), metal)
1358
- base.position.y = 2.5
1359
- const pole = new THREE.Mesh(new THREE.CylinderGeometry(1.6, 1.6, 30, 8), metal)
1360
- pole.position.y = 18
1361
- const tip = new THREE.Mesh(new THREE.OctahedronGeometry(6.5), new THREE.MeshBasicMaterial({ color }))
1362
- // HDR 亮度:信标顶标是 HMI 定位销的"灯",bloom 起晕(seleRing/边界保持 LDR 不起晕)
1363
- ;(tip.material as THREE.MeshBasicMaterial).color.multiplyScalar(4.5)
1364
- tip.position.y = 37
1365
- g.add(base, pole, tip)
1366
- return g
1367
- }
307
+ buildBlocks(seeds: TownEntityInput[]): void { buildBlocks(this, seeds) }
1368
308
 
1369
309
  /** 命中频道信标(中心 ±34 单位;点击信标 = 定位中心 + 唤醒边界编辑,任何模式) */
1370
- private pickBeacon(x: number, z: number): { cid: string, x: number, z: number } | null {
1371
- for (const b of this.blocks.values()) {
1372
- if (Math.hypot(b.x - x, b.z - z) < 34) return { cid: b.channelId, x: b.x, z: b.z }
1373
- }
1374
- return null
1375
- }
1376
-
1377
- /** 领地平台面几何:按真实半轴的外形(椭圆/矩形);放平烘焙进顶点(XY→XZ,无镜像),
1378
- * 网格仅需 rotation.y —— 与边界线同一旋转语义,任意朝向严格贴合 */
1379
- private makePadShapeGeometry(shape: 'ellipse' | 'rect', radiusX: number, radiusZ: number): THREE.ShapeGeometry {
1380
- const shp = new THREE.Shape()
1381
- if (shape === 'rect') {
1382
- shp.moveTo(-radiusX, -radiusZ)
1383
- shp.lineTo(radiusX, -radiusZ)
1384
- shp.lineTo(radiusX, radiusZ)
1385
- shp.lineTo(-radiusX, radiusZ)
1386
- shp.closePath()
1387
- }
1388
- else {
1389
- shp.absellipse(0, 0, radiusX, radiusZ, 0, Math.PI * 2)
1390
- }
1391
- const geo = new THREE.ShapeGeometry(shp, shape === 'rect' ? 1 : 56)
1392
- geo.rotateX(Math.PI / 2)
1393
- return geo
1394
- }
1395
-
1396
- /** 领地边框几何:外形挖去内缩 ringW 的同形内形(椭圆/矩形;恒定世界线宽) */
1397
- private makePadRingGeometry(shape: 'ellipse' | 'rect', radiusX: number, radiusZ: number, ringW = 13): THREE.ShapeGeometry {
1398
- const irx = Math.max(4, radiusX - ringW)
1399
- const irz = Math.max(4, radiusZ - ringW)
1400
- const outer = new THREE.Shape()
1401
- const inner = new THREE.Path()
1402
- if (shape === 'rect') {
1403
- outer.moveTo(-radiusX, -radiusZ)
1404
- outer.lineTo(radiusX, -radiusZ)
1405
- outer.lineTo(radiusX, radiusZ)
1406
- outer.lineTo(-radiusX, radiusZ)
1407
- outer.closePath()
1408
- inner.moveTo(-irx, -irz)
1409
- inner.lineTo(irx, -irz)
1410
- inner.lineTo(irx, irz)
1411
- inner.lineTo(-irx, irz)
1412
- inner.closePath()
1413
- }
1414
- else {
1415
- outer.absellipse(0, 0, radiusX, radiusZ, 0, Math.PI * 2)
1416
- inner.absellipse(0, 0, irx, irz, 0, Math.PI * 2)
1417
- }
1418
- outer.holes.push(inner)
1419
- const geo = new THREE.ShapeGeometry(outer, shape === 'rect' ? 1 : 56)
1420
- geo.rotateX(Math.PI / 2)
1421
- return geo
1422
- }
1423
-
1424
- /** 平台/边框贴形(半径或形状变化后):平台与边框几何都按真实尺寸重建;同步朝向 */
1425
- private applyPadToBlock(b: Block3D): void {
1426
- const rotY = b.rotationY * Math.PI / 180
1427
- b.platform.rotation.set(0, rotY, 0)
1428
- b.padRing.rotation.set(0, rotY, 0)
1429
- const oldP = b.platform.geometry
1430
- b.platform.geometry = this.makePadShapeGeometry(b.shape, b.radiusX, b.radiusZ)
1431
- oldP.dispose()
1432
- const old = b.padRing.geometry
1433
- b.padRing.geometry = this.makePadRingGeometry(b.shape, b.radiusX, b.radiusZ)
1434
- old.dispose()
1435
- }
1436
-
1437
- /** 文本 Sprite(名牌/名字)—— HMI 铭牌:深底 + 发丝描边 + 左缘数据条 + 等宽字。
1438
- * accent: 左缘数据条与描边 tint 的身份色(Agent 铭牌传所属频道哈希色 → 归属一眼可辨)。 */
1439
- private makeLabel(text: string, x: number, y: number, z: number, accent = '#41c8f4'): THREE.Sprite {
1440
- const canvas = document.createElement('canvas')
1441
- const ctx = canvas.getContext('2d')!
1442
- const font = '600 21px "Geist Mono", Geist, "PingFang SC", monospace'
1443
- ctx.font = font
1444
- const padL = 26
1445
- const padR = 14
1446
- const w = Math.max(104, Math.ceil(ctx.measureText(text).width) + padL + padR)
1447
- const h = 48
1448
- canvas.width = w
1449
- canvas.height = h
1450
- ctx.font = font
1451
- // 深色铭牌底 + 半透明度(不遮场景,只提字)
1452
- ctx.fillStyle = 'rgba(10,14,20,0.8)'
1453
- ctx.fillRect(0, 0, w, h)
1454
- // 发丝描边(身份色轻 tint)+ 左缘数据条(身份色)
1455
- ctx.strokeStyle = `${accent}66`
1456
- ctx.lineWidth = 1
1457
- ctx.strokeRect(0.5, 0.5, w - 1, h - 1)
1458
- ctx.fillStyle = accent
1459
- ctx.fillRect(0, 0, 3, h)
1460
- ctx.fillStyle = '#dce7f0'
1461
- ctx.textAlign = 'left'
1462
- ctx.textBaseline = 'middle'
1463
- ctx.fillText(text, padL, h / 2 + 1)
1464
- const tex = new THREE.CanvasTexture(canvas)
1465
- tex.colorSpace = THREE.SRGBColorSpace
1466
- const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: tex, transparent: true, depthTest: false }))
1467
- sprite.scale.set(w / 6, h / 6, 1)
1468
- sprite.position.set(x, y, z)
1469
- this.scene.add(sprite)
1470
- return sprite
1471
- }
1472
-
1473
- /** int 颜色 → CSS hex(铭牌身份色用) */
1474
- private hexOf(color: number): string {
1475
- return `#${color.toString(16).padStart(6, '0')}`
1476
- }
310
+ pickBeacon(x: number, z: number): { cid: string, x: number, z: number } | null { return pickBeacon(this, x, z) }
1477
311
 
1478
312
  // ================================================================
1479
- // Agent(3D 角色:根 Group + 模型 + 同频道色环 + 名字)
313
+ // Agent(3D 角色:根 Group + 模型 + 同频道色环 + 名字;
314
+ // 实现见 scene/town-scene3d-agents.ts)
1480
315
  // ================================================================
1481
316
 
1482
- private ensureAgent(a: TownEntityInput['agents'][number] & { channelId: string }, cx: number, cz: number, color: number): void {
1483
- const key = a.agentId
1484
- if (this.agents.has(key)) return
1485
- // 管理员布局落点(来自 config.homeX/homeZ)优先;缺省按领地内排布;整体钳制在频道边界内
1486
- const layout = this.blockLayout(a.channelId)
1487
- let homeX = a.homeX ?? cx
1488
- let homeZ = a.homeZ ?? cz
1489
- if (layout) {
1490
- const clamped = clampToBoundary(layout, homeX, homeZ, 20)
1491
- homeX = clamped.x
1492
- homeZ = clamped.z
1493
- }
1494
- // 管理员布局活动范围(来自 config.range;缺省 null = 沿用频道边界);收进频道边界
1495
- const range: AgentRangeLayout | null = a.range ?? null
1496
- const root = new THREE.Group()
1497
- root.position.set(homeX, GROUND_Y, homeZ)
1498
- const nameSprite = this.makeLabel(a.name, homeX, 48, homeZ, this.hexOf(color))
1499
- // 默认模型(内置二次元角色 hero-anime-1「樱叶少女」;模型库换装可覆盖)
1500
- const texKey = a.modelRef || 'hero-anime-1'
1501
- const model = new THREE.Group()
1502
- root.add(model)
1503
- this.scene.add(root)
1504
- const agent = new Agent3D({
1505
- channelId: a.channelId,
1506
- agentId: key,
1507
- name: a.name,
1508
- role: a.role,
1509
- root,
1510
- model,
1511
- mixer: null,
1512
- clips: [],
1513
- colorNum: color,
1514
- nameSprite,
1515
- bubble: null,
1516
- bubbleText: null,
1517
- bubbleTimer: null,
1518
- state: a.state,
1519
- progress: a.currentTaskProgress ?? null,
1520
- dragging: false,
1521
- homeX,
1522
- homeZ,
1523
- range,
1524
- rangeLine: null,
1525
- textureKey: texKey,
1526
- modelRef: a.modelRef ?? '',
1527
- behavior: { mode: 'idle', roamTarget: null, targetId: null, waitUntil: 0, pauseUntil: 0, engaged: false },
1528
- })
1529
- agent.host = this
1530
- agent.attachAura(color, a.role)
1531
- this.agents.set(key, agent)
1532
- // 载入模型(GLB);模型库注册先于/晚于挂载都能正确解析(内置二次元角色按 id 直取文件)
1533
- const info = this.modelsById.get(texKey)
1534
- ?? (texKey.startsWith('hero-anime-')
1535
- ? { id: texKey, file: `/assets/game/character/${texKey}.glb`, name: '二次元角色' }
1536
- : (this.modelsById.get('hero-3d') ?? { id: 'hero-3d', file: '/assets/game/character/hero-3d.glb', name: '标准员工模型' }))
1537
- void this.mountModel(agent, info.file, info.name)
1538
- agent.renderRangeLine()
1539
- this.emit('agentCount', this.agents.size)
1540
- }
1541
-
1542
- /**
1543
- * 绑定模型到 Agent(GLTFLoader 加载 → 归一化 scale/锚点贴地 → 若有动画则 mixer)。
1544
- * 缺失/失败 → 回退内置 hero-3d,永不白屏。
1545
- */
1546
- private async mountModel(asp: Agent3D, file: string, name: string): Promise<void> {
1547
- const cached = this.gltfCache.get(file)
1548
- let loaded: THREE.Group
1549
- let height: number
1550
- if (cached) {
1551
- loaded = cached.scene.clone(true)
1552
- height = cached.height
1553
- }
1554
- else {
1555
- try {
1556
- const gltf = await this.loadGltf(file)
1557
- loaded = gltf.scene
1558
- const box = new THREE.Box3().setFromObject(loaded)
1559
- height = Math.max(0.5, box.max.y - box.min.y)
1560
- this.gltfCache.set(file, { file, scene: loaded, height })
1561
- // 缓存动画 clip(供 mixer 状态切换;GLTFLoader 动画必须在解析层保留)
1562
- this.agentAnimClips.set(file, gltf.animations)
1563
- }
1564
- catch {
1565
- // 加载失败回退程序化「孪生机器人」(胶囊躯干 + 发光核心 + 天线),角色永不隐形
1566
- loaded = this.makeFallbackBot()
1567
- height = 1.4
1568
- }
1569
- }
1570
- // 归一化 scale(使模型高度≈UNITS 世界单位)并贴地(模型大小自适应)
1571
- loaded.position.y = 0
1572
- loaded.scale.setScalar(UNITS / height)
1573
- // 真实投影 + PBR 增强:模型网格 cast/receiveShadow(体积感) + 分级环境反射(角色质感)
1574
- loaded.traverse((o) => {
1575
- if ((o as THREE.Mesh).isMesh) o.castShadow = true
1576
- })
1577
- this.enhancePbrMaterials(loaded, 'character')
1578
- // 清掉旧模型子节点与 mixer
1579
- asp.model.clear()
1580
- asp.model.add(loaded)
1581
- // 客制化:注册可缩放目标并恢复用户缩放(套在 asp.model 上,作为自适应之上的倍率层)
1582
- this.registerScalable('agent', asp.agentId, asp.model)
1583
- asp.mixer = null
1584
- asp.activeAction = null
1585
- const clips = this.agentAnimClips.get(file) ?? []
1586
- const firstClip = clips[0]
1587
- if (firstClip) {
1588
- asp.mixer = new THREE.AnimationMixer(loaded)
1589
- asp.clips = clips
1590
- const firstAction = asp.mixer.clipAction(firstClip)
1591
- firstAction.play()
1592
- asp.activeAction = firstAction
1593
- }
1594
- void name
1595
- this.dirty = true
1596
- }
1597
-
1598
- private agentAnimClips = new Map<string, THREE.AnimationClip[]>()
1599
-
1600
- private loadGltf(file: string): Promise<{ scene: THREE.Group, animations: THREE.AnimationClip[] }> {
1601
- return new Promise((resolve, reject) => {
1602
- this.gltfLoader.load(file, gltf => resolve({ scene: gltf.scene as THREE.Group, animations: gltf.animations ?? [] }), undefined, reject)
1603
- })
317
+ /** 按实体元数据实例化 Agent3D(落点钳制 / 活动范围 / 身份光环 / 名牌) */
318
+ ensureAgent(a: TownEntityInput['agents'][number] & { channelId: string }, cx: number, cz: number, color: number): void {
319
+ ensureAgent(this, a, cx, cz, color)
1604
320
  }
1605
321
 
1606
- /**
1607
- * 程序化「孪生机器人」兜底模型:GLB 缺失/加载失败时使用,保证 Agent 永远可见。
1608
- * 结构:胶囊躯干 + 头部 + 发光核心胸灯 + 天线信号球 + 悬浮底盘;中性工业灰 + 青蓝核心。
1609
- */
1610
- private makeFallbackBot(): THREE.Group {
1611
- const bot = new THREE.Group()
1612
- const bodyMat = new THREE.MeshStandardMaterial({ color: 0x2a3542, roughness: 0.42, metalness: 0.62 })
1613
- const darkMat = new THREE.MeshStandardMaterial({ color: 0x141b24, roughness: 0.6, metalness: 0.4 })
1614
- const coreMat = new THREE.MeshBasicMaterial({ color: 0x41c8f4 })
1615
- // 躯干(胶囊)
1616
- const body = new THREE.Mesh(new THREE.CapsuleGeometry(0.42, 0.62, 8, 16), bodyMat)
1617
- body.position.y = 0.86
1618
- // 头部
1619
- const head = new THREE.Mesh(new THREE.SphereGeometry(0.32, 16, 12), bodyMat)
1620
- head.position.y = 1.62
1621
- // 面窗(发光条带,数字孪生眼)
1622
- const visor = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.1, 0.12), coreMat)
1623
- visor.position.set(0, 1.66, 0.27)
1624
- // 发光核心胸灯
1625
- const core = new THREE.Mesh(new THREE.SphereGeometry(0.14, 12, 8), coreMat)
1626
- core.position.set(0, 1.1, 0.36)
1627
- // 悬浮底盘
1628
- const base = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.62, 0.26, 16), darkMat)
1629
- base.position.y = 0.16
1630
- // 天线 + 信号球
1631
- const antenna = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.03, 0.5, 6), darkMat)
1632
- antenna.position.set(0, 2.04, 0)
1633
- const signal = new THREE.Mesh(new THREE.SphereGeometry(0.08, 10, 8), coreMat)
1634
- signal.position.set(0, 2.34, 0)
1635
- bot.add(body, head, visor, core, base, antenna, signal)
1636
- return bot
1637
- }
322
+ agentAnimClips = new Map<string, THREE.AnimationClip[]>()
1638
323
 
1639
324
  /** 注册模型清单,并将晚到资产挂载到已存在的 Agent/设备。 */
1640
325
  registerModelsFromList(list: Array<{ id: string, file: string, name: string, kind?: string, hFactor?: number }>): void {
1641
- if (this.disposed) return
1642
- const changed = new Set<string>()
1643
- for (const model of list) {
1644
- const previous = this.modelsById.get(model.id)
1645
- if (!previous || previous.file !== model.file || previous.name !== model.name || previous.kind !== model.kind || previous.hFactor !== (model.hFactor ?? 1)) {
1646
- this.modelsById.set(model.id, { ...model, hFactor: model.hFactor ?? 1 })
1647
- changed.add(model.id)
1648
- }
1649
- }
1650
- if (changed.size > 0) {
1651
- for (const agent of this.agents.values()) {
1652
- if (!agent.modelRef || !changed.has(agent.modelRef)) continue
1653
- const model = this.modelsById.get(agent.modelRef)
1654
- if (model) void this.mountModel(agent, model.file, model.name)
1655
- }
1656
- for (const device of this.deviceNodes.values()) {
1657
- if (changed.has(device.modelRef)) this.swapDeviceModelSprite(device, device.modelRef)
1658
- }
1659
- }
1660
- for (const twin of [...this.pendingDeviceTwins.values()]) {
1661
- const isDaq = twin.kind === 'daq' || (twin.modelRef ?? '').startsWith('daq-')
1662
- if (!isDaq && !this.modelsById.get(twin.modelRef)?.file) continue
1663
- this.pendingDeviceTwins.delete(twin.id)
1664
- this.recreateDeviceNode(twin)
1665
- }
326
+ registerModelsFromList(this, list)
1666
327
  }
1667
328
 
1668
329
  // ================================================================
1669
- // 布局注入 / 重建 / 聚焦 / 事件入口(公开面与 2D 镜像)
330
+ // 布局注入 / 重建 / 聚焦 / 事件入口(公开面与 2D 镜像;
331
+ // 领地层实现见 scene/town-scene3d-blocks.ts)
1670
332
  // ================================================================
1671
333
 
1672
334
  /**
1673
335
  * 注入频道领地布局(来自 useSceneLayouts)。构建场景前必须调用;
1674
336
  * 未放置的频道会在 rebuild 时被跳过(只停留在频道坞)。
1675
337
  */
1676
- applySceneLayouts(layouts: ChannelLayout[]): void {
1677
- this.layouts.clear()
1678
- for (const l of layouts) {
1679
- const n = normLayout(l)
1680
- this.layouts.set(n.channelId, n)
1681
- }
1682
- }
338
+ applySceneLayouts(layouts: ChannelLayout[]): void { applySceneLayouts(this, layouts) }
1683
339
 
1684
340
  /** 频道是否已放入场景(供频道坞标记已放置/未放置) */
1685
- hasChannel(channelId: string): boolean {
1686
- return this.blocks.has(channelId)
1687
- }
341
+ hasChannel(channelId: string): boolean { return hasChannel(this, channelId) }
1688
342
 
1689
343
  /** 频道当前布局(供边界约束;未放置返回空) */
1690
- private blockLayout(channelId: string): ChannelLayout | null {
1691
- const b = this.blocks.get(channelId)
1692
- return b ? b.layout() : null
1693
- }
344
+ blockLayout(channelId: string): ChannelLayout | null { return blockLayout(this, channelId) }
1694
345
 
1695
346
  /** 已放入场景的频道集(供频道坞/选中面板) */
1696
- placedChannels(): string[] {
1697
- return [...this.blocks.keys()]
1698
- }
347
+ placedChannels(): string[] { return placedChannels(this) }
1699
348
 
1700
349
  /** 频道当前布局(供边界编辑面板初始化) */
1701
- getChannelLayout(channelId: string): ChannelLayout | null {
1702
- const b = this.blocks.get(channelId)
1703
- return b ? b.layout() : null
1704
- }
350
+ getChannelLayout(channelId: string): ChannelLayout | null { return getChannelLayout(this, channelId) }
1705
351
 
1706
352
  // ================================================================
1707
353
  // 控制器公开面(供实体 class 反向调用:场景图/脏标记/布局表/设备名牌与模型)
@@ -1718,29 +364,13 @@ export class TownScene3D {
1718
364
  }
1719
365
 
1720
366
  /** 记录频道当前布局到场景布局表(移动/缩放后由 Block3D 调用) */
1721
- trackLayout(b: Block3D): void {
1722
- this.layouts.set(b.channelId, b.normLayout())
1723
- }
367
+ trackLayout(b: Block3D): void { trackLayout(this, b) }
1724
368
 
1725
- /** 设备改名:重建名牌 Sprite(由 DeviceNode.applyTwin 委托) */
1726
- renameDeviceSprite(dev: DeviceNode, name: string): void {
1727
- dev.name = name.trim()
1728
- this.scene.remove(dev.label)
1729
- dev.label = this.makeLabel(`⚙ ${dev.name}`, dev.root.position.x, 60, dev.root.position.z)
1730
- this.dirty = true
1731
- }
369
+ /** 设备改名:重建名牌 Sprite(由 DeviceNode.applyTwin 委托;实现见 scene/town-scene3d-devices.ts) */
370
+ renameDeviceSprite(dev: DeviceNode, name: string): void { renameDeviceSprite(this, dev, name) }
1732
371
 
1733
- /** 设备换模型:按 modelRef 重挂 GLB 到 holder(由 DeviceNode.applyTwin 委托)。 */
1734
- swapDeviceModelSprite(dev: DeviceNode, modelRef: string): void {
1735
- const info = this.modelsById.get(modelRef)
1736
- if (!info?.file) return
1737
- if (dev.modelRef === modelRef && dev.holder.userData.modelFile === info.file) return
1738
- dev.modelRef = modelRef
1739
- dev.holder.clear()
1740
- dev.holder.userData.modelFile = info.file
1741
- void this.loadGltfToGroup(info.file, dev.holder, UNITS * 1.6)
1742
- this.dirty = true
1743
- }
372
+ /** 设备换模型:按 modelRef 重挂 GLB 到 holder(由 DeviceNode.applyTwin 委托) */
373
+ swapDeviceModelSprite(dev: DeviceNode, modelRef: string): void { swapDeviceModelSprite(this, dev, modelRef) }
1744
374
 
1745
375
  /** 统一实例化入口:按数据库元数据(布局/实体/设备孪生)实例化并初始化场景内全部实例。 */
1746
376
  hydrate(channels: TownEntityInput[], layouts: ChannelLayout[], devices: DeviceTwinSync[]): void {
@@ -1765,7 +395,7 @@ export class TownScene3D {
1765
395
  }
1766
396
 
1767
397
  // ================================================================
1768
- // 频道放置(drop 入口)+ 活动边界编辑
398
+ // 频道放置(drop 入口)+ 活动边界编辑(实现见 scene/town-scene3d-blocks.ts)
1769
399
  // ================================================================
1770
400
 
1771
401
  /**
@@ -1773,285 +403,61 @@ export class TownScene3D {
1773
403
  * 频道 id 需已在实体基线中;返回落点世界坐标(供 HUD 提示)。
1774
404
  */
1775
405
  dropChannelOnWorld(x: number, z: number, channelId: string, channelName: string, agentCount: number): { x: number, z: number, channelId: string, name: string } {
1776
- const existing = this.blocks.get(channelId)
1777
- if (existing) {
1778
- this.focusTo(existing.x, existing.z)
1779
- return { x: Math.round(existing.x), z: Math.round(existing.z), channelId, name: existing.name }
1780
- }
1781
- // 缺省布局:以落点为中心、与 Agent 数匹配的边界
1782
- const rx = Math.max(120, 110 + agentCount * 18)
1783
- const rz = Math.max(80, 70 + agentCount * 14)
1784
- const layout: ChannelLayout = { channelId, x, z, radiusX: rx, radiusZ: rz, shape: 'ellipse', rotationY: 0 }
1785
- this.layouts.set(channelId, normLayout(layout))
1786
- // 从实体基线取该频道 agents(由 TownView ensureChannelPresent 提前放置)
1787
- const seed = this.entityIndex.get(channelId)
1788
- if (seed) {
1789
- this.placeChannel(seed, layout)
1790
- }
1791
- else {
1792
- // 无实体基线(频道坞拖入,数据尚未到达):只建空领地占位
1793
- const color = channelColorNum(channelId)
1794
- const pad = this.makeBlock(channelName, color, layout)
1795
- const block = new Block3D({
1796
- channelId, name: channelName, x, z,
1797
- radiusX: layout.radiusX, radiusZ: layout.radiusZ,
1798
- shape: layout.shape, rotationY: layout.rotationY, color,
1799
- platform: pad.platform,
1800
- padRing: pad.padRing,
1801
- beacon: pad.beacon,
1802
- boundary: makeBoundary(layout.shape, layout.radiusX, layout.radiusZ, color),
1803
- label: this.makeLabel(channelName, x, 30, z),
1804
- })
1805
- block.host = this
1806
- block.boundary.position.set(x, 0.3, z)
1807
- this.scene.add(block.boundary)
1808
- this.blocks.set(channelId, block)
1809
- }
1810
- this.emit('blockCount', this.blocks.size)
1811
- this.emit('agentCount', this.agents.size)
1812
- return { x: Math.round(x), z: Math.round(z), channelId, name: channelName }
406
+ return dropChannelOnWorld(this, x, z, channelId, channelName, agentCount)
1813
407
  }
1814
408
 
1815
409
  /** 更新频道布局(编辑边界后本地即时生效;持久化由 TownView 经 useSceneLayouts.save 落库) */
1816
- updateChannelLayout(channelId: string, patch: Partial<ChannelLayout>): void {
1817
- const b = this.blocks.get(channelId)
1818
- if (!b) return
1819
- if (patch.x !== undefined) b.x = patch.x
1820
- if (patch.z !== undefined) b.z = patch.z
1821
- if (patch.radiusX !== undefined) b.radiusX = Math.max(60, patch.radiusX)
1822
- if (patch.radiusZ !== undefined) b.radiusZ = Math.max(40, patch.radiusZ)
1823
- if (patch.shape !== undefined) b.shape = patch.shape
1824
- if (patch.rotationY !== undefined) b.rotationY = patch.rotationY
1825
- const layout = normLayout({ channelId, x: b.x, z: b.z, radiusX: b.radiusX, radiusZ: b.radiusZ, shape: b.shape, rotationY: b.rotationY })
1826
- this.layouts.set(channelId, layout)
1827
- // 重建领地平台/边界/名牌
1828
- this.scene.remove(b.boundary)
1829
- this.applyPadToBlock(b)
1830
- b.boundary = makeBoundary(layout.shape, layout.radiusX, layout.radiusZ, b.color)
1831
- b.boundary.position.set(layout.x, 0.3, layout.z)
1832
- b.boundary.rotation.y = layout.rotationY * Math.PI / 180
1833
- this.scene.add(b.boundary)
1834
- // 频道边界变化后把成员活动范围一并收进新边界(面板滑杆编辑路径)
1835
- this.clampAgentsToBoundary(channelId)
1836
- this.dirty = true
1837
- }
410
+ updateChannelLayout(channelId: string, patch: Partial<ChannelLayout>): void { updateChannelLayout(this, channelId, patch) }
1838
411
 
1839
412
  /** 移除频道放置(从场景撤走领地及其 Agent) */
1840
- removeChannel(channelId: string): void {
1841
- const b = this.blocks.get(channelId)
1842
- if (!b) return
1843
- this.scene.remove(b.platform)
1844
- this.scene.remove(b.padRing)
1845
- this.scene.remove(b.beacon)
1846
- if (b.boundary) this.scene.remove(b.boundary)
1847
- this.scene.remove(b.label)
1848
- for (const [aid, a] of [...this.agents.entries()]) {
1849
- if (a.channelId === channelId) {
1850
- this.scene.remove(a.root)
1851
- if (a.nameSprite) this.scene.remove(a.nameSprite)
1852
- if (a.bubble) this.scene.remove(a.bubble)
1853
- if (a.rangeLine) this.scene.remove(a.rangeLine)
1854
- this.disposeAgentAssets(a)
1855
- this.agents.delete(aid)
1856
- }
1857
- }
1858
- if (b.label) this.disposeCanvasTextures(b.label)
1859
- if (this.rangeDrawAgent && this.agents.get(this.rangeDrawAgent) === undefined) this.cancelRangeDraw()
1860
- this.blocks.delete(channelId)
1861
- this.layouts.delete(channelId)
1862
- this.receivers.delete(channelId)
1863
- for (const hl of this.resizeHandles) hl.mesh.visible = false
1864
- for (const hl of this.agentRangeHandles) hl.mesh.visible = false
1865
- if (this.selectedChannel === channelId) {
1866
- this.selectedChannel = null
1867
- this.emit('selectChannel', null)
1868
- }
1869
- this.emit('blockCount', this.blocks.size)
1870
- this.emit('agentCount', this.agents.size)
1871
- this.dirty = true
1872
- }
413
+ removeChannel(channelId: string): void { removeChannel(this, channelId) }
1873
414
 
1874
415
  // ================================================================
1875
416
  // 频道整体拖拽 / 边界手柄缩放(编辑模式;用户自定义布局)
417
+ // 实现见 scene/town-scene3d-editing.ts
1876
418
  // ================================================================
1877
419
 
1878
420
  /** 把频道地块整体平移(委托 Block3D.moveBy:平台/边界/名牌/成员落点与各自范围一并位移) */
1879
- private applyBlockMove(b: Block3D, nx: number, nz: number): void {
1880
- b.moveBy(nx - b.x, nz - b.z)
1881
- this.refreshAgentRangeHandles()
1882
- this.dirty = true
1883
- }
421
+ applyBlockMove(b: Block3D, nx: number, nz: number): void { applyBlockMove(this, b, nx, nz) }
1884
422
 
1885
423
  /** 按当前 Block 字段重建领地几何(平台刻度/边界线框/朝向),供缩放与移动共用 */
1886
- private applyLayoutToBlock(b: Block3D): void {
1887
- const layout = { channelId: b.channelId, x: b.x, z: b.z, radiusX: b.radiusX, radiusZ: b.radiusZ, shape: b.shape, rotationY: b.rotationY }
1888
- this.layouts.set(b.channelId, normLayout(layout))
1889
- b.platform.position.set(b.x, 0.16, b.z)
1890
- b.padRing.position.set(b.x, 0.34, b.z)
1891
- this.applyPadToBlock(b)
1892
- if (b.boundary) this.scene.remove(b.boundary)
1893
- b.boundary = makeBoundary(b.shape, b.radiusX, b.radiusZ, b.color)
1894
- b.boundary.position.set(b.x, 0.3, b.z)
1895
- b.boundary.rotation.y = b.rotationY * Math.PI / 180
1896
- this.scene.add(b.boundary)
1897
- this.dirty = true
1898
- }
424
+ applyLayoutToBlock(b: Block3D): void { applyLayoutToBlock(this, b) }
1899
425
 
1900
- /** 边界手柄本地坐标(椭圆:轴向四点;矩形:四角;radius 是半轴或半宽)。 */
1901
- private boundaryHandlePoints(layout: { radiusX: number, radiusZ: number, shape: 'ellipse' | 'rect' }): Array<[number, number]> {
1902
- if (layout.shape === 'rect') return [[layout.radiusX, layout.radiusZ], [-layout.radiusX, layout.radiusZ], [-layout.radiusX, -layout.radiusZ], [layout.radiusX, -layout.radiusZ]]
1903
- return [[layout.radiusX, 0], [-layout.radiusX, 0], [0, layout.radiusZ], [0, -layout.radiusZ]]
1904
- }
426
+ /** 边界手柄本地坐标(椭圆:轴向四点;矩形:四角;radius 是半轴或半宽)。宿主契约面:ranges 模块回调。 */
427
+ boundaryHandlePoints(layout: { radiusX: number, radiusZ: number, shape: 'ellipse' | 'rect' }): Array<[number, number]> { return boundaryHandlePoints(layout) }
1905
428
 
1906
429
  /** 刷新边界手柄位置/可见性(编辑模式选中频道时显示;其余隐藏) */
1907
- private refreshChannelHandles(): void {
1908
- for (const hl of this.resizeHandles) hl.mesh.visible = false
1909
- if (this.mode !== 'edit' || !this.selectedChannel) return
1910
- const b = this.blocks.get(this.selectedChannel)
1911
- if (!b) return
1912
- const rot = b.rotationY * Math.PI / 180
1913
- const pts = this.boundaryHandlePoints(b)
1914
- for (let i = 0; i < this.resizeHandles.length && i < pts.length; i++) {
1915
- const [lx, lz] = pts[i]!
1916
- const wx = b.x + lx * Math.cos(rot) - lz * Math.sin(rot)
1917
- const wz = b.z + lx * Math.sin(rot) + lz * Math.cos(rot)
1918
- this.resizeHandles[i]!.cid = b.channelId
1919
- this.resizeHandles[i]!.mesh.position.set(wx, 1.4, wz)
1920
- this.resizeHandles[i]!.mesh.visible = true
1921
- }
1922
- }
430
+ refreshChannelHandles(): void { refreshChannelHandles(this) }
1923
431
 
1924
432
  /** 命中边界手柄(仅编辑模式;返回频道 id + 手柄号) */
1925
- private pickResizeHandle(x: number, z: number): { cid: string, handle: number } | null {
1926
- if (this.mode !== 'edit') return null
1927
- for (const hl of this.resizeHandles) {
1928
- if (!hl.mesh.visible) continue
1929
- if (Math.hypot(hl.mesh.position.x - x, hl.mesh.position.z - z) < 90) return { cid: hl.cid, handle: hl.handle }
1930
- }
1931
- return null
1932
- }
433
+ pickResizeHandle(x: number, z: number): { cid: string, handle: number } | null { return pickResizeHandle(this, x, z) }
1933
434
 
1934
435
  /** 拖拽手柄 → 实时调整 radiusX/radiusZ(矩形:角点双轴;椭圆:对应轴向轴点)。 */
1935
- private applyResize(b: Block3D, handle: number, wx: number, wz: number): void {
1936
- const rad = -b.rotationY * Math.PI / 180
1937
- const cos = Math.cos(rad)
1938
- const sin = Math.sin(rad)
1939
- const dx = wx - b.x
1940
- const dz = wz - b.z
1941
- const lx = dx * cos - dz * sin
1942
- const lz = dx * sin + dz * cos
1943
- if (b.shape === 'rect') {
1944
- // radiusX/radiusZ 表示半宽:角点到中心的局部距离即新半宽(无上限,相机自动跟随取景)
1945
- b.radiusX = Math.max(60, Math.abs(lx))
1946
- b.radiusZ = Math.max(40, Math.abs(lz))
1947
- }
1948
- else {
1949
- if (handle === 0 || handle === 1) b.radiusX = Math.max(60, Math.abs(lx))
1950
- else b.radiusZ = Math.max(40, Math.abs(lz))
1951
- }
1952
- this.applyLayoutToBlock(b)
1953
- this.refreshChannelHandles()
1954
- this.autoFrameTo(b.radiusX, b.radiusZ)
1955
- this.dirty = true
1956
- }
436
+ applyResize(b: Block3D, handle: number, wx: number, wz: number): void { applyResize(this, b, handle, wx, wz) }
1957
437
 
1958
438
  /** 缩放/移动结束后把该频道成员落点与活动范围钳回新边界内(委托 Block3D) */
1959
- private clampAgentsToBoundary(channelId: string): void {
1960
- const b = this.blocks.get(channelId)
1961
- if (!b) return
1962
- b.clampMembersAndRanges()
1963
- this.dirty = true
1964
- }
439
+ clampAgentsToBoundary(channelId: string): void { clampAgentsToBoundary(this, channelId) }
1965
440
 
1966
441
  /** 频道是否在场景内且被选中(供边界编辑面板) */
1967
- getSelectedChannel(): string | null {
1968
- return this.selectedChannel
1969
- }
442
+ getSelectedChannel(): string | null { return getSelectedChannel(this) }
1970
443
 
1971
444
  /** 点选频道(供频道坞/边界编辑面板打开) */
1972
- selectChannel(channelId: string | null): void {
1973
- this.selectedChannel = channelId
1974
- this.emit('selectChannel', channelId)
1975
- this.refreshChannelHandles()
1976
- this.cancelRangeDraw()
1977
- this.refreshAgentRangeHandles()
1978
- // 选中频道边界高亮勾边(取消时恢复频道本色)
1979
- for (const bb of this.blocks.values()) {
1980
- const m = bb.boundary.material as THREE.LineBasicMaterial
1981
- m.color.setHex(bb.channelId === channelId ? 0xf6c453 : bb.color)
1982
- m.opacity = bb.channelId === channelId ? 1 : 0.75
1983
- }
1984
- // 拖拽中不聚焦(避免拖频道/手柄时相机被拽走导致落点错乱)
1985
- if (channelId && !this.pointerDrag) this.focusTo(this.blocks.get(channelId)?.x ?? WORLD_CX, this.blocks.get(channelId)?.z ?? WORLD_CZ)
1986
- }
445
+ selectChannel(channelId: string | null): void { selectChannel(this, channelId) }
1987
446
 
1988
447
  /** 当前场景内全部频道布局(供「保存全部布局」/E2E) */
1989
- getAllChannelLayouts(): ChannelLayout[] {
1990
- return [...this.blocks.values()].map(b => ({
1991
- channelId: b.channelId, x: b.x, z: b.z,
1992
- radiusX: b.radiusX, radiusZ: b.radiusZ,
1993
- shape: b.shape, rotationY: b.rotationY,
1994
- }))
1995
- }
448
+ getAllChannelLayouts(): ChannelLayout[] { return getAllChannelLayouts(this) }
1996
449
 
1997
- handleTownEvent(e: AepEnvelope): void {
1998
- if (e.type === 'channel.snapshot') return
1999
- // 频道布局事件:本地已放同一频道则即时应用(他人编辑边界/移入场景 → 本端同步)
2000
- if (e.type === 'scene.layout.saved') {
2001
- const l = e.payload as ChannelLayout
2002
- if (this.blocks.has(l.channelId)) {
2003
- this.applySceneLayouts([...(this.layouts.values()), l])
2004
- // 更新对应领地几何
2005
- const cur = this.getChannelLayout(l.channelId)
2006
- if (cur) this.updateChannelLayout(l.channelId, l)
2007
- }
2008
- return
2009
- }
2010
- if (e.type === 'scene.layout.removed') {
2011
- const { channelId } = e.payload as { channelId: string }
2012
- this.removeChannel(channelId)
2013
- return
2014
- }
2015
- const intent = mapEnvelopeToIntent(e)
2016
- if (!intent) return
2017
- if (intent.agentId) {
2018
- const asp = this.agents.get(intent.agentId)
2019
- if (asp) {
2020
- if (e.type === 'agent.status') {
2021
- asp.state = (e.payload as { state: 'idle' | 'busy' | 'stopped' }).state
2022
- if (asp.state !== 'busy') asp.progress = null
2023
- }
2024
- if (e.type === 'task.progress') asp.progress = (e.payload as { progress: number }).progress
2025
- this.dirty = true
2026
- }
2027
- }
2028
- if (intent.bubble) this.enqueueBubble(intent.bubble.channelId, intent.bubble.agentId, intent.bubble.kind, intent.bubble.text, intent.bubble.ttlMs)
2029
- this.emitResonance(e)
2030
- const action = parseActionFromEnvelope(e, { resolveTaskAssignee: this.resolveTaskAssignee ?? undefined })
2031
- if (action) this.startBehavior(action)
2032
- }
450
+ /** WS 事件入口(与 2D 同构):布局同步 / 状态收敛 / 气泡入队 / 共鸣 / 行为决策(实现见 scene/town-scene3d-events.ts) */
451
+ handleTownEvent(e: AepEnvelope): void { handleTownEvent(this, e) }
2033
452
 
2034
453
  // ================================================================
2035
- // 行为 FSM(3D x/z 平面,复刻 2D 逻辑)
454
+ // 行为 FSM(3D x/z 平面,复刻 2D 逻辑;实现见 scene/town-scene3d-events.ts)
2036
455
  // ================================================================
2037
456
 
2038
- private startBehavior(action: ActionContext): void {
2039
- const from = this.agents.get(action.fromId)
2040
- const to = this.agents.get(action.toId)
2041
- if (!from || !to || from.dragging) return
2042
- from.behavior.mode = 'approach'
2043
- from.behavior.targetId = to.agentId
2044
- from.behavior.action = action
2045
- if (!to.dragging) {
2046
- to.behavior.engaged = true
2047
- to.behavior.roamTarget = null
2048
- }
2049
- this.emit('behavior', { agentName: from.name, action: this.behaviorActionLabel(action.kind), targetName: to.name })
2050
- }
457
+ /** 触发一次「跑去下发」行为:from 跑到 to 身边 */
458
+ startBehavior(action: ActionContext): void { startBehavior(this, action) }
2051
459
 
2052
- private behaviorActionLabel(kind: ActionKind): string {
2053
- return kind === 'task' ? '下发任务' : '回复'
2054
- }
460
+ behaviorActionLabel(kind: ActionKind): string { return behaviorActionLabel(kind) }
2055
461
 
2056
462
  /** 动画状态切换广播(数据驱动模型 → HUD/E2E 观察) */
2057
463
  notifyMotion(asp: Agent3D): void {
@@ -2082,348 +488,43 @@ export class TownScene3D {
2082
488
  return this.blockLayout(channelId)
2083
489
  }
2084
490
 
2085
- private resolveFile(id: string): string {
2086
- return this.modelsById.get(id)?.file ?? ''
2087
- }
2088
-
2089
491
  /** 相机注视目标(供频道坞首放落点/聚焦) */
2090
- getCameraTarget(): { x: number, z: number } {
2091
- return { x: Math.round(this.camTarget.x), z: Math.round(this.camTarget.z) }
2092
- }
492
+ getCameraTarget(): { x: number, z: number } { return getCameraTarget(this) }
2093
493
 
2094
494
  /** 为指定角色换装模型(选择器绑定;要求实体已在场景,否则仅记录) */
2095
- swapAgentModel(agentId: string, modelRef: string): void {
2096
- const asp = this.agents.get(agentId)
2097
- if (!asp) return
2098
- const info = this.modelsById.get(modelRef)
2099
- const file = info?.file ?? '/assets/game/character/hero-3d.glb'
2100
- asp.modelRef = modelRef
2101
- asp.textureKey = modelRef
2102
- void this.mountModel(asp, file, info?.name ?? modelRef)
2103
- }
495
+ swapAgentModel(agentId: string, modelRef: string): void { swapAgentModel(this, agentId, modelRef) }
2104
496
 
2105
497
  // ================================================================
2106
498
  // 事件共鸣(说话/完成/错误/旗) —— 3D 圆环/光柱
499
+ // 实现见 scene/town-scene3d-events.ts
2107
500
  // ================================================================
2108
501
 
2109
- private emitResonance(e: AepEnvelope): void {
2110
- const b = this.blocks.get(e.channelId)
2111
- const asp = e.agentId ? this.agents.get(e.agentId) : undefined
2112
- const x = asp?.root.position.x ?? b?.x
2113
- const z = asp?.root.position.z ?? b?.z
2114
- if (x === undefined || z === undefined) return
2115
- if (e.type === 'agent.message' || e.type === 'agent.status.message' || e.type === 'a2a.message') {
2116
- this.pulseRing(x, z, asp ? 0x41c8f4 : (b?.color ?? 0x41c8f4))
2117
- }
2118
- else if (e.type === 'error' || (e.type === 'task.status' && ((e.payload as { state?: string }).state === 'failed' || (e.payload as { state?: string }).state === 'canceled'))) {
2119
- this.pulseRing(x, z, 0xff6b6b)
2120
- }
2121
- else if (e.type === 'task.status' && (e.payload as { state?: string }).state === 'completed') {
2122
- this.lightColumn(x, z, 0x35e0a0)
2123
- }
2124
- }
2125
-
2126
- /** 事件共鸣扩散环(时间基:800ms 缓出扩散 + 淡出;帧率无关,结束即释放资源) */
2127
- private pulseRing(x: number, z: number, color: number): void {
2128
- const ring = new THREE.Mesh(new THREE.RingGeometry(10, 22, 24), new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.8, side: THREE.DoubleSide, depthWrite: false }))
2129
- ring.rotation.x = -Math.PI / 2
2130
- ring.position.set(x, 0.4, z)
2131
- this.scene.add(ring)
2132
- const start = performance.now()
2133
- const dur = 800
2134
- const step = () => {
2135
- const k = Math.min(1, (performance.now() - start) / dur)
2136
- const e = 1 - Math.pow(1 - k, 3)
2137
- ring.scale.setScalar(1 + e * 1.6)
2138
- ;(ring.material as THREE.MeshBasicMaterial).opacity = 0.8 * (1 - e)
2139
- if (k >= 1) {
2140
- this.scene.remove(ring)
2141
- ring.geometry.dispose()
2142
- ;(ring.material as THREE.MeshBasicMaterial).dispose()
2143
- return
2144
- }
2145
- this.rafAnims.push(step)
2146
- }
2147
- this.rafAnims.push(step)
2148
- }
2149
-
2150
- /** 交付光柱(时间基:底部锚定向上生长 + 淡出;几何上移锚定,修复旧零高度柱不可见的缺陷) */
2151
- private lightColumn(x: number, z: number, color: number): void {
2152
- const geo = new THREE.CylinderGeometry(6, 12, 120, 16)
2153
- geo.translate(0, 60, 0)
2154
- const beam = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.8, depthWrite: false }))
2155
- beam.position.set(x, 0, z)
2156
- beam.scale.y = 0.01
2157
- this.scene.add(beam)
2158
- const start = performance.now()
2159
- const dur = 900
2160
- const step = () => {
2161
- const k = Math.min(1, (performance.now() - start) / dur)
2162
- const e = 1 - Math.pow(1 - k, 2)
2163
- beam.scale.y = 0.01 + e * 0.99
2164
- ;(beam.material as THREE.MeshBasicMaterial).opacity = 0.8 * (1 - k)
2165
- if (k >= 1) {
2166
- this.scene.remove(beam)
2167
- beam.geometry.dispose()
2168
- ;(beam.material as THREE.MeshBasicMaterial).dispose()
2169
- return
2170
- }
2171
- this.rafAnims.push(step)
2172
- }
2173
- this.rafAnims.push(step)
2174
- }
502
+ emitResonance(e: AepEnvelope): void { emitResonance(this, e) }
2175
503
 
2176
- private rafAnims: Array<() => void> = []
504
+ rafAnims: Array<() => void> = []
2177
505
 
2178
506
  /** 频道领地布局(channelId → 放置);由 applySceneLayouts 注入,驱动 buildBlocks/边界约束 */
2179
- private layouts = new Map<string, ChannelLayout>()
507
+ layouts = new Map<string, ChannelLayout>()
2180
508
  /** 频道实体基线(channelId → seed;供 dropChannelOnWorld 即时铺放) */
2181
- private entityIndex = new Map<string, TownEntityInput>()
509
+ entityIndex = new Map<string, TownEntityInput>()
2182
510
  /** 当前选中频道(供边界编辑面板) */
2183
- private selectedChannel: string | null = null
511
+ selectedChannel: string | null = null
2184
512
 
2185
513
  // ================================================================
2186
514
  // 信息接收器 + 聊天气泡(每个实例化 Channel 一个接收器:FIFO 逐条消费,
2187
515
  // WS 实时信息经 townBus → handleTownEvent 入队,渲染到对应 Agent 头顶,像真实对话)
516
+ // 实现见 scene/town-scene3d-bubbles.ts
2188
517
  // ================================================================
2189
518
 
2190
519
  /** 实时信息(讲话/交付/错误)入队到目标频道的信息接收器;系统指标即时更新,3D 展示按 FIFO 消费 */
2191
- private enqueueBubble(channelId: string, agentId: string | undefined, kind: TownBubbleKind, text: string, ttlMs: number): void {
2192
- // 事件流 / 最近活动 / 调试气泡即时更新(展示延迟只作用于 3D 气泡)
2193
- this.dbgBubbles.push({ text, at: Date.now() })
2194
- if (this.dbgBubbles.length > 30) this.dbgBubbles.splice(0, this.dbgBubbles.length - 30)
2195
- const speaker = agentId ? this.agents.get(agentId)?.name : undefined
2196
- this.lastActivity = { channelId, agentName: speaker ?? this.blocks.get(channelId)?.name ?? '系统', text, at: Date.now() }
2197
- this.recentActivity.push(this.lastActivity)
2198
- if (this.recentActivity.length > 30) this.recentActivity.splice(0, this.recentActivity.length - 30)
2199
- this.emit('lastActivity', this.lastActivity)
2200
- // 入队(FIFO;超长队列丢最旧,防止异常风暴撑爆内存)
2201
- let rec = this.receivers.get(channelId)
2202
- if (!rec) {
2203
- rec = { channelId, queue: [], current: null, currentUntil: 0 }
2204
- this.receivers.set(channelId, rec)
2205
- }
2206
- if (rec.queue.length >= 24) rec.queue.splice(0, rec.queue.length - 24)
2207
- rec.queue.push({ agentId: agentId ?? null, kind, text, ttlMs })
2208
- this.dirty = true
520
+ enqueueBubble(channelId: string, agentId: string | undefined, kind: TownBubbleKind, text: string, ttlMs: number): void {
521
+ enqueueBubble(this, channelId, agentId, kind, text, ttlMs)
2209
522
  }
2210
523
 
2211
524
  /** 各频道接收器 FIFO 消费:当前条目展示期满 → 清理其气泡并取下一条实时渲染(渲染循环每帧调用)。
2212
525
  * 队列积压时按 drainDisplayMs 压缩单条时长,让爆发期消息尽快追平。 */
2213
- private drainReceivers(now: number): void {
2214
- for (const rec of this.receivers.values()) {
2215
- if (rec.current) {
2216
- if (now < rec.currentUntil) continue
2217
- this.clearReceiverBubble(rec.channelId, rec.current)
2218
- rec.current = null
2219
- }
2220
- const msg = rec.queue.shift()
2221
- if (!msg) continue
2222
- rec.current = msg
2223
- rec.currentUntil = now + drainDisplayMs(msg.text, rec.queue.length)
2224
- this.renderReceiverBubble(rec.channelId, msg)
2225
- }
2226
- }
2227
-
2228
- /** 清除接收器条目对应的场景气泡(不同说话人衔接时避免叠泡) */
2229
- private clearReceiverBubble(channelId: string, msg: BubbleMsg): void {
2230
- if (!msg.agentId) return
2231
- const asp = this.agents.get(msg.agentId)
2232
- if (asp?.bubble) {
2233
- this.scene.remove(asp.bubble)
2234
- asp.bubble = null
2235
- asp.bubbleText = null
2236
- if (asp.bubbleTimer) {
2237
- clearTimeout(asp.bubbleTimer)
2238
- asp.bubbleTimer = null
2239
- }
2240
- }
2241
- }
2242
-
2243
- /** 将接收器消息渲染为 2.5D 聊天气泡(说话 Agent 头顶;无对应用户则挂频道名牌上方) */
2244
- private renderReceiverBubble(channelId: string, msg: BubbleMsg): void {
2245
- const asp = msg.agentId ? this.agents.get(msg.agentId) : undefined
2246
- const b = this.blocks.get(channelId)
2247
- // 频道未放置且无对应用户 → 暂不渲染(队列保留,放置/用户出现后补显)
2248
- if (!asp && !b) return
2249
- const name = asp?.name ?? b?.name ?? '系统'
2250
- const accent = asp
2251
- ? asp.colorNum
2252
- : (b?.color ?? 0x41c8f4)
2253
- const text = (msg.kind === 'artifact' && !msg.text.startsWith('📦') ? `📦 ${msg.text}` : msg.text) || '…'
2254
- const sprite = this.makeChatBubble(text, name, accent, msg.kind)
2255
- const x = asp?.root.position.x ?? b!.x
2256
- const z = asp?.root.position.z ?? b!.z
2257
- // 放大气泡后锚点整体抬升(避免压到 48 处名牌)
2258
- sprite.position.set(x, asp ? BUBBLE_Y + 22 + Math.max(0, asp.model.scale.y - 1) * 22 : 64, z)
2259
- this.scene.add(sprite)
2260
- if (!asp) {
2261
- // 频道级(系统)气泡:展示期满自行移除
2262
- window.setTimeout(() => {
2263
- this.scene.remove(sprite)
2264
- }, bubbleDisplayMs(msg.text) + 500)
2265
- return
2266
- }
2267
- if (asp.bubble) {
2268
- this.scene.remove(asp.bubble)
2269
- this.disposeCanvasTextures(asp.bubble)
2270
- asp.bubbleText = null
2271
- }
2272
- asp.bubble = sprite
2273
- asp.bubbleText = text
2274
- this.popInSprite(sprite)
2275
- this.dirty = true
2276
- }
2277
-
2278
- /** 2.5D 聊天气泡:工业 HMI 风格 —— 头部身份条(等宽名牌 + 类型印章)+ 多行正文 + 尾角 */
2279
- private makeChatBubble(text: string, name: string, accent: number, kind: TownBubbleKind): THREE.Sprite {
2280
- const accentHex = `#${accent.toString(16).padStart(6, '0')}`
2281
- const canvas = document.createElement('canvas')
2282
- const ctx = canvas.getContext('2d')!
2283
- // 超大字号会话气泡:正文 18px / 头名 13px / 最多 6 行 / 宽 ≤ 680px —— 实时信息一眼可读
2284
- const bodyFont = '18px Geist, "PingFang SC", sans-serif'
2285
- const headFont = '600 13px "Geist Mono", Geist, "PingFang SC", monospace'
2286
- const tagFont = '600 10px "Geist Mono", Geist, monospace'
2287
- const maxTextW = 470
2288
- const padX = 18
2289
- const padY = 12
2290
- const nameH = 26
2291
- const lineH = 24
2292
- const tailH = 13
2293
- ctx.font = bodyFont
2294
- const lines = this.wrapBubbleLines(ctx, text.replace(/\s+/g, ' ').trim() || '…', maxTextW, 6)
2295
- const textW = Math.max(...lines.map(l => ctx.measureText(l).width))
2296
- const bw = Math.min(680, Math.max(140, Math.ceil(textW) + padX * 2))
2297
- const bodyH = nameH + lines.length * lineH + padY * 2
2298
- const bh = bodyH + tailH
2299
- canvas.width = bw
2300
- canvas.height = bh
2301
- ctx.clearRect(0, 0, bw, bh)
2302
- // 方角矩形 + 底部中央尾角(指向说话人;HMI 直角配方)
2303
- const r = 4
2304
- const cx = bw / 2
2305
- const t = Math.min(24, bw * 0.32)
2306
- const path = () => {
2307
- ctx.beginPath()
2308
- ctx.moveTo(r, 0)
2309
- ctx.lineTo(bw - r, 0)
2310
- ctx.quadraticCurveTo(bw, 0, bw, r)
2311
- ctx.lineTo(bw, bodyH - r)
2312
- ctx.quadraticCurveTo(bw, bodyH, bw - r, bodyH)
2313
- ctx.lineTo(cx + t / 2, bodyH)
2314
- ctx.lineTo(cx, bodyH + tailH)
2315
- ctx.lineTo(cx - t / 2, bodyH)
2316
- ctx.lineTo(r, bodyH)
2317
- ctx.quadraticCurveTo(0, bodyH, 0, bodyH - r)
2318
- ctx.lineTo(0, r)
2319
- ctx.quadraticCurveTo(0, 0, r, 0)
2320
- ctx.closePath()
2321
- }
2322
- // 柔和投影 → 深色面板底
2323
- ctx.save()
2324
- ctx.shadowColor = 'rgba(0,0,0,0.55)'
2325
- ctx.shadowBlur = 14
2326
- ctx.shadowOffsetY = 4
2327
- path()
2328
- ctx.fillStyle = 'rgba(14,18,27,0.94)'
2329
- ctx.fill()
2330
- ctx.restore()
2331
- // 头部数据带(轻微抬升 + 发丝底边)
2332
- ctx.save()
2333
- path()
2334
- ctx.clip()
2335
- ctx.fillStyle = 'rgba(255,255,255,0.045)'
2336
- ctx.fillRect(0, 0, bw, padY + nameH)
2337
- ctx.restore()
2338
- ctx.globalAlpha = 0.28
2339
- ctx.fillStyle = '#fff'
2340
- ctx.fillRect(padX, padY + 18, bw - padX * 2, 1)
2341
- ctx.globalAlpha = 1
2342
- // 发丝描边 + 左缘身份色数据条
2343
- path()
2344
- ctx.lineWidth = 1.2
2345
- ctx.strokeStyle = 'rgba(140,170,195,0.4)'
2346
- ctx.stroke()
2347
- ctx.save()
2348
- path()
2349
- ctx.clip()
2350
- ctx.fillStyle = accentHex
2351
- ctx.fillRect(0, 0, 3, bodyH)
2352
- ctx.restore()
2353
- // 头牌:等宽名牌(身份色)+ 右侧类型印章(交付/异常)
2354
- ctx.font = headFont
2355
- ctx.fillStyle = accentHex
2356
- ctx.textBaseline = 'middle'
2357
- ctx.textAlign = 'left'
2358
- ctx.fillText(name, padX, padY + 10)
2359
- const tag = kind === 'artifact' ? 'ARTIFACT' : kind === 'error' ? 'FAULT' : ''
2360
- if (tag) {
2361
- const tw = ctx.measureText(tag).width + 12
2362
- ctx.font = tagFont
2363
- ctx.fillStyle = kind === 'error' ? '#ff8d80' : '#9fc3e8'
2364
- ctx.strokeStyle = kind === 'error' ? 'rgba(255,141,128,0.55)' : 'rgba(159,195,232,0.45)'
2365
- ctx.lineWidth = 1
2366
- const tx = bw - padX - tw
2367
- const ty = padY + 2
2368
- const th = 16
2369
- ctx.beginPath()
2370
- ctx.rect(tx, ty, tw, th)
2371
- ctx.fill()
2372
- ctx.stroke()
2373
- ctx.textAlign = 'center'
2374
- ctx.fillText(tag, tx + tw / 2, ty + th / 2 + 0.5)
2375
- }
2376
- // 正文(错误微红)
2377
- ctx.font = bodyFont
2378
- ctx.fillStyle = kind === 'error' ? '#ff9d9d' : '#eef2fb'
2379
- ctx.textAlign = 'left'
2380
- lines.forEach((l, i) => ctx.fillText(l, padX, padY + nameH + 10 + i * lineH))
2381
- // Sprite(纹理按 1/2.4 缩放到世界单位 —— 放大气泡后保持更大可见尺度)
2382
- const tex = new THREE.CanvasTexture(canvas)
2383
- tex.colorSpace = THREE.SRGBColorSpace
2384
- const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: tex, transparent: true, depthTest: false, depthWrite: false }))
2385
- const k = 2.4
2386
- sprite.scale.set(bw / k, bh / k, 1)
2387
- return sprite
2388
- }
2389
-
2390
- /** 气泡弹入动画:0.62 → 1.0 缓出(180ms,2.5D 手感) */
2391
- private popInSprite(sprite: THREE.Sprite): void {
2392
- const tx = sprite.scale.x
2393
- const ty = sprite.scale.y
2394
- sprite.scale.set(tx * 0.62, ty * 0.62, 1)
2395
- const start = performance.now()
2396
- const dur = 180
2397
- const step = () => {
2398
- if (this.disposed) return
2399
- const t = Math.min(1, (performance.now() - start) / dur)
2400
- const s = 0.62 + 0.38 * (1 - Math.pow(1 - t, 3))
2401
- sprite.scale.set(tx * s, ty * s, 1)
2402
- if (t < 1) this.rafAnims.push(step)
2403
- }
2404
- this.rafAnims.push(step)
2405
- }
2406
-
2407
- /** 按宽度逐字符换行(兼容中英混排);超 maxLines 行末加省略号 */
2408
- private wrapBubbleLines(ctx: CanvasRenderingContext2D, text: string, maxW: number, maxLines: number): string[] {
2409
- const lines: string[] = []
2410
- let line = ''
2411
- for (const ch of text) {
2412
- if (ctx.measureText(line + ch).width > maxW && line) {
2413
- lines.push(line)
2414
- line = ch
2415
- if (lines.length >= maxLines) {
2416
- lines[maxLines - 1] = `${lines[maxLines - 1] ?? ''}…`
2417
- return lines
2418
- }
2419
- }
2420
- else {
2421
- line += ch
2422
- }
2423
- }
2424
- if (line) lines.push(line)
2425
- if (lines.length === 0) lines.push('…')
2426
- return lines
526
+ drainReceivers(now: number): void {
527
+ drainReceivers(this, now)
2427
528
  }
2428
529
 
2429
530
  // ================================================================
@@ -2435,491 +536,87 @@ export class TownScene3D {
2435
536
  return this.renderer.domElement
2436
537
  }
2437
538
 
539
+ // ==== 相机公开面(实现见 scene/town-scene3d-camera.ts;薄壳保持签名与语义不变) ====
540
+
2438
541
  /** 世界坐标 → 屏幕像素(数据标注/悬浮 callout 投影;返回 null = 在相机背后) */
2439
- worldToScreen(x: number, y: number, z: number): { x: number, y: number } | null {
2440
- const rect = this.renderer.domElement.getBoundingClientRect()
2441
- // 相机背后的点 project() 会按负 w 翻转到错误的屏幕位置 → 先用前向点积剔除
2442
- const dir = TownScene3D._wDir
2443
- this.camera.getWorldDirection(dir)
2444
- const rel = TownScene3D._wRel.set(x - this.camera.position.x, y - this.camera.position.y, z - this.camera.position.z)
2445
- if (rel.dot(dir) <= 0.1) return null
2446
- const v = TownScene3D._wVec.set(x, y, z).project(this.camera)
2447
- if (v.z > 1) return null
2448
- // 视锥外过远(NDC ±1.6)的点投影无意义(可能落到数千 px 外)→ 剔除;边缘 ±1 内保留供夹取
2449
- if (Math.abs(v.x) > 1.6 || Math.abs(v.y) > 1.6) return null
2450
- return {
2451
- x: rect.left + (v.x + 1) / 2 * rect.width,
2452
- y: rect.top + (1 - v.y) / 2 * rect.height,
2453
- }
2454
- }
542
+ worldToScreen(x: number, y: number, z: number): { x: number, y: number } | null { return worldToScreen(this, x, y, z) }
2455
543
 
2456
544
  /** 页面坐标 → 世界 xz(经 canvas rect + 相机射线打在 y=0 平面) */
2457
- screenToWorld(clientX: number, clientY: number): { x: number, z: number } {
2458
- const rect = this.renderer.domElement.getBoundingClientRect()
2459
- const ndcX = ((clientX - rect.left) / rect.width) * 2 - 1
2460
- const ndcY = -((clientY - rect.top) / rect.height) * 2 + 1
2461
- const raycaster = TownScene3D._raycaster
2462
- raycaster.setFromCamera(TownScene3D._wVec2.set(ndcX, ndcY), this.camera)
2463
- const pt = TownScene3D._wVec
2464
- raycaster.ray.intersectPlane(TownScene3D._groundPlane, pt)
2465
- return pt ? { x: pt.x, z: pt.z } : { x: this.camera.position.x, z: this.camera.position.z }
2466
- }
545
+ screenToWorld(clientX: number, clientY: number): { x: number, z: number } { return screenToWorld(this, clientX, clientY) }
2467
546
 
2468
547
  /** 平移相机(拖拽):移动 camTarget(相机位置由 loop 据 target+dolly 推导) */
2469
- panBy(dxWorld: number, dzWorld: number): void {
2470
- this.camTarget.x -= dxWorld
2471
- this.camTarget.z -= dzWorld
2472
- }
2473
-
2474
- /** 自动取景目标 dolly(缩放拖拽期间按范围尺寸计算;渲染循环平滑跟随,拖拽结束清除) */
2475
- private autoDolly: number | null = null
2476
-
2477
- /** 范围半轴 → 容纳它的 dolly(基线几何:水平半幅 ≈ 613*aspect*d,地面纵深 ≈ 950*d) */
2478
- private fitDollyFor(rx: number, rz: number): number {
2479
- const aspect = this.camera.aspect || 1.6
2480
- return Math.max(rx / (0.82 * 613 * aspect), rz / (0.82 * 950))
2481
- }
2482
-
2483
- /** 缩放过程中调用:范围超出当前视野 → 相机自动拉远(只放大不缩小,收敛于拖拽结束) */
2484
- private autoFrameTo(rx: number, rz: number): void {
2485
- const fit = this.fitDollyFor(rx, rz)
2486
- if (fit > this.dolly) this.autoDolly = Math.min(40, fit * 1.18)
2487
- }
2488
-
2489
- /** 变换模式(Blender 规范):translate=移动 G / rotate=旋转 R / scale=缩放 S。
2490
- * 轴约束:移动仅 XZ(模型贴地),旋转仅 Y(2.5D 朝向),缩放 XYZ。 */
2491
- setTransformMode(mode: 'translate' | 'rotate' | 'scale'): void {
2492
- if (!this.tControls) return
2493
- this.tControls.setMode(mode)
2494
- this.tControls.showX = mode !== 'rotate'
2495
- this.tControls.showY = mode !== 'translate'
2496
- this.tControls.showZ = mode !== 'rotate'
2497
- }
548
+ panBy(dxWorld: number, dzWorld: number): void { panBy(this, dxWorld, dzWorld) }
2498
549
 
2499
- /** 手柄是否悬停/拖拽中(调用方应让出相机平移与点选) */
2500
- isGizmoBusy(): boolean {
2501
- return !!this.tControls && (this.tControls.dragging || !!this.tControls.axis)
2502
- }
550
+ /** 缩放过程中调用:范围超出当前视野 → 相机自动拉远(只放大不缩小,收敛于拖拽结束)。宿主契约面:editing 模块回调。 */
551
+ autoFrameTo(rx: number, rz: number): void { autoFrameTo(this, rx, rz) }
2503
552
 
2504
- /** 滚轮缩放(改变 dolly 距离) */
2505
- /** 视角预设(设计稿 angle-chip):std 标准 / top 俯视 / front 前视 / side 侧视。
2506
- * 轨道参数:yaw 方位 / pitch 仰角(rad) / radius 半径(再乘 dolly)。 */
2507
- setViewPreset(p: 'std' | 'top' | 'front' | 'side'): void {
2508
- const presets: Record<string, { yaw: number, pitch: number, radius: number }> = {
2509
- std: { yaw: 0, pitch: 0.70, radius: 1178 },
2510
- top: { yaw: 0, pitch: 1.51, radius: 2400 },
2511
- front: { yaw: 0, pitch: 0.09, radius: 1510 },
2512
- side: { yaw: Math.PI / 2, pitch: 0.33, radius: 1320 },
2513
- }
2514
- const t = presets[p] ?? presets.std!
2515
- this.viewTarget = { ...t }
2516
- // 预设切换走平滑趋近;环绕拖拽则是即时双写
2517
- }
553
+ /** 视角预设(设计稿 angle-chip):std 标准 / top 俯视 / front 前视 / side 侧视。 */
554
+ setViewPreset(p: 'std' | 'top' | 'front' | 'side'): void { setViewPreset(this, p) }
2518
555
 
2519
- /** 左键环绕(设计稿 OrbitLite 语义):theta -= dx·0.0052(拖右=场景右转,内容跟手不反向);
2520
- * phi -= dy·0.0052(拖上=压低视角看地平,拖下=抬升俯视),夹在仰角安全区间 */
2521
- /** 环绕(设计稿 OrbitLite 1:1):theta -= dx·0.0052;仰角 += dy·0.0052
2522
- * (设计稿 phi -= dy·0.0052,phi 为极角 → 仰角随 dy 增大:往下拖 = 相机升向俯视) */
2523
- orbitBy(dxPx: number, dyPx: number): void {
2524
- const yaw = this.viewCur.yaw - dxPx * 0.0052
2525
- const pitch = Math.min(1.52, Math.max(0.06, this.viewCur.pitch + dyPx * 0.0052))
2526
- this.viewCur = { yaw, pitch, radius: this.viewCur.radius }
2527
- this.viewTarget = { ...this.viewCur }
2528
- this.dirty = true
2529
- }
556
+ /** 环绕(设计稿 OrbitLite 1:1):左键拖拽即时改 yaw/pitch,夹在仰角安全区间 */
557
+ orbitBy(dxPx: number, dyPx: number): void { orbitBy(this, dxPx, dyPx) }
2530
558
 
2531
559
  /** 自动环绕(设计稿 tOrbit):渲染循环缓转 yaw;orbitBy 拖拽时暂停 */
2532
- setAutoOrbit(on: boolean): void {
2533
- this.autoOrbit = on
2534
- }
560
+ setAutoOrbit(on: boolean): void { setAutoOrbit(this, on) }
2535
561
 
2536
- getAutoOrbit(): boolean {
2537
- return this.autoOrbit
2538
- }
562
+ getAutoOrbit(): boolean { return getAutoOrbit(this) }
2539
563
 
2540
- /** 滚轮缩放(改变 dolly 距离) */
2541
-
2542
- /** 平移(设计稿 OrbitLite 1:1):target += (−right·dx + up·dy)·s。
2543
- * right=(cosY,0,−sinY),up 地面分量 = −sinP·(sinY,cosY);panBy 内部取负 → 注视点
2544
- * 拖右时沿 −right 移动 = 相机左扫 = 内容跟随光标(抓取语义,与设计稿逐项一致)。 */
2545
- panByScreen(dxPx: number, dyPx: number): void {
2546
- const r = this.viewCur.radius * this.dolly
2547
- const k = r * 0.0011
2548
- const sinY = Math.sin(this.viewCur.yaw)
2549
- const cosY = Math.cos(this.viewCur.yaw)
2550
- const sinP = Math.sin(this.viewCur.pitch)
2551
- const dxw = (cosY * dxPx + sinP * sinY * dyPx) * k
2552
- const dzw = (-sinY * dxPx + sinP * cosY * dyPx) * k
2553
- this.panBy(dxw, dzw)
2554
- }
564
+ /** 平移(设计稿 OrbitLite 1:1):target += (−right·dx + up·dy)·s */
565
+ panByScreen(dxPx: number, dyPx: number): void { panByScreen(this, dxPx, dyPx) }
2555
566
 
2556
567
  /** 飞往预设视角(平滑;设计稿 flyTo) */
2557
- flyToPreset(p: 'std' | 'top' | 'front' | 'side', _dur = 900): void {
2558
- this.setViewPreset(p)
2559
- }
568
+ flyToPreset(p: 'std' | 'top' | 'front' | 'side', dur = 900): void { flyToPreset(this, p, dur) }
2560
569
 
2561
570
  /** 相机即时状态(导航地图/callout 距离显隐;世界坐标) */
2562
- getCameraPose(): { pos: { x: number, y: number, z: number }, target: { x: number, z: number }, yaw: number, dolly: number } {
2563
- return {
2564
- pos: { x: this.camera.position.x, y: this.camera.position.y, z: this.camera.position.z },
2565
- target: { x: this.camTarget.x, z: this.camTarget.z },
2566
- yaw: this.viewCur.yaw,
2567
- dolly: this.dolly,
2568
- }
2569
- }
571
+ getCameraPose(): { pos: { x: number, y: number, z: number }, target: { x: number, z: number }, yaw: number, dolly: number } { return getCameraPose(this) }
2570
572
 
2571
573
  /** 导航地图拖拽平移:世界位移直接作用于注视点与相机位置(设计稿 minimap drag) */
2572
- panWorldBy(dxw: number, dzw: number): void {
2573
- this.camTarget.x -= dxw
2574
- this.camTarget.z -= dzw
2575
- this.camera.position.x -= dxw
2576
- this.camera.position.z -= dzw
2577
- this.dirty = true
2578
- }
2579
-
2580
- /** 渲染曝光(场景控制坞"环境光照";0.2~2.2) */
2581
- setExposure(v: number): void {
2582
- this.renderer.toneMappingExposure = Math.min(2.2, Math.max(0.2, v))
2583
- }
2584
-
2585
- /** 领地染色浓度(平台透明度系数;0.05~1) */
2586
- setTerritoryOpacity(v: number): void {
2587
- const o = Math.min(1, Math.max(0.05, v))
2588
- for (const b of this.blocks.values()) {
2589
- ;(b.platform.material as THREE.MeshStandardMaterial).opacity = 0.24 * o
2590
- }
2591
- this.dirty = true
2592
- }
574
+ panWorldBy(dxw: number, dzw: number): void { panWorldBy(this, dxw, dzw) }
2593
575
 
2594
576
  /** 重置视角(注视世界中心,标准轨道,基准 dolly) */
2595
- resetView(): void {
2596
- this.dolly = 1
2597
- this.autoDolly = null
2598
- this.tweenCamTo(WORLD_CX, WORLD_CZ)
2599
- this.setViewPreset('std')
2600
- }
577
+ resetView(): void { resetView(this) }
2601
578
 
2602
- zoomBy(f: number): void {
2603
- this.autoDolly = null
2604
- this.dolly = Math.min(1000, Math.max(0.001, this.dolly + f))
2605
- }
579
+ zoomBy(f: number): void { zoomBy(this, f) }
2606
580
 
2607
581
  /** 缓动聚焦某世界点(移动 camTarget) */
2608
- /** 缓动聚焦某世界点(移动 camTarget) */
2609
- focusTo(x: number, z: number): void {
2610
- this.tweenCamTo(x, z)
2611
- }
2612
-
2613
- private tweenCamTo(x: number, z: number): void {
2614
- const start = performance.now()
2615
- const sx = this.camTarget.x
2616
- const sz = this.camTarget.z
2617
- const dur = 600
2618
- const step = () => {
2619
- const t = Math.min(1, (performance.now() - start) / dur)
2620
- const e = 1 - Math.pow(1 - t, 3)
2621
- this.camTarget.x = sx + (x - sx) * e
2622
- this.camTarget.z = sz + (z - sz) * e
2623
- if (t < 1) this.rafAnims.push(step)
2624
- }
2625
- this.rafAnims.push(step)
2626
- }
582
+ focusTo(x: number, z: number): void { focusTo(this, x, z) }
2627
583
 
2628
- // ================================================================
2629
- // 拖拽换装 / 生成居民(drop 入口)
2630
- // ================================================================
584
+ /** 登记逐帧回调(渲染循环统一 flush;抽出模块经此驱动缓动/脉冲) */
585
+ pushAnim(f: () => void): void { this.rafAnims.push(f) }
2631
586
 
2632
- dropModelOnWorld(x: number, z: number, assetId: string): { mode: 'rebind' | 'spawn', agentId?: string, textureKey: string, x: number, y: number } {
2633
- const near = this.nearestAgent(x, z, 80)
2634
- const model = this.modelsById.get(assetId)
2635
- const texKey = model?.id ?? assetId
2636
- // 数字孪生实体模型(kind=dev):拖入场景生成"设备节点" + 绑定 device twin
2637
- if (model?.kind === 'dev') {
2638
- const twinId = this.spawnDeviceNode(x, z, texKey, model.file, model.name)
2639
- return { mode: 'spawn', agentId: twinId, textureKey: texKey, x: Math.round(x), y: Math.round(z) }
2640
- }
2641
- if (near) {
2642
- this.swapTexture(near, texKey)
2643
- this.enqueueBubble(near.channelId, near.agentId, 'info', `换装 → ${model?.name ?? assetId}`, 2200)
2644
- return { mode: 'rebind', agentId: near.agentId, textureKey: texKey, x: Math.round(near.root.position.x), y: Math.round(near.root.position.z) }
2645
- }
2646
- // 落点生成居民
2647
- const info = model ?? { id: assetId, file: '', name: assetId }
2648
- this.spawnResident(x, z, texKey, info.name)
2649
- return { mode: 'spawn', textureKey: texKey, x: Math.round(x), y: Math.round(z) }
2650
- }
2651
-
2652
- private nearestAgent(x: number, z: number, maxDist: number): Agent3D | undefined {
2653
- let best: Agent3D | undefined
2654
- let bestD = maxDist
2655
- for (const a of this.agents.values()) {
2656
- const d = Math.hypot(a.root.position.x - x, a.root.position.z - z)
2657
- if (d < bestD) {
2658
- best = a
2659
- bestD = d
2660
- }
2661
- }
2662
- return best
2663
- }
587
+ /** 自动取景目标 dolly(缩放拖拽期间按范围尺寸计算;渲染循环平滑跟随,拖拽结束清除) */
588
+ autoDolly: number | null = null
589
+ /** 变换模式(Blender 规范):translate=移动 G / rotate=旋转 R / scale=缩放 S。
590
+ * 轴约束:移动仅 XZ(模型贴地),旋转仅 Y(2.5D 朝向),缩放 XYZ。 */
591
+ setTransformMode(mode: 'translate' | 'rotate' | 'scale'): void { setTransformMode(this, mode) }
2664
592
 
2665
- private swapTexture(asp: Agent3D, texKey: string): void {
2666
- const info = this.modelsById.get(texKey)
2667
- const file = info?.file ?? this.resolveFile(texKey) ?? '/assets/game/character/hero-3d.glb'
2668
- asp.modelRef = texKey
2669
- asp.textureKey = texKey
2670
- void this.mountModel(asp, file, info?.name ?? texKey)
2671
- }
593
+ /** 手柄是否悬停/拖拽中(调用方应让出相机平移与点选) */
594
+ isGizmoBusy(): boolean { return isGizmoBusy(this) }
2672
595
 
2673
- private spawnResident(x: number, z: number, texKey: string, name: string): void {
2674
- const root = new THREE.Group()
2675
- root.position.set(x, GROUND_Y, z)
2676
- const model = new THREE.Group()
2677
- root.add(model)
2678
- const nameSprite = this.makeLabel(name, x, 48, z, this.hexOf(0xffe9c4))
2679
- this.scene.add(root)
2680
- const resident = new Agent3D({
2681
- channelId: '', agentId: `resident-${Date.now().toString(36)}`, name,
2682
- role: 'worker', root, model, mixer: null, clips: [], colorNum: 0xffe9c4, nameSprite,
2683
- bubble: null, bubbleText: null, bubbleTimer: null, state: 'idle', progress: null,
2684
- dragging: false, homeX: x, homeZ: z, range: null, rangeLine: null, textureKey: texKey, modelRef: texKey,
2685
- behavior: { mode: 'idle', roamTarget: null, targetId: null, waitUntil: 0, pauseUntil: 0, engaged: false },
2686
- })
2687
- resident.host = this
2688
- resident.attachAura(0xffe9c4, 'worker')
2689
- this.agents.set(resident.agentId, resident)
2690
- const info = this.modelsById.get(texKey)
2691
- void this.mountModel(resident, info?.file ?? '/assets/game/character/hero-3d.glb', info?.name ?? name)
2692
- }
596
+ /** 渲染曝光(场景控制坞"环境光照";0.2~2.2) */
597
+ setExposure(v: number): void { setExposure(this, v) }
2693
598
 
2694
- /** 设备状态环 + 运行态动效弧(局部坐标一次定位随 root 移动 —— 修复曾被每帧写成世界坐标导致环 2× 漂移的缺陷)。
2695
- * 颜色/显隐由 DeviceNode.updateRing 数据驱动,渲染循环只负责弧的缓转。 */
2696
- private makeDeviceRing(): { ring: THREE.Mesh, arc: THREE.Mesh } {
2697
- const ring = new THREE.Mesh(
2698
- new THREE.RingGeometry(20, 26, 32),
2699
- new THREE.MeshBasicMaterial({ color: 0xf6c453, transparent: true, opacity: 0.8, side: THREE.DoubleSide }),
2700
- )
2701
- ring.rotation.x = -Math.PI / 2
2702
- ring.position.y = 0.3
2703
- const arc = new THREE.Mesh(
2704
- new THREE.RingGeometry(20, 26, 32, 1, 0, Math.PI * 1.5),
2705
- new THREE.MeshBasicMaterial({ color: 0x35e0a0, transparent: true, opacity: 0.55, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending }),
2706
- )
2707
- arc.rotation.x = -Math.PI / 2
2708
- arc.position.y = 0.34
2709
- arc.visible = false
2710
- return { ring, arc }
2711
- }
599
+ /** 领地染色浓度(平台透明度系数;0.05~1) */
600
+ setTerritoryOpacity(v: number): void { setTerritoryOpacity(this, v) }
2712
601
 
2713
- /**
2714
- * 数字孪生设备节点:拖 dev 模型进场景生成。
2715
- * - 3D:挂 device GLB + 设备名牌 + 状态环(telemetry 驱动颜色);
2716
- * - 数据:经 devices.create 落一个 device twin,与 modelRef 绑定;
2717
- * - state 驱动:alarm→红环,running→青环,offline→灰环,idle→亮环。
2718
- */
2719
- private spawnDeviceNode(x: number, z: number, texKey: string, file: string, name: string): string {
2720
- const tempId = `dev-${Date.now().toString(36)}`
2721
- const root = new THREE.Group()
2722
- root.position.set(x, GROUND_Y, z)
2723
- const { ring, arc } = this.makeDeviceRing()
2724
- root.add(ring, arc)
2725
- const holder = new THREE.Group()
2726
- root.add(holder)
2727
- holder.userData.modelFile = file
2728
- const label = this.makeLabel(`⚙ ${name}`, x, 60, z)
2729
- this.scene.add(root)
2730
- const twin = new DeviceNode({ twinId: tempId, name, modelRef: texKey, root, holder, ring, label, state: 'idle', telemetry: {} })
2731
- twin.host = this
2732
- twin.arc = arc
2733
- twin.updateRing()
2734
- this.deviceNodes.set(tempId, twin)
2735
- this.registerScalable('device', tempId, holder)
2736
- void this.loadGltfToGroup(file, holder, UNITS * 1.6)
2737
- const st = this.scalables.get(`device:${tempId}`)
2738
- const createPromise = this.devices?.create({
2739
- name, modelRef: texKey, kind: 'device', controls: ['power_on', 'power_off', 'set_speed'],
2740
- posX: Math.round(x * 10) / 10,
2741
- posZ: Math.round(z * 10) / 10,
2742
- scale: st ? Math.round(st.userScale * 100) / 100 : 1,
2743
- })
2744
- if (!createPromise) return tempId
2745
- this.pendingDeviceCreates.set(tempId, { name, modelRef: texKey, posX: x, posZ: z })
2746
- void createPromise
2747
- .then((created) => {
2748
- const cancelled = this.cancelledDeviceCreates.delete(tempId)
2749
- this.pendingDeviceCreates.delete(tempId)
2750
- if (cancelled) {
2751
- void this.devices?.remove?.(created.id).catch(() => {})
2752
- return
2753
- }
2754
- if (this.disposed) return
2755
- this.adoptDeviceNode(tempId, created.id)
2756
- })
2757
- .catch(() => {
2758
- this.cancelledDeviceCreates.delete(tempId)
2759
- this.pendingDeviceCreates.delete(tempId)
2760
- })
2761
- return tempId
2762
- }
602
+ // ================================================================
603
+ // 拖拽换装 / 生成居民(drop 入口;实现见 scene/town-scene3d-agents.ts)
604
+ // ================================================================
2763
605
 
2764
- /** 将本地临时设备原子迁移到服务端 ID,避免后续更新/删除命中旧键。 */
2765
- private adoptDeviceNode(tempId: string, realId: string): void {
2766
- this.pendingDeviceCreates.delete(tempId)
2767
- const node = this.deviceNodes.get(tempId)
2768
- if (!node) return
2769
- const duplicate = this.deviceNodes.get(realId)
2770
- if (duplicate && duplicate !== node) {
2771
- this.removeDeviceNode(tempId)
2772
- if (this.selected?.kind === 'device' && this.selected.id === tempId) this.setSelected({ kind: 'device', id: realId })
2773
- return
2774
- }
2775
- this.deviceNodes.delete(tempId)
2776
- node.twinId = realId
2777
- this.deviceNodes.set(realId, node)
2778
- const scalable = this.scalables.get(`device:${tempId}`)
2779
- this.scalables.delete(`device:${tempId}`)
2780
- if (scalable) {
2781
- scalable.id = realId
2782
- this.scalables.set(`device:${realId}`, scalable)
2783
- }
2784
- const timer = this.pendingSaveTimers.get(tempId)
2785
- this.pendingSaveTimers.delete(tempId)
2786
- if (timer) this.pendingSaveTimers.set(realId, timer)
2787
- if (this.pointerDrag?.kind === 'device' && this.pointerDrag.id === tempId) this.pointerDrag.id = realId
2788
- if (this.selected?.kind === 'device' && this.selected.id === tempId) this.setSelected({ kind: 'device', id: realId })
2789
- this.persistDeviceTransform(realId)
606
+ dropModelOnWorld(x: number, z: number, assetId: string): { mode: 'rebind' | 'spawn', agentId?: string, textureKey: string, x: number, y: number } {
607
+ return dropModelOnWorld(this, x, z, assetId)
2790
608
  }
2791
609
 
2792
- /** 将 GLB 加载进 Group(按高度归一化 scale) */
2793
- private async loadGltfToGroup(file: string, group: THREE.Group, targetH: number): Promise<void> {
2794
- try {
2795
- const gltf = await this.loadGltf(file)
2796
- const box = new THREE.Box3().setFromObject(gltf.scene)
2797
- const h = Math.max(0.5, box.max.y - box.min.y)
2798
- const scale = targetH / h
2799
- gltf.scene.scale.setScalar(scale)
2800
- gltf.scene.position.y = 0
2801
- // 真实投影 + PBR 材质增强(设备:金属机身材质反射拉满,像真实工业设备)
2802
- gltf.scene.traverse((o) => {
2803
- if ((o as THREE.Mesh).isMesh) o.castShadow = true
2804
- })
2805
- this.enhancePbrMaterials(gltf.scene, 'device')
2806
- // 静态子树矩阵冻结:设备模型加载后局部变换恒定,子孙 matrixAutoUpdate=false +
2807
- // 一次性 updateMatrix,免每帧全树 updateMatrix 合成(拖拽/缩放只改祖先链,
2808
- // world 矩阵经 force 传播仍正确;设备无动画,mixer 仅角色路径使用)
2809
- gltf.scene.traverse((o) => {
2810
- if (o === gltf.scene) return
2811
- o.matrixAutoUpdate = false
2812
- o.updateMatrix()
2813
- })
2814
- group.add(gltf.scene)
2815
- }
2816
- catch {
2817
- // 加载失败:空 Group,静默(节点仍存在,只是无网格)
2818
- }
610
+ /** 数字孪生设备节点:拖 dev 模型进场景生成(实现见 scene/town-scene3d-devices.ts) */
611
+ spawnDeviceNode(x: number, z: number, texKey: string, file: string, name: string): string {
612
+ return spawnDeviceNode(this, x, z, texKey, file, name)
2819
613
  }
2820
614
 
2821
- /**
2822
- * 模型 PBR 材质增强(放入场景后的渲染优化;设备/角色分治)。
2823
- * 根因修复:角色与产线设备 GLB 大量使用 KHR_materials_unlit(three 加载为
2824
- * MeshBasicMaterial),不受灯光/阴影约束,夜景里像自发光贴片 —— 这既是
2825
- * 「角色过亮」也是设备「假金属光泽」的来源。统一就地转标准材质接入光照
2826
- * 体系,再分级调参:
2827
- * - 角色:哑光 + 极轻环境反射(无清漆/自发光,保持原作贴图观感);
2828
- * - 设备:LED 指示灯保持无光照语义并提 HDR(bloom 起晕,幂等防重);
2829
- * 透光件微自发光 / 金属件适度镜面(0.85,克制)/ 烤漆件轻清漆。
2830
- */
2831
- private enhancePbrMaterials(root: THREE.Object3D, kind: 'device' | 'character'): void {
2832
- root.traverse((o) => {
2833
- const mesh = o as THREE.Mesh
2834
- if (!mesh.isMesh) return
2835
- mesh.receiveShadow = true
2836
- const tune = (raw: THREE.Material): THREE.Material => {
2837
- const b = raw as THREE.MeshBasicMaterial & { isMeshBasicMaterial?: boolean }
2838
- let m: THREE.MeshStandardMaterial
2839
- if (b?.isMeshBasicMaterial) {
2840
- if (kind === 'device' && /led|lamp|light/i.test(b.name ?? '')) {
2841
- // 设备指示灯:保持无光照语义;提为 HDR(bloom 起晕)。clone 共享同一材质,
2842
- // userData 幂等护桥避免多实例挂载时倍率叠乘。
2843
- const flag = (b.userData ?? (b.userData = {})) as { hdrBoost?: boolean }
2844
- if (!flag.hdrBoost) {
2845
- b.color.multiplyScalar(4.5)
2846
- flag.hdrBoost = true
2847
- }
2848
- return b
2849
- }
2850
- m = new THREE.MeshStandardMaterial({
2851
- map: b.map ?? null,
2852
- // unlit 贴图按"全亮"绘制,直接接入 ~8 倍总光的灯光系会过曝成白炽;
2853
- // 反照率按类型压暗:角色 0.4 / 设备 0.55(ACES 肩部回落到合理亮度)
2854
- color: b.color.clone().multiplyScalar(kind === 'character' ? 0.38 : 0.5),
2855
- transparent: b.transparent,
2856
- opacity: b.opacity,
2857
- alphaTest: b.alphaTest,
2858
- side: b.side,
2859
- roughness: kind === 'character' ? 0.92 : 0.6,
2860
- metalness: kind === 'character' ? 0 : 0.25,
2861
- })
2862
- m.name = b.name ?? ''
2863
- b.dispose()
2864
- }
2865
- else {
2866
- const s = raw as THREE.MeshStandardMaterial
2867
- if (!s || !('envMapIntensity' in s)) return raw
2868
- m = s
2869
- }
2870
- if (kind === 'character') {
2871
- // 角色:哑光 + 极轻环境反射(曾因 emissive 底光 + 清漆层整体过亮)
2872
- m.envMapIntensity = 0.45
2873
- if (m.metalness > 0.3) m.metalness = 0
2874
- return m
2875
- }
2876
- // 设备分级
2877
- if (m.transparent) {
2878
- // 灯罩/屏幕/指示窗:轻微自发光,像通电的设备部件
2879
- m.envMapIntensity = 0.42
2880
- if (m.emissive) m.emissive.setScalar(Math.max(m.emissive.r, 0.06))
2881
- return m
2882
- }
2883
- if (m.metalness >= 0.5) {
2884
- m.envMapIntensity = 0.85 // 金属机身:适可而止的镜面(1.35 曾过亮)
2885
- return m
2886
- }
2887
- // 涂装/塑料外壳 → 物理材质轻清漆层(已是物理材质只调参,避免 clone 共享材质重复替换)
2888
- if ((m as unknown as { isMeshPhysicalMaterial?: boolean }).isMeshPhysicalMaterial) {
2889
- const p = m as THREE.MeshPhysicalMaterial
2890
- p.clearcoat = 0.35
2891
- p.clearcoatRoughness = 0.4
2892
- p.envMapIntensity = 0.7
2893
- p.needsUpdate = true
2894
- return p
2895
- }
2896
- const p = new THREE.MeshPhysicalMaterial({
2897
- color: m.color,
2898
- map: m.map,
2899
- normalMap: m.normalMap,
2900
- roughnessMap: m.roughnessMap,
2901
- aoMap: m.aoMap,
2902
- emissive: m.emissive,
2903
- emissiveMap: m.emissiveMap,
2904
- emissiveIntensity: m.emissiveIntensity,
2905
- roughness: Math.min(m.roughness, 0.55),
2906
- metalness: m.metalness,
2907
- clearcoat: 0.35,
2908
- clearcoatRoughness: 0.4,
2909
- })
2910
- p.name = m.name
2911
- p.side = m.side
2912
- m.dispose()
2913
- return p
2914
- }
2915
- if (Array.isArray(mesh.material)) mesh.material = mesh.material.map(tune)
2916
- else mesh.material = tune(mesh.material)
2917
- })
2918
- this.dirty = true
2919
- }
615
+ /** 模型 PBR 材质增强(设备/角色分治;实现见 scene/town-scene3d-models.ts) */
616
+ enhancePbrMaterials(root: THREE.Object3D, kind: 'device' | 'character'): void { enhancePbrMaterials(this, root, kind) }
2920
617
 
2921
618
  // ================================================================
2922
- // 调试 / HUD 数据
619
+ // 调试 / HUD 数据(实现见 scene/town-scene3d-hud.ts)
2923
620
  // ================================================================
2924
621
 
2925
622
  getDebugState(): {
@@ -2928,39 +625,7 @@ export class TownScene3D {
2928
625
  bubbles: Array<{ text: string, at: number }>
2929
626
  activity: { channelId: string, agentName: string, text: string } | null
2930
627
  player: { x: number, y: number }
2931
- } {
2932
- return {
2933
- blocks: this.blocks.size,
2934
- agents: [...this.agents.values()].map(a => ({
2935
- agentId: a.agentId,
2936
- name: a.name,
2937
- role: a.role,
2938
- channelId: a.channelId,
2939
- state: a.state,
2940
- progress: a.progress,
2941
- x: Math.round(a.root.position.x),
2942
- y: Math.round(a.root.position.z),
2943
- visible: true,
2944
- draggable: true,
2945
- auraColor: a.colorNum,
2946
- behavior: a.behavior.mode,
2947
- targetId: a.behavior.targetId,
2948
- homeX: Math.round(a.homeX),
2949
- homeY: Math.round(a.homeZ),
2950
- textureKey: a.textureKey,
2951
- modelRef: a.modelRef,
2952
- decorated: !a.channelId,
2953
- range: a.range
2954
- ? { x: Math.round(a.range.x), z: Math.round(a.range.z), radiusX: Math.round(a.range.radiusX), radiusZ: Math.round(a.range.radiusZ), shape: a.range.shape }
2955
- : null,
2956
- bubbleText: a.bubbleText,
2957
- anim: a.animState,
2958
- })),
2959
- bubbles: this.dbgBubbles.slice(-8),
2960
- activity: this.lastActivity,
2961
- player: { x: Math.round(this.camera.position.x), y: Math.round(this.camera.position.z) },
2962
- }
2963
- }
628
+ } { return getDebugState(this) }
2964
629
 
2965
630
  getMinimapState(): {
2966
631
  world: { w: number, h: number }
@@ -2968,711 +633,146 @@ export class TownScene3D {
2968
633
  agents: Array<{ x: number, y: number, color: number, busy: boolean }>
2969
634
  devices: Array<{ x: number, y: number, color: number, state: string }>
2970
635
  player: { x: number, y: number }
2971
- } {
2972
- const stateColor = (state: 'idle' | 'running' | 'offline' | 'alarm'): number =>
2973
- state === 'alarm' ? 0xff6b6b : state === 'offline' ? 0x8496a5 : state === 'running' ? 0x35e0a0 : 0xf6c453
2974
- return {
2975
- world: { w: WORLD_W, h: WORLD_H },
2976
- // 领地含真实形状/半轴/朝向(归一化;镜头居中小地图按形状绘制)
2977
- blocks: [...this.blocks.values()].map(b => ({
2978
- x: b.x / WORLD_W,
2979
- y: b.z / WORLD_H,
2980
- color: b.color,
2981
- name: b.name,
2982
- shape: b.shape,
2983
- rx: b.radiusX / WORLD_W,
2984
- rz: b.radiusZ / WORLD_H,
2985
- rot: b.rotationY,
2986
- })),
2987
- agents: [...this.agents.values()].map(a => ({ x: a.root.position.x / WORLD_W, y: a.root.position.z / WORLD_H, color: a.colorNum, busy: a.state === 'busy' })),
2988
- devices: [...this.deviceNodes.values()].map(d => ({
2989
- twinId: d.twinId,
2990
- x: d.root.position.x / WORLD_W,
2991
- y: d.root.position.z / WORLD_H,
2992
- color: stateColor(d.state),
2993
- state: d.state,
2994
- daq: d.modelRef.startsWith('daq-') || d.modelRef.includes('daq'),
2995
- bound: false,
2996
- })),
2997
- // 镜头 = camTarget(画面注视中心;小地图准星锚点)
2998
- player: { x: this.camTarget.x / WORLD_W, y: this.camTarget.z / WORLD_H },
2999
- }
3000
- }
636
+ } { return getMinimapState(this) }
3001
637
 
3002
- getRecentActivity(): Array<{ channelId: string, agentName: string, text: string }> {
3003
- return [...this.recentActivity]
3004
- }
638
+ getRecentActivity(): Array<{ channelId: string, agentName: string, text: string }> { return getRecentActivity(this) }
3005
639
 
3006
- /** 更新设备节点状态/遥测(由 useDeviceTwins 轮询或控制反馈驱动;数据驱动渲染状态环) */
640
+ /** 更新设备节点状态/遥测(数据驱动渲染状态环;实现见 scene/town-scene3d-devices.ts) */
3007
641
  updateDeviceNode(twinId: string, state: DeviceNode['state'], telemetry?: Record<string, number | string | boolean>): void {
3008
- const dev = this.deviceNodes.get(twinId)
3009
- if (!dev) return
3010
- if (state) {
3011
- dev.state = state
3012
- dev.updateRing()
3013
- }
3014
- if (telemetry) dev.telemetry = { ...dev.telemetry, ...telemetry }
3015
- this.dirty = true
642
+ updateDeviceNode(this, twinId, state, telemetry)
3016
643
  }
3017
644
 
3018
645
  /** 设备改名(重建名牌 Sprite + 落库由 TownView 走 devices.update) */
3019
- renameDevice(id: string, name: string): void {
3020
- const dev = this.deviceNodes.get(id)
3021
- if (!dev || !name.trim() || name.trim() === dev.name) return
3022
- this.renameDeviceSprite(dev, name)
3023
- }
646
+ renameDevice(id: string, name: string): void { renameDevice(this, id, name) }
3024
647
 
3025
648
  /** 设备换模型(按 modelRef 重挂 GLB 到 holder;持久化由 TownView 走 devices.update) */
3026
- swapDeviceModel(id: string, modelRef: string): void {
3027
- const dev = this.deviceNodes.get(id)
3028
- if (!dev) return
3029
- this.swapDeviceModelSprite(dev, modelRef)
3030
- }
649
+ swapDeviceModel(id: string, modelRef: string): void { swapDeviceModel(this, id, modelRef) }
3031
650
 
3032
651
  /** 设备当前名称(供属性面板初始化) */
3033
- getDeviceName(id: string): string {
3034
- return this.deviceNodes.get(id)?.name ?? ''
3035
- }
652
+ getDeviceName(id: string): string { return getDeviceName(this, id) }
3036
653
 
3037
654
  /** 设备当前绑定模型 id(供模型下拉高亮) */
3038
- getDeviceModelRef(id: string): string {
3039
- return this.deviceNodes.get(id)?.modelRef ?? ''
3040
- }
655
+ getDeviceModelRef(id: string): string { return getDeviceModelRef(this, id) }
3041
656
 
3042
657
  /** 删除设备实例:落库删除 + 移除场景节点(由 TownView 触发;失败仅移除本地节点)。 */
3043
- async removeDevice(id: string): Promise<void> {
3044
- const dev = this.deviceNodes.get(id)
3045
- if (!dev) return
3046
- if (this.pendingDeviceCreates.has(id)) {
3047
- this.cancelledDeviceCreates.add(id)
3048
- this.removeDeviceNode(id)
3049
- return
3050
- }
3051
- try {
3052
- await this.devices?.remove?.(id)
3053
- }
3054
- catch { /* 服务端失败仍移除本地节点(尽力同步) */ }
3055
- this.removeDeviceNode(id)
3056
- }
658
+ async removeDevice(id: string): Promise<void> { await removeDevice(this, id) }
3057
659
 
3058
660
  /** 设备节点列表(供 HUD/E2E;topY = 模型顶面世界高度,callout 锚定用) */
3059
661
  getDeviceNodes(): Array<{ twinId: string, name: string, x: number, z: number, topY: number, state: string, telemetry: Record<string, number | string | boolean> }> {
3060
- return [...this.deviceNodes.values()].map(d => ({
3061
- twinId: d.twinId,
3062
- name: d.name,
3063
- x: Math.round(d.root.position.x),
3064
- z: Math.round(d.root.position.z),
3065
- topY: Math.round(this.deviceTopY(d)),
3066
- state: d.state,
3067
- telemetry: d.telemetry,
3068
- }))
662
+ return getDeviceNodes(this)
3069
663
  }
3070
664
 
3071
665
  /** 动作绑定:为某 Agent 指定动画 clip(按名称 idle/walk/work 或索引)。供 AssetLibrary 选择动作后调用。 */
3072
- setAnimPref(agentId: string, clipName: string): void {
3073
- const asp = this.agents.get(agentId)
3074
- if (!asp || asp.clips.length === 0 || !asp.mixer) return
3075
- // 解析:优先名称匹配,否则 idle→0/walk→1/work→末帧
3076
- let idx = asp.clips.findIndex(c => c.name.toLowerCase().includes(clipName.toLowerCase()))
3077
- if (idx < 0) idx = clipName === 'work' ? asp.clips.length - 1 : clipName === 'walk' ? Math.min(1, asp.clips.length - 1) : 0
3078
- const clip = asp.clips[idx]
3079
- if (!clip) return
3080
- asp.mixer.stopAllAction()
3081
- asp.mixer.clipAction(clip).reset().play()
3082
- }
666
+ setAnimPref(agentId: string, clipName: string): void { setAnimPref(this, agentId, clipName) }
3083
667
 
3084
668
  /** 角色当前绑定模型 id(供模型选择器高亮) */
3085
- getAgentModel(agentId: string): string | null {
3086
- const asp = this.agents.get(agentId)
3087
- return asp?.modelRef ?? null
3088
- }
669
+ getAgentModel(agentId: string): string | null { return getAgentModel(this, agentId) }
3089
670
 
3090
671
  /** 角色名字(供模型选择器标题) */
3091
- getAgentName(agentId: string): string {
3092
- return this.agents.get(agentId)?.name ?? agentId.slice(0, 8)
3093
- }
672
+ getAgentName(agentId: string): string { return getAgentName(this, agentId) }
3094
673
 
3095
674
  /** 角色动画 clip 名列表(供 UI 展示可绑定动作;无则空) */
3096
- getAgentClips(agentId: string): Array<{ name: string, duration: number }> {
3097
- const asp = this.agents.get(agentId)
3098
- if (!asp) return []
3099
- return asp.clips.map(c => ({ name: c.name, duration: Math.round(c.duration * 10) / 10 }))
3100
- }
675
+ getAgentClips(agentId: string): Array<{ name: string, duration: number }> { return getAgentClips(this, agentId) }
3101
676
 
3102
677
  // ================================================================
3103
678
  // 选中 + 缩放(客制化:场景内点选 Agent/设备 → 弹滑杆调大小)
3104
679
  // ================================================================
3105
680
 
3106
681
  /** 以世界坐标 hit 一个 Agent 或设备节点(近似距离阈值;供点击选中) */
3107
- private pickAt(x: number, z: number): { kind: 'agent' | 'device', id: string } | null {
3108
- let best: { kind: 'agent' | 'device', id: string, d: number } | null = null
3109
- for (const a of this.agents.values()) {
3110
- const d = Math.hypot(a.root.position.x - x, a.root.position.z - z)
3111
- if (d < 90 && (!best || d < best.d)) best = { kind: 'agent', id: a.agentId, d }
3112
- }
3113
- for (const dev of this.deviceNodes.values()) {
3114
- const d = Math.hypot(dev.root.position.x - x, dev.root.position.z - z)
3115
- if (d < 90 && (!best || d < best.d)) best = { kind: 'device', id: dev.twinId, d }
3116
- }
3117
- return best ? { kind: best.kind, id: best.id } : null
3118
- }
682
+ pickAt(x: number, z: number): { kind: 'agent' | 'device', id: string } | null { return pickAt(this, x, z) }
3119
683
 
3120
684
  /** 点选频道:返回包含该点的频道 id(边界内;供编辑模式打开边界编辑面板) */
3121
- private pickChannel(x: number, z: number): string | null {
3122
- for (const b of this.blocks.values()) {
3123
- const layout: ChannelLayout = {
3124
- channelId: b.channelId, x: b.x, z: b.z,
3125
- radiusX: b.radiusX, radiusZ: b.radiusZ,
3126
- shape: b.shape, rotationY: b.rotationY,
3127
- }
3128
- if (pointInBoundary(layout, x, z)) return b.channelId
3129
- }
3130
- return null
3131
- }
685
+ pickChannel(x: number, z: number): string | null { return pickChannel(this, x, z) }
3132
686
 
3133
- /** 命中频道边界线(世界点落在某领地边界带 ±42 单位内 → 边界拖拽等比缩放)。 */
3134
- private pickChannelEdge(x: number, z: number): { cid: string, rx0: number, rz0: number, rd0: number } | null {
3135
- for (const b of this.blocks.values()) {
3136
- const lxz = toLocal(b, x, z)
3137
- const rd = Math.hypot(lxz.x / b.radiusX, lxz.z / b.radiusZ)
3138
- const tol = 42 / Math.min(b.radiusX, b.radiusZ)
3139
- if (Math.abs(rd - 1) <= tol) {
3140
- return { cid: b.channelId, rx0: b.radiusX, rz0: b.radiusZ, rd0: rd }
3141
- }
3142
- }
3143
- return null
3144
- }
687
+ /** 命中频道边界线(世界点落在某领地边界带 ±42 单位内 → 边界拖拽等比缩放)。宿主契约面:editing 模块回调。 */
688
+ pickChannelEdge(x: number, z: number): { cid: string, rx0: number, rz0: number, rd0: number } | null { return pickChannelEdge(this, x, z) }
3145
689
 
3146
690
  /** 设置选中(点击后由 Vue 弹缩放/旋转滑杆) */
3147
- private setSelected(sel: { kind: 'agent' | 'device', id: string } | null): void {
3148
- // 选中切换/取消 → 退出框选绘制(清理预览)
3149
- if (!sel || sel.kind !== 'agent' || sel.id !== this.rangeDrawAgent) this.cancelRangeDraw()
3150
- this.selected = sel
3151
- // 变换手柄跟随选中设备(Blender 规范:选中即出手柄;运行模式只读 → 不出手柄)
3152
- if (this.tControls) {
3153
- if (sel?.kind === 'device' && this.mode === 'edit' && this.deviceNodes.has(sel.id)) this.tControls.attach(this.deviceNodes.get(sel.id)!.root)
3154
- else this.tControls.detach()
3155
- }
3156
- this.refreshAgentRangeLines()
3157
- this.refreshAgentRangeHandles()
3158
- if (!sel) {
3159
- this.emit('select', null)
3160
- return
3161
- }
3162
- const st = this.scalables.get(`${sel.kind}:${sel.id}`)
3163
- if (st) {
3164
- this.emit('select', {
3165
- kind: sel.kind,
3166
- id: sel.id,
3167
- scale: st.userScale,
3168
- rotation: Math.round(this.getModelRotation(sel.id, sel.kind) * 10) / 10,
3169
- })
3170
- }
3171
- }
691
+ setSelected(sel: { kind: 'agent' | 'device', id: string } | null): void { setSelected(this, sel) }
3172
692
 
3173
693
  /** 场景内缩放:为 Agent(id)或设备(id)设定用户缩放倍率(0.2~5;1=默认归一化) */
3174
- setModelScale(id: string, scale: number, kind?: 'agent' | 'device'): void {
3175
- const k = kind ?? (this.scalables.has(`device:${id}`) ? 'device' : 'agent')
3176
- const key = `${k}:${id}`
3177
- const st = this.scalables.get(key)
3178
- if (!st) return
3179
- st.userScale = scale
3180
- // 施加于模型 holder(相对已归一化的默认尺寸)
3181
- st.holder.scale.setScalar(st.userScale)
3182
- if (k === 'device') {
3183
- const dev = this.deviceNodes.get(id)
3184
- if (dev) dev.topYCache = null
3185
- }
3186
- this.dirty = true
3187
- }
694
+ setModelScale(id: string, scale: number, kind?: 'agent' | 'device'): void { setModelScale(this, id, scale, kind) }
3188
695
 
3189
696
  /** 当前选中对象(供 Vue/滑杆初始化) */
3190
- getSelectedScale(): { kind: 'agent' | 'device', id: string, scale: number, rotation: number } | null {
3191
- if (!this.selected) return null
3192
- const st = this.scalables.get(`${this.selected.kind}:${this.selected.id}`)
3193
- if (!st) return null
3194
- return {
3195
- kind: this.selected.kind,
3196
- id: this.selected.id,
3197
- scale: st.userScale,
3198
- rotation: Math.round(this.getModelRotation(this.selected.id, this.selected.kind) * 10) / 10,
3199
- }
3200
- }
697
+ getSelectedScale(): { kind: 'agent' | 'device', id: string, scale: number, rotation: number } | null { return getSelectedScale(this) }
3201
698
 
3202
699
  /** 组装可缩放目标并恢复初始化缩放(initialScale 优先=服务端持久化;否则 localStorage) */
3203
- private registerScalable(kind: 'agent' | 'device', id: string, holder: THREE.Group, initialScale?: number): void {
3204
- const saved = initialScale !== undefined
3205
- ? initialScale
3206
- : (typeof localStorage !== 'undefined' ? Number(localStorage.getItem(`town.scale.${kind}:${id}`) ?? 1) : 1)
3207
- const userScale = Number.isFinite(saved) && saved > 0 ? saved : 1
3208
- this.scalables.set(`${kind}:${id}`, { kind, id, userScale, holder })
3209
- holder.scale.setScalar(userScale)
3210
- if (kind === 'device') {
3211
- const dev = this.deviceNodes.get(id)
3212
- if (dev) dev.topYCache = null
3213
- }
3214
- }
700
+ registerScalable(kind: 'agent' | 'device', id: string, holder: THREE.Group, initialScale?: number): void { registerScalable(this, kind, id, holder, initialScale) }
3215
701
 
3216
702
  /** 持久化指定对象的缩放(滑杆松手时调用;设备同时落库,角色仅本地) */
3217
- persistScale(kind: 'agent' | 'device', id: string): void {
3218
- const st = this.scalables.get(`${kind}:${id}`)
3219
- if (!st || typeof localStorage === 'undefined') return
3220
- localStorage.setItem(`town.scale.${kind}:${id}`, String(st.userScale))
3221
- if (kind === 'device') this.persistDeviceTransform(id)
3222
- }
703
+ persistScale(kind: 'agent' | 'device', id: string): void { persistScale(this, kind, id) }
3223
704
 
3224
705
  // ================================================================
3225
706
  // 编辑/浏览模式 + 场景对象拖拽(设备↔地面,角色↔落点)
707
+ // 实现见 scene/town-scene3d-editing.ts
3226
708
  // ================================================================
3227
709
 
3228
710
  /** 模式切换:浏览(只读:相机+点选) / 编辑(可拖拽设备/调整角色落点/旋转/频道整体移动/边界手柄/Agent 活动范围) */
3229
- setMode(mode: TownScene3DMode): void {
3230
- if (this.mode === mode) return
3231
- this.mode = mode
3232
- if (mode === 'browse') {
3233
- if (this.pointerDrag) this.endPointerDrag()
3234
- this.cancelRangeDraw()
3235
- this.setSelected(null)
3236
- // 运行模式只读:收起变换手柄(拖拽已在 tryStartPointerDrag 门禁)
3237
- this.tControls?.detach()
3238
- }
3239
- this.refreshChannelHandles()
3240
- this.refreshAgentRangeHandles()
3241
- // 编辑模式范围线框可见性(range 线框在编辑模式全部显示,浏览模式仅选中显示)
3242
- for (const a of this.agents.values()) a.renderRangeLine()
3243
- this.dirty = true
3244
- }
711
+ setMode(mode: TownScene3DMode): void { setMode(this, mode) }
3245
712
 
3246
- getMode(): TownScene3DMode {
3247
- return this.mode
3248
- }
713
+ getMode(): TownScene3DMode { return getMode(this) }
3249
714
 
3250
715
  /** 网格吸附开关(编辑拖拽落点;默认开) */
3251
- setSnap(enabled: boolean): void {
3252
- this.snapEnabled = enabled
3253
- }
716
+ setSnap(enabled: boolean): void { setSnap(this, enabled) }
3254
717
 
3255
- getSnap(): boolean {
3256
- return this.snapEnabled
3257
- }
718
+ getSnap(): boolean { return getSnap(this) }
3258
719
 
3259
720
  /**
3260
721
  * 尝试开始场景拖拽(编辑模式;指针按下命中设备/角色 → 占用该手势)。
3261
722
  * 返回 true 表示场景已接管指针(调用方应跳过相机平移)。
3262
723
  */
3263
- tryStartPointerDrag(clientX: number, clientY: number): boolean {
3264
- if (this.mode !== 'edit' || this.isGizmoBusy()) return false
3265
- const w = this.screenToWorld(clientX, clientY)
3266
- // 1) Agent 活动范围手柄(编辑模式选中带范围角色时 → 拖拽调整该 Agent 范围大小)
3267
- const ar = this.pickAgentRangeHandle(w.x, w.z)
3268
- if (ar) {
3269
- this.pointerDrag = { kind: 'agentRangeResize', id: ar.agentId, handle: ar.handle }
3270
- this.emitSaveState('dirty')
3271
- return true
3272
- }
3273
- // 2) 边界缩放手柄(编辑模式选中频道的 4 个手柄 → 调整频道范围大小)
3274
- const h = this.pickResizeHandle(w.x, w.z)
3275
- if (h) {
3276
- this.pointerDrag = { kind: 'resize', id: h.cid, handle: h.handle }
3277
- this.setSelected(null)
3278
- this.emitSaveState('dirty')
3279
- return true
3280
- }
3281
- // 3) 框选绘制:rangeDraw 模式激活 → 以按下点为框角拉动(优先于点选,便于从角色身上起手)
3282
- if (this.rangeDrawAgent) {
3283
- this.pointerDrag = { kind: 'rangeDraw', id: this.rangeDrawAgent, x0: w.x, z0: w.z }
3284
- this.rangeDrawPreviewState = null
3285
- return true
3286
- }
3287
- // 4) 设备 / 角色
3288
- const hit = this.pickAt(w.x, w.z)
3289
- if (hit) {
3290
- if (hit.kind === 'device') {
3291
- if (!this.deviceNodes.has(hit.id)) return false
3292
- this.pointerDrag = hit
3293
- this.setSelected(hit)
3294
- this.emitSaveState('dirty')
3295
- return true
3296
- }
3297
- // 角色:仅频道角色可调整落点(装饰居民不持久化、不可布局)
3298
- const asp = this.agents.get(hit.id)
3299
- if (!asp || !asp.channelId) return false
3300
- if (asp.bubbleTimer) {
3301
- clearTimeout(asp.bubbleTimer)
3302
- asp.bubbleTimer = null
3303
- }
3304
- asp.dragging = true
3305
- asp.behavior.mode = 'idle'
3306
- asp.behavior.targetId = null
3307
- this.pointerDrag = hit
3308
- this.setSelected(hit)
3309
- this.emitSaveState('dirty')
3310
- return true
3311
- }
3312
- // 5) 拖某成员活动范围边界线 → 整框平移(频道整体移动之前;频道移动先排除已按范围接管)
3313
- const rangeHit = this.hitAgentRangeBoundary(w.x, w.z)
3314
- if (rangeHit && rangeHit.range) {
3315
- this.pointerDrag = { kind: 'agentRange', id: rangeHit.agentId, dx: rangeHit.range.x - w.x, dz: rangeHit.range.z - w.z }
3316
- this.setSelected({ kind: 'agent', id: rangeHit.agentId })
3317
- this.emitSaveState('dirty')
3318
- return true
3319
- }
3320
- // 6) 频道边界线拖拽:抓住边界附近 → 整体等比缩放(手柄之外,自由拉边调节范围)
3321
- const edge = this.pickChannelEdge(w.x, w.z)
3322
- if (edge) {
3323
- this.pointerDrag = { kind: 'channelEdge', id: edge.cid, rx0: edge.rx0, rz0: edge.rz0, rd0: edge.rd0 }
3324
- this.setSelected(null)
3325
- this.selectChannel(edge.cid)
3326
- this.emitSaveState('dirty')
3327
- return true
3328
- }
3329
- // 7) 频道信标:非拖拽目标(点击定位语义交给 pointerup;避免编辑模式下点信标变成整体拖动)
3330
- if (this.pickBeacon(w.x, w.z)) return false
3331
- // 8) 频道领地整体拖拽:点中领地空白处 → 平移整个频道(平台/边界/名牌/成员落点)
3332
- const cid = this.pickChannel(w.x, w.z)
3333
- if (cid) {
3334
- const b = this.blocks.get(cid)
3335
- if (!b) return false
3336
- this.pointerDrag = { kind: 'channel', id: cid, dx: b.x - w.x, dz: b.z - w.z }
3337
- this.setSelected(null)
3338
- this.selectChannel(cid)
3339
- this.emitSaveState('dirty')
3340
- return true
3341
- }
3342
- return false
3343
- }
724
+ tryStartPointerDrag(clientX: number, clientY: number): boolean { return tryStartPointerDrag(this, clientX, clientY) }
3344
725
 
3345
- isPointerDragging(): boolean {
3346
- return this.pointerDrag !== null
3347
- }
726
+ isPointerDragging(): boolean { return isPointerDragging(this) }
3348
727
 
3349
- private snapWorld(v: number): number {
3350
- return this.snapEnabled ? Math.round(v / SNAP_SIZE) * SNAP_SIZE : Math.round(v * 10) / 10
3351
- }
728
+ snapWorld(v: number): number { return snapWorld(this, v) }
3352
729
 
3353
730
  /** 拖拽中:对象跟随指针指向的 xz 平面(仅改内存;落库在 endPointerDrag) */
3354
- movePointerDrag(clientX: number, clientY: number): void {
3355
- if (!this.pointerDrag) return
3356
- const w = this.screenToWorld(clientX, clientY)
3357
- // 无边界画布:仅网格吸附,不钳制世界范围(模型可摆放到任意位置)
3358
- const x = this.snapWorld(w.x)
3359
- const z = this.snapWorld(w.z)
3360
- const pd = this.pointerDrag
3361
- if (pd.kind === 'device') {
3362
- const dev = this.deviceNodes.get(pd.id)
3363
- if (dev) {
3364
- dev.root.position.x = x
3365
- dev.root.position.z = z
3366
- this.dirty = true
3367
- }
3368
- }
3369
- else if (pd.kind === 'agent') {
3370
- const asp = this.agents.get(pd.id)
3371
- if (asp) {
3372
- // 频道角色落点钳制在所属领地边界内(不可拖出频道)
3373
- let px = x
3374
- let pz = z
3375
- const layout = this.blockLayout(asp.channelId)
3376
- if (layout) {
3377
- const clamped = clampToBoundary(layout, x, z, 20)
3378
- px = clamped.x
3379
- pz = clamped.z
3380
- }
3381
- asp.root.position.x = px
3382
- asp.root.position.z = pz
3383
- this.dirty = true
3384
- }
3385
- }
3386
- else if (pd.kind === 'rangeDraw') {
3387
- // 框选绘制:当前点为对角终点(钳入频道边界),实时更新预览矩形
3388
- const asp = this.agents.get(pd.id)
3389
- if (!asp) return
3390
- const layout = this.blockLayout(asp.channelId)
3391
- let cx = w.x
3392
- let cz = w.z
3393
- if (layout) {
3394
- const clamped = clampToBoundary(layout, cx, cz, 12)
3395
- cx = clamped.x
3396
- cz = clamped.z
3397
- }
3398
- this.rangeDrawPreviewState = { x0: pd.x0, z0: pd.z0, x1: cx, z1: cz }
3399
- this.updateRangeDrawPreview(pd.x0, pd.z0, cx, cz)
3400
- this.dirty = true
3401
- }
3402
- else if (pd.kind === 'agentRange') {
3403
- // 拖 Agent 活动范围边界 → 整框平移(钳入频道边界;范围线框/手柄跟随)
3404
- const asp = this.agents.get(pd.id)
3405
- if (!asp || !asp.range) return
3406
- const nx = x + pd.dx
3407
- const nz = z + pd.dz
3408
- asp.range = { ...asp.range, x: nx, z: nz }
3409
- asp.renderRangeLine()
3410
- this.refreshAgentRangeHandles()
3411
- this.dirty = true
3412
- }
3413
- else if (pd.kind === 'agentRangeResize') {
3414
- // 拖 Agent 范围手柄 → 实时调整 radiusX/radiusZ(矩形角点双轴 / 椭圆轴向轴点)
3415
- const asp = this.agents.get(pd.id)
3416
- if (!asp || !asp.range) return
3417
- asp.range = this.applyAgentRangeResize(asp.range, pd.handle, w.x, w.z)
3418
- asp.renderRangeLine()
3419
- this.refreshAgentRangeHandles()
3420
- this.autoFrameTo(asp.range.radiusX, asp.range.radiusZ)
3421
- this.dirty = true
3422
- }
3423
- else if (pd.kind === 'channel') {
3424
- const b = this.blocks.get(pd.id)
3425
- if (b) this.applyBlockMove(b, x + pd.dx, z + pd.dz)
3426
- }
3427
- else if (pd.kind === 'resize') {
3428
- const b = this.blocks.get(pd.id)
3429
- if (b) this.applyResize(b, pd.handle, w.x, w.z)
3430
- }
3431
- else if (pd.kind === 'channelEdge') {
3432
- // 边界线等比缩放:指针沿"中心→边界"射线的归一化距离比例 → 双轴同倍率
3433
- const b = this.blocks.get(pd.id)
3434
- if (b) {
3435
- const lxz = toLocal(b, x, z)
3436
- const rd = Math.hypot(lxz.x / pd.rx0, lxz.z / pd.rz0)
3437
- const factor = pd.rd0 > 0.05 ? rd / pd.rd0 : 1
3438
- b.radiusX = Math.max(60, Math.round(pd.rx0 * factor))
3439
- b.radiusZ = Math.max(40, Math.round(pd.rz0 * factor))
3440
- this.applyLayoutToBlock(b)
3441
- this.autoFrameTo(b.radiusX, b.radiusZ)
3442
- }
3443
- }
3444
- }
731
+ movePointerDrag(clientX: number, clientY: number): void { movePointerDrag(this, clientX, clientY) }
3445
732
 
3446
733
  /** 拖拽结束:设备 → 防抖落库;角色 → home 更新 + 持久化;频道 → 布局落库 */
3447
- endPointerDrag(): void {
3448
- if (!this.pointerDrag) return
3449
- const pd = this.pointerDrag
3450
- this.pointerDrag = null
3451
- if (pd.kind === 'device') {
3452
- const dev = this.deviceNodes.get(pd.id)
3453
- if (dev) this.persistDeviceTransform(pd.id)
3454
- }
3455
- else if (pd.kind === 'agent') {
3456
- const asp = this.agents.get(pd.id)
3457
- if (asp) {
3458
- asp.homeX = asp.root.position.x
3459
- asp.homeZ = asp.root.position.z
3460
- asp.dragging = false
3461
- this.emitSaveState('saving')
3462
- void this.agentApi?.updateHome(pd.id, asp.channelId, asp.homeX, asp.homeZ)
3463
- .then(() => this.emitSaveState('saved', Date.now()))
3464
- .catch(() => this.emitSaveState('error'))
3465
- }
3466
- }
3467
- else if (pd.kind === 'rangeDraw') {
3468
- // 框选结束:以拖框中点为中心、半宽为半径生成矩形范围(过小拖动视为取消)
3469
- const asp = this.agents.get(pd.id)
3470
- if (asp && this.rangeDrawPreviewState) {
3471
- const d = this.rangeDrawPreviewState
3472
- const range: AgentRangeLayout = {
3473
- x: (d.x0 + d.x1) / 2,
3474
- z: (d.z0 + d.z1) / 2,
3475
- radiusX: Math.abs(d.x1 - d.x0) / 2,
3476
- radiusZ: Math.abs(d.z1 - d.z0) / 2,
3477
- shape: 'rect',
3478
- rotationY: 0,
3479
- }
3480
- if (range.radiusX >= 40 && range.radiusZ >= 40) {
3481
- asp.range = range
3482
- asp.renderRangeLine()
3483
- // 把角色落点收进新范围(home 若在范围外会导致回归/漫游卡死)
3484
- const c = clampToAgentRange(asp.range, asp.homeX, asp.homeZ, 0)
3485
- if (c.x !== asp.homeX || c.z !== asp.homeZ) {
3486
- asp.homeX = c.x
3487
- asp.homeZ = c.z
3488
- asp.root.position.x = c.x
3489
- asp.root.position.z = c.z
3490
- void this.agentApi?.updateHome(pd.id, asp.channelId, c.x, c.z).catch(() => {})
3491
- }
3492
- this.emitSaveState('saving')
3493
- void this.agentApi?.updateRange(pd.id, asp.channelId, asp.range)
3494
- .then(() => this.emitSaveState('saved', Date.now()))
3495
- .catch(() => this.emitSaveState('error'))
3496
- }
3497
- }
3498
- this.cancelRangeDraw()
3499
- this.refreshAgentRangeHandles()
3500
- this.emitAgentRangeChanged(pd.id)
3501
- }
3502
- else if (pd.kind === 'agentRange' || pd.kind === 'agentRangeResize') {
3503
- // 拖范围整框 / 拖范围手柄结束 → 落库 config.range
3504
- const asp = this.agents.get(pd.id)
3505
- if (asp && asp.range) {
3506
- this.emitSaveState('saving')
3507
- void this.agentApi?.updateRange(pd.id, asp.channelId, asp.range)
3508
- .then(() => this.emitSaveState('saved', Date.now()))
3509
- .catch(() => this.emitSaveState('error'))
3510
- }
3511
- this.refreshAgentRangeHandles()
3512
- this.emitAgentRangeChanged(pd.id)
3513
- }
3514
- else {
3515
- // channel / resize:把成员钳回新边界 + 布局落库 + 通知 Vue 刷新边界面板草稿
3516
- const b = this.blocks.get(pd.id)
3517
- if (b) {
3518
- this.clampAgentsToBoundary(pd.id)
3519
- this.refreshChannelHandles()
3520
- const layout = this.getChannelLayout(pd.id)
3521
- if (layout) {
3522
- this.emitSaveState('saving')
3523
- void this.channelApi?.save(pd.id, layout)
3524
- .then(() => this.emitSaveState('saved', Date.now()))
3525
- .catch(() => this.emitSaveState('error'))
3526
- this.emit('channelResized', { channelId: pd.id, layout })
3527
- }
3528
- }
3529
- }
3530
- }
734
+ endPointerDrag(): void { endPointerDrag(this) }
3531
735
 
3532
736
  // ================================================================
3533
737
  // Agent 独立活动范围(编辑模式:框选绘制 / 整框平移 / 手柄收缩扩张)
738
+ // 实现见 scene/town-scene3d-ranges.ts
3534
739
  // ================================================================
3535
740
 
3536
741
  /** 活动范围线框可见性:编辑模式全部显示;浏览模式仅选中角色的范围显示 */
3537
- private rangeLineVisible(asp: Agent3D): boolean {
3538
- if (this.mode === 'edit') return true
3539
- return this.selected?.kind === 'agent' && this.selected.id === asp.agentId
3540
- }
742
+ rangeLineVisible(asp: Agent3D): boolean { return rangeLineVisible(this, asp) }
3541
743
 
3542
744
  /** 通知 Vue 对象面板刷新活动范围草稿 */
3543
- private emitAgentRangeChanged(agentId: string): void {
3544
- this.emit('agentRangeChanged', { agentId })
3545
- }
745
+ emitAgentRangeChanged(agentId: string): void { emitAgentRangeChanged(this, agentId) }
3546
746
 
3547
747
  /** 同步全部范围线框可见性(选中/模式切换后调用) */
3548
- private refreshAgentRangeLines(): void {
3549
- for (const a of this.agents.values()) {
3550
- if (a.rangeLine) a.rangeLine.visible = this.rangeLineVisibleFor(a)
3551
- }
3552
- }
748
+ refreshAgentRangeLines(): void { refreshAgentRangeLines(this) }
3553
749
 
3554
750
  /** 活动范围线框可见性(公众面,供 Agent3D.renderRangeLine):编辑模式全部显示;浏览模式仅选中显示 */
3555
- rangeLineVisibleFor(asp: Agent3D): boolean {
3556
- if (this.mode === 'edit') return true
3557
- return this.selected?.kind === 'agent' && this.selected.id === asp.agentId
3558
- }
751
+ rangeLineVisibleFor(asp: Agent3D): boolean { return rangeLineVisibleFor(this, asp) }
3559
752
 
3560
753
  /** 刷新 Agent 活动范围手柄(编辑模式 + 选中带范围角色时显示;矩形四角 / 椭圆轴向四点) */
3561
- private refreshAgentRangeHandles(): void {
3562
- for (const hl of this.agentRangeHandles) hl.mesh.visible = false
3563
- if (this.mode !== 'edit' || !this.selected || this.selected.kind !== 'agent') return
3564
- const asp = this.agents.get(this.selected.id)
3565
- if (!asp || !asp.range) return
3566
- const rot = asp.range.rotationY * Math.PI / 180
3567
- const pts = this.boundaryHandlePoints(asp.range)
3568
- for (let i = 0; i < this.agentRangeHandles.length && i < pts.length; i++) {
3569
- const [lx, lz] = pts[i]!
3570
- const wx = asp.range.x + lx * Math.cos(rot) - lz * Math.sin(rot)
3571
- const wz = asp.range.z + lx * Math.sin(rot) + lz * Math.cos(rot)
3572
- this.agentRangeHandles[i]!.agentId = asp.agentId
3573
- this.agentRangeHandles[i]!.mesh.position.set(wx, 1.2, wz)
3574
- this.agentRangeHandles[i]!.mesh.visible = true
3575
- }
3576
- }
754
+ refreshAgentRangeHandles(): void { refreshAgentRangeHandles(this) }
3577
755
 
3578
756
  /** 命中选中 Agent 的活动范围手柄(编辑模式;返回 agentId + 手柄号) */
3579
- private pickAgentRangeHandle(x: number, z: number): { agentId: string, handle: number } | null {
3580
- if (this.mode !== 'edit') return null
3581
- for (const hl of this.agentRangeHandles) {
3582
- if (!hl.mesh.visible) continue
3583
- if (Math.hypot(hl.mesh.position.x - x, hl.mesh.position.z - z) < 40) return { agentId: hl.agentId, handle: hl.handle }
3584
- }
3585
- return null
3586
- }
757
+ pickAgentRangeHandle(x: number, z: number): { agentId: string, handle: number } | null { return pickAgentRangeHandle(this, x, z) }
3587
758
 
3588
759
  /** 命中某成员活动范围边界线(供整框拖移;编辑模式;26 单位内视为命中) */
3589
- private hitAgentRangeBoundary(x: number, z: number): Agent3D | undefined {
3590
- if (this.mode !== 'edit') return undefined
3591
- for (const a of this.agents.values()) {
3592
- if (!a.range || !a.channelId) continue
3593
- if (distToRangeBoundary(a.range, x, z) < 26) return a
3594
- }
3595
- return undefined
3596
- }
760
+ hitAgentRangeBoundary(x: number, z: number): Agent3D | undefined { return hitAgentRangeBoundary(this, x, z) }
3597
761
 
3598
762
  /** 拖范围手柄 → 实时调整 radiusX/radiusZ(矩形:角点双轴;椭圆:对应轴向轴点),并收进频道边界 */
3599
- private applyAgentRangeResize(range: AgentRangeLayout, handle: number, wx: number, wz: number): AgentRangeLayout {
3600
- const rad = -range.rotationY * Math.PI / 180
3601
- const cos = Math.cos(rad)
3602
- const sin = Math.sin(rad)
3603
- const dx = wx - range.x
3604
- const dz = wz - range.z
3605
- const lx = dx * cos - dz * sin
3606
- const lz = dx * sin + dz * cos
3607
- let next: AgentRangeLayout
3608
- if (range.shape === 'rect') {
3609
- next = { ...range, radiusX: Math.max(30, Math.abs(lx)), radiusZ: Math.max(30, Math.abs(lz)) }
3610
- }
3611
- else if (handle === 0 || handle === 1) {
3612
- next = { ...range, radiusX: Math.max(30, Math.abs(lx)) }
3613
- }
3614
- else {
3615
- next = { ...range, radiusZ: Math.max(30, Math.abs(lz)) }
3616
- }
3617
- return next
3618
- }
763
+ applyAgentRangeResize(range: AgentRangeLayout, handle: number, wx: number, wz: number): AgentRangeLayout { return applyAgentRangeResize(range, handle, wx, wz) }
3619
764
 
3620
765
  /** 框选预览:以 (x0,z0)-(x1,z1) 为对角生成矩形线框(实时跟随指针) */
3621
- private updateRangeDrawPreview(x0: number, z0: number, x1: number, z1: number): void {
3622
- const lx = Math.min(x0, x1)
3623
- const hx = Math.max(x0, x1)
3624
- const lz = Math.min(z0, z1)
3625
- const hz = Math.max(z0, z1)
3626
- const cx = (lx + hx) / 2
3627
- const cz = (lz + hz) / 2
3628
- const rx = Math.max(8, (hx - lx) / 2)
3629
- const rz = Math.max(8, (hz - lz) / 2)
3630
- if (!this.rangeDrawLine) {
3631
- const line = makeBoundary('rect', 1, 1, 0x41c8f4)
3632
- ;(line.material as THREE.LineBasicMaterial).opacity = 0.95
3633
- line.position.y = 0.5
3634
- this.scene.add(line)
3635
- this.rangeDrawLine = line
3636
- }
3637
- this.rangeDrawLine.position.set(cx, 0.5, cz)
3638
- this.rangeDrawLine.rotation.y = 0
3639
- const geo = new THREE.BufferGeometry().setFromPoints([
3640
- new THREE.Vector3(-rx, 0, -rz), new THREE.Vector3(rx, 0, -rz),
3641
- new THREE.Vector3(rx, 0, rz), new THREE.Vector3(-rx, 0, rz),
3642
- ])
3643
- this.rangeDrawLine.geometry.dispose()
3644
- this.rangeDrawLine.geometry = geo
3645
- }
766
+ updateRangeDrawPreview(x0: number, z0: number, x1: number, z1: number): void { updateRangeDrawPreview(this, x0, z0, x1, z1) }
3646
767
 
3647
768
  /** 退出框选绘制模式(清理预览与状态) */
3648
- cancelRangeDraw(): void {
3649
- if (this.rangeDrawAgent === null && !this.rangeDrawLine) return
3650
- this.rangeDrawAgent = null
3651
- this.rangeDrawPreviewState = null
3652
- if (this.rangeDrawLine) {
3653
- this.scene.remove(this.rangeDrawLine)
3654
- this.rangeDrawLine = null
3655
- }
3656
- this.dirty = true
3657
- }
769
+ cancelRangeDraw(): void { cancelRangeDraw(this) }
3658
770
 
3659
771
  /** 进入「框选绘制」模式:为该角色拉动矩形框生成活动范围(编辑模式) */
3660
- startRangeDraw(agentId: string): void {
3661
- const asp = this.agents.get(agentId)
3662
- if (!asp || !asp.channelId || this.mode !== 'edit') return
3663
- this.setSelected({ kind: 'agent', id: agentId })
3664
- this.cancelRangeDraw()
3665
- this.rangeDrawAgent = agentId
3666
- this.rangeDrawPreviewState = null
3667
- this.refreshAgentRangeHandles()
3668
- this.dirty = true
3669
- }
772
+ startRangeDraw(agentId: string): void { startRangeDraw(this, agentId) }
3670
773
 
3671
774
  /** 当前是否正在为某角色框选绘制 */
3672
- isRangeDrawing(agentId?: string): boolean {
3673
- if (!this.rangeDrawAgent) return false
3674
- return agentId === undefined || this.rangeDrawAgent === agentId
3675
- }
775
+ isRangeDrawing(agentId?: string): boolean { return isRangeDrawing(this, agentId) }
3676
776
 
3677
777
  /** E2E/调试:强制某频道接收器跳过当前条目,立即消费下一条(FIFO 顺序断言用) */
3678
778
  debugAdvanceReceiver(channelId: string): void {
@@ -3681,799 +781,92 @@ export class TownScene3D {
3681
781
  }
3682
782
 
3683
783
  /** Agent 当前活动范围(供面板初始化;未设置返回 null) */
3684
- getAgentRange(agentId: string): AgentRangeLayout | null {
3685
- return this.agents.get(agentId)?.range ?? null
3686
- }
784
+ getAgentRange(agentId: string): AgentRangeLayout | null { return getAgentRange(this, agentId) }
3687
785
 
3688
786
  /** 面板滑杆/形状即时调整:局部更新范围(无上限,信任用户设定) + 线框/手柄刷新;home 被迫位移时记档待提交 */
3689
- setAgentRangeScene(agentId: string, patch: Partial<AgentRangeLayout>): void {
3690
- const asp = this.agents.get(agentId)
3691
- if (!asp) return
3692
- if (!asp.range) {
3693
- // 尚无范围:以落点为中心、频道半径 1/4 起手(面板直接给值时)
3694
- asp.range = { x: asp.homeX, z: asp.homeZ, radiusX: 120, radiusZ: 90, shape: 'ellipse', rotationY: 0 }
3695
- }
3696
- const next = { ...asp.range, ...patch }
3697
- asp.range = next
3698
- // 范围收缩致 home 越界 → 落点收进范围(提交时随 updateRange 一并落库)
3699
- const c = clampToAgentRange(asp.range, asp.homeX, asp.homeZ, 0)
3700
- if (c.x !== asp.homeX || c.z !== asp.homeZ) {
3701
- asp.homeX = c.x
3702
- asp.homeZ = c.z
3703
- asp.root.position.x = c.x
3704
- asp.root.position.z = c.z
3705
- this.rangeHomeMoved = { agentId, x: c.x, z: c.z }
3706
- }
3707
- asp.renderRangeLine()
3708
- this.refreshAgentRangeHandles()
3709
- this.dirty = true
3710
- }
787
+ setAgentRangeScene(agentId: string, patch: Partial<AgentRangeLayout>): void { setAgentRangeScene(this, agentId, patch) }
3711
788
 
3712
789
  /** 面板提交:落库范围(updateRange);home 若被迫位移一并落库(updateHome) */
3713
- commitAgentRange(agentId: string): void {
3714
- const asp = this.agents.get(agentId)
3715
- if (!asp) return
3716
- if (this.rangeHomeMoved?.agentId === agentId) {
3717
- const { x, z } = this.rangeHomeMoved
3718
- this.rangeHomeMoved = null
3719
- void this.agentApi?.updateHome(agentId, asp.channelId, x, z).catch(() => {})
3720
- }
3721
- this.emitSaveState('saving')
3722
- void this.agentApi?.updateRange(agentId, asp.channelId, asp.range)
3723
- .then(() => this.emitSaveState('saved', Date.now()))
3724
- .catch(() => this.emitSaveState('error'))
3725
- this.emitAgentRangeChanged(agentId)
3726
- }
790
+ commitAgentRange(agentId: string): void { commitAgentRange(this, agentId) }
3727
791
 
3728
792
  /** 清除角色活动范围(局部 + 落库 null;回退频道边界) */
3729
- clearAgentRange(agentId: string): void {
3730
- const asp = this.agents.get(agentId)
3731
- if (!asp) return
3732
- if (asp.rangeLine) {
3733
- this.scene.remove(asp.rangeLine)
3734
- asp.rangeLine = null
3735
- }
3736
- asp.range = null
3737
- this.rangeHomeMoved = null
3738
- this.refreshAgentRangeHandles()
3739
- this.emitSaveState('saving')
3740
- void this.agentApi?.updateRange(agentId, asp.channelId, null)
3741
- .then(() => this.emitSaveState('saved', Date.now()))
3742
- .catch(() => this.emitSaveState('error'))
3743
- this.emitAgentRangeChanged(agentId)
3744
- this.dirty = true
3745
- }
793
+ clearAgentRange(agentId: string): void { clearAgentRange(this, agentId) }
3746
794
 
3747
795
  /** 对象朝向(度;编辑模式旋转滑杆实时) */
3748
- setModelRotation(id: string, deg: number, kind?: 'agent' | 'device'): void {
3749
- const k = kind ?? (this.deviceNodes.has(id) ? 'device' : 'agent')
3750
- if (k === 'device') {
3751
- const dev = this.deviceNodes.get(id)
3752
- if (!dev) return
3753
- dev.root.rotation.y = deg * Math.PI / 180
3754
- }
3755
- else {
3756
- const asp = this.agents.get(id)
3757
- if (!asp) return
3758
- asp.root.rotation.y = deg * Math.PI / 180
3759
- }
3760
- this.dirty = true
3761
- }
796
+ setModelRotation(id: string, deg: number, kind?: 'agent' | 'device'): void { setModelRotation(this, id, deg, kind) }
3762
797
 
3763
- getModelRotation(id: string, kind?: 'agent' | 'device'): number {
3764
- const k = kind ?? (this.deviceNodes.has(id) ? 'device' : 'agent')
3765
- if (k === 'device') {
3766
- const dev = this.deviceNodes.get(id)
3767
- return dev ? THREE.MathUtils.radToDeg(dev.root.rotation.y) : 0
3768
- }
3769
- const asp = this.agents.get(id)
3770
- return asp ? THREE.MathUtils.radToDeg(asp.root.rotation.y) : 0
3771
- }
798
+ getModelRotation(id: string, kind?: 'agent' | 'device'): number { return getModelRotation(this, id, kind) }
3772
799
 
3773
800
  // ================================================================
3774
801
  // 场景保存状态(未保存/保存中/已保存/失败)+ 设备 transform 防抖落库
802
+ // 实现见 scene/town-scene3d-devices.ts
3775
803
  // ================================================================
3776
804
 
3777
- private emitSaveState(state: 'idle' | 'dirty' | 'saving' | 'saved' | 'error', at = Date.now()): void {
805
+ emitSaveState(state: 'idle' | 'dirty' | 'saving' | 'saved' | 'error', at = Date.now()): void {
3778
806
  this.emit('saveState', { state, at })
3779
807
  }
3780
808
 
3781
809
  /** 持久化设备 transform(位置/朝向/缩放一次写入;防抖 350ms,不逐帧写库) */
3782
- persistDeviceTransform(id: string): void {
3783
- const dev = this.deviceNodes.get(id)
3784
- if (!dev || !this.devices?.update || this.pendingDeviceCreates.has(id)) return
3785
- const st = this.scalables.get(`device:${id}`)
3786
- const prev = this.pendingSaveTimers.get(id)
3787
- if (prev) clearTimeout(prev)
3788
- this.pendingSaveTimers.set(id, setTimeout(() => {
3789
- this.pendingSaveTimers.delete(id)
3790
- if (this.disposed || this.pendingDeviceCreates.has(id)) return
3791
- this.emitSaveState('saving')
3792
- void this.devices!.update(id, {
3793
- posX: Math.round(dev.root.position.x * 10) / 10,
3794
- posZ: Math.round(dev.root.position.z * 10) / 10,
3795
- rotationY: Math.round(THREE.MathUtils.radToDeg(dev.root.rotation.y) * 10) / 10,
3796
- scale: st ? Math.round(st.userScale * 100) / 100 : undefined,
3797
- })
3798
- .then(() => this.emitSaveState('saved', Date.now()))
3799
- .catch(() => this.emitSaveState('error'))
3800
- }, 350))
3801
- this.emitSaveState('dirty')
3802
- }
810
+ persistDeviceTransform(id: string): void { persistDeviceTransform(this, id) }
3803
811
 
3804
812
  /** 保存全部设备节点(「保存布局」按钮:强制全量落库) */
3805
- persistAllDevices(): void {
3806
- for (const id of [...this.deviceNodes.keys()]) this.persistDeviceTransform(id)
3807
- }
813
+ persistAllDevices(): void { persistAllDevices(this) }
3808
814
 
3809
- private pendingSaveTimers = new Map<string, ReturnType<typeof setTimeout>>()
815
+ pendingSaveTimers = new Map<string, ReturnType<typeof setTimeout>>()
3810
816
 
3811
817
  // ================================================================
3812
- // 设备场景实例同步(服务端设备孪生 → 本地节点;多客户端一致)
818
+ // 设备场景实例同步(服务端设备孪生 → 本地节点;多客户端一致;
819
+ // 实现见 scene/town-scene3d-devices.ts)
3813
820
  // ================================================================
3814
821
 
3815
822
  /**
3816
- * 与设备孪生清单对齐:
3817
- * - 服务端有落点且本地无节点 → 按保存的 transform 重建节点(刷新后恢复);
3818
- * - 已存在 → 收敛状态/遥测,并在未拖拽时收敛 transform(他人编辑即时跟随);
3819
- * - 服务端已删除 → 移除本地节点。
3820
- * TownView 在 device.* 事件与轮询时调用;轮询兜底多客户端同步。
823
+ * 与设备孪生清单对齐:服务端有落点且本地无节点 → 重建;已存在 → 收敛状态/遥测/transform;
824
+ * 服务端已删除 → 移除本地节点。TownView 在 device.* 事件与轮询时调用。
3821
825
  */
3822
- syncDevices(twins: DeviceTwinSync[]): void {
3823
- const serverIds = new Set(twins.map(t => t.id))
3824
- for (const id of [...this.deviceNodes.keys()]) {
3825
- if (!serverIds.has(id) && !this.pendingDeviceCreates.has(id)) this.removeDeviceNode(id)
3826
- }
3827
- for (const t of twins) {
3828
- let existing = this.deviceNodes.get(t.id)
3829
- if (!existing) {
3830
- const pending = [...this.pendingDeviceCreates.entries()].find(([, p]) =>
3831
- p.name === t.name && p.modelRef === t.modelRef,
3832
- )
3833
- if (pending) {
3834
- this.adoptDeviceNode(pending[0], t.id)
3835
- existing = this.deviceNodes.get(t.id)
3836
- }
3837
- }
3838
- if (!existing) {
3839
- // 数采/智控节点走程序化路径(无 GLB 也可实例化);设备需模型可解析 + 已入场景
3840
- const isDaq = t.kind === 'daq' || (t.modelRef ?? '').startsWith('daq-') || (t.modelRef ?? '').startsWith('dcw-')
3841
- const resolvable = isDaq || !!this.resolveDeviceModel(t.modelRef)?.file
3842
- if (resolvable && typeof t.posX === 'number' && typeof t.posZ === 'number') this.recreateDeviceNode(t)
3843
- else this.pendingDeviceTwins.set(t.id, t)
3844
- continue
3845
- }
3846
- this.pendingDeviceTwins.delete(t.id)
3847
- existing.applyTwin(t)
3848
- if (this.pointerDrag?.id !== t.id) {
3849
- const moved = (typeof t.posX === 'number' && t.posX !== existing.root.position.x)
3850
- || (typeof t.posZ === 'number' && t.posZ !== existing.root.position.z)
3851
- if (typeof t.posX === 'number') existing.root.position.x = t.posX
3852
- if (typeof t.posZ === 'number') existing.root.position.z = t.posZ
3853
- if (typeof t.rotationY === 'number') existing.root.rotation.y = t.rotationY * Math.PI / 180
3854
- const st = this.scalables.get(`device:${t.id}`)
3855
- if (typeof t.scale === 'number' && st) {
3856
- st.userScale = t.scale
3857
- st.holder.scale.setScalar(t.scale)
3858
- }
3859
- if (moved) this.dirty = true
3860
- }
3861
- this.dirty = true
3862
- }
3863
- // 设备节点就位后重仲裁绑定链路(链路期望先于节点到达的时序兜底)
3864
- if (this.daqLinksWanted.length) this.syncDaqLinks(this.daqLinksWanted)
3865
- for (const id of [...this.pendingDeviceTwins.keys()]) {
3866
- if (!serverIds.has(id)) this.pendingDeviceTwins.delete(id)
3867
- }
3868
- }
826
+ syncDevices(twins: DeviceTwinSync[]): void { syncDevices(this, twins) }
3869
827
 
3870
828
  /** 按设备孪生记录重建场景节点(持久化恢复:pos/rotation/scale;模型缺失则跳过) */
3871
- /** 设备模型解析:精确 id → `dev-folder-<ref>` 前缀 → `-<ref>` 后缀(兼容旧数据短 modelRef) */
3872
- private resolveDeviceModel(ref: string): { id: string, file: string, hFactor: number } | null {
3873
- const direct = this.modelsById.get(ref)
3874
- if (direct?.file) return { id: direct.id, file: direct.file, hFactor: direct.hFactor ?? 1 }
3875
- for (const [id, m] of this.modelsById) {
3876
- if (m.kind !== 'dev' || !m.file) continue
3877
- if (id === `dev-folder-${ref}` || id.endsWith(`-${ref}`)) return { id, file: m.file, hFactor: m.hFactor ?? 1 }
3878
- }
3879
- return null
3880
- }
3881
-
3882
- private recreateDeviceNode(t: DeviceTwinSync): void {
3883
- // 数采/智控节点:程序化传感网格(基座 + 传感头 + 信号环),不依赖 GLB 资产
3884
- // (modelRef 前缀兜底:旧数据 kind 落成 'device' 的数采实例同样走程序化路径)
3885
- if (t.kind === 'daq' || (t.modelRef ?? '').startsWith('daq-') || (t.modelRef ?? '').startsWith('dcw-')) {
3886
- this.recreateDaqNode(t)
3887
- return
3888
- }
3889
- const model = this.resolveDeviceModel(t.modelRef)
3890
- const file = model?.file ?? ''
3891
- if (!file) return
3892
- const x = typeof t.posX === 'number' ? t.posX : WORLD_CX
3893
- const z = typeof t.posZ === 'number' ? t.posZ : WORLD_CZ
3894
- const root = new THREE.Group()
3895
- root.position.set(x, GROUND_Y, z)
3896
- root.rotation.y = typeof t.rotationY === 'number' ? t.rotationY * Math.PI / 180 : 0
3897
- const { ring, arc } = this.makeDeviceRing()
3898
- root.add(ring, arc)
3899
- const holder = new THREE.Group()
3900
- root.add(holder)
3901
- const label = this.makeLabel(`⚙ ${t.name}`, x, 60, z)
3902
- holder.userData.modelFile = file
3903
- this.scene.add(root)
3904
- const twin = new DeviceNode({ twinId: t.id, name: t.name, modelRef: t.modelRef, root, holder, ring, label, state: t.state ?? 'idle', telemetry: t.telemetry ?? {} })
3905
- twin.host = this
3906
- twin.arc = arc
3907
- twin.updateRing()
3908
- this.deviceNodes.set(t.id, twin)
3909
- // 服务端缩放优先(持久化恢复);缺省回退 localStorage
3910
- this.registerScalable('device', t.id, holder, typeof t.scale === 'number' ? t.scale : undefined)
3911
- // 高度系数:产线设备按设计稿比例错落(基准高 × 模型系数),未知模型维持 1
3912
- void this.loadGltfToGroup(file, holder, UNITS * 1.6 * (model?.hFactor ?? 1))
3913
- this.dirty = true
3914
- }
3915
-
3916
- /** 数采节点传感头(设计稿 DAQ 预制件 1:1 移植,×34 世界单位):
3917
- * 立杆基座 + 顶端 LED 环 + 每模板独立传感形态 —— 温度计/压力表/张力辊/编码盘/相机/电参天线。 */
3918
- private makeDaqMesh(modelRef: string): { group: THREE.Group, ledRing: THREE.Mesh } {
3919
- const S = 34
3920
- const g = new THREE.Group()
3921
- const steel = new THREE.MeshStandardMaterial({ color: 0x9fb2c8, metalness: 0.85, roughness: 0.32 })
3922
- const body = new THREE.MeshStandardMaterial({ color: 0x74869c, metalness: 0.35, roughness: 0.5 })
3923
- const body2 = new THREE.MeshStandardMaterial({ color: 0x55647a, metalness: 0.4, roughness: 0.55 })
3924
- const dark = new THREE.MeshStandardMaterial({ color: 0x2b3442, metalness: 0.7, roughness: 0.45 })
3925
- const chrome = new THREE.MeshStandardMaterial({ color: 0xdfe8f2, metalness: 1, roughness: 0.14 })
3926
- const copper = new THREE.MeshStandardMaterial({ color: 0xc57a45, metalness: 0.9, roughness: 0.35 })
3927
- const glow = new THREE.MeshBasicMaterial({ color: 0x41c8f4 })
3928
- glow.color.multiplyScalar(5)
3929
- const B = (w: number, h: number, d: number, m: THREE.Material): THREE.Mesh => {
3930
- const mesh = new THREE.Mesh(new THREE.BoxGeometry(w * S, h * S, d * S), m)
3931
- mesh.castShadow = true
3932
- return mesh
3933
- }
3934
- const Cyl = (rt: number, rb: number, h: number, m: THREE.Material, seg = 20): THREE.Mesh => {
3935
- const mesh = new THREE.Mesh(new THREE.CylinderGeometry(rt * S, rb * S, h * S, seg), m)
3936
- mesh.castShadow = true
3937
- return mesh
3938
- }
3939
- const at = (m: THREE.Mesh, x: number, y: number, z: number): THREE.Mesh => {
3940
- m.position.set(x * S, y * S, z * S)
3941
- g.add(m)
3942
- return m
3943
- }
3944
- // 立杆基座(设计稿 daqPole)
3945
- at(Cyl(0.17, 0.22, 0.09, dark), 0, 0.045, 0)
3946
- at(Cyl(0.028, 0.028, 0.95, steel, 10), 0, 0.55, 0)
3947
- const ledRing = new THREE.Mesh(new THREE.TorusGeometry(0.09 * S, 0.02 * S, 10, 28), new THREE.MeshBasicMaterial({ color: 0x35e0a0 }))
3948
- // HDR 亮度:LED 环(及其复用材质的 halo/iris/tip)提到阈值之上,"真发光"元件(bloom 起晕)
3949
- ;(ledRing.material as THREE.MeshBasicMaterial).color.multiplyScalar(6)
3950
- ledRing.rotation.x = Math.PI / 2
3951
- g.add(ledRing)
3952
- ledRing.position.y = 1.02 * S
3953
- // 每模板传感头(modelRef = `daq-<tplId>`)
3954
- if (modelRef.endsWith('temp-tc')) {
3955
- at(B(0.24, 0.32, 0.24, body), 0, 1.2, 0)
3956
- const probe = at(Cyl(0.014, 0.014, 0.4, steel, 8), 0.14, 0.85, 0)
3957
- probe.rotation.z = -0.5
3958
- }
3959
- else if (modelRef.endsWith('pressure-tx')) {
3960
- at(Cyl(0.13, 0.16, 0.3, steel), 0, 1.22, 0)
3961
- at(Cyl(0.12, 0.12, 0.05, dark), 0, 1.4, 0)
3962
- at(B(0.05, 0.05, 0.2, copper), 0, 1.1, 0.2)
3963
- }
3964
- else if (modelRef.endsWith('tension-cell')) {
3965
- at(B(0.34, 0.2, 0.2, body), 0, 1.25, 0)
3966
- for (const x of [-0.11, 0.11]) {
3967
- const roll = at(Cyl(0.06, 0.06, 0.26, chrome), x, 1.25, 0)
3968
- roll.rotation.x = Math.PI / 2
3969
- }
3970
- }
3971
- else if (modelRef.endsWith('line-encoder')) {
3972
- at(B(0.34, 0.05, 0.05, steel), 0.14, 1.28, 0)
3973
- const disc = at(Cyl(0.16, 0.16, 0.05, dark), 0.32, 1.28, 0)
3974
- disc.rotation.z = Math.PI / 2
3975
- const halo = new THREE.Mesh(new THREE.TorusGeometry(0.1 * S, 0.015 * S, 10, 26), ledRing.material as THREE.Material)
3976
- at(halo, 0.35, 1.28, 0)
3977
- }
3978
- else if (modelRef.endsWith('vision-cam')) {
3979
- at(B(0.28, 0.2, 0.36, body2), 0, 1.3, 0)
3980
- const lens = at(Cyl(0.07, 0.09, 0.14, dark), 0, 1.3, 0.24)
3981
- lens.rotation.x = Math.PI / 2
3982
- const iris = new THREE.Mesh(new THREE.TorusGeometry(0.075 * S, 0.012 * S, 10, 24), ledRing.material as THREE.Material)
3983
- at(iris, 0, 1.3, 0.3)
3984
- }
3985
- else if (modelRef.endsWith('power-meter')) {
3986
- at(B(0.32, 0.42, 0.18, body), 0, 1.3, 0)
3987
- at(Cyl(0.012, 0.012, 0.42, steel, 8), 0.1, 1.7, 0)
3988
- const tip = new THREE.Mesh(new THREE.SphereGeometry(0.028 * S, 10, 8), ledRing.material as THREE.Material)
3989
- at(tip, 0.1, 1.92, 0)
3990
- at(B(0.2, 0.06, 0.02, glow), 0, 1.42, 0.1)
3991
- }
3992
- return { group: g, ledRing }
3993
- }
3994
-
3995
- /** 数采/智控节点(程序化):模板分化传感网格 + **产线光晕**(同产线同色光环)+ 名牌 */
3996
- private recreateDaqNode(t: DeviceTwinSync): void {
3997
- const x = typeof t.posX === 'number' ? t.posX : WORLD_CX
3998
- const z = typeof t.posZ === 'number' ? t.posZ : WORLD_CZ
3999
- const root = new THREE.Group()
4000
- root.position.set(x, GROUND_Y, z)
4001
- root.rotation.y = typeof t.rotationY === 'number' ? t.rotationY * Math.PI / 180 : 0
4002
- const { group: sensor, ledRing } = this.makeDaqMesh(t.modelRef)
4003
- root.add(sensor)
4004
- const sigRing = new THREE.Mesh(
4005
- new THREE.RingGeometry(16, 21, 36),
4006
- new THREE.MeshBasicMaterial({ color: 0x35e0a0, transparent: true, opacity: 0.5, side: THREE.DoubleSide, depthWrite: false }),
4007
- )
4008
- sigRing.rotation.x = -Math.PI / 2
4009
- sigRing.position.y = 0.35
4010
- root.add(sigRing)
4011
- // 产线光晕:外圈加性光环(soft) + 内圈亮环;未分配 = 缺省绿
4012
- const haloOuter = new THREE.Mesh(
4013
- new THREE.RingGeometry(24, 34, 48),
4014
- new THREE.MeshBasicMaterial({ color: 0x35e0a0, transparent: true, opacity: 0.16, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending }),
4015
- )
4016
- haloOuter.rotation.x = -Math.PI / 2
4017
- haloOuter.position.y = 0.28
4018
- root.add(haloOuter)
4019
- const accent = t.lineColor || '#35e0a0'
4020
- const label = this.makeLabel(t.name, x, 62, z, accent)
4021
- this.scene.add(root)
4022
- const node = new DeviceNode({
4023
- twinId: t.id, name: t.name, modelRef: t.modelRef,
4024
- root, holder: root, ring: sigRing, label,
4025
- state: t.state ?? 'running', telemetry: t.telemetry ?? {},
4026
- })
4027
- node.host = this
4028
- node.lineColor = t.lineColor ?? ''
4029
- // 换产线/换色:重 tint 光环 + LED 环 + 名牌(applyTwin 检测 lineColor 变化时触发)
4030
- node.applyLine = (color: string) => {
4031
- const c = new THREE.Color(color || '#35e0a0')
4032
- ;(sigRing.material as THREE.MeshBasicMaterial).color.copy(c)
4033
- ;(haloOuter.material as THREE.MeshBasicMaterial).color.copy(c)
4034
- ;(ledRing.material as THREE.MeshBasicMaterial).color.copy(c).multiplyScalar(6) // 保持 HDR(LED 起晕)
4035
- this.scene.remove(label)
4036
- label.material.dispose()
4037
- const fresh = this.makeLabel(node.name, root.position.x, 62, root.position.z, color || '#35e0a0')
4038
- node.label = fresh
4039
- this.scene.add(fresh)
4040
- this.dirty = true
4041
- }
4042
- if (t.lineColor) node.applyLine(t.lineColor)
4043
- node.updateRing()
4044
- this.deviceNodes.set(t.id, node)
4045
- this.daqLedRings.set(t.id, ledRing)
4046
- this.registerScalable('device', t.id, root, typeof t.scale === 'number' ? t.scale : undefined)
4047
- this.dirty = true
4048
- }
4049
-
4050
- /** 移除设备场景节点(服务端记录被删/本地重建清理) */
4051
- private removeDeviceNode(id: string): void {
4052
- const dev = this.deviceNodes.get(id)
4053
- if (!dev) return
4054
- this.scene.remove(dev.root)
4055
- this.scene.remove(dev.label)
4056
- this.disposeDeviceAssets(dev)
4057
- if (this.selected?.kind === 'device' && this.selected.id === id) this.setSelected(null)
4058
- this.scalables.delete(`device:${id}`)
4059
- this.deviceNodes.delete(id)
4060
- this.daqLedRings.delete(id)
4061
- this.daqLinkSig = ''
4062
- this.filmWebSig = ''
4063
- this.dirty = true
4064
- }
829
+ recreateDeviceNode(t: DeviceTwinSync): void { recreateDeviceNode(this, t) }
4065
830
 
4066
831
  // ================================================================
4067
- // 数采绑定链路 + 薄膜 web(设计稿 buildChanLine / rebuildWeb 移植)
832
+ // 数采绑定链路 + 薄膜 web(设计稿 buildChanLine / rebuildWeb 移植;
833
+ // 实现见 scene/town-scene3d-links.ts)
4068
834
  // ================================================================
4069
835
 
4070
- /** 设备顶端世界高度(holder 包围盒;链路/膜 web 的挂点)。
4071
- * 缓存优先:Box3.setFromObject 遍历整个 GLB 子树,原每帧每设备全量计算是 HUD 最大热点;
4072
- * 设备贴地 y=0,高度只随缩放变化 → setModelScale/registerScalable 时失效即可。 */
4073
- private deviceTopY(dev: DeviceNode): number {
4074
- if (dev.topYCache != null) return dev.topYCache
4075
- const box = new THREE.Box3().setFromObject(dev.holder)
4076
- const y = Number.isFinite(box.max.y) ? Math.max(40, box.max.y) : 64
4077
- dev.topYCache = y
4078
- return y
4079
- }
836
+ /** 设备顶端世界高度(holder 包围盒;链路/膜 web 的挂点) */
837
+ deviceTopY(dev: DeviceNode): number { return deviceTopY(dev) }
4080
838
 
4081
839
  /** 同步数采→设备绑定链路(TownView 传入 [{daqId, deviceId}];端点移动时逐帧跟随重建) */
4082
- syncDaqLinks(links: Array<{ daqId: string, deviceId: string }>): void {
4083
- this.daqLinksWanted = links
4084
- // 签名按"成功建链"的链路计算:节点未就绪被跳过的链路不计入 → 设备节点晚到时 syncDevices 末尾重仲裁补链
4085
- const built = links.filter(l => this.deviceNodes.has(l.daqId) && this.deviceNodes.has(l.deviceId))
4086
- const sig = built.map(l => `${l.daqId}>${l.deviceId}`).sort().join('|')
4087
- if (sig === this.daqLinkSig) return
4088
- this.daqLinkSig = sig
4089
- for (const l of this.daqLinks) {
4090
- this.daqLinkGroup.remove(l.line)
4091
- this.daqLinkGroup.remove(l.pulse)
4092
- l.line.geometry.dispose()
4093
- }
4094
- this.daqLinks = []
4095
- for (const l of built) {
4096
- const daq = this.deviceNodes.get(l.daqId)!
4097
- const dev = this.deviceNodes.get(l.deviceId)!
4098
- const a = new THREE.Vector3(daq.root.position.x, 42, daq.root.position.z)
4099
- const b = new THREE.Vector3(dev.root.position.x, this.deviceTopY(dev) + 6, dev.root.position.z)
4100
- const mid = a.clone().add(b).multiplyScalar(0.5)
4101
- mid.y = Math.max(a.y, b.y) + 42
4102
- const curve = new THREE.QuadraticBezierCurve3(a, mid, b)
4103
- const geo = new THREE.BufferGeometry().setFromPoints(curve.getPoints(28))
4104
- const line = new THREE.Line(geo, new THREE.LineDashedMaterial({ color: 0x35e0a0, transparent: true, opacity: 0.55, dashSize: 8, gapSize: 5 }))
4105
- line.computeLineDistances()
4106
- const pulse = new THREE.Mesh(new THREE.SphereGeometry(2.6, 10, 8), new THREE.MeshBasicMaterial({ color: 0x41c8f4 }))
4107
- ;(pulse.material as THREE.MeshBasicMaterial).color.multiplyScalar(5) // HDR:链路数据脉冲起晕
4108
- this.daqLinkGroup.add(line, pulse)
4109
- this.daqLinks.push({ ...l, line, pulse, curve, pt: Math.random(), ptSig: '' })
4110
- }
4111
- this.dirty = true
4112
- }
840
+ syncDaqLinks(links: Array<{ daqId: string, deviceId: string }>): void { syncDaqLinks(this, links) }
4113
841
 
4114
842
  /** 端点跟随:任一端点位移超阈值 → 重建该链路曲线(拖拽设备/数采时虚线实时跟随) */
4115
- /** 链路端点 scratch(逐帧跟随用;免每帧 3 次分配) */
4116
- private static readonly _lkA = new THREE.Vector3()
4117
- private static readonly _lkB = new THREE.Vector3()
4118
- private static readonly _lkM = new THREE.Vector3()
4119
-
4120
- private refreshDaqLinks(): void {
4121
- for (const l of this.daqLinks) {
4122
- const daq = this.deviceNodes.get(l.daqId)
4123
- const dev = this.deviceNodes.get(l.deviceId)
4124
- if (!daq || !dev) continue
4125
- const topY = this.deviceTopY(dev) + 6
4126
- const sig = `${Math.round(daq.root.position.x)},${Math.round(daq.root.position.z)},${Math.round(dev.root.position.x)},${Math.round(dev.root.position.z)},${Math.round(topY)}`
4127
- if (sig === l.ptSig) continue // 端点未动:曲线/geometry 不重建(脉冲仍逐帧走)
4128
- l.ptSig = sig
4129
- const a = TownScene3D._lkA.set(daq.root.position.x, 42, daq.root.position.z)
4130
- const b = TownScene3D._lkB.set(dev.root.position.x, topY, dev.root.position.z)
4131
- const mid = TownScene3D._lkM.copy(a).add(b).multiplyScalar(0.5)
4132
- mid.y = Math.max(a.y, b.y) + 42
4133
- l.curve.v0.copy(a)
4134
- l.curve.v1.copy(mid)
4135
- l.curve.v2.copy(b)
4136
- const pts = l.curve.getPoints(28)
4137
- l.line.geometry.setFromPoints(pts)
4138
- l.line.computeLineDistances()
4139
- }
4140
- }
4141
-
4142
- /** 产线设备型号识别(modelRef 含产线关键字 → 薄膜 web 连线成员) */
4143
- private isLineDevice(modelRef: string): boolean {
4144
- const ref = modelRef.toLowerCase()
4145
- return /extruder|caster|mdo|tdo|winder/.test(ref)
4146
- }
843
+ refreshDaqLinks(): void { refreshDaqLinks(this) }
4147
844
 
4148
845
  /** 薄膜 web:按 X 序连接产线设备(挤出→流延→MD→TD→收卷),半透明膜面 —— 产线工艺连续性可视化 */
4149
- private rebuildFilmWeb(): void {
4150
- const list = [...this.deviceNodes.values()]
4151
- .filter(d => this.isLineDevice(d.modelRef) && !d.modelRef.startsWith('daq-'))
4152
- .sort((a, b) => a.root.position.x - b.root.position.x)
4153
- const sig = list.map(d => `${d.twinId}:${Math.round(d.root.position.x)},${Math.round(d.root.position.z)}`).join('|')
4154
- if (sig === this.filmWebSig) return
4155
- this.filmWebSig = sig
4156
- while (this.filmWebGroup.children.length) {
4157
- const c = this.filmWebGroup.children[0] as THREE.Mesh
4158
- this.filmWebGroup.remove(c)
4159
- c.geometry.dispose()
4160
- }
4161
- if (!this.filmWebMat) {
4162
- this.filmWebMat = new THREE.MeshStandardMaterial({
4163
- color: 0xaad8ff, metalness: 0.1, roughness: 0.35, transparent: true, opacity: 0.28,
4164
- emissive: 0x2b6b8f, emissiveIntensity: 0.25, side: THREE.DoubleSide, depthWrite: false,
4165
- })
4166
- }
4167
- for (let i = 0; i < list.length - 1; i++) {
4168
- const a = list[i]!.root.position
4169
- const b = list[i + 1]!.root.position
4170
- const len = Math.abs(b.x - a.x) - 110
4171
- if (len < 30) continue
4172
- const web = new THREE.Mesh(new THREE.BoxGeometry(len, 1.2, 46), this.filmWebMat)
4173
- web.position.set((a.x + b.x) / 2, 48, (a.z + b.z) / 2)
4174
- this.filmWebGroup.add(web)
4175
- }
4176
- this.dirty = true
4177
- }
846
+ rebuildFilmWeb(): void { rebuildFilmWeb(this) }
4178
847
 
4179
848
  // ================================================================
4180
849
  // 生命周期 / 渲染循环
4181
850
  // ================================================================
4182
851
 
4183
- private loop(): void {
4184
- const animate = () => {
4185
- if (this.disposed) return
4186
- this.raf = requestAnimationFrame(animate)
4187
- this.rafCount += 1
4188
- const rawDt = this.clock.getDelta()
4189
- // 帧预算门控:数据消费与帧率解耦 —— WS 帧直写实时缓冲(消费层,不受此门控),
4190
- // 渲染循环每帧只取「当前最新值」上屏(展示层);budget 0 = 不限制(用户可选 60/120/∞)。
4191
- // 后台标签页 rAF 自动停摆;回前台 rawDt 巨大 → 钳制单步 ≤100ms 防动画跳变
4192
- if (this.frameBudgetMs > 0) {
4193
- this.frameAcc += rawDt * 1000
4194
- if (this.frameAcc < this.frameBudgetMs - 0.5) return
4195
- this.frameAcc = 0
4196
- }
4197
- const dt = Math.min(rawDt, 0.1)
4198
- const t = this.clock.elapsedTime
4199
- // 频道信息接收器:FIFO 逐条消费实时消息(每帧检查,展示期满取下一条)
4200
- this.drainReceivers(performance.now())
4201
- // 行为 FSM
4202
- for (const asp of this.agents.values()) {
4203
- if (asp.state !== 'stopped' && !asp.dragging) asp.update(dt)
4204
- }
4205
- // mixer 更新
4206
- for (const asp of this.agents.values()) {
4207
- if (asp.mixer) asp.mixer.update(dt)
4208
- }
4209
- // 频道信标缓转(顶标菱形旋转,中心定位销的生命感)
4210
- for (const b of this.blocks.values()) b.beacon.rotation.y += dt * 0.4
4211
- // 名字/气泡跟随 + 光环呼吸 + 逐帧动画(脉冲/光柱/缓动)
4212
- for (const asp of this.agents.values()) {
4213
- asp.nameSprite.position.set(asp.root.position.x, 48, asp.root.position.z)
4214
- if (asp.bubble) {
4215
- asp.bubble.position.set(asp.root.position.x, BUBBLE_Y + 22 + Math.max(0, asp.model.scale.y - 1) * 22, asp.root.position.z)
4216
- }
4217
- if (asp.auraMats.length) {
4218
- const br = 0.8 + 0.2 * Math.sin(t * 2.2 + asp.auraPhase)
4219
- for (const m of asp.auraMats) m.opacity = (m.userData.baseOp as number) * br
4220
- }
4221
- }
4222
- // 设备节点:名牌跟随(状态环是 root 子节点自动跟随;颜色由 updateRing 在状态变化时刷新)+ 运行弧缓转
4223
- for (const dev of this.deviceNodes.values()) {
4224
- dev.label.position.set(dev.root.position.x, 60, dev.root.position.z)
4225
- if (dev.arc?.visible) dev.arc.rotation.z += dt * 1.6
4226
- }
4227
- // 数采节点 LED 环:缓转 + 呼吸(绑定链路的信号生命感)
4228
- for (const ring of this.daqLedRings.values()) {
4229
- ring.rotation.z += dt * 1.2
4230
- const s = 1 + Math.sin(t * 3.2) * 0.08
4231
- ring.scale.setScalar(s)
4232
- }
4233
- // 绑定链路:端点位移时重建曲线(dirty 门控;脉冲仍逐帧行走)
4234
- if (this.daqLinks.length) {
4235
- if (this.dirty) this.refreshDaqLinks()
4236
- for (const l of this.daqLinks) {
4237
- l.pt = (l.pt + dt * 0.3) % 1
4238
- l.pulse.position.copy(l.curve.getPoint(l.pt))
4239
- }
4240
- }
4241
- // 薄膜 web:产线设备增删/移动时重建(dirty 门控;免每帧签名字符串分配)
4242
- if (this.dirty) this.rebuildFilmWeb()
4243
- // 选中高亮环:跟随选中目标(角色按用户缩放、设备按顶高定半径;慢转活性)
4244
- if (this.selRing) {
4245
- const sel = this.selected
4246
- let sx = 0
4247
- let sz = 0
4248
- let sr = 0
4249
- if (sel?.kind === 'agent') {
4250
- const a = this.agents.get(sel.id)
4251
- if (a) {
4252
- sx = a.root.position.x
4253
- sz = a.root.position.z
4254
- sr = 46 * Math.max(0.6, a.model.scale.y)
4255
- }
4256
- }
4257
- else if (sel?.kind === 'device') {
4258
- const d = this.deviceNodes.get(sel.id)
4259
- if (d) {
4260
- sx = d.root.position.x
4261
- sz = d.root.position.z
4262
- sr = Math.max(34, this.deviceTopY(d) * 0.42)
4263
- }
4264
- }
4265
- this.selRing.visible = sr > 0
4266
- if (sr > 0) {
4267
- this.selRing.position.set(sx, 0.5, sz)
4268
- this.selRing.scale.setScalar(sr)
4269
- this.selRing.rotation.z += dt * 0.9
4270
- }
4271
- }
4272
- const anims = this.rafAnims
4273
- this.rafAnims = []
4274
- for (const f of anims) f()
4275
- // 相机:围绕 camTarget 按 dolly 距离摆放(拖拽平移 camTarget,滚轮调 dolly,tween 平移 camTarget);
4276
- // 基线 1250/760 + FOV55 = 全园区电影化 2.5D 框景;穹顶随镜头平移(无限视野观感)
4277
- // 指数阻尼(帧率无关:1-e^(-λ·dt);60fps 下与旧每帧系数 0.12/0.08 等效,高刷屏不再加速)
4278
- const kDolly = 1 - Math.exp(-dt * 7.2)
4279
- if (this.autoDolly !== null) {
4280
- this.dolly += (this.autoDolly - this.dolly) * kDolly
4281
- if (Math.abs(this.autoDolly - this.dolly) < 0.01) this.autoDolly = null
4282
- }
4283
- const kView = 1 - Math.exp(-dt * 4.8)
4284
- for (const k of ['yaw', 'pitch', 'radius'] as const) {
4285
- this.viewCur[k] += (this.viewTarget[k] - this.viewCur[k]) * kView
4286
- }
4287
- // 自动环绕(设计稿 tOrbit):缓转方位角(dt 基 ≈ 24s/圈;指针按住场景时暂停)
4288
- if (this.autoOrbit && !this.pointerDrag) {
4289
- this.viewCur.yaw -= dt * 0.156
4290
- this.viewTarget.yaw = this.viewCur.yaw
4291
- }
4292
- const r = this.viewCur.radius * this.dolly
4293
- const cp = Math.cos(this.viewCur.pitch)
4294
- this.camera.position.set(
4295
- this.camTarget.x + r * cp * Math.sin(this.viewCur.yaw),
4296
- 20 + r * Math.sin(this.viewCur.pitch),
4297
- this.camTarget.z + r * cp * Math.cos(this.viewCur.yaw),
4298
- )
4299
- this.camera.lookAt(this.camTarget.x, 20, this.camTarget.z)
4300
- // 无级缩放:近/远裁剪面随 dolly 伸缩(贴脸到星野全程不裁剪)
4301
- const near = Math.max(0.05, 1.2 * this.dolly)
4302
- const far = 16000 * this.dolly
4303
- if (Math.abs(this.camera.near - near) > 0.005 || Math.abs(this.camera.far - far) > 1) {
4304
- this.camera.near = near
4305
- this.camera.far = far
4306
- this.camera.updateProjectionMatrix()
4307
- }
4308
- if (this.skyDome) {
4309
- this.skyDome.position.set(this.camTarget.x, 0, this.camTarget.z)
4310
- // 穹顶随 dolly 缩放:高空拉远时相机始终在穹内,无硬地平线
4311
- this.skyDome.scale.setScalar(Math.max(1, this.dolly * 1.3))
4312
- }
4313
- // 地面跟随镜头滑动(重复纹理 = 无限地面;高空不见地面边缘);
4314
- // UV 偏移反向补偿平面位移 → 网格钉死世界坐标(否则纹理跟平面一起滑,平移时有"冰面漂移"感)
4315
- this.ground.position.set(this.camTarget.x, 0, this.camTarget.z)
4316
- const gmap = (this.ground.material as THREE.MeshStandardMaterial).map
4317
- if (gmap) {
4318
- gmap.offset.set(
4319
- this.camTarget.x / ((WORLD_W * 16) / gmap.repeat.x),
4320
- -this.camTarget.z / ((WORLD_H * 16) / gmap.repeat.y),
4321
- )
4322
- }
4323
- // 雾距/阴影范围随 dolly 缩放:任意 zoom 层级下取景内容都不被雾吞、投影不消失
4324
- const fog = this.scene.fog
4325
- if (fog instanceof THREE.Fog) {
4326
- fog.near = 1400 * this.dolly
4327
- fog.far = 8800 * this.dolly
4328
- }
4329
- const sc = this.keyLight.shadow.camera
4330
- const ext = 1600 * Math.max(1, this.dolly)
4331
- if (Math.abs(sc.right - ext) > 1) {
4332
- sc.left = -ext
4333
- sc.right = ext
4334
- sc.top = 2300 * Math.max(1, this.dolly)
4335
- sc.bottom = -2300 * Math.max(1, this.dolly)
4336
- sc.updateProjectionMatrix()
4337
- this.renderer.shadowMap.needsUpdate = true
4338
- }
4339
- // 阴影按需:内容变化(dirty)或有动画角色(mixer 驱动蒙皮位移)才重绘阴影贴图
4340
- let shadowAnimated = false
4341
- for (const asp of this.agents.values()) {
4342
- if (asp.mixer) {
4343
- shadowAnimated = true
4344
- break
4345
- }
4346
- }
4347
- this.renderer.shadowMap.needsUpdate = this.dirty || shadowAnimated
4348
- // 后处理管线出图(RenderPass → Bloom → OutputPass;替代直渲染)
4349
- this.renderer.info.reset()
4350
- this.composer.render(dt)
4351
- this.dirty = false
4352
- // FPS(墙钟 1000ms 窗口:performance.now 真实时钟;原 dt 累计在长任务饿死 rAF 时虚高)
4353
- this.frameCount += 1
4354
- const wallNow = performance.now()
4355
- if (this.fpsWinT0 === 0) this.fpsWinT0 = wallNow
4356
- if (wallNow - this.fpsWinT0 >= 1000) {
4357
- const fps = Math.round((this.frameCount * 1000) / (wallNow - this.fpsWinT0))
4358
- this.emit('fps', fps)
4359
- this.adaptQuality(fps)
4360
- // 仪表化快照(1Hz;e2e/性能回归经 window.__townStats 读取,免再猜渲染真相)
4361
- const info = this.renderer.info
4362
- ;(globalThis as typeof globalThis & { __townStats?: Record<string, number> }).__townStats = {
4363
- fps,
4364
- rafHz: this.rafCount,
4365
- drawCalls: info.render.calls,
4366
- triangles: info.render.triangles,
4367
- geometries: info.memory.geometries,
4368
- textures: info.memory.textures,
4369
- tier: this.qTier,
4370
- dpr: +this.renderer.getPixelRatio().toFixed(2),
4371
- frameBudgetMs: +this.frameBudgetMs.toFixed(2),
4372
- agents: this.agents.size,
4373
- devices: this.deviceNodes.size,
4374
- }
4375
- this.frameCount = 0
4376
- this.rafCount = 0
4377
- this.fpsWinT0 = wallNow
4378
- }
4379
- }
4380
- animate()
4381
- }
4382
-
4383
- /** 质量阶梯:实测 fps 连续 2s < 17 → 降一档;连续 4s ≥ 27(帧预算附近)→ 升一档。
4384
- * 档位 = dprScale × 阴影贴图 × Bloom 开关的组合(Bloom+2048² 阴影是两大单项开销,
4385
- * 重活机器 11fps = 每帧 ~90ms 主线程阻塞)。统一阶梯,避免多套自适应互相打架。 */
4386
- private static readonly Q_TIERS = [
4387
- { scale: 1.0, shadow: 2048, bloom: true },
4388
- { scale: 0.8, shadow: 2048, bloom: true },
4389
- { scale: 0.65, shadow: 1024, bloom: true },
4390
- { scale: 0.55, shadow: 1024, bloom: false },
4391
- ] as const
852
+ private loop(): void { startRenderLoop(this) }
4392
853
 
4393
- private qTier = 0
854
+ qTier = 0
4394
855
 
4395
- private qLowStreak = 0
856
+ qLowStreak = 0
4396
857
 
4397
- private qHighStreak = 0
858
+ qHighStreak = 0
4398
859
 
4399
860
  /** 画质模式:auto = 质量阶梯自适应;manual 三档由用户固定(渲染配置不再自动变动) */
4400
- private qualityMode: 'auto' | 'normal' | 'hd' | 'ultra' = 'auto'
4401
-
4402
- /** 用户画质三档(超清 = WebGL 全配置:满 DPR/2048² 阴影/Bloom/最大各向异性) */
4403
- private static readonly USER_QUALITY = {
4404
- ultra: { scale: 1.0, shadow: 2048, bloom: true },
4405
- hd: { scale: 0.8, shadow: 2048, bloom: true },
4406
- normal: { scale: 0.6, shadow: 1024, bloom: true },
4407
- } as const
4408
-
4409
- setQualityMode(mode: 'auto' | 'normal' | 'hd' | 'ultra'): void {
4410
- this.qualityMode = mode
4411
- if (mode === 'auto') {
4412
- this.applyQuality()
4413
- return
4414
- }
4415
- const q = TownScene3D.USER_QUALITY[mode]
4416
- this.applyPixelRatio(Math.max(0.5, this.baseDpr * q.scale))
4417
- const key = this.keyLight
4418
- if ((key.shadow.mapSize.x ?? 0) !== q.shadow) {
4419
- key.shadow.mapSize.set(q.shadow, q.shadow)
4420
- key.shadow.map?.dispose()
4421
- key.shadow.map = null
4422
- this.renderer.shadowMap.needsUpdate = true
4423
- }
4424
- const bloomPass = this.composer.passes.find(p => p instanceof UnrealBloomPass)
4425
- if (bloomPass) bloomPass.enabled = q.bloom
4426
- }
861
+ qualityMode: 'auto' | 'normal' | 'hd' | 'ultra' = 'auto'
4427
862
 
4428
- private adaptQuality(fps: number): void {
4429
- if (this.qualityMode !== 'auto') return
4430
- const maxTier = TownScene3D.Q_TIERS.length - 1
4431
- if (fps < 17 && this.qTier < maxTier) {
4432
- if (++this.qLowStreak >= 2) {
4433
- this.qTier++
4434
- this.applyQuality()
4435
- this.qLowStreak = 0
4436
- this.qHighStreak = 0
4437
- }
4438
- }
4439
- else if (fps >= 27 && this.qTier > 0) {
4440
- if (++this.qHighStreak >= 4) {
4441
- this.qTier--
4442
- this.applyQuality()
4443
- this.qHighStreak = 0
4444
- this.qLowStreak = 0
4445
- }
4446
- }
4447
- else {
4448
- this.qLowStreak = 0
4449
- this.qHighStreak = 0
4450
- }
4451
- }
863
+ setQualityMode(mode: 'auto' | 'normal' | 'hd' | 'ultra'): void { setQualityMode(this, mode) }
4452
864
 
4453
- private applyQuality(): void {
4454
- const tier = TownScene3D.Q_TIERS[this.qTier]!
4455
- this.applyPixelRatio(Math.max(0.5, this.baseDpr * tier.scale))
4456
- // 阴影贴图缩容:置空 map 让 three 按新尺寸重分配(渲染循环按需重绘阴影)
4457
- const key = this.keyLight
4458
- if ((key.shadow.mapSize.x ?? 0) !== tier.shadow) {
4459
- key.shadow.mapSize.set(tier.shadow, tier.shadow)
4460
- key.shadow.map?.dispose()
4461
- key.shadow.map = null
4462
- this.renderer.shadowMap.needsUpdate = true
4463
- }
4464
- // Bloom 开关:Pass.enabled=false 跳过该 pass(RenderPass→OutputPass 直通)
4465
- const bloomPass = this.composer.passes.find(p => p instanceof UnrealBloomPass)
4466
- if (bloomPass) bloomPass.enabled = tier.bloom
4467
- }
865
+ adaptQuality(fps: number): void { adaptQuality(this, fps) }
4468
866
 
4469
- private applyPixelRatio(next: number): void {
4470
- this.renderer.setPixelRatio(next)
4471
- this.composer.setPixelRatio(next)
4472
- const w = this.el.clientWidth || 1100
4473
- const h = this.el.clientHeight || 700
4474
- this.renderer.setSize(w, h)
4475
- this.composer.setSize(w, h)
4476
- }
867
+ applyQuality(): void { applyQuality(this) }
868
+
869
+ applyPixelRatio(next: number): void { applyPixelRatio(this, next) }
4477
870
 
4478
871
  // ================================================================
4479
872
  // 实例本地资源释放(防长会话 GPU 内存只涨不跌)
@@ -4484,98 +877,17 @@ export class TownScene3D {
4484
877
  // 名牌/气泡的 CanvasTexture(材质 map)、状态环/链路等自建几何与材质。
4485
878
 
4486
879
  /** 释放对象树内所有 Canvas 纹理(名牌/气泡 sprite 的独占资源) */
4487
- private disposeCanvasTextures(root: THREE.Object3D | null): void {
4488
- if (!root) return
4489
- root.traverse((o) => {
4490
- const mesh = o as THREE.Mesh
4491
- const mat = mesh.material as THREE.Material | THREE.Material[] | undefined
4492
- const mats = Array.isArray(mat) ? mat : mat ? [mat] : []
4493
- for (const m of mats) {
4494
- const std = m as THREE.Material & { map?: THREE.Texture | null }
4495
- if (std.map && std.map.image instanceof HTMLCanvasElement) std.map.dispose()
4496
- m.dispose()
4497
- }
4498
- })
4499
- }
880
+ disposeCanvasTextures(root: THREE.Object3D | null): void { disposeCanvasTextures(root) }
4500
881
 
4501
882
  /** 释放单个设备节点的实例资源(状态环 + 运行弧 + LED 环 + 名牌) */
4502
- private disposeDeviceAssets(dev: DeviceNode): void {
4503
- dev.ring.geometry.dispose()
4504
- const rmat = dev.ring.material as THREE.Material & { map?: THREE.Texture | null }
4505
- rmat.dispose()
4506
- if (dev.arc) {
4507
- dev.arc.geometry.dispose()
4508
- ;(dev.arc.material as THREE.Material).dispose()
4509
- }
4510
- this.disposeCanvasTextures(dev.label)
4511
- }
883
+ disposeDeviceAssets(dev: DeviceNode): void { disposeDeviceAssets(dev) }
4512
884
 
4513
885
  /** 释放单个 agent 的实例资源(名牌/气泡纹理 + 光环 + 活动范围线几何;GLB 克隆不动) */
4514
- private disposeAgentAssets(a: { nameSprite?: THREE.Sprite | null, bubble?: THREE.Sprite | null, rangeLine?: THREE.Line | null, aura?: THREE.Group | null }): void {
4515
- this.disposeCanvasTextures(a.nameSprite ?? null)
4516
- this.disposeCanvasTextures(a.bubble ?? null)
4517
- a.rangeLine?.geometry.dispose()
4518
- if (a.aura) {
4519
- a.aura.traverse((o) => {
4520
- const m = o as THREE.Mesh
4521
- if (m.isMesh) {
4522
- m.geometry.dispose()
4523
- ;(m.material as THREE.Material).dispose()
4524
- }
4525
- })
4526
- a.aura = null
4527
- }
4528
- }
886
+ disposeAgentAssets(a: { nameSprite?: THREE.Sprite | null, bubble?: THREE.Sprite | null, rangeLine?: THREE.Line | null, aura?: THREE.Group | null }): void { disposeAgentAssets(a) }
4529
887
 
4530
888
  /** 重置全部(rebuild 用) */
4531
- private resetAll(): void {
4532
- for (const a of this.agents.values()) {
4533
- this.scene.remove(a.root)
4534
- if (a.nameSprite) this.scene.remove(a.nameSprite)
4535
- if (a.bubble) this.scene.remove(a.bubble)
4536
- if (a.rangeLine) this.scene.remove(a.rangeLine)
4537
- this.disposeAgentAssets(a)
4538
- }
4539
- for (const b of this.blocks.values()) {
4540
- this.scene.remove(b.platform)
4541
- this.scene.remove(b.padRing)
4542
- this.scene.remove(b.beacon)
4543
- this.scene.remove(b.boundary)
4544
- this.scene.remove(b.label)
4545
- this.disposeCanvasTextures(b.label)
4546
- }
4547
- for (const dev of this.deviceNodes.values()) {
4548
- this.scene.remove(dev.root)
4549
- this.scene.remove(dev.label)
4550
- this.disposeDeviceAssets(dev)
4551
- }
4552
- this.deviceNodes.clear()
4553
- this.scalables.clear()
4554
- this.receivers.clear()
4555
- this.pointerDrag = null
4556
- this.cancelRangeDraw()
4557
- this.selected = null
4558
- this.selectedChannel = null
4559
- for (const hl of this.resizeHandles) hl.mesh.visible = false
4560
- for (const hl of this.agentRangeHandles) hl.mesh.visible = false
4561
- this.agents.clear()
4562
- this.blocks.clear()
4563
- }
889
+ resetAll(): void { resetScene(this) }
4564
890
 
4565
891
  /** 销毁(卸载时由 TownView 调用) */
4566
- dispose(): void {
4567
- this.disposed = true
4568
- cancelAnimationFrame(this.raf)
4569
- delete (globalThis as { __townScene3d?: unknown }).__townScene3d
4570
- for (const t of this.pendingSaveTimers.values()) clearTimeout(t)
4571
- this.pendingSaveTimers.clear()
4572
- this.receivers.clear()
4573
- this.resizeOb?.disconnect()
4574
- this.resizeOb = null
4575
- this.tControls?.dispose()
4576
- this.tControls = null
4577
- this.composer?.dispose()
4578
- this.renderer.dispose()
4579
- if (this.renderer.domElement.parentElement === this.el) this.renderer.domElement.remove()
4580
- }
892
+ dispose(): void { disposeScene(this) }
4581
893
  }