@oasys/oecs 0.4.0 → 0.5.1

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 (273) hide show
  1. package/CHANGELOG.md +636 -0
  2. package/README.md +47 -32
  3. package/dist/core/ecs/access_check.d.cts +77 -0
  4. package/dist/core/ecs/access_check.d.ts +8 -8
  5. package/dist/core/ecs/access_check.d.ts.map +1 -1
  6. package/dist/core/ecs/archetype.d.cts +486 -0
  7. package/dist/core/ecs/archetype.d.ts +25 -13
  8. package/dist/core/ecs/archetype.d.ts.map +1 -1
  9. package/dist/core/ecs/archetype_graph.d.cts +96 -0
  10. package/dist/core/ecs/archetype_graph.d.ts +96 -0
  11. package/dist/core/ecs/archetype_graph.d.ts.map +1 -0
  12. package/dist/core/ecs/builtin_relations.d.cts +43 -0
  13. package/dist/core/ecs/builtin_relations.d.ts +8 -8
  14. package/dist/core/ecs/builtin_relations.d.ts.map +1 -1
  15. package/dist/core/ecs/command_log.d.cts +116 -0
  16. package/dist/core/ecs/command_log.d.ts +13 -10
  17. package/dist/core/ecs/command_log.d.ts.map +1 -1
  18. package/dist/core/ecs/component.d.cts +201 -0
  19. package/dist/core/ecs/component.d.ts +100 -5
  20. package/dist/core/ecs/component.d.ts.map +1 -1
  21. package/dist/core/ecs/compute_backend.d.cts +37 -0
  22. package/dist/core/ecs/compute_backend.d.ts +2 -2
  23. package/dist/core/ecs/debug_names.d.cts +7 -0
  24. package/dist/core/ecs/debug_names.d.ts +7 -0
  25. package/dist/core/ecs/debug_names.d.ts.map +1 -0
  26. package/dist/core/ecs/deferred_commands.d.cts +55 -0
  27. package/dist/core/ecs/deferred_commands.d.ts +55 -0
  28. package/dist/core/ecs/deferred_commands.d.ts.map +1 -0
  29. package/dist/core/ecs/dispatch_trace.d.cts +93 -0
  30. package/dist/core/ecs/dispatch_trace.d.ts +3 -3
  31. package/dist/core/ecs/ecs.d.cts +580 -0
  32. package/dist/core/ecs/ecs.d.ts +340 -293
  33. package/dist/core/ecs/ecs.d.ts.map +1 -1
  34. package/dist/core/ecs/ecs_memory.d.cts +179 -0
  35. package/dist/core/ecs/ecs_memory.d.ts +1 -1
  36. package/dist/core/ecs/entity.d.cts +34 -0
  37. package/dist/core/ecs/entity.d.ts +8 -2
  38. package/dist/core/ecs/entity.d.ts.map +1 -1
  39. package/dist/core/ecs/entity_allocator.d.cts +59 -0
  40. package/dist/core/ecs/entity_allocator.d.ts +59 -0
  41. package/dist/core/ecs/entity_allocator.d.ts.map +1 -0
  42. package/dist/core/ecs/event.d.cts +79 -0
  43. package/dist/core/ecs/event.d.ts +36 -11
  44. package/dist/core/ecs/event.d.ts.map +1 -1
  45. package/dist/core/ecs/event_registry.d.cts +22 -0
  46. package/dist/core/ecs/event_registry.d.ts +22 -0
  47. package/dist/core/ecs/event_registry.d.ts.map +1 -0
  48. package/dist/core/ecs/facades.d.cts +158 -0
  49. package/dist/core/ecs/facades.d.ts +158 -0
  50. package/dist/core/ecs/facades.d.ts.map +1 -0
  51. package/dist/core/ecs/frame_stepper.d.cts +58 -0
  52. package/dist/core/ecs/frame_stepper.d.ts +58 -0
  53. package/dist/core/ecs/frame_stepper.d.ts.map +1 -0
  54. package/dist/core/ecs/frame_trace.d.cts +133 -0
  55. package/dist/core/ecs/frame_trace.d.ts +5 -5
  56. package/dist/core/ecs/frame_trace.d.ts.map +1 -1
  57. package/dist/core/ecs/host_commands.d.cts +284 -0
  58. package/dist/core/ecs/host_commands.d.ts +77 -45
  59. package/dist/core/ecs/host_commands.d.ts.map +1 -1
  60. package/dist/core/ecs/index.d.cts +46 -0
  61. package/dist/core/ecs/index.d.ts +45 -40
  62. package/dist/core/ecs/index.d.ts.map +1 -1
  63. package/dist/core/ecs/observer.d.cts +173 -0
  64. package/dist/core/ecs/observer.d.ts +44 -13
  65. package/dist/core/ecs/observer.d.ts.map +1 -1
  66. package/dist/core/ecs/query.d.cts +734 -0
  67. package/dist/core/ecs/query.d.ts +267 -90
  68. package/dist/core/ecs/query.d.ts.map +1 -1
  69. package/dist/core/ecs/ref.d.cts +31 -0
  70. package/dist/core/ecs/ref.d.ts +3 -3
  71. package/dist/core/ecs/ref.d.ts.map +1 -1
  72. package/dist/core/ecs/relation.d.cts +243 -0
  73. package/dist/core/ecs/relation.d.ts +26 -14
  74. package/dist/core/ecs/relation.d.ts.map +1 -1
  75. package/dist/core/ecs/relation_service.d.cts +235 -0
  76. package/dist/core/ecs/relation_service.d.ts +235 -0
  77. package/dist/core/ecs/relation_service.d.ts.map +1 -0
  78. package/dist/core/ecs/resource.d.cts +53 -0
  79. package/dist/core/ecs/resource.d.ts +25 -5
  80. package/dist/core/ecs/resource.d.ts.map +1 -1
  81. package/dist/core/ecs/resource_registry.d.cts +20 -0
  82. package/dist/core/ecs/resource_registry.d.ts +20 -0
  83. package/dist/core/ecs/resource_registry.d.ts.map +1 -0
  84. package/dist/core/ecs/resume.d.cts +85 -0
  85. package/dist/core/ecs/resume.d.ts +6 -6
  86. package/dist/core/ecs/resume.d.ts.map +1 -1
  87. package/dist/core/ecs/run_condition.d.cts +81 -0
  88. package/dist/core/ecs/run_condition.d.ts +16 -10
  89. package/dist/core/ecs/run_condition.d.ts.map +1 -1
  90. package/dist/core/ecs/schedule.d.cts +142 -0
  91. package/dist/core/ecs/schedule.d.ts +15 -6
  92. package/dist/core/ecs/schedule.d.ts.map +1 -1
  93. package/dist/core/ecs/snapshot_service.d.cts +94 -0
  94. package/dist/core/ecs/snapshot_service.d.ts +94 -0
  95. package/dist/core/ecs/snapshot_service.d.ts.map +1 -0
  96. package/dist/core/ecs/sparse_store.d.cts +110 -0
  97. package/dist/core/ecs/sparse_store.d.ts +5 -2
  98. package/dist/core/ecs/sparse_store.d.ts.map +1 -1
  99. package/dist/core/ecs/store.d.cts +937 -0
  100. package/dist/core/ecs/store.d.ts +144 -356
  101. package/dist/core/ecs/store.d.ts.map +1 -1
  102. package/dist/core/ecs/store_layout_listener.d.cts +23 -0
  103. package/dist/core/ecs/system.d.cts +250 -0
  104. package/dist/core/ecs/system.d.ts +138 -22
  105. package/dist/core/ecs/system.d.ts.map +1 -1
  106. package/dist/{utils/arrays.d.ts → core/ecs/utils/arrays.d.cts} +1 -0
  107. package/dist/core/ecs/utils/error.d.cts +61 -0
  108. package/dist/core/ecs/utils/error.d.ts +12 -2
  109. package/dist/core/ecs/utils/error.d.ts.map +1 -1
  110. package/dist/core/store/action_ring.d.cts +136 -0
  111. package/dist/core/store/allocator.d.cts +238 -0
  112. package/dist/core/store/allocator.d.ts.map +1 -1
  113. package/dist/core/store/buffer_backed_column.d.cts +69 -0
  114. package/dist/core/store/buffer_backed_column.d.ts +2 -2
  115. package/dist/core/store/column_store.d.cts +273 -0
  116. package/dist/core/store/column_store.d.ts +18 -10
  117. package/dist/core/store/column_store.d.ts.map +1 -1
  118. package/dist/core/store/command_dispatch.d.cts +52 -0
  119. package/dist/core/store/command_ring.d.cts +107 -0
  120. package/dist/core/store/descriptor.d.cts +80 -0
  121. package/dist/core/store/descriptor.d.ts +1 -1
  122. package/dist/core/store/descriptor.d.ts.map +1 -1
  123. package/dist/core/store/entity_index.d.cts +108 -0
  124. package/dist/core/store/event_ring.d.cts +95 -0
  125. package/dist/core/store/extend.d.cts +39 -0
  126. package/dist/core/store/extend.d.ts +3 -73
  127. package/dist/core/store/extend.d.ts.map +1 -1
  128. package/dist/core/store/grow.d.cts +34 -0
  129. package/dist/core/store/grow.d.ts +4 -9
  130. package/dist/core/store/grow.d.ts.map +1 -1
  131. package/dist/core/store/header.d.cts +64 -0
  132. package/dist/core/store/header.d.ts +1 -1
  133. package/dist/core/store/header.d.ts.map +1 -1
  134. package/dist/core/store/index.d.cts +17 -0
  135. package/dist/core/store/index.d.ts +16 -15
  136. package/dist/core/store/index.d.ts.map +1 -1
  137. package/dist/core/store/layout_ops.d.cts +152 -0
  138. package/dist/core/store/layout_ops.d.ts +152 -0
  139. package/dist/core/store/layout_ops.d.ts.map +1 -0
  140. package/dist/core/store/region_table.d.cts +74 -0
  141. package/dist/core/store/region_table.d.ts +1 -1
  142. package/dist/core/store/region_table.d.ts.map +1 -1
  143. package/dist/core/store/snapshot.d.cts +43 -0
  144. package/dist/core/store/snapshot.d.ts +2 -2
  145. package/dist/core/store/state_hash.d.cts +38 -0
  146. package/dist/core/store/state_hash.d.ts +1 -1
  147. package/dist/core/store/store_regions.d.cts +38 -0
  148. package/dist/core/store/store_regions.d.ts +1 -1
  149. package/dist/core/store/vendored_abi/abi.d.ts +43 -0
  150. package/dist/core/store/{__generated__ → vendored_abi}/abi.d.ts.map +1 -1
  151. package/dist/dev_flag.d.cts +2 -0
  152. package/dist/dev_flag.d.ts +2 -0
  153. package/dist/dev_flag.d.ts.map +1 -0
  154. package/dist/extensions/editor/editor.d.cts +172 -0
  155. package/dist/extensions/editor/editor.d.ts +65 -42
  156. package/dist/extensions/editor/editor.d.ts.map +1 -1
  157. package/dist/extensions/editor/field_handle.d.cts +40 -0
  158. package/dist/extensions/editor/field_handle.d.ts +10 -5
  159. package/dist/extensions/editor/field_handle.d.ts.map +1 -1
  160. package/dist/extensions/editor/index.cjs +1 -1
  161. package/dist/extensions/editor/index.d.cts +21 -0
  162. package/dist/extensions/editor/index.d.ts +2 -2
  163. package/dist/extensions/editor/index.js +130 -100
  164. package/dist/extensions/reactive/ecs_sync.d.cts +214 -0
  165. package/dist/extensions/reactive/ecs_sync.d.ts +26 -22
  166. package/dist/extensions/reactive/ecs_sync.d.ts.map +1 -1
  167. package/dist/extensions/reactive/index.cjs +1 -1
  168. package/dist/extensions/reactive/index.d.cts +23 -0
  169. package/dist/extensions/reactive/index.d.ts +1 -1
  170. package/dist/extensions/reactive/index.js +106 -116
  171. package/dist/extensions/solid/index.cjs +1 -1
  172. package/dist/extensions/solid/index.d.cts +6 -0
  173. package/dist/extensions/solid/index.d.ts +2 -2
  174. package/dist/extensions/solid/index.js +8 -4
  175. package/dist/extensions/solid/kernel_solid.d.cts +45 -0
  176. package/dist/extensions/solid/kernel_solid.d.ts +6 -3
  177. package/dist/extensions/solid/kernel_solid.d.ts.map +1 -1
  178. package/dist/host_commands-BI8pEmjH.js +2326 -0
  179. package/dist/host_commands-CxhpzMx9.cjs +1 -0
  180. package/dist/index.cjs +1 -2
  181. package/dist/index.d.cts +66 -0
  182. package/dist/index.d.ts +50 -1
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.js +4489 -5898
  185. package/dist/internal.cjs +2 -0
  186. package/dist/internal.d.cts +20 -0
  187. package/dist/internal.d.ts +20 -0
  188. package/dist/internal.d.ts.map +1 -0
  189. package/dist/internal.js +141 -0
  190. package/dist/primitives.cjs +1 -1
  191. package/dist/primitives.d.cts +18 -0
  192. package/dist/primitives.d.ts +6 -6
  193. package/dist/primitives.js +24 -23
  194. package/dist/reactive/array.d.ts +24 -0
  195. package/dist/reactive/array.d.ts.map +1 -0
  196. package/dist/reactive/index.cjs +1 -0
  197. package/dist/reactive/index.d.cts +11 -0
  198. package/dist/reactive/index.d.ts +11 -0
  199. package/dist/reactive/index.d.ts.map +1 -0
  200. package/dist/reactive/index.js +18 -0
  201. package/dist/reactive/interop.d.cts +19 -0
  202. package/dist/{core/reactive → reactive}/interop.d.ts +1 -1
  203. package/dist/reactive/interop.d.ts.map +1 -0
  204. package/dist/reactive/kernel.d.cts +73 -0
  205. package/dist/{core/reactive → reactive}/kernel.d.ts +5 -3
  206. package/dist/reactive/kernel.d.ts.map +1 -0
  207. package/dist/reactive/map.d.ts +16 -0
  208. package/dist/reactive/map.d.ts.map +1 -0
  209. package/dist/reactive/shallow.d.cts +11 -0
  210. package/dist/reactive/shallow.d.ts +11 -0
  211. package/dist/reactive/shallow.d.ts.map +1 -0
  212. package/dist/reactive/struct.d.cts +10 -0
  213. package/dist/{core/reactive → reactive}/struct.d.ts +1 -1
  214. package/dist/reactive/struct.d.ts.map +1 -0
  215. package/dist/shallow-DQ8aG51_.cjs +1 -0
  216. package/dist/shallow-DfnAm9vG.js +150 -0
  217. package/dist/shared-BU1Cd40h.js +103 -0
  218. package/dist/shared-BymrGTyR.cjs +1 -0
  219. package/dist/shared.cjs +1 -1
  220. package/dist/shared.d.cts +22 -0
  221. package/dist/shared.d.ts +2 -2
  222. package/dist/shared.js +1 -1
  223. package/dist/topological_sort-BDvEyb9W.cjs +1 -0
  224. package/dist/topological_sort-DK6EjpWa.js +135 -0
  225. package/dist/type_primitives/assertions.d.cts +32 -0
  226. package/dist/type_primitives/assertions.d.ts +13 -1
  227. package/dist/type_primitives/assertions.d.ts.map +1 -1
  228. package/dist/type_primitives/binary_heap/binary_heap.d.cts +37 -0
  229. package/dist/type_primitives/bitset/bitset.d.cts +54 -0
  230. package/dist/type_primitives/brand.d.cts +23 -0
  231. package/dist/type_primitives/error.d.cts +16 -0
  232. package/dist/type_primitives/error.d.ts +4 -3
  233. package/dist/type_primitives/error.d.ts.map +1 -1
  234. package/dist/type_primitives/index.d.cts +13 -0
  235. package/dist/type_primitives/index.d.ts +9 -9
  236. package/dist/type_primitives/sparse_map/sparse_map.d.cts +30 -0
  237. package/dist/type_primitives/sparse_set/sparse_set.d.cts +28 -0
  238. package/dist/type_primitives/topological_sort/topological_sort.d.cts +29 -0
  239. package/dist/type_primitives/typed_arrays/typed_arrays.d.cts +127 -0
  240. package/dist/typed_arrays-BcXcnzB4.js +258 -0
  241. package/dist/typed_arrays-D3NmwtCG.cjs +1 -0
  242. package/dist/utils/error.d.cts +6 -0
  243. package/dist/version.d.cts +10 -0
  244. package/dist/version.d.ts +10 -0
  245. package/dist/version.d.ts.map +1 -0
  246. package/package.json +102 -19
  247. package/dist/array-CxbPyiHO.cjs +0 -1
  248. package/dist/array-uFR7Q8fU.js +0 -132
  249. package/dist/core/reactive/array.d.ts.map +0 -1
  250. package/dist/core/reactive/index.cjs +0 -1
  251. package/dist/core/reactive/index.d.ts +0 -10
  252. package/dist/core/reactive/index.d.ts.map +0 -1
  253. package/dist/core/reactive/index.js +0 -17
  254. package/dist/core/reactive/interop.d.ts.map +0 -1
  255. package/dist/core/reactive/kernel.d.ts.map +0 -1
  256. package/dist/core/reactive/map.d.ts.map +0 -1
  257. package/dist/core/reactive/struct.d.ts.map +0 -1
  258. package/dist/log/console_sink.d.ts +0 -4
  259. package/dist/log/console_sink.d.ts.map +0 -1
  260. package/dist/log/index.d.ts +0 -3
  261. package/dist/log/index.d.ts.map +0 -1
  262. package/dist/log/logger.d.ts +0 -27
  263. package/dist/log/logger.d.ts.map +0 -1
  264. package/dist/shared-BXSZnxx4.cjs +0 -1
  265. package/dist/shared-C678TAPY.js +0 -99
  266. package/dist/topological_sort-DlRpSrxu.js +0 -391
  267. package/dist/topological_sort-WAT-VHb-.cjs +0 -1
  268. package/dist/utils/arrays.d.ts.map +0 -1
  269. package/dist/utils/constants.d.ts.map +0 -1
  270. /package/dist/{utils/constants.d.ts → core/ecs/utils/constants.d.cts} +0 -0
  271. /package/dist/core/store/{__generated__/abi.d.ts → vendored_abi/abi.d.cts} +0 -0
  272. /package/dist/{core/reactive/array.d.ts → reactive/array.d.cts} +0 -0
  273. /package/dist/{core/reactive/map.d.ts → reactive/map.d.cts} +0 -0
