@oasys/oecs 0.4.0 → 0.5.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 (272) hide show
  1. package/CHANGELOG.md +553 -0
  2. package/README.md +42 -31
  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 +23 -11
  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 +176 -0
  19. package/dist/core/ecs/component.d.ts +75 -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 +562 -0
  32. package/dist/core/ecs/ecs.d.ts +314 -285
  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 +7 -1
  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 +274 -0
  58. package/dist/core/ecs/host_commands.d.ts +53 -31
  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 +43 -12
  65. package/dist/core/ecs/observer.d.ts.map +1 -1
  66. package/dist/core/ecs/query.d.cts +718 -0
  67. package/dist/core/ecs/query.d.ts +251 -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/relation.d.cts +243 -0
  72. package/dist/core/ecs/relation.d.ts +26 -14
  73. package/dist/core/ecs/relation.d.ts.map +1 -1
  74. package/dist/core/ecs/relation_service.d.cts +235 -0
  75. package/dist/core/ecs/relation_service.d.ts +235 -0
  76. package/dist/core/ecs/relation_service.d.ts.map +1 -0
  77. package/dist/core/ecs/resource.d.cts +53 -0
  78. package/dist/core/ecs/resource.d.ts +25 -5
  79. package/dist/core/ecs/resource.d.ts.map +1 -1
  80. package/dist/core/ecs/resource_registry.d.cts +20 -0
  81. package/dist/core/ecs/resource_registry.d.ts +20 -0
  82. package/dist/core/ecs/resource_registry.d.ts.map +1 -0
  83. package/dist/core/ecs/resume.d.cts +85 -0
  84. package/dist/core/ecs/resume.d.ts +6 -6
  85. package/dist/core/ecs/resume.d.ts.map +1 -1
  86. package/dist/core/ecs/run_condition.d.cts +81 -0
  87. package/dist/core/ecs/run_condition.d.ts +15 -9
  88. package/dist/core/ecs/run_condition.d.ts.map +1 -1
  89. package/dist/core/ecs/schedule.d.cts +142 -0
  90. package/dist/core/ecs/schedule.d.ts +15 -6
  91. package/dist/core/ecs/schedule.d.ts.map +1 -1
  92. package/dist/core/ecs/snapshot_service.d.cts +94 -0
  93. package/dist/core/ecs/snapshot_service.d.ts +94 -0
  94. package/dist/core/ecs/snapshot_service.d.ts.map +1 -0
  95. package/dist/core/ecs/sparse_store.d.cts +110 -0
  96. package/dist/core/ecs/sparse_store.d.ts +5 -2
  97. package/dist/core/ecs/sparse_store.d.ts.map +1 -1
  98. package/dist/core/ecs/store.d.cts +950 -0
  99. package/dist/core/ecs/store.d.ts +143 -342
  100. package/dist/core/ecs/store.d.ts.map +1 -1
  101. package/dist/core/ecs/store_layout_listener.d.cts +23 -0
  102. package/dist/core/ecs/system.d.cts +250 -0
  103. package/dist/core/ecs/system.d.ts +138 -22
  104. package/dist/core/ecs/system.d.ts.map +1 -1
  105. package/dist/{utils/arrays.d.ts → core/ecs/utils/arrays.d.cts} +1 -0
  106. package/dist/core/ecs/utils/error.d.cts +61 -0
  107. package/dist/core/ecs/utils/error.d.ts +12 -2
  108. package/dist/core/ecs/utils/error.d.ts.map +1 -1
  109. package/dist/core/store/action_ring.d.cts +136 -0
  110. package/dist/core/store/allocator.d.cts +238 -0
  111. package/dist/core/store/allocator.d.ts.map +1 -1
  112. package/dist/core/store/buffer_backed_column.d.cts +69 -0
  113. package/dist/core/store/buffer_backed_column.d.ts +2 -2
  114. package/dist/core/store/column_store.d.cts +273 -0
  115. package/dist/core/store/column_store.d.ts +18 -10
  116. package/dist/core/store/column_store.d.ts.map +1 -1
  117. package/dist/core/store/command_dispatch.d.cts +52 -0
  118. package/dist/core/store/command_ring.d.cts +107 -0
  119. package/dist/core/store/descriptor.d.cts +80 -0
  120. package/dist/core/store/descriptor.d.ts +1 -1
  121. package/dist/core/store/descriptor.d.ts.map +1 -1
  122. package/dist/core/store/entity_index.d.cts +108 -0
  123. package/dist/core/store/event_ring.d.cts +95 -0
  124. package/dist/core/store/extend.d.cts +39 -0
  125. package/dist/core/store/extend.d.ts +3 -73
  126. package/dist/core/store/extend.d.ts.map +1 -1
  127. package/dist/core/store/grow.d.cts +34 -0
  128. package/dist/core/store/grow.d.ts +4 -9
  129. package/dist/core/store/grow.d.ts.map +1 -1
  130. package/dist/core/store/header.d.cts +64 -0
  131. package/dist/core/store/header.d.ts +1 -1
  132. package/dist/core/store/header.d.ts.map +1 -1
  133. package/dist/core/store/index.d.cts +17 -0
  134. package/dist/core/store/index.d.ts +16 -15
  135. package/dist/core/store/index.d.ts.map +1 -1
  136. package/dist/core/store/layout_ops.d.cts +152 -0
  137. package/dist/core/store/layout_ops.d.ts +152 -0
  138. package/dist/core/store/layout_ops.d.ts.map +1 -0
  139. package/dist/core/store/region_table.d.cts +74 -0
  140. package/dist/core/store/region_table.d.ts +1 -1
  141. package/dist/core/store/region_table.d.ts.map +1 -1
  142. package/dist/core/store/snapshot.d.cts +43 -0
  143. package/dist/core/store/snapshot.d.ts +2 -2
  144. package/dist/core/store/state_hash.d.cts +38 -0
  145. package/dist/core/store/state_hash.d.ts +1 -1
  146. package/dist/core/store/store_regions.d.cts +38 -0
  147. package/dist/core/store/store_regions.d.ts +1 -1
  148. package/dist/core/store/vendored_abi/abi.d.ts +43 -0
  149. package/dist/core/store/{__generated__ → vendored_abi}/abi.d.ts.map +1 -1
  150. package/dist/dev_flag.d.cts +17 -0
  151. package/dist/dev_flag.d.ts +17 -0
  152. package/dist/dev_flag.d.ts.map +1 -0
  153. package/dist/extensions/editor/editor.d.cts +170 -0
  154. package/dist/extensions/editor/editor.d.ts +43 -22
  155. package/dist/extensions/editor/editor.d.ts.map +1 -1
  156. package/dist/extensions/editor/field_handle.d.cts +40 -0
  157. package/dist/extensions/editor/field_handle.d.ts +8 -3
  158. package/dist/extensions/editor/field_handle.d.ts.map +1 -1
  159. package/dist/extensions/editor/index.cjs +1 -1
  160. package/dist/extensions/editor/index.d.cts +21 -0
  161. package/dist/extensions/editor/index.d.ts +2 -2
  162. package/dist/extensions/editor/index.js +116 -88
  163. package/dist/extensions/reactive/ecs_sync.d.cts +214 -0
  164. package/dist/extensions/reactive/ecs_sync.d.ts +26 -22
  165. package/dist/extensions/reactive/ecs_sync.d.ts.map +1 -1
  166. package/dist/extensions/reactive/index.cjs +1 -1
  167. package/dist/extensions/reactive/index.d.cts +23 -0
  168. package/dist/extensions/reactive/index.d.ts +1 -1
  169. package/dist/extensions/reactive/index.js +106 -116
  170. package/dist/extensions/solid/index.cjs +1 -1
  171. package/dist/extensions/solid/index.d.cts +6 -0
  172. package/dist/extensions/solid/index.d.ts +2 -2
  173. package/dist/extensions/solid/index.js +8 -4
  174. package/dist/extensions/solid/kernel_solid.d.cts +45 -0
  175. package/dist/extensions/solid/kernel_solid.d.ts +6 -3
  176. package/dist/extensions/solid/kernel_solid.d.ts.map +1 -1
  177. package/dist/host_commands-BF8QMi3c.cjs +1 -0
  178. package/dist/host_commands-i4cAeyL5.js +2318 -0
  179. package/dist/index.cjs +1 -2
  180. package/dist/index.d.cts +66 -0
  181. package/dist/index.d.ts +50 -1
  182. package/dist/index.d.ts.map +1 -1
  183. package/dist/index.js +4469 -5933
  184. package/dist/internal.cjs +2 -0
  185. package/dist/internal.d.cts +20 -0
  186. package/dist/internal.d.ts +20 -0
  187. package/dist/internal.d.ts.map +1 -0
  188. package/dist/internal.js +141 -0
  189. package/dist/primitives.cjs +1 -1
  190. package/dist/primitives.d.cts +18 -0
  191. package/dist/primitives.d.ts +6 -6
  192. package/dist/primitives.js +24 -23
  193. package/dist/reactive/array.d.ts +24 -0
  194. package/dist/reactive/array.d.ts.map +1 -0
  195. package/dist/reactive/index.cjs +1 -0
  196. package/dist/reactive/index.d.cts +11 -0
  197. package/dist/reactive/index.d.ts +11 -0
  198. package/dist/reactive/index.d.ts.map +1 -0
  199. package/dist/reactive/index.js +18 -0
  200. package/dist/reactive/interop.d.cts +19 -0
  201. package/dist/{core/reactive → reactive}/interop.d.ts +1 -1
  202. package/dist/reactive/interop.d.ts.map +1 -0
  203. package/dist/reactive/kernel.d.cts +73 -0
  204. package/dist/{core/reactive → reactive}/kernel.d.ts +5 -3
  205. package/dist/reactive/kernel.d.ts.map +1 -0
  206. package/dist/reactive/map.d.ts +16 -0
  207. package/dist/reactive/map.d.ts.map +1 -0
  208. package/dist/reactive/shallow.d.cts +11 -0
  209. package/dist/reactive/shallow.d.ts +11 -0
  210. package/dist/reactive/shallow.d.ts.map +1 -0
  211. package/dist/reactive/struct.d.cts +10 -0
  212. package/dist/{core/reactive → reactive}/struct.d.ts +1 -1
  213. package/dist/reactive/struct.d.ts.map +1 -0
  214. package/dist/shallow-DQ8aG51_.cjs +1 -0
  215. package/dist/shallow-DfnAm9vG.js +150 -0
  216. package/dist/shared-BU1Cd40h.js +103 -0
  217. package/dist/shared-BymrGTyR.cjs +1 -0
  218. package/dist/shared.cjs +1 -1
  219. package/dist/shared.d.cts +22 -0
  220. package/dist/shared.d.ts +2 -2
  221. package/dist/shared.js +1 -1
  222. package/dist/topological_sort-BDvEyb9W.cjs +1 -0
  223. package/dist/topological_sort-DK6EjpWa.js +135 -0
  224. package/dist/type_primitives/assertions.d.cts +32 -0
  225. package/dist/type_primitives/assertions.d.ts +13 -1
  226. package/dist/type_primitives/assertions.d.ts.map +1 -1
  227. package/dist/type_primitives/binary_heap/binary_heap.d.cts +37 -0
  228. package/dist/type_primitives/bitset/bitset.d.cts +54 -0
  229. package/dist/type_primitives/brand.d.cts +23 -0
  230. package/dist/type_primitives/error.d.cts +16 -0
  231. package/dist/type_primitives/error.d.ts +4 -3
  232. package/dist/type_primitives/error.d.ts.map +1 -1
  233. package/dist/type_primitives/index.d.cts +13 -0
  234. package/dist/type_primitives/index.d.ts +9 -9
  235. package/dist/type_primitives/sparse_map/sparse_map.d.cts +30 -0
  236. package/dist/type_primitives/sparse_set/sparse_set.d.cts +28 -0
  237. package/dist/type_primitives/topological_sort/topological_sort.d.cts +29 -0
  238. package/dist/type_primitives/typed_arrays/typed_arrays.d.cts +127 -0
  239. package/dist/typed_arrays-BcXcnzB4.js +258 -0
  240. package/dist/typed_arrays-D3NmwtCG.cjs +1 -0
  241. package/dist/utils/error.d.cts +6 -0
  242. package/dist/version.d.cts +10 -0
  243. package/dist/version.d.ts +10 -0
  244. package/dist/version.d.ts.map +1 -0
  245. package/package.json +102 -19
  246. package/dist/array-CxbPyiHO.cjs +0 -1
  247. package/dist/array-uFR7Q8fU.js +0 -132
  248. package/dist/core/reactive/array.d.ts.map +0 -1
  249. package/dist/core/reactive/index.cjs +0 -1
  250. package/dist/core/reactive/index.d.ts +0 -10
  251. package/dist/core/reactive/index.d.ts.map +0 -1
  252. package/dist/core/reactive/index.js +0 -17
  253. package/dist/core/reactive/interop.d.ts.map +0 -1
  254. package/dist/core/reactive/kernel.d.ts.map +0 -1
  255. package/dist/core/reactive/map.d.ts.map +0 -1
  256. package/dist/core/reactive/struct.d.ts.map +0 -1
  257. package/dist/log/console_sink.d.ts +0 -4
  258. package/dist/log/console_sink.d.ts.map +0 -1
  259. package/dist/log/index.d.ts +0 -3
  260. package/dist/log/index.d.ts.map +0 -1
  261. package/dist/log/logger.d.ts +0 -27
  262. package/dist/log/logger.d.ts.map +0 -1
  263. package/dist/shared-BXSZnxx4.cjs +0 -1
  264. package/dist/shared-C678TAPY.js +0 -99
  265. package/dist/topological_sort-DlRpSrxu.js +0 -391
  266. package/dist/topological_sort-WAT-VHb-.cjs +0 -1
  267. package/dist/utils/arrays.d.ts.map +0 -1
  268. package/dist/utils/constants.d.ts.map +0 -1
  269. /package/dist/{utils/constants.d.ts → core/ecs/utils/constants.d.cts} +0 -0
  270. /package/dist/core/store/{__generated__/abi.d.ts → vendored_abi/abi.d.cts} +0 -0
  271. /package/dist/{core/reactive/array.d.ts → reactive/array.d.cts} +0 -0
  272. /package/dist/{core/reactive/map.d.ts → reactive/map.d.cts} +0 -0
