@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
@@ -0,0 +1,102 @@
1
+ import {
2
+ EXECUTION_CAPABILITY_NAMES,
3
+ type ExecutionCapabilities,
4
+ type ExecutionCapabilityName,
5
+ } from './types';
6
+
7
+ export function unavailableExecutionCapabilities(reason: string): ExecutionCapabilities {
8
+ return Object.fromEntries(
9
+ EXECUTION_CAPABILITY_NAMES.map((name) => [name, { available: false, reason }]),
10
+ ) as unknown as ExecutionCapabilities;
11
+ }
12
+
13
+ export function missingExecutionCapabilities(
14
+ capabilities: ExecutionCapabilities,
15
+ required: readonly ExecutionCapabilityName[],
16
+ ): readonly ExecutionCapabilityName[] {
17
+ return required.filter((name) => !capabilities[name].available);
18
+ }
19
+
20
+ interface WorkerProbeResult {
21
+ readonly workerAnimationFrame: boolean;
22
+ readonly workerWebGpu: boolean;
23
+ readonly atomicsWait: boolean;
24
+ }
25
+
26
+ function probeWorker(timeoutMs: number): Promise<WorkerProbeResult> {
27
+ return new Promise((resolve) => {
28
+ const source = `postMessage({workerAnimationFrame:typeof requestAnimationFrame==='function',workerWebGpu:typeof navigator==='object'&&navigator.gpu!==undefined,atomicsWait:typeof Atomics==='object'&&typeof Atomics.wait==='function'})`;
29
+ const url = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
30
+ const worker = new Worker(url);
31
+ const finish = (result: WorkerProbeResult): void => {
32
+ worker.terminate();
33
+ URL.revokeObjectURL(url);
34
+ resolve(result);
35
+ };
36
+ const timeout = setTimeout(
37
+ () => finish({ workerAnimationFrame: false, workerWebGpu: false, atomicsWait: false }),
38
+ timeoutMs,
39
+ );
40
+ worker.onmessage = (event: MessageEvent<WorkerProbeResult>) => {
41
+ clearTimeout(timeout);
42
+ finish(event.data);
43
+ };
44
+ worker.onerror = () => {
45
+ clearTimeout(timeout);
46
+ finish({ workerAnimationFrame: false, workerWebGpu: false, atomicsWait: false });
47
+ };
48
+ });
49
+ }
50
+
51
+ export async function probeExecutionCapabilities(
52
+ canvas: HTMLCanvasElement,
53
+ timeoutMs = 2_000,
54
+ ): Promise<ExecutionCapabilities> {
55
+ const workerAvailable = typeof Worker === 'function';
56
+ const offscreenAvailable = typeof canvas.transferControlToOffscreen === 'function';
57
+ const isolated = globalThis.crossOriginIsolated === true;
58
+ const sabAvailable = typeof SharedArrayBuffer === 'function';
59
+ const worker = workerAvailable
60
+ ? await probeWorker(timeoutMs)
61
+ : { workerAnimationFrame: false, workerWebGpu: false, atomicsWait: false };
62
+ return {
63
+ worker: {
64
+ available: workerAvailable,
65
+ reason: workerAvailable ? 'Worker constructor observed' : 'Worker constructor unavailable',
66
+ },
67
+ offscreenCanvas: {
68
+ available: offscreenAvailable,
69
+ reason: offscreenAvailable
70
+ ? 'canvas transferControlToOffscreen observed'
71
+ : 'canvas cannot transfer to OffscreenCanvas',
72
+ },
73
+ workerAnimationFrame: {
74
+ available: worker.workerAnimationFrame,
75
+ reason: worker.workerAnimationFrame
76
+ ? 'requestAnimationFrame observed in DedicatedWorker'
77
+ : 'requestAnimationFrame unavailable in DedicatedWorker',
78
+ },
79
+ workerWebGpu: {
80
+ available: worker.workerWebGpu,
81
+ reason: worker.workerWebGpu
82
+ ? 'navigator.gpu observed in DedicatedWorker'
83
+ : 'navigator.gpu unavailable in DedicatedWorker',
84
+ },
85
+ crossOriginIsolated: {
86
+ available: isolated,
87
+ reason: isolated ? 'crossOriginIsolated is true' : 'crossOriginIsolated is false',
88
+ },
89
+ sharedArrayBuffer: {
90
+ available: sabAvailable,
91
+ reason: sabAvailable
92
+ ? 'SharedArrayBuffer constructor observed'
93
+ : 'SharedArrayBuffer constructor unavailable',
94
+ },
95
+ atomicsWait: {
96
+ available: worker.atomicsWait,
97
+ reason: worker.atomicsWait
98
+ ? 'Atomics.wait observed in DedicatedWorker'
99
+ : 'Atomics.wait unavailable in DedicatedWorker',
100
+ },
101
+ };
102
+ }
@@ -0,0 +1,48 @@
1
+ import type { AudioState } from '@forgeax/engine-audio';
2
+ import { err } from '@forgeax/engine-types';
3
+ import { APP_ERROR_HINTS, APP_EXPECTED, AppError } from '../errors';
4
+ import { executionAudioReport } from './report';
5
+ import type { ExecutionControl, ExecutionEngineHealth, ExecutionReport } from './types';
6
+
7
+ export function cloneExecutionReport(report: ExecutionReport): ExecutionReport {
8
+ return structuredClone(report);
9
+ }
10
+
11
+ export interface LocalExecutionControl extends ExecutionControl {
12
+ setEngineHealth(health: ExecutionEngineHealth): void;
13
+ }
14
+
15
+ export interface LocalExecutionReportProviders {
16
+ readonly audio?: () => AudioState;
17
+ readonly world?: () => ExecutionReport['world'];
18
+ }
19
+
20
+ export function createLocalExecutionControl(
21
+ initial: ExecutionReport,
22
+ providers: LocalExecutionReportProviders = {},
23
+ ): LocalExecutionControl {
24
+ let current = cloneExecutionReport(initial);
25
+ return {
26
+ setEngineHealth: (health) => {
27
+ current = { ...current, engine: { ...current.engine, health } };
28
+ },
29
+ report: () =>
30
+ cloneExecutionReport({
31
+ ...current,
32
+ world: providers.world?.() ?? current.world,
33
+ audio: executionAudioReport(providers.audio?.()),
34
+ }),
35
+ rebuild: async () =>
36
+ err(
37
+ new AppError({
38
+ code: 'app-execution-rebuild-failed',
39
+ expected: APP_EXPECTED['app-execution-rebuild-failed'],
40
+ hint: APP_ERROR_HINTS['app-execution-rebuild-failed'],
41
+ detail: {
42
+ worldIdentity: current.world.identity,
43
+ cause: new Error('This local assembly has no bootstrap module.'),
44
+ },
45
+ }),
46
+ ),
47
+ };
48
+ }
@@ -0,0 +1,383 @@
1
+ import { type AudioIntent, createAudioIntentBackend } from '@forgeax/engine-audio';
2
+ import { createWorldContext, World } from '@forgeax/engine-ecs';
3
+ import type { SharedKernelExecutor } from '@forgeax/engine-ecs/shared';
4
+ import type { InputBackend, InputBackendSample } from '@forgeax/engine-input';
5
+ import type { Context, Plugin } from '@forgeax/engine-plugin';
6
+ import type { Renderer } from '@forgeax/engine-render';
7
+ import { constructRuntimeRendererHost } from '@forgeax/engine-runtime/internal/renderer-host';
8
+ import { createAnimationPayloadLookup } from '../animation-asset-lookup';
9
+ import { syncCameraAspect } from '../canvas-policy';
10
+ import { workerEngineProfile } from '../internal/worker-engine-profile';
11
+ import { createRenderFeatureHost } from '../renderer-plugin';
12
+ import { commitAttachedWorld, SerializedRebuildQueue } from './attached-world-swap';
13
+ import {
14
+ executionBootstrapHostPlugin,
15
+ type PreparedExecutionBootstrap,
16
+ prepareBootstrapEntry,
17
+ } from './bootstrap-entry';
18
+ import { createKernelPool, type KernelPool } from './kernel-pool';
19
+ import type {
20
+ EngineToHostMessage,
21
+ ExecutionFrameMessage,
22
+ ExecutionInitMessage,
23
+ ExecutionRebuildMessage,
24
+ HostToEngineMessage,
25
+ } from './protocol';
26
+
27
+ const scope = globalThis as unknown as {
28
+ postMessage(message: EngineToHostMessage): void;
29
+ onmessage: ((event: MessageEvent<HostToEngineMessage>) => void) | null;
30
+ close(): void;
31
+ };
32
+
33
+ let renderer: Renderer | undefined;
34
+ let assets: import('@forgeax/engine-assets-runtime').AssetRegistry | undefined;
35
+ let currentSample: InputBackendSample = {
36
+ downKeys: new Set(),
37
+ upKeys: new Set(),
38
+ buttons: [false, false, false],
39
+ movementX: 0,
40
+ movementY: 0,
41
+ wheelDelta: 0,
42
+ focused: true,
43
+ pointerLocked: false,
44
+ };
45
+ let lastFrameId = 0;
46
+ let engineCanvas: OffscreenCanvas | undefined;
47
+ interface WorkerRealm {
48
+ readonly world: World;
49
+ readonly init: ExecutionInitMessage;
50
+ pendingAudioIntents: AudioIntent[];
51
+ kernelPool: KernelPool | undefined;
52
+ pluginContext: Context | undefined;
53
+ }
54
+
55
+ let realm: WorkerRealm | undefined;
56
+ const rebuildQueue = new SerializedRebuildQueue();
57
+
58
+ const inputBackend: InputBackend = {
59
+ sample: () => currentSample,
60
+ detach: () => {},
61
+ };
62
+
63
+ function sharedKernelPlugin(target: WorkerRealm): Plugin {
64
+ return {
65
+ name: 'shared-kernel-executor',
66
+ inject: ['world'],
67
+ apply(ctx) {
68
+ const executor: SharedKernelExecutor = {
69
+ warmup(kernel) {
70
+ target.kernelPool ??= createKernelPool();
71
+ target.kernelPool.warmup?.(kernel);
72
+ },
73
+ execute(kernel, spans) {
74
+ target.kernelPool ??= createKernelPool();
75
+ return target.kernelPool.execute(kernel, spans);
76
+ },
77
+ };
78
+ ctx.effect(() => {
79
+ ctx.world.insertResource('SharedKernelExecutor', executor);
80
+ return () => {
81
+ ctx.world.removeResource('SharedKernelExecutor');
82
+ target.kernelPool?.dispose();
83
+ target.kernelPool = undefined;
84
+ };
85
+ }, 'execution/shared-kernel');
86
+ },
87
+ };
88
+ }
89
+
90
+ function serializableCause(cause: unknown): { readonly name: string; readonly message: string } {
91
+ return cause instanceof Error
92
+ ? { name: cause.name, message: cause.message }
93
+ : { name: 'Error', message: String(cause) };
94
+ }
95
+
96
+ function serializableDetail(cause: unknown): unknown {
97
+ if (cause instanceof Error) return serializableCause(cause);
98
+ if (Array.isArray(cause)) return cause.map(serializableDetail);
99
+ if (typeof cause === 'object' && cause !== null) {
100
+ return Object.fromEntries(
101
+ Object.entries(cause).map(([key, value]) => [key, serializableDetail(value)]),
102
+ );
103
+ }
104
+ return cause;
105
+ }
106
+
107
+ function postFault(
108
+ source: 'bootstrap' | 'runtime' | 'world' | 'rebuild',
109
+ code: string,
110
+ expected: string,
111
+ hint: string,
112
+ cause: unknown,
113
+ partialWrite = false,
114
+ ): void {
115
+ scope.postMessage({
116
+ kind: 'fault',
117
+ worldIdentity: realm?.world.identity ?? null,
118
+ source,
119
+ code,
120
+ expected,
121
+ hint,
122
+ detail: serializableDetail(cause),
123
+ partialWrite,
124
+ retryable: false,
125
+ });
126
+ }
127
+
128
+ async function disposeRealm(target: WorkerRealm): Promise<void> {
129
+ await target.pluginContext?.fiber.dispose();
130
+ target.pluginContext = undefined;
131
+ target.pendingAudioIntents = [];
132
+ }
133
+
134
+ function postBootstrapFault(error: {
135
+ readonly code: string;
136
+ readonly expected: string;
137
+ readonly hint: string;
138
+ readonly detail: unknown;
139
+ }): void {
140
+ postFault('bootstrap', error.code, error.expected, error.hint, error.detail);
141
+ }
142
+
143
+ async function createRealm(init: ExecutionInitMessage): Promise<boolean> {
144
+ const preparedResult = await prepareBootstrapEntry(init.bootstrapUrl, init.bootstrapData);
145
+ if (!preparedResult.ok) {
146
+ postBootstrapFault(preparedResult.error);
147
+ return false;
148
+ }
149
+ const prepared: PreparedExecutionBootstrap = preparedResult.value;
150
+ const nextWorld = new World({
151
+ ...(init.time !== undefined ? { time: init.time } : {}),
152
+ storage: init.tier === 'shared' ? 'shared' : 'local',
153
+ });
154
+ const candidate: WorkerRealm = {
155
+ world: nextWorld,
156
+ init,
157
+ pendingAudioIntents: [],
158
+ kernelPool: undefined,
159
+ pluginContext: undefined,
160
+ };
161
+ const audioBackend = createAudioIntentBackend({
162
+ emit: (intent) => candidate.pendingAudioIntents.push(intent),
163
+ });
164
+ let candidateRenderer: Renderer | undefined;
165
+ let rendererLifecycleTransferred = false;
166
+ const previousRenderer = renderer;
167
+ let previousSurfaceReleased = false;
168
+ try {
169
+ if (previousRenderer !== undefined) {
170
+ const released = previousRenderer.releaseSurface();
171
+ if (!released.ok) throw released.error;
172
+ previousSurfaceReleased = true;
173
+ }
174
+ const constructed = await constructRuntimeRendererHost(
175
+ init.canvas,
176
+ prepared.features === undefined ? {} : { features: prepared.features },
177
+ init.shaderManifestUrl === undefined
178
+ ? undefined
179
+ : { shaderManifestUrl: init.shaderManifestUrl },
180
+ );
181
+ if (!constructed.ok) throw constructed.error;
182
+ candidateRenderer = constructed.value.renderer;
183
+ assets = constructed.value.assets;
184
+ rendererLifecycleTransferred = true;
185
+ const pluginContext = await createWorldContext(
186
+ nextWorld,
187
+ workerEngineProfile({
188
+ renderer: candidateRenderer,
189
+ rendererFeatureHost: createRenderFeatureHost(constructed.value.featureHost),
190
+ assets,
191
+ input: inputBackend,
192
+ audio: audioBackend,
193
+ animationPayloads: createAnimationPayloadLookup(assets),
194
+ extensions: [
195
+ ...(init.tier === 'shared' ? [sharedKernelPlugin(candidate)] : []),
196
+ executionBootstrapHostPlugin({
197
+ ...(init.bootstrapPort === undefined ? {} : { port: init.bootstrapPort }),
198
+ setPointerLockAllowed(allowed): void {
199
+ scope.postMessage({
200
+ kind: 'host-control',
201
+ command: 'set-pointer-lock-allowed',
202
+ allowed,
203
+ });
204
+ },
205
+ }),
206
+ ...(prepared.plugins ?? []),
207
+ ],
208
+ }),
209
+ );
210
+ candidate.pluginContext = pluginContext;
211
+ const activeRenderer = candidateRenderer;
212
+ const previousRealm = realm;
213
+ const committed = await commitAttachedWorld(candidateRenderer, nextWorld, async () => {
214
+ await candidate.kernelPool?.ready();
215
+ return true;
216
+ });
217
+ if (!committed) {
218
+ await disposeRealm(candidate);
219
+ if (previousSurfaceReleased) previousRenderer?.restoreSurface();
220
+ return false;
221
+ }
222
+ realm = candidate;
223
+ renderer = activeRenderer;
224
+ lastFrameId = 0;
225
+ if (previousRealm !== undefined) await disposeRealm(previousRealm);
226
+ return true;
227
+ } catch (cause) {
228
+ await disposeRealm(candidate);
229
+ if (!rendererLifecycleTransferred) candidateRenderer?.dispose();
230
+ if (previousSurfaceReleased) previousRenderer?.restoreSurface();
231
+ throw cause;
232
+ }
233
+ }
234
+
235
+ async function initialize(message: ExecutionInitMessage): Promise<void> {
236
+ try {
237
+ engineCanvas = message.canvas;
238
+ if (!(await createRealm(message))) return;
239
+ scope.postMessage({
240
+ kind: 'ready',
241
+ worldIdentity: realm?.world.identity ?? '',
242
+ realm: 'worker',
243
+ workerWebGpu: typeof navigator === 'object' && navigator.gpu !== undefined,
244
+ });
245
+ } catch (cause) {
246
+ postFault(
247
+ 'bootstrap',
248
+ 'app-execution-bootstrap-failed',
249
+ 'Engine Worker creates a realm-local World, Renderer and GPU owner',
250
+ 'inspect the worker bootstrap cause and module URL',
251
+ cause,
252
+ );
253
+ }
254
+ }
255
+
256
+ function runFrame(message: ExecutionFrameMessage): void {
257
+ const activeRealm = realm;
258
+ if (activeRealm === undefined || renderer === undefined) return;
259
+ const { world } = activeRealm;
260
+ if (message.worldIdentity !== world.identity || message.frameId <= lastFrameId) return;
261
+ currentSample = message.inputSample;
262
+ const canvasWidth =
263
+ Number.isFinite(message.canvasWidth) && message.canvasWidth > 0
264
+ ? Math.max(1, Math.floor(message.canvasWidth))
265
+ : undefined;
266
+ const canvasHeight =
267
+ Number.isFinite(message.canvasHeight) && message.canvasHeight > 0
268
+ ? Math.max(1, Math.floor(message.canvasHeight))
269
+ : undefined;
270
+ if (canvasWidth !== undefined && canvasHeight !== undefined) {
271
+ if (engineCanvas !== undefined) {
272
+ if (engineCanvas.width !== canvasWidth) engineCanvas.width = canvasWidth;
273
+ if (engineCanvas.height !== canvasHeight) engineCanvas.height = canvasHeight;
274
+ }
275
+ syncCameraAspect(world, canvasWidth, canvasHeight);
276
+ }
277
+ const started = performance.now();
278
+ try {
279
+ const update = world.update(message.deltaSeconds);
280
+ if (!update.ok) throw update.error;
281
+ if (world.execution.health === 'poisoned') {
282
+ const fault = world.execution.fault;
283
+ postFault(
284
+ 'world',
285
+ fault?.code ?? 'world-poisoned',
286
+ 'World remains healthy through update',
287
+ 'rebuild the poisoned World explicitly',
288
+ fault,
289
+ fault?.partialWrite ?? true,
290
+ );
291
+ return;
292
+ }
293
+ const updateFinished = performance.now();
294
+ const attached = renderer.attach(world);
295
+ if (!attached.ok) throw attached.error;
296
+ const draw = renderer.draw({
297
+ leases: [attached.value],
298
+ camera: { lease: attached.value },
299
+ environment: { lease: attached.value },
300
+ });
301
+ if (!draw.ok) throw draw.error;
302
+ lastFrameId = message.frameId;
303
+ const kernelDispatch = activeRealm.kernelPool?.takeLastDispatch() ?? null;
304
+ const audioIntents = activeRealm.pendingAudioIntents;
305
+ activeRealm.pendingAudioIntents = [];
306
+ scope.postMessage({
307
+ kind: 'frame-complete',
308
+ worldIdentity: world.identity,
309
+ frameId: message.frameId,
310
+ engineUpdateMs: updateFinished - started,
311
+ kernelWaitMs: kernelDispatch?.waitMs ?? 0,
312
+ ...(audioIntents.length > 0 ? { audioIntents } : {}),
313
+ ...(kernelDispatch !== null
314
+ ? {
315
+ kernelDispatch: {
316
+ eligible: true,
317
+ usedShared: kernelDispatch.mode === 'shared',
318
+ reason:
319
+ kernelDispatch.mode === 'shared' ? ('shared' as const) : ('forced-inline' as const),
320
+ dispatched: kernelDispatch.dispatched,
321
+ completed: kernelDispatch.completed,
322
+ },
323
+ }
324
+ : {}),
325
+ });
326
+ } catch (cause) {
327
+ const fault = world.execution.fault;
328
+ postFault(
329
+ fault === null ? 'runtime' : 'world',
330
+ fault?.code ?? 'app-system-update-failed',
331
+ 'World update completes before Renderer draw',
332
+ fault === null ? 'inspect the runtime cause' : 'rebuild the poisoned World explicitly',
333
+ cause,
334
+ fault?.partialWrite ?? false,
335
+ );
336
+ }
337
+ }
338
+
339
+ async function rebuild(message: ExecutionRebuildMessage): Promise<void> {
340
+ const activeRealm = realm;
341
+ if (
342
+ activeRealm === undefined ||
343
+ renderer === undefined ||
344
+ message.worldIdentity !== activeRealm.world.identity
345
+ )
346
+ return;
347
+ const previousWorldIdentity = activeRealm.world.identity;
348
+ try {
349
+ if (engineCanvas === undefined) return;
350
+ const init: ExecutionInitMessage = { ...activeRealm.init, canvas: engineCanvas };
351
+ if (!(await createRealm(init))) return;
352
+ scope.postMessage({
353
+ kind: 'rebuilt',
354
+ previousWorldIdentity,
355
+ worldIdentity: realm?.world.identity ?? '',
356
+ });
357
+ } catch (cause) {
358
+ postFault(
359
+ 'rebuild',
360
+ 'app-execution-rebuild-failed',
361
+ 'bootstrap creates a fresh World identity',
362
+ 'inspect the bootstrap cause or create a new App',
363
+ cause,
364
+ );
365
+ }
366
+ }
367
+
368
+ scope.onmessage = (event): void => {
369
+ const message = event.data;
370
+ if (message.kind === 'init') void initialize(message);
371
+ else if (message.kind === 'frame') runFrame(message);
372
+ else if (message.kind === 'rebuild') {
373
+ void rebuildQueue.enqueue(() => rebuild(message));
374
+ } else if (message.kind === 'dispose') {
375
+ void (async () => {
376
+ if (realm !== undefined) {
377
+ await disposeRealm(realm);
378
+ realm = undefined;
379
+ }
380
+ scope.close();
381
+ })();
382
+ }
383
+ };
@@ -0,0 +1,163 @@
1
+ import { err, ok, type Result } from '@forgeax/engine-types';
2
+ import { APP_ERROR_HINTS, APP_EXPECTED, AppError, type AppError as AppErrorType } from '../errors';
3
+ import { validateExecutionBootstrapData } from './bootstrap-entry';
4
+ import type {
5
+ EngineToHostMessage,
6
+ ExecutionFaultMessage,
7
+ ExecutionInitMessage,
8
+ ExecutionReadyMessage,
9
+ HostToEngineMessage,
10
+ } from './protocol';
11
+
12
+ export interface EngineWorkerSession {
13
+ readonly worker: Worker;
14
+ readonly ready: ExecutionReadyMessage;
15
+ post(message: HostToEngineMessage, transfer?: Transferable[]): void;
16
+ listen(listener: (message: EngineToHostMessage) => void): () => void;
17
+ dispose(): void;
18
+ }
19
+
20
+ export interface StartEngineWorkerOptions {
21
+ readonly canvas: HTMLCanvasElement;
22
+ readonly bootstrapUrl: string;
23
+ readonly bootstrapData?: import('./types').ExecutionBootstrapValue;
24
+ readonly bootstrapPort?: MessagePort;
25
+ readonly assetCatalog?: import('./types').ExecutionAssetCatalog;
26
+ readonly shaderManifestUrl?: string;
27
+ readonly time?: import('@forgeax/engine-ecs').TimePolicy;
28
+ readonly timeoutMs: number;
29
+ readonly tier: import('./types').ExecutionTier;
30
+ readonly workerFactory?: () => Worker;
31
+ }
32
+
33
+ function deadlineError(timeoutMs: number): AppErrorType {
34
+ return new AppError({
35
+ code: 'app-execution-deadline-exceeded',
36
+ expected: APP_EXPECTED['app-execution-deadline-exceeded'],
37
+ hint: APP_ERROR_HINTS['app-execution-deadline-exceeded'],
38
+ detail: { phase: 'handshake', timeoutMs },
39
+ });
40
+ }
41
+
42
+ function startupFault(message: ExecutionFaultMessage, moduleUrl: string): AppErrorType {
43
+ return new AppError({
44
+ code: 'app-execution-bootstrap-failed',
45
+ expected: APP_EXPECTED['app-execution-bootstrap-failed'],
46
+ hint: APP_ERROR_HINTS['app-execution-bootstrap-failed'],
47
+ detail: { phase: 'bootstrap', moduleUrl, cause: message.detail },
48
+ });
49
+ }
50
+
51
+ export async function startEngineWorker(
52
+ options: StartEngineWorkerOptions,
53
+ ): Promise<Result<EngineWorkerSession, AppErrorType>> {
54
+ const validData = validateExecutionBootstrapData(options.bootstrapData, options.bootstrapUrl);
55
+ if (!validData.ok) return validData;
56
+ const worker =
57
+ options.workerFactory?.() ??
58
+ new Worker(new URL('./engine-worker-runtime.mjs', import.meta.url), {
59
+ type: 'module',
60
+ name: 'forgeax-engine',
61
+ });
62
+ const listeners = new Set<(message: EngineToHostMessage) => void>();
63
+ let settled = false;
64
+ let resolveReady: (result: Result<ExecutionReadyMessage, AppErrorType>) => void = () => {};
65
+ const readyPromise = new Promise<Result<ExecutionReadyMessage, AppErrorType>>((resolve) => {
66
+ resolveReady = resolve;
67
+ });
68
+ const timeout = setTimeout(() => {
69
+ if (settled) return;
70
+ settled = true;
71
+ worker.terminate();
72
+ resolveReady(err(deadlineError(options.timeoutMs)));
73
+ }, options.timeoutMs);
74
+ worker.onmessage = (event: MessageEvent<EngineToHostMessage>) => {
75
+ const message = event.data;
76
+ if (!settled && message.kind === 'ready') {
77
+ settled = true;
78
+ clearTimeout(timeout);
79
+ resolveReady(ok(message));
80
+ } else if (!settled && message.kind === 'fault') {
81
+ settled = true;
82
+ clearTimeout(timeout);
83
+ worker.terminate();
84
+ resolveReady(err(startupFault(message, options.bootstrapUrl)));
85
+ }
86
+ for (const listener of listeners) listener(message);
87
+ };
88
+ worker.onerror = (event) => {
89
+ if (settled) {
90
+ const crash: ExecutionFaultMessage = {
91
+ kind: 'fault',
92
+ worldIdentity: null,
93
+ source: 'runtime',
94
+ code: 'worker-crashed',
95
+ expected: 'Engine Worker remains alive while the App is running',
96
+ hint: 'inspect the Worker error and create a new App; transferred canvas ownership is terminal',
97
+ detail: { message: event.message },
98
+ partialWrite: false,
99
+ retryable: false,
100
+ };
101
+ for (const listener of listeners) listener(crash);
102
+ return;
103
+ }
104
+ settled = true;
105
+ clearTimeout(timeout);
106
+ worker.terminate();
107
+ resolveReady(
108
+ err(
109
+ startupFault(
110
+ {
111
+ kind: 'fault',
112
+ worldIdentity: null,
113
+ source: 'bootstrap',
114
+ code: 'worker-error',
115
+ expected: 'Engine Worker module loads',
116
+ hint: 'inspect the Worker module graph',
117
+ detail: { message: event.message },
118
+ partialWrite: false,
119
+ retryable: false,
120
+ },
121
+ options.bootstrapUrl,
122
+ ),
123
+ ),
124
+ );
125
+ };
126
+
127
+ const offscreen = options.canvas.transferControlToOffscreen();
128
+ const init: ExecutionInitMessage = {
129
+ kind: 'init',
130
+ canvas: offscreen,
131
+ bootstrapUrl: options.bootstrapUrl,
132
+ ...(options.bootstrapData === undefined ? {} : { bootstrapData: options.bootstrapData }),
133
+ ...(options.bootstrapPort === undefined ? {} : { bootstrapPort: options.bootstrapPort }),
134
+ ...(options.assetCatalog === undefined ? {} : { assetCatalog: options.assetCatalog }),
135
+ ...(options.shaderManifestUrl !== undefined
136
+ ? { shaderManifestUrl: options.shaderManifestUrl }
137
+ : {}),
138
+ ...(options.time !== undefined ? { time: options.time } : {}),
139
+ tier: options.tier,
140
+ };
141
+ worker.postMessage(init, [
142
+ offscreen,
143
+ ...(options.bootstrapPort === undefined ? [] : [options.bootstrapPort]),
144
+ ]);
145
+ const ready = await readyPromise;
146
+ if (!ready.ok) return ready;
147
+ return ok({
148
+ worker,
149
+ ready: ready.value,
150
+ post(message, transfer = []): void {
151
+ worker.postMessage(message, transfer);
152
+ },
153
+ listen(listener): () => void {
154
+ listeners.add(listener);
155
+ return () => listeners.delete(listener);
156
+ },
157
+ dispose(): void {
158
+ worker.postMessage({ kind: 'dispose' } satisfies HostToEngineMessage);
159
+ worker.terminate();
160
+ listeners.clear();
161
+ },
162
+ });
163
+ }