@@ -0,0 +1,937 @@
1
+ import { EntityID } from './entity.cjs';
2
+ import { FrameTraceSink } from './frame_trace.cjs';
3
+ import { ComponentDef, ComponentHandle, ComponentSchema, FieldValues } from './component.cjs';
4
+ import { SparseComponentDef, SparseComponentID } from './sparse_store.cjs';
5
+ import { RelationDef, RelationOptions } from './relation.cjs';
6
+ import { EmptyEventSchema, EventDef, EventReader, EventShape } from './event.cjs';
7
+ import { BitSet, TypedArrayTag } from '../../type_primitives/index.cjs';
8
+ import { Archetype, ArchetypeID } from './archetype.cjs';
9
+ import { Query, QueryHost } from './query.cjs';
10
+ import { ObserverHost } from './observer.cjs';
11
+ import { InPlaceBufferAllocator, ColumnStoreRegionHandle, StoreRegionSpec, ColumnStore } from '../store/index.cjs';
12
+ import { ECSMemoryCapContext } from './ecs_memory.cjs';
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. */
64
+ type TemplateEntryData = {
65
+ readonly def: ComponentDef;
66
+ readonly values?: Readonly<Record<string, number>>;
67
+ };
68
+ /** Union of every field name owned by a component in `Defs` (distributes
69
+ * over the def list). */
70
+ type TemplateFieldNames<Defs extends readonly ComponentDef[]> = Defs[number] extends ComponentDef<infer S> ? keyof S & string : never;
71
+ /** Flat per-instance override map for `ECS.spawn`: any field of any
72
+ * component in the template, each optional. A misspelled field is a compile
73
+ * error (and still a `DEV` throw at runtime for untyped call sites). */
74
+ export type TemplateOverrides<Defs extends readonly ComponentDef[]> = {
75
+ readonly [K in TemplateFieldNames<Defs>]?: number;
76
+ };
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
+ export interface StoreOptions {
95
+ initialCapacity?: number;
96
+ /** Pluggable SAB buffer source. When provided, `createColumnStore`,
97
+ * `extendColumnStore`, and `growColumnStore` route through it. Default is
98
+ * `growableSabAllocator()`. Typed `InPlaceBufferAllocator` (#682): a live
99
+ * Store's flush loops hoist entity-index views across grows, so only
100
+ * in-place allocators may back one (ADR-0008) — the constructor also
101
+ * runtime-asserts the marker for untyped JS callers. Consumers normally
102
+ * don't touch this directly; `ECSOptions.memory` resolves to it. */
103
+ bufferAllocator?: InPlaceBufferAllocator;
104
+ /** Sizing intent the world was constructed with (#682), used to phrase
105
+ * 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. */
108
+ capContext?: ECSMemoryCapContext;
109
+ /** Fired after every SAB resize (extend or grow). The new SAB has
110
+ * 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
116
+ * 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. */
120
+ entityIndexCapacity?: number;
121
+ /** Consumer-declared SAB regions (#623), forwarded verbatim to
122
+ * `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. */
127
+ regions?: readonly StoreRegionSpec[];
128
+ /** Byte size of the opt-in sim-bindings region (#625), forwarded verbatim to
129
+ * `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. */
135
+ 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)
138
+ * order: `stateHash`, `snapshotSparse`, `restoreSparse`. When `false`
139
+ * those throw `DETERMINISM_DISABLED` — the canonical-ordering tax (sparse
140
+ * `canonicalIndices` sort + relation target-set sort) is never paid, and a
141
+ * 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
143
+ * 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
145
+ * 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. */
148
+ deterministic?: boolean;
149
+ }
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;
164
+ private readonly _deferred;
165
+ private readonly _snapshots;
166
+ _tick: number;
167
+ /** Per-world frame-trace sink (ADR-0030), installed via `ECS.setTrace`.
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;
174
+ private _structuralObserverCount;
175
+ /** Count of components with any onDisable/onEnable observer (#677). While 0
176
+ * (with `_structuralObserverCount` also 0), `flushStructural` takes the
177
+ * byte-for-byte fast path and the toggle drain skips event collection. */
178
+ private _toggleObserverCount;
179
+ /** Reused effective-event scratch for the current flush round. */
180
+ 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;
189
+ private _collectDestroyEid;
190
+ private readonly _collectDestroyRemoveBit;
191
+ private _collectToggleEid;
192
+ private readonly _collectDisableBit;
193
+ 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
196
+ * `_flushToggles` emit one event per *net* transition (disable→enable→disable
197
+ * within a tick = a single onDisable) instead of one per buffered op — required
198
+ * because the radix canonical-order pass would otherwise reorder duplicate eids
199
+ * and mis-sequence a consumer's delete/republish. */
200
+ private readonly _toggleInitial;
201
+ _anyDirtyTracked: boolean;
202
+ private readonly _dirtyTrackedCids;
203
+ private readonly _dirtyLists;
204
+ private readonly _dirtyMarks;
205
+ /** Set by any path that changes a SAB-backed archetype's live row count
206
+ * (`flushStructural`/`flushDestroyed` when they did work; immediate
207
+ * `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). */
210
+ private _rowCountsDirty;
211
+ /** Monotonic counter bumped by every membership-changing path (immediate
212
+ * `addComponent(s)`, `removeComponent(s)`, `destroyEntity`,
213
+ * `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;
225
+ private _columnStore;
226
+ /** Installed on every SAB-backed Archetype so the Archetype can
227
+ * request a SAB grow when an insertion would exceed its column
228
+ * capacity. Doubles the offending archetype's row capacity (or jumps
229
+ * to whatever fits `arch.length + additional`, whichever is larger),
230
+ * reallocs the SAB via `growColumnStore` (live rows of every archetype
231
+ * 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) */
233
+ 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
236
+ * declared (`capContext`) and how many entities are live, so the error
237
+ * says "3.2× the declared budget — runaway creation upstream?" instead
238
+ * of leaving the caller to reverse-engineer byte counts. Fatality is
239
+ * unchanged (#380: no grow-beyond-cap fallback). */
240
+ private _capExceededError;
241
+ private readonly _bufferAllocator;
242
+ private readonly _capContext;
243
+ 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
247
+ * that pass `new Store(4)` keep working. */
248
+ constructor(arg?: number | StoreOptions);
249
+ /** Capacity of the entity-index SAB region (max slots ≈ max live
250
+ * entities). Fixed at construction in #245's PR 4B scope; a future
251
+ * follow-up will grow it via `growColumnStore` when `entityHighWater`
252
+ * hits the cap. */
253
+ private readonly _entityIndexCapacity;
254
+ /** Consumer-declared SAB regions (#623), captured so the realloc path
255
+ * re-lays them out. `undefined` when no consumer regions were declared.
256
+ * The region contents survive a grow via the self-describing region table
257
+ * (`extend.ts` snapshot/restore), so this is only the layout recipe. */
258
+ 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. */
263
+ 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. */
269
+ private readonly _deterministic;
270
+ /** Whether the determinism surface is enabled (#626). `false` ⇒ `stateHash`
271
+ * / `snapshotSparse` / `restoreSparse` throw `DETERMINISM_DISABLED`. */
272
+ get deterministic(): boolean;
273
+ /** Guard the canonical-ordering determinism surface. Throws
274
+ * `DETERMINISM_DISABLED` when determinism wasn't opted into, naming the
275
+ * method so the caller knows to pass `{ deterministic: true }`. Always on
276
+ * (not `DEV`-gated): the surface is cold (never per-tick) so one boolean
277
+ * check is free, and a silent non-canonical digest is the failure mode we're
278
+ * 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;
290
+ /** 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. */
294
+ private _refreshEntityIndexViews;
295
+ /** Centralised "SAB was just reallocated" handler. Refreshes the
296
+ * Int32Array views FIRST (so user callbacks observe valid views),
297
+ * then mirrors `entityHighWater` into the region's length header,
298
+ * then fires the user-supplied callback. */
299
+ 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
304
+ * still go through `Archetype.getColumnRead` (which sources from this
305
+ * SAB under the hood). */
306
+ get columnStore(): ColumnStore;
307
+ /** Resolve a consumer-declared SAB region's byte offset by `region_id`, or
308
+ * 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`. */
313
+ regionOffset(regionId: number): number;
314
+ /** 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
316
+ * `offset` and `bytes`, so a consumer's region module can build a TypedArray
317
+ * 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) */
319
+ regionHandle(regionId: number): ColumnStoreRegionHandle | null;
320
+ /**
321
+ * Stamp every SAB-backed archetype's live `length` into its descriptor's
322
+ * `row_count` field (#252 / Phase 4 PR 4E). `extendColumnStore` /
323
+ * `growColumnStore` are the only other writers of `row_count`, and they
324
+ * record the count at the moment of the resize — `Archetype.addEntity`
325
+ * 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.
329
+ *
330
+ * Lockstep walk: SAB descriptors are written by `extendColumnStore` in
331
+ * the order non-SAB archetypes are promoted, which is the same id-order
332
+ * those archetypes occupy in `this.archGraph.archetypes`. Iterating that array
333
+ * once, skipping non-SAB entries, and advancing an `archAddr` cursor
334
+ * by the descriptor's `column_count` lets us write `row_count` without
335
+ * the throwaway `Map<archId, length>` the previous version allocated
336
+ * on every call (#323). Cheap: descriptor-region seeks only, no column
337
+ * I/O.
338
+ *
339
+ * Gated by `_rowCountsDirty` (#324) — mutation paths
340
+ * (`flushStructural`, `flushDestroyed`, immediate `destroyEntity`,
341
+ * `addComponent(s)`, `removeComponent(s)`) set the flag; this method
342
+ * clears it. Read-only phases that flush only to drain empty buffers
343
+ * pay nothing. */
344
+ publishRowCountsToDescriptor(): void;
345
+ /** FNV-1a-style 32-bit digest over (archetype_id, live_row_count, live
346
+ * 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.
351
+ *
352
+ * **Sparse coverage (#470).** Sparse data lives outside the archetype
353
+ * graph, so it is folded separately after the archetype loop — per store:
354
+ * the sparse-component id, the member count, then each member's source
355
+ * entity index + f64 field words, walked in CANONICAL ascending-index order
356
+ * (`SparseComponentStore.canonicalIndices`). Canonical order is what makes
357
+ * the digest insertion-order-independent: two worlds with identical sparse
358
+ * contents built by different add/remove sequences agree. Keyed by entity
359
+ * index, and destruction purges the slot, so a recycled index never carries
360
+ * a stale occupant's data into the hash.
361
+ *
362
+ * It is strictly broader than the prior per-networked-component fold
363
+ * (covers every column, not just a hand-picked subset of networked
364
+ * components), and strictly tighter than `columnStoreStateHash(...)`
365
+ * which scans the full SAB including trailing unused capacity.
366
+ *
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 0–3 tail bytes (only possible for
372
+ * u8/u16 columns at odd row counts) are folded together as a single
373
+ * little-endian word so the algorithm stays branch-free in the inner
374
+ * loop. The 4-byte `id` and `len` headers are folded as words for the
375
+ * same reason. Byte order is little-endian to match the platform's
376
+ * native TypedArray layout; the digest is opaque (no consumer compares
377
+ * against a literal value), so endianness is an implementation detail
378
+ * rather than wire contract.
379
+ *
380
+ * 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).
383
+ *
384
+ * **Opt-in (#626 / ADR-0020).** Throws `DETERMINISM_DISABLED` unless the
385
+ * Store was constructed with `{ deterministic: true }`. The canonical
386
+ * ordering this fold relies on (sparse `canonicalIndices`, sorted relation
387
+ * target sets) is the determinism tax the flag gates. */
388
+ 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)
396
+ *
397
+ * 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. */
402
+ entityIdAtRow(archetypeId: number, row: number): EntityID;
403
+ /** Find or create an archetype for the given component mask — see
404
+ * `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`. */
409
+ 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
+ * handler (`ArchetypeGraphHost.materialize`). Store-owned so the graph
419
+ * never touches `_columnStore`. */
420
+ 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
+ /** Resolve "add component_id to archetype_id" → target ArchetypeID (edge-cached). */
426
+ private archResolveAdd;
427
+ /** Resolve "remove component_id from archetype_id" → target ArchetypeID (edge-cached). */
428
+ private archResolveRemove;
429
+ 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
433
+ * membership write `createEntity` performs (the caller installs the real
434
+ * archetype + row). This *commits* the slot (bumps counts, stamps the
435
+ * generation so `isAlive` is already true), so the caller MUST have
436
+ * 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). */
439
+ private _allocEntity;
440
+ /** 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
443
+ * throw `EID_MAX_INDEX_OVERFLOW` partway through the alloc loop, leaving the
444
+ * slots it already committed phantom-alive. Free-list reuse covers the first
445
+ * `entityFreeIndices.length` slots; only the remainder draws down the
446
+ * high-water headroom. */
447
+ private _ensureEntityIndexCapacity;
448
+ /** Resolve a template: compute the target archetype (creating it if absent —
449
+ * fits the prewarm model), pre-flatten default field values into
450
+ * `_flatColumns` order, and build the override index (field name → flat
451
+ * column index; `TEMPLATE_OVERRIDE_AMBIGUOUS` for a name shared by more than
452
+ * one component, which a flat override cannot target). */
453
+ resolveTemplate(entries: readonly TemplateEntryData[]): Template;
454
+ /** 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
456
+ * fallback where DEV would have thrown). */
457
+ private _resolveOverrideColumn;
458
+ /** Apply per-instance overrides to the freshly-spawned row. Each key is a
459
+ * field name resolved through the template's override index. */
460
+ private _applyOverrides;
461
+ /** Bulk variant of `_applyOverrides`: one `fill` per overridden column
462
+ * across the contiguous rows `[start, start + count)`. */
463
+ private _applyOverridesRange;
464
+ /** Spawn one entity directly into the template's archetype (zero archetype
465
+ * transitions). Writes the template defaults in a single append pass, then
466
+ * applies any per-instance overrides. */
467
+ spawn(p: Template, overrides?: Record<string, number | undefined>): EntityID;
468
+ /** 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
471
+ * spawn order. */
472
+ spawnMany(p: Template, count: number, overrides?: Record<string, number | undefined>): EntityID[];
473
+ /** Immediately destroy an entity, removing it from its archetype.
474
+ *
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
477
+ * 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):
479
+ * the `work.length` re-read drives chains and trees out without recursion, so
480
+ * 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`
483
+ * buffer there vs. a local work-list here. `isAlive` dedups a source reached
484
+ * twice (diamonds) and terminates cycles, exactly as the generation guard does
485
+ * in the deferred loop. */
486
+ destroyEntity(id: EntityID): void;
487
+ /** Tear a single entity out of its archetype, relation, and sparse stores,
488
+ * then recycle (or retire) its slot. Shared by both immediate-destroy entry
489
+ * points (the fast no-cascade path and the work-list driver in
490
+ * `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);
492
+ * `null` skips that collection for callers that cannot cascade. The caller
493
+ * must have already confirmed `id` is alive. */
494
+ private _destroyOne;
495
+ /**
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
499
+ * dead rather than alias a slot:
500
+ * - **Out of range** — an `id` outside the 31-bit packed space (`< 0` or
501
+ * `> 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
505
+ * the allocator stamps into a retired slot and never issues to a live
506
+ * entity, would otherwise match a retired slot's parked generation and read
507
+ * alive (the ABA tombstone, previously documented as a known gap).
508
+ * 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).
510
+ */
511
+ isAlive(id: EntityID): boolean;
512
+ get entityCount(): number;
513
+ /** An archetype's row count moved from `preLen` to its current
514
+ * `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
519
+ * move row counts within the same side (6→5) leave the non-empty set unchanged
520
+ * and skip the bump.
521
+ *
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
527
+ * an all-disabled archetype can stale-*exclude* a live row, so the enabled
528
+ * crossing lives in `_onArchGrow`, off this path.
529
+ *
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
542
+ * dirty and bumps the query-dirty epoch on a `length` 0/non-zero crossing
543
+ * (`includeDisabled` membership), but it *also* bumps on an `enabledCount`
544
+ * 0→1 crossing (#812). The non-empty filter is field-split (#577): a default
545
+ * query keeps archetypes with `enabledCount > 0`. An enabled row appended to
546
+ * an archetype that is non-empty but all-disabled (`length > 0,
547
+ * 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.
551
+ *
552
+ * **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
556
+ * `(pre === 0) !== (post === 0)` boundary test collapses given the post side:
557
+ * - `length`: post > 0 always ⇒ a crossing iff `preLen === 0`.
558
+ * - `enabledCount`: non-decreasing on a grow ⇒ a 0-crossing iff it was 0
559
+ * before and is non-zero now (`preEnabled === 0 && enabledCount !== 0`);
560
+ * a disabled-row append leaves it 0 and correctly skips. The `enabledCount`
561
+ * read is short-circuited away on the hot path (`preLen` or `preEnabled`
562
+ * non-zero), so a no-disabled workload pays only two scalar compares. */
563
+ 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`). */
571
+ private _onArchEnabledChange;
572
+ /** 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). */
575
+ disableEntity(id: EntityID): void;
576
+ /** Immediately enable an entity (idempotent). */
577
+ enableEntity(id: EntityID): void;
578
+ /** Whether `id` is currently disabled. A component-less entity is never
579
+ * disabled (it has no row to partition). */
580
+ isDisabled(id: EntityID): boolean;
581
+ /** 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
584
+ * 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;
587
+ * `_settleFlushDirty` walks the list once after the loop. The field
588
+ * accesses per entity replace a `Map.has` + `Map.set` hash probe pair. The
589
+ * epoch is bumped at settle so the next flush re-records. */
590
+ private _flushEpoch;
591
+ private readonly _flushTouched;
592
+ /** 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
594
+ * the query epoch once if any touched archetype crossed the 0 boundary on
595
+ * *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
598
+ * (queries only need to know "something changed"). Clears the touched list
599
+ * and advances the flush epoch on exit. */
600
+ private _settleFlushDirty;
601
+ destroyEntityDeferred(id: EntityID): void;
602
+ /** Buffer an enable/disable toggle for the phase flush (#577). The row swap a
603
+ * toggle performs would corrupt a `forEach` over that archetype if applied
604
+ * mid-system, so it is deferred like add/remove. */
605
+ disableEntityDeferred(id: EntityID): void;
606
+ enableEntityDeferred(id: EntityID): void;
607
+ /** Drain buffered enable/disable toggles, applying each in operation order via
608
+ * 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
610
+ * entity's final archetype placement for the tick.
611
+ *
612
+ * When an onDisable/onEnable observer is registered (`_toggleObserverCount >
613
+ * 0`, #677) this also collects effective toggle events into `_obsEvents` for
614
+ * the dispatch hook, collapsed to one event per *net* transition across the
615
+ * drain (see `_toggleInitial`). The no-observer path is byte-for-byte the
616
+ * pre-#677 drain. */
617
+ 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
621
+ * nothing and is skipped. */
622
+ private _collectToggle;
623
+ get pendingToggleCount(): number;
624
+ /** Flush all buffered entity destructions in batch.
625
+ *
626
+ * 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
629
+ * `flushStructural` fixed-point loop, the only caller in that mode (it drains
630
+ * `pendingDestroy` each round so the trailing `ctx.flush()` call is a no-op).
631
+ * Same commit-then-observe discipline as `_flushRemoves`: the entity is fully
632
+ * freed before the callback runs, so onRemove receives the (now dead) eid as
633
+ * 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.
635
+ *
636
+ * Re-entrancy: while the observed fixed point owns the flush, the loop
637
+ * drains destroys itself via `_drainDestroyed`, so a re-entrant
638
+ * `ctx.flush()` from a callback no-ops (the guard lives in
639
+ * `DeferredCommandBuffer.flushDestroyed`) — otherwise it would collect
640
+ * into the shared `_obsEvents` scratch mid-dispatch and corrupt it. */
641
+ flushDestroyed(): void;
642
+ private _drainDestroyed;
643
+ get pendingDestroyCount(): number;
644
+ addComponentDeferred(entityId: EntityID, def: ComponentDef<Record<string, never>>): void;
645
+ addComponentDeferred<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, values: FieldValues<S>): void;
646
+ 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. */
652
+ flushStructural(): void;
653
+ /** Batch-apply all deferred component additions. */
654
+ private _flushAdds;
655
+ /** Batch-apply all deferred component removals. */
656
+ private _flushRemoves;
657
+ 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
661
+ * 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;
678
+ /** 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;
683
+ /** Enabled live entities currently carrying `cid`, used by `yieldExisting` to
684
+ * replay onAdd on registration. Bounded by `enabled_count` (#677): a disabled
685
+ * 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
688
+ * registry radix-sorts. */
689
+ _collectEntitiesWithComponent(cid: number): EntityID[];
690
+ registerComponent<S extends Record<string, TypedArrayTag>>(schema: S, name?: string): ComponentDef<S>;
691
+ /** `'Pos' (component 5)` when the component was registered with a debug
692
+ * name, else `component 5` — the label diagnostics interpolate. */
693
+ 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). */
698
+ fieldIdOf(def: ComponentHandle, fieldName: string): number;
699
+ /** Register a sparse component or tag. Unlike `registerComponent`, this
700
+ * allocates from a separate id space and never touches the archetype mask,
701
+ * so it does **not** count against `STORE_DESCRIPTOR_COMPONENT_LIMIT`. See
702
+ * ADR-0011 and `sparse_store.ts`. */
703
+ registerSparseComponent<S extends Record<string, TypedArrayTag>>(schema: S, name?: string): SparseComponentDef<S>;
704
+ /** Sparse sibling of `componentLabel` — sparse ids are a separate id space. */
705
+ 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
708
+ * 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
710
+ * *arithmetic* rounding, which a target slot never undergoes). User schemas go
711
+ * through `registerSparseComponent`, which guards first. */
712
+ private _pushSparseStore;
713
+ private sparseStoreOf;
714
+ /** Add (or overwrite) a sparse component on an entity. No archetype
715
+ * transition, no row copy — the entity's `archetype_id` is unchanged. */
716
+ addSparse(entityId: EntityID, def: SparseComponentDef, values?: Record<string, number>): void;
717
+ /** Remove a sparse component from an entity. No-op if absent. */
718
+ removeSparse(entityId: EntityID, def: SparseComponentDef): void;
719
+ /** Total, like `hasComponent` — `false` for a dead entity, never a throw. */
720
+ hasSparse(entityId: EntityID, def: SparseComponentDef): boolean;
721
+ getSparseField(entityId: EntityID, def: SparseComponentDef, field: string): number;
722
+ setSparseField(entityId: EntityID, def: SparseComponentDef, field: string, value: number): void;
723
+ /** 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
725
+ * keep the destroy hot path free when sparse storage is unused. */
726
+ private _purgeSparse;
727
+ /** 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
732
+ * sets, which live outside the sparse store). Both are written in canonical
733
+ * 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
736
+ * `restoreSparse`.
737
+ *
738
+ * **Opt-in (#626 / ADR-0020).** Throws `DETERMINISM_DISABLED` unless the
739
+ * Store was constructed with `{ deterministic: true }` — the canonical
740
+ * entity-index ordering is the determinism tax the flag gates. */
741
+ snapshotSparse(): Uint8Array;
742
+ /** Repopulate the sparse stores from `snapshotSparse` bytes, replacing all
743
+ * current sparse data (full-equality round-trip of membership + data), then
744
+ * rebuild every relation's derived side indices: multi forward sets from the
745
+ * relation section, and the reverse index for both cardinalities (exclusive
746
+ * from the just-restored sparse target field, multi from the rebuilt forward
747
+ * sets). The sparse components and relations must already be registered in
748
+ * the same order — restore carries data, not the registration (which is
749
+ * code). Throws `SparseRestoreError` if the snapshot's shape, field identity,
750
+ * entity-index bounds, or frame length don't validate.
751
+ *
752
+ * **Opt-in (#626 / ADR-0020).** Throws `DETERMINISM_DISABLED` unless the
753
+ * Store was constructed with `{ deterministic: true }`; paired with
754
+ * `snapshotSparse`, which produces the canonical bytes restore consumes. */
755
+ restoreSparse(bytes: Uint8Array): void;
756
+ /**
757
+ * 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
763
+ * order is load-bearing for byte-identical resume), the alive count, and each
764
+ * archetype's `length` / `enabledCount`.
765
+ *
766
+ * **Opt-in (ADR-0020).** Throws `DETERMINISM_DISABLED` unless constructed with
767
+ * `{ deterministic: true }` — the sparse section rides the canonical-ordering
768
+ * surface and byte-identical resume is a determinism property. Pairs with
769
+ * `restoreInto`.
770
+ *
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. */
776
+ snapshot(): Uint8Array;
777
+ /**
778
+ * Mount a `snapshot()` buffer onto this live world and leave it ready to keep
779
+ * 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
782
+ * success the world's dense + sparse state, entity allocator, and tick are
783
+ * exactly the captured world's.
784
+ *
785
+ * Requires a world whose SAB-backed archetype set + column layout match the
786
+ * snapshot's exactly (prewarm so the archetype set is stable) and the same
787
+ * entity-index capacity. **Opt-in (ADR-0020):** throws `DETERMINISM_DISABLED`
788
+ * unless `{ deterministic: true }`. See `snapshot()` for the v1 scope. */
789
+ restoreInto(bytes: Uint8Array): void;
790
+ /** Adopt a restored dense store (`SnapshotService.restoreInto`'s mount
791
+ * step): swap the live backing, refresh every buffer-backed archetype's
792
+ * views, recover the allocator high-water from the restored region, and
793
+ * republish (the grow tail). Store-owned because it assigns
794
+ * `_columnStore` — the service never writes Store fields. */
795
+ 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). */
802
+ 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
824
+ * dense mask **and** sparse-membership terms, invoking `cb` per entity.
825
+ * Yields `EntityID`s, not archetype spans — sparse members are scattered
826
+ * across archetypes, so there is no SoA column to hand back. Driven by the
827
+ * cheapest candidate set:
828
+ *
829
+ * - **sparse require present** → walk the *smallest* required store's
830
+ * `indices` (an upper bound on the result), filtering each by the other
831
+ * required stores, the excluded stores, and the dense mask resolved from
832
+ * the entity's own archetype. Independent of archetype count.
833
+ * - **sparse exclude only** → walk `denseArchetypes` (already dense-mask-
834
+ * matched and non-empty by the caller), skipping rows in any excluded
835
+ * store.
836
+ * - **neither** → walk `denseArchetypes`' entity ids (dense-only fallback).
837
+ *
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;
844
+ addComponent(entityId: EntityID, def: ComponentDef<Record<string, never>>): void;
845
+ addComponent<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, values: FieldValues<S>): void;
846
+ /** Add multiple components in one transition (resolves final archetype, then moves once).
847
+ *
848
+ * 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
851
+ * triggered a fresh `extendColumnStore` even though no entity ever lived
852
+ * 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
856
+ * archetypes the entity never visits.
857
+ *
858
+ * #659 — composite-add edge cache. The final-mask resolve, unlike the
859
+ * 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
864
+ * `Map.get` on an exact packed key yields the target + transition map, and we
865
+ * 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. */
868
+ 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
871
+ * `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
873
+ * composite-edge-cache hit and the final-mask cold path funnel through here so
874
+ * the placement logic lives once. */
875
+ private _addComponentsInto;
876
+ removeComponent(entityId: EntityID, def: ComponentDef): void;
877
+ /** Remove multiple components in one transition (resolves final archetype, then moves once).
878
+ *
879
+ * Final-mask resolve, not graph walk. Same rationale as `addComponents`
880
+ * above — the previous per-step path threaded `archResolveRemove`
881
+ * once per def, which materialised every intermediate archetype on the
882
+ * removal path. Computing the difference mask up front and resolving
883
+ * once avoids planting N-1 intermediates the entity never lives in. */
884
+ 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
887
+ * touching dead entities, so it must be safe to ask. */
888
+ hasComponent(entityId: EntityID, def: ComponentHandle): boolean;
889
+ /**
890
+ * Bulk add a component to ALL entities in the given archetype.
891
+ * Uses TypedArray.set() for O(columns) instead of O(N×columns).
892
+ * The archetype must not already contain this component.
893
+ */
894
+ batchAddComponent(src: ArchetypeID, def: ComponentDef, values?: Record<string, number>): void;
895
+ /**
896
+ * Bulk remove a component from ALL entities in the given archetype.
897
+ * Uses TypedArray.set() for O(columns) instead of O(N×columns).
898
+ * The archetype must contain this component.
899
+ */
900
+ batchRemoveComponent(src: ArchetypeID, def: ComponentDef): void;
901
+ getEntityArchetype(entityId: EntityID): Archetype;
902
+ getEntityRow(entityId: EntityID): number;
903
+ /**
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.
907
+ */
908
+ getMatchingArchetypes(required: BitSet, excluded?: BitSet, anyOf?: BitSet): readonly Archetype[];
909
+ /**
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.
912
+ */
913
+ registerQuery(include: BitSet, exclude?: BitSet, anyOf?: BitSet): Archetype[];
914
+ updateQueryRef(result: Archetype[], query: Query<any>): void;
915
+ 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;
935
+ }
936
+ export {};
937
+ //# sourceMappingURL=store.d.ts.map