@@ -1,24 +1,28 @@
1
- import { Template, TemplateEntries, TemplateOverrides } from './store';
2
- import { FrameTraceSink } from './frame_trace';
3
- import { ObserverHandle, EntitySetObserverConfig, ArchetypeSetObserverConfig, StructuralObserverConfig } from './observer';
4
- import { ColumnStore, ColumnStoreRegionHandle, StoreRegionSpec } from '../store';
5
- import { SCHEDULE, SystemEntry, SystemSet, SystemSetConfig } from './schedule';
6
- import { Archetype, ArchetypeID } from './archetype';
7
- import { SystemContext, Query, QueryBuilder, ChangedQuery, QueryResolver } from './query';
8
- import { EntityID } from './entity';
9
- import { ComponentDef, ComponentSchema, FieldValues, BundleOrDef } from './component';
10
- import { SparseComponentDef, SparseComponentID } from './sparse_store';
11
- import { RelationDef, RelationOptions } from './relation';
12
- import { EventKey, EventReader, EventSchema, SignalKey } from './event';
13
- import { ResourceKey } from './resource';
14
- import { SystemFn, SystemConfig, SystemDescriptor } from './system';
15
- import { BitSet, TypedArrayTag } from '../../type_primitives';
16
- import { StoreLayoutListener } from './store_layout_listener';
17
- import { ComputeBackend } from './compute_backend';
18
- import { ResolvedECSMemory, ECSMemoryOptions } from './ecs_memory';
1
+ import { Template, TemplateEntries, TemplateOverrides } from './store.js';
2
+ import { FrameTraceSink } from './frame_trace.js';
3
+ import { ObserverHandle, EntitySetObserverConfig, ArchetypeSetObserverConfig, StructuralObserverConfig } from './observer.js';
4
+ import { ColumnStore, ColumnStoreRegionHandle, StoreRegionSpec } from '../store/index.js';
5
+ import { ECSRelations, ECSEvents, ECSResources, ECSSnapshots } from './facades.js';
6
+ import { SCHEDULE, SystemEntry, SystemSet, SystemSetConfig } from './schedule.js';
7
+ import { Archetype, ArchetypeID } from './archetype.js';
8
+ import { SystemContext, Query, QueryBuilder, QueryCache, QueryResolver } from './query.js';
9
+ import { EntityID } from './entity.js';
10
+ import { ReadonlyComponentRef } from './ref.js';
11
+ import { ComponentDef, ComponentHandle, ComponentRegisterOptions, ComponentSchema, CompleteFieldValues, Bundle, BundleOrDef } from './component.js';
12
+ import { SparseComponentDef, SparseComponentID } from './sparse_store.js';
13
+ import { RelationDef } from './relation.js';
14
+ import { SystemFn, SystemConfig, SystemDescriptor, TypedSystemConfig, DenseAccessDecl, SpawnsAccessDecl, DespawnsAccessDecl, TransitionsAccessDecl, SparseAccessDecl, RelationsAccessDecl, ResourcesAccessDecl } from './system.js';
15
+ import { BitSet, TypedArrayTag } from '../../type_primitives/index.js';
16
+ import { StoreLayoutListener } from './store_layout_listener.js';
17
+ import { ComputeBackend } from './compute_backend.js';
18
+ import { ResolvedECSMemory, ECSMemoryOptions } from './ecs_memory.js';
19
19
  export interface ECSOptions {
20
20
  fixedTimestep?: number;
21
21
  maxFixedSteps?: number;
22
+ /** Sink for dev-mode engine diagnostics (currently the schedule's
23
+ * dropped-ordering-edge warning). Defaults to `console.warn`. Mirrors the
24
+ * `FrameTraceSink` seam's injectable style — no global logger. */
25
+ onWarn?: (message: string) => void;
22
26
  /** How the world's memory is sized and backed (#682) — the single
23
27
  * sizing surface, replacing the pre-release `initialCapacity` +
24
28
  * `bufferAllocator` pair. Express intent through exactly one arm:
@@ -64,26 +68,28 @@ export declare class ECS implements QueryResolver {
64
68
  /** Component observers (#517 §1 / ADR-0013). Inert until `observe(...)` is
65
69
  * called — the structural-flush fast path is byte-for-byte unchanged. */
66
70
  private readonly _observers;
71
+ /** Relations: register/add/remove/has, wildcard + traversal reads,
72
+ * reverse-index compaction. See `ECSRelations`. */
73
+ readonly relations: ECSRelations;
74
+ /** Host-side event channels + signals: register/registerSignal/emit/read
75
+ * (system-side `ctx.emit` is unchanged). See `ECSEvents`. */
76
+ readonly events: ECSEvents;
77
+ /** World resources: register/get/set/remove/has. See `ECSResources`. */
78
+ readonly resources: ECSResources;
79
+ /** Determinism surface: capture/restore (full + sparse), stateHash,
80
+ * the `deterministic` flag. See `ECSSnapshots`. */
81
+ readonly snapshots: ECSSnapshots;
67
82
  private readonly systems;
68
83
  private nextSystemId;
69
84
  private _tick;
85
+ private _updating;
70
86
  private _fixedTimestep;
71
87
  private _accumulator;
72
88
  private _maxFixedSteps;
73
- private readonly queryCache;
74
89
  private readonly scratchMask;
75
90
  private _nextQueryIdCounter;
76
- readonly _andSingleCache: Map<number, Query<any>>;
77
- readonly _notSingleCache: Map<number, Query<any>>;
78
- readonly _anyOfSingleCache: Map<number, Query<any>>;
79
- readonly _changedSingleCache: Map<number, ChangedQuery<any>>;
80
- readonly _withSparseSingleCache: Map<number, Query<any>>;
81
- readonly _withoutSparseSingleCache: Map<number, Query<any>>;
82
- readonly _withRelationSingleCache: Map<number, Query<any>>;
83
- readonly _withoutRelationSingleCache: Map<number, Query<any>>;
84
- readonly _optionalSingleCache: Map<number, Query<any>>;
85
- readonly _includeDisabledSingleCache: Map<number, Query<any>>;
86
- readonly _hierarchySingleCache: Map<number, Query<any>>;
91
+ /** @internal Query-composition caches (QueryResolver seam) — not public API. */
92
+ readonly _caches: QueryCache;
87
93
  private readonly _layoutSubscribers;
88
94
  private _backend;
89
95
  private readonly _memory;
@@ -98,16 +104,6 @@ export declare class ECS implements QueryResolver {
98
104
  * share the same bytes. */
99
105
  get wasmMemory(): WebAssembly.Memory | null;
100
106
  constructor(options?: ECSOptions);
101
- /** Resolve a consumer-declared SAB region's byte offset by `region_id`, or
102
- * 0 when absent. Generic, de-gamed replacement (#623) for the removed
103
- * game-named accessors; pair with the consumer's own region module to
104
- * materialise a typed view. Delegates to `Store.regionOffset`. */
105
- regionOffset(regionId: number): number;
106
- /** A handle (`{ buffer, view, offset, bytes }`) to a consumer-declared SAB
107
- * region resolved by `region_id`, or `null` when absent. A consumer's
108
- * region module builds a TypedArray view over the region's span from this.
109
- * Re-fetch after a SAB grow. Delegates to `Store.regionHandle`. (#623) */
110
- regionHandle(regionId: number): ColumnStoreRegionHandle | null;
111
107
  /** Batch variant of `regionHandle` for hosts wiring several consumer
112
108
  * regions at startup: returns the handles in argument order, never null —
113
109
  * throws ONE `REGION_NOT_DECLARED` naming every missing region id instead
@@ -137,44 +133,12 @@ export declare class ECS implements QueryResolver {
137
133
  * the schedule to the pure-TS path.
138
134
  *
139
135
  * One backend per ECS: attaching while one is already attached throws in
140
- * `__DEV__` (detach first). The engine never inspects the backend beyond
136
+ * `DEV` (detach first). The engine never inspects the backend beyond
141
137
  * `setLayout` / `run` — it carries no game vocabulary. */
142
138
  attachBackend(backend: ComputeBackend): () => void;
143
- /** Look up the field index a component reserves for `fieldName`. The
144
- * index is assigned by `registerComponent` in insertion order and is
145
- * stable for the lifetime of the ECS. Used by systems that need to
146
- * pass `(component_id, field_id)` pairs across the WASM FFI — the Zig
147
- * side identifies columns by these numeric IDs. */
148
- fieldId<S extends Record<string, TypedArrayTag>>(def: ComponentDef<S>, fieldName: Extract<keyof S, string>): number;
149
- /** Resolve an archetype's row index to the `EntityID` at that slot.
150
- * A WASM system that drains events from the event ring as
151
- * `(archId, row, …)` payloads uses this to convert the (archId, row)
152
- * pair into the `EntityID` the `ctx.emit(...)` API expects.
153
- * Throws if the (archId, row) pair is out of range. (#250 / Phase 4
154
- * PR 4D) */
155
- entityIdAtRow(archetypeId: number, row: number): EntityID;
156
139
  get fixedTimestep(): number;
157
140
  set fixedTimestep(value: number);
158
141
  get fixedAlpha(): number;
159
- /** The single SAB backing every archetype's column views. Exposed for
160
- * snapshot/restore, `columnStoreStateHash`-based determinism checks, and
161
- * Phase 2+ WASM/worker hand-off paths. Mutation flows through the
162
- * usual `addComponent` / `removeComponent` / `flush` APIs; readers
163
- * that hold a column view across a grow must consult
164
- * `header.view_stamp` to detect a republish (#171 §8.1). */
165
- get columnStore(): ColumnStore;
166
- /** Count of live archetypes (including the empty one). Surfaces the
167
- * Store-side `archetype_count` so Phase C tests can assert the
168
- * pre-warmed closure was materialised; equally useful for diagnostics. */
169
- get archetypeCount(): number;
170
- /** Count of registered relations (#471). Surfaces the Store-side count so
171
- * tests can assert it alongside `archetype_count` when checking the
172
- * no-transition invariant. */
173
- get relationCount(): number;
174
- /** Whether the determinism surface is enabled (#626 / ADR-0020). `false`
175
- * (the default) ⇒ `stateHash` / `snapshotSparse` / `restoreSparse` throw
176
- * `DETERMINISM_DISABLED`. Opt in via `new ECS({ deterministic: true })`. */
177
- get deterministic(): boolean;
178
142
  /** Attach (or detach with `null`) a per-world frame-trace sink (ADR-0030):
179
143
  * the engine then fires structured `FrameTraceSink` events at each system,
180
144
  * flush, command, observer firing, and event during `update()`, so a consumer
@@ -182,110 +146,51 @@ export declare class ECS implements QueryResolver {
182
146
  * also receives a `phaseBoundary(phase)` at each phase's post-flush settle
183
147
  * point — the safe seam to read `stateHash()` between phases of one frame and
184
148
  * bisect a divergence to the exact phase (#797 / ADR-0032). The seam is
185
- * `__DEV__`-gated end to end — in a production build this setter keeps an empty
149
+ * `DEV`-gated end to end — in a production build this setter keeps an empty
186
150
  * body and the world never retains a sink. The sink only observes; it does not
187
151
  * perturb `stateHash`, ordering, or any behaviour. */
188
152
  setTrace(sink: FrameTraceSink | null): void;
189
- /** FNV-1a 32 over (archetype_id, live row count, live column bytes)
190
- * for every archetype in id order. The canonical "live ECS state
191
- * digest" broader than the pre-#171 per-networked-component fold
192
- * (every column contributes, not just BIT_HEALTH / BIT_HEX_POS etc.)
193
- * and tighter than `columnStoreStateHash(ecs.columnStore)` (skips trailing
194
- * unused SAB capacity). Per-call cost scales with live entity count,
195
- * not SAB capacity. (#171 §6.1.9 Phase 5)
196
- *
197
- * Opt-in (#626 / ADR-0020): throws `DETERMINISM_DISABLED` unless the ECS was
198
- * constructed with `{ deterministic: true }`. */
199
- stateHash(): number;
200
- /** Serialize the sparse stores (out-of-identity components, ADR-0011) to a
201
- * self-contained byte buffer — the sparse half of a world snapshot, written
202
- * in canonical entity-index order so it's insertion-order-independent
203
- * (#470). The dense half is the SAB snapshot (`snapshotColumnStore(columnStore)`).
204
- * Pairs with `restoreSparse`.
205
- *
206
- * Opt-in (#626 / ADR-0020): throws `DETERMINISM_DISABLED` unless the ECS was
207
- * constructed with `{ deterministic: true }`. */
208
- snapshotSparse(): Uint8Array;
209
- /** Repopulate the sparse stores from `snapshotSparse` bytes (full-equality
210
- * round-trip of membership + data). Sparse components must already be
211
- * registered in the same order; throws `SparseRestoreError` on a shape or
212
- * identity mismatch (store/field count, field-identity schema hash, an entity
213
- * index past `MAX_INDEX`, or a non-canonical frame with trailing bytes).
214
- *
215
- * Opt-in (#626 / ADR-0020): throws `DETERMINISM_DISABLED` unless the ECS was
216
- * constructed with `{ deterministic: true }`. */
217
- restoreSparse(bytes: Uint8Array): void;
218
- /** Capture the full live world — dense (SAB columns), sparse + relations, and
219
- * the host-side bookkeeping the SAB omits (tick, entity recycle free-list,
220
- * alive count, per-archetype row/enabled counts) — to one self-contained byte
221
- * buffer that `restoreInto` can mount back onto a live, ticking world
222
- * ("rewind a running world and keep ticking", #789). Take it at a tick
223
- * boundary (between `update()`s).
153
+ /**
154
+ * Register a dense component and get back its typed handle. Record syntax
155
+ * gives per-field type control; the array shorthand types every field the
156
+ * same (default `"f64"` rejected on a `{ deterministic: true }` world
157
+ * (#777), pass an explicit integer type there). An empty schema `{}` is a
158
+ * tag. `opts.name` labels dev-mode diagnostics (`'Pos' (component 5)`
159
+ * instead of `component 5`) diagnostic only, no behavioural effect.
224
160
  *
225
- * Opt-in (ADR-0020): throws `DETERMINISM_DISABLED` unless `{ deterministic:
226
- * true }`. v1 does NOT capture resources, events, or change-detection /
227
- * scheduler baselines (`changed()` queries) — see ADR-0031. */
228
- snapshot(): Uint8Array;
229
- /** Mount a `snapshot()` buffer onto this live world and leave it ready to keep
230
- * ticking (#789). Fails closed on a malformed frame or a registration mismatch
231
- * (different component/archetype graph, mismatched entity-index capacity, or a
232
- * divergent sparse-store registration) BEFORE mutating any live state — the
233
- * guard reads the snapshot's descriptors + sparse-section shape from the bytes,
234
- * since the dense build reuses (and overwrites) the live in-place backing.
235
- * Throws `WorldRestoreError` (dense) / `SparseRestoreError` (sparse). Requires a
236
- * world whose archetype set + column layout match the snapshot's (prewarm so the
237
- * set is stable).
161
+ * The handle is *callable*: `Pos({ x: 1 })` mints a `Bundle` for the
162
+ * attach surfaces (`spawnBundle`, `ctx.commands.spawn`, `addComponent`).
238
163
  *
239
- * Opt-in (ADR-0020): throws `DETERMINISM_DISABLED` unless `{ deterministic:
240
- * true }`. */
241
- restoreInto(bytes: Uint8Array): void;
242
- registerComponent<S extends Record<string, TypedArrayTag>>(schema: S): ComponentDef<S>;
243
- registerComponent<const F extends readonly string[], T extends TypedArrayTag = "f64">(fields: F, type?: T): ComponentDef<{
164
+ * @example
165
+ * const Pos = ecs.registerComponent({ x: "f64", y: "f64" });
166
+ * const Hp = ecs.registerComponent(["current", "max"], "i32");
167
+ * const Frozen = ecs.registerComponent({}, { name: "Frozen" }); // tag
168
+ * ecs.getField(e, Pos, "x"); // field names/types flow from the schema
169
+ */
170
+ registerComponent<S extends Record<string, TypedArrayTag>>(schema: S, opts?: ComponentRegisterOptions): ComponentDef<S>;
171
+ registerComponent<const F extends readonly string[], T extends TypedArrayTag = "f64">(fields: F, type?: T, opts?: ComponentRegisterOptions): ComponentDef<{
244
172
  readonly [K in F[number]]: T;
245
173
  }>;
246
- registerTag(): ComponentDef<Record<string, never>>;
247
- registerSparseComponent<S extends Record<string, TypedArrayTag>>(schema: S): SparseComponentDef<S>;
248
- registerSparseComponent<const F extends readonly string[], T extends TypedArrayTag = "f64">(fields: F, type?: T): SparseComponentDef<{
174
+ registerSparseComponent<S extends Record<string, TypedArrayTag>>(schema: S, opts?: ComponentRegisterOptions): SparseComponentDef<S>;
175
+ registerSparseComponent<const F extends readonly string[], T extends TypedArrayTag = "f64">(fields: F, type?: T, opts?: ComponentRegisterOptions): SparseComponentDef<{
249
176
  readonly [K in F[number]]: T;
250
177
  }>;
251
- /** Register a sparse tag (empty schema) — membership only, no data. */
252
- registerSparseTag(): SparseComponentDef<Record<string, never>>;
253
- registerEvent<S extends EventSchema>(key: EventKey<S>, fields: readonly (keyof S & string)[]): void;
254
- registerSignal(key: SignalKey): void;
255
- registerResource<T>(key: ResourceKey<T>, value: T): void;
256
- resource<T>(key: ResourceKey<T>): T;
257
- setResource<T>(key: ResourceKey<T>, value: T): void;
258
- /** Drop a resource from the world (#798). Unlike {@link registerResource}
259
- * — a one-time world-setup op — this is a runtime mutation, so it is access-
260
- * checked as a *write* (a system removing a resource must declare it in
261
- * `resourceWrites`, which is what serialises it against readers/writers of the
262
- * same key). Fails closed on a missing key. Afterwards the key is free to
263
- * `registerResource` again — the present → absent → present lifecycle.
264
- * Resources are out of `stateHash` and snapshot/resume, so a remove never
265
- * perturbs the determinism hash. */
266
- removeResource<T>(key: ResourceKey<T>): void;
267
- hasResource<T>(key: ResourceKey<T>): boolean;
268
- createEntity(): EntityID;
269
- createEntity<Defs extends readonly ComponentDef[]>(template: Template<Defs>, overrides?: TemplateOverrides<Defs>): EntityID;
270
- /** Register an archetype template (#462). Resolves the component set +
271
- * default field values to a target archetype once (creating it if absent —
272
- * fits the prewarm model), so later `spawn` / `spawnMany` calls land
273
- * entities directly in that archetype with **zero archetype transitions**.
178
+ /**
179
+ * Spawn an entity, immediately. Bare `spawn()` creates an empty entity —
180
+ * attach components afterward. `spawn(template, overrides?)` lands
181
+ * directly in the template's archetype with zero archetype transitions,
182
+ * applying optional flat per-field overrides on top of the template
183
+ * defaults. Inside a system use `ctx.commands.spawn(...)` instead.
274
184
  *
275
- * const Bullet = ecs.template([
276
- * { def: Position, values: { x: 0, y: 0 } },
277
- * { def: Velocity, values: { vx: 0, vy: 0 } },
278
- * ]);
185
+ * @example
186
+ * const e = ecs.spawn();
187
+ * ecs.addComponent(e, Pos, { x: 0, y: 0 });
279
188
  *
280
- * The big win is multi-component entities and bulk spawns; a single-
281
- * component spawn is no faster than `createEntity` + `addComponent`, which
282
- * already bump-allocates a fresh entity into the target archetype. See
283
- * ADR-0010. */
284
- template<Defs extends readonly ComponentDef[]>(entries: TemplateEntries<Defs>): Template<Defs>;
285
- /** Bulk-spawn `count` identical entities from `template`. Field writes are
286
- * O(columns) (one `TypedArray.fill` per column), not O(count×columns).
287
- * Returns the new ids in spawn order. */
288
- createEntities(template: Template, count: number): EntityID[];
189
+ * const Bullet = ecs.template([{ def: Pos, values: { x: 0, y: 0 } }]);
190
+ * const b = ecs.spawn(Bullet, { x: 5 }); // override a template default
191
+ */
192
+ spawn(): EntityID;
193
+ spawn<Defs extends readonly ComponentDef[]>(template: Template<Defs>, overrides?: TemplateOverrides<Defs>): EntityID;
289
194
  /**
290
195
  * Spawn an entity from varargs bundles (§bundles) — the immediate
291
196
  * host-side analog of `ctx.commands.spawn`. `ecs.spawnBundle(bundle(Pos,{x,y}),
@@ -295,81 +200,53 @@ export declare class ECS implements QueryResolver {
295
200
  * prototype this mirrors the existing per-component `addComponent` path.
296
201
  */
297
202
  spawnBundle(...items: BundleOrDef[]): EntityID;
298
- /** Buffer an entity for deferred destruction (applied at the next phase
299
- * flush matches the semantics of `SystemContext.destroyEntity`). The
300
- * ECS surface is unsuffixed because the context (`ECS` vs Store) already
301
- * implies the mode; `Store.destroyEntity` is the immediate path. */
302
- destroyEntity(id: EntityID): void;
303
- isAlive(id: EntityID): boolean;
304
- get entityCount(): number;
305
- addComponent(entityId: EntityID, def: ComponentDef<Record<string, never>>): this;
306
- addComponent<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, values: FieldValues<S>): this;
307
- addComponents(entityId: EntityID, entries: {
308
- def: ComponentDef;
309
- values?: Record<string, number>;
310
- }[]): void;
311
- removeComponent(entityId: EntityID, def: ComponentDef): this;
312
- removeComponents(entityId: EntityID, defs: ComponentDef[]): void;
313
- hasComponent(entityId: EntityID, def: ComponentDef): boolean;
203
+ /** DEV-only: throw when an *immediate* host structural mutator is called
204
+ * from inside one of THIS world's system bodies (or an observer / onAdded
205
+ * hook they run in the same access spans). One rule for every host
206
+ * mutator, not just despawn: an immediate structural op mid-schedule can
207
+ * move or swap rows a running query is walking, and it is invisible to
208
+ * observers. The archetype-level `_iterDepth` guard only catches mutations
209
+ * touching the archetype currently being iterated — an op landing elsewhere
210
+ * would silently skip observers, so the receiver rule ("inside a system,
211
+ * use ctx.commands") is enforced wholesale here.
212
+ *
213
+ * `_updating` scopes the guard to THIS world: the accessCheck slot is
214
+ * process-global, so without it a system of world A mutating world B (a
215
+ * supported #785 pattern — B is not mid-iteration) would false-throw. */
216
+ private _assertHostMutationOutsideSystem;
217
+ /** Immediately destroy an entity — `ecs.despawn(e); ecs.isAlive(e)` is
218
+ * `false` on the next line, matching the immediacy of every other host
219
+ * facade mutation. Inside a system the buffered path is
220
+ * `ctx.commands.despawn` (applied at the phase flush); calling this from
221
+ * a system body throws in DEV, since an immediate destroy mid-iteration
222
+ * can invalidate rows the running query is walking. */
223
+ despawn(id: EntityID): void;
314
224
  /** Disable `id` (idempotent). Excluded from default queries until re-enabled. */
315
225
  disable(id: EntityID): this;
316
226
  /** Re-enable a disabled `id` (idempotent). */
317
227
  enable(id: EntityID): this;
318
- /** Whether `id` is currently disabled. */
319
- isDisabled(id: EntityID): boolean;
320
- addSparse(entityId: EntityID, def: SparseComponentDef<Record<string, never>>): this;
321
- addSparse<S extends ComponentSchema>(entityId: EntityID, def: SparseComponentDef<S>, values: FieldValues<S>): this;
322
- removeSparse(entityId: EntityID, def: SparseComponentDef): this;
323
- hasSparse(entityId: EntityID, def: SparseComponentDef): boolean;
324
- getSparseField<S extends ComponentSchema>(entityId: EntityID, def: SparseComponentDef<S>, field: string & keyof S): number;
325
- setSparseField<S extends ComponentSchema>(entityId: EntityID, def: SparseComponentDef<S>, field: string & keyof S, value: number): void;
326
- /** Register a relation kind. Exclusive (default) stores one target per
327
- * source in a backing sparse component; `{ multi: true }` stores a target
328
- * set per source. `{ onDeleteTarget: "delete" | "clear" | "orphan" }`
329
- * selects what happens to a relation's sources when a target is destroyed
330
- * (default `orphan`, #473). See `registerRelation` on `Store` / ADR-0011. */
331
- registerRelation(opts?: RelationOptions): RelationDef;
332
- /** Add a `(R, tgt)` pair to `src`. Exclusive replaces the existing target;
333
- * multi adds to the set. No archetype transition. */
334
- addRelation(src: EntityID, def: RelationDef, tgt: EntityID): this;
335
- /** Remove a `(R, tgt)` pair from `src`. For multi, omitting `tgt` removes
336
- * all of `src`'s targets. No archetype transition. */
337
- removeRelation(src: EntityID, def: RelationDef, tgt?: EntityID): this;
338
- /** The single target of `src` under an exclusive relation, or `undefined`. */
339
- targetOf(src: EntityID, def: RelationDef): EntityID | undefined;
340
- /** All targets of `src` under `R`, ascending by id. */
341
- targetsOf(src: EntityID, def: RelationDef): EntityID[];
342
- /** Sources pointing at `tgt` under `R` (the reverse index), ascending by id. */
343
- sourcesOf(def: RelationDef, tgt: EntityID): EntityID[];
344
- /** Whether `src` holds any pair under `R`. */
345
- hasRelation(src: EntityID, def: RelationDef): boolean;
346
- /** All `(source, target)` pairs of relation `R` — the `(R, *)` wildcard
347
- * (#472). Sources in canonical entity-index order; a multi source's targets
348
- * ascending by id. Cold path. */
349
- pairsOf(def: RelationDef): [EntityID, EntityID][];
350
- /** Every `(relation, source)` pointing at `tgt`, across all relation kinds —
351
- * the `(*, T)` wildcard (#472). Ordered by relation id then source id. The
352
- * single-relation form is `sourcesOf(def, tgt)`. */
353
- sourcesOfAny(tgt: EntityID): [RelationDef, EntityID][];
354
- /** Reclaim relation reverse-index memory: drop every reverse entry whose
355
- * target has been destroyed, returning the total dropped (#491). Under the
356
- * default `orphan` policy a destroyed target's reverse entry lingers until
357
- * each source re-targets or dies, so orphan-pointing at a churn of
358
- * short-lived targets grows the index without bound. A purely cold-path
359
- * reclaim — no observable state change (forward links stay dangling per
360
- * `orphan`, `stateHash` is unaffected) — call it at scene/snapshot
361
- * boundaries. */
362
- compactRelations(): number;
363
- /** Walk relation `R` up from `src` to its chain root, returning
364
- * `[src, parent, …, root]` (nearest-ancestor-first). Exclusive only. */
365
- ancestorsOf(src: EntityID, def: RelationDef): EntityID[];
366
- /** The root of `src`'s `R`-chain (`src` itself when it has no target).
367
- * Exclusive only. */
368
- rootOf(src: EntityID, def: RelationDef): EntityID;
369
- /** Walk relation `R` down from `root` over the reverse index, returning the
370
- * subtree (including `root`) breadth-first — parents before children (the
371
- * `cascade` order). Exclusive only. */
372
- cascadeOf(root: EntityID, def: RelationDef): EntityID[];
228
+ /**
229
+ * Attach a component to an entity, immediately (inside a system, use the
230
+ * deferred `ctx.commands.add`). Three shapes: a bare def attaches a tag; a
231
+ * bundle (`Pos({ x: 1 })`) zero-fills omitted fields; the explicit
232
+ * `(e, def, values)` form demands every field, so a typo'd or missing
233
+ * field is a compile error.
234
+ *
235
+ * @example
236
+ * ecs.addComponent(e, Frozen); // tag
237
+ * ecs.addComponent(e, Pos({ x: 1 })); // bundle y zero-fills
238
+ * ecs.addComponent(e, Pos, { x: 1, y: 2 }); // complete values
239
+ */
240
+ addComponent(entityId: EntityID, def: ComponentDef<Record<string, never>>): this;
241
+ addComponent<S extends ComponentSchema>(entityId: EntityID, bundle: Bundle<S>): this;
242
+ addComponent<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, values: CompleteFieldValues<S>): this;
243
+ /** Batch-attach several components in one archetype transition. Each
244
+ * entry's `values` is checked against its own def's schema (a misspelled
245
+ * field is a compile error; tags refuse `values`) same typing as
246
+ * `ECS.template` entries. Omitted fields zero-fill. */
247
+ addComponents<Defs extends readonly ComponentDef[]>(entityId: EntityID, entries: TemplateEntries<Defs>): this;
248
+ removeComponent(entityId: EntityID, def: ComponentDef): this;
249
+ removeComponents(entityId: EntityID, defs: ComponentDef[]): this;
373
250
  /**
374
251
  * Bulk add a component to ALL entities in the given archetype.
375
252
  * O(columns) via TypedArray.set() instead of O(N×columns).
@@ -377,31 +254,218 @@ export declare class ECS implements QueryResolver {
377
254
  * Takes an `ArchetypeID` (from `ArchetypeView.id`) rather than a concrete
378
255
  * `Archetype` — the concrete type is internal (issue #378).
379
256
  */
380
- batchAddComponent(src: ArchetypeID, def: ComponentDef<Record<string, never>>): void;
381
- batchAddComponent<S extends ComponentSchema>(src: ArchetypeID, def: ComponentDef<S>, values: FieldValues<S>): void;
257
+ batchAddComponent(src: ArchetypeID, def: ComponentDef<Record<string, never>>): this;
258
+ batchAddComponent<S extends ComponentSchema>(src: ArchetypeID, def: ComponentDef<S>, values: CompleteFieldValues<S>): this;
382
259
  /**
383
260
  * Bulk remove a component from ALL entities in the given archetype.
384
261
  * O(columns) via TypedArray.set() instead of O(N×columns).
385
262
  *
386
263
  * Takes an `ArchetypeID` (from `ArchetypeView.id`); see `batchAddComponent`.
387
264
  */
388
- batchRemoveComponent(src: ArchetypeID, def: ComponentDef): void;
265
+ batchRemoveComponent(src: ArchetypeID, def: ComponentDef): this;
389
266
  getField<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, field: string & keyof S): number;
267
+ /** Host-side parity with `SystemContext.refRead` (POLISH_AUDIT M7): a
268
+ * read-only whole-component view for tooling/tests, instead of reading
269
+ * field-by-field. Same advisory-`readonly` semantics as the ctx variant;
270
+ * no `_changedTick` bump. Dev-throws on a dead entity, or when the entity
271
+ * doesn't hold the component (tags included — no fields, nothing to ref).
272
+ *
273
+ * **Staleness:** unlike ctx refs (protected by deferred structural changes
274
+ * until the phase flush), host-side structural mutations apply immediately —
275
+ * any `addComponent`/`removeComponent`/`despawn` after creating the ref can
276
+ * row-swap so the old ref silently reads *another entity's* data. The ref is
277
+ * only valid until the next structural mutation; treat it as an immediate
278
+ * single-expression read and re-create it after any structural change. */
279
+ refRead<S extends ComponentSchema>(def: ComponentDef<S>, entityId: EntityID): ReadonlyComponentRef<S>;
280
+ /** Total sibling of {@link getField} (POLISH_AUDIT #9): `undefined` when the
281
+ * entity is dead or doesn't hold the component, instead of a dev throw /
282
+ * prod garbage read. The safe way to probe-and-read in one call:
283
+ * `ecs.tryGetField(e, Health, "current") ?? 0`. */
284
+ tryGetField<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, field: string & keyof S): number | undefined;
390
285
  setField<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, field: string & keyof S, value: number): void;
