@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,98 @@
1
+ // feat-20260614-ecs-shared-component-and-unique-rename M6 w52 (AC-31, R-14):
2
+ // write-barrier slot-range short-circuit.
3
+ //
4
+ // D-15: builtin asset handles (slot < BUILTIN_BASE, e.g. HANDLE_CUBE=1) are
5
+ // process-static and NEVER reference-counted. The ECS write barrier for
6
+ // `shared<T>` columns must short-circuit on builtin slots: retain/release on a
7
+ // builtin handle makes ZERO calls to World.sharedRefs and never surfaces a
8
+ // SharedRefReleasedError / BuiltinSlotNotOwnedError. R-14 requires the
9
+ // short-circuit cover BOTH the scalar `shared<T>` arm AND the
10
+ // `array<shared<T>>` element dispatch (single helper SSOT).
11
+ //
12
+ // User-tier handles (slot >= BUILTIN_BASE, minted by allocSharedRef) keep
13
+ // flowing through World.sharedRefs as before.
14
+
15
+ import { BUILTIN_BASE, toShared } from '@forgeax/engine-types';
16
+ import { describe, expect, it, vi } from 'vitest';
17
+ import { type Component, defineComponent } from '../component';
18
+ import { World } from '../world';
19
+
20
+ // HANDLE_CUBE's runtime slot (1) — a builtin-tier handle constructed without
21
+ // importing @forgeax/engine-runtime (ecs must not depend on runtime).
22
+ const BUILTIN_HANDLE = toShared<'MeshAsset'>(1);
23
+
24
+ describe('M6 w52: write-barrier builtin-slot short-circuit (AC-31 / R-14)', () => {
25
+ it('scalar shared<T> field = builtin handle: spawn retains 0 calls + no error', () => {
26
+ const Holder = defineComponent('W52ScalarHolder', { asset: { type: 'shared<MeshAsset>' } });
27
+ const w = new World();
28
+ const retainSpy = vi.spyOn(w.sharedRefs, 'retain');
29
+ const releaseSpy = vi.spyOn(w.sharedRefs, 'release');
30
+
31
+ const e = w
32
+ .spawn({
33
+ component: Holder as unknown as Component,
34
+ data: { asset: BUILTIN_HANDLE } as never,
35
+ })
36
+ .unwrap();
37
+ // spawn write-barrier must short-circuit on the builtin slot.
38
+ expect(retainSpy).not.toHaveBeenCalled();
39
+
40
+ w.despawn(e).unwrap();
41
+ // despawn write-barrier must short-circuit on the builtin slot.
42
+ expect(releaseSpy).not.toHaveBeenCalled();
43
+ });
44
+
45
+ it('array<shared<T>> field = [builtin handle]: retain/release 0 calls, no SharedRefReleasedError (R-14)', () => {
46
+ const Holder = defineComponent('W52ArrayHolder', {
47
+ assets: { type: 'array<shared<MeshAsset>>' },
48
+ });
49
+ const w = new World();
50
+ const retainSpy = vi.spyOn(w.sharedRefs, 'retain');
51
+ const releaseSpy = vi.spyOn(w.sharedRefs, 'release');
52
+
53
+ const e = w
54
+ .spawn({
55
+ component: Holder as unknown as Component,
56
+ data: { assets: [BUILTIN_HANDLE, BUILTIN_HANDLE] } as never,
57
+ })
58
+ .unwrap();
59
+ expect(retainSpy).not.toHaveBeenCalled();
60
+
61
+ expect(() => w.despawn(e).unwrap()).not.toThrow();
62
+ expect(releaseSpy).not.toHaveBeenCalled();
63
+ });
64
+
65
+ it('user-tier scalar handle still flows through World.sharedRefs (retain on spawn)', () => {
66
+ const Holder = defineComponent('W52UserScalarHolder', {
67
+ asset: { type: 'shared<MeshAsset>' },
68
+ });
69
+ const w = new World();
70
+ const userHandle = w.allocSharedRef('MeshAsset', { kind: 'mesh' });
71
+ expect(userHandle).toBeGreaterThanOrEqual(BUILTIN_BASE);
72
+ const retainSpy = vi.spyOn(w.sharedRefs, 'retain');
73
+
74
+ w.spawn({
75
+ component: Holder as unknown as Component,
76
+ data: { asset: userHandle } as never,
77
+ }).unwrap();
78
+ // user-tier handle is retained by the write barrier (rc 1 -> 2).
79
+ expect(retainSpy).toHaveBeenCalled();
80
+ expect(w.sharedRefs.refcount(userHandle)).toBe(2);
81
+ });
82
+
83
+ it('user-tier array element still flows through World.sharedRefs (R-14 control)', () => {
84
+ const Holder = defineComponent('W52UserArrayHolder', {
85
+ assets: { type: 'array<shared<MeshAsset>>' },
86
+ });
87
+ const w = new World();
88
+ const userHandle = w.allocSharedRef('MeshAsset', { kind: 'mesh' });
89
+ const retainSpy = vi.spyOn(w.sharedRefs, 'retain');
90
+
91
+ w.spawn({
92
+ component: Holder as unknown as Component,
93
+ data: { assets: [userHandle] } as never,
94
+ }).unwrap();
95
+ expect(retainSpy).toHaveBeenCalled();
96
+ expect(w.sharedRefs.refcount(userHandle)).toBe(2);
97
+ });
98
+ });
@@ -0,0 +1,52 @@
1
+ import { err } from '@forgeax/engine-types';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { SystemFailedError } from '../errors';
4
+ import { FixedUpdate, Update, World } from '../index';
5
+
6
+ describe('M4 world scheduling failure boundary', () => {
7
+ it('stops the frame and poisons the World when a system throws', () => {
8
+ const world = new World();
9
+ world.addSystem(Update, {
10
+ name: 'thrower',
11
+ queries: [],
12
+ fn: () => {
13
+ throw new Error('system');
14
+ },
15
+ });
16
+
17
+ const result = world.update(1 / 60);
18
+ expect(result.ok).toBe(false);
19
+ if (!result.ok) expect(result.error).toBeInstanceOf(SystemFailedError);
20
+ expect(world.execution.health).toBe('poisoned');
21
+ expect(world.update(1 / 60).ok).toBe(false);
22
+ });
23
+
24
+ it('poisons a frame when a system returns an expected command failure', () => {
25
+ const world = new World();
26
+ const failure = new Error('command failure');
27
+ world.addSystem(Update, { name: 'result-failure', queries: [], fn: () => err(failure) });
28
+
29
+ const result = world.update();
30
+ expect(result.ok).toBe(false);
31
+ if (!result.ok) {
32
+ expect(result.error).toBeInstanceOf(SystemFailedError);
33
+ expect((result.error as SystemFailedError).detail.cause).toBe(failure);
34
+ }
35
+ expect(world.execution.health).toBe('poisoned');
36
+ });
37
+
38
+ it('advances the clock only for a successful frame and keeps fixed order stable', () => {
39
+ const world = new World({ time: { fixedDeltaSeconds: 0.01, maxStepsPerUpdate: 4 } });
40
+ const order: string[] = [];
41
+ world.addSystem(Update, { name: 'update', queries: [], fn: () => order.push('update') });
42
+ world.addSystem(FixedUpdate, {
43
+ name: 'fixed',
44
+ queries: [],
45
+ fn: () => order.push('fixed'),
46
+ });
47
+
48
+ expect(world.update(0.02).ok).toBe(true);
49
+ expect(order).toEqual(['update', 'fixed', 'fixed']);
50
+ expect(world.getResource<{ elapsed: number }>('Time').elapsed).toBeCloseTo(0.02);
51
+ });
52
+ });
@@ -0,0 +1,357 @@
1
+ // feat-20260614-ecs-managed-lifecycle-ssot M2 t-w3: world-level round-trip
2
+ // net-zero matrix for managed-resource fields.
3
+ //
4
+ // Verifies AC-12 (requirements §5 / §8 boundary table): every release-dispatch
5
+ // path on World must net-zero the BufferPool and UniqueRefStore live counters
6
+ // after a spawn -> mutate -> tear-down cycle, for each managed-vocab keyword.
7
+ //
8
+ // Matrix: 4 vocab x 2 round shapes = 8 cases.
9
+ //
10
+ // Vocab dimensions (keyword family routed through releaseManagedFieldOnRow
11
+ // after w4):
12
+ // - 'string' -> UniqueRefStore handle (isManagedField arm)
13
+ // - 'unique<T>' -> UniqueRefStore handle (isManagedField arm)
14
+ // - 'buffer' variable -> BufferPool slot id (isManagedBufferField arm)
15
+ // - 'array<T>' variable-> BufferPool slot id (isManagedArrayField arm)
16
+ //
17
+ // Round shapes:
18
+ // (R1) spawn -> world.set(e, C, { f: v2 }) -> world.despawn(e)
19
+ // (R2) spawn -> world.removeComponent(e, C) -> world.addComponent(e, C, ...)
20
+ //
21
+ // R2 uses addComponent (not a second spawn) to stress the same release-dispatch
22
+ // path that releaseManagedRefsOnRow walks (research Finding 1.2 row 4 / plan-
23
+ // strategy §5.3 M2 key test).
24
+ //
25
+ // OOS-1: this test only exercises increment release on per-entity despawn /
26
+ // per-component remove; it does NOT call any World.clear/dispose path. OOS-2:
27
+ // SceneInstance state alloc/release at world.ts:3494/3522 are out of scope
28
+ // for the helper (D-5) and not exercised here.
29
+ //
30
+ // TDD positioning: the round-trip invariant is already in place pre-w4 because
31
+ // the dispatch is correct, only duplicated; w3 locks in the invariant before
32
+ // the SSOT collapse so w4/w5 reviewers see it stays green through the refactor.
33
+
34
+ import type { Handle } from '@forgeax/engine-types';
35
+ import { describe, expect, it } from 'vitest';
36
+ import type { BufferPool } from '../buffer-pool';
37
+ import { type Component, defineComponent } from '../component';
38
+ import type { SharedRefStore } from '../shared-ref-store';
39
+ import type { UniqueRefStore } from '../unique-ref-store';
40
+ import { World } from '../world';
41
+
42
+ interface MaterialAsset {
43
+ albedo: [number, number, number, number];
44
+ }
45
+
46
+ interface WorldInternals {
47
+ uniqueRefs: UniqueRefStore;
48
+ sharedRefs: SharedRefStore;
49
+ bufferPool: BufferPool;
50
+ }
51
+
52
+ function refsOf(w: World): UniqueRefStore {
53
+ return (w as unknown as WorldInternals).uniqueRefs;
54
+ }
55
+ function poolOf(w: World): BufferPool {
56
+ return (w as unknown as WorldInternals).bufferPool;
57
+ }
58
+ function liveRefs(w: World): number {
59
+ return refsOf(w)._liveCount();
60
+ }
61
+ function liveSlots(w: World): number {
62
+ return poolOf(w)._liveCount();
63
+ }
64
+
65
+ // Allocates a fresh ref<MaterialAsset> handle inside the same World so the
66
+ // round-trip ledger stays self-contained: every alloc must net-zero too.
67
+ function allocMat(
68
+ w: World,
69
+ albedo: [number, number, number, number],
70
+ ): Handle<'MaterialAsset', 'unique'> {
71
+ return refsOf(w).alloc<'MaterialAsset', MaterialAsset>('MaterialAsset', { albedo });
72
+ }
73
+
74
+ interface RoundCase {
75
+ vocab: 'string' | 'unique<T>' | 'buffer' | 'array<T>';
76
+ Component: Component;
77
+ v1: () => unknown;
78
+ v2: () => unknown;
79
+ v3: () => unknown;
80
+ }
81
+
82
+ // Build the 4 vocab cases. Each defines a fresh component with one managed
83
+ // field of that vocab keyword; the value generators v1/v2/v3 produce
84
+ // distinct payloads so the set / addComponent paths actually mutate.
85
+ function buildCases(w: World): RoundCase[] {
86
+ const StrName = defineComponent('RtStrName', { value: { type: 'string' } });
87
+ const Mat = defineComponent('RtMat', { material: { type: 'unique<MaterialAsset>' } });
88
+ const Buf = defineComponent('RtBuf', { bytes: { type: 'buffer' } });
89
+ const Arr = defineComponent('RtArr', { ids: { type: 'array<u32>' } });
90
+
91
+ return [
92
+ {
93
+ vocab: 'string',
94
+ Component: StrName as unknown as Component,
95
+ v1: () => 'alpha',
96
+ v2: () => 'beta',
97
+ v3: () => 'gamma',
98
+ },
99
+ {
100
+ vocab: 'unique<T>',
101
+ Component: Mat as unknown as Component,
102
+ // The ref<T> vocab requires alloc'ing a handle into the World's
103
+ // UniqueRefStore. The generators alloc inside the round (after the
104
+ // counter snapshot), so the closing despawn / set / removeComponent
105
+ // path must release them back to net-zero.
106
+ v1: () => allocMat(w, [1, 0, 0, 1]),
107
+ v2: () => allocMat(w, [0, 1, 0, 1]),
108
+ v3: () => allocMat(w, [0, 0, 1, 1]),
109
+ },
110
+ {
111
+ vocab: 'buffer',
112
+ Component: Buf as unknown as Component,
113
+ v1: () => new Uint8Array([1, 2, 3, 4]),
114
+ v2: () => new Uint8Array([5, 6, 7, 8, 9]),
115
+ v3: () => new Uint8Array([10]),
116
+ },
117
+ {
118
+ vocab: 'array<T>',
119
+ Component: Arr as unknown as Component,
120
+ v1: () => new Uint32Array([1, 2, 3]),
121
+ v2: () => new Uint32Array([4, 5, 6, 7]),
122
+ v3: () => new Uint32Array([8]),
123
+ },
124
+ ];
125
+ }
126
+
127
+ describe('world managed round-trip net-zero matrix (feat-20260614 M2 / AC-12)', () => {
128
+ describe('R1: spawn -> set -> despawn', () => {
129
+ for (const vocab of ['string', 'unique<T>', 'buffer', 'array<T>'] as const) {
130
+ it(`${vocab}: live counters net-zero after spawn -> set -> despawn`, () => {
131
+ const w = new World();
132
+ const cases = buildCases(w);
133
+ const c = cases.find((cs) => cs.vocab === vocab);
134
+ if (!c) throw new Error('case missing');
135
+
136
+ // Snapshot counters BEFORE any value generation. ref<T>'s value
137
+ // generator alloc's into the World's UniqueRefStore inside the
138
+ // round, so the round's full release-dispatch must net-zero those
139
+ // back to 0 by the end of despawn.
140
+ const refsBefore = liveRefs(w);
141
+ const slotsBefore = liveSlots(w);
142
+
143
+ const e = w
144
+ .spawn({ component: c.Component, data: { [fieldOf(c)]: c.v1() } as never })
145
+ .unwrap();
146
+ w.set(e, c.Component, { [fieldOf(c)]: c.v2() } as never).unwrap();
147
+ w.despawn(e).unwrap();
148
+
149
+ expect(liveRefs(w)).toBe(refsBefore);
150
+ expect(liveSlots(w)).toBe(slotsBefore);
151
+ });
152
+ }
153
+ });
154
+
155
+ describe('R2: spawn -> removeComponent -> addComponent', () => {
156
+ for (const vocab of ['string', 'unique<T>', 'buffer', 'array<T>'] as const) {
157
+ it(`${vocab}: live counters net-zero after spawn -> removeComponent -> addComponent`, () => {
158
+ const w = new World();
159
+ const cases = buildCases(w);
160
+ const c = cases.find((cs) => cs.vocab === vocab);
161
+ if (!c) throw new Error('case missing');
162
+
163
+ // Snapshot before any v1/v3 alloc so ref<T>'s allocator hits land
164
+ // inside the round; the closing despawn must release both the
165
+ // v1-released-by-removeComponent and the v3-installed-by-addComponent
166
+ // payloads to net-zero the counters.
167
+ const refsBefore = liveRefs(w);
168
+ const slotsBefore = liveSlots(w);
169
+
170
+ const e = w
171
+ .spawn({ component: c.Component, data: { [fieldOf(c)]: c.v1() } as never })
172
+ .unwrap();
173
+ w.removeComponent(e, c.Component).unwrap();
174
+ w.addComponent(e, {
175
+ component: c.Component,
176
+ data: { [fieldOf(c)]: c.v3() } as never,
177
+ } as never).unwrap();
178
+ w.despawn(e).unwrap();
179
+
180
+ expect(liveRefs(w)).toBe(refsBefore);
181
+ expect(liveSlots(w)).toBe(slotsBefore);
182
+ });
183
+ }
184
+ });
185
+ });
186
+
187
+ function fieldOf(c: RoundCase): string {
188
+ switch (c.vocab) {
189
+ case 'string':
190
+ return 'value';
191
+ case 'unique<T>':
192
+ return 'material';
193
+ case 'buffer':
194
+ return 'bytes';
195
+ case 'array<T>':
196
+ return 'ids';
197
+ }
198
+ }
199
+
200
+ // ──────────────────────────────────────────────────────────────────────────
201
+ // M4 / w11 — array<shared<T>> element-level retain/release coverage
202
+ //
203
+ // Covers AC-09 (array<shared<T>> independent meta key + element-level
204
+ // retain/release) + AC-11 (archetype migration net-zero).
205
+ //
206
+ // `array<shared<T>>` differs from `array<handle<T>>` in that the ECS owns
207
+ // retain/release on each element; the holder writes the array, and the
208
+ // SharedRefStore refcount is the SSOT for liveness. Five scenarios cover
209
+ // the full element-level lifecycle:
210
+ // (S1) set elements → SharedRefStore.retain called once per element
211
+ // (S2) overwrite element set → prior elements release, new elements retain
212
+ // (S3) clear (set to empty array) → all current elements release
213
+ // (S4) archetype migration (add component triggers row move) → net-zero
214
+ // (S5) spawn → despawn → all array<shared<T>> elements release back to producer rc=1
215
+ // ──────────────────────────────────────────────────────────────────────────
216
+
217
+ interface SharedAsset {
218
+ tag: string;
219
+ }
220
+
221
+ function sharedRefsOf(w: World): SharedRefStore {
222
+ return (w as unknown as WorldInternals).sharedRefs;
223
+ }
224
+
225
+ function allocSharedAsset(w: World, tag: string): Handle<'SharedAsset', 'shared'> {
226
+ return w.allocSharedRef<'SharedAsset', SharedAsset>('SharedAsset', { tag });
227
+ }
228
+
229
+ describe('array<shared<T>> element-level retain/release (feat-20260614 M4 / AC-09 / AC-11)', () => {
230
+ it('S1: set elements → each element retained (rc=2 = producer alloc-grant + ECS retain)', () => {
231
+ const w = new World();
232
+ const Holder = defineComponent('S1Holder', { assets: { type: 'array<shared<SharedAsset>>' } });
233
+
234
+ const h1 = allocSharedAsset(w, 'a');
235
+ const h2 = allocSharedAsset(w, 'b');
236
+ expect(sharedRefsOf(w).refcount(h1)).toBe(1);
237
+ expect(sharedRefsOf(w).refcount(h2)).toBe(1);
238
+
239
+ w.spawn({
240
+ component: Holder as unknown as Component,
241
+ data: { assets: [h1, h2] } as never,
242
+ }).unwrap();
243
+
244
+ // Each array element is retained once by the ECS write barrier.
245
+ expect(sharedRefsOf(w).refcount(h1)).toBe(2);
246
+ expect(sharedRefsOf(w).refcount(h2)).toBe(2);
247
+ });
248
+
249
+ it('S2: overwrite array → prior elements released, new elements retained (net-zero rc on shared elements)', () => {
250
+ const w = new World();
251
+ const Holder = defineComponent('S2Holder', { assets: { type: 'array<shared<SharedAsset>>' } });
252
+
253
+ const h1 = allocSharedAsset(w, 'a');
254
+ const h2 = allocSharedAsset(w, 'b');
255
+ const h3 = allocSharedAsset(w, 'c');
256
+
257
+ const e = w
258
+ .spawn({ component: Holder as unknown as Component, data: { assets: [h1, h2] } as never })
259
+ .unwrap();
260
+
261
+ // After spawn: h1.rc=2, h2.rc=2, h3.rc=1.
262
+ expect(sharedRefsOf(w).refcount(h1)).toBe(2);
263
+ expect(sharedRefsOf(w).refcount(h2)).toBe(2);
264
+ expect(sharedRefsOf(w).refcount(h3)).toBe(1);
265
+
266
+ // Overwrite to [h3]. h1+h2 lose their ECS-retain, h3 gains it.
267
+ w.set(e, Holder as unknown as Component, { assets: [h3] } as never).unwrap();
268
+
269
+ expect(sharedRefsOf(w).refcount(h1)).toBe(1);
270
+ expect(sharedRefsOf(w).refcount(h2)).toBe(1);
271
+ expect(sharedRefsOf(w).refcount(h3)).toBe(2);
272
+ });
273
+
274
+ it('S3: clear (set to empty array) → all current elements released', () => {
275
+ const w = new World();
276
+ const Holder = defineComponent('S3Holder', { assets: { type: 'array<shared<SharedAsset>>' } });
277
+
278
+ const h1 = allocSharedAsset(w, 'a');
279
+ const h2 = allocSharedAsset(w, 'b');
280
+
281
+ const e = w
282
+ .spawn({ component: Holder as unknown as Component, data: { assets: [h1, h2] } as never })
283
+ .unwrap();
284
+
285
+ expect(sharedRefsOf(w).refcount(h1)).toBe(2);
286
+ expect(sharedRefsOf(w).refcount(h2)).toBe(2);
287
+
288
+ w.set(e, Holder as unknown as Component, { assets: [] } as never).unwrap();
289
+
290
+ // ECS-retain released on both; producer alloc-grant remains.
291
+ expect(sharedRefsOf(w).refcount(h1)).toBe(1);
292
+ expect(sharedRefsOf(w).refcount(h2)).toBe(1);
293
+ });
294
+
295
+ it('S4: archetype migration (addComponent triggers row move) → element rc net-zero across migration', () => {
296
+ const w = new World();
297
+ const Holder = defineComponent('S4Holder', { assets: { type: 'array<shared<SharedAsset>>' } });
298
+ const Tag = defineComponent('S4Tag', { v: { type: 'u32' } });
299
+
300
+ const h1 = allocSharedAsset(w, 'a');
301
+ const h2 = allocSharedAsset(w, 'b');
302
+
303
+ const e = w
304
+ .spawn({ component: Holder as unknown as Component, data: { assets: [h1, h2] } as never })
305
+ .unwrap();
306
+
307
+ const before1 = sharedRefsOf(w).refcount(h1);
308
+ const before2 = sharedRefsOf(w).refcount(h2);
309
+ expect(before1).toBe(2);
310
+ expect(before2).toBe(2);
311
+
312
+ // addComponent triggers archetype migration (row swap-pop into a new
313
+ // archetype). The shared-array column bytes move with the row; the
314
+ // element refcount must NOT shift — this is the net-zero invariant
315
+ // (AC-11). Migration copies the slot id, no new retain/release.
316
+ w.addComponent(e, {
317
+ component: Tag as unknown as Component,
318
+ data: { v: 7 } as never,
319
+ } as never).unwrap();
320
+
321
+ expect(sharedRefsOf(w).refcount(h1)).toBe(before1);
322
+ expect(sharedRefsOf(w).refcount(h2)).toBe(before2);
323
+
324
+ // removeComponent migrates back; still net-zero.
325
+ w.removeComponent(e, Tag as unknown as Component).unwrap();
326
+ expect(sharedRefsOf(w).refcount(h1)).toBe(before1);
327
+ expect(sharedRefsOf(w).refcount(h2)).toBe(before2);
328
+ });
329
+
330
+ it('S5: spawn → despawn round-trip → all array<shared<T>> elements release back to producer rc=1', () => {
331
+ const w = new World();
332
+ const Holder = defineComponent('S5Holder', { assets: { type: 'array<shared<SharedAsset>>' } });
333
+
334
+ const h1 = allocSharedAsset(w, 'a');
335
+ const h2 = allocSharedAsset(w, 'b');
336
+ const h3 = allocSharedAsset(w, 'c');
337
+
338
+ const e = w
339
+ .spawn({
340
+ component: Holder as unknown as Component,
341
+ data: { assets: [h1, h2, h3] } as never,
342
+ })
343
+ .unwrap();
344
+
345
+ expect(sharedRefsOf(w).refcount(h1)).toBe(2);
346
+ expect(sharedRefsOf(w).refcount(h2)).toBe(2);
347
+ expect(sharedRefsOf(w).refcount(h3)).toBe(2);
348
+
349
+ w.despawn(e).unwrap();
350
+
351
+ // Despawn releases the ECS-retain on every element; producer alloc-grant
352
+ // (rc=1) survives so consumers (e.g. AssetRegistry) keep ownership.
353
+ expect(sharedRefsOf(w).refcount(h1)).toBe(1);
354
+ expect(sharedRefsOf(w).refcount(h2)).toBe(1);
355
+ expect(sharedRefsOf(w).refcount(h3)).toBe(1);
356
+ });
357
+ });
@@ -0,0 +1,156 @@
1
+ // feat-20260517-spawn-default-fallback / M2 / t6.
2
+ //
3
+ // Compile-time type assertions for the `ComponentData<S>['data']` surface
4
+ // after the M2 widening + the spawn mapped-tuple primary-inference probe.
5
+ //
6
+ // Three pinned invariants (Red until t9 lands):
7
+ //
8
+ // (a) ComponentData<S>['data'] === Partial<ShapeOf<S>> — AC-01
9
+ // The shared field bridge between `world.spawn` / `world.addComponent`
10
+ // / overrides; widening from ShapeOf<S> to Partial<ShapeOf<S>> is the
11
+ // single physical edit that takes the whole route off the ts(2739)
12
+ // cliff (AC-02).
13
+ //
14
+ // (b) `world.spawn({ component: Camera, data: { fov, aspect, near, far } })`
15
+ // compiles — AC-02
16
+ // hello-window:93 baseline. With ShapeOf<S> the same call reports
17
+ // ts(2739) "missing 5 properties"; with Partial<ShapeOf<S>> it
18
+ // compiles (and at runtime layer-2 / layer-3 fallback fills the
19
+ // remaining fields).
20
+ //
21
+ // (c) `data: { fov: 'bad' }` fires field-level TS2322 — AC-03
22
+ // Mapped-tuple primary inference must NOT degrade after the
23
+ // widening: the wrong-VALUE case continues to surface as
24
+ // TS2322 ("Type 'string' is not assignable to type 'number'")
25
+ // directly on the offending key — NOT as the "No overload matches"
26
+ // wall the legacy fallback overload would fall back to (C-4).
27
+ //
28
+ // AI users grep `// @ts-expect-error AC-` here to find the regression
29
+ // barrier that protects the mapped-tuple primary-inference behaviour
30
+ // across TS upgrades (R-1 / plan-strategy §2.7 P3).
31
+
32
+ import { describe, expectTypeOf, it } from 'vitest';
33
+ import { defineComponent, type SchemaOf, type ShapeOf } from '../component';
34
+ import { type ComponentData, World } from '../world';
35
+
36
+ // Shared schema fixture — mirrors the runtime Camera token (9 f32) without
37
+ // pulling @forgeax/engine-runtime in.
38
+ const CameraLike = defineComponent('CameraLike', {
39
+ fov: { type: 'f32' },
40
+ aspect: { type: 'f32' },
41
+ near: { type: 'f32' },
42
+ far: { type: 'f32' },
43
+ projection: { type: 'f32' },
44
+ left: { type: 'f32' },
45
+ right: { type: 'f32' },
46
+ bottom: { type: 'f32' },
47
+ top: { type: 'f32' },
48
+ });
49
+
50
+ type CameraLikeSchema = SchemaOf<typeof CameraLike>;
51
+
52
+ describe('ComponentData<S>.data — Partial<ShapeOf<S>> (AC-01, t6.a)', () => {
53
+ it('ComponentData<S>["data"] equals Partial<ShapeOf<S>>', () => {
54
+ type DataField = ComponentData<CameraLikeSchema>['data'];
55
+ expectTypeOf<DataField>().toEqualTypeOf<Partial<ShapeOf<CameraLikeSchema>>>();
56
+ });
57
+
58
+ it('ComponentData<S>["data"] is NOT the un-Partial ShapeOf<S>', () => {
59
+ type DataField = ComponentData<CameraLikeSchema>['data'];
60
+ // The wider Partial<ShapeOf<S>> is assignable to itself but the
61
+ // narrower ShapeOf<S> would need every key. After M2 widening,
62
+ // a Partial value is assignable to DataField; before M2 the same
63
+ // Partial is rejected by ShapeOf<S>'s required-keys contract.
64
+ const partial: Partial<ShapeOf<CameraLikeSchema>> = { fov: 1 };
65
+ const field: DataField = partial;
66
+ void field;
67
+ });
68
+
69
+ it('ComponentData<S>["data"] is NOT widened to any', () => {
70
+ type DataField = ComponentData<CameraLikeSchema>['data'];
71
+ expectTypeOf<DataField>().not.toBeAny();
72
+ });
73
+ });
74
+
75
+ describe('world.spawn — partial data accepted (AC-02, t6.b)', () => {
76
+ it('hello-window-shape 4-field call compiles after M2 widening', () => {
77
+ const w = new World();
78
+ // The hello-window:93 baseline call: drop the 5 ortho fields on a
79
+ // perspective spawn. Before M2 this fires ts(2739) ("missing 5
80
+ // properties"); after M2 it compiles and runtime layer-2 / layer-3
81
+ // fallback fills the missing fields.
82
+ const r = w.spawn({
83
+ component: CameraLike,
84
+ data: { fov: 1, aspect: 1, near: 1, far: 100 },
85
+ });
86
+ void r;
87
+ });
88
+
89
+ it('full 9-field call still compiles (no regression)', () => {
90
+ const w = new World();
91
+ const r = w.spawn({
92
+ component: CameraLike,
93
+ data: {
94
+ fov: 1,
95
+ aspect: 1,
96
+ near: 1,
97
+ far: 100,
98
+ projection: 0,
99
+ left: -1,
100
+ right: 1,
101
+ bottom: -1,
102
+ top: 1,
103
+ },
104
+ });
105
+ void r;
106
+ });
107
+
108
+ it('completely empty data: {} compiles (every field defaulted)', () => {
109
+ const w = new World();
110
+ const r = w.spawn({
111
+ component: CameraLike,
112
+ data: {},
113
+ });
114
+ void r;
115
+ });
116
+ });
117
+
118
+ describe('world.spawn — wrong-value fires field-level TS2322 (AC-03, t6.c)', () => {
119
+ it('data: { fov: "bad" } reports field-level TS2322 (mapped-tuple primary intact)', () => {
120
+ const w = new World();
121
+ // AC-03: mapped-tuple primary inference surfaces wrong-VALUE on the
122
+ // OFFENDING KEY (field-level TS2322), not as the overload wall.
123
+ // The directive sits directly above the offending key so vitest's
124
+ // ts-expect-error tracker sees the suppression land on the right
125
+ // expression.
126
+ w.spawn({
127
+ component: CameraLike,
128
+ data: {
129
+ // @ts-expect-error AC-03 fov is f32 (number), not string
130
+ fov: 'bad',
131
+ },
132
+ });
133
+ });
134
+
135
+ it('data: { aspect: false } reports field-level TS2322 (bool->f32 narrowing)', () => {
136
+ const w = new World();
137
+ w.spawn({
138
+ component: CameraLike,
139
+ data: {
140
+ // @ts-expect-error AC-03 aspect is f32 (number), not boolean
141
+ aspect: false,
142
+ },
143
+ });
144
+ });
145
+
146
+ it('data: { unknownKey: 1 } reports excess-property error, not "No overload matches"', () => {
147
+ const w = new World();
148
+ w.spawn({
149
+ component: CameraLike,
150
+ data: {
151
+ // @ts-expect-error AC-03 unknownKey is not part of CameraLike schema
152
+ unknownKey: 1,
153
+ },
154
+ });
155
+ });
156
+ });