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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +424 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/animation-asset-lookup.unit.test.d.ts +2 -0
  5. package/dist/__tests__/animation-asset-lookup.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/callback-deletion.unit.test.d.ts +2 -0
  7. package/dist/__tests__/callback-deletion.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/callback-inference.test-d.d.ts +2 -0
  9. package/dist/__tests__/callback-inference.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/component-introspection.unit.test.d.ts +2 -0
  11. package/dist/__tests__/component-introspection.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/consumer-migration.unit.test.d.ts +2 -0
  13. package/dist/__tests__/consumer-migration.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/create-app-error-retention.integration.test.d.ts +2 -0
  15. package/dist/__tests__/create-app-error-retention.integration.test.d.ts.map +1 -0
  16. package/dist/__tests__/create-app-features.test.d.ts +2 -0
  17. package/dist/__tests__/create-app-features.test.d.ts.map +1 -0
  18. package/dist/__tests__/create-app-listener-sync.test.d.ts +2 -0
  19. package/dist/__tests__/create-app-listener-sync.test.d.ts.map +1 -0
  20. package/dist/__tests__/create-app-lock-provider.test.d.ts +2 -0
  21. package/dist/__tests__/create-app-lock-provider.test.d.ts.map +1 -0
  22. package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts +2 -0
  23. package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts.map +1 -0
  24. package/dist/__tests__/create-app-plugin.test.d.ts +2 -0
  25. package/dist/__tests__/create-app-plugin.test.d.ts.map +1 -0
  26. package/dist/__tests__/create-app-stop.test.d.ts +2 -0
  27. package/dist/__tests__/create-app-stop.test.d.ts.map +1 -0
  28. package/dist/__tests__/create-app-time.unit.test.d.ts +2 -0
  29. package/dist/__tests__/create-app-time.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/create-app.test-d.d.ts +2 -0
  31. package/dist/__tests__/create-app.test-d.d.ts.map +1 -0
  32. package/dist/__tests__/create-app.test.d.ts +2 -0
  33. package/dist/__tests__/create-app.test.d.ts.map +1 -0
  34. package/dist/__tests__/create-renderer-error.integration.test.d.ts +2 -0
  35. package/dist/__tests__/create-renderer-error.integration.test.d.ts.map +1 -0
  36. package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts +2 -0
  37. package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts.map +1 -0
  38. package/dist/__tests__/docs-gate-green.unit.test.d.ts +2 -0
  39. package/dist/__tests__/docs-gate-green.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts +2 -0
  41. package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts.map +1 -0
  42. package/dist/__tests__/draw-source-single-world-regression.test.d.ts +2 -0
  43. package/dist/__tests__/draw-source-single-world-regression.test.d.ts.map +1 -0
  44. package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts +2 -0
  45. package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts +2 -0
  47. package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts.map +1 -0
  48. package/dist/__tests__/errors-pointer-lock-failed.test.d.ts +2 -0
  49. package/dist/__tests__/errors-pointer-lock-failed.test.d.ts.map +1 -0
  50. package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts +2 -0
  51. package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/execution-error-detail.test-d.d.ts +2 -0
  53. package/dist/__tests__/execution-error-detail.test-d.d.ts.map +1 -0
  54. package/dist/__tests__/execution-error-detail.unit.test.d.ts +2 -0
  55. package/dist/__tests__/execution-error-detail.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/execution-frame-order.unit.test.d.ts +2 -0
  57. package/dist/__tests__/execution-frame-order.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/execution-frame-protocol.unit.test.d.ts +2 -0
  59. package/dist/__tests__/execution-frame-protocol.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/execution-input-sample.contract.test.d.ts +2 -0
  61. package/dist/__tests__/execution-input-sample.contract.test.d.ts.map +1 -0
  62. package/dist/__tests__/execution-measurement.unit.test.d.ts +2 -0
  63. package/dist/__tests__/execution-measurement.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/execution-public-api.test-d.d.ts +2 -0
  65. package/dist/__tests__/execution-public-api.test-d.d.ts.map +1 -0
  66. package/dist/__tests__/execution-public-api.unit.test.d.ts +2 -0
  67. package/dist/__tests__/execution-public-api.unit.test.d.ts.map +1 -0
  68. package/dist/__tests__/execution-rebuild.contract.test.d.ts +2 -0
  69. package/dist/__tests__/execution-rebuild.contract.test.d.ts.map +1 -0
  70. package/dist/__tests__/execution-report.contract.test.d.ts +2 -0
  71. package/dist/__tests__/execution-report.contract.test.d.ts.map +1 -0
  72. package/dist/__tests__/execution-report.types.test-d.d.ts +2 -0
  73. package/dist/__tests__/execution-report.types.test-d.d.ts.map +1 -0
  74. package/dist/__tests__/execution-schema-freshness.unit.test.d.ts +2 -0
  75. package/dist/__tests__/execution-schema-freshness.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/execution-selector.unit.test.d.ts +2 -0
  77. package/dist/__tests__/execution-selector.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/execution-shared-kernel.unit.test.d.ts +2 -0
  79. package/dist/__tests__/execution-shared-kernel.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/fallback-camera.unit.test.d.ts +2 -0
  81. package/dist/__tests__/fallback-camera.unit.test.d.ts.map +1 -0
  82. package/dist/__tests__/frame-loop-time-elapsed.test.d.ts +2 -0
  83. package/dist/__tests__/frame-loop-time-elapsed.test.d.ts.map +1 -0
  84. package/dist/__tests__/frame-loop-time.unit.test.d.ts +2 -0
  85. package/dist/__tests__/frame-loop-time.unit.test.d.ts.map +1 -0
  86. package/dist/__tests__/frame-loop-world-array.test.d.ts +2 -0
  87. package/dist/__tests__/frame-loop-world-array.test.d.ts.map +1 -0
  88. package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts +2 -0
  89. package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/migration-surface-inventory.unit.test.d.ts +2 -0
  91. package/dist/__tests__/migration-surface-inventory.unit.test.d.ts.map +1 -0
  92. package/dist/__tests__/plugin-policy.integration.test.d.ts +2 -0
  93. package/dist/__tests__/plugin-policy.integration.test.d.ts.map +1 -0
  94. package/dist/__tests__/plugin-profiles.unit.test.d.ts +2 -0
  95. package/dist/__tests__/plugin-profiles.unit.test.d.ts.map +1 -0
  96. package/dist/__tests__/profiler-default-off.test.d.ts +2 -0
  97. package/dist/__tests__/profiler-default-off.test.d.ts.map +1 -0
  98. package/dist/__tests__/profiler-frame-token.test.d.ts +2 -0
  99. package/dist/__tests__/profiler-frame-token.test.d.ts.map +1 -0
  100. package/dist/__tests__/profiler-phase-catalog.test.d.ts +2 -0
  101. package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +1 -0
  102. package/dist/__tests__/profiler-phase-integration.test.d.ts +2 -0
  103. package/dist/__tests__/profiler-phase-integration.test.d.ts.map +1 -0
  104. package/dist/__tests__/profiler-remote-bridge.test.d.ts +2 -0
  105. package/dist/__tests__/profiler-remote-bridge.test.d.ts.map +1 -0
  106. package/dist/__tests__/remote-integration.test.d.ts +2 -0
  107. package/dist/__tests__/remote-integration.test.d.ts.map +1 -0
  108. package/dist/__tests__/rhi-capture-capability.test-d.d.ts +2 -0
  109. package/dist/__tests__/rhi-capture-capability.test-d.d.ts.map +1 -0
  110. package/dist/__tests__/rhi-capture-host.integration.test.d.ts +2 -0
  111. package/dist/__tests__/rhi-capture-host.integration.test.d.ts.map +1 -0
  112. package/dist/__tests__/rhi-capture.browser.test.d.ts +2 -0
  113. package/dist/__tests__/rhi-capture.browser.test.d.ts.map +1 -0
  114. package/dist/__tests__/surface-handoff.test.d.ts +2 -0
  115. package/dist/__tests__/surface-handoff.test.d.ts.map +1 -0
  116. package/dist/__tests__/types-m2.test-d.d.ts +2 -0
  117. package/dist/__tests__/types-m2.test-d.d.ts.map +1 -0
  118. package/dist/__tests__/visibility-remote.integration.test.d.ts +2 -0
  119. package/dist/__tests__/visibility-remote.integration.test.d.ts.map +1 -0
  120. package/dist/__tests__/worker-execution-stop.test.d.ts +2 -0
  121. package/dist/__tests__/worker-execution-stop.test.d.ts.map +1 -0
  122. package/dist/animation-asset-lookup.d.ts +7 -0
  123. package/dist/animation-asset-lookup.d.ts.map +1 -0
  124. package/dist/browser-frame-signal.d.ts +17 -0
  125. package/dist/browser-frame-signal.d.ts.map +1 -0
  126. package/dist/canvas-policy.d.ts +4 -0
  127. package/dist/canvas-policy.d.ts.map +1 -0
  128. package/dist/create-app.d.ts +78 -0
  129. package/dist/create-app.d.ts.map +1 -0
  130. package/dist/engine-worker-runtime.mjs +827 -0
  131. package/dist/engine-worker-runtime.mjs.map +1 -0
  132. package/dist/errors.d.ts +254 -0
  133. package/dist/errors.d.ts.map +1 -0
  134. package/dist/execution/attached-world-swap.d.ts +10 -0
  135. package/dist/execution/attached-world-swap.d.ts.map +1 -0
  136. package/dist/execution/bootstrap-entry.d.ts +30 -0
  137. package/dist/execution/bootstrap-entry.d.ts.map +1 -0
  138. package/dist/execution/bootstrap-url.d.ts +5 -0
  139. package/dist/execution/bootstrap-url.d.ts.map +1 -0
  140. package/dist/execution/capabilities.d.ts +5 -0
  141. package/dist/execution/capabilities.d.ts.map +1 -0
  142. package/dist/execution/control.d.ts +12 -0
  143. package/dist/execution/control.d.ts.map +1 -0
  144. package/dist/execution/engine-worker-runtime.d.ts +2 -0
  145. package/dist/execution/engine-worker-runtime.d.ts.map +1 -0
  146. package/dist/execution/engine-worker.d.ts +24 -0
  147. package/dist/execution/engine-worker.d.ts.map +1 -0
  148. package/dist/execution/host-controller.d.ts +16 -0
  149. package/dist/execution/host-controller.d.ts.map +1 -0
  150. package/dist/execution/index.d.ts +10 -0
  151. package/dist/execution/index.d.ts.map +1 -0
  152. package/dist/execution/kernel-pool.d.ts +14 -0
  153. package/dist/execution/kernel-pool.d.ts.map +1 -0
  154. package/dist/execution/kernel-worker-runtime.d.ts +2 -0
  155. package/dist/execution/kernel-worker-runtime.d.ts.map +1 -0
  156. package/dist/execution/measurement.d.ts +7 -0
  157. package/dist/execution/measurement.d.ts.map +1 -0
  158. package/dist/execution/protocol.d.ts +87 -0
  159. package/dist/execution/protocol.d.ts.map +1 -0
  160. package/dist/execution/report.d.ts +5 -0
  161. package/dist/execution/report.d.ts.map +1 -0
  162. package/dist/execution/schema.d.ts +4 -0
  163. package/dist/execution/schema.d.ts.map +1 -0
  164. package/dist/execution/selector.d.ts +10 -0
  165. package/dist/execution/selector.d.ts.map +1 -0
  166. package/dist/execution/types.d.ts +123 -0
  167. package/dist/execution/types.d.ts.map +1 -0
  168. package/dist/fallback-camera.d.ts +8 -0
  169. package/dist/fallback-camera.d.ts.map +1 -0
  170. package/dist/game-context.d.ts +98 -0
  171. package/dist/game-context.d.ts.map +1 -0
  172. package/dist/index.d.ts +27 -0
  173. package/dist/index.d.ts.map +1 -0
  174. package/dist/index.mjs +3980 -0
  175. package/dist/index.mjs.map +1 -0
  176. package/dist/input-map-plugin.d.ts +5 -0
  177. package/dist/input-map-plugin.d.ts.map +1 -0
  178. package/dist/input-plugin.d.ts +4 -0
  179. package/dist/input-plugin.d.ts.map +1 -0
  180. package/dist/internal/assembled-engine-profile.d.ts +5 -0
  181. package/dist/internal/assembled-engine-profile.d.ts.map +1 -0
  182. package/dist/internal/assets-world-plugin.d.ts +7 -0
  183. package/dist/internal/assets-world-plugin.d.ts.map +1 -0
  184. package/dist/internal/browser-remote-bridge.d.ts +24 -0
  185. package/dist/internal/browser-remote-bridge.d.ts.map +1 -0
  186. package/dist/internal/browser-rhi-debug-runtime.d.ts +18 -0
  187. package/dist/internal/browser-rhi-debug-runtime.d.ts.map +1 -0
  188. package/dist/internal/component-introspection.d.ts +24 -0
  189. package/dist/internal/component-introspection.d.ts.map +1 -0
  190. package/dist/internal/debug-draw.d.ts +9 -0
  191. package/dist/internal/debug-draw.d.ts.map +1 -0
  192. package/dist/internal/ecs-import.d.ts +6 -0
  193. package/dist/internal/ecs-import.d.ts.map +1 -0
  194. package/dist/internal/engine-profile-common.d.ts +9 -0
  195. package/dist/internal/engine-profile-common.d.ts.map +1 -0
  196. package/dist/internal/error-fanout.d.ts +66 -0
  197. package/dist/internal/error-fanout.d.ts.map +1 -0
  198. package/dist/internal/frame-loop.d.ts +38 -0
  199. package/dist/internal/frame-loop.d.ts.map +1 -0
  200. package/dist/internal/input-attach.d.ts +44 -0
  201. package/dist/internal/input-attach.d.ts.map +1 -0
  202. package/dist/internal/main-engine-profile.d.ts +19 -0
  203. package/dist/internal/main-engine-profile.d.ts.map +1 -0
  204. package/dist/internal/remote-serve-flag.d.ts +19 -0
  205. package/dist/internal/remote-serve-flag.d.ts.map +1 -0
  206. package/dist/internal/remote-server-plugin.d.ts +8 -0
  207. package/dist/internal/remote-server-plugin.d.ts.map +1 -0
  208. package/dist/internal/rhi-capture.d.ts +19 -0
  209. package/dist/internal/rhi-capture.d.ts.map +1 -0
  210. package/dist/internal/rhi-debug-flag.d.ts +18 -0
  211. package/dist/internal/rhi-debug-flag.d.ts.map +1 -0
  212. package/dist/internal/worker-engine-profile.d.ts +15 -0
  213. package/dist/internal/worker-engine-profile.d.ts.map +1 -0
  214. package/dist/kernel-worker-runtime.mjs +69 -0
  215. package/dist/kernel-worker-runtime.mjs.map +1 -0
  216. package/dist/load-game-errors.d.ts +131 -0
  217. package/dist/load-game-errors.d.ts.map +1 -0
  218. package/dist/load-game.d.ts +34 -0
  219. package/dist/load-game.d.ts.map +1 -0
  220. package/dist/plugin-factories.d.ts +3 -0
  221. package/dist/plugin-factories.d.ts.map +1 -0
  222. package/dist/renderer-plugin.d.ts +25 -0
  223. package/dist/renderer-plugin.d.ts.map +1 -0
  224. package/dist/tool-preview/bootstrap.d.ts +113 -0
  225. package/dist/tool-preview/bootstrap.d.ts.map +1 -0
  226. package/dist/tool-preview/evidence.d.ts +20 -0
  227. package/dist/tool-preview/evidence.d.ts.map +1 -0
  228. package/dist/tool-preview/framing.d.ts +13 -0
  229. package/dist/tool-preview/framing.d.ts.map +1 -0
  230. package/dist/tool-preview/recipe.d.ts +54 -0
  231. package/dist/tool-preview/recipe.d.ts.map +1 -0
  232. package/dist/types.d.ts +404 -0
  233. package/dist/types.d.ts.map +1 -0
  234. package/package.json +85 -0
  235. package/schema/execution-report.schema.json +168 -0
  236. package/src/__tests__/animation-asset-lookup.unit.test.ts +20 -0
  237. package/src/__tests__/callback-deletion.unit.test.ts +14 -0
  238. package/src/__tests__/callback-inference.test-d.ts +36 -0
  239. package/src/__tests__/component-introspection.unit.test.ts +56 -0
  240. package/src/__tests__/consumer-migration.unit.test.ts +34 -0
  241. package/src/__tests__/create-app-error-retention.integration.test.ts +125 -0
  242. package/src/__tests__/create-app-features.test.ts +51 -0
  243. package/src/__tests__/create-app-listener-sync.test.ts +381 -0
  244. package/src/__tests__/create-app-lock-provider.test.ts +212 -0
  245. package/src/__tests__/create-app-physics-plugin-failure.integration.test.ts +142 -0
  246. package/src/__tests__/create-app-plugin.test.ts +217 -0
  247. package/src/__tests__/create-app-stop.test.ts +609 -0
  248. package/src/__tests__/create-app-time.unit.test.ts +26 -0
  249. package/src/__tests__/create-app.test-d.ts +83 -0
  250. package/src/__tests__/create-app.test.ts +287 -0
  251. package/src/__tests__/create-renderer-error.integration.test.ts +25 -0
  252. package/src/__tests__/create-renderer-lifecycle.integration.test.ts +24 -0
  253. package/src/__tests__/docs-gate-green.unit.test.ts +61 -0
  254. package/src/__tests__/draw-source-injected-world-update-visibility.test.ts +176 -0
  255. package/src/__tests__/draw-source-single-world-regression.test.ts +184 -0
  256. package/src/__tests__/engine-worker-bootstrap-channel.unit.test.ts +82 -0
  257. package/src/__tests__/engine-worker-world-swap.unit.test.ts +69 -0
  258. package/src/__tests__/errors-pointer-lock-failed.test.ts +265 -0
  259. package/src/__tests__/execution-bootstrap-isolation.unit.test.ts +79 -0
  260. package/src/__tests__/execution-error-detail.test-d.ts +13 -0
  261. package/src/__tests__/execution-error-detail.unit.test.ts +35 -0
  262. package/src/__tests__/execution-frame-order.unit.test.ts +38 -0
  263. package/src/__tests__/execution-frame-protocol.unit.test.ts +60 -0
  264. package/src/__tests__/execution-input-sample.contract.test.ts +23 -0
  265. package/src/__tests__/execution-measurement.unit.test.ts +20 -0
  266. package/src/__tests__/execution-public-api.test-d.ts +14 -0
  267. package/src/__tests__/execution-public-api.unit.test.ts +9 -0
  268. package/src/__tests__/execution-rebuild.contract.test.ts +36 -0
  269. package/src/__tests__/execution-report.contract.test.ts +50 -0
  270. package/src/__tests__/execution-report.types.test-d.ts +16 -0
  271. package/src/__tests__/execution-schema-freshness.unit.test.ts +20 -0
  272. package/src/__tests__/execution-selector.unit.test.ts +72 -0
  273. package/src/__tests__/execution-shared-kernel.unit.test.ts +180 -0
  274. package/src/__tests__/fallback-camera.unit.test.ts +32 -0
  275. package/src/__tests__/frame-loop-time-elapsed.test.ts +115 -0
  276. package/src/__tests__/frame-loop-time.unit.test.ts +61 -0
  277. package/src/__tests__/frame-loop-world-array.test.ts +450 -0
  278. package/src/__tests__/kernel-worker-runtime.unit.test.ts +50 -0
  279. package/src/__tests__/migration-surface-inventory.unit.test.ts +13 -0
  280. package/src/__tests__/plugin-policy.integration.test.ts +15 -0
  281. package/src/__tests__/plugin-profiles.unit.test.ts +190 -0
  282. package/src/__tests__/profiler-default-off.test.ts +53 -0
  283. package/src/__tests__/profiler-frame-token.test.ts +235 -0
  284. package/src/__tests__/profiler-phase-catalog.test.ts +30 -0
  285. package/src/__tests__/profiler-phase-integration.test.ts +117 -0
  286. package/src/__tests__/profiler-remote-bridge.test.ts +31 -0
  287. package/src/__tests__/remote-integration.test.ts +224 -0
  288. package/src/__tests__/rhi-capture-capability.test-d.ts +22 -0
  289. package/src/__tests__/rhi-capture-host.integration.test.ts +157 -0
  290. package/src/__tests__/rhi-capture.browser.test.ts +51 -0
  291. package/src/__tests__/surface-handoff.test.ts +89 -0
  292. package/src/__tests__/types-m2.test-d.ts +81 -0
  293. package/src/__tests__/visibility-remote.integration.test.ts +116 -0
  294. package/src/__tests__/worker-execution-stop.test.ts +173 -0
  295. package/src/animation-asset-lookup.ts +13 -0
  296. package/src/browser-frame-signal.ts +44 -0
  297. package/src/canvas-policy.ts +16 -0
  298. package/src/create-app.ts +1264 -0
  299. package/src/errors.ts +442 -0
  300. package/src/execution/attached-world-swap.ts +33 -0
  301. package/src/execution/bootstrap-entry.ts +122 -0
  302. package/src/execution/bootstrap-url.ts +25 -0
  303. package/src/execution/capabilities.ts +102 -0
  304. package/src/execution/control.ts +48 -0
  305. package/src/execution/engine-worker-runtime.ts +383 -0
  306. package/src/execution/engine-worker.ts +163 -0
  307. package/src/execution/host-controller.ts +460 -0
  308. package/src/execution/index.ts +44 -0
  309. package/src/execution/kernel-pool.ts +236 -0
  310. package/src/execution/kernel-worker-runtime.ts +103 -0
  311. package/src/execution/measurement.ts +39 -0
  312. package/src/execution/protocol.ts +134 -0
  313. package/src/execution/report.ts +66 -0
  314. package/src/execution/schema.ts +168 -0
  315. package/src/execution/selector.ts +98 -0
  316. package/src/execution/types.ts +163 -0
  317. package/src/fallback-camera.ts +20 -0
  318. package/src/game-context.ts +126 -0
  319. package/src/index.ts +190 -0
  320. package/src/input-map-plugin.ts +21 -0
  321. package/src/input-plugin.ts +26 -0
  322. package/src/internal/assembled-engine-profile.ts +14 -0
  323. package/src/internal/assets-world-plugin.ts +33 -0
  324. package/src/internal/browser-remote-bridge.ts +269 -0
  325. package/src/internal/browser-rhi-debug-runtime.ts +67 -0
  326. package/src/internal/component-introspection.ts +116 -0
  327. package/src/internal/debug-draw.ts +38 -0
  328. package/src/internal/ecs-import.ts +41 -0
  329. package/src/internal/engine-profile-common.ts +9 -0
  330. package/src/internal/error-fanout.ts +129 -0
  331. package/src/internal/frame-loop.ts +539 -0
  332. package/src/internal/input-attach.ts +104 -0
  333. package/src/internal/main-engine-profile.ts +84 -0
  334. package/src/internal/remote-serve-flag.ts +41 -0
  335. package/src/internal/remote-server-plugin.ts +16 -0
  336. package/src/internal/rhi-capture.ts +211 -0
  337. package/src/internal/rhi-debug-flag.ts +40 -0
  338. package/src/internal/worker-engine-profile.ts +47 -0
  339. package/src/load-game-errors.ts +211 -0
  340. package/src/load-game.ts +115 -0
  341. package/src/plugin-factories.ts +2 -0
  342. package/src/renderer-plugin.ts +96 -0
  343. package/src/tool-preview/bootstrap.ts +835 -0
  344. package/src/tool-preview/evidence.ts +59 -0
  345. package/src/tool-preview/framing.ts +52 -0
  346. package/src/tool-preview/recipe.ts +137 -0
  347. package/src/types.ts +450 -0
  348. package/src/vite-env.d.ts +10 -0
