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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +424 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/animation-asset-lookup.unit.test.d.ts +2 -0
  5. package/dist/__tests__/animation-asset-lookup.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/callback-deletion.unit.test.d.ts +2 -0
  7. package/dist/__tests__/callback-deletion.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/callback-inference.test-d.d.ts +2 -0
  9. package/dist/__tests__/callback-inference.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/component-introspection.unit.test.d.ts +2 -0
  11. package/dist/__tests__/component-introspection.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/consumer-migration.unit.test.d.ts +2 -0
  13. package/dist/__tests__/consumer-migration.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/create-app-error-retention.integration.test.d.ts +2 -0
  15. package/dist/__tests__/create-app-error-retention.integration.test.d.ts.map +1 -0
  16. package/dist/__tests__/create-app-features.test.d.ts +2 -0
  17. package/dist/__tests__/create-app-features.test.d.ts.map +1 -0
  18. package/dist/__tests__/create-app-listener-sync.test.d.ts +2 -0
  19. package/dist/__tests__/create-app-listener-sync.test.d.ts.map +1 -0
  20. package/dist/__tests__/create-app-lock-provider.test.d.ts +2 -0
  21. package/dist/__tests__/create-app-lock-provider.test.d.ts.map +1 -0
  22. package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts +2 -0
  23. package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts.map +1 -0
  24. package/dist/__tests__/create-app-plugin.test.d.ts +2 -0
  25. package/dist/__tests__/create-app-plugin.test.d.ts.map +1 -0
  26. package/dist/__tests__/create-app-stop.test.d.ts +2 -0
  27. package/dist/__tests__/create-app-stop.test.d.ts.map +1 -0
  28. package/dist/__tests__/create-app-time.unit.test.d.ts +2 -0
  29. package/dist/__tests__/create-app-time.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/create-app.test-d.d.ts +2 -0
  31. package/dist/__tests__/create-app.test-d.d.ts.map +1 -0
  32. package/dist/__tests__/create-app.test.d.ts +2 -0
  33. package/dist/__tests__/create-app.test.d.ts.map +1 -0
  34. package/dist/__tests__/create-renderer-error.integration.test.d.ts +2 -0
  35. package/dist/__tests__/create-renderer-error.integration.test.d.ts.map +1 -0
  36. package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts +2 -0
  37. package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts.map +1 -0
  38. package/dist/__tests__/docs-gate-green.unit.test.d.ts +2 -0
  39. package/dist/__tests__/docs-gate-green.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts +2 -0
  41. package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts.map +1 -0
  42. package/dist/__tests__/draw-source-single-world-regression.test.d.ts +2 -0
  43. package/dist/__tests__/draw-source-single-world-regression.test.d.ts.map +1 -0
  44. package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts +2 -0
  45. package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts +2 -0
  47. package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts.map +1 -0
  48. package/dist/__tests__/errors-pointer-lock-failed.test.d.ts +2 -0
  49. package/dist/__tests__/errors-pointer-lock-failed.test.d.ts.map +1 -0
  50. package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts +2 -0
  51. package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/execution-error-detail.test-d.d.ts +2 -0
  53. package/dist/__tests__/execution-error-detail.test-d.d.ts.map +1 -0
  54. package/dist/__tests__/execution-error-detail.unit.test.d.ts +2 -0
  55. package/dist/__tests__/execution-error-detail.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/execution-frame-order.unit.test.d.ts +2 -0
  57. package/dist/__tests__/execution-frame-order.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/execution-frame-protocol.unit.test.d.ts +2 -0
  59. package/dist/__tests__/execution-frame-protocol.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/execution-input-sample.contract.test.d.ts +2 -0
  61. package/dist/__tests__/execution-input-sample.contract.test.d.ts.map +1 -0
  62. package/dist/__tests__/execution-measurement.unit.test.d.ts +2 -0
  63. package/dist/__tests__/execution-measurement.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/execution-public-api.test-d.d.ts +2 -0
  65. package/dist/__tests__/execution-public-api.test-d.d.ts.map +1 -0
  66. package/dist/__tests__/execution-public-api.unit.test.d.ts +2 -0
  67. package/dist/__tests__/execution-public-api.unit.test.d.ts.map +1 -0
  68. package/dist/__tests__/execution-rebuild.contract.test.d.ts +2 -0
  69. package/dist/__tests__/execution-rebuild.contract.test.d.ts.map +1 -0
  70. package/dist/__tests__/execution-report.contract.test.d.ts +2 -0
  71. package/dist/__tests__/execution-report.contract.test.d.ts.map +1 -0
  72. package/dist/__tests__/execution-report.types.test-d.d.ts +2 -0
  73. package/dist/__tests__/execution-report.types.test-d.d.ts.map +1 -0
  74. package/dist/__tests__/execution-schema-freshness.unit.test.d.ts +2 -0
  75. package/dist/__tests__/execution-schema-freshness.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/execution-selector.unit.test.d.ts +2 -0
  77. package/dist/__tests__/execution-selector.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/execution-shared-kernel.unit.test.d.ts +2 -0
  79. package/dist/__tests__/execution-shared-kernel.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/fallback-camera.unit.test.d.ts +2 -0
  81. package/dist/__tests__/fallback-camera.unit.test.d.ts.map +1 -0
  82. package/dist/__tests__/frame-loop-time-elapsed.test.d.ts +2 -0
  83. package/dist/__tests__/frame-loop-time-elapsed.test.d.ts.map +1 -0
  84. package/dist/__tests__/frame-loop-time.unit.test.d.ts +2 -0
  85. package/dist/__tests__/frame-loop-time.unit.test.d.ts.map +1 -0
  86. package/dist/__tests__/frame-loop-world-array.test.d.ts +2 -0
  87. package/dist/__tests__/frame-loop-world-array.test.d.ts.map +1 -0
  88. package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts +2 -0
  89. package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/migration-surface-inventory.unit.test.d.ts +2 -0
  91. package/dist/__tests__/migration-surface-inventory.unit.test.d.ts.map +1 -0
  92. package/dist/__tests__/plugin-policy.integration.test.d.ts +2 -0
  93. package/dist/__tests__/plugin-policy.integration.test.d.ts.map +1 -0
  94. package/dist/__tests__/plugin-profiles.unit.test.d.ts +2 -0
  95. package/dist/__tests__/plugin-profiles.unit.test.d.ts.map +1 -0
  96. package/dist/__tests__/profiler-default-off.test.d.ts +2 -0
  97. package/dist/__tests__/profiler-default-off.test.d.ts.map +1 -0
  98. package/dist/__tests__/profiler-frame-token.test.d.ts +2 -0
  99. package/dist/__tests__/profiler-frame-token.test.d.ts.map +1 -0
  100. package/dist/__tests__/profiler-phase-catalog.test.d.ts +2 -0
  101. package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +1 -0
  102. package/dist/__tests__/profiler-phase-integration.test.d.ts +2 -0
  103. package/dist/__tests__/profiler-phase-integration.test.d.ts.map +1 -0
  104. package/dist/__tests__/profiler-remote-bridge.test.d.ts +2 -0
  105. package/dist/__tests__/profiler-remote-bridge.test.d.ts.map +1 -0
  106. package/dist/__tests__/remote-integration.test.d.ts +2 -0
  107. package/dist/__tests__/remote-integration.test.d.ts.map +1 -0
  108. package/dist/__tests__/rhi-capture-capability.test-d.d.ts +2 -0
  109. package/dist/__tests__/rhi-capture-capability.test-d.d.ts.map +1 -0
  110. package/dist/__tests__/rhi-capture-host.integration.test.d.ts +2 -0
  111. package/dist/__tests__/rhi-capture-host.integration.test.d.ts.map +1 -0
  112. package/dist/__tests__/rhi-capture.browser.test.d.ts +2 -0
  113. package/dist/__tests__/rhi-capture.browser.test.d.ts.map +1 -0
  114. package/dist/__tests__/surface-handoff.test.d.ts +2 -0
  115. package/dist/__tests__/surface-handoff.test.d.ts.map +1 -0
  116. package/dist/__tests__/types-m2.test-d.d.ts +2 -0
  117. package/dist/__tests__/types-m2.test-d.d.ts.map +1 -0
  118. package/dist/__tests__/visibility-remote.integration.test.d.ts +2 -0
  119. package/dist/__tests__/visibility-remote.integration.test.d.ts.map +1 -0
  120. package/dist/__tests__/worker-execution-stop.test.d.ts +2 -0
  121. package/dist/__tests__/worker-execution-stop.test.d.ts.map +1 -0
  122. package/dist/animation-asset-lookup.d.ts +7 -0
  123. package/dist/animation-asset-lookup.d.ts.map +1 -0
  124. package/dist/browser-frame-signal.d.ts +17 -0
  125. package/dist/browser-frame-signal.d.ts.map +1 -0
  126. package/dist/canvas-policy.d.ts +4 -0
  127. package/dist/canvas-policy.d.ts.map +1 -0
  128. package/dist/create-app.d.ts +78 -0
  129. package/dist/create-app.d.ts.map +1 -0
  130. package/dist/engine-worker-runtime.mjs +827 -0
  131. package/dist/engine-worker-runtime.mjs.map +1 -0
  132. package/dist/errors.d.ts +254 -0
  133. package/dist/errors.d.ts.map +1 -0
  134. package/dist/execution/attached-world-swap.d.ts +10 -0
  135. package/dist/execution/attached-world-swap.d.ts.map +1 -0
  136. package/dist/execution/bootstrap-entry.d.ts +30 -0
  137. package/dist/execution/bootstrap-entry.d.ts.map +1 -0
  138. package/dist/execution/bootstrap-url.d.ts +5 -0
  139. package/dist/execution/bootstrap-url.d.ts.map +1 -0
  140. package/dist/execution/capabilities.d.ts +5 -0
  141. package/dist/execution/capabilities.d.ts.map +1 -0
  142. package/dist/execution/control.d.ts +12 -0
  143. package/dist/execution/control.d.ts.map +1 -0
  144. package/dist/execution/engine-worker-runtime.d.ts +2 -0
  145. package/dist/execution/engine-worker-runtime.d.ts.map +1 -0
  146. package/dist/execution/engine-worker.d.ts +24 -0
  147. package/dist/execution/engine-worker.d.ts.map +1 -0
  148. package/dist/execution/host-controller.d.ts +16 -0
  149. package/dist/execution/host-controller.d.ts.map +1 -0
  150. package/dist/execution/index.d.ts +10 -0
  151. package/dist/execution/index.d.ts.map +1 -0
  152. package/dist/execution/kernel-pool.d.ts +14 -0
  153. package/dist/execution/kernel-pool.d.ts.map +1 -0
  154. package/dist/execution/kernel-worker-runtime.d.ts +2 -0
  155. package/dist/execution/kernel-worker-runtime.d.ts.map +1 -0
  156. package/dist/execution/measurement.d.ts +7 -0
  157. package/dist/execution/measurement.d.ts.map +1 -0
  158. package/dist/execution/protocol.d.ts +87 -0
  159. package/dist/execution/protocol.d.ts.map +1 -0
  160. package/dist/execution/report.d.ts +5 -0
  161. package/dist/execution/report.d.ts.map +1 -0
  162. package/dist/execution/schema.d.ts +4 -0
  163. package/dist/execution/schema.d.ts.map +1 -0
  164. package/dist/execution/selector.d.ts +10 -0
  165. package/dist/execution/selector.d.ts.map +1 -0
  166. package/dist/execution/types.d.ts +123 -0
  167. package/dist/execution/types.d.ts.map +1 -0
  168. package/dist/fallback-camera.d.ts +8 -0
  169. package/dist/fallback-camera.d.ts.map +1 -0
  170. package/dist/game-context.d.ts +98 -0
  171. package/dist/game-context.d.ts.map +1 -0
  172. package/dist/index.d.ts +27 -0
  173. package/dist/index.d.ts.map +1 -0
  174. package/dist/index.mjs +3980 -0
  175. package/dist/index.mjs.map +1 -0
  176. package/dist/input-map-plugin.d.ts +5 -0
  177. package/dist/input-map-plugin.d.ts.map +1 -0
  178. package/dist/input-plugin.d.ts +4 -0
  179. package/dist/input-plugin.d.ts.map +1 -0
  180. package/dist/internal/assembled-engine-profile.d.ts +5 -0
  181. package/dist/internal/assembled-engine-profile.d.ts.map +1 -0
  182. package/dist/internal/assets-world-plugin.d.ts +7 -0
  183. package/dist/internal/assets-world-plugin.d.ts.map +1 -0
  184. package/dist/internal/browser-remote-bridge.d.ts +24 -0
  185. package/dist/internal/browser-remote-bridge.d.ts.map +1 -0
  186. package/dist/internal/browser-rhi-debug-runtime.d.ts +18 -0
  187. package/dist/internal/browser-rhi-debug-runtime.d.ts.map +1 -0
  188. package/dist/internal/component-introspection.d.ts +24 -0
  189. package/dist/internal/component-introspection.d.ts.map +1 -0
  190. package/dist/internal/debug-draw.d.ts +9 -0
  191. package/dist/internal/debug-draw.d.ts.map +1 -0
  192. package/dist/internal/ecs-import.d.ts +6 -0
  193. package/dist/internal/ecs-import.d.ts.map +1 -0
  194. package/dist/internal/engine-profile-common.d.ts +9 -0
  195. package/dist/internal/engine-profile-common.d.ts.map +1 -0
  196. package/dist/internal/error-fanout.d.ts +66 -0
  197. package/dist/internal/error-fanout.d.ts.map +1 -0
  198. package/dist/internal/frame-loop.d.ts +38 -0
  199. package/dist/internal/frame-loop.d.ts.map +1 -0
  200. package/dist/internal/input-attach.d.ts +44 -0
  201. package/dist/internal/input-attach.d.ts.map +1 -0
  202. package/dist/internal/main-engine-profile.d.ts +19 -0
  203. package/dist/internal/main-engine-profile.d.ts.map +1 -0
  204. package/dist/internal/remote-serve-flag.d.ts +19 -0
  205. package/dist/internal/remote-serve-flag.d.ts.map +1 -0
  206. package/dist/internal/remote-server-plugin.d.ts +8 -0
  207. package/dist/internal/remote-server-plugin.d.ts.map +1 -0
  208. package/dist/internal/rhi-capture.d.ts +19 -0
  209. package/dist/internal/rhi-capture.d.ts.map +1 -0
  210. package/dist/internal/rhi-debug-flag.d.ts +18 -0
  211. package/dist/internal/rhi-debug-flag.d.ts.map +1 -0
  212. package/dist/internal/worker-engine-profile.d.ts +15 -0
  213. package/dist/internal/worker-engine-profile.d.ts.map +1 -0
  214. package/dist/kernel-worker-runtime.mjs +69 -0
  215. package/dist/kernel-worker-runtime.mjs.map +1 -0
  216. package/dist/load-game-errors.d.ts +131 -0
  217. package/dist/load-game-errors.d.ts.map +1 -0
  218. package/dist/load-game.d.ts +34 -0
  219. package/dist/load-game.d.ts.map +1 -0
  220. package/dist/plugin-factories.d.ts +3 -0
  221. package/dist/plugin-factories.d.ts.map +1 -0
  222. package/dist/renderer-plugin.d.ts +25 -0
  223. package/dist/renderer-plugin.d.ts.map +1 -0
  224. package/dist/tool-preview/bootstrap.d.ts +113 -0
  225. package/dist/tool-preview/bootstrap.d.ts.map +1 -0
  226. package/dist/tool-preview/evidence.d.ts +20 -0
  227. package/dist/tool-preview/evidence.d.ts.map +1 -0
  228. package/dist/tool-preview/framing.d.ts +13 -0
  229. package/dist/tool-preview/framing.d.ts.map +1 -0
  230. package/dist/tool-preview/recipe.d.ts +54 -0
  231. package/dist/tool-preview/recipe.d.ts.map +1 -0
  232. package/dist/types.d.ts +404 -0
  233. package/dist/types.d.ts.map +1 -0
  234. package/package.json +85 -0
  235. package/schema/execution-report.schema.json +168 -0
  236. package/src/__tests__/animation-asset-lookup.unit.test.ts +20 -0
  237. package/src/__tests__/callback-deletion.unit.test.ts +14 -0
  238. package/src/__tests__/callback-inference.test-d.ts +36 -0
  239. package/src/__tests__/component-introspection.unit.test.ts +56 -0
  240. package/src/__tests__/consumer-migration.unit.test.ts +34 -0
  241. package/src/__tests__/create-app-error-retention.integration.test.ts +125 -0
  242. package/src/__tests__/create-app-features.test.ts +51 -0
  243. package/src/__tests__/create-app-listener-sync.test.ts +381 -0
  244. package/src/__tests__/create-app-lock-provider.test.ts +212 -0
  245. package/src/__tests__/create-app-physics-plugin-failure.integration.test.ts +142 -0
  246. package/src/__tests__/create-app-plugin.test.ts +217 -0
  247. package/src/__tests__/create-app-stop.test.ts +609 -0
  248. package/src/__tests__/create-app-time.unit.test.ts +26 -0
  249. package/src/__tests__/create-app.test-d.ts +83 -0
  250. package/src/__tests__/create-app.test.ts +287 -0
  251. package/src/__tests__/create-renderer-error.integration.test.ts +25 -0
  252. package/src/__tests__/create-renderer-lifecycle.integration.test.ts +24 -0
  253. package/src/__tests__/docs-gate-green.unit.test.ts +61 -0
  254. package/src/__tests__/draw-source-injected-world-update-visibility.test.ts +176 -0
  255. package/src/__tests__/draw-source-single-world-regression.test.ts +184 -0
  256. package/src/__tests__/engine-worker-bootstrap-channel.unit.test.ts +82 -0
  257. package/src/__tests__/engine-worker-world-swap.unit.test.ts +69 -0
  258. package/src/__tests__/errors-pointer-lock-failed.test.ts +265 -0
  259. package/src/__tests__/execution-bootstrap-isolation.unit.test.ts +79 -0
  260. package/src/__tests__/execution-error-detail.test-d.ts +13 -0
  261. package/src/__tests__/execution-error-detail.unit.test.ts +35 -0
  262. package/src/__tests__/execution-frame-order.unit.test.ts +38 -0
  263. package/src/__tests__/execution-frame-protocol.unit.test.ts +60 -0
  264. package/src/__tests__/execution-input-sample.contract.test.ts +23 -0
  265. package/src/__tests__/execution-measurement.unit.test.ts +20 -0
  266. package/src/__tests__/execution-public-api.test-d.ts +14 -0
  267. package/src/__tests__/execution-public-api.unit.test.ts +9 -0
  268. package/src/__tests__/execution-rebuild.contract.test.ts +36 -0
  269. package/src/__tests__/execution-report.contract.test.ts +50 -0
  270. package/src/__tests__/execution-report.types.test-d.ts +16 -0
  271. package/src/__tests__/execution-schema-freshness.unit.test.ts +20 -0
  272. package/src/__tests__/execution-selector.unit.test.ts +72 -0
  273. package/src/__tests__/execution-shared-kernel.unit.test.ts +180 -0
  274. package/src/__tests__/fallback-camera.unit.test.ts +32 -0
  275. package/src/__tests__/frame-loop-time-elapsed.test.ts +115 -0
  276. package/src/__tests__/frame-loop-time.unit.test.ts +61 -0
  277. package/src/__tests__/frame-loop-world-array.test.ts +450 -0
  278. package/src/__tests__/kernel-worker-runtime.unit.test.ts +50 -0
  279. package/src/__tests__/migration-surface-inventory.unit.test.ts +13 -0
  280. package/src/__tests__/plugin-policy.integration.test.ts +15 -0
  281. package/src/__tests__/plugin-profiles.unit.test.ts +190 -0
  282. package/src/__tests__/profiler-default-off.test.ts +53 -0
  283. package/src/__tests__/profiler-frame-token.test.ts +235 -0
  284. package/src/__tests__/profiler-phase-catalog.test.ts +30 -0
  285. package/src/__tests__/profiler-phase-integration.test.ts +117 -0
  286. package/src/__tests__/profiler-remote-bridge.test.ts +31 -0
  287. package/src/__tests__/remote-integration.test.ts +224 -0
  288. package/src/__tests__/rhi-capture-capability.test-d.ts +22 -0
  289. package/src/__tests__/rhi-capture-host.integration.test.ts +157 -0
  290. package/src/__tests__/rhi-capture.browser.test.ts +51 -0
  291. package/src/__tests__/surface-handoff.test.ts +89 -0
  292. package/src/__tests__/types-m2.test-d.ts +81 -0
  293. package/src/__tests__/visibility-remote.integration.test.ts +116 -0
  294. package/src/__tests__/worker-execution-stop.test.ts +173 -0
  295. package/src/animation-asset-lookup.ts +13 -0
  296. package/src/browser-frame-signal.ts +44 -0
  297. package/src/canvas-policy.ts +16 -0
  298. package/src/create-app.ts +1264 -0
  299. package/src/errors.ts +442 -0
  300. package/src/execution/attached-world-swap.ts +33 -0
  301. package/src/execution/bootstrap-entry.ts +122 -0
  302. package/src/execution/bootstrap-url.ts +25 -0
  303. package/src/execution/capabilities.ts +102 -0
  304. package/src/execution/control.ts +48 -0
  305. package/src/execution/engine-worker-runtime.ts +383 -0
  306. package/src/execution/engine-worker.ts +163 -0
  307. package/src/execution/host-controller.ts +460 -0
  308. package/src/execution/index.ts +44 -0
  309. package/src/execution/kernel-pool.ts +236 -0
  310. package/src/execution/kernel-worker-runtime.ts +103 -0
  311. package/src/execution/measurement.ts +39 -0
  312. package/src/execution/protocol.ts +134 -0
  313. package/src/execution/report.ts +66 -0
  314. package/src/execution/schema.ts +168 -0
  315. package/src/execution/selector.ts +98 -0
  316. package/src/execution/types.ts +163 -0
  317. package/src/fallback-camera.ts +20 -0
  318. package/src/game-context.ts +126 -0
  319. package/src/index.ts +190 -0
  320. package/src/input-map-plugin.ts +21 -0
  321. package/src/input-plugin.ts +26 -0
  322. package/src/internal/assembled-engine-profile.ts +14 -0
  323. package/src/internal/assets-world-plugin.ts +33 -0
  324. package/src/internal/browser-remote-bridge.ts +269 -0
  325. package/src/internal/browser-rhi-debug-runtime.ts +67 -0
  326. package/src/internal/component-introspection.ts +116 -0
  327. package/src/internal/debug-draw.ts +38 -0
  328. package/src/internal/ecs-import.ts +41 -0
  329. package/src/internal/engine-profile-common.ts +9 -0
  330. package/src/internal/error-fanout.ts +129 -0
  331. package/src/internal/frame-loop.ts +539 -0
  332. package/src/internal/input-attach.ts +104 -0
  333. package/src/internal/main-engine-profile.ts +84 -0
  334. package/src/internal/remote-serve-flag.ts +41 -0
  335. package/src/internal/remote-server-plugin.ts +16 -0
  336. package/src/internal/rhi-capture.ts +211 -0
  337. package/src/internal/rhi-debug-flag.ts +40 -0
  338. package/src/internal/worker-engine-profile.ts +47 -0
  339. package/src/load-game-errors.ts +211 -0
  340. package/src/load-game.ts +115 -0
  341. package/src/plugin-factories.ts +2 -0
  342. package/src/renderer-plugin.ts +96 -0
  343. package/src/tool-preview/bootstrap.ts +835 -0
  344. package/src/tool-preview/evidence.ts +59 -0
  345. package/src/tool-preview/framing.ts +52 -0
  346. package/src/tool-preview/recipe.ts +137 -0
  347. package/src/types.ts +450 -0
  348. package/src/vite-env.d.ts +10 -0
