@forgeax/engine-ecs 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 (383) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +278 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__fixtures__/field-shape-fixture.d.ts +16 -0
  5. package/dist/__fixtures__/field-shape-fixture.d.ts.map +1 -0
  6. package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts +3 -0
  7. package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts.map +1 -0
  8. package/dist/__tests__/archetype.unit.test.d.ts +2 -0
  9. package/dist/__tests__/archetype.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/array-handle-element.test-d.d.ts +2 -0
  11. package/dist/__tests__/array-handle-element.test-d.d.ts.map +1 -0
  12. package/dist/__tests__/buffer-array-vocab.test-d.d.ts +2 -0
  13. package/dist/__tests__/buffer-array-vocab.test-d.d.ts.map +1 -0
  14. package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts +2 -0
  15. package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/buffer-pool.test-d.d.ts +2 -0
  17. package/dist/__tests__/buffer-pool.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/change-detection.unit.test.d.ts +2 -0
  19. package/dist/__tests__/change-detection.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/command-buffer.test.d.ts +2 -0
  21. package/dist/__tests__/command-buffer.test.d.ts.map +1 -0
  22. package/dist/__tests__/commands-deferred.unit.test.d.ts +2 -0
  23. package/dist/__tests__/commands-deferred.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/component-catalog.test.d.ts +2 -0
  25. package/dist/__tests__/component-catalog.test.d.ts.map +1 -0
  26. package/dist/__tests__/component-definition-identity.unit.test.d.ts +2 -0
  27. package/dist/__tests__/component-definition-identity.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/component-nan-preflight.unit.test.d.ts +2 -0
  29. package/dist/__tests__/component-nan-preflight.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/component-schema.test-d.d.ts +2 -0
  31. package/dist/__tests__/component-schema.test-d.d.ts.map +1 -0
  32. package/dist/__tests__/component-schema.test.d.ts +2 -0
  33. package/dist/__tests__/component-schema.test.d.ts.map +1 -0
  34. package/dist/__tests__/define-component-options.test-d.d.ts +2 -0
  35. package/dist/__tests__/define-component-options.test-d.d.ts.map +1 -0
  36. package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts +2 -0
  37. package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/despawn-all.unit.test.d.ts +2 -0
  39. package/dist/__tests__/despawn-all.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts +2 -0
  41. package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts.map +1 -0
  42. package/dist/__tests__/ecs-core-reduction.perf.test.d.ts +2 -0
  43. package/dist/__tests__/ecs-core-reduction.perf.test.d.ts.map +1 -0
  44. package/dist/__tests__/ecs-error-code-31.test-d.d.ts +2 -0
  45. package/dist/__tests__/ecs-error-code-31.test-d.d.ts.map +1 -0
  46. package/dist/__tests__/entity-disabling.unit.test.d.ts +2 -0
  47. package/dist/__tests__/entity-disabling.unit.test.d.ts.map +1 -0
  48. package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts +2 -0
  49. package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  51. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts +2 -0
  53. package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/execution-shared-storage.unit.test.d.ts +2 -0
  55. package/dist/__tests__/execution-shared-storage.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/externalization-portable-value.unit.test.d.ts +2 -0
  57. package/dist/__tests__/externalization-portable-value.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/externalization-projection.unit.test.d.ts +2 -0
  59. package/dist/__tests__/externalization-projection.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/externalization-render-read-lease.test-d.d.ts +2 -0
  61. package/dist/__tests__/externalization-render-read-lease.test-d.d.ts.map +1 -0
  62. package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts +2 -0
  63. package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/externalization-root-surface.unit.test.d.ts +2 -0
  65. package/dist/__tests__/externalization-root-surface.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/field-enum-labels-reflection.test.d.ts +2 -0
  67. package/dist/__tests__/field-enum-labels-reflection.test.d.ts.map +1 -0
  68. package/dist/__tests__/field-shape-fixture.test.d.ts +2 -0
  69. package/dist/__tests__/field-shape-fixture.test.d.ts.map +1 -0
  70. package/dist/__tests__/field-transient-reflection.test.d.ts +2 -0
  71. package/dist/__tests__/field-transient-reflection.test.d.ts.map +1 -0
  72. package/dist/__tests__/field-value-type.test-d.d.ts +2 -0
  73. package/dist/__tests__/field-value-type.test-d.d.ts.map +1 -0
  74. package/dist/__tests__/fixed-update.unit.test.d.ts +2 -0
  75. package/dist/__tests__/fixed-update.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/handle.test-d.d.ts +2 -0
  77. package/dist/__tests__/handle.test-d.d.ts.map +1 -0
  78. package/dist/__tests__/hierarchy.unit.test.d.ts +2 -0
  79. package/dist/__tests__/hierarchy.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/managed-array-vocab.test-d.d.ts +2 -0
  81. package/dist/__tests__/managed-array-vocab.test-d.d.ts.map +1 -0
  82. package/dist/__tests__/minimal-example.test-d.d.ts +2 -0
  83. package/dist/__tests__/minimal-example.test-d.d.ts.map +1 -0
  84. package/dist/__tests__/name-read-types.test-d.d.ts +2 -0
  85. package/dist/__tests__/name-read-types.test-d.d.ts.map +1 -0
  86. package/dist/__tests__/plugin-service.test.d.ts +2 -0
  87. package/dist/__tests__/plugin-service.test.d.ts.map +1 -0
  88. package/dist/__tests__/query-combinations.unit.test.d.ts +2 -0
  89. package/dist/__tests__/query-combinations.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts +2 -0
  91. package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts.map +1 -0
  92. package/dist/__tests__/query-storage-trends.perf.test.d.ts +2 -0
  93. package/dist/__tests__/query-storage-trends.perf.test.d.ts.map +1 -0
  94. package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts +2 -0
  95. package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts.map +1 -0
  96. package/dist/__tests__/query.unit.test.d.ts +2 -0
  97. package/dist/__tests__/query.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/relationship-index.perf.test.d.ts +2 -0
  99. package/dist/__tests__/relationship-index.perf.test.d.ts.map +1 -0
  100. package/dist/__tests__/relationship-index.test.d.ts +2 -0
  101. package/dist/__tests__/relationship-index.test.d.ts.map +1 -0
  102. package/dist/__tests__/scalar-field-type-owner.test-d.d.ts +2 -0
  103. package/dist/__tests__/scalar-field-type-owner.test-d.d.ts.map +1 -0
  104. package/dist/__tests__/schedule-determinism.unit.test.d.ts +2 -0
  105. package/dist/__tests__/schedule-determinism.unit.test.d.ts.map +1 -0
  106. package/dist/__tests__/schedule-inspection.unit.test.d.ts +2 -0
  107. package/dist/__tests__/schedule-inspection.unit.test.d.ts.map +1 -0
  108. package/dist/__tests__/schedule-scope-error.unit.test.d.ts +2 -0
  109. package/dist/__tests__/schedule-scope-error.unit.test.d.ts.map +1 -0
  110. package/dist/__tests__/schedule-token.unit.test.d.ts +2 -0
  111. package/dist/__tests__/schedule-token.unit.test.d.ts.map +1 -0
  112. package/dist/__tests__/schema-vocab.test-d.d.ts +2 -0
  113. package/dist/__tests__/schema-vocab.test-d.d.ts.map +1 -0
  114. package/dist/__tests__/serialization.unit.test.d.ts +2 -0
  115. package/dist/__tests__/serialization.unit.test.d.ts.map +1 -0
  116. package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts +2 -0
  117. package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts.map +1 -0
  118. package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts +2 -0
  119. package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts.map +1 -0
  120. package/dist/__tests__/shared-ref-store.unit.test.d.ts +2 -0
  121. package/dist/__tests__/shared-ref-store.unit.test.d.ts.map +1 -0
  122. package/dist/__tests__/sparse-tag.unit.test.d.ts +2 -0
  123. package/dist/__tests__/sparse-tag.unit.test.d.ts.map +1 -0
  124. package/dist/__tests__/spawn-field-error.test-d.d.ts +2 -0
  125. package/dist/__tests__/spawn-field-error.test-d.d.ts.map +1 -0
  126. package/dist/__tests__/stale-error.test-d.d.ts +2 -0
  127. package/dist/__tests__/stale-error.test-d.d.ts.map +1 -0
  128. package/dist/__tests__/string-view-symmetry.test-d.d.ts +2 -0
  129. package/dist/__tests__/string-view-symmetry.test-d.d.ts.map +1 -0
  130. package/dist/__tests__/string-vocab.test-d.d.ts +2 -0
  131. package/dist/__tests__/string-vocab.test-d.d.ts.map +1 -0
  132. package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts +2 -0
  133. package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts.map +1 -0
  134. package/dist/__tests__/structure-epoch.unit.test.d.ts +2 -0
  135. package/dist/__tests__/structure-epoch.unit.test.d.ts.map +1 -0
  136. package/dist/__tests__/table-archetype-mapping.unit.test.d.ts +2 -0
  137. package/dist/__tests__/table-archetype-mapping.unit.test.d.ts.map +1 -0
  138. package/dist/__tests__/time.unit.test.d.ts +2 -0
  139. package/dist/__tests__/time.unit.test.d.ts.map +1 -0
  140. package/dist/__tests__/types.test-d.d.ts +2 -0
  141. package/dist/__tests__/types.test-d.d.ts.map +1 -0
  142. package/dist/__tests__/unique-ref-store.unit.test.d.ts +2 -0
  143. package/dist/__tests__/unique-ref-store.unit.test.d.ts.map +1 -0
  144. package/dist/__tests__/utils/handle-numeric.d.ts +14 -0
  145. package/dist/__tests__/utils/handle-numeric.d.ts.map +1 -0
  146. package/dist/__tests__/validation-root-surface.unit.test.d.ts +2 -0
  147. package/dist/__tests__/validation-root-surface.unit.test.d.ts.map +1 -0
  148. package/dist/__tests__/visibility-atomic-write.unit.test.d.ts +2 -0
  149. package/dist/__tests__/visibility-atomic-write.unit.test.d.ts.map +1 -0
  150. package/dist/__tests__/world-change-journal.unit.test.d.ts +2 -0
  151. package/dist/__tests__/world-change-journal.unit.test.d.ts.map +1 -0
  152. package/dist/__tests__/world-characterization.unit.test.d.ts +2 -0
  153. package/dist/__tests__/world-characterization.unit.test.d.ts.map +1 -0
  154. package/dist/__tests__/world-consumer-inference.test-d.d.ts +2 -0
  155. package/dist/__tests__/world-consumer-inference.test-d.d.ts.map +1 -0
  156. package/dist/__tests__/world-health.contract.test.d.ts +2 -0
  157. package/dist/__tests__/world-health.contract.test.d.ts.map +1 -0
  158. package/dist/__tests__/world-inspection.test.d.ts +2 -0
  159. package/dist/__tests__/world-inspection.test.d.ts.map +1 -0
  160. package/dist/__tests__/world-internal-identity.unit.test.d.ts +2 -0
  161. package/dist/__tests__/world-internal-identity.unit.test.d.ts.map +1 -0
  162. package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts +2 -0
  163. package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts.map +1 -0
  164. package/dist/__tests__/world-scheduling.m4.test.d.ts +2 -0
  165. package/dist/__tests__/world-scheduling.m4.test.d.ts.map +1 -0
  166. package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts +2 -0
  167. package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts.map +1 -0
  168. package/dist/__tests__/world.test-d.d.ts +2 -0
  169. package/dist/__tests__/world.test-d.d.ts.map +1 -0
  170. package/dist/buffer-pool.d.ts +109 -0
  171. package/dist/buffer-pool.d.ts.map +1 -0
  172. package/dist/commands.d.ts +93 -0
  173. package/dist/commands.d.ts.map +1 -0
  174. package/dist/component-default-fallback.d.ts +88 -0
  175. package/dist/component-default-fallback.d.ts.map +1 -0
  176. package/dist/component-schema.d.ts +34 -0
  177. package/dist/component-schema.d.ts.map +1 -0
  178. package/dist/component-value-validate.d.ts +29 -0
  179. package/dist/component-value-validate.d.ts.map +1 -0
  180. package/dist/component.d.ts +581 -0
  181. package/dist/component.d.ts.map +1 -0
  182. package/dist/entity-handle.d.ts +68 -0
  183. package/dist/entity-handle.d.ts.map +1 -0
  184. package/dist/entity.d.ts +86 -0
  185. package/dist/entity.d.ts.map +1 -0
  186. package/dist/errors/query-and-component-errors.d.ts +132 -0
  187. package/dist/errors/query-and-component-errors.d.ts.map +1 -0
  188. package/dist/errors/relationship-errors.d.ts +112 -0
  189. package/dist/errors/relationship-errors.d.ts.map +1 -0
  190. package/dist/errors/sprite-and-shared-errors.d.ts +107 -0
  191. package/dist/errors/sprite-and-shared-errors.d.ts.map +1 -0
  192. package/dist/errors/validation-errors.d.ts +229 -0
  193. package/dist/errors/validation-errors.d.ts.map +1 -0
  194. package/dist/errors.d.ts +763 -0
  195. package/dist/errors.d.ts.map +1 -0
  196. package/dist/execution/shared-kernel.d.ts +106 -0
  197. package/dist/execution/shared-kernel.d.ts.map +1 -0
  198. package/dist/externalization/index.d.ts +31 -0
  199. package/dist/externalization/index.d.ts.map +1 -0
  200. package/dist/externalization/index.mjs +298 -0
  201. package/dist/externalization/index.mjs.map +1 -0
  202. package/dist/index.d.ts +261 -0
  203. package/dist/index.d.ts.map +1 -0
  204. package/dist/index.mjs +7422 -0
  205. package/dist/index.mjs.map +1 -0
  206. package/dist/internal.mjs +479 -0
  207. package/dist/internal.mjs.map +1 -0
  208. package/dist/plugin-service.d.ts +11 -0
  209. package/dist/plugin-service.d.ts.map +1 -0
  210. package/dist/projection/index.d.ts +103 -0
  211. package/dist/projection/index.d.ts.map +1 -0
  212. package/dist/projection/index.mjs +615 -0
  213. package/dist/projection/index.mjs.map +1 -0
  214. package/dist/query/query.d.ts +58 -0
  215. package/dist/query/query.d.ts.map +1 -0
  216. package/dist/relationship-index.d.ts +81 -0
  217. package/dist/relationship-index.d.ts.map +1 -0
  218. package/dist/resource.d.ts +70 -0
  219. package/dist/resource.d.ts.map +1 -0
  220. package/dist/schedule-token.d.ts +16 -0
  221. package/dist/schedule-token.d.ts.map +1 -0
  222. package/dist/schedule.d.ts +271 -0
  223. package/dist/schedule.d.ts.map +1 -0
  224. package/dist/shared-ref-store.d.ts +144 -0
  225. package/dist/shared-ref-store.d.ts.map +1 -0
  226. package/dist/shared.d.ts +3 -0
  227. package/dist/shared.d.ts.map +1 -0
  228. package/dist/shared.mjs +364 -0
  229. package/dist/shared.mjs.map +1 -0
  230. package/dist/storage/archetype-graph.d.ts +54 -0
  231. package/dist/storage/archetype-graph.d.ts.map +1 -0
  232. package/dist/storage/archetype.d.ts +23 -0
  233. package/dist/storage/archetype.d.ts.map +1 -0
  234. package/dist/storage/change-detection.d.ts +40 -0
  235. package/dist/storage/change-detection.d.ts.map +1 -0
  236. package/dist/storage/column.d.ts +111 -0
  237. package/dist/storage/column.d.ts.map +1 -0
  238. package/dist/storage/table.d.ts +29 -0
  239. package/dist/storage/table.d.ts.map +1 -0
  240. package/dist/time.d.ts +57 -0
  241. package/dist/time.d.ts.map +1 -0
  242. package/dist/unique-ref-store.d.ts +105 -0
  243. package/dist/unique-ref-store.d.ts.map +1 -0
  244. package/dist/world-change-journal.d.ts +34 -0
  245. package/dist/world-change-journal.d.ts.map +1 -0
  246. package/dist/world-component-access.d.ts +265 -0
  247. package/dist/world-component-access.d.ts.map +1 -0
  248. package/dist/world-component-storage.d.ts +282 -0
  249. package/dist/world-component-storage.d.ts.map +1 -0
  250. package/dist/world-core.d.ts +39 -0
  251. package/dist/world-core.d.ts.map +1 -0
  252. package/dist/world-entity-lifecycle.d.ts +30 -0
  253. package/dist/world-entity-lifecycle.d.ts.map +1 -0
  254. package/dist/world-internal.d.ts +14 -0
  255. package/dist/world-internal.d.ts.map +1 -0
  256. package/dist/world-scheduling.d.ts +30 -0
  257. package/dist/world-scheduling.d.ts.map +1 -0
  258. package/dist/world.d.ts +620 -0
  259. package/dist/world.d.ts.map +1 -0
  260. package/package.json +77 -0
  261. package/src/__fixtures__/field-shape-fixture.ts +30 -0
  262. package/src/__tests__/__fixtures__/inspect-scripts.snapshot.ts +2 -0
  263. package/src/__tests__/archetype.unit.test.ts +1837 -0
  264. package/src/__tests__/array-handle-element.test-d.ts +34 -0
  265. package/src/__tests__/buffer-array-vocab.test-d.ts +53 -0
  266. package/src/__tests__/buffer-field-input-widen.unit.test.ts +99 -0
  267. package/src/__tests__/buffer-pool.test-d.ts +32 -0
  268. package/src/__tests__/change-detection.unit.test.ts +86 -0
  269. package/src/__tests__/command-buffer.test.ts +179 -0
  270. package/src/__tests__/commands-deferred.unit.test.ts +68 -0
  271. package/src/__tests__/component-catalog.test.ts +59 -0
  272. package/src/__tests__/component-definition-identity.unit.test.ts +24 -0
  273. package/src/__tests__/component-nan-preflight.unit.test.ts +186 -0
  274. package/src/__tests__/component-schema.test-d.ts +119 -0
  275. package/src/__tests__/component-schema.test.ts +55 -0
  276. package/src/__tests__/define-component-options.test-d.ts +35 -0
  277. package/src/__tests__/deprecated-vocab-rejection.unit.test.ts +59 -0
  278. package/src/__tests__/despawn-all.unit.test.ts +17 -0
  279. package/src/__tests__/ecs-core-reduction.characterization.test.ts +130 -0
  280. package/src/__tests__/ecs-core-reduction.perf.test.ts +260 -0
  281. package/src/__tests__/ecs-error-code-31.test-d.ts +42 -0
  282. package/src/__tests__/entity-disabling.unit.test.ts +37 -0
  283. package/src/__tests__/entity-handle-codec-parity.unit.test.ts +126 -0
  284. package/src/__tests__/errors.unit.test.ts +1456 -0
  285. package/src/__tests__/execution-conflict-boundary.unit.test.ts +123 -0
  286. package/src/__tests__/execution-shared-storage.unit.test.ts +40 -0
  287. package/src/__tests__/externalization-portable-value.unit.test.ts +275 -0
  288. package/src/__tests__/externalization-projection.unit.test.ts +325 -0
  289. package/src/__tests__/externalization-render-read-lease.test-d.ts +30 -0
  290. package/src/__tests__/externalization-render-read-lease.unit.test.ts +79 -0
  291. package/src/__tests__/externalization-root-surface.unit.test.ts +33 -0
  292. package/src/__tests__/field-enum-labels-reflection.test.ts +66 -0
  293. package/src/__tests__/field-shape-fixture.test.ts +61 -0
  294. package/src/__tests__/field-transient-reflection.test.ts +59 -0
  295. package/src/__tests__/field-value-type.test-d.ts +57 -0
  296. package/src/__tests__/fixed-update.unit.test.ts +78 -0
  297. package/src/__tests__/handle.test-d.ts +86 -0
  298. package/src/__tests__/hierarchy.unit.test.ts +2534 -0
  299. package/src/__tests__/managed-array-vocab.test-d.ts +137 -0
  300. package/src/__tests__/minimal-example.test-d.ts +42 -0
  301. package/src/__tests__/name-read-types.test-d.ts +72 -0
  302. package/src/__tests__/plugin-service.test.ts +13 -0
  303. package/src/__tests__/query-combinations.unit.test.ts +42 -0
  304. package/src/__tests__/query-light-extract-flat-column-ratio.perf.test.ts +252 -0
  305. package/src/__tests__/query-storage-trends.perf.test.ts +104 -0
  306. package/src/__tests__/query-trs-flat-column-ratio.perf.test.ts +194 -0
  307. package/src/__tests__/query.unit.test.ts +196 -0
  308. package/src/__tests__/relationship-index.perf.test.ts +126 -0
  309. package/src/__tests__/relationship-index.test.ts +70 -0
  310. package/src/__tests__/scalar-field-type-owner.test-d.ts +71 -0
  311. package/src/__tests__/schedule-determinism.unit.test.ts +36 -0
  312. package/src/__tests__/schedule-inspection.unit.test.ts +81 -0
  313. package/src/__tests__/schedule-scope-error.unit.test.ts +63 -0
  314. package/src/__tests__/schedule-token.unit.test.ts +54 -0
  315. package/src/__tests__/schema-vocab.test-d.ts +103 -0
  316. package/src/__tests__/serialization.unit.test.ts +2740 -0
  317. package/src/__tests__/shared-kernel-eligibility.test-d.ts +18 -0
  318. package/src/__tests__/shared-kernel-eligibility.unit.test.ts +78 -0
  319. package/src/__tests__/shared-ref-store.unit.test.ts +646 -0
  320. package/src/__tests__/sparse-tag.unit.test.ts +201 -0
  321. package/src/__tests__/spawn-field-error.test-d.ts +41 -0
  322. package/src/__tests__/stale-error.test-d.ts +208 -0
  323. package/src/__tests__/string-view-symmetry.test-d.ts +26 -0
  324. package/src/__tests__/string-vocab.test-d.ts +49 -0
  325. package/src/__tests__/structure-epoch-boundaries.unit.test.ts +39 -0
  326. package/src/__tests__/structure-epoch.unit.test.ts +48 -0
  327. package/src/__tests__/table-archetype-mapping.unit.test.ts +78 -0
  328. package/src/__tests__/time.unit.test.ts +79 -0
  329. package/src/__tests__/types.test-d.ts +38 -0
  330. package/src/__tests__/unique-ref-store.unit.test.ts +348 -0
  331. package/src/__tests__/utils/handle-numeric.ts +30 -0
  332. package/src/__tests__/validation-root-surface.unit.test.ts +15 -0
  333. package/src/__tests__/visibility-atomic-write.unit.test.ts +82 -0
  334. package/src/__tests__/world-change-journal.unit.test.ts +97 -0
  335. package/src/__tests__/world-characterization.unit.test.ts +408 -0
  336. package/src/__tests__/world-consumer-inference.test-d.ts +16 -0
  337. package/src/__tests__/world-health.contract.test.ts +87 -0
  338. package/src/__tests__/world-inspection.test.ts +37 -0
  339. package/src/__tests__/world-internal-identity.unit.test.ts +17 -0
  340. package/src/__tests__/world-managed-roundtrip.unit.test.ts +98 -0
  341. package/src/__tests__/world-scheduling.m4.test.ts +52 -0
  342. package/src/__tests__/world-unique-roundtrip.unit.test.ts +357 -0
  343. package/src/__tests__/world.test-d.ts +156 -0
  344. package/src/buffer-pool.ts +357 -0
  345. package/src/commands.ts +644 -0
  346. package/src/component-default-fallback.ts +236 -0
  347. package/src/component-schema.ts +92 -0
  348. package/src/component-value-validate.ts +135 -0
  349. package/src/component.ts +1268 -0
  350. package/src/entity-handle.ts +104 -0
  351. package/src/entity.ts +140 -0
  352. package/src/errors/query-and-component-errors.ts +200 -0
  353. package/src/errors/relationship-errors.ts +198 -0
  354. package/src/errors/sprite-and-shared-errors.ts +231 -0
  355. package/src/errors/validation-errors.ts +506 -0
  356. package/src/errors.ts +1464 -0
  357. package/src/execution/shared-kernel.ts +392 -0
  358. package/src/externalization/index.ts +170 -0
  359. package/src/index.ts +414 -0
  360. package/src/plugin-service.ts +35 -0
  361. package/src/projection/index.ts +318 -0
  362. package/src/query/query.ts +606 -0
  363. package/src/relationship-index.ts +175 -0
  364. package/src/resource.ts +106 -0
  365. package/src/schedule-token.ts +40 -0
  366. package/src/schedule.ts +846 -0
  367. package/src/shared-ref-store.ts +418 -0
  368. package/src/shared.ts +23 -0
  369. package/src/storage/archetype-graph.ts +166 -0
  370. package/src/storage/archetype.ts +78 -0
  371. package/src/storage/change-detection.ts +231 -0
  372. package/src/storage/column.ts +247 -0
  373. package/src/storage/table.ts +153 -0
  374. package/src/time.ts +122 -0
  375. package/src/unique-ref-store.ts +278 -0
  376. package/src/world-change-journal.ts +84 -0
  377. package/src/world-component-access.ts +1421 -0
  378. package/src/world-component-storage.ts +1178 -0
  379. package/src/world-core.ts +74 -0
  380. package/src/world-entity-lifecycle.ts +539 -0
  381. package/src/world-internal.ts +75 -0
  382. package/src/world-scheduling.ts +499 -0
  383. package/src/world.ts +1399 -0