391
286
  /** Read-modify-write one field: `updateField(e, Gold, "value", v => v - cost)`
392
287
  * is the one-line form of the `getField` → compute → `setField` round trip.
393
288
  * Returns the written value. Same access-check and observer semantics as the
394
289
  * two calls it composes. */
395
290
  updateField<S extends ComponentSchema>(entityId: EntityID, def: ComponentDef<S>, field: string & keyof S, fn: (current: number) => number): number;
396
- emit(key: SignalKey): void;
397
- emit<S extends EventSchema>(key: EventKey<S>, values: S): void;
398
- read<S extends EventSchema>(key: EventKey<S>): EventReader<S>;
291
+ /**
292
+ * Get the live, cached query matching entities that have **all** of
293
+ * `defs`. Queries are deduplicated by mask — calling this twice with the
294
+ * same terms returns the same instance — so build once at setup and reuse;
295
+ * the view stays live as archetypes appear. Refine with `.and()` /
296
+ * `.without()` / `.anyOf()`; iterate with `eachChunk` (mutating hot path),
297
+ * `forEach` (per-archetype), or `forEachEntity` (per-entity).
298
+ *
299
+ * @example
300
+ * const movers = ecs.query(Pos, Vel);
301
+ * movers.eachChunk((cols, count) => {
302
+ * const { x, y } = cols.mut(Pos);
303
+ * const { vx, vy } = cols.read(Vel);
304
+ * for (let i = 0; i < count; i++) { x[i] += vx[i]; y[i] += vy[i]; }
305
+ * });
306
+ */
399
307
  query<T extends ComponentDef[]>(...defs: T): Query<T>;
