@forgeax/engine-app 0.1.2

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 (354) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +418 -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__/app-rebuild.test.d.ts +2 -0
  7. package/dist/__tests__/app-rebuild.test.d.ts.map +1 -0
  8. package/dist/__tests__/asset-registry-types.test-d.d.ts +2 -0
  9. package/dist/__tests__/asset-registry-types.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/assets-runtime-assembly.integration.test.d.ts +2 -0
  11. package/dist/__tests__/assets-runtime-assembly.integration.test.d.ts.map +1 -0
  12. package/dist/__tests__/callback-deletion.unit.test.d.ts +2 -0
  13. package/dist/__tests__/callback-deletion.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/callback-inference.test-d.d.ts +2 -0
  15. package/dist/__tests__/callback-inference.test-d.d.ts.map +1 -0
  16. package/dist/__tests__/component-introspection.unit.test.d.ts +2 -0
  17. package/dist/__tests__/component-introspection.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/consumer-migration.unit.test.d.ts +2 -0
  19. package/dist/__tests__/consumer-migration.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/create-app-features.test.d.ts +2 -0
  21. package/dist/__tests__/create-app-features.test.d.ts.map +1 -0
  22. package/dist/__tests__/create-app-listener-sync.test.d.ts +2 -0
  23. package/dist/__tests__/create-app-listener-sync.test.d.ts.map +1 -0
  24. package/dist/__tests__/create-app-lock-provider.test.d.ts +2 -0
  25. package/dist/__tests__/create-app-lock-provider.test.d.ts.map +1 -0
  26. package/dist/__tests__/create-app-plugin.test.d.ts +2 -0
  27. package/dist/__tests__/create-app-plugin.test.d.ts.map +1 -0
  28. package/dist/__tests__/create-app-stop.test.d.ts +2 -0
  29. package/dist/__tests__/create-app-stop.test.d.ts.map +1 -0
  30. package/dist/__tests__/create-app-time.unit.test.d.ts +2 -0
  31. package/dist/__tests__/create-app-time.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/create-app.test-d.d.ts +2 -0
  33. package/dist/__tests__/create-app.test-d.d.ts.map +1 -0
  34. package/dist/__tests__/create-app.test.d.ts +2 -0
  35. package/dist/__tests__/create-app.test.d.ts.map +1 -0
  36. package/dist/__tests__/create-renderer-error.integration.test.d.ts +2 -0
  37. package/dist/__tests__/create-renderer-error.integration.test.d.ts.map +1 -0
  38. package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts +2 -0
  39. package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts.map +1 -0
  40. package/dist/__tests__/docs-gate-green.unit.test.d.ts +2 -0
  41. package/dist/__tests__/docs-gate-green.unit.test.d.ts.map +1 -0
  42. package/dist/__tests__/draw-source-headless-full-chain.test.d.ts +2 -0
  43. package/dist/__tests__/draw-source-headless-full-chain.test.d.ts.map +1 -0
  44. package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts +2 -0
  45. package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts.map +1 -0
  46. package/dist/__tests__/draw-source-single-world-regression.test.d.ts +2 -0
  47. package/dist/__tests__/draw-source-single-world-regression.test.d.ts.map +1 -0
  48. package/dist/__tests__/ecs-import.unit.test.d.ts +2 -0
  49. package/dist/__tests__/ecs-import.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts +2 -0
  51. package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts +2 -0
  53. package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/errors-pointer-lock-failed.test.d.ts +2 -0
  55. package/dist/__tests__/errors-pointer-lock-failed.test.d.ts.map +1 -0
  56. package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts +2 -0
  57. package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/execution-error-detail.test-d.d.ts +2 -0
  59. package/dist/__tests__/execution-error-detail.test-d.d.ts.map +1 -0
  60. package/dist/__tests__/execution-error-detail.unit.test.d.ts +2 -0
  61. package/dist/__tests__/execution-error-detail.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/execution-frame-order.unit.test.d.ts +2 -0
  63. package/dist/__tests__/execution-frame-order.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/execution-frame-protocol.unit.test.d.ts +2 -0
  65. package/dist/__tests__/execution-frame-protocol.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/execution-input-sample.contract.test.d.ts +2 -0
  67. package/dist/__tests__/execution-input-sample.contract.test.d.ts.map +1 -0
  68. package/dist/__tests__/execution-measurement.unit.test.d.ts +2 -0
  69. package/dist/__tests__/execution-measurement.unit.test.d.ts.map +1 -0
  70. package/dist/__tests__/execution-public-api.test-d.d.ts +2 -0
  71. package/dist/__tests__/execution-public-api.test-d.d.ts.map +1 -0
  72. package/dist/__tests__/execution-public-api.unit.test.d.ts +2 -0
  73. package/dist/__tests__/execution-public-api.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/execution-rebuild.contract.test.d.ts +2 -0
  75. package/dist/__tests__/execution-rebuild.contract.test.d.ts.map +1 -0
  76. package/dist/__tests__/execution-report.contract.test.d.ts +2 -0
  77. package/dist/__tests__/execution-report.contract.test.d.ts.map +1 -0
  78. package/dist/__tests__/execution-report.types.test-d.d.ts +2 -0
  79. package/dist/__tests__/execution-report.types.test-d.d.ts.map +1 -0
  80. package/dist/__tests__/execution-schema-freshness.unit.test.d.ts +2 -0
  81. package/dist/__tests__/execution-schema-freshness.unit.test.d.ts.map +1 -0
  82. package/dist/__tests__/execution-selector.unit.test.d.ts +2 -0
  83. package/dist/__tests__/execution-selector.unit.test.d.ts.map +1 -0
  84. package/dist/__tests__/execution-shared-kernel.unit.test.d.ts +2 -0
  85. package/dist/__tests__/execution-shared-kernel.unit.test.d.ts.map +1 -0
  86. package/dist/__tests__/execution-world-health-owner.test-d.d.ts +2 -0
  87. package/dist/__tests__/execution-world-health-owner.test-d.d.ts.map +1 -0
  88. package/dist/__tests__/fallback-camera.unit.test.d.ts +2 -0
  89. package/dist/__tests__/fallback-camera.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/frame-loop-time-elapsed.test.d.ts +2 -0
  91. package/dist/__tests__/frame-loop-time-elapsed.test.d.ts.map +1 -0
  92. package/dist/__tests__/frame-loop-time.unit.test.d.ts +2 -0
  93. package/dist/__tests__/frame-loop-time.unit.test.d.ts.map +1 -0
  94. package/dist/__tests__/frame-loop-world-array.test.d.ts +2 -0
  95. package/dist/__tests__/frame-loop-world-array.test.d.ts.map +1 -0
  96. package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts +2 -0
  97. package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/migration-surface-inventory.unit.test.d.ts +2 -0
  99. package/dist/__tests__/migration-surface-inventory.unit.test.d.ts.map +1 -0
  100. package/dist/__tests__/plugin-policy.integration.test.d.ts +2 -0
  101. package/dist/__tests__/plugin-policy.integration.test.d.ts.map +1 -0
  102. package/dist/__tests__/plugin-profiles.unit.test.d.ts +2 -0
  103. package/dist/__tests__/plugin-profiles.unit.test.d.ts.map +1 -0
  104. package/dist/__tests__/profiler-default-off.test.d.ts +2 -0
  105. package/dist/__tests__/profiler-default-off.test.d.ts.map +1 -0
  106. package/dist/__tests__/profiler-frame-token.test.d.ts +2 -0
  107. package/dist/__tests__/profiler-frame-token.test.d.ts.map +1 -0
  108. package/dist/__tests__/profiler-phase-catalog.test.d.ts +2 -0
  109. package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +1 -0
  110. package/dist/__tests__/profiler-phase-integration.test.d.ts +2 -0
  111. package/dist/__tests__/profiler-phase-integration.test.d.ts.map +1 -0
  112. package/dist/__tests__/profiler-remote-bridge.test.d.ts +2 -0
  113. package/dist/__tests__/profiler-remote-bridge.test.d.ts.map +1 -0
  114. package/dist/__tests__/remote-integration.test.d.ts +2 -0
  115. package/dist/__tests__/remote-integration.test.d.ts.map +1 -0
  116. package/dist/__tests__/remote-serve-flag.test.d.ts +2 -0
  117. package/dist/__tests__/remote-serve-flag.test.d.ts.map +1 -0
  118. package/dist/__tests__/rhi-capture-capability.test-d.d.ts +2 -0
  119. package/dist/__tests__/rhi-capture-capability.test-d.d.ts.map +1 -0
  120. package/dist/__tests__/rhi-capture-host.integration.test.d.ts +2 -0
  121. package/dist/__tests__/rhi-capture-host.integration.test.d.ts.map +1 -0
  122. package/dist/__tests__/rhi-capture.browser.test.d.ts +2 -0
  123. package/dist/__tests__/rhi-capture.browser.test.d.ts.map +1 -0
  124. package/dist/__tests__/surface-handoff.test.d.ts +2 -0
  125. package/dist/__tests__/surface-handoff.test.d.ts.map +1 -0
  126. package/dist/__tests__/types-m2.test-d.d.ts +2 -0
  127. package/dist/__tests__/types-m2.test-d.d.ts.map +1 -0
  128. package/dist/__tests__/visibility-remote.integration.test.d.ts +2 -0
  129. package/dist/__tests__/visibility-remote.integration.test.d.ts.map +1 -0
  130. package/dist/__tests__/worker-execution-stop.test.d.ts +2 -0
  131. package/dist/__tests__/worker-execution-stop.test.d.ts.map +1 -0
  132. package/dist/animation-asset-lookup.d.ts +8 -0
  133. package/dist/animation-asset-lookup.d.ts.map +1 -0
  134. package/dist/assets-runtime-assembly.d.ts +19 -0
  135. package/dist/assets-runtime-assembly.d.ts.map +1 -0
  136. package/dist/canvas-policy.d.ts +4 -0
  137. package/dist/canvas-policy.d.ts.map +1 -0
  138. package/dist/create-app.d.ts +57 -0
  139. package/dist/create-app.d.ts.map +1 -0
  140. package/dist/engine-worker-runtime.mjs +884 -0
  141. package/dist/engine-worker-runtime.mjs.map +1 -0
  142. package/dist/errors.d.ts +263 -0
  143. package/dist/errors.d.ts.map +1 -0
  144. package/dist/execution/attached-world-swap.d.ts +21 -0
  145. package/dist/execution/attached-world-swap.d.ts.map +1 -0
  146. package/dist/execution/bootstrap-entry.d.ts +30 -0
  147. package/dist/execution/bootstrap-entry.d.ts.map +1 -0
  148. package/dist/execution/capabilities.d.ts +5 -0
  149. package/dist/execution/capabilities.d.ts.map +1 -0
  150. package/dist/execution/control.d.ts +14 -0
  151. package/dist/execution/control.d.ts.map +1 -0
  152. package/dist/execution/engine-worker-runtime.d.ts +2 -0
  153. package/dist/execution/engine-worker-runtime.d.ts.map +1 -0
  154. package/dist/execution/engine-worker.d.ts +23 -0
  155. package/dist/execution/engine-worker.d.ts.map +1 -0
  156. package/dist/execution/host-controller.d.ts +16 -0
  157. package/dist/execution/host-controller.d.ts.map +1 -0
  158. package/dist/execution/index.d.ts +10 -0
  159. package/dist/execution/index.d.ts.map +1 -0
  160. package/dist/execution/kernel-pool.d.ts +14 -0
  161. package/dist/execution/kernel-pool.d.ts.map +1 -0
  162. package/dist/execution/kernel-worker-runtime.d.ts +2 -0
  163. package/dist/execution/kernel-worker-runtime.d.ts.map +1 -0
  164. package/dist/execution/measurement.d.ts +7 -0
  165. package/dist/execution/measurement.d.ts.map +1 -0
  166. package/dist/execution/protocol.d.ts +84 -0
  167. package/dist/execution/protocol.d.ts.map +1 -0
  168. package/dist/execution/report.d.ts +5 -0
  169. package/dist/execution/report.d.ts.map +1 -0
  170. package/dist/execution/schema.d.ts +4 -0
  171. package/dist/execution/schema.d.ts.map +1 -0
  172. package/dist/execution/selector.d.ts +10 -0
  173. package/dist/execution/selector.d.ts.map +1 -0
  174. package/dist/execution/types.d.ts +107 -0
  175. package/dist/execution/types.d.ts.map +1 -0
  176. package/dist/fallback-camera.d.ts +8 -0
  177. package/dist/fallback-camera.d.ts.map +1 -0
  178. package/dist/game-context.d.ts +98 -0
  179. package/dist/game-context.d.ts.map +1 -0
  180. package/dist/index.d.ts +24 -0
  181. package/dist/index.d.ts.map +1 -0
  182. package/dist/index.mjs +3985 -0
  183. package/dist/index.mjs.map +1 -0
  184. package/dist/input-map-plugin.d.ts +5 -0
  185. package/dist/input-map-plugin.d.ts.map +1 -0
  186. package/dist/input-plugin.d.ts +4 -0
  187. package/dist/input-plugin.d.ts.map +1 -0
  188. package/dist/internal/assembled-engine-profile.d.ts +5 -0
  189. package/dist/internal/assembled-engine-profile.d.ts.map +1 -0
  190. package/dist/internal/assets-world-plugin.d.ts +21 -0
  191. package/dist/internal/assets-world-plugin.d.ts.map +1 -0
  192. package/dist/internal/browser-remote-bridge.d.ts +25 -0
  193. package/dist/internal/browser-remote-bridge.d.ts.map +1 -0
  194. package/dist/internal/browser-rhi-debug-runtime.d.ts +18 -0
  195. package/dist/internal/browser-rhi-debug-runtime.d.ts.map +1 -0
  196. package/dist/internal/component-introspection.d.ts +24 -0
  197. package/dist/internal/component-introspection.d.ts.map +1 -0
  198. package/dist/internal/ecs-import.d.ts +6 -0
  199. package/dist/internal/ecs-import.d.ts.map +1 -0
  200. package/dist/internal/engine-profile-common.d.ts +9 -0
  201. package/dist/internal/engine-profile-common.d.ts.map +1 -0
  202. package/dist/internal/error-fanout.d.ts +66 -0
  203. package/dist/internal/error-fanout.d.ts.map +1 -0
  204. package/dist/internal/frame-loop.d.ts +35 -0
  205. package/dist/internal/frame-loop.d.ts.map +1 -0
  206. package/dist/internal/input-attach.d.ts +44 -0
  207. package/dist/internal/input-attach.d.ts.map +1 -0
  208. package/dist/internal/main-engine-profile.d.ts +15 -0
  209. package/dist/internal/main-engine-profile.d.ts.map +1 -0
  210. package/dist/internal/remote-serve-flag.d.ts +23 -0
  211. package/dist/internal/remote-serve-flag.d.ts.map +1 -0
  212. package/dist/internal/remote-server-plugin.d.ts +8 -0
  213. package/dist/internal/remote-server-plugin.d.ts.map +1 -0
  214. package/dist/internal/rhi-capture.d.ts +19 -0
  215. package/dist/internal/rhi-capture.d.ts.map +1 -0
  216. package/dist/internal/rhi-debug-flag.d.ts +18 -0
  217. package/dist/internal/rhi-debug-flag.d.ts.map +1 -0
  218. package/dist/internal/worker-engine-profile.d.ts +13 -0
  219. package/dist/internal/worker-engine-profile.d.ts.map +1 -0
  220. package/dist/kernel-worker-runtime.mjs +69 -0
  221. package/dist/kernel-worker-runtime.mjs.map +1 -0
  222. package/dist/load-game-errors.d.ts +131 -0
  223. package/dist/load-game-errors.d.ts.map +1 -0
  224. package/dist/load-game.d.ts +34 -0
  225. package/dist/load-game.d.ts.map +1 -0
  226. package/dist/plugin-factories.d.ts +3 -0
  227. package/dist/plugin-factories.d.ts.map +1 -0
  228. package/dist/tool-preview/bootstrap.d.ts +113 -0
  229. package/dist/tool-preview/bootstrap.d.ts.map +1 -0
  230. package/dist/tool-preview/evidence.d.ts +20 -0
  231. package/dist/tool-preview/evidence.d.ts.map +1 -0
  232. package/dist/tool-preview/framing.d.ts +13 -0
  233. package/dist/tool-preview/framing.d.ts.map +1 -0
  234. package/dist/tool-preview/recipe.d.ts +54 -0
  235. package/dist/tool-preview/recipe.d.ts.map +1 -0
  236. package/dist/types.d.ts +444 -0
  237. package/dist/types.d.ts.map +1 -0
  238. package/package.json +86 -0
  239. package/schema/execution-report.schema.json +168 -0
  240. package/src/__tests__/animation-asset-lookup.unit.test.ts +20 -0
  241. package/src/__tests__/app-rebuild.test.ts +31 -0
  242. package/src/__tests__/asset-registry-types.test-d.ts +16 -0
  243. package/src/__tests__/assets-runtime-assembly.integration.test.ts +68 -0
  244. package/src/__tests__/callback-deletion.unit.test.ts +14 -0
  245. package/src/__tests__/callback-inference.test-d.ts +32 -0
  246. package/src/__tests__/component-introspection.unit.test.ts +56 -0
  247. package/src/__tests__/consumer-migration.unit.test.ts +21 -0
  248. package/src/__tests__/create-app-features.test.ts +40 -0
  249. package/src/__tests__/create-app-listener-sync.test.ts +377 -0
  250. package/src/__tests__/create-app-lock-provider.test.ts +212 -0
  251. package/src/__tests__/create-app-plugin.test.ts +217 -0
  252. package/src/__tests__/create-app-stop.test.ts +617 -0
  253. package/src/__tests__/create-app-time.unit.test.ts +26 -0
  254. package/src/__tests__/create-app.test-d.ts +82 -0
  255. package/src/__tests__/create-app.test.ts +346 -0
  256. package/src/__tests__/create-renderer-error.integration.test.ts +24 -0
  257. package/src/__tests__/create-renderer-lifecycle.integration.test.ts +19 -0
  258. package/src/__tests__/docs-gate-green.unit.test.ts +61 -0
  259. package/src/__tests__/draw-source-headless-full-chain.test.ts +226 -0
  260. package/src/__tests__/draw-source-injected-world-update-visibility.test.ts +182 -0
  261. package/src/__tests__/draw-source-single-world-regression.test.ts +185 -0
  262. package/src/__tests__/ecs-import.unit.test.ts +40 -0
  263. package/src/__tests__/engine-worker-bootstrap-channel.unit.test.ts +74 -0
  264. package/src/__tests__/engine-worker-world-swap.unit.test.ts +76 -0
  265. package/src/__tests__/errors-pointer-lock-failed.test.ts +268 -0
  266. package/src/__tests__/execution-bootstrap-isolation.unit.test.ts +80 -0
  267. package/src/__tests__/execution-error-detail.test-d.ts +13 -0
  268. package/src/__tests__/execution-error-detail.unit.test.ts +35 -0
  269. package/src/__tests__/execution-frame-order.unit.test.ts +38 -0
  270. package/src/__tests__/execution-frame-protocol.unit.test.ts +61 -0
  271. package/src/__tests__/execution-input-sample.contract.test.ts +23 -0
  272. package/src/__tests__/execution-measurement.unit.test.ts +20 -0
  273. package/src/__tests__/execution-public-api.test-d.ts +10 -0
  274. package/src/__tests__/execution-public-api.unit.test.ts +9 -0
  275. package/src/__tests__/execution-rebuild.contract.test.ts +36 -0
  276. package/src/__tests__/execution-report.contract.test.ts +50 -0
  277. package/src/__tests__/execution-report.types.test-d.ts +16 -0
  278. package/src/__tests__/execution-schema-freshness.unit.test.ts +20 -0
  279. package/src/__tests__/execution-selector.unit.test.ts +72 -0
  280. package/src/__tests__/execution-shared-kernel.unit.test.ts +180 -0
  281. package/src/__tests__/execution-world-health-owner.test-d.ts +9 -0
  282. package/src/__tests__/fallback-camera.unit.test.ts +32 -0
  283. package/src/__tests__/frame-loop-time-elapsed.test.ts +115 -0
  284. package/src/__tests__/frame-loop-time.unit.test.ts +61 -0
  285. package/src/__tests__/frame-loop-world-array.test.ts +441 -0
  286. package/src/__tests__/kernel-worker-runtime.unit.test.ts +50 -0
  287. package/src/__tests__/migration-surface-inventory.unit.test.ts +13 -0
  288. package/src/__tests__/plugin-policy.integration.test.ts +15 -0
  289. package/src/__tests__/plugin-profiles.unit.test.ts +190 -0
  290. package/src/__tests__/profiler-default-off.test.ts +53 -0
  291. package/src/__tests__/profiler-frame-token.test.ts +248 -0
  292. package/src/__tests__/profiler-phase-catalog.test.ts +30 -0
  293. package/src/__tests__/profiler-phase-integration.test.ts +129 -0
  294. package/src/__tests__/profiler-remote-bridge.test.ts +31 -0
  295. package/src/__tests__/remote-integration.test.ts +224 -0
  296. package/src/__tests__/remote-serve-flag.test.ts +24 -0
  297. package/src/__tests__/rhi-capture-capability.test-d.ts +22 -0
  298. package/src/__tests__/rhi-capture-host.integration.test.ts +157 -0
  299. package/src/__tests__/rhi-capture.browser.test.ts +51 -0
  300. package/src/__tests__/surface-handoff.test.ts +92 -0
  301. package/src/__tests__/types-m2.test-d.ts +81 -0
  302. package/src/__tests__/visibility-remote.integration.test.ts +119 -0
  303. package/src/__tests__/worker-execution-stop.test.ts +116 -0
  304. package/src/animation-asset-lookup.ts +13 -0
  305. package/src/assets-runtime-assembly.ts +70 -0
  306. package/src/canvas-policy.ts +16 -0
  307. package/src/create-app.ts +1351 -0
  308. package/src/errors.ts +454 -0
  309. package/src/execution/attached-world-swap.ts +67 -0
  310. package/src/execution/bootstrap-entry.ts +122 -0
  311. package/src/execution/capabilities.ts +102 -0
  312. package/src/execution/control.ts +52 -0
  313. package/src/execution/engine-worker-runtime.ts +400 -0
  314. package/src/execution/engine-worker.ts +161 -0
  315. package/src/execution/host-controller.ts +454 -0
  316. package/src/execution/index.ts +43 -0
  317. package/src/execution/kernel-pool.ts +236 -0
  318. package/src/execution/kernel-worker-runtime.ts +103 -0
  319. package/src/execution/measurement.ts +39 -0
  320. package/src/execution/protocol.ts +131 -0
  321. package/src/execution/report.ts +66 -0
  322. package/src/execution/schema.ts +168 -0
  323. package/src/execution/selector.ts +98 -0
  324. package/src/execution/types.ts +146 -0
  325. package/src/fallback-camera.ts +20 -0
  326. package/src/game-context.ts +126 -0
  327. package/src/index.ts +176 -0
  328. package/src/input-map-plugin.ts +21 -0
  329. package/src/input-plugin.ts +26 -0
  330. package/src/internal/assembled-engine-profile.ts +22 -0
  331. package/src/internal/assets-world-plugin.ts +79 -0
  332. package/src/internal/browser-remote-bridge.ts +267 -0
  333. package/src/internal/browser-rhi-debug-runtime.ts +67 -0
  334. package/src/internal/component-introspection.ts +116 -0
  335. package/src/internal/ecs-import.ts +41 -0
  336. package/src/internal/engine-profile-common.ts +10 -0
  337. package/src/internal/error-fanout.ts +129 -0
  338. package/src/internal/frame-loop.ts +525 -0
  339. package/src/internal/input-attach.ts +104 -0
  340. package/src/internal/main-engine-profile.ts +80 -0
  341. package/src/internal/remote-serve-flag.ts +50 -0
  342. package/src/internal/remote-server-plugin.ts +16 -0
  343. package/src/internal/rhi-capture.ts +211 -0
  344. package/src/internal/rhi-debug-flag.ts +40 -0
  345. package/src/internal/worker-engine-profile.ts +47 -0
  346. package/src/load-game-errors.ts +211 -0
  347. package/src/load-game.ts +115 -0
  348. package/src/plugin-factories.ts +2 -0
  349. package/src/tool-preview/bootstrap.ts +848 -0
  350. package/src/tool-preview/evidence.ts +59 -0
  351. package/src/tool-preview/framing.ts +52 -0
  352. package/src/tool-preview/recipe.ts +137 -0
  353. package/src/types.ts +505 -0
  354. package/src/vite-env.d.ts +12 -0