package/src/errors.ts ADDED
@@ -0,0 +1,1464 @@
1
+ // @forgeax/engine-ecs — typed error class collection.
2
+ //
3
+ // Typed error classes covering all boundary conditions. Each follows progressive
4
+ // disclosure format: one-line summary → context fields → hint fix suggestion.
5
+ // Each exposes a `.hint` readonly property for programmatic extraction.
6
+
7
+ import type { QuerySpanUnavailableReason } from './errors/query-and-component-errors';
8
+
9
+ // ────────────────────────────────────────────────────────────────────────────
10
+ // Re-exports from split error sub-files (w3-b — package cohesion split)
11
+ // ────────────────────────────────────────────────────────────────────────────
12
+
13
+ export {
14
+ ComponentNotDefinedError,
15
+ QueryDataRequiresFieldsError,
16
+ QueryDescriptorConflictError,
17
+ QueryIterationActiveError,
18
+ QueryIterationInvalidatedError,
19
+ QuerySpanUnavailableError,
20
+ type QuerySpanUnavailableReason,
21
+ RemoveEssentialComponentError,
22
+ SpawnDataUnknownFieldError,
23
+ } from './errors/query-and-component-errors';
24
+
25
+ export {
26
+ RelationshipDetachMismatchError,
27
+ RelationshipMirrorComponentNotRegisteredError,
28
+ RelationshipMirrorFieldTypeMismatchError,
29
+ RelationshipSelfCycleError,
30
+ RelationshipTargetReadonlyError,
31
+ } from './errors/relationship-errors';
32
+ export {
33
+ SharedFieldInvalidValueError,
34
+ SpriteInstancesCountMismatchError,
35
+ SpriteInstancesMutuallyExclusiveWithInstancesError,
36
+ SpriteInstancesRequiresSpriteShaderError,
37
+ } from './errors/sprite-and-shared-errors';
38
+ export {
39
+ ComponentFieldInvalidValueError,
40
+ ComponentNumericValueInvalidError,
41
+ ManagedArrayInvalidValueError,
42
+ ResourceInvalidValueError,
43
+ ScheduleScopeMismatchError,
44
+ SpawnLightInvalidBoundsError,
45
+ SpriteAnimationInvalidError,
46
+ TimeConfigInvalidError,
47
+ TimeDeltaInvalidError,
48
+ validateEnumFieldValues,
49
+ validateNumericFieldValues,
50
+ } from './errors/validation-errors';
51
+
52
+ export {
53
+ SharedKernelEligibilityError,
54
+ SharedKernelFailureError,
55
+ WorldPoisonedError,
56
+ } from './execution/shared-kernel';
57
+
58
+ /**
59
+ * Thrown when an attempt is made to encode an entity index that does not fit
60
+ * in 24 bits (i.e. >= 2^24 = 16_777_216).
61
+ *
62
+ * `.code = 'entity-index-overflow'`
63
+ * `.hint` — suggests reducing entity count or investigating leaks.
64
+ */
65
+ export class EntityIndexOverflowError extends RangeError {
66
+ override readonly name = 'EntityIndexOverflowError';
67
+ readonly code = 'entity-index-overflow' as const;
68
+ readonly hint: string;
69
+
70
+ constructor(index: number) {
71
+ const hint =
72
+ 'Entity index exceeds 24-bit max (16777215). Reduce simultaneous entity count or investigate entity leaks.';
73
+ super(
74
+ `Entity index ${index} exceeds 24-bit max (16777215).\n` +
75
+ ` index: ${index}\n` +
76
+ ` hint: ${hint}`,
77
+ );
78
+ this.hint = hint;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Thrown when `defineComponent` is given a field type that is not in the
84
+ * supported scalar field type set.
85
+ *
86
+ * `.code = 'schema-unsupported-field'`
87
+ * `.hint` — lists all supported scalar field types.
88
+ */
89
+ export class SchemaUnsupportedFieldError extends Error {
90
+ override readonly name = 'SchemaUnsupportedFieldError';
91
+ readonly code = 'schema-unsupported-field' as const;
92
+ readonly hint: string;
93
+
94
+ constructor(fieldName: string, fieldType: string) {
95
+ let hint = 'Supported types: f32 / f64 / i32 / u32 / i16 / u16 / i8 / u8 / bool / enum / ref.';
96
+ // feat-20260614 M1 / M5: explicit migration hints for the two
97
+ // retired schema-vocab keyword families. Both renames preserve brand
98
+ // and storage layout (u32 column); only the keyword + dispatch arm
99
+ // changed. AI users hitting either literal land directly on the new
100
+ // keyword instead of grepping for the rename note (charter F1
101
+ // single-entry indexability).
102
+ if (fieldType.startsWith('handle<') && fieldType.endsWith('>')) {
103
+ const tag = fieldType.slice(7, -1);
104
+ hint = `'handle<${tag}>' was removed in feat-20260614 M5; use 'shared<${tag}>' instead. The brand and storage layout are unchanged; only the keyword + write-barrier dispatch (SharedRefStore retain/release) is new.`;
105
+ } else if (fieldType.startsWith('ref<') && fieldType.endsWith('>')) {
106
+ const tag = fieldType.slice(4, -1);
107
+ hint = `'ref<${tag}>' was renamed in feat-20260614 M1; use 'unique<${tag}>' instead. The brand and storage layout are unchanged; the dispatch still routes through UniqueRefStore (single-holder direct release).`;
108
+ }
109
+ super(
110
+ `Schema field "${fieldName}" has unsupported type "${fieldType}".\n` +
111
+ ` field: ${fieldName}\n` +
112
+ ` type: ${fieldType}\n` +
113
+ ` hint: ${hint}`,
114
+ );
115
+ this.hint = hint;
116
+ }
117
+ }
118
+
119
+ export class SparseStorageRequiresTagError extends Error {
120
+ override readonly name = 'SparseStorageRequiresTagError';
121
+ readonly code = 'sparse-storage-requires-tag' as const;
122
+ readonly expected = 'sparse components have an empty schema and no relationship metadata';
123
+ readonly hint = 'remove all fields and relationship metadata, or use storage: table';
124
+ readonly detail: { readonly componentName: string };
125
+
126
+ constructor(componentName: string) {
127
+ super(
128
+ `Sparse component "${componentName}" must be a zero-field, non-relationship tag.\n` +
129
+ ` component: ${componentName}\n` +
130
+ ` hint: remove all fields and relationship metadata, or use storage: table`,
131
+ );
132
+ this.detail = { componentName };
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Thrown (or returned via the `Result` err branch — `r.ok === false`, `r.error`)
138
+ * when get/set/addComponent/removeComponent is called on an entity that has
139
+ * been despawned (stale handle).
140
+ *
141
+ * `.code = 'stale-entity'`
142
+ * `.hint` — includes operation name, expected/actual generation, and component name.
143
+ * Enhanced fields: `.component`, `.operation`, `.expectedGeneration`, `.actualGeneration`.
144
+ */
145
+ export class StaleEntityError extends Error {
146
+ override readonly name = 'StaleEntityError';
147
+ readonly code = 'stale-entity' as const;
148
+ readonly hint: string;
149
+
150
+ /** Component name involved in the operation (undefined when the operation does not target a specific component). */
151
+ readonly component: string | undefined;
152
+ /** The component-level operation that triggered this error (e.g. 'get' / 'set' / 'add' / 'remove'). Entity-level operations like `despawn` are not surfaced here — see `EntityHandle` lifecycle errors. */
153
+ readonly operation: string | undefined;
154
+ /** The generation the caller expected (from the entity handle). */
155
+ readonly expectedGeneration: number | undefined;
156
+ /**
157
+ * The actual generation found in the entity pool. `-1` is the sentinel
158
+ * value for entities never allocated (slot was never occupied), as opposed
159
+ * to allocated-then-despawned entities which carry a real (incremented)
160
+ * generation number.
161
+ */
162
+ readonly actualGeneration: number | undefined;
163
+
164
+ constructor(
165
+ entityId: number,
166
+ index: number,
167
+ generation: number,
168
+ enhanced?: {
169
+ component?: string;
170
+ operation: string;
171
+ expectedGeneration: number;
172
+ actualGeneration: number;
173
+ },
174
+ ) {
175
+ const hint = enhanced
176
+ ? `Entity was despawned. Operation "${enhanced.operation}" on entity ${entityId}` +
177
+ (enhanced.component ? ` (component: ${enhanced.component})` : '') +
178
+ ` expected generation ${enhanced.expectedGeneration}, found ${enhanced.actualGeneration}.` +
179
+ ' Check entity lifecycle before access.'
180
+ : 'Entity was despawned. Check entity lifecycle before access.';
181
+ super(
182
+ `Operation on stale entity handle.\n` +
183
+ ` entity: ${entityId} (index=${index}, generation=${generation})\n` +
184
+ (enhanced ? ` operation: ${enhanced.operation}\n` : '') +
185
+ (enhanced?.component ? ` component: ${enhanced.component}\n` : '') +
186
+ ` hint: ${hint}`,
187
+ );
188
+ this.hint = hint;
189
+ this.component = enhanced?.component;
190
+ this.operation = enhanced?.operation;
191
+ this.expectedGeneration = enhanced?.expectedGeneration;
192
+ this.actualGeneration = enhanced?.actualGeneration;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Returned via the `Result` err branch (`r.ok === false`, `r.error`) when
198
+ * addComponent tries to add a component that the entity already possesses.
199
+ *
200
+ * `.code = 'component-already-present'`
201
+ * `.hint` — suggests using `set()` to update values instead.
202
+ */
203
+ export class ComponentAlreadyPresentError extends Error {
204
+ override readonly name = 'ComponentAlreadyPresentError';
205
+ readonly code = 'component-already-present' as const;
206
+ readonly hint: string;
207
+
208
+ constructor(entityId: number, componentName: string) {
209
+ const hint = 'Entity already has this component. Use set() to update values.';
210
+ super(
211
+ `Entity ${entityId} already has component "${componentName}".\n` +
212
+ ` entity: ${entityId}\n` +
213
+ ` component: ${componentName}\n` +
214
+ ` hint: ${hint}`,
215
+ );
216
+ this.hint = hint;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Returned via the `Result` err branch (`r.ok === false`, `r.error`) when
222
+ * removeComponent / set is called for a component the entity does not possess.
223
+ *
224
+ * `.code = 'component-not-present'`
225
+ * `.hint` — suggests checking with query or inspect().
226
+ */
227
+ export class ComponentNotPresentError extends Error {
228
+ override readonly name = 'ComponentNotPresentError';
229
+ readonly code = 'component-not-present' as const;
230
+ readonly hint: string;
231
+
232
+ constructor(entityId: number, componentName: string) {
233
+ const hint = 'Entity does not have this component. Check with query or inspect().';
234
+ super(
235
+ `Entity ${entityId} does not have component "${componentName}".\n` +
236
+ ` entity: ${entityId}\n` +
237
+ ` component: ${componentName}\n` +
238
+ ` hint: ${hint}`,
239
+ );
240
+ this.hint = hint;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Thrown when DAG Schedule detects a cyclic dependency among systems.
246
+ *
247
+ * `.code = 'cyclic-dependency'`
248
+ * `.hint` — includes the cycle path and suggests removing one constraint.
249
+ * `.detail` — `{ code: 'cyclic-dependency'; cycle: readonly string[] }` contains the
250
+ * structured cycle path for programmatic consumption.
251
+ */
252
+ export class CyclicDependencyError extends Error {
253
+ override readonly name = 'CyclicDependencyError';
254
+ readonly code = 'cyclic-dependency' as const;
255
+ readonly expected = 'the schedule dependency graph is acyclic';
256
+ readonly hint: string;
257
+ /** Structured cycle path — programmatic consumers read this, not the message. */
258
+ readonly detail: { readonly code: 'cyclic-dependency'; readonly cycle: readonly string[] };
259
+
260
+ constructor(cycle: readonly string[]) {
261
+ const cycleStr = cycle.join(' -> ');
262
+ const hint = `Cycle path: ${cycleStr}. Remove one ordering constraint to break the cycle.`;
263
+ super(`DAG Schedule has a cyclic dependency.\n cycle: ${cycleStr}\n hint: ${hint}`);
264
+ this.hint = hint;
265
+ this.detail = { code: 'cyclic-dependency' as const, cycle };
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Returned via `Result.err` from `world.addSystems`
271
+ * when a SystemSet token fails local structural validation.
272
+ *
273
+ * The sole public invalid-token error type (D-2a). Covers all rejection
274
+ * scenarios: plain-object cast, unregistered name, stale token after
275
+ * overwrite, and cross-realm copies.
276
+ *
277
+ * `.code = 'system-set-not-registered'`
278
+ * `.expected` — the name of the unregistered token.
279
+ * `.hint` — suggests passing the current token to the owning World schedule.
280
+ * `.detail` — `{ code, name, registered }` where `registered` is a deterministic snapshot
281
+ * of the current World-local schedule keys.
282
+ */
283
+ export class SystemSetNotRegisteredError extends Error {
284
+ override readonly name = 'SystemSetNotRegisteredError';
285
+ readonly code = 'system-set-not-registered' as const;
286
+ /** The name carried by the rejected token. */
287
+ readonly expected: string;
288
+ readonly hint: string;
289
+ /** Deterministic snapshot of the current World-local schedule for repair. */
290
+ readonly detail: {
291
+ readonly code: 'system-set-not-registered';
292
+ readonly name: string;
293
+ readonly registered: readonly string[];
294
+ };
295
+
296
+ constructor(name: string, registered: readonly string[]) {
297
+ const hint =
298
+ `SystemSet "${name}" is not valid for the current World schedule. ` +
299
+ `Pass a non-empty SystemSet token owned by this World schedule.`;
300
+ const message =
301
+ `SystemSet "${name}" is not registered.\n` +
302
+ ` expected: ${name}\n` +
303
+ ` registered: [${registered.join(', ')}]\n` +
304
+ ` hint: ${hint}`;
305
+ super(message);
306
+ this.expected = name;
307
+ this.hint = hint;
308
+ this.detail = { code: 'system-set-not-registered' as const, name, registered };
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Factory for {@link SystemSetNotRegisteredError}. Consumed by
314
+ * {@link validateSystemSetTokens} (w4) and the two mutation entry points.
315
+ */
316
+ export function systemSetNotRegistered(
317
+ name: string,
318
+ registered: readonly string[],
319
+ ): SystemSetNotRegisteredError {
320
+ return new SystemSetNotRegisteredError(name, registered);
321
+ }
322
+
323
+ /**
324
+ * Closed-set ScheduleMutationError code union (M2 — plan-strategy D-3).
325
+ *
326
+ * Schedule add-only API (`removeSystem` / `replaceSystem`) returns
327
+ * `Result<void, ScheduleMutationError>` carrying one of these codes. The
328
+ * `@forgeax/engine-remote` layer (M3) bridges these strings to JSON-RPC
329
+ * `RemoteErrorCode` 1:1 — keeping ECS free of console / wire dependencies.
330
+ *
331
+ * - `system-before-unknown` — name argument does not match any registered system.
332
+ * - `system-name-conflict` — reserved for the M3 inject path; surfaced from
333
+ * schedule when callers inject a name that already exists.
334
+ * - `cyclic-injection` — schedule build detected a cycle introduced by
335
+ * the mutation; carries the cycle path in `.detail.cycle`.
336
+ */
337
+ export type ScheduleMutationErrorCode = 'system-before-unknown';
338
+
339
+ export interface ScheduleMutationErrorDetail {
340
+ readonly cycle?: readonly string[];
341
+ readonly candidates?: readonly string[];
342
+ }
343
+
344
+ /**
345
+ * Returned via `Result.err` from `world.removeSystem` / `world.replaceSystem`.
346
+ *
347
+ * `.code` is the closed-set string SSOT consumed by the M3 console bridge;
348
+ * `.hint` carries an AI-friendly self-repair suggestion; `.detail` is the
349
+ * discriminated payload (cycle path for `cyclic-injection`, candidate list
350
+ * for `system-before-unknown`).
351
+ */
352
+ export class ScheduleMutationError extends Error {
353
+ override readonly name = 'ScheduleMutationError';
354
+ readonly code: ScheduleMutationErrorCode;
355
+ readonly hint: string;
356
+ readonly detail: ScheduleMutationErrorDetail;
357
+
358
+ constructor(
359
+ code: ScheduleMutationErrorCode,
360
+ message: string,
361
+ hint: string,
362
+ detail: ScheduleMutationErrorDetail = {},
363
+ ) {
364
+ super(`${message}\n code: ${code}\n hint: ${hint}`);
365
+ this.code = code;
366
+ this.hint = hint;
367
+ this.detail = detail;
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Thrown when getResource is called with a key that does not exist.
373
+ *
374
+ * `.code = 'resource-not-found'`
375
+ * `.hint` — suggests using `world.insertResource()` first.
376
+ */
377
+ export class ResourceNotFoundError extends Error {
378
+ override readonly name = 'ResourceNotFoundError';
379
+ readonly code = 'resource-not-found' as const;
380
+ readonly hint: string;
381
+
382
+ constructor(key: string) {
383
+ const hint = `Resource "${key}" not found. Insert with world.insertResource() first.`;
384
+ super(`Resource "${key}" not found.\n` + ` key: ${key}\n` + ` hint: ${hint}`);
385
+ this.hint = hint;
386
+ }
387
+ }
388
+
389
+ export class ChangeEpochExhaustedError extends Error {
390
+ override readonly name = 'ChangeEpochExhaustedError';
391
+ readonly code = 'change-epoch-exhausted' as const;
392
+ readonly expected = 'mutationEpoch < Number.MAX_SAFE_INTEGER';
393
+ readonly hint = 'Rebuild the World before performing another mutation.';
394
+ readonly detail: { readonly epoch: number };
395
+
396
+ constructor(epoch: number) {
397
+ super(`World mutation epoch is exhausted at ${epoch}.\n hint: Rebuild the World.`);
398
+ this.detail = { epoch };
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Thrown when insertResource/removeResource is called on a World-owned
404
+ * protected resource (Time or FixedTime).
405
+ *
406
+ * `.code = 'resource-protected'`
407
+ * `.hint` — suggests using `world.update(delta)` or reading the resource.
408
+ * `.expected` — the resource name that was rejected.
409
+ */
410
+ export class ProtectedResourceError extends Error {
411
+ override readonly name = 'ProtectedResourceError';
412
+ readonly code = 'resource-protected' as const;
413
+ readonly hint: string;
414
+ readonly expected: string;
415
+
416
+ constructor(resourceName: string, operation: 'insert' | 'remove') {
417
+ const hint =
418
+ operation === 'insert'
419
+ ? `"${resourceName}" is a World-owned protected resource. It is advanced by world.update(delta); read it via world.getResource(${resourceName}).`
420
+ : `"${resourceName}" is a World-owned protected resource. It is owned by the World scheduler and cannot be removed.`;
421
+ const expected = `a user-owned resource key (not ${resourceName})`;
422
+ super(
423
+ `Protected resource "${resourceName}" cannot be ${operation}ed.\n` +
424
+ ` code: resource-protected\n` +
425
+ ` resource: ${resourceName}\n` +
426
+ ` expected: ${expected}\n` +
427
+ ` hint: ${hint}`,
428
+ );
429
+ this.hint = hint;
430
+ this.expected = expected;
431
+ }
432
+ }
433
+
434
+ // ────────────────────────────────────────────────────────────────────────────
435
+ // w5 — managed-* closed-union extension (M0).
436
+ //
437
+ // Four error classes covering the managed-* family:
438
+ //
439
+ // managed-* : UniqueRefStore + BufferPool runtime fail-fast.
440
+ // Returned via Result.err from M1 / M2 storage paths.
441
+ //
442
+ // `.code` uses lowercase-kebab literals consistent with `ScheduleMutationErrorCode`
443
+ // (the prior closed-set convention). The 9 legacy errors keep their
444
+ // SCREAMING_SNAKE_CASE codes — codes are append-only per the evolution contract.
445
+ // `EcsErrorCode` (declared at the foot of this file) merges all literal codes
446
+ // into one closed union; downstream `switch (err.code)` is exhaustive.
447
+ //
448
+ // Every detail object is a discriminated payload — narrowed per `.code` via
449
+ // `EcsErrorDetail` (also at foot of file).
450
+ // ────────────────────────────────────────────────────────────────────────────
451
+
452
+ /**
453
+ * Thrown / returned via `Result.err` when UniqueRefStore.alloc encounters a
454
+ * slot whose refcount has already dropped to zero (sentinel for a released
455
+ * slot reused without re-init).
456
+ *
457
+ * `.code = 'unique-ref-released'`
458
+ * `.detail = { handle, target }`
459
+ * `.hint` — recommends checking handle lifetime against owner despawn.
460
+ */
461
+ export class UniqueRefReleasedError extends Error {
462
+ override readonly name = 'UniqueRefReleasedError';
463
+ readonly code = 'unique-ref-released' as const;
464
+ readonly hint: string;
465
+ readonly expected: string;
466
+ readonly detail: { readonly handle: number; readonly target: string };
467
+
468
+ constructor(handle: number, target: string) {
469
+ const hint = `Handle ${handle} (target ${target}) was released before this access. Re-acquire via the producing system or re-spawn the asset before reading.`;
470
+ const expected = 'live (refcount >= 1) managed handle';
471
+ super(
472
+ `UniqueRefStore: handle is already released.\n` +
473
+ ` code: unique-ref-released\n` +
474
+ ` handle: ${handle}\n` +
475
+ ` target: ${target}\n` +
476
+ ` expected: ${expected}\n` +
477
+ ` hint: ${hint}`,
478
+ );
479
+ this.hint = hint;
480
+ this.expected = expected;
481
+ this.detail = { handle, target };
482
+ }
483
+ }
484
+
485
+ /**
486
+ * Thrown / returned via `Result.err` when UniqueRefStore.release is called on
487
+ * a handle whose refcount is already zero (double-free).
488
+ *
489
+ * `.code = 'unique-ref-double-release'`
490
+ * `.detail = { handle, target }`
491
+ * `.hint` — recommends auditing the release-loop entry points (set / removeComponent / despawn).
492
+ */
493
+ export class UniqueRefDoubleReleaseError extends Error {
494
+ override readonly name = 'UniqueRefDoubleReleaseError';
495
+ readonly code = 'unique-ref-double-release' as const;
496
+ readonly hint: string;
497
+ readonly expected: string;
498
+ readonly detail: { readonly handle: number; readonly target: string };
499
+
500
+ constructor(handle: number, target: string) {
501
+ const hint = `Handle ${handle} (target ${target}) was released twice. Only one of {despawn / removeComponent / set} should release a managed handle per lifecycle.`;
502
+ const expected = 'first release of a managed handle (refcount transition 1 -> 0)';
503
+ super(
504
+ `UniqueRefStore: double release on handle.\n` +
505
+ ` code: unique-ref-double-release\n` +
506
+ ` handle: ${handle}\n` +
507
+ ` target: ${target}\n` +
508
+ ` expected: ${expected}\n` +
509
+ ` hint: ${hint}`,
510
+ );
511
+ this.hint = hint;
512
+ this.expected = expected;
513
+ this.detail = { handle, target };
514
+ }
515
+ }
516
+
517
+ // ────────────────────────────────────────────────────────────────────────────
518
+ // feat-20260614-ecs-shared-component-and-unique-rename M3 — SharedRefStore
519
+ // closed-union extension (+2). Mirrors the UniqueRef* pair — `'shared-ref-released'`
520
+ // covers resolve-after-release / retain-after-release; `'shared-ref-double-release'`
521
+ // covers release-when-rc-already-zero. Both are Result.err returns (not throws);
522
+ // AI users branch on `.code` and read `.detail.handle` for the offending slot.
523
+ //
524
+ // Detail field shape mirrors UniqueRef* with one addition (`rc`) so AI users
525
+ // debugging a double-release see the exact rc transition that surfaced the
526
+ // failure (charter P3 progressive disclosure). Empty `target` handled the
527
+ // same way as UniqueRef* — runtime-erased phantom, surfaced as '<unknown>'.
528
+ // ────────────────────────────────────────────────────────────────────────────
529
+
530
+ /**
531
+ * Thrown / returned via `Result.err` when SharedRefStore.resolve / .retain is
532
+ * called on a handle whose refcount has already dropped to zero (slot released).
533
+ *
534
+ * `.code = 'shared-ref-released'`
535
+ * `.detail = { handle, target }`
536
+ * `.hint` — recommends checking handle lifetime against owner / consumer release.
537
+ */
538
+ export class SharedRefReleasedError extends Error {
539
+ override readonly name = 'SharedRefReleasedError';
540
+ readonly code = 'shared-ref-released' as const;
541
+ readonly hint: string;
542
+ readonly expected: string;
543
+ readonly detail: { readonly handle: number; readonly target: string };
544
+
545
+ constructor(handle: number, target: string) {
546
+ const hint = `Handle ${handle} (target ${target}) was released (refcount reached 0). Re-acquire via the producing system or re-spawn the asset before reading.`;
547
+ const expected = 'live (refcount >= 1) shared handle';
548
+ super(
549
+ `SharedRefStore: handle is already released.\n` +
550
+ ` code: shared-ref-released\n` +
551
+ ` handle: ${handle}\n` +
552
+ ` target: ${target}\n` +
553
+ ` expected: ${expected}\n` +
554
+ ` hint: ${hint}`,
555
+ );
556
+ this.hint = hint;
557
+ this.expected = expected;
558
+ this.detail = { handle, target };
559
+ }
560
+ }
561
+
562
+ /**
563
+ * Thrown / returned via `Result.err` when SharedRefStore.release is called on
564
+ * a handle whose refcount is already zero (double-release). Distinct from the
565
+ * UniqueRef family because shared release is rc--, not direct slot drop —
566
+ * AI users debug this by reading `.detail.rc` (always 0 here) alongside the
567
+ * payload-presence signal.
568
+ *
569
+ * `.code = 'shared-ref-double-release'`
570
+ * `.detail = { handle, target, rc }`
571
+ * `.hint` — recommends auditing the producer / consumer release pairs.
572
+ */
573
+ export class SharedRefDoubleReleaseError extends Error {
574
+ override readonly name = 'SharedRefDoubleReleaseError';
575
+ readonly code = 'shared-ref-double-release' as const;
576
+ readonly hint: string;
577
+ readonly expected: string;
578
+ readonly detail: { readonly handle: number; readonly target: string; readonly rc: number };
579
+
580
+ constructor(handle: number, target: string, rc: number) {
581
+ const hint = `Handle ${handle} (target ${target}) released with rc=${rc}. Each shared handle must have a matching alloc/retain for every release; audit the producer / consumer release pairs.`;
582
+ const expected = 'rc >= 1 before release';
583
+ super(
584
+ `SharedRefStore: double release on handle.\n` +
585
+ ` code: shared-ref-double-release\n` +
586
+ ` handle: ${handle}\n` +
587
+ ` target: ${target}\n` +
588
+ ` rc: ${rc}\n` +
589
+ ` expected: ${expected}\n` +
590
+ ` hint: ${hint}`,
591
+ );
592
+ this.hint = hint;
593
+ this.expected = expected;
594
+ this.detail = { handle, target, rc };
595
+ }
596
+ }
597
+
598
+ /** Raised before SharedRefStore mutates slot state for a nullish payload. */
599
+ export class SharedRefPayloadInvalidError extends Error {
600
+ override readonly name = 'SharedRefPayloadInvalidError';
601
+ readonly code = 'shared-ref-payload-invalid' as const;
602
+ readonly expected = 'a non-null, non-undefined shared payload';
603
+ readonly hint = 'Allocate a concrete payload and let its owning effect dispose it.';
604
+ readonly detail: { readonly target: string; readonly actual: 'null' | 'undefined' };
605
+
606
+ constructor(target: string, actual: 'null' | 'undefined') {
607
+ super(`SharedRefStore: ${actual} payload is not a valid shared reference for ${target}.`);
608
+ this.detail = { target, actual };
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Returned via `Result.err` when a builtin-tier slot (`slot < BUILTIN_BASE`)
614
+ * is passed to SharedRefStore.alloc / retain / release / resolve
615
+ * (feat-20260614 M6 D-15). The SharedRefStore manages ONLY user-tier slots
616
+ * (`>= BUILTIN_BASE`); builtin asset payloads are process-static and owned by
617
+ * the package that authored their builtin handle, never reference-counted by
618
+ * this World store.
619
+ *
620
+ * `.code = 'builtin-slot-not-owned'`
621
+ * `.detail = { slot }`
622
+ * `.hint` — points the caller back to the builtin handle owner.
623
+ */
624
+ export class BuiltinSlotNotOwnedError extends Error {
625
+ override readonly name = 'BuiltinSlotNotOwnedError';
626
+ readonly code = 'builtin-slot-not-owned' as const;
627
+ readonly hint: string;
628
+ readonly expected: string;
629
+ readonly detail: { readonly slot: number };
630
+
631
+ constructor(slot: number) {
632
+ const hint = `Slot ${slot} is a builtin-tier handle (< BUILTIN_BASE). World.sharedRefs manages only user-tier handles (>= BUILTIN_BASE). Obtain the builtin payload from the package that authored the handle; builtin payloads are process-static and never reference-counted by this World.`;
633
+ const expected = 'user-tier slot (>= BUILTIN_BASE)';
634
+ super(
635
+ `SharedRefStore: builtin slot is not owned by this store.\n` +
636
+ ` code: builtin-slot-not-owned\n` +
637
+ ` slot: ${slot}\n` +
638
+ ` expected: ${expected}\n` +
639
+ ` hint: ${hint}`,
640
+ );
641
+ this.hint = hint;
642
+ this.expected = expected;
643
+ this.detail = { slot };
644
+ }
645
+ }
646
+
647
+ // ────────────────────────────────────────────────────────────────────────────
648
+ // feat-20260623-asset-handle-generation M4 — stale error classes (+2).
649
+ //
650
+ // Two error classes covering gen-based staleness detection in SharedRefStore
651
+ // and UniqueRefStore. Distinguish from the existing `*-ref-released` codes
652
+ // (slot empty / never allocated) — `*-ref-stale` means the slot has been
653
+ // released AND re-allocated, so the caller's handle generation no longer
654
+ // matches the store's current generation. AI users pick different recovery
655
+ // strategies: released -> re-load the asset; stale -> re-acquire the handle
656
+ // from AssetRegistry (charter P3 explicit failure, two semantics two
657
+ // recovery paths).
658
+ //
659
+ // `.detail` carries { slot, expectedGeneration, actualGeneration } aligned
660
+ // with StaleEntityError field names (AC-11). Codes are add-only minor
661
+ // members of EcsErrorCode (AC-10) and detail shapes extend EcsErrorDetail
662
+ // discriminator.
663
+ // ────────────────────────────────────────────────────────────────────────────
664
+
665
+ /**
666
+ * Returned via `Result.err` when SharedRefStore.resolve / .retain / .release
667
+ * is called with a handle whose generation no longer matches the store's
668
+ * current generation for that slot — the slot was released and re-allocated
669
+ * to a different payload. Distinct from `'shared-ref-released'` (slot empty,
670
+ * never re-allocated): stale means the slot IS live but belongs to a newer
671
+ * allocation.
672
+ *
673
+ * `.code = 'shared-ref-stale'`
674
+ * `.detail = { slot, expectedGeneration, actualGeneration }`
675
+ * `.hint` — recommends re-acquiring the handle from AssetRegistry.
676
+ */
677
+ export class SharedRefStaleError extends Error {
678
+ override readonly name = 'SharedRefStaleError';
679
+ readonly code = 'shared-ref-stale' as const;
680
+ readonly hint: string;
681
+ readonly expected: string;
682
+ readonly detail: {
683
+ readonly slot: number;
684
+ readonly expectedGeneration: number;
685
+ readonly actualGeneration: number;
686
+ };
687
+
688
+ constructor(slot: number, expectedGeneration: number, actualGeneration: number) {
689
+ const hint = `Handle for slot ${slot} is stale: expected generation ${expectedGeneration}, but the store has generation ${actualGeneration} (slot was released and re-allocated). Re-acquire the handle from AssetRegistry.`;
690
+ const expected = `generation === ${actualGeneration} (current store generation)`;
691
+ super(
692
+ `SharedRefStore: stale handle.\n` +
693
+ ` code: shared-ref-stale\n` +
694
+ ` slot: ${slot}\n` +
695
+ ` expectedGeneration: ${expectedGeneration}\n` +
696
+ ` actualGeneration: ${actualGeneration}\n` +
697
+ ` expected: ${expected}\n` +
698
+ ` hint: ${hint}`,
699
+ );
700
+ this.hint = hint;
701
+ this.expected = expected;
702
+ this.detail = { slot, expectedGeneration, actualGeneration };
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Returned via `Result.err` when UniqueRefStore.resolve / .release is
708
+ * called with a handle whose generation no longer matches the store's
709
+ * current generation for that slot — the slot was released and re-allocated.
710
+ * Distinct from `'unique-ref-released'` (slot empty). UniqueRefStore has
711
+ * no retain method; the stale surface is resolve + release only.
712
+ *
713
+ * `.code = 'unique-ref-stale'`
714
+ * `.detail = { slot, expectedGeneration, actualGeneration }`
715
+ * `.hint` — recommends re-acquiring the handle from the producing system.
716
+ */
717
+ export class UniqueRefStaleError extends Error {
718
+ override readonly name = 'UniqueRefStaleError';
719
+ readonly code = 'unique-ref-stale' as const;
720
+ readonly hint: string;
721
+ readonly expected: string;
722
+ readonly detail: {
723
+ readonly slot: number;
724
+ readonly expectedGeneration: number;
725
+ readonly actualGeneration: number;
726
+ };
727
+
728
+ constructor(slot: number, expectedGeneration: number, actualGeneration: number) {
729
+ const hint = `Handle for slot ${slot} is stale: expected generation ${expectedGeneration}, but the store has generation ${actualGeneration} (slot was released and re-allocated). Re-acquire the handle via the producing system or re-spawn the asset.`;
730
+ const expected = `generation === ${actualGeneration} (current store generation)`;
731
+ super(
732
+ `UniqueRefStore: stale handle.\n` +
733
+ ` code: unique-ref-stale\n` +
734
+ ` slot: ${slot}\n` +
735
+ ` expectedGeneration: ${expectedGeneration}\n` +
736
+ ` actualGeneration: ${actualGeneration}\n` +
737
+ ` expected: ${expected}\n` +
738
+ ` hint: ${hint}`,
739
+ );
740
+ this.hint = hint;
741
+ this.expected = expected;
742
+ this.detail = { slot, expectedGeneration, actualGeneration };
743
+ }
744
+ }
745
+
746
+ /**
747
+ * Thrown / returned via `Result.err` when BufferPool indexing reads or writes
748
+ * an offset outside the slot's `[0, size)` byte range. Triggers are limited
749
+ * to the `buffer:<N>` and managed-array-element-buffer paths; the
750
+ * `'string'` schema vocab no longer routes through this code (collapsed onto
751
+ * the managed-ref dispatch by feat-20260515-string-managed-collapse — JS
752
+ * string capacity is bounded by the host runtime, not by BufferPool buckets).
753
+ *
754
+ * `.code = 'managed-buffer-out-of-bounds'`
755
+ * `.detail = { index, size }`
756
+ * `.hint` — points at the field's `'buffer'` / `buffer<N>` schema declaration.
757
+ */
758
+ export class ManagedBufferOutOfBoundsError extends RangeError {
759
+ override readonly name = 'ManagedBufferOutOfBoundsError';
760
+ readonly code = 'managed-buffer-out-of-bounds' as const;
761
+ readonly hint: string;
762
+ readonly expected: string;
763
+ readonly detail: { readonly index: number; readonly size: number };
764
+
765
+ constructor(index: number, size: number) {
766
+ const hint = `Index ${index} is outside [0, ${size}). Check the field's 'buffer' / 'buffer<N>' declaration matches the access pattern.`;
767
+ const expected = `index in [0, ${size})`;
768
+ super(
769
+ `BufferPool: index out of bounds.\n` +
770
+ ` code: managed-buffer-out-of-bounds\n` +
771
+ ` index: ${index}\n` +
772
+ ` size: ${size}\n` +
773
+ ` expected: ${expected}\n` +
774
+ ` hint: ${hint}`,
775
+ );
776
+ this.hint = hint;
777
+ this.expected = expected;
778
+ this.detail = { index, size };
779
+ }
780
+ }
781
+
782
+ /**
783
+ * Thrown / returned via `Result.err` when BufferPool resize is asked to shrink
784
+ * a slot below its current allocated size — the pool only grows.
785
+ *
786
+ * `.code = 'managed-buffer-shrink-not-supported'`
787
+ * `.detail = { requested, current }`
788
+ * `.hint` — directs callers to allocate a fresh slot if a smaller buffer is needed.
789
+ */
790
+ export class ManagedBufferShrinkNotSupportedError extends Error {
791
+ override readonly name = 'ManagedBufferShrinkNotSupportedError';
792
+ readonly code = 'managed-buffer-shrink-not-supported' as const;
793
+ readonly hint: string;
794
+ readonly expected: string;
795
+ readonly detail: { readonly requested: number; readonly current: number };
796
+
797
+ constructor(requested: number, current: number) {
798
+ const hint = `BufferPool only grows. Requested ${requested} bytes < current ${current}; allocate a fresh slot if a smaller buffer is required.`;
799
+ const expected = `requested >= ${current}`;
800
+ super(
801
+ `BufferPool: shrink not supported.\n` +
802
+ ` code: managed-buffer-shrink-not-supported\n` +
803
+ ` requested: ${requested}\n` +
804
+ ` current: ${current}\n` +
805
+ ` expected: ${expected}\n` +
806
+ ` hint: ${hint}`,
807
+ );
808
+ this.hint = hint;
809
+ this.expected = expected;
810
+ this.detail = { requested, current };
811
+ }
812
+ }
813
+
814
+ // ────────────────────────────────────────────────────────────────────────────
815
+ // feat-20260515-buffer-array-vocab-collapse w11 — closed-union evolution.
816
+ //
817
+ // 4 managed-array-* error classes deleted (replaced by the 4 collapsed-vocab
818
+ // codes below); ManagedArrayElementTypeNotAllowedError preserved (still
819
+ // surfaced from defineComponent's schema parser).
820
+ //
821
+ // 2 surviving error classes:
822
+ // - FixedSizeMismatchError ('fixed-size-mismatch')
823
+ // - InstanceTransformsStrideMismatchError ('instance-transforms-stride-mismatch')
824
+ //
825
+ // Naming-prefix orthogonality (plan-strategy §2.5):
826
+ // fixed- element-type or capacity contract violations on fixed shape
827
+ // array- operation failures (pop on empty) on the array vocab keyword
828
+ // instance- GPU-render component-specific stride contract (Instances.transforms)
829
+ //
830
+ // The array element-wise facade was removed; only write-shape and render
831
+ // stride errors remain on this boundary. The
832
+ // `instance-transforms-stride-mismatch` member is the plan-strategy §2.4
833
+ // evolution surfaced from `packages/runtime/src/render-system-extract.ts`
834
+ // defensive entry (consumed by w15 in M3, but the error class lives here so
835
+ // the EcsErrorCode union closure is owned by ECS — RhiError is not extended
836
+ // per plan-strategy §2.4 decision).
837
+ // ────────────────────────────────────────────────────────────────────────────
838
+
839
+ /**
840
+ * Returned via `Result.err` from `world.set` when a `buffer<N>` field is
841
+ * written with a `Uint8Array` whose `byteLength` does not equal the
842
+ * schema-declared fixed size `N`. AI users resize their payload to exactly
843
+ * `N` bytes (zero-pad or truncate at the producer) before calling `world.set`.
844
+ *
845
+ * `.code = 'fixed-size-mismatch'`
846
+ * `.detail = { expected, actual }`
847
+ * `.hint` — points at the producer's payload sizing.
848
+ */
849
+ export class FixedSizeMismatchError extends Error {
850
+ override readonly name = 'FixedSizeMismatchError';
851
+ readonly code = 'fixed-size-mismatch' as const;
852
+ readonly hint: string;
853
+ readonly expected: string;
854
+ readonly detail: { readonly expected: number; readonly actual: number };
855
+
856
+ constructor(fieldName: string, expected: number, actual: number) {
857
+ const hint = `buffer<${expected}> set with byteLength ${actual} (expected ${expected}); resize your Uint8Array to exactly ${expected} bytes before world.set`;
858
+ const expectedStr = `byteLength === ${expected}`;
859
+ super(
860
+ `buffer<N>: fixed-size mismatch.\n` +
861
+ ` code: fixed-size-mismatch\n` +
862
+ ` field: ${fieldName}\n` +
863
+ ` expected: ${expected}\n` +
864
+ ` actual: ${actual}\n` +
865
+ ` hint: ${hint}`,
866
+ );
867
+ this.hint = hint;
868
+ this.expected = expectedStr;
869
+ this.detail = { expected, actual };
870
+ }
871
+ }
872
+
873
+ /**
874
+ * Surfaced via the Layer-3 ErrorHandler from
875
+ * `packages/runtime/src/render-system-extract.ts` defensive entry when an
876
+ * `Instances.transforms` array<f32> length violates the column-major mat4
877
+ * stride contract (`length % 16 === 0`). Locates the failure adjacent to the
878
+ * extract pipeline rather than at GPU upload (plan-strategy §2.4 D-P2 +
879
+ * §8.3 hint SSOT).
880
+ *
881
+ * `.code = 'instance-transforms-stride-mismatch'`
882
+ * `.detail = { actualLength, expectedStride: 16 }`
883
+ * `.hint` — names the stride invariant + the call sites to audit.
884
+ */
885
+ export class InstanceTransformsStrideMismatchError extends Error {
886
+ override readonly name = 'InstanceTransformsStrideMismatchError';
887
+ readonly code = 'instance-transforms-stride-mismatch' as const;
888
+ readonly hint: string;
889
+ readonly expected: string;
890
+ readonly detail: { readonly actualLength: number; readonly expectedStride: 16 };
891
+
892
+ constructor(actualLength: number) {
893
+ const hint = `Instances.transforms length ${actualLength} violates stride 16 (mat4); ensure transforms.length % 16 === 0 before render frame; verify world.set / world.push call sites`;
894
+ const expectedStr = 'actualLength % 16 === 0';
895
+ super(
896
+ `Instances.transforms: stride mismatch.\n` +
897
+ ` code: instance-transforms-stride-mismatch\n` +
898
+ ` actualLength: ${actualLength}\n` +
899
+ ` expectedStride: 16\n` +
900
+ ` hint: ${hint}`,
901
+ );
902
+ this.hint = hint;
903
+ this.expected = expectedStr;
904
+ this.detail = { actualLength, expectedStride: 16 };
905
+ }
906
+ }
907
+
908
+ /**
909
+ * Thrown by `defineComponent` when an `array<T>` / `array<T,N>` schema field
910
+ * carries an element type outside the legal whitelist (scalars + entity).
911
+ * Forms like `array<ref<X>>` / `array<handle<X>>` / `array<buffer:N>` /
912
+ * `array<array<...>>` are rejected (AC-03 runtime fail-safe). The TS layer
913
+ * blocks these forms at compile time; this error is the runtime backstop for
914
+ * `as unknown as SchemaFieldType` casts.
915
+ *
916
+ * `.code = 'managed-array-element-type-not-allowed'`
917
+ * `.detail = { fieldName, elementType, hint }`
918
+ * `.hint` — lists the whitelist of legal element types.
919
+ */
920
+ export class ManagedArrayElementTypeNotAllowedError extends Error {
921
+ override readonly name = 'ManagedArrayElementTypeNotAllowedError';
922
+ readonly code = 'managed-array-element-type-not-allowed' as const;
923
+ readonly hint: string;
924
+ readonly expected: string;
925
+ readonly detail: {
926
+ readonly fieldName: string;
927
+ readonly elementType: string;
928
+ readonly hint: string;
929
+ };
930
+
931
+ constructor(fieldName: string, elementType: string) {
932
+ const hint = `array<T> element type must be a scalar (f32/f64/i32/u32/i16/u16/i8/u8/bool/enum/ref) or entity. ref<X> / handle<X> / buffer:N / nested array<...> are forbidden on field "${fieldName}".`;
933
+ const expected = 'element type in {scalar | entity}';
934
+ super(
935
+ `managed-array: element type not allowed.\n` +
936
+ ` code: managed-array-element-type-not-allowed\n` +
937
+ ` field: ${fieldName}\n` +
938
+ ` elementType: ${elementType}\n` +
939
+ ` expected: ${expected}\n` +
940
+ ` hint: ${hint}`,
941
+ );
942
+ this.hint = hint;
943
+ this.expected = expected;
944
+ this.detail = { fieldName, elementType, hint };
945
+ }
946
+ }
947
+
948
+ // ────────────────────────────────────────────────────────────────────────────
949
+ // EcsErrorCode closed union (w5)
950
+ //
951
+ // Merges every `.code` literal across the EcsError family. Downstream
952
+ // `switch (err.code)` blocks become exhaustive; `assertNever(code)` catches
953
+ // any future code addition without a matching case at compile time.
954
+ //
955
+ // Order is grouped (legacy SCREAMING_SNAKE first, then closed-set kebab) but
956
+ // not load-bearing — TS unions are unordered.
957
+ // ────────────────────────────────────────────────────────────────────────────
958
+
959
+ /** Closed union of every `.code` literal carried by EcsError instances. */
960
+ export type EcsErrorCode =
961
+ // Legacy SCREAMING_SNAKE codes (7, carried unchanged; the two
962
+ // registration codes COMPONENT_ALREADY_REGISTERED / COMPONENT_NOT_REGISTERED
963
+ // were dropped by feat-20260602 along with the per-World register concept).
964
+ | 'stale-entity'
965
+ | 'component-already-present'
966
+ | 'component-not-present'
967
+ | 'cyclic-dependency'
968
+ | 'resource-not-found'
969
+ // ECS time and schedule-scope errors (M2 w16, approved 43 -> 46 baseline; verify hotfix +1 → 47).
970
+ | 'time-delta-invalid'
971
+ | 'time-config-invalid'
972
+ | 'schedule-scope-mismatch'
973
+ // ScheduleMutationError closed-set kebab code.
974
+ | ScheduleMutationErrorCode
975
+ // w5 managed-* kebab codes (4).
976
+ | 'unique-ref-released'
977
+ | 'unique-ref-double-release'
978
+ // feat-20260614-ecs-shared-component-and-unique-rename M3 — SharedRefStore
979
+ // closed-union extension (+2). `'shared-ref-released'` covers resolve / retain
980
+ // on rc=0; `'shared-ref-double-release'` covers release on rc=0.
981
+ | 'shared-ref-released'
982
+ | 'shared-ref-double-release'
983
+ | 'shared-ref-payload-invalid'
984
+ // feat-20260614-ecs-shared-component-and-unique-rename M6 D-15 (+1).
985
+ // SharedRefStore manages ONLY user-tier slots (>= BUILTIN_BASE); a builtin
986
+ // slot (< BUILTIN_BASE) passed to alloc/retain/release/resolve is a caller
987
+ // error -> `'builtin-slot-not-owned'` (the authoring package owns the payload).
988
+ | 'builtin-slot-not-owned'
989
+ // feat-20260623-asset-handle-generation M4 — stale error codes (+2).
990
+ // `'shared-ref-stale'` / `'unique-ref-stale'` cover gen mismatch on resolve /
991
+ // retain / release after slot re-allocation. Add-only minor per AGENTS.md
992
+ // Error model evolution contract; distinct from the existing `*-ref-released`
993
+ // codes (slot empty vs slot re-allocated).
994
+ | 'shared-ref-stale'
995
+ | 'unique-ref-stale'
996
+ | 'managed-buffer-out-of-bounds'
997
+ | 'managed-buffer-shrink-not-supported'
998
+ // managed-array-* kebab codes — surviving member from feat-20260514;
999
+ // the other 4 (`managed-array-{index-out-of-bounds, pop-empty,
1000
+ // shrink-not-supported, stride-mismatch}`) were dropped by
1001
+ // feat-20260515-buffer-array-vocab-collapse w11 in favour of the 4 new
1002
+ // collapsed-vocab codes below. Kept here because `defineComponent`'s schema
1003
+ // parser still surfaces it for illegal `array<...>` element types.
1004
+ // feat-20260515-buffer-array-vocab-collapse w11 collapsed-vocab codes (4,
1005
+ // plan-strategy §2.4 + §2.5 four-prefix taxonomy).
1006
+ | 'fixed-size-mismatch'
1007
+ // feat-20260519-light-casters-point-spot-pbr w2 — PointLight / SpotLight
1008
+ // spawn-time payload bound violation (plan-strategy D-S3 a). 23 -> 24
1009
+ // minor evolution per AGENTS.md Error model evolution contract.
1010
+ // feat-20260520-2d-sprite-layer-mvp M-2 w13 — resource-setter bound
1011
+ // validation (plan-strategy D-4). 25 -> 26 minor evolution; first
1012
+ // consumer is `setTransparentSortConfig` (mode ∈ {0, 1, 2}).
1013
+ // feat-20260521-sprite-atlas-animation M1 T-05 — spriteAnimationTickSystem
1014
+ // runtime invariant violation (plan-strategy D-1). 26 -> 27 minor evolution
1015
+ // per AGENTS.md §Error model evolution contract; same-shape mirror of
1016
+ // 'spawn-light-invalid-bounds' (feat-20260519 w2) and 'resource-invalid-
1017
+ // value' (feat-20260520 w13) — the `<noun>-invalid-...` kebab series keeps
1018
+ // switch (err.code) narrows visually consistent for AI users (charter P4).
1019
+ // feat-20260531-ecs-relationship-abstraction-bidirectional-sync M2 —
1020
+ // relationship bidirectional sync + defineComponent relationship validation +
1021
+ // addChild/reparent cycle detection + removeChild detach guard
1022
+ // (plan-strategy D-5). 27 -> 31 minor evolution per AGENTS.md Error model
1023
+ // evolution contract. `relationship-exclusive-violation` is NOT a member:
1024
+ // exclusive re-add is an automatic reparent (success path), not an error.
1025
+ | 'relationship-self-cycle'
1026
+ | 'relationship-detach-mismatch'
1027
+ // feat-20260602-drop-component-registration w16-a — scene instantiate
1028
+ // fail-fast when a SceneAsset entity names a component that was never defined
1029
+ // via defineComponent (the per-World register concept was dropped; a
1030
+ // component becomes globally usable the moment defineComponent runs). 30 ->
1031
+ // 31 minor evolution per AGENTS.md Error model evolution contract. Replaces
1032
+ // the deleted COMPONENT_NOT_REGISTERED code at the scene-instance producer
1033
+ // sites (research Finding 5 missed these 3 producers; human escalation-
1034
+ // response authorized this scope-amendment).
1035
+ // feat-20260602-archetype-stores-full-packed-entity M1 / w3 — removeComponent
1036
+ // rejection when the target is an essential (undeletable) component. The only
1037
+ // essential component is the id=0 `Entity` (plan-strategy D-3). Net +1 minor
1038
+ // evolution per AGENTS.md §Error model evolution contract.
1039
+ | 'remove-essential-component'
1040
+ // feat-20260608-scene-nesting-ecs-fication M1 / w9 — setSceneOverride
1041
+ // type-mismatch fail-fast (plan-strategy D-9). 30 -> 31 minor evolution per
1042
+ // AGENTS.md §Error model evolution contract. Surfaced from
1043
+ // `world.setSceneOverride(root, member, comp, field, value)` when `value`'s
1044
+ // runtime type does not match the per-component schema field type (the
1045
+ // override apply path never silently coerces — value writes are typed at the
1046
+ // ECS layer; requirements §Edge cases table last row, reviewer Issue 1).
1047
+ // bug-20260615-spawn-data-unknown-field-fail-fast — spawn / addComponent /
1048
+ // SceneAsset.instantiate / Commands.spawn fail-fast when the caller-supplied
1049
+ // payload carries a key that is not declared in the component schema. Pre-
1050
+ // fix the unknown key was silently dropped by `fillComponentDefaults`
1051
+ // (which iterated only over schema keys), routing typos like
1052
+ // `MeshRenderer { material }` (singular legacy name) into the empty-default
1053
+ // path and producing invisible / mid-grey entities downstream. AI users
1054
+ // narrow on `.code` then read `.detail.field` for the offending key and
1055
+ // `.detail.knownFields` for the valid field whitelist.
1056
+ | 'spawn-data-unknown-field'
1057
+ // feat-20260625-sprite-instances-and-tilemap-terrain-static-batch M1 / w2 —
1058
+ // SpriteInstances primitive + tilemap terrain static-batch path. Three
1059
+ // codes declared together; fire path lands in M3 w13 at the
1060
+ // render-system-extract QueryRow loop. Minor evolution +3 per
1061
+ // AGENTS.md §Error model evolution contract; plan-strategy D-6 keeps the
1062
+ // detection in the render domain (not the ECS spawn path) to avoid an
1063
+ // ECS -> AssetRegistry reverse dep for the shader-id lookup.
1064
+ // feat-20260713-mount-override-component-add-and-shared-ref-round M2 / w9 —
1065
+ // P3 shared-field value gate. A `shared<T>` scalar or `array<shared<T>>`
1066
+ // element must be a resolved numeric Handle; a raw GUID string / `{ guid }` /
1067
+ // `{ kind }` object (the pre-resolution shape an AI user gets from a sidecar)
1068
+ // was silently coerced to the all-zero sentinel by the column packer
1069
+ // (`typed[i] = typeof val === 'number' ? val : 0`) / scalar write, so a
1070
+ // mis-bound reference read back as `0` / `[0,0,0,0]` and rendered blank with
1071
+ // no error. `validateComponentDataKeys` only checks key names, not value
1072
+ // types — this code closes the value-type gap at all three write entries
1073
+ // (spawn / addComponent / set). AI users resolve a GUID via
1074
+ // `AssetRegistry.load(guid, kind) + allocSharedRef` first; passing the raw GUID now fails fast.
1075
+ // Minor evolution +1 per AGENTS.md §Error model evolution contract.
1076
+ | 'shared-field-invalid-value'
1077
+ // feat-20260714-bevy-style-system-sets M1 / w3 — sole invalid-SystemSet
1078
+ // error code. Surfaced from world.addSystems when a
1079
+ // token fails identity validation (brand bypass + registry identity check).
1080
+ // Minor evolution +1 per AGENTS.md §Error model evolution contract.
1081
+ | 'system-set-not-registered'
1082
+ // Closed enum field writes fail before archetype or column mutation.
1083
+ | 'component-field-invalid-value'
1084
+ | 'component-numeric-value-invalid'
1085
+ | 'managed-array-invalid-value'
1086
+ | 'shared-kernel-ineligible'
1087
+ | 'shared-kernel-failed'
1088
+ | 'world-poisoned'
1089
+ // World.update terminal failures. These remain in the same closed union as
1090
+ // structural errors so consumers never need a second error discriminator.
1091
+ | 'command-failed'
1092
+ | 'system-failed';
1093
+
1094
+ /**
1095
+ * Discriminated `.detail` payload per `.code`.
1096
+ *
1097
+ * Narrowed via `switch (err.code)` against `EcsErrorCode`. Empty-detail entries
1098
+ * (legacy errors without `.detail`) are intentionally omitted from this map —
1099
+ * only the w5 family + `cyclic-injection` carry structured payloads today.
1100
+ */
1101
+ export type EcsErrorDetail =
1102
+ | {
1103
+ readonly code: 'shared-kernel-ineligible';
1104
+ readonly kernelName: string;
1105
+ readonly reason: string;
1106
+ }
1107
+ | {
1108
+ readonly code: 'shared-kernel-failed';
1109
+ readonly kernelName: string;
1110
+ readonly worldIdentity: string;
1111
+ readonly cause: unknown;
1112
+ readonly partialWrite: boolean;
1113
+ readonly retryable: false;
1114
+ }
1115
+ | { readonly code: 'world-poisoned'; readonly worldIdentity: string; readonly fault: unknown }
1116
+ | { readonly code: 'sparse-storage-requires-tag'; readonly componentName: string }
1117
+ | {
1118
+ readonly code: 'query-descriptor-conflict';
1119
+ readonly componentName: string;
1120
+ readonly roles: readonly string[];
1121
+ }
1122
+ | { readonly code: 'query-data-requires-fields'; readonly componentName: string }
1123
+ | { readonly code: 'query-span-unavailable'; readonly reason: QuerySpanUnavailableReason }
1124
+ | {
1125
+ readonly code: 'query-iteration-invalidated';
1126
+ readonly expectedStructureEpoch: number;
1127
+ readonly actualStructureEpoch: number;
1128
+ }
1129
+ | { readonly code: 'query-iteration-active' }
1130
+ | { readonly code: 'change-epoch-exhausted'; readonly epoch: number }
1131
+ | { readonly code: 'unique-ref-released'; readonly handle: number; readonly target: string }
1132
+ | {
1133
+ readonly code: 'unique-ref-double-release';
1134
+ readonly handle: number;
1135
+ readonly target: string;
1136
+ }
1137
+ // feat-20260614 M3 — SharedRefStore detail variants (+2).
1138
+ | { readonly code: 'shared-ref-released'; readonly handle: number; readonly target: string }
1139
+ | {
1140
+ readonly code: 'shared-ref-double-release';
1141
+ readonly handle: number;
1142
+ readonly target: string;
1143
+ readonly rc: number;
1144
+ }
1145
+ | {
1146
+ readonly code: 'shared-ref-payload-invalid';
1147
+ readonly target: string;
1148
+ readonly actual: 'null' | 'undefined';
1149
+ }
1150
+ // feat-20260614 M6 D-15 — builtin-slot fail-fast detail variant (+1).
1151
+ | { readonly code: 'builtin-slot-not-owned'; readonly slot: number }
1152
+ // feat-20260623-asset-handle-generation M4 — stale error detail variants (+2).
1153
+ | {
1154
+ readonly code: 'shared-ref-stale';
1155
+ readonly slot: number;
1156
+ readonly expectedGeneration: number;
1157
+ readonly actualGeneration: number;
1158
+ }
1159
+ | {
1160
+ readonly code: 'unique-ref-stale';
1161
+ readonly slot: number;
1162
+ readonly expectedGeneration: number;
1163
+ readonly actualGeneration: number;
1164
+ }
1165
+ | { readonly code: 'managed-buffer-out-of-bounds'; readonly index: number; readonly size: number }
1166
+ | {
1167
+ readonly code: 'managed-buffer-shrink-not-supported';
1168
+ readonly requested: number;
1169
+ readonly current: number;
1170
+ }
1171
+ // feat-20260514 surviving managed-array-* discriminated detail variant (1).
1172
+ | {
1173
+ readonly code: 'managed-array-element-type-not-allowed';
1174
+ readonly fieldName: string;
1175
+ readonly elementType: string;
1176
+ readonly hint: string;
1177
+ }
1178
+ // feat-20260515-buffer-array-vocab-collapse w11 collapsed-vocab detail
1179
+ // variants (4). Per-code field names are SSOT-anchored at AC-07 + plan-
1180
+ // strategy §2.4 §detail-list (NOT renamed for "consistency" — name follows
1181
+ // semantics).
1182
+ | {
1183
+ readonly code: 'fixed-size-mismatch';
1184
+ readonly expected: number;
1185
+ readonly actual: number;
1186
+ }
1187
+ | {
1188
+ readonly code: 'instance-transforms-stride-mismatch';
1189
+ readonly actualLength: number;
1190
+ readonly expectedStride: 16;
1191
+ }
1192
+ // feat-20260519-light-casters-point-spot-pbr w2 — PointLight / SpotLight
1193
+ // spawn-time payload bound violation (plan-strategy D-S3 a). detail.field
1194
+ // three-branch ('range' | 'innerOuter' | 'outerNinety') keeps four bound
1195
+ // violations under one code; AI users narrow on `.detail.field` after the
1196
+ // outer `switch (err.code)` to pick the specific recovery hint.
1197
+ | {
1198
+ readonly code: 'spawn-light-invalid-bounds';
1199
+ readonly field: 'range' | 'innerOuter' | 'outerNinety';
1200
+ readonly got: number;
1201
+ }
1202
+ // feat-20260520-2d-sprite-layer-mvp M-2 w13 — resource-setter bound
1203
+ // violation (plan-strategy D-4). receivedMode carries the rejected
1204
+ // payload number; receivedKey is optional so future resource
1205
+ // validators can share the same code while disambiguating which
1206
+ // resource produced the failure.
1207
+ | {
1208
+ readonly code: 'resource-invalid-value';
1209
+ readonly receivedMode: number;
1210
+ readonly receivedKey?: string;
1211
+ }
1212
+ // feat-20260521-sprite-atlas-animation M1 T-05 — sprite-animation tick
1213
+ // runtime invariant violation (plan-strategy D-1 + section 5 AC-09).
1214
+ // detail.field two-branch keeps the regions-length / frame-duration
1215
+ // invariants under one code; AI users narrow on `.detail.field` after
1216
+ // the outer `switch (err.code)` to pick the specific recovery hint
1217
+ // (charter P3 + P4). Two top-level variants give each `.field` branch
1218
+ // its own required sub-field shape so AI users get strong narrowing
1219
+ // inside `switch (err.detail.field)` without optional sub-fields
1220
+ // bleeding across branches.
1221
+ | {
1222
+ readonly code: 'sprite-animation-invalid';
1223
+ readonly field: 'regions-length';
1224
+ readonly regionsLength: number;
1225
+ readonly frameCount: number;
1226
+ }
1227
+ | {
1228
+ readonly code: 'sprite-animation-invalid';
1229
+ readonly field: 'frame-duration';
1230
+ readonly frameDuration: number;
1231
+ }
1232
+ // feat-20260531-ecs-relationship-abstraction-bidirectional-sync M2 — the 4
1233
+ // relationship-* discriminated detail variants (plan-strategy D-5). Each
1234
+ // carries the component name + the entities involved so AI users narrow on
1235
+ // `.code` then read `.detail` to locate the offending relationship surface.
1236
+ | {
1237
+ readonly code: 'relationship-self-cycle';
1238
+ readonly component: string;
1239
+ readonly entity: number;
1240
+ readonly ancestor: number;
1241
+ }
1242
+ | {
1243
+ readonly code: 'relationship-mirror-component-not-registered';
1244
+ readonly component: string;
1245
+ readonly mirror: string;
1246
+ }
1247
+ | {
1248
+ readonly code: 'relationship-mirror-field-type-mismatch';
1249
+ readonly component: string;
1250
+ readonly mirror: string;
1251
+ readonly field: string;
1252
+ readonly actualType: string;
1253
+ }
1254
+ | {
1255
+ readonly code: 'relationship-detach-mismatch';
1256
+ readonly component: string;
1257
+ readonly child: number;
1258
+ readonly expectedParent: number;
1259
+ readonly actualParent: number;
1260
+ }
1261
+ // feat-20260602-drop-component-registration w16-a — scene instantiate
1262
+ // unknown-component fail-fast (30 -> 31). `.detail.name` carries the
1263
+ // component name that was never defined via defineComponent.
1264
+ | {
1265
+ readonly code: 'component-not-defined';
1266
+ readonly name: string;
1267
+ }
1268
+ // feat-20260602-archetype-stores-full-packed-entity M1 / w3 — removeComponent
1269
+ // essential-component rejection. `.detail.componentName` carries the essential
1270
+ // component name (the id=0 `Entity`).
1271
+ | {
1272
+ readonly code: 'remove-essential-component';
1273
+ readonly componentName: string;
1274
+ }
1275
+ // feat-20260608-scene-nesting-ecs-fication M1 / w9 — setSceneOverride
1276
+ // value-type rejection (plan-strategy D-9; requirements §Edge cases last
1277
+ // row + reviewer Issue 1). `.detail.comp` / `.detail.field` locate the
1278
+ // override target; `.detail.expectedType` carries the schema-declared
1279
+ // type literal (e.g. 'f32', 'bool', 'string'); `.detail.actualType`
1280
+ // carries the runtime `typeof value` (typed `unknown` because the
1281
+ // override write is not coerced — fail-fast surfaces the mismatch).
1282
+ | {
1283
+ readonly code: 'scene-override-type-mismatch';
1284
+ readonly comp: string;
1285
+ readonly field: string;
1286
+ readonly expectedType: string;
1287
+ readonly actualType: unknown;
1288
+ }
1289
+ // bug-20260615-spawn-data-unknown-field-fail-fast — spawn-data unknown-key
1290
+ // fail-fast. `.detail.component` names the schema's component, `.detail.field`
1291
+ // is the offending raw key, `.detail.knownFields` is the schema's full field
1292
+ // whitelist (sorted, used by AI users / hint formatters to surface "did you
1293
+ // mean" suggestions without round-tripping to the schema).
1294
+ | {
1295
+ readonly code: 'spawn-data-unknown-field';
1296
+ readonly component: string;
1297
+ readonly field: string;
1298
+ readonly knownFields: readonly string[];
1299
+ }
1300
+ // feat-20260625-sprite-instances-and-tilemap-terrain-static-batch M1 / w2 —
1301
+ // 3 discriminated detail variants for the SpriteInstances primitive
1302
+ // (declared in M1, fired at render-system-extract entry in M3).
1303
+ | {
1304
+ readonly code: 'sprite-instances-count-mismatch';
1305
+ readonly transformsLength: number;
1306
+ readonly regionsLength: number;
1307
+ readonly expectedStride: { readonly transforms: 16; readonly regions: 4 };
1308
+ }
1309
+ | {
1310
+ readonly code: 'sprite-instances-requires-sprite-shader';
1311
+ readonly entityId: number;
1312
+ readonly observedMaterialShaderId: string;
1313
+ }
1314
+ | {
1315
+ readonly code: 'sprite-instances-mutually-exclusive-with-instances';
1316
+ readonly entityId: number;
1317
+ }
1318
+ // feat-20260713-mount-override-component-add-and-shared-ref-round M2 / w9 —
1319
+ // shared-field value gate. `.detail.component` / `.detail.field` locate the
1320
+ // shared reference field; `.detail.fieldType` is the schema-declared type
1321
+ // literal (`shared<T>` scalar or `array<shared<T>>`); `.detail.actualValue`
1322
+ // is the offending non-handle value (typed `unknown` — a raw GUID string /
1323
+ // `{ guid }` / `{ kind }` object is not coerced, the fail-fast surfaces it);
1324
+ // `.detail.index` is the array element index for the array form (undefined for
1325
+ // the scalar form). AI users read `.detail.field` + `.detail.fieldType` to see
1326
+ // which reference needs `AssetRegistry.load(guid, kind) + allocSharedRef` before binding.
1327
+ | {
1328
+ readonly code: 'shared-field-invalid-value';
1329
+ readonly component: string;
1330
+ readonly field: string;
1331
+ readonly fieldType: string;
1332
+ readonly actualValue: unknown;
1333
+ readonly index?: number;
1334
+ }
1335
+ // feat-20260714-bevy-style-system-sets M1 / w3 — invalid-SystemSet detail.
1336
+ // `.detail.name` is the rejected token name; `.detail.registered` is a
1337
+ // deterministic snapshot of the current registry keys.
1338
+ | {
1339
+ readonly code: 'system-set-not-registered';
1340
+ readonly name: string;
1341
+ readonly registered: readonly string[];
1342
+ }
1343
+ | {
1344
+ readonly code: 'component-field-invalid-value';
1345
+ readonly entity: number | undefined;
1346
+ readonly component: string;
1347
+ readonly field: string;
1348
+ readonly received: unknown;
1349
+ readonly allowedValues: Readonly<Record<string, number>>;
1350
+ }
1351
+ | {
1352
+ readonly code: 'component-numeric-value-invalid';
1353
+ readonly entity: number | undefined;
1354
+ readonly component: string;
1355
+ readonly field: string;
1356
+ readonly received: number;
1357
+ readonly index?: number;
1358
+ }
1359
+ | {
1360
+ readonly code: 'managed-array-invalid-value';
1361
+ readonly component: string;
1362
+ readonly field: string;
1363
+ readonly fieldType: string;
1364
+ readonly actualValue: unknown;
1365
+ }
1366
+ // feat-20260714-bevy-style-system-sets M2 / w12 — structured cyclic-dependency
1367
+ // detail. `.detail.cycle` is the ordered cycle path array; consumers read
1368
+ // this instead of parsing the message string.
1369
+ | {
1370
+ readonly code: 'cyclic-dependency';
1371
+ readonly cycle: readonly string[];
1372
+ }
1373
+ | {
1374
+ readonly code: 'command-failed';
1375
+ readonly systemName: string;
1376
+ readonly schedule: string;
1377
+ readonly commandIndex: number;
1378
+ readonly commandKind: CommandKind;
1379
+ readonly cause: unknown;
1380
+ }
1381
+ | {
1382
+ readonly code: 'system-failed';
1383
+ readonly systemName: string;
1384
+ readonly schedule: string;
1385
+ readonly cause: unknown;
1386
+ readonly lastCommittedCommand: CommandCommitEvidence | null;
1387
+ };
1388
+
1389
+ /**
1390
+ * Layer-3 error envelope routed through managed-storage callbacks. Callers
1391
+ * narrow `detail` through the source-owned `EcsErrorDetail` union.
1392
+ */
1393
+ export interface ManagedArrayErrorEnvelope {
1394
+ readonly code: EcsErrorCode;
1395
+ readonly hint: string;
1396
+ readonly expected: string;
1397
+ readonly detail: unknown;
1398
+ }
1399
+
1400
+ export type CommandKind = 'spawn' | 'despawn' | 'addComponent' | 'removeComponent';
1401
+
1402
+ export interface CommandCommitEvidence {
1403
+ readonly index: number;
1404
+ readonly kind: CommandKind;
1405
+ }
1406
+
1407
+ /** Expected command preflight failure, with the exact batch location. */
1408
+ export class CommandFailedError extends Error {
1409
+ override readonly name = 'CommandFailedError';
1410
+ readonly code = 'command-failed' as const;
1411
+ readonly expected = 'all deferred commands pass preflight before commit';
1412
+ readonly hint =
1413
+ 'Inspect detail.cause, repair the command at detail.commandIndex, and run the World again.';
1414
+ override readonly cause: unknown;
1415
+ readonly detail: {
1416
+ readonly systemName: string;
1417
+ readonly schedule: string;
1418
+ readonly commandIndex: number;
1419
+ readonly commandKind: CommandKind;
1420
+ readonly cause: unknown;
1421
+ };
1422
+
1423
+ constructor(
1424
+ systemName: string,
1425
+ schedule: string,
1426
+ commandIndex: number,
1427
+ commandKind: CommandKind,
1428
+ cause: unknown,
1429
+ ) {
1430
+ super(
1431
+ `Deferred command failed before commit in ${schedule}/${systemName} ` +
1432
+ `at command ${commandIndex} (${commandKind}).`,
1433
+ );
1434
+ this.cause = cause;
1435
+ this.detail = { systemName, schedule, commandIndex, commandKind, cause };
1436
+ }
1437
+ }
1438
+
1439
+ /** Unknown system failure. The World is poisoned because row writes may exist. */
1440
+ export class SystemFailedError extends Error {
1441
+ override readonly name = 'SystemFailedError';
1442
+ readonly code = 'system-failed' as const;
1443
+ readonly expected = 'a system completes without throwing or returning a failed Result';
1444
+ readonly hint =
1445
+ 'Inspect detail.cause, stop using this poisoned World, and rebuild it from the owning App.';
1446
+ override readonly cause: unknown;
1447
+ readonly detail: {
1448
+ readonly systemName: string;
1449
+ readonly schedule: string;
1450
+ readonly cause: unknown;
1451
+ readonly lastCommittedCommand: CommandCommitEvidence | null;
1452
+ };
1453
+
1454
+ constructor(
1455
+ systemName: string,
1456
+ schedule: string,
1457
+ cause: unknown,
1458
+ lastCommittedCommand: CommandCommitEvidence | null = null,
1459
+ ) {
1460
+ super(`System ${schedule}/${systemName} failed; World is poisoned.`);
1461
+ this.cause = cause;
1462
+ this.detail = { systemName, schedule, cause, lastCommittedCommand };
1463
+ }
1464
+ }