308
+ _nextQueryId(): number;
309
+ /** QueryResolver implementation — creates or retrieves a cached Query. */
310
+ _resolveQuery(include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, defs: readonly ComponentDef[]): Query<any>;
311
+ /**
312
+ * Register a system and get its scheduling handle. The config form is the
313
+ * production shape: it declares the access surface (`reads` / `writes` are
314
+ * mandatory; `spawns` / `despawns` / resource and sparse/relation terms
315
+ * optional), which is enforced at runtime in dev *and* narrows `ctx` at
316
+ * the type level so undeclared access fails to compile. Registration does
317
+ * not schedule — pass the returned descriptor to
318
+ * `ecs.addSystems(SCHEDULE.UPDATE, ...)`.
319
+ *
320
+ * @example
321
+ * // Full config — declared access, dev-checked and compile-checked
322
+ * const move = ecs.registerSystem({
323
+ * reads: [Vel],
324
+ * writes: [Pos],
325
+ * fn(ctx, dt) {
326
+ * movers.eachChunk((cols, count) => { ... });
327
+ * },
328
+ * });
329
+ * ecs.addSystems(SCHEDULE.UPDATE, move);
330
+ *
331
+ * @example
332
+ * // Bare function (no declared access — any component touch throws in dev)
333
+ * ecs.registerSystem((ctx, dt) => { ... });
334
+ * // Function + query builder (query resolved at registration time)
335
+ * ecs.registerSystem((q, ctx, dt) => { q.forEach((arch) => { ... }); }, (qb) => qb.with(Pos, Vel));
336
+ */
337
+ registerSystem(fn: SystemFn): SystemDescriptor;
338
+ registerSystem<Defs extends readonly ComponentDef[]>(fn: (q: Query<Defs>, ctx: SystemContext, dt: number) => void, queryFn: (qb: QueryBuilder) => Query<Defs>): SystemDescriptor;
339
+ /** `exclusive: true` grants full world access at runtime (§system.ts), so
340
+ * the context stays fully permissive at the type layer too. Declared BEFORE
341
+ * the typed-config overload so exclusive configs never get narrowed. */
342
+ registerSystem(config: SystemConfig & {
343
+ readonly exclusive: true;
344
+ }): SystemDescriptor;
345
+ /** Config form (§typestate, system.ts): the declaration lists are inferred
346
+ * as literal tuples and `fn` / `onAdded` receive
347
+ * `SystemContext<DeclaredAccess<…>>` — undeclared access fails to compile
348
+ * with the same taxonomy the runtime `accessCheck` throws with in
349
+ * `DEV`. A config VALUE typed as plain `SystemConfig` (dynamically
350
+ * built) still matches: its erased declaration lists compute a permissive
351
+ * access record. Escape hatch: annotate `fn(ctx: SystemContext, dt)`
352
+ * explicitly to keep a system permissive at compile time. */
353
+ registerSystem<R extends DenseAccessDecl, W extends DenseAccessDecl, Sp extends SpawnsAccessDecl = readonly never[], De extends DespawnsAccessDecl = readonly never[], Tr extends TransitionsAccessDecl = readonly never[], SR extends SparseAccessDecl = readonly never[], SW extends SparseAccessDecl = readonly never[], RR extends RelationsAccessDecl = readonly never[], RW extends RelationsAccessDecl = readonly never[], QR extends ResourcesAccessDecl = readonly never[], QW extends ResourcesAccessDecl = readonly never[]>(config: TypedSystemConfig<R, W, Sp, De, Tr, SR, SW, RR, RW, QR, QW>): SystemDescriptor;
354
+ removeSystem(system: SystemDescriptor): void;
355
+ get systemCount(): number;
356
+ /**
357
+ * Run the startup phases, once, before the first `update()`. Prewarms
358
+ * every archetype the registered systems/observers can produce, runs each
359
+ * system's `onAdded` hook, then the `PRE_STARTUP` → `STARTUP` →
360
+ * `POST_STARTUP` schedule. Events emitted during startup are drained at
361
+ * its tail — they do not leak into frame 1.
362
+ *
363
+ * @example
364
+ * ecs.addSystems(SCHEDULE.UPDATE, move);
365
+ * ecs.startup();
366
+ * ecs.update(1 / 60); // now tick every frame
367
+ */
368
+ startup(): void;
369
+ /** Compute the archetype closure from every registered system's AND
370
+ * observer's `spawns` + `transitions` and ask the store to plant the
371
+ * whole set in one `extendColumnStore` call. Observers carry the same
372
+ * access shape systems do (a synthesized `SystemDescriptor`), so an
373
+ * observer that spawns/transitions gets its target archetype prewarmed
374
+ * too rather than first-touching lazily mid-tick (#768). Exposed as
375
+ * `private` because the only caller is `startup()`; visible to tests via
376
+ * the `archetype_count` delta on the public ECS facade. */
377
+ private prewarmArchetypes;
378
+ /**
379
+ * Advance the world one frame. Runs the fixed-timestep accumulator loop
380
+ * (`FIXED_UPDATE`, when any fixed system is registered), then
381
+ * `PRE_UPDATE` → `UPDATE` → `POST_UPDATE`, flushing deferred structural
382
+ * commands at each phase boundary. Events emitted this tick are readable
383
+ * for the rest of the tick and cleared at the tail. `dt` is in seconds.
384
+ *
385
+ * @example
386
+ * let last = performance.now();
387
+ * function frame(now: number) {
388
+ * ecs.update((now - last) / 1000);
389
+ * last = now;
390
+ * requestAnimationFrame(frame);
391
+ * }
392
+ * requestAnimationFrame(frame);
393
+ */
394
+ update(dt: number): void;
395
+ dispose(): void;
396
+ /** Resolve a consumer-declared SAB region's byte offset by `region_id`, or
397
+ * 0 when absent. Generic, de-gamed replacement (#623) for the removed
398
+ * game-named accessors; pair with the consumer's own region module to
399
+ * materialise a typed view. Delegates to `Store.regionOffset`. */
400
+ regionOffset(regionId: number): number;
401
+ /** A handle (`{ buffer, view, offset, bytes }`) to a consumer-declared SAB
402
+ * region resolved by `region_id`, or `null` when absent. A consumer's
403
+ * region module builds a TypedArray view over the region's span from this.
404
+ * Re-fetch after a SAB grow. Delegates to `Store.regionHandle`. (#623) */
405
+ regionHandle(regionId: number): ColumnStoreRegionHandle | null;
406
+ /** Look up the field index a component reserves for `fieldName`. The
407
+ * index is assigned by `registerComponent` in insertion order and is
408
+ * stable for the lifetime of the ECS. Used by systems that need to
409
+ * pass `(component_id, field_id)` pairs across the WASM FFI — the Zig
410
+ * side identifies columns by these numeric IDs. */
411
+ fieldId<S extends Record<string, TypedArrayTag>>(def: ComponentDef<S>, fieldName: Extract<keyof S, string>): number;
412
+ /** Resolve an archetype's row index to the `EntityID` at that slot.
413
+ * A WASM system that drains events from the event ring as
414
+ * `(archId, row, …)` payloads uses this to convert the (archId, row)
415
+ * pair into the `EntityID` the `ctx.emit(...)` API expects.
416
+ * Throws if the (archId, row) pair is out of range. (#250 / Phase 4
417
+ * PR 4D) */
418
+ entityIdAtRow(archetypeId: number, row: number): EntityID;
419
+ /** The single SAB backing every archetype's column views. Exposed for
420
+ * snapshot/restore, `columnStoreStateHash`-based determinism checks, and
421
+ * Phase 2+ WASM/worker hand-off paths. Mutation flows through the
422
+ * usual `addComponent` / `removeComponent` / `flush` APIs; readers
423
+ * that hold a column view across a grow must consult
424
+ * `header.view_stamp` to detect a republish (#171 §8.1). */
425
+ get columnStore(): ColumnStore;
426
+ /** Count of live archetypes (including the empty one). Surfaces the
427
+ * Store-side `archetype_count` so Phase C tests can assert the
428
+ * pre-warmed closure was materialised; equally useful for diagnostics. */
429
+ get archetypeCount(): number;
430
+ registerTag(): ComponentDef<Record<string, never>>;
431
+ /** Register a sparse tag (empty schema) — membership only, no data. */
432
+ registerSparseTag(): SparseComponentDef<Record<string, never>>;
433
+ /** Register an archetype template (#462). Resolves the component set +
434
+ * default field values to a target archetype once (creating it if absent —
435
+ * fits the prewarm model), so later `spawn` / `spawnMany` calls land
436
+ * entities directly in that archetype with **zero archetype transitions**.
437
+ *
438
+ * const Bullet = ecs.template([
439
+ * { def: Position, values: { x: 0, y: 0 } },
440
+ * { def: Velocity, values: { vx: 0, vy: 0 } },
441
+ * ]);
442
+ *
443
+ * The big win is multi-component entities and bulk spawns; a single-
444
+ * component spawn is no faster than `spawn` + `addComponent`, which
445
+ * already bump-allocates a fresh entity into the target archetype. See
446
+ * ADR-0010. */
447
+ template<Defs extends readonly ComponentDef[]>(entries: TemplateEntries<Defs>): Template<Defs>;
448
+ /** Bulk-spawn `count` entities from `template`, optionally applying one
449
+ * shared `overrides` object to every spawned row (same typed keys as
450
+ * `spawn`). Field writes are O(columns) (one `TypedArray.fill` per
451
+ * column), not O(count×columns). Returns the new ids in spawn order. */
452
+ spawnMany<Defs extends readonly ComponentDef[]>(template: Template<Defs>, count: number, overrides?: TemplateOverrides<Defs>): EntityID[];
453
+ isAlive(id: EntityID): boolean;
454
+ get entityCount(): number;
455
+ hasComponent(entityId: EntityID, def: ComponentDef): boolean;
456
+ /** Whether `id` is currently disabled. Toggle via `disable` / `enable`
457
+ * (immediate, above the band — they carry the in-system dev guard). */
458
+ isDisabled(id: EntityID): boolean;
459
+ addSparse(entityId: EntityID, def: SparseComponentDef<Record<string, never>>): this;
460
+ addSparse<S extends ComponentSchema>(entityId: EntityID, def: SparseComponentDef<S>, values: CompleteFieldValues<S>): this;
461
+ removeSparse(entityId: EntityID, def: SparseComponentDef): this;
462
+ hasSparse(entityId: EntityID, def: SparseComponentDef): boolean;
463
+ getSparseField<S extends ComponentSchema>(entityId: EntityID, def: SparseComponentDef<S>, field: string & keyof S): number;
464
+ setSparseField<S extends ComponentSchema>(entityId: EntityID, def: SparseComponentDef<S>, field: string & keyof S, value: number): void;
400
465
  _getLastRunTick(): number;
