@oasys/oecs 0.3.1 → 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 (324) hide show
  1. package/CHANGELOG.md +553 -0
  2. package/README.md +207 -267
  3. package/dist/core/ecs/access_check.d.cts +77 -0
  4. package/dist/core/ecs/access_check.d.ts +77 -0
  5. package/dist/core/ecs/access_check.d.ts.map +1 -0
  6. package/dist/core/ecs/archetype.d.cts +486 -0
  7. package/dist/core/ecs/archetype.d.ts +486 -0
  8. package/dist/core/ecs/archetype.d.ts.map +1 -0
  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 +43 -0
  14. package/dist/core/ecs/builtin_relations.d.ts.map +1 -0
  15. package/dist/core/ecs/command_log.d.cts +116 -0
  16. package/dist/core/ecs/command_log.d.ts +116 -0
  17. package/dist/core/ecs/command_log.d.ts.map +1 -0
  18. package/dist/core/ecs/component.d.cts +176 -0
  19. package/dist/core/ecs/component.d.ts +176 -0
  20. package/dist/core/ecs/component.d.ts.map +1 -0
  21. package/dist/core/ecs/compute_backend.d.cts +37 -0
  22. package/dist/core/ecs/compute_backend.d.ts +37 -0
  23. package/dist/core/ecs/compute_backend.d.ts.map +1 -0
  24. package/dist/core/ecs/debug_names.d.cts +7 -0
  25. package/dist/core/ecs/debug_names.d.ts +7 -0
  26. package/dist/core/ecs/debug_names.d.ts.map +1 -0
  27. package/dist/core/ecs/deferred_commands.d.cts +55 -0
  28. package/dist/core/ecs/deferred_commands.d.ts +55 -0
  29. package/dist/core/ecs/deferred_commands.d.ts.map +1 -0
  30. package/dist/core/ecs/dispatch_trace.d.cts +93 -0
  31. package/dist/core/ecs/dispatch_trace.d.ts +93 -0
  32. package/dist/core/ecs/dispatch_trace.d.ts.map +1 -0
  33. package/dist/core/ecs/ecs.d.cts +562 -0
  34. package/dist/core/ecs/ecs.d.ts +562 -0
  35. package/dist/core/ecs/ecs.d.ts.map +1 -0
  36. package/dist/core/ecs/ecs_memory.d.cts +179 -0
  37. package/dist/core/ecs/ecs_memory.d.ts +179 -0
  38. package/dist/core/ecs/ecs_memory.d.ts.map +1 -0
  39. package/dist/core/ecs/entity.d.cts +34 -0
  40. package/dist/core/ecs/entity.d.ts +34 -0
  41. package/dist/core/ecs/entity.d.ts.map +1 -0
  42. package/dist/core/ecs/entity_allocator.d.cts +59 -0
  43. package/dist/core/ecs/entity_allocator.d.ts +59 -0
  44. package/dist/core/ecs/entity_allocator.d.ts.map +1 -0
  45. package/dist/core/ecs/event.d.cts +79 -0
  46. package/dist/core/ecs/event.d.ts +79 -0
  47. package/dist/core/ecs/event.d.ts.map +1 -0
  48. package/dist/core/ecs/event_registry.d.cts +22 -0
  49. package/dist/core/ecs/event_registry.d.ts +22 -0
  50. package/dist/core/ecs/event_registry.d.ts.map +1 -0
  51. package/dist/core/ecs/facades.d.cts +158 -0
  52. package/dist/core/ecs/facades.d.ts +158 -0
  53. package/dist/core/ecs/facades.d.ts.map +1 -0
  54. package/dist/core/ecs/frame_stepper.d.cts +58 -0
  55. package/dist/core/ecs/frame_stepper.d.ts +58 -0
  56. package/dist/core/ecs/frame_stepper.d.ts.map +1 -0
  57. package/dist/core/ecs/frame_trace.d.cts +133 -0
  58. package/dist/core/ecs/frame_trace.d.ts +133 -0
  59. package/dist/core/ecs/frame_trace.d.ts.map +1 -0
  60. package/dist/core/ecs/host_commands.d.cts +274 -0
  61. package/dist/core/ecs/host_commands.d.ts +274 -0
  62. package/dist/core/ecs/host_commands.d.ts.map +1 -0
  63. package/dist/core/ecs/index.d.cts +46 -0
  64. package/dist/core/ecs/index.d.ts +46 -0
  65. package/dist/core/ecs/index.d.ts.map +1 -0
  66. package/dist/core/ecs/observer.d.cts +173 -0
  67. package/dist/core/ecs/observer.d.ts +173 -0
  68. package/dist/core/ecs/observer.d.ts.map +1 -0
  69. package/dist/core/ecs/query.d.cts +718 -0
  70. package/dist/core/ecs/query.d.ts +718 -0
  71. package/dist/core/ecs/query.d.ts.map +1 -0
  72. package/dist/core/ecs/ref.d.cts +31 -0
  73. package/dist/core/ecs/ref.d.ts +31 -0
  74. package/dist/core/ecs/ref.d.ts.map +1 -0
  75. package/dist/core/ecs/relation.d.cts +243 -0
  76. package/dist/core/ecs/relation.d.ts +243 -0
  77. package/dist/core/ecs/relation.d.ts.map +1 -0
  78. package/dist/core/ecs/relation_service.d.cts +235 -0
  79. package/dist/core/ecs/relation_service.d.ts +235 -0
  80. package/dist/core/ecs/relation_service.d.ts.map +1 -0
  81. package/dist/core/ecs/resource.d.cts +53 -0
  82. package/dist/core/ecs/resource.d.ts +53 -0
  83. package/dist/core/ecs/resource.d.ts.map +1 -0
  84. package/dist/core/ecs/resource_registry.d.cts +20 -0
  85. package/dist/core/ecs/resource_registry.d.ts +20 -0
  86. package/dist/core/ecs/resource_registry.d.ts.map +1 -0
  87. package/dist/core/ecs/resume.d.cts +85 -0
  88. package/dist/core/ecs/resume.d.ts +85 -0
  89. package/dist/core/ecs/resume.d.ts.map +1 -0
  90. package/dist/core/ecs/run_condition.d.cts +81 -0
  91. package/dist/core/ecs/run_condition.d.ts +81 -0
  92. package/dist/core/ecs/run_condition.d.ts.map +1 -0
  93. package/dist/core/ecs/schedule.d.cts +142 -0
  94. package/dist/core/ecs/schedule.d.ts +142 -0
  95. package/dist/core/ecs/schedule.d.ts.map +1 -0
  96. package/dist/core/ecs/snapshot_service.d.cts +94 -0
  97. package/dist/core/ecs/snapshot_service.d.ts +94 -0
  98. package/dist/core/ecs/snapshot_service.d.ts.map +1 -0
  99. package/dist/core/ecs/sparse_store.d.cts +110 -0
  100. package/dist/core/ecs/sparse_store.d.ts +110 -0
  101. package/dist/core/ecs/sparse_store.d.ts.map +1 -0
  102. package/dist/core/ecs/store.d.cts +950 -0
  103. package/dist/core/ecs/store.d.ts +950 -0
  104. package/dist/core/ecs/store.d.ts.map +1 -0
  105. package/dist/core/ecs/store_layout_listener.d.cts +23 -0
  106. package/dist/core/ecs/store_layout_listener.d.ts +23 -0
  107. package/dist/core/ecs/store_layout_listener.d.ts.map +1 -0
  108. package/dist/core/ecs/system.d.cts +250 -0
  109. package/dist/core/ecs/system.d.ts +250 -0
  110. package/dist/core/ecs/system.d.ts.map +1 -0
  111. package/dist/core/ecs/utils/arrays.d.cts +7 -0
  112. package/dist/{utils → core/ecs/utils}/arrays.d.ts +2 -1
  113. package/dist/core/ecs/utils/arrays.d.ts.map +1 -0
  114. package/dist/core/ecs/utils/constants.d.ts +12 -0
  115. package/dist/core/ecs/utils/constants.d.ts.map +1 -0
  116. package/dist/core/ecs/utils/error.d.cts +61 -0
  117. package/dist/core/ecs/utils/error.d.ts +61 -0
  118. package/dist/core/ecs/utils/error.d.ts.map +1 -0
  119. package/dist/core/store/action_ring.d.cts +136 -0
  120. package/dist/core/store/action_ring.d.ts +136 -0
  121. package/dist/core/store/action_ring.d.ts.map +1 -0
  122. package/dist/core/store/allocator.d.cts +238 -0
  123. package/dist/core/store/allocator.d.ts +238 -0
  124. package/dist/core/store/allocator.d.ts.map +1 -0
  125. package/dist/core/store/buffer_backed_column.d.cts +69 -0
  126. package/dist/core/store/buffer_backed_column.d.ts +69 -0
  127. package/dist/core/store/buffer_backed_column.d.ts.map +1 -0
  128. package/dist/core/store/column_store.d.cts +273 -0
  129. package/dist/core/store/column_store.d.ts +273 -0
  130. package/dist/core/store/column_store.d.ts.map +1 -0
  131. package/dist/core/store/command_dispatch.d.cts +52 -0
  132. package/dist/core/store/command_dispatch.d.ts +52 -0
  133. package/dist/core/store/command_dispatch.d.ts.map +1 -0
  134. package/dist/core/store/command_ring.d.cts +107 -0
  135. package/dist/core/store/command_ring.d.ts +107 -0
  136. package/dist/core/store/command_ring.d.ts.map +1 -0
  137. package/dist/core/store/descriptor.d.cts +80 -0
  138. package/dist/core/store/descriptor.d.ts +80 -0
  139. package/dist/core/store/descriptor.d.ts.map +1 -0
  140. package/dist/core/store/entity_index.d.cts +108 -0
  141. package/dist/core/store/entity_index.d.ts +108 -0
  142. package/dist/core/store/entity_index.d.ts.map +1 -0
  143. package/dist/core/store/event_ring.d.cts +95 -0
  144. package/dist/core/store/event_ring.d.ts +95 -0
  145. package/dist/core/store/event_ring.d.ts.map +1 -0
  146. package/dist/core/store/extend.d.cts +39 -0
  147. package/dist/core/store/extend.d.ts +39 -0
  148. package/dist/core/store/extend.d.ts.map +1 -0
  149. package/dist/core/store/grow.d.cts +34 -0
  150. package/dist/core/store/grow.d.ts +34 -0
  151. package/dist/core/store/grow.d.ts.map +1 -0
  152. package/dist/core/store/header.d.cts +64 -0
  153. package/dist/core/store/header.d.ts +64 -0
  154. package/dist/core/store/header.d.ts.map +1 -0
  155. package/dist/core/store/index.d.cts +17 -0
  156. package/dist/core/store/index.d.ts +17 -0
  157. package/dist/core/store/index.d.ts.map +1 -0
  158. package/dist/core/store/layout_ops.d.cts +152 -0
  159. package/dist/core/store/layout_ops.d.ts +152 -0
  160. package/dist/core/store/layout_ops.d.ts.map +1 -0
  161. package/dist/core/store/region_table.d.cts +74 -0
  162. package/dist/core/store/region_table.d.ts +74 -0
  163. package/dist/core/store/region_table.d.ts.map +1 -0
  164. package/dist/core/store/snapshot.d.cts +43 -0
  165. package/dist/core/store/snapshot.d.ts +43 -0
  166. package/dist/core/store/snapshot.d.ts.map +1 -0
  167. package/dist/core/store/state_hash.d.cts +38 -0
  168. package/dist/core/store/state_hash.d.ts +38 -0
  169. package/dist/core/store/state_hash.d.ts.map +1 -0
  170. package/dist/core/store/store_regions.d.cts +38 -0
  171. package/dist/core/store/store_regions.d.ts +38 -0
  172. package/dist/core/store/store_regions.d.ts.map +1 -0
  173. package/dist/core/store/vendored_abi/abi.d.cts +43 -0
  174. package/dist/core/store/vendored_abi/abi.d.ts +43 -0
  175. package/dist/core/store/vendored_abi/abi.d.ts.map +1 -0
  176. package/dist/dev_flag.d.cts +17 -0
  177. package/dist/dev_flag.d.ts +17 -0
  178. package/dist/dev_flag.d.ts.map +1 -0
  179. package/dist/extensions/editor/editor.d.cts +170 -0
  180. package/dist/extensions/editor/editor.d.ts +170 -0
  181. package/dist/extensions/editor/editor.d.ts.map +1 -0
  182. package/dist/extensions/editor/field_handle.d.cts +40 -0
  183. package/dist/extensions/editor/field_handle.d.ts +40 -0
  184. package/dist/extensions/editor/field_handle.d.ts.map +1 -0
  185. package/dist/extensions/editor/index.cjs +1 -0
  186. package/dist/extensions/editor/index.d.cts +21 -0
  187. package/dist/extensions/editor/index.d.ts +21 -0
  188. package/dist/extensions/editor/index.d.ts.map +1 -0
  189. package/dist/extensions/editor/index.js +237 -0
  190. package/dist/extensions/reactive/ecs_sync.d.cts +214 -0
  191. package/dist/extensions/reactive/ecs_sync.d.ts +214 -0
  192. package/dist/extensions/reactive/ecs_sync.d.ts.map +1 -0
  193. package/dist/extensions/reactive/index.cjs +1 -0
  194. package/dist/extensions/reactive/index.d.cts +23 -0
  195. package/dist/extensions/reactive/index.d.ts +23 -0
  196. package/dist/extensions/reactive/index.d.ts.map +1 -0
  197. package/dist/extensions/reactive/index.js +215 -0
  198. package/dist/extensions/solid/index.cjs +1 -0
  199. package/dist/extensions/solid/index.d.cts +6 -0
  200. package/dist/extensions/solid/index.d.ts +6 -0
  201. package/dist/extensions/solid/index.d.ts.map +1 -0
  202. package/dist/extensions/solid/index.js +36 -0
  203. package/dist/extensions/solid/kernel_solid.d.cts +45 -0
  204. package/dist/extensions/solid/kernel_solid.d.ts +45 -0
  205. package/dist/extensions/solid/kernel_solid.d.ts.map +1 -0
  206. package/dist/host_commands-BF8QMi3c.cjs +1 -0
  207. package/dist/host_commands-i4cAeyL5.js +2318 -0
  208. package/dist/index.cjs +1 -1
  209. package/dist/index.d.cts +66 -0
  210. package/dist/index.d.ts +65 -12
  211. package/dist/index.d.ts.map +1 -1
  212. package/dist/index.js +7080 -1289
  213. package/dist/internal.cjs +2 -0
  214. package/dist/internal.d.cts +20 -0
  215. package/dist/internal.d.ts +20 -0
  216. package/dist/internal.d.ts.map +1 -0
  217. package/dist/internal.js +141 -0
  218. package/dist/interop-CT-REx0W.cjs +1 -0
  219. package/dist/interop-CcY6ASQc.js +18 -0
  220. package/dist/kernel-DgyrLFjW.js +227 -0
  221. package/dist/kernel-yWV3XnAb.cjs +1 -0
  222. package/dist/primitives.cjs +1 -0
  223. package/dist/primitives.d.cts +18 -0
  224. package/dist/primitives.d.ts +18 -0
  225. package/dist/primitives.d.ts.map +1 -0
  226. package/dist/primitives.js +45 -0
  227. package/dist/reactive/array.d.cts +24 -0
  228. package/dist/reactive/array.d.ts +24 -0
  229. package/dist/reactive/array.d.ts.map +1 -0
  230. package/dist/reactive/index.cjs +1 -0
  231. package/dist/reactive/index.d.cts +11 -0
  232. package/dist/reactive/index.d.ts +11 -0
  233. package/dist/reactive/index.d.ts.map +1 -0
  234. package/dist/reactive/index.js +18 -0
  235. package/dist/reactive/interop.d.cts +19 -0
  236. package/dist/reactive/interop.d.ts +19 -0
  237. package/dist/reactive/interop.d.ts.map +1 -0
  238. package/dist/reactive/kernel.d.cts +73 -0
  239. package/dist/reactive/kernel.d.ts +73 -0
  240. package/dist/reactive/kernel.d.ts.map +1 -0
  241. package/dist/reactive/map.d.cts +16 -0
  242. package/dist/reactive/map.d.ts +16 -0
  243. package/dist/reactive/map.d.ts.map +1 -0
  244. package/dist/reactive/shallow.d.cts +11 -0
  245. package/dist/reactive/shallow.d.ts +11 -0
  246. package/dist/reactive/shallow.d.ts.map +1 -0
  247. package/dist/reactive/struct.d.cts +10 -0
  248. package/dist/reactive/struct.d.ts +10 -0
  249. package/dist/reactive/struct.d.ts.map +1 -0
  250. package/dist/shallow-DQ8aG51_.cjs +1 -0
  251. package/dist/shallow-DfnAm9vG.js +150 -0
  252. package/dist/shared-BU1Cd40h.js +103 -0
  253. package/dist/shared-BymrGTyR.cjs +1 -0
  254. package/dist/shared.cjs +1 -0
  255. package/dist/shared.d.cts +22 -0
  256. package/dist/shared.d.ts +22 -0
  257. package/dist/shared.d.ts.map +1 -0
  258. package/dist/shared.js +7 -0
  259. package/dist/topological_sort-BDvEyb9W.cjs +1 -0
  260. package/dist/topological_sort-DK6EjpWa.js +135 -0
  261. package/dist/type_primitives/assertions.d.cts +32 -0
  262. package/dist/type_primitives/assertions.d.ts +25 -9
  263. package/dist/type_primitives/assertions.d.ts.map +1 -1
  264. package/dist/type_primitives/binary_heap/binary_heap.d.cts +37 -0
  265. package/dist/type_primitives/binary_heap/binary_heap.d.ts +6 -2
  266. package/dist/type_primitives/binary_heap/binary_heap.d.ts.map +1 -1
  267. package/dist/type_primitives/bitset/bitset.d.cts +54 -0
  268. package/dist/type_primitives/bitset/bitset.d.ts +16 -4
  269. package/dist/type_primitives/bitset/bitset.d.ts.map +1 -1
  270. package/dist/type_primitives/brand.d.cts +23 -0
  271. package/dist/type_primitives/brand.d.ts +6 -1
  272. package/dist/type_primitives/brand.d.ts.map +1 -1
  273. package/dist/type_primitives/error.d.cts +16 -0
  274. package/dist/type_primitives/error.d.ts +8 -3
  275. package/dist/type_primitives/error.d.ts.map +1 -1
  276. package/dist/type_primitives/index.d.cts +13 -0
  277. package/dist/type_primitives/index.d.ts +12 -9
  278. package/dist/type_primitives/index.d.ts.map +1 -1
  279. package/dist/type_primitives/sparse_map/sparse_map.d.cts +30 -0
  280. package/dist/type_primitives/sparse_map/sparse_map.d.ts +7 -3
  281. package/dist/type_primitives/sparse_map/sparse_map.d.ts.map +1 -1
  282. package/dist/type_primitives/sparse_set/sparse_set.d.cts +28 -0
  283. package/dist/type_primitives/sparse_set/sparse_set.d.ts +4 -0
  284. package/dist/type_primitives/sparse_set/sparse_set.d.ts.map +1 -1
  285. package/dist/type_primitives/topological_sort/topological_sort.d.cts +29 -0
  286. package/dist/type_primitives/topological_sort/topological_sort.d.ts +7 -3
  287. package/dist/type_primitives/topological_sort/topological_sort.d.ts.map +1 -1
  288. package/dist/type_primitives/typed_arrays/typed_arrays.d.cts +127 -0
  289. package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +53 -16
  290. package/dist/type_primitives/typed_arrays/typed_arrays.d.ts.map +1 -1
  291. package/dist/typed_arrays-BcXcnzB4.js +258 -0
  292. package/dist/typed_arrays-D3NmwtCG.cjs +1 -0
  293. package/dist/utils/error.d.cts +6 -0
  294. package/dist/utils/error.d.ts +2 -20
  295. package/dist/utils/error.d.ts.map +1 -1
  296. package/dist/version.d.cts +10 -0
  297. package/dist/version.d.ts +10 -0
  298. package/dist/version.d.ts.map +1 -0
  299. package/package.json +115 -5
  300. package/dist/archetype.d.ts +0 -108
  301. package/dist/archetype.d.ts.map +0 -1
  302. package/dist/component.d.ts +0 -45
  303. package/dist/component.d.ts.map +0 -1
  304. package/dist/ecs.d.ts +0 -104
  305. package/dist/ecs.d.ts.map +0 -1
  306. package/dist/entity.d.ts +0 -11
  307. package/dist/entity.d.ts.map +0 -1
  308. package/dist/event.d.ts +0 -30
  309. package/dist/event.d.ts.map +0 -1
  310. package/dist/query.d.ts +0 -94
  311. package/dist/query.d.ts.map +0 -1
  312. package/dist/ref.d.ts +0 -23
  313. package/dist/ref.d.ts.map +0 -1
  314. package/dist/resource.d.ts +0 -23
  315. package/dist/resource.d.ts.map +0 -1
  316. package/dist/schedule.d.ts +0 -45
  317. package/dist/schedule.d.ts.map +0 -1
  318. package/dist/store.d.ts +0 -118
  319. package/dist/store.d.ts.map +0 -1
  320. package/dist/system.d.ts +0 -16
  321. package/dist/system.d.ts.map +0 -1
  322. package/dist/utils/arrays.d.ts.map +0 -1
  323. package/dist/utils/constants.d.ts.map +0 -1
  324. /package/dist/{utils/constants.d.ts → core/ecs/utils/constants.d.cts} +0 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,553 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.5.0] — 2026-07-06
