@oasys/oecs 0.5.3 → 0.6.0

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 (510) hide show
  1. package/CHANGELOG.md +1399 -276
  2. package/README.md +327 -169
  3. package/dist/core/ecs/access_check.d.cts +31 -31
  4. package/dist/core/ecs/access_check.d.ts +31 -31
  5. package/dist/core/ecs/access_check.d.ts.map +1 -1
  6. package/dist/core/ecs/archetype.d.cts +273 -205
  7. package/dist/core/ecs/archetype.d.ts +273 -205
  8. package/dist/core/ecs/archetype.d.ts.map +1 -1
  9. package/dist/core/ecs/archetype_closure.d.cts +23 -0
  10. package/dist/core/ecs/archetype_closure.d.ts +23 -0
  11. package/dist/core/ecs/archetype_closure.d.ts.map +1 -0
  12. package/dist/core/ecs/archetype_graph.d.cts +34 -34
  13. package/dist/core/ecs/archetype_graph.d.ts +34 -34
  14. package/dist/core/ecs/archetype_graph.d.ts.map +1 -1
  15. package/dist/core/ecs/archetype_types.d.cts +55 -0
  16. package/dist/core/ecs/archetype_types.d.ts +55 -0
  17. package/dist/core/ecs/archetype_types.d.ts.map +1 -0
  18. package/dist/core/ecs/change_feed.d.cts +53 -0
  19. package/dist/core/ecs/change_feed.d.ts +53 -0
  20. package/dist/core/ecs/change_feed.d.ts.map +1 -0
  21. package/dist/core/ecs/changed_query.d.cts +36 -0
  22. package/dist/core/ecs/changed_query.d.ts +36 -0
  23. package/dist/core/ecs/changed_query.d.ts.map +1 -0
  24. package/dist/core/ecs/chunk_columns.d.cts +49 -0
  25. package/dist/core/ecs/chunk_columns.d.ts +49 -0
  26. package/dist/core/ecs/chunk_columns.d.ts.map +1 -0
  27. package/dist/core/ecs/command_log.d.cts +30 -30
  28. package/dist/core/ecs/command_log.d.ts +30 -30
  29. package/dist/core/ecs/command_log.d.ts.map +1 -1
  30. package/dist/core/ecs/component.d.cts +45 -44
  31. package/dist/core/ecs/component.d.ts +45 -44
  32. package/dist/core/ecs/component.d.ts.map +1 -1
  33. package/dist/core/ecs/component_registry.d.cts +53 -0
  34. package/dist/core/ecs/component_registry.d.ts +53 -0
  35. package/dist/core/ecs/component_registry.d.ts.map +1 -0
  36. package/dist/core/ecs/compute_backend.d.cts +18 -7
  37. package/dist/core/ecs/compute_backend.d.ts +18 -7
  38. package/dist/core/ecs/compute_backend.d.ts.map +1 -1
  39. package/dist/core/ecs/deferred_commands.d.cts +15 -14
  40. package/dist/core/ecs/deferred_commands.d.ts +15 -14
  41. package/dist/core/ecs/deferred_commands.d.ts.map +1 -1
  42. package/dist/core/ecs/dispatch_trace.d.cts +32 -31
  43. package/dist/core/ecs/dispatch_trace.d.ts +32 -31
  44. package/dist/core/ecs/dispatch_trace.d.ts.map +1 -1
  45. package/dist/core/ecs/ecs.d.cts +337 -276
  46. package/dist/core/ecs/ecs.d.ts +337 -276
  47. package/dist/core/ecs/ecs.d.ts.map +1 -1
  48. package/dist/core/ecs/ecs_memory.d.cts +134 -136
  49. package/dist/core/ecs/ecs_memory.d.ts +134 -136
  50. package/dist/core/ecs/ecs_memory.d.ts.map +1 -1
  51. package/dist/core/ecs/ecs_options.d.cts +89 -0
  52. package/dist/core/ecs/ecs_options.d.ts +89 -0
  53. package/dist/core/ecs/ecs_options.d.ts.map +1 -0
  54. package/dist/core/ecs/entity.d.cts +4 -4
  55. package/dist/core/ecs/entity.d.ts +4 -4
  56. package/dist/core/ecs/entity_allocator.d.cts +24 -22
  57. package/dist/core/ecs/entity_allocator.d.ts +24 -22
  58. package/dist/core/ecs/entity_allocator.d.ts.map +1 -1
  59. package/dist/core/ecs/event.d.cts +37 -23
  60. package/dist/core/ecs/event.d.ts +37 -23
  61. package/dist/core/ecs/event.d.ts.map +1 -1
  62. package/dist/core/ecs/facades.d.cts +12 -126
  63. package/dist/core/ecs/facades.d.ts +12 -126
  64. package/dist/core/ecs/facades.d.ts.map +1 -1
  65. package/dist/core/ecs/frame_stepper.d.cts +12 -12
  66. package/dist/core/ecs/frame_stepper.d.ts +12 -12
  67. package/dist/core/ecs/frame_stepper.d.ts.map +1 -1
  68. package/dist/core/ecs/frame_trace.d.cts +34 -34
  69. package/dist/core/ecs/frame_trace.d.ts +34 -34
  70. package/dist/core/ecs/frame_trace.d.ts.map +1 -1
  71. package/dist/core/ecs/host_commands.d.cts +107 -89
  72. package/dist/core/ecs/host_commands.d.ts +107 -89
  73. package/dist/core/ecs/host_commands.d.ts.map +1 -1
  74. package/dist/core/ecs/index.d.cts +22 -12
  75. package/dist/core/ecs/index.d.ts +22 -12
  76. package/dist/core/ecs/index.d.ts.map +1 -1
  77. package/dist/core/ecs/observer.d.cts +41 -122
  78. package/dist/core/ecs/observer.d.ts +41 -122
  79. package/dist/core/ecs/observer.d.ts.map +1 -1
  80. package/dist/core/ecs/phase.d.cts +79 -0
  81. package/dist/core/ecs/phase.d.ts +79 -0
  82. package/dist/core/ecs/phase.d.ts.map +1 -0
  83. package/dist/core/ecs/plugin.d.cts +125 -0
  84. package/dist/core/ecs/plugin.d.ts +125 -0
  85. package/dist/core/ecs/plugin.d.ts.map +1 -0
  86. package/dist/core/ecs/plugin_slots.d.cts +26 -0
  87. package/dist/core/ecs/plugin_slots.d.ts +26 -0
  88. package/dist/core/ecs/plugin_slots.d.ts.map +1 -0
  89. package/dist/core/ecs/query.d.cts +219 -568
  90. package/dist/core/ecs/query.d.ts +219 -568
  91. package/dist/core/ecs/query.d.ts.map +1 -1
  92. package/dist/core/ecs/query_cache.d.cts +130 -0
  93. package/dist/core/ecs/query_cache.d.ts +130 -0
  94. package/dist/core/ecs/query_cache.d.ts.map +1 -0
  95. package/dist/core/ecs/query_registry.d.cts +47 -0
  96. package/dist/core/ecs/query_registry.d.ts +47 -0
  97. package/dist/core/ecs/query_registry.d.ts.map +1 -0
  98. package/dist/core/ecs/query_terms.d.cts +126 -0
  99. package/dist/core/ecs/query_terms.d.ts +126 -0
  100. package/dist/core/ecs/query_terms.d.ts.map +1 -0
  101. package/dist/core/ecs/ref.d.cts +129 -16
  102. package/dist/core/ecs/ref.d.ts +129 -16
  103. package/dist/core/ecs/ref.d.ts.map +1 -1
  104. package/dist/core/ecs/relation.d.cts +105 -186
  105. package/dist/core/ecs/relation.d.ts +105 -186
  106. package/dist/core/ecs/relation.d.ts.map +1 -1
  107. package/dist/core/ecs/resource.d.cts +14 -14
  108. package/dist/core/ecs/resource.d.ts +14 -14
  109. package/dist/core/ecs/resource_registry.d.cts +6 -6
  110. package/dist/core/ecs/resource_registry.d.ts +6 -6
  111. package/dist/core/ecs/resource_registry.d.ts.map +1 -1
  112. package/dist/core/ecs/row_kinds.d.cts +24 -0
  113. package/dist/core/ecs/row_kinds.d.ts +24 -0
  114. package/dist/core/ecs/row_kinds.d.ts.map +1 -0
  115. package/dist/core/ecs/run_condition.d.cts +23 -23
  116. package/dist/core/ecs/run_condition.d.ts +23 -23
  117. package/dist/core/ecs/run_condition.d.ts.map +1 -1
  118. package/dist/core/ecs/schedule.d.cts +98 -114
  119. package/dist/core/ecs/schedule.d.ts +98 -114
  120. package/dist/core/ecs/schedule.d.ts.map +1 -1
  121. package/dist/core/ecs/schedule_plan.d.cts +33 -0
  122. package/dist/core/ecs/schedule_plan.d.ts +33 -0
  123. package/dist/core/ecs/schedule_plan.d.ts.map +1 -0
  124. package/dist/core/ecs/snapshot.d.cts +77 -0
  125. package/dist/core/ecs/snapshot.d.ts +77 -0
  126. package/dist/core/ecs/snapshot.d.ts.map +1 -0
  127. package/dist/core/ecs/snapshot_mount.d.cts +42 -0
  128. package/dist/core/ecs/snapshot_mount.d.ts +42 -0
  129. package/dist/core/ecs/snapshot_mount.d.ts.map +1 -0
  130. package/dist/core/ecs/sparse_store.d.cts +110 -53
  131. package/dist/core/ecs/sparse_store.d.ts +110 -53
  132. package/dist/core/ecs/sparse_store.d.ts.map +1 -1
  133. package/dist/core/ecs/store.d.cts +644 -511
  134. package/dist/core/ecs/store.d.ts +644 -511
  135. package/dist/core/ecs/store.d.ts.map +1 -1
  136. package/dist/core/ecs/store_layout_listener.d.cts +8 -8
  137. package/dist/core/ecs/store_layout_listener.d.ts +8 -8
  138. package/dist/core/ecs/store_types.d.cts +122 -0
  139. package/dist/core/ecs/store_types.d.ts +122 -0
  140. package/dist/core/ecs/store_types.d.ts.map +1 -0
  141. package/dist/core/ecs/system.d.cts +151 -66
  142. package/dist/core/ecs/system.d.ts +151 -66
  143. package/dist/core/ecs/system.d.ts.map +1 -1
  144. package/dist/core/ecs/system_context.d.cts +296 -0
  145. package/dist/core/ecs/system_context.d.ts +296 -0
  146. package/dist/core/ecs/system_context.d.ts.map +1 -0
  147. package/dist/core/ecs/system_set.d.cts +48 -0
  148. package/dist/core/ecs/system_set.d.ts +48 -0
  149. package/dist/core/ecs/system_set.d.ts.map +1 -0
  150. package/dist/core/ecs/utils/arrays.d.cts +15 -1
  151. package/dist/core/ecs/utils/arrays.d.ts +15 -1
  152. package/dist/core/ecs/utils/arrays.d.ts.map +1 -1
  153. package/dist/core/ecs/utils/error.d.cts +87 -4
  154. package/dist/core/ecs/utils/error.d.ts +87 -4
  155. package/dist/core/ecs/utils/error.d.ts.map +1 -1
  156. package/dist/core/ecs/utils/plugin_error.d.cts +10 -0
  157. package/dist/core/ecs/utils/plugin_error.d.ts +10 -0
  158. package/dist/core/ecs/utils/plugin_error.d.ts.map +1 -0
  159. package/dist/core/restore_errors.d.cts +30 -0
  160. package/dist/core/restore_errors.d.ts +30 -0
  161. package/dist/core/restore_errors.d.ts.map +1 -0
  162. package/dist/core/store/action_ring.d.cts +48 -49
  163. package/dist/core/store/action_ring.d.ts +48 -49
  164. package/dist/core/store/action_ring.d.ts.map +1 -1
  165. package/dist/core/store/allocator.d.cts +166 -114
  166. package/dist/core/store/allocator.d.ts +166 -114
  167. package/dist/core/store/allocator.d.ts.map +1 -1
  168. package/dist/core/store/buffer_backed_column.d.cts +21 -22
  169. package/dist/core/store/buffer_backed_column.d.ts +21 -22
  170. package/dist/core/store/buffer_backed_column.d.ts.map +1 -1
  171. package/dist/core/store/column_store.d.cts +138 -100
  172. package/dist/core/store/column_store.d.ts +138 -100
  173. package/dist/core/store/column_store.d.ts.map +1 -1
  174. package/dist/core/store/command_dispatch.d.cts +14 -15
  175. package/dist/core/store/command_dispatch.d.ts +14 -15
  176. package/dist/core/store/command_dispatch.d.ts.map +1 -1
  177. package/dist/core/store/command_ring.d.cts +41 -45
  178. package/dist/core/store/command_ring.d.ts +41 -45
  179. package/dist/core/store/command_ring.d.ts.map +1 -1
  180. package/dist/core/store/descriptor.d.cts +35 -18
  181. package/dist/core/store/descriptor.d.ts +35 -18
  182. package/dist/core/store/descriptor.d.ts.map +1 -1
  183. package/dist/core/store/entity_index.d.cts +34 -35
  184. package/dist/core/store/entity_index.d.ts +34 -35
  185. package/dist/core/store/entity_index.d.ts.map +1 -1
  186. package/dist/core/store/event_ring.d.cts +40 -42
  187. package/dist/core/store/event_ring.d.ts +40 -42
  188. package/dist/core/store/event_ring.d.ts.map +1 -1
  189. package/dist/core/store/extend.d.cts +15 -9
  190. package/dist/core/store/extend.d.ts +15 -9
  191. package/dist/core/store/extend.d.ts.map +1 -1
  192. package/dist/core/store/grow.d.cts +3 -3
  193. package/dist/core/store/grow.d.ts +3 -3
  194. package/dist/core/store/grow.d.ts.map +1 -1
  195. package/dist/core/store/header.d.cts +37 -33
  196. package/dist/core/store/header.d.ts +37 -33
  197. package/dist/core/store/header.d.ts.map +1 -1
  198. package/dist/core/store/index.d.cts +10 -10
  199. package/dist/core/store/index.d.ts +10 -10
  200. package/dist/core/store/index.d.ts.map +1 -1
  201. package/dist/core/store/layout_ops.d.cts +72 -63
  202. package/dist/core/store/layout_ops.d.ts +72 -63
  203. package/dist/core/store/layout_ops.d.ts.map +1 -1
  204. package/dist/core/store/region_table.d.cts +28 -21
  205. package/dist/core/store/region_table.d.ts +28 -21
  206. package/dist/core/store/region_table.d.ts.map +1 -1
  207. package/dist/core/store/snapshot.d.cts +31 -16
  208. package/dist/core/store/snapshot.d.ts +31 -16
  209. package/dist/core/store/snapshot.d.ts.map +1 -1
  210. package/dist/core/store/state_hash.d.cts +14 -14
  211. package/dist/core/store/state_hash.d.ts +14 -14
  212. package/dist/core/store/state_hash.d.ts.map +1 -1
  213. package/dist/core/store/store_regions.d.cts +10 -10
  214. package/dist/core/store/store_regions.d.ts +10 -10
  215. package/dist/core/store/store_regions.d.ts.map +1 -1
  216. package/dist/core/store/vendored_abi/abi.d.cts +27 -2
  217. package/dist/core/store/vendored_abi/abi.d.ts +27 -2
  218. package/dist/core/store/vendored_abi/abi.d.ts.map +1 -1
  219. package/dist/host_commands-B2KMx8O2.cjs +1 -0
  220. package/dist/host_commands-ClVPvhSa.cjs +2 -0
  221. package/dist/host_commands-CwxVQS1z.js +1524 -0
  222. package/dist/host_commands-nJvTy88E.js +1667 -0
  223. package/dist/index.cjs +1 -1
  224. package/dist/index.d.cts +16 -11
  225. package/dist/index.d.ts +16 -11
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.development.cjs +1 -1
  228. package/dist/index.development.js +6709 -5729
  229. package/dist/index.js +5664 -4743
  230. package/dist/internal.cjs +2 -2
  231. package/dist/internal.d.cts +5 -2
  232. package/dist/internal.d.ts +5 -2
  233. package/dist/internal.d.ts.map +1 -1
  234. package/dist/internal.development.cjs +1 -1
  235. package/dist/internal.development.js +70 -20
  236. package/dist/internal.js +138 -83
  237. package/dist/plugins/abi-D3V-QUSf.cjs +1 -0
  238. package/dist/plugins/abi-NP8pNxpd.js +42 -0
  239. package/dist/plugins/arrays-Dr_1hCjL.js +28 -0
  240. package/dist/plugins/arrays-Dt-Joc4i.cjs +1 -0
  241. package/dist/plugins/assertions-gtDVfbnQ.js +6 -0
  242. package/dist/plugins/assertions-z5GQd_bB.cjs +1 -0
  243. package/dist/{extensions → plugins}/editor/editor.d.cts +56 -56
  244. package/dist/{extensions → plugins}/editor/editor.d.ts +56 -56
  245. package/dist/plugins/editor/editor.d.ts.map +1 -0
  246. package/dist/{extensions → plugins}/editor/field_handle.d.cts +13 -13
  247. package/dist/{extensions → plugins}/editor/field_handle.d.ts +13 -13
  248. package/dist/plugins/editor/field_handle.d.ts.map +1 -0
  249. package/dist/plugins/editor/index.d.cts +22 -0
  250. package/dist/plugins/editor/index.d.ts +22 -0
  251. package/dist/plugins/editor/index.d.ts.map +1 -0
  252. package/dist/plugins/editor.cjs +1 -0
  253. package/dist/plugins/editor.d.cts +1 -0
  254. package/dist/plugins/editor.d.ts +1 -0
  255. package/dist/plugins/editor.development.cjs +1 -0
  256. package/dist/{extensions/editor/index.development.js → plugins/editor.development.js} +83 -66
  257. package/dist/{extensions/editor/index.js → plugins/editor.js} +83 -66
  258. package/dist/plugins/events/event_channel.d.cts +13 -0
  259. package/dist/plugins/events/event_channel.d.ts +13 -0
  260. package/dist/plugins/events/event_channel.d.ts.map +1 -0
  261. package/dist/plugins/events/event_registry.d.cts +22 -0
  262. package/dist/plugins/events/event_registry.d.ts +22 -0
  263. package/dist/plugins/events/event_registry.d.ts.map +1 -0
  264. package/dist/plugins/events/facade.d.cts +26 -0
  265. package/dist/plugins/events/facade.d.ts +26 -0
  266. package/dist/plugins/events/facade.d.ts.map +1 -0
  267. package/dist/plugins/events/index.d.cts +10 -0
  268. package/dist/plugins/events/index.d.ts +10 -0
  269. package/dist/plugins/events/index.d.ts.map +1 -0
  270. package/dist/plugins/events.cjs +1 -0
  271. package/dist/plugins/events.d.cts +1 -0
  272. package/dist/plugins/events.d.ts +1 -0
  273. package/dist/plugins/events.development.cjs +1 -0
  274. package/dist/plugins/events.development.js +174 -0
  275. package/dist/plugins/events.js +159 -0
  276. package/dist/plugins/observers/index.d.cts +23 -0
  277. package/dist/plugins/observers/index.d.ts +23 -0
  278. package/dist/plugins/observers/index.d.ts.map +1 -0
  279. package/dist/plugins/observers/observer_registry.d.cts +101 -0
  280. package/dist/plugins/observers/observer_registry.d.ts +101 -0
  281. package/dist/plugins/observers/observer_registry.d.ts.map +1 -0
  282. package/dist/plugins/observers.cjs +1 -0
  283. package/dist/plugins/observers.d.cts +1 -0
  284. package/dist/plugins/observers.d.ts +1 -0
  285. package/dist/plugins/observers.development.cjs +1 -0
  286. package/dist/plugins/observers.development.js +431 -0
  287. package/dist/plugins/observers.js +403 -0
  288. package/dist/plugins/relation_store-BsoegrZh.js +313 -0
  289. package/dist/plugins/relation_store-DQKSnxsm.cjs +1 -0
  290. package/dist/plugins/relation_store-RBhpcE28.cjs +1 -0
  291. package/dist/plugins/relation_store-RqW5Snx6.js +328 -0
  292. package/dist/plugins/relations/builtin_relations.d.cts +48 -0
  293. package/dist/plugins/relations/builtin_relations.d.ts +48 -0
  294. package/dist/plugins/relations/builtin_relations.d.ts.map +1 -0
  295. package/dist/plugins/relations/facade.d.cts +73 -0
  296. package/dist/plugins/relations/facade.d.ts +73 -0
  297. package/dist/plugins/relations/facade.d.ts.map +1 -0
  298. package/dist/plugins/relations/index.d.cts +11 -0
  299. package/dist/plugins/relations/index.d.ts +11 -0
  300. package/dist/plugins/relations/index.d.ts.map +1 -0
  301. package/dist/{core/ecs → plugins/relations}/relation_service.d.cts +93 -105
  302. package/dist/{core/ecs → plugins/relations}/relation_service.d.ts +93 -105
  303. package/dist/plugins/relations/relation_service.d.ts.map +1 -0
  304. package/dist/plugins/relations/relation_store.d.cts +177 -0
  305. package/dist/plugins/relations/relation_store.d.ts +177 -0
  306. package/dist/plugins/relations/relation_store.d.ts.map +1 -0
  307. package/dist/plugins/relations.cjs +1 -0
  308. package/dist/plugins/relations.d.cts +1 -0
  309. package/dist/plugins/relations.d.ts +1 -0
  310. package/dist/plugins/relations.development.cjs +1 -0
  311. package/dist/plugins/relations.development.js +603 -0
  312. package/dist/plugins/relations.js +553 -0
  313. package/dist/plugins/snapshots/index.d.cts +33 -0
  314. package/dist/plugins/snapshots/index.d.ts +33 -0
  315. package/dist/plugins/snapshots/index.d.ts.map +1 -0
  316. package/dist/plugins/snapshots/resume.d.cts +57 -0
  317. package/dist/plugins/snapshots/resume.d.ts +57 -0
  318. package/dist/plugins/snapshots/resume.d.ts.map +1 -0
  319. package/dist/plugins/snapshots/snapshot_service.d.cts +59 -0
  320. package/dist/plugins/snapshots/snapshot_service.d.ts +59 -0
  321. package/dist/plugins/snapshots/snapshot_service.d.ts.map +1 -0
  322. package/dist/plugins/snapshots.cjs +1 -0
  323. package/dist/plugins/snapshots.d.cts +1 -0
  324. package/dist/plugins/snapshots.d.ts +1 -0
  325. package/dist/plugins/snapshots.development.cjs +1 -0
  326. package/dist/plugins/snapshots.development.js +737 -0
  327. package/dist/plugins/snapshots.js +737 -0
  328. package/dist/plugins/solid/index.d.cts +8 -0
  329. package/dist/plugins/solid/index.d.ts +8 -0
  330. package/dist/plugins/solid/index.d.ts.map +1 -0
  331. package/dist/plugins/solid/solid.d.cts +97 -0
  332. package/dist/plugins/solid/solid.d.ts +97 -0
  333. package/dist/plugins/solid/solid.d.ts.map +1 -0
  334. package/dist/plugins/solid.cjs +1 -0
  335. package/dist/plugins/solid.d.cts +1 -0
  336. package/dist/plugins/solid.d.ts +1 -0
  337. package/dist/plugins/solid.development.cjs +1 -0
  338. package/dist/plugins/solid.development.js +319 -0
  339. package/dist/plugins/solid.js +319 -0
  340. package/dist/plugins/workers/index.d.cts +75 -0
  341. package/dist/plugins/workers/index.d.ts +75 -0
  342. package/dist/plugins/workers/index.d.ts.map +1 -0
  343. package/dist/plugins/workers/node_threads.d.cts +25 -0
  344. package/dist/plugins/workers/node_threads.d.ts +25 -0
  345. package/dist/plugins/workers/node_threads.d.ts.map +1 -0
  346. package/dist/plugins/workers/plan.d.cts +61 -0
  347. package/dist/plugins/workers/plan.d.ts +61 -0
  348. package/dist/plugins/workers/plan.d.ts.map +1 -0
  349. package/dist/plugins/workers/pool.d.cts +144 -0
  350. package/dist/plugins/workers/pool.d.ts +144 -0
  351. package/dist/plugins/workers/pool.d.ts.map +1 -0
  352. package/dist/plugins/workers/protocol.d.cts +112 -0
  353. package/dist/plugins/workers/protocol.d.ts +112 -0
  354. package/dist/plugins/workers/protocol.d.ts.map +1 -0
  355. package/dist/plugins/workers/worker_bind.d.cts +64 -0
  356. package/dist/plugins/workers/worker_bind.d.ts +64 -0
  357. package/dist/plugins/workers/worker_bind.d.ts.map +1 -0
  358. package/dist/plugins/workers/worker_loop.d.cts +14 -0
  359. package/dist/plugins/workers/worker_loop.d.ts +14 -0
  360. package/dist/plugins/workers/worker_loop.d.ts.map +1 -0
  361. package/dist/plugins/workers.cjs +1 -0
  362. package/dist/plugins/workers.d.cts +1 -0
  363. package/dist/plugins/workers.d.ts +1 -0
  364. package/dist/plugins/workers.development.cjs +1 -0
  365. package/dist/plugins/workers.development.js +596 -0
  366. package/dist/plugins/workers.js +474 -0
  367. package/dist/primitives.cjs +1 -1
  368. package/dist/primitives.d.cts +4 -4
  369. package/dist/primitives.d.ts +4 -4
  370. package/dist/primitives.development.cjs +1 -1
  371. package/dist/primitives.development.js +66 -22
  372. package/dist/primitives.js +66 -22
  373. package/dist/shared-BKl12xF1.js +132 -0
  374. package/dist/shared-Dk-pACZT.cjs +1 -0
  375. package/dist/shared.cjs +1 -1
  376. package/dist/shared.d.cts +7 -7
  377. package/dist/shared.d.ts +7 -7
  378. package/dist/shared.d.ts.map +1 -1
  379. package/dist/shared.development.cjs +1 -1
  380. package/dist/shared.development.js +4 -3
  381. package/dist/shared.js +4 -3
  382. package/dist/topological_sort-Cc_yXqPT.js +346 -0
  383. package/dist/topological_sort-vp1imU6D.cjs +1 -0
  384. package/dist/type_primitives/assertions.d.cts +12 -16
  385. package/dist/type_primitives/assertions.d.ts +12 -16
  386. package/dist/type_primitives/assertions.d.ts.map +1 -1
  387. package/dist/type_primitives/binary_heap/binary_heap.d.cts +2 -6
  388. package/dist/type_primitives/binary_heap/binary_heap.d.ts +2 -6
  389. package/dist/type_primitives/binary_heap/binary_heap.d.ts.map +1 -1
  390. package/dist/type_primitives/bitset/bitset.d.cts +7 -11
  391. package/dist/type_primitives/bitset/bitset.d.ts +7 -11
  392. package/dist/type_primitives/bitset/bitset.d.ts.map +1 -1
  393. package/dist/type_primitives/brand.d.cts +2 -7
  394. package/dist/type_primitives/brand.d.ts +2 -7
  395. package/dist/type_primitives/brand.d.ts.map +1 -1
  396. package/dist/type_primitives/error.d.cts +1 -2
  397. package/dist/type_primitives/error.d.ts +1 -2
  398. package/dist/type_primitives/error.d.ts.map +1 -1
  399. package/dist/type_primitives/index.d.cts +1 -1
  400. package/dist/type_primitives/index.d.ts +1 -1
  401. package/dist/type_primitives/sparse_map/sparse_map.d.cts +3 -7
  402. package/dist/type_primitives/sparse_map/sparse_map.d.ts +3 -7
  403. package/dist/type_primitives/sparse_map/sparse_map.d.ts.map +1 -1
  404. package/dist/type_primitives/sparse_set/sparse_set.d.cts +2 -6
  405. package/dist/type_primitives/sparse_set/sparse_set.d.ts +2 -6
  406. package/dist/type_primitives/sparse_set/sparse_set.d.ts.map +1 -1
  407. package/dist/type_primitives/topological_sort/topological_sort.d.cts +1 -5
  408. package/dist/type_primitives/topological_sort/topological_sort.d.ts +1 -5
  409. package/dist/type_primitives/topological_sort/topological_sort.d.ts.map +1 -1
  410. package/dist/type_primitives/typed_arrays/typed_arrays.d.cts +18 -17
  411. package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +18 -17
  412. package/dist/type_primitives/typed_arrays/typed_arrays.d.ts.map +1 -1
  413. package/dist/utils/error.d.ts.map +1 -1
  414. package/dist/version.d.cts +4 -4
  415. package/dist/version.d.ts +4 -4
  416. package/dist/worker.cjs +1 -0
  417. package/dist/worker.d.cts +17 -0
  418. package/dist/worker.d.ts +17 -0
  419. package/dist/worker.d.ts.map +1 -0
  420. package/dist/worker.development.cjs +1 -0
  421. package/dist/worker.development.js +253 -0
  422. package/dist/worker.js +253 -0
  423. package/package.json +363 -191
  424. package/dist/core/ecs/builtin_relations.d.cts +0 -43
  425. package/dist/core/ecs/builtin_relations.d.ts +0 -43
  426. package/dist/core/ecs/builtin_relations.d.ts.map +0 -1
  427. package/dist/core/ecs/event_registry.d.cts +0 -22
  428. package/dist/core/ecs/event_registry.d.ts +0 -22
  429. package/dist/core/ecs/event_registry.d.ts.map +0 -1
  430. package/dist/core/ecs/relation_service.d.ts.map +0 -1
  431. package/dist/core/ecs/resume.d.cts +0 -85
  432. package/dist/core/ecs/resume.d.ts +0 -85
  433. package/dist/core/ecs/resume.d.ts.map +0 -1
  434. package/dist/core/ecs/snapshot_service.d.cts +0 -94
  435. package/dist/core/ecs/snapshot_service.d.ts +0 -94
  436. package/dist/core/ecs/snapshot_service.d.ts.map +0 -1
  437. package/dist/extensions/editor/editor.d.ts.map +0 -1
  438. package/dist/extensions/editor/field_handle.d.ts.map +0 -1
  439. package/dist/extensions/editor/index.cjs +0 -1
  440. package/dist/extensions/editor/index.d.cts +0 -21
  441. package/dist/extensions/editor/index.d.ts +0 -21
  442. package/dist/extensions/editor/index.d.ts.map +0 -1
  443. package/dist/extensions/editor/index.development.cjs +0 -1
  444. package/dist/extensions/reactive/ecs_sync.d.cts +0 -214
  445. package/dist/extensions/reactive/ecs_sync.d.ts +0 -214
  446. package/dist/extensions/reactive/ecs_sync.d.ts.map +0 -1
  447. package/dist/extensions/reactive/index.cjs +0 -1
  448. package/dist/extensions/reactive/index.d.cts +0 -23
  449. package/dist/extensions/reactive/index.d.ts +0 -23
  450. package/dist/extensions/reactive/index.d.ts.map +0 -1
  451. package/dist/extensions/reactive/index.development.cjs +0 -1
  452. package/dist/extensions/reactive/index.development.js +0 -215
  453. package/dist/extensions/reactive/index.js +0 -215
  454. package/dist/extensions/solid/index.cjs +0 -1
  455. package/dist/extensions/solid/index.d.cts +0 -6
  456. package/dist/extensions/solid/index.d.ts +0 -6
  457. package/dist/extensions/solid/index.d.ts.map +0 -1
  458. package/dist/extensions/solid/index.development.cjs +0 -1
  459. package/dist/extensions/solid/index.development.js +0 -40
  460. package/dist/extensions/solid/index.js +0 -36
  461. package/dist/extensions/solid/kernel_solid.d.cts +0 -45
  462. package/dist/extensions/solid/kernel_solid.d.ts +0 -45
  463. package/dist/extensions/solid/kernel_solid.d.ts.map +0 -1
  464. package/dist/host_commands-3jyFfWNg.js +0 -2326
  465. package/dist/host_commands-DNf0f6ko.cjs +0 -1
  466. package/dist/internal-C9jjL90H.js +0 -2485
  467. package/dist/internal-RZN14uMw.cjs +0 -2
  468. package/dist/interop-CT-REx0W.cjs +0 -1
  469. package/dist/interop-CcY6ASQc.js +0 -18
  470. package/dist/interop-DqxleBo7.js +0 -18
  471. package/dist/interop-T1HZ-Dpa.cjs +0 -1
  472. package/dist/kernel-Cz2Kxlwl.js +0 -240
  473. package/dist/kernel-DgyrLFjW.js +0 -227
  474. package/dist/kernel-sw7ScKWu.cjs +0 -1
  475. package/dist/kernel-yWV3XnAb.cjs +0 -1
  476. package/dist/reactive/array.d.cts +0 -24
  477. package/dist/reactive/array.d.ts +0 -24
  478. package/dist/reactive/array.d.ts.map +0 -1
  479. package/dist/reactive/index.cjs +0 -1
  480. package/dist/reactive/index.d.cts +0 -11
  481. package/dist/reactive/index.d.ts +0 -11
  482. package/dist/reactive/index.d.ts.map +0 -1
  483. package/dist/reactive/index.development.cjs +0 -1
  484. package/dist/reactive/index.development.js +0 -18
  485. package/dist/reactive/index.js +0 -18
  486. package/dist/reactive/interop.d.cts +0 -19
  487. package/dist/reactive/interop.d.ts +0 -19
  488. package/dist/reactive/interop.d.ts.map +0 -1
  489. package/dist/reactive/kernel.d.cts +0 -73
  490. package/dist/reactive/kernel.d.ts +0 -73
  491. package/dist/reactive/kernel.d.ts.map +0 -1
  492. package/dist/reactive/map.d.cts +0 -16
  493. package/dist/reactive/map.d.ts +0 -16
  494. package/dist/reactive/map.d.ts.map +0 -1
  495. package/dist/reactive/shallow.d.cts +0 -11
  496. package/dist/reactive/shallow.d.ts +0 -11
  497. package/dist/reactive/shallow.d.ts.map +0 -1
  498. package/dist/reactive/struct.d.cts +0 -10
  499. package/dist/reactive/struct.d.ts +0 -10
  500. package/dist/reactive/struct.d.ts.map +0 -1
  501. package/dist/shallow-9S1pY_Iw.js +0 -159
  502. package/dist/shallow-C5YmTbtb.cjs +0 -1
  503. package/dist/shallow-DQ8aG51_.cjs +0 -1
  504. package/dist/shallow-DfnAm9vG.js +0 -150
  505. package/dist/shared-BMXh9hxm.cjs +0 -1
  506. package/dist/shared-DQKK0i-E.js +0 -109
  507. package/dist/topological_sort-BDvEyb9W.cjs +0 -1
  508. package/dist/topological_sort-DK6EjpWa.js +0 -135
  509. package/dist/typed_arrays-BcXcnzB4.js +0 -258
  510. package/dist/typed_arrays-D3NmwtCG.cjs +0 -1
