@forgeax/engine-ecs 0.1.32 → 0.1.34

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 (62) hide show
  1. package/README.md +35 -18
  2. package/dist/__tests__/query-idle.unit.test.d.ts +2 -0
  3. package/dist/__tests__/query-idle.unit.test.d.ts.map +1 -0
  4. package/dist/__tests__/set-allocation.unit.test.d.ts +2 -0
  5. package/dist/__tests__/set-allocation.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/state-projection.unit.test.d.ts +2 -0
  7. package/dist/__tests__/state-projection.unit.test.d.ts.map +1 -0
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.mjs +579 -623
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/projection/index.d.ts +2 -18
  13. package/dist/projection/index.d.ts.map +1 -1
  14. package/dist/projection/index.mjs +433 -23
  15. package/dist/projection/index.mjs.map +1 -1
  16. package/dist/projection/state-projection.d.ts +37 -0
  17. package/dist/projection/state-projection.d.ts.map +1 -0
  18. package/dist/query/query.d.ts.map +1 -1
  19. package/dist/shared-ref-store.d.ts +0 -24
  20. package/dist/shared-ref-store.d.ts.map +1 -1
  21. package/dist/shared.mjs.map +1 -1
  22. package/dist/storage/archetype-graph.d.ts +2 -0
  23. package/dist/storage/archetype-graph.d.ts.map +1 -1
  24. package/dist/storage/change-detection.d.ts +4 -0
  25. package/dist/storage/change-detection.d.ts.map +1 -1
  26. package/dist/storage/table.d.ts +6 -3
  27. package/dist/storage/table.d.ts.map +1 -1
  28. package/dist/world-internal.d.ts +0 -2
  29. package/dist/world-internal.d.ts.map +1 -1
  30. package/dist/world.d.ts +0 -2
  31. package/dist/world.d.ts.map +1 -1
  32. package/package.json +4 -4
  33. package/src/__tests__/component-version-surface.test.ts +1 -7
  34. package/src/__tests__/derived-range-writer.contract.test.ts +6 -0
  35. package/src/__tests__/ecs-core-reduction.characterization.test.ts +2 -3
  36. package/src/__tests__/execution-conflict-boundary.unit.test.ts +4 -0
  37. package/src/__tests__/externalization-render-read-lease.unit.test.ts +4 -10
  38. package/src/__tests__/query-idle.unit.test.ts +19 -0
  39. package/src/__tests__/set-allocation.unit.test.ts +30 -0
  40. package/src/__tests__/shared-ref-lifetime.unit.test.ts +1 -2
  41. package/src/__tests__/shared-ref-store.unit.test.ts +2 -34
  42. package/src/__tests__/state-projection.unit.test.ts +159 -0
  43. package/src/__tests__/world-health.contract.test.ts +0 -23
  44. package/src/index.ts +1 -1
  45. package/src/projection/index.ts +9 -39
  46. package/src/projection/state-projection.ts +250 -0
  47. package/src/query/query.ts +23 -16
  48. package/src/shared-ref-store.ts +0 -70
  49. package/src/storage/archetype-graph.ts +15 -1
  50. package/src/storage/change-detection.ts +36 -4
  51. package/src/storage/table.ts +20 -1
  52. package/src/world-internal.ts +0 -2
  53. package/src/world.ts +35 -38
  54. package/dist/__tests__/structural-evidence.contract.test-d.d.ts +0 -2
  55. package/dist/__tests__/structural-evidence.contract.test-d.d.ts.map +0 -1
  56. package/dist/__tests__/structural-evidence.contract.test.d.ts +0 -2
  57. package/dist/__tests__/structural-evidence.contract.test.d.ts.map +0 -1
  58. package/dist/storage/structural-evidence.d.ts +0 -30
  59. package/dist/storage/structural-evidence.d.ts.map +0 -1
  60. package/src/__tests__/structural-evidence.contract.test-d.ts +0 -6
  61. package/src/__tests__/structural-evidence.contract.test.ts +0 -49
  62. package/src/storage/structural-evidence.ts +0 -64
@@ -1,13 +1,9 @@
1
1
  import type { Result } from '@forgeax/engine-types';