package/src/errors.ts ADDED
@@ -0,0 +1,442 @@
1
+ // @forgeax/engine-app -- AppError + closed AppErrorCode union (12 members) +
2
+ // APP_ERROR_HINTS / APP_EXPECTED + discriminated AppErrorDetail per code.
3
+ //
4
+ // Shape:
5
+ // - AppErrorCode = closed union 12 members (charter P4 closed-union
6
+ // exhaustive switch needs no default fallback; tsc strict mode guards
7
+ // completeness; AGENTS.md "Errors are structured" + AC-07).
8
+ // Members:
9
+ // - 'app-not-started'
10
+ // - 'app-already-running'
11
+ // - 'app-canvas-detached'
12
+ // - 'app-system-update-failed'
13
+ // - 'app-pointer-lock-failed'
14
+ // Plan-strategy D-3 lock: device-lost stays on RhiErrorCode (18-member
15
+ // union); the AppError surface does NOT add a seventh 'app-device-lost'
16
+ // member. Host onError listener receives RhiError({code:'device-lost'})
17
+ // verbatim through the fan-out.
18
+ //
19
+ // - AppError class = 4-field surface (.code / .expected / .hint /
20
+ // .detail) byte-for-byte parallel to RhiError (packages/rhi/src/errors.ts).
21
+ // The class extends Error so debug surfaces (stack, name) work in host
22
+ // environments; AI users walk .code / .detail by property access (charter
23
+ // P3 explicit failure: never parse the message string).
24
+ //
25
+ // - AppError is exposed as a discriminated union (variant per code) so AI
26
+ // users get .detail narrowing for free after `if (err.code === '...')`:
27
+ //
28
+ // if (err.code === 'app-canvas-detached') {
29
+ // console.warn('reattach canvas', err.detail.canvasId);
30
+ // }
31
+ //
32
+ // Constructor accepts a generic args object and infers the variant from
33
+ // the literal `code` argument; `detail` must satisfy the per-code
34
+ // payload (`AppErrorDetailFor<C>`). Callers therefore cannot supply
35
+ // non-canonical fields like `{ state: 'running' }` on the
36
+ // 'app-already-running' arm.
37
+ //
38
+ // - APP_ERROR_HINTS / APP_EXPECTED are 12-key Records keyed by AppErrorCode;
39
+ // one private policy owner supplies both projections. The focused policy-owner
40
+ // proof asserts that every code has the exact expected and hint strings.
41
+ //
42
+ // Related: requirements AC-07 / AC-04 / AC-09; plan-strategy section 2 D-3
43
+ // (6-member lock) + D-4 ('app-system-update-failed' detail = { cause,
44
+ // systemName? }) + D-6 (dual-layer instanceof + switch consumption form);
45
+ // research section 2.7 (AppErrorCode lands in AGENTS.md Error model table
46
+ // alongside RhiErrorCode et al.); charter P3 (explicit failure) + P4
47
+ // (closed-union exhaustive switch).
48
+
49
+ import type { RhiError } from '@forgeax/engine-rhi/errors';
50
+ import type { ExecutionCapabilityName, ExecutionTier } from './execution/types';
51
+
52
+ /**
53
+ * Closed AppErrorCode union (12 members).
54
+ *
55
+ * | code | trigger |
56
+ * |:--|:--|
57
+ * | `'app-not-started'` | `stop()` / `pause()` / `resume()` invoked while the rAF loop is in the terminal `'idle'` or `'stopped'` state (post-device-lost terminal sink). The handle has no live frame to interrupt; AI users either call `start()` first or accept that this handle is dead and rebuild via `createApp({...})`. |
58
+ * | `'app-already-running'` | second `start()` invocation against an already-running handle; the call is a no-op state-machine-wise (state preserved). |
59
+ * | `'app-canvas-detached'` | `createApp(canvas)` thin wrapper found `canvas.isConnected === false` at entry. Detail carries optional `canvasId` so the host can surface the offending canvas in a multi-canvas page (D-2 minor). |
60
+ * | `'app-system-update-failed'` | World update, renderer draw, or a frame-loop callback failed; the original failure value is forwarded on `detail.cause`. `detail.systemName` is optional when the call site can name the failing owner. |
61
+ * | `'app-pointer-lock-failed'` | `attachInputAuto`'s `onLockError` callback received a lock failure from the input backend. `detail.path` carries `'w3c'` (W3C `requestPointerLock` rejection) or `'provider'` (host-injected `lockProvider.requestLock` throw/reject). `detail.cause` carries the original rejection value verbatim. The host recovers by remaining in unlocked state; the next trusted click will retry the lock request. |
62
+ *
63
+ * Plan-strategy D-4 locked the count at 6; device-lost rides RhiErrorCode.
64
+ */
65
+ export type AppErrorCode = keyof typeof appErrorPolicy;
66
+
67
+ /**
68
+ * Detail variant for the `'app-canvas-detached'` arm.
69
+ *
70
+ * `canvasId` carries an optional identifier the host can surface to AI
71
+ * users when multiple canvases live on the page (e.g. preview vs live
72
+ * canvas). `undefined` when the host did not assign an id.
73
+ */
74
+ export interface AppDetailCanvasDetached {
75
+ readonly canvasId?: string | undefined;
76
+ }
77
+
78
+ /**
79
+ * Detail variant for the `'app-system-update-failed'` arm (plan-strategy D-4).
80
+ *
81
+ * `cause` carries the original thrown value verbatim so AI users can
82
+ * `cause instanceof EcsError` / `cause instanceof RhiError` narrow without
83
+ * losing structure. `systemName` is populated when the call site can name
84
+ * the offending system (input-attach cleanup path forwards
85
+ * `FRAME_START_SCAN_SYSTEM_NAME`).
86
+ */
87
+ export interface AppDetailSystemUpdateFailed {
88
+ readonly cause: unknown;
89
+ readonly systemName?: string | undefined;
90
+ }
91
+
92
+ /**
93
+ * Detail variant for the `'app-pointer-lock-failed'` arm (plan-strategy D-4).
94
+ *
95
+ * `path` discriminates between W3C `requestPointerLock` rejections (`'w3c'`)
96
+ * and host-injected `lockProvider.requestLock` throw/reject (`'provider'`).
97
+ * `cause` carries the original rejection value verbatim so AI users can
98
+ * narrow further (e.g. `cause instanceof DOMException` for W3C timeout
99
+ * rejections).
100
+ */
101
+ export interface AppDetailPointerLockFailed {
102
+ readonly path: 'w3c' | 'provider';
103
+ readonly cause: unknown;
104
+ }
105
+
106
+ /** Why a host-requested deterministic frame could not run. */
107
+ export interface AppDetailFrameStepInvalid {
108
+ readonly state: 'idle' | 'running' | 'paused' | 'stopped';
109
+ readonly deltaSeconds: number;
110
+ readonly reason: 'state' | 'delta';
111
+ }
112
+
113
+ export interface AppDetailExecutionTierUnavailable {
114
+ readonly requestedTier: ExecutionTier;
115
+ readonly missingCapabilities: readonly ExecutionCapabilityName[];
116
+ readonly sharedEvidencePassed: boolean;
117
+ }
118
+
119
+ export interface AppDetailExecutionBootstrapFailed {
120
+ readonly phase: 'import' | 'export' | 'prepare' | 'bootstrap' | 'data';
121
+ readonly moduleUrl: string;
122
+ readonly cause: unknown;
123
+ }
124
+
125
+ /** Original startup failure thrown by a Cordis plugin fiber. */
126
+ export interface AppDetailPluginActivationFailed {
127
+ readonly cause: unknown;
128
+ }
129
+
130
+ export interface AppDetailExecutionDeadlineExceeded {
131
+ readonly phase: 'startup' | 'handshake' | 'frame';
132
+ readonly timeoutMs: number;
133
+ }
134
+
135
+ export interface AppDetailExecutionKernelFailed {
136
+ readonly kernelName: string;
137
+ readonly worldIdentity: string;
138
+ readonly cause: unknown;
139
+ readonly partialWrite: true;
140
+ readonly retryable: false;
141
+ }
142
+
143
+ export interface AppDetailExecutionStaleWorld {
144
+ readonly expectedIdentity: string;
145
+ readonly receivedIdentity: string;
146
+ readonly messageKind: string;
147
+ }
148
+
149
+ export interface AppDetailExecutionRebuildFailed {
150
+ readonly worldIdentity: string | null;
151
+ readonly cause: unknown;
152
+ }
153
+
154
+ /**
155
+ * Empty-detail shape for the 2 codes that carry no payload
156
+ * (`'app-not-started'`, `'app-already-running'`).
157
+ *
158
+ * Modelled as `Readonly<Record<string, never>>` so literal builders use
159
+ * `{}` while still narrowing under tsc strict (no extra property allowed).
160
+ */
161
+ export type AppDetailEmpty = Readonly<Record<string, never>>;
162
+
163
+ /**
164
+ * Conditional resolver from `AppErrorCode` to its detail payload type.
165
+ *
166
+ * Used by the constructor signature so `new AppError({ code: 'X', ... })`
167
+ * narrows the `detail` parameter to the variant payload at compile time.
168
+ */
169
+ export type AppErrorDetailFor<C extends AppErrorCode> = C extends 'app-canvas-detached'
170
+ ? AppDetailCanvasDetached
171
+ : C extends 'app-frame-step-invalid'
172
+ ? AppDetailFrameStepInvalid
173
+ : C extends 'app-system-update-failed'
174
+ ? AppDetailSystemUpdateFailed
175
+ : C extends 'app-pointer-lock-failed'
176
+ ? AppDetailPointerLockFailed
177
+ : C extends 'app-plugin-activation-failed'
178
+ ? AppDetailPluginActivationFailed
179
+ : C extends 'app-execution-tier-unavailable'
180
+ ? AppDetailExecutionTierUnavailable
181
+ : C extends 'app-execution-bootstrap-failed'
182
+ ? AppDetailExecutionBootstrapFailed
183
+ : C extends 'app-execution-deadline-exceeded'
184
+ ? AppDetailExecutionDeadlineExceeded
185
+ : C extends 'app-execution-kernel-failed'
186
+ ? AppDetailExecutionKernelFailed
187
+ : C extends 'app-execution-stale-world'
188
+ ? AppDetailExecutionStaleWorld
189
+ : C extends 'app-execution-rebuild-failed'
190
+ ? AppDetailExecutionRebuildFailed
191
+ : AppDetailEmpty;
192
+
193
+ /**
194
+ * Tagged union of `.detail` payloads carried by structured AppError.
195
+ *
196
+ * Each variant maps to its `AppErrorCode` arm via `AppErrorDetailFor<C>`;
197
+ * the variants are unique by structural fields (`AppDetailCanvasDetached`
198
+ * has `canvasId`, `AppDetailSystemUpdateFailed` has `cause`, the empty
199
+ * shape has neither).
200
+ */
201
+ export type AppErrorDetail = AppErrorDetailFor<AppErrorCode>;
202
+
203
+ /**
204
+ * Render a one-line summary of `detail.cause` for embedding in
205
+ * `AppError.message`. `cause` is structurally typed (`unknown`) because
206
+ * upstream may throw any value; callers downstream still get the verbatim
207
+ * value via `err.detail.cause` — this helper exists so tools that only see
208
+ * `err.message` (`console.error(err)`, raw stringification) still surface
209
+ * the root cause without consumers manually expanding `detail`.
210
+ *
211
+ * Closed precedence:
212
+ * 1. Structured engine errors (RhiError / EcsError / RenderGraphError /
213
+ * AssetError / ShaderError / ...) carry `.code` + `.message`; render
214
+ * `<Name> <code>: <message>`.
215
+ * 2. Plain `Error`: render `<name>: <message>`.
216
+ * 3. Anything else: `String(cause)` (covers thrown strings, numbers, null).
217
+ *
218
+ * Newlines are replaced with `' / '` so the final AppError message stays a
219
+ * single line (callers already split on `\n` in stack traces, so a multiline
220
+ * message would corrupt their parsing).
221
+ */
222
+ function summarizeCause(cause: unknown): string {
223
+ if (cause === null || cause === undefined) return String(cause);
224
+ if (typeof cause !== 'object') return String(cause);
225
+ const r = cause as { name?: unknown; message?: unknown; code?: unknown };
226
+ const name = typeof r.name === 'string' && r.name.length > 0 ? r.name : 'Error';
227
+ const code = typeof r.code === 'string' && r.code.length > 0 ? r.code : '';
228
+ const message = typeof r.message === 'string' ? r.message : '';
229
+ const head = code !== '' ? `${name} ${code}` : name;
230
+ const body = message !== '' ? `: ${message}` : '';
231
+ return `${head}${body}`.replace(/\s*\n+\s*/g, ' / ');
232
+ }
233
+
234
+ class AppErrorClass extends Error {
235
+ readonly code: AppErrorCode;
236
+ readonly expected: string;
237
+ readonly hint: string;
238
+ readonly detail: AppErrorDetail;
239
+
240
+ constructor(args: {
241
+ code: AppErrorCode;
242
+ expected: string;
243
+ hint: string;
244
+ detail: AppErrorDetail;
245
+ }) {
246
+ // Surface `detail.cause` directly inside `.message` for the
247
+ // `'app-system-update-failed'` arm. Without this, a generic
248
+ // `console.error(err)` shows only the wrapper expected/hint and
249
+ // hides the actual EcsError / RhiError / host-system Error that
250
+ // tripped the frame loop. The verbatim `cause` value remains on
251
+ // `err.detail.cause` for two-level-narrow consumers (charter P3).
252
+ let causeSuffix = '';
253
+ if (args.code === 'app-system-update-failed') {
254
+ const d = args.detail as AppDetailSystemUpdateFailed;
255
+ const sys =
256
+ typeof d.systemName === 'string' && d.systemName.length > 0
257
+ ? ` (system=${d.systemName})`
258
+ : '';
259
+ causeSuffix = `; cause: ${summarizeCause(d.cause)}${sys}`;
260
+ } else if (args.code === 'app-pointer-lock-failed') {
261
+ const d = args.detail as AppDetailPointerLockFailed;
262
+ causeSuffix = `; path: ${d.path}; cause: ${summarizeCause(d.cause)}`;
263
+ } else if (args.code === 'app-plugin-activation-failed') {
264
+ const d = args.detail as AppDetailPluginActivationFailed;
265
+ causeSuffix = `; cause: ${summarizeCause(d.cause)}`;
266
+ } else if (args.code === 'app-execution-bootstrap-failed') {
267
+ const d = args.detail as AppDetailExecutionBootstrapFailed;
268
+ causeSuffix = `; phase: ${d.phase}; cause: ${summarizeCause(d.cause)}`;
269
+ } else if (args.code === 'app-execution-kernel-failed') {
270
+ const d = args.detail as AppDetailExecutionKernelFailed;
271
+ causeSuffix = `; kernel: ${d.kernelName}; cause: ${summarizeCause(d.cause)}`;
272
+ } else if (args.code === 'app-execution-rebuild-failed') {
273
+ const d = args.detail as AppDetailExecutionRebuildFailed;
274
+ causeSuffix = `; cause: ${summarizeCause(d.cause)}`;
275
+ }
276
+ super(`[AppError ${args.code}] expected: ${args.expected}; hint: ${args.hint}${causeSuffix}`);
277
+ this.name = 'AppError';
278
+ this.code = args.code;
279
+ this.expected = args.expected;
280
+ this.hint = args.hint;
281
+ this.detail = args.detail;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Variant intersection: an `AppErrorClass` instance whose `code` literal
287
+ * narrows to `C` and whose `detail` narrows to `AppErrorDetailFor<C>`. The
288
+ * shape lets `if (err.code === 'X')` simultaneously narrow `err.detail` to
289
+ * the per-code payload (charter P3 + AC-07 discriminated union).
290
+ */
291
+ type AppErrorVariant<C extends AppErrorCode> = AppErrorClass & {
292
+ readonly code: C;
293
+ readonly detail: AppErrorDetailFor<C>;
294
+ };
295
+
296
+ /**
297
+ * Public AppError type — discriminated union of the policy variants.
298
+ *
299
+ * AI-user form (charter P3 explicit failure):
300
+ *
301
+ * ```ts
302
+ * function recover(err: AppError): string {
303
+ * switch (err.code) {
304
+ * case 'app-not-started': return 'call start() first';
305
+ * case 'app-already-running': return 'state preserved; ignore';
306
+ * case 'app-canvas-detached': return `reattach ${err.detail.canvasId ?? 'canvas'}`;
307
+ * case 'app-system-update-failed':
308
+ * return err.detail.cause instanceof Error ? err.detail.cause.message : 'unknown';
309
+ * case 'app-pointer-lock-failed':
310
+ * return `lock failed (${err.detail.path}): ${err.detail.cause}`;
311
+ * }
312
+ * }
313
+ * ```
314
+ */
315
+ export type AppError = {
316
+ [C in AppErrorCode]: AppErrorVariant<C>;
317
+ }[AppErrorCode];
318
+
319
+ interface AppErrorConstructor {
320
+ new <C extends AppErrorCode>(args: {
321
+ code: C;
322
+ expected: string;
323
+ hint: string;
324
+ detail: AppErrorDetailFor<C>;
325
+ }): AppErrorVariant<C>;
326
+ readonly prototype: AppErrorClass;
327
+ }
328
+
329
+ /**
330
+ * AppError constructor — `new AppError({ code, expected, hint, detail })`.
331
+ *
332
+ * The generic `C` is inferred from the literal `code` argument, which
333
+ * narrows `detail` to the per-code payload (`AppErrorDetailFor<C>`) and
334
+ * narrows the return type to the corresponding `AppErrorVariant<C>` so the
335
+ * call site walks the discriminated union without manual cast.
336
+ *
337
+ * The constructor delegates to the `AppErrorClass` runtime; the typed-cast
338
+ * here is the ergonomic affordance for AI users (TS class declarations
339
+ * cannot directly express `<C> ... AppErrorVariant<C>` polymorphism).
340
+ */
341
+ export const AppError: AppErrorConstructor = AppErrorClass as unknown as AppErrorConstructor;
342
+
343
+ /**
344
+ * `expected` table — the engine-side invariant that was violated when each
345
+ * code surfaces. AI users read this as the L2 detail (charter F2 priority
346
+ * text); `.hint` carries the recovery action.
347
+ *
348
+ * 12 keys; the focused policy-owner proof locks the exact key set and values.
349
+ * locks the count and non-emptiness of every entry.
350
+ */
351
+ type AppErrorPolicy = { readonly expected: string; readonly hint: string };
352
+
353
+ const appErrorPolicy = {
354
+ 'app-not-started': {
355
+ expected:
356
+ 'state must be "running" or "paused" to accept stop/pause/resume; "idle" / "stopped" terminal sinks reject',
357
+ hint: 'check getState() before calling stop/pause/resume; rebuild the handle via createApp({...}) when the previous one terminated on device-lost',
358
+ },
359
+ 'app-already-running': {
360
+ expected:
361
+ 'state must be "idle" or "paused" to start; "running" handles ignore subsequent start() calls',
362
+ hint: 'call stop() first or audit start() call sites; the second start() is a no-op so state is preserved',
363
+ },
364
+ 'app-canvas-detached': {
365
+ expected: 'canvas.isConnected === true at createApp(canvas) entry',
366
+ hint: 'append the canvas to the document tree before calling createApp(canvas), or use the assemble entry createApp({ renderer, world }) when the host already manages canvas lifetime',
367
+ },
368
+ 'app-frame-step-invalid': {
369
+ expected:
370
+ 'stepFrame(deltaSeconds) runs only while the App is paused and deltaSeconds is finite and non-negative',
371
+ hint: 'pause the App before deterministic stepping and pass an explicit finite delta; resume after the bounded step sequence completes',
372
+ },
373
+ 'app-system-update-failed': {
374
+ expected:
375
+ 'world.update(world), renderer.draw(world), and frame-loop callbacks complete without failure',
376
+ hint: 'inspect detail.cause for the original thrown value (EcsError / RhiError / host system bug); detail.systemName names the offending system when the call site can supply it',
377
+ },
378
+ 'app-pointer-lock-failed': {
379
+ expected:
380
+ 'pointer-lock request (W3C requestPointerLock or host lockProvider.requestLock) to succeed; failure signals the browser rejected the lock or the host provider threw',
381
+ hint: 'remain in unlocked state; the next trusted click will automatically retry the lock request. inspect detail.path ("w3c" or "provider") and detail.cause to determine the root cause',
382
+ },
383
+ 'app-plugin-activation-failed': {
384
+ expected: 'every requested Cordis plugin fiber reaches a stable active or pending state',
385
+ hint: 'inspect detail.cause and the plugin fiber effects; repair the failing activation before creating the App again',
386
+ },
387
+ 'app-execution-tier-unavailable': {
388
+ expected:
389
+ 'the explicitly requested execution tier has every required observed capability and the shipped shared evidence gate',
390
+ hint: 'inspect detail.missingCapabilities and detail.sharedEvidencePassed; use tier="auto" only when an observed fallback is acceptable',
391
+ },
392
+ 'app-execution-bootstrap-failed': {
393
+ expected:
394
+ 'the bootstrap URL imports a module whose default export completes as an ExecutionBootstrapEntry in the selected Engine Realm',
395
+ hint: 'inspect detail.phase, moduleUrl and cause; export one default ExecutionBootstrapEntry that creates only realm-local engine state',
396
+ },
397
+ 'app-execution-deadline-exceeded': {
398
+ expected:
399
+ 'the execution startup, handshake or frame completes within its configured bounded deadline',
400
+ hint: 'inspect detail.phase and timeoutMs; the timed-out Worker has been terminated, so fix startup or frame work before creating a new App',
401
+ },
402
+ 'app-execution-kernel-failed': {
403
+ expected:
404
+ 'a shared kernel completes every dispatched shard without leaving a possibly partial World write',
405
+ hint: 'do not retry or draw the poisoned World; inspect detail.kernelName and cause, then call app.execution.rebuild()',
406
+ },
407
+ 'app-execution-stale-world': {
408
+ expected:
409
+ 'every execution message targets the currently active World identity before it can write',
410
+ hint: 'discard the late message and keep the current World; inspect expectedIdentity, receivedIdentity and messageKind',
411
+ },
412
+ 'app-execution-rebuild-failed': {
413
+ expected:
414
+ 'explicit rebuild disposes the poisoned World and bootstraps a fresh World identity in the surviving Engine Realm',
415
+ hint: 'inspect detail.cause; this App remains stopped, so fix the bootstrap failure or create a new App explicitly',
416
+ },
417
+ } satisfies Record<string, AppErrorPolicy>;
418
+
419
+ export const APP_EXPECTED: Readonly<Record<AppErrorCode, string>> = Object.fromEntries(
420
+ Object.entries(appErrorPolicy).map(([code, policy]) => [code, policy.expected]),
421
+ ) as Readonly<Record<AppErrorCode, string>>;
422
+
423
+ export const APP_ERROR_HINTS: Readonly<Record<AppErrorCode, string>> = Object.fromEntries(
424
+ Object.entries(appErrorPolicy).map(([code, policy]) => [code, policy.hint]),
425
+ ) as Readonly<Record<AppErrorCode, string>>;
426
+
427
+ /**
428
+ * Type guard for narrowing `CanvasAppError`-compatible mixed signals to AppError.
429
+ *
430
+ * The fan-out signature is `(err: CanvasAppError) => void`; AI users
431
+ * who want to handle only the AppError leg call `if (isAppError(err)) ...`
432
+ * before walking `.code`. Reverse-compatible with `instanceof AppError`
433
+ * (the class is exposed); the function form is provided as the
434
+ * canonical idiom in JSDoc / README so AI users do not need to reason
435
+ * about cross-realm `instanceof` quirks.
436
+ *
437
+ * The parameter accepts the complete App/RHI boundary union so callers can
438
+ * narrow the error without casts.
439
+ */
440
+ export function isAppError(err: AppError | RhiError): err is AppError {
441
+ return err instanceof AppErrorClass;
442
+ }
@@ -0,0 +1,33 @@
1
+ import type { World } from '@forgeax/engine-ecs';
2
+ import type { Renderer } from '@forgeax/engine-render';
3
+
4
+ /** Serialize realm rebuilds so candidate Worlds never overlap ownership. */
5
+ export class SerializedRebuildQueue {
6
+ private tail: Promise<void> = Promise.resolve();
7
+
8
+ enqueue(action: () => Promise<void>): Promise<void> {
9
+ const current = this.tail.then(action, action);
10
+ this.tail = current.catch(() => undefined);
11
+ return current;
12
+ }
13
+ }
14
+
15
+ /** Transactionally replace one Renderer-attached World. */
16
+ export async function commitAttachedWorld(
17
+ renderer: Pick<Renderer, 'attach'>,
18
+ nextWorld: World,
19
+ initializeCandidate: () => Promise<boolean>,
20
+ ): Promise<boolean> {
21
+ const attached = renderer.attach(nextWorld);
22
+ if (!attached.ok) throw attached.error;
23
+ try {
24
+ if (!(await initializeCandidate())) {
25
+ attached.value.dispose();
26
+ return false;
27
+ }
28
+ } catch (cause) {
29
+ attached.value.dispose();
30
+ throw cause;
31
+ }
32
+ return true;
33
+ }
@@ -0,0 +1,122 @@
1
+ import type { Plugin } from '@forgeax/engine-plugin';
2
+ import type { RenderFeature } from '@forgeax/engine-render';
3
+ import { err, ok, type Result } from '@forgeax/engine-types';
4
+ import { APP_ERROR_HINTS, APP_EXPECTED, AppError, type AppError as AppErrorType } from '../errors';
5
+ import type { ExecutionBootstrapValue } from './types';
6
+
7
+ /** Realm-local engine assembly returned by an execution bootstrap module. */
8
+ export interface PreparedExecutionBootstrap {
9
+ /** Render features constructed in the realm that will own the Renderer. */
10
+ readonly features?: readonly RenderFeature<unknown>[];
11
+ /** Plugins constructed in the realm that will own the World. */
12
+ readonly plugins?: readonly Plugin[];
13
+ }
14
+
15
+ /** Realm-local Host bridge available to execution bootstrap plugins. */
16
+ export interface ExecutionBootstrapHost {
17
+ readonly port?: MessagePort;
18
+ setPointerLockAllowed(allowed: boolean): void;
19
+ }
20
+
21
+ declare module '@forgeax/engine-plugin' {
22
+ interface EngineContextServices {
23
+ executionBootstrapHost: ExecutionBootstrapHost;
24
+ }
25
+ }
26
+
27
+ /** Bind the optional Host transport to the same Fiber that owns bootstrap plugins. */
28
+ export function executionBootstrapHostPlugin(host: ExecutionBootstrapHost): Plugin {
29
+ return {
30
+ name: 'execution-bootstrap-host',
31
+ provide: 'executionBootstrapHost',
32
+ apply(ctx) {
33
+ ctx.provide('executionBootstrapHost', host);
34
+ if (host.port !== undefined) {
35
+ ctx.effect(() => () => host.port?.close(), 'execution/bootstrap-port');
36
+ }
37
+ },
38
+ };
39
+ }
40
+
41
+ /** Default export contract for `ExecutionOptions.bootstrap`. */
42
+ export type ExecutionBootstrapEntry = (
43
+ data: ExecutionBootstrapValue | undefined,
44
+ ) => PreparedExecutionBootstrap | Promise<PreparedExecutionBootstrap>;
45
+
46
+ function bootstrapError(
47
+ phase: 'import' | 'export' | 'prepare' | 'bootstrap' | 'data',
48
+ moduleUrl: string,
49
+ cause: unknown,
50
+ ): AppErrorType {
51
+ return new AppError({
52
+ code: 'app-execution-bootstrap-failed',
53
+ expected: APP_EXPECTED['app-execution-bootstrap-failed'],
54
+ hint: APP_ERROR_HINTS['app-execution-bootstrap-failed'],
55
+ detail: { phase, moduleUrl, cause },
56
+ });
57
+ }
58
+
59
+ export function validateExecutionBootstrapData(
60
+ data: ExecutionBootstrapValue | undefined,
61
+ moduleUrl: string,
62
+ ): Result<void, AppErrorType> {
63
+ if (data === undefined) return ok(undefined);
64
+ try {
65
+ structuredClone(data);
66
+ return ok(undefined);
67
+ } catch (cause) {
68
+ return err(bootstrapError('data', moduleUrl, cause));
69
+ }
70
+ }
71
+
72
+ export async function loadBootstrapEntry(
73
+ moduleUrl: string,
74
+ ): Promise<Result<ExecutionBootstrapEntry, AppErrorType>> {
75
+ let loaded: unknown;
76
+ try {
77
+ loaded = await import(/* @vite-ignore */ moduleUrl);
78
+ } catch (cause) {
79
+ return err(bootstrapError('import', moduleUrl, cause));
80
+ }
81
+ const entry = (loaded as { default?: unknown }).default;
82
+ if (typeof entry !== 'function') {
83
+ return err(
84
+ bootstrapError(
85
+ 'export',
86
+ moduleUrl,
87
+ new TypeError('default export is not an ExecutionBootstrapEntry function'),
88
+ ),
89
+ );
90
+ }
91
+ return ok(entry as ExecutionBootstrapEntry);
92
+ }
93
+
94
+ export async function prepareBootstrapEntry(
95
+ moduleUrl: string,
96
+ data: ExecutionBootstrapValue | undefined,
97
+ ): Promise<Result<PreparedExecutionBootstrap, AppErrorType>> {
98
+ const valid = validateExecutionBootstrapData(data, moduleUrl);
99
+ if (!valid.ok) return valid;
100
+ const loaded = await loadBootstrapEntry(moduleUrl);
101
+ if (!loaded.ok) return loaded;
102
+ try {
103
+ const prepared = await loaded.value(data);
104
+ if (
105
+ typeof prepared !== 'object' ||
106
+ prepared === null ||
107
+ (prepared.features !== undefined && !Array.isArray(prepared.features)) ||
108
+ (prepared.plugins !== undefined && !Array.isArray(prepared.plugins))
109
+ ) {
110
+ return err(
111
+ bootstrapError(
112
+ 'prepare',
113
+ moduleUrl,
114
+ new TypeError('execution bootstrap must return an object with feature and plugin arrays'),
115
+ ),
116
+ );
117
+ }
118
+ return ok(prepared);
119
+ } catch (cause) {
120
+ return err(bootstrapError('prepare', moduleUrl, cause));
121
+ }
122
+ }
@@ -0,0 +1,25 @@
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
+
4
+ /** Normalize a bootstrap module before any realm probing or worker startup. */
5
+ export function normalizeExecutionBootstrapUrl(
6
+ bootstrap: string | URL,
7
+ ): Result<string, AppErrorType> {
8
+ const moduleUrl = typeof bootstrap === 'string' ? bootstrap : bootstrap.href;
9
+ try {
10
+ return ok(new URL(bootstrap, globalThis.location?.href).href);
11
+ } catch (cause) {
12
+ return err(
13
+ new AppError({
14
+ code: 'app-execution-bootstrap-failed',
15
+ expected: APP_EXPECTED['app-execution-bootstrap-failed'],
16
+ hint: APP_ERROR_HINTS['app-execution-bootstrap-failed'],
17
+ detail: {
18
+ phase: 'prepare',
19
+ moduleUrl,
20
+ cause: cause instanceof TypeError ? cause : new TypeError(String(cause)),
21
+ },
22
+ }),
23
+ );
24
+ }
25
+ }