@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,646 @@
1
+ import { Update } from '../schedule-token';
2
+ // feat-20260614-ecs-shared-component-and-unique-rename M3 — SharedRefStore
3
+ // red-green-refactor unit tests.
4
+ //
5
+ // Drives the SharedRefStore class directly (no World) per plan-strategy §5.3.
6
+ // The store mirrors UniqueRefStore in shape but adds reference counting and
7
+ // structured release evidence. Five public API surfaces:
8
+ //
9
+ // alloc(tag, value) -> Handle<T, 'shared'> (rc starts at 1)
10
+ // resolve(handle) -> Result<T, SharedRefReleasedError>
11
+ // retain(handle) -> Result<void, SharedRefReleasedError>
12
+ // release(handle) -> Result<release evidence | undefined, ...>
13
+ // readReleaseEvidence() -> bounded final-release records
14
+ //
15
+ // Tests are split across three describe blocks tracking the w6 / w7 / w8
16
+ // task boundary. The first two run as TDD red against the still-absent
17
+ // SharedRefStore class in w6 / w7; w9 implements the class to turn them
18
+ // green. w8 covers the World.allocSharedRef facade including AC-16 type
19
+ // inference inside an addSystem fn callback.
20
+ //
21
+ // M3 scope: store + facade + parser/TYPE_METADATA wiring. Spawn-retain /
22
+ // despawn-release of `'shared<T>'` schema fields is M4 work (w12 inserts the
23
+ // fieldType.startsWith('shared<') sub-dispatch in releaseManagedFieldOnRow);
24
+ // that integration is covered by the M4 test surface (w11), not here.
25
+
26
+ import type { Handle } from '@forgeax/engine-types';
27
+ import {
28
+ BUILTIN_BASE,
29
+ handleGeneration,
30
+ handleSlot,
31
+ MAX_SLOT,
32
+ pack,
33
+ toShared,
34
+ unwrapHandle,
35
+ } from '@forgeax/engine-types';
36
+ import { describe, expect, expectTypeOf, it } from 'vitest';
37
+ import { defineComponent } from '../component';
38
+ import { BuiltinSlotNotOwnedError, SharedRefStaleError } from '../errors';
39
+ import { SharedRefStore } from '../shared-ref-store';
40
+ import { World } from '../world';
41
+
42
+ // ─── w6: alloc + resolve ────────────────────────────────────────────────
43
+ describe('w6 SharedRefStore: alloc + resolve', () => {
44
+ it('alloc returns a Handle<T, "shared"> branded value with rc=1', () => {
45
+ const store = new SharedRefStore();
46
+ const handle = store.alloc('TestAsset', { id: 1 });
47
+
48
+ expectTypeOf(handle).toEqualTypeOf<Handle<'TestAsset', 'shared'>>();
49
+ expect(store.refcount(handle)).toBe(1);
50
+ expect(store._liveCount()).toBe(1);
51
+ });
52
+
53
+ it('resolve returns the payload while rc > 0', () => {
54
+ const store = new SharedRefStore();
55
+ const payload = { mesh: 'cube' };
56
+ const handle = store.alloc('MeshAsset', payload);
57
+
58
+ const r = store.resolve(handle);
59
+ expect(r.ok).toBe(true);
60
+ if (r.ok) {
61
+ expect(r.value).toBe(payload);
62
+ }
63
+ });
64
+
65
+ it('publishes explicit in-place payload changes through one monotonic epoch', () => {
66
+ const store = new SharedRefStore();
67
+ const handle = store.alloc('MaterialAsset', { value: 1 });
68
+
69
+ expect(store.getMutationEpoch()).toBe(0);
70
+ expect(store.markChanged(handle).ok).toBe(true);
71
+ expect(store.getMutationEpoch()).toBe(1);
72
+ expect(store.readChangesSince(0)).toEqual({
73
+ status: 'ok',
74
+ cursor: 1,
75
+ records: [{ epoch: 1, handle: unwrapHandle(handle) }],
76
+ });
77
+
78
+ expect(store.release(handle).ok).toBe(true);
79
+ expect(store.markChanged(handle).ok).toBe(false);
80
+ expect(store.getMutationEpoch()).toBe(1);
81
+ });
82
+
83
+ it('reports bounded-journal overflow instead of hiding changed handles', () => {
84
+ const store = new SharedRefStore();
85
+ const handle = store.alloc('MaterialAsset', { value: 1 });
86
+ for (let index = 0; index < 4097; index += 1) store.markChanged(handle).unwrap();
87
+
88
+ expect(store.readChangesSince(0)).toEqual({
89
+ status: 'overflow',
90
+ cursor: 4097,
91
+ oldestAvailable: 2,
92
+ });
93
+ const latest = store.readChangesSince(4096);
94
+ expect(latest.status).toBe('ok');
95
+ if (latest.status === 'ok') {
96
+ expect(latest.records).toEqual([{ epoch: 4097, handle: unwrapHandle(handle) }]);
97
+ }
98
+ });
99
+
100
+ it('resolve returns SharedRefStaleError after rc drops to 0 (gen incremented on release)', () => {
101
+ const store = new SharedRefStore();
102
+ const handle = store.alloc('MeshAsset', { id: 7 });
103
+
104
+ const releaseResult = store.release(handle);
105
+ expect(releaseResult.ok).toBe(true);
106
+
107
+ // After w10 gen increment on release, old handle gen=0 mismatches store
108
+ // gen=1 — stale, not released. Gen comparison runs before payload lookup.
109
+ const r = store.resolve(handle);
110
+ expect(r.ok).toBe(false);
111
+ if (!r.ok) {
112
+ expect(r.error.code).toBe('shared-ref-stale');
113
+ if (r.error instanceof SharedRefStaleError) {
114
+ expect(typeof r.error.detail.slot).toBe('number');
115
+ }
116
+ }
117
+ });
118
+
119
+ it('release after rc=0 returns SharedRefStaleError (gen mismatch after first release)', () => {
120
+ const store = new SharedRefStore();
121
+ const handle = store.alloc('MeshAsset', { id: 9 });
122
+
123
+ expect(store.release(handle).ok).toBe(true);
124
+ // Second release: gen=0 vs store gen=1 — stale, not double-release.
125
+ const second = store.release(handle);
126
+ expect(second.ok).toBe(false);
127
+ if (!second.ok) {
128
+ expect(second.error.code).toBe('shared-ref-stale');
129
+ // error.detail is a discriminated union; use error instance check
130
+ if (second.error instanceof SharedRefStaleError) {
131
+ expect(typeof second.error.detail.slot).toBe('number');
132
+ }
133
+ }
134
+ });
135
+ });
136
+
137
+ describe('SharedRefStore.intern: producer identity', () => {
138
+ it('returns one producer handle for the same target and payload object', () => {
139
+ const store = new SharedRefStore();
140
+ const payload = { id: 1 };
141
+
142
+ const first = store.intern('MaterialAsset', payload);
143
+ const second = store.intern('MaterialAsset', payload);
144
+
145
+ expect(second).toBe(first);
146
+ expect(store.refcount(first)).toBe(1);
147
+ expect(store._liveCount()).toBe(1);
148
+ });
149
+
150
+ it('does not alias ordinary allocations or different targets', () => {
151
+ const store = new SharedRefStore();
152
+ const payload = { id: 1 };
153
+
154
+ const allocatedA = store.alloc('MaterialAsset', payload);
155
+ const allocatedB = store.alloc('MaterialAsset', payload);
156
+ const material = store.intern('MaterialAsset', payload);
157
+ const texture = store.intern('TextureAsset', payload);
158
+
159
+ expect(allocatedB).not.toBe(allocatedA);
160
+ expect(material).not.toBe(allocatedA);
161
+ expect(texture).not.toBe(material);
162
+ });
163
+
164
+ it('forgets the identity entry when the producer grant reaches zero', () => {
165
+ const store = new SharedRefStore();
166
+ const payload = { id: 1 };
167
+ const released = store.intern('MaterialAsset', payload);
168
+
169
+ expect(store.release(released).ok).toBe(true);
170
+ const next = store.intern('MaterialAsset', payload);
171
+
172
+ expect(next).not.toBe(released);
173
+ expect(store.resolve(next)).toMatchObject({ ok: true, value: payload });
174
+ });
175
+ });
176
+
177
+ // ─── w29 (D-10): retain + release evidence ─────────────────────────────
178
+ describe('w29 SharedRefStore: retain + structured release evidence', () => {
179
+ it('alloc -> retain N -> release N+1 cycles through rc=0 cleanly', () => {
180
+ const store = new SharedRefStore();
181
+ const payload = { id: 4 };
182
+ const handle = store.alloc('Asset', payload);
183
+ for (let i = 0; i < 5; i++) {
184
+ expect(store.retain(handle).ok).toBe(true);
185
+ }
186
+ expect(store.refcount(handle)).toBe(6);
187
+
188
+ for (let i = 0; i < 6; i++) {
189
+ expect(store.release(handle).ok).toBe(true);
190
+ }
191
+ expect(store.refcount(handle)).toBe(0);
192
+ expect(store.readReleaseEvidence()).toEqual([
193
+ { payload, refcount: 0, generation: 1, evidence: 'released' },
194
+ ]);
195
+ });
196
+
197
+ it('final release returns the same payload/refcount/generation evidence', () => {
198
+ const store = new SharedRefStore();
199
+ const payload = { id: 1 };
200
+ const handle = store.alloc('Asset', payload);
201
+
202
+ const result = store.release(handle);
203
+ expect(result.ok).toBe(true);
204
+ expect(result.unwrap()).toEqual({ payload, refcount: 0, generation: 1, evidence: 'released' });
205
+ });
206
+
207
+ it('retain after release-to-zero is a SharedRefStaleError (gen mismatch after w10)', () => {
208
+ const store = new SharedRefStore();
209
+ const handle = store.alloc('Asset', { id: 5 });
210
+ expect(store.release(handle).ok).toBe(true);
211
+
212
+ const r = store.retain(handle);
213
+ expect(r.ok).toBe(false);
214
+ if (!r.ok) {
215
+ expect(r.error.code).toBe('shared-ref-stale');
216
+ }
217
+ });
218
+ });
219
+
220
+ // ─── w8: World.allocSharedRef + AC-16 type inference inside addSystem fn ─
221
+ describe('w8 World.allocSharedRef: facade + AC-16 type inference', () => {
222
+ it('World.allocSharedRef returns Handle<Tag, "shared"> (no `as` assertion)', () => {
223
+ const world = new World();
224
+ const handle = world.allocSharedRef('SkinAsset', { joints: 24 });
225
+
226
+ expectTypeOf(handle).toEqualTypeOf<Handle<'SkinAsset', 'shared'>>();
227
+ expect(world.sharedRefs.refcount(handle)).toBe(1);
228
+ });
229
+
230
+ it('World.internSharedRef preserves target handle inference', () => {
231
+ const world = new World();
232
+ const handle = world.internSharedRef('MaterialAsset', { kind: 'material' });
233
+
234
+ expectTypeOf(handle).toEqualTypeOf<Handle<'MaterialAsset', 'shared'>>();
235
+ });
236
+
237
+ it('explicit release of the alloc-grant takes rc to 0 and publishes evidence', () => {
238
+ const world = new World();
239
+
240
+ const payload = { id: 99 };
241
+ const handle = world.allocSharedRef('Asset', payload);
242
+ const r = world.sharedRefs.release(handle);
243
+ expect(r.ok).toBe(true);
244
+ expect(world.sharedRefs.refcount(handle)).toBe(0);
245
+ expect(r.unwrap()).toMatchObject({ payload, refcount: 0, generation: 1, evidence: 'released' });
246
+ });
247
+
248
+ it('AC-16 fourth application point: addSystem fn callback infers Handle<Target, "shared">', () => {
249
+ // AC-16 application point #4: `addSystem({ fn })` callback consumer reads
250
+ // a `'shared<T>'` schema field via world.get and the bundle field type
251
+ // must narrow to `Handle<Target, 'shared'>` with no `as` assertion.
252
+ // Validated inside a real system fn (not a *.test-d.ts file).
253
+ //
254
+ // This test exercises the type-inference end-to-end: schema vocab keyword
255
+ // 'shared<MaterialAsset>' -> column u32 -> world.get unwrap -> field
256
+ // value type. Runtime spawn-retain wiring is M4 (w12) work; here we only
257
+ // exercise the type assertion, not the rc transition.
258
+ const Material = defineComponent('SharedMaterialField', {
259
+ asset: 'shared<MaterialAsset>',
260
+ });
261
+ const world = new World();
262
+ const handle = world.allocSharedRef('MaterialAsset', { albedo: 0xffffff });
263
+
264
+ const e = world.spawn({ component: Material, data: { asset: handle } }).unwrap();
265
+
266
+ let observedType: 'matched' | 'unmatched' = 'unmatched';
267
+ world.addSystem(Update, {
268
+ name: 'shared-bundle-reader',
269
+ queries: [{ with: [Material] }],
270
+ fn: () => {
271
+ const row = world.get(e, Material).unwrap();
272
+ // The type assertion: row.asset MUST be Handle<'MaterialAsset', 'shared'>
273
+ // (not Handle<'MaterialAsset', 'unique'>, not Uint32Array, not unknown).
274
+ expectTypeOf(row.asset).toEqualTypeOf<Handle<'MaterialAsset', 'shared'>>();
275
+ observedType = 'matched';
276
+ },
277
+ });
278
+
279
+ world.update();
280
+ expect(observedType).toBe('matched');
281
+ });
282
+ });
283
+
284
+ // ─── w50 (AC-32): builtin-slot fail-fast guard ───────────────────────────
285
+ // D-15: World.sharedRefs manages ONLY user-tier slots (>= BUILTIN_BASE).
286
+ // Passing a builtin slot (< BUILTIN_BASE) to alloc/retain/release/resolve is a
287
+ // caller error -> BuiltinSlotNotOwnedError with a hint pointing at
288
+ // the builtin asset owner. (toShared(1) is the cube slot.)
289
+ describe('w50 SharedRefStore: fail-fast on builtin slot < BUILTIN_BASE (AC-32)', () => {
290
+ it('retain(builtin slot) returns BuiltinSlotNotOwnedError with a hint', () => {
291
+ const store = new SharedRefStore();
292
+ const r = store.retain(toShared<'MeshAsset'>(1));
293
+ expect(r.ok).toBe(false);
294
+ if (!r.ok) {
295
+ expect(r.error).toBeInstanceOf(BuiltinSlotNotOwnedError);
296
+ expect(r.error.code).toBe('builtin-slot-not-owned');
297
+ if (r.error.code === 'builtin-slot-not-owned') {
298
+ expect(r.error.hint.length).toBeGreaterThan(0);
299
+ expect(r.error.detail.slot).toBe(1);
300
+ }
301
+ }
302
+ });
303
+
304
+ it('release(builtin slot) returns BuiltinSlotNotOwnedError', () => {
305
+ const store = new SharedRefStore();
306
+ const r = store.release(toShared<'MeshAsset'>(2));
307
+ expect(r.ok).toBe(false);
308
+ if (!r.ok) expect(r.error).toBeInstanceOf(BuiltinSlotNotOwnedError);
309
+ });
310
+
311
+ it('resolve(builtin slot) returns BuiltinSlotNotOwnedError', () => {
312
+ const store = new SharedRefStore();
313
+ const r = store.resolve(toShared<'MeshAsset'>(3));
314
+ expect(r.ok).toBe(false);
315
+ if (!r.ok) expect(r.error).toBeInstanceOf(BuiltinSlotNotOwnedError);
316
+ });
317
+
318
+ it('alloc mints user-tier slots (>= BUILTIN_BASE), never a builtin slot', () => {
319
+ const store = new SharedRefStore();
320
+ const handle = store.alloc('MeshAsset', { kind: 'mesh' });
321
+ expect(unwrapHandle(handle)).toBeGreaterThanOrEqual(BUILTIN_BASE);
322
+ });
323
+ });
324
+
325
+ // ─── w6 M3: gen welding alloc tests ─────────────────────────────────────
326
+ // feat-20260623-asset-handle-generation M3 — alloc embeds generation into
327
+ // the returned handle via codec.pack(slot, gen). First alloc gen=0 (AC-06);
328
+ // builtin invariant pack(slot,0)===slot holds (AC-05).
329
+ // M3 scope: gen welding only, no gen increment on release (that's M4).
330
+ // Reused slots always get gen=0 in M3 (release does NOT increment
331
+ // _generations[slot] yet).
332
+ describe('w6 M3 SharedRefStore: gen welding on alloc', () => {
333
+ it('AC-06: first alloc gen=0 => pack(slot,0) === slot (handleSlot matches raw slot)', () => {
334
+ const store = new SharedRefStore();
335
+ const handle = store.alloc('TestAsset', { id: 1 });
336
+
337
+ const raw = unwrapHandle(handle);
338
+ const slot = handleSlot(handle);
339
+ const gen = handleGeneration(handle);
340
+
341
+ expect(gen).toBe(0);
342
+ expect(raw).toBe(slot);
343
+ // AC-06: slot is the raw value when gen=0
344
+ expect(raw).toBeGreaterThanOrEqual(BUILTIN_BASE);
345
+ expect(raw).toBeLessThanOrEqual(MAX_SLOT);
346
+ });
347
+
348
+ it('AC-05: builtin invariant — pack(slot,0) === slot for slot 1-5', () => {
349
+ // Builtin handle constants (slot 1..5, gen=0) must encode to the same
350
+ // u32 value as the raw slot — ensures AssetRegistry builtin Map keys,
351
+ // GUID pre-registration, and entity bit patterns stay unchanged.
352
+ for (let s = 1; s <= 5; s++) {
353
+ expect(pack(s, 0)).toBe(s);
354
+ }
355
+ });
356
+
357
+ it('alloc welds gen into handle — gen extractable via handleGeneration', () => {
358
+ // After alloc gen welding, handleGeneration returns the gen embedded
359
+ // during alloc. In M3, gen is always 0 because release does not yet
360
+ // increment _generations (that's M4). But the code path — pack(slot,gen)
361
+ // inside alloc -> toShared -> handleGeneration unpacks it — must work.
362
+ const store = new SharedRefStore();
363
+ const h = store.alloc('TestAsset', { id: 1 });
364
+ expect(handleGeneration(h)).toBe(0);
365
+ expect(handleSlot(h)).toBeGreaterThanOrEqual(BUILTIN_BASE);
366
+ // pack(slot, gen) round-trips: unpackSlot(pack(s,0)) === s
367
+ expect(pack(handleSlot(h), handleGeneration(h))).toBe(unwrapHandle(h));
368
+ });
369
+
370
+ it('resolve works correctly after gen-welded alloc (gen increments on release in M4)', () => {
371
+ // Alloc + release + re-alloc: the second handle resolves to the
372
+ // second payload. After M4, release increments gen so the reused
373
+ // slot gets gen=1.
374
+ const store = new SharedRefStore();
375
+ const h1 = store.alloc('MeshAsset', { mesh: 'cube' });
376
+ expect(handleGeneration(h1)).toBe(0);
377
+
378
+ expect(store.release(h1).ok).toBe(true);
379
+
380
+ const h2 = store.alloc('MeshAsset', { mesh: 'sphere' });
381
+ // M4: release incremented gen, so re-alloc gets gen=1
382
+ expect(handleGeneration(h2)).toBe(1);
383
+
384
+ const r2 = store.resolve(h2);
385
+ expect(r2.ok).toBe(true);
386
+ if (r2.ok) {
387
+ expect(r2.value).toEqual({ mesh: 'sphere' });
388
+ }
389
+ });
390
+
391
+ it('alloc publishes release evidence after gen-welded release', () => {
392
+ const store = new SharedRefStore();
393
+ const payload = { key: 1 };
394
+ const h = store.alloc('Asset', payload);
395
+ expect(store.release(h).unwrap()).toEqual({
396
+ payload,
397
+ refcount: 0,
398
+ generation: 1,
399
+ evidence: 'released',
400
+ });
401
+ });
402
+
403
+ it('M4: alloc after release reuses slot with gen 1 (gen increments on release)', () => {
404
+ const store = new SharedRefStore();
405
+ const h1 = store.alloc('MeshAsset', { mesh: 'cube' });
406
+ void handleSlot(h1); // probe slot
407
+ expect(store.release(h1).ok).toBe(true);
408
+
409
+ const h2 = store.alloc('MeshAsset', { mesh: 'sphere' });
410
+ // M4: release incremented gen to 1, so re-alloc gets gen=1
411
+ expect(handleGeneration(h2)).toBe(1);
412
+
413
+ // h2 resolves to the new payload
414
+ const r2 = store.resolve(h2);
415
+ expect(r2.ok).toBe(true);
416
+ if (r2.ok) expect(r2.value).toEqual({ mesh: 'sphere' });
417
+
418
+ // h1 (gen=0) is stale now
419
+ const r1 = store.resolve(h1);
420
+ expect(r1.ok).toBe(false);
421
+ if (!r1.ok) expect(r1.error.code).toBe('shared-ref-stale');
422
+ });
423
+ });
424
+
425
+ // ─── w9 M4: stale detection unit tests (red phase) ─────────────────────
426
+ // feat-20260623-asset-handle-generation M4 — gen comparison on
427
+ // resolve/retain/release. RED phase: gen increment on release is NOT
428
+ // now via release (gen 0->1) + re-alloc (gen=1), making h1 (gen=0)
429
+ // stale. No manual _generations mutation needed.
430
+ describe('w9 M4 SharedRefStore: stale detection (resolve/retain/release + retire)', () => {
431
+ it('AC-01: stale resolve returns error with code shared-ref-stale', () => {
432
+ const store = new SharedRefStore();
433
+ const h1 = store.alloc('MeshAsset', { mesh: 'cube' });
434
+ // Release bumps gen from 0 to 1, then re-alloc gets gen=1.
435
+ // h1 (gen=0) is now stale.
436
+ expect(store.release(h1).ok).toBe(true);
437
+ const h2 = store.alloc('MeshAsset', { mesh: 'sphere' });
438
+
439
+ const r = store.resolve(h1);
440
+ expect(r.ok).toBe(false);
441
+ if (!r.ok) {
442
+ expect(r.error.code).toBe('shared-ref-stale');
443
+ }
444
+ // h2 is the current handle and should always resolve
445
+ const r2 = store.resolve(h2);
446
+ expect(r2.ok).toBe(true);
447
+ });
448
+
449
+ it('AC-01: stale resolve never returns a payload (verify error branch has no value)', () => {
450
+ const store = new SharedRefStore();
451
+ const h1 = store.alloc('MeshAsset', { mesh: 'cube' });
452
+ expect(store.release(h1).ok).toBe(true);
453
+ store.alloc('MeshAsset', { mesh: 'sphere' });
454
+
455
+ const r = store.resolve(h1);
456
+ expect(r.ok).toBe(false);
457
+ if (!r.ok) {
458
+ // The error must NOT carry a payload — stale resolve does
459
+ // not return the new payload through any channel.
460
+ // biome-ignore lint/suspicious/noExplicitAny: accessing value on Result union error branch to verify no payload leak
461
+ expect((r as any).value).toBeUndefined();
462
+ }
463
+ });
464
+
465
+ it('AC-02: stale retain returns error, rc unchanged', () => {
466
+ const store = new SharedRefStore();
467
+ const h1 = store.alloc('MeshAsset', { mesh: 'cube' });
468
+ expect(store.release(h1).ok).toBe(true);
469
+ const h2 = store.alloc('MeshAsset', { mesh: 'sphere' });
470
+
471
+ // h2 is alive with rc=1. Read rc before stale retain.
472
+ const rcBefore = store.refcount(h2);
473
+ expect(rcBefore).toBe(1);
474
+
475
+ const r = store.retain(h1);
476
+ expect(r.ok).toBe(false);
477
+ if (!r.ok) {
478
+ expect(r.error.code).toBe('shared-ref-stale');
479
+ }
480
+
481
+ const rcAfter = store.refcount(h2);
482
+ // AC-02: rc MUST be unchanged after stale retain
483
+ expect(rcAfter).toBe(rcBefore);
484
+ });
485
+
486
+ it('AC-03: stale release returns error, rc unchanged — read rc before and after', () => {
487
+ const store = new SharedRefStore();
488
+ const h1 = store.alloc('MeshAsset', { mesh: 'cube' });
489
+ expect(store.release(h1).ok).toBe(true);
490
+
491
+ const h2 = store.alloc('MeshAsset', { mesh: 'sphere' });
492
+ expect(handleGeneration(h2)).toBe(1);
493
+
494
+ // h2 is alive with rc=1. Stale release with h1 (gen=0) must NOT
495
+ // decrement h2's rc.
496
+ const rcBefore = store.refcount(h2);
497
+ expect(rcBefore).toBe(1);
498
+
499
+ const r = store.release(h1);
500
+ expect(r.ok).toBe(false);
501
+ if (!r.ok) {
502
+ expect(r.error.code).toBe('shared-ref-stale');
503
+ }
504
+
505
+ const rcAfter = store.refcount(h2);
506
+ // AC-03 core: stale release MUST NOT touch the new holder's rc.
507
+ expect(rcAfter).toBe(rcBefore);
508
+ });
509
+
510
+ it('AC-03: stale release when h2 rc>1 does not decrement', () => {
511
+ const store = new SharedRefStore();
512
+ const h1 = store.alloc('MeshAsset', { mesh: 'cube' });
513
+ expect(store.release(h1).ok).toBe(true);
514
+
515
+ const h2 = store.alloc('MeshAsset', { mesh: 'sphere' });
516
+ // build rc=3 for h2
517
+ expect(store.retain(h2).ok).toBe(true);
518
+ expect(store.retain(h2).ok).toBe(true);
519
+ expect(store.refcount(h2)).toBe(3);
520
+
521
+ const rcBefore = store.refcount(h2);
522
+ const r = store.release(h1);
523
+ expect(r.ok).toBe(false);
524
+ if (!r.ok) {
525
+ expect(r.error.code).toBe('shared-ref-stale');
526
+ }
527
+ // Stale release must not affect h2's rc, even when rc>1
528
+ expect(store.refcount(h2)).toBe(rcBefore);
529
+ });
530
+
531
+ it('AC-04: new handle after reuse resolves, retains, releases normally', () => {
532
+ const store = new SharedRefStore();
533
+ const h1 = store.alloc('MeshAsset', { mesh: 'cube' });
534
+ expect(store.release(h1).ok).toBe(true);
535
+
536
+ // New handle with gen=1 works normally
537
+ const h2 = store.alloc('MeshAsset', { mesh: 'sphere' });
538
+ expect(handleGeneration(h2)).toBe(1);
539
+
540
+ // resolve
541
+ const r = store.resolve(h2);
542
+ expect(r.ok).toBe(true);
543
+ if (r.ok) {
544
+ expect(r.value).toEqual({ mesh: 'sphere' });
545
+ }
546
+
547
+ // retain
548
+ expect(store.retain(h2).ok).toBe(true);
549
+ expect(store.refcount(h2)).toBe(2);
550
+
551
+ // release back to rc=1 (gen does NOT increment — rc>1)
552
+ expect(store.release(h2).ok).toBe(true);
553
+ expect(store.refcount(h2)).toBe(1);
554
+
555
+ // final release to rc=0 (gen increments 1->2)
556
+ expect(store.release(h2).ok).toBe(true);
557
+ expect(store.refcount(h2)).toBe(0);
558
+
559
+ // h1 still stale (gen=0 vs gen=2)
560
+ const rStale = store.resolve(h1);
561
+ expect(rStale.ok).toBe(false);
562
+ if (!rStale.ok) {
563
+ expect(rStale.error.code).toBe('shared-ref-stale');
564
+ }
565
+ });
566
+
567
+ it('M32: public World mutation fences a released producer from its replacement', () => {
568
+ const Material = defineComponent('M32SharedMaterialField', {
569
+ asset: 'shared<MaterialAsset>',
570
+ });
571
+ const world = new World();
572
+ const sibling = world.allocSharedRef('MaterialAsset', { id: 'healthy-sibling' });
573
+ const oldPayload = { id: 'old-material' };
574
+ const oldHandle = world.allocSharedRef('MaterialAsset', oldPayload);
575
+ const entity = world.spawn({ component: Material, data: { asset: oldHandle } }).unwrap();
576
+
577
+ expect(world.sharedRefs.refcount(oldHandle)).toBe(2);
578
+ expect(world.sharedRefs.refcount(sibling)).toBe(1);
579
+
580
+ expect(world.removeComponent(entity, Material).ok).toBe(true);
581
+ expect(world.sharedRefs.refcount(oldHandle)).toBe(1);
582
+ expect(world.sharedRefs.release(oldHandle).ok).toBe(true);
583
+
584
+ const replacementPayload = { id: 'replacement-material' };
585
+ const replacementHandle = world.allocSharedRef('MaterialAsset', replacementPayload);
586
+ expect(handleSlot(replacementHandle)).toBe(handleSlot(oldHandle));
587
+ expect(handleGeneration(replacementHandle)).toBe(handleGeneration(oldHandle) + 1);
588
+
589
+ const stale = world.sharedRefs.resolve(oldHandle);
590
+ expect(stale.ok).toBe(false);
591
+ if (!stale.ok) {
592
+ expect(stale.error.code).toBe('shared-ref-stale');
593
+ expect(stale.error.detail).toEqual({
594
+ slot: handleSlot(oldHandle),
595
+ expectedGeneration: handleGeneration(oldHandle),
596
+ actualGeneration: handleGeneration(replacementHandle),
597
+ });
598
+ }
599
+
600
+ expect(
601
+ world.addComponent(entity, { component: Material, data: { asset: replacementHandle } }).ok,
602
+ ).toBe(true);
603
+ expect(world.sharedRefs.refcount(replacementHandle)).toBe(2);
604
+ expect(world.sharedRefs.resolve(replacementHandle)).toMatchObject({
605
+ ok: true,
606
+ value: replacementPayload,
607
+ });
608
+
609
+ expect(world.removeComponent(entity, Material).ok).toBe(true);
610
+ expect(world.sharedRefs.release(replacementHandle).ok).toBe(true);
611
+ expect(world.sharedRefs.release(sibling).ok).toBe(true);
612
+ expect(world.sharedRefs.refcount(oldHandle)).toBe(0);
613
+ expect(world.sharedRefs.refcount(replacementHandle)).toBe(0);
614
+ expect(world.sharedRefs.refcount(sibling)).toBe(0);
615
+ expect(world.sharedRefs._liveCount()).toBe(0);
616
+ });
617
+
618
+ it('AC-07: retire-on-255 — gen pushed past MAX_GEN then slot not in freeSlots after release', () => {
619
+ const store = new SharedRefStore();
620
+ const h = store.alloc('MeshAsset', { mesh: 'cube' });
621
+ const slot = handleSlot(h);
622
+
623
+ // gen=255 is still a usable handle under the new gen > MAX_GEN predicate.
624
+ // Bump _generations[slot] to 255 and push to freeSlots so the next alloc
625
+ // reuses it with gen=255.
626
+ // biome-ignore lint/suspicious/noExplicitAny: private mutation for retire edge boundary
627
+ (store as any)._generations[slot] = 255;
628
+ // biome-ignore lint/suspicious/noExplicitAny: push slot to free list so alloc reuses
629
+ (store as any).freeSlots.push(slot);
630
+
631
+ // Alloc reuses slot, reads gen=255 from _generations.
632
+ const h2 = store.alloc('MeshAsset', { mesh: 'sphere' });
633
+ expect(handleGeneration(h2)).toBe(255);
634
+ // Verify gen=255 is a usable handle — resolve succeeds.
635
+ const rResolve = store.resolve(h2);
636
+ expect(rResolve.ok).toBe(true);
637
+
638
+ // Release h2: gen matches (255==255), proceed, gen++ to 256 (>MAX_GEN), retired.
639
+ expect(store.release(h2).ok).toBe(true);
640
+
641
+ // Verify slot is NOT in freeSlots (retired)
642
+ // biome-ignore lint/suspicious/noExplicitAny: private read
643
+ const freeSlots: number[] = (store as any).freeSlots;
644
+ expect(freeSlots.indexOf(slot)).toBe(-1);
645
+ });
646
+ });