@forgeax/engine-app 0.0.0-dev.8d955ade1c79

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 (348) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +424 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/animation-asset-lookup.unit.test.d.ts +2 -0
  5. package/dist/__tests__/animation-asset-lookup.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/callback-deletion.unit.test.d.ts +2 -0
  7. package/dist/__tests__/callback-deletion.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/callback-inference.test-d.d.ts +2 -0
  9. package/dist/__tests__/callback-inference.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/component-introspection.unit.test.d.ts +2 -0
  11. package/dist/__tests__/component-introspection.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/consumer-migration.unit.test.d.ts +2 -0
  13. package/dist/__tests__/consumer-migration.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/create-app-error-retention.integration.test.d.ts +2 -0
  15. package/dist/__tests__/create-app-error-retention.integration.test.d.ts.map +1 -0
  16. package/dist/__tests__/create-app-features.test.d.ts +2 -0
  17. package/dist/__tests__/create-app-features.test.d.ts.map +1 -0
  18. package/dist/__tests__/create-app-listener-sync.test.d.ts +2 -0
  19. package/dist/__tests__/create-app-listener-sync.test.d.ts.map +1 -0
  20. package/dist/__tests__/create-app-lock-provider.test.d.ts +2 -0
  21. package/dist/__tests__/create-app-lock-provider.test.d.ts.map +1 -0
  22. package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts +2 -0
  23. package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts.map +1 -0
  24. package/dist/__tests__/create-app-plugin.test.d.ts +2 -0
  25. package/dist/__tests__/create-app-plugin.test.d.ts.map +1 -0
  26. package/dist/__tests__/create-app-stop.test.d.ts +2 -0
  27. package/dist/__tests__/create-app-stop.test.d.ts.map +1 -0
  28. package/dist/__tests__/create-app-time.unit.test.d.ts +2 -0
  29. package/dist/__tests__/create-app-time.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/create-app.test-d.d.ts +2 -0
  31. package/dist/__tests__/create-app.test-d.d.ts.map +1 -0
  32. package/dist/__tests__/create-app.test.d.ts +2 -0
  33. package/dist/__tests__/create-app.test.d.ts.map +1 -0
  34. package/dist/__tests__/create-renderer-error.integration.test.d.ts +2 -0
  35. package/dist/__tests__/create-renderer-error.integration.test.d.ts.map +1 -0
  36. package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts +2 -0
  37. package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts.map +1 -0
  38. package/dist/__tests__/docs-gate-green.unit.test.d.ts +2 -0
  39. package/dist/__tests__/docs-gate-green.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts +2 -0
  41. package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts.map +1 -0
  42. package/dist/__tests__/draw-source-single-world-regression.test.d.ts +2 -0
  43. package/dist/__tests__/draw-source-single-world-regression.test.d.ts.map +1 -0
  44. package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts +2 -0
  45. package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts +2 -0
  47. package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts.map +1 -0
  48. package/dist/__tests__/errors-pointer-lock-failed.test.d.ts +2 -0
  49. package/dist/__tests__/errors-pointer-lock-failed.test.d.ts.map +1 -0
  50. package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts +2 -0
  51. package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/execution-error-detail.test-d.d.ts +2 -0
  53. package/dist/__tests__/execution-error-detail.test-d.d.ts.map +1 -0
  54. package/dist/__tests__/execution-error-detail.unit.test.d.ts +2 -0
  55. package/dist/__tests__/execution-error-detail.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/execution-frame-order.unit.test.d.ts +2 -0
  57. package/dist/__tests__/execution-frame-order.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/execution-frame-protocol.unit.test.d.ts +2 -0
  59. package/dist/__tests__/execution-frame-protocol.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/execution-input-sample.contract.test.d.ts +2 -0
  61. package/dist/__tests__/execution-input-sample.contract.test.d.ts.map +1 -0
  62. package/dist/__tests__/execution-measurement.unit.test.d.ts +2 -0
  63. package/dist/__tests__/execution-measurement.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/execution-public-api.test-d.d.ts +2 -0
  65. package/dist/__tests__/execution-public-api.test-d.d.ts.map +1 -0
  66. package/dist/__tests__/execution-public-api.unit.test.d.ts +2 -0
  67. package/dist/__tests__/execution-public-api.unit.test.d.ts.map +1 -0
  68. package/dist/__tests__/execution-rebuild.contract.test.d.ts +2 -0
  69. package/dist/__tests__/execution-rebuild.contract.test.d.ts.map +1 -0
  70. package/dist/__tests__/execution-report.contract.test.d.ts +2 -0
  71. package/dist/__tests__/execution-report.contract.test.d.ts.map +1 -0
  72. package/dist/__tests__/execution-report.types.test-d.d.ts +2 -0
  73. package/dist/__tests__/execution-report.types.test-d.d.ts.map +1 -0
  74. package/dist/__tests__/execution-schema-freshness.unit.test.d.ts +2 -0
  75. package/dist/__tests__/execution-schema-freshness.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/execution-selector.unit.test.d.ts +2 -0
  77. package/dist/__tests__/execution-selector.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/execution-shared-kernel.unit.test.d.ts +2 -0
  79. package/dist/__tests__/execution-shared-kernel.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/fallback-camera.unit.test.d.ts +2 -0
  81. package/dist/__tests__/fallback-camera.unit.test.d.ts.map +1 -0
  82. package/dist/__tests__/frame-loop-time-elapsed.test.d.ts +2 -0
  83. package/dist/__tests__/frame-loop-time-elapsed.test.d.ts.map +1 -0
  84. package/dist/__tests__/frame-loop-time.unit.test.d.ts +2 -0
  85. package/dist/__tests__/frame-loop-time.unit.test.d.ts.map +1 -0
  86. package/dist/__tests__/frame-loop-world-array.test.d.ts +2 -0
  87. package/dist/__tests__/frame-loop-world-array.test.d.ts.map +1 -0
  88. package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts +2 -0
  89. package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/migration-surface-inventory.unit.test.d.ts +2 -0
  91. package/dist/__tests__/migration-surface-inventory.unit.test.d.ts.map +1 -0
  92. package/dist/__tests__/plugin-policy.integration.test.d.ts +2 -0
  93. package/dist/__tests__/plugin-policy.integration.test.d.ts.map +1 -0
  94. package/dist/__tests__/plugin-profiles.unit.test.d.ts +2 -0
  95. package/dist/__tests__/plugin-profiles.unit.test.d.ts.map +1 -0
  96. package/dist/__tests__/profiler-default-off.test.d.ts +2 -0
  97. package/dist/__tests__/profiler-default-off.test.d.ts.map +1 -0
  98. package/dist/__tests__/profiler-frame-token.test.d.ts +2 -0
  99. package/dist/__tests__/profiler-frame-token.test.d.ts.map +1 -0
  100. package/dist/__tests__/profiler-phase-catalog.test.d.ts +2 -0
  101. package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +1 -0
  102. package/dist/__tests__/profiler-phase-integration.test.d.ts +2 -0
  103. package/dist/__tests__/profiler-phase-integration.test.d.ts.map +1 -0
  104. package/dist/__tests__/profiler-remote-bridge.test.d.ts +2 -0
  105. package/dist/__tests__/profiler-remote-bridge.test.d.ts.map +1 -0
  106. package/dist/__tests__/remote-integration.test.d.ts +2 -0
  107. package/dist/__tests__/remote-integration.test.d.ts.map +1 -0
  108. package/dist/__tests__/rhi-capture-capability.test-d.d.ts +2 -0
  109. package/dist/__tests__/rhi-capture-capability.test-d.d.ts.map +1 -0
  110. package/dist/__tests__/rhi-capture-host.integration.test.d.ts +2 -0
  111. package/dist/__tests__/rhi-capture-host.integration.test.d.ts.map +1 -0
  112. package/dist/__tests__/rhi-capture.browser.test.d.ts +2 -0
  113. package/dist/__tests__/rhi-capture.browser.test.d.ts.map +1 -0
  114. package/dist/__tests__/surface-handoff.test.d.ts +2 -0
  115. package/dist/__tests__/surface-handoff.test.d.ts.map +1 -0
  116. package/dist/__tests__/types-m2.test-d.d.ts +2 -0
  117. package/dist/__tests__/types-m2.test-d.d.ts.map +1 -0
  118. package/dist/__tests__/visibility-remote.integration.test.d.ts +2 -0
  119. package/dist/__tests__/visibility-remote.integration.test.d.ts.map +1 -0
  120. package/dist/__tests__/worker-execution-stop.test.d.ts +2 -0
  121. package/dist/__tests__/worker-execution-stop.test.d.ts.map +1 -0
  122. package/dist/animation-asset-lookup.d.ts +7 -0
  123. package/dist/animation-asset-lookup.d.ts.map +1 -0
  124. package/dist/browser-frame-signal.d.ts +17 -0
  125. package/dist/browser-frame-signal.d.ts.map +1 -0
  126. package/dist/canvas-policy.d.ts +4 -0
  127. package/dist/canvas-policy.d.ts.map +1 -0
  128. package/dist/create-app.d.ts +78 -0
  129. package/dist/create-app.d.ts.map +1 -0
  130. package/dist/engine-worker-runtime.mjs +827 -0
  131. package/dist/engine-worker-runtime.mjs.map +1 -0
  132. package/dist/errors.d.ts +254 -0
  133. package/dist/errors.d.ts.map +1 -0
  134. package/dist/execution/attached-world-swap.d.ts +10 -0
  135. package/dist/execution/attached-world-swap.d.ts.map +1 -0
  136. package/dist/execution/bootstrap-entry.d.ts +30 -0
  137. package/dist/execution/bootstrap-entry.d.ts.map +1 -0
  138. package/dist/execution/bootstrap-url.d.ts +5 -0
  139. package/dist/execution/bootstrap-url.d.ts.map +1 -0
  140. package/dist/execution/capabilities.d.ts +5 -0
  141. package/dist/execution/capabilities.d.ts.map +1 -0
  142. package/dist/execution/control.d.ts +12 -0
  143. package/dist/execution/control.d.ts.map +1 -0
  144. package/dist/execution/engine-worker-runtime.d.ts +2 -0
  145. package/dist/execution/engine-worker-runtime.d.ts.map +1 -0
  146. package/dist/execution/engine-worker.d.ts +24 -0
  147. package/dist/execution/engine-worker.d.ts.map +1 -0
  148. package/dist/execution/host-controller.d.ts +16 -0
  149. package/dist/execution/host-controller.d.ts.map +1 -0
  150. package/dist/execution/index.d.ts +10 -0
  151. package/dist/execution/index.d.ts.map +1 -0
  152. package/dist/execution/kernel-pool.d.ts +14 -0
  153. package/dist/execution/kernel-pool.d.ts.map +1 -0
  154. package/dist/execution/kernel-worker-runtime.d.ts +2 -0
  155. package/dist/execution/kernel-worker-runtime.d.ts.map +1 -0
  156. package/dist/execution/measurement.d.ts +7 -0
  157. package/dist/execution/measurement.d.ts.map +1 -0
  158. package/dist/execution/protocol.d.ts +87 -0
  159. package/dist/execution/protocol.d.ts.map +1 -0
  160. package/dist/execution/report.d.ts +5 -0
  161. package/dist/execution/report.d.ts.map +1 -0
  162. package/dist/execution/schema.d.ts +4 -0
  163. package/dist/execution/schema.d.ts.map +1 -0
  164. package/dist/execution/selector.d.ts +10 -0
  165. package/dist/execution/selector.d.ts.map +1 -0
  166. package/dist/execution/types.d.ts +123 -0
  167. package/dist/execution/types.d.ts.map +1 -0
  168. package/dist/fallback-camera.d.ts +8 -0
  169. package/dist/fallback-camera.d.ts.map +1 -0
  170. package/dist/game-context.d.ts +98 -0
  171. package/dist/game-context.d.ts.map +1 -0
  172. package/dist/index.d.ts +27 -0
  173. package/dist/index.d.ts.map +1 -0
  174. package/dist/index.mjs +3980 -0
  175. package/dist/index.mjs.map +1 -0
  176. package/dist/input-map-plugin.d.ts +5 -0
  177. package/dist/input-map-plugin.d.ts.map +1 -0
  178. package/dist/input-plugin.d.ts +4 -0
  179. package/dist/input-plugin.d.ts.map +1 -0
  180. package/dist/internal/assembled-engine-profile.d.ts +5 -0
  181. package/dist/internal/assembled-engine-profile.d.ts.map +1 -0
  182. package/dist/internal/assets-world-plugin.d.ts +7 -0
  183. package/dist/internal/assets-world-plugin.d.ts.map +1 -0
  184. package/dist/internal/browser-remote-bridge.d.ts +24 -0
  185. package/dist/internal/browser-remote-bridge.d.ts.map +1 -0
  186. package/dist/internal/browser-rhi-debug-runtime.d.ts +18 -0
  187. package/dist/internal/browser-rhi-debug-runtime.d.ts.map +1 -0
  188. package/dist/internal/component-introspection.d.ts +24 -0
  189. package/dist/internal/component-introspection.d.ts.map +1 -0
  190. package/dist/internal/debug-draw.d.ts +9 -0
  191. package/dist/internal/debug-draw.d.ts.map +1 -0
  192. package/dist/internal/ecs-import.d.ts +6 -0
  193. package/dist/internal/ecs-import.d.ts.map +1 -0
  194. package/dist/internal/engine-profile-common.d.ts +9 -0
  195. package/dist/internal/engine-profile-common.d.ts.map +1 -0
  196. package/dist/internal/error-fanout.d.ts +66 -0
  197. package/dist/internal/error-fanout.d.ts.map +1 -0
  198. package/dist/internal/frame-loop.d.ts +38 -0
  199. package/dist/internal/frame-loop.d.ts.map +1 -0
  200. package/dist/internal/input-attach.d.ts +44 -0
  201. package/dist/internal/input-attach.d.ts.map +1 -0
  202. package/dist/internal/main-engine-profile.d.ts +19 -0
  203. package/dist/internal/main-engine-profile.d.ts.map +1 -0
  204. package/dist/internal/remote-serve-flag.d.ts +19 -0
  205. package/dist/internal/remote-serve-flag.d.ts.map +1 -0
  206. package/dist/internal/remote-server-plugin.d.ts +8 -0
  207. package/dist/internal/remote-server-plugin.d.ts.map +1 -0
  208. package/dist/internal/rhi-capture.d.ts +19 -0
  209. package/dist/internal/rhi-capture.d.ts.map +1 -0
  210. package/dist/internal/rhi-debug-flag.d.ts +18 -0
  211. package/dist/internal/rhi-debug-flag.d.ts.map +1 -0
  212. package/dist/internal/worker-engine-profile.d.ts +15 -0
  213. package/dist/internal/worker-engine-profile.d.ts.map +1 -0
  214. package/dist/kernel-worker-runtime.mjs +69 -0
  215. package/dist/kernel-worker-runtime.mjs.map +1 -0
  216. package/dist/load-game-errors.d.ts +131 -0
  217. package/dist/load-game-errors.d.ts.map +1 -0
  218. package/dist/load-game.d.ts +34 -0
  219. package/dist/load-game.d.ts.map +1 -0
  220. package/dist/plugin-factories.d.ts +3 -0
  221. package/dist/plugin-factories.d.ts.map +1 -0
  222. package/dist/renderer-plugin.d.ts +25 -0
  223. package/dist/renderer-plugin.d.ts.map +1 -0
  224. package/dist/tool-preview/bootstrap.d.ts +113 -0
  225. package/dist/tool-preview/bootstrap.d.ts.map +1 -0
  226. package/dist/tool-preview/evidence.d.ts +20 -0
  227. package/dist/tool-preview/evidence.d.ts.map +1 -0
  228. package/dist/tool-preview/framing.d.ts +13 -0
  229. package/dist/tool-preview/framing.d.ts.map +1 -0
  230. package/dist/tool-preview/recipe.d.ts +54 -0
  231. package/dist/tool-preview/recipe.d.ts.map +1 -0
  232. package/dist/types.d.ts +404 -0
  233. package/dist/types.d.ts.map +1 -0
  234. package/package.json +85 -0
  235. package/schema/execution-report.schema.json +168 -0
  236. package/src/__tests__/animation-asset-lookup.unit.test.ts +20 -0
  237. package/src/__tests__/callback-deletion.unit.test.ts +14 -0
  238. package/src/__tests__/callback-inference.test-d.ts +36 -0
  239. package/src/__tests__/component-introspection.unit.test.ts +56 -0
  240. package/src/__tests__/consumer-migration.unit.test.ts +34 -0
  241. package/src/__tests__/create-app-error-retention.integration.test.ts +125 -0
  242. package/src/__tests__/create-app-features.test.ts +51 -0
  243. package/src/__tests__/create-app-listener-sync.test.ts +381 -0
  244. package/src/__tests__/create-app-lock-provider.test.ts +212 -0
  245. package/src/__tests__/create-app-physics-plugin-failure.integration.test.ts +142 -0
  246. package/src/__tests__/create-app-plugin.test.ts +217 -0
  247. package/src/__tests__/create-app-stop.test.ts +609 -0
  248. package/src/__tests__/create-app-time.unit.test.ts +26 -0
  249. package/src/__tests__/create-app.test-d.ts +83 -0
  250. package/src/__tests__/create-app.test.ts +287 -0
  251. package/src/__tests__/create-renderer-error.integration.test.ts +25 -0
  252. package/src/__tests__/create-renderer-lifecycle.integration.test.ts +24 -0
  253. package/src/__tests__/docs-gate-green.unit.test.ts +61 -0
  254. package/src/__tests__/draw-source-injected-world-update-visibility.test.ts +176 -0
  255. package/src/__tests__/draw-source-single-world-regression.test.ts +184 -0
  256. package/src/__tests__/engine-worker-bootstrap-channel.unit.test.ts +82 -0
  257. package/src/__tests__/engine-worker-world-swap.unit.test.ts +69 -0
  258. package/src/__tests__/errors-pointer-lock-failed.test.ts +265 -0
  259. package/src/__tests__/execution-bootstrap-isolation.unit.test.ts +79 -0
  260. package/src/__tests__/execution-error-detail.test-d.ts +13 -0
  261. package/src/__tests__/execution-error-detail.unit.test.ts +35 -0
  262. package/src/__tests__/execution-frame-order.unit.test.ts +38 -0
  263. package/src/__tests__/execution-frame-protocol.unit.test.ts +60 -0
  264. package/src/__tests__/execution-input-sample.contract.test.ts +23 -0
  265. package/src/__tests__/execution-measurement.unit.test.ts +20 -0
  266. package/src/__tests__/execution-public-api.test-d.ts +14 -0
  267. package/src/__tests__/execution-public-api.unit.test.ts +9 -0
  268. package/src/__tests__/execution-rebuild.contract.test.ts +36 -0
  269. package/src/__tests__/execution-report.contract.test.ts +50 -0
  270. package/src/__tests__/execution-report.types.test-d.ts +16 -0
  271. package/src/__tests__/execution-schema-freshness.unit.test.ts +20 -0
  272. package/src/__tests__/execution-selector.unit.test.ts +72 -0
  273. package/src/__tests__/execution-shared-kernel.unit.test.ts +180 -0
  274. package/src/__tests__/fallback-camera.unit.test.ts +32 -0
  275. package/src/__tests__/frame-loop-time-elapsed.test.ts +115 -0
  276. package/src/__tests__/frame-loop-time.unit.test.ts +61 -0
  277. package/src/__tests__/frame-loop-world-array.test.ts +450 -0
  278. package/src/__tests__/kernel-worker-runtime.unit.test.ts +50 -0
  279. package/src/__tests__/migration-surface-inventory.unit.test.ts +13 -0
  280. package/src/__tests__/plugin-policy.integration.test.ts +15 -0
  281. package/src/__tests__/plugin-profiles.unit.test.ts +190 -0
  282. package/src/__tests__/profiler-default-off.test.ts +53 -0
  283. package/src/__tests__/profiler-frame-token.test.ts +235 -0
  284. package/src/__tests__/profiler-phase-catalog.test.ts +30 -0
  285. package/src/__tests__/profiler-phase-integration.test.ts +117 -0
  286. package/src/__tests__/profiler-remote-bridge.test.ts +31 -0
  287. package/src/__tests__/remote-integration.test.ts +224 -0
  288. package/src/__tests__/rhi-capture-capability.test-d.ts +22 -0
  289. package/src/__tests__/rhi-capture-host.integration.test.ts +157 -0
  290. package/src/__tests__/rhi-capture.browser.test.ts +51 -0
  291. package/src/__tests__/surface-handoff.test.ts +89 -0
  292. package/src/__tests__/types-m2.test-d.ts +81 -0
  293. package/src/__tests__/visibility-remote.integration.test.ts +116 -0
  294. package/src/__tests__/worker-execution-stop.test.ts +173 -0
  295. package/src/animation-asset-lookup.ts +13 -0
  296. package/src/browser-frame-signal.ts +44 -0
  297. package/src/canvas-policy.ts +16 -0
  298. package/src/create-app.ts +1264 -0
  299. package/src/errors.ts +442 -0
  300. package/src/execution/attached-world-swap.ts +33 -0
  301. package/src/execution/bootstrap-entry.ts +122 -0
  302. package/src/execution/bootstrap-url.ts +25 -0
  303. package/src/execution/capabilities.ts +102 -0
  304. package/src/execution/control.ts +48 -0
  305. package/src/execution/engine-worker-runtime.ts +383 -0
  306. package/src/execution/engine-worker.ts +163 -0
  307. package/src/execution/host-controller.ts +460 -0
  308. package/src/execution/index.ts +44 -0
  309. package/src/execution/kernel-pool.ts +236 -0
  310. package/src/execution/kernel-worker-runtime.ts +103 -0
  311. package/src/execution/measurement.ts +39 -0
  312. package/src/execution/protocol.ts +134 -0
  313. package/src/execution/report.ts +66 -0
  314. package/src/execution/schema.ts +168 -0
  315. package/src/execution/selector.ts +98 -0
  316. package/src/execution/types.ts +163 -0
  317. package/src/fallback-camera.ts +20 -0
  318. package/src/game-context.ts +126 -0
  319. package/src/index.ts +190 -0
  320. package/src/input-map-plugin.ts +21 -0
  321. package/src/input-plugin.ts +26 -0
  322. package/src/internal/assembled-engine-profile.ts +14 -0
  323. package/src/internal/assets-world-plugin.ts +33 -0
  324. package/src/internal/browser-remote-bridge.ts +269 -0
  325. package/src/internal/browser-rhi-debug-runtime.ts +67 -0
  326. package/src/internal/component-introspection.ts +116 -0
  327. package/src/internal/debug-draw.ts +38 -0
  328. package/src/internal/ecs-import.ts +41 -0
  329. package/src/internal/engine-profile-common.ts +9 -0
  330. package/src/internal/error-fanout.ts +129 -0
  331. package/src/internal/frame-loop.ts +539 -0
  332. package/src/internal/input-attach.ts +104 -0
  333. package/src/internal/main-engine-profile.ts +84 -0
  334. package/src/internal/remote-serve-flag.ts +41 -0
  335. package/src/internal/remote-server-plugin.ts +16 -0
  336. package/src/internal/rhi-capture.ts +211 -0
  337. package/src/internal/rhi-debug-flag.ts +40 -0
  338. package/src/internal/worker-engine-profile.ts +47 -0
  339. package/src/load-game-errors.ts +211 -0
  340. package/src/load-game.ts +115 -0
  341. package/src/plugin-factories.ts +2 -0
  342. package/src/renderer-plugin.ts +96 -0
  343. package/src/tool-preview/bootstrap.ts +835 -0
  344. package/src/tool-preview/evidence.ts +59 -0
  345. package/src/tool-preview/framing.ts +52 -0
  346. package/src/tool-preview/recipe.ts +137 -0
  347. package/src/types.ts +450 -0
  348. package/src/vite-env.d.ts +10 -0
