@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,418 @@
1
+ // @forgeax/engine-ecs - SharedRefStore.
2
+ //
3
+ // Reference-counted store for AssetRegistry-style shared handles. Schema vocab
4
+ // `shared<T>` fields derive `Handle<T, 'shared'>` whose lifecycle is tracked
5
+ // by retain/release on the holder side - allocators (typically the asset
6
+ // registry) call `alloc` once and let consumers retain/release as the asset
7
+ // flows through ECS components and external systems. When rc transitions
8
+ // 1 -> 0 the store publishes release evidence. External owners dispose their
9
+ // payloads from their own lifecycle effects; ECS does not invoke callbacks.
10
+ //
11
+ // Companion to UniqueRefStore (1-holder-direct-release semantics). The two
12
+ // stores share storage shape and code patterns; only the lifecycle differs:
13
+ //
14
+ // UniqueRefStore: alloc -> store; release -> drop. No retain.
15
+ // SharedRefStore: alloc -> rc=1; retain -> rc++; release -> rc--; rc=0 -> drop.
16
+ //
17
+ // §contract - shared handles are refcounted AND carry a generation
18
+ // The handle u32 packs `(generation << 24) | slot` via the shared codec in
19
+ // `@forgeax/engine-types` (same SSOT codec as UniqueRefStore and ECS
20
+ // EntityHandle). `alloc` welds the slot's current generation; resolve /
21
+ // retain / release compare the handle generation against
22
+ // `_generations[slot]` BEFORE any refcount/payload work, so a handle whose
23
+ // slot was released and re-allocated returns `SharedRefStaleError`
24
+ // ('shared-ref-stale') instead of silently resolving the next payload.
25
+ // Generation advances on the rc=0 drop and retires the slot when gen would
26
+ // exceed MAX_GEN (gen 255 is still usable; retire at would-be 256, no wrap).
27
+ // AI users keep the rule "don't cache a handle
28
+ // past release" - the AssetRegistry mediates most shared handle lifecycles,
29
+ // but a stale handle now fails structurally rather than silently.
30
+ //
31
+ // §tier boundary (feat-20260614 M6 D-15)
32
+ // This store manages ONLY user-tier slots (`slot >= BUILTIN_BASE`). Builtin
33
+ // asset payloads in the builtin range are process-static and live in their
34
+ // authoring package; they
35
+ // are never reference-counted. `nextSlot` starts at `BUILTIN_BASE` so minted
36
+ // handles never collide with builtin slots, and alloc/retain/release/resolve
37
+ // fail-fast with `BuiltinSlotNotOwnedError` when handed a builtin slot.
38
+ //
39
+ // Storage shape:
40
+ // - `payloads: Map<number, unknown>` - key = handle u32 = slot index.
41
+ // - `refcounts: Map<number, number>` - key = handle u32; rc >= 1 while live;
42
+ // removed (not set to 0) on final release
43
+ // so resolve / retain can detect the
44
+ // released state via `payloads.has(raw)`.
45
+ // - `freeSlots: number[]` - LIFO stack of recyclable slot indices.
46
+ // - `nextSlot` - bump counter for the never-recycled tail
47
+ // (starts at BUILTIN_BASE; user tier only).
48
+ //
49
+ // 24 bits = 16_777_215 simultaneous live shared handles - same ceiling as
50
+ // UniqueRefStore + Entity, so the three managed-handle resources fail-fast
51
+ // at the same bound.
52
+ //
53
+ // Release path (D-1 codes):
54
+ // - resolve(h): err(SharedRefReleasedError) if payload absent.
55
+ // - markChanged(h): publish an in-place payload mutation to subscribers.
56
+ // - retain(h): err(SharedRefReleasedError) if payload absent.
57
+ // - release(h): err(SharedRefDoubleReleaseError, rc=0) on rc=0 input.
58
+ // - any(builtin slot): err(BuiltinSlotNotOwnedError) (D-15).
59
+ //
60
+ // Identity invariant (mirrors UniqueRefStore): resolve(h) returns the SAME
61
+ // payload object on every call until the final release. Archetype migration
62
+ // preserves this trivially - the column carries the u32 handle, never the
63
+ // payload reference.
64
+
65
+ import type { Handle } from '@forgeax/engine-types';
66
+ import {
67
+ BUILTIN_BASE,
68
+ err,
69
+ handleGeneration,
70
+ handleSlot,
71
+ isRetiredSlot,
72
+ MAX_SLOT,
73
+ ok,
74
+ pack,
75
+ type Result,
76
+ toShared,
77
+ unwrapHandle,
78
+ } from '@forgeax/engine-types';
79
+ import {
80
+ BuiltinSlotNotOwnedError,
81
+ SharedRefDoubleReleaseError,
82
+ SharedRefPayloadInvalidError,
83
+ SharedRefReleasedError,
84
+ SharedRefStaleError,
85
+ } from './errors';
86
+
87
+ const SHARED_REF_MUTATION_JOURNAL_CAPACITY = 4096;
88
+
89
+ export interface SharedRefMutation {
90
+ readonly epoch: number;
91
+ readonly handle: number;
92
+ }
93
+
94
+ export interface SharedRefReleaseEvidence {
95
+ readonly payload: unknown;
96
+ readonly refcount: 0;
97
+ readonly generation: number;
98
+ readonly evidence: 'released';
99
+ }
100
+
101
+ export type SharedRefMutationRead =
102
+ | {
103
+ readonly status: 'ok';
104
+ readonly cursor: number;
105
+ readonly records: readonly SharedRefMutation[];
106
+ }
107
+ | {
108
+ readonly status: 'overflow';
109
+ readonly cursor: number;
110
+ readonly oldestAvailable: number;
111
+ };
112
+
113
+ // MAX_SLOT is now imported from @forgeax/engine-types (codec SSOT, D-1).
114
+ // The local constant is removed to avoid drift (AC-15).
115
+
116
+ /**
117
+ * Reference-counted store for ECS-aware `Handle<T, 'shared'>` lifecycles.
118
+ *
119
+ * The producer (typically AssetRegistry) calls `alloc` once and owns the
120
+ * "alloc-grant" rc=1; consumers (ECS schema fields, external systems) call
121
+ * `retain` on each new holder and `release` when the holder drops. When rc
122
+ * transitions 1 -> 0 the store publishes one structured release-evidence
123
+ * record; it does not invoke a user callback or own payload disposal.
124
+ *
125
+ * D-15: manages ONLY user-tier slots (`slot >= BUILTIN_BASE`). Builtin slots
126
+ * (`< BUILTIN_BASE`) fail-fast with `BuiltinSlotNotOwnedError`.
127
+ *
128
+ * Public API:
129
+ * - alloc(target, payload) -> Handle<T, 'shared'> (rc=1)
130
+ * - intern(target, payload) -> stable producer handle per target + object identity
131
+ * - resolve(handle) -> Result<T, SharedRefReleasedError | SharedRefStaleError | BuiltinSlotNotOwnedError>
132
+ * - markChanged(handle) -> Result<void, SharedRefReleasedError | SharedRefStaleError | BuiltinSlotNotOwnedError>
133
+ * - getMutationEpoch() -> monotonic payload-mutation cursor
134
+ * - retain(handle) -> Result<void, SharedRefReleasedError | SharedRefStaleError | BuiltinSlotNotOwnedError>
135
+ * - release(handle) -> Result<release evidence | undefined, ...>
136
+ * - refcount(handle) -> number (0 == released; debug + tests)
137
+ * - _liveCount() -> live slot count (debug + inspector)
138
+ */
139
+ export class SharedRefStore {
140
+ private readonly payloads = new Map<number, unknown>();
141
+ private readonly refcounts = new Map<number, number>();
142
+ private readonly freeSlots: number[] = [];
143
+ private readonly internedByTarget = new Map<string, WeakMap<object, number>>();
144
+ private readonly internedKeys = new Map<
145
+ number,
146
+ { readonly target: string; readonly payload: object }
147
+ >();
148
+ private nextSlot = BUILTIN_BASE;
149
+ private mutationEpoch = 0;
150
+ private readonly mutationJournal: SharedRefMutation[] = [];
151
+ private readonly releaseJournal: SharedRefReleaseEvidence[] = [];
152
+
153
+ /**
154
+ * Generation table indexed by slot (D-6). Each entry tracks the current
155
+ * generation for the slot — written to during alloc (welded into the
156
+ * returned handle via pack) and incremented on release (M4).
157
+ *
158
+ * @internal
159
+ */
160
+ // biome-ignore lint/style/useNamingConvention: internal field — @internal JSDoc suppresses lint:internal gate
161
+ private readonly _generations: number[] = [];
162
+
163
+ /**
164
+ * Allocate a fresh shared handle for `payload`, branded against `target`.
165
+ * Refcount starts at 1 (the alloc-grant). Nullish payloads are rejected
166
+ * before a slot or free-list is touched.
167
+ *
168
+ * The returned handle carries a generation tag welded via codec.pack
169
+ * (D-8, OOS-2): first allocation gen=0 (AC-06), reused slot gen = the
170
+ * current generation from _generations[slot]. The toShared brand cast
171
+ * happens internally — external callers no longer construct Handle<...>
172
+ * directly.
173
+ *
174
+ * Minted slots are user-tier (`>= BUILTIN_BASE`); builtin slots are never
175
+ * produced here (D-15).
176
+ */
177
+ alloc<Target extends string, T = unknown>(target: Target, payload: T): Handle<Target, 'shared'> {
178
+ void target; // target is a phantom - tag flows only at the type level via Handle<Target,_>.
179
+ if (payload === null || payload === undefined) {
180
+ throw new SharedRefPayloadInvalidError(target, payload === null ? 'null' : 'undefined');
181
+ }
182
+ const slot = this.freeSlots.pop() ?? this.nextSlot++;
183
+ if (slot > MAX_SLOT) {
184
+ throw new RangeError(
185
+ `SharedRefStore: slot index ${slot} exceeds 24-bit max (${MAX_SLOT}). ` +
186
+ 'Reduce simultaneous shared handles or investigate handle leaks.',
187
+ );
188
+ }
189
+ const gen = this._generations[slot] ?? 0;
190
+ const raw = pack(slot, gen);
191
+ this.payloads.set(raw, payload);
192
+ this.refcounts.set(raw, 1);
193
+ return toShared(raw);
194
+ }
195
+
196
+ /**
197
+ * Return the idempotent producer handle for one object payload in this
198
+ * store. Identity includes `target`, so the same object branded for two
199
+ * asset kinds does not alias.
200
+ *
201
+ * A cache hit deliberately does not retain: this is one producer grant,
202
+ * discovered repeatedly by an asset catalogue. Actual holders retain and
203
+ * release through the ECS write barrier. Callers needing independent
204
+ * grants or a per-handle deleter must use {@link alloc}.
205
+ */
206
+ intern<Target extends string, T extends object>(
207
+ target: Target,
208
+ payload: T,
209
+ ): Handle<Target, 'shared'> {
210
+ let byPayload = this.internedByTarget.get(target);
211
+ if (byPayload === undefined) {
212
+ byPayload = new WeakMap<object, number>();
213
+ this.internedByTarget.set(target, byPayload);
214
+ }
215
+
216
+ const existingRaw = byPayload.get(payload);
217
+ if (existingRaw !== undefined && this.payloads.has(existingRaw)) {
218
+ return toShared(existingRaw);
219
+ }
220
+
221
+ const handle = this.alloc(target, payload);
222
+ const raw = unwrapHandle(handle);
223
+ byPayload.set(payload, raw);
224
+ this.internedKeys.set(raw, { target, payload });
225
+ return handle;
226
+ }
227
+
228
+ /**
229
+ * Look up `payload` by handle. Returns `err(shared-ref-released)` when
230
+ * the handle's slot has no live payload (rc reached 0, no re-alloc has
231
+ * filled the slot); `err(builtin-slot-not-owned)` for a builtin slot.
232
+ *
233
+ * §contract - mirrors UniqueRefStore: a stale handle whose slot has been
234
+ * released and re-allocated returns `err(shared-ref-stale)` - the handle's
235
+ * welded generation no longer matches `_generations[slot]`. The gen check
236
+ * runs before payload lookup, so stale-by-reuse is caught deterministically.
237
+ */
238
+ resolve<Target extends string, T = unknown>(
239
+ handle: Handle<Target, 'shared'>,
240
+ ): Result<T, SharedRefReleasedError | SharedRefStaleError | BuiltinSlotNotOwnedError> {
241
+ const raw = unwrapHandle(handle);
242
+ if (raw < BUILTIN_BASE) return err(new BuiltinSlotNotOwnedError(raw));
243
+ // Gen comparison (M4): extract slot + handle gen, compare against
244
+ // store's current gen. Mismatch means slot was released and re-allocated —
245
+ // the caller's handle is stale. This check runs BEFORE payload lookup so
246
+ // stale-by-reuse is always caught (AC-01).
247
+ const slot = handleSlot(handle);
248
+ const handleGen = handleGeneration(handle);
249
+ const storeGen = this._generations[slot] ?? 0;
250
+ if (handleGen !== storeGen) {
251
+ return err(new SharedRefStaleError(slot, handleGen, storeGen));
252
+ }
253
+ const payload = this.payloads.get(raw);
254
+ if (payload === undefined) {
255
+ return err(new SharedRefReleasedError(raw, '<unknown>'));
256
+ }
257
+ return ok(payload as T);
258
+ }
259
+
260
+ /**
261
+ * Publish that a live payload was mutated in place. Consumers that retain
262
+ * projections of shared payload data compare the monotonic epoch and
263
+ * explicitly refresh instead of rescanning every payload each frame.
264
+ */
265
+ markChanged<Target extends string>(
266
+ handle: Handle<Target, 'shared'>,
267
+ ): Result<void, SharedRefReleasedError | SharedRefStaleError | BuiltinSlotNotOwnedError> {
268
+ const resolved = this.resolve(handle);
269
+ if (!resolved.ok) return resolved;
270
+ if (this.mutationEpoch >= Number.MAX_SAFE_INTEGER) {
271
+ throw new RangeError('SharedRefStore mutation epoch exhausted');
272
+ }
273
+ this.mutationEpoch += 1;
274
+ this.mutationJournal.push({ epoch: this.mutationEpoch, handle: unwrapHandle(handle) });
275
+ if (this.mutationJournal.length > SHARED_REF_MUTATION_JOURNAL_CAPACITY) {
276
+ this.mutationJournal.shift();
277
+ }
278
+ return ok(undefined);
279
+ }
280
+
281
+ /** Current upper bound for explicitly published payload mutations. */
282
+ getMutationEpoch(): number {
283
+ return this.mutationEpoch;
284
+ }
285
+
286
+ /** Read exact changed handles after `cursor`; overflow requires consumer resync. */
287
+ readChangesSince(cursor: number): SharedRefMutationRead {
288
+ const oldestAvailable = this.mutationJournal[0]?.epoch ?? this.mutationEpoch + 1;
289
+ if (cursor < oldestAvailable - 1) {
290
+ return { status: 'overflow', cursor: this.mutationEpoch, oldestAvailable };
291
+ }
292
+ return {
293
+ status: 'ok',
294
+ cursor: this.mutationEpoch,
295
+ records: this.mutationJournal.filter((record) => record.epoch > cursor),
296
+ };
297
+ }
298
+
299
+ /**
300
+ * Increment the refcount of a live shared handle. Returns
301
+ * `err(shared-ref-released)` when the handle is not live - retain MUST
302
+ * NOT resurrect a released slot (charter P3 explicit failure; would
303
+ * defeat the rc=0 -> drop invariant); `err(builtin-slot-not-owned)` for a
304
+ * builtin slot.
305
+ */
306
+ retain<Target extends string>(
307
+ handle: Handle<Target, 'shared'>,
308
+ ): Result<void, SharedRefReleasedError | SharedRefStaleError | BuiltinSlotNotOwnedError> {
309
+ const raw = unwrapHandle(handle);
310
+ if (raw < BUILTIN_BASE) return err(new BuiltinSlotNotOwnedError(raw));
311
+ // Gen comparison runs before rc read (q12: all three operations compare gen first).
312
+ const slot = handleSlot(handle);
313
+ const handleGen = handleGeneration(handle);
314
+ const storeGen = this._generations[slot] ?? 0;
315
+ if (handleGen !== storeGen) {
316
+ return err(new SharedRefStaleError(slot, handleGen, storeGen));
317
+ }
318
+ const rc = this.refcounts.get(raw);
319
+ if (rc === undefined) {
320
+ return err(new SharedRefReleasedError(raw, '<unknown>'));
321
+ }
322
+ this.refcounts.set(raw, rc + 1);
323
+ return ok(undefined);
324
+ }
325
+
326
+ /**
327
+ * Decrement the refcount. When rc transitions 1 -> 0, the slot is dropped
328
+ * and one structured release-evidence record is published. The evidence
329
+ * captures the payload, zero refcount, next generation and `released` marker
330
+ * after the store has removed the live slot.
331
+ *
332
+ * Returns `err(shared-ref-double-release, rc=0)` when the handle has
333
+ * already reached rc=0 (or was never live); `err(builtin-slot-not-owned)`
334
+ * for a builtin slot. AI users branch on `.code` and route the
335
+ * second-release log to Layer 3 ErrorHandler without aborting the despawn
336
+ * chain.
337
+ */
338
+ release<Target extends string>(
339
+ handle: Handle<Target, 'shared'>,
340
+ ): Result<
341
+ SharedRefReleaseEvidence | undefined,
342
+ SharedRefDoubleReleaseError | SharedRefStaleError | BuiltinSlotNotOwnedError
343
+ > {
344
+ const raw = unwrapHandle(handle);
345
+ if (raw < BUILTIN_BASE) return err(new BuiltinSlotNotOwnedError(raw));
346
+ // Gen comparison runs FIRST — before rc read, before any mutation (q12).
347
+ const slot = handleSlot(handle);
348
+ const handleGen = handleGeneration(handle);
349
+ const storeGen = this._generations[slot] ?? 0;
350
+ if (handleGen !== storeGen) {
351
+ // AC-03: stale release MUST NOT touch rc / payload / freeSlots.
352
+ return err(new SharedRefStaleError(slot, handleGen, storeGen));
353
+ }
354
+ const rc = this.refcounts.get(raw);
355
+ if (rc === undefined) {
356
+ return err(new SharedRefDoubleReleaseError(raw, '<unknown>', 0));
357
+ }
358
+ if (rc > 1) {
359
+ this.refcounts.set(raw, rc - 1);
360
+ return ok(undefined);
361
+ }
362
+ // rc === 1 -> drop. Capture the payload for release evidence before
363
+ // clearing the live slot; nullish payloads never enter the store.
364
+ const payload = this.payloads.get(raw);
365
+ const internedKey = this.internedKeys.get(raw);
366
+ if (internedKey !== undefined) {
367
+ const byPayload = this.internedByTarget.get(internedKey.target);
368
+ if (byPayload?.get(internedKey.payload) === raw) {
369
+ byPayload.delete(internedKey.payload);
370
+ }
371
+ this.internedKeys.delete(raw);
372
+ }
373
+ this.refcounts.delete(raw);
374
+ this.payloads.delete(raw);
375
+ // Gen increment + retire (AC-07): bump gen; once it would exceed MAX_GEN
376
+ // (gen 255 is still usable; the bump to 256 triggers retire) the slot is
377
+ // permanently retired — NOT pushed to freeSlots. This prevents handle
378
+ // aliasing. Shares the isRetiredSlot SSOT predicate with EntityHandle.
379
+ const generation = storeGen + 1;
380
+ this._generations[slot] = generation;
381
+ if (!isRetiredSlot(generation)) {
382
+ this.freeSlots.push(slot);
383
+ }
384
+ // else: slot retired (gen exceeded MAX_GEN) - never returns to freeSlots.
385
+ const evidence = Object.freeze({
386
+ payload,
387
+ refcount: 0 as const,
388
+ generation,
389
+ evidence: 'released' as const,
390
+ });
391
+ this.releaseJournal.push(evidence);
392
+ if (this.releaseJournal.length > SHARED_REF_MUTATION_JOURNAL_CAPACITY) {
393
+ this.releaseJournal.shift();
394
+ }
395
+ return ok(evidence);
396
+ }
397
+
398
+ /** Read the bounded release evidence owned by this store. */
399
+ readReleaseEvidence(): readonly SharedRefReleaseEvidence[] {
400
+ return this.releaseJournal;
401
+ }
402
+
403
+ /**
404
+ * Return the current refcount for `handle`. Returns 0 for a released
405
+ * (or never-allocated) slot. Primarily a debug + tests entry point;
406
+ * production code rarely reads rc directly (the rc=0 -> drop invariant
407
+ * is the surface AI users consume via release / the per-handle deleter).
408
+ */
409
+ refcount<Target extends string>(handle: Handle<Target, 'shared'>): number {
410
+ const raw = unwrapHandle(handle);
411
+ return this.refcounts.get(raw) ?? 0;
412
+ }
413
+
414
+ /** @internal Diagnostic count of live slots. Exposed for tests + inspector. */
415
+ _liveCount(): number {
416
+ return this.payloads.size;
417
+ }
418
+ }
package/src/shared.ts ADDED
@@ -0,0 +1,23 @@
1
+ export type {
2
+ KernelDispatchFailure,
3
+ KernelDispatchResult,
4
+ KernelDispatchSpan,
5
+ SharedFieldView,
6
+ SharedKernelDefinition,
7
+ SharedKernelDispatch,
8
+ SharedKernelEligibilityReason,
9
+ SharedKernelExecutor,
10
+ SharedKernelHandle,
11
+ SharedSpanBinding,
12
+ WorldExecutionHealth,
13
+ } from './execution/shared-kernel';
14
+ export {
15
+ bindSharedSpan,
16
+ defineSharedKernel,
17
+ isKernelDispatchFailure,
18
+ isSharedSpan,
19
+ SHARED_KERNEL_ELIGIBILITY_REASONS,
20
+ SHARED_KERNEL_EXECUTOR_RESOURCE_KEY,
21
+ sharedKernelEligibility,
22
+ splitSharedSpan,
23
+ } from './execution/shared-kernel';
@@ -0,0 +1,166 @@
1
+ // @forgeax/engine-ecs — ArchetypeGraph: manages archetype creation and edge caching.
2
+ //
3
+ // O(1) archetype migration lookup via cached edges (D-02 / Finding 2).
4
+ // Global `generation` counter incremented on each new archetype creation
5
+ // (ArchetypeGeneration for query cache incremental update).
6
+
7
+ import type { Component, ComponentId } from '../component';
8
+ import * as componentOwner from '../component';
9
+ import { type Archetype, type ArchetypeId, archetypeKey, createArchetype } from './archetype';
10
+ import { createSparseTagSet, type SparseTagSet } from './change-detection';
11
+ import { canonicalComponents, createTable, type Table, type TableId, tableKey } from './table';
12
+
13
+ /**
14
+ * The ArchetypeGraph owns all archetypes and their edge caches.
15
+ *
16
+ * `archetypes` is the SSOT: a dense array indexed by `ArchetypeId` (the id is
17
+ * allocated as the next array slot, so `archetypes[id].id === id`).
18
+ * `dedupByKey` is a derived index `key → ArchetypeId` used only at archetype
19
+ * creation to dedupe; consumers always read through `archetypes[id]`
20
+ * (architecture-principles.md §1 SSOT, §2 Derive — replaces the historical
21
+ * `byKey: Map<string, Archetype>` / `byId: Archetype[]` pair which carried two
22
+ * full Archetype refs per row).
23
+ */
24
+ export interface ArchetypeGraph {
25
+ /** SSOT: dense array indexed by `ArchetypeId` (id == array slot). */
26
+ archetypes: Archetype[];
27
+ /** Derived dedup index: canonical-key string → ArchetypeId. */
28
+ dedupByKey: Map<string, ArchetypeId>;
29
+ /** Global generation counter. Incremented on each new archetype. */
30
+ generation: number;
31
+ tables: Table[];
32
+ tableDedupByKey: Map<string, TableId>;
33
+ tableGeneration: number;
34
+ sparseTags: Map<ComponentId, SparseTagSet>;
35
+ readonly shared: boolean;
36
+ }
37
+
38
+ /**
39
+ * Create a fresh ArchetypeGraph.
40
+ */
41
+ export function createArchetypeGraph(shared = false): ArchetypeGraph {
42
+ return {
43
+ archetypes: [],
44
+ dedupByKey: new Map(),
45
+ generation: 0,
46
+ tables: [],
47
+ tableDedupByKey: new Map(),
48
+ tableGeneration: 0,
49
+ sparseTags: new Map(),
50
+ shared,
51
+ };
52
+ }
53
+
54
+ export function getTable(graph: ArchetypeGraph, id: TableId): Table {
55
+ const table = graph.tables[id];
56
+ if (table === undefined) throw new Error(`Table ${id} does not exist.`);
57
+ return table;
58
+ }
59
+
60
+ export function getOrCreateTable(
61
+ graph: ArchetypeGraph,
62
+ components: ReadonlyArray<Component>,
63
+ ): Table {
64
+ const tableComponents = canonicalComponents(
65
+ components.filter((component) => component.storage === 'table'),
66
+ );
67
+ const key = tableKey(tableComponents.map((component) => componentOwner.componentId(component)));
68
+ const existingId = graph.tableDedupByKey.get(key);
69
+ if (existingId !== undefined) return getTable(graph, existingId);
70
+ const table = createTable(tableComponents, graph.tables.length, graph.shared);
71
+ graph.tables.push(table);
72
+ graph.tableDedupByKey.set(key, table.id);
73
+ graph.tableGeneration += 1;
74
+ return table;
75
+ }
76
+
77
+ export function getOrCreateSparseTagSet(graph: ArchetypeGraph, component: Component): SparseTagSet {
78
+ const current = graph.sparseTags.get(componentOwner.componentId(component));
79
+ if (current !== undefined) return current;
80
+ const set = createSparseTagSet(component);
81
+ graph.sparseTags.set(componentOwner.componentId(component), set);
82
+ return set;
83
+ }
84
+
85
+ /**
86
+ * Get or create an archetype for the given component set.
87
+ * ComponentIds are sorted to produce a canonical key.
88
+ */
89
+ export function getOrCreateArchetype(
90
+ graph: ArchetypeGraph,
91
+ componentIds: ReadonlyArray<ComponentId>,
92
+ components: ReadonlyArray<Component>,
93
+ ): Archetype {
94
+ const key = archetypeKey(componentIds);
95
+ const existingId = graph.dedupByKey.get(key);
96
+ if (existingId !== undefined) {
97
+ // biome-ignore lint/style/noNonNullAssertion: dedupByKey only ever holds ids that were pushed into archetypes[].
98
+ return graph.archetypes[existingId]!;
99
+ }
100
+
101
+ const fullComponents = canonicalComponents(components);
102
+ const table = getOrCreateTable(graph, fullComponents);
103
+ const archId = graph.archetypes.length;
104
+ const arch = createArchetype(fullComponents, archId, table.id);
105
+ graph.archetypes.push(arch);
106
+ graph.dedupByKey.set(key, archId);
107
+ graph.generation += 1;
108
+ return arch;
109
+ }
110
+
111
+ /**
112
+ * Get the target archetype after adding `componentId` to `src`.
113
+ * Caches the edge for O(1) subsequent lookups.
114
+ */
115
+ export function getAddEdge(
116
+ graph: ArchetypeGraph,
117
+ src: Archetype,
118
+ componentId: ComponentId,
119
+ component: Component,
120
+ ): Archetype {
121
+ const cached = src.addEdges.get(componentId);
122
+ if (cached !== undefined) {
123
+ const target = graph.archetypes[cached];
124
+ if (target) {
125
+ return target;
126
+ }
127
+ }
128
+
129
+ // Compute target archetype: src components + new component.
130
+ const newIds = [...src.components.map((c) => componentOwner.componentId(c)), componentId];
131
+ const newComponents = [...src.components, component];
132
+ const target = getOrCreateArchetype(graph, newIds, newComponents);
133
+ src.addEdges.set(componentId, target.id);
134
+ return target;
135
+ }
136
+
137
+ /**
138
+ * Get the target archetype after removing `componentId` from `src`.
139
+ * Caches the edge for O(1) subsequent lookups.
140
+ *
141
+ * Component list is derived from `src.components.filter(c => componentOwner.componentId(c) !== componentId)`
142
+ * (no separate componentRegistry — feat-20260611 AC-09).
143
+ */
144
+ export function getRemoveEdge(
145
+ graph: ArchetypeGraph,
146
+ src: Archetype,
147
+ componentId: ComponentId,
148
+ ): Archetype {
149
+ const cached = src.removeEdges.get(componentId);
150
+ if (cached !== undefined) {
151
+ const target = graph.archetypes[cached];
152
+ if (target) {
153
+ return target;
154
+ }
155
+ }
156
+
157
+ // Compute target archetype: src components minus removed component.
158
+ // Derive componentIds from src.components (single-field Archetype).
159
+ const newIds = src.components
160
+ .map((c) => componentOwner.componentId(c))
161
+ .filter((id) => id !== componentId);
162
+ const newComponents = src.components.filter((c) => componentOwner.componentId(c) !== componentId);
163
+ const target = getOrCreateArchetype(graph, newIds, newComponents);
164
+ src.removeEdges.set(componentId, target.id);
165
+ return target;
166
+ }
@@ -0,0 +1,78 @@
1
+ import type { Component, ComponentId } from '../component';
2
+ import { componentId } from '../component';
3
+ import { foldEssentials } from '../entity';
4
+ import type { TableId } from './table';
5
+
6
+ const INITIAL_CAPACITY = 64;
7
+
8
+ export type ArchetypeId = number;
9
+
10
+ export interface Archetype {
11
+ readonly id: ArchetypeId;
12
+ readonly key: string;
13
+ readonly components: ReadonlyArray<Component>;
14
+ readonly tableId: TableId;
15
+ rows: Uint32Array;
16
+ size: number;
17
+ capacity: number;
18
+ addEdges: Map<ComponentId, ArchetypeId>;
19
+ removeEdges: Map<ComponentId, ArchetypeId>;
20
+ }
21
+
22
+ export function archetypeKey(componentIds: ReadonlyArray<ComponentId>): string {
23
+ return [...foldEssentials(componentIds)].sort((a, b) => a - b).join('+');
24
+ }
25
+
26
+ export function createArchetype(
27
+ components: ReadonlyArray<Component>,
28
+ id: ArchetypeId,
29
+ tableId: TableId,
30
+ ): Archetype {
31
+ const byId = new Map<ComponentId, Component>();
32
+ for (const component of components) byId.set(componentId(component), component);
33
+ const sorted = [...byId.values()].sort((a, b) => componentId(a) - componentId(b));
34
+ return {
35
+ id,
36
+ key: archetypeKey(sorted.map((component) => componentId(component))),
37
+ components: sorted,
38
+ tableId,
39
+ rows: new Uint32Array(INITIAL_CAPACITY),
40
+ size: 0,
41
+ capacity: INITIAL_CAPACITY,
42
+ addEdges: new Map(),
43
+ removeEdges: new Map(),
44
+ };
45
+ }
46
+
47
+ export function appendArchetypeRow(archetype: Archetype, tableRow: number): number {
48
+ if (archetype.size === archetype.capacity) growArchetype(archetype, archetype.capacity * 2);
49
+ const row = archetype.size;
50
+ archetype.rows[row] = tableRow;
51
+ archetype.size = row + 1;
52
+ return row;
53
+ }
54
+
55
+ export function removeArchetypeRow(
56
+ archetype: Archetype,
57
+ row: number,
58
+ ): { movedTableRow: number; newRow: number } | null {
59
+ const lastRow = archetype.size - 1;
60
+ if (row === lastRow) {
61
+ archetype.size = lastRow;
62
+ return null;
63
+ }
64
+ const movedTableRow = archetype.rows[lastRow] ?? 0;
65
+ archetype.rows[row] = movedTableRow;
66
+ archetype.size = lastRow;
67
+ return { movedTableRow, newRow: row };
68
+ }
69
+
70
+ export function growArchetype(archetype: Archetype, targetCapacity: number): void {
71
+ let capacity = archetype.capacity;
72
+ while (capacity < targetCapacity) capacity *= 2;
73
+ if (capacity === archetype.capacity) return;
74
+ const rows = new Uint32Array(capacity);
75
+ rows.set(archetype.rows);
76
+ archetype.rows = rows;
77
+ archetype.capacity = capacity;
78
+ }