package/src/index.ts ADDED
@@ -0,0 +1,176 @@
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
+ type AssetRuntimeAssembly,
40
+ type AssetRuntimeAssemblyError,
41
+ assembleAssetRuntime,
42
+ } from './assets-runtime-assembly';
43
+ export { createApp, measureCanvasDrawingBuffer, syncCanvasDrawingBuffer } from './create-app';
44
+ export type {
45
+ AppDetailCanvasDetached,
46
+ AppDetailEmpty,
47
+ AppDetailExecutionBootstrapFailed,
48
+ AppDetailExecutionDeadlineExceeded,
49
+ AppDetailExecutionKernelFailed,
50
+ AppDetailExecutionRebuildFailed,
51
+ AppDetailExecutionStaleWorld,
52
+ AppDetailExecutionTierUnavailable,
53
+ AppDetailPluginActivationFailed,
54
+ AppDetailSystemUpdateFailed,
55
+ AppErrorCode,
56
+ AppErrorDetail,
57
+ AppErrorDetailFor,
58
+ } from './errors';
59
+ export {
60
+ APP_ERROR_HINTS,
61
+ APP_EXPECTED,
62
+ AppError,
63
+ isAppError,
64
+ } from './errors';
65
+ export type {
66
+ ExecutionBootstrapEntry,
67
+ ExecutionBootstrapHost,
68
+ ExecutionBootstrapValue,
69
+ ExecutionCapabilities,
70
+ ExecutionCapabilityFact,
71
+ ExecutionCapabilityName,
72
+ ExecutionControl,
73
+ ExecutionEngineHealth,
74
+ ExecutionFault,
75
+ ExecutionMeasurement,
76
+ ExecutionOptions,
77
+ ExecutionReport,
78
+ ExecutionRequestedTier,
79
+ ExecutionSelection,
80
+ ExecutionSelectionReason,
81
+ ExecutionTier,
82
+ ExecutionWorldHealth,
83
+ KernelDispatchReason,
84
+ PreparedExecutionBootstrap,
85
+ } from './execution';
86
+ export {
87
+ createExecutionReport,
88
+ EXECUTION_CAPABILITY_NAMES,
89
+ EXECUTION_REPORT_SCHEMA_VERSION,
90
+ EXECUTION_REQUESTED_TIERS,
91
+ EXECUTION_TIERS,
92
+ executionBootstrapHostPlugin,
93
+ isExecutionReport,
94
+ loadBootstrapEntry,
95
+ missingExecutionCapabilities,
96
+ prepareBootstrapEntry,
97
+ probeExecutionCapabilities,
98
+ selectExecutionTier,
99
+ unavailableExecutionCapabilities,
100
+ validateExecutionBootstrapData,
101
+ } from './execution';
102
+ export { ensureFallbackCamera } from './fallback-camera';
103
+ export type {
104
+ GameActionArgsSchema,
105
+ GameActionDef,
106
+ GameHost,
107
+ GameProjectionRegistrar,
108
+ GameProjectionValue,
109
+ GameReadDef,
110
+ } from './game-context';
111
+ export { gameHostPlugin } from './game-context';
112
+ export { inputPlugin } from './plugin-factories';
113
+
114
+ import {
115
+ isLoadGameError,
116
+ LOAD_GAME_ERROR_HINTS,
117
+ LOAD_GAME_EXPECTED,
118
+ LoadGameError,
119
+ } from './load-game-errors';
120
+
121
+ export type { GamePluginResolver } from './load-game';
122
+ export { loadGame } from './load-game';
123
+ export type {
124
+ LoadGameDetailImportFailed,
125
+ LoadGameDetailInvalidFormat,
126
+ LoadGameDetailModuleNotFound,
127
+ LoadGameErrorCode,
128
+ LoadGameErrorDetail,
129
+ LoadGameErrorDetailFor,
130
+ } from './load-game-errors';
131
+ export {
132
+ createToolPreviewHost,
133
+ replayToolPreviewCapture,
134
+ type ToolPreviewCaptureResult,
135
+ type ToolPreviewHost,
136
+ type ToolPreviewHostError,
137
+ type ToolPreviewHostOptions,
138
+ type ToolPreviewResourceFacts,
139
+ type ToolPreviewResourceKind,
140
+ type ToolPreviewResourceRequest,
141
+ type ToolPreviewRunResult,
142
+ } from './tool-preview/bootstrap';
143
+ export {
144
+ createToolPreviewEvidence,
145
+ joinResourcePreviewEvidence,
146
+ joinToolPreviewEvidence,
147
+ type ToolPreviewEvidence,
148
+ } from './tool-preview/evidence';
149
+ export {
150
+ fitToolPreviewCameraToAabb,
151
+ type ToolPreviewCameraFrame,
152
+ } from './tool-preview/framing';
153
+ export {
154
+ createToolPreviewRecipe,
155
+ type ToolPreviewAction,
156
+ type ToolPreviewPresentation,
157
+ type ToolPreviewRecipe,
158
+ type ToolPreviewRecipeOptions,
159
+ type ToolPreviewTrace,
160
+ type ToolPreviewTraceEvent,
161
+ validateToolPreviewTrace,
162
+ } from './tool-preview/recipe';
163
+ export type {
164
+ App,
165
+ AppAssembleArgs,
166
+ AssembleAppError,
167
+ BundlerOptions,
168
+ CanvasAppError,
169
+ CanvasDrawingBufferSize,
170
+ CreateAppOptions,
171
+ DrawSource,
172
+ DrawSourceResult,
173
+ ExecutionApp,
174
+ } from './types';
175
+ export { APP_PHASE_CATALOG } from './types';
176
+ 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,22 @@
1
+ import type { Plugin } from '@forgeax/engine-plugin';
2
+ import { rendererPlugin } from '@forgeax/engine-render';
3
+ import {
4
+ assetRegistryPlugin,
5
+ assetRegistryWorldPlugin,
6
+ assetsWorldPlugin,
7
+ rendererAssetsPlugin,
8
+ } from './assets-world-plugin';
9
+ import type { EngineProfileBase } from './engine-profile-common';
10
+
11
+ /** Assemble-form roots plus explicitly selected host capabilities. */
12
+ export function assembledEngineProfile(options: EngineProfileBase): Plugin[] {
13
+ return [
14
+ rendererPlugin(options.renderer),
15
+ rendererAssetsPlugin(),
16
+ ...(options.assetAssembly === undefined
17
+ ? []
18
+ : [assetRegistryPlugin(options.assetAssembly), assetRegistryWorldPlugin()]),
19
+ assetsWorldPlugin(),
20
+ ...(options.extensions ?? []),
21
+ ];
22
+ }
@@ -0,0 +1,79 @@
1
+ import type { AssetRegistry } from '@forgeax/engine-assets-runtime';
2
+ import type { Plugin } from '@forgeax/engine-plugin';
3
+ import type { RenderAssetPort } from '@forgeax/engine-render/internal';
4
+ import type { AssetRuntimeAssembly } from '../assets-runtime-assembly';
5
+
6
+ const RENDER_ASSET_PORT_RESOURCE_KEY = 'RenderAssetPort' as const;
7
+ const ASSET_REGISTRY_RESOURCE_KEY = 'AssetRegistry' as const;
8
+
9
+ /** Project the Renderer-owned render-only asset view as a declared service. */
10
+ export function rendererAssetsPlugin(): Plugin {
11
+ return {
12
+ name: 'renderer-assets',
13
+ inject: ['renderer'],
14
+ provide: 'assets',
15
+ apply(ctx) {
16
+ if (ctx.renderer === undefined) throw new Error('assets require the renderer service');
17
+ ctx.provide('assets', ctx.renderer.assets);
18
+ },
19
+ };
20
+ }
21
+
22
+ /** Adapt the Registry and its decoder leases to the App Fiber lifecycle. */
23
+ export function assetRegistryPlugin(assembly: AssetRuntimeAssembly): Plugin {
24
+ return {
25
+ name: 'asset-registry',
26
+ provide: 'assetRegistry',
27
+ apply(ctx) {
28
+ ctx.provide('assetRegistry', assembly.registry);
29
+ ctx.effect(
30
+ () => () => {
31
+ assembly.dispose();
32
+ assembly.registry.dispose();
33
+ },
34
+ 'assets/registry',
35
+ );
36
+ },
37
+ };
38
+ }
39
+
40
+ /** Project the render-only view into World resource lookup reversibly. */
41
+ export function assetsWorldPlugin(): Plugin {
42
+ return {
43
+ name: 'assets-world',
44
+ inject: ['world', 'assets'],
45
+ apply(ctx) {
46
+ ctx.effect(() => {
47
+ ctx.world.insertResource(RENDER_ASSET_PORT_RESOURCE_KEY, ctx.assets);
48
+ return () => {
49
+ ctx.world.removeResource(RENDER_ASSET_PORT_RESOURCE_KEY);
50
+ };
51
+ }, 'assets/world-render-projection');
52
+ },
53
+ };
54
+ }
55
+
56
+ /** Project the App-owned Registry into World resource lookup reversibly. */
57
+ export function assetRegistryWorldPlugin(): Plugin {
58
+ return {
59
+ name: 'asset-registry-world',
60
+ inject: ['world', 'assetRegistry'],
61
+ apply(ctx) {
62
+ ctx.effect(() => {
63
+ ctx.world.insertResource(ASSET_REGISTRY_RESOURCE_KEY, ctx.assetRegistry);
64
+ return () => {
65
+ ctx.world.removeResource(ASSET_REGISTRY_RESOURCE_KEY);
66
+ };
67
+ }, 'assets/world-registry');
68
+ },
69
+ };
70
+ }
71
+
72
+ declare module '@forgeax/engine-plugin' {
73
+ interface EngineContextServices {
74
+ /** Renderer-owned render projection; GameHost owns the load registry separately. */
75
+ assets?: RenderAssetPort;
76
+ /** App-owned typed runtime Registry for GUID loads and snapshots. */
77
+ assetRegistry?: AssetRegistry;
78
+ }
79
+ }
@@ -0,0 +1,267 @@
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 @forgeax/engine-remote dependency — the
19
+ // eval core is pulled by a further dynamic import, keeping @forgeax/engine-app
20
+ // free of a runtime dep on @forgeax/engine-remote (same discipline as the
21
+ // createApp startServer path).
22
+
23
+ import type { AssetRegistry } from '@forgeax/engine-assets-runtime';
24
+ import { Update, type World } from '@forgeax/engine-ecs';
25
+ import { createEcsImportModule } from './ecs-import';
26
+
27
+ type ExecuteResult = { ok: true; value: unknown } | { ok: false; error: unknown };
28
+
29
+ type ExecuteModule = {
30
+ executeScript: (
31
+ script: string,
32
+ ctx: {
33
+ world: unknown;
34
+ renderer: unknown;
35
+ assets: AssetRegistry;
36
+ rhiCapture?: unknown;
37
+ profiler?: unknown;
38
+ execution?: unknown;
39
+ importModule?: (specifier: string) => Promise<unknown>;
40
+ },
41
+ ) => Promise<ExecuteResult>;
42
+ };
43
+
44
+ type ComponentLike = { readonly name: string };
45
+
46
+ /**
47
+ * Project component exports onto the tokens already stored by this World.
48
+ *
49
+ * WHY: Vite may evaluate a workspace package once through the host package's
50
+ * dist graph and once through a source graph. Component ids are process-local,
51
+ * so equal `{ name, schema }` objects are not interchangeable with World
52
+ * access. The bridge is the one place that can reconcile the public module
53
+ * recipe with the live World's archetype SSOT.
54
+ */
55
+ function canonicalRuntimeModule(moduleValue: unknown, world: World): unknown {
56
+ if (moduleValue === null || typeof moduleValue !== 'object') return moduleValue;
57
+ const componentsByName = new Map<string, ComponentLike>();
58
+ for (const [name, component] of world.components.entries()) {
59
+ if (!componentsByName.has(name)) componentsByName.set(name, component);
60
+ }
61
+ const projected: Record<string, unknown> = { ...(moduleValue as Record<string, unknown>) };
62
+ for (const [key, value] of Object.entries(projected)) {
63
+ if (value === null || typeof value !== 'object' || !('name' in value)) continue;
64
+ const canonical = componentsByName.get((value as ComponentLike).name);
65
+ if (canonical !== undefined) projected[key] = canonical;
66
+ }
67
+ return projected;
68
+ }
69
+
70
+ export interface BrowserRemoteBridgeDeps {
71
+ readonly world: World;
72
+ readonly renderer: unknown;
73
+ readonly assets: AssetRegistry;
74
+ /** The host's already-loaded runtime namespace; preserves component-token identity. */
75
+ readonly runtimeModule: unknown;
76
+ readonly rhiCapture?: unknown;
77
+ /** The host's explicit CPU profiler capability, when opted in. */
78
+ readonly profiler?: unknown;
79
+ readonly execution?: unknown;
80
+ /** Relay port. */
81
+ readonly port: string;
82
+ }
83
+
84
+ /** Serialize a RemoteError-shaped object (or any thrown value) into a JSON-safe
85
+ * {code, expected, hint, detail?} envelope. AI users branch on error.code by
86
+ * property access, so the four structured fields must survive the wire. */
87
+ function serializeError(error: unknown): Record<string, unknown> {
88
+ if (error !== null && typeof error === 'object') {
89
+ const e = error as {
90
+ code?: unknown;
91
+ expected?: unknown;
92
+ hint?: unknown;
93
+ detail?: unknown;
94
+ message?: unknown;
95
+ };
96
+ const out: Record<string, unknown> = {
97
+ code: typeof e.code === 'string' ? e.code : 'script-runtime-error',
98
+ };
99
+ if (typeof e.expected === 'string') out.expected = e.expected;
100
+ if (typeof e.hint === 'string') out.hint = e.hint;
101
+ if (e.detail !== undefined) out.detail = e.detail;
102
+ if (out.hint === undefined && typeof e.message === 'string') out.hint = e.message;
103
+ return out;
104
+ }
105
+ return { code: 'script-runtime-error', hint: String(error) };
106
+ }
107
+
108
+ /**
109
+ * Install the DEV-only browser remote bridge. Idempotent per call site; the
110
+ * caller gates on import.meta.env.DEV so this never runs in production.
111
+ *
112
+ * Returns a teardown function that stops reconnection and closes the socket —
113
+ * the caller wires it to import.meta.hot.dispose so a vite HMR of the host app
114
+ * does not stack duplicate bridges.
115
+ */
116
+ export async function installBrowserRemoteBridge(
117
+ deps: BrowserRemoteBridgeDeps,
118
+ ): Promise<() => void> {
119
+ const { world, renderer, assets, rhiCapture, profiler, execution, port } = deps;
120
+
121
+ // The ws-free eval core. Dynamic import keeps @forgeax/engine-app free of a
122
+ // static @forgeax/engine-remote dependency (@vite-ignore mirrors the
123
+ // startServer path in create-app.ts).
124
+ const mod = (await import(/* @vite-ignore */ '@forgeax/engine-remote/execute')) as ExecuteModule;
125
+ const executeScript = mod.executeScript;
126
+ const importModule = createEcsImportModule((specifier: string): Promise<unknown> => {
127
+ // The host app and the bridge must share the same component-token objects.
128
+ // Vite can otherwise serve `/@id/@forgeax/engine-runtime` as a second
129
+ // module graph entry, so `world.get(entity, Transform)` sees a different
130
+ // Component id even though the token has the same name and schema.
131
+ if (specifier === '@forgeax/engine-runtime')
132
+ return Promise.resolve(canonicalRuntimeModule(deps.runtimeModule, world));
133
+ const browserSpecifier = specifier.startsWith('@') ? `/@id/${specifier}` : specifier;
134
+ return import(/* @vite-ignore */ browserSpecifier).then((moduleValue) =>
135
+ canonicalRuntimeModule(moduleValue, world),
136
+ );
137
+ });
138
+
139
+ let ws: WebSocket | null = null;
140
+ let backoff = 1000;
141
+ let stopped = false;
142
+
143
+ // Frame-start eval queue: a WebSocket `message` fires at an arbitrary phase of
144
+ // the rAF tick, so running eval inline would land world writes at an
145
+ // unpredictable phase. Enqueue instead and drain from Update system (frame
146
+ // start) so every bridge write passes through this frame's systems.
147
+ const evalQueue: Array<{ id: number; code: string }> = [];
148
+
149
+ const drainEvalQueue = (): void => {
150
+ if (evalQueue.length === 0) return;
151
+ // Snapshot + clear so an eval that enqueues runs next frame, not in an
152
+ // unbounded same-frame loop.
153
+ const jobs = evalQueue.splice(0, evalQueue.length);
154
+ for (const job of jobs) {
155
+ const reply = (payload: unknown): void => {
156
+ // Reply on the CURRENT socket (it may have reconnected since enqueue).
157
+ // The relay keys replies by request id, so the live socket resolves it.
158
+ try {
159
+ ws?.send(JSON.stringify({ type: 'result', id: job.id, payload }));
160
+ } catch {
161
+ /* socket gone; relay times the request out */
162
+ }
163
+ };
164
+ void (async () => {
165
+ let res: ExecuteResult;
166
+ try {
167
+ res = await executeScript(job.code, {
168
+ world,
169
+ renderer,
170
+ assets,
171
+ rhiCapture,
172
+ profiler: profiler,
173
+ execution,
174
+ importModule,
175
+ });
176
+ } catch (e) {
177
+ reply({
178
+ ok: false,
179
+ error: { code: 'BRIDGE_EVAL_THREW', hint: String((e as Error)?.message ?? e) },
180
+ });
181
+ return;
182
+ }
183
+ const envelope = res.ok
184
+ ? { ok: true as const, value: res.value }
185
+ : { ok: false as const, error: serializeError(res.error) };
186
+ // JSON-guard: non-serializable values (opaque engine handles, cycles)
187
+ // degrade to a marker so one bad field never wedges the channel.
188
+ try {
189
+ JSON.stringify(envelope);
190
+ reply(envelope);
191
+ } catch {
192
+ reply({ ok: true, value: '[unserializable value — inspect in the live window]' });
193
+ }
194
+ })();
195
+ }
196
+ };
197
+ world
198
+ .addSystem(Update, {
199
+ name: 'browser-remote-bridge-drain-eval-queue',
200
+ queries: [],
201
+ fn: drainEvalQueue,
202
+ })
203
+ .unwrap();
204
+
205
+ const connect = (): void => {
206
+ if (stopped) return;
207
+ try {
208
+ ws = new WebSocket(`ws://127.0.0.1:${port}/bridge`);
209
+ } catch {
210
+ return;
211
+ }
212
+ ws.addEventListener('open', () => {
213
+ backoff = 1000;
214
+ });
215
+ ws.addEventListener('message', (ev: MessageEvent) => {
216
+ let msg: { type?: string; id?: number; code?: string };
217
+ try {
218
+ msg = JSON.parse(typeof ev.data === 'string' ? ev.data : '');
219
+ } catch {
220
+ return;
221
+ }
222
+ if (msg?.type !== 'eval' || typeof msg.id !== 'number' || typeof msg.code !== 'string')
223
+ return;
224
+ evalQueue.push({ id: msg.id, code: msg.code });
225
+ });
226
+ const retry = (): void => {
227
+ ws = null;
228
+ if (stopped) return;
229
+ setTimeout(connect, backoff);
230
+ backoff = Math.min(backoff * 2, 15000);
231
+ };
232
+ ws.addEventListener('close', retry);
233
+ ws.addEventListener('error', () => {
234
+ try {
235
+ ws?.close();
236
+ } catch {
237
+ /* */
238
+ }
239
+ });
240
+ };
241
+ connect();
242
+
243
+ const teardown = (): void => {
244
+ if (stopped) return;
245
+ stopped = true;
246
+ world.removeSystem(Update, 'browser-remote-bridge-drain-eval-queue');
247
+ const s = ws;
248
+ ws = null;
249
+ if (s) {
250
+ try {
251
+ s.onclose = null;
252
+ s.close();
253
+ } catch {
254
+ /* */
255
+ }
256
+ }
257
+ };
258
+
259
+ // Self-register HMR teardown so a vite HMR of the host app does not stack
260
+ // duplicate bridges. Kept here (not in create-app.ts) so create-app.ts carries
261
+ // no import.meta.hot reference — the rhi-debug guard gate requires every
262
+ // import.meta.hot there to sit inside the FORGEAX_ENGINE_RHI_DEBUG block.
263
+ const hot = (import.meta as { hot?: { dispose(cb: () => void): void } }).hot;
264
+ if (hot) hot.dispose(teardown);
265
+
266
+ return teardown;
267
+ }
@@ -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').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
+ }