401
466
  /** Current ECS write tick — the tick `eachChunk` stamps via `cols.mut` (§eachChunk). */
402
467
  _getCurrentTick(): number;
403
468
  _getQueryDirtyEpoch(): number;
404
- _nextQueryId(): number;
405
469
  /** QueryResolver implementation — sparse-membership match path (#469). */
406
470
  _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;
407
471
  /** QueryResolver implementation — backing sparse id of a relation, for the
@@ -411,27 +475,6 @@ export declare class ECS implements QueryResolver {
411
475
  _forEachRelationTargetMatch(target: EntityID, include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, sparseInclude: readonly SparseComponentID[], sparseExclude: readonly SparseComponentID[], includeDisabled: boolean, cb: (entityId: EntityID) => void): void;
412
476
  /** QueryResolver implementation — depth-ordered hierarchy match path (#581). */
413
477
  _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;
414
- /** QueryResolver implementation — creates or retrieves a cached Query. */
415
- _resolveQuery(include: BitSet, exclude: BitSet | null, anyOf: BitSet | null, defs: readonly ComponentDef[]): Query<any>;
416
- private _findCached;
417
- /**
418
- * Register a system.
419
- *
420
- * // Bare function (no query, no lifecycle hooks)
421
- * world.registerSystem((ctx, dt) => { ... });
422
- *
423
- * // Function + query builder (query resolved at registration time)
424
- * world.registerSystem(
425
- * (q, ctx, dt) => { q.forEach((arch) => { ... }); },
426
- * (qb) => qb.with(Pos, Vel),
427
- * );
428
- *
429
- * // Full config — declares reads/writes (dev-checked) + optional lifecycle hooks
430
- * world.registerSystem({ reads: [Pos, Vel], writes: [Pos], fn(ctx, dt) { ... } });
431
- */
432
- registerSystem(fn: SystemFn): SystemDescriptor;
433
- registerSystem<Defs extends readonly ComponentDef[]>(fn: (q: Query<Defs>, ctx: SystemContext, dt: number) => void, queryFn: (qb: QueryBuilder) => Query<Defs>): SystemDescriptor;
434
- registerSystem(config: SystemConfig): SystemDescriptor;
435
478
  addSystems(label: SCHEDULE, ...entries: (SystemDescriptor | SystemEntry)[]): this;