@@ -1,66 +1,30 @@
1
1
  import { EntityID } from './entity.js';
2
+ import { AccessorColumns, CursorBinder, SparseCursorCheck } from './ref.js';
2
3
  import { FrameTraceSink } from './frame_trace.js';
3
4
  import { ComponentDef, ComponentHandle, ComponentSchema, FieldValues } from './component.js';
4
- import { SparseComponentDef, SparseComponentID } from './sparse_store.js';
5
- import { RelationDef, RelationOptions } from './relation.js';
6
- import { EmptyEventSchema, EventDef, EventReader, EventShape } from './event.js';
5
+ import { SparseComponentStore, SparseComponentDef, SparseComponentID } from './sparse_store.js';
6
+ import { RelationDef, RelationHooks, RelationServiceHost } from './relation.js';
7
+ import { EventHooks } from './event.js';
8
+ import { ResourceRegistry } from './resource_registry.js';
7
9
  import { BitSet, TypedArrayTag } from '../../type_primitives/index.js';
8
10
  import { Archetype, ArchetypeID } from './archetype.js';
9
- import { Query, QueryHost } from './query.js';
11
+ import { Query } from './query.js';
12
+ import { QueryHost } from './query_cache.js';
13
+ import { QueryTerms } from './query_terms.js';
14
+ import { DrainResult, ObservationFlags, StructuralObserverEvents, Template } from './store_types.js';
10
15
  import { ObserverHost } from './observer.js';