9
+
10
+ ### Changed (breaking) — lifecycle & naming unification
11
+
12
+ One vocabulary across host, commands, and access declarations; the receiver now implies the
13
+ timing (host = immediate, `ctx.commands` = deferred). Hard renames, no deprecation aliases —
14
+ see [docs/MIGRATION-0.4-to-0.5.md](docs/MIGRATION-0.4-to-0.5.md) for the complete
15
+ rename/removal map:
16
+
17
+ | 0.4 | 0.5 |
18
+ | --- | --- |
19
+ | `ecs.createEntity()` / `ecs.createEntity(template, overrides?)` | `ecs.spawn()` / `ecs.spawn(template, overrides?)` |
20
+ | `ecs.createEntities(template, count)` | `ecs.spawnMany(template, count, overrides?)` |
21
+ | `ecs.destroyEntity(e)` *(deferred)* | `ecs.despawn(e)` — **now immediate** |
22
+ | `ctx.createEntity()` | `ctx.commands.spawn()` |
23
+ | `ctx.destroyEntity(e)` | `ctx.commands.despawn(e)` |
24
+ | `ctx.addComponent(e, def, values?)` | `ctx.commands.add(e, def, values)` or `ctx.commands.add(e, def({ … }))` |
25
+ | `ctx.removeComponent(e, def)` | `ctx.commands.remove(e, def)` |
26
+ | `ctx.disable(e)` / `ctx.enable(e)` | `ctx.commands.disable(e)` / `ctx.commands.enable(e)` |
27
+ | `sourcesOf(def, tgt)` | `sourcesOf(tgt, def)` — matches `targetOf` / `targetsOf` |
28
+ | `query.count()` | `query.entityCount` (getter, beside `archetypeCount`) |
29
+ | `WorldRestoreError` / `WORLD_SNAPSHOT_VERSION` | `ECSRestoreError` / `ECS_SNAPSHOT_VERSION` |
30
+ | `DestroyEntityArg` (type) | `DespawnArg` |
31
+
32
+ - **Host `despawn` is immediate** — `ecs.despawn(e); ecs.isAlive(e)` is `false` on the next
33
+ line, closing the audit's M1 finding (host `addComponent` immediate but destroy buffered).
34
+ **Observer note:** like every immediate op, host `despawn` fires no *structural* observers —
35
+ `onRemove` no longer sees host-despawned entities (it did at 0.4, when host destroy was
36
+ deferred). Observer-driven consumers, including the `reactive-sync` map bridges, only see
37
+ despawns that go through `ctx.commands.despawn` or the host-command seam. (`onSet` is
38
+ receiver-blind — derived change detection sees host `setField` writes as always.)
39
+ - **Every immediate host structural mutator throws in dev when called from inside a system
40
+ body** — `despawn`, `addComponent`/`addComponents`, `removeComponent`/`removeComponents`,
41
+ `batchAddComponent`/`batchRemoveComponent`, `disable`/`enable` — each error pointing at its
42
+ `ctx.commands` equivalent. Mid-system these ops can move rows a running query is walking and
43
+ are invisible to observers; previously only `despawn` was guarded wholesale (the others were
44
+ caught only when they touched the archetype being iterated). Cross-world host mutation from
45
+ another world's system (#785) is unaffected — the guard is scoped to the mutated world.
46
+ - **The bare deferred duplicates on `ctx` are removed** — `ctx.addComponent`,
47
+ `ctx.removeComponent`, `ctx.disable`, `ctx.enable` join the already-removed
48
+ `ctx.createEntity` / `ctx.destroyEntity`. `ctx.commands` is now the *only* deferred surface,
49
+ completing the receiver-implies-timing rule with zero exceptions. `ctx.commands.add` gains
50
+ the explicit complete-values shape (`ctx.commands.add(e, Pos, { x: 0, y: 0 })`) the removed
51
+ `ctx.addComponent` carried, so compile-checked complete attaches survive the move.
52
+ `ctx.isDisabled` stays (immediate read), as do the immediate sparse/relation ops.
53
+ - **`sourcesOf` canonicalized to `(entity, def)`** on `ecs.relations` and `SystemContext` —
54
+ it was the one arg-order outlier on the relation surface (M3).
55
+ - **The package root is now a curated, explicit export list** — `export *` no longer flattens the
56
+ whole core barrel, so future barrel additions cannot silently widen the public API. A checked-in
57
+ public-API snapshot test makes any surface change an explicit diff in review.
58
+ - **Internal/tooling symbols moved to `@oasys/oecs/internal`** (explicitly **unstable — no semver
59
+ guarantees**): the packed-EntityID codec (`createEntityId`, `getEntityGeneration`, `MAX_INDEX`,
60
+ `MAX_GENERATION`, `MAX_LIVE_GENERATION`, `RETIRED_GENERATION`, `MAX_ENTITY_ID`), the SAB
61
+ command-ring transport (`HostCommandDispatcher`, `ring*Codec`, `HOST_COMMAND_PAYLOAD_BYTES`),
62
+ memory-sizing internals (`resolveECSMemory`, `DEFAULT_ECS_CAP_BYTES`, `BUDGET_*`), and the
63
+ dev-mode singletons (`accessCheck`, `dispatchTrace`). `getEntityIndex` stays at the root.
64
+
65
+ ### Added
66
+
67
+ - **`addComponent` bundle overload** — `ecs.addComponent(e, Pos({ x: 1 }))` accepts a bundle
68
+ with the usual zero-fill semantics (M2); the explicit `(e, def, values)` form stays
69
+ complete-values, so a typo'd or missing field is still a compile error.
70
+ - **`spawnMany` typed template + shared overrides** — bulk spawn takes the same typed
71
+ `Template<Defs>` as `spawn` plus one optional `TemplateOverrides<Defs>` object applied to
72
+ every row (contiguous batches use one `fill` per overridden column).
73
+ - **JSDoc `@example` on the core surface** — `registerComponent`, `spawn`, `addComponent`,
74
+ `query`, `registerSystem`, `startup`, `update`, `ctx.emit` / `ctx.read`,
75
+ `events.register`, `resources.register` now carry hover-visible examples (M23).
76
+ - **Component debug names** — `registerComponent(schema, { name: "Pos" })` (and the sparse
77
+ sibling) records a diagnostic label, so access-violation and liveness errors read
78
+ `'Pos' (component 5)` instead of leaving you to count registration order
79
+ (`ComponentRegisterOptions`).
80
+ - **Total probes + `tryGetField`** — `hasComponent` / `hasSparse` / `relations.has` now return
81
+ `false` for a dead entity instead of dev-throwing (a "has" probe is exactly the call made to
82
+ avoid dead entities); `ecs.tryGetField(e, def, field)` returns `undefined` for a dead entity or
83
+ missing component, and `ctx.tryGetField` mirrors it inside systems (declared-read checked).
84
+ - **Plural host mutators chain** — `addComponents`, `removeComponents`, `batchAddComponent`,
85
+ `batchRemoveComponent` return `this` (previously `void`), matching their singular siblings.
86
+ - **`Query.firstEntity()` / `Query.singleEntity()`** — singleton reads (player, camera) without a
87
+ hand-rolled `forEach` + capture; `singleEntity` dev-throws `QUERY_NOT_SINGLETON` on 0 or >1.
88
+ - **Host-side `ecs.refRead(def, e)`** — whole-component read-only view, parity with
89
+ `ctx.refRead`.
90
+ - **Run-condition combinators** — `not()` / `allOf()` / `anyOf()`, merging the operands' declared
91
+ read surfaces.
92
+ - **Editor change notification** — `editor.onChange(cb)` (fires on commit/undo/redo/clear) plus
93
+ `canUndo` / `canRedo` getters; no more per-frame `depths()` polling.
94
+ - **`using` support** — `ObserverHandle` implements `Symbol.dispose`.
95
+ - **Write-seam lifecycle** — `uninstallHostCommandSeam(world, queue)`,
96
+ `HostCommandQueue.clear()`, `HostCommandDispatcher.off(opCode)`,
97
+ `HostCommandRecorder.snapshotLog()` (stable deep copy).
98
+ - **`VERSION`** export and a `"./package.json"` export; `engines: { node: ">=20" }` and a README
99
+ runtime note (resizable `ArrayBuffer`).
100
+ - Root re-exports so failure modes are nameable without extra entry points:
101
+ `StoreRestoreError`, `SabUnavailableError`, `TypedArrayTag`; `/reactive` now exports `Eq` and
102
+ `shallow` (moved from `/reactive-sync`, which re-exports for compat); `signal()` gains the
103
+ zero-arg Solid-parity overload; `SingletonSyncOptions.eq`.
104
+ - **`FrameStepper`** — optional host-side driver over the authoritative `ecs.update(dt)`:
105
+ `play()`/`pause()`/`toggle()` on `requestAnimationFrame` (injectable `requestFrame`/`cancelFrame`
106
+ for tests and non-browser hosts), explicit `step()`/`stepFrames()` for debuggers, editors, and
107
+ rollback playback, and a `maxDt` clamp (default 0.25 s) so a resumed background tab doesn't feed
108
+ the whole suspension into the accumulator as one delta. Validation throws `INVALID_FRAME_STEP`.
109
+ - **`ObserverConfig.name`** — diagnostic label surfaced as the frame trace's
110
+ `observer_fired.observer` field (the role a system's `name` plays); observe-only, never affects
111
+ `stateHash` or dispatch order. Unnamed observers fall back to `observer(<component debug name>)`
112
+ when the component was registered with a name, else `observer(<cid>)`.
113
+ - **`ECSOptions.onWarn`** — injectable sink for dev-mode engine diagnostics (currently the
114
+ schedule's dropped-ordering-edge warning and the `ECSOptions` unknown-key warning),
115
+ defaulting to `console.warn`. Replaces the internal `src/log` singleton, which is deleted.
116
+ - **Editor `fieldHandle` `read` thunk is optional** — defaults to `Editor.committedField`.
117
+
118
+ ### Fixed
119
+
120
+ - **Host iteration guard (`STRUCTURAL_DURING_ITERATION`)** — with host `despawn` now immediate,
121
+ a host-side `forEach`/`eachChunk` callback that despawned (or transitioned/toggled) an entity of
122
+ the archetype it was visiting silently skipped entities via the row swap-remove. Row-removing
123
+ ops on an archetype a live dense walk is standing in now throw in dev, *before* any mutation
124
+ lands (the transition path checks ahead of the destination append, so no dual-residency
125
+ half-state). Collect ids during the walk and mutate after it. Mutating archetypes the walk is
126
+ *not* currently visiting stays legal — the #431 fresh-snapshot machinery still covers those.
127
+ - **Cross-world despawn false positive** — `worldB.despawn(e)` from inside world A's system no
128
+ longer trips the in-system despawn guard (the accessCheck span is process-global; the guard now
129
+ also requires *this* world to be mid-schedule). Driving a second world from a system (#785)
130
+ mutates it host-style, which is safe — B is not iterating. Unnamed systems in the guard message
131
+ now render as `system_<id>` instead of `'?'`.
132
+ - **Frame trace records every deferred command (ADR-0030)** — the removed bare `ctx.*` deferred
133
+ forms bypassed the `commandQueued` trace hook, so host-command-seam adds/removes/toggles (and
134
+ any system using the bare forms) were invisible to an attached `FrameTraceSink` while their
135
+ spawns/despawns were visible. With `ctx.commands` as the only deferred surface every queued
136
+ command is traced, and `ctx.commands.spawn` now also traces each bundle attach it queues
137
+ (previously only the spawn itself).
138
+ - **Stale deferred-attach docs** — `host_commands.ts` / the host-write-seam page claimed the
139
+ deferred add path does not zero-fill omitted fields (NaN readback); every attach path
140
+ zero-fills since #716 (`writeFields`'s `?? 0`). The complete-values requirement on
141
+ `SpawnEntry` is documented as what it is — explicit intent in a reified, replayable record —
142
+ and the observer docs now scope "immediate ops fire no observers" to *structural* observers
143
+ (`onSet` is derived change detection and sees host `setField` writes).
144
+ - **`ecs.refRead` / `ctx.ref` / `ctx.refRead` on a missing component or tag def** — threw a raw
145
+ `TypeError` from the ref internals; now a dev `ECSError` (`COMPONENT_NOT_REGISTERED`) naming the
146
+ op and component, matching `getField`. Host `refRead`'s docstring now states the single-
147
+ expression lifetime rule (any immediate structural mutation can row-swap under a held ref).
148
+ - **Editor: aborted transactions no longer poison undo** — `transaction(tx => …)` staged its
149
+ `setField` shadow writes into the editor's shared map at build time, so a build callback that
150
+ threw left phantom pending values behind and seeded the *next* edit's undo inverse with a value
151
+ the world never held. Staging is now transaction-local and merges only on commit.
152
+ - **Editor: `pendingField` self-resolves for dead slots** — a shadow entry for a despawned entity
153
+ (or removed component) echoed its stale value forever and leaked; the reconcile-on-read now
154
+ prunes it and returns `undefined`.
155
+ - **JSR/Deno consumers no longer break on the `__DEV__` global** — shipped source now reads a
156
+ guarded `DEV` flag (`src/dev_flag.ts`) that constant-folds in the npm bundle and defaults to
157
+ dev-on for raw-source consumers (`globalThis.__DEV__ = false` opts out).
158
+ - **Error experience** — every `ENTITY_NOT_ALIVE` names the operation and decodes the packed id
159
+ (index + generation, with context); system access violations use the new `ACCESS_UNDECLARED`
160
+ category instead of overloading `*_NOT_REGISTERED`; resource/event "not registered" messages
161
+ name the key and hint the registration call; messages no longer reference pre-0.4 snake_case
162
+ option names or private tracker issue numbers.
163
+ - **Packaging** — per-entry `.d.cts` and explicit-extension declaration specifiers
164
+ (`attw --pack` fully green: node10/node16/bundler across all eight entry points, was
165
+ masquerading + resolution errors); `typesVersions` for `moduleResolution: node10` subpaths; npm
166
+ tarball ships `CHANGELOG.md`; `@internal` editor internals no longer leak into published types.
167
+ - **Type-level closures** — `EventShape<S>` homomorphic bound (interface-declared event schemas
168
+ now accepted); `RelationOptions` is a union so `{ exclusive: true, multi: true }` is a compile
169
+ error; `ResourceKey`'s phantom is a unique symbol (no `.__phantom` in autocomplete);
170
+ `pairsOf` / `sourcesOfAny` return readonly tuples; `SystemConfig.fn` optional when
171
+ `backendHandle` is present.
172
+ - Dev-mode diagnostics: ownerless `computed()` / `onCleanup()` warn (kernel); ECSOptions warns on
173
+ unknown keys; `runIfResourceEq` warns on object-valued `expected` (reference-identity `===`);
174
+ `runEveryNTicks` validation throws `ECSError` (`INVALID_RUN_CONDITION`).
175
+ - **Docs standardized on the `ecs` receiver** — README, GETTING_STARTED, BEST_PRACTICES, the
176
+ api reference, and every in-source JSDoc example now spell `const ecs = new ECS()`
177
+ (M22; with the `World*` names renamed to `ECS*`, "world" survives only as prose). The
178
+ host-write-seam docs now explain *why* `queue.spawn` takes complete-value `spawnEntry`s
179
+ rather than zero-filling bundles (M4: commands are a reified, replayable record — complete
180
+ values are explicit intent legible to replay, not a correctness need; the deferred add path
181
+ zero-fills omitted fields since #716).
182
+ - **JSR publish no longer ships `__tests__` helper files** (`casing_codemod.ts`,
183
+ `test_helpers.ts` — including a `node:fs` import subject to JSR type-checking).
184
+
185
+ ### Changed (breaking) — type-level & facade surface
186
+
187
+ - **Compile-time typestate across the system, query, relation, and key seams.** The config-form
188
+ `registerSystem` now infers your access declarations as literal types and hands `fn`/`onAdded` a
189
+ `SystemContext<DeclaredAccess<…>>` narrowed to exactly the declared surface — undeclared access
190
+ is a compile error naming the missing declaration, with the dev-mode runtime check remaining as
191
+ the backstop for dynamic values. Query columns are typed by the query's terms
192
+ (`ChunkColumns<Defs>` / `ArchetypeView<Defs>`; `.and(...)` extends the term set), relation
193
+ handles carry their cardinality (`RelationDef<"exclusive">` vs `RelationDef<"multi">` — the
194
+ exclusive-only traversal surfaces reject a multi handle at compile time), and
195
+ `ResourceKey`/`EventKey`/`EventDef` are invariant so a key can no longer widen through
196
+ `unknown`. A checked-in type battery (`typing_assertions.ts`) pins every rule.
197
+ - **Grouped facades: `ecs.relations`, `ecs.events`, `ecs.resources`, `ecs.snapshots`.** Cohesive
198
+ secondary surfaces move off the flat namespace onto narrow typed facades —
199
+ `ecs.relations.add(child, ChildOf, parent)`, `ecs.events.emit(Damage, {...})`,
200
+ `ecs.resources.get(Time)`, `ecs.snapshots.capture()`. The facades mirror the typestate
201
+ surface exactly (cardinality-stamped `relations.register`, exclusive-only traversal). Hot-path
202
+ API (component ops, queries, spawn/destroy, sparse ops) stays flat by design. Facade classes
203
+ are exported type-only; the runtime export list is unchanged.
204
+ - **Value arguments are schema-checked at compile time across every attach seam.** Tag defs
205
+ reject value objects (`Frozen({ x: 1 })` no longer compiles — tags carry no data);
206
+ `addComponents` takes schema-checked entries (`TemplateEntries<Defs>`), so a misspelled or
207
+ cross-component field key is a compile error instead of a silent zero-fill; host-seam
208
+ `queue.spawn` entries (`SpawnEntries<Defs>`) are checked complete against each def's own
209
+ schema (`ValuesArg` / `CompleteFieldValues` exported); and `events.register` requires the
210
+ field list to cover the event schema (`EventFieldsCover`) — a partial list silently dropped
211
+ columns and read back `undefined` at runtime. Smaller closures in the same vein: `observe`
212
+ accepts any `ComponentHandle`, `NoInfer` pins key-typed value params (`events.emit`,
213
+ resources), and reactive-sync's `JoinReader.field` is constrained to the join's component
214
+ set.
215
+
216
+ ### Removed (breaking)
217
+
218
+ - The 29 flat forms the new facades replace (`registerRelation`/`addRelation`/`targetOf`/…,
219
+ `registerEvent`/`registerSignal`/`emit`/`read`, `registerResource`/`resource`/`setResource`/
220
+ `removeResource`/`hasResource`, `snapshot`/`restoreInto`/`snapshotSparse`/`restoreSparse`/
221
+ `stateHash`/`deterministic`, `relationCount`/`compactRelations`). Each maps 1:1 onto its
222
+ grouped replacement — `ecs.relations.add(...)`, `ecs.events.emit(...)`, `ecs.resources.get(...)`,
223
+ `ecs.snapshots.capture()` (was `snapshot()`) / `ecs.snapshots.restore(...)` (was
224
+ `restoreInto(...)`), `ecs.relations.count` (was `relationCount`), `ecs.relations.compact()`
225
+ (was `compactRelations()`). System-side `ctx.*` and all `Store`-level methods are unchanged.
226
+
227
+ ### Changed (internal)
228
+
229
+ - **`Store` decomposed into seven focused collaborators** (RelationService, EventRegistry +
230
+ ResourceRegistry, EntityAllocator, DeferredCommandBuffer, SnapshotService, ArchetypeGraph) with
231
+ `Store` as the coordinator; the hot-path extractions were A/B-benchmarked against
232
+ identical-code controls with no regression. The `ECS` facade's pure delegations now live in a
233
+ marker-delimited pass-through band whose logic-free invariant is enforced by an AST guard test.
234
+ - Typed per-consumer host seams (`ObserverHost`, `QueryHost`) replace underscore-convention
235
+ reach-through on `Store`; `QueryCache` now owns all 12 query-resolution cache maps.
236
+ - Store layer consolidation: one strategy-parameterized factory behind
237
+ `growableSabAllocator` / `heapArraybufferAllocator`; a typed `isColumnStoreInternal` guard
238
+ replaces six structural casts; grow/extend's ~200 duplicated lines moved to a shared
239
+ `layout_ops.ts` (bit-identical layouts pinned by a golden differential test across the
240
+ full allocator matrix).
241
+ - `core/reactive` moved to `src/reactive` (the published `./reactive` subpath is unchanged);
242
+ `__generated__/abi.ts` renamed to `vendored_abi/abi.ts` (it is a hand-maintained snapshot,
243
+ not generated output).
244
+ - Deleted orphaned duplicate `src/utils/{arrays,constants}.ts`; renamed the custom `TypeError`
245
+ (shadowed the ECMAScript global) to `AssertionError`; retired the 246-line casing codemod +
246
+ guard test (the 0.4 rename has converged).
247
+
248
+ ## [0.4.0] — 2026-06-24
249
+
250
+ Major release. oecs is **re-derived from the upstream oasys engine ECS** — its modern descendant — and
251
+ gains whole subsystems while staying pure-TS and zero-dependency by default. The public API moves to
252
+ the engine's surface, so **every consumer touches breaking changes** — chiefly a global
253
+ `snake_case` → `camelCase` rename. See [docs/MIGRATION-0.3-to-0.4.md](docs/MIGRATION-0.3-to-0.4.md).
254
+
255
+ ### Changed (breaking)
256
+
257
+ - **The entire public API is now `camelCase`.** Every method, property, parameter, and field renamed
258
+ from `snake_case` (`create_entity` → `createEntity`, `add_component` → `addComponent`, `get_field` →
259
+ `getField`, `is_alive` → `isAlive`, `register_system` → `registerSystem`, …). Types and handles stay
260
+ PascalCase and SCREAMING_SNAKE constants are unchanged. A `vitest` casing guard prevents regressions.
261
+ - **Renamed query/context verbs.** `QueryBuilder.every` → `with`; `query.not` → `without`;
262
+ `query.any_of` → `anyOf`; `query.for_each` → `forEach`; `archetype.get_column` → `getColumnRead`;
263
+ `event_key` / `signal_key` / `resource_key` → `eventKey` / `signalKey` / `resourceKey`;
264
+ `is_ecs_error` → `isEcsError`; `destroy_entity_deferred` → `destroyEntity` (still deferred).
265
+ - **Ref mutability flipped on the unsuffixed name.** `ctx.ref` is now the **mutable** default (was
266
+ read-only in 0.3); the read-only variant is `ctx.refRead` (was `ctx.ref_mut` for the mutable one).
267
+ Same rule for columns: mutable `getColumn` (internal) vs read-only `getColumnRead`.
268
+ - **`WorldOptions` → `ECSOptions`; `fixed_timestep` → `fixedTimestep`.**
269
+ - **`initial_capacity` removed** — replaced by the `memory` surface (`memory: { budget }` /
270
+ `{ maxBytes }` / `{ columnCapacity }` pin / `{ shared }` / `{ wasm }` / `{ allocator }`). Passing the
271
+ old option keys throws at construction, pointing at `memory`.
272
+ - **Component-touching systems must declare `reads` / `writes`.** A new `__DEV__` access checker
273
+ (tree-shaken from production) validates every column / ref / field / resource access against a
274
+ system's declared surface. The bare `(ctx, dt)` and `(q, ctx, dt)` + query-builder `registerSystem`
275
+ overloads declare no access, so a system that touches ECS data through them throws in dev — move it
276
+ to the config form (`registerSystem({ reads, writes, fn })`). `exclusive: true` systems bypass the
277
+ checker. A registration-time lint (`QUERY_ACCESS_UNDECLARED`) additionally checks any declared
278
+ `queries ⊆ reads ∪ writes`.
279
+ - **`removeComponents` takes an array, not varargs** (`removeComponents(e, [A, B])`);
280
+ `batchAddComponent` / `batchRemoveComponent` key on `ArchetypeID` instead of an `Archetype` object.
281
+ - **Event schema shape.** `eventKey`'s type parameter is now a field → value-type record
282
+ (`eventKey<{ target: EntityID; amount: number }>("Damage")`) rather than a tuple of field names, so
283
+ branded fields round-trip through `emit` / `read`. `registerEvent(key, [...fieldNames])` unchanged
284
+ otherwise.
285
+
286
+ ### Added
287
+
288
+ - **Two storage profiles over one backing-neutral `ColumnStore`.** Default is pure-TS **heap** (a plain
289
+ resizable `ArrayBuffer`) — no `SharedArrayBuffer`, no cross-origin isolation. Opt-in
290
+ `@oasys/oecs/shared` (`memory: { shared: {} }`) uses a `SharedArrayBuffer` for worker offload / a WASM
291
+ compute backend. Same code path; identical state hash.
292
+ - **Determinism** (opt-in `deterministic: true`): a state hash over column bytes + `snapshot()` /
293
+ `restoreInto()` (and `snapshotSparse` / `restoreSparse`), **backing-agnostic** — a heap world and a
294
+ shared world with identical history agree. `WorldRestoreError` / `SparseRestoreError` fail closed
295
+ before overwriting live backing.
296
+ - **Observers** — `world.observe(def, { onAdd, onRemove, onSet, onDisable, onEnable })`, structural +
297
+ per-entity.
298
+ - **Relations** — `(relation, target)` pairs, `ChildOf` / `IsA` presets (`registerChildOf` /
299
+ `registerIsA`), `(R,*)` / `(*,T)` wildcard queries (`withRelation`, `forEachRelatedTo`,
300
+ `ANY_RELATION`), hierarchy queries (`query.hierarchy`), traversal (`ancestorsOf` / `rootOf` /
301
+ `cascadeOf`), and on-delete cleanup policies.
302
+ - **Sparse component storage** (`registerSparseComponent` / `addSparse` / `query.withSparse`),
303
+ **run conditions / system sets** (`systemSet` + `configureSet`; `runIfResourceEq` / `runEveryNTicks`
304
+ / `runIfAnyMatch`), **entity enable/disable** (row-partitioned; `disable` / `enable` /
305
+ `includeDisabled`), and **templates** (`world.template([...])` + `createEntity(template, overrides)`
306
+ / `createEntities(template, count)` for zero-transition spawns).
307
+ - **Typed host→ECS write seam** — `installHostCommandSeam(world)` + `applyHostCommand` + a
308
+ `HostCommandQueue` drained by a blessed `exclusive` apply system; a cross-thread ring transport
309
+ (`HostCommandDispatcher`); record/replay (`HostCommandRecorder`, `replayCommandLog`,
310
+ `serializeCommandLog`); and an undo/redo + field-handle layer at `@oasys/oecs/editor`.
311
+ - **Frame trace** — `world.setTrace(sink)` + `FrameTraceRecorder` emit a structured per-frame event
312
+ stream (`__DEV__`-gated). **Compute backend seam** — `world.attachBackend(backend)` runs a system's
313
+ body on a compiled backend instead of its TS closure.
314
+ - **Reactive UI seam (optional):** zero-dependency kernel at `@oasys/oecs/reactive`; ECS→reactive
315
+ bridge at `@oasys/oecs/reactive-sync` (publish-only-dirty, O(changed)); SolidJS adapter at
316
+ `@oasys/oecs/solid` with `solid-js` as an **optional** peer dependency.
317
+ - **`memory` sizing surface** on the constructor: `budget` (by expected `entities`) / `maxBytes` /
318
+ `columnCapacity` / `shared` / `wasm` / `allocator` arms; `resolveECSMemory(...)` exported to inspect
319
+ what an intent resolves to.
320
+ - **Hot-path iteration ergonomics:**
321
+ - **`query.eachChunk((cols, count) => …)`** — the mutable per-archetype iterator. `cols.mut(def)` /
322
+ `cols.read(def)` resolve a whole component's field columns at once into a destructurable group
323
+ (`const { x, y } = cols.mut(Pos)`), stamping the change tick once inside `mut` and handing back
324
+ `count` (= `entityCount`). The only mutable column accessor reachable through iteration (the
325
+ `ArchetypeView` from `forEach` stays read-only). Honours `includeDisabled()`; dense-only like `forEach`.
326
+ - **`ctx.commands`** — a Bevy-`Commands`-style facade namespacing the **deferred** structural ops
327
+ (`spawn` / `add` / `remove` / `despawn` / `disable` / `enable`), unambiguously deferred vs the
328
+ immediate `world.addComponent`.
329
+ - **Callable bundles** — `bundle(def, values)` pairs a def with field values (omitted fields
330
+ zero-fill); `world.spawnBundle(...)` (immediate) and `ctx.commands.spawn` / `.add` (deferred)
331
+ accept a `bundle(...)` or a bare def (tag / all-zero), unifying the attach shapes.
332
+ - **`ctx.updateField` / `ctx.markChanged`**, and optional-component queries (`query.optional(...)` +
333
+ `getOptionalColumnRead`).
334
+ - **Composable change-detection queries** — `query.changed(...)` returns a `ChangedQuery` that now
335
+ mirrors the dense query verbs (`and` / `without` / `anyOf` / `optional`), so
336
+ `q.changed(Pos).without(Dead)` works (refining *after* `changed()`, previously a dead end).
337
+ - **New public exports** — entity-ID codec (`createEntityId` / `getEntityIndex` / `getEntityGeneration`
338
+ + `MAX_*` bounds) for snapshot/replication decode; the error taxonomy (`ECSError`, `ECS_ERROR`,
339
+ `isEcsError`) for catch-and-branch; and `@oasys/oecs/primitives` (`BitSet`, `SparseSet`, `SparseMap`,
340
+ growable typed arrays, `BinaryHeap`, `topologicalSort`).
341
+
342
+ ### Packaging
343
+
344
+ - **Multi-entry build** → `dist/` emits ESM + CJS + `.d.ts` for every subpath (`.`, `/primitives`,
345
+ `/shared`, `/reactive`, `/reactive-sync`, `/editor`, `/solid`); `sideEffects:false` + tree-shaking
346
+ keep core consumers from pulling SAB / Solid. `solid-js` is an optional peer dependency. `jsr.json`
347
+ exports updated.
348
+
349
+ ## [0.3.3] — 2026-04-30
350
+
351
+ Release-process and packaging hygiene. No runtime changes.
352
+
353
+ ### Changed
354
+
355
+ - **JSR bundle slimmed.** `.github/` and `docs/` are now excluded from the published JSR package. Consumers download less; build/CI artefacts stay on GitHub.
356
+ - **Tag-driven publish workflow.** `.github/workflows/publish.yml` now triggers on `v*` tag pushes instead of every push to `main`, and creates a GitHub Release alongside the JSR publish. Cuts a release by tagging.
357
+
358
+ ## [0.3.2] — 2026-04-30
359
+
360
+ Documentation-only release. No runtime changes.
361
+
362
+ ### Added
363
+
364
+ - **Module overview on `src/index.ts`.** A `@module` block now renders as the JSR Overview tab.
365
+ - **JSDoc on the full public surface.** `ECS` and its public methods, `Query` / `QueryBuilder` / `SystemContext` / `ChangedQuery`, all type aliases and interfaces, the event/resource key minters, and the `SCHEDULE` phases are now documented in-source.
366
+ - **`@internal` tags on internal-but-public TS members** (e.g. `_resolve_query`, `Query._include`, `SystemContext.store`) so JSR hides them from the rendered docs.
367
+
368
+ ## [0.3.1] — 2026-04-23
369
+
370
+ Performance-only patch release. Two targeted allocation-elimination changes on hot paths; no API changes; full 466-test suite unchanged.
371
+
372
+ ### Performance
373
+
374
+ - **Cache multi-component transition maps on `Archetype`.** `add_components` / `remove_components` on already-populated entities previously allocated a fresh `Int16Array` per call via `build_transition_map`. A per-archetype `batch_transition_maps: Map<ArchetypeID, Int16Array>` now caches the map on first use. Single-component paths unchanged. Measured: **+12–15%** throughput on `add_components` (already-populated) at 10k / 100k / 1M; **−35–42%** peak heap and **−49–61%** peak RSS on the same workload. ([#9](https://github.com/oasys-works/oecs/pull/9))
375
+ - **Per-Query composition cache for single-component composition shapes.** `q.and(X)`, `q.not(X)`, `q.any_of(X)`, and `q.changed(X)` previously allocated a BitSet copy, a defs slice (and, for `.changed`, a new `ChangedQuery`) on every call, even though the resolver already cached the resulting `Query` object. Single-component calls now short-circuit through a per-parent-`Query` Map and skip the allocation path entirely. Multi-component compositions fall through unchanged. Measured: **~6×** throughput on a 4-shape compose loop at 10k / 100k / 1M; **−40–56%** peak heap and **essentially zero RSS growth** during the workload. ([#10](https://github.com/oasys-works/oecs/pull/10))
376
+
377
+ ## [0.3.0] — 2026-04-21
378
+
379
+ A substantial release focused on change detection, stricter component-access
380
+ typing, and a simpler key-based API for events and resources. Several public
381
+ entry points change shape; see the migration notes under *Breaking changes*.
382
+
383
+ ### Added
384
+
385
+ #### Change detection
386
+
387
+ - Frame-based tick counter on the world. `ECS` now holds a `_tick` that
388
+ advances once per `update()`. Systems can see it via `ctx.world_tick`,
389
+ and each `SystemContext` receives `last_run_tick` — the tick at which that
390
+ system last executed.
391
+ - Per-component change ticks on archetypes. Each archetype tracks
392
+ `_changed_tick[component_id]` — the tick at which any entity in that
393
+ archetype last had the component mutated. Maintained automatically by
394
+ `write_fields`, `write_fields_positional`, `copy_shared_from`,
395
+ `move_entity_from`, and `bulk_move_all_from`, all of which now accept a
396
+ `tick` parameter.
397
+ - `ChangedQuery<Defs>` — a new query variant, produced by `query.changed(...)`,
398
+ that restricts iteration to archetypes whose tracked components were
399
+ modified after `last_run_tick`. Validates at construction that the named
400
+ components are part of the parent query's include set.
401
+
402
+ #### Readonly component views
403
+
404
+ - `ReadonlyColumn<T>` and `ReadonlyUint32Array` — compile-time readonly views
405
+ of typed-array columns. Returned by `archetype.get_column()` and the new
406
+ `archetype.entity_ids` getter. Prevents accidental indexed writes at the
407
+ type level; zero runtime cost.
408
+ - `ReadonlyComponentRef<S>` — readonly variant of `ComponentRef`. Returned by
409
+ `query.ref(...)`. Use it when you only need to read component fields.
410
+ - `archetype.get_column_mut(def, field, tick)` — explicit mutable column
411
+ accessor. Writes through `get_column_mut` update `_changed_tick`.
412
+ - `query.ref_mut(...)` — mutable sibling of `ref()`. Returns a `ComponentRef`
413
+ and records the component as changed for the current tick.
414
+
415
+ #### Key-based Event API
416
+
417
+ - `EventKey<F>` — symbol-typed key that carries the event's field schema as
418
+ a phantom type.
419
+ - `event_key<F>(name)` / `signal_key(name)` — factories for module-scope
420
+ event keys. `signal_key` is a convenience wrapper for zero-field events.
421
+
422
+ #### Key-based Resource API
423
+
424
+ - `ResourceKey<T>` — symbol-typed key carrying the resource's value type as
425
+ a phantom type.
426
+ - `resource_key<T>(name)` — factory for module-scope resource keys.
427
+ - `world.has_resource(key)` — existence check.
428
+ - Resources are now plain key→value storage. `world.resource(key)` returns
429
+ the stored `T` directly.
430
+
431
+ #### Errors
432
+
433
+ - New `ECS_ERROR` categories: `RESOURCE_ALREADY_REGISTERED`,
434
+ `EVENT_ALREADY_REGISTERED`, `EVENT_NOT_REGISTERED`.
435
+ - New `TYPE_ERROR` category: `ASSERTION_FAIL_NON_NULLABLE`, emitted by the
436
+ new `assert_non_null` helper.
437
+
438
+ #### Assertions
439
+
440
+ - `assert_non_null<T>(value, message?)` in `type_primitives/assertions` —
441
+ dev-only (`__DEV__` guarded) assertion that narrows `T` to `NonNullable<T>`
442
+ and throws a `TypeError` with contextual info on failure.
443
+
444
+ #### New primitives
445
+
446
+ - `BinaryHeap<T>` in `type_primitives/binary_heap` — generic array-backed
447
+ heap with a user-supplied comparator. `push`, `pop`, `peek`, `clear`,
448
+ `size`. O(log n) push/pop, O(1) peek.
449
+ - `topological_sort<T>(nodes, edges, tiebreaker, node_name?)` in
450
+ `type_primitives/topological_sort` — Kahn's algorithm with a
451
+ `BinaryHeap`-backed ready queue for deterministic tie-breaking. Throws
452
+ `TypeError` on cycles; the schedule layer re-wraps as
453
+ `ECSError(CIRCULAR_SYSTEM_DEPENDENCY)`.
454
+
455
+ #### Public exports
456
+
457
+ - `SystemFn`, `ReadonlyComponentRef`, `ChangedQuery`, `ReadonlyColumn`,
458
+ `ReadonlyUint32Array`, `EventKey`, `event_key`, `signal_key`,
459
+ `ResourceKey`, `resource_key` are now part of the package surface.
460
+
461
+ ### Changed
462
+
463
+ - Query iteration is callback-based. `Query` no longer implements
464
+ `[Symbol.iterator]`. Iterate with `query.for_each((archetype) => { ... })`.
465
+ - `world.register_event`, `world.register_signal`, and `world.register_resource`
466
+ return `void` and take an `EventKey` / `ResourceKey` as their first argument.
467
+ - `world.emit`, `world.read`, `world.resource`, and `world.set_resource`
468
+ accept keys instead of definition objects. `world.resource(key)` returns
469
+ the typed value `T` directly rather than a field-reader wrapper.
470
+ - Schedule execution methods take a tick. `run_startup(label, tick)`,
471
+ `run_update(label, tick)`, and `run_fixed_update(label, tick)` require
472
+ the current frame tick. `ECS.update()` wires this automatically.
473
+ - System ordering now uses the shared `topological_sort` primitive. Observable
474
+ behavior is unchanged: `before`/`after` constraints respected,
475
+ `insertion_order` remains the tie-breaker, cycles surface as
476
+ `ECSError(CIRCULAR_SYSTEM_DEPENDENCY)`.
477
+ - Store/query wiring. The store keeps a reference to each active `Query` via
478
+ `update_query_ref` and calls `mark_non_empty_dirty` only when structural
479
+ changes occur, avoiding spurious query rebuilds on stable frames.
480
+ - Bit-manipulation and hash constants (`BITS_PER_WORD`, `BITS_PER_WORD_SHIFT`,
481
+ `BITS_PER_WORD_MASK`, `FNV_OFFSET_BASIS`, `FNV_PRIME`) are exported from
482
+ `type_primitives/bitset` rather than `utils/constants`.
483
+ - Growable-array defaults (`DEFAULT_INITIAL_CAPACITY`, `GROWTH_FACTOR`) are
484
+ exported from `type_primitives/typed_arrays`.
485
+
486
+ ### Fixed
487
+
488
+ - Query dirty propagation. `flush_destroyed` now marks affected queries dirty
489
+ so subsequent iteration sees the correct archetype set. `flush_structural`
490
+ skips dirty marking when no changes occurred.
491
+ - `set_field` on the world goes through `get_column_mut` with the current
492
+ tick, so mutations via the high-level API are visible to `ChangedQuery`.
493
+
494
+ ### Removed
495
+
496
+ - `ResourceChannel`, `ResourceDef<F>`, `ResourceReader<F>`, `ResourceID`,
497
+ `as_resource_id`, and the `__resource_schema` marker symbol — the entire
498
+ SoA column-based resource storage layer. Resources are now key→value.
499
+ - `RESOURCE_ROW` constant — unused.
500
+ - `EventDef<F>` — replaced by `EventKey<F>`.
501
+
502
+ ### Breaking changes
503
+
504
+ 1. **Event definitions.** Define a key at module scope, then register and use it:
505
+ ```ts
506
+ // before
507
+ const damage = world.register_event({ amount: "u32" } as const);
508
+ world.emit(damage, { amount: 5 });
509
+
510
+ // after
511
+ const DAMAGE = event_key<{ amount: "u32" }>("damage");
512
+ world.register_event(DAMAGE, { amount: "u32" } as const);
513
+ world.emit(DAMAGE, { amount: 5 });
514
+ ```
515
+
516
+ 2. **Resource registration / access.**
517
+ ```ts
518
+ // before
519
+ const clock = world.register_resource({ ms: "u32" } as const, { ms: 0 });
520
+ const ms = world.resource(clock).ms;
521
+
522
+ // after
523
+ const CLOCK = resource_key<{ ms: number }>("clock");
524
+ world.register_resource(CLOCK, { ms: 0 });
525
+ const ms = world.resource(CLOCK).ms;
526
+ ```
527
+ `world.resource()` returns the stored value directly; the reader wrapper
528
+ and the SoA column storage are gone.
529
+
530
+ 3. **Query iteration.**
531
+ ```ts
532
+ // before
533
+ for (const arch of query) { ... }
534
+
535
+ // after
536
+ query.for_each((arch) => { ... });
537
+ ```
538
+
539
+ 4. **Mutable vs readonly refs.** `query.ref(...)` now returns
540
+ `ReadonlyComponentRef`. Switch to `query.ref_mut(...)` when writing —
541
+ this is also what enables change detection for that component.
542
+
543
+ 5. **Archetype column access.** `archetype.get_column(...)` returns a
544
+ `ReadonlyColumn`. Use `archetype.get_column_mut(def, field, tick)` for
545
+ direct writes. Most callers should use `query.ref_mut` and won't notice.
546
+
547
+ 6. **Schedule driver signatures.** If you drive the scheduler directly
548
+ (bypassing `ECS.update()`), `run_startup`, `run_update`, and
549
+ `run_fixed_update` now require a `tick: number` argument.
550
+
551
+ ## [0.2.1] and earlier
552
+
553
+ Prior releases — see git history.