436
479
  /**
437
480
  * Configure a `SystemSet` (#576) — the shared run condition and/or ordering
@@ -439,8 +482,6 @@ export declare class ECS implements QueryResolver {
439
482
  * `addSystems`: see `Schedule.configureSet`. Returns `this` to chain.
440
483
  */
441
484
  configureSet(set: SystemSet, config: SystemSetConfig): this;
442
- removeSystem(system: SystemDescriptor): void;
443
- get systemCount(): number;
444
485
  /**
445
486
  * Register a per-component observer (#517 §1 / ADR-0013). Reactions that were
446
487
  * hand-polled every tick — "on `Death` added → spawn corpse", "on `HexPos`
@@ -455,8 +496,8 @@ export declare class ECS implements QueryResolver {
455
496
  * when an entity carrying the component is *disabled* / *enabled* (#577,
456
497
  * ADR-0023), once per net transition, for every component the entity carries
457
498
  * (a disable is a soft remove of the whole mask from default queries). Like
458
- * `onAdd`/`onRemove`, an *immediate* `world.disable()` does not fire — only
459
- * the deferred `ctx.disable()` toggle does. `yieldExisting` seeds enabled
499
+ * `onAdd`/`onRemove`, an *immediate* `ecs.disable()` does not fire — only
500
+ * the deferred `ctx.commands.disable()` toggle does. `yieldExisting` seeds enabled
460
501
  * members only, so a disabled entity is correctly absent at seed.
461
502
  * - **`onSet`** fires at the post-update detection point. Default
462
503
  * `granularity: "archetype"` fires `(arch, ctx)` once per changed
@@ -469,24 +510,13 @@ export declare class ECS implements QueryResolver {
469
510
  *
470
511
  * Observer callbacks that touch ECS state must declare it via `access`
471
512
  * (merged over an all-empty declaration) — undeclared access throws in
472
- * `__DEV__`, and those decls drive the firing order. `yieldExisting` replays
513
+ * `DEV`, and those decls drive the firing order. `yieldExisting` replays
473
514
  * `onAdd` over current matches on registration. Register at world-build time
474
515
  * (before `startup()`); the returned handle's `dispose()` unregisters.
475
516
  */
