@forgeax/engine-ecs 0.1.21 → 0.1.24

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 (104) hide show
  1. package/README.md +83 -17
  2. package/dist/__tests__/changed-block-summary.candidate.test.d.ts +2 -0
  3. package/dist/__tests__/changed-block-summary.candidate.test.d.ts.map +1 -0
  4. package/dist/__tests__/component-requirements.unit.test.d.ts +2 -0
  5. package/dist/__tests__/component-requirements.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/component-version-surface.test.d.ts +2 -0
  7. package/dist/__tests__/component-version-surface.test.d.ts.map +1 -0
  8. package/dist/__tests__/derived-range-writer.contract.test-d.d.ts +2 -0
  9. package/dist/__tests__/derived-range-writer.contract.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/derived-range-writer.contract.test.d.ts +2 -0
  11. package/dist/__tests__/derived-range-writer.contract.test.d.ts.map +1 -0
  12. package/dist/__tests__/structural-evidence.contract.test-d.d.ts +2 -0
  13. package/dist/__tests__/structural-evidence.contract.test-d.d.ts.map +1 -0
  14. package/dist/__tests__/structural-evidence.contract.test.d.ts +2 -0
  15. package/dist/__tests__/structural-evidence.contract.test.d.ts.map +1 -0
  16. package/dist/component-schema.d.ts +17 -0
  17. package/dist/component-schema.d.ts.map +1 -1
  18. package/dist/component.d.ts +16 -7
  19. package/dist/component.d.ts.map +1 -1
  20. package/dist/errors/query-and-component-errors.d.ts +1 -1
  21. package/dist/errors/query-and-component-errors.d.ts.map +1 -1
  22. package/dist/errors/validation-errors.d.ts +24 -0
  23. package/dist/errors/validation-errors.d.ts.map +1 -1
  24. package/dist/errors.d.ts +14 -2
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/externalization/index.mjs.map +1 -1
  27. package/dist/index.mjs +977 -255
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/internal.d.ts +18 -0
  30. package/dist/internal.d.ts.map +1 -0
  31. package/dist/internal.mjs +20 -2
  32. package/dist/internal.mjs.map +1 -1
  33. package/dist/projection/index.d.ts +27 -52
  34. package/dist/projection/index.d.ts.map +1 -1
  35. package/dist/projection/index.mjs +66 -76
  36. package/dist/projection/index.mjs.map +1 -1
  37. package/dist/query/derived-range-writer.d.ts +62 -0
  38. package/dist/query/derived-range-writer.d.ts.map +1 -0
  39. package/dist/query/query.d.ts +1 -1
  40. package/dist/query/query.d.ts.map +1 -1
  41. package/dist/relationship-index.d.ts +8 -0
  42. package/dist/relationship-index.d.ts.map +1 -1
  43. package/dist/shared-ref-store.d.ts +5 -9
  44. package/dist/shared-ref-store.d.ts.map +1 -1
  45. package/dist/shared.mjs.map +1 -1
  46. package/dist/storage/change-detection.d.ts +1 -3
  47. package/dist/storage/change-detection.d.ts.map +1 -1
  48. package/dist/storage/structural-evidence.d.ts +30 -0
  49. package/dist/storage/structural-evidence.d.ts.map +1 -0
  50. package/dist/world-component-access.d.ts +65 -19
  51. package/dist/world-component-access.d.ts.map +1 -1
  52. package/dist/world-component-storage.d.ts +26 -10
  53. package/dist/world-component-storage.d.ts.map +1 -1
  54. package/dist/world-core.d.ts +2 -2
  55. package/dist/world-core.d.ts.map +1 -1
  56. package/dist/world-entity-lifecycle.d.ts.map +1 -1
  57. package/dist/world-internal.d.ts +1 -1
  58. package/dist/world-internal.d.ts.map +1 -1
  59. package/dist/world.d.ts +15 -14
  60. package/dist/world.d.ts.map +1 -1
  61. package/package.json +5 -5
  62. package/src/__tests__/changed-block-summary.candidate.test.ts +57 -0
  63. package/src/__tests__/component-requirements.unit.test.ts +76 -0
  64. package/src/__tests__/component-version-surface.test.ts +45 -0
  65. package/src/__tests__/derived-range-writer.contract.test-d.ts +35 -0
  66. package/src/__tests__/derived-range-writer.contract.test.ts +339 -0
  67. package/src/__tests__/errors.unit.test.ts +21 -5
  68. package/src/__tests__/externalization-render-read-lease.test-d.ts +1 -1
  69. package/src/__tests__/externalization-render-read-lease.unit.test.ts +15 -15
  70. package/src/__tests__/query-storage-trends.perf.test.ts +19 -0
  71. package/src/__tests__/query.unit.test.ts +10 -6
  72. package/src/__tests__/relationship-index.test.ts +147 -1
  73. package/src/__tests__/shared-ref-store.unit.test.ts +3 -8
  74. package/src/__tests__/structural-evidence.contract.test-d.ts +6 -0
  75. package/src/__tests__/structural-evidence.contract.test.ts +49 -0
  76. package/src/component-schema.ts +51 -0
  77. package/src/component.ts +21 -8
  78. package/src/errors/query-and-component-errors.ts +4 -1
  79. package/src/errors/validation-errors.ts +30 -0
  80. package/src/errors.ts +29 -5
  81. package/src/internal.ts +59 -0
  82. package/src/projection/index.ts +68 -149
  83. package/src/query/derived-range-writer.ts +443 -0
  84. package/src/query/query.ts +115 -16
  85. package/src/relationship-index.ts +13 -1
  86. package/src/shared-ref-store.ts +16 -24
  87. package/src/storage/change-detection.ts +0 -15
  88. package/src/storage/structural-evidence.ts +64 -0
  89. package/src/world-component-access.ts +479 -130
  90. package/src/world-component-storage.ts +104 -18
  91. package/src/world-core.ts +2 -2
  92. package/src/world-entity-lifecycle.ts +59 -10
  93. package/src/world-internal.ts +10 -5
  94. package/src/world.ts +97 -53
  95. package/dist/.tsbuildinfo +0 -1
  96. package/dist/__tests__/world-change-journal.unit.test.d.ts +0 -2
  97. package/dist/__tests__/world-change-journal.unit.test.d.ts.map +0 -1
  98. package/dist/__tests__/world-internal-identity.unit.test.d.ts +0 -2
  99. package/dist/__tests__/world-internal-identity.unit.test.d.ts.map +0 -1
  100. package/dist/world-change-journal.d.ts +0 -34
  101. package/dist/world-change-journal.d.ts.map +0 -1
  102. package/src/__tests__/world-change-journal.unit.test.ts +0 -97
  103. package/src/__tests__/world-internal-identity.unit.test.ts +0 -17
  104. package/src/world-change-journal.ts +0 -84
package/dist/index.mjs CHANGED
@@ -21,6 +21,31 @@ function componentDefinition(component) {
21
21
  }
22
22
  return definition;
23
23
  }
