agentworkshop 0.7.24 → 0.7.27

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 (243) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/2jaJHWIJ.js +1 -0
  3. package/.output/public/_nuxt/B7gZokg82.js +209 -0
  4. package/.output/public/_nuxt/{Dfe8vXdG.js → B8kt898d.js} +1 -1
  5. package/.output/public/_nuxt/{BvNJUu3s.js → BFGpEo8S.js} +1 -1
  6. package/.output/public/_nuxt/{BAfp6tmu.js → BG4zduPf.js} +5 -5
  7. package/.output/public/_nuxt/{BMb80_tm.js → BGhOrgle.js} +1 -1
  8. package/.output/public/_nuxt/{Bc-_IsBP.js → BHcpzI2n.js} +1 -1
  9. package/.output/public/_nuxt/{Djir5rSD.js → BTLEvDxM.js} +1 -1
  10. package/.output/public/_nuxt/{b50gYVZl.js → BWyR3awA.js} +1 -1
  11. package/.output/public/_nuxt/Bc89zKq2.js +1 -0
  12. package/.output/public/_nuxt/{CxY6LfeN.js → BetmGSO4.js} +1 -1
  13. package/.output/public/_nuxt/{DAR70Lf52.js → BgCrZKv_2.js} +1 -1
  14. package/.output/public/_nuxt/{DUToq-k_.js → BowTbasd.js} +1 -1
  15. package/.output/public/_nuxt/{D6HDb-vN2.js → BvtknqSC2.js} +1 -1
  16. package/.output/public/_nuxt/{CLZWFhZC.js → CTfqWGdy.js} +1 -1
  17. package/.output/public/_nuxt/Chc5pfAA.js +1 -0
  18. package/.output/public/_nuxt/CobSUzQv.js +2 -0
  19. package/.output/public/_nuxt/{kZV5TQvh2.js → CucID8qg2.js} +1 -1
  20. package/.output/public/_nuxt/{CAGL7TNK.js → DHbTyoAV.js} +1 -1
  21. package/.output/public/_nuxt/{DSa2O56y.js → DfLUN8kY.js} +1 -1
  22. package/.output/public/_nuxt/{Coik560n.js → DjU9Dx5Y.js} +1 -1
  23. package/.output/public/_nuxt/DnziMlcX.js +1 -0
  24. package/.output/public/_nuxt/{Ct68Z0232.js → DosOJEmZ2.js} +1 -1
  25. package/.output/public/_nuxt/{CDLv-n51.js → Q_SJDSgx.js} +1 -1
  26. package/.output/public/_nuxt/{TownView.zu2rU5nS.css → TownView.CtskuMXS.css} +1 -1
  27. package/.output/public/_nuxt/{CuVGPuP1.js → XY2z78rj.js} +1 -1
  28. package/.output/public/_nuxt/_id_.DdbYeLqn.css +1 -0
  29. package/.output/public/_nuxt/{C1CSiSBN.js → bFnZa2yd.js} +1 -1
  30. package/.output/public/_nuxt/builds/latest.json +1 -1
  31. package/.output/public/_nuxt/builds/meta/f7a8e33d-5422-45ac-895d-27168f97e3ad.json +1 -0
  32. package/.output/public/_nuxt/daq.DGV9QS8d.css +1 -0
  33. package/.output/public/_nuxt/{entry.AZM1Culo.css → entry.BN02X8iR.css} +1 -1
  34. package/.output/public/_nuxt/{X6IraCTB.js → rkVgzg62.js} +1 -1
  35. package/.output/public/_nuxt/settings.W6qrYAui.css +1 -0
  36. package/.output/public/_nuxt/teams.BBXVkyKq.css +1 -0
  37. package/.output/public/assets/game/character/yangyang-walk.glb +0 -0
  38. package/.output/server/chunks/_/host-bindings.mjs +1 -1
  39. package/.output/server/chunks/_/nitro.mjs +1013 -844
  40. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  41. package/.output/server/chunks/build/{TownView-DxeQe1Bl.mjs → TownView-ftIylqiD.mjs} +178 -151
  42. package/.output/server/chunks/build/{TownView-DxeQe1Bl.mjs.map → TownView-ftIylqiD.mjs.map} +1 -1
  43. package/.output/server/chunks/build/{_id_-DQDqj-cZ.mjs → _id_-3Axz2-gV.mjs} +35 -35
  44. package/.output/server/chunks/build/{_id_-DQDqj-cZ.mjs.map → _id_-3Axz2-gV.mjs.map} +1 -1
  45. package/.output/server/chunks/build/{_id_-DY6v6sqB.mjs → _id_-CahfkmEt.mjs} +5 -5
  46. package/.output/server/chunks/build/{_id_-DY6v6sqB.mjs.map → _id_-CahfkmEt.mjs.map} +1 -1
  47. package/.output/server/chunks/build/{_wsId_-D38Vx6Ky.mjs → _wsId_-BymlQhzS.mjs} +7 -7
  48. package/.output/server/chunks/build/{_wsId_-D38Vx6Ky.mjs.map → _wsId_-BymlQhzS.mjs.map} +1 -1
  49. package/.output/server/chunks/build/{agents-GnmUGZir.mjs → agents-DuMiSWKp.mjs} +4 -4
  50. package/.output/server/chunks/build/{agents-GnmUGZir.mjs.map → agents-DuMiSWKp.mjs.map} +1 -1
  51. package/.output/server/chunks/build/{channel-templates-B4z6v8Z5.mjs → channel-templates-BStqJ-of.mjs} +2 -2
  52. package/.output/server/chunks/build/{channel-templates-B4z6v8Z5.mjs.map → channel-templates-BStqJ-of.mjs.map} +1 -1
  53. package/.output/server/chunks/build/{checkbox-dzcpU_Cd.mjs → checkbox-C1gABOca.mjs} +4 -4
  54. package/.output/server/chunks/build/{checkbox-dzcpU_Cd.mjs.map → checkbox-C1gABOca.mjs.map} +1 -1
  55. package/.output/server/chunks/build/{daq-B-3xCp_2.mjs → daq-ChYCABz1.mjs} +312 -426
  56. package/.output/server/chunks/build/daq-ChYCABz1.mjs.map +1 -0
  57. package/.output/server/chunks/build/{dcw-BL2dhuz7.mjs → dcw-CcQNGggh.mjs} +3 -3
  58. package/.output/server/chunks/build/{dcw-BL2dhuz7.mjs.map → dcw-CcQNGggh.mjs.map} +1 -1
  59. package/.output/server/chunks/build/{default-CLf5m0NE.mjs → default-95-zOTPt.mjs} +2 -2
  60. package/.output/server/chunks/build/{default-CLf5m0NE.mjs.map → default-95-zOTPt.mjs.map} +1 -1
  61. package/.output/server/chunks/build/{logs-Dto_umFY.mjs → logs-D-2UyGtE.mjs} +3 -3
  62. package/.output/server/chunks/build/{logs-Dto_umFY.mjs.map → logs-D-2UyGtE.mjs.map} +1 -1
  63. package/.output/server/chunks/build/{monitor-Cp4V-H2n.mjs → monitor-mfG5jBtz.mjs} +3 -3
  64. package/.output/server/chunks/build/{monitor-Cp4V-H2n.mjs.map → monitor-mfG5jBtz.mjs.map} +1 -1
  65. package/.output/server/chunks/build/{pages-Dc2H1QFu.mjs → pages-Dfosfchr.mjs} +4 -4
  66. package/.output/server/chunks/build/{pages-Dc2H1QFu.mjs.map → pages-Dfosfchr.mjs.map} +1 -1
  67. package/.output/server/chunks/build/{permissions-BTRW2CVd.mjs → permissions-B-hhZuzm.mjs} +3 -3
  68. package/.output/server/chunks/build/{permissions-BTRW2CVd.mjs.map → permissions-B-hhZuzm.mjs.map} +1 -1
  69. package/.output/server/chunks/build/{settings-UfGyDgM7.mjs → settings-BajN9TQW.mjs} +249 -20
  70. package/.output/server/chunks/build/settings-BajN9TQW.mjs.map +1 -0
  71. package/.output/server/chunks/build/{table-Cswq-l2r.mjs → table-Bf1r2JjE.mjs} +2 -2
  72. package/.output/server/chunks/build/{table-Cswq-l2r.mjs.map → table-Bf1r2JjE.mjs.map} +1 -1
  73. package/.output/server/chunks/build/{teams-Cbg27MWW.mjs → teams-t2SkRlJP.mjs} +346 -46
  74. package/.output/server/chunks/build/teams-t2SkRlJP.mjs.map +1 -0
  75. package/.output/server/chunks/build/{tokens-Bmm6rDfj.mjs → tokens-DiCojbN8.mjs} +3 -3
  76. package/.output/server/chunks/build/{tokens-Bmm6rDfj.mjs.map → tokens-DiCojbN8.mjs.map} +1 -1
  77. package/.output/server/chunks/build/{town-Dsh0jlwZ.mjs → town-C7QM8DYK.mjs} +5 -5
  78. package/.output/server/chunks/build/{town-Dsh0jlwZ.mjs.map → town-C7QM8DYK.mjs.map} +1 -1
  79. package/.output/server/chunks/build/{useDaqStream-DkRL3tEW.mjs → useDaqStream-BeA2X8KR.mjs} +26 -11
  80. package/.output/server/chunks/build/useDaqStream-BeA2X8KR.mjs.map +1 -0
  81. package/.output/server/chunks/build/{useDcwStream-DyB2ufQI.mjs → useDcwStream-BJW_MitY.mjs} +14 -6
  82. package/.output/server/chunks/build/useDcwStream-BJW_MitY.mjs.map +1 -0
  83. package/.output/server/chunks/build/{useDeviceTwins-ZbT86L9O.mjs → useDeviceTwins-Dwz_EY_i.mjs} +2 -2
  84. package/.output/server/chunks/build/{useDeviceTwins-ZbT86L9O.mjs.map → useDeviceTwins-Dwz_EY_i.mjs.map} +1 -1
  85. package/.output/server/chunks/build/{useOpsLog-8HCYFb6n.mjs → useOpsLog-Dm8QEb1I.mjs} +2 -2
  86. package/.output/server/chunks/build/{useOpsLog-8HCYFb6n.mjs.map → useOpsLog-Dm8QEb1I.mjs.map} +1 -1
  87. package/.output/server/chunks/build/{useWorkshopApi-D2gq06N2.mjs → useWorkshopApi-B-L3MmIX.mjs} +9 -2
  88. package/.output/server/chunks/build/{useWorkshopApi-D2gq06N2.mjs.map → useWorkshopApi-B-L3MmIX.mjs.map} +1 -1
  89. package/.output/server/chunks/build/{users-B50HHrfi.mjs → users-B9SAdEtH.mjs} +3 -3
  90. package/.output/server/chunks/build/{users-B50HHrfi.mjs.map → users-B9SAdEtH.mjs.map} +1 -1
  91. package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs +14 -2
  92. package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs.map +1 -1
  93. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  94. package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs +40 -0
  95. package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs.map +1 -0
  96. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs +21 -9
  97. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs.map +1 -1
  98. package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs +33 -0
  99. package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs.map +1 -0
  100. package/.output/server/chunks/routes/api/workshop/channels/_id/terminals.get.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +15 -1
  102. package/.output/server/chunks/routes/api/workshop/channels.post.mjs.map +1 -1
  103. package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/daq/_id/frames/content.get.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/daq/_id/frames.get.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +1 -1
  108. package/.output/server/chunks/routes/api/workshop/daq/_id/test.post.mjs +1 -1
  109. package/.output/server/chunks/routes/api/workshop/daq/_id_.delete.mjs +1 -1
  110. package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +1 -1
  111. package/.output/server/chunks/routes/api/workshop/daq/alarms/_id/ack.post.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/daq/controller.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/daq/index.post.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +1 -1
  116. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +1 -1
  117. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +1 -1
  118. package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +1 -1
  119. package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +1 -1
  120. package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +1 -1
  121. package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +1 -1
  122. package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +1 -1
  123. package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +1 -1
  124. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +1 -1
  125. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +1 -1
  126. package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +1 -1
  127. package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +1 -1
  128. package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +1 -1
  129. package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +1 -1
  130. package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +1 -1
  131. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +1 -1
  132. package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +1 -1
  133. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +1 -1
  134. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +1 -1
  135. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +1 -1
  136. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +1 -1
  137. package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +1 -1
  138. package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +1 -1
  139. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  140. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  141. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
  142. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +1 -1
  143. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +1 -1
  144. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +1 -1
  145. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +1 -1
  146. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +1 -1
  147. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +1 -1
  148. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +1 -1
  149. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +1 -1
  150. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +1 -1
  151. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +1 -1
  152. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +1 -1
  153. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +1 -1
  154. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +1 -1
  155. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  156. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  157. package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +1 -1
  158. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +1 -1
  159. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.patch.mjs +1 -1
  160. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  161. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  162. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  163. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  164. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  165. package/.output/server/chunks/routes/api/workshop/index3.post.mjs +1 -1
  166. package/.output/server/chunks/routes/api/workshop/index4.get.mjs +1 -1
  167. package/.output/server/chunks/routes/api/workshop/index4.post.mjs +1 -1
  168. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +1 -1
  169. package/.output/server/chunks/routes/api/workshop/index5.post.mjs +1 -1
  170. package/.output/server/chunks/routes/api/workshop/index6.post.mjs +1 -1
  171. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  172. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  173. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  174. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  175. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  176. package/.output/server/chunks/routes/renderer.mjs +2 -2
  177. package/.output/server/chunks/virtual/entry.mjs +352 -433
  178. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  179. package/.output/server/package.json +1 -1
  180. package/app/components/workshop/DaqNodeRow.vue +398 -0
  181. package/app/components/workshop/town/ModelPreview3D.vue +26 -4
  182. package/app/components/workshop/town/TownScene3D.ts +60 -12
  183. package/app/components/workshop/town/TownView.vue +4 -3
  184. package/app/composables/workshop/useCharacterAssets.ts +1 -0
  185. package/app/composables/workshop/useDaqStream.ts +24 -5
  186. package/app/composables/workshop/useDcwStream.ts +13 -4
  187. package/app/composables/workshop/useWorkshopApi.ts +294 -259
  188. package/app/pages/daq/[id].vue +27 -3
  189. package/app/pages/daq/index.vue +8 -355
  190. package/app/pages/settings.vue +253 -2
  191. package/app/pages/workshop/teams.vue +212 -2
  192. package/i18n/dicts/_en.json +361 -1
  193. package/i18n/dicts/plugins.json +10 -0
  194. package/i18n/dicts/settings.json +1 -0
  195. package/i18n/dicts/teamPlugins.json +10 -0
  196. package/i18n/locales/en.ts +1860 -1900
  197. package/i18n/locales/zh-CN.ts +1788 -1829
  198. package/package.json +149 -148
  199. package/public/assets/game/character/yangyang-walk.glb +0 -0
  200. package/scripts/_dbg-cpu-profile.mjs +53 -0
  201. package/scripts/_dbg-render-perf.mjs +142 -0
  202. package/scripts/_dbg-render-regression.mjs +130 -0
  203. package/scripts/i18n-merge.mjs +152 -30
  204. package/scripts/production-closed-loop-e2e.mjs +254 -0
  205. package/scripts/team-plugins-api-smoke.mjs +149 -0
  206. package/scripts/three-system-e2e.mjs +448 -0
  207. package/server/api/plugins/[name]/[...path].ts +46 -31
  208. package/server/api/workshop/channels/[id]/plugins/index.get.ts +35 -0
  209. package/server/api/workshop/channels/[id]/plugins/index.put.ts +39 -0
  210. package/server/api/workshop/channels.post.ts +17 -0
  211. package/server/data/dcw-lines.json +7 -0
  212. package/server/data/dcw-products.json +7 -0
  213. package/server/data/dcw-recipes.json +1 -1
  214. package/server/data/dcw-rollback.json +523 -11
  215. package/server/data/dcw-writes.json +72 -0
  216. package/server/data/dcws.json +368 -368
  217. package/server/data/scene-layouts.json +66 -0
  218. package/server/plugins/aw-plugins.ts +35 -27
  219. package/server/plugins-builtin/diag-bridge/index.mjs +556 -0
  220. package/server/plugins-builtin/rag-bridge/index.mjs +364 -0
  221. package/server/services/workshop/agents/host-tool-bridge.ts +659 -642
  222. package/server/services/workshop/agents/omp-agent.ts +1178 -1178
  223. package/server/services/workshop/agents/plugin-tools.ts +10 -1
  224. package/server/services/workshop/db/channel-plugins.repo.ts +65 -0
  225. package/server/services/workshop/plugins/host.mjs +52 -4
  226. package/server/services/workshop/runtime/manager.ts +2976 -2968
  227. package/shared/config/schema.json +4 -0
  228. package/.output/public/_nuxt/BUcmySDa2.js +0 -209
  229. package/.output/public/_nuxt/CYzpZzB_.js +0 -1
  230. package/.output/public/_nuxt/CjXIpPMR.js +0 -1
  231. package/.output/public/_nuxt/DKHcFE1i.js +0 -1
  232. package/.output/public/_nuxt/DjtE7AYt.js +0 -1
  233. package/.output/public/_nuxt/JOwsShod.js +0 -2
  234. package/.output/public/_nuxt/_id_.C-YXjn8Q.css +0 -1
  235. package/.output/public/_nuxt/builds/meta/024de5f7-6a10-4f53-82ab-bc65f3ec9e35.json +0 -1
  236. package/.output/public/_nuxt/daq.3OKj58HH.css +0 -1
  237. package/.output/public/_nuxt/settings.BqMF2nfj.css +0 -1
  238. package/.output/public/_nuxt/teams.DN0pAGGu.css +0 -1
  239. package/.output/server/chunks/build/daq-B-3xCp_2.mjs.map +0 -1
  240. package/.output/server/chunks/build/settings-UfGyDgM7.mjs.map +0 -1
  241. package/.output/server/chunks/build/teams-Cbg27MWW.mjs.map +0 -1
  242. package/.output/server/chunks/build/useDaqStream-DkRL3tEW.mjs.map +0 -1
  243. package/.output/server/chunks/build/useDcwStream-DyB2ufQI.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworkshop-prod",