476
- observe<S extends ComponentSchema>(def: ComponentDef<S>, config: StructuralObserverConfig): ObserverHandle;
477
- observe<S extends ComponentSchema>(def: ComponentDef<S>, config: EntitySetObserverConfig): ObserverHandle;
478
- observe<S extends ComponentSchema>(def: ComponentDef<S>, config: ArchetypeSetObserverConfig): ObserverHandle;
479
- startup(): void;
480
- /** Compute the archetype closure from every registered system's AND
481
- * observer's `spawns` + `transitions` and ask the store to plant the
482
- * whole set in one `extendColumnStore` call. Observers carry the same
483
- * access shape systems do (a synthesized `SystemDescriptor`), so an
484
- * observer that spawns/transitions gets its target archetype prewarmed
485
- * too rather than first-touching lazily mid-tick (#768). Exposed as
486
- * `private` because the only caller is `startup()`; visible to tests via
487
- * the `archetype_count` delta on the public ECS facade. */
488
- private prewarmArchetypes;
489
- update(dt: number): void;
517
+ observe(def: ComponentHandle, config: StructuralObserverConfig): ObserverHandle;
518
+ observe(def: ComponentHandle, config: EntitySetObserverConfig): ObserverHandle;
519
+ observe(def: ComponentHandle, config: ArchetypeSetObserverConfig): ObserverHandle;
490
520
  /**
491
521
  * Stamp every SAB-backed archetype's live `length` into its SAB
492
522
  * descriptor's `row_count` field. **You usually don't need to call
@@ -501,7 +531,6 @@ export declare class ECS implements QueryResolver {
501
531
  */
502
532
  publishArchetypeRowCounts(): void;
503
533
  flush(): void;
504
- dispose(): void;
505
534
  }
506
535
  /** Phase C of issue #213 — archetype closure from a descriptor set.
507
536
  *