@forgeax/engine-app 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +418 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/animation-asset-lookup.unit.test.d.ts +2 -0
  5. package/dist/__tests__/animation-asset-lookup.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/app-rebuild.test.d.ts +2 -0
  7. package/dist/__tests__/app-rebuild.test.d.ts.map +1 -0
  8. package/dist/__tests__/asset-registry-types.test-d.d.ts +2 -0
  9. package/dist/__tests__/asset-registry-types.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/assets-runtime-assembly.integration.test.d.ts +2 -0
  11. package/dist/__tests__/assets-runtime-assembly.integration.test.d.ts.map +1 -0
  12. package/dist/__tests__/callback-deletion.unit.test.d.ts +2 -0
  13. package/dist/__tests__/callback-deletion.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/callback-inference.test-d.d.ts +2 -0
  15. package/dist/__tests__/callback-inference.test-d.d.ts.map +1 -0
  16. package/dist/__tests__/component-introspection.unit.test.d.ts +2 -0
  17. package/dist/__tests__/component-introspection.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/consumer-migration.unit.test.d.ts +2 -0
  19. package/dist/__tests__/consumer-migration.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/create-app-features.test.d.ts +2 -0
  21. package/dist/__tests__/create-app-features.test.d.ts.map +1 -0
  22. package/dist/__tests__/create-app-listener-sync.test.d.ts +2 -0
  23. package/dist/__tests__/create-app-listener-sync.test.d.ts.map +1 -0
  24. package/dist/__tests__/create-app-lock-provider.test.d.ts +2 -0
  25. package/dist/__tests__/create-app-lock-provider.test.d.ts.map +1 -0
  26. package/dist/__tests__/create-app-plugin.test.d.ts +2 -0
  27. package/dist/__tests__/create-app-plugin.test.d.ts.map +1 -0
  28. package/dist/__tests__/create-app-stop.test.d.ts +2 -0
  29. package/dist/__tests__/create-app-stop.test.d.ts.map +1 -0
  30. package/dist/__tests__/create-app-time.unit.test.d.ts +2 -0
  31. package/dist/__tests__/create-app-time.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/create-app.test-d.d.ts +2 -0
  33. package/dist/__tests__/create-app.test-d.d.ts.map +1 -0
  34. package/dist/__tests__/create-app.test.d.ts +2 -0
  35. package/dist/__tests__/create-app.test.d.ts.map +1 -0
  36. package/dist/__tests__/create-renderer-error.integration.test.d.ts +2 -0
  37. package/dist/__tests__/create-renderer-error.integration.test.d.ts.map +1 -0
  38. package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts +2 -0
  39. package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts.map +1 -0
  40. package/dist/__tests__/docs-gate-green.unit.test.d.ts +2 -0
  41. package/dist/__tests__/docs-gate-green.unit.test.d.ts.map +1 -0
  42. package/dist/__tests__/draw-source-headless-full-chain.test.d.ts +2 -0
  43. package/dist/__tests__/draw-source-headless-full-chain.test.d.ts.map +1 -0
  44. package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts +2 -0
  45. package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts.map +1 -0
  46. package/dist/__tests__/draw-source-single-world-regression.test.d.ts +2 -0
  47. package/dist/__tests__/draw-source-single-world-regression.test.d.ts.map +1 -0
  48. package/dist/__tests__/ecs-import.unit.test.d.ts +2 -0
  49. package/dist/__tests__/ecs-import.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts +2 -0
  51. package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts +2 -0
  53. package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/errors-pointer-lock-failed.test.d.ts +2 -0
  55. package/dist/__tests__/errors-pointer-lock-failed.test.d.ts.map +1 -0
  56. package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts +2 -0
  57. package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/execution-error-detail.test-d.d.ts +2 -0
  59. package/dist/__tests__/execution-error-detail.test-d.d.ts.map +1 -0
  60. package/dist/__tests__/execution-error-detail.unit.test.d.ts +2 -0
  61. package/dist/__tests__/execution-error-detail.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/execution-frame-order.unit.test.d.ts +2 -0
  63. package/dist/__tests__/execution-frame-order.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/execution-frame-protocol.unit.test.d.ts +2 -0
  65. package/dist/__tests__/execution-frame-protocol.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/execution-input-sample.contract.test.d.ts +2 -0
  67. package/dist/__tests__/execution-input-sample.contract.test.d.ts.map +1 -0
  68. package/dist/__tests__/execution-measurement.unit.test.d.ts +2 -0
  69. package/dist/__tests__/execution-measurement.unit.test.d.ts.map +1 -0
  70. package/dist/__tests__/execution-public-api.test-d.d.ts +2 -0
  71. package/dist/__tests__/execution-public-api.test-d.d.ts.map +1 -0
  72. package/dist/__tests__/execution-public-api.unit.test.d.ts +2 -0
  73. package/dist/__tests__/execution-public-api.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/execution-rebuild.contract.test.d.ts +2 -0
  75. package/dist/__tests__/execution-rebuild.contract.test.d.ts.map +1 -0
  76. package/dist/__tests__/execution-report.contract.test.d.ts +2 -0
  77. package/dist/__tests__/execution-report.contract.test.d.ts.map +1 -0
  78. package/dist/__tests__/execution-report.types.test-d.d.ts +2 -0
  79. package/dist/__tests__/execution-report.types.test-d.d.ts.map +1 -0
  80. package/dist/__tests__/execution-schema-freshness.unit.test.d.ts +2 -0
  81. package/dist/__tests__/execution-schema-freshness.unit.test.d.ts.map +1 -0
  82. package/dist/__tests__/execution-selector.unit.test.d.ts +2 -0
  83. package/dist/__tests__/execution-selector.unit.test.d.ts.map +1 -0
  84. package/dist/__tests__/execution-shared-kernel.unit.test.d.ts +2 -0
  85. package/dist/__tests__/execution-shared-kernel.unit.test.d.ts.map +1 -0
  86. package/dist/__tests__/execution-world-health-owner.test-d.d.ts +2 -0
  87. package/dist/__tests__/execution-world-health-owner.test-d.d.ts.map +1 -0
  88. package/dist/__tests__/fallback-camera.unit.test.d.ts +2 -0
  89. package/dist/__tests__/fallback-camera.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/frame-loop-time-elapsed.test.d.ts +2 -0
  91. package/dist/__tests__/frame-loop-time-elapsed.test.d.ts.map +1 -0
  92. package/dist/__tests__/frame-loop-time.unit.test.d.ts +2 -0
  93. package/dist/__tests__/frame-loop-time.unit.test.d.ts.map +1 -0
  94. package/dist/__tests__/frame-loop-world-array.test.d.ts +2 -0
  95. package/dist/__tests__/frame-loop-world-array.test.d.ts.map +1 -0
  96. package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts +2 -0
  97. package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/migration-surface-inventory.unit.test.d.ts +2 -0
  99. package/dist/__tests__/migration-surface-inventory.unit.test.d.ts.map +1 -0
  100. package/dist/__tests__/plugin-policy.integration.test.d.ts +2 -0
  101. package/dist/__tests__/plugin-policy.integration.test.d.ts.map +1 -0
  102. package/dist/__tests__/plugin-profiles.unit.test.d.ts +2 -0
  103. package/dist/__tests__/plugin-profiles.unit.test.d.ts.map +1 -0
  104. package/dist/__tests__/profiler-default-off.test.d.ts +2 -0
  105. package/dist/__tests__/profiler-default-off.test.d.ts.map +1 -0
  106. package/dist/__tests__/profiler-frame-token.test.d.ts +2 -0
  107. package/dist/__tests__/profiler-frame-token.test.d.ts.map +1 -0
  108. package/dist/__tests__/profiler-phase-catalog.test.d.ts +2 -0
  109. package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +1 -0
  110. package/dist/__tests__/profiler-phase-integration.test.d.ts +2 -0
  111. package/dist/__tests__/profiler-phase-integration.test.d.ts.map +1 -0
  112. package/dist/__tests__/profiler-remote-bridge.test.d.ts +2 -0
  113. package/dist/__tests__/profiler-remote-bridge.test.d.ts.map +1 -0
  114. package/dist/__tests__/remote-integration.test.d.ts +2 -0
  115. package/dist/__tests__/remote-integration.test.d.ts.map +1 -0
  116. package/dist/__tests__/remote-serve-flag.test.d.ts +2 -0
  117. package/dist/__tests__/remote-serve-flag.test.d.ts.map +1 -0
  118. package/dist/__tests__/rhi-capture-capability.test-d.d.ts +2 -0
  119. package/dist/__tests__/rhi-capture-capability.test-d.d.ts.map +1 -0
  120. package/dist/__tests__/rhi-capture-host.integration.test.d.ts +2 -0
  121. package/dist/__tests__/rhi-capture-host.integration.test.d.ts.map +1 -0
  122. package/dist/__tests__/rhi-capture.browser.test.d.ts +2 -0
  123. package/dist/__tests__/rhi-capture.browser.test.d.ts.map +1 -0
  124. package/dist/__tests__/surface-handoff.test.d.ts +2 -0
  125. package/dist/__tests__/surface-handoff.test.d.ts.map +1 -0
  126. package/dist/__tests__/types-m2.test-d.d.ts +2 -0
  127. package/dist/__tests__/types-m2.test-d.d.ts.map +1 -0
  128. package/dist/__tests__/visibility-remote.integration.test.d.ts +2 -0
  129. package/dist/__tests__/visibility-remote.integration.test.d.ts.map +1 -0
  130. package/dist/__tests__/worker-execution-stop.test.d.ts +2 -0
  131. package/dist/__tests__/worker-execution-stop.test.d.ts.map +1 -0
  132. package/dist/animation-asset-lookup.d.ts +8 -0
  133. package/dist/animation-asset-lookup.d.ts.map +1 -0
  134. package/dist/assets-runtime-assembly.d.ts +19 -0
  135. package/dist/assets-runtime-assembly.d.ts.map +1 -0
  136. package/dist/canvas-policy.d.ts +4 -0
  137. package/dist/canvas-policy.d.ts.map +1 -0
  138. package/dist/create-app.d.ts +57 -0
  139. package/dist/create-app.d.ts.map +1 -0
  140. package/dist/engine-worker-runtime.mjs +884 -0
  141. package/dist/engine-worker-runtime.mjs.map +1 -0
  142. package/dist/errors.d.ts +263 -0
  143. package/dist/errors.d.ts.map +1 -0
  144. package/dist/execution/attached-world-swap.d.ts +21 -0
  145. package/dist/execution/attached-world-swap.d.ts.map +1 -0
  146. package/dist/execution/bootstrap-entry.d.ts +30 -0
  147. package/dist/execution/bootstrap-entry.d.ts.map +1 -0
  148. package/dist/execution/capabilities.d.ts +5 -0
  149. package/dist/execution/capabilities.d.ts.map +1 -0
  150. package/dist/execution/control.d.ts +14 -0
  151. package/dist/execution/control.d.ts.map +1 -0
  152. package/dist/execution/engine-worker-runtime.d.ts +2 -0
  153. package/dist/execution/engine-worker-runtime.d.ts.map +1 -0
  154. package/dist/execution/engine-worker.d.ts +23 -0
  155. package/dist/execution/engine-worker.d.ts.map +1 -0
  156. package/dist/execution/host-controller.d.ts +16 -0
  157. package/dist/execution/host-controller.d.ts.map +1 -0
  158. package/dist/execution/index.d.ts +10 -0
  159. package/dist/execution/index.d.ts.map +1 -0
  160. package/dist/execution/kernel-pool.d.ts +14 -0
  161. package/dist/execution/kernel-pool.d.ts.map +1 -0
  162. package/dist/execution/kernel-worker-runtime.d.ts +2 -0
  163. package/dist/execution/kernel-worker-runtime.d.ts.map +1 -0
  164. package/dist/execution/measurement.d.ts +7 -0
  165. package/dist/execution/measurement.d.ts.map +1 -0
  166. package/dist/execution/protocol.d.ts +84 -0
  167. package/dist/execution/protocol.d.ts.map +1 -0
  168. package/dist/execution/report.d.ts +5 -0
  169. package/dist/execution/report.d.ts.map +1 -0
  170. package/dist/execution/schema.d.ts +4 -0
  171. package/dist/execution/schema.d.ts.map +1 -0
  172. package/dist/execution/selector.d.ts +10 -0
  173. package/dist/execution/selector.d.ts.map +1 -0
  174. package/dist/execution/types.d.ts +107 -0
  175. package/dist/execution/types.d.ts.map +1 -0
  176. package/dist/fallback-camera.d.ts +8 -0
  177. package/dist/fallback-camera.d.ts.map +1 -0
  178. package/dist/game-context.d.ts +98 -0
  179. package/dist/game-context.d.ts.map +1 -0
  180. package/dist/index.d.ts +24 -0
  181. package/dist/index.d.ts.map +1 -0
  182. package/dist/index.mjs +3985 -0
  183. package/dist/index.mjs.map +1 -0
  184. package/dist/input-map-plugin.d.ts +5 -0
  185. package/dist/input-map-plugin.d.ts.map +1 -0
  186. package/dist/input-plugin.d.ts +4 -0
  187. package/dist/input-plugin.d.ts.map +1 -0
  188. package/dist/internal/assembled-engine-profile.d.ts +5 -0
  189. package/dist/internal/assembled-engine-profile.d.ts.map +1 -0
  190. package/dist/internal/assets-world-plugin.d.ts +21 -0
  191. package/dist/internal/assets-world-plugin.d.ts.map +1 -0
  192. package/dist/internal/browser-remote-bridge.d.ts +25 -0
  193. package/dist/internal/browser-remote-bridge.d.ts.map +1 -0
  194. package/dist/internal/browser-rhi-debug-runtime.d.ts +18 -0
  195. package/dist/internal/browser-rhi-debug-runtime.d.ts.map +1 -0
  196. package/dist/internal/component-introspection.d.ts +24 -0
  197. package/dist/internal/component-introspection.d.ts.map +1 -0
  198. package/dist/internal/ecs-import.d.ts +6 -0
  199. package/dist/internal/ecs-import.d.ts.map +1 -0
  200. package/dist/internal/engine-profile-common.d.ts +9 -0
  201. package/dist/internal/engine-profile-common.d.ts.map +1 -0
  202. package/dist/internal/error-fanout.d.ts +66 -0
  203. package/dist/internal/error-fanout.d.ts.map +1 -0
  204. package/dist/internal/frame-loop.d.ts +35 -0
  205. package/dist/internal/frame-loop.d.ts.map +1 -0
  206. package/dist/internal/input-attach.d.ts +44 -0
  207. package/dist/internal/input-attach.d.ts.map +1 -0
  208. package/dist/internal/main-engine-profile.d.ts +15 -0
  209. package/dist/internal/main-engine-profile.d.ts.map +1 -0
  210. package/dist/internal/remote-serve-flag.d.ts +23 -0
  211. package/dist/internal/remote-serve-flag.d.ts.map +1 -0
  212. package/dist/internal/remote-server-plugin.d.ts +8 -0
  213. package/dist/internal/remote-server-plugin.d.ts.map +1 -0
  214. package/dist/internal/rhi-capture.d.ts +19 -0
  215. package/dist/internal/rhi-capture.d.ts.map +1 -0
  216. package/dist/internal/rhi-debug-flag.d.ts +18 -0
  217. package/dist/internal/rhi-debug-flag.d.ts.map +1 -0
  218. package/dist/internal/worker-engine-profile.d.ts +13 -0
  219. package/dist/internal/worker-engine-profile.d.ts.map +1 -0
  220. package/dist/kernel-worker-runtime.mjs +69 -0
  221. package/dist/kernel-worker-runtime.mjs.map +1 -0
  222. package/dist/load-game-errors.d.ts +131 -0
  223. package/dist/load-game-errors.d.ts.map +1 -0
  224. package/dist/load-game.d.ts +34 -0
  225. package/dist/load-game.d.ts.map +1 -0
  226. package/dist/plugin-factories.d.ts +3 -0
  227. package/dist/plugin-factories.d.ts.map +1 -0
  228. package/dist/tool-preview/bootstrap.d.ts +113 -0
  229. package/dist/tool-preview/bootstrap.d.ts.map +1 -0
  230. package/dist/tool-preview/evidence.d.ts +20 -0
  231. package/dist/tool-preview/evidence.d.ts.map +1 -0
  232. package/dist/tool-preview/framing.d.ts +13 -0
  233. package/dist/tool-preview/framing.d.ts.map +1 -0
  234. package/dist/tool-preview/recipe.d.ts +54 -0
  235. package/dist/tool-preview/recipe.d.ts.map +1 -0
  236. package/dist/types.d.ts +444 -0
  237. package/dist/types.d.ts.map +1 -0
  238. package/package.json +86 -0
  239. package/schema/execution-report.schema.json +168 -0
  240. package/src/__tests__/animation-asset-lookup.unit.test.ts +20 -0
  241. package/src/__tests__/app-rebuild.test.ts +31 -0
  242. package/src/__tests__/asset-registry-types.test-d.ts +16 -0
  243. package/src/__tests__/assets-runtime-assembly.integration.test.ts +68 -0
  244. package/src/__tests__/callback-deletion.unit.test.ts +14 -0
  245. package/src/__tests__/callback-inference.test-d.ts +32 -0
  246. package/src/__tests__/component-introspection.unit.test.ts +56 -0
  247. package/src/__tests__/consumer-migration.unit.test.ts +21 -0
  248. package/src/__tests__/create-app-features.test.ts +40 -0
  249. package/src/__tests__/create-app-listener-sync.test.ts +377 -0
  250. package/src/__tests__/create-app-lock-provider.test.ts +212 -0
  251. package/src/__tests__/create-app-plugin.test.ts +217 -0
  252. package/src/__tests__/create-app-stop.test.ts +617 -0
  253. package/src/__tests__/create-app-time.unit.test.ts +26 -0
  254. package/src/__tests__/create-app.test-d.ts +82 -0
  255. package/src/__tests__/create-app.test.ts +346 -0
  256. package/src/__tests__/create-renderer-error.integration.test.ts +24 -0
  257. package/src/__tests__/create-renderer-lifecycle.integration.test.ts +19 -0
  258. package/src/__tests__/docs-gate-green.unit.test.ts +61 -0
  259. package/src/__tests__/draw-source-headless-full-chain.test.ts +226 -0
  260. package/src/__tests__/draw-source-injected-world-update-visibility.test.ts +182 -0
  261. package/src/__tests__/draw-source-single-world-regression.test.ts +185 -0
  262. package/src/__tests__/ecs-import.unit.test.ts +40 -0
  263. package/src/__tests__/engine-worker-bootstrap-channel.unit.test.ts +74 -0
  264. package/src/__tests__/engine-worker-world-swap.unit.test.ts +76 -0
  265. package/src/__tests__/errors-pointer-lock-failed.test.ts +268 -0
  266. package/src/__tests__/execution-bootstrap-isolation.unit.test.ts +80 -0
  267. package/src/__tests__/execution-error-detail.test-d.ts +13 -0
  268. package/src/__tests__/execution-error-detail.unit.test.ts +35 -0
  269. package/src/__tests__/execution-frame-order.unit.test.ts +38 -0
  270. package/src/__tests__/execution-frame-protocol.unit.test.ts +61 -0
  271. package/src/__tests__/execution-input-sample.contract.test.ts +23 -0
  272. package/src/__tests__/execution-measurement.unit.test.ts +20 -0
  273. package/src/__tests__/execution-public-api.test-d.ts +10 -0
  274. package/src/__tests__/execution-public-api.unit.test.ts +9 -0
  275. package/src/__tests__/execution-rebuild.contract.test.ts +36 -0
  276. package/src/__tests__/execution-report.contract.test.ts +50 -0
  277. package/src/__tests__/execution-report.types.test-d.ts +16 -0
  278. package/src/__tests__/execution-schema-freshness.unit.test.ts +20 -0
  279. package/src/__tests__/execution-selector.unit.test.ts +72 -0
  280. package/src/__tests__/execution-shared-kernel.unit.test.ts +180 -0
  281. package/src/__tests__/execution-world-health-owner.test-d.ts +9 -0
  282. package/src/__tests__/fallback-camera.unit.test.ts +32 -0
  283. package/src/__tests__/frame-loop-time-elapsed.test.ts +115 -0
  284. package/src/__tests__/frame-loop-time.unit.test.ts +61 -0
  285. package/src/__tests__/frame-loop-world-array.test.ts +441 -0
  286. package/src/__tests__/kernel-worker-runtime.unit.test.ts +50 -0
  287. package/src/__tests__/migration-surface-inventory.unit.test.ts +13 -0
  288. package/src/__tests__/plugin-policy.integration.test.ts +15 -0
  289. package/src/__tests__/plugin-profiles.unit.test.ts +190 -0
  290. package/src/__tests__/profiler-default-off.test.ts +53 -0
  291. package/src/__tests__/profiler-frame-token.test.ts +248 -0
  292. package/src/__tests__/profiler-phase-catalog.test.ts +30 -0
  293. package/src/__tests__/profiler-phase-integration.test.ts +129 -0
  294. package/src/__tests__/profiler-remote-bridge.test.ts +31 -0
  295. package/src/__tests__/remote-integration.test.ts +224 -0
  296. package/src/__tests__/remote-serve-flag.test.ts +24 -0
  297. package/src/__tests__/rhi-capture-capability.test-d.ts +22 -0
  298. package/src/__tests__/rhi-capture-host.integration.test.ts +157 -0
  299. package/src/__tests__/rhi-capture.browser.test.ts +51 -0
  300. package/src/__tests__/surface-handoff.test.ts +92 -0
  301. package/src/__tests__/types-m2.test-d.ts +81 -0
  302. package/src/__tests__/visibility-remote.integration.test.ts +119 -0
  303. package/src/__tests__/worker-execution-stop.test.ts +116 -0
  304. package/src/animation-asset-lookup.ts +13 -0
  305. package/src/assets-runtime-assembly.ts +70 -0
  306. package/src/canvas-policy.ts +16 -0
  307. package/src/create-app.ts +1351 -0
  308. package/src/errors.ts +454 -0
  309. package/src/execution/attached-world-swap.ts +67 -0
  310. package/src/execution/bootstrap-entry.ts +122 -0
  311. package/src/execution/capabilities.ts +102 -0
  312. package/src/execution/control.ts +52 -0
  313. package/src/execution/engine-worker-runtime.ts +400 -0
  314. package/src/execution/engine-worker.ts +161 -0
  315. package/src/execution/host-controller.ts +454 -0
  316. package/src/execution/index.ts +43 -0
  317. package/src/execution/kernel-pool.ts +236 -0
  318. package/src/execution/kernel-worker-runtime.ts +103 -0
  319. package/src/execution/measurement.ts +39 -0
  320. package/src/execution/protocol.ts +131 -0
  321. package/src/execution/report.ts +66 -0
  322. package/src/execution/schema.ts +168 -0
  323. package/src/execution/selector.ts +98 -0
  324. package/src/execution/types.ts +146 -0
  325. package/src/fallback-camera.ts +20 -0
  326. package/src/game-context.ts +126 -0
  327. package/src/index.ts +176 -0
  328. package/src/input-map-plugin.ts +21 -0
  329. package/src/input-plugin.ts +26 -0
  330. package/src/internal/assembled-engine-profile.ts +22 -0
  331. package/src/internal/assets-world-plugin.ts +79 -0
  332. package/src/internal/browser-remote-bridge.ts +267 -0
  333. package/src/internal/browser-rhi-debug-runtime.ts +67 -0
  334. package/src/internal/component-introspection.ts +116 -0
  335. package/src/internal/ecs-import.ts +41 -0
  336. package/src/internal/engine-profile-common.ts +10 -0
  337. package/src/internal/error-fanout.ts +129 -0
  338. package/src/internal/frame-loop.ts +525 -0
  339. package/src/internal/input-attach.ts +104 -0
  340. package/src/internal/main-engine-profile.ts +80 -0
  341. package/src/internal/remote-serve-flag.ts +50 -0
  342. package/src/internal/remote-server-plugin.ts +16 -0
  343. package/src/internal/rhi-capture.ts +211 -0
  344. package/src/internal/rhi-debug-flag.ts +40 -0
  345. package/src/internal/worker-engine-profile.ts +47 -0
  346. package/src/load-game-errors.ts +211 -0
  347. package/src/load-game.ts +115 -0
  348. package/src/plugin-factories.ts +2 -0
  349. package/src/tool-preview/bootstrap.ts +848 -0
  350. package/src/tool-preview/evidence.ts +59 -0
  351. package/src/tool-preview/framing.ts +52 -0
  352. package/src/tool-preview/recipe.ts +137 -0
  353. package/src/types.ts +505 -0
  354. package/src/vite-env.d.ts +12 -0
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { startEngineWorker } from '../execution/engine-worker';
3
+ import type { EngineToHostMessage, HostToEngineMessage } from '../execution/protocol';
4
+
5
+ class FakeWorker {
6
+ onmessage: ((event: MessageEvent<EngineToHostMessage>) => void) | null = null;
7
+ onerror: ((event: ErrorEvent) => void) | null = null;
8
+ readonly posts: Array<{
9
+ readonly message: HostToEngineMessage;
10
+ readonly transfer: readonly Transferable[];
11
+ }> = [];
12
+ readonly terminate = vi.fn();
13
+
14
+ postMessage(message: HostToEngineMessage, transfer: Transferable[] = []): void {
15
+ this.posts.push({ message, transfer });
16
+ if (message.kind !== 'init') return;
17
+ queueMicrotask(() => {
18
+ this.onmessage?.({
19
+ data: {
20
+ kind: 'ready',
21
+ worldIdentity: 'world-test',
22
+ realm: 'worker',
23
+ workerWebGpu: true,
24
+ },
25
+ } as MessageEvent<EngineToHostMessage>);
26
+ });
27
+ }
28
+ }
29
+
30
+ describe('Engine Worker thick bootstrap channel', () => {
31
+ it('transfers bootstrap data and the realm MessagePort with the canvas', async () => {
32
+ const worker = new FakeWorker();
33
+ const offscreen = {} as OffscreenCanvas;
34
+ const transferControlToOffscreen = vi.fn(() => offscreen);
35
+ const channel = new MessageChannel();
36
+ const started = await startEngineWorker({
37
+ canvas: { transferControlToOffscreen } as unknown as HTMLCanvasElement,
38
+ bootstrapUrl: 'https://example.test/bootstrap.js',
39
+ bootstrapData: { gameId: 'sample' },
40
+ bootstrapPort: channel.port2,
41
+ timeoutMs: 100,
42
+ tier: 'engine-worker',
43
+ workerFactory: () => worker as unknown as Worker,
44
+ });
45
+
46
+ expect(started.ok).toBe(true);
47
+ expect(transferControlToOffscreen).toHaveBeenCalledOnce();
48
+ expect(worker.posts[0]?.message).toMatchObject({
49
+ kind: 'init',
50
+ bootstrapData: { gameId: 'sample' },
51
+ bootstrapPort: channel.port2,
52
+ });
53
+ expect(worker.posts[0]?.transfer).toEqual([offscreen, channel.port2]);
54
+ started.ok && started.value.dispose();
55
+ channel.port1.close();
56
+ });
57
+
58
+ it('rejects non-cloneable data before Worker creation or canvas transfer', async () => {
59
+ const workerFactory = vi.fn();
60
+ const transferControlToOffscreen = vi.fn();
61
+ const started = await startEngineWorker({
62
+ canvas: { transferControlToOffscreen } as unknown as HTMLCanvasElement,
63
+ bootstrapUrl: 'https://example.test/bootstrap.js',
64
+ bootstrapData: { callback: (() => {}) as never },
65
+ timeoutMs: 100,
66
+ tier: 'engine-worker',
67
+ workerFactory,
68
+ });
69
+
70
+ expect(started.ok).toBe(false);
71
+ expect(workerFactory).not.toHaveBeenCalled();
72
+ expect(transferControlToOffscreen).not.toHaveBeenCalled();
73
+ });
74
+ });
@@ -0,0 +1,76 @@
1
+ import { World } from '@forgeax/engine-ecs';
2
+ import type { Renderer } from '@forgeax/engine-render';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ import { commitAttachedWorld, SerializedRebuildQueue } from '../execution/attached-world-swap';
5
+
6
+ function rendererOwner(): Pick<Renderer, 'attachWorld' | 'detachWorld'> {
7
+ return {
8
+ attachWorld: vi.fn(() => ({ ok: true as const, value: undefined })),
9
+ detachWorld: vi.fn(),
10
+ };
11
+ }
12
+
13
+ describe('worker attached World swap', () => {
14
+ it('serializes concurrent rebuild requests', async () => {
15
+ const queue = new SerializedRebuildQueue();
16
+ const trace: string[] = [];
17
+ let releaseFirst: (() => void) | undefined;
18
+ const first = queue.enqueue(async () => {
19
+ trace.push('first:start');
20
+ await new Promise<void>((resolve) => {
21
+ releaseFirst = resolve;
22
+ });
23
+ trace.push('first:end');
24
+ });
25
+ const second = queue.enqueue(async () => {
26
+ trace.push('second:start');
27
+ trace.push('second:end');
28
+ });
29
+
30
+ await Promise.resolve();
31
+ expect(trace).toEqual(['first:start']);
32
+ releaseFirst?.();
33
+ await Promise.all([first, second]);
34
+ expect(trace).toEqual(['first:start', 'first:end', 'second:start', 'second:end']);
35
+ });
36
+
37
+ it('publishes the candidate before detaching the previous World', async () => {
38
+ const renderer = rendererOwner();
39
+ const previous = new World();
40
+ const next = new World();
41
+
42
+ await expect(commitAttachedWorld(renderer, previous, next, async () => true)).resolves.toBe(
43
+ true,
44
+ );
45
+ expect(renderer.attachWorld).toHaveBeenCalledWith(next);
46
+ expect(renderer.detachWorld).toHaveBeenCalledOnce();
47
+ expect(renderer.detachWorld).toHaveBeenCalledWith(previous);
48
+ });
49
+
50
+ it('detaches a candidate whose bootstrap fails and keeps the previous World', async () => {
51
+ const renderer = rendererOwner();
52
+ const previous = new World();
53
+ const next = new World();
54
+
55
+ await expect(commitAttachedWorld(renderer, previous, next, async () => false)).resolves.toBe(
56
+ false,
57
+ );
58
+ expect(renderer.detachWorld).toHaveBeenCalledOnce();
59
+ expect(renderer.detachWorld).toHaveBeenCalledWith(next);
60
+ expect(renderer.detachWorld).not.toHaveBeenCalledWith(previous);
61
+ });
62
+
63
+ it('detaches a candidate whose bootstrap throws', async () => {
64
+ const renderer = rendererOwner();
65
+ const previous = new World();
66
+ const next = new World();
67
+
68
+ await expect(
69
+ commitAttachedWorld(renderer, previous, next, async () => {
70
+ throw new Error('bootstrap failed');
71
+ }),
72
+ ).rejects.toThrow('bootstrap failed');
73
+ expect(renderer.detachWorld).toHaveBeenCalledOnce();
74
+ expect(renderer.detachWorld).toHaveBeenCalledWith(next);
75
+ });
76
+ });
@@ -0,0 +1,268 @@
1
+ // errors-pointer-lock-failed.test.ts -- M2 w10: AppErrorCode closed union
2
+ // (6 members) + 'app-pointer-lock-failed' detail shape + exhaustive switch
3
+ // + APP_EXPECTED / APP_ERROR_HINTS bidirectional symmetry.
4
+ //
5
+ // TDD red phase: this test is written BEFORE the 'app-pointer-lock-failed'
6
+ // member lands in errors.ts (w12). It will fail typecheck / assertions until
7
+ // w12 expands the union and adds the detail variant.
8
+ //
9
+ // charter awareness:
10
+ // P3 explicit failure -- every assertion produces a clear diagnostic
11
+ // P4 closed-union exhaustive switch -- zero default branch, tsc guards completeness
12
+
13
+ import { describe, expect, it } from 'vitest';
14
+ import {
15
+ APP_ERROR_HINTS,
16
+ APP_EXPECTED,
17
+ AppError,
18
+ type AppErrorCode,
19
+ type AppErrorDetail,
20
+ type AppErrorDetailFor,
21
+ isAppError,
22
+ } from '../errors';
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // 1. AppErrorCode closed union
26
+ // ---------------------------------------------------------------------------
27
+
28
+ describe('AppErrorCode closed union', () => {
29
+ it('has every source-owned member', () => {
30
+ // Verify the full set of members. When 'app-pointer-lock-failed' is added
31
+ // in w12, this set will be complete and the exhaustive-switch check below
32
+ // will pass tsc with no default branch.
33
+ const allCodes: AppErrorCode[] = [
34
+ 'app-not-started',
35
+ 'app-already-running',
36
+ 'app-canvas-detached',
37
+ 'app-invalid-canvas-pixel-ratio',
38
+ 'app-frame-step-invalid',
39
+ 'app-system-update-failed',
40
+ 'app-pointer-lock-failed',
41
+ 'app-plugin-activation-failed',
42
+ 'app-execution-tier-unavailable',
43
+ 'app-execution-bootstrap-failed',
44
+ 'app-execution-deadline-exceeded',
45
+ 'app-execution-kernel-failed',
46
+ 'app-execution-stale-world',
47
+ 'app-execution-rebuild-failed',
48
+ ];
49
+
50
+ expect(allCodes).toHaveLength(14);
51
+
52
+ // Each member must be unique.
53
+ const unique = new Set(allCodes);
54
+ expect(unique.size).toBe(14);
55
+ });
56
+
57
+ it('exhaustive switch over AppErrorCode has zero default branch', () => {
58
+ // This function MUST compile with no default. If a member is missing from
59
+ // the union, tsc will flag the switch as non-exhaustive. If a member is
60
+ // removed from the union but left here, tsc will flag the dead case.
61
+ // The test assertion itself is trivially true -- the test is the
62
+ // compile-time guarantee.
63
+ function exhaust(code: AppErrorCode): string {
64
+ switch (code) {
65
+ case 'app-not-started':
66
+ return 'not started';
67
+ case 'app-already-running':
68
+ return 'already running';
69
+ case 'app-canvas-detached':
70
+ return 'canvas detached';
71
+ case 'app-invalid-canvas-pixel-ratio':
72
+ return 'invalid canvas pixel ratio';
73
+ case 'app-frame-step-invalid':
74
+ return 'frame step invalid';
75
+ case 'app-system-update-failed':
76
+ return 'system update failed';
77
+ case 'app-pointer-lock-failed':
78
+ return 'pointer lock failed';
79
+ case 'app-plugin-activation-failed':
80
+ return 'plugin activation failed';
81
+ case 'app-execution-tier-unavailable':
82
+ case 'app-execution-bootstrap-failed':
83
+ case 'app-execution-deadline-exceeded':
84
+ case 'app-execution-kernel-failed':
85
+ case 'app-execution-stale-world':
86
+ case 'app-execution-rebuild-failed':
87
+ return 'execution';
88
+ }
89
+ }
90
+
91
+ // Verify the function is callable for representative codes.
92
+ expect(exhaust('app-not-started')).toBe('not started');
93
+ expect(exhaust('app-already-running')).toBe('already running');
94
+ expect(exhaust('app-canvas-detached')).toBe('canvas detached');
95
+ expect(exhaust('app-system-update-failed')).toBe('system update failed');
96
+ expect(exhaust('app-pointer-lock-failed')).toBe('pointer lock failed');
97
+ });
98
+ });
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // 2. 'app-pointer-lock-failed' detail shape
102
+ // ---------------------------------------------------------------------------
103
+
104
+ describe("'app-pointer-lock-failed' detail shape", () => {
105
+ it('has .code === "app-pointer-lock-failed"', () => {
106
+ const err = new AppError({
107
+ code: 'app-pointer-lock-failed',
108
+ expected: 'pointer-lock request to succeed',
109
+ hint: 'remain in unlocked state; next trusted click will retry',
110
+ detail: { path: 'w3c', cause: new Error('test') },
111
+ });
112
+ expect(err.code).toBe('app-pointer-lock-failed');
113
+ });
114
+
115
+ it('detail.path narrows to "w3c" | "provider"', () => {
116
+ const w3cErr = new AppError({
117
+ code: 'app-pointer-lock-failed',
118
+ expected: 'pointer-lock request to succeed',
119
+ hint: 'remain in unlocked state; next trusted click will retry',
120
+ detail: { path: 'w3c', cause: new DOMException('test', 'NotAllowedError') },
121
+ });
122
+ expect(w3cErr.detail.path).toBe('w3c');
123
+
124
+ const provErr = new AppError({
125
+ code: 'app-pointer-lock-failed',
126
+ expected: 'pointer-lock request to succeed',
127
+ hint: 'remain in unlocked state; next trusted click will retry',
128
+ detail: { path: 'provider', cause: new Error('provider reject') },
129
+ });
130
+ expect(provErr.detail.path).toBe('provider');
131
+ });
132
+
133
+ it('detail.cause carries the original rejection value verbatim', () => {
134
+ const cause = new Error('W3C lock rejected');
135
+ const err = new AppError({
136
+ code: 'app-pointer-lock-failed',
137
+ expected: 'pointer-lock request to succeed',
138
+ hint: 'remain in unlocked state; next trusted click will retry',
139
+ detail: { path: 'w3c', cause },
140
+ });
141
+ expect(err.detail.cause).toBe(cause);
142
+ });
143
+
144
+ it('isAppError returns true for pointer-lock-failed errors', () => {
145
+ const err = new AppError({
146
+ code: 'app-pointer-lock-failed',
147
+ expected: 'pointer-lock request to succeed',
148
+ hint: 'remain in unlocked state; next trusted click will retry',
149
+ detail: { path: 'w3c', cause: new Error('test') },
150
+ });
151
+ expect(isAppError(err)).toBe(true);
152
+ });
153
+
154
+ it('message carries path and cause summary', () => {
155
+ const err = new AppError({
156
+ code: 'app-pointer-lock-failed',
157
+ expected: 'pointer-lock request to succeed',
158
+ hint: 'remain in unlocked state; next trusted click will retry',
159
+ detail: { path: 'w3c', cause: new Error('NotAllowedError') },
160
+ });
161
+ expect(err.message).toContain('app-pointer-lock-failed');
162
+ expect(err.message).toContain('NotAllowedError');
163
+ });
164
+
165
+ it('narrows via discriminated union: if (err.code === "app-pointer-lock-failed")', () => {
166
+ const err = new AppError({
167
+ code: 'app-pointer-lock-failed',
168
+ expected: 'pointer-lock request to succeed',
169
+ hint: 'remain in unlocked state; next trusted click will retry',
170
+ detail: { path: 'w3c', cause: new Error('test') },
171
+ }) as AppError;
172
+
173
+ if (err.code === 'app-pointer-lock-failed') {
174
+ // detail should narrow to { path: 'w3c' | 'provider'; cause: unknown }
175
+ const { path, cause } = err.detail;
176
+ expect(path).toBe('w3c');
177
+ expect(cause).toBeInstanceOf(Error);
178
+ } else {
179
+ // Should not reach here for this test.
180
+ expect.unreachable('expected code to be app-pointer-lock-failed');
181
+ }
182
+ });
183
+ });
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // 3. APP_EXPECTED / APP_ERROR_HINTS bidirectional symmetry (6 keys)
187
+ // ---------------------------------------------------------------------------
188
+
189
+ describe('APP_EXPECTED / APP_ERROR_HINTS bidirectional symmetry', () => {
190
+ it('APP_EXPECTED has one key per AppErrorCode member', () => {
191
+ const keys = Object.keys(APP_EXPECTED);
192
+ expect(keys).toHaveLength(14);
193
+ });
194
+
195
+ it('APP_ERROR_HINTS has one key per AppErrorCode member', () => {
196
+ const keys = Object.keys(APP_ERROR_HINTS);
197
+ expect(keys).toHaveLength(14);
198
+ });
199
+
200
+ it('APP_EXPECTED and APP_ERROR_HINTS have the same key set', () => {
201
+ const expectedKeys = Object.keys(APP_EXPECTED).sort();
202
+ const hintKeys = Object.keys(APP_ERROR_HINTS).sort();
203
+ expect(expectedKeys).toEqual(hintKeys);
204
+ });
205
+
206
+ it('every APP_EXPECTED entry is non-empty', () => {
207
+ for (const [key, value] of Object.entries(APP_EXPECTED)) {
208
+ expect(value, `APP_EXPECTED[${key}] must be non-empty`).toBeTruthy();
209
+ expect(value.length, `APP_EXPECTED[${key}] must be non-empty`).toBeGreaterThan(0);
210
+ }
211
+ });
212
+
213
+ it('every APP_ERROR_HINTS entry is non-empty', () => {
214
+ for (const [key, value] of Object.entries(APP_ERROR_HINTS)) {
215
+ expect(value, `APP_ERROR_HINTS[${key}] must be non-empty`).toBeTruthy();
216
+ expect(value.length, `APP_ERROR_HINTS[${key}] must be non-empty`).toBeGreaterThan(0);
217
+ }
218
+ });
219
+
220
+ it('app-pointer-lock-failed has expected and hint entries', () => {
221
+ expect(APP_EXPECTED).toHaveProperty('app-pointer-lock-failed');
222
+ expect(APP_ERROR_HINTS).toHaveProperty('app-pointer-lock-failed');
223
+ expect(APP_EXPECTED['app-pointer-lock-failed'].length).toBeGreaterThan(0);
224
+ expect(APP_ERROR_HINTS['app-pointer-lock-failed'].length).toBeGreaterThan(0);
225
+ });
226
+ });
227
+
228
+ // ---------------------------------------------------------------------------
229
+ // 4. AppErrorDetail union includes the new variant
230
+ // ---------------------------------------------------------------------------
231
+
232
+ describe('AppErrorDetail union', () => {
233
+ it('accepts the pointer-lock-failed detail variant', () => {
234
+ const detail: AppErrorDetail = {
235
+ path: 'w3c',
236
+ cause: new Error('test'),
237
+ };
238
+ expect(detail.path).toBe('w3c');
239
+ expect(detail.cause).toBeInstanceOf(Error);
240
+ });
241
+
242
+ it('accepts the provider path variant', () => {
243
+ const detail: AppErrorDetail = {
244
+ path: 'provider',
245
+ cause: 'provider string rejection',
246
+ };
247
+ // cause is unknown, so string is valid.
248
+ expect(detail.path).toBe('provider');
249
+ expect(typeof detail.cause).toBe('string');
250
+ });
251
+ });
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // 5. AppErrorDetailFor conditional type narrows correctly
255
+ // ---------------------------------------------------------------------------
256
+
257
+ describe('AppErrorDetailFor conditional type', () => {
258
+ it('narrows "app-pointer-lock-failed" to { path, cause }', () => {
259
+ // This is a compile-time assertion: if AppErrorDetailFor<'app-pointer-lock-failed'>
260
+ // does not resolve to { path: 'w3c' | 'provider'; cause: unknown }, tsc will fail.
261
+ const detail: AppErrorDetailFor<'app-pointer-lock-failed'> = {
262
+ path: 'w3c',
263
+ cause: new Error('test'),
264
+ };
265
+ expect(detail.path).toBe('w3c');
266
+ expect(detail.cause).toBeInstanceOf(Error);
267
+ });
268
+ });
@@ -0,0 +1,80 @@
1
+ import { createWorldContext, Update, World } from '@forgeax/engine-ecs';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import {
4
+ executionBootstrapHostPlugin,
5
+ loadBootstrapEntry,
6
+ prepareBootstrapEntry,
7
+ validateExecutionBootstrapData,
8
+ } from '../execution';
9
+
10
+ function moduleUrl(source: string): string {
11
+ return `data:text/javascript,${encodeURIComponent(source)}`;
12
+ }
13
+
14
+ describe('execution bootstrap isolation', () => {
15
+ it('activates realm-local bootstrap plugins and disposes their effects', async () => {
16
+ const url = moduleUrl(`export default function(data){return {plugins:[{
17
+ name:'test-bootstrap',inject:['world','executionBootstrapHost'],apply(ctx){
18
+ ctx.world.insertResource('bootstrapped',data.value);
19
+ ctx.effect(()=>()=>ctx.world.insertResource('cleaned',true),'test/cleanup');
20
+ ctx.executionBootstrapHost.setPointerLockAllowed(false);
21
+ }
22
+ }]}}`);
23
+ const prepared = await prepareBootstrapEntry(url, { value: true });
24
+ expect(prepared.ok).toBe(true);
25
+ if (!prepared.ok) return;
26
+ const world = new World();
27
+ const pointerLock = vi.fn();
28
+ const close = vi.fn();
29
+ const context = await createWorldContext(world, [
30
+ executionBootstrapHostPlugin({
31
+ port: { close } as unknown as MessagePort,
32
+ setPointerLockAllowed: pointerLock,
33
+ }),
34
+ ...(prepared.value.plugins ?? []),
35
+ ]);
36
+ expect(world.getResource('bootstrapped')).toBe(true);
37
+ expect(pointerLock).toHaveBeenCalledWith(false);
38
+ await context.fiber.dispose();
39
+ expect(world.getResource('cleaned')).toBe(true);
40
+ expect(close).toHaveBeenCalledOnce();
41
+ });
42
+
43
+ it('keeps schedule identity in the realm-local World', async () => {
44
+ const url = moduleUrl(`export default function(){return {plugins:[{
45
+ name:'module-system',inject:['world'],apply(ctx){
46
+ const system={name:'module-system',queries:[],fn(){}};
47
+ const update=ctx.world.scheduleToken('Update');
48
+ ctx.world.addSystem(update,system).unwrap();
49
+ ctx.effect(()=>()=>ctx.world.removeSystem(update,system.name),'test/system');
50
+ }
51
+ }]}}`);
52
+ const prepared = await prepareBootstrapEntry(url, undefined);
53
+ expect(prepared.ok).toBe(true);
54
+ if (!prepared.ok) return;
55
+ const world = new World();
56
+ const context = await createWorldContext(world, prepared.value.plugins ?? []);
57
+ expect(world.inspect().scheduleSystemCount(Update)).toBe(1);
58
+ await context.fiber.dispose();
59
+ expect(world.inspect().scheduleSystemCount(Update)).toBe(0);
60
+ });
61
+
62
+ it('rejects a missing default export structurally', async () => {
63
+ const result = await loadBootstrapEntry('data:text/javascript,export const value=1');
64
+ expect(result.ok).toBe(false);
65
+ if (!result.ok && result.error.code === 'app-execution-bootstrap-failed') {
66
+ expect(result.error.detail.phase).toBe('export');
67
+ }
68
+ });
69
+
70
+ it('rejects non-cloneable bootstrap data before canvas transfer', () => {
71
+ const result = validateExecutionBootstrapData(
72
+ { callback: (() => {}) as never },
73
+ 'https://example.test/bootstrap.js',
74
+ );
75
+ expect(result.ok).toBe(false);
76
+ if (!result.ok && result.error.code === 'app-execution-bootstrap-failed') {
77
+ expect(result.error.detail.phase).toBe('data');
78
+ }
79
+ });
80
+ });
@@ -0,0 +1,13 @@
1
+ import { expectTypeOf, it } from 'vitest';
2
+ import type { AppError } from '../index';
3
+
4
+ it('narrows execution error detail by code', () => {
5
+ const error = null as unknown as AppError;
6
+ if (error.code === 'app-execution-kernel-failed') {
7
+ expectTypeOf(error.detail.partialWrite).toEqualTypeOf<true>();
8
+ expectTypeOf(error.detail.retryable).toEqualTypeOf<false>();
9
+ }
10
+ if (error.code === 'app-execution-deadline-exceeded') {
11
+ expectTypeOf(error.detail.phase).toEqualTypeOf<'startup' | 'handshake' | 'frame'>();
12
+ }
13
+ });
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { APP_ERROR_HINTS, APP_EXPECTED, AppError, type AppErrorCode } from '../index';
3
+
4
+ const EXECUTION_CODES = [
5
+ 'app-execution-tier-unavailable',
6
+ 'app-execution-bootstrap-failed',
7
+ 'app-execution-deadline-exceeded',
8
+ 'app-execution-kernel-failed',
9
+ 'app-execution-stale-world',
10
+ 'app-execution-rebuild-failed',
11
+ ] as const satisfies readonly AppErrorCode[];
12
+
13
+ describe('execution errors', () => {
14
+ it('has executable expected and hint tables for every execution code', () => {
15
+ for (const code of EXECUTION_CODES) {
16
+ expect(APP_EXPECTED[code].length).toBeGreaterThan(0);
17
+ expect(APP_ERROR_HINTS[code].length).toBeGreaterThan(0);
18
+ }
19
+ });
20
+
21
+ it('exposes unavailable facts without message parsing', () => {
22
+ const error = new AppError({
23
+ code: 'app-execution-tier-unavailable',
24
+ expected: APP_EXPECTED['app-execution-tier-unavailable'],
25
+ hint: APP_ERROR_HINTS['app-execution-tier-unavailable'],
26
+ detail: {
27
+ requestedTier: 'shared',
28
+ missingCapabilities: ['sharedArrayBuffer'],
29
+ sharedEvidencePassed: true,
30
+ },
31
+ });
32
+ expect(error.detail.requestedTier).toBe('shared');
33
+ expect(error.detail.missingCapabilities).toEqual(['sharedArrayBuffer']);
34
+ });
35
+ });
@@ -0,0 +1,38 @@
1
+ import type { InputBackendSample } from '@forgeax/engine-input';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { FrameCreditLedger } from '../execution/protocol';
4
+
5
+ describe('execution frame ordering', () => {
6
+ it('samples once and cannot issue the next credit before update, draw and completion', () => {
7
+ const trace: string[] = [];
8
+ let samples = 0;
9
+ const sample = (): InputBackendSample => {
10
+ samples += 1;
11
+ trace.push('sample');
12
+ return {
13
+ downKeys: new Set(),
14
+ upKeys: new Set(),
15
+ buttons: [false, false, false],
16
+ movementX: 0,
17
+ movementY: 0,
18
+ wheelDelta: 0,
19
+ focused: true,
20
+ pointerLocked: false,
21
+ };
22
+ };
23
+ const ledger = new FrameCreditLedger('world');
24
+ const frame = ledger.issue(1 / 60, sample, { width: 320, height: 180 });
25
+ expect(ledger.issue(1 / 60, sample, { width: 320, height: 180 })).toBeUndefined();
26
+ trace.push('update', 'draw');
27
+ ledger.complete({
28
+ kind: 'frame-complete',
29
+ worldIdentity: 'world',
30
+ frameId: frame?.frameId ?? 0,
31
+ engineUpdateMs: 1,
32
+ kernelWaitMs: 0,
33
+ });
34
+ trace.push('complete');
35
+ expect(trace).toEqual(['sample', 'update', 'draw', 'complete']);
36
+ expect(samples).toBe(1);
37
+ });
38
+ });
@@ -0,0 +1,61 @@
1
+ import type { InputBackendSample } from '@forgeax/engine-input';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { FrameCreditLedger } from '../execution/protocol';
4
+
5
+ const sample: InputBackendSample = {
6
+ downKeys: new Set(),
7
+ upKeys: new Set(),
8
+ buttons: [false, false, false],
9
+ movementX: 0,
10
+ movementY: 0,
11
+ wheelDelta: 0,
12
+ focused: true,
13
+ pointerLocked: false,
14
+ };
15
+
16
+ describe('frame credit protocol', () => {
17
+ it('allows one in-flight frame and handles duplicate, late and old identities without writes', () => {
18
+ const ledger = new FrameCreditLedger('world-1');
19
+ const first = ledger.issue(0.016, () => sample, { width: 320, height: 180 });
20
+ expect(first?.frameId).toBe(1);
21
+ expect(first).toMatchObject({ canvasWidth: 320, canvasHeight: 180 });
22
+ expect(ledger.issue(0.016, () => sample, { width: 320, height: 180 })).toBeUndefined();
23
+ expect(
24
+ ledger.complete({
25
+ kind: 'frame-complete',
26
+ worldIdentity: 'old',
27
+ frameId: 1,
28
+ engineUpdateMs: 1,
29
+ kernelWaitMs: 0,
30
+ }),
31
+ ).toBe('stale-world');
32
+ expect(
33
+ ledger.complete({
34
+ kind: 'frame-complete',
35
+ worldIdentity: 'world-1',
36
+ frameId: 2,
37
+ engineUpdateMs: 1,
38
+ kernelWaitMs: 0,
39
+ }),
40
+ ).toBe('late');
41
+ expect(
42
+ ledger.complete({
43
+ kind: 'frame-complete',
44
+ worldIdentity: 'world-1',
45
+ frameId: 1,
46
+ engineUpdateMs: 1,
47
+ kernelWaitMs: 0,
48
+ }),
49
+ ).toBe('accepted');
50
+ expect(
51
+ ledger.complete({
52
+ kind: 'frame-complete',
53
+ worldIdentity: 'world-1',
54
+ frameId: 1,
55
+ engineUpdateMs: 1,
56
+ kernelWaitMs: 0,
57
+ }),
58
+ ).toBe('duplicate');
59
+ expect(ledger.issue(0.016, () => sample, { width: 640, height: 320 })?.frameId).toBe(2);
60
+ });
61
+ });
@@ -0,0 +1,23 @@
1
+ import { type InputBackendSample, snapshotFromSample } from '@forgeax/engine-input';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ describe('Host to Engine input boundary', () => {
5
+ it('reconstructs the existing InputSnapshot from one structured-cloneable sample', () => {
6
+ const sample: InputBackendSample = {
7
+ downKeys: new Set(['w']),
8
+ upKeys: new Set(),
9
+ buttons: [false, false, false],
10
+ movementX: 4,
11
+ movementY: -2,
12
+ wheelDelta: 1,
13
+ focused: true,
14
+ pointerLocked: true,
15
+ };
16
+ const clone = structuredClone(sample);
17
+ const snapshot = snapshotFromSample(clone);
18
+ expect(snapshot.keyboard.down('w')).toBe(true);
19
+ expect(snapshot.mouse.movementDelta).toEqual({ x: 4, y: -2 });
20
+ expect(clone).not.toHaveProperty('window');
21
+ expect(clone).not.toHaveProperty('document');
22
+ });
23
+ });