3
- "version": "0.7.24",
3
+ "version": "0.7.27",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "dependencies": {
@@ -0,0 +1,398 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * DaqNodeRow —— 数采节点表格行(渲染隔离单元)。
4
+ *
5
+ * 行级派生态(pill/趋势串/限值线/产线运行态/节拍文案)全部在本组件 computed 内求值,
6
+ * 只追踪「本行节点」的响应式依赖:其它行/其它节点的 WS 读数帧失效不再波及本行 patch
7
+ * (等价 v-memo 的隔离效果,但 v-memo 在 SSR 编译产物引用未定义的 _cache 导致 500,
8
+ * 子组件 props 身份跳过是 SSR 安全形态)。父层 v-for 传稳定节点引用,依赖不变零重渲。
9
+ */
10
+ import { computed } from 'vue'
11
+ import { message } from 'ant-design-vue'
12
+ import { useDaqStream, type DaqNodeLive } from '@/app/composables/workshop/useDaqStream'
13
+ import { useDcwStream } from '@/app/composables/workshop/useDcwStream'
14
+ import { useDeviceTwins } from '@/app/composables/workshop/useDeviceTwins'
15
+ import { apiErrorMessage } from '@/app/utils/api-error'
16
+ import { daqKeyFromRef, type DaqNodeState } from '#shared/daq-protocol'
17
+
18
+ const props = defineProps<{ n: DaqNodeLive }>()
19
+
20
+ const { t } = useI18n()
21
+ const daq = useDaqStream()
22
+ const dcw = useDcwStream()
23
+ const deviceTwins = useDeviceTwins()
24
+
25
+ // ---------- 行级展示状态(与页面筛选同源的诚实状态机) ----------
26
+ type RowState = DaqNodeState | 'idle' | 'disabled' | 'unassigned'
27
+
28
+ /** 数据新鲜度:超过 max(4×有效周期, 12s) 无新样本视为「采不到数据」(容忍 5s REST 刷新拍与时钟偏差) */
29
+ function staleOf(n: DaqNodeLive): boolean {
30
+ if (!n.lastAt) return true
31
+ const iv = n.intervalMs ?? daq.controller.defaultIntervalMs
32
+ return Date.now() - Date.parse(n.lastAt) > Math.max(iv * 4, 12_000)
33
+ }
34
+
35
+ function rowStateOf(n: DaqNodeLive): RowState {
36
+ if (!n.enabled) return 'disabled'
37
+ if (!n.lineId) return 'unassigned'
38
+ if (!dcw.lineStateOf(n.lineId).active) return 'idle'
39
+ if (!daq.controller.running || staleOf(n)) return 'offline'
40
+ return n.state
41
+ }
42
+
43
+ function isLive(n: DaqNodeLive): boolean {
44
+ const s = rowStateOf(n)
45
+ return s === 'ok' || s === 'warn' || s === 'alarm'
46
+ }
47
+
48
+ /** 活动配方对该节点的数采监控窗口(本线活动批次;不同 Recipe 不同窗口) */
49
+ function recipeWinOf(n: { lineId: string, id: string }): { min?: number, max?: number } | null {
50
+ if (!n.lineId) return null
51
+ const run = dcw.lineStateOf(n.lineId)
52
+ if (!run.active || !run.recipeId) return null
53
+ const r = dcw.recipes.find(x => x.id === run.recipeId)
54
+ return r?.daqWindows?.find(w => w.nodeId === n.id) ?? null
55
+ }
56
+
57
+ /** 配方窗口越限(与服务端 alarm 判定同源;行标红) */
58
+ function recipeAlarm(n: DaqNodeLive): boolean {
59
+ const w = recipeWinOf(n)
60
+ if (!w || n.value == null) return false
61
+ return (w.min != null && n.value < w.min) || (w.max != null && n.value > w.max)
62
+ }
63
+
64
+ /** 行内趋势的有效限值:活动配方窗口优先,节点预警带兜底,皆无 → 不画限值线 */
65
+ function limitOf(n: DaqNodeLive): { min: number | null | undefined, max: number | null | undefined } {
66
+ const w = recipeWinOf(n)
67
+ if (w && (w.min != null || w.max != null)) return { min: w.min, max: w.max }
68
+ if (n.warnLow != null || n.warnHigh != null) return { min: n.warnLow, max: n.warnHigh }
69
+ return { min: null, max: null }
70
+ }
71
+
72
+ // ---------- 行内趋势(SVG polyline;数据节拍由 store 合批,本组件按行失效) ----------
73
+ const TREND_W = 120
74
+ const TREND_H = 26
75
+
76
+ function trendPath(hist: number[]): string {
77
+ const vals = hist.filter(Number.isFinite)
78
+ if (vals.length < 2) return ''
79
+ const min = Math.min(...vals)
80
+ const max = Math.max(...vals)
81
+ const span = max - min || 1
82
+ return vals.map((v, i) => `${((i / (vals.length - 1)) * TREND_W).toFixed(1)},${(TREND_H - ((v - min) / span) * (TREND_H - 2) - 1).toFixed(1)}`).join(' ')
83
+ }
84
+
85
+ /** 上下限参考线在趋势图内的 y 坐标(量程不可知或无数据时返回 null 不画) */
86
+ function limitY(hist: number[], limit: number | undefined | null): number | null {
87
+ if (limit == null || !Number.isFinite(limit)) return null
88
+ const vals = hist.filter(Number.isFinite)
89
+ if (vals.length < 2) return null
90
+ const min = Math.min(...vals, limit)
91
+ const max = Math.max(...vals, limit)
92
+ const span = max - min || 1
93
+ return Number((TREND_H - ((limit - min) / span) * (TREND_H - 2) - 1).toFixed(1))
94
+ }
95
+
96
+ /** 状态 pill(含诚实的原因提示;配方越限标签仅在数据新鲜时展示) */
97
+ function statePillOf(n: DaqNodeLive): { key: RowState, label: string, tip: string } {
98
+ const s = rowStateOf(n)
99
+ const offlineLabel = t('daq.k44c2n065')
100
+ if (s === 'offline') {
101
+ if (n.lastError) return { key: s, label: offlineLabel, tip: n.lastError }
102
+ const tip = !daq.controller.running
103
+ ? t('daq.k1gateoff140')
104
+ : t('daq.k1staled129', { p0: Math.round(Math.max((n.intervalMs ?? daq.controller.defaultIntervalMs) * 4, 12_000) / 1000) })
105
+ return { key: s, label: offlineLabel, tip }
106
+ }
107
+ const labels: Record<RowState, string> = {
108
+ ok: t('daq.k41k5c062'),
109
+ warn: t('daq.k49z8v063'),
110
+ alarm: t('daq.k3xmid064'),
111
+ offline: offlineLabel,
112
+ idle: t('daq.k3ozyqz117'),
113
+ disabled: t('daq.k1disabl130'),
114
+ unassigned: t('daq.k3ootr6053'),
115
+ }
116
+ if (s === 'idle') return { key: s, label: labels.idle, tip: t('daq.k1idlehin139') }
117
+ if ((s === 'ok' || s === 'warn' || s === 'alarm') && recipeAlarm(n)) {
118
+ const w = recipeWinOf(n)
119
+ return { key: 'alarm', label: t('daq.k1l3pt51104'), tip: t('daq.k1hrrqa1122', { p0: w?.min ?? '-∞', p1: w?.max ?? '+∞', p2: '' }) }
120
+ }
121
+ return { key: s, label: labels[s], tip: '' }
122
+ }
123
+
124
+ /** 模板通道语义(server 目录为唯一事实源;模板已删除 → 显示 templateRef 原文降级) */
125
+ function daqTemplateRefCh(templateRef: string): string {
126
+ const tpl = daq.templates.find(x => x.key === daqKeyFromRef(templateRef))
127
+ return tpl ? `${tpl.name} · ${tpl.ch}` : templateRef || '-'
128
+ }
129
+
130
+ const intervalOf = (intervalMs: number | null): string => {
131
+ if (intervalMs == null) return t('daq.k9vnp9h124', { p0: daq.controller.defaultIntervalMs })
132
+ return `${intervalMs}ms`
133
+ }
134
+
135
+ /** WS 下发节拍展示(null=跟随全局;0=每帧;>0 独立间隔) */
136
+ const publishOf = (v: number | null): string => {
137
+ if (v == null) return t('daq.k9vnp9h124', { p0: daq.controller.defaultPublishIntervalMs })
138
+ if (v === 0) return t('daq.k41mvv078')
139
+ return `${v}ms`
140
+ }
141
+
142
+ /** 驱动是否为预留协议(meta status=planned) */
143
+ const driverPlanned = (kind: string): boolean =>
144
+ daq.meta.drivers.find(d => d.kind === kind)?.status === 'planned'
145
+
146
+ const nodeDeviceIds = (n: DaqNodeLive): string[] =>
147
+ n.deviceIds ?? (n.deviceBindingId ? [n.deviceBindingId] : [])
148
+ const deviceNameById = (id: string): string =>
149
+ deviceTwins.twins.find(x => x.id === id)?.name ?? id
150
+ /** 绑定设备下拉选项:全部真实设备孪生(剔除 daq/dcw 伪孪生) */
151
+ const bindableDeviceTwins = computed(() =>
152
+ deviceTwins.twins.filter(x => x.kind !== 'daq' && !(x.modelRef ?? '').startsWith('daq-') && !(x.modelRef ?? '').startsWith('dcw-')))
153
+
154
+ // ---------- 行渲染上下文:本节点依赖变化才重算(隔离单元核心) ----------
155
+ const ctx = computed(() => {
156
+ const n = props.n
157
+ const lim = limitOf(n)
158
+ const ls = n.lineId ? dcw.lineStateOf(n.lineId) : null
159
+ return {
160
+ tpl: daqTemplateRefCh(n.templateRef),
161
+ pill: statePillOf(n),
162
+ alarm: recipeAlarm(n),
163
+ live: isLive(n),
164
+ trend: trendPath(n.hist),
165
+ limMin: limitY(n.hist, lim.min),
166
+ limMax: limitY(n.hist, lim.max),
167
+ interval: intervalOf(n.intervalMs),
168
+ publish: publishOf(n.publishIntervalMs),
169
+ lineActive: !!ls?.active,
170
+ lineProduct: ls?.productName ?? '',
171
+ lineRecipe: ls?.recipeName ?? '',
172
+ }
173
+ })
174
+
175
+ // ---------- 行内交互(与原页面实现同构;toast 后端可读原因) ----------
176
+ /** 单节点独立 启动/停止采集(PATCH enabled;server 权威,本地乐观翻转 + WS/轮询收敛)。
177
+ * n 即 store 内响应式对象(身份同源),此处乐观翻转与原页面实现一致 */
178
+ async function toggleNodeEnabled(): Promise<void> {
179
+ const node = props.n
180
+ const next = !node.enabled
181
+ try {
182
+ await daq.patchNode(node.id, { enabled: next })
183
+ node.enabled = next
184
+ if (!next) node.state = 'offline'
185
+ message.success(next ? t('daq.k1nodestart147') : t('daq.k1nodestop146'))
186
+ }
187
+ catch (err) {
188
+ message.error(apiErrorMessage(err))
189
+ }
190
+ }
191
+
192
+ async function setNodeLine(e: Event): Promise<void> {
193
+ const lineId = (e.target as HTMLSelectElement).value
194
+ try {
195
+ await daq.patchNode(props.n.id, { lineId })
196
+ const node = props.n
197
+ node.lineId = lineId
198
+ }
199
+ catch (err) {
200
+ message.error(apiErrorMessage(err))
201
+ }
202
+ }
203
+
204
+ /** 节点绑定设备(多对多:add 挂新设备 / remove 摘除一台;server 落库) */
205
+ async function addNodeDevice(e: Event): Promise<void> {
206
+ const deviceId = (e.target as HTMLSelectElement).value
207
+ if (!deviceId) return
208
+ const node = props.n
209
+ if (node.deviceIds?.includes(deviceId)) return
210
+ try {
211
+ await daq.setNodeBindings(node.id, [...(node.deviceIds ?? []), deviceId])
212
+ }
213
+ catch (err) {
214
+ message.error(apiErrorMessage(err))
215
+ }
216
+ ;(e.target as HTMLSelectElement).value = ''
217
+ }
218
+
219
+ async function removeNodeDevice(deviceId: string): Promise<void> {
220
+ const node = props.n
221
+ try {
222
+ await daq.setNodeBindings(node.id, (node.deviceIds ?? []).filter(d => d !== deviceId))
223
+ }
224
+ catch (err) {
225
+ message.error(apiErrorMessage(err))
226
+ }
227
+ }
228
+ </script>
229
+
230
+ <template>
231
+ <tr :class="{ 'row-recipe-alarm': ctx.alarm }">
232
+ <td>
233
+ <span class="mono dim">{{ n.id.slice(0, 8) }}</span>
234
+ <b>{{ n.name }}</b>
235
+ <small class="mono ch">{{ ctx.tpl }}</small>
236
+ </td>
237
+ <td>
238
+ <span
239
+ class="st-pill"
240
+ :class="[ctx.pill.key]"
241
+ :title="ctx.pill.tip"
242
+ >{{ ctx.pill.label }}</span>
243
+ </td>
244
+ <td
245
+ class="mono val"
246
+ :class="{ 'stale': !ctx.live, 'val-alarm': n.state === 'alarm' || ctx.alarm }"
247
+ :title="ctx.live ? undefined : ctx.pill.tip"
248
+ >
249
+ {{ n.value != null ? n.value.toFixed(n.decimals) : '--' }}
250
+ <small>{{ n.unit }}</small>
251
+ </td>
252
+ <td class="trend-cell">
253
+ <svg
254
+ class="trend"
255
+ viewBox="0 0 120 26"
256
+ preserveAspectRatio="none"
257
+ :title="$t('daq.k1trnd002')"
258
+ >
259
+ <line
260
+ v-if="ctx.limMin != null"
261
+ class="lim"
262
+ x1="0"
263
+ :y1="ctx.limMin"
264
+ x2="120"
265
+ :y2="ctx.limMin"
266
+ />
267
+ <line
268
+ v-if="ctx.limMax != null"
269
+ class="lim max"
270
+ x1="0"
271
+ :y1="ctx.limMax"
272
+ x2="120"
273
+ :y2="ctx.limMax"
274
+ />
275
+ <polyline
276
+ class="trace"
277
+ :class="{ alarm: n.state === 'alarm' || ctx.alarm }"
278
+ :points="ctx.trend"
279
+ />
280
+ </svg>
281
+ </td>
282
+ <td class="mono">
283
+ {{ ctx.interval }}
284
+ </td>
285
+ <td class="mono">
286
+ {{ ctx.publish }}
287
+ </td>
288
+ <td>
289
+ <span
290
+ class="drv-tag"
291
+ :class="{ planned: driverPlanned(n.driver) }"
292
+ :title="driverPlanned(n.driver) ? $t('daq.k1plndrv138') : ''"
293
+ >{{ n.driver }}</span>
294
+ </td>
295
+ <td>
296
+ <select
297
+ class="line-sel"
298
+ :value="n.lineId"
299
+ :title="$t('daq.k3q23v3010')"
300
+ @change="setNodeLine"
301
+ >
302
+ <option value="">
303
+ {{ $t('daq.k3ootr6053') }}
304
+ </option>
305
+ <option
306
+ v-for="l in dcw.lines"
307
+ :key="l.id"
308
+ :value="l.id"
309
+ >
310
+ {{ l.name }}
311
+ </option>
312
+ </select>
313
+ </td>
314
+ <!-- 产线运行:呼吸绿点=运行中 / 空心点=未运行(与产线状态带同一套点语义) -->
315
+ <td>
316
+ <span
317
+ v-if="n.lineId"
318
+ class="run-pill"
319
+ :class="{ on: ctx.lineActive }"
320
+ :title="$t('daq.k1runtipc131')"
321
+ >
322
+ <span class="rp-dot" />
323
+ {{ ctx.lineActive ? $t('daq.k3vp67i096') : $t('daq.k3ozyqz117') }}
324
+ </span>
325
+ <span
326
+ v-else
327
+ class="run-pill na"
328
+ >{{ $t('daq.k3ootr6053') }}</span>
329
+ </td>
330
+ <!-- 产品 / Recipe:本线活动批次(运行中才携带;停线/未分配 → --) -->
331
+ <td class="prod-cell">
332
+ <template v-if="n.lineId && ctx.lineActive">
333
+ <b :title="$t('daq.k1prdrtip132')">{{ ctx.lineProduct || '--' }}</b>
334
+ <small
335
+ class="mono"
336
+ :title="$t('daq.k1prdrtip132')"
337
+ >{{ ctx.lineRecipe || '--' }}</small>
338
+ </template>
339
+ <span
340
+ v-else
341
+ class="dim"
342
+ >--</span>
343
+ </td>
344
+ <td>
345
+ <div class="dev-binds">
346
+ <span
347
+ v-for="did in nodeDeviceIds(n)"
348
+ :key="did"
349
+ class="dev-bind-chip"
350
+ :title="$t('daq.k2bindtip137')"
351
+ >
352
+ {{ deviceNameById(did) }}
353
+ <span
354
+ class="dev-unbind"
355
+ title="unbind"
356
+ @click="removeNodeDevice(did)"
357
+ >✕</span>
358
+ </span>
359
+ <select
360
+ class="line-sel dev-add"
361
+ :value="''"
362
+ :title="$t('daq.k2bindtip137')"
363
+ @change="addNodeDevice"
364
+ >
365
+ <option value="">
366
+ {{ $t('daq.k3own4q056') }}
367
+ </option>
368
+ <option
369
+ v-for="d in bindableDeviceTwins"
370
+ :key="d.id"
371
+ :value="d.id"
372
+ :disabled="(n.deviceIds ?? []).includes(d.id)"
373
+ >
374
+ {{ deviceNameById(d.id) }}
375
+ </option>
376
+ </select>
377
+ </div>
378
+ </td>
379
+ <td class="right">
380
+ <button
381
+ class="node-toggle"
382
+ :class="{ off: !n.enabled }"
383
+ :title="n.enabled ? $t('daq.k1nodestop146') : $t('daq.k1nodestart147')"
384
+ @click="toggleNodeEnabled"
385
+ >
386
+ <span :class="n.enabled ? 'i-tabler-player-pause' : 'i-tabler-player-play'" />
387
+ {{ n.enabled ? $t('daq.k1nodestop146') : $t('daq.k1nodestart147') }}
388
+ </button>
389
+ <NuxtLink
390
+ class="console-link"
391
+ :to="`/daq/${n.id}`"
392
+ >
393
+ <span class="i-tabler-dashboard" />
394
+ {{ $t('daq.k3o3jg2074') }}
395
+ </NuxtLink>
396
+ </td>
397
+ </tr>
398
+ </template>
@@ -38,6 +38,9 @@ interface Slot {
38
38
  w: number
39
39
  h: number
40
40
  speed: number
41
+ /** 可见性门控:面板收起/滚出视口/已卸载的槽位零渲染(IntersectionObserver 维护) */
42
+ visible: boolean
43
+ io: IntersectionObserver | null
41
44
  }
42
45
 
43
46
  interface SharedRig {
@@ -57,13 +60,19 @@ function acquireRig(): SharedRig {
57
60
  renderer.toneMapping = THREE.ACESFilmicToneMapping
58
61
  renderer.toneMappingExposure = 1.15
59
62
  const rig: SharedRig = { renderer, slots: new Set(), raf: 0, clock: new THREE.Clock() }
63
+ let acc = 0
60
64
  const tick = (): void => {
61
65
  rig.raf = requestAnimationFrame(tick)
62
66
  const dt = rig.clock.getDelta()
63
- // 逐槽渲染(76px 卡 × ~20 槽 = 极小开销);槽画布不在文档中(卸载竞态)则跳过
67
+ // 30fps 封顶:预览旋转 30fps 与满帧视觉无差,渲染+blit 开销按比例下降
68
+ acc += dt * 1000
69
+ if (acc < 1000 / 30) return
70
+ const step = acc / 1000
71
+ acc = 0
72
+ // 逐槽渲染;不可见/已卸载槽位跳过(原实现仅跳过「断连且零尺寸」,面板收起照烧)
64
73
  for (const s of rig.slots) {
65
- if (!s.canvas.isConnected && s.canvas.width === 0) continue
66
- if (s.group) s.group.rotation.y += dt * s.speed
74
+ if (!s.canvas.isConnected || !s.visible) continue
75
+ if (s.group) s.group.rotation.y += step * s.speed
67
76
  rig.renderer.setSize(s.w, s.h, false)
68
77
  rig.renderer.render(s.scene, s.camera)
69
78
  s.ctx.clearRect(0, 0, s.w, s.h)
@@ -145,7 +154,19 @@ function mountScene(source: string): void {
145
154
  const group = new THREE.Group()
146
155
  scene.add(group)
147
156
 
148
- slot = { canvas, ctx, scene, camera, group, w: canvas.width, h: canvas.height, speed: 0.6 }
157
+ slot = { canvas, ctx, scene, camera, group, w: canvas.width, h: canvas.height, speed: 0.6, visible: false, io: null }
158
+ // 可见性门控:视口外(含面板收起/滚动)不渲染;重新入屏自动恢复
159
+ if (typeof IntersectionObserver !== 'undefined') {
160
+ slot.io = new IntersectionObserver((entries) => {
161
+ for (const e of entries) {
162
+ if (slot) slot.visible = e.isIntersecting
163
+ }
164
+ })
165
+ slot.io.observe(canvas)
166
+ }
167
+ else {
168
+ slot.visible = true
169
+ }
149
170
  rig.slots.add(slot)
150
171
 
151
172
  loading.value = true
@@ -174,6 +195,7 @@ function dispose(): void {
174
195
  objectUrl = ''
175
196
  if (slot) {
176
197
  const rig = rigGlobal.__modelPreviewRig
198
+ slot.io?.disconnect()
177
199
  rig?.slots.delete(slot)
178
200
  // 显存释放:几何/材质/纹理逐项 dispose(场景小,遍历即可)
179
201
  slot.scene.traverse((c) => {
@@ -523,8 +523,8 @@ class Agent3D {
523
523
  this.animState = next
524
524
  this.host.notifyMotion(this)
525
525
  }
526
- if (this.clips.length === 0) {
527
- // 无动画 clip → 程序化动作绑定(model 局部,色环/名牌保持贴地稳定):
526
+ const bob = () => {
527
+ // 程序化动作绑定(model 局部,色环/名牌保持贴地稳定):
528
528
  // 待机呼吸浮动 + 行走跳跃颠簸 + 行走左右微摆
529
529
  const t = performance.now()
530
530
  const breathe = 1.2 + Math.sin(t * 0.0016) * 1.6
@@ -532,13 +532,27 @@ class Agent3D {
532
532
  this.model.position.y = moving ? hop : breathe
533
533
  const swayTarget = moving ? Math.sin(t * 0.006) * 0.07 : 0
534
534
  this.model.rotation.z += (swayTarget - this.model.rotation.z) * 0.12
535
+ }
536
+ if (this.clips.length === 0) {
537
+ bob()
535
538
  return
536
539
  }
537
- // 有动画 clip:按移动状态切换 idle/walk,crossfade 平滑过渡(避免双 action 叠加)
540
+ // 有动画 clip:名字感知选择(剪辑命名各异:WALK/Walk、idle/stand/breath...),
541
+ // 先按语义匹配,找不到回退索引(idle→0,walk→1);仅含走路剪辑的模型待机时
542
+ // 回退程序化 bob,避免原地播放走路。crossfade 平滑过渡(避免双 action 叠加)。
538
543
  if (!this.mixer) return
539
- const idx = moving ? (this.clips.length > 1 ? 1 : 0) : 0
540
- const clip = this.clips[idx]
541
- if (!clip) 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
+ }
542
556
  const nextAction = this.mixer.clipAction(clip)
543
557
  if (this.activeAction === nextAction) {
544
558
  nextAction.timeScale = moving ? 1.3 : 0.9
@@ -763,6 +777,9 @@ export class TownScene3D {
763
777
  private clock = new THREE.Clock()
764
778
  private frameCount = 0
765
779
  private fpsAccum = 0
780
+ /** 墙钟 FPS 窗口起点与 rAF 回调计数(徽标/自适应质量的真实输入;原 dt 累计会漂移) */
781
+ private fpsWinT0 = 0
782
+ private rafCount = 0
766
783
  /** 帧预算(ms):渲染节流上限;0 = 不限制。数据消费不走此门控(帧到达即入实时缓冲) */
767
784
  private frameBudgetMs = 1000 / 40
768
785
 
@@ -875,6 +892,9 @@ export class TownScene3D {
875
892
  // 电影级色调映射:高光滚降 + 中间调层次,GLB 材质不再"平板曝光"
876
893
  this.renderer.toneMapping = THREE.ACESFilmicToneMapping
877
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
878
898
  this.el.appendChild(this.renderer.domElement)
879
899
  // 舞台尺寸跟随宿主(网格布局/抽屉/全屏切换都会改变宿主尺寸)
880
900
  this.resizeOb = new ResizeObserver(() => {
@@ -2783,6 +2803,14 @@ export class TownScene3D {
2783
2803
  if ((o as THREE.Mesh).isMesh) o.castShadow = true
2784
2804
  })
2785
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
+ })
2786
2814
  group.add(gltf.scene)
2787
2815
  }
2788
2816
  catch {
@@ -4156,6 +4184,7 @@ export class TownScene3D {
4156
4184
  const animate = () => {
4157
4185
  if (this.disposed) return
4158
4186
  this.raf = requestAnimationFrame(animate)
4187
+ this.rafCount += 1
4159
4188
  const rawDt = this.clock.getDelta()
4160
4189
  // 帧预算门控:数据消费与帧率解耦 —— WS 帧直写实时缓冲(消费层,不受此门控),
4161
4190
  // 渲染循环每帧只取「当前最新值」上屏(展示层);budget 0 = 不限制(用户可选 60/120/∞)。
@@ -4317,16 +4346,35 @@ export class TownScene3D {
4317
4346
  }
4318
4347
  this.renderer.shadowMap.needsUpdate = this.dirty || shadowAnimated
4319
4348
  // 后处理管线出图(RenderPass → Bloom → OutputPass;替代直渲染)
4349
+ this.renderer.info.reset()
4320
4350
  this.composer.render(dt)
4321
4351
  this.dirty = false
4322
- // FPS
4352
+ // FPS(墙钟 1000ms 窗口:performance.now 真实时钟;原 dt 累计在长任务饿死 rAF 时虚高)
4323
4353
  this.frameCount += 1
4324
- this.fpsAccum += dt * 1000
4325
- if (this.fpsAccum >= 1000) {
4326
- this.emit('fps', this.frameCount)
4327
- this.adaptQuality(this.frameCount)
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
+ }
4328
4375
  this.frameCount = 0
4329
- this.fpsAccum = 0
4376
+ this.rafCount = 0
4377
+ this.fpsWinT0 = wallNow
4330
4378
  }
4331
4379
  }
4332
4380
  animate()
@@ -1574,15 +1574,16 @@ const liveTick = ref(0)
1574
1574
  const rtcVals = new Map<string, RtcPoint>()
1575
1575
  const rtcHist = new Map<string, number[]>()
1576
1576
  let unsubLiveVals: (() => void) | null = null
1577
- /** 帧计数合批:高频帧流只按渲染帧节拍失效一次(展示层一帧一拍;缓冲仍逐帧入账) */
1577
+ /** 帧计数合批:高频帧流按 200ms 定时节拍失效(原 rAF 合批在 60-220Hz rAF 下每秒
1578
+ * 重建 60-220 次 227 条目的 daqSim Map,是实测长任务主源;200ms 展示延迟不可感知) */
1578
1579
  let tickQueued = false
1579
1580
  function bumpLiveTick(): void {
1580
1581
  if (tickQueued) return
1581
1582
  tickQueued = true
1582
- requestAnimationFrame(() => {
1583
+ setTimeout(() => {
1583
1584
  tickQueued = false
1584
1585
  liveTick.value++
1585
- })
1586
+ }, 200)
1586
1587
  }
1587
1588
 
1588
1589
  /** 读数帧消费:实时缓冲直写 + 状态边沿即时告警(与批量 watch 共享 prevDaqState 去重) */
@@ -78,6 +78,7 @@ function createStore(): CharacterAssetStore {
78
78
  { id: 'mage', name: '紫晶法师', file: '/assets/game/character/mage.png', frames: 4, frameWidth: 48, frameHeight: 88, kind: 'sheet', builtin: true, applied: false },
79
79
  { id: 'bot', name: '青枢机械', file: '/assets/game/character/bot.png', frames: 4, frameWidth: 48, frameHeight: 88, kind: 'sheet', builtin: true, applied: false },
80
80
  { id: 'hero-3d', name: '标准员工模型', file: '/assets/game/character/hero-3d.glb', frames: 1, frameWidth: 0, frameHeight: 0, kind: 'glb', builtin: true, applied: false, hint: '默认员工 3D 模型' },
81
+ { id: 'yangyang-walk', name: '杨杨(走路)', file: '/assets/game/character/yangyang-walk.glb', frames: 1, frameWidth: 0, frameHeight: 0, kind: 'glb', builtin: true, applied: false, hint: '自定义角色:骨骼走路动画(剪辑 WALK;文件较大,建议少量绑定)' },
81
82
  { id: 'device-3d', name: '工业泵设备', file: '/assets/game/character/device-3d.glb', frames: 1, frameWidth: 0, frameHeight: 0, kind: 'dev', builtin: true, applied: false, hint: '数字孪生实体模型(拖入场景生成设备)' },
82
83
  ]
83
84