@forgeax/engine-ecs 0.1.2

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 (374) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +272 -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-schema.test-d.d.ts +2 -0
  29. package/dist/__tests__/component-schema.test-d.d.ts.map +1 -0
  30. package/dist/__tests__/component-schema.test.d.ts +2 -0
  31. package/dist/__tests__/component-schema.test.d.ts.map +1 -0
  32. package/dist/__tests__/define-component-options.test-d.d.ts +2 -0
  33. package/dist/__tests__/define-component-options.test-d.d.ts.map +1 -0
  34. package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts +2 -0
  35. package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/despawn-all.unit.test.d.ts +2 -0
  37. package/dist/__tests__/despawn-all.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts +2 -0
  39. package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts.map +1 -0
  40. package/dist/__tests__/ecs-core-reduction.perf.test.d.ts +2 -0
  41. package/dist/__tests__/ecs-core-reduction.perf.test.d.ts.map +1 -0
  42. package/dist/__tests__/ecs-error-code-31.test-d.d.ts +2 -0
  43. package/dist/__tests__/ecs-error-code-31.test-d.d.ts.map +1 -0
  44. package/dist/__tests__/entity-disabling.unit.test.d.ts +2 -0
  45. package/dist/__tests__/entity-disabling.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts +2 -0
  47. package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts.map +1 -0
  48. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  49. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts +2 -0
  51. package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/execution-shared-storage.unit.test.d.ts +2 -0
  53. package/dist/__tests__/execution-shared-storage.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/externalization-portable-value.unit.test.d.ts +2 -0
  55. package/dist/__tests__/externalization-portable-value.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/externalization-projection.unit.test.d.ts +2 -0
  57. package/dist/__tests__/externalization-projection.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/externalization-root-surface.unit.test.d.ts +2 -0
  59. package/dist/__tests__/externalization-root-surface.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/field-enum-labels-reflection.test.d.ts +2 -0
  61. package/dist/__tests__/field-enum-labels-reflection.test.d.ts.map +1 -0
  62. package/dist/__tests__/field-shape-fixture.test.d.ts +2 -0
  63. package/dist/__tests__/field-shape-fixture.test.d.ts.map +1 -0
  64. package/dist/__tests__/field-transient-reflection.test.d.ts +2 -0
  65. package/dist/__tests__/field-transient-reflection.test.d.ts.map +1 -0
  66. package/dist/__tests__/field-value-type.test-d.d.ts +2 -0
  67. package/dist/__tests__/field-value-type.test-d.d.ts.map +1 -0
  68. package/dist/__tests__/fixed-update.unit.test.d.ts +2 -0
  69. package/dist/__tests__/fixed-update.unit.test.d.ts.map +1 -0
  70. package/dist/__tests__/handle.test-d.d.ts +2 -0
  71. package/dist/__tests__/handle.test-d.d.ts.map +1 -0
  72. package/dist/__tests__/hierarchy.unit.test.d.ts +2 -0
  73. package/dist/__tests__/hierarchy.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/managed-array-vocab.test-d.d.ts +2 -0
  75. package/dist/__tests__/managed-array-vocab.test-d.d.ts.map +1 -0
  76. package/dist/__tests__/minimal-example.test-d.d.ts +2 -0
  77. package/dist/__tests__/minimal-example.test-d.d.ts.map +1 -0
  78. package/dist/__tests__/name-read-types.test-d.d.ts +2 -0
  79. package/dist/__tests__/name-read-types.test-d.d.ts.map +1 -0
  80. package/dist/__tests__/plugin-service.test.d.ts +2 -0
  81. package/dist/__tests__/plugin-service.test.d.ts.map +1 -0
  82. package/dist/__tests__/query-combinations.unit.test.d.ts +2 -0
  83. package/dist/__tests__/query-combinations.unit.test.d.ts.map +1 -0
  84. package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts +2 -0
  85. package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts.map +1 -0
  86. package/dist/__tests__/query-storage-trends.perf.test.d.ts +2 -0
  87. package/dist/__tests__/query-storage-trends.perf.test.d.ts.map +1 -0
  88. package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts +2 -0
  89. package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts.map +1 -0
  90. package/dist/__tests__/query.unit.test.d.ts +2 -0
  91. package/dist/__tests__/query.unit.test.d.ts.map +1 -0
  92. package/dist/__tests__/relationship-index.perf.test.d.ts +2 -0
  93. package/dist/__tests__/relationship-index.perf.test.d.ts.map +1 -0
  94. package/dist/__tests__/relationship-index.test.d.ts +2 -0
  95. package/dist/__tests__/relationship-index.test.d.ts.map +1 -0
  96. package/dist/__tests__/scalar-field-type-owner.test-d.d.ts +2 -0
  97. package/dist/__tests__/scalar-field-type-owner.test-d.d.ts.map +1 -0
  98. package/dist/__tests__/schedule-determinism.unit.test.d.ts +2 -0
  99. package/dist/__tests__/schedule-determinism.unit.test.d.ts.map +1 -0
  100. package/dist/__tests__/schedule-inspection.unit.test.d.ts +2 -0
  101. package/dist/__tests__/schedule-inspection.unit.test.d.ts.map +1 -0
  102. package/dist/__tests__/schedule-scope-error.unit.test.d.ts +2 -0
  103. package/dist/__tests__/schedule-scope-error.unit.test.d.ts.map +1 -0
  104. package/dist/__tests__/schedule-token.unit.test.d.ts +2 -0
  105. package/dist/__tests__/schedule-token.unit.test.d.ts.map +1 -0
  106. package/dist/__tests__/schema-vocab.test-d.d.ts +2 -0
  107. package/dist/__tests__/schema-vocab.test-d.d.ts.map +1 -0
  108. package/dist/__tests__/serialization.unit.test.d.ts +2 -0
  109. package/dist/__tests__/serialization.unit.test.d.ts.map +1 -0
  110. package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts +2 -0
  111. package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts.map +1 -0
  112. package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts +2 -0
  113. package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts.map +1 -0
  114. package/dist/__tests__/shared-ref-store.unit.test.d.ts +2 -0
  115. package/dist/__tests__/shared-ref-store.unit.test.d.ts.map +1 -0
  116. package/dist/__tests__/sparse-tag.unit.test.d.ts +2 -0
  117. package/dist/__tests__/sparse-tag.unit.test.d.ts.map +1 -0
  118. package/dist/__tests__/spawn-field-error.test-d.d.ts +2 -0
  119. package/dist/__tests__/spawn-field-error.test-d.d.ts.map +1 -0
  120. package/dist/__tests__/stale-error.test-d.d.ts +2 -0
  121. package/dist/__tests__/stale-error.test-d.d.ts.map +1 -0
  122. package/dist/__tests__/string-view-symmetry.test-d.d.ts +2 -0
  123. package/dist/__tests__/string-view-symmetry.test-d.d.ts.map +1 -0
  124. package/dist/__tests__/string-vocab.test-d.d.ts +2 -0
  125. package/dist/__tests__/string-vocab.test-d.d.ts.map +1 -0
  126. package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts +2 -0
  127. package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts.map +1 -0
  128. package/dist/__tests__/structure-epoch.unit.test.d.ts +2 -0
  129. package/dist/__tests__/structure-epoch.unit.test.d.ts.map +1 -0
  130. package/dist/__tests__/table-archetype-mapping.unit.test.d.ts +2 -0
  131. package/dist/__tests__/table-archetype-mapping.unit.test.d.ts.map +1 -0
  132. package/dist/__tests__/time.unit.test.d.ts +2 -0
  133. package/dist/__tests__/time.unit.test.d.ts.map +1 -0
  134. package/dist/__tests__/types.test-d.d.ts +2 -0
  135. package/dist/__tests__/types.test-d.d.ts.map +1 -0
  136. package/dist/__tests__/unique-ref-store.unit.test.d.ts +2 -0
  137. package/dist/__tests__/unique-ref-store.unit.test.d.ts.map +1 -0
  138. package/dist/__tests__/utils/handle-numeric.d.ts +14 -0
  139. package/dist/__tests__/utils/handle-numeric.d.ts.map +1 -0
  140. package/dist/__tests__/validation-root-surface.unit.test.d.ts +2 -0
  141. package/dist/__tests__/validation-root-surface.unit.test.d.ts.map +1 -0
  142. package/dist/__tests__/visibility-atomic-write.unit.test.d.ts +2 -0
  143. package/dist/__tests__/visibility-atomic-write.unit.test.d.ts.map +1 -0
  144. package/dist/__tests__/world-change-journal.unit.test.d.ts +2 -0
  145. package/dist/__tests__/world-change-journal.unit.test.d.ts.map +1 -0
  146. package/dist/__tests__/world-characterization.unit.test.d.ts +2 -0
  147. package/dist/__tests__/world-characterization.unit.test.d.ts.map +1 -0
  148. package/dist/__tests__/world-consumer-inference.test-d.d.ts +2 -0
  149. package/dist/__tests__/world-consumer-inference.test-d.d.ts.map +1 -0
  150. package/dist/__tests__/world-health.contract.test.d.ts +2 -0
  151. package/dist/__tests__/world-health.contract.test.d.ts.map +1 -0
  152. package/dist/__tests__/world-inspection.test.d.ts +2 -0
  153. package/dist/__tests__/world-inspection.test.d.ts.map +1 -0
  154. package/dist/__tests__/world-internal-identity.unit.test.d.ts +2 -0
  155. package/dist/__tests__/world-internal-identity.unit.test.d.ts.map +1 -0
  156. package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts +2 -0
  157. package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts.map +1 -0
  158. package/dist/__tests__/world-scheduling.m4.test.d.ts +2 -0
  159. package/dist/__tests__/world-scheduling.m4.test.d.ts.map +1 -0
  160. package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts +2 -0
  161. package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts.map +1 -0
  162. package/dist/__tests__/world.test-d.d.ts +2 -0
  163. package/dist/__tests__/world.test-d.d.ts.map +1 -0
  164. package/dist/buffer-pool.d.ts +109 -0
  165. package/dist/buffer-pool.d.ts.map +1 -0
  166. package/dist/commands.d.ts +93 -0
  167. package/dist/commands.d.ts.map +1 -0
  168. package/dist/component-default-fallback.d.ts +88 -0
  169. package/dist/component-default-fallback.d.ts.map +1 -0
  170. package/dist/component-schema.d.ts +34 -0
  171. package/dist/component-schema.d.ts.map +1 -0
  172. package/dist/component-value-validate.d.ts +29 -0
  173. package/dist/component-value-validate.d.ts.map +1 -0
  174. package/dist/component.d.ts +581 -0
  175. package/dist/component.d.ts.map +1 -0
  176. package/dist/entity-handle.d.ts +68 -0
  177. package/dist/entity-handle.d.ts.map +1 -0
  178. package/dist/entity.d.ts +86 -0
  179. package/dist/entity.d.ts.map +1 -0
  180. package/dist/errors/query-and-component-errors.d.ts +132 -0
  181. package/dist/errors/query-and-component-errors.d.ts.map +1 -0
  182. package/dist/errors/relationship-errors.d.ts +112 -0
  183. package/dist/errors/relationship-errors.d.ts.map +1 -0
  184. package/dist/errors/sprite-and-shared-errors.d.ts +107 -0
  185. package/dist/errors/sprite-and-shared-errors.d.ts.map +1 -0
  186. package/dist/errors/validation-errors.d.ts +214 -0
  187. package/dist/errors/validation-errors.d.ts.map +1 -0
  188. package/dist/errors.d.ts +756 -0
  189. package/dist/errors.d.ts.map +1 -0
  190. package/dist/execution/shared-kernel.d.ts +106 -0
  191. package/dist/execution/shared-kernel.d.ts.map +1 -0
  192. package/dist/externalization/index.d.ts +31 -0
  193. package/dist/externalization/index.d.ts.map +1 -0
  194. package/dist/externalization/index.mjs +298 -0
  195. package/dist/externalization/index.mjs.map +1 -0
  196. package/dist/index.d.ts +261 -0
  197. package/dist/index.d.ts.map +1 -0
  198. package/dist/index.mjs +7354 -0
  199. package/dist/index.mjs.map +1 -0
  200. package/dist/internal.mjs +479 -0
  201. package/dist/internal.mjs.map +1 -0
  202. package/dist/plugin-service.d.ts +11 -0
  203. package/dist/plugin-service.d.ts.map +1 -0
  204. package/dist/projection/index.d.ts +50 -0
  205. package/dist/projection/index.d.ts.map +1 -0
  206. package/dist/projection/index.mjs +517 -0
  207. package/dist/projection/index.mjs.map +1 -0
  208. package/dist/query/query.d.ts +58 -0
  209. package/dist/query/query.d.ts.map +1 -0
  210. package/dist/relationship-index.d.ts +81 -0
  211. package/dist/relationship-index.d.ts.map +1 -0
  212. package/dist/resource.d.ts +70 -0
  213. package/dist/resource.d.ts.map +1 -0
  214. package/dist/schedule-token.d.ts +16 -0
  215. package/dist/schedule-token.d.ts.map +1 -0
  216. package/dist/schedule.d.ts +271 -0
  217. package/dist/schedule.d.ts.map +1 -0
  218. package/dist/shared-ref-store.d.ts +144 -0
  219. package/dist/shared-ref-store.d.ts.map +1 -0
  220. package/dist/shared.d.ts +3 -0
  221. package/dist/shared.d.ts.map +1 -0
  222. package/dist/shared.mjs +364 -0
  223. package/dist/shared.mjs.map +1 -0
  224. package/dist/storage/archetype-graph.d.ts +54 -0
  225. package/dist/storage/archetype-graph.d.ts.map +1 -0
  226. package/dist/storage/archetype.d.ts +23 -0
  227. package/dist/storage/archetype.d.ts.map +1 -0
  228. package/dist/storage/change-detection.d.ts +40 -0
  229. package/dist/storage/change-detection.d.ts.map +1 -0
  230. package/dist/storage/column.d.ts +111 -0
  231. package/dist/storage/column.d.ts.map +1 -0
  232. package/dist/storage/table.d.ts +29 -0
  233. package/dist/storage/table.d.ts.map +1 -0
  234. package/dist/time.d.ts +57 -0
  235. package/dist/time.d.ts.map +1 -0
  236. package/dist/unique-ref-store.d.ts +105 -0
  237. package/dist/unique-ref-store.d.ts.map +1 -0
  238. package/dist/world-change-journal.d.ts +34 -0
  239. package/dist/world-change-journal.d.ts.map +1 -0
  240. package/dist/world-component-access.d.ts +264 -0
  241. package/dist/world-component-access.d.ts.map +1 -0
  242. package/dist/world-component-storage.d.ts +282 -0
  243. package/dist/world-component-storage.d.ts.map +1 -0
  244. package/dist/world-core.d.ts +39 -0
  245. package/dist/world-core.d.ts.map +1 -0
  246. package/dist/world-entity-lifecycle.d.ts +30 -0
  247. package/dist/world-entity-lifecycle.d.ts.map +1 -0
  248. package/dist/world-internal.d.ts +14 -0
  249. package/dist/world-internal.d.ts.map +1 -0
  250. package/dist/world-scheduling.d.ts +30 -0
  251. package/dist/world-scheduling.d.ts.map +1 -0
  252. package/dist/world.d.ts +620 -0
  253. package/dist/world.d.ts.map +1 -0
  254. package/package.json +77 -0
  255. package/src/__fixtures__/field-shape-fixture.ts +30 -0
  256. package/src/__tests__/__fixtures__/inspect-scripts.snapshot.ts +2 -0
  257. package/src/__tests__/archetype.unit.test.ts +1837 -0
  258. package/src/__tests__/array-handle-element.test-d.ts +34 -0
  259. package/src/__tests__/buffer-array-vocab.test-d.ts +53 -0
  260. package/src/__tests__/buffer-field-input-widen.unit.test.ts +99 -0
  261. package/src/__tests__/buffer-pool.test-d.ts +32 -0
  262. package/src/__tests__/change-detection.unit.test.ts +86 -0
  263. package/src/__tests__/command-buffer.test.ts +179 -0
  264. package/src/__tests__/commands-deferred.unit.test.ts +68 -0
  265. package/src/__tests__/component-catalog.test.ts +59 -0
  266. package/src/__tests__/component-definition-identity.unit.test.ts +24 -0
  267. package/src/__tests__/component-schema.test-d.ts +119 -0
  268. package/src/__tests__/component-schema.test.ts +55 -0
  269. package/src/__tests__/define-component-options.test-d.ts +35 -0
  270. package/src/__tests__/deprecated-vocab-rejection.unit.test.ts +59 -0
  271. package/src/__tests__/despawn-all.unit.test.ts +17 -0
  272. package/src/__tests__/ecs-core-reduction.characterization.test.ts +130 -0
  273. package/src/__tests__/ecs-core-reduction.perf.test.ts +260 -0
  274. package/src/__tests__/ecs-error-code-31.test-d.ts +42 -0
  275. package/src/__tests__/entity-disabling.unit.test.ts +37 -0
  276. package/src/__tests__/entity-handle-codec-parity.unit.test.ts +126 -0
  277. package/src/__tests__/errors.unit.test.ts +1452 -0
  278. package/src/__tests__/execution-conflict-boundary.unit.test.ts +123 -0
  279. package/src/__tests__/execution-shared-storage.unit.test.ts +40 -0
  280. package/src/__tests__/externalization-portable-value.unit.test.ts +275 -0
  281. package/src/__tests__/externalization-projection.unit.test.ts +325 -0
  282. package/src/__tests__/externalization-root-surface.unit.test.ts +33 -0
  283. package/src/__tests__/field-enum-labels-reflection.test.ts +66 -0
  284. package/src/__tests__/field-shape-fixture.test.ts +61 -0
  285. package/src/__tests__/field-transient-reflection.test.ts +59 -0
  286. package/src/__tests__/field-value-type.test-d.ts +57 -0
  287. package/src/__tests__/fixed-update.unit.test.ts +78 -0
  288. package/src/__tests__/handle.test-d.ts +86 -0
  289. package/src/__tests__/hierarchy.unit.test.ts +2534 -0
  290. package/src/__tests__/managed-array-vocab.test-d.ts +137 -0
  291. package/src/__tests__/minimal-example.test-d.ts +42 -0
  292. package/src/__tests__/name-read-types.test-d.ts +72 -0
  293. package/src/__tests__/plugin-service.test.ts +13 -0
  294. package/src/__tests__/query-combinations.unit.test.ts +42 -0
  295. package/src/__tests__/query-light-extract-flat-column-ratio.perf.test.ts +252 -0
  296. package/src/__tests__/query-storage-trends.perf.test.ts +104 -0
  297. package/src/__tests__/query-trs-flat-column-ratio.perf.test.ts +194 -0
  298. package/src/__tests__/query.unit.test.ts +196 -0
  299. package/src/__tests__/relationship-index.perf.test.ts +126 -0
  300. package/src/__tests__/relationship-index.test.ts +70 -0
  301. package/src/__tests__/scalar-field-type-owner.test-d.ts +71 -0
  302. package/src/__tests__/schedule-determinism.unit.test.ts +36 -0
  303. package/src/__tests__/schedule-inspection.unit.test.ts +81 -0
  304. package/src/__tests__/schedule-scope-error.unit.test.ts +63 -0
  305. package/src/__tests__/schedule-token.unit.test.ts +54 -0
  306. package/src/__tests__/schema-vocab.test-d.ts +103 -0
  307. package/src/__tests__/serialization.unit.test.ts +2740 -0
  308. package/src/__tests__/shared-kernel-eligibility.test-d.ts +18 -0
  309. package/src/__tests__/shared-kernel-eligibility.unit.test.ts +78 -0
  310. package/src/__tests__/shared-ref-store.unit.test.ts +646 -0
  311. package/src/__tests__/sparse-tag.unit.test.ts +201 -0
  312. package/src/__tests__/spawn-field-error.test-d.ts +41 -0
  313. package/src/__tests__/stale-error.test-d.ts +207 -0
  314. package/src/__tests__/string-view-symmetry.test-d.ts +26 -0
  315. package/src/__tests__/string-vocab.test-d.ts +49 -0
  316. package/src/__tests__/structure-epoch-boundaries.unit.test.ts +39 -0
  317. package/src/__tests__/structure-epoch.unit.test.ts +48 -0
  318. package/src/__tests__/table-archetype-mapping.unit.test.ts +78 -0
  319. package/src/__tests__/time.unit.test.ts +79 -0
  320. package/src/__tests__/types.test-d.ts +38 -0
  321. package/src/__tests__/unique-ref-store.unit.test.ts +348 -0
  322. package/src/__tests__/utils/handle-numeric.ts +30 -0
  323. package/src/__tests__/validation-root-surface.unit.test.ts +15 -0
  324. package/src/__tests__/visibility-atomic-write.unit.test.ts +82 -0
  325. package/src/__tests__/world-change-journal.unit.test.ts +97 -0
  326. package/src/__tests__/world-characterization.unit.test.ts +408 -0
  327. package/src/__tests__/world-consumer-inference.test-d.ts +16 -0
  328. package/src/__tests__/world-health.contract.test.ts +87 -0
  329. package/src/__tests__/world-inspection.test.ts +37 -0
  330. package/src/__tests__/world-internal-identity.unit.test.ts +17 -0
  331. package/src/__tests__/world-managed-roundtrip.unit.test.ts +98 -0
  332. package/src/__tests__/world-scheduling.m4.test.ts +52 -0
  333. package/src/__tests__/world-unique-roundtrip.unit.test.ts +357 -0
  334. package/src/__tests__/world.test-d.ts +156 -0
  335. package/src/buffer-pool.ts +357 -0
  336. package/src/commands.ts +644 -0
  337. package/src/component-default-fallback.ts +236 -0
  338. package/src/component-schema.ts +92 -0
  339. package/src/component-value-validate.ts +135 -0
  340. package/src/component.ts +1268 -0
  341. package/src/entity-handle.ts +104 -0
  342. package/src/entity.ts +140 -0
  343. package/src/errors/query-and-component-errors.ts +200 -0
  344. package/src/errors/relationship-errors.ts +198 -0
  345. package/src/errors/sprite-and-shared-errors.ts +231 -0
  346. package/src/errors/validation-errors.ts +418 -0
  347. package/src/errors.ts +1453 -0
  348. package/src/execution/shared-kernel.ts +392 -0
  349. package/src/externalization/index.ts +170 -0
  350. package/src/index.ts +414 -0
  351. package/src/plugin-service.ts +35 -0
  352. package/src/projection/index.ts +142 -0
  353. package/src/query/query.ts +606 -0
  354. package/src/relationship-index.ts +175 -0
  355. package/src/resource.ts +106 -0
  356. package/src/schedule-token.ts +40 -0
  357. package/src/schedule.ts +846 -0
  358. package/src/shared-ref-store.ts +418 -0
  359. package/src/shared.ts +23 -0
  360. package/src/storage/archetype-graph.ts +166 -0
  361. package/src/storage/archetype.ts +78 -0
  362. package/src/storage/change-detection.ts +231 -0
  363. package/src/storage/column.ts +247 -0
  364. package/src/storage/table.ts +153 -0
  365. package/src/time.ts +122 -0
  366. package/src/unique-ref-store.ts +278 -0
  367. package/src/world-change-journal.ts +84 -0
  368. package/src/world-component-access.ts +1412 -0
  369. package/src/world-component-storage.ts +1178 -0
  370. package/src/world-core.ts +74 -0
  371. package/src/world-entity-lifecycle.ts +539 -0
  372. package/src/world-internal.ts +75 -0
  373. package/src/world-scheduling.ts +499 -0
  374. package/src/world.ts +1397 -0