2
2
  import type { Component } from '../component';
3
3
  import type { EntityHandle } from '../entity-handle';
4
- import type { SharedRefMutationRead } from '../shared-ref-store';
5
- import type { StructuralEvidenceRead } from '../storage/structural-evidence';
6
4
  import type { EcsError, World } from '../world';
7
5
  export { fillComponentDefaults } from '../component-default-fallback';
8
6
  export { ComponentNotDefinedError, InstanceTransformsStrideMismatchError, ManagedBufferOutOfBoundsError, ResourceInvalidValueError, SpawnLightInvalidBoundsError, SpriteAnimationInvalidError, SpriteInstancesCountMismatchError, SpriteInstancesMutuallyExclusiveWithInstancesError, SpriteInstancesRequiresSpriteShaderError, StaleEntityError, } from '../errors';
9
- /** Read producer-owned typed structural facts without inferring from a World scan. */
10
- export declare function readStructuralEvidence(world: World, cursor: number): StructuralEvidenceRead;
11
7
  export interface RenderProjectionComponentRequest {
12
8
  readonly component: Component;
13
9
  readonly fields: readonly string[];
@@ -21,29 +17,17 @@ export interface RenderProjectionSpan {
21
17
  }
22
18
  export interface RenderProjectionSpans {
23
19
  readonly generation: number;
24
- readonly sharedRefEpoch: number;
25
20
  readonly spans: readonly RenderProjectionSpan[];
26
21
  }
27
- export interface RenderChangeBatchOk {
28
- readonly status: 'ok';
22
+ export interface RenderChangeBatch {
29
23
  readonly version: RenderReadVersion;
30
24
  readonly world: RenderWorldChanges;
31
- readonly sharedRefs: SharedRefMutationRead;
32
- }
33
- export interface RenderChangeBatchRebuild {
34
- readonly status: 'rebuild';
35
- readonly version: RenderReadVersion;
36
- readonly resync: true;
37
- readonly reason: 'structure-changed';
38
- readonly world: RenderWorldChanges;
39
- readonly sharedRefs: SharedRefMutationRead;
40
25
  }
41
26
  export interface RenderWorldChanges {
42
27
  readonly fromEpoch: number;
43
28
  readonly toEpoch: number;
44
29
  readonly changedComponentIds: readonly number[];
45
30
  }
46
- export type RenderChangeBatch = RenderChangeBatchOk | RenderChangeBatchRebuild;
47
31
  export interface RenderReadLease {
48
32
  readonly worldIdentity: string;
49
33
  readonly generation: number;
@@ -61,7 +45,6 @@ export declare function readRenderArrayView(world: World, entity: EntityHandle,
61
45
  export interface RenderReadVersion {
62
46
  readonly mutationEpoch: number;
63
47
  readonly structureEpoch: number;
64
- readonly sharedRefEpoch: number;
65
48
  }
66
49
  /** Create the render-owned lease from the ECS projection boundary. */
67
50
  export declare function createRenderReadLease(world: World, token?: object): RenderReadLease;
@@ -75,4 +58,5 @@ export declare function setDerivedComponent(world: World, entity: EntityHandle,
75
58
  export declare function routeWorldError(world: World, error: unknown, context?: {
76
59
  readonly systemName: string;
77
60
  }): void;
61
+ export { createStateProjection, type StateProjection, type StateProjectionBatch, StateProjectionExpiredError, } from './state-projection';
78
62
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/projection/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/projection/index.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import '@forgeax/engine-types';
1
+ import { unpackSlot, pack, MAX_SLOT } from '@forgeax/engine-types';
2
2
 
3
3
  // src/component.ts
4
4
 
@@ -10,6 +10,9 @@ var definitions = globalSymbols[COMPONENT_REGISTRY] ?? (() => {
10
10
  globalSymbols[COMPONENT_REGISTRY] = registry;
11
11
  return registry;
12
12
  })();
13
+ function registerComponentDefinition(component, definition) {
14
+ definitions.definitions.set(component, definition);
15
+ }
13
16
  function componentDefinition(component) {
14
17
  const definition = definitions.definitions.get(component);
15
18
  if (definition === void 0) {
@@ -17,6 +20,22 @@ function componentDefinition(component) {
17
20
  }
18
21
  return definition;
19
22
  }
23
+ function deepFreeze(value) {
24
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
25
+ return value;
26
+ }
27
+ if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
28
+ return value;
29
+ }
30
+ const object = value;
31
+ for (const key of Reflect.ownKeys(object)) {
32
+ const child = object[key];
33
+ if (child !== null && (typeof child === "object" || typeof child === "function")) {
34
+ deepFreeze(child);
35
+ }
36
+ }
37
+ return Object.freeze(value);
38
+ }
20
39
 
21
40
  // src/errors/query-and-component-errors.ts
22
41
  var ComponentNotDefinedError = class extends Error {
@@ -248,7 +267,56 @@ var worldInternal = /* @__PURE__ */ Symbol.for(
248
267
  "forgeax.ecs.worldInternal"
249
268
  );
250
269
 
270
+ // src/execution/shared-kernel.ts
271
+ var WorldPoisonedError = class extends Error {
272
+ code = "world-poisoned";
273
+ expected = "World health is healthy before update";
274
+ hint = "stop scheduling this World and explicitly bootstrap a new World identity";
275
+ detail;
276
+ constructor(worldIdentity, fault) {
277
+ super(`World ${worldIdentity} is poisoned and cannot update.`);
278
+ this.name = "WorldPoisonedError";
279
+ this.detail = { worldIdentity, fault };
280
+ }
281
+ };
282
+
251
283
  // src/errors.ts
284
+ var EntityIndexOverflowError = class extends RangeError {
285
+ name = "EntityIndexOverflowError";
286
+ code = "entity-index-overflow";
287
+ hint;
288
+ constructor(index) {
289
+ const hint = "Entity index exceeds 24-bit max (16777215). Reduce simultaneous entity count or investigate entity leaks.";
290
+ super(
291
+ `Entity index ${index} exceeds 24-bit max (16777215).
292
+ index: ${index}
293
+ hint: ${hint}`
294
+ );
295
+ this.hint = hint;
296
+ }
297
+ };
298
+ var SchemaUnsupportedFieldError = class extends Error {
299
+ name = "SchemaUnsupportedFieldError";
300
+ code = "schema-unsupported-field";
301
+ hint;
302
+ constructor(fieldName, fieldType) {
303
+ let hint = "Supported types: f32 / f64 / i32 / u32 / i16 / u16 / i8 / u8 / bool / enum / ref.";
304
+ if (fieldType.startsWith("handle<") && fieldType.endsWith(">")) {
305
+ const tag = fieldType.slice(7, -1);
306
+ hint = `'handle<${tag}>' was removed in feat-20260614 M5; use 'shared<${tag}>' instead. The brand and storage layout are unchanged; only the keyword + write-barrier dispatch (SharedRefStore retain/release) is new.`;
307
+ } else if (fieldType.startsWith("ref<") && fieldType.endsWith(">")) {
308
+ const tag = fieldType.slice(4, -1);
309
+ hint = `'ref<${tag}>' was renamed in feat-20260614 M1; use 'unique<${tag}>' instead. The brand and storage layout are unchanged; the dispatch still routes through UniqueRefStore (single-holder direct release).`;
310
+ }
311
+ super(
312
+ `Schema field "${fieldName}" has unsupported type "${fieldType}".
313
+ field: ${fieldName}
314
+ type: ${fieldType}
315
+ hint: ${hint}`
316
+ );
317
+ this.hint = hint;
318
+ }
319
+ };
252
320
  var StaleEntityError = class extends Error {
253
321
  name = "StaleEntityError";
254
322
  code = "stale-entity";
@@ -325,6 +393,28 @@ var InstanceTransformsStrideMismatchError = class extends Error {
325
393
  this.detail = { actualLength, expectedStride: 16 };
326
394
  }
327
395
  };
396
+ var ManagedArrayElementTypeNotAllowedError = class extends Error {
397
+ name = "ManagedArrayElementTypeNotAllowedError";
398
+ code = "managed-array-element-type-not-allowed";
399
+ hint;
400
+ expected;
401
+ detail;
402
+ constructor(fieldName, elementType) {
403
+ const hint = `array<T> element type must be a scalar (f32/f64/i32/u32/i16/u16/i8/u8/bool/enum/ref) or entity. ref<X> / handle<X> / buffer:N / nested array<...> are forbidden on field "${fieldName}".`;
404
+ const expected = "element type in {scalar | entity}";
405
+ super(
406
+ `managed-array: element type not allowed.
407
+ code: managed-array-element-type-not-allowed
408
+ field: ${fieldName}
409
+ elementType: ${elementType}
410
+ expected: ${expected}
411
+ hint: ${hint}`
412
+ );
413
+ this.hint = hint;
414
+ this.expected = expected;
415
+ this.detail = { fieldName, elementType, hint };
416
+ }
417
+ };
328
418
 
329
419
  // src/component.ts
330
420
  var FIELD_SIZE_BYTES = {
@@ -340,6 +430,59 @@ var FIELD_SIZE_BYTES = {
340
430
  enum: 4,
341
431
  ref: 4
342
432
  };
433
+ var MANAGED_ARRAY_ELEMENT_TYPES = /* @__PURE__ */ new Set([
434
+ "f32",
435
+ "f64",
436
+ "i32",
437
+ "u32",
438
+ "i16",
439
+ "u16",
440
+ "i8",
441
+ "u8",
442
+ "bool",
443
+ "enum",
444
+ "ref",
445
+ "entity"
446
+ ]);
447
+ function isValidArrayElementType(elementType) {
448
+ if (MANAGED_ARRAY_ELEMENT_TYPES.has(elementType)) return true;
449
+ if (elementType.startsWith("shared<") && elementType.endsWith(">") && elementType.length > 9)
450
+ return true;
451
+ return false;
452
+ }
453
+ function parseManagedArraySchema(fieldType) {
454
+ if (!fieldType.startsWith("array<") || !fieldType.endsWith(">")) return null;
455
+ const inner = fieldType.slice(6, -1);
456
+ const commaIdx = inner.indexOf(",");
457
+ if (commaIdx === -1) {
458
+ if (!isValidArrayElementType(inner)) return null;
459
+ return { elementType: inner, length: void 0 };
460
+ }
461
+ const head = inner.slice(0, commaIdx).trim();
462
+ const tail = inner.slice(commaIdx + 1).trim();
463
+ if (!isValidArrayElementType(head)) return null;
464
+ if (!/^[1-9]\d*$/.test(tail)) return null;
465
+ return { elementType: head, length: Number.parseInt(tail, 10) };
466
+ }
467
+ function isSchemaVocabKeyword(s) {
468
+ if (s === "string") return true;
469
+ if (s === "entity") return true;
470
+ if (s === "buffer") return true;
471
+ if (s.startsWith("buffer<") && s.endsWith(">")) {
472
+ const tail = s.slice(7, -1);
473
+ return /^[1-9]\d*$/.test(tail);
474
+ }
475
+ if (s.startsWith("unique<") && s.endsWith(">")) {
476
+ return /^\w+$/.test(s.slice(7, -1));
477
+ }
478
+ if (s.startsWith("shared<") && s.endsWith(">")) {
479
+ return /^\w+$/.test(s.slice(7, -1));
480
+ }
481
+ if (s.startsWith("array<") && s.endsWith(">")) {
482
+ return parseManagedArraySchema(s) !== null;
483
+ }
484
+ return false;
485
+ }
343
486
  var COMPONENT_OWNER_REGISTRY = /* @__PURE__ */ Symbol.for("forgeax.ecs.componentOwnerRegistry");
344
487
  var ownerSymbols = globalThis;
345
488
  var ownerRegistry = ownerSymbols[COMPONENT_OWNER_REGISTRY] ?? (() => {
@@ -388,7 +531,7 @@ function scalarRow(t) {
388
531
  isArray: false
389
532
  };
390
533
  }
391
- Object.freeze({
534
+ var TYPE_METADATA = Object.freeze({
392
535
  f32: scalarRow("f32"),
393
536
  f64: scalarRow("f64"),
394
537
  i32: scalarRow("i32"),
@@ -460,7 +603,89 @@ Object.freeze({
460
603
  isArray: true
461
604
  }
462
605
  });
606
+ function fieldSpecType(fieldName, spec) {
607
+ if (typeof spec === "string") return spec;
608
+ const t = spec.type;
609
+ if (typeof t !== "string") {
610
+ throw new SchemaUnsupportedFieldError(
611
+ fieldName,
612
+ `<field-descriptor missing 'type'> (expected { type, default?, meta? })`
613
+ );
614
+ }
615
+ return t;
616
+ }
617
+ function defineComponent(name, fields, options) {
618
+ const storage = "table";
619
+ const schema = {};
620
+ const reflectedFields = {};
621
+ const collectedMeta = {};
622
+ const collectedDefaults = {};
623
+ for (const fieldName of Object.keys(fields)) {
624
+ const spec = fields[fieldName];
625
+ const fieldType = fieldSpecType(fieldName, spec);
626
+ let arrayMeta;
627
+ if (TYPE_METADATA[fieldType]?.isScalar === true) ; else if (fieldType === "string") ; else if (fieldType.startsWith("array<") && fieldType.endsWith(">")) {
628
+ const parsed = parseManagedArraySchema(fieldType);
629
+ if (parsed === null) {
630
+ const elementType = fieldType.slice(6, -1);
631
+ throw new ManagedArrayElementTypeNotAllowedError(fieldName, elementType);
632
+ }
633
+ arrayMeta = deepFreeze(
634
+ parsed.length === void 0 ? { elementType: parsed.elementType } : { elementType: parsed.elementType, length: parsed.length }
635
+ );
636
+ } else if (!isSchemaVocabKeyword(fieldType)) {
637
+ throw new SchemaUnsupportedFieldError(fieldName, fieldType);
638
+ }
639
+ schema[fieldName] = fieldType;
640
+ const row = {
641
+ type: fieldType
642
+ };
643
+ if (typeof spec !== "string") {
644
+ const desc = spec;
645
+ if ("default" in desc) {
646
+ row.default = desc.default;
647
+ collectedDefaults[fieldName] = desc.default;
648
+ }
649
+ if (desc.shape !== void 0) row.shape = desc.shape;
650
+ if (desc.meta !== void 0) {
651
+ Object.assign(collectedMeta, desc.meta);
652
+ }
653
+ if (desc.transient !== void 0) row.transient = desc.transient;
654
+ if (desc.labels !== void 0) row.labels = deepFreeze({ ...desc.labels });
655
+ }
656
+ if (arrayMeta !== void 0) row.arrayMeta = arrayMeta;
657
+ reflectedFields[fieldName] = Object.freeze(row);
658
+ }
659
+ const id = name === "Entity" ? 0 : ownerRegistry.nextId++;
660
+ const frozenDefaults = Object.keys(collectedDefaults).length === 0 ? void 0 : deepFreeze(collectedDefaults);
661
+ const frozenSchema = deepFreeze(schema);
662
+ const frozenFields = deepFreeze(reflectedFields);
663
+ const meta = collectedMeta;
664
+ const token = Object.freeze({ name, fields: frozenFields, storage });
665
+ ownerRegistry.ids.set(token, id);
666
+ ownerRegistry.schemas.set(token, frozenSchema);
667
+ registerComponentDefinition(token, {
668
+ fields: frozenFields,
669
+ defaults: frozenDefaults,
670
+ policy: {
671
+ transient: false,
672
+ meta,
673
+ requires: Object.freeze([...[]])
674
+ }
675
+ });
676
+ return Object.freeze(token);
677
+ }
678
+ var ENTITY_MAX_INDEX = MAX_SLOT;
463
679
  var ENTITY_NULL_RAW = 4294967295;
680
+ function encodeEntity(index, generation) {
681
+ if (index < 0 || index > ENTITY_MAX_INDEX) {
682
+ throw new EntityIndexOverflowError(index);
683
+ }
684
+ return pack(index, generation);
685
+ }
686
+ function entityIndex(entity) {
687
+ return unpackSlot(entity);
688
+ }
464
689
 
465
690
  // src/component-default-fallback.ts
466
691
  function typeDefault(fieldType) {
@@ -490,10 +715,210 @@ function fillComponentDefaults(token, raw) {
490
715
  return out;
491
716
  }
492
717
 
493
- // src/projection/index.ts
494
- function readStructuralEvidence(world, cursor) {
495
- return world[worldInternal].getStructuralEvidence().readAfter(cursor);
718
+ // src/entity.ts
719
+ var Entity = defineComponent("Entity", {
720
+ // Layer-2 default is never observed: `world.spawn` always overwrites `self`
721
+ // with the freshly encoded handle for the row. `null` is the type-correct
722
+ // "no handle yet" placeholder (`'entity'` decodes to `EntityHandle | null`).
723
+ self: { type: "entity", default: null }
724
+ });
725
+ defineComponent("Disabled", {});
726
+ Object.freeze([
727
+ componentId(Entity)
728
+ ]);
729
+
730
+ // src/storage/change-detection.ts
731
+ var PROJECTION_BLOCK_SIZE = 256;
732
+
733
+ // src/projection/state-projection.ts
734
+ var StateProjectionExpiredError = class extends Error {
735
+ code = "state-projection-expired";
736
+ expected = "an unmodified source and the latest valid projection candidate";
737
+ hint = "Read and apply the current state again before accepting the candidate.";
738
+ constructor() {
739
+ super("State projection candidate expired; read and apply the current state again.");
740
+ this.name = "StateProjectionExpiredError";
741
+ }
742
+ };
743
+ function createStateProjection(world, components, candidates = components) {
744
+ const owner = world[worldInternal];
745
+ const graph = owner.getGraph();
746
+ const ids = components.map(componentId);
747
+ const candidateIds = candidates.map(componentId);
748
+ const sparseCandidates = candidates.some((component) => component.storage === "sparse");
749
+ const baseline = /* @__PURE__ */ new Map();
750
+ let acceptedEpoch = -1;
751
+ let acceptedStructure = -1;
752
+ let invalid = true;
753
+ let readToken = 0;
754
+ let stamp = new Uint32Array(64);
755
+ let serial = 0;
756
+ const work = [];
757
+ function enqueue(index) {
758
+ if (index >= stamp.length) {
759
+ let size = stamp.length;
760
+ while (size <= index) size *= 2;
761
+ const next = new Uint32Array(size);
762
+ next.set(stamp);
763
+ stamp = next;
764
+ }
765
+ if (stamp[index] === serial) return;
766
+ stamp[index] = serial;
767
+ work.push(index);
768
+ }
769
+ return {
770
+ isCurrent() {
771
+ return world.execution.health !== "poisoned" && !invalid && acceptedEpoch === owner.getMutationEpoch() && acceptedStructure === owner.getStructureEpoch();
772
+ },
773
+ entity(index) {
774
+ const record = owner.getRecords()[index];
775
+ if (record === void 0 || record.archetypeId < 0) return void 0;
776
+ return encodeEntity(index, record.generation);
777
+ },
778
+ changed(entity, component) {
779
+ const id = componentId(component);
780
+ if ((owner.getComponentMutationEpochs()[id] ?? 0) <= acceptedEpoch) return false;
781
+ return (owner.getComponentChange(entity, id)?.changed ?? -1) > acceptedEpoch;
782
+ },
783
+ invalidate() {
784
+ readToken++;
785
+ invalid = true;
786
+ },
787
+ read() {
788
+ if (world.execution.health === "poisoned")
789
+ throw new WorldPoisonedError(world.identity, world.execution.fault);
790
+ const token = ++readToken;
791
+ const epoch = owner.getMutationEpoch();
792
+ const structure = owner.getStructureEpoch();
793
+ const changedComponents = components.filter(
794
+ (component) => (owner.getComponentMutationEpochs()[componentId(component)] ?? 0) > acceptedEpoch
795
+ );
796
+ const membershipChanged = invalid || structure !== acceptedStructure;
797
+ const changedRoots = invalid || structure !== acceptedStructure || ids.some((id) => (owner.getComponentMutationEpochs()[id] ?? 0) > acceptedEpoch);
798
+ work.length = 0;
799
+ serial = serial + 1 >>> 0;
800
+ if (serial === 0) {
801
+ stamp.fill(0);
802
+ serial = 1;
803
+ }
804
+ let scannedRows = 0;
805
+ let checkedBlocks = 0;
806
+ const updates = [];
807
+ const visited = /* @__PURE__ */ new Set();
808
+ if (changedRoots) {
809
+ const tables = /* @__PURE__ */ new Set();
810
+ if (sparseCandidates) {
811
+ for (const table of graph.activeTables) tables.add(table);
812
+ } else {
813
+ for (const id of candidateIds)
814
+ for (const table of graph.activeTablesByComponent.get(id) ?? []) tables.add(table);
815
+ }
816
+ for (const table of tables) {
817
+ visited.add(table);
818
+ const prior = baseline.get(table);
819
+ const entities = table.storage.get(componentId(Entity))?.fields.get("self")?.view;
820
+ if (entities === void 0) continue;
821
+ const columns = ids.flatMap((id) => {
822
+ const epochs = table.storage.get(id)?.epochs;
823
+ return epochs === void 0 ? [] : [epochs];
824
+ });
825
+ const blocks = Math.ceil(table.size / PROJECTION_BLOCK_SIZE);
826
+ for (let block = 0; block < blocks; block++) {
827
+ checkedBlocks++;
828
+ const previous = prior?.get(block);
829
+ const membership = table.membership[block] ?? 0;
830
+ const start = block * PROJECTION_BLOCK_SIZE;
831
+ const end = Math.min(table.size, start + PROJECTION_BLOCK_SIZE);
832
+ if (invalid || previous === void 0 || previous.membership !== membership) {
833
+ if (previous !== void 0) {
834
+ for (const index of previous.ids) enqueue(index);
835
+ scannedRows += previous.ids.length;
836
+ }
837
+ const current = new Uint32Array(end - start);
838
+ for (let row = start; row < end; row++) {
839
+ const index = entityIndex(entities[row]);
840
+ current[row - start] = index;
841
+ enqueue(index);
842
+ }
843
+ scannedRows += end - start;
844
+ updates.push({ table, block, value: { ids: current, membership } });
845
+ } else {
846
+ let changedBlock = false;
847
+ for (const column of columns) {
848
+ if ((column.blocks[block] ?? 0) <= acceptedEpoch) continue;
849
+ changedBlock = true;
850
+ for (let row = start; row < end; row++) {
851
+ if ((column.changed[row] ?? 0) > acceptedEpoch)
852
+ enqueue(entityIndex(entities[row]));
853
+ }
854
+ }
855
+ if (!changedBlock) continue;
856
+ scannedRows += end - start;
857
+ }
858
+ }
859
+ if (prior !== void 0) {
860
+ for (const [block, previous] of prior) {
861
+ if (block < blocks) continue;
862
+ checkedBlocks++;
863
+ for (const index of previous.ids) enqueue(index);
864
+ scannedRows += previous.ids.length;
865
+ updates.push({ table, block, value: void 0 });
866
+ }
867
+ }
868
+ }
869
+ for (const [table, prior] of baseline) {
870
+ if (visited.has(table)) continue;
871
+ for (const [block, previous] of prior) {
872
+ checkedBlocks++;
873
+ for (const index of previous.ids) enqueue(index);
874
+ scannedRows += previous.ids.length;
875
+ updates.push({ table, block, value: void 0 });
876
+ }
877
+ }
878
+ }
879
+ let accepted = false;
880
+ const validate = () => {
881
+ if (world.execution.health === "poisoned")
882
+ throw new WorldPoisonedError(world.identity, world.execution.fault);
883
+ if (token !== readToken || epoch !== owner.getMutationEpoch() || structure !== owner.getStructureEpoch()) {
884
+ throw new StateProjectionExpiredError();
885
+ }
886
+ };
887
+ return {
888
+ indices: work,
889
+ epoch,
890
+ scannedRows,
891
+ checkedBlocks,
892
+ membershipChanged,
893
+ changedComponents,
894
+ validate,
895
+ accept() {
896
+ if (accepted) return;
897
+ validate();
898
+ for (const update of updates) {
899
+ let blocks = baseline.get(update.table);
900
+ if (update.value === void 0) {
901
+ blocks?.delete(update.block);
902
+ if (blocks?.size === 0) baseline.delete(update.table);
903
+ } else {
904
+ if (blocks === void 0) {
905
+ blocks = /* @__PURE__ */ new Map();
906
+ baseline.set(update.table, blocks);
907
+ }
908
+ blocks.set(update.block, update.value);
909
+ }
910
+ }
911
+ acceptedEpoch = epoch;
912
+ acceptedStructure = structure;
913
+ invalid = false;
914
+ accepted = true;
915
+ }
916
+ };
917
+ }
918
+ };
496
919
  }
920
+
921
+ // src/projection/index.ts
497
922
  function readRenderArrayView(world, entity, component, fieldName) {
498
923
  return world[worldInternal].getArrayView(entity, component, fieldName);
499
924
  }
@@ -522,12 +947,10 @@ function readProjectionSpans(world, generation, request) {
522
947
  }
523
948
  return {
524
949
  generation,
525
- sharedRefEpoch: world[worldInternal].getSharedRefs().getMutationEpoch(),
526
950
  spans: Object.freeze(spans)
527
951
  };
528
952
  }
529
953
  function createRenderReadLease(world, token = {}) {
530
- const sharedRefs = world[worldInternal].getSharedRefs();
531
954
  let disposed = false;
532
955
  const assertLive = () => {
533
956
  if (disposed) throw new Error("RenderReadLease is disposed.");
@@ -535,8 +958,7 @@ function createRenderReadLease(world, token = {}) {
535
958
  const captureVersion = () => {
536
959
  return {
537
960
  mutationEpoch: world[worldInternal].getMutationEpoch(),
538
- structureEpoch: world[worldInternal].getStructureEpoch(),
539
- sharedRefEpoch: sharedRefs.getMutationEpoch()
961
+ structureEpoch: world[worldInternal].getStructureEpoch()
540
962
  };
541
963
  };
542
964
  return {
@@ -562,20 +984,8 @@ function createRenderReadLease(world, token = {}) {
562
984
  toEpoch,
563
985
  changedComponentIds
564
986
  };
565
- const sharedRead = sharedRefs.readChangesSince(start.sharedRefEpoch);
566
987
  const version = captureVersion();
567
- const structureChanged = start.structureEpoch !== world[worldInternal].getStructureEpoch();
568
- if (structureChanged) {
569
- return {
570
- status: "rebuild",
571
- version,
572
- resync: true,
573
- reason: "structure-changed",
574
- world: worldRead,
575
- sharedRefs: sharedRead
576
- };
577
- }
578
- return { status: "ok", version, world: worldRead, sharedRefs: sharedRead };
988
+ return { version, world: worldRead };
579
989
  },
580
990
  querySpans(request) {
581
991
  assertLive();
@@ -600,6 +1010,6 @@ function routeWorldError(world, error, context) {
600
1010
  world[worldInternal].routeError(error, context);
601
1011
  }
602
1012
 
603
- export { ComponentNotDefinedError, InstanceTransformsStrideMismatchError, ManagedBufferOutOfBoundsError, ResourceInvalidValueError, SpawnLightInvalidBoundsError, SpriteAnimationInvalidError, SpriteInstancesCountMismatchError, SpriteInstancesMutuallyExclusiveWithInstancesError, SpriteInstancesRequiresSpriteShaderError, StaleEntityError, createRenderReadLease, fillComponentDefaults, readRenderArrayView, readStructuralEvidence, routeWorldError, setDerivedComponent };
1013
+ export { ComponentNotDefinedError, InstanceTransformsStrideMismatchError, ManagedBufferOutOfBoundsError, ResourceInvalidValueError, SpawnLightInvalidBoundsError, SpriteAnimationInvalidError, SpriteInstancesCountMismatchError, SpriteInstancesMutuallyExclusiveWithInstancesError, SpriteInstancesRequiresSpriteShaderError, StaleEntityError, StateProjectionExpiredError, createRenderReadLease, createStateProjection, fillComponentDefaults, readRenderArrayView, routeWorldError, setDerivedComponent };
604
1014
  //# sourceMappingURL=index.mjs.map
605
1015
  //# sourceMappingURL=index.mjs.map