24
+ function componentRequirements(component) {
25
+ return definitions.definitions.get(component)?.policy.requires ?? [];
26
+ }
27
+ function expandComponentRequirements(componentDatas) {
28
+ let hasRequirements = false;
29
+ for (const entry of componentDatas) {
30
+ if (componentRequirements(entry.component).length !== 0) {
31
+ hasRequirements = true;
32
+ break;
33
+ }
34
+ }
35
+ if (!hasRequirements) return componentDatas;
36
+ const expanded = [...componentDatas];
37
+ const seen = new Set(expanded.map((entry) => entry.component));
38
+ for (let index = 0; index < expanded.length; index++) {
39
+ const component = expanded[index]?.component;
40
+ if (component === void 0) continue;
41
+ for (const required of componentRequirements(component)) {
42
+ if (seen.has(required)) continue;
43
+ seen.add(required);
44
+ expanded.push({ component: required, data: {} });
45
+ }
46
+ }
47
+ return expanded;
48
+ }
24
49
  function deepFreeze(value) {
25
50
  if (value === null || typeof value !== "object" && typeof value !== "function") {
26
51
  return value;
@@ -654,6 +679,17 @@ var ChangeEpochExhaustedError = class extends Error {
654
679
  this.detail = { epoch };
655
680
  }
656
681
  };
682
+ var DerivedRangeOutOfBoundsError = class extends Error {
683
+ name = "DerivedRangeOutOfBoundsError";
684
+ code = "derived-range-out-of-bounds";
685
+ expected = "a non-negative span-relative range with start + count <= span.length";
686
+ hint = "check start and count against the QuerySpan length, then retry without changing World state";
687
+ detail;
688
+ constructor(start, count, spanLength) {
689
+ super(`Derived range [${start}, ${start + count}) exceeds QuerySpan length ${spanLength}.`);
690
+ this.detail = { start, count, spanLength };
691
+ }
692
+ };
657
693
  var ProtectedResourceError = class extends Error {
658
694
  name = "ProtectedResourceError";
659
695
  code = "resource-protected";
@@ -1254,7 +1290,11 @@ function defineComponent(name, fields, options) {
1254
1290
  registerComponentDefinition(token, {
1255
1291
  fields: frozenFields,
1256
1292
  defaults: frozenDefaults,
1257
- policy: { transient: options?.transient ?? false, meta }
1293
+ policy: {
1294
+ transient: options?.transient ?? false,
1295
+ meta,
1296
+ requires: Object.freeze([...options?.requires ?? []])
1297
+ }
1258
1298
  });
1259
1299
  return Object.freeze(token);
1260
1300
  }
@@ -1385,7 +1425,11 @@ function defineRelationship(options) {
1385
1425
  };
1386
1426
  const target = defineComponent(options.targetName, targetFields);
1387
1427
  const sourceFields = { [options.sourceField]: { type: "entity" } };
1388
- const source = defineComponent(options.sourceName, sourceFields);
1428
+ const source = defineComponent(
1429
+ options.sourceName,
1430
+ sourceFields,
1431
+ options.sourceRequires === void 0 ? void 0 : { requires: options.sourceRequires }
1432
+ );
1389
1433
  roles.set(source, {
1390
1434
  kind: "source",
1391
1435
  target,
@@ -1563,51 +1607,10 @@ function entityGeneration(entity) {
1563
1607
  return unpackGen(entity);
1564
1608
  }
1565
1609
 
1566
- // src/world-change-journal.ts
1567
- var EMPTY_CHANGES = Object.freeze([]);
1568
- var WorldChangeJournal = class {
1569
- constructor(capacity = 65536) {
1570
- this.capacity = capacity;
1571
- if (!Number.isSafeInteger(capacity) || capacity <= 0) {
1572
- throw new RangeError("WorldChangeJournal capacity must be a positive safe integer");
1573
- }
1574
- this.records = new Array(capacity);
1575
- }
1576
- capacity;
1577
- records;
1578
- nextSequence = 1;
1579
- cursor() {
1580
- return this.nextSequence - 1;
1581
- }
1582
- append(change) {
1583
- const sequence = this.nextSequence;
1584
- this.nextSequence += 1;
1585
- this.records[(sequence - 1) % this.capacity] = { ...change, sequence };
1586
- return sequence;
1587
- }
1588
- readAfter(cursor) {
1589
- const latest = this.cursor();
1590
- if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > latest) {
1591
- throw new RangeError(`WorldChangeJournal cursor ${cursor} is outside 0..${latest}`);
1592
- }
1593
- const oldestAvailable = Math.max(1, latest - this.capacity + 1);
1594
- if (cursor < oldestAvailable - 1) {
1595
- return { status: "overflow", cursor: latest, oldestAvailable };
1596
- }
1597
- if (cursor === latest) {
1598
- return { status: "ok", cursor: latest, records: EMPTY_CHANGES };
1599
- }
1600
- const records = [];
1601
- for (let sequence = cursor + 1; sequence <= latest; sequence += 1) {
1602
- const record = this.records[(sequence - 1) % this.capacity];
1603
- if (record === void 0 || record.sequence !== sequence) {
1604
- return { status: "overflow", cursor: latest, oldestAvailable };
1605
- }
1606
- records.push(record);
1607
- }
1608
- return { status: "ok", cursor: latest, records };
1609
- }
1610
- };
1610
+ // src/internal.ts
1611
+ var DERIVED_WRITER = /* @__PURE__ */ Symbol.for(
1612
+ "forgeax.ecs.query.derivedWriter"
1613
+ );
1611
1614
 
1612
1615
  // src/storage/archetype.ts
1613
1616
  var INITIAL_CAPACITY = 64;
@@ -2039,10 +2042,246 @@ function markComponentChanged(graph, location, entity, componentId2, epoch) {
2039
2042
  const tableRow2 = archetype.rows[location.archetypeRow] ?? -1;
2040
2043
  epochs.changed[tableRow2] = epoch();
2041
2044
  }
2042
- function readTableEntityRange(table, rowStart, rowCount) {
2043
- const entities = table.storage.get(componentId(Entity))?.fields.get("self")?.view;
2044
- if (entities === void 0) return [];
2045
- return entities.subarray(rowStart, rowStart + rowCount);
2045
+ function createDerivedRangeWriter(world, component, source) {
2046
+ let boundEpoch = -1;
2047
+ let bindingTables = [];
2048
+ let bindings = [];
2049
+ let bindingByTable = /* @__PURE__ */ new Map();
2050
+ let runStartBuffers = [];
2051
+ let runCountBuffers = [];
2052
+ const rebind = () => {
2053
+ const tables = source.tables();
2054
+ const nextBindings = [];
2055
+ const nextRunStartBuffers = [];
2056
+ const nextRunCountBuffers = [];
2057
+ for (const table of tables) {
2058
+ nextBindings.push({
2059
+ tableId: table.id,
2060
+ entities: table.storage.get(componentId(Entity))?.fields.get("self")?.view ?? new Uint32Array(0),
2061
+ read: buildWholeColumnShape(table, source.readComponents),
2062
+ write: buildWholeColumnShape(table, source.writeComponents),
2063
+ rowCapacity: table.size
2064
+ });
2065
+ const runCapacity = Math.max(1, table.size);
2066
+ nextRunStartBuffers.push(new Int32Array(runCapacity));
2067
+ nextRunCountBuffers.push(new Int32Array(runCapacity));
2068
+ }
2069
+ bindingTables = tables;
2070
+ bindings = nextBindings;
2071
+ bindingByTable = /* @__PURE__ */ new Map();
2072
+ for (let index = 0; index < nextBindings.length; index += 1) {
2073
+ const binding = nextBindings[index];
2074
+ if (binding !== void 0) bindingByTable.set(binding.tableId, index);
2075
+ }
2076
+ runStartBuffers = nextRunStartBuffers;
2077
+ runCountBuffers = nextRunCountBuffers;
2078
+ boundEpoch = source.structureEpoch();
2079
+ };
2080
+ rebind();
2081
+ const writer = {
2082
+ get bindings() {
2083
+ if (boundEpoch !== source.structureEpoch()) rebind();
2084
+ return bindings;
2085
+ },
2086
+ locateEntity(entity, cursor) {
2087
+ if (world.execution.health === "poisoned") return false;
2088
+ if (boundEpoch !== source.structureEpoch()) rebind();
2089
+ const archetype = world[worldInternal].getEntityArchetype(entity);
2090
+ if (archetype === void 0) return false;
2091
+ const bindingIndex = bindingByTable.get(archetype.tableId);
2092
+ if (bindingIndex === void 0) return false;
2093
+ const record = world[worldInternal].getRecords()[entity & 16777215];
2094
+ if (record === void 0 || record.archetypeId !== archetype.id) return false;
2095
+ const row = archetype.rows[record.archetypeRow];
2096
+ const binding = bindings[bindingIndex];
2097
+ if (binding === void 0 || row === void 0 || row < 0 || row >= binding.rowCapacity) {
2098
+ return false;
2099
+ }
2100
+ cursor.bindingIndex = bindingIndex;
2101
+ cursor.row = row;
2102
+ return true;
2103
+ },
2104
+ publishChangedRows(bindingIndex, changed) {
2105
+ if (world.execution.health === "poisoned") {
2106
+ return err(new WorldPoisonedError(world.identity, world.execution.fault));
2107
+ }
2108
+ if (boundEpoch !== source.structureEpoch()) rebind();
2109
+ const binding = bindings[bindingIndex];
2110
+ const table = bindingTables[bindingIndex];
2111
+ const runStarts = runStartBuffers[bindingIndex];
2112
+ const runCounts = runCountBuffers[bindingIndex];
2113
+ if (binding === void 0 || table === void 0 || runStarts === void 0 || runCounts === void 0 || !Number.isSafeInteger(bindingIndex) || bindingIndex < 0 || changed.length < binding.rowCapacity || table.storage.get(componentId(component)) === void 0) {
2114
+ return err(new DerivedRangeOutOfBoundsError(0, changed.length, binding?.rowCapacity ?? 0));
2115
+ }
2116
+ let runCount = 0;
2117
+ let runStart = -1;
2118
+ for (let row = 0; row < binding.rowCapacity; row += 1) {
2119
+ if ((changed[row] ?? 0) !== 0) {
2120
+ if (runStart < 0) runStart = row;
2121
+ } else if (runStart >= 0) {
2122
+ runStarts[runCount] = runStart;
2123
+ runCounts[runCount] = row - runStart;
2124
+ runCount += 1;
2125
+ runStart = -1;
2126
+ }
2127
+ }
2128
+ if (runStart >= 0) {
2129
+ runStarts[runCount] = runStart;
2130
+ runCounts[runCount] = binding.rowCapacity - runStart;
2131
+ runCount += 1;
2132
+ }
2133
+ if (runCount === 0) return ok(void 0);
2134
+ const previousEpoch = world[worldInternal].getMutationEpoch();
2135
+ let epoch;
2136
+ try {
2137
+ epoch = world[worldInternal].nextMutationEpoch();
2138
+ const componentIdentifier = componentId(component);
2139
+ for (let index = 0; index < runCount; index += 1) {
2140
+ world[worldInternal].publishDerivedRange(
2141
+ table,
2142
+ componentIdentifier,
2143
+ runStarts[index] ?? 0,
2144
+ runCounts[index] ?? 0,
2145
+ epoch
2146
+ );
2147
+ }
2148
+ changed.fill(0, 0, binding.rowCapacity);
2149
+ return ok(void 0);
2150
+ } catch (cause) {
2151
+ world[worldInternal].restoreMutationEpoch(previousEpoch);
2152
+ world[worldInternal].poisonExecution({
2153
+ code: "shared-kernel-failed",
2154
+ kernelName: `derived-range:${component.name}:publish`,
2155
+ cause,
2156
+ partialWrite: true,
2157
+ retryable: false
2158
+ });
2159
+ return err(new SharedKernelFailureError(component.name, world.identity, cause, true));
2160
+ }
2161
+ },
2162
+ writeRange(bindingIndex, base, start, count, kernel, context) {
2163
+ if (world.execution.health === "poisoned") {
2164
+ return err(new WorldPoisonedError(world.identity, world.execution.fault));
2165
+ }
2166
+ if (boundEpoch !== source.structureEpoch()) rebind();
2167
+ const binding = bindings[bindingIndex];
2168
+ const table = bindingTables[bindingIndex];
2169
+ if (binding === void 0 || table === void 0 || !Number.isSafeInteger(bindingIndex) || !Number.isSafeInteger(base) || !Number.isSafeInteger(start) || !Number.isSafeInteger(count) || bindingIndex < 0 || base < 0 || start < 0 || count < 0 || base + start + count > binding.rowCapacity || table.storage.get(componentId(component)) === void 0) {
2170
+ return err(new DerivedRangeOutOfBoundsError(start, count, binding?.rowCapacity ?? 0));
2171
+ }
2172
+ if (count === 0) return ok(void 0);
2173
+ const previousEpoch = world[worldInternal].getMutationEpoch();
2174
+ let epoch;
2175
+ try {
2176
+ epoch = world[worldInternal].nextMutationEpoch();
2177
+ } catch (cause) {
2178
+ return err(cause);
2179
+ }
2180
+ try {
2181
+ kernel(binding, base, start, count, context);
2182
+ world[worldInternal].publishDerivedRange(
2183
+ table,
2184
+ componentId(component),
2185
+ base + start,
2186
+ count,
2187
+ epoch
2188
+ );
2189
+ return ok(void 0);
2190
+ } catch (cause) {
2191
+ world[worldInternal].restoreMutationEpoch(previousEpoch);
2192
+ world[worldInternal].poisonExecution({
2193
+ code: "shared-kernel-failed",
2194
+ kernelName: `derived-range:${component.name}`,
2195
+ cause,
2196
+ partialWrite: true,
2197
+ retryable: false
2198
+ });
2199
+ return err(new SharedKernelFailureError(component.name, world.identity, cause, true));
2200
+ }
2201
+ },
2202
+ probeAndCommitRange(bindingIndex, base, start, count, probe, commit, context) {
2203
+ if (world.execution.health === "poisoned") {
2204
+ return err(new WorldPoisonedError(world.identity, world.execution.fault));
2205
+ }
2206
+ if (boundEpoch !== source.structureEpoch()) rebind();
2207
+ const binding = bindings[bindingIndex];
2208
+ const table = bindingTables[bindingIndex];
2209
+ const runStarts = runStartBuffers[bindingIndex];
2210
+ const runCounts = runCountBuffers[bindingIndex];
2211
+ if (binding === void 0 || table === void 0 || runStarts === void 0 || runCounts === void 0 || !Number.isSafeInteger(bindingIndex) || !Number.isSafeInteger(base) || !Number.isSafeInteger(start) || !Number.isSafeInteger(count) || bindingIndex < 0 || base < 0 || start < 0 || count < 0 || base + start + count > binding.rowCapacity || table.storage.get(componentId(component)) === void 0) {
2212
+ return err(new DerivedRangeOutOfBoundsError(start, count, binding?.rowCapacity ?? 0));
2213
+ }
2214
+ if (count === 0) return ok(void 0);
2215
+ const previousEpoch = world[worldInternal].getMutationEpoch();
2216
+ let runCount = 0;
2217
+ let epoch = 0;
2218
+ let epochReserved = false;
2219
+ let runStart = -1;
2220
+ try {
2221
+ for (let offset = 0; offset < count; offset += 1) {
2222
+ const row = base + start + offset;
2223
+ const changed = probe(binding, row, context);
2224
+ if (changed) {
2225
+ if (!epochReserved) {
2226
+ try {
2227
+ epoch = world[worldInternal].nextMutationEpoch();
2228
+ epochReserved = true;
2229
+ } catch (cause) {
2230
+ return err(cause);
2231
+ }
2232
+ }
2233
+ if (runStart < 0) runStart = row;
2234
+ commit(binding, row, context);
2235
+ } else if (runStart >= 0) {
2236
+ runStarts[runCount] = runStart;
2237
+ runCounts[runCount] = row - runStart;
2238
+ runCount += 1;
2239
+ runStart = -1;
2240
+ }
2241
+ }
2242
+ if (runStart >= 0) {
2243
+ runStarts[runCount] = runStart;
2244
+ runCounts[runCount] = base + start + count - runStart;
2245
+ runCount += 1;
2246
+ }
2247
+ if (runCount > 0) {
2248
+ const componentIdentifier = componentId(component);
2249
+ for (let index = 0; index < runCount; index += 1) {
2250
+ const runStartValue = runStarts[index] ?? 0;
2251
+ const runLength = runCounts[index] ?? 0;
2252
+ world[worldInternal].publishDerivedRange(
2253
+ table,
2254
+ componentIdentifier,
2255
+ runStartValue,
2256
+ runLength,
2257
+ epoch
2258
+ );
2259
+ }
2260
+ }
2261
+ return ok(void 0);
2262
+ } catch (cause) {
2263
+ if (epochReserved) world[worldInternal].restoreMutationEpoch(previousEpoch);
2264
+ world[worldInternal].poisonExecution({
2265
+ code: "shared-kernel-failed",
2266
+ kernelName: `derived-range:${component.name}:probe`,
2267
+ cause,
2268
+ partialWrite: true,
2269
+ retryable: false
2270
+ });
2271
+ return err(new SharedKernelFailureError(component.name, world.identity, cause, true));
2272
+ }
2273
+ }
2274
+ };
2275
+ return writer;
2276
+ }
2277
+ function buildWholeColumnShape(table, components) {
2278
+ const shape = {};
2279
+ for (const component of components) {
2280
+ const fields = table.storage.get(componentId(component))?.fields;
2281
+ if (fields === void 0) continue;
2282
+ for (const [fieldName, column] of fields) shape[fieldName] = column.view;
2283
+ }
2284
+ return shape;
2046
2285
  }
2047
2286
 
2048
2287
  // src/query/query.ts
@@ -2133,7 +2372,13 @@ var QueryRowFacade = class _QueryRowFacade {
2133
2372
  mut(component) {
2134
2373
  const current = this.get(component);
2135
2374
  if (current === void 0) throw new Error(`Query row lacks ${component.name}.`);
2136
- this.world[worldInternal].markComponentChanged(this.entity, componentId(component));
2375
+ if (isRelationshipTarget(component)) {
2376
+ throw new RelationshipTargetReadonlyError(component.name, "query row");
2377
+ }
2378
+ const relationshipSource2 = relationshipRole(component)?.kind === "source";
2379
+ if (!relationshipSource2) {
2380
+ this.world[worldInternal].markComponentChanged(this.entity, componentId(component));
2381
+ }
2137
2382
  return new Proxy(current, {
2138
2383
  set: (target, property, value) => {
2139
2384
  if (typeof property !== "string") return false;
@@ -2154,7 +2399,7 @@ var QuerySpanFacade = class {
2154
2399
  this.rowStart = rowStart;
2155
2400
  this.length = length;
2156
2401
  const entityColumn = table.storage.get(componentId(Entity))?.fields.get("self");
2157
- this.entities = entityColumn?.view.subarray(rowStart, rowStart + length) ?? new Uint32Array(0);
2402
+ this.entities = entityColumn === void 0 ? new Uint32Array(0) : rowStart === 0 && length === entityColumn.view.length ? entityColumn.view : entityColumn.view.subarray(rowStart, rowStart + length);
2158
2403
  }
2159
2404
  world;
2160
2405
  table;
@@ -2170,6 +2415,9 @@ var QuerySpanFacade = class {
2170
2415
  );
2171
2416
  }
2172
2417
  mut(component) {
2418
+ if (relationshipRole(component) !== void 0) {
2419
+ throw new RelationshipTargetReadonlyError(component.name, "query span");
2420
+ }
2173
2421
  this.world[worldInternal].markComponentRangeChanged(
2174
2422
  this.table,
2175
2423
  componentId(component),
@@ -2185,7 +2433,7 @@ var QuerySpanFacade = class {
2185
2433
  }
2186
2434
  };
2187
2435
  function makeManagedColumnReader(view, length, fieldType) {
2188
- const slots = view.subarray(0, length);
2436
+ const slots = view.length === length ? view : view.subarray(0, length);
2189
2437
  return Object.freeze({
2190
2438
  length,
2191
2439
  get(i) {
@@ -2201,7 +2449,7 @@ function buildColumnShape(table, component, rowStart, rowCount) {
2201
2449
  for (const [fieldName, column] of fields) {
2202
2450
  const start = rowStart * column.arity;
2203
2451
  const end = start + rowCount * column.arity;
2204
- const view = column.view.subarray(start, end);
2452
+ const view = start === 0 && end === column.view.length ? column.view : column.view.subarray(start, end);
2205
2453
  const fieldType = componentSchema(component)[fieldName];
2206
2454
  shape[fieldName] = fieldType !== void 0 && isManagedField(fieldType) ? makeManagedColumnReader(view, view.length, fieldType) : view;
2207
2455
  }
@@ -2216,6 +2464,7 @@ var ExecutableQuery = class {
2216
2464
  compiled;
2217
2465
  matchedArchetypes = [];
2218
2466
  matchedTables = [];
2467
+ matchedTableObjects = [];
2219
2468
  lastGraphGeneration = -1;
2220
2469
  lastObservedEpoch = 0;
2221
2470
  active = false;
@@ -2306,7 +2555,8 @@ var ExecutableQuery = class {
2306
2555
  query.refreshMatches();
2307
2556
  const structureEpoch = query.world[worldInternal].getStructureEpoch();
2308
2557
  const upperBound = query.world[worldInternal].getMutationEpoch();
2309
- let index = 0;
2558
+ let tableIndex = 0;
2559
+ let rowIndex = 0;
2310
2560
  let finished = false;
2311
2561
  const close = (commit) => {
2312
2562
  if (finished) return;
@@ -2324,12 +2574,37 @@ var ExecutableQuery = class {
2324
2574
  query.world[worldInternal].getStructureEpoch()
2325
2575
  );
2326
2576
  }
2327
- while (index < query.matchedTables.length) {
2328
- const table = query.world[worldInternal].getGraph().tables[query.matchedTables[index++] ?? -1];
2329
- if (table === void 0 || table.size === 0) continue;
2577
+ while (tableIndex < query.matchedTables.length) {
2578
+ const table = query.world[worldInternal].getGraph().tables[query.matchedTables[tableIndex] ?? -1];
2579
+ if (table === void 0 || table.size === 0) {
2580
+ tableIndex += 1;
2581
+ rowIndex = 0;
2582
+ continue;
2583
+ }
2584
+ if (query.compiled.changedIds.length === 0 && query.compiled.addedIds.length === 0) {
2585
+ tableIndex += 1;
2586
+ rowIndex = 0;
2587
+ return {
2588
+ done: false,
2589
+ value: new QuerySpanFacade(query.world, table, 0, table.size)
2590
+ };
2591
+ }
2592
+ while (rowIndex < table.size && !query.denseChangeMatches(table, rowIndex, upperBound)) {
2593
+ rowIndex += 1;
2594
+ }
2595
+ if (rowIndex >= table.size) {
2596
+ tableIndex += 1;
2597
+ rowIndex = 0;
2598
+ continue;
2599
+ }
2600
+ const start = rowIndex;
2601
+ rowIndex += 1;
2602
+ while (rowIndex < table.size && query.denseChangeMatches(table, rowIndex, upperBound)) {
2603
+ rowIndex += 1;
2604
+ }
2330
2605
  return {
2331
2606
  done: false,
2332
- value: new QuerySpanFacade(query.world, table, 0, table.size)
2607
+ value: new QuerySpanFacade(query.world, table, start, rowIndex - start)
2333
2608
  };
2334
2609
  }
2335
2610
  close(true);
@@ -2343,6 +2618,25 @@ var ExecutableQuery = class {
2343
2618
  }
2344
2619
  });
2345
2620
  }
2621
+ [DERIVED_WRITER](component) {
2622
+ const reason = this.spanUnavailableReason();
2623
+ if (reason !== void 0) return err(new QuerySpanUnavailableError(reason));
2624
+ return ok(
2625
+ createDerivedRangeWriter(
2626
+ this.world,
2627
+ component,
2628
+ {
2629
+ structureEpoch: () => this.world[worldInternal].getStructureEpoch(),
2630
+ tables: () => {
2631
+ this.refreshMatches();
2632
+ return this.matchedTableObjects;
2633
+ },
2634
+ readComponents: this.compiled.descriptor.read ?? [],
2635
+ writeComponents: this.compiled.descriptor.write ?? []
2636
+ }
2637
+ )
2638
+ );
2639
+ }
2346
2640
  combinations(k = 2) {
2347
2641
  const query = this;
2348
2642
  return {
@@ -2393,6 +2687,10 @@ var ExecutableQuery = class {
2393
2687
  if (this.lastGraphGeneration === graph.generation) return;
2394
2688
  this.matchedArchetypes = graph.archetypes.filter((archetype) => this.archetypeMatches(archetype)).map((archetype) => archetype.id);
2395
2689
  this.matchedTables = graph.tables.filter((table) => this.tableMatches(table)).map((table) => table.id);
2690
+ this.matchedTableObjects = this.matchedTables.flatMap((id) => {
2691
+ const table = graph.tables[id];
2692
+ return table === void 0 ? [] : [table];
2693
+ });
2396
2694
  this.lastGraphGeneration = graph.generation;
2397
2695
  }
2398
2696
  archetypeMatches(archetype) {
@@ -2431,11 +2729,23 @@ var ExecutableQuery = class {
2431
2729
  }
2432
2730
  return true;
2433
2731
  }
2732
+ denseChangeMatches(table, row, upperBound) {
2733
+ for (const componentId2 of this.compiled.changedIds) {
2734
+ const epoch = table.storage.get(componentId2)?.epochs.changed[row] ?? 0;
2735
+ if (epoch <= this.lastObservedEpoch || epoch > upperBound) return false;
2736
+ }
2737
+ for (const componentId2 of this.compiled.addedIds) {
2738
+ const epoch = table.storage.get(componentId2)?.epochs.added[row] ?? 0;
2739
+ if (epoch <= this.lastObservedEpoch || epoch > upperBound) return false;
2740
+ }
2741
+ return true;
2742
+ }
2434
2743
  spanUnavailableReason() {
2435
2744
  if (this.compiled.sparseRoute) return "sparse-component";
2436
2745
  if ((this.compiled.descriptor.optional?.length ?? 0) > 0) return "optional-data";
2437
- if ((this.compiled.descriptor.changed?.length ?? 0) > 0 || (this.compiled.descriptor.added?.length ?? 0) > 0)
2438
- return "row-change-filter";
2746
+ if ((this.compiled.descriptor.write ?? []).some((component) => relationshipRole(component))) {
2747
+ return "relationship-component";
2748
+ }
2439
2749
  return void 0;
2440
2750
  }
2441
2751
  };
@@ -3394,8 +3704,8 @@ var ComponentStorage = class {
3394
3704
  get sharedRefs() {
3395
3705
  return this.state.sharedRefs;
3396
3706
  }
3397
- routeError(err11, ctx) {
3398
- this.state.routeError(err11, ctx);
3707
+ routeError(err12, ctx) {
3708
+ this.state.routeError(err12, ctx);
3399
3709
  }
3400
3710
  readArrayView(arch, component, row, fieldName) {
3401
3711
  const fieldCols = this.table(arch).storage.get(componentId(component))?.fields;
@@ -3423,6 +3733,52 @@ var ComponentStorage = class {
3423
3733
  const elementCount = countCol?.view[row] ?? 0;
3424
3734
  return reinterpretSlotBytes(liveBytes, arrayMeta.elementType, elementCount);
3425
3735
  }
3736
+ /**
3737
+ * Read the live length of an array field without materialising a typed view.
3738
+ * Relationship consumers use this narrow storage seam for hot reverse-list
3739
+ * walks; the target array remains owned by the ECS column and no snapshot is
3740
+ * created per entity.
3741
+ */
3742
+ readArrayLength(arch, component, row, fieldName) {
3743
+ const fieldCols = this.table(arch).storage.get(componentId(component))?.fields;
3744
+ if (fieldCols === void 0) return void 0;
3745
+ const arrayMeta = componentDefinition(component).fields[fieldName]?.arrayMeta;
3746
+ if (arrayMeta === void 0) return void 0;
3747
+ if (arrayMeta.length !== void 0) return arrayMeta.length;
3748
+ const count = fieldCols.get(arrayCountColumnName(fieldName))?.view[row];
3749
+ return typeof count === "number" ? count : 0;
3750
+ }
3751
+ /**
3752
+ * Read one array element directly from its backing column/BufferPool slot.
3753
+ * The relationship target vocabulary is `array<entity>`, so its hot path
3754
+ * decodes the packed u32 in-place and does not allocate a TypedArray view.
3755
+ */
3756
+ readArrayElement(arch, component, row, fieldName, index) {
3757
+ if (!Number.isSafeInteger(index) || index < 0) return void 0;
3758
+ const fieldCols = this.table(arch).storage.get(componentId(component))?.fields;
3759
+ if (fieldCols === void 0) return void 0;
3760
+ const arrayMeta = componentDefinition(component).fields[fieldName]?.arrayMeta;
3761
+ if (arrayMeta === void 0) return void 0;
3762
+ const length = this.readArrayLength(arch, component, row, fieldName);
3763
+ if (length === void 0 || index >= length) return void 0;
3764
+ const col = fieldCols.get(fieldName);
3765
+ if (col === void 0) return void 0;
3766
+ if (arrayMeta.length !== void 0) {
3767
+ return col.view[row * col.arity + index];
3768
+ }
3769
+ const slotId = col.view[row];
3770
+ const bytes = this.bufferPool.view(slotId);
3771
+ if (arrayMeta.elementType === "entity") {
3772
+ const byteOffset = index * 4;
3773
+ if (byteOffset + 4 > bytes.byteLength) return void 0;
3774
+ return ((bytes[byteOffset] ?? 0) | (bytes[byteOffset + 1] ?? 0) << 8 | (bytes[byteOffset + 2] ?? 0) << 16 | (bytes[byteOffset + 3] ?? 0) << 24) >>> 0;
3775
+ }
3776
+ return this.readArrayElementAt(bytes, index, arrayMeta.elementType);
3777
+ }
3778
+ /** Read one scalar field without constructing a component shape. */
3779
+ readFieldValue(arch, component, row, fieldName) {
3780
+ return this.table(arch).storage.get(componentId(component))?.fields.get(fieldName)?.view[row];
3781
+ }
3426
3782
  // ──────────────────────────────────────────────────────────────────────────
3427
3783
  // Internal — archetype data read/write
3428
3784
  // ──────────────────────────────────────────────────────────────────────────
@@ -3487,7 +3843,7 @@ var ComponentStorage = class {
3487
3843
  if (!col) return;
3488
3844
  col.view[row] = handle;
3489
3845
  }
3490
- writeRow(arch, component, row, value) {
3846
+ writeRow(arch, component, row, value, options) {
3491
3847
  const localId = componentId(component);
3492
3848
  const fieldCols = this.table(arch).storage.get(localId)?.fields;
3493
3849
  if (!fieldCols) {
@@ -3536,6 +3892,12 @@ var ComponentStorage = class {
3536
3892
  } else {
3537
3893
  const arrayMeta = componentDefinition(component).fields[fieldName]?.arrayMeta;
3538
3894
  if (arrayMeta !== void 0) {
3895
+ if (options?.skipVariableArrayInitialization === true && arrayMeta.length === void 0) {
3896
+ col.view[row] = 0;
3897
+ const countCol = fieldCols.get(arrayCountColumnName(fieldName));
3898
+ if (countCol !== void 0) countCol.view[row] = 0;
3899
+ continue;
3900
+ }
3539
3901
  this.writeArrayField(arch, component, row, fieldName, fieldType, arrayMeta, raw);
3540
3902
  } else {
3541
3903
  col.view[row] = raw;
@@ -3762,11 +4124,11 @@ var ComponentStorage = class {
3762
4124
  * the EcsError contract; this helper only attaches the systemName context
3763
4125
  * so AI users can correlate the error with the holder component / field.
3764
4126
  */
3765
- routeArrayError(err11, componentName, fieldName) {
4127
+ routeArrayError(err12, componentName, fieldName) {
3766
4128
  const ctx = {
3767
4129
  systemName: `World.write (${componentName}.${fieldName})`
3768
4130
  };
3769
- this.routeError(err11, ctx);
4131
+ this.routeError(err12, ctx);
3770
4132
  }
3771
4133
  /**
3772
4134
  * Write the value of an `array<T>` / `array<T,N>` field at `row` (D-3 +
@@ -3911,20 +4273,19 @@ var ComponentStorage = class {
3911
4273
  return readArrayElementAt(bytes, idx, elementType);
3912
4274
  }
3913
4275
  /**
3914
- * Materialise a fresh `TypedArray` snapshot for an `array<T,N>` /
3915
- * `array<T>` field at `row` (plan-strategy §2.2 -- read-only snapshot
3916
- * contract; mutations route through `world.set` / `world.push` /
3917
- * `world.pop`).
4276
+ * Materialise a fresh `TypedArray` view for an `array<T,N>` / `array<T>`
4277
+ * field at `row`. This is the internal row-storage path: the returned view
4278
+ * aliases the live column or BufferPool slot. `WorldComponentAccess.get`
4279
+ * detaches relationship target arrays at the public boundary; internal ECS
4280
+ * relationship maintenance and Scene traversal keep this zero-copy path.
3918
4281
  *
3919
4282
  * **Transient view contract (feat-20260602):** for fixed `array<T,N>`
3920
4283
  * columns the returned `TypedArray` aliases the inline column buffer
3921
4284
  * directly (`col.view.subarray(row * arity, ...)`); for variable
3922
4285
  * `array<T>` columns it aliases the live `BufferPool` slot bytes
3923
- * (zero-copy; `pool.view(slotId)` is the SSOT byte region). In both
3924
- * cases the view is valid only until the next structural change. Writing
3925
- * into the snapshot is undefined behaviour -- the contract is read-only
3926
- * and the implementation may switch to a copy in the future without
3927
- * breaking AI users who consume only `length` / index reads.
4286
+ * (zero-copy; `pool.view(slotId)` is the SSOT byte region). In both cases
4287
+ * the view is valid only until the next structural change. Internal callers
4288
+ * must not write through it except via the owner mutation helpers.
3928
4289
  *
3929
4290
  * For variable arrays the typed-array length matches the live count from
3930
4291
  * the sidecar `<fieldName>:count` column; for fixed arrays it matches the
@@ -4183,6 +4544,9 @@ function relationshipPayloadWrites(data) {
4183
4544
  return true;
4184
4545
  });
4185
4546
  }
4547
+ function detachRelationshipTargetArray(value) {
4548
+ return value instanceof Uint32Array ? value.slice() : value;
4549
+ }
4186
4550
  var WorldComponentAccess = class {
4187
4551
  constructor(state) {
4188
4552
  this.state = state;
@@ -4211,8 +4575,8 @@ var WorldComponentAccess = class {
4211
4575
  get uniqueRefs() {
4212
4576
  return this.state.uniqueRefs;
4213
4577
  }
4214
- routeError(err11, ctx) {
4215
- this.state.routeError(err11, ctx);
4578
+ routeError(err12, ctx) {
4579
+ this.state.routeError(err12, ctx);
4216
4580
  }
4217
4581
  relationshipIndex(component) {
4218
4582
  if (relationshipRole(component)?.kind !== "source") return void 0;
@@ -4223,16 +4587,19 @@ var WorldComponentAccess = class {
4223
4587
  }
4224
4588
  return index;
4225
4589
  }
4226
- /** Read the World-owned materialized target array; never consults a shadow list. */
4590
+ /**
4591
+ * Read the World-owned materialized target array without a public snapshot.
4592
+ * This is an internal relationship-owner path: it borrows the live
4593
+ * `Uint32Array` so attach/detach stays zero-copy. Public `World.get` detaches
4594
+ * target arrays before returning them to callers.
4595
+ */
4227
4596
  relationshipTargetEntries(source, target) {
4228
4597
  const role = relationshipRole(source);
4229
4598
  if (role?.kind !== "source") return [];
4230
4599
  const mirror = relationshipMirror(source);
4231
4600
  if (mirror === void 0) return [];
4232
- const result = this.get(target, mirror);
4233
- if (!result.ok) return [];
4234
- const entries = result.value[role.targetField];
4235
- return entries !== void 0 && typeof entries === "object" ? entries : [];
4601
+ const entries = this._getArrayView(target, mirror, role.targetField);
4602
+ return entries === void 0 ? [] : entries;
4236
4603
  }
4237
4604
  markComponentAdded(entity, component) {
4238
4605
  this.state.markComponentAdded(entity, componentId(component));
@@ -4312,8 +4679,7 @@ var WorldComponentAccess = class {
4312
4679
  const targetRecord = this.records[entityIndex(target)];
4313
4680
  const actualGeneration = targetRecord?.generation ?? -1;
4314
4681
  const targetLive = this.recordIsLive(targetRecord, entityGeneration(target));
4315
- const holderIsPending = holder === null || pendingEntities?.has(holder) === true;
4316
- if (!targetIsPending && !targetLive && !holderIsPending) {
4682
+ if (!targetIsPending && !targetLive) {
4317
4683
  return err(
4318
4684
  new StaleEntityError(target, entityIndex(target), entityGeneration(target), {
4319
4685
  operation: "relationship-insert",
@@ -4370,7 +4736,14 @@ var WorldComponentAccess = class {
4370
4736
  current = next;
4371
4737
  }
4372
4738
  }
4373
- /** Prepare the target side before a source archetype mutation commits. */
4739
+ /**
4740
+ * Reserve target-side relationship capacity without touching World columns.
4741
+ *
4742
+ * A missing mirror reserves its first slot before the mirror archetype is
4743
+ * created. An existing mirror either reserves an empty slot or grows its
4744
+ * existing BufferPool slot; both failure paths return before any component,
4745
+ * relationship index, or ECS epoch changes.
4746
+ */
4374
4747
  prepareRelationshipInsert(component, value) {
4375
4748
  const role = relationshipRole(component);
4376
4749
  if (role?.kind !== "source") return ok(void 0);
@@ -4391,34 +4764,114 @@ var WorldComponentAccess = class {
4391
4764
  );
4392
4765
  }
4393
4766
  const targetArch = this.graph.archetypes[targetRec.archetypeId];
4394
- const hasMirror = targetArch?.components.some((candidate) => componentId(candidate) === componentId(mirror)) ?? false;
4395
- if (!hasMirror) {
4396
- const added = this._addComponentCore(
4397
- target,
4398
- { component: mirror, data: {} },
4399
- true
4767
+ const mirrorLocalId = componentId(mirror);
4768
+ const mirrorPresent = targetArch?.components.some((candidate) => componentId(candidate) === mirrorLocalId) ?? false;
4769
+ const fieldName = role.targetField;
4770
+ const arrayMeta = componentDefinition(mirror).fields[fieldName]?.arrayMeta;
4771
+ if (arrayMeta === void 0) {
4772
+ return err(new ComponentNotPresentError(target, mirror.name));
4773
+ }
4774
+ const meta = TYPE_METADATA[arrayMeta.elementType];
4775
+ if (meta?.byteSize === void 0) {
4776
+ return err(new ComponentNotPresentError(target, mirror.name));
4777
+ }
4778
+ let currentLength = 0;
4779
+ let slotId = 0;
4780
+ if (mirrorPresent) {
4781
+ currentLength = this._getArrayLength(target, mirror, fieldName) ?? 0;
4782
+ const fieldCols = this.table(targetArch).storage.get(mirrorLocalId)?.fields;
4783
+ const column = fieldCols?.get(fieldName);
4784
+ if (column === void 0) {
4785
+ return err(new ComponentNotPresentError(target, mirror.name));
4786
+ }
4787
+ const row = this.tableRow(targetRec);
4788
+ slotId = column.view[row];
4789
+ }
4790
+ const requiredBytes = (currentLength + 1) * meta.byteSize;
4791
+ const maximum = Math.floor(262144 / meta.byteSize);
4792
+ if (!Number.isSafeInteger(currentLength + 1) || currentLength + 1 > maximum) {
4793
+ return err(new ManagedBufferOutOfBoundsError(requiredBytes, 262144));
4794
+ }
4795
+ const preparation = {
4796
+ target,
4797
+ mirror,
4798
+ fieldName,
4799
+ mirrorPresent,
4800
+ reservedSlotId: void 0
4801
+ };
4802
+ if (!mirrorPresent || slotId === 0) {
4803
+ const allocated = this.bufferPool.alloc(requiredBytes);
4804
+ if (!allocated.ok) return allocated;
4805
+ preparation.reservedSlotId = allocated.value.id;
4806
+ } else if (this.bufferPool.view(slotId).byteLength < requiredBytes) {
4807
+ const grown = this.bufferPool.grow(slotId, requiredBytes);
4808
+ if (!grown.ok) return grown;
4809
+ }
4810
+ return ok(preparation);
4811
+ }
4812
+ /** Release a target-capacity reservation that did not reach commit. */
4813
+ releaseRelationshipPreparation(preparation) {
4814
+ if (preparation === void 0 || preparation.reservedSlotId === void 0) return;
4815
+ const slotId = preparation.reservedSlotId;
4816
+ this.bufferPool.release(slotId);
4817
+ preparation.reservedSlotId = void 0;
4818
+ }
4819
+ /** Install a reserved slot after the target mirror archetype exists. */
4820
+ installRelationshipPreparation(preparation) {
4821
+ const record = this.lookupAlive(
4822
+ preparation.target,
4823
+ "relationship-capacity",
4824
+ preparation.mirror.name
4825
+ );
4826
+ if (!record.ok) return record;
4827
+ const arch = this.graph.archetypes[record.value.archetypeId];
4828
+ if (arch === void 0) {
4829
+ return err(
4830
+ new ComponentNotPresentError(preparation.target, preparation.mirror.name)
4831
+ );
4832
+ }
4833
+ const fieldCols = this.table(arch).storage.get(componentId(preparation.mirror))?.fields;
4834
+ const column = fieldCols?.get(preparation.fieldName);
4835
+ if (column === void 0) {
4836
+ return err(
4837
+ new ComponentNotPresentError(preparation.target, preparation.mirror.name)
4400
4838
  );
4401
- if (!added.ok) return added;
4402
4839
  }
4403
- const length = this.relationshipTargetEntries(component, target).length;
4404
- return this.ensureArrayCapacity(target, mirror, role.targetField, length + 1);
4840
+ const slotId = preparation.reservedSlotId;
4841
+ if (slotId !== void 0) {
4842
+ column.view[this.tableRow(record.value)] = slotId;
4843
+ preparation.reservedSlotId = void 0;
4844
+ }
4845
+ return ok(void 0);
4405
4846
  }
4406
4847
  /** Append `holder` to the materialized target list. */
4407
- relationshipOnInsert(holder, component, value) {
4848
+ relationshipOnInsert(holder, component, value, preparation) {
4408
4849
  const role = relationshipRole(component);
4409
4850
  if (role?.kind !== "source") return ok(void 0);
4410
4851
  const target = this.relationshipTargetEntity(component, value);
4411
4852
  if (target === null) return ok(void 0);
4412
4853
  const mirror = relationshipMirror(component);
4413
4854
  if (mirror === void 0) return ok(void 0);
4414
- const prepared = this.prepareRelationshipInsert(component, value);
4415
- if (!prepared.ok) {
4416
- if (prepared.error.code === "stale-entity") return ok(void 0);
4417
- return prepared;
4855
+ let prepared = preparation;
4856
+ if (prepared === void 0) {
4857
+ const preparedResult = this.prepareRelationshipInsert(component, value);
4858
+ if (!preparedResult.ok) return preparedResult;
4859
+ prepared = preparedResult.value;
4418
4860
  }
4861
+ if (prepared === void 0) return ok(void 0);
4419
4862
  const targetSlot = entityIndex(target);
4420
4863
  const targetRec = this.records[targetSlot];
4421
- if (!this.recordIsLive(targetRec, entityGeneration(target))) return ok(void 0);
4864
+ const actualGeneration = targetRec?.generation ?? -1;
4865
+ if (!this.recordIsLive(targetRec, entityGeneration(target))) {
4866
+ return err(
4867
+ new StaleEntityError(target, targetSlot, entityGeneration(target), {
4868
+ operation: "relationship-insert",
4869
+ component: component.name,
4870
+ expectedGeneration: entityGeneration(target),
4871
+ actualGeneration
4872
+ })
4873
+ );
4874
+ }
4422
4875
  const targetArch = this.graph.archetypes[targetRec.archetypeId];
4423
4876
  const mirrorLocalId = componentId(mirror);
4424
4877
  const hasMirror = targetArch?.components.some((component2) => componentId(component2) === mirrorLocalId) ?? false;
@@ -4429,9 +4882,19 @@ var WorldComponentAccess = class {
4429
4882
  component: mirror,
4430
4883
  data: {}
4431
4884
  },
4885
+ true,
4886
+ false,
4432
4887
  true
4433
4888
  );
4434
- if (!added.ok) return added;
4889
+ if (!added.ok) {
4890
+ this.releaseRelationshipPreparation(prepared);
4891
+ return added;
4892
+ }
4893
+ }
4894
+ const installed = this.installRelationshipPreparation(prepared);
4895
+ if (!installed.ok) {
4896
+ this.releaseRelationshipPreparation(prepared);
4897
+ return installed;
4435
4898
  }
4436
4899
  const targetEntries = this.relationshipTargetEntries(component, target);
4437
4900
  const slot = targetEntries.length;
@@ -4441,7 +4904,10 @@ var WorldComponentAccess = class {
4441
4904
  role.targetField,
4442
4905
  holder
4443
4906
  );
4444
- if (!mirrored.ok) return mirrored;
4907
+ if (!mirrored.ok) {
4908
+ this.releaseRelationshipPreparation(prepared);
4909
+ return mirrored;
4910
+ }
4445
4911
  this.relationshipIndex(component)?.attach(holder, target, slot);
4446
4912
  return ok(void 0);
4447
4913
  }
@@ -4474,15 +4940,16 @@ var WorldComponentAccess = class {
4474
4940
  /**
4475
4941
  * Read component data from an entity.
4476
4942
  *
4477
- * **Transient view contract (feat-20260602):** for fixed-capacity
4478
- * `array<T,N>` and `buffer<N>` fields, the returned `TypedArray` (and any
4479
- * subarray of it) aliases the archetype column buffer directly. The view is
4480
- * valid only until the next structural change (`spawn` / `despawn` /
4481
- * `addComponent` / `removeComponent`). Holding a view across a structural
4482
- * change is undefined behaviour -- the backing `ArrayBuffer` is detached on
4483
- * column growth, and swap-remove at the same row index points to the wrong
4484
- * entity. **Re-fetch `world.get(e, C)` on every access.** See
4485
- * `packages/ecs/README.md` Transient view contract section.
4943
+ * **Public array contract:** relationship target `array<entity>` fields are
4944
+ * detached `Uint32Array` copies. Mutating that returned array cannot alter
4945
+ * the materialized target, relationship index, or source. Other array
4946
+ * fields retain the existing transient view contract: fixed-capacity
4947
+ * `array<T,N>` and `buffer<N>` fields alias the archetype column buffer
4948
+ * directly, while variable managed arrays alias their BufferPool slot. Those
4949
+ * views are valid only until the next structural change (`spawn` /
4950
+ * `despawn` / `addComponent` / `removeComponent`); callers must re-fetch
4951
+ * `world.get(e, C)` on every access. Internal owners use `readRow` and
4952
+ * `_getArrayView` directly and retain zero-copy access.
4486
4953
  *
4487
4954
  * @returns `Result<ShapeOf<S>, EcsError>` —
4488
4955
  * `ok(ShapeOf<S>)` on success;
@@ -4522,7 +4989,13 @@ var WorldComponentAccess = class {
4522
4989
  if (!arch.components.some((candidate) => componentId(candidate) === localId)) {
4523
4990
  return err(new ComponentNotPresentError(entity, component.name));
4524
4991
  }
4525
- return ok(this.storage.readRow(arch, component, this.tableRow(rec)));
4992
+ const value = this.storage.readRow(arch, component, this.tableRow(rec));
4993
+ const role = relationshipRole(component);
4994
+ if (role?.kind === "target") {
4995
+ const targetValue = value;
4996
+ targetValue[role.targetField] = detachRelationshipTargetArray(targetValue[role.targetField]);
4997
+ }
4998
+ return ok(value);
4526
4999
  }
4527
5000
  /**
4528
5001
  * Column-level zero-copy view of an `array<T, N>` / `array<T>` field.
@@ -4556,9 +5029,10 @@ var WorldComponentAccess = class {
4556
5029
  * Returns `undefined` when the entity is dead, the component is absent, the
4557
5030
  * field does not exist, or the field is not an `array<...>` column.
4558
5031
  *
4559
- * @internal Engine-internal fast path; AI users read the typed view through
4560
- * `world.get(e, Transform).world`. The accessor is the zero-materialization
4561
- * route the propagate kernel and render walk use.
5032
+ * @internal Engine-internal fast path; AI users read public component values
5033
+ * through `world.get`. This accessor is the zero-materialization route the
5034
+ * propagate kernel, relationship owner, and render walk use; it bypasses
5035
+ * the detached public relationship-target snapshot.
4562
5036
  */
4563
5037
  _getArrayView(entity, component, fieldName) {
4564
5038
  const record = this.lookupAlive(entity, "_getArrayView", component.name);
@@ -4568,6 +5042,109 @@ var WorldComponentAccess = class {
4568
5042
  if (!arch) return void 0;
4569
5043
  return this.storage.readArrayView(arch, component, this.tableRow(rec), fieldName);
4570
5044
  }
5045
+ /** Internal zero-materialisation read for ECS-owned relationship lists. */
5046
+ _getArrayLength(entity, component, fieldName) {
5047
+ const record = this.lookupAlive(entity, "relationship-read", component.name);
5048
+ if (!record.ok) return void 0;
5049
+ const arch = this.graph.archetypes[record.value.archetypeId];
5050
+ if (arch === void 0) return void 0;
5051
+ return this.storage.readArrayLength(arch, component, this.tableRow(record.value), fieldName);
5052
+ }
5053
+ /** Internal zero-materialisation read for one ECS-owned array element. */
5054
+ _getArrayElement(entity, component, fieldName, index) {
5055
+ const record = this.lookupAlive(entity, "relationship-read", component.name);
5056
+ if (!record.ok) return void 0;
5057
+ const arch = this.graph.archetypes[record.value.archetypeId];
5058
+ if (arch === void 0) return void 0;
5059
+ return this.storage.readArrayElement(
5060
+ arch,
5061
+ component,
5062
+ this.tableRow(record.value),
5063
+ fieldName,
5064
+ index
5065
+ );
5066
+ }
5067
+ /** Internal scalar-column read used by parent-first hierarchy traversal. */
5068
+ _getFieldValue(entity, component, fieldName) {
5069
+ const record = this.lookupAlive(entity, "relationship-read", component.name);
5070
+ if (!record.ok) return void 0;
5071
+ const arch = this.graph.archetypes[record.value.archetypeId];
5072
+ if (arch === void 0) return void 0;
5073
+ return this.storage.readFieldValue(arch, component, this.tableRow(record.value), fieldName);
5074
+ }
5075
+ /**
5076
+ * Converge one writable relationship source mutation through the source
5077
+ * owner. The source scalar and its materialized target list are committed as
5078
+ * one operation; no caller receives a raw source column view that could
5079
+ * bypass the mirror/index maintenance.
5080
+ */
5081
+ setRelationshipSource(entity, component, value, record, arch, markChanged) {
5082
+ const role = relationshipRole(component);
5083
+ if (role?.kind !== "source") return ok(void 0);
5084
+ const row = this.tableRow(record);
5085
+ const current = this.storage.readRow(arch, component, row);
5086
+ const valuePreflight = this.preflightComponentFieldValues(entity, {
5087
+ component,
5088
+ data: value
5089
+ });
5090
+ if (!valuePreflight.ok) return valuePreflight;
5091
+ const merged = { ...current, ...value };
5092
+ const enumError = validateEnumFieldValues(component, merged, entity);
5093
+ if (enumError !== null) return err(enumError);
5094
+ const oldTarget = this.relationshipTargetEntity(component, current);
5095
+ const target = this.relationshipTargetEntity(component, merged);
5096
+ if (target !== null) {
5097
+ const targetRecord = this.records[entityIndex(target)];
5098
+ const actualGeneration = targetRecord?.generation ?? -1;
5099
+ if (!this.recordIsLive(targetRecord, entityGeneration(target))) {
5100
+ return err(
5101
+ new StaleEntityError(target, entityIndex(target), entityGeneration(target), {
5102
+ operation: "relationship-insert",
5103
+ component: component.name,
5104
+ expectedGeneration: entityGeneration(target),
5105
+ actualGeneration
5106
+ })
5107
+ );
5108
+ }
5109
+ const roleAllowsSelf = role.allowSelf;
5110
+ if (entity === target && !roleAllowsSelf) {
5111
+ return err(
5112
+ new RelationshipSelfCycleError(component.name, entity, target)
5113
+ );
5114
+ }
5115
+ const cycleHit = entity === target && roleAllowsSelf ? null : this.relationshipCycleHit(component, target, entity);
5116
+ if (cycleHit !== null) {
5117
+ return err(
5118
+ new RelationshipSelfCycleError(component.name, entity, cycleHit)
5119
+ );
5120
+ }
5121
+ }
5122
+ if (oldTarget !== target) {
5123
+ const prepared = this.prepareRelationshipInsert(component, merged);
5124
+ if (!prepared.ok) return prepared;
5125
+ const preparation = prepared.value;
5126
+ if (oldTarget !== null) {
5127
+ const detached = this.relationshipOnRemove(entity, component, current);
5128
+ if (!detached.ok) {
5129
+ this.releaseRelationshipPreparation(preparation);
5130
+ return detached;
5131
+ }
5132
+ }
5133
+ const attached = this.relationshipOnInsert(entity, component, merged, preparation);
5134
+ if (!attached.ok) {
5135
+ this.releaseRelationshipPreparation(preparation);
5136
+ return attached;
5137
+ }
5138
+ }
5139
+ const fieldCols = this.table(arch).storage.get(componentId(component))?.fields;
5140
+ const sourceColumn = fieldCols?.get(role.sourceField);
5141
+ if (sourceColumn === void 0) {
5142
+ return err(new ComponentNotPresentError(entity, component.name));
5143
+ }
5144
+ sourceColumn.view[row] = target === null ? ENTITY_NULL_RAW : target;
5145
+ if (markChanged) this.markComponentChanged(entity, component);
5146
+ return ok(void 0);
5147
+ }
4571
5148
  /**
4572
5149
  * Write (partial) component data to an entity.
4573
5150
  *
@@ -4606,6 +5183,20 @@ var WorldComponentAccess = class {
4606
5183
  if (!arch.components.some((candidate) => componentId(candidate) === localId)) {
4607
5184
  return err(new ComponentNotPresentError(entity, component.name));
4608
5185
  }
5186
+ const role = relationshipRole(component);
5187
+ if (role?.kind === "target") {
5188
+ return err(new RelationshipTargetReadonlyError(component.name, "set"));
5189
+ }
5190
+ if (role?.kind === "source") {
5191
+ return this.setRelationshipSource(
5192
+ entity,
5193
+ component,
5194
+ value,
5195
+ rec,
5196
+ arch,
5197
+ markChanged
5198
+ );
5199
+ }
4609
5200
  const valuePreflight = this.preflightComponentFieldValues(entity, {
4610
5201
  component,
4611
5202
  data: value
@@ -4772,51 +5363,6 @@ var WorldComponentAccess = class {
4772
5363
  this.markComponentChanged(entity, component);
4773
5364
  return ok(void 0);
4774
5365
  }
4775
- ensureArrayCapacity(entity, component, fieldName, minimum) {
4776
- const record = this.lookupAlive(entity, "relationship-capacity", component.name);
4777
- if (!record.ok) return record;
4778
- const rec = record.value;
4779
- const arch = this.graph.archetypes[rec.archetypeId];
4780
- if (!arch) {
4781
- return err(
4782
- new StaleEntityError(entity, entityIndex(entity), entityGeneration(entity), {
4783
- operation: "relationship-capacity",
4784
- component: component.name,
4785
- expectedGeneration: entityGeneration(entity),
4786
- actualGeneration: rec.generation
4787
- })
4788
- );
4789
- }
4790
- const fieldCols = this.table(arch).storage.get(componentId(component))?.fields;
4791
- if (!fieldCols) return err(new ComponentNotPresentError(entity, component.name));
4792
- const fieldNameStr = fieldName;
4793
- const col = fieldCols.get(fieldNameStr);
4794
- if (!col) return err(new ComponentNotPresentError(entity, component.name));
4795
- const arrayMeta = componentDefinition(component).fields[fieldNameStr]?.arrayMeta;
4796
- if (arrayMeta === void 0) {
4797
- return err(new ComponentNotPresentError(entity, component.name));
4798
- }
4799
- const meta = TYPE_METADATA[arrayMeta.elementType];
4800
- if (!meta?.byteSize) {
4801
- return err(new ComponentNotPresentError(entity, component.name));
4802
- }
4803
- const maximum = Math.floor(262144 / meta.byteSize);
4804
- if (!Number.isSafeInteger(minimum) || minimum < 0 || minimum > maximum) {
4805
- return err(new ManagedBufferOutOfBoundsError(minimum, maximum));
4806
- }
4807
- const byteLength = minimum * meta.byteSize;
4808
- const slotId = col.view[this.tableRow(rec)];
4809
- if (slotId === 0) {
4810
- if (minimum === 0) return ok(void 0);
4811
- const allocated = this.bufferPool.alloc(byteLength);
4812
- if (!allocated.ok) return allocated;
4813
- col.view[this.tableRow(rec)] = allocated.value.id;
4814
- return ok(void 0);
4815
- }
4816
- if (this.bufferPool.view(slotId).byteLength >= byteLength) return ok(void 0);
4817
- const grown = this.bufferPool.grow(slotId, byteLength);
4818
- return grown.ok ? ok(void 0) : grown;
4819
- }
4820
5366
  /**
4821
5367
  * Remove one variable-array element at a known slot. Relationship holders
4822
5368
  * supply the slot from their backpointer, so this is O(1) and never scans
@@ -4884,11 +5430,11 @@ var WorldComponentAccess = class {
4884
5430
  * (lazy mirror create or exclusive reparent).
4885
5431
  * @internal
4886
5432
  */
4887
- _addComponentCore(entity, componentData, internal) {
5433
+ _addComponentCore(entity, componentData, internal, resolveRequirements = true, skipVariableArrayInitialization = false) {
4888
5434
  const record = this.lookupAlive(entity, "addComponent", componentData.component.name);
4889
5435
  if (!record.ok) return record;
4890
5436
  const rec = record.value;
4891
- const srcArch = this.graph.archetypes[rec.archetypeId];
5437
+ let srcArch = this.graph.archetypes[rec.archetypeId];
4892
5438
  if (!srcArch) {
4893
5439
  return err(
4894
5440
  new StaleEntityError(entity, entityIndex(entity), entityGeneration(entity), {
@@ -4931,31 +5477,60 @@ var WorldComponentAccess = class {
4931
5477
  return err(enumErr);
4932
5478
  }
4933
5479
  const localId = componentId(componentData.component);
5480
+ let relationshipPreparation;
5481
+ const componentAlreadyPresent = srcArch.components.some(
5482
+ (candidate) => componentId(candidate) === localId
5483
+ );
5484
+ if (!internal && !componentAlreadyPresent && relationshipRole(componentData.component)?.kind === "source") {
5485
+ const prepared = this.prepareRelationshipInsert(
5486
+ componentData.component,
5487
+ filled
5488
+ );
5489
+ if (!prepared.ok) return prepared;
5490
+ relationshipPreparation = prepared.value;
5491
+ }
5492
+ if (resolveRequirements) {
5493
+ const required = expandComponentRequirements([componentData]).slice(1);
5494
+ for (const requirement of required) {
5495
+ if (srcArch.components.some(
5496
+ (candidate) => componentId(candidate) === componentId(requirement.component)
5497
+ )) {
5498
+ continue;
5499
+ }
5500
+ const added = this._addComponentCore(entity, requirement, internal, false);
5501
+ if (!added.ok) {
5502
+ this.releaseRelationshipPreparation(relationshipPreparation);
5503
+ return added;
5504
+ }
5505
+ srcArch = this.graph.archetypes[rec.archetypeId];
5506
+ if (!srcArch) {
5507
+ this.releaseRelationshipPreparation(relationshipPreparation);
5508
+ return err(
5509
+ new StaleEntityError(entity, entityIndex(entity), entityGeneration(entity), {
5510
+ operation: "addComponent",
5511
+ component: componentData.component.name,
5512
+ expectedGeneration: rec.generation,
5513
+ actualGeneration: rec.generation
5514
+ })
5515
+ );
5516
+ }
5517
+ }
5518
+ }
4934
5519
  if (srcArch.components.some((candidate) => componentId(candidate) === localId)) {
5520
+ this.releaseRelationshipPreparation(relationshipPreparation);
4935
5521
  const role = relationshipRole(componentData.component);
4936
5522
  if (role?.kind === "source" && role.exclusive && !internal) {
4937
- const prepared = this.prepareRelationshipInsert(
4938
- componentData.component,
4939
- filled
4940
- );
4941
- if (!prepared.ok) return prepared;
4942
- const removeR = this._removeComponentCore(
5523
+ return this.setRelationshipSource(
4943
5524
  entity,
4944
5525
  componentData.component,
4945
- false
5526
+ filled,
5527
+ rec,
5528
+ srcArch,
5529
+ true
4946
5530
  );
4947
- if (!removeR.ok) return removeR;
4948
- return this._addComponentCore(entity, componentData, false);
4949
5531
  }
4950
5532
  return err(new ComponentAlreadyPresentError(entity, componentData.component.name));
4951
5533
  }
4952
- if (!internal && relationshipRole(componentData.component)?.kind === "source") {
4953
- const prepared = this.prepareRelationshipInsert(
4954
- componentData.component,
4955
- filled
4956
- );
4957
- if (!prepared.ok) return prepared;
4958
- }
4959
5534
  const targetArch = getAddEdge(
4960
5535
  this.graph,
4961
5536
  srcArch,
@@ -4972,7 +5547,8 @@ var WorldComponentAccess = class {
4972
5547
  targetArch,
4973
5548
  componentData.component,
4974
5549
  this.tableRow(rec),
4975
- filled
5550
+ filled,
5551
+ skipVariableArrayInitialization ? { skipVariableArrayInitialization: true } : void 0
4976
5552
  );
4977
5553
  }
4978
5554
  this.markComponentAdded(entity, componentData.component);
@@ -4980,11 +5556,20 @@ var WorldComponentAccess = class {
4980
5556
  const relationshipResult = this.relationshipOnInsert(
4981
5557
  entity,
4982
5558
  componentData.component,
4983
- filled
5559
+ filled,
5560
+ relationshipPreparation
4984
5561
  );
4985
- if (!relationshipResult.ok) return relationshipResult;
5562
+ if (!relationshipResult.ok) {
5563
+ this.releaseRelationshipPreparation(relationshipPreparation);
5564
+ return relationshipResult;
5565
+ }
4986
5566
  }
4987
5567
  this.markStructureChanged();
5568
+ this.state.recordStructuralEvidence({
5569
+ kind: "component-added",
5570
+ entity,
5571
+ componentId: localId
5572
+ });
4988
5573
  return ok(void 0);
4989
5574
  }
4990
5575
  /**
@@ -5062,8 +5647,12 @@ var WorldComponentAccess = class {
5062
5647
  } else {
5063
5648
  this.storage.migrateEntity(rec, srcArch, targetArch);
5064
5649
  }
5065
- this.state.removeComponentChange(entity, componentId(component));
5066
5650
  this.markStructureChanged();
5651
+ this.state.recordStructuralEvidence({
5652
+ kind: "component-removed",
5653
+ entity,
5654
+ componentId: localId
5655
+ });
5067
5656
  return ok(void 0);
5068
5657
  }
5069
5658
  // ──────────────────────────────────────────────────────────────────────────
@@ -5097,9 +5686,39 @@ var WorldComponentAccess = class {
5097
5686
  * Idempotent: a record with archetypeId !== -1 is already materialized.
5098
5687
  */
5099
5688
  _materializePendingEntity(entity, componentDatas) {
5689
+ componentDatas = expandComponentRequirements(componentDatas);
5100
5690
  const slot = entityIndex(entity);
5101
5691
  const record = this.records[slot];
5102
5692
  if (!record || record.archetypeId !== -1) return ok(void 0);
5693
+ const relationshipPreparations = [];
5694
+ for (let index = 0; index < componentDatas.length; index += 1) {
5695
+ const componentData = componentDatas[index];
5696
+ if (componentData === void 0 || relationshipRole(componentData.component)?.kind !== "source") {
5697
+ continue;
5698
+ }
5699
+ const filled = fillComponentDefaults(
5700
+ componentData.component,
5701
+ componentData.data
5702
+ );
5703
+ const target = this.relationshipTargetEntity(
5704
+ componentData.component,
5705
+ filled
5706
+ );
5707
+ const targetRecord = target === null ? void 0 : this.records[entityIndex(target)];
5708
+ if (target !== null && targetRecord !== void 0 && this.recordIsLive(targetRecord, entityGeneration(target))) {
5709
+ const prepared = this.prepareRelationshipInsert(
5710
+ componentData.component,
5711
+ filled
5712
+ );
5713
+ if (!prepared.ok) {
5714
+ for (const reservation of relationshipPreparations) {
5715
+ this.releaseRelationshipPreparation(reservation);
5716
+ }
5717
+ return prepared;
5718
+ }
5719
+ relationshipPreparations[index] = prepared.value;
5720
+ }
5721
+ }
5103
5722
  const componentIds = componentDatas.map((cd) => componentId(cd.component));
5104
5723
  const components = componentDatas.map((cd) => cd.component);
5105
5724
  const arch = getOrCreateArchetype(this.graph, componentIds, components);
@@ -5117,20 +5736,30 @@ var WorldComponentAccess = class {
5117
5736
  this.storage.writeRow(arch, cd.component, tableRow2, filled);
5118
5737
  }
5119
5738
  this.storage.writeEntitySelf(arch, tableRow2, entity);
5120
- for (const cd of componentDatas) {
5739
+ for (let index = 0; index < componentDatas.length; index += 1) {
5740
+ const cd = componentDatas[index];
5741
+ if (cd === void 0) continue;
5121
5742
  if (relationshipRole(cd.component)?.kind === "source") {
5122
5743
  const filled = fillComponentDefaults(cd.component, cd.data);
5123
5744
  const relationshipResult = this.relationshipOnInsert(
5124
5745
  entity,
5125
5746
  cd.component,
5126
- filled
5747
+ filled,
5748
+ relationshipPreparations[index]
5127
5749
  );
5128
5750
  if (!relationshipResult.ok) {
5751
+ for (const reservation of relationshipPreparations) {
5752
+ this.releaseRelationshipPreparation(reservation);
5753
+ }
5129
5754
  return relationshipResult;
5130
5755
  }
5131
5756
  }
5132
5757
  }
5133
5758
  this.markStructureChanged();
5759
+ this.state.recordStructuralEvidence({
5760
+ kind: "spawn",
5761
+ entity
5762
+ });
5134
5763
  return ok(void 0);
5135
5764
  }
5136
5765
  // ──────────────────────────────────────────────────────────────────────────
@@ -5411,7 +6040,7 @@ var BufferPool = class {
5411
6040
  return n;
5412
6041
  }
5413
6042
  };
5414
- var SHARED_REF_MUTATION_JOURNAL_CAPACITY = 4096;
6043
+ var SHARED_REF_RELEASE_EVIDENCE_CAPACITY = 4096;
5415
6044
  var SharedRefStore = class {
5416
6045
  payloads = /* @__PURE__ */ new Map();
5417
6046
  refcounts = /* @__PURE__ */ new Map();
@@ -5420,7 +6049,8 @@ var SharedRefStore = class {
5420
6049
  internedKeys = /* @__PURE__ */ new Map();
5421
6050
  nextSlot = BUILTIN_BASE;
5422
6051
  mutationEpoch = 0;
5423
- mutationJournal = [];
6052
+ /** Latest published mutation epoch per live handle; not an event journal. */
6053
+ mutationEpochs = /* @__PURE__ */ new Map();
5424
6054
  releaseJournal = [];
5425
6055
  /**
5426
6056
  * Generation table indexed by slot (D-6). Each entry tracks the current
@@ -5524,26 +6154,23 @@ var SharedRefStore = class {
5524
6154
  throw new RangeError("SharedRefStore mutation epoch exhausted");
5525
6155
  }
5526
6156
  this.mutationEpoch += 1;
5527
- this.mutationJournal.push({ epoch: this.mutationEpoch, handle: unwrapHandle(handle) });
5528
- if (this.mutationJournal.length > SHARED_REF_MUTATION_JOURNAL_CAPACITY) {
5529
- this.mutationJournal.shift();
5530
- }
6157
+ this.mutationEpochs.set(unwrapHandle(handle), this.mutationEpoch);
5531
6158
  return ok(void 0);
5532
6159
  }
5533
6160
  /** Current upper bound for explicitly published payload mutations. */
5534
6161
  getMutationEpoch() {
5535
6162
  return this.mutationEpoch;
5536
6163
  }
5537
- /** Read exact changed handles after `cursor`; overflow requires consumer resync. */
6164
+ /** Read each live handle whose latest published mutation is after `cursor`. */
5538
6165
  readChangesSince(cursor) {
5539
- const oldestAvailable = this.mutationJournal[0]?.epoch ?? this.mutationEpoch + 1;
5540
- if (cursor < oldestAvailable - 1) {
5541
- return { status: "overflow", cursor: this.mutationEpoch, oldestAvailable };
6166
+ const records = [];
6167
+ for (const [handle, epoch] of this.mutationEpochs) {
6168
+ if (epoch > cursor) records.push({ epoch, handle });
5542
6169
  }
6170
+ records.sort((left, right) => left.epoch - right.epoch || left.handle - right.handle);
5543
6171
  return {
5544
- status: "ok",
5545
6172
  cursor: this.mutationEpoch,
5546
- records: this.mutationJournal.filter((record) => record.epoch > cursor)
6173
+ records
5547
6174
  };
5548
6175
  }
5549
6176
  /**
@@ -5609,6 +6236,7 @@ var SharedRefStore = class {
5609
6236
  }
5610
6237
  this.refcounts.delete(raw);
5611
6238
  this.payloads.delete(raw);
6239
+ this.mutationEpochs.delete(raw);
5612
6240
  const generation = storeGen + 1;
5613
6241
  this._generations[slot] = generation;
5614
6242
  if (!isRetiredSlot(generation)) {
@@ -5621,7 +6249,7 @@ var SharedRefStore = class {
5621
6249
  evidence: "released"
5622
6250
  });
5623
6251
  this.releaseJournal.push(evidence);
5624
- if (this.releaseJournal.length > SHARED_REF_MUTATION_JOURNAL_CAPACITY) {
6252
+ if (this.releaseJournal.length > SHARED_REF_RELEASE_EVIDENCE_CAPACITY) {
5625
6253
  this.releaseJournal.shift();
5626
6254
  }
5627
6255
  return ok(evidence);
@@ -5645,6 +6273,47 @@ var SharedRefStore = class {
5645
6273
  return this.payloads.size;
5646
6274
  }
5647
6275
  };
6276
+
6277
+ // src/storage/structural-evidence.ts
6278
+ var StructuralEvidenceRing = class {
6279
+ constructor(capacity = 1024) {
6280
+ this.capacity = capacity;
6281
+ if (!Number.isSafeInteger(capacity) || capacity <= 0) {
6282
+ throw new RangeError("StructuralEvidenceRing capacity must be a positive safe integer");
6283
+ }
6284
+ this.events = new Array(capacity);
6285
+ }
6286
+ capacity;
6287
+ events;
6288
+ nextSequence = 1;
6289
+ get cursor() {
6290
+ return this.nextSequence - 1;
6291
+ }
6292
+ append(input) {
6293
+ const sequence = this.nextSequence;
6294
+ this.nextSequence += 1;
6295
+ this.events[(sequence - 1) % this.capacity] = { ...input, sequence };
6296
+ return sequence;
6297
+ }
6298
+ readAfter(cursor) {
6299
+ const latest = this.cursor;
6300
+ if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > latest) {
6301
+ throw new RangeError(`StructuralEvidenceRing cursor ${cursor} is outside 0..${latest}`);
6302
+ }
6303
+ const oldestAvailable = Math.max(1, latest - this.capacity + 1);
6304
+ if (cursor < oldestAvailable - 1)
6305
+ return { status: "overflow", cursor: latest, oldestAvailable };
6306
+ const events = [];
6307
+ for (let sequence = cursor + 1; sequence <= latest; sequence += 1) {
6308
+ const event = this.events[(sequence - 1) % this.capacity];
6309
+ if (event === void 0 || event.sequence !== sequence) {
6310
+ return { status: "overflow", cursor: latest, oldestAvailable };
6311
+ }
6312
+ events.push(event);
6313
+ }
6314
+ return { status: "ok", cursor: latest, events };
6315
+ }
6316
+ };
5648
6317
  var UniqueRefStore = class {
5649
6318
  payloads = /* @__PURE__ */ new Map();
5650
6319
  freeSlots = [];
@@ -5801,7 +6470,7 @@ var WorldCore = class {
5801
6470
  sharedRefs = new SharedRefStore();
5802
6471
  storage;
5803
6472
  componentMutationEpochs = [];
5804
- changeJournal = new WorldChangeJournal();
6473
+ structuralEvidence = new StructuralEvidenceRing();
5805
6474
  /** One packed reverse index per relationship source component. */
5806
6475
  relationshipIndexes = /* @__PURE__ */ new Map();
5807
6476
  mutationEpoch = 0;
@@ -5830,6 +6499,7 @@ function tableRow(world, record) {
5830
6499
  return archetype?.rows[record.archetypeRow] ?? -1;
5831
6500
  }
5832
6501
  function spawnCore(world, componentDatas, internal) {
6502
+ componentDatas = expandComponentRequirements(componentDatas);
5833
6503
  const filledData = [];
5834
6504
  for (const cd of componentDatas) {
5835
6505
  const preflight = world[worldInternal].preflightComponentData(null, cd);
@@ -5845,6 +6515,27 @@ function spawnCore(world, componentDatas, internal) {
5845
6515
  if (enumErr !== null) return err(enumErr);
5846
6516
  filledData.push(filled);
5847
6517
  }
6518
+ const relationshipPreparations = [];
6519
+ if (!internal) {
6520
+ for (let index = 0; index < componentDatas.length; index += 1) {
6521
+ const componentData = componentDatas[index];
6522
+ const value = filledData[index];
6523
+ if (componentData === void 0 || value === void 0 || relationshipRole(componentData.component)?.kind !== "source") {
6524
+ continue;
6525
+ }
6526
+ const prepared = world[worldInternal].prepareRelationshipInsert(
6527
+ componentData.component,
6528
+ value
6529
+ );
6530
+ if (!prepared.ok) {
6531
+ for (const reservation of relationshipPreparations) {
6532
+ world[worldInternal].releaseRelationshipPreparation(reservation);
6533
+ }
6534
+ return prepared;
6535
+ }
6536
+ relationshipPreparations[index] = prepared.value;
6537
+ }
6538
+ }
5848
6539
  const indexSlot = world[worldInternal].allocateIndex();
5849
6540
  const record = world[worldInternal].getRecords()[indexSlot];
5850
6541
  if (record === void 0)
@@ -5880,12 +6571,22 @@ function spawnCore(world, componentDatas, internal) {
5880
6571
  const relation = world[worldInternal].relationshipOnInsert(
5881
6572
  spawnedEntity,
5882
6573
  cd.component,
5883
- filled
6574
+ filled,
6575
+ relationshipPreparations[i]
5884
6576
  );
5885
- if (!relation.ok) return relation;
6577
+ if (!relation.ok) {
6578
+ for (const reservation of relationshipPreparations) {
6579
+ world[worldInternal].releaseRelationshipPreparation(reservation);
6580
+ }
6581
+ return relation;
6582
+ }
5886
6583
  }
5887
6584
  }
5888
6585
  world[worldInternal].markStructureChanged();
6586
+ world[worldInternal].recordStructuralEvidence({
6587
+ kind: "spawn",
6588
+ entity: spawnedEntity
6589
+ });
5889
6590
  return ok(spawnedEntity);
5890
6591
  }
5891
6592
  function despawnCore(world, entity, internal) {
@@ -5936,8 +6637,11 @@ function despawnCore(world, entity, internal) {
5936
6637
  }
5937
6638
  }
5938
6639
  }
6640
+ world[worldInternal].recordStructuralEvidence({
6641
+ kind: "despawn",
6642
+ entity
6643
+ });
5939
6644
  if (record) {
5940
- world[worldInternal].removeEntityChanges(entity);
5941
6645
  record.archetypeId = -1;
5942
6646
  record.archetypeRow = -1;
5943
6647
  record.generation += 1;
@@ -5949,7 +6653,8 @@ function despawnCore(world, entity, internal) {
5949
6653
  }
5950
6654
  function worldAddChild(world, parent, child, component, data) {
5951
6655
  const holderComp = component;
5952
- if (relationshipRole(holderComp)?.kind !== "source") {
6656
+ const role = relationshipRole(holderComp);
6657
+ if (role?.kind !== "source") {
5953
6658
  return err(new ComponentNotPresentError(child, component.name));
5954
6659
  }
5955
6660
  const parentSlot = entityIndex(parent);
@@ -5978,7 +6683,6 @@ function worldAddChild(world, parent, child, component, data) {
5978
6683
  })
5979
6684
  );
5980
6685
  }
5981
- const role = relationshipRole(holderComp);
5982
6686
  if (child === parent && !(role?.kind === "source" && role.allowSelf)) {
5983
6687
  return err(new RelationshipSelfCycleError(component.name, child, child));
5984
6688
  }
@@ -6029,10 +6733,10 @@ function worldRemoveChild(world, parent, child, component) {
6029
6733
  }
6030
6734
  function worldReparent(world, child, newParent, component, data) {
6031
6735
  const holderComp = component;
6032
- if (relationshipRole(holderComp)?.kind !== "source") {
6736
+ const role = relationshipRole(holderComp);
6737
+ if (role?.kind !== "source") {
6033
6738
  return err(new ComponentNotPresentError(child, component.name));
6034
6739
  }
6035
- const role = relationshipRole(holderComp);
6036
6740
  if (child === newParent && !(role?.kind === "source" && role.allowSelf)) {
6037
6741
  return err(
6038
6742
  new RelationshipSelfCycleError(component.name, child, newParent)
@@ -6062,11 +6766,14 @@ function worldReparent(world, child, newParent, component, data) {
6062
6766
  })
6063
6767
  );
6064
6768
  }
6065
- if (childArch.components.some((component2) => componentId(component2) === componentId(holderComp))) {
6066
- const removeResult = world.removeComponent(child, component);
6067
- if (!removeResult.ok) return removeResult;
6769
+ const payload = {
6770
+ ...data,
6771
+ [role.sourceField]: newParent
6772
+ };
6773
+ if (childArch.components.some((candidate) => componentId(candidate) === componentId(holderComp))) {
6774
+ return world.set(child, component, payload);
6068
6775
  }
6069
- return world.addComponent(child, { component, data });
6776
+ return world.addComponent(child, { component, data: payload });
6070
6777
  }
6071
6778
  function worldIterAncestors(world, entity) {
6072
6779
  return {
@@ -6586,9 +7293,6 @@ var World = class {
6586
7293
  get componentMutationEpochs() {
6587
7294
  return this.core.componentMutationEpochs;
6588
7295
  }
6589
- get changeJournal() {
6590
- return this.core.changeJournal;
6591
- }
6592
7296
  /** Free index slots (LIFO stack). */
6593
7297
  get records() {
6594
7298
  return this.core.records;
@@ -6668,8 +7372,8 @@ var World = class {
6668
7372
  markComponentAdded: (entity, component) => this.internalmarkComponentAdded(entity, component),
6669
7373
  markComponentsAdded: (entity, components) => this.internalmarkComponentsAdded(entity, components),
6670
7374
  markComponentChanged: (entity, component) => this.internalmarkComponentChanged(entity, component),
6671
- removeComponentChange: (entity, component) => this.internalremoveComponentChange(entity, component),
6672
7375
  markStructureChanged: () => this.internalmarkStructureChanged(),
7376
+ recordStructuralEvidence: (evidence) => this.internalrecordStructuralEvidence(evidence),
6673
7377
  routeError: (error, context) => this.internalrouteError(error, context)
6674
7378
  });
6675
7379
  this.clock = createWorldClock({ ...DEFAULT_TIME_POLICY, ...options.time });
@@ -6690,11 +7394,14 @@ var World = class {
6690
7394
  cancelPendingEntity: this.internalcancelPendingEntity.bind(this),
6691
7395
  despawnCore: this.internaldespawnCore.bind(this),
6692
7396
  getArrayView: this.internalgetArrayView.bind(this),
7397
+ getArrayLength: this.internalgetArrayLength.bind(this),
7398
+ getArrayElement: this.internalgetArrayElement.bind(this),
7399
+ getFieldValue: this.internalgetFieldValue.bind(this),
6693
7400
  getBufferPool: this.internalgetBufferPool.bind(this),
6694
- getChangeCursor: this.internalgetChangeCursor.bind(this),
6695
7401
  getClockWriter: this.internalgetClockWriter.bind(this),
6696
7402
  getComponentChange: this.internalgetComponentChange.bind(this),
6697
7403
  getComponentMutationEpoch: this.internalgetComponentMutationEpoch.bind(this),
7404
+ getComponentMutationEpochs: this.internalgetComponentMutationEpochs.bind(this),
6698
7405
  getEntityArchetype: this.internalgetEntityArchetype.bind(this),
6699
7406
  getFixedAccumulator: this.internalgetFixedAccumulator.bind(this),
6700
7407
  getFreeIndices: this.internalgetFreeIndices.bind(this),
@@ -6709,28 +7416,30 @@ var World = class {
6709
7416
  getSchedules: this.internalgetSchedules.bind(this),
6710
7417
  getSharedRefs: this.internalgetSharedRefs.bind(this),
6711
7418
  getStructureEpoch: this.internalgetStructureEpoch.bind(this),
7419
+ getStructuralEvidence: this.internalgetStructuralEvidence.bind(this),
6712
7420
  getUniqueRefs: this.internalgetUniqueRefs.bind(this),
6713
7421
  lookupAlive: this.internallookupAlive.bind(this),
6714
7422
  markComponentAdded: this.internalmarkComponentAdded.bind(this),
6715
7423
  markComponentChanged: this.internalmarkComponentChanged.bind(this),
6716
7424
  markComponentRangeChanged: this.internalmarkComponentRangeChanged.bind(this),
6717
7425
  markComponentsAdded: this.internalmarkComponentsAdded.bind(this),
6718
- markDerivedComponentChanges: this.internalmarkDerivedComponentChanges.bind(this),
6719
7426
  markStructureChanged: this.internalmarkStructureChanged.bind(this),
6720
7427
  materializePendingEntity: this.internalmaterializePendingEntity.bind(this),
6721
7428
  nextMutationEpoch: this.internalnextMutationEpoch.bind(this),
6722
7429
  poisonExecution: this.internalpoisonExecution.bind(this),
7430
+ prepareRelationshipInsert: this.internalprepareRelationshipInsert.bind(this),
7431
+ publishDerivedRange: this.internalpublishDerivedRange.bind(this),
6723
7432
  preflightComponentData: this.internalpreflightComponentData.bind(this),
6724
- readChangesSince: this.internalreadChangesSince.bind(this),
6725
7433
  readRow: this.internalreadRow.bind(this),
7434
+ recordStructuralEvidence: this.internalrecordStructuralEvidence.bind(this),
6726
7435
  recordIsLive: this.internalrecordIsLive.bind(this),
6727
7436
  relationshipOnInsert: this.internalrelationshipOnInsert.bind(this),
6728
7437
  relationshipOnRemove: this.internalrelationshipOnRemove.bind(this),
6729
7438
  releaseManagedRefsOnRow: this.internalreleaseManagedRefsOnRow.bind(this),
6730
- removeComponentChange: this.internalremoveComponentChange.bind(this),
7439
+ releaseRelationshipPreparation: this.internalreleaseRelationshipPreparation.bind(this),
6731
7440
  removeComponentCore: this.internalremoveComponentCore.bind(this),
6732
- removeEntityChanges: this.internalremoveEntityChanges.bind(this),
6733
7441
  routeError: this.internalrouteError.bind(this),
7442
+ restoreMutationEpoch: this.internalrestoreMutationEpoch.bind(this),
6734
7443
  setFixedAccumulator: this.internalsetFixedAccumulator.bind(this),
6735
7444
  setQueryRow: this.internalsetQueryRow.bind(this),
6736
7445
  spawnCore: this.internalspawnCore.bind(this),
@@ -6776,18 +7485,17 @@ var World = class {
6776
7485
  internalgetMutationEpoch() {
6777
7486
  return this.mutationEpoch;
6778
7487
  }
6779
- /** Current cursor for a persistent projection subscriber. */
6780
- internalgetChangeCursor() {
6781
- return this.changeJournal.cursor();
6782
- }
6783
- /** Read complete mutation evidence or an explicit rebuild signal. */
6784
- internalreadChangesSince(cursor) {
6785
- return this.changeJournal.readAfter(cursor);
6786
- }
6787
7488
  /** Structure snapshot used to invalidate borrowed query facades. */
6788
7489
  internalgetStructureEpoch() {
6789
7490
  return this.structureEpoch;
6790
7491
  }
7492
+ /** Typed structural facts are produced by this World and consumed by projections. */
7493
+ internalgetStructuralEvidence() {
7494
+ return this.core.structuralEvidence;
7495
+ }
7496
+ internalrecordStructuralEvidence(evidence) {
7497
+ this.core.structuralEvidence.append(evidence);
7498
+ }
6791
7499
  /** Resolve current logical identity for a packed entity handle. */
6792
7500
  internalgetEntityArchetype(entity) {
6793
7501
  const record = this.records[entityIndex(entity)];
@@ -6808,6 +7516,17 @@ var World = class {
6808
7516
  this.mutationEpoch += 1;
6809
7517
  return this.mutationEpoch;
6810
7518
  }
7519
+ internalrestoreMutationEpoch(epoch) {
7520
+ this.mutationEpoch = epoch;
7521
+ }
7522
+ internalpublishDerivedRange(table, componentId2, rowStart, rowCount, epoch) {
7523
+ const epochs = table.storage.get(componentId2)?.epochs;
7524
+ if (epochs === void 0) {
7525
+ throw new Error(`Derived component ${componentId2} is not in the table.`);
7526
+ }
7527
+ epochs.changed.fill(epoch, rowStart, rowStart + rowCount);
7528
+ this.componentMutationEpochs[componentId2] = epoch;
7529
+ }
6811
7530
  /** Record one successful structural mutation. */
6812
7531
  internalmarkStructureChanged() {
6813
7532
  this.structureEpoch += 1;
@@ -6828,7 +7547,6 @@ var World = class {
6828
7547
  markComponentsAdded(this.graph, record, entity, componentIds, epoch);
6829
7548
  for (const componentId2 of componentIds) {
6830
7549
  this.componentMutationEpochs[componentId2] = epoch;
6831
- this.changeJournal.append({ kind: "component-added", entity, componentId: componentId2 });
6832
7550
  }
6833
7551
  }
6834
7552
  /** Mark an existing component as changed at the current tick. */
@@ -6842,7 +7560,6 @@ var World = class {
6842
7560
  });
6843
7561
  if (epoch !== void 0) {
6844
7562
  this.componentMutationEpochs[componentId2] = epoch;
6845
- this.changeJournal.append({ kind: "component-changed", entity, componentId: componentId2 });
6846
7563
  }
6847
7564
  }
6848
7565
  /** Mark one contiguous component range with a single epoch. */
@@ -6852,18 +7569,15 @@ var World = class {
6852
7569
  const epoch = this.internalnextMutationEpoch();
6853
7570
  epochs.changed.fill(epoch, rowStart, rowStart + rowCount);
6854
7571
  this.componentMutationEpochs[componentId2] = epoch;
6855
- for (const entity of readTableEntityRange(table, rowStart, rowCount)) {
6856
- this.changeJournal.append({
6857
- kind: "component-changed",
6858
- entity,
6859
- componentId: componentId2
6860
- });
6861
- }
6862
7572
  }
6863
7573
  /** Latest mutation token for one component-owned projection. */
6864
7574
  internalgetComponentMutationEpoch(componentId2) {
6865
7575
  return this.componentMutationEpochs[componentId2] ?? 0;
6866
7576
  }
7577
+ /** Borrow the component-version summary used to skip unrelated value columns. */
7578
+ internalgetComponentMutationEpochs() {
7579
+ return this.componentMutationEpochs;
7580
+ }
6867
7581
  /** Read a materialized relationship target in O(1 + k). */
6868
7582
  internalgetRelationshipTargetEntities(source, target) {
6869
7583
  return this.componentAccess.relationshipTargetEntries(source, target);
@@ -6872,28 +7586,19 @@ var World = class {
6872
7586
  internalgetRelationshipEpoch(source) {
6873
7587
  return this.core.relationshipIndexes.get(componentId(source))?.epoch ?? 0;
6874
7588
  }
6875
- /** Publish changes to a value derived without an authored mutation epoch. */
6876
- internalmarkDerivedComponentChanges(componentId2, entities) {
6877
- for (const entity of entities) {
6878
- this.changeJournal.append({ kind: "derived-component-changed", entity, componentId: componentId2 });
6879
- }
6880
- }
6881
7589
  /** Query facade write after the facade has already marked evidence. */
6882
7590
  internalsetQueryRow(entity, component, value) {
6883
- return this.componentAccess.set(entity, component, value, false);
7591
+ return this.componentAccess.set(
7592
+ entity,
7593
+ component,
7594
+ value,
7595
+ relationshipRole(component)?.kind === "source"
7596
+ );
6884
7597
  }
6885
7598
  /** Query facade read that does not re-enter the public World API. */
6886
7599
  internalgetQueryRow(entity, component) {
6887
7600
  return this.componentAccess.get(entity, component);
6888
7601
  }
6889
- /** Remove one component's change state after archetype removal. */
6890
- internalremoveComponentChange(entity, componentId2) {
6891
- this.changeJournal.append({ kind: "component-removed", entity, componentId: componentId2 });
6892
- }
6893
- /** Remove all change state before an entity handle is retired. */
6894
- internalremoveEntityChanges(entity) {
6895
- this.changeJournal.append({ kind: "entity-removed", entity });
6896
- }
6897
7602
  /** Return resource change ticks for diagnostics and resource-driven systems. */
6898
7603
  getResourceChange(name) {
6899
7604
  const entry = this.resources.entries.get(name);
@@ -6910,8 +7615,8 @@ var World = class {
6910
7615
  *
6911
7616
  * Not part of the public API.
6912
7617
  */
6913
- internalrouteError(err11, ctx) {
6914
- console.error(`[${ctx?.systemName ?? "World"}]`, err11);
7618
+ internalrouteError(err12, ctx) {
7619
+ console.error(`[${ctx?.systemName ?? "World"}]`, err12);
6915
7620
  }
6916
7621
  // ──────────────────────────────────────────────────────────────────────────
6917
7622
  // accessors — M1 extraction seam
@@ -7236,6 +7941,15 @@ var World = class {
7236
7941
  internalgetArrayView(entity, component, fieldName) {
7237
7942
  return this.componentAccess._getArrayView(entity, component, fieldName);
7238
7943
  }
7944
+ internalgetArrayLength(entity, component, fieldName) {
7945
+ return this.componentAccess._getArrayLength(entity, component, fieldName);
7946
+ }
7947
+ internalgetArrayElement(entity, component, fieldName, index) {
7948
+ return this.componentAccess._getArrayElement(entity, component, fieldName, index);
7949
+ }
7950
+ internalgetFieldValue(entity, component, fieldName) {
7951
+ return this.componentAccess._getFieldValue(entity, component, fieldName);
7952
+ }
7239
7953
  set(entity, component, value) {
7240
7954
  if (isRelationshipTarget(component)) return this.relationshipTargetWriteError(component, "set");
7241
7955
  return this.componentAccess.set(entity, component, value);
@@ -7304,8 +8018,16 @@ var World = class {
7304
8018
  this.componentAccess.releaseManagedRefsOnRow(a, c, r);
7305
8019
  }
7306
8020
  /** */
7307
- internalrelationshipOnInsert(h, c, v) {
7308
- return this.componentAccess.relationshipOnInsert(h, c, v);
8021
+ internalrelationshipOnInsert(h, c, v, preparation) {
8022
+ return this.componentAccess.relationshipOnInsert(h, c, v, preparation);
8023
+ }
8024
+ /** */
8025
+ internalprepareRelationshipInsert(c, v) {
8026
+ return this.componentAccess.prepareRelationshipInsert(c, v);
8027
+ }
8028
+ /** */
8029
+ internalreleaseRelationshipPreparation(preparation) {
8030
+ this.componentAccess.releaseRelationshipPreparation(preparation);
7309
8031
  }
7310
8032
  /** */
7311
8033
  internalrelationshipOnRemove(h, c, v) {