@forgeax/engine-ecs 0.0.0-dev.8d955ade1c79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +278 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__fixtures__/field-shape-fixture.d.ts +16 -0
  5. package/dist/__fixtures__/field-shape-fixture.d.ts.map +1 -0
  6. package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts +3 -0
  7. package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts.map +1 -0
  8. package/dist/__tests__/archetype.unit.test.d.ts +2 -0
  9. package/dist/__tests__/archetype.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/array-handle-element.test-d.d.ts +2 -0
  11. package/dist/__tests__/array-handle-element.test-d.d.ts.map +1 -0
  12. package/dist/__tests__/buffer-array-vocab.test-d.d.ts +2 -0
  13. package/dist/__tests__/buffer-array-vocab.test-d.d.ts.map +1 -0
  14. package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts +2 -0
  15. package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/buffer-pool.test-d.d.ts +2 -0
  17. package/dist/__tests__/buffer-pool.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/change-detection.unit.test.d.ts +2 -0
  19. package/dist/__tests__/change-detection.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/command-buffer.test.d.ts +2 -0
  21. package/dist/__tests__/command-buffer.test.d.ts.map +1 -0
  22. package/dist/__tests__/commands-deferred.unit.test.d.ts +2 -0
  23. package/dist/__tests__/commands-deferred.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/component-catalog.test.d.ts +2 -0
  25. package/dist/__tests__/component-catalog.test.d.ts.map +1 -0
  26. package/dist/__tests__/component-definition-identity.unit.test.d.ts +2 -0
  27. package/dist/__tests__/component-definition-identity.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/component-nan-preflight.unit.test.d.ts +2 -0
  29. package/dist/__tests__/component-nan-preflight.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/component-schema.test-d.d.ts +2 -0
  31. package/dist/__tests__/component-schema.test-d.d.ts.map +1 -0
  32. package/dist/__tests__/component-schema.test.d.ts +2 -0
  33. package/dist/__tests__/component-schema.test.d.ts.map +1 -0
  34. package/dist/__tests__/define-component-options.test-d.d.ts +2 -0
  35. package/dist/__tests__/define-component-options.test-d.d.ts.map +1 -0
  36. package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts +2 -0
  37. package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/despawn-all.unit.test.d.ts +2 -0
  39. package/dist/__tests__/despawn-all.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts +2 -0
  41. package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts.map +1 -0
  42. package/dist/__tests__/ecs-core-reduction.perf.test.d.ts +2 -0
  43. package/dist/__tests__/ecs-core-reduction.perf.test.d.ts.map +1 -0
  44. package/dist/__tests__/ecs-error-code-31.test-d.d.ts +2 -0
  45. package/dist/__tests__/ecs-error-code-31.test-d.d.ts.map +1 -0
  46. package/dist/__tests__/entity-disabling.unit.test.d.ts +2 -0
  47. package/dist/__tests__/entity-disabling.unit.test.d.ts.map +1 -0
  48. package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts +2 -0
  49. package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  51. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts +2 -0
  53. package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/execution-shared-storage.unit.test.d.ts +2 -0
  55. package/dist/__tests__/execution-shared-storage.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/externalization-portable-value.unit.test.d.ts +2 -0
  57. package/dist/__tests__/externalization-portable-value.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/externalization-projection.unit.test.d.ts +2 -0
  59. package/dist/__tests__/externalization-projection.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/externalization-render-read-lease.test-d.d.ts +2 -0
  61. package/dist/__tests__/externalization-render-read-lease.test-d.d.ts.map +1 -0
  62. package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts +2 -0
  63. package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/externalization-root-surface.unit.test.d.ts +2 -0
  65. package/dist/__tests__/externalization-root-surface.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/field-enum-labels-reflection.test.d.ts +2 -0
  67. package/dist/__tests__/field-enum-labels-reflection.test.d.ts.map +1 -0
  68. package/dist/__tests__/field-shape-fixture.test.d.ts +2 -0
  69. package/dist/__tests__/field-shape-fixture.test.d.ts.map +1 -0
  70. package/dist/__tests__/field-transient-reflection.test.d.ts +2 -0
  71. package/dist/__tests__/field-transient-reflection.test.d.ts.map +1 -0
  72. package/dist/__tests__/field-value-type.test-d.d.ts +2 -0
  73. package/dist/__tests__/field-value-type.test-d.d.ts.map +1 -0
  74. package/dist/__tests__/fixed-update.unit.test.d.ts +2 -0
  75. package/dist/__tests__/fixed-update.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/handle.test-d.d.ts +2 -0
  77. package/dist/__tests__/handle.test-d.d.ts.map +1 -0
  78. package/dist/__tests__/hierarchy.unit.test.d.ts +2 -0
  79. package/dist/__tests__/hierarchy.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/managed-array-vocab.test-d.d.ts +2 -0
  81. package/dist/__tests__/managed-array-vocab.test-d.d.ts.map +1 -0
  82. package/dist/__tests__/minimal-example.test-d.d.ts +2 -0
  83. package/dist/__tests__/minimal-example.test-d.d.ts.map +1 -0
  84. package/dist/__tests__/name-read-types.test-d.d.ts +2 -0
  85. package/dist/__tests__/name-read-types.test-d.d.ts.map +1 -0
  86. package/dist/__tests__/plugin-service.test.d.ts +2 -0
  87. package/dist/__tests__/plugin-service.test.d.ts.map +1 -0
  88. package/dist/__tests__/query-combinations.unit.test.d.ts +2 -0
  89. package/dist/__tests__/query-combinations.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts +2 -0
  91. package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts.map +1 -0
  92. package/dist/__tests__/query-storage-trends.perf.test.d.ts +2 -0
  93. package/dist/__tests__/query-storage-trends.perf.test.d.ts.map +1 -0
  94. package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts +2 -0
  95. package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts.map +1 -0
  96. package/dist/__tests__/query.unit.test.d.ts +2 -0
  97. package/dist/__tests__/query.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/relationship-index.perf.test.d.ts +2 -0
  99. package/dist/__tests__/relationship-index.perf.test.d.ts.map +1 -0
  100. package/dist/__tests__/relationship-index.test.d.ts +2 -0
  101. package/dist/__tests__/relationship-index.test.d.ts.map +1 -0
  102. package/dist/__tests__/scalar-field-type-owner.test-d.d.ts +2 -0
  103. package/dist/__tests__/scalar-field-type-owner.test-d.d.ts.map +1 -0
  104. package/dist/__tests__/schedule-determinism.unit.test.d.ts +2 -0
  105. package/dist/__tests__/schedule-determinism.unit.test.d.ts.map +1 -0
  106. package/dist/__tests__/schedule-inspection.unit.test.d.ts +2 -0
  107. package/dist/__tests__/schedule-inspection.unit.test.d.ts.map +1 -0
  108. package/dist/__tests__/schedule-scope-error.unit.test.d.ts +2 -0
  109. package/dist/__tests__/schedule-scope-error.unit.test.d.ts.map +1 -0
  110. package/dist/__tests__/schedule-token.unit.test.d.ts +2 -0
  111. package/dist/__tests__/schedule-token.unit.test.d.ts.map +1 -0
  112. package/dist/__tests__/schema-vocab.test-d.d.ts +2 -0
  113. package/dist/__tests__/schema-vocab.test-d.d.ts.map +1 -0
  114. package/dist/__tests__/serialization.unit.test.d.ts +2 -0
  115. package/dist/__tests__/serialization.unit.test.d.ts.map +1 -0
  116. package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts +2 -0
  117. package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts.map +1 -0
  118. package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts +2 -0
  119. package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts.map +1 -0
  120. package/dist/__tests__/shared-ref-store.unit.test.d.ts +2 -0
  121. package/dist/__tests__/shared-ref-store.unit.test.d.ts.map +1 -0
  122. package/dist/__tests__/sparse-tag.unit.test.d.ts +2 -0
  123. package/dist/__tests__/sparse-tag.unit.test.d.ts.map +1 -0
  124. package/dist/__tests__/spawn-field-error.test-d.d.ts +2 -0
  125. package/dist/__tests__/spawn-field-error.test-d.d.ts.map +1 -0
  126. package/dist/__tests__/stale-error.test-d.d.ts +2 -0
  127. package/dist/__tests__/stale-error.test-d.d.ts.map +1 -0
  128. package/dist/__tests__/string-view-symmetry.test-d.d.ts +2 -0
  129. package/dist/__tests__/string-view-symmetry.test-d.d.ts.map +1 -0
  130. package/dist/__tests__/string-vocab.test-d.d.ts +2 -0
  131. package/dist/__tests__/string-vocab.test-d.d.ts.map +1 -0
  132. package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts +2 -0
  133. package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts.map +1 -0
  134. package/dist/__tests__/structure-epoch.unit.test.d.ts +2 -0
  135. package/dist/__tests__/structure-epoch.unit.test.d.ts.map +1 -0
  136. package/dist/__tests__/table-archetype-mapping.unit.test.d.ts +2 -0
  137. package/dist/__tests__/table-archetype-mapping.unit.test.d.ts.map +1 -0
  138. package/dist/__tests__/time.unit.test.d.ts +2 -0
  139. package/dist/__tests__/time.unit.test.d.ts.map +1 -0
  140. package/dist/__tests__/types.test-d.d.ts +2 -0
  141. package/dist/__tests__/types.test-d.d.ts.map +1 -0
  142. package/dist/__tests__/unique-ref-store.unit.test.d.ts +2 -0
  143. package/dist/__tests__/unique-ref-store.unit.test.d.ts.map +1 -0
  144. package/dist/__tests__/utils/handle-numeric.d.ts +14 -0
  145. package/dist/__tests__/utils/handle-numeric.d.ts.map +1 -0
  146. package/dist/__tests__/validation-root-surface.unit.test.d.ts +2 -0
  147. package/dist/__tests__/validation-root-surface.unit.test.d.ts.map +1 -0
  148. package/dist/__tests__/visibility-atomic-write.unit.test.d.ts +2 -0
  149. package/dist/__tests__/visibility-atomic-write.unit.test.d.ts.map +1 -0
  150. package/dist/__tests__/world-change-journal.unit.test.d.ts +2 -0
  151. package/dist/__tests__/world-change-journal.unit.test.d.ts.map +1 -0
  152. package/dist/__tests__/world-characterization.unit.test.d.ts +2 -0
  153. package/dist/__tests__/world-characterization.unit.test.d.ts.map +1 -0
  154. package/dist/__tests__/world-consumer-inference.test-d.d.ts +2 -0
  155. package/dist/__tests__/world-consumer-inference.test-d.d.ts.map +1 -0
  156. package/dist/__tests__/world-health.contract.test.d.ts +2 -0
  157. package/dist/__tests__/world-health.contract.test.d.ts.map +1 -0
  158. package/dist/__tests__/world-inspection.test.d.ts +2 -0
  159. package/dist/__tests__/world-inspection.test.d.ts.map +1 -0
  160. package/dist/__tests__/world-internal-identity.unit.test.d.ts +2 -0
  161. package/dist/__tests__/world-internal-identity.unit.test.d.ts.map +1 -0
  162. package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts +2 -0
  163. package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts.map +1 -0
  164. package/dist/__tests__/world-scheduling.m4.test.d.ts +2 -0
  165. package/dist/__tests__/world-scheduling.m4.test.d.ts.map +1 -0
  166. package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts +2 -0
  167. package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts.map +1 -0
  168. package/dist/__tests__/world.test-d.d.ts +2 -0
  169. package/dist/__tests__/world.test-d.d.ts.map +1 -0
  170. package/dist/buffer-pool.d.ts +109 -0
  171. package/dist/buffer-pool.d.ts.map +1 -0
  172. package/dist/commands.d.ts +93 -0
  173. package/dist/commands.d.ts.map +1 -0
  174. package/dist/component-default-fallback.d.ts +88 -0
  175. package/dist/component-default-fallback.d.ts.map +1 -0
  176. package/dist/component-schema.d.ts +34 -0
  177. package/dist/component-schema.d.ts.map +1 -0
  178. package/dist/component-value-validate.d.ts +29 -0
  179. package/dist/component-value-validate.d.ts.map +1 -0
  180. package/dist/component.d.ts +581 -0
  181. package/dist/component.d.ts.map +1 -0
  182. package/dist/entity-handle.d.ts +68 -0
  183. package/dist/entity-handle.d.ts.map +1 -0
  184. package/dist/entity.d.ts +86 -0
  185. package/dist/entity.d.ts.map +1 -0
  186. package/dist/errors/query-and-component-errors.d.ts +132 -0
  187. package/dist/errors/query-and-component-errors.d.ts.map +1 -0
  188. package/dist/errors/relationship-errors.d.ts +112 -0
  189. package/dist/errors/relationship-errors.d.ts.map +1 -0
  190. package/dist/errors/sprite-and-shared-errors.d.ts +107 -0
  191. package/dist/errors/sprite-and-shared-errors.d.ts.map +1 -0
  192. package/dist/errors/validation-errors.d.ts +229 -0
  193. package/dist/errors/validation-errors.d.ts.map +1 -0
  194. package/dist/errors.d.ts +763 -0
  195. package/dist/errors.d.ts.map +1 -0
  196. package/dist/execution/shared-kernel.d.ts +106 -0
  197. package/dist/execution/shared-kernel.d.ts.map +1 -0
  198. package/dist/externalization/index.d.ts +31 -0
  199. package/dist/externalization/index.d.ts.map +1 -0
  200. package/dist/externalization/index.mjs +298 -0
  201. package/dist/externalization/index.mjs.map +1 -0
  202. package/dist/index.d.ts +261 -0
  203. package/dist/index.d.ts.map +1 -0
  204. package/dist/index.mjs +7422 -0
  205. package/dist/index.mjs.map +1 -0
  206. package/dist/internal.mjs +479 -0
  207. package/dist/internal.mjs.map +1 -0
  208. package/dist/plugin-service.d.ts +11 -0
  209. package/dist/plugin-service.d.ts.map +1 -0
  210. package/dist/projection/index.d.ts +103 -0
  211. package/dist/projection/index.d.ts.map +1 -0
  212. package/dist/projection/index.mjs +615 -0
  213. package/dist/projection/index.mjs.map +1 -0
  214. package/dist/query/query.d.ts +58 -0
  215. package/dist/query/query.d.ts.map +1 -0
  216. package/dist/relationship-index.d.ts +81 -0
  217. package/dist/relationship-index.d.ts.map +1 -0
  218. package/dist/resource.d.ts +70 -0
  219. package/dist/resource.d.ts.map +1 -0
  220. package/dist/schedule-token.d.ts +16 -0
  221. package/dist/schedule-token.d.ts.map +1 -0
  222. package/dist/schedule.d.ts +271 -0
  223. package/dist/schedule.d.ts.map +1 -0
  224. package/dist/shared-ref-store.d.ts +144 -0
  225. package/dist/shared-ref-store.d.ts.map +1 -0
  226. package/dist/shared.d.ts +3 -0
  227. package/dist/shared.d.ts.map +1 -0
  228. package/dist/shared.mjs +364 -0
  229. package/dist/shared.mjs.map +1 -0
  230. package/dist/storage/archetype-graph.d.ts +54 -0
  231. package/dist/storage/archetype-graph.d.ts.map +1 -0
  232. package/dist/storage/archetype.d.ts +23 -0
  233. package/dist/storage/archetype.d.ts.map +1 -0
  234. package/dist/storage/change-detection.d.ts +40 -0
  235. package/dist/storage/change-detection.d.ts.map +1 -0
  236. package/dist/storage/column.d.ts +111 -0
  237. package/dist/storage/column.d.ts.map +1 -0
  238. package/dist/storage/table.d.ts +29 -0
  239. package/dist/storage/table.d.ts.map +1 -0
  240. package/dist/time.d.ts +57 -0
  241. package/dist/time.d.ts.map +1 -0
  242. package/dist/unique-ref-store.d.ts +105 -0
  243. package/dist/unique-ref-store.d.ts.map +1 -0
  244. package/dist/world-change-journal.d.ts +34 -0
  245. package/dist/world-change-journal.d.ts.map +1 -0
  246. package/dist/world-component-access.d.ts +265 -0
  247. package/dist/world-component-access.d.ts.map +1 -0
  248. package/dist/world-component-storage.d.ts +282 -0
  249. package/dist/world-component-storage.d.ts.map +1 -0
  250. package/dist/world-core.d.ts +39 -0
  251. package/dist/world-core.d.ts.map +1 -0
  252. package/dist/world-entity-lifecycle.d.ts +30 -0
  253. package/dist/world-entity-lifecycle.d.ts.map +1 -0
  254. package/dist/world-internal.d.ts +14 -0
  255. package/dist/world-internal.d.ts.map +1 -0
  256. package/dist/world-scheduling.d.ts +30 -0
  257. package/dist/world-scheduling.d.ts.map +1 -0
  258. package/dist/world.d.ts +620 -0
  259. package/dist/world.d.ts.map +1 -0
  260. package/package.json +77 -0
  261. package/src/__fixtures__/field-shape-fixture.ts +30 -0
  262. package/src/__tests__/__fixtures__/inspect-scripts.snapshot.ts +2 -0
  263. package/src/__tests__/archetype.unit.test.ts +1837 -0
  264. package/src/__tests__/array-handle-element.test-d.ts +34 -0
  265. package/src/__tests__/buffer-array-vocab.test-d.ts +53 -0
  266. package/src/__tests__/buffer-field-input-widen.unit.test.ts +99 -0
  267. package/src/__tests__/buffer-pool.test-d.ts +32 -0
  268. package/src/__tests__/change-detection.unit.test.ts +86 -0
  269. package/src/__tests__/command-buffer.test.ts +179 -0
  270. package/src/__tests__/commands-deferred.unit.test.ts +68 -0
  271. package/src/__tests__/component-catalog.test.ts +59 -0
  272. package/src/__tests__/component-definition-identity.unit.test.ts +24 -0
  273. package/src/__tests__/component-nan-preflight.unit.test.ts +186 -0
  274. package/src/__tests__/component-schema.test-d.ts +119 -0
  275. package/src/__tests__/component-schema.test.ts +55 -0
  276. package/src/__tests__/define-component-options.test-d.ts +35 -0
  277. package/src/__tests__/deprecated-vocab-rejection.unit.test.ts +59 -0
  278. package/src/__tests__/despawn-all.unit.test.ts +17 -0
  279. package/src/__tests__/ecs-core-reduction.characterization.test.ts +130 -0
  280. package/src/__tests__/ecs-core-reduction.perf.test.ts +260 -0
  281. package/src/__tests__/ecs-error-code-31.test-d.ts +42 -0
  282. package/src/__tests__/entity-disabling.unit.test.ts +37 -0
  283. package/src/__tests__/entity-handle-codec-parity.unit.test.ts +126 -0
  284. package/src/__tests__/errors.unit.test.ts +1456 -0
  285. package/src/__tests__/execution-conflict-boundary.unit.test.ts +123 -0
  286. package/src/__tests__/execution-shared-storage.unit.test.ts +40 -0
  287. package/src/__tests__/externalization-portable-value.unit.test.ts +275 -0
  288. package/src/__tests__/externalization-projection.unit.test.ts +325 -0
  289. package/src/__tests__/externalization-render-read-lease.test-d.ts +30 -0
  290. package/src/__tests__/externalization-render-read-lease.unit.test.ts +79 -0
  291. package/src/__tests__/externalization-root-surface.unit.test.ts +33 -0
  292. package/src/__tests__/field-enum-labels-reflection.test.ts +66 -0
  293. package/src/__tests__/field-shape-fixture.test.ts +61 -0
  294. package/src/__tests__/field-transient-reflection.test.ts +59 -0
  295. package/src/__tests__/field-value-type.test-d.ts +57 -0
  296. package/src/__tests__/fixed-update.unit.test.ts +78 -0
  297. package/src/__tests__/handle.test-d.ts +86 -0
  298. package/src/__tests__/hierarchy.unit.test.ts +2534 -0
  299. package/src/__tests__/managed-array-vocab.test-d.ts +137 -0
  300. package/src/__tests__/minimal-example.test-d.ts +42 -0
  301. package/src/__tests__/name-read-types.test-d.ts +72 -0
  302. package/src/__tests__/plugin-service.test.ts +13 -0
  303. package/src/__tests__/query-combinations.unit.test.ts +42 -0
  304. package/src/__tests__/query-light-extract-flat-column-ratio.perf.test.ts +252 -0
  305. package/src/__tests__/query-storage-trends.perf.test.ts +104 -0
  306. package/src/__tests__/query-trs-flat-column-ratio.perf.test.ts +194 -0
  307. package/src/__tests__/query.unit.test.ts +196 -0
  308. package/src/__tests__/relationship-index.perf.test.ts +126 -0
  309. package/src/__tests__/relationship-index.test.ts +70 -0
  310. package/src/__tests__/scalar-field-type-owner.test-d.ts +71 -0
  311. package/src/__tests__/schedule-determinism.unit.test.ts +36 -0
  312. package/src/__tests__/schedule-inspection.unit.test.ts +81 -0
  313. package/src/__tests__/schedule-scope-error.unit.test.ts +63 -0
  314. package/src/__tests__/schedule-token.unit.test.ts +54 -0
  315. package/src/__tests__/schema-vocab.test-d.ts +103 -0
  316. package/src/__tests__/serialization.unit.test.ts +2740 -0
  317. package/src/__tests__/shared-kernel-eligibility.test-d.ts +18 -0
  318. package/src/__tests__/shared-kernel-eligibility.unit.test.ts +78 -0
  319. package/src/__tests__/shared-ref-store.unit.test.ts +646 -0
  320. package/src/__tests__/sparse-tag.unit.test.ts +201 -0
  321. package/src/__tests__/spawn-field-error.test-d.ts +41 -0
  322. package/src/__tests__/stale-error.test-d.ts +208 -0
  323. package/src/__tests__/string-view-symmetry.test-d.ts +26 -0
  324. package/src/__tests__/string-vocab.test-d.ts +49 -0
  325. package/src/__tests__/structure-epoch-boundaries.unit.test.ts +39 -0
  326. package/src/__tests__/structure-epoch.unit.test.ts +48 -0
  327. package/src/__tests__/table-archetype-mapping.unit.test.ts +78 -0
  328. package/src/__tests__/time.unit.test.ts +79 -0
  329. package/src/__tests__/types.test-d.ts +38 -0
  330. package/src/__tests__/unique-ref-store.unit.test.ts +348 -0
  331. package/src/__tests__/utils/handle-numeric.ts +30 -0
  332. package/src/__tests__/validation-root-surface.unit.test.ts +15 -0
  333. package/src/__tests__/visibility-atomic-write.unit.test.ts +82 -0
  334. package/src/__tests__/world-change-journal.unit.test.ts +97 -0
  335. package/src/__tests__/world-characterization.unit.test.ts +408 -0
  336. package/src/__tests__/world-consumer-inference.test-d.ts +16 -0
  337. package/src/__tests__/world-health.contract.test.ts +87 -0
  338. package/src/__tests__/world-inspection.test.ts +37 -0
  339. package/src/__tests__/world-internal-identity.unit.test.ts +17 -0
  340. package/src/__tests__/world-managed-roundtrip.unit.test.ts +98 -0
  341. package/src/__tests__/world-scheduling.m4.test.ts +52 -0
  342. package/src/__tests__/world-unique-roundtrip.unit.test.ts +357 -0
  343. package/src/__tests__/world.test-d.ts +156 -0
  344. package/src/buffer-pool.ts +357 -0
  345. package/src/commands.ts +644 -0
  346. package/src/component-default-fallback.ts +236 -0
  347. package/src/component-schema.ts +92 -0
  348. package/src/component-value-validate.ts +135 -0
  349. package/src/component.ts +1268 -0
  350. package/src/entity-handle.ts +104 -0
  351. package/src/entity.ts +140 -0
  352. package/src/errors/query-and-component-errors.ts +200 -0
  353. package/src/errors/relationship-errors.ts +198 -0
  354. package/src/errors/sprite-and-shared-errors.ts +231 -0
  355. package/src/errors/validation-errors.ts +506 -0
  356. package/src/errors.ts +1464 -0
  357. package/src/execution/shared-kernel.ts +392 -0
  358. package/src/externalization/index.ts +170 -0
  359. package/src/index.ts +414 -0
  360. package/src/plugin-service.ts +35 -0
  361. package/src/projection/index.ts +318 -0
  362. package/src/query/query.ts +606 -0
  363. package/src/relationship-index.ts +175 -0
  364. package/src/resource.ts +106 -0
  365. package/src/schedule-token.ts +40 -0
  366. package/src/schedule.ts +846 -0
  367. package/src/shared-ref-store.ts +418 -0
  368. package/src/shared.ts +23 -0
  369. package/src/storage/archetype-graph.ts +166 -0
  370. package/src/storage/archetype.ts +78 -0
  371. package/src/storage/change-detection.ts +231 -0
  372. package/src/storage/column.ts +247 -0
  373. package/src/storage/table.ts +153 -0
  374. package/src/time.ts +122 -0
  375. package/src/unique-ref-store.ts +278 -0
  376. package/src/world-change-journal.ts +84 -0
  377. package/src/world-component-access.ts +1421 -0
  378. package/src/world-component-storage.ts +1178 -0
  379. package/src/world-core.ts +74 -0
  380. package/src/world-entity-lifecycle.ts +539 -0
  381. package/src/world-internal.ts +75 -0
  382. package/src/world-scheduling.ts +499 -0
  383. package/src/world.ts +1399 -0