package/src/world.ts ADDED
@@ -0,0 +1,1397 @@
1
+ // @forgeax/engine-ecs — World: top-level ECS container.
2
+ //
3
+ // World owns entities, archetypes (via ArchetypeGraph), and component registry.
4
+ // Supports multi-component spawn, despawn (with generation retirement D-08),
5
+ // get/set, addComponent/removeComponent (archetype migration via edges).
6
+ // M3: addSystem / update (DAG schedule) + deferred commands + Resource CRUD.
7
+ //
8
+ // [w6] All 6 public methods return Result<T, EcsError> (AP-8 Layer 1).
9
+ // Construction errors (EntityIndexOverflowError) still throw — they are
10
+ // build-time / infrastructure failures.
11
+
12
+ import type { Handle, Result } from '@forgeax/engine-types';
13
+ import { err, ok } from '@forgeax/engine-types';
14
+ import type { BufferPool } from './buffer-pool';
15
+ import {
16
+ type Component,
17
+ ComponentCatalog,
18
+ type ComponentSchema,
19
+ componentId,
20
+ type InputShapeOf,
21
+ type ShapeOf,
22
+ } from './component';
23
+ import { type EntityHandle, encodeEntity, entityGeneration, entityIndex } from './entity-handle';
24
+ import type {
25
+ CommandFailedError,
26
+ ComponentAlreadyPresentError,
27
+ ComponentFieldInvalidValueError,
28
+ ComponentNotDefinedError,
29
+ ComponentNotPresentError,
30
+ FixedSizeMismatchError,
31
+ ManagedArrayInvalidValueError,
32
+ ManagedBufferOutOfBoundsError,
33
+ ManagedBufferShrinkNotSupportedError,
34
+ RelationshipDetachMismatchError,
35
+ RelationshipMirrorComponentNotRegisteredError,
36
+ RelationshipMirrorFieldTypeMismatchError,
37
+ RelationshipSelfCycleError,
38
+ RemoveEssentialComponentError,
39
+ ScheduleMutationError,
40
+ ScheduleScopeMismatchError,
41
+ SharedKernelEligibilityError,
42
+ SharedKernelFailureError,
43
+ StaleEntityError,
44
+ SystemFailedError,
45
+ SystemSetNotRegisteredError,
46
+ TimeConfigInvalidError,
47
+ TimeDeltaInvalidError,
48
+ UniqueRefDoubleReleaseError,
49
+ UniqueRefReleasedError,
50
+ WorldPoisonedError,
51
+ } from './errors';
52
+ import { ChangeEpochExhaustedError, RelationshipTargetReadonlyError } from './errors';
53
+ import {
54
+ healthyWorldExecutionState,
55
+ poisonedWorldExecutionState,
56
+ type WorldExecutionFault,
57
+ type WorldExecutionState,
58
+ } from './execution/shared-kernel';
59
+ import type { QueryDescriptor } from './query/query';
60
+ import { createQuery, type Query, type QueryCreationError } from './query/query';
61
+ import { isRelationshipTarget, type RelationshipTargetComponent } from './relationship-index';
62
+ import { createResourceStore, type ResourceStore } from './resource';
63
+ import { createSchedule, type Schedule, type SystemDescriptor, type SystemSet } from './schedule';
64
+ import { FixedUpdate, Update } from './schedule-token';
65
+ import type { SharedRefStore } from './shared-ref-store';
66
+ import type { Archetype } from './storage/archetype';
67
+ import type { ArchetypeGraph } from './storage/archetype-graph';
68
+ import {
69
+ type ChangeTicks,
70
+ markComponentChanged,
71
+ markComponentsAdded,
72
+ readComponentChange,
73
+ readTableEntityRange,
74
+ type WorldChangeRead,
75
+ } from './storage/change-detection';
76
+ import {
77
+ type ClockWriter,
78
+ createWorldClock,
79
+ DEFAULT_TIME_POLICY,
80
+ FIXED_TIME_RESOURCE_KEY,
81
+ TIME_RESOURCE_KEY,
82
+ type WorldOptions,
83
+ } from './time';
84
+ import type { UniqueRefStore } from './unique-ref-store';
85
+ import { WorldComponentAccess } from './world-component-access';
86
+ import { WorldCore } from './world-core';
87
+ import {
88
+ despawnCore,
89
+ spawnCore,
90
+ worldAddChild,
91
+ worldIterAncestors,
92
+ worldIterDescendants,
93
+ worldRemoveChild,
94
+ worldReparent,
95
+ } from './world-entity-lifecycle';
96
+ import { type WorldInternal, worldInternal } from './world-internal';
97
+ import {
98
+ worldAddSystem,
99
+ worldAddSystems,
100
+ worldAllocSharedRef,
101
+ worldAllocUniqueRef,
102
+ worldGetResource,
103
+ worldHasResource,
104
+ worldInsertResource,
105
+ worldInspect,
106
+ worldInternSharedRef,
107
+ worldRemoveResource,
108
+ worldRemoveSystem,
109
+ worldReplaceSystem,
110
+ worldScheduleData,
111
+ worldScheduleUsesComponent,
112
+ worldUpdate,
113
+ } from './world-scheduling';
114
+
115
+ /**
116
+ * Union of all EcsError types that World methods can return via Result.
117
+ * AI users: switch on `.code` for programmatic branching.
118
+ */
119
+ export type EcsError =
120
+ | CommandFailedError
121
+ | StaleEntityError
122
+ | ComponentNotPresentError
123
+ | ComponentAlreadyPresentError
124
+ | ComponentFieldInvalidValueError
125
+ | ManagedArrayInvalidValueError
126
+ | UniqueRefReleasedError
127
+ | UniqueRefDoubleReleaseError
128
+ | ManagedBufferOutOfBoundsError
129
+ | ManagedBufferShrinkNotSupportedError
130
+ | FixedSizeMismatchError
131
+ | RelationshipSelfCycleError
132
+ | RelationshipMirrorComponentNotRegisteredError
133
+ | RelationshipMirrorFieldTypeMismatchError
134
+ | RelationshipDetachMismatchError
135
+ | RelationshipTargetReadonlyError
136
+ | ComponentNotDefinedError
137
+ | RemoveEssentialComponentError
138
+ | SystemSetNotRegisteredError
139
+ | SystemFailedError
140
+ | TimeDeltaInvalidError
141
+ | TimeConfigInvalidError
142
+ | ScheduleScopeMismatchError
143
+ | SharedKernelEligibilityError
144
+ | SharedKernelFailureError
145
+ | WorldPoisonedError;
146
+
147
+ /** Component data for spawn/addComponent: component token + initial values.
148
+ *
149
+ * `data` is `Partial<InputShapeOf<S>>` (feat-20260517 / M2; tweak-20260616
150
+ * input/output split): spawn / addComponent / SceneAsset.instantiate share the
151
+ * SAME shape contract via the layer-2 + layer-3 silent fallback applied inside
152
+ * `writeRow` (`fillComponentDefaults`). The input shape widens
153
+ * `array<scalar, N>` / `array<scalar>` to also accept `readonly number[]`
154
+ * because writeArrayField copies bytes from either shape — AI users can write
155
+ * `times: [0.5]` instead of `new Float32Array([0.5])` boilerplate. Wrong-VALUE
156
+ * fields (e.g. `{ fov: 'bad' }`) still fire field-level TS2322 — mapped-tuple
157
+ * primary inference does not degrade to the "No overload matches" wall
158
+ * (AC-03 / C-4). */
159
+ export interface ComponentData<S extends ComponentSchema = ComponentSchema> {
160
+ component: Component<string, S>;
161
+ data: Partial<InputShapeOf<S>>;
162
+ }
163
+
164
+ type WritableComponent<C extends Component> = C extends RelationshipTargetComponent ? never : C;
165
+
166
+ /**
167
+ * Per-archetype summary returned by `world.inspect()`. Sorted ComponentId key
168
+ * (always prefixed by the essential id=0 Entity column, e.g. "0+2+5+7"),
169
+ * human-readable component names, live entity count, allocated row capacity.
170
+ */
171
+ export interface ArchetypeInfo {
172
+ /** Sorted ComponentId key, always prefixed by the id=0 Entity column (e.g. "0+2+5+7"). */
173
+ readonly key: string;
174
+ /** Human-readable component names in this archetype. */
175
+ readonly componentNames: string[];
176
+ /** Number of live entities in this archetype. */
177
+ readonly entityCount: number;
178
+ readonly tableId: number;
179
+ }
180
+
181
+ export interface TableInfo {
182
+ readonly id: number;
183
+ readonly key: string;
184
+ readonly componentNames: string[];
185
+ readonly entityCount: number;
186
+ readonly capacity: number;
187
+ }
188
+
189
+ /**
190
+ * Typed diagnostic snapshot of the World state.
191
+ * Returned by `world.inspect()` for programmatic introspection by AI users.
192
+ */
193
+ export interface WorldInspection {
194
+ /** Total number of live entities. */
195
+ readonly entityCount: number;
196
+ /** Number of archetypes currently allocated. */
197
+ readonly archetypeCount: number;
198
+ /** Per-archetype details. */
199
+ readonly archetypes: ArchetypeInfo[];
200
+ readonly tableCount: number;
201
+ readonly tables: TableInfo[];
202
+ /**
203
+ * Names of components that are currently active in this World — i.e.
204
+ * every distinct component name appearing on at least one non-empty
205
+ * archetype. Collected by walking the archetype graph, so a component
206
+ * that was defined but never spawned into this World does not appear.
207
+ */
208
+ readonly activeComponents: string[];
209
+ /** Number of registered systems. Always equals `systems.length` (M2 derived invariant). */
210
+ readonly systemCount: number;
211
+ /**
212
+ * Per-system summary (M3 — plan-strategy D-8). One entry per
213
+ * registered system, in registration order. The `systemCount` field is
214
+ * preserved as a derived alias of `systems.length` so existing inspector
215
+ * P0 e2e cases that read `systemCount` keep working.
216
+ *
217
+ * `sets` is the list of set names this system belongs to (empty array for
218
+ * systems registered via plain `addSystem` without `addSystems`).
219
+ */
220
+ readonly systems: ReadonlyArray<{ readonly name: string; readonly sets: readonly string[] }>;
221
+ /** Keys of all inserted resources. */
222
+ readonly resourceKeys: string[];
223
+ /** Systems grouped by their schedule token. */
224
+ readonly schedules: ReadonlyArray<{
225
+ readonly schedule: import('./schedule-token').ScheduleToken;
226
+ readonly systems: ReadonlyArray<{ readonly name: string; readonly sets: readonly string[] }>;
227
+ }>;
228
+ /** Count systems in one explicit schedule. */
229
+ scheduleSystemCount(schedule: import('./schedule-token').ScheduleToken): number;
230
+ }
231
+
232
+ /** JSON-safe schedule graph and access metadata returned by `world.scheduleData()`. */
233
+ export interface WorldScheduleQueryData {
234
+ readonly with: readonly string[];
235
+ readonly without: readonly string[];
236
+ readonly optional: readonly string[];
237
+ readonly changed: readonly string[];
238
+ readonly added: readonly string[];
239
+ }
240
+
241
+ /** JSON-safe system registration and access metadata. */
242
+ export interface WorldScheduleSystemData {
243
+ readonly name: string;
244
+ readonly sets: readonly string[];
245
+ readonly before: readonly string[];
246
+ readonly after: readonly string[];
247
+ readonly queries: readonly WorldScheduleQueryData[];
248
+ readonly resources: readonly string[];
249
+ }
250
+
251
+ /** JSON-safe system-set membership and ordering metadata. */
252
+ export interface WorldScheduleSetData {
253
+ readonly name: string;
254
+ readonly members: readonly string[];
255
+ readonly before: readonly string[];
256
+ readonly after: readonly string[];
257
+ readonly chained: boolean;
258
+ }
259
+
260
+ /** JSON-safe projection of one explicit World schedule. */
261
+ export interface WorldScheduleData {
262
+ readonly name: string;
263
+ readonly systems: readonly WorldScheduleSystemData[];
264
+ readonly systemSets: readonly WorldScheduleSetData[];
265
+ readonly dependencies: readonly (readonly [string, string])[];
266
+ }
267
+
268
+ /**
269
+ * Internal record describing where an entity lives.
270
+ *
271
+ * Liveness (feat-20260602 / plan-strategy D-4): the former `alive` boolean was
272
+ * absorbed into `generation`. A despawn unconditionally bumps `generation` (so a
273
+ * stale handle's `gen` no longer matches), and `gen > 255` retires the slot
274
+ * permanently (it is never pushed back to `freeIndices`). The single liveness
275
+ * predicate is therefore "handle gen matches AND archetypeId !== -1" -- see
276
+ * `World.recordIsLive`. A deferred-spawn allocation is "pending" when
277
+ * archetypeId === -1 (not yet materialized into an archetype row); no separate
278
+ * boolean is needed.
279
+ */
280
+ export interface EntityRecord {
281
+ generation: number;
282
+ archetypeId: number; // -1 if no archetype (pending / despawned)
283
+ archetypeRow: number;
284
+ }
285
+
286
+ /**
287
+ * The World owns:
288
+ * - the registry of known component schemas;
289
+ * - all archetypes (via ArchetypeGraph);
290
+ * - the entity index table (records by index slot);
291
+ * - the free-list of recyclable entity slots.
292
+ */
293
+ export class World {
294
+ /** Package-internal implementation seam; not exported from any entry point. */
295
+ readonly [worldInternal]: WorldInternal;
296
+ // ── Internal state ──
297
+
298
+ /** The single package-private owner of storage, epochs, and change evidence. */
299
+ private readonly core: WorldCore;
300
+ get identity() {
301
+ return this.core.identity;
302
+ }
303
+ /** Plugin-owned component discovery scoped to this World and removed through leases. */
304
+ readonly components = new ComponentCatalog((component) => this.componentIsInUse(component));
305
+ private executionState: WorldExecutionState;
306
+ /** Monotonic clock advanced exactly once per successful mutation. */
307
+ /** Monotonic revision for successful entity/component structure writes. */
308
+ /** Last mutation epoch for each component id, used by component-owned projections. */
309
+ /** Ordered, bounded evidence consumed by persistent engine-owned projections. */
310
+ private get mutationEpoch() {
311
+ return this.core.mutationEpoch;
312
+ }
313
+ private set mutationEpoch(value: number) {
314
+ this.core.mutationEpoch = value;
315
+ }
316
+ private get structureEpoch() {
317
+ return this.core.structureEpoch;
318
+ }
319
+ private set structureEpoch(value: number) {
320
+ this.core.structureEpoch = value;
321
+ }
322
+ private get componentMutationEpochs() {
323
+ return this.core.componentMutationEpochs;
324
+ }
325
+ private get changeJournal() {
326
+ return this.core.changeJournal;
327
+ }
328
+ /** Free index slots (LIFO stack). */
329
+ private get records() {
330
+ return this.core.records;
331
+ }
332
+ private get freeIndices() {
333
+ return this.core.freeIndices;
334
+ }
335
+ /**
336
+ * Relationship-sync reentry guard (feat-20260531 M2 / plan-strategy D-7).
337
+ /** The archetype graph: manages all archetypes + edge caching. */
338
+ private get graph(): ArchetypeGraph {
339
+ return this.core.graph;
340
+ }
341
+ /** DAG schedules for the two built-in execution scopes. */
342
+ private readonly schedules = new Map([
343
+ [Update, createSchedule(Update)],
344
+ [FixedUpdate, createSchedule(FixedUpdate)],
345
+ ]);
346
+ /** Resource store: typed key-value global singletons. */
347
+ private readonly resources: ResourceStore = createResourceStore();
348
+ private readonly clock: ReturnType<typeof createWorldClock>;
349
+ /** Remainder carried between fixed-step runs. */
350
+ private fixedAccumulator = 0;
351
+ /**
352
+ * ECS-managed handle store (M1). Owned by the World - constructed eagerly
353
+ * so every spawn / despawn / set path can dispatch managed-ref releases
354
+ * without caller-side wiring. AI users obtain `Handle<T,'unique'>` values
355
+ * by accessing the store through internal channels (the surface is
356
+ * private; managed-ref-bearing fields read through `world.get`).
357
+ */
358
+ // UniqueRefStore is type-erased at the storage layer (alloc/resolve are
359
+ // method-generic over `T`); World holds the single per-instance store and
360
+ // routes payload-agnostic release calls. Typed access flows through
361
+ // `UniqueRefStore.resolve<T>` at the consumer layer.
362
+ private get uniqueRefs(): UniqueRefStore {
363
+ return this.core.uniqueRefs;
364
+ }
365
+ /**
366
+ * Per-World `SharedRefStore` (feat-20260614 M3). Backs every `shared<T>`
367
+ * schema field + the `world.allocSharedRef` facade. Public read-only so AI
368
+ * users can `retain` / `release` / `resolve` user-tier handles directly off
369
+ * the world (the surface is small enough that hiding it behind another
370
+ * facade would be a phantom indirection - charter F1 single-entry
371
+ * indexability).
372
+ *
373
+ * Final release publishes structured evidence; there is no callback surface.
374
+ * M6 D-15: the store manages only user-tier slots
375
+ * (`>= BUILTIN_BASE`); builtin handles are process-static in their
376
+ * authoring package and never reference-counted.
377
+ */
378
+ get sharedRefs(): SharedRefStore {
379
+ return this.core.sharedRefs;
380
+ }
381
+ /**
382
+ * BufferPool backing every `buffer:<N>` schema-vocab field (M2). Eagerly
383
+ * constructed (per-World, D-2). `spawn` allocs slots for buffer fields and
384
+ * stores the slot id in the u32 column; `despawn` / `removeComponent`
385
+ * release the slots; `set(e, C, { field: Uint8Array })` copies bytes into
386
+ * the live view without re-allocating (schema-declared byteLength is
387
+ * fixed in v1; runtime grow is reserved for the M4 carry-over path).
388
+ */
389
+ private get bufferPool() {
390
+ return this.core.bufferPool;
391
+ }
392
+ private readonly componentAccess: WorldComponentAccess;
393
+
394
+ constructor(options: WorldOptions = {}) {
395
+ this.core = new WorldCore(options.storage === 'shared');
396
+ this.executionState = healthyWorldExecutionState(this.identity);
397
+ this.componentAccess = new WorldComponentAccess({
398
+ graph: this.graph,
399
+ records: this.records,
400
+ freeIndices: this.freeIndices,
401
+ bufferPool: this.bufferPool,
402
+ uniqueRefs: this.uniqueRefs,
403
+ sharedRefs: this.sharedRefs,
404
+ relationshipIndexes: this.core.relationshipIndexes,
405
+ markComponentAdded: (entity, component) => this.internalmarkComponentAdded(entity, component),
406
+ markComponentsAdded: (entity, components) =>
407
+ this.internalmarkComponentsAdded(entity, components),
408
+ markComponentChanged: (entity, component) =>
409
+ this.internalmarkComponentChanged(entity, component),
410
+ removeComponentChange: (entity, component) =>
411
+ this.internalremoveComponentChange(entity, component),
412
+ markStructureChanged: () => this.internalmarkStructureChanged(),
413
+ routeError: (error, context) => this.internalrouteError(error as EcsError, context),
414
+ });
415
+ this.clock = createWorldClock({ ...DEFAULT_TIME_POLICY, ...options.time });
416
+ this.resources.entries.set(TIME_RESOURCE_KEY, {
417
+ value: this.clock.time,
418
+ added: 0,
419
+ changed: 0,
420
+ });
421
+ this.resources.entries.set(FIXED_TIME_RESOURCE_KEY, {
422
+ value: this.clock.fixed,
423
+ added: 0,
424
+ changed: 0,
425
+ });
426
+ this[worldInternal] = {
427
+ addComponentCore: this.internaladdComponentCore.bind(this),
428
+ allocateIndex: this.internalallocateIndex.bind(this),
429
+ allocatePendingEntity: this.internalallocatePendingEntity.bind(this),
430
+ cancelPendingEntity: this.internalcancelPendingEntity.bind(this),
431
+ despawnCore: this.internaldespawnCore.bind(this),
432
+ getArrayView: this.internalgetArrayView.bind(this),
433
+ getBufferPool: this.internalgetBufferPool.bind(this),
434
+ getChangeCursor: this.internalgetChangeCursor.bind(this),
435
+ getClockWriter: this.internalgetClockWriter.bind(this),
436
+ getComponentChange: this.internalgetComponentChange.bind(this),
437
+ getComponentMutationEpoch: this.internalgetComponentMutationEpoch.bind(this),
438
+ getEntityArchetype: this.internalgetEntityArchetype.bind(this),
439
+ getFixedAccumulator: this.internalgetFixedAccumulator.bind(this),
440
+ getFreeIndices: this.internalgetFreeIndices.bind(this),
441
+ getGraph: this.internalgetGraph.bind(this),
442
+ getMutationEpoch: this.internalgetMutationEpoch.bind(this),
443
+ getQueryRow: this.internalgetQueryRow.bind(this),
444
+ getRecords: this.internalgetRecords.bind(this),
445
+ getRelationshipEpoch: this.internalgetRelationshipEpoch.bind(this),
446
+ getRelationshipTargetEntities: this.internalgetRelationshipTargetEntities.bind(this),
447
+ getResources: this.internalgetResources.bind(this),
448
+ getSchedule: this.internalgetSchedule.bind(this),
449
+ getSchedules: this.internalgetSchedules.bind(this),
450
+ getSharedRefs: this.internalgetSharedRefs.bind(this),
451
+ getStructureEpoch: this.internalgetStructureEpoch.bind(this),
452
+ getUniqueRefs: this.internalgetUniqueRefs.bind(this),
453
+ lookupAlive: this.internallookupAlive.bind(this),
454
+ markComponentAdded: this.internalmarkComponentAdded.bind(this),
455
+ markComponentChanged: this.internalmarkComponentChanged.bind(this),
456
+ markComponentRangeChanged: this.internalmarkComponentRangeChanged.bind(this),
457
+ markComponentsAdded: this.internalmarkComponentsAdded.bind(this),
458
+ markDerivedComponentChanges: this.internalmarkDerivedComponentChanges.bind(this),
459
+ markStructureChanged: this.internalmarkStructureChanged.bind(this),
460
+ materializePendingEntity: this.internalmaterializePendingEntity.bind(this),
461
+ nextMutationEpoch: this.internalnextMutationEpoch.bind(this),
462
+ poisonExecution: this.internalpoisonExecution.bind(this),
463
+ preflightComponentData: this.internalpreflightComponentData.bind(this),
464
+ readChangesSince: this.internalreadChangesSince.bind(this),
465
+ readRow: this.internalreadRow.bind(this),
466
+ recordIsLive: this.internalrecordIsLive.bind(this),
467
+ relationshipOnInsert: this.internalrelationshipOnInsert.bind(this),
468
+ relationshipOnRemove: this.internalrelationshipOnRemove.bind(this),
469
+ releaseManagedRefsOnRow: this.internalreleaseManagedRefsOnRow.bind(this),
470
+ removeComponentChange: this.internalremoveComponentChange.bind(this),
471
+ removeComponentCore: this.internalremoveComponentCore.bind(this),
472
+ removeEntityChanges: this.internalremoveEntityChanges.bind(this),
473
+ routeError: this.internalrouteError.bind(this),
474
+ setFixedAccumulator: this.internalsetFixedAccumulator.bind(this),
475
+ setQueryRow: this.internalsetQueryRow.bind(this),
476
+ spawnCore: this.internalspawnCore.bind(this),
477
+ writeEntitySelf: this.internalwriteEntitySelf.bind(this),
478
+ writeRow: this.internalwriteRow.bind(this),
479
+ };
480
+ }
481
+
482
+ /** Immutable integrity state for execution coordinators and headless callers. */
483
+ get execution(): WorldExecutionState {
484
+ return this.executionState;
485
+ }
486
+
487
+ /** Resolve a schedule token owned by this World realm without package singleton identity. */
488
+ scheduleToken(
489
+ name: import('./schedule-token').ScheduleName,
490
+ ): import('./schedule-token').ScheduleToken {
491
+ if (name === 'Update') return Update;
492
+ if (name === 'FixedUpdate') return FixedUpdate;
493
+ return FixedUpdate;
494
+ }
495
+
496
+ /** SharedKernel is the only writer; application code recovers by constructing a new World. */
497
+ private internalpoisonExecution(fault: WorldExecutionFault): void {
498
+ if (this.executionState.health === 'healthy') {
499
+ this.executionState = poisonedWorldExecutionState(this.identity, fault);
500
+ }
501
+ }
502
+
503
+ query<
504
+ const R extends readonly Component[] = readonly [],
505
+ const W extends readonly Component[] = readonly [],
506
+ const O extends readonly Component[] = readonly [],
507
+ >(descriptor: QueryDescriptor<R, W, O>): Result<Query<R, W, O>, QueryCreationError> {
508
+ return createQuery(this, descriptor);
509
+ }
510
+
511
+ // ──────────────────────────────────────────────────────────────────────────
512
+ // Internal access — query engine
513
+ // ──────────────────────────────────────────────────────────────────────────
514
+
515
+ /** Expose archetype graph for query engine. Not part of public API. */
516
+ private internalgetGraph(): ArchetypeGraph {
517
+ return this.graph;
518
+ }
519
+
520
+ private componentIsInUse(component: Component): boolean {
521
+ if (
522
+ this.graph.archetypes.some(
523
+ (archetype) =>
524
+ archetype.size > 0 && archetype.components.some((candidate) => candidate === component),
525
+ )
526
+ ) {
527
+ return true;
528
+ }
529
+ return worldScheduleUsesComponent(this, component);
530
+ }
531
+
532
+ /** Current upper bound for mutation observation. */
533
+ private internalgetMutationEpoch(): number {
534
+ return this.mutationEpoch;
535
+ }
536
+
537
+ /** Current cursor for a persistent projection subscriber. */
538
+ private internalgetChangeCursor(): number {
539
+ return this.changeJournal.cursor();
540
+ }
541
+
542
+ /** Read complete mutation evidence or an explicit rebuild signal. */
543
+ private internalreadChangesSince(cursor: number): WorldChangeRead {
544
+ return this.changeJournal.readAfter(cursor);
545
+ }
546
+
547
+ /** Structure snapshot used to invalidate borrowed query facades. */
548
+ private internalgetStructureEpoch(): number {
549
+ return this.structureEpoch;
550
+ }
551
+
552
+ /** Resolve current logical identity for a packed entity handle. */
553
+ private internalgetEntityArchetype(entity: EntityHandle): Archetype | undefined {
554
+ const record = this.records[entityIndex(entity)];
555
+ if (!this.internalrecordIsLive(record, entityGeneration(entity))) return undefined;
556
+ return this.graph.archetypes[record.archetypeId];
557
+ }
558
+
559
+ /** Component change state for query filters. */
560
+ private internalgetComponentChange(
561
+ entity: EntityHandle,
562
+ componentId: number,
563
+ ): ChangeTicks | undefined {
564
+ const record = this.records[entityIndex(entity)];
565
+ if (!this.internalrecordIsLive(record, entityGeneration(entity))) return undefined;
566
+ return readComponentChange(this.graph, record, entity, componentId);
567
+ }
568
+
569
+ /** Allocate one epoch after a mutation has succeeded. */
570
+ private internalnextMutationEpoch(): number {
571
+ if (this.mutationEpoch >= Number.MAX_SAFE_INTEGER) {
572
+ throw new ChangeEpochExhaustedError(this.mutationEpoch);
573
+ }
574
+ this.mutationEpoch += 1;
575
+ return this.mutationEpoch;
576
+ }
577
+
578
+ /** Record one successful structural mutation. */
579
+ private internalmarkStructureChanged(): void {
580
+ this.structureEpoch += 1;
581
+ }
582
+
583
+ /** Current structural revision for mounted World projections. */
584
+ getStructureEpoch(): number {
585
+ return this.structureEpoch;
586
+ }
587
+
588
+ /** Mark a component as both added and changed at the current tick. */
589
+ private internalmarkComponentAdded(entity: EntityHandle, componentId: number): void {
590
+ this.internalmarkComponentsAdded(entity, [componentId]);
591
+ }
592
+
593
+ /** Mark one mutation's component instances with a shared epoch. */
594
+ private internalmarkComponentsAdded(entity: EntityHandle, componentIds: readonly number[]): void {
595
+ const record = this.records[entityIndex(entity)];
596
+ if (!this.internalrecordIsLive(record, entityGeneration(entity))) return;
597
+ const epoch = this.internalnextMutationEpoch();
598
+ markComponentsAdded(this.graph, record, entity, componentIds, epoch);
599
+ for (const componentId of componentIds) {
600
+ this.componentMutationEpochs[componentId] = epoch;
601
+ this.changeJournal.append({ kind: 'component-added', entity, componentId });
602
+ }
603
+ }
604
+
605
+ /** Mark an existing component as changed at the current tick. */
606
+ private internalmarkComponentChanged(entity: EntityHandle, componentId: number): void {
607
+ const record = this.records[entityIndex(entity)];
608
+ if (!this.internalrecordIsLive(record, entityGeneration(entity))) return;
609
+ let epoch: number | undefined;
610
+ markComponentChanged(this.graph, record, entity, componentId, () => {
611
+ epoch = this.internalnextMutationEpoch();
612
+ return epoch;
613
+ });
614
+ if (epoch !== undefined) {
615
+ this.componentMutationEpochs[componentId] = epoch;
616
+ this.changeJournal.append({ kind: 'component-changed', entity, componentId });
617
+ }
618
+ }
619
+
620
+ /** Mark one contiguous component range with a single epoch. */
621
+ private internalmarkComponentRangeChanged(
622
+ table: ArchetypeGraph['tables'][number],
623
+ componentId: number,
624
+ rowStart: number,
625
+ rowCount: number,
626
+ ): void {
627
+ const epochs = table.storage.get(componentId)?.epochs;
628
+ if (epochs === undefined || rowCount === 0) return;
629
+ const epoch = this.internalnextMutationEpoch();
630
+ epochs.changed.fill(epoch, rowStart, rowStart + rowCount);
631
+ this.componentMutationEpochs[componentId] = epoch;
632
+ for (const entity of readTableEntityRange(table, rowStart, rowCount)) {
633
+ this.changeJournal.append({
634
+ kind: 'component-changed',
635
+ entity,
636
+ componentId,
637
+ });
638
+ }
639
+ }
640
+
641
+ /** Latest mutation token for one component-owned projection. */
642
+ private internalgetComponentMutationEpoch(componentId: number): number {
643
+ return this.componentMutationEpochs[componentId] ?? 0;
644
+ }
645
+
646
+ /** Read a materialized relationship target in O(1 + k). */
647
+ private internalgetRelationshipTargetEntities(
648
+ source: Component,
649
+ target: EntityHandle,
650
+ ): readonly EntityHandle[] {
651
+ return this.componentAccess.relationshipTargetEntries(source, target);
652
+ }
653
+
654
+ /** Monotonic epoch for the materialized relationship index. */
655
+ private internalgetRelationshipEpoch(source: Component): number {
656
+ return this.core.relationshipIndexes.get(componentId(source))?.epoch ?? 0;
657
+ }
658
+
659
+ /** Publish changes to a value derived without an authored mutation epoch. */
660
+ private internalmarkDerivedComponentChanges(
661
+ componentId: number,
662
+ entities: Iterable<EntityHandle>,
663
+ ): void {
664
+ for (const entity of entities) {
665
+ this.changeJournal.append({ kind: 'derived-component-changed', entity, componentId });
666
+ }
667
+ }
668
+
669
+ /** Query facade write after the facade has already marked evidence. */
670
+ private internalsetQueryRow(
671
+ entity: EntityHandle,
672
+ component: Component,
673
+ value: Record<string, unknown>,
674
+ ): Result<void, EcsError> {
675
+ return this.componentAccess.set(entity, component, value as never, false);
676
+ }
677
+
678
+ /** Query facade read that does not re-enter the public World API. */
679
+ private internalgetQueryRow(
680
+ entity: EntityHandle,
681
+ component: Component,
682
+ ): Result<Record<string, unknown>, EcsError> {
683
+ return this.componentAccess.get(entity, component) as Result<Record<string, unknown>, EcsError>;
684
+ }
685
+
686
+ /** Remove one component's change state after archetype removal. */
687
+ private internalremoveComponentChange(entity: EntityHandle, componentId: number): void {
688
+ this.changeJournal.append({ kind: 'component-removed', entity, componentId });
689
+ }
690
+
691
+ /** Remove all change state before an entity handle is retired. */
692
+ private internalremoveEntityChanges(entity: EntityHandle): void {
693
+ this.changeJournal.append({ kind: 'entity-removed', entity });
694
+ }
695
+
696
+ /** Return resource change ticks for diagnostics and resource-driven systems. */
697
+ getResourceChange(name: string): ChangeTicks | undefined {
698
+ const entry = this.resources.entries.get(name);
699
+ return entry === undefined ? undefined : { added: entry.added, changed: entry.changed };
700
+ }
701
+
702
+ /**
703
+ * Route a structured error from
704
+ * an engine-internal subsystem (e.g. RenderSystem extract stage, w15).
705
+ *
706
+ * Mirrors the private `errorHandler(err, ctx)` call sites inside `World`
707
+ * itself; the dedicated accessor avoids exposing `errorHandler` directly
708
+ * and keeps the routing contract under the `_xxx` `@internal` umbrella so
709
+ * AI users do not discover it through IDE autocomplete on `World`.
710
+ *
711
+ * Not part of the public API.
712
+ */
713
+ private internalrouteError(err: EcsError, ctx?: { readonly systemName: string }): void {
714
+ // Internal expected failures are reported without becoming a second
715
+ // schedule or terminal hook. The host owns fatal frame policy.
716
+ console.error(`[${ctx?.systemName ?? 'World'}]`, err);
717
+ }
718
+
719
+ // ──────────────────────────────────────────────────────────────────────────
720
+ // accessors — M1 extraction seam
721
+ // ──────────────────────────────────────────────────────────────────────────
722
+
723
+ /** */ private internalgetRecords(): EntityRecord[] {
724
+ return this.records;
725
+ }
726
+ /** */ private internalgetFreeIndices(): number[] {
727
+ return this.freeIndices;
728
+ }
729
+ /** */ private internalgetResources(): ResourceStore {
730
+ return this.resources;
731
+ }
732
+ /** */ private internalgetFixedAccumulator(): number {
733
+ return this.fixedAccumulator;
734
+ }
735
+ /** */ private internalsetFixedAccumulator(value: number): void {
736
+ this.fixedAccumulator = value;
737
+ }
738
+ /** */ private internalgetUniqueRefs(): UniqueRefStore {
739
+ return this.uniqueRefs;
740
+ }
741
+ /** */ private internalgetBufferPool(): BufferPool {
742
+ return this.bufferPool;
743
+ }
744
+ /** Scheduler-owned mutable clock capability. */
745
+ private internalgetClockWriter(): ClockWriter {
746
+ return this.clock.writer;
747
+ }
748
+ /** */ private internalgetSchedule(
749
+ token: import('./schedule-token').ScheduleToken,
750
+ ): Schedule | undefined {
751
+ return this.schedules.get(token);
752
+ }
753
+ /** */ private internalgetSchedules(): ReadonlyMap<
754
+ import('./schedule-token').ScheduleToken,
755
+ Schedule
756
+ > {
757
+ return this.schedules;
758
+ }
759
+ /** */ private internalgetSharedRefs(): SharedRefStore {
760
+ return this.sharedRefs;
761
+ }
762
+
763
+ // ──────────────────────────────────────────────────────────────────────────
764
+ // System registration + update (M3)
765
+ // ──────────────────────────────────────────────────────────────────────────
766
+
767
+ /**
768
+ * Register a system with query descriptor and optional ordering constraints.
769
+ *
770
+ * `const Qs` mirrors the free `addSystem` signature so the call-site
771
+ * `queries` tuple is locked literal-form, letting `descriptor.fn`'s first
772
+ * parameter recover per-query row access shapes (S-5, KD-3 — class method
773
+ * generic, not free function double track).
774
+ *
775
+ * @example
776
+ * ```ts
777
+ * const Position = defineComponent('Position', { x: 'f32', y: 'f32' });
778
+ * const world = new World();
779
+ * world.addSystem(Update, {
780
+ * name: 'read-pos',
781
+ * queries: [{ with: [Position] }],
782
+ * fn: (world, queries) => { void world; for (const row of queries[0]) { void row.entity; } },
783
+ * });
784
+ * ```
785
+ */
786
+ addSystem<const Qs extends ReadonlyArray<QueryDescriptor>>(
787
+ schedule: import('./schedule-token').ScheduleToken,
788
+ descriptor: SystemDescriptor<Qs>,
789
+ ): Result<void, ScheduleScopeMismatchError> {
790
+ return worldAddSystem(this, schedule, descriptor);
791
+ }
792
+
793
+ /**
794
+ * Remove a registered system by name (M2 — plan-strategy D-3).
795
+ *
796
+ * Returns `Result<void, ScheduleMutationError>`:
797
+ * - ok branch: the slot is dropped and the schedule will rebuild on the
798
+ * next `update()`.
799
+ * - err branch with `.code === 'system-before-unknown'`: no system carries
800
+ * this name; `.detail.candidates` lists the registered names.
801
+ *
802
+ * Designed to support `@forgeax/engine-remote`'s typed `injectSystem` /
803
+ * `removeSystem` channel and the WS-disconnect reverse-remove path.
804
+ *
805
+ * @example
806
+ * ```ts
807
+ * const r = world.removeSystem(Update, 'movement');
808
+ * if (!r.ok) console.error(r.error.code, r.error.detail.candidates);
809
+ * ```
810
+ */
811
+ removeSystem(
812
+ schedule: import('./schedule-token').ScheduleToken,
813
+ name: string,
814
+ ): Result<void, ScheduleMutationError | ScheduleScopeMismatchError> {
815
+ return worldRemoveSystem(this, schedule, name);
816
+ }
817
+
818
+ /**
819
+ * Replace a registered system in-place (M2 — plan-strategy D-3 atomic semantics).
820
+ *
821
+ * Overwrites the descriptor stored under `name` while preserving the
822
+ * registration slot — `before / after` references that target this name
823
+ * remain bound.
824
+ *
825
+ * Returns `Result<void, ScheduleMutationError>`:
826
+ * - ok branch: descriptor swapped, schedule marked dirty.
827
+ * - err branch with `.code === 'system-before-unknown'`: no system carries
828
+ * this name; use `addSystem(descriptor)` to register a new one instead.
829
+ *
830
+ * @example
831
+ * ```ts
832
+ * const r = world.replaceSystem(Update, 'movement', {
833
+ * name: 'movement',
834
+ * queries: [{ with: [Position] }],
835
+ * fn: (world, queryResults) => { ... },
836
+ * });
837
+ * ```
838
+ */
839
+ replaceSystem<const Qs extends ReadonlyArray<QueryDescriptor>>(
840
+ schedule: import('./schedule-token').ScheduleToken,
841
+ name: string,
842
+ descriptor: SystemDescriptor<Qs>,
843
+ ): Result<void, ScheduleMutationError | ScheduleScopeMismatchError> {
844
+ return worldReplaceSystem(this, schedule, name, descriptor);
845
+ }
846
+
847
+ /**
848
+ * Batch-register systems to a set. Validates the set token before writing.
849
+ *
850
+ * - First call for a system name: registers it via the existing `addSystem` path.
851
+ * - Subsequent calls: only adds the system name to the set's members (dedup).
852
+ *
853
+ * Returns `Result.err` with `SystemSetNotRegisteredError` if the set token
854
+ * fails identity validation.
855
+ *
856
+ * @example
857
+ * ```ts
858
+ * const GameplaySet = defineSystemSet({ name: 'gameplay' });
859
+ * const world = new World();
860
+ * const r = world.addSystems(Update, GameplaySet, [movement, collision]);
861
+ * if (!r.ok) console.error(r.error.code, r.error.hint);
862
+ * ```
863
+ */
864
+ addSystems<const Qs extends ReadonlyArray<QueryDescriptor>>(
865
+ schedule: import('./schedule-token').ScheduleToken,
866
+ set: SystemSet,
867
+ systems: ReadonlyArray<SystemDescriptor<Qs>>,
868
+ ): Result<void, SystemSetNotRegisteredError | ScheduleScopeMismatchError> {
869
+ return worldAddSystems(this, schedule, set, systems);
870
+ }
871
+
872
+ /**
873
+ * Execute one frame: run all systems in DAG order, then flush deferred commands.
874
+ * Empty world (no systems) completes silently (E-09).
875
+ *
876
+ * @example
877
+ * ```ts
878
+ * const Position = defineComponent('Position', { x: 'f32', y: 'f32' });
879
+ * const world = new World();
880
+ * world.spawn({ component: Position, data: { x: 0, y: 0 } }).unwrap();
881
+ * world.update(); // run all systems + flush commands
882
+ * ```
883
+ */
884
+ update(
885
+ deltaSeconds = 0,
886
+ ): Result<
887
+ void,
888
+ | TimeDeltaInvalidError
889
+ | TimeConfigInvalidError
890
+ | ScheduleScopeMismatchError
891
+ | WorldPoisonedError
892
+ | CommandFailedError
893
+ | SystemFailedError
894
+ | import('./errors').CyclicDependencyError
895
+ | SharedKernelFailureError
896
+ > {
897
+ return worldUpdate(this, deltaSeconds);
898
+ }
899
+
900
+ // ──────────────────────────────────────────────────────────────────────────
901
+ // Resource CRUD (M3)
902
+ // ──────────────────────────────────────────────────────────────────────────
903
+
904
+ /** Insert or overwrite a resource (idempotent, E-13). */
905
+ insertResource<T>(key: string | { readonly name: string }, value: T): void {
906
+ worldInsertResource(this, key, value);
907
+ }
908
+
909
+ /**
910
+ * Get a resource by key.
911
+ * @throws ResourceNotFoundError if key not found (E-14).
912
+ */
913
+ getResource(key: typeof import('./time').Time): import('./time').TimeResource;
914
+ getResource(key: typeof import('./time').FixedTime): import('./time').FixedTimeResource;
915
+ getResource<T>(key: string | { readonly name: string }): T;
916
+ getResource<T>(key: string | { readonly name: string }): T {
917
+ return worldGetResource<T>(this, key);
918
+ }
919
+
920
+ /** Check if a resource exists. */
921
+ hasResource(key: string | { readonly name: string }): boolean {
922
+ return worldHasResource(this, key);
923
+ }
924
+
925
+ /** Remove a resource by key. */
926
+ removeResource(key: string | { readonly name: string }): void {
927
+ worldRemoveResource(this, key);
928
+ }
929
+
930
+ // ──────────────────────────────────────────────────────────────────────────
931
+ // Inspection / diagnostics (M4)
932
+ // ──────────────────────────────────────────────────────────────────────────
933
+
934
+ /**
935
+ * Return a typed diagnostic snapshot of the World state.
936
+ * All fields are non-undefined. Useful for AI users to programmatically
937
+ * introspect entity count, archetypes, registered components, systems,
938
+ * and resources without console.log or a debugger.
939
+ *
940
+ * @example
941
+ * ```ts
942
+ * const Position = defineComponent('Position', { x: 'f32', y: 'f32' });
943
+ * const world = new World();
944
+ * world.spawn({ component: Position, data: { x: 0, y: 0 } }).unwrap();
945
+ * const snap = world.inspect();
946
+ * console.log(snap.entityCount, snap.activeComponents);
947
+ * ```
948
+ */
949
+ inspect(): WorldInspection {
950
+ return detachWorldInspection(worldInspect(this));
951
+ }
952
+
953
+ /** Return the registered schedule graphs and their declared access metadata. */
954
+ scheduleData(): ReadonlyArray<WorldScheduleData> {
955
+ return worldScheduleData(this);
956
+ }
957
+
958
+ // ──────────────────────────────────────────────────────────────────────────
959
+ // Managed-ref public API (feat-20260528-rapier-physics M1 / t4)
960
+ // ──────────────────────────────────────────────────────────────────────────
961
+
962
+ /**
963
+ * Allocate a standalone managed reference handle with an optional release
964
+ * callback. Returns a branded {@link Handle}<Target, 'unique'> that can be
965
+ * stored in schema-vocab `ref<T>` fields or resolved through
966
+ * {@link UniqueRefStore.resolve} (via `world.get` on a component with
967
+ * `ref<T>` fields).
968
+ *
969
+ * When the handle is released (despawn / removeComponent / set-overwrite),
970
+ * the `onRelease` callback fires with the payload (captured on the stack);
971
+ * by then the slot's bookkeeping (callback table, payload map, freelist) is
972
+ * already cleared, so a *throwing* `onRelease` re-propagates from the first
973
+ * `release` call without leaving the store inconsistent — a second `release`
974
+ * of the same handle returns `UniqueRefDoubleReleaseError` as expected. RAII
975
+ * cleanup semantics preserved (plan-strategy D-5; throw-safety AC-01/02).
976
+ *
977
+ * Handles are *operational, not persistent*: caching them across release
978
+ * boundaries (despawn / removeComponent / set-overwrite) is undefined
979
+ * behavior — the same `u32` may silently resolve to a freshly allocated
980
+ * payload after slot reuse. See `packages/ecs/README.md` § "Managed handles
981
+ * are operational, not persistent" and `docs/specs/2026-06-14-ecs-managed-
982
+ * lifecycle-ssot-design.md` § 3.3.
983
+ *
984
+ * @typeParam Target - phantom string branding the handle (type-level only).
985
+ * @typeParam T - the payload type stored alongside the handle.
986
+ * @param target - phantom target string (type-level discriminant).
987
+ * @param payload - the value to store. Identity-stable until release.
988
+ * @param onRelease - optional cleanup hook called with the payload on release.
989
+ * @returns a branded `Handle<Target, 'unique'>` u32.
990
+ *
991
+ * @example
992
+ * ```ts
993
+ * const world = new World();
994
+ * const handle = world.allocUniqueRef<'PhysicsBody', RigidBodyHandle>(
995
+ * 'PhysicsBody',
996
+ * rapierHandle,
997
+ * (h) => rapierWorld.removeRigidBody(h),
998
+ * );
999
+ * const Holder = defineComponent('Holder', { body: 'unique<PhysicsBody>' });
1000
+ * world.spawn(Holder, { body: handle });
1001
+ * // Despawn triggers onRelease -> Rapier body is cleaned up.
1002
+ * ```
1003
+ */
1004
+ allocUniqueRef<Target extends string, T>(
1005
+ target: Target,
1006
+ payload: T,
1007
+ onRelease?: (payload: T) => void,
1008
+ ): Handle<Target, 'unique'> {
1009
+ return worldAllocUniqueRef(this, target, payload, onRelease);
1010
+ }
1011
+
1012
+ /**
1013
+ * Allocate a shared (refcount-tracked) handle through the per-World
1014
+ * {@link SharedRefStore}. Returns a `Handle<Target, 'shared'>` u32 with
1015
+ * rc=1 (the alloc-grant). Consumers retain/release via `world.sharedRefs`.
1016
+ *
1017
+ * Final release publishes structured evidence through the owning
1018
+ * {@link SharedRefStore}; payload disposal remains with the
1019
+ * renderer/assets/plugin owner and is not a user callback.
1020
+ *
1021
+ * Intended for asset-registry-style producers — anything whose lifecycle
1022
+ * is shared across multiple holders (ECS components + external systems).
1023
+ * The single-holder one-shot release pattern stays on
1024
+ * {@link World.allocUniqueRef} (`Handle<T, 'unique'>`).
1025
+ *
1026
+ * @typeParam Target - phantom string branding the handle (type-level only).
1027
+ * @typeParam T - the payload type stored alongside the handle.
1028
+ * @param target - phantom target string (type-level discriminant).
1029
+ * @param payload - the value to store. Identity-stable until final release.
1030
+ * @returns a branded `Handle<Target, 'shared'>` u32 with rc=1.
1031
+ *
1032
+ * @example
1033
+ * ```ts
1034
+ * const world = new World();
1035
+ * const handle = world.allocSharedRef<'MaterialAsset', MaterialPayload>(
1036
+ * 'MaterialAsset',
1037
+ * payload,
1038
+ * );
1039
+ * const M = defineComponent('M', { asset: 'shared<MaterialAsset>' });
1040
+ * world.spawn({ component: M, data: { asset: handle } });
1041
+ * // The write-barrier dispatch retains/releases automatically on spawn / despawn.
1042
+ * ```
1043
+ */
1044
+ allocSharedRef<Target extends string, T>(target: Target, payload: T): Handle<Target, 'shared'> {
1045
+ return worldAllocSharedRef(this, target, payload);
1046
+ }
1047
+
1048
+ /**
1049
+ * Return one producer-owned shared handle per `(target, payload object)` in
1050
+ * this World. Repeated discovery does not retain; ECS holders still retain
1051
+ * and release through the normal write barrier. Asset catalogues use this
1052
+ * when repeated scene instantiation resolves the same catalogued payload.
1053
+ * Use {@link World.allocSharedRef} for independent resources or deleters.
1054
+ */
1055
+ internSharedRef<Target extends string, T extends object>(
1056
+ target: Target,
1057
+ payload: T,
1058
+ ): Handle<Target, 'shared'> {
1059
+ return worldInternSharedRef(this, target, payload);
1060
+ }
1061
+
1062
+ // ──────────────────────────────────────────────────────────────────────────
1063
+ // Component access facade — storage ownership is world-component-access.
1064
+ // ──────────────────────────────────────────────────────────────────────────
1065
+
1066
+ private relationshipTargetWriteError(
1067
+ component: Component,
1068
+ operation: string,
1069
+ ): Result<never, EcsError> {
1070
+ return err(new RelationshipTargetReadonlyError(component.name, operation));
1071
+ }
1072
+
1073
+ private relationshipTargetPayloadWrites(data: Readonly<Record<string, unknown>>): boolean {
1074
+ return Object.values(data).some((value) => {
1075
+ if (Array.isArray(value)) return value.length > 0;
1076
+ if (ArrayBuffer.isView(value)) return value.byteLength > 0;
1077
+ return true;
1078
+ });
1079
+ }
1080
+
1081
+ get<S extends ComponentSchema>(
1082
+ entity: EntityHandle,
1083
+ component: Component<string, S>,
1084
+ ): Result<ShapeOf<S>, EcsError> {
1085
+ return this.componentAccess.get(entity, component);
1086
+ }
1087
+
1088
+ /**
1089
+ * Test live component presence without constructing a Result error.
1090
+ *
1091
+ * Read projections commonly need to branch on optional components for many
1092
+ * entities. Calling `get` for that branch allocates a structured
1093
+ * ComponentNotPresentError on every ordinary miss (and StaleEntityError for
1094
+ * a dangling handle). This predicate is deliberately non-throwing and
1095
+ * returns false for both cases; callers that need the detailed error should
1096
+ * continue to use `get`.
1097
+ */
1098
+ hasComponent(entity: EntityHandle, component: Component): boolean {
1099
+ const archetype = this.internalgetEntityArchetype(entity);
1100
+ return (
1101
+ archetype?.components.some(
1102
+ (candidate) => componentId(candidate) === componentId(component),
1103
+ ) === true
1104
+ );
1105
+ }
1106
+
1107
+ private internalgetArrayView(
1108
+ entity: EntityHandle,
1109
+ component: Component,
1110
+ fieldName: string,
1111
+ ): ArrayLike<number> | undefined {
1112
+ return this.componentAccess._getArrayView(entity, component, fieldName);
1113
+ }
1114
+
1115
+ set<S extends ComponentSchema, C extends Component<string, S>>(
1116
+ entity: EntityHandle,
1117
+ component: C & WritableComponent<C>,
1118
+ value: Partial<InputShapeOf<S>>,
1119
+ ): Result<void, EcsError> {
1120
+ if (isRelationshipTarget(component)) return this.relationshipTargetWriteError(component, 'set');
1121
+ return this.componentAccess.set(entity, component, value);
1122
+ }
1123
+
1124
+ addComponent<S extends ComponentSchema, C extends Component<string, S>>(
1125
+ entity: EntityHandle,
1126
+ componentData: ComponentData<S> & { component: C & WritableComponent<C> },
1127
+ ): Result<void, EcsError> {
1128
+ if (
1129
+ isRelationshipTarget(componentData.component) &&
1130
+ this.relationshipTargetPayloadWrites(componentData.data as Record<string, unknown>)
1131
+ )
1132
+ return this.relationshipTargetWriteError(componentData.component, 'addComponent');
1133
+ return this.componentAccess.addComponent(entity, componentData);
1134
+ }
1135
+
1136
+ private internaladdComponentCore<S extends ComponentSchema>(
1137
+ entity: EntityHandle,
1138
+ componentData: ComponentData<S>,
1139
+ internal: boolean,
1140
+ ): Result<void, EcsError> {
1141
+ return this.componentAccess._addComponentCore(entity, componentData, internal);
1142
+ }
1143
+
1144
+ removeComponent<S extends ComponentSchema, C extends Component<string, S>>(
1145
+ entity: EntityHandle,
1146
+ component: C & WritableComponent<C>,
1147
+ ): Result<void, EcsError> {
1148
+ if (isRelationshipTarget(component))
1149
+ return this.relationshipTargetWriteError(component, 'removeComponent');
1150
+ return this.componentAccess.removeComponent(entity, component);
1151
+ }
1152
+
1153
+ private internalremoveComponentCore<S extends ComponentSchema>(
1154
+ entity: EntityHandle,
1155
+ component: Component<string, S>,
1156
+ internal: boolean,
1157
+ ): Result<void, EcsError> {
1158
+ return this.componentAccess._removeComponentCore(entity, component, internal);
1159
+ }
1160
+
1161
+ private internalallocatePendingEntity(): EntityHandle {
1162
+ return this.componentAccess._allocatePendingEntity();
1163
+ }
1164
+ /** */ private internalcancelPendingEntity(entity: EntityHandle): void {
1165
+ this.componentAccess._cancelPendingEntity(entity);
1166
+ }
1167
+
1168
+ private internalmaterializePendingEntity(
1169
+ entity: EntityHandle,
1170
+ componentDatas: ComponentData[],
1171
+ ): Result<void, EcsError> {
1172
+ return this.componentAccess._materializePendingEntity(entity, componentDatas);
1173
+ }
1174
+
1175
+ /** Shared structural preflight for direct and deferred writes. */
1176
+ private internalpreflightComponentData(
1177
+ holder: EntityHandle | null,
1178
+ componentData: ComponentData,
1179
+ pendingEntities?: ReadonlySet<number>,
1180
+ unavailableEntities?: ReadonlySet<number>,
1181
+ ): Result<void, EcsError> {
1182
+ return this.componentAccess.preflightComponentData(
1183
+ holder,
1184
+ componentData,
1185
+ pendingEntities,
1186
+ unavailableEntities,
1187
+ );
1188
+ }
1189
+
1190
+ /** */ private internalallocateIndex(): number {
1191
+ return this.componentAccess.allocateIndex();
1192
+ }
1193
+ /** */ private internalrecordIsLive(r: EntityRecord | undefined, g: number): r is EntityRecord {
1194
+ return this.componentAccess.recordIsLive(r, g);
1195
+ }
1196
+ /** */ private internallookupAlive(
1197
+ e: EntityHandle,
1198
+ op: string,
1199
+ c?: string,
1200
+ ): Result<EntityRecord, EcsError> {
1201
+ return this.componentAccess.lookupAlive(e, op, c);
1202
+ }
1203
+ /** */ private internalreadRow<S extends ComponentSchema>(
1204
+ a: Archetype,
1205
+ c: Component<string, S>,
1206
+ r: number,
1207
+ ): ShapeOf<S> {
1208
+ return this.componentAccess.readRow(a, c, r);
1209
+ }
1210
+ /** */ private internalwriteEntitySelf(a: Archetype, r: number, h: EntityHandle): void {
1211
+ this.componentAccess.writeEntitySelf(a, r, h);
1212
+ }
1213
+ /** */ private internalwriteRow<S extends ComponentSchema>(
1214
+ a: Archetype,
1215
+ c: Component<string, S>,
1216
+ r: number,
1217
+ v: ShapeOf<S>,
1218
+ ): void {
1219
+ this.componentAccess.writeRow(a, c, r, v);
1220
+ }
1221
+ /** */ private internalreleaseManagedRefsOnRow(a: Archetype, c: Component, r: number): void {
1222
+ this.componentAccess.releaseManagedRefsOnRow(a, c, r);
1223
+ }
1224
+ /** */ private internalrelationshipOnInsert(
1225
+ h: EntityHandle,
1226
+ c: Component,
1227
+ v: Record<string, unknown>,
1228
+ ): Result<void, EcsError> {
1229
+ return this.componentAccess.relationshipOnInsert(h, c, v);
1230
+ }
1231
+ /** */ private internalrelationshipOnRemove(
1232
+ h: EntityHandle,
1233
+ c: Component,
1234
+ v: Record<string, unknown>,
1235
+ ): Result<void, EcsError> {
1236
+ return this.componentAccess.relationshipOnRemove(h, c, v);
1237
+ }
1238
+
1239
+ // ──────────────────────────────────────────────────────────────────────────
1240
+ // Spawn
1241
+ // ──────────────────────────────────────────────────────────────────────────
1242
+
1243
+ /**
1244
+ * Spawn an entity with one or more components.
1245
+ * Multi-component spawn directly targets the correct archetype (AC-06).
1246
+ *
1247
+ * @returns `Result<Entity, EcsError>` — `ok(Entity)` on success.
1248
+ * EntityIndexOverflowError still throws (build-time / infrastructure failure).
1249
+ *
1250
+ * @example
1251
+ * ```ts
1252
+ * const Position = defineComponent('Position', { x: 'f32', y: 'f32' });
1253
+ * const world = new World();
1254
+ * const r = world.spawn({ component: Position, data: { x: 0, y: 0 } });
1255
+ * if (!r.ok) { console.error(r.error.code); return; }
1256
+ * const entity = r.value;
1257
+ * ```
1258
+ */
1259
+ spawn<const SArr extends readonly ComponentSchema[]>(
1260
+ ...componentDatas: {
1261
+ [K in keyof SArr]: {
1262
+ component: Component<string, SArr[K]>;
1263
+ data: Partial<InputShapeOf<SArr[K]>>;
1264
+ };
1265
+ }
1266
+ ): Result<EntityHandle, EcsError>;
1267
+ spawn(...componentDatas: ComponentData[]): Result<EntityHandle, EcsError> {
1268
+ const target = componentDatas.find(
1269
+ (data) =>
1270
+ isRelationshipTarget(data.component) &&
1271
+ this.relationshipTargetPayloadWrites(data.data as Record<string, unknown>),
1272
+ );
1273
+ if (target !== undefined) return this.relationshipTargetWriteError(target.component, 'spawn');
1274
+ return spawnCore(this, componentDatas, false);
1275
+ }
1276
+
1277
+ /**
1278
+ * Core implementation of `spawn` with reentry guard.
1279
+ *
1280
+ * @param internal — `true` when called from relationship maintenance
1281
+ * (lazy mirror create or exclusive reparent).
1282
+ */
1283
+ private internalspawnCore(
1284
+ componentDatas: ComponentData[],
1285
+ internal: boolean,
1286
+ ): Result<EntityHandle, EcsError> {
1287
+ return spawnCore(this, componentDatas, internal);
1288
+ }
1289
+
1290
+ // ──────────────────────────────────────────────────────────────────────────
1291
+ // Despawn (D-08: generation retirement)
1292
+ // ──────────────────────────────────────────────────────────────────────────
1293
+
1294
+ /**
1295
+ * Despawn an entity. Stale handles are silently ignored (E-01, AC-17).
1296
+ * Generation retirement: gen=255 → index permanently retired (D-08/E-08).
1297
+ *
1298
+ * @returns `Result<void, EcsError>` — `ok(void)` always (idempotent on stale handles).
1299
+ *
1300
+ * @example
1301
+ * ```ts
1302
+ * const Position = defineComponent('Position', { x: 'f32', y: 'f32' });
1303
+ * const world = new World();
1304
+ * const e = world.spawn({ component: Position, data: { x: 0, y: 0 } }).unwrap();
1305
+ * const r = world.despawn(e);
1306
+ * r.unwrap(); // idempotent: ok(void) even on stale handle
1307
+ * ```
1308
+ */
1309
+ despawn(entity: EntityHandle): Result<void, EcsError> {
1310
+ return despawnCore(this, entity, false);
1311
+ }
1312
+
1313
+ /** Despawn every live entity through the normal lifecycle and ref cleanup path. */
1314
+ despawnAll(): Result<void, EcsError> {
1315
+ const entities: EntityHandle[] = [];
1316
+ for (let index = 0; index < this.records.length; index += 1) {
1317
+ const record = this.records[index];
1318
+ if (record !== undefined && record.archetypeId >= 0) {
1319
+ entities.push(encodeEntity(index, record.generation));
1320
+ }
1321
+ }
1322
+ for (const entity of entities) {
1323
+ const result = this.despawn(entity);
1324
+ if (!result.ok) return result;
1325
+ }
1326
+ return ok(undefined);
1327
+ }
1328
+
1329
+ /**
1330
+ * Core implementation of `despawn` with reentry guard.
1331
+ *
1332
+ * @param internal — `true` when called from within linkedSpawn cascade.
1333
+ * Nested despawn skips relationship pruning after the parent is retired;
1334
+ * the linkedSpawn collection still walks the subtree so grandchildren
1335
+ * cascade correctly (tweak-20260714 M2, R-6).
1336
+ */
1337
+ private internaldespawnCore(entity: EntityHandle, internal: boolean): Result<void, EcsError> {
1338
+ return despawnCore(this, entity, internal);
1339
+ }
1340
+
1341
+ // ──────────────────────────────────────────────────────────────────────────
1342
+ // Hierarchy facade — lifecycle orchestration lives in world-entity-lifecycle.
1343
+ // Component storage still owns typed relationship mutation primitives.
1344
+ // ──────────────────────────────────────────────────────────────────────────
1345
+
1346
+ addChild<S extends ComponentSchema>(
1347
+ parent: EntityHandle,
1348
+ child: EntityHandle,
1349
+ component: Component<string, S>,
1350
+ data: Partial<InputShapeOf<S>>,
1351
+ ): Result<void, EcsError> {
1352
+ return worldAddChild(this, parent, child, component, data);
1353
+ }
1354
+
1355
+ removeChild<S extends ComponentSchema>(
1356
+ parent: EntityHandle,
1357
+ child: EntityHandle,
1358
+ component: Component<string, S>,
1359
+ ): Result<void, EcsError> {
1360
+ return worldRemoveChild(this, parent, child, component);
1361
+ }
1362
+
1363
+ reparent<S extends ComponentSchema>(
1364
+ child: EntityHandle,
1365
+ newParent: EntityHandle,
1366
+ component: Component<string, S>,
1367
+ data: Partial<InputShapeOf<S>>,
1368
+ ): Result<void, EcsError> {
1369
+ return worldReparent(this, child, newParent, component, data);
1370
+ }
1371
+
1372
+ iterAncestors(entity: EntityHandle): Iterable<EntityHandle> {
1373
+ return worldIterAncestors(this, entity);
1374
+ }
1375
+
1376
+ iterDescendants(entity: EntityHandle): Iterable<EntityHandle> {
1377
+ return worldIterDescendants(this, entity);
1378
+ }
1379
+ }
1380
+
1381
+ /** Freeze the detached POD produced by World.inspect(). */
1382
+ function detachWorldInspection<T extends object>(snapshot: T): Readonly<T> {
1383
+ return freezeInspection(snapshot);
1384
+ }
1385
+
1386
+ function freezeInspection<T>(value: T): Readonly<T> {
1387
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
1388
+ return value as Readonly<T>;
1389
+ }
1390
+ for (const key of Reflect.ownKeys(value as object)) {
1391
+ const child = (value as Record<PropertyKey, unknown>)[key];
1392
+ if (child !== null && (typeof child === 'object' || typeof child === 'function')) {
1393
+ freezeInspection(child);
1394
+ }
1395
+ }
1396
+ return Object.freeze(value) as Readonly<T>;
1397
+ }