@oasys/oecs 0.3.0 → 0.4.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 (204) hide show
  1. package/README.md +196 -267
  2. package/dist/array-CxbPyiHO.cjs +1 -0
  3. package/dist/array-uFR7Q8fU.js +132 -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.ts +474 -0
  7. package/dist/core/ecs/archetype.d.ts.map +1 -0
  8. package/dist/core/ecs/builtin_relations.d.ts +43 -0
  9. package/dist/core/ecs/builtin_relations.d.ts.map +1 -0
  10. package/dist/core/ecs/command_log.d.ts +113 -0
  11. package/dist/core/ecs/command_log.d.ts.map +1 -0
  12. package/dist/core/ecs/component.d.ts +106 -0
  13. package/dist/core/ecs/component.d.ts.map +1 -0
  14. package/dist/core/ecs/compute_backend.d.ts +37 -0
  15. package/dist/core/ecs/compute_backend.d.ts.map +1 -0
  16. package/dist/core/ecs/dispatch_trace.d.ts +93 -0
  17. package/dist/core/ecs/dispatch_trace.d.ts.map +1 -0
  18. package/dist/core/ecs/ecs.d.ts +533 -0
  19. package/dist/core/ecs/ecs.d.ts.map +1 -0
  20. package/dist/core/ecs/ecs_memory.d.ts +179 -0
  21. package/dist/core/ecs/ecs_memory.d.ts.map +1 -0
  22. package/dist/core/ecs/entity.d.ts +28 -0
  23. package/dist/core/ecs/entity.d.ts.map +1 -0
  24. package/dist/core/ecs/event.d.ts +54 -0
  25. package/dist/core/ecs/event.d.ts.map +1 -0
  26. package/dist/core/ecs/frame_trace.d.ts +133 -0
  27. package/dist/core/ecs/frame_trace.d.ts.map +1 -0
  28. package/dist/core/ecs/host_commands.d.ts +252 -0
  29. package/dist/core/ecs/host_commands.d.ts.map +1 -0
  30. package/dist/core/ecs/index.d.ts +41 -0
  31. package/dist/core/ecs/index.d.ts.map +1 -0
  32. package/dist/core/ecs/observer.d.ts +142 -0
  33. package/dist/core/ecs/observer.d.ts.map +1 -0
  34. package/dist/core/ecs/query.d.ts +557 -0
  35. package/dist/core/ecs/query.d.ts.map +1 -0
  36. package/dist/core/ecs/ref.d.ts +31 -0
  37. package/dist/core/ecs/ref.d.ts.map +1 -0
  38. package/dist/core/ecs/relation.d.ts +231 -0
  39. package/dist/core/ecs/relation.d.ts.map +1 -0
  40. package/dist/core/ecs/resource.d.ts +33 -0
  41. package/dist/core/ecs/resource.d.ts.map +1 -0
  42. package/dist/core/ecs/resume.d.ts +85 -0
  43. package/dist/core/ecs/resume.d.ts.map +1 -0
  44. package/dist/core/ecs/run_condition.d.ts +75 -0
  45. package/dist/core/ecs/run_condition.d.ts.map +1 -0
  46. package/dist/core/ecs/schedule.d.ts +133 -0
  47. package/dist/core/ecs/schedule.d.ts.map +1 -0
  48. package/dist/core/ecs/sparse_store.d.ts +107 -0
  49. package/dist/core/ecs/sparse_store.d.ts.map +1 -0
  50. package/dist/core/ecs/store.d.ts +1149 -0
  51. package/dist/core/ecs/store.d.ts.map +1 -0
  52. package/dist/core/ecs/store_layout_listener.d.ts +23 -0
  53. package/dist/core/ecs/store_layout_listener.d.ts.map +1 -0
  54. package/dist/core/ecs/system.d.ts +134 -0
  55. package/dist/core/ecs/system.d.ts.map +1 -0
  56. package/dist/core/ecs/utils/arrays.d.ts +7 -0
  57. package/dist/core/ecs/utils/arrays.d.ts.map +1 -0
  58. package/dist/core/ecs/utils/constants.d.ts +12 -0
  59. package/dist/core/ecs/utils/constants.d.ts.map +1 -0
  60. package/dist/core/ecs/utils/error.d.ts +51 -0
  61. package/dist/core/ecs/utils/error.d.ts.map +1 -0
  62. package/dist/core/reactive/array.d.ts +24 -0
  63. package/dist/core/reactive/array.d.ts.map +1 -0
  64. package/dist/core/reactive/index.cjs +1 -0
  65. package/dist/core/reactive/index.d.ts +10 -0
  66. package/dist/core/reactive/index.d.ts.map +1 -0
  67. package/dist/core/reactive/index.js +17 -0
  68. package/dist/core/reactive/interop.d.ts +19 -0
  69. package/dist/core/reactive/interop.d.ts.map +1 -0
  70. package/dist/core/reactive/kernel.d.ts +71 -0
  71. package/dist/core/reactive/kernel.d.ts.map +1 -0
  72. package/dist/core/reactive/map.d.ts +16 -0
  73. package/dist/core/reactive/map.d.ts.map +1 -0
  74. package/dist/core/reactive/struct.d.ts +10 -0
  75. package/dist/core/reactive/struct.d.ts.map +1 -0
  76. package/dist/core/store/__generated__/abi.d.ts +43 -0
  77. package/dist/core/store/__generated__/abi.d.ts.map +1 -0
  78. package/dist/core/store/action_ring.d.ts +136 -0
  79. package/dist/core/store/action_ring.d.ts.map +1 -0
  80. package/dist/core/store/allocator.d.ts +238 -0
  81. package/dist/core/store/allocator.d.ts.map +1 -0
  82. package/dist/core/store/buffer_backed_column.d.ts +69 -0
  83. package/dist/core/store/buffer_backed_column.d.ts.map +1 -0
  84. package/dist/core/store/column_store.d.ts +265 -0
  85. package/dist/core/store/column_store.d.ts.map +1 -0
  86. package/dist/core/store/command_dispatch.d.ts +52 -0
  87. package/dist/core/store/command_dispatch.d.ts.map +1 -0
  88. package/dist/core/store/command_ring.d.ts +107 -0
  89. package/dist/core/store/command_ring.d.ts.map +1 -0
  90. package/dist/core/store/descriptor.d.ts +80 -0
  91. package/dist/core/store/descriptor.d.ts.map +1 -0
  92. package/dist/core/store/entity_index.d.ts +108 -0
  93. package/dist/core/store/entity_index.d.ts.map +1 -0
  94. package/dist/core/store/event_ring.d.ts +95 -0
  95. package/dist/core/store/event_ring.d.ts.map +1 -0
  96. package/dist/core/store/extend.d.ts +109 -0
  97. package/dist/core/store/extend.d.ts.map +1 -0
  98. package/dist/core/store/grow.d.ts +39 -0
  99. package/dist/core/store/grow.d.ts.map +1 -0
  100. package/dist/core/store/header.d.ts +64 -0
  101. package/dist/core/store/header.d.ts.map +1 -0
  102. package/dist/core/store/index.d.ts +16 -0
  103. package/dist/core/store/index.d.ts.map +1 -0
  104. package/dist/core/store/region_table.d.ts +74 -0
  105. package/dist/core/store/region_table.d.ts.map +1 -0
  106. package/dist/core/store/snapshot.d.ts +43 -0
  107. package/dist/core/store/snapshot.d.ts.map +1 -0
  108. package/dist/core/store/state_hash.d.ts +38 -0
  109. package/dist/core/store/state_hash.d.ts.map +1 -0
  110. package/dist/core/store/store_regions.d.ts +38 -0
  111. package/dist/core/store/store_regions.d.ts.map +1 -0
  112. package/dist/extensions/editor/editor.d.ts +149 -0
  113. package/dist/extensions/editor/editor.d.ts.map +1 -0
  114. package/dist/extensions/editor/field_handle.d.ts +35 -0
  115. package/dist/extensions/editor/field_handle.d.ts.map +1 -0
  116. package/dist/extensions/editor/index.cjs +1 -0
  117. package/dist/extensions/editor/index.d.ts +21 -0
  118. package/dist/extensions/editor/index.d.ts.map +1 -0
  119. package/dist/extensions/editor/index.js +209 -0
  120. package/dist/extensions/reactive/ecs_sync.d.ts +210 -0
  121. package/dist/extensions/reactive/ecs_sync.d.ts.map +1 -0
  122. package/dist/extensions/reactive/index.cjs +1 -0
  123. package/dist/extensions/reactive/index.d.ts +23 -0
  124. package/dist/extensions/reactive/index.d.ts.map +1 -0
  125. package/dist/extensions/reactive/index.js +225 -0
  126. package/dist/extensions/solid/index.cjs +1 -0
  127. package/dist/extensions/solid/index.d.ts +6 -0
  128. package/dist/extensions/solid/index.d.ts.map +1 -0
  129. package/dist/extensions/solid/index.js +32 -0
  130. package/dist/extensions/solid/kernel_solid.d.ts +42 -0
  131. package/dist/extensions/solid/kernel_solid.d.ts.map +1 -0
  132. package/dist/index.cjs +2 -1
  133. package/dist/index.d.ts +16 -12
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +8563 -1233
  136. package/dist/interop-CT-REx0W.cjs +1 -0
  137. package/dist/interop-CcY6ASQc.js +18 -0
  138. package/dist/kernel-DgyrLFjW.js +227 -0
  139. package/dist/kernel-yWV3XnAb.cjs +1 -0
  140. package/dist/log/console_sink.d.ts +4 -0
  141. package/dist/log/console_sink.d.ts.map +1 -0
  142. package/dist/log/index.d.ts +3 -0
  143. package/dist/log/index.d.ts.map +1 -0
  144. package/dist/log/logger.d.ts +27 -0
  145. package/dist/log/logger.d.ts.map +1 -0
  146. package/dist/primitives.cjs +1 -0
  147. package/dist/primitives.d.ts +18 -0
  148. package/dist/primitives.d.ts.map +1 -0
  149. package/dist/primitives.js +44 -0
  150. package/dist/shared-BXSZnxx4.cjs +1 -0
  151. package/dist/shared-C678TAPY.js +99 -0
  152. package/dist/shared.cjs +1 -0
  153. package/dist/shared.d.ts +22 -0
  154. package/dist/shared.d.ts.map +1 -0
  155. package/dist/shared.js +7 -0
  156. package/dist/topological_sort-DlRpSrxu.js +391 -0
  157. package/dist/topological_sort-WAT-VHb-.cjs +1 -0
  158. package/dist/type_primitives/assertions.d.ts +12 -8
  159. package/dist/type_primitives/assertions.d.ts.map +1 -1
  160. package/dist/type_primitives/binary_heap/binary_heap.d.ts +6 -2
  161. package/dist/type_primitives/binary_heap/binary_heap.d.ts.map +1 -1
  162. package/dist/type_primitives/bitset/bitset.d.ts +16 -4
  163. package/dist/type_primitives/bitset/bitset.d.ts.map +1 -1
  164. package/dist/type_primitives/brand.d.ts +6 -1
  165. package/dist/type_primitives/brand.d.ts.map +1 -1
  166. package/dist/type_primitives/error.d.ts +4 -0
  167. package/dist/type_primitives/error.d.ts.map +1 -1
  168. package/dist/type_primitives/index.d.ts +3 -0
  169. package/dist/type_primitives/index.d.ts.map +1 -1
  170. package/dist/type_primitives/sparse_map/sparse_map.d.ts +7 -3
  171. package/dist/type_primitives/sparse_map/sparse_map.d.ts.map +1 -1
  172. package/dist/type_primitives/sparse_set/sparse_set.d.ts +4 -0
  173. package/dist/type_primitives/sparse_set/sparse_set.d.ts.map +1 -1
  174. package/dist/type_primitives/topological_sort/topological_sort.d.ts +7 -3
  175. package/dist/type_primitives/topological_sort/topological_sort.d.ts.map +1 -1
  176. package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +53 -16
  177. package/dist/type_primitives/typed_arrays/typed_arrays.d.ts.map +1 -1
  178. package/dist/utils/arrays.d.ts +1 -1
  179. package/dist/utils/arrays.d.ts.map +1 -1
  180. package/dist/utils/error.d.ts +2 -20
  181. package/dist/utils/error.d.ts.map +1 -1
  182. package/package.json +36 -9
  183. package/dist/archetype.d.ts +0 -101
  184. package/dist/archetype.d.ts.map +0 -1
  185. package/dist/component.d.ts +0 -45
  186. package/dist/component.d.ts.map +0 -1
  187. package/dist/ecs.d.ts +0 -104
  188. package/dist/ecs.d.ts.map +0 -1
  189. package/dist/entity.d.ts +0 -11
  190. package/dist/entity.d.ts.map +0 -1
  191. package/dist/event.d.ts +0 -30
  192. package/dist/event.d.ts.map +0 -1
  193. package/dist/query.d.ts +0 -90
  194. package/dist/query.d.ts.map +0 -1
  195. package/dist/ref.d.ts +0 -23
  196. package/dist/ref.d.ts.map +0 -1
  197. package/dist/resource.d.ts +0 -23
  198. package/dist/resource.d.ts.map +0 -1
  199. package/dist/schedule.d.ts +0 -45
  200. package/dist/schedule.d.ts.map +0 -1
  201. package/dist/store.d.ts +0 -118
  202. package/dist/store.d.ts.map +0 -1
  203. package/dist/system.d.ts +0 -16
  204. package/dist/system.d.ts.map +0 -1