package/src/index.ts ADDED
@@ -0,0 +1,190 @@
1
+ // @forgeax/engine-app -- public surface (M5: errors.ts complete).
2
+ //
3
+ // AI users:
4
+ // - One-screen takeoff: `await createApp(canvas)`. Lands in M4 (M1 stub
5
+ // returns a structured error pointing at the assemble entry).
6
+ // - Assemble form: `await createApp({ renderer, world, input?, schedule? })`.
7
+ // M3 ships rAF + frame-loop wired; M4 wires error fan-out + console.error
8
+ // fallback + canvas-detach guard; M5 ships the AppError class + 5-member
9
+ // closed AppErrorCode union + APP_ERROR_HINTS / APP_EXPECTED tables.
10
+ // - loadGame validates the native default Cordis plugin exported by a project.
11
+ //
12
+ // Single import path:
13
+ // import {
14
+ // createApp,
15
+ // loadGame,
16
+ // AppError, LoadGameError,
17
+ // APP_ERROR_HINTS, LOAD_GAME_ERROR_HINTS,
18
+ // APP_EXPECTED, LOAD_GAME_EXPECTED,
19
+ // isAppError, isLoadGameError,
20
+ // type App, type GameHost, type Plugin,
21
+ // type AppAssembleArgs, type CreateAppOptions,
22
+ // type AppErrorCode, type LoadGameErrorCode,
23
+ // type AppErrorDetail, type LoadGameErrorDetail,
24
+ // type AppErrorDetailFor, type LoadGameErrorDetailFor,
25
+ // type AppDetailCanvasDetached, type AppDetailSystemUpdateFailed,
26
+ // type LoadGameDetailImportFailed, type LoadGameDetailInvalidFormat,
27
+ // type LoadGameDetailModuleNotFound, type GamePluginResolver,
28
+ // } from '@forgeax/engine-app';
29
+
30
+ export type {
31
+ Effect,
32
+ EffectMeta,
33
+ Fiber,
34
+ Inject,
35
+ Plugin,
36
+ } from '@forgeax/engine-plugin';
37
+ export { Context } from '@forgeax/engine-plugin';
38
+ export {
39
+ createFullscreenRenderFeature,
40
+ type FullscreenRenderFeatureOptions,
41
+ } from '@forgeax/engine-render/authoring';
42
+ export {
43
+ type BrowserFrameSubmitted,
44
+ FORGEAX_FRAME_SUBMITTED_DATASET,
45
+ FORGEAX_FRAME_SUBMITTED_EVENT,
46
+ publishBrowserFrameSubmitted,
47
+ resetBrowserFrameSubmitted,
48
+ } from './browser-frame-signal';
49
+ export { createApp, measureCanvasDrawingBuffer, syncCanvasDrawingBuffer } from './create-app';
50
+ export type {
51
+ AppDetailCanvasDetached,
52
+ AppDetailEmpty,
53
+ AppDetailExecutionBootstrapFailed,
54
+ AppDetailExecutionDeadlineExceeded,
55
+ AppDetailExecutionKernelFailed,
56
+ AppDetailExecutionRebuildFailed,
57
+ AppDetailExecutionStaleWorld,
58
+ AppDetailExecutionTierUnavailable,
59
+ AppDetailPluginActivationFailed,
60
+ AppDetailSystemUpdateFailed,
61
+ AppErrorCode,
62
+ AppErrorDetail,
63
+ AppErrorDetailFor,
64
+ } from './errors';
65
+ export {
66
+ APP_ERROR_HINTS,
67
+ APP_EXPECTED,
68
+ AppError,
69
+ isAppError,
70
+ } from './errors';
71
+ export type {
72
+ ExecutionAssetCatalog,
73
+ ExecutionBootstrapEntry,
74
+ ExecutionBootstrapHost,
75
+ ExecutionBootstrapValue,
76
+ ExecutionCapabilities,
77
+ ExecutionCapabilityFact,
78
+ ExecutionCapabilityName,
79
+ ExecutionControl,
80
+ ExecutionEngineHealth,
81
+ ExecutionFault,
82
+ ExecutionMeasurement,
83
+ ExecutionOptions,
84
+ ExecutionReport,
85
+ ExecutionRequestedTier,
86
+ ExecutionSelection,
87
+ ExecutionSelectionReason,
88
+ ExecutionTier,
89
+ ExecutionWorldHealth,
90
+ KernelDispatchReason,
91
+ PreparedExecutionBootstrap,
92
+ } from './execution';
93
+ export {
94
+ createExecutionReport,
95
+ EXECUTION_CAPABILITY_NAMES,
96
+ EXECUTION_REPORT_SCHEMA_VERSION,
97
+ EXECUTION_REQUESTED_TIERS,
98
+ EXECUTION_TIERS,
99
+ executionBootstrapHostPlugin,
100
+ isExecutionReport,
101
+ loadBootstrapEntry,
102
+ missingExecutionCapabilities,
103
+ prepareBootstrapEntry,
104
+ probeExecutionCapabilities,
105
+ selectExecutionTier,
106
+ unavailableExecutionCapabilities,
107
+ validateExecutionBootstrapData,
108
+ } from './execution';
109
+ export { ensureFallbackCamera } from './fallback-camera';
110
+ export type {
111
+ GameActionArgsSchema,
112
+ GameActionDef,
113
+ GameHost,
114
+ GameProjectionRegistrar,
115
+ GameProjectionValue,
116
+ GameReadDef,
117
+ } from './game-context';
118
+ export { gameHostPlugin } from './game-context';
119
+ export { inputPlugin } from './plugin-factories';
120
+ export type { RenderFeatureHost } from './renderer-plugin';
121
+ export {
122
+ ownedRendererPlugin,
123
+ rendererPlugin,
124
+ renderFeatureHostPlugin,
125
+ renderFeaturePlugin,
126
+ } from './renderer-plugin';
127
+
128
+ import {
129
+ isLoadGameError,
130
+ LOAD_GAME_ERROR_HINTS,
131
+ LOAD_GAME_EXPECTED,
132
+ LoadGameError,
133
+ } from './load-game-errors';
134
+
135
+ export type { GamePluginResolver } from './load-game';
136
+ export { loadGame } from './load-game';
137
+ export type {
138
+ LoadGameDetailImportFailed,
139
+ LoadGameDetailInvalidFormat,
140
+ LoadGameDetailModuleNotFound,
141
+ LoadGameErrorCode,
142
+ LoadGameErrorDetail,
143
+ LoadGameErrorDetailFor,
144
+ } from './load-game-errors';
145
+ export {
146
+ createToolPreviewHost,
147
+ replayToolPreviewCapture,
148
+ type ToolPreviewCaptureResult,
149
+ type ToolPreviewHost,
150
+ type ToolPreviewHostError,
151
+ type ToolPreviewHostOptions,
152
+ type ToolPreviewResourceFacts,
153
+ type ToolPreviewResourceKind,
154
+ type ToolPreviewResourceRequest,
155
+ type ToolPreviewRunResult,
156
+ } from './tool-preview/bootstrap';
157
+ export {
158
+ createToolPreviewEvidence,
159
+ joinResourcePreviewEvidence,
160
+ joinToolPreviewEvidence,
161
+ type ToolPreviewEvidence,
162
+ } from './tool-preview/evidence';
163
+ export {
164
+ fitToolPreviewCameraToAabb,
165
+ type ToolPreviewCameraFrame,
166
+ } from './tool-preview/framing';
167
+ export {
168
+ createToolPreviewRecipe,
169
+ type ToolPreviewAction,
170
+ type ToolPreviewPresentation,
171
+ type ToolPreviewRecipe,
172
+ type ToolPreviewRecipeOptions,
173
+ type ToolPreviewTrace,
174
+ type ToolPreviewTraceEvent,
175
+ validateToolPreviewTrace,
176
+ } from './tool-preview/recipe';
177
+ export type {
178
+ App,
179
+ AppAssembleArgs,
180
+ AssembleAppError,
181
+ BundlerOptions,
182
+ CanvasAppError,
183
+ CanvasDrawingBufferSize,
184
+ CreateAppOptions,
185
+ DrawSource,
186
+ DrawSourceResult,
187
+ ExecutionApp,
188
+ } from './types';
189
+ export { APP_PHASE_CATALOG } from './types';
190
+ export { isLoadGameError, LOAD_GAME_ERROR_HINTS, LOAD_GAME_EXPECTED, LoadGameError };
@@ -0,0 +1,21 @@
1
+ import { type ActionConfig, INPUT_MAP_KEY } from '@forgeax/engine-input';
2
+ import type { Plugin } from '@forgeax/engine-plugin';
3
+
4
+ /** Install the canvas-form action map as an App-owned World contribution. */
5
+ export function inputMapPlugin(configs: readonly ActionConfig[]): Plugin {
6
+ const deduped = new Map<string, ActionConfig>();
7
+ for (const config of configs) {
8
+ if (config.action.length > 0) deduped.set(config.action, config);
9
+ }
10
+ const inputMap = Object.freeze([...deduped.values()]);
11
+ return {
12
+ name: 'input-map',
13
+ inject: ['world'],
14
+ apply(ctx) {
15
+ ctx.effect(() => {
16
+ ctx.world.insertResource(INPUT_MAP_KEY, inputMap);
17
+ return () => ctx.world.removeResource(INPUT_MAP_KEY);
18
+ }, 'input/action-map');
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,26 @@
1
+ import { Update } from '@forgeax/engine-ecs';
2
+ import { INPUT_BACKEND_KEY, InputFrameStartScan, InputSet } from '@forgeax/engine-input';
3
+ import type { Plugin } from '@forgeax/engine-plugin';
4
+
5
+ /** Project one Host-owned input provider into reversible World contributions. */
6
+ export function inputPlugin(): Plugin {
7
+ return {
8
+ name: 'input',
9
+ inject: ['world', 'input'],
10
+ apply(ctx) {
11
+ const world = ctx.world;
12
+ const input = ctx.input;
13
+ if (input === undefined) throw new Error('Cordis activated input without its provider');
14
+ ctx.effect(() => {
15
+ world.insertResource(INPUT_BACKEND_KEY, input);
16
+ return () => {
17
+ world.removeResource(INPUT_BACKEND_KEY);
18
+ };
19
+ }, 'input/resource');
20
+ ctx.effect(() => {
21
+ world.addSystems(Update, InputSet, [InputFrameStartScan]).unwrap();
22
+ return () => world.removeSystem(Update, InputFrameStartScan.name);
23
+ }, 'input/frame-start-scan');
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,14 @@
1
+ import type { Plugin } from '@forgeax/engine-plugin';
2
+ import { rendererPlugin } from '../renderer-plugin';
3
+ import { assetsWorldPlugin, rendererAssetsPlugin } from './assets-world-plugin';
4
+ import type { EngineProfileBase } from './engine-profile-common';
5
+
6
+ /** Assemble-form roots plus explicitly selected host capabilities. */
7
+ export function assembledEngineProfile(options: EngineProfileBase): Plugin[] {
8
+ return [
9
+ rendererPlugin(options.renderer),
10
+ rendererAssetsPlugin(options.assets),
11
+ assetsWorldPlugin(),
12
+ ...(options.extensions ?? []),
13
+ ];
14
+ }
@@ -0,0 +1,33 @@
1
+ import type { AssetRegistry } from '@forgeax/engine-assets-runtime';
2
+ import type { Plugin } from '@forgeax/engine-plugin';
3
+
4
+ const ASSET_REGISTRY_RESOURCE_KEY = 'AssetRegistry' as const;
5
+
6
+ /** Project the Renderer-owned AssetRegistry as a declared dependent service. */
7
+ export function rendererAssetsPlugin(assets: AssetRegistry | undefined): Plugin {
8
+ return {
9
+ name: 'renderer-assets',
10
+ inject: ['renderer'],
11
+ provide: 'assets',
12
+ apply(ctx) {
13
+ if (assets !== undefined) ctx.provide('assets', assets);
14
+ },
15
+ };
16
+ }
17
+
18
+ /** Project the realm AssetRegistry into legacy World resource lookup reversibly. */
19
+ export function assetsWorldPlugin(): Plugin {
20
+ return {
21
+ name: 'assets-world',
22
+ inject: ['world', 'assets'],
23
+ apply(ctx) {
24
+ if (ctx.assets === undefined) return;
25
+ ctx.effect(() => {
26
+ ctx.world.insertResource(ASSET_REGISTRY_RESOURCE_KEY, ctx.assets);
27
+ return () => {
28
+ ctx.world.removeResource(ASSET_REGISTRY_RESOURCE_KEY);
29
+ };
30
+ }, 'assets/world-resource');
31
+ },
32
+ };
33
+ }
@@ -0,0 +1,269 @@
1
+ // @forgeax/engine-app/internal/browser-remote-bridge — DEV-only page-side bridge
2
+ // that makes a live BROWSER engine drivable over a loopback relay.
3
+ //
4
+ // WHY: @forgeax/engine-remote's only external transport is a Node WebSocket
5
+ // server (packages/remote/src/server.ts). A browser page cannot bind a listening
6
+ // socket, so createApp's startServer attempt throws on ws's browser shim and
7
+ // app.remote stays undefined — the running engine is unreachable in a real
8
+ // `pnpm --filter <app> dev` browser. But a page CAN dial OUT. So we open a
9
+ // WebSocket CLIENT to the loopback relay
10
+ // (skills/forgeax-engine-cli/scripts/remote-bridge-server.mjs) and run
11
+ // @forgeax/engine-remote/execute (the ws-free eval core) in the page realm
12
+ // against the live world/renderer/assets/rhiCapture. A CLI POSTs to the relay;
13
+ // the relay forwards to us; we eval and reply. This is the engine-side mirror of
14
+ // the editor's ViewportComponent DEV bridge.
15
+ //
16
+ // This module is reached only via a DEV-gated dynamic import from create-app.ts,
17
+ // so production (import.meta.env.DEV === false) never bundles it (tree-shake /
18
+ // zero-injection). It carries NO static top-level @forgeax/engine-remote
19
+ // dependency — the eval core is pulled by a further dynamic import, keeping
20
+ // @forgeax/engine-app free of a runtime dep on @forgeax/engine-remote (same
21
+ // discipline as the createApp startServer path). The import is deliberately
22
+ // left visible to Vite: the SDK host aliases the focused package to its exact
23
+ // installed path, while @vite-ignore would make a browser resolve the bare
24
+ // specifier from the consumer document root and yield a 500.
25
+
26
+ import { Update, type World } from '@forgeax/engine-ecs';
27
+ import { createEcsImportModule } from './ecs-import';
28
+
29
+ type ExecuteResult = { ok: true; value: unknown } | { ok: false; error: unknown };
30
+
31
+ type ExecuteModule = {
32
+ executeScript: (
33
+ script: string,
34
+ ctx: {
35
+ world: unknown;
36
+ renderer: unknown;
37
+ assets: unknown;
38
+ rhiCapture?: unknown;
39
+ profiler?: unknown;
40
+ execution?: unknown;
41
+ importModule?: (specifier: string) => Promise<unknown>;
42
+ },
43
+ ) => Promise<ExecuteResult>;
44
+ };
45
+
46
+ type ComponentLike = { readonly name: string };
47
+
48
+ /**
49
+ * Project component exports onto the tokens already stored by this World.
50
+ *
51
+ * WHY: Vite may evaluate a workspace package once through the host package's
52
+ * dist graph and once through a source graph. Component ids are process-local,
53
+ * so equal `{ name, schema }` objects are not interchangeable with World
54
+ * access. The bridge is the one place that can reconcile the public module
55
+ * recipe with the live World's archetype SSOT.
56
+ */
57
+ function canonicalRuntimeModule(moduleValue: unknown, world: World): unknown {
58
+ if (moduleValue === null || typeof moduleValue !== 'object') return moduleValue;
59
+ const componentsByName = new Map<string, ComponentLike>();
60
+ for (const [name, component] of world.components.entries()) {
61
+ if (!componentsByName.has(name)) componentsByName.set(name, component);
62
+ }
63
+ const projected: Record<string, unknown> = { ...(moduleValue as Record<string, unknown>) };
64
+ for (const [key, value] of Object.entries(projected)) {
65
+ if (value === null || typeof value !== 'object' || !('name' in value)) continue;
66
+ const canonical = componentsByName.get((value as ComponentLike).name);
67
+ if (canonical !== undefined) projected[key] = canonical;
68
+ }
69
+ return projected;
70
+ }
71
+
72
+ export interface BrowserRemoteBridgeDeps {
73
+ readonly world: World;
74
+ readonly renderer: unknown;
75
+ readonly assets: unknown;
76
+ /** The host's already-loaded runtime namespace; preserves component-token identity. */
77
+ readonly runtimeModule: unknown;
78
+ readonly rhiCapture?: unknown;
79
+ /** The host's explicit CPU profiler capability, when opted in. */
80
+ readonly profiler?: unknown;
81
+ readonly execution?: unknown;
82
+ /** Relay port. */
83
+ readonly port: string;
84
+ }
85
+
86
+ /** Serialize a RemoteError-shaped object (or any thrown value) into a JSON-safe
87
+ * {code, expected, hint, detail?} envelope. AI users branch on error.code by
88
+ * property access, so the four structured fields must survive the wire. */
89
+ function serializeError(error: unknown): Record<string, unknown> {
90
+ if (error !== null && typeof error === 'object') {
91
+ const e = error as {
92
+ code?: unknown;
93
+ expected?: unknown;
94
+ hint?: unknown;
95
+ detail?: unknown;
96
+ message?: unknown;
97
+ };
98
+ const out: Record<string, unknown> = {
99
+ code: typeof e.code === 'string' ? e.code : 'script-runtime-error',
100
+ };
101
+ if (typeof e.expected === 'string') out.expected = e.expected;
102
+ if (typeof e.hint === 'string') out.hint = e.hint;
103
+ if (e.detail !== undefined) out.detail = e.detail;
104
+ if (out.hint === undefined && typeof e.message === 'string') out.hint = e.message;
105
+ return out;
106
+ }
107
+ return { code: 'script-runtime-error', hint: String(error) };
108
+ }
109
+
110
+ /**
111
+ * Install the DEV-only browser remote bridge. Idempotent per call site; the
112
+ * caller gates on import.meta.env.DEV so this never runs in production.
113
+ *
114
+ * Returns a teardown function that stops reconnection and closes the socket —
115
+ * the caller wires it to import.meta.hot.dispose so a vite HMR of the host app
116
+ * does not stack duplicate bridges.
117
+ */
118
+ export async function installBrowserRemoteBridge(
119
+ deps: BrowserRemoteBridgeDeps,
120
+ ): Promise<() => void> {
121
+ const { world, renderer, assets, rhiCapture, profiler, execution, port } = deps;
122
+
123
+ // The ws-free eval core. Dynamic import keeps @forgeax/engine-app free of a
124
+ // static @forgeax/engine-remote dependency while allowing the consumer's
125
+ // Vite config to resolve the focused package through its SDK alias.
126
+ const mod = (await import('@forgeax/engine-remote/execute')) as ExecuteModule;
127
+ const executeScript = mod.executeScript;
128
+ const importModule = createEcsImportModule((specifier: string): Promise<unknown> => {
129
+ // The host app and the bridge must share the same component-token objects.
130
+ // Vite can otherwise serve `/@id/@forgeax/engine-runtime` as a second
131
+ // module graph entry, so `world.get(entity, Transform)` sees a different
132
+ // Component id even though the token has the same name and schema.
133
+ if (specifier === '@forgeax/engine-runtime')
134
+ return Promise.resolve(canonicalRuntimeModule(deps.runtimeModule, world));
135
+ const browserSpecifier = specifier.startsWith('@') ? `/@id/${specifier}` : specifier;
136
+ return import(/* @vite-ignore */ browserSpecifier).then((moduleValue) =>
137
+ canonicalRuntimeModule(moduleValue, world),
138
+ );
139
+ });
140
+
141
+ let ws: WebSocket | null = null;
142
+ let backoff = 1000;
143
+ let stopped = false;
144
+
145
+ // Frame-start eval queue: a WebSocket `message` fires at an arbitrary phase of
146
+ // the rAF tick, so running eval inline would land world writes at an
147
+ // unpredictable phase. Enqueue instead and drain from Update system (frame
148
+ // start) so every bridge write passes through this frame's systems.
149
+ const evalQueue: Array<{ id: number; code: string }> = [];
150
+
151
+ const drainEvalQueue = (): void => {
152
+ if (evalQueue.length === 0) return;
153
+ // Snapshot + clear so an eval that enqueues runs next frame, not in an
154
+ // unbounded same-frame loop.
155
+ const jobs = evalQueue.splice(0, evalQueue.length);
156
+ for (const job of jobs) {
157
+ const reply = (payload: unknown): void => {
158
+ // Reply on the CURRENT socket (it may have reconnected since enqueue).
159
+ // The relay keys replies by request id, so the live socket resolves it.
160
+ try {
161
+ ws?.send(JSON.stringify({ type: 'result', id: job.id, payload }));
162
+ } catch {
163
+ /* socket gone; relay times the request out */
164
+ }
165
+ };
166
+ void (async () => {
167
+ let res: ExecuteResult;
168
+ try {
169
+ res = await executeScript(job.code, {
170
+ world,
171
+ renderer,
172
+ assets,
173
+ rhiCapture,
174
+ profiler: profiler,
175
+ execution,
176
+ importModule,
177
+ });
178
+ } catch (e) {
179
+ reply({
180
+ ok: false,
181
+ error: { code: 'BRIDGE_EVAL_THREW', hint: String((e as Error)?.message ?? e) },
182
+ });
183
+ return;
184
+ }
185
+ const envelope = res.ok
186
+ ? { ok: true as const, value: res.value }
187
+ : { ok: false as const, error: serializeError(res.error) };
188
+ // JSON-guard: non-serializable values (opaque engine handles, cycles)
189
+ // degrade to a marker so one bad field never wedges the channel.
190
+ try {
191
+ JSON.stringify(envelope);
192
+ reply(envelope);
193
+ } catch {
194
+ reply({ ok: true, value: '[unserializable value — inspect in the live window]' });
195
+ }
196
+ })();
197
+ }
198
+ };
199
+ world
200
+ .addSystem(Update, {
201
+ name: 'browser-remote-bridge-drain-eval-queue',
202
+ queries: [],
203
+ fn: drainEvalQueue,
204
+ })
205
+ .unwrap();
206
+
207
+ const connect = (): void => {
208
+ if (stopped) return;
209
+ try {
210
+ ws = new WebSocket(`ws://127.0.0.1:${port}/bridge`);
211
+ } catch {
212
+ return;
213
+ }
214
+ ws.addEventListener('open', () => {
215
+ backoff = 1000;
216
+ });
217
+ ws.addEventListener('message', (ev: MessageEvent) => {
218
+ let msg: { type?: string; id?: number; code?: string };
219
+ try {
220
+ msg = JSON.parse(typeof ev.data === 'string' ? ev.data : '');
221
+ } catch {
222
+ return;
223
+ }
224
+ if (msg?.type !== 'eval' || typeof msg.id !== 'number' || typeof msg.code !== 'string')
225
+ return;
226
+ evalQueue.push({ id: msg.id, code: msg.code });
227
+ });
228
+ const retry = (): void => {
229
+ ws = null;
230
+ if (stopped) return;
231
+ setTimeout(connect, backoff);
232
+ backoff = Math.min(backoff * 2, 15000);
233
+ };
234
+ ws.addEventListener('close', retry);
235
+ ws.addEventListener('error', () => {
236
+ try {
237
+ ws?.close();
238
+ } catch {
239
+ /* */
240
+ }
241
+ });
242
+ };
243
+ connect();
244
+
245
+ const teardown = (): void => {
246
+ if (stopped) return;
247
+ stopped = true;
248
+ world.removeSystem(Update, 'browser-remote-bridge-drain-eval-queue');
249
+ const s = ws;
250
+ ws = null;
251
+ if (s) {
252
+ try {
253
+ s.onclose = null;
254
+ s.close();
255
+ } catch {
256
+ /* */
257
+ }
258
+ }
259
+ };
260
+
261
+ // Self-register HMR teardown so a vite HMR of the host app does not stack
262
+ // duplicate bridges. Kept here (not in create-app.ts) so create-app.ts carries
263
+ // no import.meta.hot reference — the rhi-debug guard gate requires every
264
+ // import.meta.hot there to sit inside the FORGEAX_ENGINE_RHI_DEBUG block.
265
+ const hot = (import.meta as { hot?: { dispose(cb: () => void): void } }).hot;
266
+ if (hot) hot.dispose(teardown);
267
+
268
+ return teardown;
269
+ }
@@ -0,0 +1,67 @@
1
+ import type { Renderer } from '@forgeax/engine-render';
2
+ import type { RhiDevice, RhiInstance } from '@forgeax/engine-rhi';
3
+ import type {
4
+ CreateShaderModuleFn,
5
+ RecordableBackend,
6
+ RecorderAttachment,
7
+ } from '@forgeax/engine-rhi-debug';
8
+ import { attachRecorder } from '@forgeax/engine-rhi-debug';
9
+ import { createRhiInstrumentation } from './rhi-capture';
10
+
11
+ export interface BrowserRhiDebugRuntime {
12
+ readonly rhi: RhiInstance;
13
+ readonly attachment: RecorderAttachment;
14
+ readonly createReplayDevice: () => Promise<
15
+ import('@forgeax/engine-types').Result<RhiDevice, unknown>
16
+ >;
17
+ readonly createShaderModule: CreateShaderModuleFn;
18
+ readonly rhiInstrumentation: import('@forgeax/engine-render/internal/construct-renderer').RhiBackendInstrumentation;
19
+ attachRenderer(renderer: Renderer): () => void;
20
+ }
21
+
22
+ interface BackendModule extends RecordableBackend {
23
+ readonly ensureReady?: () => Promise<unknown>;
24
+ }
25
+
26
+ function hasWebGpu(): boolean {
27
+ const nav = (globalThis as { navigator?: { gpu?: unknown } }).navigator;
28
+ return nav?.gpu !== undefined;
29
+ }
30
+
31
+ async function loadBackend(): Promise<BackendModule> {
32
+ const backend = (hasWebGpu()
33
+ ? await import('@forgeax/engine-rhi-webgpu')
34
+ : await import('@forgeax/engine-rhi-wgpu')) as unknown as BackendModule;
35
+ if (!hasWebGpu()) await backend.ensureReady?.();
36
+ return backend;
37
+ }
38
+
39
+ /**
40
+ * Assemble the browser preview's recorder at the Runtime seam. The preview
41
+ * host owns the attachment and consumes only the encoded tape; it does not
42
+ * reach into recorder state or reintroduce the removed DebugRhi facade.
43
+ */
44
+ export async function createBrowserRhiDebugRuntime(): Promise<BrowserRhiDebugRuntime> {
45
+ const backend = await loadBackend();
46
+ const attached = attachRecorder(backend);
47
+ if (!attached.ok) throw new Error(attached.error.hint);
48
+ const attachment = attached.value;
49
+ return {
50
+ rhi: attachment.backend.rhi,
51
+ attachment,
52
+ createShaderModule: backend.createShaderModule,
53
+ rhiInstrumentation: createRhiInstrumentation(attachment),
54
+ createReplayDevice: async () => {
55
+ const adapter = await backend.rhi.requestAdapter();
56
+ if (!adapter.ok) return adapter;
57
+ return adapter.value.requestDevice();
58
+ },
59
+ attachRenderer(_renderer: Renderer): () => void {
60
+ // Frame boundaries and device loss are connected by the typed
61
+ // rhiInstrumentation passed to createApp. This hook remains a no-op so
62
+ // the Preview host keeps an explicit lifecycle slot without subscribing
63
+ // a second boundary callback.
64
+ return () => {};
65
+ },
66
+ };
67
+ }