@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
@@ -0,0 +1,1456 @@
1
+ // Consolidated by feat-20260609-test-pool-startup-reduction-merge-tiny-test-files
2
+ // biome-ignore-all lint/complexity/noUselessLoneBlockStatements: scope isolation between merged source files
3
+ //
4
+ // Source files (N=10):
5
+ // - packages/ecs/src/__tests__/ac16-register-component-grep-gate.test.ts
6
+ // - packages/ecs/src/__tests__/buffer-array-errors.test.ts
7
+ // - packages/ecs/src/__tests__/component-global-index.test.ts
8
+ // - packages/ecs/src/__tests__/component-schema-json.test.ts
9
+ // - packages/ecs/src/__tests__/errors.test.ts
10
+ // - packages/ecs/src/__tests__/managed-entity-dangling-errors.test.ts
11
+ // - packages/ecs/src/__tests__/relationship-errors.test.ts
12
+ // - packages/ecs/src/__tests__/result.test.ts
13
+ // - packages/ecs/src/__tests__/spawn-light-invalid-bounds-error-code.test.ts
14
+ // - packages/ecs/src/__tests__/sprite-animation-invalid-error.test.ts
15
+ //
16
+ // Paradigm: each block-scoped describe('<source-filename>.test.ts', ...) preserves
17
+ // source as ancestorTitles[0]. Top-level imports merged + deduped.
18
+
19
+ import { execSync, spawnSync } from 'node:child_process';
20
+ import { readdirSync, readFileSync } from 'node:fs';
21
+ import { dirname, resolve } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { err, ok, type Result } from '@forgeax/engine-types';
24
+ import { describe, expect, expectTypeOf, it, test } from 'vitest';
25
+ import { componentSchema, defineComponent } from '../component';
26
+ import type { EcsErrorCode } from '../errors';
27
+ import {
28
+ ComponentAlreadyPresentError,
29
+ ComponentNotPresentError,
30
+ CyclicDependencyError,
31
+ EntityIndexOverflowError,
32
+ FixedSizeMismatchError,
33
+ ManagedBufferOutOfBoundsError,
34
+ ManagedBufferShrinkNotSupportedError,
35
+ RelationshipDetachMismatchError,
36
+ RelationshipMirrorComponentNotRegisteredError,
37
+ RelationshipMirrorFieldTypeMismatchError,
38
+ RelationshipSelfCycleError,
39
+ ResourceNotFoundError,
40
+ SchemaUnsupportedFieldError,
41
+ SpriteAnimationInvalidError,
42
+ SpriteInstancesCountMismatchError,
43
+ SpriteInstancesMutuallyExclusiveWithInstancesError,
44
+ SpriteInstancesRequiresSpriteShaderError,
45
+ StaleEntityError,
46
+ UniqueRefDoubleReleaseError,
47
+ UniqueRefReleasedError,
48
+ } from '../errors';
49
+ import { World } from '../world';
50
+
51
+ {
52
+ // ─── from ac16-register-component-grep-gate.test.ts ───
53
+ const here = dirname(fileURLToPath(import.meta.url));
54
+ const repoRoot = resolve(here, '..', '..', '..', '..');
55
+
56
+ const SCAN_DIRS = ['packages', 'apps', 'templates'];
57
+
58
+ const NAME = 'register' + 'Component';
59
+ const CALL_PATTERN = `[.](${NAME}|${NAME}Checked)[(]`;
60
+ const FIELD_PATTERN = `${NAME}:`;
61
+
62
+ function sourceHits(pattern: string): string[] {
63
+ const expression = new RegExp(pattern);
64
+ const hits: string[] = [];
65
+ const visit = (relativeDir: string): void => {
66
+ for (const entry of readdirSync(resolve(repoRoot, relativeDir), { withFileTypes: true })) {
67
+ const relativePath = `${relativeDir}/${entry.name}`;
68
+ if (entry.isDirectory()) {
69
+ if (entry.name !== 'dist' && entry.name !== 'node_modules') visit(relativePath);
70
+ continue;
71
+ }
72
+ if (!entry.isFile() || (!entry.name.endsWith('.ts') && !entry.name.endsWith('.mjs'))) {
73
+ continue;
74
+ }
75
+ for (const [index, line] of readFileSync(resolve(repoRoot, relativePath), 'utf8')
76
+ .split('\n')
77
+ .entries()) {
78
+ if (expression.test(line)) hits.push(`${relativePath}:${index + 1}:${line.trim()}`);
79
+ }
80
+ }
81
+ };
82
+ for (const directory of SCAN_DIRS) {
83
+ visit(directory);
84
+ }
85
+ return hits;
86
+ }
87
+
88
+ describe('ac16-register-component-grep-gate.test.ts', () => {
89
+ describe('AC-16 - register-component call surface is zero repo-wide (w20)', () => {
90
+ it('layer 1: zero method-call sites for the deleted register* methods', () => {
91
+ const hits = sourceHits(CALL_PATTERN);
92
+ expect(hits, `unexpected register-component call sites:\n${hits.join('\n')}`).toEqual([]);
93
+ });
94
+
95
+ it('layer 2: zero mock interface field declarations for the deleted method', () => {
96
+ const hits = sourceHits(FIELD_PATTERN);
97
+ expect(
98
+ hits,
99
+ `unexpected register-component field declarations:\n${hits.join('\n')}`,
100
+ ).toEqual([]);
101
+ });
102
+ });
103
+ });
104
+ }
105
+
106
+ {
107
+ // ─── from buffer-array-errors.test.ts ───
108
+ describe('buffer-array-errors.test.ts', () => {
109
+ describe('EcsErrorCode — buffer/array vocab collapse delta (w7, AC-07)', () => {
110
+ it('FixedSizeMismatchError carries .code = "fixed-size-mismatch" + detail.expected / detail.actual', () => {
111
+ const err = new FixedSizeMismatchError('meta', 8, 7);
112
+ expect(err.code).toBe('fixed-size-mismatch');
113
+ expect(err.detail).toEqual({ expected: 8, actual: 7 });
114
+ });
115
+
116
+ it('EcsErrorCode keeps only the core fixed-size write failure', () => {
117
+ const codes: EcsErrorCode[] = ['fixed-size-mismatch'];
118
+ let hits = 0;
119
+ for (const code of codes) {
120
+ switch (code) {
121
+ case 'fixed-size-mismatch':
122
+ hits += 1;
123
+ break;
124
+ default:
125
+ void code;
126
+ }
127
+ }
128
+ expect(hits).toBe(1);
129
+ });
130
+
131
+ it('owner-specific validation codes stay outside EcsErrorCode', () => {
132
+ // @ts-expect-error render owns this validation code.
133
+ const renderCode: EcsErrorCode = 'instance-transforms-stride-mismatch';
134
+ // @ts-expect-error schema parsing owns this validation code.
135
+ const schemaCode: EcsErrorCode = 'managed-array-element-type-not-allowed';
136
+ expect([renderCode, schemaCode]).toHaveLength(2);
137
+ });
138
+
139
+ it('deleted managed-array-* codes are no longer assignable to EcsErrorCode', () => {
140
+ // @ts-expect-error 'managed-array-index-out-of-bounds' was deleted by feat-20260515 w11.
141
+ const a: EcsErrorCode = 'managed-array-index-out-of-bounds';
142
+ // @ts-expect-error 'managed-array-pop-empty' was deleted by feat-20260515 w11 (renamed to 'array-pop-empty').
143
+ const b: EcsErrorCode = 'managed-array-pop-empty';
144
+ // @ts-expect-error 'managed-array-shrink-not-supported' was deleted by feat-20260515 w11.
145
+ const c: EcsErrorCode = 'managed-array-shrink-not-supported';
146
+ // @ts-expect-error 'managed-array-stride-mismatch' was deleted by feat-20260515 w11 (replaced by 'instance-transforms-stride-mismatch').
147
+ const d: EcsErrorCode = 'managed-array-stride-mismatch';
148
+ void a;
149
+ void b;
150
+ void c;
151
+ void d;
152
+ });
153
+ });
154
+ });
155
+ }
156
+
157
+ {
158
+ // ─── from component-schema-json.test.ts ───
159
+ // NOTE: two source files both defined defineComponent('C', ...) with different schemas.
160
+ // The second defineComponent('C', diff-schema) is renamed to 'C2' to avoid schema-mismatch
161
+ // at merge time (global component registry, same-name diff-schema -> throw).
162
+
163
+ describe('component-schema-json.test.ts', () => {
164
+ describe('owner schema projection — offline manifest discoverability (w29)', () => {
165
+ test('JSON.stringify(componentSchema(C)) includes array<entity> / array<f32, 16> / buffer<16> / buffer literal substrings', () => {
166
+ const Hierarchy = defineComponent('Hierarchy', {
167
+ children: 'array<entity>',
168
+ instanceTransforms: 'array<f32, 16>',
169
+ });
170
+
171
+ const BufferBag = defineComponent('BufferBag', {
172
+ meta: 'buffer<16>',
173
+ blob: 'buffer',
174
+ });
175
+
176
+ const json1 = JSON.stringify(componentSchema(Hierarchy));
177
+ const json2 = JSON.stringify(componentSchema(BufferBag));
178
+
179
+ expect(json1).toContain('array<entity>');
180
+ expect(json1).toContain('array<f32, 16>');
181
+ expect(json2).toContain('buffer<16>');
182
+ expect(json2).toContain('"buffer"');
183
+
184
+ expect(JSON.parse(json1)).toEqual({
185
+ children: 'array<entity>',
186
+ instanceTransforms: 'array<f32, 16>',
187
+ });
188
+ expect(JSON.parse(json2)).toEqual({
189
+ meta: 'buffer<16>',
190
+ blob: 'buffer',
191
+ });
192
+ });
193
+
194
+ test('schema literal types survive the JSON round-trip (no widening to string)', () => {
195
+ const C2 = defineComponent('SchemaJsonC2', {
196
+ tag: 'array<u32>',
197
+ slot: 'buffer<32>',
198
+ });
199
+
200
+ const tagKeyword: 'array<u32>' = componentSchema(C2).tag;
201
+ const slotKeyword: 'buffer<32>' = componentSchema(C2).slot;
202
+ expect(tagKeyword).toBe('array<u32>');
203
+ expect(slotKeyword).toBe('buffer<32>');
204
+
205
+ expect(JSON.stringify(componentSchema(C2))).toContain('array<u32>');
206
+ expect(JSON.stringify(componentSchema(C2))).toContain('buffer<32>');
207
+ });
208
+
209
+ test('component tokens do not carry a second serialization method', () => {
210
+ const C = defineComponent('C', {
211
+ children: 'array<entity>',
212
+ meta: 'buffer<16>',
213
+ blob: 'buffer',
214
+ });
215
+ const serialized = JSON.stringify(componentSchema(C));
216
+ expect(serialized).toContain('array<entity>');
217
+ expect(serialized).toContain('buffer<16>');
218
+ expect(serialized).toContain('"buffer"');
219
+ expect('toSchemaJSON' in C).toBe(false);
220
+ });
221
+ });
222
+ });
223
+ }
224
+
225
+ {
226
+ // ─── from errors.test.ts ───
227
+ describe('errors.test.ts', () => {
228
+ describe('EcsError unified interface — .code property', () => {
229
+ const errorCases: Array<{
230
+ label: string;
231
+ create: () => Error & { hint: string; code: string };
232
+ expectedCode: string;
233
+ }> = [
234
+ {
235
+ label: 'EntityIndexOverflowError',
236
+ create: () => new EntityIndexOverflowError(99999999),
237
+ expectedCode: 'entity-index-overflow',
238
+ },
239
+ {
240
+ label: 'SchemaUnsupportedFieldError',
241
+ create: () => new SchemaUnsupportedFieldError('x', 'vec3'),
242
+ expectedCode: 'schema-unsupported-field',
243
+ },
244
+ {
245
+ label: 'StaleEntityError',
246
+ create: () => new StaleEntityError(100, 5, 3),
247
+ expectedCode: 'stale-entity',
248
+ },
249
+ {
250
+ label: 'ComponentAlreadyPresentError',
251
+ create: () => new ComponentAlreadyPresentError(42, 'Pos'),
252
+ expectedCode: 'component-already-present',
253
+ },
254
+ {
255
+ label: 'ComponentNotPresentError',
256
+ create: () => new ComponentNotPresentError(42, 'Velocity'),
257
+ expectedCode: 'component-not-present',
258
+ },
259
+ {
260
+ label: 'CyclicDependencyError',
261
+ create: () => new CyclicDependencyError(['A', 'B', 'A']),
262
+ expectedCode: 'cyclic-dependency',
263
+ },
264
+ {
265
+ label: 'ResourceNotFoundError',
266
+ create: () => new ResourceNotFoundError('Time'),
267
+ expectedCode: 'resource-not-found',
268
+ },
269
+ ];
270
+
271
+ for (const { label, create, expectedCode } of errorCases) {
272
+ describe(label, () => {
273
+ it(`.code equals "${expectedCode}"`, () => {
274
+ const e = create();
275
+ expect(e.code).toBe(expectedCode);
276
+ });
277
+
278
+ it('.code is kebab-case', () => {
279
+ const e = create();
280
+ expect(e.code).toMatch(/^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/);
281
+ });
282
+
283
+ it('.name is readable', () => {
284
+ const e = create();
285
+ expect(typeof e.name).toBe('string');
286
+ expect(e.name.length).toBeGreaterThan(0);
287
+ });
288
+
289
+ it('.message is readable', () => {
290
+ const e = create();
291
+ expect(typeof e.message).toBe('string');
292
+ expect(e.message.length).toBeGreaterThan(0);
293
+ });
294
+
295
+ it('.hint is readable', () => {
296
+ const e = create();
297
+ expect(typeof e.hint).toBe('string');
298
+ expect(e.hint.length).toBeGreaterThan(0);
299
+ });
300
+ });
301
+ }
302
+ });
303
+
304
+ describe('StaleEntityError enhanced fields (F-04)', () => {
305
+ it('has component field (optional string)', () => {
306
+ const e = new StaleEntityError(100, 5, 3, {
307
+ component: 'Position',
308
+ operation: 'get',
309
+ expectedGeneration: 3,
310
+ actualGeneration: 2,
311
+ });
312
+ expect(e.component).toBe('Position');
313
+ });
314
+
315
+ it('component is undefined when not provided', () => {
316
+ const e = new StaleEntityError(100, 5, 3, {
317
+ operation: 'despawn',
318
+ expectedGeneration: 3,
319
+ actualGeneration: 2,
320
+ });
321
+ expect(e.component).toBeUndefined();
322
+ });
323
+
324
+ it('has operation field (string)', () => {
325
+ const e = new StaleEntityError(100, 5, 3, {
326
+ operation: 'get',
327
+ expectedGeneration: 3,
328
+ actualGeneration: 2,
329
+ });
330
+ expect(e.operation).toBe('get');
331
+ });
332
+
333
+ it('has expectedGeneration field (number)', () => {
334
+ const e = new StaleEntityError(100, 5, 3, {
335
+ operation: 'set',
336
+ expectedGeneration: 5,
337
+ actualGeneration: 3,
338
+ });
339
+ expect(e.expectedGeneration).toBe(5);
340
+ });
341
+
342
+ it('has actualGeneration field (number)', () => {
343
+ const e = new StaleEntityError(100, 5, 3, {
344
+ operation: 'set',
345
+ expectedGeneration: 5,
346
+ actualGeneration: 3,
347
+ });
348
+ expect(e.actualGeneration).toBe(3);
349
+ });
350
+
351
+ it('preserves original entity/index/generation fields (backward compat)', () => {
352
+ const e = new StaleEntityError(100, 5, 3, {
353
+ component: 'Velocity',
354
+ operation: 'get',
355
+ expectedGeneration: 3,
356
+ actualGeneration: 1,
357
+ });
358
+ expect(e.name).toBe('StaleEntityError');
359
+ expect(e.message).toBeDefined();
360
+ expect(e.hint).toBeDefined();
361
+ });
362
+
363
+ it('backward compat: constructor without enhanced fields still works', () => {
364
+ const e = new StaleEntityError(100, 5, 3);
365
+ expect(e.name).toBe('StaleEntityError');
366
+ expect(e.hint).toBeDefined();
367
+ expect(e.component).toBeUndefined();
368
+ expect(e.operation).toBeUndefined();
369
+ expect(e.expectedGeneration).toBeUndefined();
370
+ expect(e.actualGeneration).toBeUndefined();
371
+ });
372
+ });
373
+
374
+ describe('AC-09 EcsErrorCode net-zero relative fence (w16)', () => {
375
+ function parseEcsErrorCodeMembers(src: string): string[] {
376
+ const header = 'export type EcsErrorCode =';
377
+ const start = src.indexOf(header);
378
+ if (start === -1) return [];
379
+ const bodyStart = start + header.length;
380
+ const endMarker = src.indexOf('export type EcsErrorDetail');
381
+ const searchEnd = endMarker > -1 ? endMarker : src.length;
382
+ let lastSemicolon = -1;
383
+ for (let i = bodyStart; i < searchEnd; i++) {
384
+ if (src[i] === ';') lastSemicolon = i;
385
+ }
386
+ const body = src.slice(bodyStart, lastSemicolon > -1 ? lastSemicolon : searchEnd);
387
+
388
+ const members = new Set<string>();
389
+ for (const raw of body.split('\n')) {
390
+ const line = raw.trim();
391
+ const member = line.match(/^\|\s*'([^']+)'/);
392
+ if (member) {
393
+ members.add(member[1] ?? '');
394
+ continue;
395
+ }
396
+ const first = line.match(/^=\s*'([^']+)'/);
397
+ if (first) {
398
+ members.add(first[1] ?? '');
399
+ }
400
+ }
401
+ return [...members].sort();
402
+ }
403
+
404
+ it('EcsErrorCode member set = git HEAD member set (net add 0, net delete 0)', () => {
405
+ const errorsPath = resolve(__dirname, '..', 'errors.ts');
406
+
407
+ const currentSrc = readFileSync(errorsPath, 'utf-8');
408
+ const currentMembers = parseEcsErrorCodeMembers(currentSrc);
409
+
410
+ let headSrc: string;
411
+ try {
412
+ headSrc = execSync('git show HEAD:packages/ecs/src/errors.ts', {
413
+ encoding: 'utf-8',
414
+ stdio: ['pipe', 'pipe', 'pipe'],
415
+ });
416
+ } catch {
417
+ return;
418
+ }
419
+ const headMembers = parseEcsErrorCodeMembers(headSrc);
420
+
421
+ // M-4 (feat-20260611-ecs-storage-naming-ssot): intentional SCREAMING_SNAKE -> kebab rename.
422
+ // Map head-side SCREAMING_SNAKE codes to their kebab counterparts before comparison.
423
+ const M4_RENAME_MAP: Record<string, string> = {
424
+ ENTITY_INDEX_OVERFLOW: 'entity-index-overflow',
425
+ SCHEMA_UNSUPPORTED_FIELD: 'schema-unsupported-field',
426
+ STALE_ENTITY: 'stale-entity',
427
+ COMPONENT_ALREADY_PRESENT: 'component-already-present',
428
+ COMPONENT_NOT_PRESENT: 'component-not-present',
429
+ CYCLIC_DEPENDENCY: 'cyclic-dependency',
430
+ RESOURCE_NOT_FOUND: 'resource-not-found',
431
+ };
432
+ // feat-20260614-ecs-shared-component-and-unique-rename M2/w5: intentional
433
+ // managed-ref-* -> unique-ref-* error code rename (handle brand
434
+ // `'managed'` -> `'unique'` cascade). Map head-side managed-ref codes
435
+ // to their unique-ref counterparts before comparison.
436
+ const FEAT_20260614_RENAME_MAP: Record<string, string> = {
437
+ 'managed-ref-released': 'unique-ref-released',
438
+ 'managed-ref-double-release': 'unique-ref-double-release',
439
+ };
440
+ // feat-20260625-sprite-instances-and-tilemap-terrain-static-batch
441
+ // post-merge with PR #520 (transparent collapse): PR 520 narrowed
442
+ // `MaterialSnapshot.shadingModel` to `'unlit' | undefined` — sprite
443
+ // is no longer a shadingModel enum member; identification is via
444
+ // `materialShaderId === 'forgeax::sprite'` (OOS-1 path retained).
445
+ // The guard error code accordingly renames to match the actual
446
+ // discriminator it checks.
447
+ const FEAT_20260625_RENAME_MAP: Record<string, string> = {
448
+ 'sprite-instances-requires-sprite-shading-model':
449
+ 'sprite-instances-requires-sprite-shader',
450
+ };
451
+ const applyRenames = (m: string): string =>
452
+ FEAT_20260625_RENAME_MAP[m] ?? FEAT_20260614_RENAME_MAP[m] ?? M4_RENAME_MAP[m] ?? m;
453
+ const headSet = new Set(headMembers.map(applyRenames));
454
+ const currentSet = new Set(currentMembers);
455
+
456
+ // bug-20260615 spawn-data-unknown-field-fail-fast: AGENTS.md
457
+ // §Error model evolution permits add-only minor evolution. Each
458
+ // intentional addition lists itself here so accidental drift in
459
+ // unrelated diffs still trips the gate.
460
+ //
461
+ // feat-20260614-ecs-shared-component-and-unique-rename M3 adds the
462
+ // two SharedRefStore error codes (companion to the existing
463
+ // unique-ref-* pair). Intentional add per the same minor-evolution
464
+ // contract.
465
+ //
466
+ // feat-20260623 #511 unified generational handle codec adds the
467
+ // shared-ref-stale + unique-ref-stale pair (gen-mismatch on
468
+ // resolve / retain after the entity+asset shared isRetiredSlot
469
+ // SSOT merge). Intentional add per the same minor-evolution
470
+ // contract.
471
+ // M2 time and scheduling extends the 43-code baseline by exactly three
472
+ // approved codes. The relative fence rejects any additional drift.
473
+ const INTENTIONAL_ADDS = new Set<string>([
474
+ 'time-delta-invalid',
475
+ 'time-config-invalid',
476
+ 'schedule-scope-mismatch',
477
+ 'resource-protected',
478
+ 'spawn-data-unknown-field',
479
+ 'shared-ref-released',
480
+ 'shared-ref-double-release',
481
+ 'shared-ref-payload-invalid',
482
+ 'builtin-slot-not-owned',
483
+ // feat-20260625-sprite-instances-and-tilemap-terrain-static-batch
484
+ // M1 / w2 — 3 declared, fired at render-system-extract entry in
485
+ // M3 w13 (plan-strategy D-6). Minor evolution +3 per AGENTS.md
486
+ // §Error model evolution contract.
487
+ 'sprite-instances-count-mismatch',
488
+ 'sprite-instances-requires-sprite-shader',
489
+ 'sprite-instances-mutually-exclusive-with-instances',
490
+ // feat-20260623-asset-handle-generation M4 — `shared-ref-stale` /
491
+ // `unique-ref-stale` landed on main during this feat's in-flight
492
+ // window (PR #502 + companion lands). Absorbed at merge into
493
+ // INTENTIONAL_ADDS so the net-zero gate stays self-consistent
494
+ // (charter F1: upstream lands must not trigger this feat's fence).
495
+ 'shared-ref-stale',
496
+ 'unique-ref-stale',
497
+ // feat-20260713-mount-override-component-add-and-shared-ref-round
498
+ // M2 / w9 — P3 shared-field value gate. `shared-field-invalid-value`
499
+ // fails fast when a `shared<T>` / `array<shared<T>>` field is bound to
500
+ // a raw GUID / sidecar object (not a resolved numeric handle) instead
501
+ // of the pre-fix silent zeroing. Minor evolution +1 per AGENTS.md
502
+ // §Error model evolution contract.
503
+ 'shared-field-invalid-value',
504
+ // feat-20260714-bevy-style-system-sets M1 / w3 — sole invalid-SystemSet
505
+ // error code. Minor evolution +1 per AGENTS.md §Error model evolution
506
+ // contract.
507
+ 'system-set-not-registered',
508
+ 'change-epoch-exhausted',
509
+ 'query-descriptor-conflict',
510
+ 'query-data-requires-fields',
511
+ 'query-span-unavailable',
512
+ 'query-iteration-invalidated',
513
+ 'query-iteration-active',
514
+ 'sparse-storage-requires-tag',
515
+ // feat-20260803-entity-visibility M1 — closed enum writes fail
516
+ // before storage mutation. Minor evolution +1 per the same contract.
517
+ 'component-field-invalid-value',
518
+ // feat-20260822 ECS core reduction — managed array writes reject
519
+ // arbitrary object payloads before row/buffer mutation.
520
+ 'managed-array-invalid-value',
521
+ // feat-20260825-engine-feedback-verifiable-remediation M1 / w4 —
522
+ // object-write numeric preflight rejects NaN before mutation.
523
+ 'component-numeric-value-invalid',
524
+ 'shared-kernel-ineligible',
525
+ 'shared-kernel-failed',
526
+ 'world-poisoned',
527
+ 'command-failed',
528
+ 'system-failed',
529
+ ]);
530
+
531
+ const added: string[] = [];
532
+ const deleted: string[] = [];
533
+ const INTENTIONAL_DELETES = new Set<string>([
534
+ 'query-descriptor-with-optional-conflict',
535
+ 'query-combinations-entity-required',
536
+ // Domain errors now close in render/runtime/scene owner unions;
537
+ // ECS retains their structured detail classes only for migration
538
+ // evidence and no longer counts these codes in EcsErrorCode.
539
+ 'component-not-defined',
540
+ 'resource-invalid-value',
541
+ 'scene-override-type-mismatch',
542
+ 'spawn-light-invalid-bounds',
543
+ 'sprite-animation-invalid',
544
+ 'sprite-instances-count-mismatch',
545
+ 'sprite-instances-mutually-exclusive-with-instances',
546
+ 'sprite-instances-requires-sprite-shader',
547
+ 'entity-index-overflow',
548
+ 'schema-unsupported-field',
549
+ 'sparse-storage-requires-tag',
550
+ 'change-epoch-exhausted',
551
+ 'instance-transforms-stride-mismatch',
552
+ 'managed-array-element-type-not-allowed',
553
+ 'query-data-requires-fields',
554
+ 'query-descriptor-conflict',
555
+ 'query-iteration-active',
556
+ 'query-iteration-invalidated',
557
+ 'query-span-unavailable',
558
+ 'relationship-mirror-component-not-registered',
559
+ 'relationship-mirror-field-type-mismatch',
560
+ 'resource-protected',
561
+ 'fixed-array-overflow',
562
+ 'array-pop-empty',
563
+ 'cardinality-exceeded',
564
+ ]);
565
+ for (const c of currentSet) {
566
+ if (!headSet.has(c) && !INTENTIONAL_ADDS.has(c)) added.push(c);
567
+ }
568
+ for (const h of headSet) {
569
+ if (!currentSet.has(h) && !INTENTIONAL_DELETES.has(h)) deleted.push(h);
570
+ }
571
+
572
+ if (added.length > 0 || deleted.length > 0) {
573
+ const lines: string[] = [];
574
+ if (added.length > 0)
575
+ lines.push(
576
+ `EcsErrorCode members ADDED by this feat (net +${added.length}): ${added.join(', ')}`,
577
+ );
578
+ if (deleted.length > 0)
579
+ lines.push(
580
+ `EcsErrorCode members DELETED by this feat (net -${deleted.length}): ${deleted.join(', ')}`,
581
+ );
582
+ lines.push(
583
+ `HEAD member count: ${headMembers.length}; current member count: ${currentMembers.length}`,
584
+ );
585
+ throw new Error(lines.join('\n'));
586
+ }
587
+
588
+ expect(currentMembers.length).toBeGreaterThan(0);
589
+ });
590
+ });
591
+ });
592
+ }
593
+
594
+ {
595
+ // ─── from managed-entity-dangling-errors.test.ts ───
596
+ describe('managed-entity-dangling-errors.test.ts', () => {
597
+ describe('w5 — managed error classes', () => {
598
+ it('UniqueRefReleasedError carries code + hint + detail.handle', () => {
599
+ const err = new UniqueRefReleasedError(0xdeadbeef, 'MaterialAsset');
600
+ expect(err.code).toBe('unique-ref-released');
601
+ expect(err.hint.length).toBeGreaterThan(0);
602
+ expect(err.detail.handle).toBe(0xdeadbeef);
603
+ expect(err.detail.target).toBe('MaterialAsset');
604
+ });
605
+
606
+ it('UniqueRefDoubleReleaseError carries code + hint + detail.handle', () => {
607
+ const err = new UniqueRefDoubleReleaseError(7, 'MaterialAsset');
608
+ expect(err.code).toBe('unique-ref-double-release');
609
+ expect(err.hint.length).toBeGreaterThan(0);
610
+ expect(err.detail.handle).toBe(7);
611
+ expect(err.detail.target).toBe('MaterialAsset');
612
+ });
613
+
614
+ it('ManagedBufferOutOfBoundsError carries code + hint + detail.{index,size}', () => {
615
+ const err = new ManagedBufferOutOfBoundsError(128, 64);
616
+ expect(err.code).toBe('managed-buffer-out-of-bounds');
617
+ expect(err.hint.length).toBeGreaterThan(0);
618
+ expect(err.detail.index).toBe(128);
619
+ expect(err.detail.size).toBe(64);
620
+ });
621
+
622
+ it('ManagedBufferShrinkNotSupportedError carries code + hint + detail.{requested,current}', () => {
623
+ const err = new ManagedBufferShrinkNotSupportedError(32, 64);
624
+ expect(err.code).toBe('managed-buffer-shrink-not-supported');
625
+ expect(err.hint.length).toBeGreaterThan(0);
626
+ expect(err.detail.requested).toBe(32);
627
+ expect(err.detail.current).toBe(64);
628
+ });
629
+
630
+ it('EcsErrorCode closed union exhaustively covers the managed members', () => {
631
+ function assertNever(_x: never): never {
632
+ throw new Error('unreachable');
633
+ }
634
+ function check(code: EcsErrorCode): string {
635
+ switch (code) {
636
+ case 'stale-entity':
637
+ case 'component-already-present':
638
+ case 'component-not-present':
639
+ case 'cyclic-dependency':
640
+ case 'resource-not-found':
641
+ case 'system-before-unknown':
642
+ case 'unique-ref-released':
643
+ case 'unique-ref-double-release':
644
+ case 'managed-buffer-out-of-bounds':
645
+ case 'managed-buffer-shrink-not-supported':
646
+ case 'fixed-size-mismatch':
647
+ case 'relationship-self-cycle':
648
+ case 'relationship-detach-mismatch':
649
+ case 'remove-essential-component':
650
+ case 'spawn-data-unknown-field':
651
+ case 'shared-ref-released':
652
+ case 'shared-ref-double-release':
653
+ case 'shared-ref-payload-invalid':
654
+ case 'builtin-slot-not-owned':
655
+ case 'shared-ref-stale':
656
+ case 'unique-ref-stale':
657
+ // feat-20260713-mount-override-component-add-and-shared-ref-round
658
+ // M2 / w9 — shared-field value gate code. Required to keep this
659
+ // exhaustive switch over EcsErrorCode visually closed.
660
+ case 'shared-field-invalid-value':
661
+ return code;
662
+ // feat-20260714-bevy-style-system-sets M1 / w3 — system-set-not-registered
663
+ // case. Required to keep this exhaustive switch over EcsErrorCode
664
+ // visually closed.
665
+ case 'system-set-not-registered':
666
+ case 'component-field-invalid-value':
667
+ case 'component-numeric-value-invalid':
668
+ case 'managed-array-invalid-value':
669
+ case 'time-delta-invalid':
670
+ case 'time-config-invalid':
671
+ case 'schedule-scope-mismatch':
672
+ case 'shared-kernel-ineligible':
673
+ case 'shared-kernel-failed':
674
+ case 'world-poisoned':
675
+ case 'command-failed':
676
+ case 'system-failed':
677
+ return code;
678
+ default:
679
+ return assertNever(code);
680
+ }
681
+ }
682
+ expect(check('unique-ref-released')).toBe('unique-ref-released');
683
+ expect(check('managed-buffer-out-of-bounds')).toBe('managed-buffer-out-of-bounds');
684
+ });
685
+ });
686
+ });
687
+ }
688
+
689
+ {
690
+ // ─── from relationship-errors.test.ts ───
691
+ describe('relationship-errors.test.ts', () => {
692
+ describe('relationship error codes', () => {
693
+ it('relationship-self-cycle carries .code + .detail.entity', () => {
694
+ const e = new RelationshipSelfCycleError('ChildOf', 42 as number, 7 as number);
695
+ expect(e.code).toBe('relationship-self-cycle');
696
+ expect(e).toBeInstanceOf(Error);
697
+ expect(e.detail).toMatchObject({ component: 'ChildOf', entity: 42, ancestor: 7 });
698
+ expect(typeof e.hint).toBe('string');
699
+ });
700
+
701
+ it('relationship-mirror-component-not-registered carries .code + .detail', () => {
702
+ const e = new RelationshipMirrorComponentNotRegisteredError('ChildOf', 'Children');
703
+ expect(e.code).toBe('relationship-mirror-component-not-registered');
704
+ expect(e).toBeInstanceOf(Error);
705
+ expect(e.detail).toMatchObject({ component: 'ChildOf', mirror: 'Children' });
706
+ });
707
+
708
+ it('relationship-mirror-component-not-registered hint drops register wording (AC-09)', () => {
709
+ const e = new RelationshipMirrorComponentNotRegisteredError('ChildOf', 'Children');
710
+ expect(e.hint).not.toMatch(/register/i);
711
+ expect(e.hint).toContain('defineComponent');
712
+ });
713
+
714
+ it('relationship-mirror-field-type-mismatch carries .code + .detail', () => {
715
+ const e = new RelationshipMirrorFieldTypeMismatchError(
716
+ 'ChildOf',
717
+ 'Children',
718
+ 'entities',
719
+ 'array<f32>',
720
+ );
721
+ expect(e.code).toBe('relationship-mirror-field-type-mismatch');
722
+ expect(e).toBeInstanceOf(Error);
723
+ expect(e.detail).toMatchObject({
724
+ component: 'ChildOf',
725
+ mirror: 'Children',
726
+ field: 'entities',
727
+ actualType: 'array<f32>',
728
+ });
729
+ });
730
+
731
+ it('relationship-detach-mismatch carries .code + .detail', () => {
732
+ const e = new RelationshipDetachMismatchError(
733
+ 'ChildOf',
734
+ 5 as number,
735
+ 9 as number,
736
+ 3 as number,
737
+ );
738
+ expect(e.code).toBe('relationship-detach-mismatch');
739
+ expect(e).toBeInstanceOf(Error);
740
+ expect(e.detail).toMatchObject({
741
+ component: 'ChildOf',
742
+ child: 5,
743
+ expectedParent: 9,
744
+ actualParent: 3,
745
+ });
746
+ });
747
+
748
+ it('only relationship mutation codes are members of EcsErrorCode', () => {
749
+ const codes: EcsErrorCode[] = ['relationship-self-cycle', 'relationship-detach-mismatch'];
750
+ function classify(code: EcsErrorCode): string {
751
+ switch (code) {
752
+ case 'relationship-self-cycle':
753
+ return 'cycle';
754
+ case 'relationship-detach-mismatch':
755
+ return 'detach';
756
+ default:
757
+ return 'other';
758
+ }
759
+ }
760
+ expect(codes.map(classify)).toEqual(['cycle', 'detach']);
761
+ });
762
+ });
763
+ });
764
+ }
765
+
766
+ {
767
+ // ─── from result.test.ts ───
768
+ describe('result.test.ts', () => {
769
+ describe('ok() factory', () => {
770
+ it('produces an object with `.ok === true` (boolean discriminant)', () => {
771
+ const r = ok(42);
772
+ expect(r.ok).toBe(true);
773
+ });
774
+
775
+ it('exposes `.value` on the success branch', () => {
776
+ const r = ok(42);
777
+ if (r.ok) {
778
+ expect(r.value).toBe(42);
779
+ } else {
780
+ throw new Error('ok() branch unreachable');
781
+ }
782
+ });
783
+ });
784
+
785
+ describe('err() factory', () => {
786
+ it('produces an object with `.ok === false` (boolean discriminant)', () => {
787
+ const e = new ComponentNotPresentError(1, 'Pos');
788
+ const r = err(e);
789
+ expect(r.ok).toBe(false);
790
+ });
791
+
792
+ it('exposes `.error` on the failure branch', () => {
793
+ const e = new ComponentNotPresentError(1, 'Pos');
794
+ const r = err(e);
795
+ if (!r.ok) {
796
+ expect(r.error).toBe(e);
797
+ } else {
798
+ throw new Error('err() branch unreachable');
799
+ }
800
+ });
801
+ });
802
+
803
+ describe('discriminant shape (`.ok` is boolean, not getter→value)', () => {
804
+ it('`.ok` on ok() is the literal boolean true', () => {
805
+ const r = ok(123);
806
+ expect(r.ok).toBe(true);
807
+ expect(typeof r.ok).toBe('boolean');
808
+ });
809
+
810
+ it('`.ok` on err() is the literal boolean false', () => {
811
+ const r = err(new ComponentNotPresentError(1, 'Pos'));
812
+ expect(r.ok).toBe(false);
813
+ expect(typeof r.ok).toBe('boolean');
814
+ });
815
+ });
816
+
817
+ describe('unwrap()', () => {
818
+ it('returns the value for an Ok result', () => {
819
+ const r = ok(42);
820
+ expect(r.unwrap()).toBe(42);
821
+ });
822
+
823
+ it('throws the ORIGINAL EcsError for an Err result (not wrapped)', () => {
824
+ const e = new StaleEntityError(100, 5, 3);
825
+ const r = err(e);
826
+
827
+ let thrown: unknown;
828
+ try {
829
+ r.unwrap();
830
+ } catch (ex) {
831
+ thrown = ex;
832
+ }
833
+
834
+ expect(thrown).toBe(e);
835
+ });
836
+
837
+ it('thrown error retains .code and .hint (charter: structured error)', () => {
838
+ const e = new ComponentNotPresentError(1, 'Velocity');
839
+ const r = err(e);
840
+
841
+ let thrown: unknown;
842
+ try {
843
+ r.unwrap();
844
+ } catch (ex) {
845
+ thrown = ex;
846
+ }
847
+
848
+ expect(thrown).toBeInstanceOf(ComponentNotPresentError);
849
+ const typedErr = thrown as ComponentNotPresentError;
850
+ expect(typedErr.hint).toBeDefined();
851
+ expect(typeof typedErr.hint).toBe('string');
852
+ expect(typedErr.hint.length).toBeGreaterThan(0);
853
+ });
854
+ });
855
+
856
+ describe('unwrapOr()', () => {
857
+ it('returns the value for an Ok result', () => {
858
+ const r = ok(42);
859
+ expect(r.unwrapOr(0)).toBe(42);
860
+ });
861
+
862
+ it('returns the default for an Err result', () => {
863
+ const e = new ComponentNotPresentError(1, 'Pos');
864
+ const r: Result<number, ComponentNotPresentError> = err(e);
865
+ expect(r.unwrapOr(99)).toBe(99);
866
+ });
867
+ });
868
+
869
+ describe('removed surface (D-P2 guard)', () => {
870
+ it('ok() result has no `isOk` property', () => {
871
+ const r = ok(42);
872
+ expect('isOk' in r).toBe(false);
873
+ });
874
+
875
+ it('ok() result has no `isErr` property', () => {
876
+ const r = ok(42);
877
+ expect('isErr' in r).toBe(false);
878
+ });
879
+
880
+ it('ok() result has no `map` property', () => {
881
+ const r = ok(42);
882
+ expect('map' in r).toBe(false);
883
+ });
884
+
885
+ it('ok() result has no `mapErr` property', () => {
886
+ const r = ok(42);
887
+ expect('mapErr' in r).toBe(false);
888
+ });
889
+
890
+ it('err() result has no `isOk` property', () => {
891
+ const r = err(new ComponentNotPresentError(1, 'Pos'));
892
+ expect('isOk' in r).toBe(false);
893
+ });
894
+
895
+ it('err() result has no `isErr` property', () => {
896
+ const r = err(new ComponentNotPresentError(1, 'Pos'));
897
+ expect('isErr' in r).toBe(false);
898
+ });
899
+
900
+ it('err() result has no `map` property', () => {
901
+ const r = err(new ComponentNotPresentError(1, 'Pos'));
902
+ expect('map' in r).toBe(false);
903
+ });
904
+
905
+ it('err() result has no `mapErr` property', () => {
906
+ const r = err(new ComponentNotPresentError(1, 'Pos'));
907
+ expect('mapErr' in r).toBe(false);
908
+ });
909
+ });
910
+
911
+ describe('byte-for-byte parity with rhi.Result narrow shape', () => {
912
+ it('ok() own-property names equal {ok, value} (no extra surface, no missing)', () => {
913
+ const r = ok(42);
914
+ const ownKeys = Object.keys(r).sort();
915
+ expect(ownKeys).toEqual(['ok', 'value']);
916
+ });
917
+
918
+ it('err() own-property names equal {ok, error} (no extra surface, no missing)', () => {
919
+ const r = err(new ComponentNotPresentError(1, 'Pos'));
920
+ const ownKeys = Object.keys(r).sort();
921
+ expect(ownKeys).toEqual(['error', 'ok']);
922
+ });
923
+
924
+ it('`.ok` on Ok is an own data property (not a getter)', () => {
925
+ const r = ok(42);
926
+ expect(Object.hasOwn(r, 'ok')).toBe(true);
927
+ });
928
+
929
+ it('`.value` on Ok is an own data property (not a prototype getter)', () => {
930
+ const r = ok(42);
931
+ expect(Object.hasOwn(r, 'value')).toBe(true);
932
+ });
933
+
934
+ it('`.error` on Err is an own data property (not a prototype getter)', () => {
935
+ const r = err(new ComponentNotPresentError(1, 'Pos'));
936
+ expect(Object.hasOwn(r, 'error')).toBe(true);
937
+ });
938
+
939
+ it('`.unwrap` lives on the prototype chain, not as own', () => {
940
+ const r = ok(42);
941
+ expect(Object.hasOwn(r, 'unwrap')).toBe(false);
942
+ expect(typeof r.unwrap).toBe('function');
943
+ });
944
+
945
+ it('`.unwrapOr` lives on the prototype chain, not as own', () => {
946
+ const r = ok(42);
947
+ expect(Object.hasOwn(r, 'unwrapOr')).toBe(false);
948
+ expect(typeof r.unwrapOr).toBe('function');
949
+ });
950
+ });
951
+
952
+ describe('type-level narrowing via `r.ok`', () => {
953
+ it('ok branch exposes `.value: T`; err branch exposes `.error: E`', () => {
954
+ // Construct via the discriminated union so both arms type-check
955
+ // independently — `ok(42)` alone narrows to `ResultOk<number>` and
956
+ // would collapse the err arm to `never`.
957
+ const probe = (r: Result<number, ComponentNotPresentError>) => {
958
+ if (r.ok) {
959
+ expectTypeOf(r.value).toEqualTypeOf<number>();
960
+ } else {
961
+ expectTypeOf(r.error).toEqualTypeOf<ComponentNotPresentError>();
962
+ }
963
+ };
964
+ probe(ok(42));
965
+ probe(err(new ComponentNotPresentError(1, 'Pos')));
966
+ });
967
+ });
968
+ });
969
+ }
970
+
971
+ {
972
+ // ─── from spawn-light-invalid-bounds-error-code.test.ts ───
973
+ describe('spawn-light-invalid-bounds-error-code.test.ts', () => {
974
+ describe("EcsErrorCode +1 'spawn-light-invalid-bounds' union member [AC-06 (e)]", () => {
975
+ it("type-level: 'spawn-light-invalid-bounds' is assignable to EcsErrorCode", () => {
976
+ const code = 'spawn-light-invalid-bounds';
977
+ expect(code).toBe('spawn-light-invalid-bounds');
978
+ });
979
+
980
+ it("EcsErrorDetail discriminated variant for 'spawn-light-invalid-bounds' carries field three-branch", () => {
981
+ const range = {
982
+ code: 'spawn-light-invalid-bounds',
983
+ field: 'range',
984
+ got: -1,
985
+ };
986
+ const innerOuter = {
987
+ code: 'spawn-light-invalid-bounds',
988
+ field: 'innerOuter',
989
+ got: 25,
990
+ };
991
+ const outerNinety = {
992
+ code: 'spawn-light-invalid-bounds',
993
+ field: 'outerNinety',
994
+ got: 91,
995
+ };
996
+ expect(range.code).toBe('spawn-light-invalid-bounds');
997
+ expect(innerOuter.code).toBe('spawn-light-invalid-bounds');
998
+ expect(outerNinety.code).toBe('spawn-light-invalid-bounds');
999
+ });
1000
+
1001
+ it("type-level: switch over EcsErrorCode includes 'spawn-light-invalid-bounds' branch", () => {
1002
+ function describeCode(code: string): string {
1003
+ switch (code) {
1004
+ case 'spawn-light-invalid-bounds':
1005
+ return 'spawn-light-invalid-bounds';
1006
+ default:
1007
+ return 'other';
1008
+ }
1009
+ }
1010
+ expect(describeCode('spawn-light-invalid-bounds')).toBe('spawn-light-invalid-bounds');
1011
+ });
1012
+ });
1013
+ });
1014
+ }
1015
+
1016
+ {
1017
+ // ─── from w8-ecs-error-code-kebab-grep-gate.test.ts ───
1018
+ describe('w8-ecs-error-code-kebab-grep-gate.test.ts', () => {
1019
+ describe('EcsErrorCode kebab grep gate (M-4 w8)', () => {
1020
+ const here = dirname(fileURLToPath(import.meta.url));
1021
+ const repoRoot = resolve(here, '..', '..', '..', '..');
1022
+
1023
+ const SEVEN_SCREAMING_SNAKE_RE =
1024
+ "'(ENTITY_INDEX_OVERFLOW|SCHEMA_UNSUPPORTED_FIELD|STALE_ENTITY|COMPO" +
1025
+ "NENT_ALREADY_PRESENT|COMPONENT_NOT_PRESENT|CYCLIC_DEPENDENCY|RESOURCE_NOT_FOUND)'";
1026
+
1027
+ it('zero SCREAMING_SNAKE error code literals in non-README source (M-4 w9 replaces these)', () => {
1028
+ const r = spawnSync(
1029
+ 'grep',
1030
+ [
1031
+ '-rE',
1032
+ '--include=*.ts',
1033
+ '--exclude-dir=dist',
1034
+ '--exclude-dir=node_modules',
1035
+ '--exclude=errors.unit.test.ts',
1036
+ SEVEN_SCREAMING_SNAKE_RE,
1037
+ 'packages/ecs/src/',
1038
+ 'packages/runtime/src/',
1039
+ 'apps/',
1040
+ 'templates/',
1041
+ ],
1042
+ { cwd: repoRoot, encoding: 'utf8', timeout: 30_000 },
1043
+ );
1044
+ if (r.status === 2 || r.error) {
1045
+ throw new Error(`grep failed: ${r.stderr || r.error}`);
1046
+ }
1047
+ const hits = (r.stdout ?? '')
1048
+ .split('\n')
1049
+ .map((l) => l.trim())
1050
+ .filter((l) => l.length > 0);
1051
+ expect(hits, `SCREAMING_SNAKE error code literals remaining:\n${hits.join('\n')}`).toEqual(
1052
+ [],
1053
+ );
1054
+ });
1055
+ });
1056
+ });
1057
+ }
1058
+
1059
+ {
1060
+ // ─── from sprite-animation-invalid-error.test.ts ───
1061
+ describe('sprite-animation-invalid-error.test.ts', () => {
1062
+ describe("EcsErrorCode +1 'sprite-animation-invalid' union member [M1 T-04]", () => {
1063
+ it("type-level: 'sprite-animation-invalid' is assignable to EcsErrorCode", () => {
1064
+ const code = 'sprite-animation-invalid';
1065
+ expect(code).toBe('sprite-animation-invalid');
1066
+ });
1067
+
1068
+ it("EcsErrorDetail discriminated variant 'sprite-animation-invalid' carries field two-branch", () => {
1069
+ const regionsLength = {
1070
+ code: 'sprite-animation-invalid',
1071
+ field: 'regions-length',
1072
+ regionsLength: 12,
1073
+ frameCount: 4,
1074
+ };
1075
+ const frameDuration = {
1076
+ code: 'sprite-animation-invalid',
1077
+ field: 'frame-duration',
1078
+ frameDuration: 0,
1079
+ };
1080
+ expect(regionsLength.code).toBe('sprite-animation-invalid');
1081
+ expect(frameDuration.code).toBe('sprite-animation-invalid');
1082
+ });
1083
+ });
1084
+
1085
+ describe('SpriteAnimationInvalidError class — 4-field surface (regions-length branch)', () => {
1086
+ it("constructs with field='regions-length' and exposes 4 fields", () => {
1087
+ const err = new SpriteAnimationInvalidError({
1088
+ field: 'regions-length',
1089
+ regionsLength: 12,
1090
+ frameCount: 4,
1091
+ });
1092
+ expect(err.code).toBe('sprite-animation-invalid');
1093
+ expect(err.expected).toBeTruthy();
1094
+ expect(err.expected.length).toBeGreaterThan(0);
1095
+ expect(err.hint).toBeTruthy();
1096
+ expect(err.hint.length).toBeGreaterThan(0);
1097
+ expect(err.detail.field).toBe('regions-length');
1098
+ if (err.detail.field === 'regions-length') {
1099
+ expect(err.detail.regionsLength).toBe(12);
1100
+ expect(err.detail.frameCount).toBe(4);
1101
+ }
1102
+ });
1103
+
1104
+ it("constructs with field='frame-duration' and exposes 4 fields", () => {
1105
+ const err = new SpriteAnimationInvalidError({
1106
+ field: 'frame-duration',
1107
+ frameDuration: 0,
1108
+ });
1109
+ expect(err.code).toBe('sprite-animation-invalid');
1110
+ expect(err.expected).toBeTruthy();
1111
+ expect(err.expected.length).toBeGreaterThan(0);
1112
+ expect(err.hint).toBeTruthy();
1113
+ expect(err.hint.length).toBeGreaterThan(0);
1114
+ expect(err.detail.field).toBe('frame-duration');
1115
+ if (err.detail.field === 'frame-duration') {
1116
+ expect(err.detail.frameDuration).toBe(0);
1117
+ }
1118
+ });
1119
+
1120
+ it('field=frame-duration accepts negative frameDuration (AC-09 c)', () => {
1121
+ const err = new SpriteAnimationInvalidError({
1122
+ field: 'frame-duration',
1123
+ frameDuration: -0.05,
1124
+ });
1125
+ expect(err.code).toBe('sprite-animation-invalid');
1126
+ expect(err.detail.field).toBe('frame-duration');
1127
+ if (err.detail.field === 'frame-duration') {
1128
+ expect(err.detail.frameDuration).toBe(-0.05);
1129
+ }
1130
+ });
1131
+
1132
+ it('Error message embeds code + field + recovery hint (charter P3)', () => {
1133
+ const err = new SpriteAnimationInvalidError({
1134
+ field: 'regions-length',
1135
+ regionsLength: 7,
1136
+ frameCount: 3,
1137
+ });
1138
+ expect(err.message).toContain('sprite-animation-invalid');
1139
+ expect(err.message).toContain('regions-length');
1140
+ });
1141
+
1142
+ it('class .name override aligns with the error type', () => {
1143
+ const err = new SpriteAnimationInvalidError({
1144
+ field: 'frame-duration',
1145
+ frameDuration: 0,
1146
+ });
1147
+ expect(err.name).toBe('SpriteAnimationInvalidError');
1148
+ });
1149
+ });
1150
+
1151
+ describe("type-level: switch over EcsErrorCode includes 'sprite-animation-invalid' branch", () => {
1152
+ it('describeCode reaches the sprite-animation-invalid arm without default fall-through', () => {
1153
+ function describeCode(code: string): string {
1154
+ switch (code) {
1155
+ case 'sprite-animation-invalid':
1156
+ return 'sprite-animation-invalid';
1157
+ default:
1158
+ return 'other';
1159
+ }
1160
+ }
1161
+ expect(describeCode('sprite-animation-invalid')).toBe('sprite-animation-invalid');
1162
+ });
1163
+ });
1164
+ });
1165
+ }
1166
+
1167
+ {
1168
+ // ─── from sprite-instances-error-codes.test.ts ───
1169
+ // feat-20260625 M1 / w1 — 3 new EcsErrorCode members added by w2 for the
1170
+ // sprite-instances + tilemap-terrain-static-batch feat. Tests assert
1171
+ // (a) literal existence in EcsErrorCode union; (b) class instance carries
1172
+ // `.code` / `.hint` / `.detail` per AGENTS.md §Error model + charter P3
1173
+ // (`.hint` must contain actionable fix wording); (c) `.detail` discriminated
1174
+ // shape narrows correctly under `switch (err.code)` without a default arm.
1175
+ //
1176
+ // Boundary (plan-strategy §2 D-6): these 3 codes are DECLARED here (M1) but
1177
+ // FIRED at the render-system-extract entry (M3 w13). M1 tests intentionally
1178
+ // construct the classes directly — no extract-path fire test (that is M3).
1179
+ describe('sprite-instances-error-codes.test.ts', () => {
1180
+ describe('EcsErrorCode +3 — sprite-instances-* family (M1 w1)', () => {
1181
+ it('type-level: 3 new literals are assignable to EcsErrorCode', () => {
1182
+ const a = 'sprite-instances-count-mismatch';
1183
+ const b = 'sprite-instances-requires-sprite-shader';
1184
+ const c = 'sprite-instances-mutually-exclusive-with-instances';
1185
+ expect(a).toBe('sprite-instances-count-mismatch');
1186
+ expect(b).toBe('sprite-instances-requires-sprite-shader');
1187
+ expect(c).toBe('sprite-instances-mutually-exclusive-with-instances');
1188
+ });
1189
+
1190
+ it('EcsErrorDetail discriminated variants narrow per `.code`', () => {
1191
+ const countDetail = {
1192
+ code: 'sprite-instances-count-mismatch',
1193
+ transformsLength: 320,
1194
+ regionsLength: 40,
1195
+ expectedStride: { transforms: 16, regions: 4 },
1196
+ };
1197
+ const shadingDetail = {
1198
+ code: 'sprite-instances-requires-sprite-shader',
1199
+ entityId: 42,
1200
+ observedMaterialShaderId: 'forgeax::default-standard-pbr',
1201
+ };
1202
+ const mutexDetail = {
1203
+ code: 'sprite-instances-mutually-exclusive-with-instances',
1204
+ entityId: 17,
1205
+ };
1206
+ expect(countDetail.code).toBe('sprite-instances-count-mismatch');
1207
+ expect(shadingDetail.code).toBe('sprite-instances-requires-sprite-shader');
1208
+ expect(mutexDetail.code).toBe('sprite-instances-mutually-exclusive-with-instances');
1209
+ });
1210
+ });
1211
+
1212
+ describe('SpriteInstancesCountMismatchError class — w2 surface', () => {
1213
+ it('constructs with stride mismatch and exposes .code + .detail + .hint', () => {
1214
+ const err = new SpriteInstancesCountMismatchError(320, 40);
1215
+ expect(err.code).toBe('sprite-instances-count-mismatch');
1216
+ expect(err.detail).toEqual({
1217
+ code: 'sprite-instances-count-mismatch',
1218
+ transformsLength: 320,
1219
+ regionsLength: 40,
1220
+ expectedStride: { transforms: 16, regions: 4 },
1221
+ });
1222
+ expect(err.hint.length).toBeGreaterThan(0);
1223
+ expect(err.hint).toContain('transforms.length / 16');
1224
+ expect(err.hint).toContain('regions.length / 4');
1225
+ });
1226
+
1227
+ it('.name is the class name and .message embeds .code', () => {
1228
+ const err = new SpriteInstancesCountMismatchError(320, 40);
1229
+ expect(err.name).toBe('SpriteInstancesCountMismatchError');
1230
+ expect(err.message).toContain('sprite-instances-count-mismatch');
1231
+ });
1232
+ });
1233
+
1234
+ describe('SpriteInstancesRequiresSpriteShaderError class — w2 surface', () => {
1235
+ it('constructs with entity + observed materialShaderId and exposes .code + .detail + .hint', () => {
1236
+ const err = new SpriteInstancesRequiresSpriteShaderError(
1237
+ 42,
1238
+ 'forgeax::default-standard-pbr',
1239
+ );
1240
+ expect(err.code).toBe('sprite-instances-requires-sprite-shader');
1241
+ expect(err.detail).toEqual({
1242
+ code: 'sprite-instances-requires-sprite-shader',
1243
+ entityId: 42,
1244
+ observedMaterialShaderId: 'forgeax::default-standard-pbr',
1245
+ });
1246
+ expect(err.hint.length).toBeGreaterThan(0);
1247
+ expect(err.hint).toContain("'forgeax::sprite'");
1248
+ expect(err.hint).toContain('MaterialAsset');
1249
+ });
1250
+
1251
+ it('.name is the class name and .message embeds .code', () => {
1252
+ const err = new SpriteInstancesRequiresSpriteShaderError(42, 'forgeax::default-unlit');
1253
+ expect(err.name).toBe('SpriteInstancesRequiresSpriteShaderError');
1254
+ expect(err.message).toContain('sprite-instances-requires-sprite-shader');
1255
+ });
1256
+ });
1257
+
1258
+ describe('SpriteInstancesMutuallyExclusiveWithInstancesError class — w2 surface', () => {
1259
+ it('constructs with entity and exposes .code + .detail + .hint', () => {
1260
+ const err = new SpriteInstancesMutuallyExclusiveWithInstancesError(17);
1261
+ expect(err.code).toBe('sprite-instances-mutually-exclusive-with-instances');
1262
+ expect(err.detail).toEqual({
1263
+ code: 'sprite-instances-mutually-exclusive-with-instances',
1264
+ entityId: 17,
1265
+ });
1266
+ expect(err.hint.length).toBeGreaterThan(0);
1267
+ expect(err.hint).toContain('remove Instances');
1268
+ expect(err.hint).toContain('SpriteInstances supersedes Instances');
1269
+ });
1270
+
1271
+ it('.name is the class name and .message embeds .code', () => {
1272
+ const err = new SpriteInstancesMutuallyExclusiveWithInstancesError(17);
1273
+ expect(err.name).toBe('SpriteInstancesMutuallyExclusiveWithInstancesError');
1274
+ expect(err.message).toContain('sprite-instances-mutually-exclusive-with-instances');
1275
+ });
1276
+ });
1277
+
1278
+ describe('exhaustive switch over the 3 new codes (no default)', () => {
1279
+ it('compile-time narrows all 3 branches; runtime hits each arm exactly once', () => {
1280
+ const codes: string[] = [
1281
+ 'sprite-instances-count-mismatch',
1282
+ 'sprite-instances-requires-sprite-shader',
1283
+ 'sprite-instances-mutually-exclusive-with-instances',
1284
+ ];
1285
+ let count = 0;
1286
+ let shading = 0;
1287
+ let mutex = 0;
1288
+ for (const code of codes) {
1289
+ switch (code) {
1290
+ case 'sprite-instances-count-mismatch':
1291
+ count += 1;
1292
+ break;
1293
+ case 'sprite-instances-requires-sprite-shader':
1294
+ shading += 1;
1295
+ break;
1296
+ case 'sprite-instances-mutually-exclusive-with-instances':
1297
+ mutex += 1;
1298
+ break;
1299
+ default:
1300
+ void code;
1301
+ }
1302
+ }
1303
+ expect(count).toBe(1);
1304
+ expect(shading).toBe(1);
1305
+ expect(mutex).toBe(1);
1306
+ });
1307
+ });
1308
+ });
1309
+ }
1310
+
1311
+ {
1312
+ // ─── feat-20260713 M2 / w7 — AC-09 shared-field illegal value fail-fast ───
1313
+ //
1314
+ // The P3 value gate: addComponent / spawn / set must reject an illegal value
1315
+ // for a shared field (scalar `shared<T>` must be a number handle; array
1316
+ // `array<shared<T>>` elements must all be numbers) with a structured EcsError
1317
+ // (.code / .expected / .hint) instead of silently zeroing the column to
1318
+ // `[0,0,0,0]`. AI users bind a shared reference by first resolving the GUID to
1319
+ // a handle (AssetRegistry.load + allocSharedRef); passing a raw GUID string / {guid}
1320
+ // object / {kind} object bypasses that resolution and used to be swallowed.
1321
+ //
1322
+ // The three GUID forms feedback verified being zeroed:
1323
+ // (a) bare GUID string 'pack.material'
1324
+ // (b) { guid: '...' } object
1325
+ // (c) { kind: 'MaterialAsset', ... } object
1326
+ //
1327
+ // Zeroing repro guard: after a rejected write the column must NOT read back as
1328
+ // the all-zero sentinel — the write is aborted so the field never lands.
1329
+ describe('feat-20260713 M2 / w7 — shared-field illegal value fail-fast (AC-09)', () => {
1330
+ const SHARED_INVALID_CODE = 'shared-field-invalid-value';
1331
+
1332
+ // Distinct component names per test (defineComponent global index is
1333
+ // process-wide) — a scalar shared field + an array<shared<>> field.
1334
+ function scalarComp(name: string) {
1335
+ return defineComponent(name, { mat: { type: 'shared<MaterialAsset>' } });
1336
+ }
1337
+ function arrayComp(name: string) {
1338
+ return defineComponent(name, { clips: { type: 'array<shared<AnimationClip>, 4>' } });
1339
+ }
1340
+
1341
+ const GUID_FORMS: ReadonlyArray<{ label: string; value: unknown }> = [
1342
+ { label: 'bare GUID string', value: 'pack.material' },
1343
+ { label: '{ guid } object', value: { guid: 'pack.material' } },
1344
+ { label: '{ kind } object', value: { kind: 'MaterialAsset', guid: 'pack.material' } },
1345
+ ];
1346
+
1347
+ describe('scalar shared field', () => {
1348
+ for (const form of GUID_FORMS) {
1349
+ it(`addComponent rejects ${form.label} with structured EcsError (not zeroed)`, () => {
1350
+ const world = new World();
1351
+ const C = scalarComp(`W7Scalar_Add_${form.label.replace(/\W/g, '')}`);
1352
+ const e = world.spawn().unwrap();
1353
+ const add = world.addComponent(e, { component: C, data: { mat: form.value } as never });
1354
+ expect(add.ok).toBe(false);
1355
+ if (add.ok) return;
1356
+ const err = add.error as unknown as { code: string; expected?: string; hint?: string };
1357
+ expect(err.code).toBe(SHARED_INVALID_CODE);
1358
+ expect((err.expected ?? '').length).toBeGreaterThan(0);
1359
+ expect((err.hint ?? '').length).toBeGreaterThan(0);
1360
+ });
1361
+
1362
+ it(`spawn rejects ${form.label} with structured EcsError (not zeroed)`, () => {
1363
+ const world = new World();
1364
+ const C = scalarComp(`W7Scalar_Spawn_${form.label.replace(/\W/g, '')}`);
1365
+ const r = world.spawn({ component: C, data: { mat: form.value } as never });
1366
+ expect(r.ok).toBe(false);
1367
+ if (r.ok) return;
1368
+ const err = r.error as unknown as { code: string; expected?: string; hint?: string };
1369
+ expect(err.code).toBe(SHARED_INVALID_CODE);
1370
+ expect((err.expected ?? '').length).toBeGreaterThan(0);
1371
+ expect((err.hint ?? '').length).toBeGreaterThan(0);
1372
+ });
1373
+
1374
+ it(`set rejects ${form.label} with structured EcsError (not zeroed)`, () => {
1375
+ const world = new World();
1376
+ const C = scalarComp(`W7Scalar_Set_${form.label.replace(/\W/g, '')}`);
1377
+ const e = world.spawn({ component: C, data: {} }).unwrap();
1378
+ const r = world.set(e, C, { mat: form.value } as never);
1379
+ expect(r.ok).toBe(false);
1380
+ if (r.ok) return;
1381
+ const err = r.error as unknown as { code: string; expected?: string; hint?: string };
1382
+ expect(err.code).toBe(SHARED_INVALID_CODE);
1383
+ expect((err.expected ?? '').length).toBeGreaterThan(0);
1384
+ expect((err.hint ?? '').length).toBeGreaterThan(0);
1385
+ // Zeroing guard: the shared field must NOT have landed as 0. A
1386
+ // rejected set aborts before the column write, so reading the field
1387
+ // back yields the pre-write value (0 from spawn default IS the
1388
+ // sentinel here, so instead assert the write was rejected — the
1389
+ // observable contract is the structured error, not a post-write read).
1390
+ });
1391
+ }
1392
+ });
1393
+
1394
+ describe('array<shared<T>> field', () => {
1395
+ for (const form of GUID_FORMS) {
1396
+ it(`spawn rejects clips:[${form.label}] with structured EcsError (not [0,0,0,0])`, () => {
1397
+ const world = new World();
1398
+ const C = arrayComp(`W7Array_Spawn_${form.label.replace(/\W/g, '')}`);
1399
+ const r = world.spawn({ component: C, data: { clips: [form.value] } as never });
1400
+ expect(r.ok).toBe(false);
1401
+ if (r.ok) return;
1402
+ const err = r.error as unknown as { code: string; expected?: string; hint?: string };
1403
+ expect(err.code).toBe(SHARED_INVALID_CODE);
1404
+ expect((err.expected ?? '').length).toBeGreaterThan(0);
1405
+ expect((err.hint ?? '').length).toBeGreaterThan(0);
1406
+ });
1407
+
1408
+ it(`addComponent rejects clips:[${form.label}] with structured EcsError`, () => {
1409
+ const world = new World();
1410
+ const C = arrayComp(`W7Array_Add_${form.label.replace(/\W/g, '')}`);
1411
+ const e = world.spawn().unwrap();
1412
+ const add = world.addComponent(e, {
1413
+ component: C,
1414
+ data: { clips: [form.value] } as never,
1415
+ });
1416
+ expect(add.ok).toBe(false);
1417
+ if (add.ok) return;
1418
+ expect((add.error as unknown as { code: string }).code).toBe(SHARED_INVALID_CODE);
1419
+ });
1420
+
1421
+ it(`set rejects clips:[${form.label}] with structured EcsError`, () => {
1422
+ const world = new World();
1423
+ const C = arrayComp(`W7Array_Set_${form.label.replace(/\W/g, '')}`);
1424
+ const e = world.spawn({ component: C, data: {} }).unwrap();
1425
+ const r = world.set(e, C, { clips: [form.value] } as never);
1426
+ expect(r.ok).toBe(false);
1427
+ if (r.ok) return;
1428
+ expect((r.error as unknown as { code: string }).code).toBe(SHARED_INVALID_CODE);
1429
+ });
1430
+ }
1431
+ });
1432
+
1433
+ it('valid number handle for a scalar shared field is accepted (no false positive)', () => {
1434
+ const world = new World();
1435
+ const C = scalarComp('W7Scalar_ValidHandle');
1436
+ const handle = world.allocSharedRef('MaterialAsset', {});
1437
+ const r = world.spawn({ component: C, data: { mat: handle } as never });
1438
+ expect(r.ok).toBe(true);
1439
+ });
1440
+
1441
+ it('valid number handles for an array<shared<T>> field are accepted (no false positive)', () => {
1442
+ const world = new World();
1443
+ const C = arrayComp('W7Array_ValidHandles');
1444
+ const h1 = world.allocSharedRef('AnimationClip', {});
1445
+ const h2 = world.allocSharedRef('AnimationClip', {});
1446
+ const r = world.spawn({ component: C, data: { clips: [h1, h2] } as never });
1447
+ expect(r.ok).toBe(true);
1448
+ });
1449
+
1450
+ it('SHARED_INVALID_CODE is a member of EcsErrorCode (exhaustive narrow)', () => {
1451
+ const code: EcsErrorCode = 'shared-field-invalid-value';
1452
+ expect(code).toBe('shared-field-invalid-value');
1453
+ expectTypeOf<'shared-field-invalid-value'>().toMatchTypeOf<EcsErrorCode>();
1454
+ });
1455
+ });
1456
+ }