@@ -0,0 +1,84 @@
1
+ import {
2
+ type AnimationPayloadLookup,
3
+ animationPayloadsPlugin,
4
+ animationRuntimePlugin,
5
+ } from '@forgeax/engine-animation';
6
+ import type { DebugDraw } from '@forgeax/engine-debug-draw';
7
+ import {
8
+ type ActionConfig,
9
+ type InputBackend,
10
+ inputBackendPlugin,
11
+ ownedInputBackendPlugin,
12
+ } from '@forgeax/engine-input';
13
+ import type { Plugin } from '@forgeax/engine-plugin';
14
+ import { renderComponentsPlugin } from '@forgeax/engine-render';
15
+ import { scenePlugin } from '@forgeax/engine-scene';
16
+ import { statePlugin } from '@forgeax/engine-state';
17
+ import { inputMapPlugin } from '../input-map-plugin';
18
+ import { inputPlugin } from '../input-plugin';
19
+ import {
20
+ ownedRendererPlugin,
21
+ type RenderFeatureHost,
22
+ renderFeatureHostPlugin,
23
+ } from '../renderer-plugin';
24
+ import { assetsWorldPlugin, rendererAssetsPlugin } from './assets-world-plugin';
25
+ import { createDebugDrawOnReady, type RendererDebugDrawHost, releaseDebugDraw } from './debug-draw';
26
+ import type { EngineProfileBase } from './engine-profile-common';
27
+
28
+ export interface MainEngineProfileOptions extends EngineProfileBase {
29
+ readonly rendererDebugDrawHost?: RendererDebugDrawHost;
30
+ readonly rendererFeatureHost?: RenderFeatureHost;
31
+ readonly animationPayloads: AnimationPayloadLookup;
32
+ readonly onDebugDrawReady?: (debugDraw: DebugDraw) => void;
33
+ readonly input?: InputBackend;
34
+ readonly inputDispose?: () => void;
35
+ readonly inputMap?: readonly ActionConfig[];
36
+ }
37
+
38
+ function debugDrawPlugin(
39
+ context: RendererDebugDrawHost,
40
+ onReady: (debugDraw: DebugDraw) => void,
41
+ ): Plugin {
42
+ return {
43
+ name: 'debug-draw',
44
+ async apply(ctx) {
45
+ try {
46
+ const debugDraw = await createDebugDrawOnReady(context);
47
+ onReady(debugDraw);
48
+ ctx.effect(() => () => releaseDebugDraw(context, debugDraw), 'render/debug-draw');
49
+ } catch {
50
+ // DebugDraw is optional; renderer error reporting owns creation failures.
51
+ }
52
+ },
53
+ };
54
+ }
55
+
56
+ /** Static browser-main selection; dependency order remains owned by inject/provide. */
57
+ export function mainEngineProfile(options: MainEngineProfileOptions): Plugin[] {
58
+ return [
59
+ ownedRendererPlugin(options.renderer),
60
+ renderComponentsPlugin(),
61
+ ...(options.rendererFeatureHost === undefined
62
+ ? []
63
+ : [renderFeatureHostPlugin(options.rendererFeatureHost)]),
64
+ rendererAssetsPlugin(options.assets),
65
+ assetsWorldPlugin(),
66
+ ...(options.input === undefined
67
+ ? []
68
+ : [
69
+ options.inputDispose === undefined
70
+ ? inputBackendPlugin(options.input)
71
+ : ownedInputBackendPlugin(options.input, options.inputDispose),
72
+ ]),
73
+ scenePlugin(),
74
+ animationPayloadsPlugin(options.animationPayloads),
75
+ animationRuntimePlugin(),
76
+ statePlugin(),
77
+ ...(options.onDebugDrawReady === undefined || options.rendererDebugDrawHost === undefined
78
+ ? []
79
+ : [debugDrawPlugin(options.rendererDebugDrawHost, options.onDebugDrawReady)]),
80
+ ...(options.input === undefined ? [] : [inputPlugin()]),
81
+ ...(options.inputMap === undefined ? [] : [inputMapPlugin(options.inputMap)]),
82
+ ...(options.extensions ?? []),
83
+ ];
84
+ }
@@ -0,0 +1,41 @@
1
+ // @forgeax/engine-app/internal/remote-serve-flag -- FORGEAX_ENGINE_REMOTE_SERVE
2
+ // dual-source resolution (plan-strategy secondary D-7).
3
+ //
4
+ // The remote eval server is auto-started in dev mode (vite's import.meta.env.DEV)
5
+ // and production builds skip it. Headless/dawn-node can opt in via the
6
+ // FORGEAX_ENGINE_REMOTE_SERVE=1 env var.
7
+ //
8
+ // Dual sources (mirrors rhi-debug-flag.ts pattern):
9
+ // browser (vite): import.meta.env.DEV is statically replaced by vite
10
+ // (true in dev server, false in production build).
11
+ // dawn-node : import.meta may be undefined; fallback to
12
+ // globalThis.process.env.FORGEAX_ENGINE_REMOTE_SERVE === '1'.
13
+ //
14
+ // The SSOT precedence: import.meta.env.DEV wins when available;
15
+ // process.env is the headless opt-in path.
16
+
17
+ /** Minimal structural view of an env bag carrying the serve flag. */
18
+ interface RemoteServeEnv {
19
+ readonly FORGEAX_ENGINE_REMOTE_SERVE?: string | undefined;
20
+ }
21
+
22
+ /**
23
+ * Resolve whether the remote eval server should be started.
24
+ *
25
+ * Returns `true` when:
26
+ * - vite dev mode (import.meta.env.DEV === true), OR
27
+ * - headless/dawn-node with FORGEAX_ENGINE_REMOTE_SERVE=1 opt-in
28
+ *
29
+ * Returns `false` in production builds and headless without explicit opt-in.
30
+ *
31
+ * @param isDev flag from import.meta.env.DEV (undefined when import.meta absent)
32
+ * @param processEnv globalThis.process?.env when present, else undefined
33
+ */
34
+ export function resolveRemoteServeFlag(
35
+ isDev: boolean | undefined,
36
+ processEnv: RemoteServeEnv | undefined,
37
+ ): boolean {
38
+ if (isDev === true) return true;
39
+ if (processEnv?.FORGEAX_ENGINE_REMOTE_SERVE === '1') return true;
40
+ return false;
41
+ }
@@ -0,0 +1,16 @@
1
+ import type { Plugin } from '@forgeax/engine-plugin';
2
+
3
+ export interface RemoteServerHandle {
4
+ readonly port: number;
5
+ close(): Promise<void>;
6
+ }
7
+
8
+ /** Own a Node remote server with the App Fiber while App still exposes its handle. */
9
+ export function remoteServerPlugin(handle: RemoteServerHandle): Plugin {
10
+ return {
11
+ name: 'remote-server',
12
+ apply(ctx) {
13
+ ctx.effect(() => async () => handle.close(), 'remote/server');
14
+ },
15
+ };
16
+ }
@@ -0,0 +1,211 @@
1
+ import type { RhiBackendInstrumentation } from '@forgeax/engine-render/internal/construct-renderer';
2
+ import { RhiError } from '@forgeax/engine-rhi';
3
+ import type {
4
+ CaptureFrameOptions,
5
+ EncodedTape,
6
+ RecorderAttachment,
7
+ RhiDebugError,
8
+ } from '@forgeax/engine-rhi-debug';
9
+ import { createRhiDebugError } from '@forgeax/engine-rhi-debug';
10
+ import { err, ok, type Result } from '@forgeax/engine-types';
11
+ import type { FrameLoopHandle } from './frame-loop';
12
+
13
+ export interface RhiTapeArtifactRef {
14
+ readonly kind: 'rhi-tape';
15
+ readonly digest: string;
16
+ readonly bytes: Uint8Array;
17
+ }
18
+
19
+ export interface RhiCapture {
20
+ captureFrame(options?: CaptureFrameOptions): Promise<Result<RhiTapeArtifactRef, RhiDebugError>>;
21
+ }
22
+
23
+ type CaptureResult = Result<EncodedTape, RhiDebugError>;
24
+ type CaptureFrameDriver = Pick<FrameLoopHandle, 'getState' | 'pause' | 'resume' | 'stepFrame'>;
25
+
26
+ const captureDrivers = new WeakMap<RhiCapture, CaptureFrameDriver>();
27
+
28
+ export function createRhiCapture(attachment: RecorderAttachment): RhiCapture {
29
+ let activeCapture: Promise<Result<RhiTapeArtifactRef, RhiDebugError>> | undefined;
30
+ const capture: RhiCapture = {
31
+ captureFrame(options) {
32
+ const driver = captureDrivers.get(capture);
33
+ if (driver === undefined) return captureAttachment(attachment, options);
34
+ if (activeCapture !== undefined) {
35
+ return Promise.resolve(
36
+ err(
37
+ createRhiDebugError('capture-busy', {
38
+ stage: 'capture',
39
+ cause: 'another App capture transaction is active',
40
+ }),
41
+ ),
42
+ );
43
+ }
44
+ const request = captureWithAppFrame(attachment, driver, options);
45
+ activeCapture = request;
46
+ void request.then(
47
+ () => {
48
+ if (activeCapture === request) activeCapture = undefined;
49
+ },
50
+ () => {
51
+ if (activeCapture === request) activeCapture = undefined;
52
+ },
53
+ );
54
+ return request;
55
+ },
56
+ };
57
+ return capture;
58
+ }
59
+
60
+ /** Bind the App-owned frame authority to its host capture capability. */
61
+ export function bindRhiCaptureFrameDriver(capture: RhiCapture, driver: CaptureFrameDriver): void {
62
+ captureDrivers.set(capture, driver);
63
+ }
64
+
65
+ async function captureAttachment(
66
+ attachment: RecorderAttachment,
67
+ options: CaptureFrameOptions | undefined,
68
+ ): Promise<Result<RhiTapeArtifactRef, RhiDebugError>> {
69
+ const result = await attachment.captureFrame(options);
70
+ if (!result.ok) return result;
71
+ return ok(toArtifact(result.value));
72
+ }
73
+
74
+ async function captureWithAppFrame(
75
+ attachment: RecorderAttachment,
76
+ driver: CaptureFrameDriver,
77
+ options: CaptureFrameOptions | undefined,
78
+ ): Promise<Result<RhiTapeArtifactRef, RhiDebugError>> {
79
+ const state = driver.getState();
80
+ if (state !== 'running' && state !== 'paused') {
81
+ return err(
82
+ createRhiDebugError('capture-unavailable', {
83
+ stage: 'capture',
84
+ cause: `App capture requires a running or paused frame loop, received '${state}'`,
85
+ }),
86
+ );
87
+ }
88
+ if (options?.signal?.aborted) return captureAttachment(attachment, options);
89
+
90
+ const resumeAfter = state === 'running';
91
+ if (resumeAfter) {
92
+ const paused = driver.pause();
93
+ if (!paused.ok) {
94
+ return err(
95
+ createRhiDebugError('capture-unavailable', {
96
+ stage: 'capture',
97
+ cause: `App capture could not pause the frame loop: ${describeFailure(paused.error)}`,
98
+ }),
99
+ );
100
+ }
101
+ }
102
+
103
+ const controller = new AbortController();
104
+ const abortFromUser = (): void => controller.abort();
105
+ options?.signal?.addEventListener('abort', abortFromUser, { once: true });
106
+ let captureResult: Promise<CaptureResult>;
107
+ try {
108
+ captureResult = attachment.captureFrame({
109
+ ...(options ?? {}),
110
+ signal: controller.signal,
111
+ });
112
+ } catch (cause: unknown) {
113
+ options?.signal?.removeEventListener('abort', abortFromUser);
114
+ if (resumeAfter && driver.getState() === 'paused') driver.resume();
115
+ return err(
116
+ createRhiDebugError('capture-unavailable', {
117
+ stage: 'capture',
118
+ cause: `App capture could not arm the recorder: ${describeFailure(cause)}`,
119
+ }),
120
+ );
121
+ }
122
+
123
+ let result: CaptureResult | undefined;
124
+ let transactionError: RhiDebugError | undefined;
125
+ try {
126
+ const snapshot = await attachment.frameBoundary();
127
+ if (!snapshot.ok) {
128
+ result = await captureResult;
129
+ } else if (options?.signal?.aborted) {
130
+ controller.abort();
131
+ result = await captureResult;
132
+ } else {
133
+ const stepped = driver.stepFrame(0);
134
+ if (!stepped.ok) {
135
+ transactionError = createRhiDebugError('capture-unavailable', {
136
+ stage: 'capture',
137
+ cause: `App capture frame failed: ${describeFailure(stepped.error)}`,
138
+ });
139
+ controller.abort();
140
+ result = await captureResult;
141
+ } else {
142
+ result = await captureResult;
143
+ }
144
+ }
145
+ } catch (cause: unknown) {
146
+ transactionError = createRhiDebugError('capture-unavailable', {
147
+ stage: 'capture',
148
+ cause: `App capture transaction failed: ${describeFailure(cause)}`,
149
+ });
150
+ controller.abort();
151
+ result = await captureResult;
152
+ } finally {
153
+ options?.signal?.removeEventListener('abort', abortFromUser);
154
+ if (resumeAfter && driver.getState() === 'paused') {
155
+ const resumed = driver.resume();
156
+ if (!resumed.ok && transactionError === undefined) {
157
+ transactionError = createRhiDebugError('capture-unavailable', {
158
+ stage: 'capture',
159
+ cause: `App capture could not resume the frame loop: ${describeFailure(resumed.error)}`,
160
+ });
161
+ }
162
+ }
163
+ }
164
+
165
+ if (transactionError !== undefined) return err(transactionError);
166
+ if (result === undefined) result = await captureResult;
167
+ if (!result.ok) return result;
168
+ return ok(toArtifact(result.value));
169
+ }
170
+
171
+ function describeFailure(cause: unknown): string {
172
+ if (typeof cause === 'string') return cause;
173
+ if (cause instanceof Error) return cause.message;
174
+ if (typeof cause === 'object' && cause !== null && 'hint' in cause) {
175
+ const hint = (cause as { hint?: unknown }).hint;
176
+ if (typeof hint === 'string') return hint;
177
+ }
178
+ return String(cause);
179
+ }
180
+
181
+ export function createRhiInstrumentation(
182
+ attachment: RecorderAttachment,
183
+ ): RhiBackendInstrumentation {
184
+ return {
185
+ resolveSurfaceDevice(device) {
186
+ const resolved = attachment.backend.unwrapDeviceForSurface(device);
187
+ if (resolved.ok) return resolved;
188
+ return err(
189
+ new RhiError({
190
+ code: 'rhi-not-available',
191
+ expected: 'the recorder can resolve the wrapped surface device',
192
+ hint: resolved.error.hint,
193
+ }),
194
+ );
195
+ },
196
+ onFrameBoundary() {
197
+ void attachment.frameBoundary();
198
+ },
199
+ onDeviceLost() {
200
+ attachment.deviceLost();
201
+ },
202
+ };
203
+ }
204
+
205
+ function toArtifact(encoded: EncodedTape): RhiTapeArtifactRef {
206
+ return {
207
+ kind: 'rhi-tape',
208
+ digest: encoded.digest,
209
+ bytes: encoded.bytes,
210
+ };
211
+ }
@@ -0,0 +1,40 @@
1
+ // @forgeax/engine-app/internal/rhi-debug-flag -- FORGEAX_ENGINE_RHI_DEBUG
2
+ // three-segment dual-source resolution (plan-strategy D-4).
3
+ //
4
+ // The canvas form reads the flag from two independent sources with a precise
5
+ // precedence + short-circuit shape so the same build serves both deployment
6
+ // targets:
7
+ //
8
+ // browser (vite): `import.meta.env.FORGEAX_ENGINE_RHI_DEBUG` is statically
9
+ // replaced by the vite-plugin-rhi-debug `define` hook.
10
+ // dawn-node : `import.meta` may be undefined and there is no vite define,
11
+ // so the flag arrives through `globalThis.process.env`.
12
+ //
13
+ // Extracting the resolution into this pure helper lets it be unit-tested across
14
+ // the three scenarios (browser '1' / dawn-node process.env / unset) without a
15
+ // real bundler or runtime. The call site keeps the literal `typeof import.meta
16
+ // !== 'undefined'` prefix (C5) -- modelled here by passing `undefined` for the
17
+ // `importMetaEnv` argument when `import.meta` is absent.
18
+
19
+ /** Minimal structural view of an env bag carrying the flag. */
20
+ interface RhiDebugEnv {
21
+ readonly FORGEAX_ENGINE_RHI_DEBUG?: string | undefined;
22
+ }
23
+
24
+ /**
25
+ * Resolve the raw FORGEAX_ENGINE_RHI_DEBUG flag from the two sources.
26
+ *
27
+ * Precedence: `import.meta.env` wins; `globalThis.process.env` is the fallback.
28
+ * Returns the raw string (or undefined); the caller compares `=== '1'`.
29
+ *
30
+ * @param importMetaEnv `import.meta.env` when `import.meta` is defined,
31
+ * else `undefined` (models the `typeof import.meta !== 'undefined'` prefix
32
+ * short-circuiting to a falsy first operand).
33
+ * @param processEnv `globalThis.process?.env` when present, else `undefined`.
34
+ */
35
+ export function resolveRhiDebugFlag(
36
+ importMetaEnv: RhiDebugEnv | undefined,
37
+ processEnv: RhiDebugEnv | undefined,
38
+ ): string | undefined {
39
+ return importMetaEnv?.FORGEAX_ENGINE_RHI_DEBUG ?? processEnv?.FORGEAX_ENGINE_RHI_DEBUG;
40
+ }
@@ -0,0 +1,47 @@
1
+ import {
2
+ type AnimationPayloadLookup,
3
+ animationPayloadsPlugin,
4
+ animationRuntimePlugin,
5
+ } from '@forgeax/engine-animation';
6
+ import { type AudioBackend, audioBackendPlugin } from '@forgeax/engine-audio';
7
+ import { type InputBackend, inputBackendPlugin } from '@forgeax/engine-input';
8
+ import type { Plugin } from '@forgeax/engine-plugin';
9
+ import { renderComponentsPlugin } from '@forgeax/engine-render';
10
+ import { scenePlugin } from '@forgeax/engine-scene';
11
+ import { statePlugin } from '@forgeax/engine-state';
12
+ import { inputPlugin } from '../input-plugin';
13
+ import {
14
+ ownedRendererPlugin,
15
+ type RenderFeatureHost,
16
+ renderFeatureHostPlugin,
17
+ } from '../renderer-plugin';
18
+ import { assetsWorldPlugin, rendererAssetsPlugin } from './assets-world-plugin';
19
+ import type { EngineProfileBase } from './engine-profile-common';
20
+
21
+ export interface WorkerEngineProfileOptions extends EngineProfileBase {
22
+ readonly animationPayloads: AnimationPayloadLookup;
23
+ readonly input: InputBackend;
24
+ readonly audio: AudioBackend;
25
+ readonly rendererFeatureHost?: RenderFeatureHost;
26
+ }
27
+
28
+ /** Static Engine Worker selection; Host-only acquisition stays outside this realm. */
29
+ export function workerEngineProfile(options: WorkerEngineProfileOptions): Plugin[] {
30
+ return [
31
+ ownedRendererPlugin(options.renderer),
32
+ renderComponentsPlugin(),
33
+ ...(options.rendererFeatureHost === undefined
34
+ ? []
35
+ : [renderFeatureHostPlugin(options.rendererFeatureHost)]),
36
+ rendererAssetsPlugin(options.assets),
37
+ assetsWorldPlugin(),
38
+ inputBackendPlugin(options.input),
39
+ audioBackendPlugin(options.audio),
40
+ scenePlugin(),
41
+ animationPayloadsPlugin(options.animationPayloads),
42
+ animationRuntimePlugin(),
43
+ statePlugin(),
44
+ inputPlugin(),
45
+ ...(options.extensions ?? []),
46
+ ];
47
+ }
@@ -0,0 +1,211 @@
1
+ // @forgeax/engine-app -- LoadGameError class + 3-code closed union +
2
+ // LOAD_GAME_ERROR_HINTS / LOAD_GAME_EXPECTED tables.
3
+ //
4
+ // Isomorphic to AppError in errors.ts: class extends Error + 4-field
5
+ // surface (.code / .expected / .hint / .detail) + discriminated detail
6
+ // per code; the co-located policy-owner proof locks the derived views.
7
+ //
8
+ // Shape:
9
+ // - LoadGameErrorCode = closed union 3 members (charter P4 closed-union
10
+ // exhaustive switch needs no default fallback; tsc strict mode guards
11
+ // completeness; AGENTS.md "Errors are structured").
12
+ // Members:
13
+ // - 'module-not-found'
14
+ // - 'invalid-format'
15
+ // - 'import-failed'
16
+ //
17
+ // - LoadGameError class = 4-field surface (.code / .expected / .hint /
18
+ // .detail) byte-for-byte parallel to AppError. AI users walk .code /
19
+ // .detail by property access (charter P3 explicit failure: never parse
20
+ // the message string).
21
+ //
22
+ // - LoadGameError is exposed as a discriminated union (variant per code)
23
+ // so AI users get .detail narrowing for free after
24
+ // `if (err.code === '...')`:
25
+ //
26
+ // if (err.code === 'module-not-found') {
27
+ // console.warn('game not found:', err.detail.slug);
28
+ // }
29
+ //
30
+ // - LOAD_GAME_ERROR_HINTS / LOAD_GAME_EXPECTED are 3-key Records keyed
31
+ // by LoadGameErrorCode; the private policy owner below is the single
32
+ // source for both projections.
33
+ //
34
+ // Related: requirements AC-08 (LoadGameError structured error 3 codes +
35
+ // hint + detail); plan-strategy D-3 (reuse codebase structured error
36
+ // pattern); charter P3 (explicit failure).
37
+
38
+ /**
39
+ * Closed LoadGameErrorCode union (3 members).
40
+ *
41
+ * | code | trigger |
42
+ * |:--|:--|
43
+ * | `'module-not-found'` | resolver throws an error distinguishable as module-not-found (e.g. "Cannot find module"). Detail carries `slug` so AI users can surface which game path failed. |
44
+ * | `'invalid-format'` | resolver returns a module without a valid native Cordis default export. Detail carries `exportKeys` so AI users can inspect the module shape. |
45
+ * | `'import-failed'` | resolver throws a generic Error (network error, build error, etc.) not distinguishable as module-not-found. Detail carries the original `cause` Error so AI users can chain narrow. |
46
+ *
47
+ * Plan-strategy D-3 locks the count at 3.
48
+ */
49
+ export type LoadGameErrorCode = keyof typeof loadGameErrorPolicy;
50
+
51
+ /**
52
+ * Detail variant for the `'module-not-found'` arm.
53
+ *
54
+ * `slug` carries the game identifier that the resolver failed to locate
55
+ * so AI users can surface exactly which game could not be loaded.
56
+ */
57
+ export interface LoadGameDetailModuleNotFound {
58
+ readonly slug: string;
59
+ }
60
+
61
+ /**
62
+ * Detail variant for the `'invalid-format'` arm.
63
+ *
64
+ * `exportKeys` carries the keys of the module object returned by the
65
+ * resolver. AI users inspect these to understand why the native default
66
+ * plugin export was rejected.
67
+ */
68
+ export interface LoadGameDetailInvalidFormat {
69
+ readonly exportKeys: string[];
70
+ }
71
+
72
+ /**
73
+ * Detail variant for the `'import-failed'` arm.
74
+ *
75
+ * `cause` carries the original thrown value verbatim so AI users can
76
+ * chain narrow (e.g. `cause instanceof TypeError` for network errors)
77
+ * without losing structure.
78
+ */
79
+ export interface LoadGameDetailImportFailed {
80
+ readonly cause: unknown;
81
+ }
82
+
83
+ /**
84
+ * Conditional resolver from `LoadGameErrorCode` to its detail payload type.
85
+ */
86
+ export type LoadGameErrorDetailFor<C extends LoadGameErrorCode> = C extends 'module-not-found'
87
+ ? LoadGameDetailModuleNotFound
88
+ : C extends 'invalid-format'
89
+ ? LoadGameDetailInvalidFormat
90
+ : LoadGameDetailImportFailed;
91
+
92
+ /**
93
+ * Tagged union of `.detail` payloads carried by structured LoadGameError.
94
+ */
95
+ export type LoadGameErrorDetail = LoadGameErrorDetailFor<LoadGameErrorCode>;
96
+
97
+ class LoadGameErrorClass extends Error {
98
+ readonly code: LoadGameErrorCode;
99
+ readonly expected: string;
100
+ readonly hint: string;
101
+ readonly detail: LoadGameErrorDetail;
102
+
103
+ constructor(args: {
104
+ code: LoadGameErrorCode;
105
+ expected: string;
106
+ hint: string;
107
+ detail: LoadGameErrorDetail;
108
+ }) {
109
+ super(`[LoadGameError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);
110
+ this.name = 'LoadGameError';
111
+ this.code = args.code;
112
+ this.expected = args.expected;
113
+ this.hint = args.hint;
114
+ this.detail = args.detail;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Variant intersection: a `LoadGameErrorClass` instance whose `code` literal
120
+ * narrows to `C` and whose `detail` narrows to `LoadGameErrorDetailFor<C>`.
121
+ */
122
+ type LoadGameErrorVariant<C extends LoadGameErrorCode> = LoadGameErrorClass & {
123
+ readonly code: C;
124
+ readonly detail: LoadGameErrorDetailFor<C>;
125
+ };
126
+
127
+ /**
128
+ * Public LoadGameError type -- discriminated union of the 3 variants.
129
+ *
130
+ * AI-user form (charter P3 explicit failure):
131
+ *
132
+ * ```ts
133
+ * function recover(err: LoadGameError): string {
134
+ * switch (err.code) {
135
+ * case 'module-not-found': return `game not found: ${err.detail.slug}`;
136
+ * case 'invalid-format': return `bad shape, exports: ${err.detail.exportKeys.join(', ')}`;
137
+ * case 'import-failed': return err.detail.cause instanceof Error ? err.detail.cause.message : 'unknown';
138
+ * }
139
+ * }
140
+ * ```
141
+ */
142
+ export type LoadGameError = {
143
+ [C in LoadGameErrorCode]: LoadGameErrorVariant<C>;
144
+ }[LoadGameErrorCode];
145
+
146
+ interface LoadGameErrorConstructor {
147
+ new <C extends LoadGameErrorCode>(args: {
148
+ code: C;
149
+ expected: string;
150
+ hint: string;
151
+ detail: LoadGameErrorDetailFor<C>;
152
+ }): LoadGameErrorVariant<C>;
153
+ readonly prototype: LoadGameErrorClass;
154
+ }
155
+
156
+ /**
157
+ * LoadGameError constructor -- `new LoadGameError({ code, expected, hint, detail })`.
158
+ */
159
+ export const LoadGameError: LoadGameErrorConstructor =
160
+ LoadGameErrorClass as unknown as LoadGameErrorConstructor;
161
+
162
+ /**
163
+ * `expected` table -- the engine-side invariant that was violated when each
164
+ * code surfaces. AI users read this as the L2 detail (charter F2 priority
165
+ * text); `.hint` carries the recovery action.
166
+ *
167
+ * 3 keys; the policy-owner proof locks the exact count, order, and values.
168
+ */
169
+ type LoadGameErrorPolicy = { readonly expected: string; readonly hint: string };
170
+
171
+ const loadGameErrorPolicy = {
172
+ 'module-not-found': {
173
+ expected:
174
+ 'resolver should return a module with a native Cordis default export for the given slug',
175
+ hint: 'verify the game slug matches an existing template directory; check the resolver import path for typos',
176
+ },
177
+ 'invalid-format': {
178
+ expected: 'resolved module must default-export a Cordis function, class, or object plugin',
179
+ hint: 'default-export one plugin and let the Host mount it into App.pluginContext',
180
+ },
181
+ 'import-failed': {
182
+ expected:
183
+ 'resolver should complete without throwing; import path, network, and build errors are forwarded here',
184
+ hint: 'inspect detail.cause for the original error (network failure, build error, dynamic import timeout, etc.)',
185
+ },
186
+ } satisfies Record<string, LoadGameErrorPolicy>;
187
+
188
+ export const LOAD_GAME_EXPECTED: Readonly<Record<LoadGameErrorCode, string>> = Object.fromEntries(
189
+ Object.entries(loadGameErrorPolicy).map(([code, policy]) => [code, policy.expected]),
190
+ ) as Readonly<Record<LoadGameErrorCode, string>>;
191
+
192
+ /**
193
+ * `hint` table -- actionable recovery guidance per code (charter P3).
194
+ *
195
+ * 3 keys; the policy-owner proof locks the exact count, order, and values.
196
+ */
197
+ export const LOAD_GAME_ERROR_HINTS: Readonly<Record<LoadGameErrorCode, string>> =
198
+ Object.fromEntries(
199
+ Object.entries(loadGameErrorPolicy).map(([code, policy]) => [code, policy.hint]),
200
+ ) as Readonly<Record<LoadGameErrorCode, string>>;
201
+
202
+ /**
203
+ * Type guard for narrowing unknown errors to LoadGameError.
204
+ *
205
+ * AI users who catch mixed error types can call `if (isLoadGameError(e))`
206
+ * before walking `.code`. Uses instanceof against the internal class
207
+ * (the public constructor delegates to LoadGameErrorClass).
208
+ */
209
+ export function isLoadGameError(err: unknown): err is LoadGameError {
210
+ return err instanceof LoadGameErrorClass;
211
+ }