@@ -0,0 +1,265 @@
1
+ import { StoreHeader } from './header';
2
+ import { ArchetypeDescriptor, TypeTagValue } from './descriptor';
3
+ import { BufferAllocator } from './allocator';
4
+ import { COMMAND_RING_DEFAULT_CAPACITY_SLOTS } from './command_ring';
5
+ import { ENTITY_INDEX_DEFAULT_CAPACITY } from './entity_index';
6
+ import { EVENT_RING_DEFAULT_CAPACITY_SLOTS } from './event_ring';
7
+ import { StoreRegionSpec } from './region_table';
8
+ /** Caller-facing column spec — no `byte_off` yet, the store computes it. */
9
+ export interface ColumnSpec {
10
+ readonly componentId: number;
11
+ readonly fieldId: number;
12
+ readonly typeTag: TypeTagValue;
13
+ }
14
+ /** Caller-facing archetype spec — no `column_count` (derived) and no
15
+ * `byte_off`s in the columns; both are computed during sizing. */
16
+ export interface ArchetypeSpec {
17
+ readonly archetypeId: number;
18
+ /** Component bitmask, `COMPONENT_MASK_WORDS` little-endian u32 words. */
19
+ readonly componentMask: readonly number[];
20
+ readonly rowCapacity: number;
21
+ readonly columns: readonly ColumnSpec[];
22
+ }
23
+ /** A single column's view after allocation. The `byte_off` matches what
24
+ * was recorded in the layout descriptor; `view` is a TypedArray of the
25
+ * right element type, length `row_capacity`, backed by the SAB. */
26
+ export interface ColumnView {
27
+ readonly componentId: number;
28
+ readonly fieldId: number;
29
+ readonly typeTag: TypeTagValue;
30
+ readonly byteOff: number;
31
+ readonly stride: number;
32
+ readonly view: AnyTypedArray;
33
+ }
34
+ /** Views for all columns in a single archetype, indexed by a numeric
35
+ * key encoding `(component_id, field_id)` so a (cid, fid) pair maps to
36
+ * its column in O(1). The encoding (see `columnKey`) is dense over
37
+ * (cid: 0..65535, fid: 0..65535), letting V8 keep this as a
38
+ * Number-keyed `Map` — meaningfully faster than the previous string
39
+ * keys (no per-lookup template-string allocation, no string hashing).
40
+ * The row order matches `ArchetypeSpec.columns`. The
41
+ * `component_mask` words mirror what's in the SAB layout descriptor so
42
+ * `growColumnStore` (and any other carry-forward path) doesn't have to
43
+ * re-parse the descriptor region. */
44
+ export interface ArchetypeViews {
45
+ readonly archetypeId: number;
46
+ /** Component bitmask, `COMPONENT_MASK_WORDS` little-endian u32 words. */
47
+ readonly componentMask: readonly number[];
48
+ readonly rowCapacity: number;
49
+ readonly columns: ReadonlyMap<number, ColumnView>;
50
+ readonly columnsInOrder: readonly ColumnView[];
51
+ }
52
+ export interface ColumnStore {
53
+ /** The backing buffer. `ArrayBufferLike` because the store is backing-agnostic:
54
+ * a `SharedArrayBuffer` for the SAB/WASM/worker profile, or a plain resizable
55
+ * `ArrayBuffer` for the pure-TS heap profile (`heapArraybufferAllocator`).
56
+ * Consumers that genuinely require sharing (worker transfer, WASM memory)
57
+ * narrow back to `SharedArrayBuffer` at their boundary. */
58
+ readonly buffer: ArrayBufferLike;
59
+ readonly view: DataView;
60
+ readonly header: StoreHeader;
61
+ /** Indexed by `archetype_id`. */
62
+ readonly archetypes: ReadonlyMap<number, ArchetypeViews>;
63
+ }
64
+ export type AnyTypedArray = Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array | Float32Array | Float64Array;
65
+ /** Pack `(component_id, field_id)` into a single non-negative integer so
66
+ * `ArchetypeViews.columns` can be a `Map<number, ColumnView>` instead of
67
+ * `Map<string, ColumnView>`. V8's number-keyed `Map` skips the
68
+ * string-hash + string-equality every lookup pays, and template-string
69
+ * keys (`"${cid}:${fid}"`) allocated a fresh string on every call. On
70
+ * the lazy-registration ramp-up the per-extend `refreshViews`
71
+ * walks `N` × `cols` of these lookups for `N` archetypes, so the saving
72
+ * compounds quadratically.
73
+ *
74
+ * Encoding: `(component_id << 16) | field_id`. `component_id` is bounded
75
+ * by `STORE_DESCRIPTOR_COMPONENT_LIMIT` (the registration cap, 128), well
76
+ * below 65536. `field_id` is bounded by the component's schema width,
77
+ * single digits in practice. */
78
+ export declare function columnKey(componentId: number, fieldId: number): number;
79
+ /** First byte offset the SAB layout math cannot represent: 2³¹.
80
+ *
81
+ * `alignUp` rounds with `& ~(align-1)`, and JS bitwise operators coerce
82
+ * their operands to **signed** 32-bit integers (`ToInt32`). Once an offset
83
+ * reaches 2³¹ the result wraps to a negative number (or, for some inputs, a
84
+ * misaligned positive one), which then flows straight into
85
+ * `new Uint8Array(buffer, byte_off, …)` — either a thrown `RangeError` deep in
86
+ * the TypedArray ctor or, worse, a silently wrong view overlapping another
87
+ * column. The 256 MiB default allocator cap (`growableSabAllocator`) keeps
88
+ * real matches three orders of magnitude below this, but the cap is tunable
89
+ * and callers are invited to raise it for bigger worlds — so the layout step
90
+ * guards the hard 2³¹ ceiling explicitly rather than relying on the policy
91
+ * cap to stay in front of it (#382). */
92
+ export declare const STORE_MAX_BYTE_OFFSET: number;
93
+ /** Thrown when a SAB column layout would place an offset at or beyond
94
+ * {@link STORE_MAX_BYTE_OFFSET} (2³¹), past which the signed-32-bit bitwise
95
+ * `alignUp` can no longer produce correct offsets. This is a hard ceiling,
96
+ * not the (tunable, much lower) 256 MiB allocator cap — see #382. */
97
+ export declare class StoreLayoutOverflowError extends Error {
98
+ constructor(byteOff: number);
99
+ }
100
+ /** Round `off` up to the next multiple of `align`. `align` must be a power
101
+ * of two (1/2/4/8 here, all `TYPE_TAG_STRIDE` values).
102
+ *
103
+ * Throws {@link StoreLayoutOverflowError} when the rounded offset would reach
104
+ * {@link STORE_MAX_BYTE_OFFSET}, because the `& ~(align-1)` step coerces to a
105
+ * signed 32-bit int and wraps past 2³¹ (#382). The guard fires before the
106
+ * bitwise op so any returned offset is always a correct, in-range value.
107
+ * Shared by `extend.ts` and `grow.ts`, whose in-place paths compute tail
108
+ * byte_offs without going through `planLayout`. */
109
+ export declare function alignUp(off: number, align: number): number;
110
+ /** Build the layout-descriptor-region descriptors (with `byte_off` and
111
+ * `stride` filled in) and return both them and the byte offset just past
112
+ * the last column — i.e. the total SAB size.
113
+ *
114
+ * `headroomBytes` reserves slack at the end of the descriptor region — ON
115
+ * TOP OF the natural size for `specs` — so future extends can append new
116
+ * descriptor entries without shifting existing column byte_offs. Used by
117
+ * the growable-SAB path (#237 Option A) — column views stay valid across
118
+ * `extendColumnStore` because their byte_offs don't move.
119
+ *
120
+ * Additive, not a floor (#541): `regionSize = natural + headroom`, NOT
121
+ * `max(natural, headroom)`. A floor only yields slack while `natural` is
122
+ * below it; the moment the descriptor region outgrows the floor (the #237
123
+ * headroom exhausts and a realloc re-plans the merged spec set), a floor
124
+ * would size the region to exactly `natural` — zero slack — and every
125
+ * subsequent extend would take the slow realloc path forever after. The
126
+ * additive form re-creates the same `headroom` margin on every realloc.
127
+ * For the engine's empty-seed store (`createColumnStore([], …)`, where
128
+ * `natural === 0`) the two forms coincide, so this is behaviour-identical
129
+ * for the only production caller. */
130
+ declare function planLayout(specs: readonly ArchetypeSpec[], regionOff: number, headroomBytes?: number): {
131
+ descriptors: ArchetypeDescriptor[];
132
+ totalBytes: number;
133
+ regionBytes: number;
134
+ };
135
+ /** Exposed for use by `extendColumnStore`'s in-place fast path so it can
136
+ * compute new column byte_offs without re-running `planLayout`. */
137
+ export { planLayout as _planLayout };
138
+ /** Optional configuration for `createColumnStore`. */
139
+ export interface CreateColumnStoreOptions {
140
+ /** Extra slack to reserve at the end of the layout descriptor region,
141
+ * ON TOP OF the natural size for `specs` (#541 — additive, not a floor).
142
+ * The descriptor region is padded with this many unused bytes so future
143
+ * `extendColumnStore` calls can append new archetype descriptors into the
144
+ * slack without shifting existing column byte_offs. Pairs with
145
+ * `growableSabAllocator` to give the #237 Option A fast path: existing
146
+ * TypedArray column views stay valid across extends because their offsets
147
+ * and the underlying buffer both stay put.
148
+ *
149
+ * Carried forward as a policy across the realloc-and-republish path: the
150
+ * value lives on `ColumnStoreInternal._reservedDescriptorBytes` and
151
+ * `optionsFromOld` re-applies it, so a store that exhausts its headroom
152
+ * and reallocs gets a fresh margin rather than dropping to zero slack and
153
+ * going permanently slow (#541). */
154
+ readonly reservedDescriptorBytes?: number;
155
+ /** When provided, allocates a command ring (#171 §7.5, Phase 4) inside
156
+ * the SAB at a stable offset right after the 48-byte header. Slot
157
+ * count MUST be a power of two; `COMMAND_RING_DEFAULT_CAPACITY_SLOTS`
158
+ * (256) is the canonical value. Omitted ⇒ no ring; `command_ring_off`
159
+ * stays at 0 ("absent"); existing test fixtures with hand-rolled SABs
160
+ * see the legacy layout (descriptor region immediately after header).
161
+ *
162
+ * Sizing the ring this way — between header and descriptor region —
163
+ * keeps `command_ring_off` stable across `extendColumnStore` /
164
+ * `growColumnStore` calls, since those grow the descriptor region and
165
+ * the column tail but never the bytes between header and descriptor. */
166
+ readonly commandRingCapacitySlots?: number;
167
+ /** When provided, allocates the entity-index region (#245 / Phase 4
168
+ * PR 4B) inside the SAB at a stable offset between the command ring
169
+ * (or header) and the descriptor region. Holds `(generations,
170
+ * archetypes, rows)` triples indexed by entity slot; the engine's
171
+ * `Store` populates them as entities are created/moved/destroyed,
172
+ * and Zig systems read them to resolve cross-entity targets without
173
+ * a callback. Capacity is in *slots* (entities), not bytes; each
174
+ * slot is 12 bytes. Omitted ⇒ no region; `entity_index_off` stays
175
+ * at 0 ("absent"). The engine's Store always sets it to
176
+ * `ENTITY_INDEX_DEFAULT_CAPACITY`; bare-SAB tests can leave it
177
+ * absent. */
178
+ readonly entityIndexCapacity?: number;
179
+ /** When provided, allocates the event ring (#247 / Phase 4 PR 4C)
180
+ * inside the SAB at a stable offset between the entity-index region
181
+ * and the descriptor region. Same SPSC shape as the command ring;
182
+ * carries ECS signal payloads so Zig systems can emit and consume
183
+ * them during `tick()` without callbacks into TS.
184
+ *
185
+ * Slot count MUST be a power of two; `EVENT_RING_DEFAULT_CAPACITY_SLOTS`
186
+ * (256) is the canonical value. Omitted ⇒ no ring; `event_ring_off`
187
+ * stays at 0 ("absent"); existing test fixtures with hand-rolled
188
+ * SABs see the pre-#247 layout. */
189
+ readonly eventRingCapacitySlots?: number;
190
+ /** When provided, allocates the action ring (#291 / Phase 5 PR 5L)
191
+ * inside the SAB at a stable offset between the entity-index/event-ring
192
+ * and the region-table directory. Main writes encoded actions to it;
193
+ * the sim worker drains them on each apply.
194
+ *
195
+ * Slot count MUST be a power of two; `ACTION_RING_DEFAULT_CAPACITY_SLOTS`
196
+ * (256) is the canonical value. Omitted ⇒ no ring; `action_ring_off`
197
+ * stays at 0 ("absent"); bare-SAB tests skip it. (Engine mechanism — the
198
+ * `Store` allocates one always-on; it is no longer a public ECS option.) */
199
+ readonly actionRingCapacitySlots?: number;
200
+ /** Consumer-declared SAB regions (#623). Each `StoreRegionSpec` carries an
201
+ * opaque `region_id`, a precomputed byte size, and an `init` closure; the
202
+ * engine lays them out after the mechanism regions, writes a generic
203
+ * region-table directory (`region_table.ts`) keyed by `region_id`, and
204
+ * snapshots/restores them across a grow/extend. The engine never
205
+ * interprets `region_id` — a game (e.g. `@internal/sim`'s region specs)
206
+ * owns it. Omitted ⇒ no consumer regions; `region_table_off` stays 0. */
207
+ readonly regions?: readonly StoreRegionSpec[];
208
+ /** Byte size of the always-before-descriptor sim-bindings region (v5 /
209
+ * "SAB-is-the-interface"). A consumer that opts into a WASM backend supplies
210
+ * its own size here — `@internal/sim`'s `SIM_BINDINGS_BYTES`, computed from
211
+ * the game's binding manifest. The engine treats the region as opaque bytes:
212
+ * it reserves the block at `bindings_off` (right before the descriptor
213
+ * region, so the offset is stable across grow/extend) and the host writes the
214
+ * `(component_id, field_id)` IDs into it via `write_sim_bindings`.
215
+ *
216
+ * Omitted / 0 ⇒ NO bindings region (`bindings_off` stays 0, "absent") — the
217
+ * default for a pure-TS game that pays nothing for the WASM seam. This used
218
+ * to be the engine-baked `SIM_BINDINGS_BYTES` ABI constant reflected from the
219
+ * game's Zig struct; #625 de-welded it so a manifest edit no longer dirties
220
+ * the engine ABI golden. Re-derived across realloc by `optionsFromOld`
221
+ * (= `layout_descriptor_off - bindings_off`), so it survives grow/extend
222
+ * without a carried policy field. */
223
+ readonly bindingsRegionBytes?: number;
224
+ }
225
+ /** Internal `ColumnStore` extension carrying the descriptor-region byte
226
+ * size. Used by the in-place extend path to know where the next
227
+ * descriptor entry should be written (= `regionOff + region_bytes_used`,
228
+ * tracked separately) and how much headroom remains. */
229
+ export interface ColumnStoreInternal extends ColumnStore {
230
+ readonly _regionBytes: number;
231
+ readonly _allocator: BufferAllocator;
232
+ /** The `reservedDescriptorBytes` policy this store was created with
233
+ * (the additive descriptor-region headroom margin; 0 when none). Carried
234
+ * with the store — NOT re-derivable from the SAB bytes, since `_regionBytes`
235
+ * holds the absolute region size (natural + this), and once `natural`
236
+ * outgrows the margin the two are indistinguishable. The realloc slow path
237
+ * reads it via `optionsFromOld` and re-reserves the same margin, so a
238
+ * store that exhausts its headroom and reallocs keeps taking the #237
239
+ * in-place fast path instead of going permanently slow (#541). The
240
+ * `*_in_place` paths carry it forward verbatim. */
241
+ readonly _reservedDescriptorBytes: number;
242
+ }
243
+ /** Allocate a SAB sized for `specs`, write the header + layout descriptor,
244
+ * and construct one TypedArray view per column.
245
+ *
246
+ * The returned `ColumnStore` is the source of truth for "where every column
247
+ * lives in this SAB". `view_stamp` is initialised to 0 — a Phase 4 SAB
248
+ * grow flow will bump it. (#171 §6.1.4 / §8.1) */
249
+ export declare function createColumnStore(specs: readonly ArchetypeSpec[], allocator?: BufferAllocator, options?: CreateColumnStoreOptions): ColumnStore;
250
+ /** Default command-ring slot count used by `ECS.Store` when constructing
251
+ * its SAB. Re-exported here so the Store doesn't reach across modules. */
252
+ export { COMMAND_RING_DEFAULT_CAPACITY_SLOTS };
253
+ /** Default entity-index capacity used by `ECS.Store` when constructing
254
+ * its SAB. Re-exported alongside `COMMAND_RING_DEFAULT_CAPACITY_SLOTS`. */
255
+ export { ENTITY_INDEX_DEFAULT_CAPACITY };
256
+ /** Default event-ring slot count used by `ECS.Store` when constructing
257
+ * its SAB. Re-exported alongside the other defaults. */
258
+ export { EVENT_RING_DEFAULT_CAPACITY_SLOTS };
259
+ /** Build the `ArchetypeViews` map from a SAB and its parsed descriptors.
260
+ * Shared by `createColumnStore` (fresh allocation, byte_offs just computed)
261
+ * and `restoreColumnStore` (existing allocation, byte_offs read out of the
262
+ * snapshot). Either way the views land at the byte_offs the descriptors
263
+ * already carry — this helper does not plan layout. */
264
+ export declare function buildArchetypeViews(buffer: ArrayBufferLike, descriptors: readonly ArchetypeDescriptor[]): Map<number, ArchetypeViews>;
265
+ //# sourceMappingURL=column_store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"column_store.d.ts","sourceRoot":"","sources":["../../../src/core/store/column_store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Command dispatch — the generic drain surface a consumer registers against
3
+ * (#624, game-agnostic ECS / epic #616).
4
+ *
5
+ * The command ring (`command_ring.ts`) carries opaque `(opCode, payload)`
6
+ * slots; the engine never interprets a code. This module is the thin,
7
+ * game-free glue that lets a consumer bind a payload codec + typed handler to
8
+ * each opcode and drain the ring in one call. The opcode enum and the codecs
9
+ * themselves stay consumer-owned — for our game they live in
10
+ * `@internal/sim`'s `command_payloads.ts` (`COMMAND_OP`, `SpawnUnitFields`,
11
+ * `encode/decode_spawn_unit_payload`); the engine knows none of those names.
12
+ *
13
+ * Usage:
14
+ *
15
+ * const dispatcher = new CommandDispatcher()
16
+ * .on(COMMAND_OP.spawn_unit, spawn_unit_codec, (fields) => spawn(fields));
17
+ * dispatcher.drain(view, ringOff); // runs after wasm.tick() returns
18
+ *
19
+ * Unregistered opcodes are skipped (the same forward-compatible stance as the
20
+ * raw `drainCommandRing` handler that ignores codes it doesn't know).
21
+ */
22
+ /** Decode (and, symmetrically, encode) the 15-byte payload region of a
23
+ * command slot into a typed value. A consumer supplies one per opcode it
24
+ * cares about; the engine only ever calls `decode` during a drain, but the
25
+ * `encode` half keeps the codec a single round-trippable unit (and is what
26
+ * test/host producers use to push). */
27
+ export interface PayloadCodec<T> {
28
+ /** Encode `value` into a fresh `COMMAND_RING_SLOT_BYTES - 1` (15) byte
29
+ * payload, ready for `pushCommand(view, off, op, payload)`. */
30
+ encode(value: T): Uint8Array;
31
+ /** Decode a 15-byte payload back into the typed value. */
32
+ decode(payload: Uint8Array): T;
33
+ }
34
+ /**
35
+ * Registry mapping command opcodes to a payload codec + handler. Generic over
36
+ * the consumer's opcodes — the engine ships the mechanism; the game supplies
37
+ * the codes and codecs.
38
+ */
39
+ export declare class CommandDispatcher {
40
+ private readonly bindings;
41
+ /** Register `handler` for `opCode`, decoding each slot with `codec`.
42
+ * Re-registering an opcode replaces its binding. `opCode` must be a u8 in
43
+ * `[1, 255]` — `0` is the reserved empty-slot marker and can never carry a
44
+ * command. Returns `this` for chaining. */
45
+ on<T>(opCode: number, codec: PayloadCodec<T>, handler: (value: T) => void): this;
46
+ /** Drain every pending command, decoding + dispatching each to its
47
+ * registered handler. Commands with no registered opcode are skipped.
48
+ * Returns the number of slots drained (including skipped ones — the ring is
49
+ * advanced regardless, matching `drainCommandRing`). */
50
+ drain(view: DataView, ringOff: number): number;
51
+ }
52
+ //# sourceMappingURL=command_dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command_dispatch.d.ts","sourceRoot":"","sources":["../../../src/core/store/command_dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH;;;;uCAIuC;AACvC,MAAM,WAAW,YAAY,CAAC,CAAC;IAC9B;mEAC+D;IAC/D,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC;IAC7B,0DAA0D;IAC1D,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,CAAC,CAAC;CAC/B;AAWD;;;;GAIG;AACH,qBAAa,iBAAiB;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAE7D;;;+CAG2C;IAC3C,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,IAAI;IAgBhF;;;4DAGwD;IACxD,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;CAO9C"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Command ring — WASM-side producer / TS-side consumer SPSC ring buffer
3
+ * for structural-change intents emitted during `sim.tick()`. Plan §7.5
4
+ * (`docs/ideas/buffer-wasm-sim-plan-2026-05-14T1600.md`).
5
+ *
6
+ * Layout:
7
+ *
8
+ * [ write_head: u32 ] slot 0..N-1, monotonic (NOT slot-modulo)
9
+ * [ read_head: u32 ] slot 0..N-1, monotonic
10
+ * [ capacity: u32 ] slot count, power-of-two
11
+ * [ overflow: u32 ] 0 = OK; 1 = WASM exhausted the ring this tick
12
+ * [ slot 0: 16 B ] opCode: u8, payload: [15]u8
13
+ * [ slot 1: 16 B ] ...
14
+ * ...
15
+ *
16
+ * SPSC contract (Phase 4 scope):
17
+ * - Producer: WASM `sim.tick()`. Pushes 0..N commands during one tick;
18
+ * bumps `write_head` after each.
19
+ * - Consumer: TS host, immediately after `wasm.tick()` returns. Drains
20
+ * 0..N pending commands; bumps `read_head` after each.
21
+ * - The two never run concurrently in Phase 4 (single host thread
22
+ * orchestrates both). Phase 5's worker offload promotes the head
23
+ * bumps to `Atomics.store`; that's an additive change without
24
+ * altering the layout.
25
+ *
26
+ * Overflow:
27
+ * - If WASM would write a slot when (`write_head - read_head == capacity`),
28
+ * it sets `overflow = 1` and drops the command. TS treats overflow as
29
+ * a hard error in dev builds; production logs and continues (a command
30
+ * might be lost rather than crash the host).
31
+ *
32
+ * Slot format (plan §7.5):
33
+ * byte 0: opCode (u8). 0 is reserved as the empty-slot marker
34
+ * (`COMMAND_OP_EMPTY`); all other codes are consumer-defined.
35
+ * The engine never interprets a code — it drains
36
+ * `(opCode, payload)` and hands them to the attached
37
+ * consumer, which owns the opcode enum + payload codecs (the
38
+ * game's live in `@internal/sim`'s `command_payloads.ts`).
39
+ * bytes 1..15: payload, op-specific. Multi-byte fields may be
40
+ * unaligned within the payload; readers must use byte-
41
+ * oriented helpers (DataView in TS, `mem.readInt` in Zig).
42
+ *
43
+ * The ring lives BEFORE the layout-descriptor region in the SAB (right
44
+ * after the 32-byte header) so its offset is stable across descriptor /
45
+ * column-region growth. The host writes `header.command_ring_off` to
46
+ * point at it during `createColumnStore`; absent ring is signalled by
47
+ * `command_ring_off === 0`.
48
+ */
49
+ /** Total bytes for the ring header. */
50
+ export declare const COMMAND_RING_HEADER_BYTES = 16;
51
+ /** Fixed slot size — 1-byte opCode + 15-byte payload. */
52
+ export declare const COMMAND_RING_SLOT_BYTES = 16;
53
+ /** Default ring capacity in slots. 256 × 16 B = 4 KiB of ring data plus
54
+ * 16 B header. Sized for the worst-case burst (peak spawn intents per
55
+ * tick) × small safety margin. Tune up if a Phase 4 burst pushes past it
56
+ * in the bench harness. */
57
+ export declare const COMMAND_RING_DEFAULT_CAPACITY_SLOTS = 256;
58
+ /** Byte offsets within the ring header. Mirrored on the Zig side in
59
+ * `packages/sim/src/command_ring.zig` — keep in sync. */
60
+ export declare const COMMAND_RING_HEADER_OFFSETS: {
61
+ readonly write_head: 0;
62
+ readonly read_head: 4;
63
+ readonly capacity_slots: 8;
64
+ readonly overflow_flag: 12;
65
+ };
66
+ /** Op-code `0` is reserved across the SAB layer as the empty-slot marker
67
+ * so a zero-initialised SAB doesn't appear to hold a valid command (mirror
68
+ * of `EVENT_OP_EMPTY`). All non-zero codes are opaque to the engine —
69
+ * the attached consumer owns the opcode enum + payload codecs (the game's
70
+ * `COMMAND_OP` + `SpawnUnitFields` live in `@internal/sim`). */
71
+ export declare const COMMAND_OP_EMPTY = 0;
72
+ /** Total bytes the ring occupies for `capacity_slots` slots. */
73
+ export declare function commandRingBytes(capacitySlots: number): number;
74
+ export declare class CommandRingError extends Error {
75
+ constructor(message: string);
76
+ }
77
+ /** Initialise the ring header at `ringOff` in the SAB. Zeroes
78
+ * `write_head`, `read_head`, and `overflow_flag`; sets `capacity_slots`.
79
+ * Slot bytes are left as-is (callers normally allocate the ring on a
80
+ * fresh, zero-initialised SAB). */
81
+ export declare function initCommandRing(view: DataView, ringOff: number, capacitySlots: number): void;
82
+ /** Read live ring-header field. */
83
+ export declare function ringWriteHead(view: DataView, ringOff: number): number;
84
+ export declare function ringReadHead(view: DataView, ringOff: number): number;
85
+ export declare function ringCapacitySlots(view: DataView, ringOff: number): number;
86
+ export declare function ringOverflow(view: DataView, ringOff: number): boolean;
87
+ /** Pending command count = `(write_head - read_head) mod 2^32`. The
88
+ * `>>> 0` keeps the result a u32 in the wrap-around case (rings live for
89
+ * the host's lifetime; 2^32 commands at 50 Hz ≈ 2 years, but the
90
+ * arithmetic should be correct regardless). */
91
+ export declare function pendingCommandCount(view: DataView, ringOff: number): number;
92
+ /** Push a command into the ring from the TS side. Production producer is
93
+ * WASM (via `command_ring.zig`); this is for host-side tests and for
94
+ * symmetric tests across the two sides. Returns `false` on overflow and
95
+ * sets the overflow flag. Payload must be exactly 15 bytes. */
96
+ export declare function pushCommand(view: DataView, ringOff: number, opCode: number, payload: Uint8Array): boolean;
97
+ /** Read one command from the ring. Returns opCode (0 = empty/no
98
+ * command) and fills `outPayload` (15 bytes) with the slot's payload.
99
+ * When 0 is returned, `outPayload` is untouched. */
100
+ export declare function popCommand(view: DataView, ringOff: number, outPayload: Uint8Array): number;
101
+ /** Visit every pending command and bump `read_head` past them. Yields
102
+ * `{ opCode, payload }` per slot where `payload` is a freshly-copied
103
+ * 15-byte Uint8Array (so the handler can hold it past the next pop
104
+ * without aliasing the ring). Stops when the ring is empty. Used by the
105
+ * TS host drain that runs right after `wasm.tick()` returns. */
106
+ export declare function drainCommandRing(view: DataView, ringOff: number, handler: (opCode: number, payload: Uint8Array) => void): number;
107
+ //# sourceMappingURL=command_ring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command_ring.d.ts","sourceRoot":"","sources":["../../../src/core/store/command_ring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,uCAAuC;AACvC,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,yDAAyD;AACzD,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C;;;2BAG2B;AAC3B,eAAO,MAAM,mCAAmC,MAAM,CAAC;AAEvD;yDACyD;AACzD,eAAO,MAAM,2BAA2B;;;;;CAK9B,CAAC;AAEX;;;;gEAIgE;AAChE,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,gEAAgE;AAChE,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE9D;AASD,qBAAa,gBAAiB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI3B;AAED;;;mCAGmC;AACnC,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAU5F;AAED,mCAAmC;AACnC,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAErE;AACD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpE;AACD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AACD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAErE;AAED;;;+CAG+C;AAC/C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;;+DAG+D;AAC/D,wBAAgB,WAAW,CAC1B,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,GACjB,OAAO,CAmCT;AAED;;oDAEoD;AACpD,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAiB1F;AAED;;;;gEAIgE;AAChE,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,KAAK,IAAI,GACpD,MAAM,CAWR"}
@@ -0,0 +1,80 @@
1
+ import { COLUMN_DESCRIPTOR_BYTES, COLUMN_DESCRIPTOR_OFFSETS, ARCHETYPE_DESCRIPTOR_HEADER_BYTES, ARCHETYPE_DESCRIPTOR_OFFSETS, COMPONENT_MASK_WORDS } from './__generated__/abi';
2
+ export { COLUMN_DESCRIPTOR_BYTES, COLUMN_DESCRIPTOR_OFFSETS, ARCHETYPE_DESCRIPTOR_HEADER_BYTES, ARCHETYPE_DESCRIPTOR_OFFSETS, COMPONENT_MASK_WORDS };
3
+ /** type_tag values for `ColumnDescriptor.type_tag`. The order matches the
4
+ * TypedArrayTag union in `packages/std/type_primitives` so a tag's element
5
+ * width is `TYPE_TAG_STRIDE[tag]`. */
6
+ export declare const TYPE_TAG: {
7
+ readonly u8: 0;
8
+ readonly i8: 1;
9
+ readonly u16: 2;
10
+ readonly i16: 3;
11
+ readonly u32: 4;
12
+ readonly i32: 5;
13
+ readonly f32: 6;
14
+ readonly f64: 7;
15
+ };
16
+ export type TypeTagValue = (typeof TYPE_TAG)[keyof typeof TYPE_TAG];
17
+ /** Element width in bytes for each `TYPE_TAG`. Indexed by tag value so a
18
+ * `stride = TYPE_TAG_STRIDE[tag]` lookup is O(1) array access. */
19
+ export declare const TYPE_TAG_STRIDE: Readonly<Record<TypeTagValue, number>>;
20
+ /** String-tag → numeric-tag bridge. `TypedArrayTag` ("u8", "f32", …) is the
21
+ * vocabulary the TS-side component registry speaks; `TypeTagValue` is the
22
+ * numeric enum the SAB descriptors carry on the wire. Bridging happens at
23
+ * the ECS↔SAB seam — Archetype layouts come in as strings, ColumnSpecs go
24
+ * out as integers. */
25
+ export declare const TYPED_ARRAY_TAG_TO_TYPE_TAG: {
26
+ readonly u8: 0;
27
+ readonly i8: 1;
28
+ readonly u16: 2;
29
+ readonly i16: 3;
30
+ readonly u32: 4;
31
+ readonly i32: 5;
32
+ readonly f32: 6;
33
+ readonly f64: 7;
34
+ };
35
+ export interface ColumnDescriptor {
36
+ readonly componentId: number;
37
+ readonly fieldId: number;
38
+ readonly typeTag: TypeTagValue;
39
+ /** Byte offset of the column's first row, measured from SAB byte 0. */
40
+ readonly byteOff: number;
41
+ /** Element width in bytes; should always equal `TYPE_TAG_STRIDE[type_tag]`. */
42
+ readonly stride: number;
43
+ }
44
+ export declare function writeColumnDescriptor(view: DataView, off: number, c: ColumnDescriptor): void;
45
+ export declare function readColumnDescriptor(view: DataView, off: number): ColumnDescriptor;
46
+ /** Number of distinct components the cross-language ECS supports:
47
+ * `COMPONENT_MASK_WORDS × 32` bits in the SAB archetype descriptor mask. The
48
+ * Zig side matches archetypes purely on this mask, so a component whose ID is
49
+ * ≥ this limit would be invisible there while the heap-side `BitSet` stayed
50
+ * correct — silently conflating archetypes that differ only in such a
51
+ * component. `Store.registerComponent` enforces this as a hard registration
52
+ * ceiling so the overflow fails loudly instead (#381). The mask is sized to
53
+ * the `BitSet`'s `INITIAL_WORD_COUNT`, so a registry within the limit never
54
+ * grows a component mask past its initial words. */
55
+ export declare const STORE_DESCRIPTOR_COMPONENT_LIMIT: number;
56
+ export interface ArchetypeDescriptor {
57
+ readonly archetypeId: number;
58
+ /** Component bitmask, `COMPONENT_MASK_WORDS` little-endian u32 words. Word
59
+ * `w` holds component IDs in `[w*32, w*32+32)`. */
60
+ readonly componentMask: readonly number[];
61
+ readonly rowCount: number;
62
+ readonly rowCapacity: number;
63
+ /** Enabled-row count `≤ row_count` (#577 / #599). Per-row entity-scan loops in
64
+ * the WASM sim bound on this so disabled entities (swapped to the tail
65
+ * `[enabled_count, row_count)`) are not simulated; row-indexed cross-entity
66
+ * reads still use `row_count`. */
67
+ readonly enabledCount: number;
68
+ readonly columns: readonly ColumnDescriptor[];
69
+ }
70
+ /** Total bytes a descriptor will occupy, given its column count. Useful for
71
+ * planning the layout descriptor region size up front. */
72
+ export declare function archetypeDescriptorBytes(columnCount: number): number;
73
+ export declare function writeArchetypeDescriptor(view: DataView, off: number, d: ArchetypeDescriptor): number;
74
+ export declare function readArchetypeDescriptor(view: DataView, off: number): ArchetypeDescriptor;
75
+ export declare function writeLayoutDescriptorRegion(view: DataView, regionOff: number, descriptors: readonly ArchetypeDescriptor[]): number;
76
+ export declare function readLayoutDescriptorRegion(view: DataView, regionOff: number, archetypeCount: number): readonly ArchetypeDescriptor[];
77
+ /** Total bytes needed for a layout descriptor region holding these archetypes.
78
+ * Use to size the SAB region between header end and the first column. */
79
+ export declare function layoutDescriptorRegionBytes(descriptors: readonly ArchetypeDescriptor[]): number;
80
+ //# sourceMappingURL=descriptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"descriptor.d.ts","sourceRoot":"","sources":["../../../src/core/store/descriptor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAeH,OAAO,EACN,uBAAuB,EACvB,yBAAyB,EACzB,iCAAiC,EACjC,4BAA4B,EAC5B,oBAAoB,EACpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACN,uBAAuB,EACvB,yBAAyB,EACzB,iCAAiC,EACjC,4BAA4B,EAC5B,oBAAoB,EACpB,CAAC;AAEF;;sCAEsC;AACtC,eAAO,MAAM,QAAQ;;;;;;;;;CASX,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC;AAEpE;kEACkE;AAClE,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CASjE,CAAC;AAEH;;;;sBAIsB;AACtB,eAAO,MAAM,2BAA2B;;;;;;;;;CAS9B,CAAC;AAUX,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAS5F;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAQlF;AAeD;;;;;;;;oDAQoD;AACpD,eAAO,MAAM,gCAAgC,QAA4B,CAAC;AAE1E,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;uDACmD;IACnD,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;sCAGkC;IAClC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC9C;AAED;0DAC0D;AAC1D,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,QAAQ,EACd,GAAG,EAAE,MAAM,EACX,CAAC,EAAE,mBAAmB,GACpB,MAAM,CAoBR;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAuBxF;AAcD,wBAAgB,2BAA2B,CAC1C,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,SAAS,mBAAmB,EAAE,GACzC,MAAM,CAMR;AAED,wBAAgB,0BAA0B,CACzC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACpB,SAAS,mBAAmB,EAAE,CAShC;AAED;yEACyE;AACzE,wBAAgB,2BAA2B,CAC1C,WAAW,EAAE,SAAS,mBAAmB,EAAE,GACzC,MAAM,CAMR"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Entity-index SAB region — `EntityID` → `(archetype_id, row, generation)`
3
+ * lookup table shared with the Zig sim. (#245 / Phase 4 PR 4B)
4
+ *
5
+ * Every hot fixed-update system that needs to resolve an `EntityID`
6
+ * held in a column (e.g. a component field that points at another
7
+ * entity → that entity's columns) has to do
8
+ * `entityId → archetype + row` translation. The plan forbids
9
+ * WASM-into-TS callbacks during `tick()` (§4 / §5.2), so the lookup
10
+ * tables must live in shared memory.
11
+ *
12
+ * Layout:
13
+ *
14
+ * [ length: u32 ] high-water index (count of slots ever issued)
15
+ * [ capacity: u32 ] backing-array length (slots × 1; not bytes)
16
+ * [ _pad0: u32 ] alignment pad to 16 bytes
17
+ * [ _pad1: u32 ] ───
18
+ * [ generations[capacity]: i32 ]
19
+ * [ archetypes[capacity]: i32 ]
20
+ * [ rows[capacity]: i32 ]
21
+ *
22
+ * Sentinels:
23
+ * - `generations[i] = 0` (INITIAL_GENERATION) for never-used slots.
24
+ * Generation grows by 1 on every destroy; an `EntityID`'s generation
25
+ * field matches `generations[index]` iff it's still alive.
26
+ * - `archetypes[i] = -1` (UNASSIGNED) when the slot hasn't been placed
27
+ * into an archetype, OR when the entity is destroyed.
28
+ * - `rows[i] = -1` (UNASSIGNED) on destroy / not-placed.
29
+ *
30
+ * Field width: i32 (signed) on the TS side so `-1` round-trips through
31
+ * `Int32Array` without unsigned coercion. Zig reads as i32 too — bit
32
+ * pattern is identical to u32 `0xFFFFFFFF` for the UNASSIGNED case, and
33
+ * for valid archetype_ids (bounded by `MAX_INDEX = 2^20`) the sign bit
34
+ * is never set, so signed/unsigned interpretation agrees.
35
+ *
36
+ * Region placement: between command ring and descriptor region so the
37
+ * offset is stable across descriptor / column-region growth (same
38
+ * property the command ring gets). Grow path (when entityHighWater
39
+ * exceeds `capacity`) uses `growColumnStore` — slow path, same as
40
+ * descriptor-region overflow.
41
+ *
42
+ * Reading from Zig: see `packages/sim/src/entity_index.zig` for the
43
+ * symmetric reader.
44
+ */
45
+ /** Fixed bytes of the region header (length, capacity, two pad u32s for
46
+ * 16-byte alignment of the i32 arrays that follow). */
47
+ export declare const ENTITY_INDEX_HEADER_BYTES = 16;
48
+ /** Bytes per slot: three i32 columns. */
49
+ export declare const ENTITY_INDEX_BYTES_PER_SLOT = 12;
50
+ /** Byte offsets within the region header. Locked by the
51
+ * `entity_index.test.ts` golden bytes; any change here is a
52
+ * `SIM_ABI_VERSION` bump. */
53
+ export declare const ENTITY_INDEX_HEADER_OFFSETS: {
54
+ readonly length: 0;
55
+ readonly capacity: 4;
56
+ };
57
+ /** Default initial slot count when the engine creates a Store. Matches
58
+ * `MAX_INDEX = (1 << 20) - 1 + 1 = 1_048_576` (the EntityID 20-bit index
59
+ * range, see `entity.ts`), so the region pre-sizes to the entire
60
+ * addressable entity space and `createEntity` can never run out under
61
+ * the default. 1M × 12 B ≈ 12 MiB SAB region — virtual memory only;
62
+ * physical pages allocate lazily via OS page-fault on first touch, so
63
+ * the typical 1000-entity workload pays for ~12 KiB physical even though the
64
+ * virtual reservation is 12 MiB.
65
+ *
66
+ * Tests / benches can pass a smaller `StoreOptions.entityIndexCapacity`
67
+ * to bench tighter reservations. A future PR will replace this with
68
+ * on-demand growth via `growColumnStore` so the default can drop. */
69
+ export declare const ENTITY_INDEX_DEFAULT_CAPACITY: number;
70
+ /** Total region bytes for `capacity` slots: header + 3 i32 columns. */
71
+ export declare function entityIndexRegionBytes(capacity: number): number;
72
+ /** Byte offset of the generations column within the region. */
73
+ export declare function entityIndexGenerationsOff(regionOff: number): number;
74
+ /** Byte offset of the archetype-id column. */
75
+ export declare function entityIndexArchetypesOff(regionOff: number, capacity: number): number;
76
+ /** Byte offset of the row column. */
77
+ export declare function entityIndexRowsOff(regionOff: number, capacity: number): number;
78
+ export declare class EntityIndexError extends Error {
79
+ constructor(message: string);
80
+ }
81
+ /** Initialise the region header at `regionOff`. Sets `length=0` and
82
+ * `capacity=<arg>`. The i32 arrays past the header are left untouched
83
+ * (callers normally allocate the region on a fresh, zero-initialised
84
+ * SAB; generation 0 is the INITIAL_GENERATION sentinel, archetype/row
85
+ * sentinels of 0 are caught by `length=0` so no read reaches them). */
86
+ export declare function initEntityIndexRegion(view: DataView, regionOff: number, capacity: number): void;
87
+ /** Read the region's current length (high-water index, ≤ capacity). */
88
+ export declare function entityIndexLength(view: DataView, regionOff: number): number;
89
+ /** Read the region's current capacity (backing-array length). */
90
+ export declare function entityIndexCapacity(view: DataView, regionOff: number): number;
91
+ /** Update the region's length (called from the engine's Store on every
92
+ * entity allocation that pushes `entityHighWater` past its previous
93
+ * value). */
94
+ export declare function setEntityIndexLength(view: DataView, regionOff: number, length: number): void;
95
+ /** Materialise the three Int32Array views over the region's column data
96
+ * for use by the engine's Store. The views live as long as the
97
+ * SharedArrayBuffer hasn't been reallocated — refresh on `view_stamp`
98
+ * bump (the engine's `_onBufferResized` callback).
99
+ *
100
+ * Three separate views (not a single struct-of-arrays object) because
101
+ * the engine's hot paths benefit from each being a direct typed-array
102
+ * read; V8 specialises `arr[i] = v` aggressively for typed arrays. */
103
+ export declare function buildEntityIndexViews(buffer: ArrayBufferLike, regionOff: number, capacity: number): {
104
+ readonly generations: Int32Array;
105
+ readonly archetypes: Int32Array;
106
+ readonly rows: Int32Array;
107
+ };
108
+ //# sourceMappingURL=entity_index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity_index.d.ts","sourceRoot":"","sources":["../../../src/core/store/entity_index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH;uDACuD;AACvD,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,yCAAyC;AACzC,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C;;6BAE6B;AAC7B,eAAO,MAAM,2BAA2B;;;CAI9B,CAAC;AAEX;;;;;;;;;;;qEAWqE;AACrE,eAAO,MAAM,6BAA6B,QAAU,CAAC;AAErD,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO/D;AAED,+DAA+D;AAC/D,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,8CAA8C;AAC9C,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED,qCAAqC;AACrC,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI3B;AAED;;;;uEAIuE;AACvE,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACd,IAAI,CASN;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,iEAAiE;AACjE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED;;aAEa;AACb,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE5F;AAED;;;;;;;sEAOsE;AACtE,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACd;IACF,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC1B,CAQA"}