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