@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,1837 @@
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__/archetype-storage.test.ts
6
+ // - packages/ecs/src/__tests__/buffer-pool.test.ts
7
+ // - packages/ecs/src/__tests__/buffer-vocab-fixup.test.ts
8
+ // - packages/ecs/src/__tests__/chained-add-component-column-loss.test.ts
9
+ // - packages/ecs/src/__tests__/column.test.ts
10
+ // - packages/ecs/src/__tests__/entity-column.test.ts
11
+ // - packages/ecs/src/__tests__/fixed-array-inline.test.ts
12
+ // - packages/ecs/src/__tests__/query-preregister.test.ts
13
+ // - packages/ecs/src/__tests__/query.test.ts
14
+ //
15
+ // Paradigm: each block-scoped describe('<source-filename>.test.ts', ...) preserves
16
+ // source as ancestorTitles[0]. Top-level imports merged + deduped.
17
+
18
+ import { afterEach, describe, expect, it, vi } from 'vitest';
19
+ import { BufferPool, SIZE_CLASSES } from '../buffer-pool';
20
+ import type { ComponentSchema, ScalarFieldType } from '../component';
21
+ import {
22
+ componentId,
23
+ componentSchema,
24
+ defineComponent,
25
+ fieldTypeToMetaKey,
26
+ isEntityField,
27
+ isManagedArrayField,
28
+ isManagedBufferField,
29
+ isManagedField,
30
+ TYPE_METADATA,
31
+ } from '../component';
32
+ import { Entity } from '../entity';
33
+ import type { EntityHandle } from '../entity-handle';
34
+ import type { Archetype } from '../storage/archetype';
35
+ import {
36
+ createArchetypeGraph,
37
+ getAddEdge,
38
+ getOrCreateArchetype,
39
+ getRemoveEdge,
40
+ } from '../storage/archetype-graph';
41
+ import { createColumn, growColumn, HAS_TRANSFER, isHotSchema } from '../storage/column';
42
+ import {
43
+ appendTableRow,
44
+ createTable as createArchetype,
45
+ growTable as growArchetype,
46
+ removeTableRow as removeEntity,
47
+ type Table,
48
+ } from '../storage/table';
49
+ import { World } from '../world';
50
+
51
+ import { worldInternal } from '../world-internal';
52
+
53
+ function tableColumns(table: Table) {
54
+ return new Map(
55
+ [...table.storage].map(([componentId, componentStorage]) => [
56
+ componentId,
57
+ componentStorage.fields,
58
+ ]),
59
+ );
60
+ }
61
+
62
+ function appendEntity(table: Table, entity: number): number {
63
+ return appendTableRow(table, entity as EntityHandle);
64
+ }
65
+
66
+ {
67
+ // ─── from archetype-storage.test.ts ───
68
+ /** Auto-incrementing archetype ID for test isolation. */
69
+ let testArchId = 0;
70
+
71
+ describe('archetype-storage.test.ts', () => {
72
+ describe('Archetype creation', () => {
73
+ it('creates archetype with sorted component-set key', () => {
74
+ const B = defineComponent('B', { b: { type: 'f32' } });
75
+ const A = defineComponent('A', { a: { type: 'f32' } });
76
+ const arch = createArchetype([B, A], testArchId++);
77
+ const ids = [componentId(Entity), componentId(A), componentId(B)].sort((a, b) => a - b);
78
+ expect(arch.key).toBe(ids.join('+'));
79
+ });
80
+
81
+ it('creates archetype with correct initial capacity', () => {
82
+ const C = defineComponent('Cap', { x: { type: 'f32' } });
83
+ const arch = createArchetype([C], testArchId++);
84
+ expect(arch.capacity).toBeGreaterThanOrEqual(1);
85
+ expect(arch.size).toBe(0);
86
+ });
87
+
88
+ it('creates columns for each component field', () => {
89
+ const Pos = defineComponent('Pos', { x: { type: 'f32' }, y: { type: 'f32' } });
90
+ const arch = createArchetype([Pos], testArchId++);
91
+ expect(tableColumns(arch).has(componentId(Pos))).toBe(true);
92
+ // biome-ignore lint/style/noNonNullAssertion: test setup guarantees componentId(Pos) exists in columns
93
+ const compCols = tableColumns(arch).get(componentId(Pos))!;
94
+ expect(compCols.has('x')).toBe(true);
95
+ expect(compCols.has('y')).toBe(true);
96
+ });
97
+
98
+ it('tag component creates archetype with no data columns', () => {
99
+ const Tag = defineComponent('ArchetypeStorageTag', {});
100
+ const arch = createArchetype([Tag], testArchId++);
101
+ const compCols = tableColumns(arch).get(componentId(Tag));
102
+ expect(compCols === undefined || compCols.size === 0).toBe(true);
103
+ });
104
+ });
105
+
106
+ describe('appendEntity / removeEntity (swap-pop)', () => {
107
+ it('appends entity and increments size', () => {
108
+ const C = defineComponent('SwapTest', { v: { type: 'i32' } });
109
+ const arch = createArchetype([C], testArchId++);
110
+ const row = appendEntity(arch, 42);
111
+ expect(row).toBe(0);
112
+ expect(arch.size).toBe(1);
113
+ // Entity index stored in id=0 self column (not a separate entities array)
114
+ const selfVal = tableColumns(arch).get(componentId(Entity))?.get('self')?.view[0];
115
+ expect(selfVal).toBe(42);
116
+ });
117
+
118
+ it('swap-pop removes entity correctly', () => {
119
+ const C = defineComponent('SwapPop', { v: { type: 'i32' } });
120
+ const arch = createArchetype([C], testArchId++);
121
+
122
+ appendEntity(arch, 10);
123
+ appendEntity(arch, 20);
124
+ appendEntity(arch, 30);
125
+
126
+ // biome-ignore lint/style/noNonNullAssertion: test setup guarantees componentId(C) exists in columns
127
+ const cols = tableColumns(arch).get(componentId(C))!;
128
+ // biome-ignore lint/style/noNonNullAssertion: test setup guarantees 'v' field exists
129
+ const vCol = cols.get('v')!;
130
+ vCol.view[0] = 100;
131
+ vCol.view[1] = 200;
132
+ vCol.view[2] = 300;
133
+
134
+ const swapped = removeEntity(arch, 0);
135
+ expect(arch.size).toBe(2);
136
+
137
+ // Entity identity now read from id=0 self column
138
+ const selfCol = tableColumns(arch).get(componentId(Entity))?.get('self');
139
+ expect(selfCol).toBeDefined();
140
+ // biome-ignore lint/style/noNonNullAssertion: guarded by expect
141
+ expect(selfCol!.view[0]! & 0xffffff).toBe(30);
142
+ expect(vCol.view[0]).toBe(300);
143
+
144
+ // biome-ignore lint/style/noNonNullAssertion: guarded by expect
145
+ expect(selfCol!.view[1]! & 0xffffff).toBe(20);
146
+ expect(vCol.view[1]).toBe(200);
147
+
148
+ expect(swapped).toEqual({ movedEntity: 30, newRow: 0 });
149
+ });
150
+
151
+ it('swap-pop on last row does not swap', () => {
152
+ const C = defineComponent('LastRow', { v: { type: 'f32' } });
153
+ const arch = createArchetype([C], testArchId++);
154
+ appendEntity(arch, 10);
155
+ appendEntity(arch, 20);
156
+
157
+ const swapped = removeEntity(arch, 1);
158
+ expect(arch.size).toBe(1);
159
+ // Entity identity from self column
160
+ const selfVal = tableColumns(arch).get(componentId(Entity))?.get('self')?.view[0];
161
+ expect(selfVal).toBe(10);
162
+ expect(swapped).toBeNull();
163
+ });
164
+ });
165
+
166
+ describe('growArchetype', () => {
167
+ it('doubles capacity and preserves data', () => {
168
+ const C = defineComponent('Grow', { x: { type: 'f32' } });
169
+ const arch = createArchetype([C], testArchId++);
170
+ const initialCap = arch.capacity;
171
+
172
+ for (let i = 0; i < initialCap; i++) {
173
+ appendEntity(arch, i);
174
+ // biome-ignore lint/style/noNonNullAssertion: test setup guarantees componentId(C) exists in columns
175
+ const cols = tableColumns(arch).get(componentId(C))!;
176
+ // biome-ignore lint/style/noNonNullAssertion: field 'x' guaranteed to exist
177
+ cols.get('x')!.view[i] = i * 1.5;
178
+ }
179
+
180
+ growArchetype(arch, initialCap * 2);
181
+ expect(arch.capacity).toBe(initialCap * 2);
182
+
183
+ // biome-ignore lint/style/noNonNullAssertion: test setup guarantees componentId(C) exists in columns
184
+ const cols = tableColumns(arch).get(componentId(C))!;
185
+ for (let i = 0; i < initialCap; i++) {
186
+ expect(cols.get('x')?.view[i]).toBeCloseTo(i * 1.5);
187
+ }
188
+ });
189
+
190
+ it('increments version on grow', () => {
191
+ const C = defineComponent('GrowVer', { x: { type: 'f32' } });
192
+ const arch = createArchetype([C], testArchId++);
193
+ const v0 = arch.version;
194
+ growArchetype(arch, arch.capacity * 2);
195
+ expect(arch.version).toBe(v0 + 1);
196
+ });
197
+
198
+ it('growArchetype is a no-op when targetCapacity <= current capacity', () => {
199
+ const C = defineComponent('GrowNoop', { x: { type: 'f32' } });
200
+ const arch = createArchetype([C], testArchId++);
201
+ const capBefore = arch.capacity;
202
+ const versionBefore = arch.version;
203
+ growArchetype(arch, capBefore);
204
+ expect(arch.capacity).toBe(capBefore);
205
+ expect(arch.version).toBe(versionBefore);
206
+ growArchetype(arch, capBefore - 1);
207
+ expect(arch.capacity).toBe(capBefore);
208
+ expect(arch.version).toBe(versionBefore);
209
+ });
210
+ });
211
+
212
+ describe('ArchetypeGraph', () => {
213
+ it('getOrCreateArchetype returns same archetype for same component set', () => {
214
+ const graph = createArchetypeGraph();
215
+ const A = defineComponent('GA', { a: { type: 'f32' } });
216
+ const B = defineComponent('GB', { b: { type: 'f32' } });
217
+
218
+ const arch1 = getOrCreateArchetype(graph, [componentId(A), componentId(B)], [A, B]);
219
+ const arch2 = getOrCreateArchetype(graph, [componentId(B), componentId(A)], [B, A]);
220
+ expect(arch1).toBe(arch2);
221
+ });
222
+
223
+ it('getAddEdge caches edge (O(1) on second call)', () => {
224
+ const graph = createArchetypeGraph();
225
+ const A = defineComponent('EA', { a: { type: 'f32' } });
226
+ const B = defineComponent('EB', { b: { type: 'f32' } });
227
+
228
+ const archA = getOrCreateArchetype(graph, [componentId(A)], [A]);
229
+ const archAB1 = getAddEdge(graph, archA, componentId(B), B);
230
+ const archAB2 = getAddEdge(graph, archA, componentId(B), B);
231
+ expect(archAB1).toBe(archAB2);
232
+ });
233
+
234
+ it('getRemoveEdge caches edge (O(1) on second call)', () => {
235
+ const graph = createArchetypeGraph();
236
+ const A = defineComponent('RA', { a: { type: 'f32' } });
237
+ const B = defineComponent('RB', { b: { type: 'f32' } });
238
+
239
+ const archAB = getOrCreateArchetype(graph, [componentId(A), componentId(B)], [A, B]);
240
+ const archA1 = getRemoveEdge(graph, archAB, componentId(B));
241
+ const archA2 = getRemoveEdge(graph, archAB, componentId(B));
242
+ expect(archA1).toBe(archA2);
243
+ });
244
+
245
+ it('multi-component spawn targets correct archetype directly', () => {
246
+ const graph = createArchetypeGraph();
247
+ const A = defineComponent('MA', { a: { type: 'f32' } });
248
+ const B = defineComponent('MB', { b: { type: 'f32' } });
249
+ const C = defineComponent('MC', { c: { type: 'f32' } });
250
+
251
+ const arch = getOrCreateArchetype(
252
+ graph,
253
+ [componentId(A), componentId(B), componentId(C)],
254
+ [A, B, C],
255
+ );
256
+ const ids = [componentId(Entity), componentId(A), componentId(B), componentId(C)].sort(
257
+ (a, b) => a - b,
258
+ );
259
+ expect(arch.key).toBe(ids.join('+'));
260
+ });
261
+
262
+ it('increments global archetype generation on new archetype creation', () => {
263
+ const graph = createArchetypeGraph();
264
+ const gen0 = graph.generation;
265
+ const X = defineComponent('GenX', { x: { type: 'f32' } });
266
+ getOrCreateArchetype(graph, [componentId(X)], [X]);
267
+ expect(graph.generation).toBe(gen0 + 1);
268
+
269
+ getOrCreateArchetype(graph, [componentId(X)], [X]);
270
+ expect(graph.generation).toBe(gen0 + 1);
271
+ });
272
+ });
273
+
274
+ describe('M1 storage characterization', () => {
275
+ it('keeps row, capacity, version, and migration owners explicit', () => {
276
+ const graph = createArchetypeGraph();
277
+ const Position = defineComponent('M1StoragePosition', { x: 'f32', y: 'f32' });
278
+ const Tag = defineComponent('M1StorageTag', {});
279
+ const source = getOrCreateArchetype(graph, [componentId(Position)], [Position]);
280
+ const target = getAddEdge(graph, source, componentId(Tag), Tag);
281
+ const back = getRemoveEdge(graph, target, componentId(Tag));
282
+ const sourceTable = graph.tables[source.tableId];
283
+ if (sourceTable === undefined) throw new Error('source table missing');
284
+
285
+ expect(back).toBe(source);
286
+ expect(sourceTable.storage.has(componentId(Entity))).toBe(true);
287
+ expect(sourceTable.storage.has(componentId(Position))).toBe(true);
288
+ for (const { fields } of sourceTable.storage.values()) {
289
+ for (const column of fields.values()) {
290
+ expect(column.capacity).toBe(sourceTable.capacity);
291
+ expect(column.arity).toBe(1);
292
+ }
293
+ }
294
+
295
+ const initialVersion = sourceTable.version;
296
+ appendEntity(sourceTable, 7 as EntityHandle);
297
+ expect(sourceTable.size).toBe(1);
298
+ growArchetype(sourceTable, sourceTable.capacity * 2);
299
+ expect(sourceTable.version).toBe(initialVersion + 1);
300
+ expect(sourceTable.size).toBe(1);
301
+ for (const { fields } of sourceTable.storage.values()) {
302
+ for (const column of fields.values()) expect(column.capacity).toBe(sourceTable.capacity);
303
+ }
304
+ });
305
+ });
306
+ });
307
+ }
308
+
309
+ {
310
+ // ─── from buffer-pool.test.ts ───
311
+ describe('buffer-pool.test.ts', () => {
312
+ describe('w10 - BufferPool size-class allocation', () => {
313
+ it('alloc(128) returns a view of byteLength === 128 (bucket 256B)', () => {
314
+ const pool = new BufferPool();
315
+ const r = pool.alloc(128);
316
+ expect(r.ok).toBe(true);
317
+ if (!r.ok) throw new Error('expected ok');
318
+ expect(r.value.view.byteLength).toBe(128);
319
+ expect(r.value.id).toBeGreaterThan(0);
320
+ });
321
+
322
+ it('alloc(0) is legal and returns a zero-length view (no bucket)', () => {
323
+ const pool = new BufferPool();
324
+ const r = pool.alloc(0);
325
+ expect(r.ok).toBe(true);
326
+ if (!r.ok) throw new Error('expected ok');
327
+ expect(r.value.view.byteLength).toBe(0);
328
+ });
329
+
330
+ it('release then alloc(128) reuses a slot from the free-list', () => {
331
+ const pool = new BufferPool();
332
+ const a = pool.alloc(128);
333
+ if (!a.ok) throw new Error('expected ok a');
334
+ const idA = a.value.id;
335
+ const rel = pool.release(idA);
336
+ expect(rel.ok).toBe(true);
337
+ const b = pool.alloc(128);
338
+ if (!b.ok) throw new Error('expected ok b');
339
+ expect(b.value.id).toBe(idA);
340
+ expect(b.value.view.byteLength).toBe(128);
341
+ });
342
+ });
343
+
344
+ describe('w10 - BufferPool.grow (D-6 Result semantics, D-7 no-shrink)', () => {
345
+ it('grow preserves prior content byte-equal across same-bucket growth', () => {
346
+ const pool = new BufferPool();
347
+ const a = pool.alloc(128);
348
+ if (!a.ok) throw new Error('expected ok a');
349
+ const view0 = a.value.view;
350
+ for (let i = 0; i < 128; i++) view0[i] = (i * 7) & 0xff;
351
+ const r = pool.grow(a.value.id, 200);
352
+ expect(r.ok).toBe(true);
353
+ if (!r.ok) throw new Error('expected ok grow');
354
+ const view1 = r.value;
355
+ expect(view1.byteLength).toBe(200);
356
+ for (let i = 0; i < 128; i++) expect(view1[i]).toBe((i * 7) & 0xff);
357
+ });
358
+
359
+ it('grow(view, byteLength === current) returns ok no-op view', () => {
360
+ const pool = new BufferPool();
361
+ const a = pool.alloc(128);
362
+ if (!a.ok) throw new Error('expected ok a');
363
+ const r = pool.grow(a.value.id, 128);
364
+ expect(r.ok).toBe(true);
365
+ if (!r.ok) throw new Error('expected ok grow');
366
+ expect(r.value.byteLength).toBe(128);
367
+ });
368
+
369
+ it('grow(view, smaller) returns err(managed-buffer-shrink-not-supported)', () => {
370
+ const pool = new BufferPool();
371
+ const a = pool.alloc(128);
372
+ if (!a.ok) throw new Error('expected ok a');
373
+ const r = pool.grow(a.value.id, 100);
374
+ expect(r.ok).toBe(false);
375
+ if (r.ok) throw new Error('expected err grow');
376
+ expect(r.error.code).toBe('managed-buffer-shrink-not-supported');
377
+ if (r.error.code !== 'managed-buffer-shrink-not-supported') throw new Error('narrow');
378
+ expect(r.error.detail.requested).toBe(100);
379
+ expect(r.error.detail.current).toBe(128);
380
+ });
381
+
382
+ it('cross-bucket grow (128 -> 4096) preserves prior content byte-equal', () => {
383
+ const pool = new BufferPool();
384
+ const a = pool.alloc(128);
385
+ if (!a.ok) throw new Error('expected ok a');
386
+ const view0 = a.value.view;
387
+ for (let i = 0; i < 128; i++) view0[i] = (i + 1) & 0xff;
388
+ const r = pool.grow(a.value.id, 4096);
389
+ expect(r.ok).toBe(true);
390
+ if (!r.ok) throw new Error('expected ok grow');
391
+ const view1 = r.value;
392
+ expect(view1.byteLength).toBe(4096);
393
+ for (let i = 0; i < 128; i++) expect(view1[i]).toBe((i + 1) & 0xff);
394
+ const refreshed = pool.view(a.value.id);
395
+ expect(refreshed.byteLength).toBe(4096);
396
+ });
397
+
398
+ it('alloc(beyond max bucket) returns err(managed-buffer-out-of-bounds)', () => {
399
+ const pool = new BufferPool();
400
+ const top = SIZE_CLASSES[SIZE_CLASSES.length - 1];
401
+ if (top === undefined) throw new Error('SIZE_CLASSES empty');
402
+ const overshoot = top + 1;
403
+ const r = pool.alloc(overshoot);
404
+ expect(r.ok).toBe(false);
405
+ if (r.ok) throw new Error('expected err alloc');
406
+ expect(r.error.code).toBe('managed-buffer-out-of-bounds');
407
+ if (r.error.code !== 'managed-buffer-out-of-bounds') throw new Error('narrow');
408
+ expect(r.error.detail.index).toBe(overshoot);
409
+ });
410
+
411
+ it('M4 prelude: distinct ids on consecutive alloc with no release in between', () => {
412
+ const pool = new BufferPool();
413
+ const a = pool.alloc(128);
414
+ const b = pool.alloc(128);
415
+ if (!a.ok || !b.ok) throw new Error('expected ok x2');
416
+ expect(a.value.id).not.toBe(b.value.id);
417
+ });
418
+ });
419
+ });
420
+ }
421
+
422
+ {
423
+ // ─── from buffer-vocab-fixup.test.ts ───
424
+ describe('buffer-vocab-fixup.test.ts', () => {
425
+ describe('verify round 1 fix-up — B1: world.set on buffer<N> validates byteLength', () => {
426
+ it('returns Result.err with code "fixed-size-mismatch" when M < N', () => {
427
+ const Print = defineComponent('Print', { fp: { type: 'buffer<16>' } });
428
+ const w = new World();
429
+ const e = w.spawn({ component: Print, data: { fp: new Uint8Array(16) } }).unwrap();
430
+ const r = w.set(e, Print, { fp: new Uint8Array(12) });
431
+ expect(r.ok).toBe(false);
432
+ if (!r.ok) {
433
+ expect(r.error.code).toBe('fixed-size-mismatch');
434
+ if (r.error.code === 'fixed-size-mismatch') {
435
+ expect(r.error.detail).toEqual({ expected: 16, actual: 12 });
436
+ expect(r.error.hint).toContain('buffer<16>');
437
+ expect(r.error.hint).toContain('byteLength 12');
438
+ }
439
+ }
440
+ });
441
+
442
+ it('returns Result.err with code "fixed-size-mismatch" when M > N', () => {
443
+ const PrintBuf8 = defineComponent('PrintBuf8', { fp: { type: 'buffer<8>' } });
444
+ const w = new World();
445
+ const e = w.spawn({ component: PrintBuf8, data: { fp: new Uint8Array(8) } }).unwrap();
446
+ const r = w.set(e, PrintBuf8, { fp: new Uint8Array(20) });
447
+ expect(r.ok).toBe(false);
448
+ if (!r.ok) {
449
+ expect(r.error.code).toBe('fixed-size-mismatch');
450
+ if (r.error.code === 'fixed-size-mismatch') {
451
+ expect(r.error.detail).toEqual({ expected: 8, actual: 20 });
452
+ }
453
+ }
454
+ });
455
+
456
+ it('matching byteLength still succeeds and writes through to world.get snapshot', () => {
457
+ const Print = defineComponent('PrintBuf4', { fp: { type: 'buffer<4>' } });
458
+ const w = new World();
459
+ const e = w.spawn({ component: Print, data: { fp: new Uint8Array(4) } }).unwrap();
460
+ const r = w.set(e, Print, { fp: new Uint8Array([1, 2, 3, 4]) });
461
+ expect(r.ok).toBe(true);
462
+ const snapshot = w.get(e, Print).unwrap().fp;
463
+ expect(snapshot.byteLength).toBe(4);
464
+ expect(Array.from(snapshot)).toEqual([1, 2, 3, 4]);
465
+ });
466
+ });
467
+
468
+ describe('verify round 1 fix-up — B2: bare "buffer" (variable) spawn + set round-trip', () => {
469
+ it('spawn with payload bytes round-trips through world.get without data loss', () => {
470
+ const Blob = defineComponent('BlobVocab', { b: { type: 'buffer' } });
471
+ const w = new World();
472
+ const payload = new Uint8Array([7, 8, 9]);
473
+ const e = w.spawn({ component: Blob, data: { b: payload } }).unwrap();
474
+ const snapshot = w.get(e, Blob).unwrap().b;
475
+ expect(snapshot.byteLength).toBe(3);
476
+ expect(Array.from(snapshot)).toEqual([7, 8, 9]);
477
+ });
478
+
479
+ it('world.set after spawn replaces variable-buffer bytes (no fixed-size constraint)', () => {
480
+ const Blob = defineComponent('BlobVocab2', { b: { type: 'buffer' } });
481
+ const w = new World();
482
+ const e = w.spawn({ component: Blob, data: { b: new Uint8Array([1]) } }).unwrap();
483
+ const r = w.set(e, Blob, { b: new Uint8Array([10, 20, 30, 40, 50]) });
484
+ expect(r.ok).toBe(true);
485
+ const snapshot = w.get(e, Blob).unwrap().b;
486
+ expect(snapshot.byteLength).toBe(5);
487
+ expect(Array.from(snapshot)).toEqual([10, 20, 30, 40, 50]);
488
+ });
489
+
490
+ it('spawn with an empty Uint8Array yields a zero-length live view (no error)', () => {
491
+ const Blob = defineComponent('BlobVocab3', { b: { type: 'buffer' } });
492
+ const w = new World();
493
+ const e = w.spawn({ component: Blob, data: { b: new Uint8Array(0) } }).unwrap();
494
+ const snapshot = w.get(e, Blob).unwrap().b;
495
+ expect(snapshot.byteLength).toBe(0);
496
+ });
497
+
498
+ it('ManagedBufferOutOfBoundsError hint references the new buffer keyword forms (no legacy "buffer:<bytes>")', () => {
499
+ const Blob = defineComponent('BlobVocab4', { b: { type: 'buffer' } });
500
+ const w = new World();
501
+ const oversized = new Uint8Array(262_145);
502
+ const result = w.spawn({ component: Blob, data: { b: oversized } });
503
+ expect(result.ok).toBe(true);
504
+ });
505
+ });
506
+ });
507
+ }
508
+
509
+ {
510
+ // ─── from chained-add-component-column-loss.test.ts ───
511
+ describe('chained-add-component-column-loss.test.ts', () => {
512
+ describe('chained addComponent preserves earlier-added column data (F-2)', () => {
513
+ it('two chained single addComponent on a >=2-component entity preserves both columns', () => {
514
+ const C = defineComponent('C', { a: { type: 'u32' } });
515
+ const D = defineComponent('D', { b: { type: 'u32' } });
516
+ const Base1 = defineComponent('Base1', { s: { type: 'string' } });
517
+ const Base2 = defineComponent('Base2', { x: { type: 'f32' } });
518
+
519
+ const w = new World();
520
+
521
+ const e = w
522
+ .spawn({ component: Base1, data: { s: 'hi' } }, { component: Base2, data: { x: 1.5 } })
523
+ .unwrap();
524
+
525
+ w.addComponent(e, { component: C, data: { a: 1033 } }).unwrap();
526
+ w.addComponent(e, { component: D, data: { b: 55 } }).unwrap();
527
+
528
+ const cGet = w.get(e, C);
529
+ const dGet = w.get(e, D);
530
+ if (!cGet.ok) throw new Error('expected ok get C');
531
+ if (!dGet.ok) throw new Error('expected ok get D');
532
+ expect(cGet.value.a).toBe(1033);
533
+ expect(dGet.value.b).toBe(55);
534
+
535
+ const b1 = w.get(e, Base1);
536
+ const b2 = w.get(e, Base2);
537
+ if (!b1.ok || !b2.ok) throw new Error('expected ok get base');
538
+ expect(b1.value.s).toBe('hi');
539
+ expect(b2.value.x).toBeCloseTo(1.5);
540
+ });
541
+
542
+ it('single-component base + two chained adds (registration adds-first)', () => {
543
+ const C = defineComponent('SC', { a: { type: 'u32' } });
544
+ const D = defineComponent('SD', { b: { type: 'u32' } });
545
+ const Base = defineComponent('SBase', { s: { type: 'string' } });
546
+
547
+ const w = new World();
548
+
549
+ const e = w.spawn({ component: Base, data: { s: 'hi' } }).unwrap();
550
+ w.addComponent(e, { component: C, data: { a: 1033 } }).unwrap();
551
+ w.addComponent(e, { component: D, data: { b: 55 } }).unwrap();
552
+
553
+ const cGet = w.get(e, C);
554
+ if (!cGet.ok) throw new Error('expected ok get C');
555
+ expect(cGet.value.a).toBe(1033);
556
+ });
557
+ });
558
+ });
559
+ }
560
+
561
+ {
562
+ // ─── from column.test.ts ───
563
+ describe('column.test.ts', () => {
564
+ describe('Column.arity', () => {
565
+ it('scalar column arity defaults to 1 (AC-01)', () => {
566
+ const col = createColumn('f32', 64);
567
+ expect(col.arity).toBe(1);
568
+ });
569
+
570
+ it('all 11 scalar field types default arity to 1', () => {
571
+ const types: ScalarFieldType[] = [
572
+ 'f32',
573
+ 'f64',
574
+ 'i32',
575
+ 'u32',
576
+ 'i16',
577
+ 'u16',
578
+ 'i8',
579
+ 'u8',
580
+ 'bool',
581
+ 'enum',
582
+ 'ref',
583
+ ];
584
+ for (const t of types) {
585
+ const col = createColumn(t, 16);
586
+ expect(col.arity, `Column.arity for ${t} should be 1`).toBe(1);
587
+ }
588
+ });
589
+
590
+ it('empty column (capacity=0) growColumn does not divide by zero', () => {
591
+ const col = createColumn('f32', 0, 1);
592
+ expect(col.capacity).toBe(0);
593
+ expect(col.arity).toBe(1);
594
+ const grown = growColumn(col, 8);
595
+ expect(grown.capacity).toBe(8);
596
+ expect(grown.arity).toBe(1);
597
+ expect(grown.buffer.byteLength).toBe(32);
598
+ });
599
+ });
600
+
601
+ describe('createColumn', () => {
602
+ it('creates a column with correct capacity', () => {
603
+ const col = createColumn('f32', 64);
604
+ expect(col.capacity).toBe(64);
605
+ });
606
+
607
+ it('creates an ArrayBuffer of correct byte length', () => {
608
+ const col = createColumn('f32', 64);
609
+ expect(col.buffer.byteLength).toBe(64 * 4);
610
+ });
611
+
612
+ it('creates a TypedArray view of correct type for f32', () => {
613
+ const col = createColumn('f32', 16);
614
+ expect(col.view).toBeInstanceOf(Float32Array);
615
+ expect(col.view.length).toBe(16);
616
+ });
617
+
618
+ it('creates correct TypedArray view for each of the 11 scalar types', () => {
619
+ const typeToView: Record<ScalarFieldType, unknown> = {
620
+ f32: Float32Array,
621
+ f64: Float64Array,
622
+ i32: Int32Array,
623
+ u32: Uint32Array,
624
+ i16: Int16Array,
625
+ u16: Uint16Array,
626
+ i8: Int8Array,
627
+ u8: Uint8Array,
628
+ bool: Uint8Array,
629
+ enum: Uint32Array,
630
+ ref: Uint32Array,
631
+ };
632
+ for (const [fieldType, ViewCtor] of Object.entries(typeToView)) {
633
+ const col = createColumn(fieldType as ScalarFieldType, 8);
634
+ // biome-ignore lint/complexity/noBannedTypes: ViewCtor is a constructor from Object.entries, Function is the correct generic type here
635
+ expect(col.view).toBeInstanceOf(ViewCtor as Function);
636
+ }
637
+ });
638
+
639
+ it('each field has an independent ArrayBuffer (not shared)', () => {
640
+ const col1 = createColumn('f32', 16);
641
+ const col2 = createColumn('f32', 16);
642
+ expect(col1.buffer).not.toBe(col2.buffer);
643
+ });
644
+ });
645
+
646
+ describe('growColumn', () => {
647
+ it('grows an ArrayBuffer column when SharedArrayBuffer is unavailable', () => {
648
+ const col = createColumn('f32', 4);
649
+ col.view[0] = 7;
650
+ vi.stubGlobal('SharedArrayBuffer', undefined);
651
+ try {
652
+ const grown = growColumn(col, 8);
653
+ expect(grown.view[0]).toBe(7);
654
+ expect(grown.capacity).toBe(8);
655
+ } finally {
656
+ vi.unstubAllGlobals();
657
+ }
658
+ });
659
+
660
+ it('grows column to new capacity with data intact', () => {
661
+ const col = createColumn('f32', 4);
662
+ col.view[0] = 1.5;
663
+ col.view[1] = 2.5;
664
+ col.view[2] = 3.5;
665
+
666
+ const grown = growColumn(col, 8);
667
+ expect(grown.capacity).toBe(8);
668
+ expect(grown.view[0]).toBeCloseTo(1.5);
669
+ expect(grown.view[1]).toBeCloseTo(2.5);
670
+ expect(grown.view[2]).toBeCloseTo(3.5);
671
+ });
672
+
673
+ it('old column is not affected by grow (fallback path)', () => {
674
+ if (HAS_TRANSFER) {
675
+ const col = createColumn('i32', 4);
676
+ col.view[0] = 42;
677
+ const grown = growColumn(col, 8);
678
+ expect(grown.view[0]).toBe(42);
679
+ expect(col.buffer.byteLength).toBe(0);
680
+ } else {
681
+ const col = createColumn('i32', 4);
682
+ col.view[0] = 42;
683
+ const grown = growColumn(col, 8);
684
+ grown.view[0] = 99;
685
+ expect(col.view[0]).toBe(42);
686
+ }
687
+ });
688
+
689
+ it('works for all scalar field types', () => {
690
+ const types: ScalarFieldType[] = [
691
+ 'f32',
692
+ 'f64',
693
+ 'i32',
694
+ 'u32',
695
+ 'i16',
696
+ 'u16',
697
+ 'i8',
698
+ 'u8',
699
+ 'bool',
700
+ 'enum',
701
+ 'ref',
702
+ ];
703
+ for (const t of types) {
704
+ const col = createColumn(t, 4);
705
+ col.view[0] = 1;
706
+ const grown = growColumn(col, 8);
707
+ expect(grown.view[0]).toBe(1);
708
+ expect(grown.capacity).toBe(8);
709
+ }
710
+ });
711
+ });
712
+
713
+ describe('growColumn transfer() / fallback equivalence', () => {
714
+ function fallbackGrow(
715
+ col: {
716
+ buffer: ArrayBufferLike;
717
+ fieldType: ScalarFieldType;
718
+ capacity: number;
719
+ view: { BYTES_PER_ELEMENT: number };
720
+ },
721
+ newCapacity: number,
722
+ ) {
723
+ const byteLen = col.view.BYTES_PER_ELEMENT * newCapacity;
724
+ const buf = new ArrayBuffer(byteLen);
725
+ new Uint8Array(buf).set(new Uint8Array(col.buffer));
726
+ return buf;
727
+ }
728
+
729
+ it('both paths produce identical data for f32', () => {
730
+ const col = createColumn('f32', 4);
731
+ col.view[0] = 1.5;
732
+ col.view[1] = -3.14;
733
+ col.view[2] = 0;
734
+ col.view[3] = 999.999;
735
+
736
+ const fallbackBuf = fallbackGrow(col, 8);
737
+
738
+ const grown = growColumn(col, 8);
739
+ const grownBytes = new Uint8Array(grown.buffer, 0, 16);
740
+ const fallbackBytes = new Uint8Array(fallbackBuf, 0, 16);
741
+ expect(Array.from(grownBytes)).toEqual(Array.from(fallbackBytes));
742
+ expect(grown.view[0]).toBeCloseTo(1.5);
743
+ expect(grown.view[1]).toBeCloseTo(-3.14);
744
+ expect(grown.view[3]).toBeCloseTo(999.999);
745
+ });
746
+
747
+ it('both paths produce identical data for i32', () => {
748
+ const col = createColumn('i32', 4);
749
+ col.view[0] = -2147483648;
750
+ col.view[1] = 2147483647;
751
+ col.view[2] = 0;
752
+ col.view[3] = 42;
753
+
754
+ const fallbackBuf = fallbackGrow(col, 8);
755
+
756
+ const grown = growColumn(col, 8);
757
+ const grownBytes = new Uint8Array(grown.buffer, 0, 16);
758
+ const fallbackBytes = new Uint8Array(fallbackBuf, 0, 16);
759
+ expect(Array.from(grownBytes)).toEqual(Array.from(fallbackBytes));
760
+ expect(grown.view[0]).toBe(-2147483648);
761
+ expect(grown.view[1]).toBe(2147483647);
762
+ expect(grown.view[3]).toBe(42);
763
+ });
764
+
765
+ it('both paths produce identical data for u8 (bool backing)', () => {
766
+ const col = createColumn('u8', 4);
767
+ col.view[0] = 0;
768
+ col.view[1] = 1;
769
+ col.view[2] = 255;
770
+ col.view[3] = 128;
771
+
772
+ const fallbackBuf = fallbackGrow(col, 16);
773
+
774
+ const grown = growColumn(col, 16);
775
+ const grownBytes = new Uint8Array(grown.buffer, 0, 4);
776
+ const fallbackBytes = new Uint8Array(fallbackBuf, 0, 4);
777
+ expect(Array.from(grownBytes)).toEqual(Array.from(fallbackBytes));
778
+ });
779
+
780
+ it('data is not lost after growing to 2x, 4x, 8x capacity', () => {
781
+ let col = createColumn('f64', 2);
782
+ col.view[0] = Math.PI;
783
+ col.view[1] = Math.E;
784
+
785
+ col = growColumn(col, 4);
786
+ expect(col.view[0]).toBeCloseTo(Math.PI, 14);
787
+ expect(col.view[1]).toBeCloseTo(Math.E, 14);
788
+
789
+ col = growColumn(col, 8);
790
+ expect(col.view[0]).toBeCloseTo(Math.PI, 14);
791
+ expect(col.view[1]).toBeCloseTo(Math.E, 14);
792
+
793
+ col = growColumn(col, 16);
794
+ expect(col.view[0]).toBeCloseTo(Math.PI, 14);
795
+ expect(col.view[1]).toBeCloseTo(Math.E, 14);
796
+ expect(col.capacity).toBe(16);
797
+ });
798
+
799
+ it('new capacity region is zeroed', () => {
800
+ const col = createColumn('i32', 4);
801
+ col.view[0] = 100;
802
+ col.view[1] = 200;
803
+
804
+ const grown = growColumn(col, 8);
805
+ for (let i = 4; i < 8; i++) {
806
+ expect(grown.view[i]).toBe(0);
807
+ }
808
+ });
809
+
810
+ it('HAS_TRANSFER is a boolean', () => {
811
+ expect(typeof HAS_TRANSFER).toBe('boolean');
812
+ });
813
+ });
814
+
815
+ describe('growColumn stride-N data fidelity (AC-05)', () => {
816
+ const ARITY = 16;
817
+ const PATTERN = Array.from({ length: ARITY }, (_, i) => i * 1.5 + 0.1);
818
+
819
+ function writeRow0(col: ReturnType<typeof createColumn>, vals: ReadonlyArray<number>) {
820
+ for (let i = 0; i < vals.length; i++) {
821
+ const v = vals[i];
822
+ if (v === undefined) throw new Error('unexpected');
823
+ col.view[i] = v;
824
+ }
825
+ }
826
+
827
+ function expectRow0Unchanged(col: ReturnType<typeof createColumn>) {
828
+ for (let i = 0; i < ARITY; i++) {
829
+ const expected = PATTERN[i];
830
+ if (expected === undefined) throw new Error('unexpected');
831
+ expect(col.view[i], `row[0][${i}] after capacity ${col.capacity}`).toBeCloseTo(
832
+ expected,
833
+ 6,
834
+ );
835
+ }
836
+ }
837
+
838
+ it('grow 0->4->8->16->32->64 preserves row 0 stride-16 data', () => {
839
+ let col = createColumn('f32', 0, ARITY);
840
+ expect(col.arity).toBe(ARITY);
841
+ expect(col.capacity).toBe(0);
842
+
843
+ const stages = [4, 8, 16, 32, 64];
844
+ for (const cap of stages) {
845
+ col = growColumn(col, cap);
846
+ expect(col.capacity).toBe(cap);
847
+ expect(col.arity).toBe(ARITY);
848
+
849
+ if (cap === 4) {
850
+ writeRow0(col, PATTERN);
851
+ }
852
+
853
+ expectRow0Unchanged(col);
854
+ }
855
+ });
856
+
857
+ it('grow from nonzero capacity preserves row 0 for arity=16', () => {
858
+ let col = createColumn('f32', 8, ARITY);
859
+ writeRow0(col, PATTERN);
860
+
861
+ const stages = [16, 32, 64, 128];
862
+ for (const cap of stages) {
863
+ col = growColumn(col, cap);
864
+ expect(col.capacity).toBe(cap);
865
+ expect(col.arity).toBe(ARITY);
866
+ expectRow0Unchanged(col);
867
+ }
868
+ });
869
+
870
+ it('grow from capacity 1 preserves arity=4 f64 row', () => {
871
+ const f64pattern = [Math.PI, Math.E, Math.LN2, Math.SQRT2];
872
+ let col = createColumn('f64', 1, 4);
873
+ expect(col.arity).toBe(4);
874
+ for (let i = 0; i < 4; i++) {
875
+ const v = f64pattern[i];
876
+ if (v === undefined) throw new Error('unexpected');
877
+ col.view[i] = v;
878
+ }
879
+
880
+ const stages = [2, 4, 8, 16, 32];
881
+ for (const cap of stages) {
882
+ col = growColumn(col, cap);
883
+ for (let i = 0; i < 4; i++) {
884
+ const v = f64pattern[i];
885
+ if (v === undefined) throw new Error('unexpected');
886
+ expect(col.view[i], `f64[${i}] at capacity ${cap}`).toBeCloseTo(v, 14);
887
+ }
888
+ }
889
+ });
890
+
891
+ it('new capacity tail rows are zeroed for arity>1', () => {
892
+ const AR = 3;
893
+ const col = createColumn('u32', 2, AR);
894
+ col.view[0] = 0xdeadbeef;
895
+ col.view[1] = 0xcafebabe;
896
+ col.view[2] = 0x12345678;
897
+
898
+ const grown = growColumn(col, 4);
899
+ for (let i = 3; i < 6; i++) {
900
+ expect(grown.view[i], `tail row[${i}]`).toBe(0);
901
+ }
902
+ expect(grown.view[0]).toBe(0xdeadbeef);
903
+ expect(grown.view[1]).toBe(0xcafebabe);
904
+ expect(grown.view[2]).toBe(0x12345678);
905
+ });
906
+ });
907
+
908
+ describe('isHotSchema', () => {
909
+ it('returns true for all-scalar schema', () => {
910
+ const schema: ComponentSchema = { x: 'f32', y: 'f32', z: 'f32' };
911
+ expect(isHotSchema(schema)).toBe(true);
912
+ });
913
+
914
+ it('returns true for single scalar field', () => {
915
+ expect(isHotSchema({ hp: 'i32' })).toBe(true);
916
+ });
917
+
918
+ it('returns true for all 11 scalar types mixed', () => {
919
+ const schema: ComponentSchema = {
920
+ a: 'f32',
921
+ b: 'f64',
922
+ c: 'i32',
923
+ d: 'u32',
924
+ e: 'i16',
925
+ f: 'u16',
926
+ g: 'i8',
927
+ h: 'u8',
928
+ i: 'bool',
929
+ j: 'enum',
930
+ k: 'ref',
931
+ };
932
+ expect(isHotSchema(schema)).toBe(true);
933
+ });
934
+
935
+ it('returns true for empty schema (tag component)', () => {
936
+ expect(isHotSchema({})).toBe(true);
937
+ });
938
+
939
+ it('returns false for schema with unsupported field type (cold-table path)', () => {
940
+ const nonScalarSchema = { data: 'string' as unknown as 'f32' };
941
+ expect(isHotSchema(nonScalarSchema)).toBe(false);
942
+ });
943
+ });
944
+ });
945
+ }
946
+
947
+ {
948
+ // ─── from entity-column.test.ts ───
949
+ let dummyCounter = 0;
950
+ function defineSpawnComponent(prefix: string) {
951
+ dummyCounter += 1;
952
+ return defineComponent(`${prefix}_${dummyCounter}`, { x: 'f32', y: 'f32' });
953
+ }
954
+
955
+ function archetypeOf(world: World, e: EntityHandle): Table {
956
+ const graph = (world as unknown as { graph: { archetypes: Archetype[]; tables: Table[] } })
957
+ .graph;
958
+ const records = (world as unknown as { records: { archetypeId: number }[] }).records;
959
+ const indexSlot = (e as unknown as number) & 0xffffff;
960
+ const archId = records[indexSlot]?.archetypeId ?? -1;
961
+ const arch = graph.archetypes[archId];
962
+ if (!arch) throw new Error('archetype not found for entity');
963
+ const table = graph.tables[arch.tableId];
964
+ if (!table) throw new Error('table not found for entity');
965
+ return table;
966
+ }
967
+
968
+ describe('entity-column.test.ts', () => {
969
+ describe('Entity id=0 structural guarantee (AC-01)', () => {
970
+ it('assigns the Entity component id=0', () => {
971
+ expect(componentId(Entity)).toBe(0);
972
+ });
973
+
974
+ it('names the single field self with the entity field type', () => {
975
+ expect(Entity.name).toBe('Entity');
976
+ expect(componentSchema(Entity)).toEqual({ self: 'entity' });
977
+ });
978
+ });
979
+
980
+ describe('Entity barrel forced-registration fail-fast (AC-01)', () => {
981
+ afterEach(() => {
982
+ vi.resetModules();
983
+ });
984
+
985
+ it('throws at barrel eval when a component registers before Entity', async () => {
986
+ vi.resetModules();
987
+ const { defineComponent: freshDefine } = await import('../component');
988
+ freshDefine('TamperOrderDummy', { x: 'f32' });
989
+ // feat-20260611 D-9: assertion text switched from `expected id=0` to
990
+ // `ESSENTIAL_COMPONENT_IDS invariant violated` (SSOT-driven contract).
991
+ await expect(import('../index')).rejects.toThrow(/ESSENTIAL_COMPONENT_IDS/);
992
+ });
993
+
994
+ it('does not throw at barrel eval under the normal import order', async () => {
995
+ vi.resetModules();
996
+ await expect(import('../index')).resolves.toBeDefined();
997
+ });
998
+ });
999
+
1000
+ describe('archetype unconditionally carries the id=0 column (AC-02)', () => {
1001
+ it('includes the Entity column in a single-component spawn archetype', () => {
1002
+ const world = new World();
1003
+ const Position = defineSpawnComponent('AC02_Pos');
1004
+ const e = world.spawn({ component: Position, data: { x: 1, y: 2 } }).unwrap();
1005
+ const arch = archetypeOf(world, e);
1006
+ expect(arch.storage.has(componentId(Entity))).toBe(true);
1007
+ expect(arch.storage.get(componentId(Entity))?.fields.has('self')).toBe(true);
1008
+ });
1009
+
1010
+ it('includes the Entity column in a bare (zero-component) spawn archetype', () => {
1011
+ const world = new World();
1012
+ const e = world.spawn().unwrap();
1013
+ const arch = archetypeOf(world, e);
1014
+ expect(arch.storage.has(componentId(Entity))).toBe(true);
1015
+ expect(arch.storage.get(componentId(Entity))?.fields.has('self')).toBe(true);
1016
+ });
1017
+
1018
+ it('includes the Entity column after addComponent migration', () => {
1019
+ const world = new World();
1020
+ const A = defineSpawnComponent('AC02_A');
1021
+ const B = defineSpawnComponent('AC02_B');
1022
+ const e = world.spawn({ component: A, data: { x: 0, y: 0 } }).unwrap();
1023
+ world.addComponent(e, { component: B, data: { x: 0, y: 0 } }).unwrap();
1024
+ const arch = archetypeOf(world, e);
1025
+ expect(arch.storage.has(componentId(Entity))).toBe(true);
1026
+ expect(arch.storage.get(componentId(Entity))?.fields.has('self')).toBe(true);
1027
+ });
1028
+ });
1029
+
1030
+ describe('world.get(e, Entity).self carries the row handle (AC-03)', () => {
1031
+ it('reads the full packed handle off the id=0 column for a single-component entity', () => {
1032
+ const world = new World();
1033
+ const Position = defineSpawnComponent('AC03_Pos');
1034
+ const e = world.spawn({ component: Position, data: { x: 1, y: 2 } }).unwrap();
1035
+ const got = world.get(e, Entity).unwrap();
1036
+ const self: EntityHandle | null = got.self;
1037
+ expect(self).toBe(e);
1038
+ });
1039
+
1040
+ it('reads the handle off a bare (zero-component) entity', () => {
1041
+ const world = new World();
1042
+ const e = world.spawn().unwrap();
1043
+ expect(world.get(e, Entity).unwrap().self).toBe(e);
1044
+ });
1045
+
1046
+ it('preserves the handle across an addComponent archetype migration', () => {
1047
+ const world = new World();
1048
+ const A = defineSpawnComponent('AC03_A');
1049
+ const B = defineSpawnComponent('AC03_B');
1050
+ const e = world.spawn({ component: A, data: { x: 0, y: 0 } }).unwrap();
1051
+ world.addComponent(e, { component: B, data: { x: 0, y: 0 } }).unwrap();
1052
+ expect(world.get(e, Entity).unwrap().self).toBe(e);
1053
+ });
1054
+ });
1055
+
1056
+ describe('world.get(e, Entity) as a liveness probe (AC-04)', () => {
1057
+ it('returns stale-entity for a despawned handle', () => {
1058
+ const world = new World();
1059
+ const Position = defineSpawnComponent('AC04_Pos');
1060
+ const e = world.spawn({ component: Position, data: { x: 0, y: 0 } }).unwrap();
1061
+ world.despawn(e).unwrap();
1062
+ const r = world.get(e, Entity);
1063
+ expect(r.ok).toBe(false);
1064
+ if (!r.ok) {
1065
+ expect(r.error.code).toBe('stale-entity');
1066
+ }
1067
+ });
1068
+ });
1069
+
1070
+ describe('removeComponent(e, Entity) is rejected (AC-05)', () => {
1071
+ it('returns remove-essential-component and leaves the Entity column intact', () => {
1072
+ const world = new World();
1073
+ const Position = defineSpawnComponent('AC05_Pos');
1074
+ const e = world.spawn({ component: Position, data: { x: 0, y: 0 } }).unwrap();
1075
+ const r = world.removeComponent(e, Entity);
1076
+ expect(r.ok).toBe(false);
1077
+ if (!r.ok) {
1078
+ expect(r.error.code).toBe('remove-essential-component');
1079
+ if (r.error.code === 'remove-essential-component') {
1080
+ expect(r.error.detail.componentName).toBe('Entity');
1081
+ }
1082
+ }
1083
+ const arch = archetypeOf(world, e);
1084
+ expect(arch.storage.has(componentId(Entity))).toBe(true);
1085
+ expect(world.get(e, Entity).unwrap().self).toBe(e);
1086
+ });
1087
+ });
1088
+ });
1089
+ }
1090
+
1091
+ {
1092
+ // ─── from fixed-array-inline.test.ts ───
1093
+ describe('fixed-array-inline.test.ts', () => {
1094
+ describe('fixed array<f32, 16> inline column (AC-02 / w5)', () => {
1095
+ it('spawn->get round-trips all 16 f32 elements', () => {
1096
+ const Mat = defineComponent('Mat16', { m: 'array<f32, 16>' });
1097
+ const world = new World();
1098
+ const values = Float32Array.from({ length: 16 }, (_, i) => (i + 1) * 1.5);
1099
+ const e = world.spawn({ component: Mat, data: { m: values } }).unwrap();
1100
+
1101
+ const read = world.get(e, Mat).unwrap().m;
1102
+ expect(read).toBeInstanceOf(Float32Array);
1103
+ expect(read.length).toBe(16);
1104
+ for (let i = 0; i < 16; i++) {
1105
+ expect(read[i]).toBe(values[i]);
1106
+ }
1107
+ });
1108
+
1109
+ // Regression (downstream template integration #2): the plain-JS-array
1110
+ // convenience path used to pack elements by byte SIZE, so a 4-byte f32
1111
+ // went through setUint32 -- storing `1` as the integer bits 0x00000001
1112
+ // (reads back ~1.4e-45 ~ 0). AnimationPlayer.speeds=[1] froze on frame 0.
1113
+ // Packing must dispatch on the declared element TYPE, not byte size.
1114
+ it('plain-JS-array write to array<f32,4> stores f32 VALUES, not u32 bits', () => {
1115
+ const C = defineComponent('PlainF32Fixed', { v: 'array<f32, 4>' });
1116
+ const world = new World();
1117
+ const e = world.spawn({ component: C, data: { v: [1, 0.5, -2, 3.25] } }).unwrap();
1118
+
1119
+ const read = world.get(e, C).unwrap().v;
1120
+ expect(read).toBeInstanceOf(Float32Array);
1121
+ expect(read[0]).toBe(1);
1122
+ expect(read[1]).toBe(0.5);
1123
+ expect(read[2]).toBe(-2);
1124
+ expect(read[3]).toBe(3.25);
1125
+ });
1126
+
1127
+ it('plain-JS-array write to variable array<f32> stores f32 VALUES', () => {
1128
+ const C = defineComponent('PlainF32Var', { v: 'array<f32>' });
1129
+ const world = new World();
1130
+ const e = world.spawn({ component: C, data: { v: [1, 2.5] } }).unwrap();
1131
+
1132
+ const read = world.get(e, C).unwrap().v;
1133
+ expect(read).toBeInstanceOf(Float32Array);
1134
+ expect(read.length).toBe(2);
1135
+ expect(read[0]).toBe(1);
1136
+ expect(read[1]).toBe(2.5);
1137
+ });
1138
+
1139
+ // Control: u32 elements are genuinely integers -- the type-keyed packer
1140
+ // must keep storing them verbatim (no regression in the handle/u32 path).
1141
+ it('plain-JS-array write to array<u32,3> still stores integer values verbatim', () => {
1142
+ const C = defineComponent('PlainU32Fixed', { v: 'array<u32, 3>' });
1143
+ const world = new World();
1144
+ const e = world.spawn({ component: C, data: { v: [7, 42, 1000] } }).unwrap();
1145
+
1146
+ const read = world.get(e, C).unwrap().v;
1147
+ expect(read).toBeInstanceOf(Uint32Array);
1148
+ expect(read[0]).toBe(7);
1149
+ expect(read[1]).toBe(42);
1150
+ expect(read[2]).toBe(1000);
1151
+ });
1152
+ });
1153
+
1154
+ describe('fixed buffer<64> inline column (AC-03 / w6)', () => {
1155
+ it('spawn->get round-trips all 64 bytes', () => {
1156
+ const Blob = defineComponent('Blob64', { b: 'buffer<64>' });
1157
+ const world = new World();
1158
+ const bytes = Uint8Array.from({ length: 64 }, (_, i) => (i * 7 + 3) & 0xff);
1159
+ const e = world.spawn({ component: Blob, data: { b: bytes } }).unwrap();
1160
+
1161
+ const read = world.get(e, Blob).unwrap().b;
1162
+ expect(read).toBeInstanceOf(Uint8Array);
1163
+ expect(read.length).toBe(64);
1164
+ for (let i = 0; i < 64; i++) {
1165
+ expect(read[i]).toBe(bytes[i]);
1166
+ }
1167
+ });
1168
+ });
1169
+
1170
+ describe('fixed array<entity, 4> inline column (AC-04 / w7)', () => {
1171
+ it('spawn->get reinterprets the 4 entity ids as Uint32Array', () => {
1172
+ const Refs = defineComponent('EntRefs4', { targets: 'array<entity, 4>' });
1173
+ const world = new World();
1174
+ const Tag = defineComponent('FixedArrTag', {});
1175
+ const t0 = world.spawn({ component: Tag, data: {} }).unwrap();
1176
+ const t1 = world.spawn({ component: Tag, data: {} }).unwrap();
1177
+ const t2 = world.spawn({ component: Tag, data: {} }).unwrap();
1178
+ const t3 = world.spawn({ component: Tag, data: {} }).unwrap();
1179
+ const ids = Uint32Array.of(t0 as number, t1 as number, t2 as number, t3 as number);
1180
+
1181
+ const holder = world.spawn({ component: Refs, data: { targets: ids } }).unwrap();
1182
+ const read = world.get(holder, Refs).unwrap().targets;
1183
+ expect(read).toBeInstanceOf(Uint32Array);
1184
+ expect(read.length).toBe(4);
1185
+ expect(read[0]).toBe(t0 as number);
1186
+ expect(read[1]).toBe(t1 as number);
1187
+ expect(read[2]).toBe(t2 as number);
1188
+ expect(read[3]).toBe(t3 as number);
1189
+ });
1190
+
1191
+ it('_getArrayView serves a non-f32 element type after the f32 gate is removed', () => {
1192
+ const Refs = defineComponent('EntRefsView4', { targets: 'array<entity, 4>' });
1193
+ const world = new World();
1194
+ const Tag = defineComponent('TagView', {});
1195
+ const t0 = world.spawn({ component: Tag, data: {} }).unwrap();
1196
+ const t1 = world.spawn({ component: Tag, data: {} }).unwrap();
1197
+ const ids = Uint32Array.of(t0 as number, t1 as number, 0, 0);
1198
+ const holder = world.spawn({ component: Refs, data: { targets: ids } }).unwrap();
1199
+
1200
+ const view = world[worldInternal].getArrayView(holder, Refs, 'targets');
1201
+ expect(view).toBeDefined();
1202
+ expect(view?.length).toBe(4);
1203
+ expect(view?.[0]).toBe(t0 as number);
1204
+ expect(view?.[1]).toBe(t1 as number);
1205
+ });
1206
+ });
1207
+
1208
+ describe('swap-remove migrates the whole stride-N block (AC-06 / w8)', () => {
1209
+ it('removing a middle row moves the last row block intact', () => {
1210
+ const Mat = defineComponent('Mat16Swap', { m: 'array<f32, 16>' });
1211
+ const world = new World();
1212
+ const row = (base: number) => Float32Array.from({ length: 16 }, (_, i) => base + i);
1213
+ const e0 = world.spawn({ component: Mat, data: { m: row(100) } }).unwrap();
1214
+ const e1 = world.spawn({ component: Mat, data: { m: row(200) } }).unwrap();
1215
+ const e2 = world.spawn({ component: Mat, data: { m: row(300) } }).unwrap();
1216
+
1217
+ world.despawn(e0);
1218
+
1219
+ const r2 = world.get(e2, Mat).unwrap().m;
1220
+ expect(r2.length).toBe(16);
1221
+ for (let i = 0; i < 16; i++) {
1222
+ expect(r2[i]).toBe(300 + i);
1223
+ }
1224
+ const r1 = world.get(e1, Mat).unwrap().m;
1225
+ for (let i = 0; i < 16; i++) {
1226
+ expect(r1[i]).toBe(200 + i);
1227
+ }
1228
+ });
1229
+ });
1230
+
1231
+ describe('fixed-only component leaves BufferPool live-count at 0 (AC-07 / w9)', () => {
1232
+ const liveCount = (world: World): number =>
1233
+ (world as unknown as { bufferPool: { _liveCount(): number } }).bufferPool._liveCount();
1234
+
1235
+ it('array<f32,16>-only spawn->get->despawn never touches the pool', () => {
1236
+ const Mat = defineComponent('Mat16Pool', { m: 'array<f32, 16>' });
1237
+ const world = new World();
1238
+ expect(liveCount(world)).toBe(0);
1239
+
1240
+ const e = world
1241
+ .spawn({ component: Mat, data: { m: Float32Array.from({ length: 16 }, (_, i) => i) } })
1242
+ .unwrap();
1243
+ expect(liveCount(world)).toBe(0);
1244
+
1245
+ world.get(e, Mat).unwrap().m;
1246
+ expect(liveCount(world)).toBe(0);
1247
+
1248
+ world.despawn(e);
1249
+ expect(liveCount(world)).toBe(0);
1250
+ });
1251
+
1252
+ it('buffer<64>-only spawn->get->despawn never touches the pool', () => {
1253
+ const Blob = defineComponent('Blob64Pool', { b: 'buffer<64>' });
1254
+ const world = new World();
1255
+ expect(liveCount(world)).toBe(0);
1256
+
1257
+ const e = world.spawn({ component: Blob, data: { b: new Uint8Array(64) } }).unwrap();
1258
+ expect(liveCount(world)).toBe(0);
1259
+
1260
+ world.get(e, Blob).unwrap().b;
1261
+ expect(liveCount(world)).toBe(0);
1262
+
1263
+ world.despawn(e);
1264
+ expect(liveCount(world)).toBe(0);
1265
+ });
1266
+ });
1267
+
1268
+ describe('AC-08 variable buffer (no N) path post-inline (w15)', () => {
1269
+ const liveCount = (world: World): number =>
1270
+ (world as unknown as { bufferPool: { _liveCount(): number } }).bufferPool._liveCount();
1271
+
1272
+ it('variable buffer spawn->set->get round-trip works', () => {
1273
+ const C = defineComponent('VarBuf', { blob: 'buffer' });
1274
+ const world = new World();
1275
+ expect(liveCount(world)).toBe(0);
1276
+
1277
+ const payload = Uint8Array.of(0xaa, 0xbb, 0xcc, 0xdd);
1278
+ const e = world.spawn({ component: C, data: { blob: payload } }).unwrap();
1279
+ expect(liveCount(world)).toBe(1);
1280
+
1281
+ const snap = world.get(e, C).unwrap().blob;
1282
+ expect(snap).toBeInstanceOf(Uint8Array);
1283
+ expect(snap.length).toBe(4);
1284
+ expect(snap[0]).toBe(0xaa);
1285
+ expect(snap[3]).toBe(0xdd);
1286
+ });
1287
+
1288
+ it('variable buffer grows via set with larger payload', () => {
1289
+ const C = defineComponent('VarBuf2', { blob: 'buffer' });
1290
+ const world = new World();
1291
+ const small = Uint8Array.of(0x01, 0x02);
1292
+ const e = world.spawn({ component: C, data: { blob: small } }).unwrap();
1293
+
1294
+ expect(world.get(e, C).unwrap().blob.length).toBe(2);
1295
+
1296
+ const large = new Uint8Array(32);
1297
+ for (let i = 0; i < 32; i++) large[i] = i + 0x10;
1298
+ world.set(e, C, { blob: large }).unwrap();
1299
+
1300
+ const snap = world.get(e, C).unwrap().blob;
1301
+ expect(snap.length).toBe(32);
1302
+ for (let i = 0; i < 32; i++) expect(snap[i]).toBe(i + 0x10);
1303
+ });
1304
+
1305
+ it('variable buffer release path: despawn returns slot to pool', () => {
1306
+ const C = defineComponent('VarBuf3', { blob: 'buffer' });
1307
+ const world = new World();
1308
+ expect(liveCount(world)).toBe(0);
1309
+
1310
+ const e = world.spawn({ component: C, data: { blob: Uint8Array.of(0xfe) } }).unwrap();
1311
+ expect(liveCount(world)).toBe(1);
1312
+
1313
+ world.despawn(e).unwrap();
1314
+ expect(liveCount(world)).toBe(0);
1315
+ });
1316
+
1317
+ it('variable + fixed coexistence: pool count only reflects variable fields', () => {
1318
+ const VarArr = defineComponent('VarArr', { data: 'array<f32>' });
1319
+ const FixedMat = defineComponent('FixedMat', { m: 'array<f32, 16>' });
1320
+ const world = new World();
1321
+ expect(liveCount(world)).toBe(0);
1322
+
1323
+ const e1 = world.spawn({ component: FixedMat, data: { m: new Float32Array(16) } }).unwrap();
1324
+ expect(liveCount(world)).toBe(0);
1325
+
1326
+ const e2 = world
1327
+ .spawn(
1328
+ { component: FixedMat, data: { m: new Float32Array(16) } },
1329
+ { component: VarArr, data: { data: new Float32Array(0) } },
1330
+ )
1331
+ .unwrap();
1332
+ expect(liveCount(world)).toBe(1);
1333
+
1334
+ world.despawn(e2).unwrap();
1335
+ expect(liveCount(world)).toBe(0);
1336
+
1337
+ world.despawn(e1).unwrap();
1338
+ expect(liveCount(world)).toBe(0);
1339
+ });
1340
+ });
1341
+ });
1342
+ }
1343
+
1344
+ {
1345
+ // ─── from query-preregister.test.ts ───
1346
+ describe('foldEssentials.test.ts', () => {
1347
+ describe('ESSENTIAL_COMPONENT_IDS SSOT', () => {
1348
+ it('is a frozen readonly array containing componentId(Entity)', async () => {
1349
+ const mod = await import('../entity');
1350
+ const ESSENTIAL_COMPONENT_IDS = (
1351
+ mod as unknown as { ESSENTIAL_COMPONENT_IDS: ReadonlyArray<number> }
1352
+ ).ESSENTIAL_COMPONENT_IDS;
1353
+ expect(Array.isArray(ESSENTIAL_COMPONENT_IDS)).toBe(true);
1354
+ expect(ESSENTIAL_COMPONENT_IDS.length).toBe(1);
1355
+ expect(ESSENTIAL_COMPONENT_IDS[0]).toBe(componentId(Entity));
1356
+ expect(Object.isFrozen(ESSENTIAL_COMPONENT_IDS)).toBe(true);
1357
+ });
1358
+ });
1359
+
1360
+ describe('foldEssentials() helper', () => {
1361
+ it('input containing componentId(Entity) returns deduped copy', async () => {
1362
+ const mod = await import('../entity');
1363
+ const foldEssentials = (
1364
+ mod as unknown as { foldEssentials: (ids: ReadonlyArray<number>) => number[] }
1365
+ ).foldEssentials;
1366
+ const input = [componentId(Entity), 2, 5];
1367
+ const out = foldEssentials(input);
1368
+ // componentId(Entity) appears exactly once
1369
+ expect(out.filter((id) => id === componentId(Entity)).length).toBe(1);
1370
+ // No id is dropped
1371
+ expect(out).toContain(2);
1372
+ expect(out).toContain(5);
1373
+ expect(out).toContain(componentId(Entity));
1374
+ });
1375
+
1376
+ it('input not containing componentId(Entity) returns array prefixed with componentId(Entity)', async () => {
1377
+ const mod = await import('../entity');
1378
+ const foldEssentials = (
1379
+ mod as unknown as { foldEssentials: (ids: ReadonlyArray<number>) => number[] }
1380
+ ).foldEssentials;
1381
+ const input = [2, 5, 7];
1382
+ const out = foldEssentials(input);
1383
+ expect(out.length).toBe(4);
1384
+ expect(out[0]).toBe(componentId(Entity));
1385
+ expect(out).toContain(2);
1386
+ expect(out).toContain(5);
1387
+ expect(out).toContain(7);
1388
+ });
1389
+
1390
+ it('empty input returns [componentId(Entity)]', async () => {
1391
+ const mod = await import('../entity');
1392
+ const foldEssentials = (
1393
+ mod as unknown as { foldEssentials: (ids: ReadonlyArray<number>) => number[] }
1394
+ ).foldEssentials;
1395
+ const out = foldEssentials([]);
1396
+ expect(out).toEqual([componentId(Entity)]);
1397
+ });
1398
+
1399
+ it('does not mutate input array', async () => {
1400
+ const mod = await import('../entity');
1401
+ const foldEssentials = (
1402
+ mod as unknown as { foldEssentials: (ids: ReadonlyArray<number>) => number[] }
1403
+ ).foldEssentials;
1404
+ const input = [2, 5];
1405
+ const inputSnapshot = [...input];
1406
+ const out = foldEssentials(input);
1407
+ expect(input).toEqual(inputSnapshot);
1408
+ // returned array must be a NEW array (different reference) when fold
1409
+ // had to insert componentId(Entity)
1410
+ expect(out).not.toBe(input);
1411
+ });
1412
+ });
1413
+ });
1414
+ }
1415
+
1416
+ {
1417
+ // ─── from entity-handle-rename-grep-gate.test.ts (feat-20260611 M-1 / w2) ───
1418
+ //
1419
+ // Grep gate verifying the type-space `Entity` -> `EntityHandle` rename
1420
+ // landed cleanly across `packages/ecs/src/`. Reads the actual source files
1421
+ // at test time (no mocking) so it directly enforces the source SSOT.
1422
+ //
1423
+ // Verifies:
1424
+ // 1. `entity.ts` exports `EntityHandle` (the renamed branded-number type)
1425
+ // and no longer exports `type Entity`.
1426
+ // 2. `entity.ts` keeps the value-space `Entity` const (id=0
1427
+ // component token) but does NOT re-export `type Entity` (the type-space
1428
+ // handle alias is now `EntityHandle`).
1429
+ //
1430
+ // Constraints:
1431
+ // - requirements §Component naming: id=0 component token is still named
1432
+ // `Entity` -- only the type-space branded handle is renamed
1433
+ // - requirements I-1: type Entity -> EntityHandle
1434
+ // - research F-5: TS rename refactor not usable due to namespace merging,
1435
+ // hence a grep-form gate
1436
+ //
1437
+ // Cross-package `: EntityHandle` type-annotation acceptance is verified by
1438
+ // `pnpm typecheck` at the milestone CI sweep level, not here.
1439
+
1440
+ describe('entity-handle-rename-grep-gate.test.ts', () => {
1441
+ describe('packages/ecs/src/entity-handle.ts', () => {
1442
+ it('exports type EntityHandle (renamed branded-number handle)', async () => {
1443
+ const fs = await import('node:fs/promises');
1444
+ const path = await import('node:path');
1445
+ const url = await import('node:url');
1446
+ const here = url.fileURLToPath(new URL('.', import.meta.url));
1447
+ const src = await fs.readFile(path.resolve(here, '..', 'entity-handle.ts'), 'utf8');
1448
+ // exactly one `export type EntityHandle =` declaration
1449
+ const handleDeclMatches = src.match(/^export type EntityHandle\b/gm) ?? [];
1450
+ expect(handleDeclMatches.length).toBe(1);
1451
+ });
1452
+
1453
+ it('no longer exports `type Entity` (type-space rename complete)', async () => {
1454
+ const fs = await import('node:fs/promises');
1455
+ const path = await import('node:path');
1456
+ const url = await import('node:url');
1457
+ const here = url.fileURLToPath(new URL('.', import.meta.url));
1458
+ const src = await fs.readFile(path.resolve(here, '..', 'entity-handle.ts'), 'utf8');
1459
+ // Match `export type Entity` not followed by `Handle` -- catches the
1460
+ // pre-rename declaration but not the post-rename `EntityHandle`.
1461
+ const entityTypeDeclMatches = src.match(/^export type Entity\b(?!Handle)/gm) ?? [];
1462
+ expect(entityTypeDeclMatches.length).toBe(0);
1463
+ });
1464
+ });
1465
+
1466
+ describe('packages/ecs/src/entity.ts', () => {
1467
+ it('keeps value-space Entity (id=0 component token)', async () => {
1468
+ const fs = await import('node:fs/promises');
1469
+ const path = await import('node:path');
1470
+ const url = await import('node:url');
1471
+ const here = url.fileURLToPath(new URL('.', import.meta.url));
1472
+ const src = await fs.readFile(path.resolve(here, '..', 'entity.ts'), 'utf8');
1473
+ // The value-space token survives; Biome may wrap the call across lines.
1474
+ expect(/^export const Entity = defineComponent\(\s*['"]Entity['"]/m.test(src)).toBe(true);
1475
+ });
1476
+
1477
+ it('does not re-export `type Entity` alias (type-space lives in entity-handle.ts as EntityHandle)', async () => {
1478
+ const fs = await import('node:fs/promises');
1479
+ const path = await import('node:path');
1480
+ const url = await import('node:url');
1481
+ const here = url.fileURLToPath(new URL('.', import.meta.url));
1482
+ const src = await fs.readFile(path.resolve(here, '..', 'entity.ts'), 'utf8');
1483
+ const entityTypeDeclMatches = src.match(/^export type Entity\b(?!Handle)/gm) ?? [];
1484
+ expect(entityTypeDeclMatches.length).toBe(0);
1485
+ });
1486
+ });
1487
+ });
1488
+ }
1489
+
1490
+ {
1491
+ // ─── from swap-pop-self-column-removeEdge-filter.test.ts (feat-20260611-ecs-storage-naming-ssot M-2 / w4) ───
1492
+ //
1493
+ // Verifies two changes that landed in M-2:
1494
+ // 1. removeEntity swap-pop reads entity index from id=0 `self` column
1495
+ // (columns.get(0)?.get('self')?.view[lastRow] & 0xffffff) instead of
1496
+ // arch.entities[lastRow] — equivalence proven by research F-3.
1497
+ // 2. getRemoveEdge uses src.components.filter(c => componentId(c) !== removedId) to
1498
+ // rebuild the component list after componentRegistry is deleted.
1499
+ //
1500
+ // TDD red mechanism (pre-w5):
1501
+ // (a) The test corrupted arch.entities[lastRow] so pre-w5 removeEntity
1502
+ // would return the wrong value. Post-w5, removeEntity reads the
1503
+ // self column directly.
1504
+ // (b) The test verified graph.componentRegistry existence then verified
1505
+ // getRemoveEdge still works with only src.components.filter.
1506
+
1507
+ describe('swap-pop-self-column-removeEdge-filter.test.ts', () => {
1508
+ let testArchId = 0;
1509
+
1510
+ describe('removeEntity swap-pop reads entity from id=0 self column', () => {
1511
+ it('2+ entities swap-pop: removed entity index comes from self column, not entities array', () => {
1512
+ const C = defineComponent('SwapSelfCol', { v: { type: 'i32' } });
1513
+ const arch = createArchetype([C], testArchId++);
1514
+
1515
+ // Populate 3 entities. appendEntity writes to the id=0 self column.
1516
+ appendEntity(arch, 0x42);
1517
+ appendEntity(arch, 0x77);
1518
+ appendEntity(arch, 0xff);
1519
+
1520
+ const selfCol = tableColumns(arch).get(componentId(Entity))?.get('self');
1521
+ expect(selfCol).toBeDefined();
1522
+ // Verify self column still has correct value
1523
+ // biome-ignore lint/style/noNonNullAssertion: guarded by expect above
1524
+ expect(selfCol!.view[2]! & 0xffffff).toBe(0xff);
1525
+
1526
+ const swapped = removeEntity(arch, 0);
1527
+
1528
+ // After w5: movedEntity comes from self column (lower 24 bits)
1529
+ if (swapped !== null) {
1530
+ expect(swapped.movedEntity).toBe(0xff);
1531
+ expect(swapped.newRow).toBe(0);
1532
+ } else {
1533
+ expect.unreachable('expected swap-pop for multi-entity archetype');
1534
+ }
1535
+ });
1536
+
1537
+ it('single entity last-row swap-pop returns null (row === lastRow, no entities read needed)', () => {
1538
+ const C = defineComponent('SwapSelfLastRow', { v: { type: 'f32' } });
1539
+ const arch = createArchetype([C], testArchId++);
1540
+
1541
+ appendEntity(arch, 0xab);
1542
+
1543
+ const swapped = removeEntity(arch, 0);
1544
+ expect(arch.size).toBe(0);
1545
+ expect(swapped).toBeNull();
1546
+ });
1547
+
1548
+ it('swap-pop self column data correctly migrated (column-level swap)', () => {
1549
+ const C = defineComponent('SwapSelfColData', { v: { type: 'i32' } });
1550
+ const arch = createArchetype([C], testArchId++);
1551
+
1552
+ appendEntity(arch, 0x10);
1553
+ appendEntity(arch, 0x20);
1554
+ appendEntity(arch, 0x30);
1555
+
1556
+ const selfCol = tableColumns(arch).get(componentId(Entity))?.get('self');
1557
+ // biome-ignore lint/style/noNonNullAssertion: test setup
1558
+ const vCol = tableColumns(arch).get(componentId(C))!.get('v')!;
1559
+ vCol.view[0] = 100;
1560
+ vCol.view[1] = 200;
1561
+ vCol.view[2] = 300;
1562
+
1563
+ // Remove row 1. Last row (2) swaps into row 1.
1564
+ const swapped = removeEntity(arch, 1);
1565
+ expect(arch.size).toBe(2);
1566
+
1567
+ if (swapped === null) {
1568
+ expect.unreachable('expected swap-pop');
1569
+ }
1570
+
1571
+ // GREEN (after w5): movedEntity = 0x30 from self column
1572
+ // RED (before w5): movedEntity = 0xcafe from corrupted arch.entities[2]
1573
+ expect(swapped.movedEntity).toBe(0x30);
1574
+ expect(swapped.newRow).toBe(1);
1575
+
1576
+ // self column swap-integrity: row 1 should now hold last-row's self value
1577
+ // biome-ignore lint/style/noNonNullAssertion: guarded by expect above
1578
+ expect(selfCol!.view[1]! & 0xffffff).toBe(0x30);
1579
+ // user column data also swapped
1580
+ expect(vCol.view[1]).toBe(300);
1581
+ });
1582
+ });
1583
+
1584
+ describe('getRemoveEdge uses src.components.filter after componentRegistry delete', () => {
1585
+ it('getRemoveEdge rebuilds component list from src.components.filter (not registry)', () => {
1586
+ const graph = createArchetypeGraph();
1587
+ const A = defineComponent('RemoveEdgeFilter_A', { a: { type: 'f32' } });
1588
+ const B = defineComponent('RemoveEdgeFilter_B', { b: { type: 'f32' } });
1589
+ const C = defineComponent('RemoveEdgeFilter_C', { c: { type: 'f32' } });
1590
+
1591
+ const archABC = getOrCreateArchetype(
1592
+ graph,
1593
+ [componentId(A), componentId(B), componentId(C)],
1594
+ [A, B, C],
1595
+ );
1596
+
1597
+ // After w5: componentRegistry field deleted; getRemoveEdge derives
1598
+ // component list from src.components.filter(c => componentId(c) !== removedId).
1599
+ const archAC = getRemoveEdge(graph, archABC, componentId(B));
1600
+
1601
+ const expectedIds = [componentId(Entity), componentId(A), componentId(C)].sort(
1602
+ (a, b) => a - b,
1603
+ );
1604
+ const actualComponentIds = archAC.components
1605
+ .map((c) => componentId(c))
1606
+ .sort((a, b) => a - b);
1607
+ expect(actualComponentIds).toEqual(expectedIds);
1608
+
1609
+ // Verify B is NOT present in the target
1610
+ expect(archAC.components.some((c) => componentId(c) === componentId(B))).toBe(false);
1611
+ });
1612
+
1613
+ it('getRemoveEdge of last non-essential component returns archetype with only Entity', () => {
1614
+ const graph = createArchetypeGraph();
1615
+ const A = defineComponent('RemoveEdgeLast_A', { a: { type: 'f32' } });
1616
+
1617
+ const archA = getOrCreateArchetype(graph, [componentId(A)], [A]);
1618
+ const archEmpty = getRemoveEdge(graph, archA, componentId(A));
1619
+
1620
+ expect(archEmpty.components.length).toBe(1);
1621
+ expect(archEmpty.components[0]?.name).toBe('Entity');
1622
+ expect(archEmpty.components.map((c) => componentId(c))).toEqual([componentId(Entity)]);
1623
+ });
1624
+ });
1625
+ });
1626
+ }
1627
+
1628
+ {
1629
+ // ─── from isManaged-column-4-predicate-equivalence.test.ts (feat-20260611-ecs-storage-naming-ssot M-3 / w6) ───
1630
+ //
1631
+ // Verifies:
1632
+ // 1. Every TYPE_METADATA row has an `isManaged` boolean column.
1633
+ // 2. The 4 `isXxxField` predicates produce the same boolean result as the
1634
+ // new TYPE_METADATA[].isXxx single-column lookups.
1635
+ // 3. `isManagedField` recognizes `'string'` and `'unique<T>'` (both isManaged).
1636
+ //
1637
+ // tweak-20260612-ecs-concept-compression dropped the redundant per-vocab
1638
+ // managed-ref predicate column (100% identical to `isManaged`); the
1639
+ // equivalence assertion below now reads `isManaged` directly.
1640
+ //
1641
+ // Plan-strategy D-3 (approach A): TYPE_METADATA gains an `isManaged` column;
1642
+ // 4 predicates are 1:1 derivations from TYPE_METADATA columns, zero OR.
1643
+ //
1644
+ // Plan-strategy D-4: `isManagedBufferField` semantic widening accepted —
1645
+ // `buffer<abc>` resolves to metaKey 'buffer', `isBuffer` returns true; the
1646
+ // upstream `isSchemaVocabKeyword` gate already rejects malformed keywords.
1647
+ //
1648
+ // TDD: these tests are RED before w7 (isManaged column does not yet exist on
1649
+ // TypeMetadataRow). They turn GREEN after w7 adds the column + rewrites
1650
+ // predicates.
1651
+
1652
+ describe('isManaged-column-4-predicate-equivalence.test.ts', () => {
1653
+ describe('isManaged column exists on every TYPE_METADATA row', () => {
1654
+ it('every TYPE_METADATA row has a boolean isManaged property', () => {
1655
+ for (const [key, row] of Object.entries(TYPE_METADATA)) {
1656
+ expect(
1657
+ typeof (row as unknown as Record<string, unknown>).isManaged,
1658
+ `TYPE_METADATA['${key}'].isManaged must be boolean`,
1659
+ ).toBe('boolean');
1660
+ }
1661
+ });
1662
+
1663
+ it('isManaged === true exactly for the managed-ref vocab + scalar rows ("string" + "ref" + "shared")', () => {
1664
+ // feat-20260614-ecs-shared-component-and-unique-rename M3 (D-3): the
1665
+ // independent `'shared'` TYPE_METADATA row joins the managed-ref tier
1666
+ // alongside `'string'` (UniqueRefStore-backed) and `'ref'` (the
1667
+ // post-M2 `'unique<T>'` family). Each meta key carries its own
1668
+ // release-semantics dispatch (M4 wires the sub-branches).
1669
+ for (const [key, row] of Object.entries(TYPE_METADATA)) {
1670
+ const im = (row as unknown as Record<string, unknown>).isManaged as boolean;
1671
+ const expected = key === 'string' || key === 'ref' || key === 'shared';
1672
+ expect(im, `TYPE_METADATA['${key}'].isManaged`).toBe(expected);
1673
+ }
1674
+ });
1675
+
1676
+ it('"string" row has isManaged === true', () => {
1677
+ const stringRow = TYPE_METADATA.string;
1678
+ if (stringRow === undefined) throw new Error('string row missing');
1679
+ const im = (stringRow as unknown as Record<string, unknown>).isManaged;
1680
+ expect(im).toBe(true);
1681
+ });
1682
+
1683
+ it('"ref" row has isManaged === true (shared scalar + vocab row)', () => {
1684
+ const refRow = TYPE_METADATA.ref;
1685
+ if (refRow === undefined) throw new Error('ref row missing');
1686
+ const im = (refRow as unknown as Record<string, unknown>).isManaged;
1687
+ expect(im).toBe(true);
1688
+ });
1689
+
1690
+ it('non-managed rows (entity, buffer, array, scalars) have isManaged === false', () => {
1691
+ const nonManagedKeys = [
1692
+ 'entity',
1693
+ 'buffer',
1694
+ 'array',
1695
+ 'f32',
1696
+ 'f64',
1697
+ 'i32',
1698
+ 'u32',
1699
+ 'i16',
1700
+ 'u16',
1701
+ 'i8',
1702
+ 'u8',
1703
+ 'bool',
1704
+ 'enum',
1705
+ ];
1706
+ for (const key of nonManagedKeys) {
1707
+ const row = TYPE_METADATA[key];
1708
+ if (row === undefined) throw new Error(`row ${key} missing`);
1709
+ const im = (row as unknown as Record<string, unknown>).isManaged as boolean;
1710
+ expect(im, `TYPE_METADATA['${key}'].isManaged`).toBe(false);
1711
+ }
1712
+ });
1713
+ });
1714
+
1715
+ describe('4 predicate equivalence (old impl vs new TYPE_METADATA column)', () => {
1716
+ // Legal SchemaFieldType samples: all 11 legacy scalars (keywords) +
1717
+ // 6 vocab-family parametric representatives. Every sample is a
1718
+ // syntactically valid keyword accepted by `defineComponent`.
1719
+ //
1720
+ // Deliberately excluded: 'buffer<0>' (invalid N, /^[1-9]\d*$/ rejects
1721
+ // 0), 'buffer<abc>' (non-integer N), 'handle<>' (empty tag). These
1722
+ // are never accepted by `defineComponent` (gated via `isSchemaVocabKeyword`),
1723
+ // so they never flow through the runtime predicates. The D-4 semantic
1724
+ // widening only affects invalid inputs — a dead path.
1725
+ const SAMPLE_FIELD_TYPES: string[] = [
1726
+ // 11 legacy scalar keys
1727
+ 'f32',
1728
+ 'f64',
1729
+ 'i32',
1730
+ 'u32',
1731
+ 'i16',
1732
+ 'u16',
1733
+ 'i8',
1734
+ 'u8',
1735
+ 'bool',
1736
+ 'enum',
1737
+ 'ref',
1738
+ // 6 vocab-family parametric reps
1739
+ 'entity',
1740
+ 'string',
1741
+ 'buffer',
1742
+ 'buffer<64>',
1743
+ 'unique<MaterialAsset>',
1744
+ 'shared<MaterialAsset>',
1745
+ 'array<f32>',
1746
+ 'array<f32, 16>',
1747
+ 'array<entity>',
1748
+ 'array<entity, 4>',
1749
+ 'array<shared<MaterialAsset>>',
1750
+ 'unknown-field-type',
1751
+ ];
1752
+
1753
+ it('isManagedField: equivalence between current impl and TYPE_METADATA[].isManaged', () => {
1754
+ for (const t of SAMPLE_FIELD_TYPES) {
1755
+ const oldResult = isManagedField(t);
1756
+ const metaKey = fieldTypeToMetaKey(t) ?? '';
1757
+ const newResult =
1758
+ ((TYPE_METADATA[metaKey] as unknown as Record<string, unknown>)
1759
+ ?.isManaged as boolean) ?? false;
1760
+ expect(newResult, `isManagedField('${t}'): old=${oldResult} new=${newResult}`).toBe(
1761
+ oldResult,
1762
+ );
1763
+ }
1764
+ });
1765
+
1766
+ it('isManagedBufferField: equivalence between current impl and TYPE_METADATA[].isBuffer', () => {
1767
+ for (const t of SAMPLE_FIELD_TYPES) {
1768
+ const oldResult = isManagedBufferField(t);
1769
+ const metaKey = fieldTypeToMetaKey(t) ?? '';
1770
+ const newResult = TYPE_METADATA[metaKey]?.isBuffer ?? false;
1771
+ expect(newResult, `isManagedBufferField('${t}'): old=${oldResult} new=${newResult}`).toBe(
1772
+ oldResult,
1773
+ );
1774
+ }
1775
+ });
1776
+
1777
+ it('isEntityField: equivalence between current impl and TYPE_METADATA[].isEntityRef', () => {
1778
+ for (const t of SAMPLE_FIELD_TYPES) {
1779
+ const oldResult = isEntityField(t);
1780
+ const metaKey = fieldTypeToMetaKey(t) ?? '';
1781
+ const newResult = TYPE_METADATA[metaKey]?.isEntityRef ?? false;
1782
+ expect(newResult, `isEntityField('${t}'): old=${oldResult} new=${newResult}`).toBe(
1783
+ oldResult,
1784
+ );
1785
+ }
1786
+ });
1787
+
1788
+ it('isManagedArrayField: equivalence between current impl and TYPE_METADATA[].isArray', () => {
1789
+ for (const t of SAMPLE_FIELD_TYPES) {
1790
+ const oldResult = isManagedArrayField(t);
1791
+ const metaKey = fieldTypeToMetaKey(t) ?? '';
1792
+ const newResult = TYPE_METADATA[metaKey]?.isArray ?? false;
1793
+ expect(newResult, `isManagedArrayField('${t}'): old=${oldResult} new=${newResult}`).toBe(
1794
+ oldResult,
1795
+ );
1796
+ }
1797
+ });
1798
+ });
1799
+
1800
+ describe('isManagedBufferField semantic widening (D-4 acceptance)', () => {
1801
+ it("'buffer<abc>' maps to metaKey 'buffer' so isBuffer returns true", () => {
1802
+ // D-4 states this widening is acceptable: defineComponent rejects
1803
+ // 'buffer<abc>' via isSchemaVocabKeyword (non-integer N), so it
1804
+ // never reaches the runtime predicate. The column-based predicate
1805
+ // unavoidably returns true via metaKey 'buffer'.isBuffer === true.
1806
+ const metaKey = fieldTypeToMetaKey('buffer<abc>') ?? '';
1807
+ expect(metaKey).toBe('buffer');
1808
+ expect(TYPE_METADATA[metaKey]?.isBuffer ?? false).toBe(true);
1809
+ // The new column-based predicate returns true for this dead path
1810
+ // (old regex impl returned false). Accepted per D-4.
1811
+ expect(isManagedBufferField('buffer<abc>')).toBe(true);
1812
+ });
1813
+ });
1814
+
1815
+ describe('isManagedField coverage: string and ref<T>', () => {
1816
+ it("returns true for 'string' literal", () => {
1817
+ expect(isManagedField('string')).toBe(true);
1818
+ });
1819
+
1820
+ it("returns true for 'unique<Foo>' template", () => {
1821
+ expect(isManagedField('unique<Foo>')).toBe(true);
1822
+ });
1823
+
1824
+ it("returns true for 'shared<X>' (rc-tracked managed scalar)", () => {
1825
+ expect(isManagedField('shared<X>')).toBe(true);
1826
+ });
1827
+
1828
+ it("returns false for 'entity', 'buffer', 'array<T>', and bare scalars", () => {
1829
+ expect(isManagedField('entity')).toBe(false);
1830
+ expect(isManagedField('buffer')).toBe(false);
1831
+ expect(isManagedField('buffer<64>')).toBe(false);
1832
+ expect(isManagedField('array<f32>')).toBe(false);
1833
+ expect(isManagedField('f32')).toBe(false);
1834
+ });
1835
+ });
1836
+ });
1837
+ }