@@ -0,0 +1,846 @@
1
+ // @forgeax/engine-ecs — DAG Schedule: Kahn topological sort + cycle detection.
2
+ //
3
+ // addSystem registers a system + marks dirty. First update() triggers buildSchedule().
4
+ // Same in-degree systems ordered by addSystem call order (stable tie-breaker, D-05).
5
+
6
+ import { err, ok, type Result } from '@forgeax/engine-types';
7
+ import { type CommandBuffer, createCommandBuffer, flushCommands } from './commands';
8
+ import type { Component } from './component';
9
+ import {
10
+ CyclicDependencyError,
11
+ ScheduleMutationError,
12
+ SystemFailedError,
13
+ type SystemSetNotRegisteredError,
14
+ systemSetNotRegistered,
15
+ } from './errors';
16
+ import type { Query, QueryDescriptor } from './query/query';
17
+ // type-only import: erases at build time, carries no runtime edge (same
18
+ // criterion as scripts/check-ecs-no-runtime-import.mjs). `world.ts` already
19
+ // value-imports `schedule.ts`; this back-reference is type-space only so no
20
+ // runtime cycle forms (plan-strategy D-1).
21
+ import type { ScheduleToken } from './schedule-token';
22
+ import type { World } from './world';
23
+ import { worldInternal } from './world-internal';
24
+
25
+ // ────────────────────────────────────────────────────────────────────────────
26
+ // Types
27
+ // ────────────────────────────────────────────────────────────────────────────
28
+
29
+ type SystemQueryResults<
30
+ Qs extends ReadonlyArray<QueryDescriptor> = ReadonlyArray<QueryDescriptor>,
31
+ > = {
32
+ [K in keyof Qs]: Qs[K] extends QueryDescriptor<
33
+ infer R extends ReadonlyArray<Component>,
34
+ infer W extends ReadonlyArray<Component>,
35
+ infer O extends ReadonlyArray<Component>
36
+ >
37
+ ? Query<NoInfer<R>, NoInfer<W>, NoInfer<O>>
38
+ : Query;
39
+ };
40
+
41
+ /**
42
+ * System descriptor passed to `world.addSystem` — `fn` receives one persistent
43
+ * executable Query per descriptor, mapped over `Qs` without casts.
44
+ *
45
+ * `Qs` is the tuple of query descriptors; defaults to
46
+ * `readonly QueryDescriptor[]` so non-generic call sites stay zero-modification
47
+ * (KD-5). The `fn` first parameter is mapped over `Qs` so each
48
+ * `queryResults[i]` recovers its own row access roles (S-5, KD-2).
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * // Single-query system — row access recovers each component schema.
53
+ * import { defineComponent, World } from '@forgeax/engine-ecs';
54
+ *
55
+ * const Position = defineComponent('Position', { x: 'f32', y: 'f32' });
56
+ * const Velocity = defineComponent('Velocity', { dx: 'f32', dy: 'f32' });
57
+ *
58
+ * const world = new World();
59
+ * world.addSystem(Update, {
60
+ * name: 'movement',
61
+ * queries: [{ write: [Position], read: [Velocity] }],
62
+ * fn: (_world, queryResults, _commands) => {
63
+ * for (const row of queryResults[0]) {
64
+ * row.mut(Position).x += row.get(Velocity).dx;
65
+ * }
66
+ * },
67
+ * });
68
+ * ```
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * // Multi-query system — each queryResults[i] keeps its own row shape.
73
+ * const Health = defineComponent('Health', { hp: 'f32' });
74
+ * world.addSystem(Update, {
75
+ * name: 'multi',
76
+ * queries: [{ read: [Position] }, { read: [Health] }],
77
+ * fn: (_world, queryResults) => {
78
+ * for (const row of queryResults[0]) void row.get(Position).x;
79
+ * for (const row of queryResults[1]) void row.get(Health).hp;
80
+ * },
81
+ * });
82
+ * ```
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * // Commands-only system — `queries: []` is a legal form; fn receives `[]`.
87
+ * world.addSystem(Update, {
88
+ * name: 'spawner',
89
+ * queries: [],
90
+ * fn: (_world, _queryResults, commands) => {
91
+ * commands.spawn({ component: Position, data: { x: 0, y: 0 } });
92
+ * },
93
+ * });
94
+ * ```
95
+ */
96
+ export interface SystemDescriptor<
97
+ Qs extends ReadonlyArray<QueryDescriptor> = ReadonlyArray<QueryDescriptor>,
98
+ > {
99
+ /** Unique system name (used for before/after references). */
100
+ readonly name: string;
101
+ /** Query descriptors this system reads. */
102
+ readonly queries: Qs;
103
+ /**
104
+ * System function — receives the World, resolved query results, and commands.
105
+ *
106
+ * @param world The owning World — read resources (`world.getResource(KEY)`),
107
+ * resolve components by name, etc. without closure capture.
108
+ * @param queryResults Mapped over `Qs`: each entry is a persistent Query.
109
+ * @param commands Deferred-mutation buffer (flushed after the system).
110
+ */
111
+ readonly fn: (
112
+ world: World,
113
+ queryResults: SystemQueryResults<Qs>,
114
+ commands: CommandBuffer,
115
+ ) => void | unknown;
116
+ /** Run this system after named systems or the FixedUpdate anchor. */
117
+ readonly after?: ReadonlyArray<string | ScheduleToken>;
118
+ /** Run this system before named systems or the FixedUpdate anchor. */
119
+ readonly before?: ReadonlyArray<string | ScheduleToken>;
120
+ /**
121
+ * Run condition. Evaluated each frame after ParamValidation passes (tag
122
+ * 'ok') and before query iteration. Returning `false` skips the system silently —
123
+ * no query runs, no fn call, no state added (plan-strategy D-8). Omitting it
124
+ * (undefined) always runs the system.
125
+ */
126
+ readonly runIf?: (world: World) => boolean;
127
+ }
128
+
129
+ /**
130
+ * A registered system token returned by {@link defineSystem}. Structurally the
131
+ * frozen {@link SystemDescriptor} itself (plan-strategy D-6 — "define ==
132
+ * register"). `world.addSystem(handle)` consumes it directly; the generic `Qs`
133
+ * flows through so the `fn` per-query row access shapes survive (S-5).
134
+ */
135
+ export type SystemHandle<
136
+ Qs extends ReadonlyArray<QueryDescriptor> = ReadonlyArray<QueryDescriptor>,
137
+ > = SystemDescriptor<Qs>;
138
+
139
+ /** Internal system record with registration index. */
140
+ interface SystemRecord {
141
+ descriptor: SystemDescriptor;
142
+ /** Registration order index (for tie-breaking). */
143
+ registrationIndex: number;
144
+ /** Cached executable Queries, one per descriptor. */
145
+ queries: Query[] | null;
146
+ }
147
+
148
+ // ────────────────────────────────────────────────────────────────────────────
149
+ // Schedule
150
+ // ────────────────────────────────────────────────────────────────────────────
151
+
152
+ /**
153
+ * Per-set record in the Schedule. Created lazily on first addSystems /
154
+ * addSystems call for a given set name.
155
+ */
156
+ export interface SetRecord {
157
+ /** Member system names (insertion-ordered, Set preserves add order). */
158
+ readonly members: Set<string>;
159
+ /** Snapshot of runIf from the defining token. */
160
+ readonly runIf: ((world: import('./world').World) => boolean) | undefined;
161
+ /** Snapshot of chained from the defining token. */
162
+ readonly chained: boolean;
163
+ }
164
+
165
+ /** The Schedule manages system registration, DAG sorting, and execution. */
166
+ export interface Schedule {
167
+ /** Owning token for scope-aware diagnostics. */
168
+ readonly token: ScheduleToken;
169
+ /** All registered systems by name. */
170
+ systems: Map<string, SystemRecord>;
171
+ /** Set records keyed by set name. Created lazily. */
172
+ sets: Map<string, SetRecord>;
173
+ /** Next registration index. */
174
+ nextIndex: number;
175
+ /** Whether the sorted order is stale. */
176
+ dirty: boolean;
177
+ /** Sorted system names after buildSchedule(). */
178
+ sortedOrder: string[];
179
+ /** Direct predecessor names derived while the DAG is built. */
180
+ predecessors: Map<string, Set<string>>;
181
+ }
182
+
183
+ /** Create a fresh Schedule. */
184
+ export function createSchedule(token: ScheduleToken): Schedule {
185
+ return {
186
+ token,
187
+ systems: new Map(),
188
+ sets: new Map(),
189
+ nextIndex: 0,
190
+ dirty: true,
191
+ sortedOrder: [],
192
+ predecessors: new Map(),
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Register a system. Marks schedule as dirty.
198
+ * Queries are lazily initialized on first runSchedule.
199
+ *
200
+ * `const Qs` locks the `queries` tuple at the call site so `fn`'s first
201
+ * parameter recovers per-query row shapes without `as const` annotations
202
+ * (S-5, KD-2). `SystemRecord` itself is intentionally non-generic — the
203
+ * heterogeneous `Qs` cannot be expressed inside the systems Map (KD-3).
204
+ */
205
+ export function addSystem<const Qs extends ReadonlyArray<QueryDescriptor>>(
206
+ schedule: Schedule,
207
+ descriptor: SystemDescriptor<Qs>,
208
+ ): void {
209
+ const record: SystemRecord = {
210
+ descriptor: descriptor as unknown as SystemDescriptor,
211
+ registrationIndex: schedule.nextIndex++,
212
+ queries: null,
213
+ };
214
+ schedule.systems.set(descriptor.name, record);
215
+ schedule.dirty = true;
216
+ }
217
+
218
+ // ────────────────────────────────────────────────────────────────────────────
219
+ // System token construction (token-first World ownership)
220
+ // ────────────────────────────────────────────────────────────────────────────
221
+
222
+ /**
223
+ * Define a system at module level. Returns a frozen {@link SystemHandle} token
224
+ * (the descriptor itself); executable registration is World-local.
225
+ *
226
+ * `world.addSystem(handle)` consumes the token directly — no by-name overload
227
+ * (OOS-8). Duplicate names are resolved by the owning World schedule.
228
+ *
229
+ * `const Qs` locks the `queries` tuple so `fn`'s query-results parameter
230
+ * recovers per-query row access shapes without `as const` (S-5).
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * const Move = defineSystem({
235
+ * name: 'movement',
236
+ * queries: [{ with: [Position, Velocity] }],
237
+ * fn: (_world, queryResults) => { ... },
238
+ * });
239
+ * world.addSystem(Move);
240
+ * ```
241
+ */
242
+ export function defineSystem<const Qs extends ReadonlyArray<QueryDescriptor>>(
243
+ descriptor: SystemDescriptor<Qs>,
244
+ ): SystemHandle<Qs> {
245
+ const handle = Object.freeze(descriptor) as unknown as SystemHandle<Qs>;
246
+ return handle;
247
+ }
248
+
249
+ /**
250
+ */
251
+ // ────────────────────────────────────────────────────────────────────────────
252
+ // SystemSet — nominal token (D-2c step 1, w2)
253
+ // ────────────────────────────────────────────────────────────────────────────
254
+
255
+ /** Brand symbol for {@link SystemSet}. Declared (not runtime-initialised) so
256
+ * the token interface carries nominal identity without a runtime allocation.
257
+ * Mirrors the `FORGEAX_STATE_BRAND` pattern in `@forgeax/engine-state`. */
258
+ declare const FORGEAX_SYSTEM_SET_BRAND: unique symbol;
259
+
260
+ /**
261
+ * Opaque branded type for system-set tokens.
262
+ *
263
+ * Use {@link defineSystemSet} to create a token; never construct manually.
264
+ * The {@link __forgeaxSystemSet} brand prevents plain-object assignment and
265
+ * enables TypeScript narrowing at the two mutation entry points.
266
+ */
267
+ export interface SystemSet {
268
+ /** Brand — prevents structural compatibility with plain objects. */
269
+ readonly __forgeaxSystemSet: typeof FORGEAX_SYSTEM_SET_BRAND;
270
+ /** The user-supplied set name. */
271
+ readonly name: string;
272
+ /** Optional per-frame run condition. Consumed by M3 condition gate. */
273
+ readonly runIf?: (world: import('./world').World) => boolean;
274
+ /** Whether this set forms a sequential chain (M2). */
275
+ readonly chained?: boolean;
276
+ }
277
+
278
+ /**
279
+ * Define a system set at module level. Returns a frozen branded token and
280
+ * keeps membership in the World-local Schedule.
281
+ *
282
+ * Duplicate names are independent tokens; the owning World decides membership.
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * const GameplaySet = defineSystemSet({ name: 'gameplay', runIf: (w) => !w.getResource<boolean>('paused') });
287
+ * const OrderedSet = defineSystemSet({ name: 'ordered', chained: true });
288
+ * ```
289
+ */
290
+ export function defineSystemSet(opts: {
291
+ readonly name: string;
292
+ readonly runIf?: (world: import('./world').World) => boolean;
293
+ readonly chained?: boolean;
294
+ }): SystemSet {
295
+ const token: Record<string, unknown> = {
296
+ __forgeaxSystemSet: undefined as unknown as typeof FORGEAX_SYSTEM_SET_BRAND,
297
+ name: opts.name,
298
+ };
299
+ if (opts.runIf !== undefined) {
300
+ token.runIf = opts.runIf;
301
+ }
302
+ if (opts.chained !== undefined) {
303
+ token.chained = opts.chained;
304
+ }
305
+ const frozen = Object.freeze(token) as unknown as SystemSet;
306
+ return frozen;
307
+ }
308
+
309
+ /**
310
+ /**
311
+ * Validate every token in `tokens` for local structural validity.
312
+ * Returns `ok(undefined)` for a structurally valid token and reports an empty
313
+ * token name as a structured registration error.
314
+ *
315
+ * Does not write any Schedule state — callers consume the `Result` and proceed
316
+ * only on `ok`.
317
+ */
318
+ export function validateSystemSetTokens(
319
+ tokens: readonly SystemSet[],
320
+ ): Result<void, SystemSetNotRegisteredError> {
321
+ for (const token of tokens) {
322
+ if (token.name.length === 0) {
323
+ return err(systemSetNotRegistered(token.name, []));
324
+ }
325
+ }
326
+ return ok(undefined);
327
+ }
328
+
329
+ /**
330
+ * Remove a registered system by name (M2 — plan-strategy D-3).
331
+ *
332
+ * Drops the entry from `schedule.systems` and marks the schedule dirty so the
333
+ * next `runSchedule` rebuilds the sorted order via Kahn topo. Cycle detection
334
+ * already happens inside `buildSchedule`, so a removal that breaks an unrelated
335
+ * `before/after` reference simply skips the unknown name (existing behaviour).
336
+ *
337
+ * Failure: name not registered → `Result.err(ScheduleMutationError)` with
338
+ * `.code = 'system-before-unknown'` and `.detail.candidates` carrying the
339
+ * registered names for AI-friendly typo recovery.
340
+ */
341
+ export function removeSystem(
342
+ schedule: Schedule,
343
+ name: string,
344
+ ): Result<void, ScheduleMutationError> {
345
+ if (!schedule.systems.has(name)) {
346
+ return err(
347
+ new ScheduleMutationError(
348
+ 'system-before-unknown',
349
+ `Cannot removeSystem: no system registered as "${name}".`,
350
+ 'Call world.inspect().systems to discover registered names.',
351
+ { candidates: [...schedule.systems.keys()] },
352
+ ),
353
+ );
354
+ }
355
+ schedule.systems.delete(name);
356
+ // Prune set membership: remove this system name from every set's members (D-1).
357
+ for (const [, setRecord] of schedule.sets) {
358
+ setRecord.members.delete(name);
359
+ }
360
+ schedule.dirty = true;
361
+ return ok(undefined);
362
+ }
363
+
364
+ /**
365
+ * Replace a registered system in-place (M2 — plan-strategy D-3 atomic semantics).
366
+ *
367
+ * Overwrites the `descriptor` field of the existing `SystemRecord` while
368
+ * keeping `registrationIndex` and the `Map` slot identical, so all `before /
369
+ * after` edges that reference this name remain bound to the same slot. Marks
370
+ * the schedule dirty: the next `runSchedule` re-sorts.
371
+ *
372
+ * Failure: name not registered → `Result.err(ScheduleMutationError)` with
373
+ * `.code = 'system-before-unknown'`.
374
+ */
375
+ export function replaceSystem<const Qs extends ReadonlyArray<QueryDescriptor>>(
376
+ schedule: Schedule,
377
+ name: string,
378
+ descriptor: SystemDescriptor<Qs>,
379
+ ): Result<void, ScheduleMutationError> {
380
+ const record = schedule.systems.get(name);
381
+ if (!record) {
382
+ return err(
383
+ new ScheduleMutationError(
384
+ 'system-before-unknown',
385
+ `Cannot replaceSystem: no system registered as "${name}".`,
386
+ 'Call world.inspect().systems to discover registered names; or addSystem(descriptor) to register a new system.',
387
+ { candidates: [...schedule.systems.keys()] },
388
+ ),
389
+ );
390
+ }
391
+ record.descriptor = descriptor as unknown as SystemDescriptor;
392
+ // Reset cached query states — descriptor.queries may have changed shape.
393
+ record.queries = null;
394
+ schedule.dirty = true;
395
+ return ok(undefined);
396
+ }
397
+
398
+ /**
399
+ * Batch-register systems to a set. Validates the set token before writing.
400
+ *
401
+ * - First call for a system name: registers via the existing `addSystem` path.
402
+ * - Subsequent calls: only adds the system name to the set's members (dedup).
403
+ * - `runIf` / `chained` are snapshotted from the token into the SetRecord on
404
+ * first encounter.
405
+ *
406
+ * Returns `Result.err` with `SystemSetNotRegisteredError` if the set token
407
+ * fails identity validation.
408
+ */
409
+ export function addSystems<const Qs extends ReadonlyArray<QueryDescriptor>>(
410
+ schedule: Schedule,
411
+ set: SystemSet,
412
+ systems: ReadonlyArray<SystemDescriptor<Qs>>,
413
+ ): Result<void, SystemSetNotRegisteredError> {
414
+ const validated = validateSystemSetTokens([set]);
415
+ if (!validated.ok) {
416
+ return err(validated.error);
417
+ }
418
+
419
+ const setName = set.name;
420
+ let record = schedule.sets.get(setName);
421
+ if (!record) {
422
+ record = {
423
+ members: new Set(),
424
+ runIf: set.runIf,
425
+ chained: set.chained ?? false,
426
+ };
427
+ schedule.sets.set(setName, record);
428
+ }
429
+
430
+ for (const system of systems) {
431
+ const name = system.name;
432
+ // Dedup: only register the system once in schedule.systems.
433
+ if (!schedule.systems.has(name)) {
434
+ addSystem(schedule, system);
435
+ }
436
+ // Always add membership — multi-belong is supported.
437
+ record.members.add(name);
438
+ }
439
+
440
+ schedule.dirty = true;
441
+ return ok(undefined);
442
+ }
443
+
444
+ /**
445
+ * Build the sorted execution order via Kahn's topological sort.
446
+ * Throws CyclicDependencyError if a cycle is detected.
447
+ *
448
+ * @returns sorted system names
449
+ */
450
+ export function buildSchedule(schedule: Schedule): string[] {
451
+ const systems = schedule.systems;
452
+ const names = [...systems.keys()];
453
+ if (schedule.token.name === 'Update') names.push('FixedUpdate');
454
+ const nameSet = new Set(names);
455
+
456
+ // Build adjacency list + in-degree map.
457
+ const adj = new Map<string, string[]>(); // adj[a] = [b] means a → b (a must run before b)
458
+ const inDegree = new Map<string, number>();
459
+ const predecessors = new Map<string, Set<string>>();
460
+
461
+ for (const name of names) {
462
+ adj.set(name, []);
463
+ inDegree.set(name, 0);
464
+ predecessors.set(name, new Set());
465
+ }
466
+
467
+ const addEdge = (source: string, target: string): void => {
468
+ adj.get(source)?.push(target);
469
+ predecessors.get(target)?.add(source);
470
+ inDegree.set(target, (inDegree.get(target) ?? 0) + 1);
471
+ };
472
+ const orderReferenceName = (reference: string | ScheduleToken): string =>
473
+ typeof reference === 'string' ? reference : reference.name;
474
+
475
+ for (const [name, record] of systems) {
476
+ const desc = record.descriptor;
477
+
478
+ // "after" constraints: for each dep in after, dep → name (dep runs before name)
479
+ if (desc.after) {
480
+ for (const reference of desc.after) {
481
+ const dep = orderReferenceName(reference);
482
+ if (!nameSet.has(dep)) continue; // scope validation handles cross-schedule references
483
+ addEdge(dep, name);
484
+ }
485
+ }
486
+
487
+ // "before" constraints: for each target in before, name → target (name runs before target)
488
+ if (desc.before) {
489
+ for (const reference of desc.before) {
490
+ const target = orderReferenceName(reference);
491
+ if (!nameSet.has(target)) continue; // scope validation handles cross-schedule references
492
+ addEdge(name, target);
493
+ }
494
+ }
495
+ }
496
+
497
+ // Set membership contributes only run conditions and optional chaining.
498
+ for (const [, setRecord] of schedule.sets) {
499
+ if (setRecord.chained) {
500
+ const members = [...setRecord.members];
501
+ for (let i = 0; i < members.length - 1; i++) {
502
+ const m1 = members[i];
503
+ const m2 = members[i + 1];
504
+ if (!m1 || !m2 || !nameSet.has(m1) || !nameSet.has(m2)) continue;
505
+ // m1 → m2 (m1 runs before m2)
506
+ addEdge(m1, m2);
507
+ }
508
+ }
509
+ }
510
+
511
+ // Kahn's algorithm with registration-order tie-breaker.
512
+ // Use a queue sorted by registrationIndex for deterministic ordering.
513
+ const queue: string[] = [];
514
+ for (const name of names) {
515
+ if (inDegree.get(name) === 0) {
516
+ queue.push(name);
517
+ }
518
+ }
519
+ // Sort initial queue by registration index. The fixed anchor is intrinsic and
520
+ // receives a deterministic slot after user systems with the same indegree.
521
+ queue.sort(
522
+ (a, b) =>
523
+ (systems.get(a)?.registrationIndex ?? Number.MAX_SAFE_INTEGER) -
524
+ (systems.get(b)?.registrationIndex ?? Number.MAX_SAFE_INTEGER),
525
+ );
526
+
527
+ const sorted: string[] = [];
528
+ while (queue.length > 0) {
529
+ // biome-ignore lint/style/noNonNullAssertion: while(queue.length > 0) guarantees shift() returns a value
530
+ const current = queue.shift()!;
531
+ sorted.push(current);
532
+
533
+ const neighbors = adj.get(current) ?? [];
534
+ // Collect newly freed neighbors, then sort by registration index
535
+ const freed: string[] = [];
536
+ for (const neighbor of neighbors) {
537
+ const newDeg = (inDegree.get(neighbor) ?? 0) - 1;
538
+ inDegree.set(neighbor, newDeg);
539
+ if (newDeg === 0) {
540
+ freed.push(neighbor);
541
+ }
542
+ }
543
+ freed.sort(
544
+ (a, b) =>
545
+ (systems.get(a)?.registrationIndex ?? Number.MAX_SAFE_INTEGER) -
546
+ (systems.get(b)?.registrationIndex ?? Number.MAX_SAFE_INTEGER),
547
+ );
548
+ queue.push(...freed);
549
+ }
550
+
551
+ // Cycle detection: if we didn't process all nodes, there's a cycle.
552
+ if (sorted.length < names.length) {
553
+ // Find nodes still with in-degree > 0 (part of cycle).
554
+ const remaining = names.filter((n) => !sorted.includes(n));
555
+ const cyclePath = findCyclePath(remaining, adj);
556
+ throw new CyclicDependencyError(cyclePath);
557
+ }
558
+
559
+ schedule.sortedOrder = sorted;
560
+ schedule.predecessors = predecessors;
561
+ schedule.dirty = false;
562
+ return sorted;
563
+ }
564
+
565
+ /**
566
+ * Find a cycle path among the remaining (unprocessed) nodes.
567
+ * Returns the cycle as a readonly array of node names.
568
+ */
569
+ function findCyclePath(remaining: string[], adj: Map<string, string[]>): readonly string[] {
570
+ const remainSet = new Set(remaining);
571
+ const visited = new Set<string>();
572
+ const path: string[] = [];
573
+
574
+ function dfs(node: string): readonly string[] | null {
575
+ if (visited.has(node)) {
576
+ // Found cycle: extract cycle from path
577
+ const cycleStart = path.indexOf(node);
578
+ const cycle = path.slice(cycleStart);
579
+ cycle.push(node);
580
+ return cycle;
581
+ }
582
+ visited.add(node);
583
+ path.push(node);
584
+
585
+ for (const neighbor of adj.get(node) ?? []) {
586
+ if (!remainSet.has(neighbor)) continue;
587
+ const result = dfs(neighbor);
588
+ if (result) return result;
589
+ }
590
+
591
+ path.pop();
592
+ return null;
593
+ }
594
+
595
+ for (const start of remaining) {
596
+ visited.clear();
597
+ path.length = 0;
598
+ const result = dfs(start);
599
+ if (result) return result;
600
+ }
601
+
602
+ /* istanbul ignore next -- fallback: DFS always finds cycle in remaining nodes */
603
+ return remaining;
604
+ }
605
+
606
+ function poisonSystemFailure(world: World, systemName: string, cause: unknown): void {
607
+ const poison = world[worldInternal].poisonExecution;
608
+ poison({
609
+ code: 'shared-kernel-failed',
610
+ kernelName: `system:${systemName}`,
611
+ cause,
612
+ partialWrite: true,
613
+ retryable: false,
614
+ });
615
+ }
616
+
617
+ function abortOutstandingCommands(
618
+ commandsBySystem: Map<string, ReturnType<typeof createCommandBuffer>>,
619
+ ): void {
620
+ for (const commands of commandsBySystem.values()) {
621
+ if (commands.status === 'open') commands.abort();
622
+ }
623
+ }
624
+
625
+ /**
626
+ * Execute all systems in sorted order. Rebuilds schedule if dirty.
627
+ * Each system receives its query results and a command buffer.
628
+ * The world parameter provides the archetype graph, component ID resolution,
629
+ * and resource checks.
630
+ *
631
+ * Layer 2 (ParamValidation): query empty → skip, resource missing → invalid.
632
+ * A returned Result error poisons the active frame and stops execution.
633
+ */
634
+ function runScheduleBody(
635
+ schedule: Schedule,
636
+ world: World,
637
+ selectedNames: readonly string[] | undefined,
638
+ commandsBySystem: Map<string, ReturnType<typeof createCommandBuffer>>,
639
+ finalDrain = true,
640
+ ): void {
641
+ if (schedule.dirty) {
642
+ buildSchedule(schedule);
643
+ }
644
+ if (
645
+ selectedNames === undefined &&
646
+ schedule.sets.size === 0 &&
647
+ commandsBySystem.size === 0 &&
648
+ schedule.sortedOrder.every((name) => {
649
+ const record = schedule.systems.get(name);
650
+ return (
651
+ record !== undefined &&
652
+ record.descriptor.queries.length === 0 &&
653
+ record.descriptor.runIf === undefined &&
654
+ (schedule.predecessors.get(name)?.size ?? 0) === 0
655
+ );
656
+ })
657
+ ) {
658
+ for (const name of schedule.sortedOrder) {
659
+ const record = schedule.systems.get(name);
660
+ /* istanbul ignore next -- sortedOrder comes from systems Map keys */
661
+ if (!record) continue;
662
+
663
+ const commands = createCommandBuffer(world, {
664
+ systemName: name,
665
+ scheduleName: schedule.token.name,
666
+ });
667
+ commandsBySystem.set(name, commands);
668
+ let returnValue: unknown;
669
+ try {
670
+ returnValue = record.descriptor.fn(world, [], commands);
671
+ } catch (error) {
672
+ abortOutstandingCommands(commandsBySystem);
673
+ poisonSystemFailure(world, name, error);
674
+ throw new SystemFailedError(name, schedule.token.name, error);
675
+ }
676
+ if (returnValue && typeof returnValue === 'object' && 'ok' in returnValue) {
677
+ const result = returnValue as { ok: boolean; error?: unknown };
678
+ if (result.ok === false && result.error !== undefined) {
679
+ abortOutstandingCommands(commandsBySystem);
680
+ poisonSystemFailure(world, name, result.error);
681
+ throw new SystemFailedError(name, schedule.token.name, result.error);
682
+ }
683
+ }
684
+ }
685
+ if (finalDrain) {
686
+ try {
687
+ for (const commands of commandsBySystem.values()) {
688
+ flushCommands(commands, world);
689
+ }
690
+ } catch (error) {
691
+ abortOutstandingCommands(commandsBySystem);
692
+ throw error;
693
+ }
694
+ }
695
+ return;
696
+ }
697
+ const selected = selectedNames ? new Set(selectedNames) : undefined;
698
+
699
+ // Build reverse map: system name → set names it belongs to (D-5).
700
+ // Rebuilt each frame so removeSystem / replaceSystem membership changes
701
+ // take effect on the next frame.
702
+ const systemToSets = new Map<string, string[]>();
703
+ for (const [setName, setRecord] of schedule.sets) {
704
+ for (const memberName of setRecord.members) {
705
+ if (schedule.systems.has(memberName)) {
706
+ let list = systemToSets.get(memberName);
707
+ if (!list) {
708
+ list = [];
709
+ systemToSets.set(memberName, list);
710
+ }
711
+ list.push(setName);
712
+ }
713
+ }
714
+ }
715
+
716
+ // Per-frame set runIf cache (D-5). Discarded at frame end — no cross-frame state.
717
+ const setRunIfCache = new Map<string, boolean>();
718
+
719
+ for (const name of schedule.sortedOrder) {
720
+ if (selected && !selected.has(name)) continue;
721
+ const record = schedule.systems.get(name);
722
+ /* istanbul ignore next -- defensive: sortedOrder comes from systems Map keys */
723
+ if (!record) continue;
724
+
725
+ // Apply only buffers that have an explicit graph edge into this system.
726
+ for (const predecessor of schedule.predecessors.get(name) ?? []) {
727
+ const producerCommands = commandsBySystem.get(predecessor);
728
+ if (producerCommands) {
729
+ try {
730
+ flushCommands(producerCommands, world);
731
+ } catch (error) {
732
+ abortOutstandingCommands(commandsBySystem);
733
+ throw error;
734
+ }
735
+ }
736
+ }
737
+
738
+ // Lazily initialize the persistent executable queries on first run.
739
+ if (record.queries === null) {
740
+ record.queries = record.descriptor.queries.map((descriptor) => {
741
+ const result = world.query(descriptor);
742
+ if (!result.ok) throw result.error;
743
+ return result.value;
744
+ });
745
+ }
746
+ // ── Set-level runIf AND gate (D-5) ──
747
+ // before system-level runIf. Each set's runIf is lazily cached per frame. ──
748
+ const setNames = systemToSets.get(name);
749
+ let allSetConditionsPass = true;
750
+ if (setNames) {
751
+ for (const setName of setNames) {
752
+ const setRecord = schedule.sets.get(setName);
753
+ if (setRecord?.runIf) {
754
+ let cached = setRunIfCache.get(setName);
755
+ if (cached === undefined) {
756
+ cached = setRecord.runIf(world);
757
+ setRunIfCache.set(setName, cached);
758
+ }
759
+ if (!cached) {
760
+ allSetConditionsPass = false;
761
+ break;
762
+ }
763
+ }
764
+ }
765
+ }
766
+ if (!allSetConditionsPass) {
767
+ continue; // skip system: no system runIf, query iteration, or fn
768
+ }
769
+
770
+ // ── Run condition (runIf) — evaluated after ParamValidation 'ok', before
771
+ // query iteration. false -> skip silently (no query, no fn, no cursor advance). ──
772
+ if (record.descriptor.runIf && !record.descriptor.runIf(world)) {
773
+ continue;
774
+ }
775
+
776
+ // ── Layer 3: system execution + Result collection ──
777
+ // trusted-cast: the descriptor tuple constructed the matching Query tuple above.
778
+ const commands = createCommandBuffer(world, {
779
+ systemName: name,
780
+ scheduleName: schedule.token.name,
781
+ });
782
+ commandsBySystem.set(name, commands);
783
+ let returnValue: unknown;
784
+ try {
785
+ returnValue = record.descriptor.fn(
786
+ world,
787
+ record.queries as Parameters<typeof record.descriptor.fn>[1],
788
+ commands,
789
+ );
790
+ } catch (error) {
791
+ abortOutstandingCommands(commandsBySystem);
792
+ poisonSystemFailure(world, name, error);
793
+ throw new SystemFailedError(name, schedule.token.name, error);
794
+ }
795
+
796
+ // A returned Result error is a poisoned frame failure.
797
+ if (returnValue && typeof returnValue === 'object' && 'ok' in (returnValue as object)) {
798
+ const result = returnValue as { ok: boolean; error?: unknown };
799
+ if (result.ok === false && result.error !== undefined) {
800
+ abortOutstandingCommands(commandsBySystem);
801
+ poisonSystemFailure(world, name, result.error);
802
+ throw new SystemFailedError(name, schedule.token.name, result.error);
803
+ }
804
+ }
805
+ // void return is treated as successful execution.
806
+ }
807
+
808
+ if (finalDrain) {
809
+ // A schedule boundary drains every remaining buffer, including commands
810
+ // enqueued by lifecycle hooks while another command is being applied.
811
+ try {
812
+ for (const commands of commandsBySystem.values()) {
813
+ flushCommands(commands, world);
814
+ }
815
+ } catch (error) {
816
+ abortOutstandingCommands(commandsBySystem);
817
+ throw error;
818
+ }
819
+ }
820
+ }
821
+
822
+ /**
823
+ * Execute one schedule with a terminalization guard. Query construction,
824
+ * run-if evaluation, DAG compilation, and command draining may all throw an
825
+ * unexpected error; every still-open buffer is nevertheless aborted before the
826
+ * error reaches World.update.
827
+ */
828
+ export function runSchedule(
829
+ schedule: Schedule,
830
+ world: World,
831
+ selectedNames?: readonly string[],
832
+ commandsBySystem = new Map<string, ReturnType<typeof createCommandBuffer>>(),
833
+ finalDrain = true,
834
+ ): void {
835
+ let completed = false;
836
+ try {
837
+ runScheduleBody(schedule, world, selectedNames, commandsBySystem, finalDrain);
838
+ completed = true;
839
+ } finally {
840
+ // When Update is split around the FixedUpdate anchor, the first segment
841
+ // deliberately leaves its command buffers open so FixedUpdate observes a
842
+ // stable pre-frame World and the second segment can drain them. Only the
843
+ // final segment (or an exceptional exit) may abort outstanding buffers.
844
+ if (!completed || finalDrain) abortOutstandingCommands(commandsBySystem);
845
+ }
846
+ }