16
+ import { ChangeFeed } from './change_feed.js';
17
+ import { EntityAllocator } from './entity_allocator.js';
18
+ import { SnapshotHooks, SnapshotHost } from './snapshot.js';
11
19
  import { InPlaceBufferAllocator, ColumnStoreRegionHandle, StoreRegionSpec, ColumnStore } from '../store/index.js';
12
20
  import { ECSMemoryCapContext } from './ecs_memory.js';
13
- export interface ComponentMeta {
14
- /** Optional debug name from `registerComponent(schema, { name })`
15
- * diagnostic messages only, never behaviour. */
16
- name?: string;
17
- fieldNames: string[];
18
- fieldIndex: Record<string, number>;
19
- fieldTypes: TypedArrayTag[];
20
- /** Has an onAdd observer — collect effective adds for this component. */
21
- obsAdd: boolean;
22
- /** Has an onRemove observer — collect effective removes for this component. */
23
- obsRem: boolean;
24
- /** Has an onDisable observer (#677) — collect effective disables for this
25
- * component at the toggle drain. */
26
- obsDisable: boolean;
27
- /** Has an onEnable observer (#677) — collect effective enables for this
28
- * component at the toggle drain. */
29
- obsEnable: boolean;
30
- /** Has a per-entity onSet observer — record dirty rows on the write path
31
- * (the ADR-0012 opt-in dirty list). */
32
- trackDirty: boolean;
33
- }
34
- /**
35
- * Effective `(component, entity)` structural events for one fixed-point round,
36
- * collected during `_flushAdds` / `_flushRemoves` and handed to the observer
37
- * dispatch hook. Flat parallel arrays, count-bounded (`*_len`), reused across
38
- * rounds — never reallocated in the flush. This is a scheduling artifact: it is
39
- * NOT part of `stateHash` or snapshot. See `observer.ts`.
40
- */
41
- export interface StructuralObserverEvents {
42
- addComp: number[];
43
- addEid: number[];
44
- addLen: number;
45
- remComp: number[];
46
- remEid: number[];
47
- remLen: number;
48
- /** Effective disable events (#677) — collected during the toggle drain
49
- * (`_flushToggles`), one per `(component, entity)` of each net-disabled
50
- * entity's mask. Empty on a structural (add/remove/destroy) round. */
51
- disComp: number[];
52
- disEid: number[];
53
- disLen: number;
54
- /** Effective enable events (#677), symmetric with the disable arrays. */
55
- enaComp: number[];
56
- enaEid: number[];
57
- enaLen: number;
58
- }
59
- /** Runtime shape of one template / `addComponents` entry: a def plus optional
60
- * field values (omitted fields zero-fill). The public authoring surface is
61
- * callable-bundle varargs, schema-checked per item by `StrictBundles`
62
- * (component.ts); the `ECS` facade normalizes those bundles into this erased
63
- * array before calling the store, so the store stays schema-agnostic. */
21
+ export type { ComponentMeta, DrainResult, ObservationFlags, StructuralObserverEvents, Template } from './store_types.js';
22
+ /** Runtime shape of one template entry or `addComponents` entry: a def plus
23
+ * optional field values (an omitted field zero-fills). The public authoring
24
+ * surface is callable-bundle varargs, schema-checked for each item by
25
+ * `StrictBundles` in `component.ts`. The `ECS` facade normalizes those bundles
26
+ * into this erased array before it calls the store, so the store stays
27
+ * schema-agnostic. */
64
28
  type TemplateEntryData = {
65
29
  readonly def: ComponentDef;
66
30
  readonly values?: Readonly<Record<string, number>>;
@@ -74,201 +38,259 @@ type TemplateFieldNames<Defs extends readonly ComponentDef[]> = Defs[number] ext
74
38
  export type TemplateOverrides<Defs extends readonly ComponentDef[]> = {
75
39
  readonly [K in TemplateFieldNames<Defs>]?: number;
76
40
  };
77
- declare const __templateDefs: unique symbol;
78
- /** A resolved template (#462) — an archetype template produced by
79
- * `ECS.template(...)`. **Opaque** apart from `defs`: callers hold it and pass
80
- * it to `ECS.spawn` / `ECS.spawnMany` (and may reference it in a system's
81
- * `spawns` / `despawns` access declaration — the scheduler expands it to
82
- * `defs`); the remaining fields are engine-internal and may change. `spawn`
83
- * lands an entity directly in `archetype_id` with zero archetype transitions,
84
- * writing `flatValues` (defaults in `_flatColumns` order) in one append
85
- * pass. See ADR-0010. */
86
- export interface Template<Defs extends readonly ComponentDef[] = readonly ComponentDef[]> {
87
- readonly archetypeId: ArchetypeID;
88
- readonly flatValues: number[];
89
- readonly overrideIndex: Map<string, number>;
90
- /** The component set this template spawns into, in entry order. */
91
- readonly defs: readonly ComponentDef[];
92
- readonly [__templateDefs]?: Defs;
93
- }
94
41
  export interface StoreOptions {
95
42
  initialCapacity?: number;
96
43
  /** Pluggable SAB buffer source. When provided, `createColumnStore`,
97
44
  * `extendColumnStore`, and `growColumnStore` route through it. Default is
98
- * `growableSabAllocator()`. Typed `InPlaceBufferAllocator` (#682): a live
45
+ * `growableSabAllocator`. Typed `InPlaceBufferAllocator`: a live
99
46
  * Store's flush loops hoist entity-index views across grows, so only
100
- * in-place allocators may back one (ADR-0008) — the constructor also
47
+ * in-place allocators may back one, the constructor also
101
48
  * runtime-asserts the marker for untyped JS callers. Consumers normally
102
- * don't touch this directly; `ECSOptions.memory` resolves to it. */
49
+ * don't touch this directly. `ECSOptions.memory` resolves to it. */
103
50
  bufferAllocator?: InPlaceBufferAllocator;
104
- /** Sizing intent the world was constructed with (#682), used to phrase
51
+ /** Sizing intent the world was constructed with, used to phrase
105
52
  * allocator-cap and entity-index-overflow errors in the caller's own
106
- * terms ("3.2× the declared budget") instead of raw bytes. Wired by
107
- * `ECS` from `resolveECSMemory`; absent for bare test Stores. */
53
+ * terms (a multiple of the declared budget) instead of raw bytes. Wired by
54
+ * `ECS` from `resolveECSMemory`. Absent for bare test Stores. */
108
55
  capContext?: ECSMemoryCapContext;
109
56
  /** Fired after every SAB resize (extend or grow). The new SAB has
110
57
  * already been built and archetypes have already refreshed their
111
- * views by the time this fires. Used by ECS to call
112
- * `sim.setLayout(0)` so WASM-side cached pointers re-walk. */
113
- onBufferResized?: () => void;
114
- /** Max live entities the SAB entity-index region holds (#245 / PR 4B).
115
- * Default `ENTITY_INDEX_DEFAULT_CAPACITY` (`1 << 20` the full EntityID
58
+ * views by the time this fires. `ECS` uses it to call `setLayout` on every
59
+ * subscribed layout listener, so a WASM-side cached pointer re-walks. */
60
+ onBufferReplaced?: () => void;
61
+ /** Max live entities the SAB entity-index region holds.
62
+ * Default `ENTITY_INDEX_DEFAULT_CAPACITY` (`1 << 20`, the full EntityID
116
63
  * index space). Exceeding this at runtime throws `EID_MAX_INDEX_OVERFLOW`.
117
- * Tests with small entity counts may set lower to bench the SAB region size
118
- * or to make index exhaustion reachable; a 1000-entity workload fits
119
- * comfortably in the default. */
64
+ * Tests with small entity counts may set lower to shrink the SAB region or
65
+ * to make index exhaustion reachable. */
120
66
  entityIndexCapacity?: number;
121
- /** Consumer-declared SAB regions (#623), forwarded verbatim to
67
+ /** Byte offset inside the backing where the store header goes. Default 0.
68
+ * Forwarded verbatim to `createColumnStore`, and every offset in the bytes
69
+ * is relative to it. `ECSOptions.memory.storeBase` resolves to it. */
70
+ storeBase?: number;
71
+ /** Consumer-declared SAB regions, forwarded verbatim to
122
72
  * `createColumnStore`. Each `StoreRegionSpec` carries an opaque `region_id`,
123
- * a precomputed byte size, and an `init` closure; the engine lays them out
124
- * generically and exposes them via `regionHandle(id)` / `regionOffset(id)`.
125
- * A game (e.g. `@internal/sim`'s region specs) supplies these the engine
126
- * ships no game regions of its own. Omitted ⇒ none. */
73
+ * a precomputed byte size, and an `init` closure. The engine lays them out
74
+ * generically and exposes them through `regionHandle(id)` and
75
+ * `regionOffset(id)`. A consumer supplies the specs. The engine ships no
76
+ * region of its own. Omitted ⇒ none. */
127
77
  regions?: readonly StoreRegionSpec[];
128
- /** Byte size of the opt-in sim-bindings region (#625), forwarded verbatim to
78
+ /** Byte size of the opt-in sim-bindings region, forwarded verbatim to
129
79
  * `createColumnStore`. A consumer that attaches a WASM backend passes its own
130
- * size (`@internal/sim`'s `SIM_BINDINGS_BYTES`, computed from the binding
131
- * manifest); the host then writes the `(component_id, field_id)` IDs into the
132
- * region. Omitted / 0 ⇒ no region (a pure-TS game pays nothing for the WASM
133
- * seam). De-welded from the engine ABI in #625 so a manifest edit doesn't
134
- * drift an engine golden. */
80
+ * size, computed from its own binding manifest. The host then writes the
81
+ * `(component_id, field_id)` ids into the region. Omitted or 0 ⇒ no region,
82
+ * so a pure-TS world pays nothing for the WASM seam. The size stays out of
83
+ * the engine ABI, so a manifest edit does not drift an engine golden. */
135
84
  bindingsRegionBytes?: number;
136
- /** Opt into the **determinism surface** (#626 / ADR-0020). Default `false`.
137
- * Gates the three methods that fold/serialize state in canonical (sorted)
85
+ /** Opt into the **determinism surface**. Default `false`.
86
+ * Gates the three methods that fold and serialize state in canonical (sorted)
138
87
  * order: `stateHash`, `snapshotSparse`, `restoreSparse`. When `false`
139
- * those throw `DETERMINISM_DISABLED` the canonical-ordering tax (sparse
88
+ * those throw `DETERMINISM_DISABLED`, the canonical-ordering tax (sparse
140
89
  * `canonicalIndices` sort + relation target-set sort) is never paid, and a
141
90
  * consumer can't accidentally read a non-canonical digest. When `true`,
142
- * today's behavior is reproduced bit-for-bit. This is the ONLY effect of the
91
+ * today's behavior is reproduced bit-for-bit. This is the only effect of the
143
92
  * flag: it does not touch the per-tick path, the in-place-allocator invariant
144
- * (ADR-0008, a memory-safety requirement that holds regardless), or the
93
+ * (a memory-safety requirement that holds regardless), or the
145
94
  * always-on `enabled_count` partition maintenance. The flag's value is a
146
- * capability gate, not a hot-path switch `stateHash`/snapshot are never
147
- * called per tick. */
95
+ * plugin gate, not a hot-path switch. `stateHash` and the snapshot are
96
+ * never called per tick. */
148
97
  deterministic?: boolean;
149
98
  }
150
- export declare class Store implements ObserverHost, QueryHost {
151
- private readonly entityAllocator;
152
- private readonly componentMetas;
153
- private componentCount;
154
- private readonly sparseStores;
155
- /** Debug names parallel to `sparseStores` — diagnostics only. */
156
- private readonly sparseNames;
157
- private readonly relationService;
158
- private readonly events;
159
- private readonly archGraph;
160
- private readonly registeredQueries;
161
- private emptyArchetypeId;
162
- private entityArchetype;
163
- private entityRow;
99
+ export declare class Store implements ChangeFeed, ObserverHost, QueryHost {
100
+ private readonly _entityAllocator;
101
+ private readonly _componentMetas;
102
+ private readonly _sparseStores;
103
+ /** Debug names parallel to `_sparseStores`, diagnostics only. */
104
+ private readonly _sparseNames;
105
+ /** The reused result of `drainSparseSet`, parallel to `_sparseStores`,
106
+ * allocated for a sparse component with an entity-level onSet. */
107
+ private readonly _sparseDrains;
108
+ private _relations;
109
+ private _events;
110
+ private readonly _archGraph;
111
+ private _emptyArchetypeId;
112
+ private _entityArchetypes;
113
+ private _entityRows;
114
+ private readonly _queries;
164
115
  private readonly _deferred;
165
- private readonly _snapshots;
166
- _tick: number;
167
- /** Per-world frame-trace sink (ADR-0030), installed via `ECS.setTrace`.
116
+ private _snapshots;
117
+ get relations(): RelationHooks;
118
+ /** The relation service, for a caller that is not `ecs.relations`.
119
+ *
120
+ * The getter above names `ecs.relations` in its fault. That is the wrong
121
+ * remedy when a system context or a query term reached the service. Every
122
+ * seam a user can reach passes its own name here. Error path only. The
123
+ * successful read is the same field read the getter makes. */
124
+ requireRelations(api: string): RelationHooks;
125
+ /** Build the host the relation service needs. Closures, not field
126
+ * references: `generations`, `entityArchetypes` and `entityRows` are
127
+ * reallocated when capacity grows, so each accessor re-reads the live
128
+ * field. */
129
+ relationHost(): RelationServiceHost;
130
+ /** Install the relations plugin. Called once, by the plugin. */
131
+ installRelations(service: RelationHooks): void;
132
+ get events(): EventHooks;
133
+ /** The event registry, for a caller that is not `ecs.events`. Names the
134
+ * seam the user reached, the way `requireRelations` does. */
135
+ requireEvents(api: string): EventHooks;
136
+ /** Whether the events plugin is installed. Read once per frame by the
137
+ * tick-tail clear, which must be a no-op on a world without channels. */
138
+ get hasEvents(): boolean;
139
+ /** Install the events plugin. Called once, by the plugin. */
140
+ installEvents(registry: EventHooks): void;
141
+ /** Build the host a snapshot plugin needs. Only the store can reach
142
+ * these fields, so it builds the record and the plugin owns the service.
143
+ * The accessors re-read live fields per call: the column store and the
144
+ * entity-index views are replaced on a restore. All cold path. */
145
+ snapshotHost(): SnapshotHost;
146
+ /** The allocator, which is its own snapshot seam. Handed to the plugin
147
+ * so the store need not construct the service itself. */
148
+ get entityAllocator(): EntityAllocator;
149
+ /** Install the snapshot plugin. Called once, by the plugin. */
150
+ installSnapshots(service: SnapshotHooks): void;
151
+ get snapshots(): SnapshotHooks;
152
+ get resources(): ResourceRegistry;
153
+ tick: number;
154
+ /** The change tick. A monotonic counter that the schedule advances before
155
+ * each system run, before each phase flush, before the onSet dispatch and
156
+ * at the end of each update. Every write stamps it on the archetype column
157
+ * it touches, and a consumer compares against the value of its own last
158
+ * run. The frame tick above cannot order a writer and a reader inside one
159
+ * frame, so a write by an earlier system was reported on two frames. This
160
+ * counter orders them. It starts above the initial column stamp, so the
161
+ * first run of a system sees every row that exists. A scheduling artifact:
162
+ * not in `stateHash` or the snapshot, and monotonic across a restore. */
163
+ changeTick: number;
164
+ /** Advance the change tick and return the new value, the stamp for the
165
+ * run that follows. Hot path: one increment per system run. */
166
+ advanceChangeTick(): number;
167
+ /** Per-world frame-trace sink, installed via `ECS.setTrace`.
168
168
  * `null` unless a consumer attaches a recorder. Every call site is
169
- * `if (DEV) store._trace?.…`, so production builds dead-code-eliminate
170
- * the seam and pay only this one nullable field. The sink observes; it never
171
- * folds into `stateHash` (a scheduling artifact, like `_changedTick` / the
172
- * observer state below). */
173
- _trace: FrameTraceSink | null;
169
+ * `if (DEV) store.trace?.…`, so production builds dead-code-eliminate
170
+ * the seam and pay only this one nullable field. The sink observes. It never
171
+ * folds into `stateHash`. It is a scheduling artifact, like `_changedTick`
172
+ * and like the observer state below. */
173
+ trace: FrameTraceSink | null;
174
174
  private _structuralObserverCount;
175
- /** Count of components with any onDisable/onEnable observer (#677). While 0
175
+ /** Count of components with any onDisable and onEnable observer. While 0
176
176
  * (with `_structuralObserverCount` also 0), `flushStructural` takes the
177
177
  * byte-for-byte fast path and the toggle drain skips event collection. */
178
178
  private _toggleObserverCount;
179
+ /** One flag record for each consumer of the change feed, keyed by the
180
+ * plugin name, each array indexed by component id and sparse. The
181
+ * store merges by OR across the records, so the flags a component carries
182
+ * are the union of what every consumer asked for. Cold: written when a
183
+ * consumer gains or loses an observer, read on the same call. */
184
+ private readonly _observationFlags;
185
+ /** The sparse half of the record above, one boolean for each sparse
186
+ * component a consumer wants the entity grain of. */
187
+ private readonly _sparseObservationFlags;
179
188
  /** Reused effective-event scratch for the current flush round. */
180
189
  private readonly _obsEvents;
181
- /** Installed via `setStructuralObserverHook` dispatches a round's collected
182
- * events to the observer registry (ordering + callbacks), which may enqueue
183
- * further structural ops. */
184
- private _structuralObserverHook;
185
- /** Install the structural-observer dispatch hook (called once by `ECS`
186
- * during construction) the named seam replacing direct writes to the
187
- * previously-public field (M1). */
188
- setStructuralObserverHook(fn: (ev: StructuralObserverEvents) => void): void;
190
+ /** Installed via `addStructuralHook`, each one dispatches a round's
191
+ * collected events to one consumer of the change feed, which may enqueue
192
+ * further structural ops. Empty on a world that installed no consumer, and
193
+ * the flush reads it once per round. */
194
+ private readonly _structuralHooks;
195
+ /** Add a consumer of the structural event batches. Each round of the
196
+ * observed flush hands its effective events to every hook, in install
197
+ * order. The events scratch is store-owned and reused, so a hook reads it
198
+ * during the call and copies whatever it keeps. Cold path. */
199
+ addStructuralHook(fn: (ev: StructuralObserverEvents) => void): void;
189
200
  private _collectDestroyEid;
190
201
  private readonly _collectDestroyRemoveBit;
191
202
  private _collectToggleEid;
192
203
  private readonly _collectDisableBit;
193
204
  private readonly _collectEnableBit;
194
- /** Net-transition snapshot for the toggle drain (#677): entity → its disabled
195
- * state at the START of the drain. Reused, cleared each drain. Lets
205
+ /** Net-transition snapshot for the toggle drain: entity → its disabled
206
+ * state at the start of the drain. Reused, cleared each drain. Lets
196
207
  * `_flushToggles` emit one event per *net* transition (disable→enable→disable
197
- * within a tick = a single onDisable) instead of one per buffered op required
208
+ * within a tick = a single onDisable) instead of one per buffered op, required
198
209
  * because the radix canonical-order pass would otherwise reorder duplicate eids
199
- * and mis-sequence a consumer's delete/republish. */
210
+ * and mis-sequence a consumer's delete and republish. */
200
211
  private readonly _toggleInitial;
201
- _anyDirtyTracked: boolean;
212
+ /** True once any component has row ticks. Gates the record at every write
213
+ * site, so a world without one pays a load and a branch there. */
214
+ anyDirtyTracked: boolean;
215
+ /** The components with row ticks, for the archetypes born later. */
202
216
  private readonly _dirtyTrackedCids;
203
217
  private readonly _dirtyLists;
204
- private readonly _dirtyMarks;
218
+ private readonly _drainResults;
205
219
  /** Set by any path that changes a SAB-backed archetype's live row count
206
- * (`flushStructural`/`flushDestroyed` when they did work; immediate
220
+ * (`flushStructural` and `flushDestroys` when they did work, immediate
207
221
  * `destroyEntity`, `addComponent(s)`, `removeComponent(s)` on the
208
- * Store). Cleared by `publishRowCountsToDescriptor`. Lets read-only
209
- * phases' `ctx.flush()` skip the descriptor walk entirely (#324). */
222
+ * Store). Cleared by `publishRowCounts`. Lets read-only
223
+ * phases' `ctx.flush` skip the descriptor walk entirely. */
210
224
  private _rowCountsDirty;
211
225
  /** Monotonic counter bumped by every membership-changing path (immediate
212
226
  * `addComponent(s)`, `removeComponent(s)`, `destroyEntity`,
213
227
  * `batchAddComponent`, `batchRemoveComponent`, `flushStructural`,
214
- * `flushDestroyed`, and new-archetype installs in `ArchetypeGraph.install`).
215
- * Read by `Query._nonEmpty()` via `QueryResolver._getQueryDirtyEpoch`
216
- * a query whose stored `_lastSeenEpoch` matches the current epoch
217
- * reuses its cached non-empty list (#327). Replaces the previous walk
218
- * over `registeredQueries` that wrote one dirty bit per query per
219
- * mutation; 5000 startup adds × Q queries used to be 5000×Q writes,
220
- * now it's 5000 integer increments. Public so ECS can forward through
221
- * its `QueryResolver` impl; not part of the user-facing API. */
222
- _queryDirtyEpoch: number;
223
- private readonly initialCapacity;
224
- private readonly _scratchTargetMask;
228
+ * `flushDestroys`, and new-archetype installs in `ArchetypeGraph.install`).
229
+ * Read by `Query.nonEmptyArchs()` via `QueryResolver.getQueryDirtyEpoch`.
230
+ * A query whose stored `_lastSeenEpoch` matches the current epoch
231
+ * reuses its cached non-empty list. Replaces the previous walk
232
+ * over `_registeredQueries` that wrote one dirty bit per query per
233
+ * mutation. A startup that adds many rows used to write one bit for each
234
+ * query at each mutation. Now it writes one integer increment. Public, so
235
+ * ECS can forward through its `QueryResolver` impl. It is not part of the
236
+ * user-facing API. */
237
+ queryDirtyEpoch: number;
225
238
  private _columnStore;
239
+ /** Row capacity every new archetype's columns start with. Taken from
240
+ * `StoreOptions.initialCapacity` at construction, read again by the
241
+ * archetype graph's `initialCapacity` host closure whenever it plants a
242
+ * new archetype. It sat under the row-grain banner, which owns the change
243
+ * detection state, and it sizes the backing instead. */
244
+ private readonly _initialCapacity;
226
245
  /** Installed on every SAB-backed Archetype so the Archetype can
227
246
  * request a SAB grow when an insertion would exceed its column
228
247
  * capacity. Doubles the offending archetype's row capacity (or jumps
229
248
  * to whatever fits `arch.length + additional`, whichever is larger),
230
249
  * reallocs the SAB via `growColumnStore` (live rows of every archetype
231
250
  * are carried forward), and republishes column views to every
232
- * SAB-backed archetype. Plan §8.1 / §8.3 / §8.4. (#171 §6.1.9 Phase 3) */
251
+ * SAB-backed archetype. */
233
252
  private readonly _growHandler;
234
- /** Build the intent-aware fatal for an allocator cap hit (#682). The
235
- * allocator can only name raw bytes; the Store knows what the caller
253
+ /** Build the intent-aware fatal for an allocator cap hit. The
254
+ * allocator can only name raw bytes. The Store knows what the caller
236
255
  * declared (`capContext`) and how many entities are live, so the error
237
- * says "3.2× the declared budget runaway creation upstream?" instead
256
+ * names the live count as a multiple of the declared budget instead
238
257
  * of leaving the caller to reverse-engineer byte counts. Fatality is
239
- * unchanged (#380: no grow-beyond-cap fallback). */
258
+ * unchanged (no grow-beyond-cap fallback). */
240
259
  private _capExceededError;
241
260
  private readonly _bufferAllocator;
242
261
  private readonly _capContext;
243
262
  private readonly _onBufferResized;
244
- /** Construct with an `initialCapacity` number (legacy form) or an
245
- * options object (new in PR 3D — adds `bufferAllocator` and
246
- * `onBufferResized` callback). Both signatures coexist so test fixtures
263
+ /** Construct with an `_initialCapacity` number (legacy form) or an
264
+ * options object (adds `bufferAllocator` and
265
+ * `onBufferReplaced` callback). Both signatures coexist so test fixtures
247
266
  * that pass `new Store(4)` keep working. */
248
267
  constructor(arg?: number | StoreOptions);
249
268
  /** Capacity of the entity-index SAB region (max slots ≈ max live
250
- * entities). Fixed at construction in #245's PR 4B scope; a future
269
+ * entities). Fixed at construction. A future
251
270
  * follow-up will grow it via `growColumnStore` when `entityHighWater`
252
271
  * hits the cap. */
253
272
  private readonly _entityIndexCapacity;
254
- /** Consumer-declared SAB regions (#623), captured so the realloc path
273
+ /** Byte offset of the store header inside the backing. Fixed at
274
+ * construction, and every offset in the bytes is relative to it. */
275
+ private readonly _storeBase;
276
+ /** Consumer-declared SAB regions, captured so the realloc path
255
277
  * re-lays them out. `undefined` when no consumer regions were declared.
256
278
  * The region contents survive a grow via the self-describing region table
257
- * (`extend.ts` snapshot/restore), so this is only the layout recipe. */
279
+ * (`extend.ts` snapshot and restore), so this is only the layout recipe. */
258
280
  private readonly _regions;
259
- /** Byte size of the opt-in sim-bindings region (#625). 0 ⇒ no region (the
260
- * pure-TS default). Captured so the initial `createColumnStore` reserves it;
261
- * across a realloc the size is re-derived from the old header by
262
- * `optionsFromOld`, so it is not threaded through the grow/extend path. */
281
+ /** Byte size of the opt-in sim-bindings region. 0 ⇒ no region (the
282
+ * pure-TS default). Captured so the initial `createColumnStore` reserves it.
283
+ * Across a realloc the size is re-derived from the old header by
284
+ * `optionsFromOld`, so it is not threaded through the grow and extend path. */
263
285
  private readonly _bindingsRegionBytes;
264
- /** Determinism opt-in (#626 / ADR-0020). When `false` (the default), the
265
- * canonical-ordering determinism surface (`stateHash` / `snapshotSparse` /
266
- * `restoreSparse`) throws `DETERMINISM_DISABLED` rather than running its
267
- * sort. Memory-safety invariants (the in-place allocator, ADR-0008) and the
268
- * `enabled_count` partition are unaffected — they hold regardless. */
286
+ /** Determinism opt-in. When `false` (the default), the canonical-ordering
287
+ * determinism surface throws `DETERMINISM_DISABLED` rather than run its
288
+ * sort. That surface is `stateHash`, `snapshotSparse` and `restoreSparse`.
289
+ * The memory-safety invariant of the in-place allocator and the
290
+ * `enabled_count` partition hold regardless. */
269
291
  private readonly _deterministic;
270
- /** Whether the determinism surface is enabled (#626). `false` ⇒ `stateHash`
271
- * / `snapshotSparse` / `restoreSparse` throw `DETERMINISM_DISABLED`. */
292
+ /** Whether the determinism surface is enabled. `false` ⇒ `stateHash`,
293
+ * `snapshotSparse` and `restoreSparse` throw `DETERMINISM_DISABLED`. */
272
294
  get deterministic(): boolean;
273
295
  /** Guard the canonical-ordering determinism surface. Throws
274
296
  * `DETERMINISM_DISABLED` when determinism wasn't opted into, naming the
@@ -276,183 +298,167 @@ export declare class Store implements ObserverHost, QueryHost {
276
298
  * (not `DEV`-gated): the surface is cold (never per-tick) so one boolean
277
299
  * check is free, and a silent non-canonical digest is the failure mode we're
278
300
  * preventing. */
279
- private _requireDeterministic;
280
- /** Reject `f32`/`f64` fields on a `deterministic: true` world at registration
281
- * (#777). IEEE-754 rounds differently across V8 / Bun / Zig at the 1-ULP
282
- * level, so a float column in a fixed-update path is a silent per-tick
283
- * `stateHash` divergence between client and server — the one thing the
284
- * determinism opt-in (ADR-0020) exists to prevent. Non-deterministic worlds
285
- * skip this entirely (floats stay allowed), so it costs the default path
286
- * nothing. `kind` names the storage class in the error ("component" /
287
- * "sparse component"); the array shorthand's `f64` default lands here too, so
288
- * a deterministic world must pass an explicit integer type. */
289
- private _rejectNonDeterministicFields;
301
+ private _assertDeterministic;
290
302
  /** Rebuild the Int32Array views over the SAB entity-index region
291
- * after a host-side SAB realloc (extend / grow). Called from
292
- * `_handleBufferResized` BEFORE the user-supplied `onBufferResized`
293
- * callback fires so any downstream reader sees coherent views. */
303
+ * after a host-side SAB realloc (extend and grow). Called from
304
+ * `_handleBufferResized`, before the user-supplied `onBufferReplaced`
305
+ * callback fires, so any downstream reader sees coherent views. */
294
306
  private _refreshEntityIndexViews;
295
- /** Centralised "SAB was just reallocated" handler. Refreshes the
296
- * Int32Array views FIRST (so user callbacks observe valid views),
307
+ /** Centralised "SAB was only reallocated" handler. Refreshes the
308
+ * Int32Array views first (so user callbacks observe valid views),
297
309
  * then mirrors `entityHighWater` into the region's length header,
298
310
  * then fires the user-supplied callback. */
299
311
  private _handleBufferResized;
300
- /** SAB backing every archetype's column views. Read-only handle; the
301
- * live mutation happens through `archGetOrCreateFromMask`.
302
- * Exposed for tests, snapshot/restore, and the upcoming
303
- * `columnStoreStateHash` wire-up. Production reads of column data should
312
+ /** SAB backing every archetype's column views. Read-only handle. The
313
+ * live mutation happens through `_archGetOrCreateFromMask`.
314
+ * Exposed for tests, snapshot and restore, and `columnStoreStateHash`.
315
+ * Production reads of column data should
304
316
  * still go through `Archetype.getColumnRead` (which sources from this
305
317
  * SAB under the hood). */
306
318
  get columnStore(): ColumnStore;
307
319
  /** Resolve a consumer-declared SAB region's byte offset by `region_id`, or
308
320
  * 0 when the region is absent (no region was declared with that id). The
309
- * generic, de-gamed replacement (#623) for the removed game-named accessors
310
- * (`terrain_view` / `spatial_grid_view` / … ); a consumer pairs this with
311
- * its own region module (e.g. `@internal/sim`'s region helpers) to
312
- * materialise a typed view. TS twin of Zig `abi.find_region`. */
321
+ * generic replacement for the removed game-named accessors, such as
322
+ * `terrain_view` and `spatial_grid_view`. A consumer pairs this with its own
323
+ * region module to materialise a typed view. It is the TS twin of the
324
+ * `abi.find_region` a WASM backend calls. */
313
325
  regionOffset(regionId: number): number;
314
326
  /** A handle to a consumer-declared SAB region resolved by `region_id`, or
315
- * `null` when absent. Carries the live `buffer`/`view` plus the region's byte
327
+ * `null` when absent. Carries the live `buffer` and `view` plus the region's byte
316
328
  * `offset` and `bytes`, so a consumer's region module can build a TypedArray
317
329
  * view over exactly the region's span without re-reading the directory.
318
- * Re-fetch after a SAB grow (the offset/view may have moved). (#623) */
330
+ * Re-fetch after a SAB grow (the offset and view may have moved). */
319
331
  regionHandle(regionId: number): ColumnStoreRegionHandle | null;
320
332
  /**
321
333
  * Stamp every SAB-backed archetype's live `length` into its descriptor's
322
- * `row_count` field (#252 / Phase 4 PR 4E). `extendColumnStore` /
334
+ * `row_count` field. `extendColumnStore` and
323
335
  * `growColumnStore` are the only other writers of `row_count`, and they
324
- * record the count at the moment of the resize `Archetype.addEntity`
336
+ * record the count at the moment of the resize, `Archetype.addEntity`
325
337
  * does not update it, so any insertion after the most recent resize
326
- * leaves the descriptor stale. Zig systems that drive their per-row loop
327
- * off `arch_hdr.row_count` (every `tick_*` export added since PR 3B)
328
- * read those stale bytes and silently skip the just-spawned rows.
338
+ * leaves the descriptor stale. A compute backend that drives its per-row
339
+ * loop off the descriptor's `row_count` reads those stale bytes and
340
+ * silently skips the newly spawned rows.
329
341
  *
330
342
  * Lockstep walk: SAB descriptors are written by `extendColumnStore` in
331
343
  * the order non-SAB archetypes are promoted, which is the same id-order
332
- * those archetypes occupy in `this.archGraph.archetypes`. Iterating that array
344
+ * those archetypes occupy in `this._archGraph.archetypes`. Iterating that array
333
345
  * once, skipping non-SAB entries, and advancing an `archAddr` cursor
334
346
  * by the descriptor's `column_count` lets us write `row_count` without
335
347
  * the throwaway `Map<archId, length>` the previous version allocated
336
- * on every call (#323). Cheap: descriptor-region seeks only, no column
348
+ * on every call. Cheap: descriptor-region seeks only, no column
337
349
  * I/O.
338
350
  *
339
- * Gated by `_rowCountsDirty` (#324) — mutation paths
340
- * (`flushStructural`, `flushDestroyed`, immediate `destroyEntity`,
341
- * `addComponent(s)`, `removeComponent(s)`) set the flag; this method
351
+ * Gated by `_rowCountsDirty`, mutation paths
352
+ * (`flushStructural`, `flushDestroys`, immediate `destroyEntity`,
353
+ * `addComponent(s)`, `removeComponent(s)`) set the flag. This method
342
354
  * clears it. Read-only phases that flush only to drain empty buffers
343
355
  * pay nothing. */
344
- publishRowCountsToDescriptor(): void;
356
+ publishRowCounts(): void;
345
357
  /** FNV-1a-style 32-bit digest over (archetype_id, live_row_count, live
346
358
  * column bytes) for each archetype in id order, followed by the sparse
347
- * stores (out-of-identity components, ADR-0011) in registration order.
348
- * Replaces the per-networked-component fold that compute_state_hash used
349
- * pre-#171 §6.1.9 Phase 5 — this is the canonical "live ECS state digest"
350
- * for cross-replay determinism.
359
+ * stores (out-of-identity components) in registration order.
360
+ * This is the canonical "live ECS state digest" for cross-replay
361
+ * determinism. It replaces the earlier per-networked-component fold.
351
362
  *
352
- * **Sparse coverage (#470).** Sparse data lives outside the archetype
353
- * graph, so it is folded separately after the archetype loop per store:
363
+ * **Sparse coverage.** Sparse data lives outside the archetype
364
+ * graph, so it is folded separately after the archetype loop, per store:
354
365
  * the sparse-component id, the member count, then each member's source
355
- * entity index + f64 field words, walked in CANONICAL ascending-index order
366
+ * entity index + f64 field words, walked in canonical ascending-index order
356
367
  * (`SparseComponentStore.canonicalIndices`). Canonical order is what makes
357
368
  * the digest insertion-order-independent: two worlds with identical sparse
358
- * contents built by different add/remove sequences agree. Keyed by entity
369
+ * contents built by different add and remove sequences agree. Keyed by entity
359
370
  * index, and destruction purges the slot, so a recycled index never carries
360
371
  * a stale occupant's data into the hash.
361
372
  *
362
373
  * It is strictly broader than the prior per-networked-component fold
363
- * (covers every column, not just a hand-picked subset of networked
374
+ * (covers every column, not only a hand-picked subset of networked
364
375
  * components), and strictly tighter than `columnStoreStateHash(...)`
365
376
  * which scans the full SAB including trailing unused capacity.
366
377
  *
367
- * **Per-word fold (#326).** The inner column loop folds one 32-bit
368
- * word at a time using FNV-1a's `xor + imul(PRIME)` step. This is NOT
369
- * byte-for-byte FNV-1a-32 of the column bytes — it's a deterministic
370
- * digest with the same equality semantics, ~4× faster than the per-
371
- * byte loop it replaces. Trailing 03 tail bytes (only possible for
372
- * u8/u16 columns at odd row counts) are folded together as a single
378
+ * **Per-word fold.** The inner column loop folds one 32-bit
379
+ * word at a time using FNV-1a's `xor + imul(PRIME)` step. This is not
380
+ * byte-for-byte FNV-1a-32 of the column bytes. It's a deterministic
381
+ * digest with the same equality semantics, and much quicker than the
382
+ * per-byte loop it replaces. Trailing 0 to 3 tail bytes (only possible for
383
+ * u8 or u16 columns at odd row counts) are folded together as a single
373
384
  * little-endian word so the algorithm stays branch-free in the inner
374
385
  * loop. The 4-byte `id` and `len` headers are folded as words for the
375
386
  * same reason. Byte order is little-endian to match the platform's
376
- * native TypedArray layout; the digest is opaque (no consumer compares
387
+ * native TypedArray layout. The digest is opaque (no consumer compares
377
388
  * against a literal value), so endianness is an implementation detail
378
389
  * rather than wire contract.
379
390
  *
380
391
  * Determinism: same store ⇒ same digest within a process, and across
381
- * processes on the same architecture (which is all `replay_match`
382
- * needs both replays run the same algorithm on the same words).
392
+ * processes on the same architecture, which is all a lockstep replay
393
+ * needs, because both replays run the same algorithm on the same words.
383
394
  *
384
- * **Opt-in (#626 / ADR-0020).** Throws `DETERMINISM_DISABLED` unless the
395
+ * **Opt-in.** Throws `DETERMINISM_DISABLED` unless the
385
396
  * Store was constructed with `{ deterministic: true }`. The canonical
386
397
  * ordering this fold relies on (sparse `canonicalIndices`, sorted relation
387
398
  * target sets) is the determinism tax the flag gates. */
388
399
  stateHash(): number;
389
- private archGet;
390
- /** Look up the `EntityID` at `row` in archetype `archetype_id`. Used
391
- * by a WASM system to resolve an
392
- * `EntityID` from an event-ring payload Zig writes
393
- * `(archId, row, …)` to the event ring,
394
- * and TS bridges it back through `ctx.emit(...)` via this method. (#250 /
395
- * Phase 4 PR 4D)
400
+ private _archGet;
401
+ /** Look up the `EntityID` at `row` in archetype `archetype_id`. A compute
402
+ * backend resolves an `EntityID` from an event-ring payload through it. The
403
+ * backend writes `(archId, row, …)` to the event ring, and TS bridges it
404
+ * back through `ctx.emit(...)` via this method.
396
405
  *
397
406
  * Throws `ECSError` if `archetype_id` is out of range or `row` is
398
- * past the archetype's live row count these would indicate a
399
- * ring-payload corruption or a stale row index (extend / grow
400
- * happened mid-tick), both of which are bugs the parity test would
401
- * surface. */
407
+ * past the archetype's live row count. Either one means a corrupt
408
+ * ring payload or a stale row index (an extend or a grow happened
409
+ * mid-tick), and both are defects. */
402
410
  entityIdAtRow(archetypeId: number, row: number): EntityID;
403
- /** Find or create an archetype for the given component mask see
411
+ /** Find or create an archetype for the given component mask, see
404
412
  * `ArchetypeGraph.getOrCreateFromMask`. */
405
- private archGetOrCreateFromMask;
406
- /** Bulk variant of `archGetOrCreateFromMask` one `extendColumnStore`
407
- * call for the whole batch (Phase C prewarm, #213). See
408
- * `ArchetypeGraph.createManyFromMasks`. */
413
+ private _archGetOrCreateFromMask;
414
+ /** Bulk variant of `_archGetOrCreateFromMask`, one `extendColumnStore`
415
+ * call for the whole batch (the prewarm pass). See
416
+ * `ArchetypeGraph.getOrCreateFromMasks`. */
409
417
  archCreateManyFromMasks(masks: readonly BitSet[]): ArchetypeID[];
410
- /** Snapshot every existing archetype's SAB rows, call `extendColumnStore`
411
- * once with `newSpecs`, then refresh every pre-existing SAB-backed
412
- * Archetype's TypedArray views. The single `existing` snapshot is the
413
- * key win in the bulk variant single-mask creation rebuilds it per
414
- * call (i.e. N times for N new archetypes). */
415
- private _archExtendStoreWithNewSpecs;
416
- /** Materialise the `Archetype` object for a freshly-minted graph node
417
- * binds the graph's topology to THIS store's column backing and grow
418
+ /** Call `extendColumnStore` once with `newSpecs`, then refresh every
419
+ * pre-existing SAB-backed Archetype's TypedArray views when the extend had
420
+ * to realloc. The `existing` row counts are built only when that realloc
421
+ * path runs: the in-place path moves no rows and reads no list, and to walk
422
+ * every archetype on every extend made the cost of the N-th archetype grow
423
+ * with N. The bulk variant makes one extend for many archetypes, so the
424
+ * list is built at most one time for them all. */
425
+ private _extendStore;
426
+ /** Materialise the `Archetype` object for a freshly-minted graph node,
427
+ * binds the graph's topology to this store's column backing and grow
418
428
  * handler (`ArchetypeGraphHost.materialize`). Store-owned so the graph
419
429
  * never touches `_columnStore`. */
420
430
  private _materializeArchetype;
421
- /** Push a newly-installed archetype into every registered query whose masks
422
- * it satisfies (`ArchetypeGraphHost.fanIntoQueries`; the query registry
423
- * stays on Store). No epoch bump — see the note in `ArchetypeGraph.install`. */
424
- private _fanIntoQueries;
425
431
  /** Resolve "add component_id to archetype_id" → target ArchetypeID (edge-cached). */
426
- private archResolveAdd;
432
+ private _archResolveAdd;
427
433
  /** Resolve "remove component_id from archetype_id" → target ArchetypeID (edge-cached). */
428
- private archResolveRemove;
434
+ private _archResolveRemove;
429
435
  createEntity(): EntityID;
430
- /** Allocate an entity slot WITHOUT placing it in the empty archetype, for
431
- * the template spawn paths. Returns the packed `EntityID`; the slot index
432
- * is left in `entityAllocator.lastIndex`. Skips the empty-archetype
436
+ /** Allocate an entity slot without placing it in the empty archetype, for
437
+ * the template spawn paths. Returns the packed `EntityID`. The slot index
438
+ * is left in `_entityAllocator.lastIndex`. Skips the empty-archetype
433
439
  * membership write `createEntity` performs (the caller installs the real
434
440
  * archetype + row). This *commits* the slot (bumps counts, stamps the
435
- * generation so `isAlive` is already true), so the caller MUST have
441
+ * generation so `isAlive` is already true), so the caller must have
436
442
  * reserved the column capacity for the row first
437
- * (`Archetype.ensureRowCapacity`) otherwise a cap throw from the
438
- * subsequent append leaves the slot phantom-alive (#775). */
443
+ * (`Archetype.reserveRows`), otherwise a cap throw from the
444
+ * subsequent append leaves the slot phantom-alive. */
439
445
  private _allocEntity;
440
446
  /** Pre-check that `count` fresh entity slots can be allocated without
441
- * exhausting the entity-index space, so `spawnMany` commits all-or-nothing
442
- * (#775). `_allocEntity`'s own per-call high-water guard would otherwise
447
+ * exhausting the entity-index space, so `spawnMany` commits all-or-nothing.
448
+ * `_allocEntity`'s own per-call high-water guard would otherwise
443
449
  * throw `EID_MAX_INDEX_OVERFLOW` partway through the alloc loop, leaving the
444
450
  * slots it already committed phantom-alive. Free-list reuse covers the first
445
- * `entityFreeIndices.length` slots; only the remainder draws down the
451
+ * `entityFreeIndices.length` slots. Only the remainder draws down the
446
452
  * high-water headroom. */
447
- private _ensureEntityIndexCapacity;
448
- /** Resolve a template: compute the target archetype (creating it if absent
453
+ private _assertEntityIndexCapacity;
454
+ /** Build a template: compute the target archetype (creating it if absent,
449
455
  * fits the prewarm model), pre-flatten default field values into
450
456
  * `_flatColumns` order, and build the override index (field name → flat
451
- * column index; `TEMPLATE_OVERRIDE_AMBIGUOUS` for a name shared by more than
457
+ * column index. `TEMPLATE_OVERRIDE_AMBIGUOUS` for a name shared by more than
452
458
  * one component, which a flat override cannot target). */
453
- resolveTemplate(entries: readonly TemplateEntryData[]): Template;
459
+ createTemplate(entries: readonly TemplateEntryData[]): Template;
454
460
  /** Resolve an override key to its flat column index, with the DEV guards
455
- * for unknown and ambiguous field names; `-1` means skip (the production
461
+ * for unknown and ambiguous field names. `-1` means skip (the production
456
462
  * fallback where DEV would have thrown). */
457
463
  private _resolveOverrideColumn;
458
464
  /** Apply per-instance overrides to the freshly-spawned row. Each key is a
@@ -466,20 +472,20 @@ export declare class Store implements ObserverHost, QueryHost {
466
472
  * applies any per-instance overrides. */
467
473
  spawn(p: Template, overrides?: Record<string, number | undefined>): EntityID;
468
474
  /** Bulk-spawn `count` identical entities into the template's archetype. The
469
- * field writes are O(columns) one `TypedArray.fill` per column via
470
- * `addEntitiesWithValues` not O(count×columns). Returns the new ids in
475
+ * field writes are O(columns), one `TypedArray.fill` per column via
476
+ * `addEntitiesWithValues`, not O(count×columns). Returns the new ids in
471
477
  * spawn order. */
472
478
  spawnMany(p: Template, count: number, overrides?: Record<string, number | undefined>): EntityID[];
473
479
  /** Immediately destroy an entity, removing it from its archetype.
474
480
  *
475
- * With no `delete`/`clear` target-cleanup policy registered (the common
476
- * case) this tears the one entity down and returns no allocation. When a
481
+ * With no `delete` or `clear` target-cleanup policy registered (the common
482
+ * case) this tears the one entity down and returns, no allocation. When a
477
483
  * policy is in play, a `delete`-target's sources are appended to a local
478
- * work-list this method then drains in the same iterative pass (#473, #492):
484
+ * work-list this method then drains in the same iterative pass:
479
485
  * the `work.length` re-read drives chains and trees out without recursion, so
480
486
  * depth is bounded by entity count, not tree depth. This mirrors the deferred
481
- * `flushDestroyed` buffer mechanism both paths are iterative and reach the
482
- * identical end state; the only difference is the shared `pendingDestroy`
487
+ * `flushDestroys` buffer mechanism, both paths are iterative and reach the
488
+ * identical end state. The only difference is the shared `pendingDestroy`
483
489
  * buffer there vs. a local work-list here. `isAlive` dedups a source reached
484
490
  * twice (diamonds) and terminates cycles, exactly as the generation guard does
485
491
  * in the deferred loop. */
@@ -488,71 +494,99 @@ export declare class Store implements ObserverHost, QueryHost {
488
494
  * then recycle (or retire) its slot. Shared by both immediate-destroy entry
489
495
  * points (the fast no-cascade path and the work-list driver in
490
496
  * `destroyEntity`). When `cascade` is non-null, a `delete`-policy target's
491
- * surviving sources are appended to it for the driver to drain (#473, #492);
497
+ * surviving sources are appended to it for the driver to drain.
492
498
  * `null` skips that collection for callers that cannot cascade. The caller
493
499
  * must have already confirmed `id` is alive. */
494
500
  private _destroyOne;
495
501
  /**
496
- * Liveness check, **fail-closed** against forged / retired / out-of-bounds
497
- * handles (#778). For a general-purpose engine that may receive a handle from
498
- * serialization, IPC, or any untrusted caller, three malformed inputs must read
502
+ * Liveness check, **fail-closed** against a forged, retired or out-of-bounds
503
+ * handles. For a general-purpose engine that may receive a handle from
504
+ * serialization, ipc, or any untrusted caller, three malformed inputs must read
499
505
  * dead rather than alias a slot:
500
- * - **Out of range** an `id` outside the 31-bit packed space (`< 0` or
506
+ * - **Out of range**, an `id` outside the 31-bit packed space (`< 0` or
501
507
  * `> MAX_ENTITY_ID`). Without this, the 20-bit index mask below silently
502
- * folds garbage high bits onto a valid slot. (Same bound the snapshot /
503
- * postMessage decode applies, #723.)
504
- * - **Tombstone generation** a handle carrying `RETIRED_GENERATION`, which
508
+ * folds garbage high bits onto a valid slot. (The snapshot decode and the
509
+ * `postMessage` decode apply the same bound.)
510
+ * - **Tombstone generation**, a handle carrying `RETIRED_GENERATION`, which
505
511
  * the allocator stamps into a retired slot and never issues to a live
506
512
  * entity, would otherwise match a retired slot's parked generation and read
507
513
  * alive (the ABA tombstone, previously documented as a known gap).
508
514
  * Both guards are comparisons predicted not-taken on the live path, so a
509
- * well-formed handle pays two branches and nothing else (#778 measured).
515
+ * well-formed handle pays two branches and nothing else.
510
516
  */
511
517
  isAlive(id: EntityID): boolean;
518
+ /**
519
+ * Liveness and the packed index in one result: the entity index if `id` is
520
+ * live, else `-1`. Same three fail-closed guards as `isAlive` (documented
521
+ * above), and the sole implementation of them, `isAlive` is a comparison on
522
+ * top of this.
523
+ *
524
+ * Why it returns the index instead of a boolean: every by-id caller needs
525
+ * both answers, and the pair used to cost two derivations of the same index.
526
+ * `hasComponent` called `isAlive(id)`, which computed `getEntityIndex(id)`
527
+ * internally, and then computed `getEntityIndex(id)` again to reach
528
+ * `_entityArchetypes`. The generational check has already touched the index. A
529
+ * caller that is about to index a parallel array with it should be handed the
530
+ * one that was computed, not re-derive it. The generations read is inlined
531
+ * here for the same reason, rather than delegated to
532
+ * `_entityAllocator.isAliveIndex`, which stays as the index-domain entry point
533
+ * for callers that already hold an index.
534
+ *
535
+ * **This gives much less than it appears to give.** The estimate for this
536
+ * change was a large decrease. The measurement shows that `has` and `isAlive`
537
+ * are only a little faster. The decrease is real, but it is much smaller than
538
+ * the estimate. The work that we removed was truly not necessary. But V8
539
+ * already made both one-line functions inline, and it already removed most of
540
+ * the duplicated calculation. Therefore "the work is plainly not necessary" is
541
+ * not an argument about performance for a jit compiler. This note stops the
542
+ * next reader from making the same estimate again.
543
+ */
544
+ private _liveIndex;
512
545
  get entityCount(): number;
513
546
  /** An archetype's row count moved from `preLen` to its current
514
547
  * `arch.length` on a **shrink** (rows removed: the source of a transition, a
515
- * destroy, a batch-source drain). Always marks SAB row counts dirty (#324 —
516
- * the descriptor walk just needs "something moved"); bumps the query-dirty
517
- * epoch (#327) only on a `length` 0/non-zero crossing, the only case where
518
- * `Query._nonEmptyArchetypes` can change on a shrink (#328). Mutations that
548
+ * destroy, a batch-source drain). Always marks SAB row counts dirty
549
+ * (the descriptor walk only needs "something moved"). It bumps the
550
+ * query-dirty epoch only when `length` crosses zero, the only case where
551
+ * `Query._nonEmptyArchetypes` can change on a shrink. Mutations that
519
552
  * move row counts within the same side (6→5) leave the non-empty set unchanged
520
553
  * and skip the bump.
521
554
  *
522
- * A shrink does **not** need the `enabledCount` crossing test (#812): the
523
- * only enabled-count move it can make is 1→0 (the last enabled row leaves an
524
- * archetype that keeps disabled rows), which leaves the archetype in a default
525
- * query's non-empty list as a harmless stale *inclusion* `count`/`forEach`
526
- * bound on `enabledCount` (now 0) iterate it zero times. Only a **grow** into
555
+ * A shrink does **not** need the `enabledCount` crossing test. The only
556
+ * enabled-count move it can make is 1→0, when the last enabled row leaves an
557
+ * archetype that keeps disabled rows. That leaves the archetype in a default
558
+ * query's non-empty list as a harmless stale *inclusion*. `count` and
559
+ * `forEach` bound on `enabledCount` (now 0), so they iterate it zero times.
560
+ * Only a **grow** into
527
561
  * an all-disabled archetype can stale-*exclude* a live row, so the enabled
528
562
  * crossing lives in `_onArchGrow`, off this path.
529
563
  *
530
- * **Inlining-sensitive keep the body tiny.** This function is called
531
- * once or twice per immediate-mode `addComponent` / `removeComponent` and the
532
- * mutation hot path depends on it being inlined at every call site.
533
- * #351 / PR #353 added an `if (registeredQueries.length === 0) return;`
534
- * gate to skip the bump for no-query workloads bench showed it
535
- * regressed `mutation: churn_loop` 16-18% because the extra statement
536
- * pushed the function past V8's per-call inlining budget. Reverted in
537
- * PR #355. Any future change here needs a `bench-vs-commit` run before
538
- * merging, not just code review. */
539
- private _onArchLenChange;
540
- /** An archetype **grew** rows were appended (the target of a transition, a
541
- * spawn, a batch-target fill). Like `_onArchLenChange` it marks row counts
564
+ * Inlining-sensitive, keep the body tiny. Each immediate-mode `addComponent`
565
+ * or `removeComponent` calls it once or twice. The mutation hot path depends
566
+ * on the inline at every call site.
567
+ * An earlier change added an `if (_registeredQueries.length === 0) return;`
568
+ * gate to skip the bump for no-query workloads. The mutation churn loop got
569
+ * far slower, because the extra statement pushed the function past V8's
570
+ * per-call inlining budget. The gate is no longer in the code. Measure the
571
+ * mutation hot path before you merge a change here. Code review alone is
572
+ * not sufficient. */
573
+ private _onArchShrink;
574
+ /** An archetype **grew**, rows were appended (the target of a transition, a
575
+ * spawn, a batch-target fill). Like `_onArchShrink` it marks row counts
542
576
  * dirty and bumps the query-dirty epoch on a `length` 0/non-zero crossing
543
577
  * (`includeDisabled` membership), but it *also* bumps on an `enabledCount`
544
- * 0→1 crossing (#812). The non-empty filter is field-split (#577): a default
578
+ * 0→1 crossing. The non-empty filter is field-split: a default
545
579
  * query keeps archetypes with `enabledCount > 0`. An enabled row appended to
546
580
  * an archetype that is non-empty but all-disabled (`length > 0,
547
581
  * enabledCount == 0`) crosses `enabledCount` 0→1 without touching `length`,
548
- * so the `preLen` test alone (the valid-while-`enabledCount === length`
549
- * pre-#577 proxy) misses it and a cached default query keeps a stale
550
- * `_nonEmpty` list. Only grows can do this, so only grow sites carry the test.
582
+ * so the `preLen` test alone (the earlier proxy, valid only while
583
+ * `enabledCount === length`) misses it and a cached default query keeps a stale
584
+ * `nonEmptyArchs` list. Only grows can do this, so only grow sites carry the test.
551
585
  *
552
586
  * **Precondition: ≥1 row was appended** (every caller adds at least one row),
553
- * so `arch.length > 0` afterward which is why the crossings simplify and the
554
- * body stays inlinable (the inlining caveat on `_onArchLenChange` applies
555
- * here too; verified with `bench-vs-commit mutation`). The general
587
+ * so `arch.length > 0` afterward, which is why the crossings simplify and the
588
+ * body stays inlinable (the inlining caveat on `_onArchShrink` applies
589
+ * here too). The general
556
590
  * `(pre === 0) !== (post === 0)` boundary test collapses given the post side:
557
591
  * - `length`: post > 0 always ⇒ a crossing iff `preLen === 0`.
558
592
  * - `enabledCount`: non-decreasing on a grow ⇒ a 0-crossing iff it was 0
@@ -561,17 +595,17 @@ export declare class Store implements ObserverHost, QueryHost {
561
595
  * read is short-circuited away on the hot path (`preLen` or `preEnabled`
562
596
  * non-zero), so a no-disabled workload pays only two scalar compares. */
563
597
  private _onArchGrow;
564
- /** Dirty bookkeeping for an enable/disable toggle (#577). `length` is
565
- * unchanged (no row added/removed) but `enabled_count` moved, so: republish
566
- * row counts (the descriptor's `enabled_count` changed, so the WASM sim and
567
- * snapshot see the new partition), and bump the query epoch only when the
568
- * *enabled* count crossed 0 the boundary at which an archetype enters/leaves
569
- * a query's non-empty set (`Query._nonEmpty` filters on `entityCount`, which
570
- * is now `enabled_count`). */
598
+ /** Dirty bookkeeping for an enable and disable toggle. `length` is
599
+ * unchanged (no row added or removed) but `enabled_count` moved, so: republish
600
+ * row counts, because the descriptor's `enabled_count` changed and a compute
601
+ * backend and the snapshot must see the new partition. Bump the query epoch only when
602
+ * the *enabled* count crossed 0. That is the boundary at which an archetype
603
+ * enters and leaves a query's non-empty set, because `Query.nonEmptyArchs`
604
+ * filters on `entityCount`, which is now `enabled_count`. */
571
605
  private _onArchEnabledChange;
572
606
  /** Immediately disable an entity (idempotent). The entity must hold at least
573
- * one component a component-less entity occupies no archetype row, so it
574
- * cannot be partitioned (a `DEV` error; prod no-op). */
607
+ * one component, a component-less entity occupies no archetype row, so it
608
+ * cannot be partitioned (a `DEV` error, prod no-op). */
575
609
  disableEntity(id: EntityID): void;
576
610
  /** Immediately enable an entity (idempotent). */
577
611
  enableEntity(id: EntityID): void;
@@ -579,250 +613,306 @@ export declare class Store implements ObserverHost, QueryHost {
579
613
  * disabled (it has no row to partition). */
580
614
  isDisabled(id: EntityID): boolean;
581
615
  /** 0-crossing detection for the per-entity flush paths (`_flushAdds`,
582
- * `_flushRemoves`) without per-entity Map traffic the same cost the
583
- * destroy drain shed in #457. Each touched archetype is stamped with the
616
+ * `_flushRemoves`) without per-entity Map traffic, the same cost the
617
+ * destroy drain also avoids. Each touched archetype is stamped with the
584
618
  * current flush epoch (`Archetype._flushSeenEpoch`), its pre-length and
585
- * pre-enabled-count recorded on first sight (`_flushPreLen` /
586
- * `_flushPreEnabled`), and pushed onto this scratch list;
619
+ * pre-enabled-count recorded on first sight (`_flushPreLen` and
620
+ * `_flushPreEnabled`), and pushed onto this scratch list
587
621
  * `_settleFlushDirty` walks the list once after the loop. The field
588
622
  * accesses per entity replace a `Map.has` + `Map.set` hash probe pair. The
589
623
  * epoch is bumped at settle so the next flush re-records. */
590
624
  private _flushEpoch;
591
625
  private readonly _flushTouched;
592
626
  /** Resolve dirty flags for a per-entity batch flush from the captured
593
- * pre-counts. Marks row counts dirty if any archetype was touched; bumps
627
+ * pre-counts. Marks row counts dirty if any archetype was touched. Bumps
594
628
  * the query epoch once if any touched archetype crossed the 0 boundary on
595
629
  * *either* `length` (includeDisabled membership) or `enabledCount`
596
- * (default-query membership) the deferred analog of the immediate
597
- * `_onArchLenChange` two-field check (#812). A single bump is sufficient
630
+ * (default-query membership), the deferred analog of the immediate
631
+ * `_onArchShrink` two-field check. A single bump is sufficient
598
632
  * (queries only need to know "something changed"). Clears the touched list
599
633
  * and advances the flush epoch on exit. */
600
634
  private _settleFlushDirty;
601
635
  destroyEntityDeferred(id: EntityID): void;
602
- /** Buffer an enable/disable toggle for the phase flush (#577). The row swap a
636
+ /** Buffer an enable and disable toggle for the phase flush. The row swap a
603
637
  * toggle performs would corrupt a `forEach` over that archetype if applied
604
- * mid-system, so it is deferred like add/remove. */
638
+ * mid-system, so it is deferred like an add and a remove. */
605
639
  disableEntityDeferred(id: EntityID): void;
606
640
  enableEntityDeferred(id: EntityID): void;
607
- /** Drain buffered enable/disable toggles, applying each in operation order via
641
+ /** Drain buffered enable and disable toggles, applying each in operation order via
608
642
  * the immediate path (which is idempotent and updates dirty flags). Called at
609
- * the flush boundary after structural adds/removes settle, so a toggle sees the
643
+ * the flush boundary after structural adds and removes settle, so a toggle sees the
610
644
  * entity's final archetype placement for the tick.
611
645
  *
612
- * When an onDisable/onEnable observer is registered (`_toggleObserverCount >
613
- * 0`, #677) this also collects effective toggle events into `_obsEvents` for
646
+ * When an onDisable and onEnable observer is registered (`_toggleObserverCount >
647
+ * 0`) this also collects effective toggle events into `_obsEvents` for
614
648
  * the dispatch hook, collapsed to one event per *net* transition across the
615
649
  * drain (see `_toggleInitial`). The no-observer path is byte-for-byte the
616
- * pre-#677 drain. */
650
+ * earlier drain. */
617
651
  private _flushToggles;
618
- /** Fan one entity's net toggle transition out to an onDisable / onEnable event
619
- * per carried component (#677). Walks the entity's archetype mask through the
620
- * matching pre-bound bit visitor; a component-less entity (no row) carries
652
+ /** Fan one entity's net toggle transition out to an onDisable and onEnable event
653
+ * per carried component. Walks the entity's archetype mask through the
654
+ * matching pre-bound bit visitor. A component-less entity (no row) carries
621
655
  * nothing and is skipped. */
622
656
  private _collectToggle;
623
657
  get pendingToggleCount(): number;
624
658
  /** Flush all buffered entity destructions in batch.
625
659
  *
626
660
  * When onRemove observers are registered (`_structuralObserverCount > 0`),
627
- * a destroy fires onRemove for every component the entity carried a destroy
628
- * *is* a remove of the whole mask collected here and dispatched by the
661
+ * a destroy fires onRemove for every component the entity carried, a destroy
662
+ * *is* a remove of the whole mask, collected here and dispatched by the
629
663
  * `flushStructural` fixed-point loop, the only caller in that mode (it drains
630
664
  * `pendingDestroy` each round so the trailing `ctx.flush()` call is a no-op).
631
665
  * Same commit-then-observe discipline as `_flushRemoves`: the entity is fully
632
666
  * freed before the callback runs, so onRemove receives the (now dead) eid as
633
667
  * the identity of what was destroyed, not a live handle to read. The
634
- * no-observer path is byte-for-byte unchanged (`collecting` gate). #531.
668
+ * no-observer path is byte-for-byte unchanged (`collecting` gate).
635
669
  *
636
670
  * Re-entrancy: while the observed fixed point owns the flush, the loop
637
- * drains destroys itself via `_drainDestroyed`, so a re-entrant
671
+ * drains destroys itself via `_flushDestroys`, so a re-entrant
638
672
  * `ctx.flush()` from a callback no-ops (the guard lives in
639
- * `DeferredCommandBuffer.flushDestroyed`) otherwise it would collect
673
+ * `DeferredCommandBuffer.flushDestroys`), otherwise it would collect
640
674
  * into the shared `_obsEvents` scratch mid-dispatch and corrupt it. */
641
- flushDestroyed(): void;
642
- private _drainDestroyed;
675
+ flushDestroys(): void;
676
+ private _flushDestroys;
643
677
  get pendingDestroyCount(): number;
644
678
  addComponentDeferred(entityId: EntityID, def: ComponentDef<Record<string, never>>): void;
645
679
  addComponentDeferred<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, values: FieldValues<S>): void;
646
680
  removeComponentDeferred(entityId: EntityID, def: ComponentDef): void;
647
- /** Phase-boundary structural flush. The drain policy no-observer fast
648
- * path, observed fixed point (adds/removes → destroys → toggles),
649
- * convergence guard, re-entrancy lives in `DeferredCommandBuffer`
650
- * (H1 step 4); the batch appliers it drives are the `_flush*` /
651
- * `_drainDestroyed` methods below. */
681
+ /** Phase-boundary structural flush. The drain policy, no-observer fast
682
+ * path, observed fixed point (adds and removes → destroys → toggles),
683
+ * convergence guard, re-entrancy, lives in `DeferredCommandBuffer`.
684
+ * The batch appliers it drives are the `_flush*` and
685
+ * `_flushDestroys` methods below. */
652
686
  flushStructural(): void;
653
687
  /** Batch-apply all deferred component additions. */
654
688
  private _flushAdds;
655
689
  /** Batch-apply all deferred component removals. */
656
690
  private _flushRemoves;
657
691
  get pendingStructuralCount(): number;
658
- /** Set the per-component observation flags from the registry's aggregate of
659
- * live observers for `cid`. Maintains `_structuralObserverCount` and
660
- * `_toggleObserverCount` (#677) (the fast-path gates) and lazily allocates the
692
+ /** Record what one consumer of the change feed asks the store to collect
693
+ * for `cid`, then apply the OR of every consumer's ask.
694
+ *
695
+ * `consumer` is the plugin name. The observer registry passes
696
+ * `"observers"`. One consumer dropping a flag never takes that flag away
697
+ * from another, which is why the merge sits in front of the transition
698
+ * logic. All-false is the same as absent. Cold path: a consumer calls this
699
+ * when it gains or loses an observer. */
700
+ configureObservation(consumer: string, cid: number, flags: ObservationFlags): void;
701
+ /** Set the per-component observation flags from the merged ask of every
702
+ * consumer for `cid`. Maintains `_structuralObserverCount` and
703
+ * `_toggleObserverCount` (the fast-path gates) and lazily allocates the
661
704
  * dirty list when per-entity onSet tracking turns on. */
662
- _configureComponentObservation(cid: number, hasAdd: boolean, hasRem: boolean, hasDisable: boolean, hasEnable: boolean, trackDirty: boolean): void;
663
- /** Record a per-entity onSet "changed" event for the entity. Called from the
664
- * field-write path (`SystemContext.setField` / `markChanged`) and gated by
665
- * the caller on `_anyDirtyTracked`. Appends to the dirty list only if the
666
- * dedup bit was clear (the ADR-0012 list + dedup-bit mechanism). */
667
- _noteSet(def: ComponentHandle, eid: EntityID): void;
668
- private _growDirtyMarks;
669
- /** Detach and return the dirty-row list for `cid`, clearing its dedup bits and
670
- * leaving the store with a fresh empty list (so re-dirties during the drain
671
- * accumulate for the NEXT tick, not this one). Returns a shared empty array
672
- * when nothing is dirty. Caller owns the returned array. */
673
- _takeDirty(cid: number): EntityID[];
674
- /** Clear any dirty dedup bits for a freed entity index across every tracked
675
- * component, so a recycled slot at the same index can be marked afresh. Gated
676
- * by `_anyDirtyTracked` at the destroy call sites. */
677
- private _clearDirtyForIndex;
705
+ private _applyObservation;
706
+ /** Give `cid` a row tick plane: one change tick for each row of every
707
+ * archetype that holds it, stamped by every write path. The row grain of
708
+ * change detection. An entity-level onSet observer calls this. Idempotent,
709
+ * and never undone. Cold. */
710
+ trackRows(def: ComponentHandle | SparseComponentDef): void;
711
+ /** Record what one consumer of the change feed asks the store to collect
712
+ * for sparse component `sid`, then apply the OR of every consumer's ask.
713
+ * Same merge rule as the dense form. Cold path. */
714
+ configureSparseObservation(consumer: string, sid: number, hasSet: boolean): void;
715
+ /** Row ticks on for sparse component `sid`, or the pending records dropped
716
+ * when the last consumer of its change feed leaves. */
717
+ private _applySparseObservation;
718
+ /** Collect the members of sparse component `sid` recorded since the last
719
+ * drain, for the entity-level onSet dispatch. `run` is the change tick of
720
+ * the dispatch. A member is alive by construction, because a destroy purges
721
+ * it, and the walk skips a disabled one, which default queries hide. The
722
+ * component-level change tick gates the walk, so an idle component costs
723
+ * one compare. Member order. The result array is reused. */
724
+ drainSparseSet(sid: number, run: number): EntityID[];
725
+ /** The row tick of `entityId`'s sparse component, 0 for a non-member, for
726
+ * `ctx.sparseChanged`. Throws when the component keeps no row ticks. */
727
+ sparseTickOf(def: SparseComponentDef, entityId: EntityID): number;
728
+ /** The store a mutable sparse cursor stamps through on each `at()`
729
+ * (ref.ts `createSparseCursor`). */
730
+ sparseTickPlane(def: SparseComponentDef): SparseComponentStore;
731
+ /** `trackRows` by component id, for the observer registry, which holds
732
+ * the id alone. */
733
+ private _trackRowsById;
734
+ /** The list length past which a frame switches to the scan. A list entry
735
+ * costs a push, three checks and a sort slot. A scan costs one compare per
736
+ * row of each stamped archetype, and those rows are at most the live
737
+ * entities, so a cap of a fraction of the live count keeps the list cost
738
+ * paid below the scan cost that replaces it, whatever the world's size. */
739
+ private _listCap;
740
+ /** Record a row for the entity-level onSet of `cid`, from a path that has
741
+ * resolved the archetype and the row (`setField`, `ref`, a cursor). Gated
742
+ * by the caller on `anyDirtyTracked`. Stamps the row tick, and pushes the
743
+ * entity onto the dirty list when the row's previous stamp lay at or below
744
+ * the last drain: the row is then new to the list. Hot path on the by-id
745
+ * writes of a tracked component: two loads, one compare, one store. */
746
+ noteSet(cid: number, arch: Archetype, row: number, eid: EntityID): void;
747
+ /** `noteSet` for a caller that holds the entity alone (`ctx.markChanged`).
748
+ * A dead entity, and one that does not hold the component, record nothing. */
749
+ noteSetEntity(def: ComponentHandle, eid: EntityID): void;
750
+ /** A chunk loop took the row tick column of `cid` (`cols.ticks`). Its
751
+ * stores make no list entry, so the next drain scans the plane. */
752
+ noteScan(cid: number): void;
753
+ /** Collect the rows recorded for `cid` since the last drain, for the
754
+ * entity-level onSet dispatch. `run` is the change tick of the dispatch.
755
+ *
756
+ * Two sources. `scanned` comes from a walk of the tick plane of every
757
+ * archetype stamped since the last drain, taken only in a frame where a
758
+ * chunk loop took `cols.ticks(def)`. A row inside the enabled partition is
759
+ * alive, a member and enabled by construction, so the dispatch fires these
760
+ * with no check, and the walk costs one compare per row of each stamped
761
+ * archetype. `listed` comes from the dirty list the by-id paths keep, minus
762
+ * the entries a scan covers. A listed entity may have died, lost the
763
+ * component or been disabled since its record, so the dispatch checks
764
+ * each. Both come back in row order, and the result object is reused. */
765
+ drainSet(cid: number, run: number): DrainResult;
678
766
  /** Visit every non-empty archetype containing `cid` whose component-column
679
- * changed at or after `baseline`, in canonical (ascending archetype-id) order
680
- * the archetype-granular onSet detection point. Reuses the existing
681
- * per-archetype change tick (free; no write-path cost). */
682
- _forEachChangedArchetype(cid: number, baseline: number, cb: (arch: Archetype) => void): void;
767
+ * changed after `baseline`, in canonical (ascending archetype-id) order,
768
+ * the archetype-granular onSet detection point. Reuses the existing
769
+ * per-archetype change tick (free, no write-path cost). `baseline` is the
770
+ * change tick of the consumer's previous visit, so a stamp equal to it was
771
+ * made by that visit and is not reported again. */
772
+ forEachChangedArchetype(cid: number, baseline: number, cb: (arch: Archetype) => void): void;
683
773
  /** Enabled live entities currently carrying `cid`, used by `yieldExisting` to
684
- * replay onAdd on registration. Bounded by `enabled_count` (#677): a disabled
774
+ * replay onAdd on registration. Bounded by `enabled_count`: a disabled
685
775
  * entity is excluded from default queries, so seeding it via onAdd would
686
- * publish a row that an immediate onDisable should have removed it is simply
687
- * absent at seed (the "delete on disable" semantics). Unordered here the
776
+ * publish a row that an immediate onDisable should have removed. It is
777
+ * absent at seed (the "delete on disable" semantics). Unordered here, the
688
778
  * registry radix-sorts. */
689
- _collectEntitiesWithComponent(cid: number): EntityID[];
779
+ collectEnabledWith(cid: number): EntityID[];
690
780
  registerComponent<S extends Record<string, TypedArrayTag>>(schema: S, name?: string): ComponentDef<S>;
691
781
  /** `'Pos' (component 5)` when the component was registered with a debug
692
- * name, else `component 5` the label diagnostics interpolate. */
782
+ * name, else `component 5`, the label diagnostics interpolate. */
693
783
  componentLabel(cid: number): string;
694
- /** Return the field index assigned to `(def, fieldName)` at component
695
- * registration. Indexes are insertion-order, zero-based, and stable for
696
- * the lifetime of the ECS. Used by systems that pass `(component_id,
697
- * field_id)` pairs across the WASM FFI (PR 3C / #231). */
784
+ /** The field index assigned to `(def, fieldName)` at registration. A system
785
+ * that passes `(component_id, field_id)` pairs across the WASM FFI resolves
786
+ * them once, at setup. */
698
787
  fieldIdOf(def: ComponentHandle, fieldName: string): number;
699
788
  /** Register a sparse component or tag. Unlike `registerComponent`, this
700
789
  * allocates from a separate id space and never touches the archetype mask,
701
790
  * so it does **not** count against `STORE_DESCRIPTOR_COMPONENT_LIMIT`. See
702
- * ADR-0011 and `sparse_store.ts`. */
791
+ * `sparse_store.ts`. */
703
792
  registerSparseComponent<S extends Record<string, TypedArrayTag>>(schema: S, name?: string): SparseComponentDef<S>;
704
- /** Sparse sibling of `componentLabel` sparse ids are a separate id space. */
793
+ /** Sparse sibling of `componentLabel`, sparse ids are a separate id space. */
705
794
  sparseLabel(sid: number): string;
706
- /** Allocate the backing sparse store WITHOUT the #777 float guard, for
707
- * engine-internal backings whose `f64` holds an EXACT integer rather than a
795
+ /** Allocate the backing sparse store without the float guard, for
796
+ * engine-internal backings whose `f64` holds an exact integer rather than a
708
797
  * user quantity: the exclusive-relation `{ target }` slot stores an `EntityID`
709
- * (≤ 2^53, so f64 is bit-exact and cross-host identical the ban targets float
798
+ * (≤ 2^53, so f64 is bit-exact and cross-host identical, the ban targets float
710
799
  * *arithmetic* rounding, which a target slot never undergoes). User schemas go
711
800
  * through `registerSparseComponent`, which guards first. */
712
801
  private _pushSparseStore;
713
- private sparseStoreOf;
802
+ private _sparseStoreOf;
714
803
  /** Add (or overwrite) a sparse component on an entity. No archetype
715
- * transition, no row copy the entity's `archetype_id` is unchanged. */
804
+ * transition, no row copy, the entity's `archetype_id` is unchanged. */
716
805
  addSparse(entityId: EntityID, def: SparseComponentDef, values?: Record<string, number>): void;
717
806
  /** Remove a sparse component from an entity. No-op if absent. */
718
807
  removeSparse(entityId: EntityID, def: SparseComponentDef): void;
719
- /** Total, like `hasComponent` `false` for a dead entity, never a throw. */
808
+ /** Total, like `hasComponent`, `false` for a dead entity, never a throw. */
720
809
  hasSparse(entityId: EntityID, def: SparseComponentDef): boolean;
721
810
  getSparseField(entityId: EntityID, def: SparseComponentDef, field: string): number;
722
811
  setSparseField(entityId: EntityID, def: SparseComponentDef, field: string, value: number): void;
723
812
  /** Drop all sparse data for a destroyed entity index so a recycled slot
724
- * can't inherit it. Gated by the caller on `sparseStores.length > 0` to
813
+ * can't inherit it. Gated by the caller on `_sparseStores.length > 0` to
725
814
  * keep the destroy hot path free when sparse storage is unused. */
726
815
  private _purgeSparse;
816
+ /** A sparse component's field names in schema order, the cursor's reserved
817
+ * name check (ref.ts). */
818
+ sparseFieldNames(def: SparseComponentDef): readonly string[];
819
+ /** A sparse component's accessor column array (ref.ts). Its identity is
820
+ * stable for the life of the store, so a cursor holds it from creation. */
821
+ sparseAccessorColumns(def: SparseComponentDef): AccessorColumns;
822
+ /**
823
+ * The DEV-only check a sparse cursor runs on every `at()` (ref.ts
824
+ * `createSparseCursor`): the access declaration (the same reason
825
+ * `cursorBinder` checks on `at()`), the liveness of the entity, and its
826
+ * membership. Production code never calls it, so a read of a non-member
827
+ * there gives whatever the column holds at that index, test with
828
+ * `hasSparse` first when the component can be absent, as `getSparseField`
829
+ * asks too.
830
+ */
831
+ sparseCursorCheck(def: SparseComponentDef, write: boolean): SparseCursorCheck;
727
832
  /** Serialize the sparse stores **and** relation side data to a self-contained
728
- * byte buffer the sparse half of a world snapshot (the dense half is the
729
- * SAB snapshot). Two framed sections: the sparse stores (`snapshot_sparse_-
730
- * stores` exclusive relation targets + multi membership ride here) followed
731
- * by the relation side data (`snapshotRelations` — multi forward target
833
+ * byte buffer, the sparse half of a world snapshot (the dense half is the
834
+ * SAB snapshot). Two framed sections: the sparse stores
835
+ * (`snapshotSparseStores`, exclusive relation targets and multi membership
836
+ * ride here) followed
837
+ * by the relation side data (`snapshotRelations`, multi forward target
732
838
  * sets, which live outside the sparse store). Both are written in canonical
733
839
  * entity-index order, so two worlds with identical contents inserted in
734
- * different orders snapshot byte-for-byte the same (#470). The reverse index
735
- * is derived and never serialized `restoreSparse` rebuilds it. Pairs with
840
+ * different orders snapshot byte-for-byte the same. The reverse index
841
+ * is derived and never serialized, `restoreSparse` rebuilds it. Pairs with
736
842
  * `restoreSparse`.
737
843
  *
738
- * **Opt-in (#626 / ADR-0020).** Throws `DETERMINISM_DISABLED` unless the
739
- * Store was constructed with `{ deterministic: true }` the canonical
844
+ * **Opt-in.** Throws `DETERMINISM_DISABLED` unless the
845
+ * Store was constructed with `{ deterministic: true }`, the canonical
740
846
  * entity-index ordering is the determinism tax the flag gates. */
741
847
  snapshotSparse(): Uint8Array;
742
848
  /** Repopulate the sparse stores from `snapshotSparse` bytes, replacing all
743
849
  * current sparse data (full-equality round-trip of membership + data), then
744
850
  * rebuild every relation's derived side indices: multi forward sets from the
745
851
  * relation section, and the reverse index for both cardinalities (exclusive
746
- * from the just-restored sparse target field, multi from the rebuilt forward
852
+ * from the newly restored sparse target field, multi from the rebuilt forward
747
853
  * sets). The sparse components and relations must already be registered in
748
- * the same order restore carries data, not the registration (which is
854
+ * the same order, restore carries data, not the registration (which is
749
855
  * code). Throws `SparseRestoreError` if the snapshot's shape, field identity,
750
856
  * entity-index bounds, or frame length don't validate.
751
857
  *
752
- * **Opt-in (#626 / ADR-0020).** Throws `DETERMINISM_DISABLED` unless the
753
- * Store was constructed with `{ deterministic: true }`; paired with
858
+ * **Opt-in.** Throws `DETERMINISM_DISABLED` unless the
859
+ * Store was constructed with `{ deterministic: true }`. Paired with
754
860
  * `snapshotSparse`, which produces the canonical bytes restore consumes. */
755
861
  restoreSparse(bytes: Uint8Array): void;
862
+ /** The members and their values are the snapshot's now, so no record made
863
+ * before the restore names a write of theirs. */
864
+ private _resetSparseTicks;
756
865
  /**
757
866
  * Capture the full live world to one self-contained byte buffer that
758
- * `restoreInto` can mount back onto a live, ticking world ("rewind a running
759
- * world and keep ticking"). Three sections (see `resume.ts`): the dense SAB
760
- * column bytes (`snapshotColumnStore`), the sparse + relation bytes
761
- * (`snapshotSparse`), and the host-side bookkeeping the SAB omits — the world
762
- * tick, the entity recycle free-list (in live order; no byte source, and its
867
+ * `restore` can mount back onto a live, ticking world ("rewind a running
868
+ * world and keep ticking"). Three sections, which the snapshots plugin
869
+ * frames: the dense SAB
870
+ * column bytes (`columnStoreBytesView`), the sparse + relation bytes
871
+ * (`snapshotSparse`), and the host-side bookkeeping the SAB omits, the world
872
+ * tick, the entity recycle free-list (in live order, no byte source, and its
763
873
  * order is load-bearing for byte-identical resume), the alive count, and each
764
- * archetype's `length` / `enabledCount`.
874
+ * archetype's `length` and `enabledCount`.
765
875
  *
766
- * **Opt-in (ADR-0020).** Throws `DETERMINISM_DISABLED` unless constructed with
767
- * `{ deterministic: true }` the sparse section rides the canonical-ordering
876
+ * **Opt-in.** Throws `DETERMINISM_DISABLED` unless constructed with
877
+ * `{ deterministic: true }`, the sparse section rides the canonical-ordering
768
878
  * surface and byte-identical resume is a determinism property. Pairs with
769
- * `restoreInto`.
879
+ * `restore`.
770
880
  *
771
- * **v1 scope.** Resources + events are NOT captured (resume requires
772
- * resource-free per-tick state; events are tick-cleared). Change-detection /
773
- * scheduler baselines (`changed()` queries) are likewise not captured — they
774
- * are scheduling artifacts, never folded into `stateHash`. Take the snapshot
775
- * at a tick boundary (between `update()`s). See the ADR. */
881
+ * Scope. It captures no resource and no event, because resume requires
882
+ * resource-free per-tick state and the tick clears events. It captures no
883
+ * change-detection baseline either, so a `changed()` query starts fresh.
884
+ * Those are scheduling artifacts, and never fold into `stateHash`. Take the
885
+ * snapshot at a tick boundary, between two `update()` calls. */
776
886
  snapshot(): Uint8Array;
777
887
  /**
778
888
  * Mount a `snapshot()` buffer onto this live world and leave it ready to keep
779
889
  * ticking. Fails closed on a malformed frame or a registration mismatch
780
- * BEFORE any live state is touched (the archetype/component graph is rebuilt
781
- * from code, not the snapshot same contract as `restoreSparse`). On
890
+ * before any live state is touched (the archetype and component graph is rebuilt
891
+ * from code, not the snapshot, same contract as `restoreSparse`). On
782
892
  * success the world's dense + sparse state, entity allocator, and tick are
783
893
  * exactly the captured world's.
784
894
  *
785
895
  * Requires a world whose SAB-backed archetype set + column layout match the
786
896
  * snapshot's exactly (prewarm so the archetype set is stable) and the same
787
- * entity-index capacity. **Opt-in (ADR-0020):** throws `DETERMINISM_DISABLED`
897
+ * entity-index capacity. **Opt-in:** throws `DETERMINISM_DISABLED`
788
898
  * unless `{ deterministic: true }`. See `snapshot()` for the v1 scope. */
789
- restoreInto(bytes: Uint8Array): void;
790
- /** Adopt a restored dense store (`SnapshotService.restoreInto`'s mount
899
+ restore(bytes: Uint8Array): void;
900
+ /** Adopt a restored dense store (the snapshot service's restore mount
791
901
  * step): swap the live backing, refresh every buffer-backed archetype's
792
902
  * views, recover the allocator high-water from the restored region, and
793
903
  * republish (the grow tail). Store-owned because it assigns
794
- * `_columnStore` the service never writes Store fields. */
904
+ * `_columnStore`, the service never writes Store fields. */
795
905
  private _mountRestoredDense;
796
- /** Rebuild each SAB-backed archetype's host-side `length` / `enabledCount` /
797
- * `_entityIds` after the dense backing was swapped in `restoreInto`. `length`
798
- * + the per-row entity-id back-reference come from a scan of the restored
799
- * entity-index region (which entity occupies which row); `enabledCount` comes
800
- * from the captured host-state (the #577 partition boundary is positional only
801
- * it has no per-entity byte source). */
906
+ /** Rebuild each SAB-backed archetype's host-side `length`, `enabledCount`
907
+ * and `_entityIds` after `restore` swapped the dense backing. `length` and
908
+ * the per-row entity-id back-reference come from a scan of the restored
909
+ * entity-index region, which says what entity occupies what row.
910
+ * `enabledCount` comes from the captured host state, because the partition
911
+ * boundary is positional and has no per-entity byte source. */
802
912
  private _reconstructHostRows;
803
- registerRelation(opts?: RelationOptions): RelationDef;
804
- /** Number of registered relations. Visible to tests asserting the
805
- * no-transition invariant alongside `archetype_count`. */
806
- get relationCount(): number;
807
- addRelation(src: EntityID, def: RelationDef, tgt: EntityID): void;
808
- removeRelation(src: EntityID, def: RelationDef, tgt?: EntityID): void;
809
- targetOf(src: EntityID, def: RelationDef): EntityID | undefined;
810
- targetsOf(src: EntityID, def: RelationDef): EntityID[];
811
- sourcesOf(tgt: EntityID, def: RelationDef): EntityID[];
812
- hasRelation(src: EntityID, def: RelationDef): boolean;
813
- pairsOf(def: RelationDef): readonly (readonly [EntityID, EntityID])[];
814
- sourcesOfAny(tgt: EntityID): readonly (readonly [RelationDef, EntityID])[];
815
- relationBackingSparseId(def: RelationDef): SparseComponentID;
816
- /** Drive a `(*, T)` wildcard query (`Query.forEachRelatedTo`) — see
817
- * `RelationService.forEachRelationTargetMatch`. */
818
- _forEachRelationTargetMatch(target: EntityID, include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, sparseInclude: readonly SparseComponentID[], sparseExclude: readonly SparseComponentID[], includeDisabled: boolean, cb: (entityId: EntityID) => void): void;
819
- compactRelations(): number;
820
- ancestorsOf(src: EntityID, def: RelationDef): EntityID[];
821
- rootOf(src: EntityID, def: RelationDef): EntityID;
822
- cascadeOf(root: EntityID, def: RelationDef): EntityID[];
823
- /** Second query-match path (#469 / ADR-0011): iterate entities matching a
913
+ /** Second query-match path: iterate entities matching a
824
914
  * dense mask **and** sparse-membership terms, invoking `cb` per entity.
825
- * Yields `EntityID`s, not archetype spans sparse members are scattered
915
+ * Yields `EntityID`s, not archetype spans, sparse members are scattered
826
916
  * across archetypes, so there is no SoA column to hand back. Driven by the
827
917
  * cheapest candidate set:
828
918
  *
@@ -835,65 +925,83 @@ export declare class Store implements ObserverHost, QueryHost {
835
925
  * store.
836
926
  * - **neither** → walk `denseArchetypes`' entity ids (dense-only fallback).
837
927
  *
838
- * Only reached via `Query.forEachEntity`; dense `forEach` never consults
839
- * the sparse stores, so dense-only queries are unaffected (#469 AC). */
840
- _forEachSparseMatch(include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, sparseInclude: readonly SparseComponentID[], sparseExclude: readonly SparseComponentID[], denseArchetypes: readonly Archetype[], cb: (entityId: EntityID) => void, includeDisabled: boolean): void;
841
- /** Fourth query-match path (#581): the matched set in hierarchy depth order
842
- * (parents before children) see `RelationService.forEachHierarchyMatch`. */
843
- _forEachHierarchyMatch(include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, sparseInclude: readonly SparseComponentID[], sparseExclude: readonly SparseComponentID[], denseArchetypes: readonly Archetype[], relation: RelationDef, maxDepth: number, includeDisabled: boolean, cb: (entityId: EntityID) => void): void;
928
+ * Only reached via `Query.forEachEntity`. Dense `forEach` never consults
929
+ * the sparse stores, so dense-only queries are unaffected. */
930
+ forEachSparseMatch(include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, terms: QueryTerms, denseArchetypes: readonly Archetype[], cb: (entityId: EntityID) => void): void;
931
+ /** Fourth query-match path: the matched set in hierarchy depth order
932
+ * (parents before children), see `forEachHierarchyMatch` on the relation service. */
933
+ forEachHierarchyMatch(include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, terms: QueryTerms, denseArchetypes: readonly Archetype[], relation: RelationDef, maxDepth: number, cb: (entityId: EntityID) => void): void;
934
+ /** Third query-match path: the `(*, T)` wildcard, every source pointing at
935
+ * one target. `Query.forEachRelatedTo` is the seam the user reaches. */
936
+ forEachTargetMatch(target: EntityID, include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, terms: QueryTerms, cb: (entityId: EntityID) => void): void;
937
+ /** The sparse membership store a relation rides, which is how a query turns
938
+ * a `(R, *)` term into a sparse term. `api` names the query verb the user
939
+ * called, because `andRelation` and `notRelation` both land here. */
940
+ relationBackingSparseId(def: RelationDef, api: string): SparseComponentID;
941
+ private readonly _scratchTargetMask;
844
942
  addComponent(entityId: EntityID, def: ComponentDef<Record<string, never>>): void;
845
943
  addComponent<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, values: FieldValues<S>): void;
944
+ /** @internal, cold tail of `addComponent`: the entity already holds `def`
945
+ * (overwrite in place, no transition), or the (source, component) add edge
946
+ * has not been cached yet (first time this pair is seen, resolve, which
947
+ * plants the edge, then re-enter the hot body). Split out so the edge-hit
948
+ * path above stays a straight line with one holey-array probe. This runs at
949
+ * most once per (archetype, component) pair plus on in-place overwrites. */
950
+ private _addComponentCold;
846
951
  /** Add multiple components in one transition (resolves final archetype, then moves once).
847
952
  *
848
953
  * Final-mask resolve, not graph walk. The previous implementation called
849
- * `archResolveAdd` once per entry, which threaded through every
850
- * intermediate archetype on the path and each unseen intermediate
954
+ * `_archResolveAdd` once per entry, which threaded through every
955
+ * intermediate archetype on the path, and each unseen intermediate
851
956
  * triggered a fresh `extendColumnStore` even though no entity ever lived
852
957
  * there. Computing the union mask up front and resolving once via
853
- * `archGetOrCreateFromMask` collapses N-1 intermediate-archetype
854
- * creations into zero for the batched case (#211 follow-up). The lazy
855
- * single-mask path remains the same; this just avoids feeding it
958
+ * `_archGetOrCreateFromMask` collapses N-1 intermediate-archetype
959
+ * creations into zero for the batched case. The lazy
960
+ * single-mask path remains the same. This only avoids feeding it
856
961
  * archetypes the entity never visits.
857
962
  *
858
- * #659 — composite-add edge cache. The final-mask resolve, unlike the
963
+ * Composite-add edge cache. The final-mask resolve, unlike the
859
964
  * single-add `edges[]` walk, re-pays a per-call `mask.hash()`, `ArchetypeGraph.lookup`
860
- * (the Map-of-buckets + `equals` scan), and `getBatchTransitionMap` on
861
- * every call a ~2× gap vs a cached edge walk that the decomposition probe
862
- * pinned on the two `Map.get`s, not the hash. So a repeated (source, added-
863
- * set) add now resolves through `currentArch`'s composite-add cache: one
965
+ * (the Map-of-buckets + `equals` scan), and `transitionMapTo` on
966
+ * every call. That is much slower than a cached edge walk, and a probe put the
967
+ * cost on the two `Map.get` calls, and not on the hash. So a repeated (source,
968
+ * added-set) add now resolves through `currentArch`'s composite-add cache: one
864
969
  * `Map.get` on an exact packed key yields the target + transition map, and we
865
970
  * skip the union-mask build entirely. First call per key still resolves via
866
- * the final-mask path below (no intermediate planting) and plants the edge.
867
- * See docs/reports/bench/regressions/add-components-composite-edge.md. */
971
+ * the final-mask path below (no intermediate planting) and plants the edge. */
868
972
  addComponents(entityId: EntityID, entries: readonly TemplateEntryData[]): void;
869
- /** Shared move+write tail of `addComponents` (#659): place the entity into
870
- * the already-resolved `targetArch` a `moveEntityFrom` along the cached
973
+ /** Shared move+write tail of `addComponents`: place the entity into
974
+ * the already-resolved `targetArch`, a `moveEntityFrom` along the cached
871
975
  * `map` when it has a row, else a fresh append (the rowless empty-archetype
872
- * source ignores `map`) then write every entry's fields. Both the
976
+ * source ignores `map`), then write every entry's fields. Both the
873
977
  * composite-edge-cache hit and the final-mask cold path funnel through here so
874
978
  * the placement logic lives once. */
875
979
  private _addComponentsInto;
876
980
  removeComponent(entityId: EntityID, def: ComponentDef): void;
981
+ /** @internal, cold tail of `removeComponent`: the entity doesn't hold `def`
982
+ * (no-op), or the (source, component) remove edge has not been cached yet.
983
+ * Mirror of `_addComponentCold`. Same depth-1 re-entry argument. */
984
+ private _removeComponentCold;
877
985
  /** Remove multiple components in one transition (resolves final archetype, then moves once).
878
986
  *
879
987
  * Final-mask resolve, not graph walk. Same rationale as `addComponents`
880
- * above the previous per-step path threaded `archResolveRemove`
988
+ * above, the previous per-step path threaded `_archResolveRemove`
881
989
  * once per def, which materialised every intermediate archetype on the
882
990
  * removal path. Computing the difference mask up front and resolving
883
991
  * once avoids planting N-1 intermediates the entity never lives in. */
884
992
  removeComponents(entityId: EntityID, defs: ComponentDef[]): void;
885
- /** Total (POLISH_AUDIT #9): a dead/stale `entityId` returns `false` rather
886
- * than throwing a "has" probe is exactly what callers reach for to avoid
993
+ /** Total: a dead or stale `entityId` returns `false` rather
994
+ * than throwing, a "has" probe is exactly what callers reach for to avoid
887
995
  * touching dead entities, so it must be safe to ask. */
888
996
  hasComponent(entityId: EntityID, def: ComponentHandle): boolean;
889
997
  /**
890
- * Bulk add a component to ALL entities in the given archetype.
998
+ * Bulk add a component to all entities in the given archetype.
891
999
  * Uses TypedArray.set() for O(columns) instead of O(N×columns).
892
1000
  * The archetype must not already contain this component.
893
1001
  */
894
1002
  batchAddComponent(src: ArchetypeID, def: ComponentDef, values?: Record<string, number>): void;
895
1003
  /**
896
- * Bulk remove a component from ALL entities in the given archetype.
1004
+ * Bulk remove a component from all entities in the given archetype.
897
1005
  * Uses TypedArray.set() for O(columns) instead of O(N×columns).
898
1006
  * The archetype must contain this component.
899
1007
  */
@@ -901,37 +1009,62 @@ export declare class Store implements ObserverHost, QueryHost {
901
1009
  getEntityArchetype(entityId: EntityID): Archetype;
902
1010
  getEntityRow(entityId: EntityID): number;
903
1011
  /**
904
- * Find all archetypes matching the given masks.
905
- * Uses the inverted componentIndex to start from the component with the
906
- * fewest archetypes, minimizing the number of superset checks.
1012
+ * The row `resolveEntity` placed the entity at, the alloc-free second
1013
+ * return value of a resolve, read immediately after the call. Same out-param
1014
+ * pattern as `EntityAllocator.lastIndex` and `_moveResult`. Returning a
1015
+ * `{ arch, row }` pair instead would allocate on every by-id read.
1016
+ */
1017
+ resolvedRow: number;
1018
+ /**
1019
+ * (archetype, row) for a by-id access, derived from one index computation.
1020
+ *
1021
+ * `getEntityArchetype` and `getEntityRow` are each one line, and every by-id
1022
+ * caller needs both, so the pair cost two derivations of the same packed
1023
+ * index and two call frames to read two elements of two parallel arrays
1024
+ * addressed identically. This is that pair, fused: index once, publish the
1025
+ * row on `resolvedRow`, return the archetype.
1026
+ *
1027
+ * The two single-purpose accessors stay above, tests reach for one half at a
1028
+ * time, but no runtime path uses them in a pair any more.
1029
+ */
1030
+ resolveEntity(entityId: EntityID): Archetype;
1031
+ /**
1032
+ * Build the `at(entity)` binder a `ComponentCursor` repoints itself through
1033
+ * (ref.ts). Handing the cursor a closure rather than the Store keeps the
1034
+ * mutation surface out of a value that user code holds onto.
1035
+ *
1036
+ * `stampTick` distinguishes the two variants once, here, instead of per
1037
+ * `at()`: a mutable cursor bumps the component's change tick on every
1038
+ * repoint (matching `ctx.ref`), a read-only one never does.
1039
+ *
1040
+ * The access check lives here, in the binder, and not only at the call that
1041
+ * creates the cursor. A cursor is made one time and then kept, so it outlives
1042
+ * the span that made it. A cursor made at host level writes an undeclared
1043
+ * component when a system body uses it, because no system is active at
1044
+ * creation and the check there passes. A `ctx.cursor` that a system stores in
1045
+ * an outer variable does the same in the next system. Both slip
1046
+ * past a check that only runs at creation. `at()` is the point of use, so the
1047
+ * check belongs on it. The creation-site check stays as well: it fails early,
1048
+ * and its stack names the line that made the cursor.
1049
+ */
1050
+ cursorBinder(def: ComponentHandle, stampTick: boolean): CursorBinder;
1051
+ /** A component's field names in schema order, the cursor prototype key and
1052
+ * ordinal source (ref.ts). One array per component, owned by its meta. */
1053
+ componentFieldNames(def: ComponentHandle): readonly string[];
1054
+ /**
1055
+ * Find all archetypes matching the given masks. Delegates to
1056
+ * `QueryRegistry`, which starts the superset scan from the smallest
1057
+ * component bucket. Query mint path only.
907
1058
  */
908
1059
  getMatchingArchetypes(required: BitSet, excluded?: BitSet, anyOf?: BitSet): readonly Archetype[];
909
1060
  /**
910
- * Register a live query. Returns a mutable Archetype[] that this Store will
911
- * push newly-created matching archetypes into, keeping the query always up-to-date.
1061
+ * Register a live query. Returns a mutable Archetype[] that the registry
1062
+ * pushes newly-created matching archetypes into, keeping the query always
1063
+ * up-to-date.
912
1064
  */
913
1065
  registerQuery(include: BitSet, exclude?: BitSet, anyOf?: BitSet): Archetype[];
914
1066
  updateQueryRef(result: Archetype[], query: Query<any>): void;
915
1067
  get archetypeCount(): number;
916
- registerEvent<S extends EventShape<S>>(fields: readonly (keyof S & string)[]): EventDef<S>;
917
- emitEvent(def: EventDef<any>, values: Record<string, number>): void;
918
- emitSignal(def: EventDef<EmptyEventSchema>): void;
919
- getEventReader<S extends EventShape<S>>(def: EventDef<S>): EventReader<S>;
920
- clearEvents(): void;
921
- /** `DEV`-only mid-update emit detection — see
922
- * `EventRegistry.devBufferedEventCount`. */
923
- _devBufferedEventCount(): number;
924
- registerEventByKey<S extends EventShape<S>>(key: symbol, fields: readonly (keyof S & string)[]): EventDef<S>;
925
- getEventDefByKey(key: symbol): EventDef<any>;
926
- hasEventKey(key: symbol): boolean;
927
- private readonly resources;
928
- registerResource(key: symbol, value: unknown): void;
929
- getResource(key: symbol): unknown;
930
- setResource(key: symbol, value: unknown): void;
931
- /** Fails closed on a missing key; the present → absent → present
932
- * lifecycle (#798) — see `ResourceRegistry.remove`. */
933
- removeResource(key: symbol): void;
934
- hasResource(key: symbol): boolean;
1068
+ private readonly _resources;
935
1069
  }
936
- export {};
937
1070
  //# sourceMappingURL=store.d.ts.map