@forgeax/engine-ecs 0.0.0-dev.8d955ade1c79
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.
- package/LICENSE +202 -0
- package/README.md +278 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__fixtures__/field-shape-fixture.d.ts +16 -0
- package/dist/__fixtures__/field-shape-fixture.d.ts.map +1 -0
- package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts +3 -0
- package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts.map +1 -0
- package/dist/__tests__/archetype.unit.test.d.ts +2 -0
- package/dist/__tests__/archetype.unit.test.d.ts.map +1 -0
- package/dist/__tests__/array-handle-element.test-d.d.ts +2 -0
- package/dist/__tests__/array-handle-element.test-d.d.ts.map +1 -0
- package/dist/__tests__/buffer-array-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/buffer-array-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts +2 -0
- package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts.map +1 -0
- package/dist/__tests__/buffer-pool.test-d.d.ts +2 -0
- package/dist/__tests__/buffer-pool.test-d.d.ts.map +1 -0
- package/dist/__tests__/change-detection.unit.test.d.ts +2 -0
- package/dist/__tests__/change-detection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/command-buffer.test.d.ts +2 -0
- package/dist/__tests__/command-buffer.test.d.ts.map +1 -0
- package/dist/__tests__/commands-deferred.unit.test.d.ts +2 -0
- package/dist/__tests__/commands-deferred.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-catalog.test.d.ts +2 -0
- package/dist/__tests__/component-catalog.test.d.ts.map +1 -0
- package/dist/__tests__/component-definition-identity.unit.test.d.ts +2 -0
- package/dist/__tests__/component-definition-identity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-nan-preflight.unit.test.d.ts +2 -0
- package/dist/__tests__/component-nan-preflight.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-schema.test-d.d.ts +2 -0
- package/dist/__tests__/component-schema.test-d.d.ts.map +1 -0
- package/dist/__tests__/component-schema.test.d.ts +2 -0
- package/dist/__tests__/component-schema.test.d.ts.map +1 -0
- package/dist/__tests__/define-component-options.test-d.d.ts +2 -0
- package/dist/__tests__/define-component-options.test-d.d.ts.map +1 -0
- package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts +2 -0
- package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/despawn-all.unit.test.d.ts +2 -0
- package/dist/__tests__/despawn-all.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts +2 -0
- package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts.map +1 -0
- package/dist/__tests__/ecs-core-reduction.perf.test.d.ts +2 -0
- package/dist/__tests__/ecs-core-reduction.perf.test.d.ts.map +1 -0
- package/dist/__tests__/ecs-error-code-31.test-d.d.ts +2 -0
- package/dist/__tests__/ecs-error-code-31.test-d.d.ts.map +1 -0
- package/dist/__tests__/entity-disabling.unit.test.d.ts +2 -0
- package/dist/__tests__/entity-disabling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts +2 -0
- package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-shared-storage.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-shared-storage.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-portable-value.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-portable-value.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-render-read-lease.test-d.d.ts +2 -0
- package/dist/__tests__/externalization-render-read-lease.test-d.d.ts.map +1 -0
- package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-root-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-root-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/field-enum-labels-reflection.test.d.ts +2 -0
- package/dist/__tests__/field-enum-labels-reflection.test.d.ts.map +1 -0
- package/dist/__tests__/field-shape-fixture.test.d.ts +2 -0
- package/dist/__tests__/field-shape-fixture.test.d.ts.map +1 -0
- package/dist/__tests__/field-transient-reflection.test.d.ts +2 -0
- package/dist/__tests__/field-transient-reflection.test.d.ts.map +1 -0
- package/dist/__tests__/field-value-type.test-d.d.ts +2 -0
- package/dist/__tests__/field-value-type.test-d.d.ts.map +1 -0
- package/dist/__tests__/fixed-update.unit.test.d.ts +2 -0
- package/dist/__tests__/fixed-update.unit.test.d.ts.map +1 -0
- package/dist/__tests__/handle.test-d.d.ts +2 -0
- package/dist/__tests__/handle.test-d.d.ts.map +1 -0
- package/dist/__tests__/hierarchy.unit.test.d.ts +2 -0
- package/dist/__tests__/hierarchy.unit.test.d.ts.map +1 -0
- package/dist/__tests__/managed-array-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/managed-array-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/minimal-example.test-d.d.ts +2 -0
- package/dist/__tests__/minimal-example.test-d.d.ts.map +1 -0
- package/dist/__tests__/name-read-types.test-d.d.ts +2 -0
- package/dist/__tests__/name-read-types.test-d.d.ts.map +1 -0
- package/dist/__tests__/plugin-service.test.d.ts +2 -0
- package/dist/__tests__/plugin-service.test.d.ts.map +1 -0
- package/dist/__tests__/query-combinations.unit.test.d.ts +2 -0
- package/dist/__tests__/query-combinations.unit.test.d.ts.map +1 -0
- package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts +2 -0
- package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts.map +1 -0
- package/dist/__tests__/query-storage-trends.perf.test.d.ts +2 -0
- package/dist/__tests__/query-storage-trends.perf.test.d.ts.map +1 -0
- package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts +2 -0
- package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts.map +1 -0
- package/dist/__tests__/query.unit.test.d.ts +2 -0
- package/dist/__tests__/query.unit.test.d.ts.map +1 -0
- package/dist/__tests__/relationship-index.perf.test.d.ts +2 -0
- package/dist/__tests__/relationship-index.perf.test.d.ts.map +1 -0
- package/dist/__tests__/relationship-index.test.d.ts +2 -0
- package/dist/__tests__/relationship-index.test.d.ts.map +1 -0
- package/dist/__tests__/scalar-field-type-owner.test-d.d.ts +2 -0
- package/dist/__tests__/scalar-field-type-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/schedule-determinism.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-determinism.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-inspection.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-inspection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-scope-error.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-scope-error.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-token.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-token.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schema-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/schema-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/serialization.unit.test.d.ts +2 -0
- package/dist/__tests__/serialization.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts +2 -0
- package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts.map +1 -0
- package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts +2 -0
- package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shared-ref-store.unit.test.d.ts +2 -0
- package/dist/__tests__/shared-ref-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sparse-tag.unit.test.d.ts +2 -0
- package/dist/__tests__/sparse-tag.unit.test.d.ts.map +1 -0
- package/dist/__tests__/spawn-field-error.test-d.d.ts +2 -0
- package/dist/__tests__/spawn-field-error.test-d.d.ts.map +1 -0
- package/dist/__tests__/stale-error.test-d.d.ts +2 -0
- package/dist/__tests__/stale-error.test-d.d.ts.map +1 -0
- package/dist/__tests__/string-view-symmetry.test-d.d.ts +2 -0
- package/dist/__tests__/string-view-symmetry.test-d.d.ts.map +1 -0
- package/dist/__tests__/string-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/string-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts +2 -0
- package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts.map +1 -0
- package/dist/__tests__/structure-epoch.unit.test.d.ts +2 -0
- package/dist/__tests__/structure-epoch.unit.test.d.ts.map +1 -0
- package/dist/__tests__/table-archetype-mapping.unit.test.d.ts +2 -0
- package/dist/__tests__/table-archetype-mapping.unit.test.d.ts.map +1 -0
- package/dist/__tests__/time.unit.test.d.ts +2 -0
- package/dist/__tests__/time.unit.test.d.ts.map +1 -0
- package/dist/__tests__/types.test-d.d.ts +2 -0
- package/dist/__tests__/types.test-d.d.ts.map +1 -0
- package/dist/__tests__/unique-ref-store.unit.test.d.ts +2 -0
- package/dist/__tests__/unique-ref-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/utils/handle-numeric.d.ts +14 -0
- package/dist/__tests__/utils/handle-numeric.d.ts.map +1 -0
- package/dist/__tests__/validation-root-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/validation-root-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-atomic-write.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-atomic-write.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-change-journal.unit.test.d.ts +2 -0
- package/dist/__tests__/world-change-journal.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-characterization.unit.test.d.ts +2 -0
- package/dist/__tests__/world-characterization.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-consumer-inference.test-d.d.ts +2 -0
- package/dist/__tests__/world-consumer-inference.test-d.d.ts.map +1 -0
- package/dist/__tests__/world-health.contract.test.d.ts +2 -0
- package/dist/__tests__/world-health.contract.test.d.ts.map +1 -0
- package/dist/__tests__/world-inspection.test.d.ts +2 -0
- package/dist/__tests__/world-inspection.test.d.ts.map +1 -0
- package/dist/__tests__/world-internal-identity.unit.test.d.ts +2 -0
- package/dist/__tests__/world-internal-identity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-scheduling.m4.test.d.ts +2 -0
- package/dist/__tests__/world-scheduling.m4.test.d.ts.map +1 -0
- package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world.test-d.d.ts +2 -0
- package/dist/__tests__/world.test-d.d.ts.map +1 -0
- package/dist/buffer-pool.d.ts +109 -0
- package/dist/buffer-pool.d.ts.map +1 -0
- package/dist/commands.d.ts +93 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/component-default-fallback.d.ts +88 -0
- package/dist/component-default-fallback.d.ts.map +1 -0
- package/dist/component-schema.d.ts +34 -0
- package/dist/component-schema.d.ts.map +1 -0
- package/dist/component-value-validate.d.ts +29 -0
- package/dist/component-value-validate.d.ts.map +1 -0
- package/dist/component.d.ts +581 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/entity-handle.d.ts +68 -0
- package/dist/entity-handle.d.ts.map +1 -0
- package/dist/entity.d.ts +86 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/errors/query-and-component-errors.d.ts +132 -0
- package/dist/errors/query-and-component-errors.d.ts.map +1 -0
- package/dist/errors/relationship-errors.d.ts +112 -0
- package/dist/errors/relationship-errors.d.ts.map +1 -0
- package/dist/errors/sprite-and-shared-errors.d.ts +107 -0
- package/dist/errors/sprite-and-shared-errors.d.ts.map +1 -0
- package/dist/errors/validation-errors.d.ts +229 -0
- package/dist/errors/validation-errors.d.ts.map +1 -0
- package/dist/errors.d.ts +763 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/execution/shared-kernel.d.ts +106 -0
- package/dist/execution/shared-kernel.d.ts.map +1 -0
- package/dist/externalization/index.d.ts +31 -0
- package/dist/externalization/index.d.ts.map +1 -0
- package/dist/externalization/index.mjs +298 -0
- package/dist/externalization/index.mjs.map +1 -0
- package/dist/index.d.ts +261 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +7422 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal.mjs +479 -0
- package/dist/internal.mjs.map +1 -0
- package/dist/plugin-service.d.ts +11 -0
- package/dist/plugin-service.d.ts.map +1 -0
- package/dist/projection/index.d.ts +103 -0
- package/dist/projection/index.d.ts.map +1 -0
- package/dist/projection/index.mjs +615 -0
- package/dist/projection/index.mjs.map +1 -0
- package/dist/query/query.d.ts +58 -0
- package/dist/query/query.d.ts.map +1 -0
- package/dist/relationship-index.d.ts +81 -0
- package/dist/relationship-index.d.ts.map +1 -0
- package/dist/resource.d.ts +70 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/schedule-token.d.ts +16 -0
- package/dist/schedule-token.d.ts.map +1 -0
- package/dist/schedule.d.ts +271 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/shared-ref-store.d.ts +144 -0
- package/dist/shared-ref-store.d.ts.map +1 -0
- package/dist/shared.d.ts +3 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.mjs +364 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/storage/archetype-graph.d.ts +54 -0
- package/dist/storage/archetype-graph.d.ts.map +1 -0
- package/dist/storage/archetype.d.ts +23 -0
- package/dist/storage/archetype.d.ts.map +1 -0
- package/dist/storage/change-detection.d.ts +40 -0
- package/dist/storage/change-detection.d.ts.map +1 -0
- package/dist/storage/column.d.ts +111 -0
- package/dist/storage/column.d.ts.map +1 -0
- package/dist/storage/table.d.ts +29 -0
- package/dist/storage/table.d.ts.map +1 -0
- package/dist/time.d.ts +57 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/unique-ref-store.d.ts +105 -0
- package/dist/unique-ref-store.d.ts.map +1 -0
- package/dist/world-change-journal.d.ts +34 -0
- package/dist/world-change-journal.d.ts.map +1 -0
- package/dist/world-component-access.d.ts +265 -0
- package/dist/world-component-access.d.ts.map +1 -0
- package/dist/world-component-storage.d.ts +282 -0
- package/dist/world-component-storage.d.ts.map +1 -0
- package/dist/world-core.d.ts +39 -0
- package/dist/world-core.d.ts.map +1 -0
- package/dist/world-entity-lifecycle.d.ts +30 -0
- package/dist/world-entity-lifecycle.d.ts.map +1 -0
- package/dist/world-internal.d.ts +14 -0
- package/dist/world-internal.d.ts.map +1 -0
- package/dist/world-scheduling.d.ts +30 -0
- package/dist/world-scheduling.d.ts.map +1 -0
- package/dist/world.d.ts +620 -0
- package/dist/world.d.ts.map +1 -0
- package/package.json +77 -0
- package/src/__fixtures__/field-shape-fixture.ts +30 -0
- package/src/__tests__/__fixtures__/inspect-scripts.snapshot.ts +2 -0
- package/src/__tests__/archetype.unit.test.ts +1837 -0
- package/src/__tests__/array-handle-element.test-d.ts +34 -0
- package/src/__tests__/buffer-array-vocab.test-d.ts +53 -0
- package/src/__tests__/buffer-field-input-widen.unit.test.ts +99 -0
- package/src/__tests__/buffer-pool.test-d.ts +32 -0
- package/src/__tests__/change-detection.unit.test.ts +86 -0
- package/src/__tests__/command-buffer.test.ts +179 -0
- package/src/__tests__/commands-deferred.unit.test.ts +68 -0
- package/src/__tests__/component-catalog.test.ts +59 -0
- package/src/__tests__/component-definition-identity.unit.test.ts +24 -0
- package/src/__tests__/component-nan-preflight.unit.test.ts +186 -0
- package/src/__tests__/component-schema.test-d.ts +119 -0
- package/src/__tests__/component-schema.test.ts +55 -0
- package/src/__tests__/define-component-options.test-d.ts +35 -0
- package/src/__tests__/deprecated-vocab-rejection.unit.test.ts +59 -0
- package/src/__tests__/despawn-all.unit.test.ts +17 -0
- package/src/__tests__/ecs-core-reduction.characterization.test.ts +130 -0
- package/src/__tests__/ecs-core-reduction.perf.test.ts +260 -0
- package/src/__tests__/ecs-error-code-31.test-d.ts +42 -0
- package/src/__tests__/entity-disabling.unit.test.ts +37 -0
- package/src/__tests__/entity-handle-codec-parity.unit.test.ts +126 -0
- package/src/__tests__/errors.unit.test.ts +1456 -0
- package/src/__tests__/execution-conflict-boundary.unit.test.ts +123 -0
- package/src/__tests__/execution-shared-storage.unit.test.ts +40 -0
- package/src/__tests__/externalization-portable-value.unit.test.ts +275 -0
- package/src/__tests__/externalization-projection.unit.test.ts +325 -0
- package/src/__tests__/externalization-render-read-lease.test-d.ts +30 -0
- package/src/__tests__/externalization-render-read-lease.unit.test.ts +79 -0
- package/src/__tests__/externalization-root-surface.unit.test.ts +33 -0
- package/src/__tests__/field-enum-labels-reflection.test.ts +66 -0
- package/src/__tests__/field-shape-fixture.test.ts +61 -0
- package/src/__tests__/field-transient-reflection.test.ts +59 -0
- package/src/__tests__/field-value-type.test-d.ts +57 -0
- package/src/__tests__/fixed-update.unit.test.ts +78 -0
- package/src/__tests__/handle.test-d.ts +86 -0
- package/src/__tests__/hierarchy.unit.test.ts +2534 -0
- package/src/__tests__/managed-array-vocab.test-d.ts +137 -0
- package/src/__tests__/minimal-example.test-d.ts +42 -0
- package/src/__tests__/name-read-types.test-d.ts +72 -0
- package/src/__tests__/plugin-service.test.ts +13 -0
- package/src/__tests__/query-combinations.unit.test.ts +42 -0
- package/src/__tests__/query-light-extract-flat-column-ratio.perf.test.ts +252 -0
- package/src/__tests__/query-storage-trends.perf.test.ts +104 -0
- package/src/__tests__/query-trs-flat-column-ratio.perf.test.ts +194 -0
- package/src/__tests__/query.unit.test.ts +196 -0
- package/src/__tests__/relationship-index.perf.test.ts +126 -0
- package/src/__tests__/relationship-index.test.ts +70 -0
- package/src/__tests__/scalar-field-type-owner.test-d.ts +71 -0
- package/src/__tests__/schedule-determinism.unit.test.ts +36 -0
- package/src/__tests__/schedule-inspection.unit.test.ts +81 -0
- package/src/__tests__/schedule-scope-error.unit.test.ts +63 -0
- package/src/__tests__/schedule-token.unit.test.ts +54 -0
- package/src/__tests__/schema-vocab.test-d.ts +103 -0
- package/src/__tests__/serialization.unit.test.ts +2740 -0
- package/src/__tests__/shared-kernel-eligibility.test-d.ts +18 -0
- package/src/__tests__/shared-kernel-eligibility.unit.test.ts +78 -0
- package/src/__tests__/shared-ref-store.unit.test.ts +646 -0
- package/src/__tests__/sparse-tag.unit.test.ts +201 -0
- package/src/__tests__/spawn-field-error.test-d.ts +41 -0
- package/src/__tests__/stale-error.test-d.ts +208 -0
- package/src/__tests__/string-view-symmetry.test-d.ts +26 -0
- package/src/__tests__/string-vocab.test-d.ts +49 -0
- package/src/__tests__/structure-epoch-boundaries.unit.test.ts +39 -0
- package/src/__tests__/structure-epoch.unit.test.ts +48 -0
- package/src/__tests__/table-archetype-mapping.unit.test.ts +78 -0
- package/src/__tests__/time.unit.test.ts +79 -0
- package/src/__tests__/types.test-d.ts +38 -0
- package/src/__tests__/unique-ref-store.unit.test.ts +348 -0
- package/src/__tests__/utils/handle-numeric.ts +30 -0
- package/src/__tests__/validation-root-surface.unit.test.ts +15 -0
- package/src/__tests__/visibility-atomic-write.unit.test.ts +82 -0
- package/src/__tests__/world-change-journal.unit.test.ts +97 -0
- package/src/__tests__/world-characterization.unit.test.ts +408 -0
- package/src/__tests__/world-consumer-inference.test-d.ts +16 -0
- package/src/__tests__/world-health.contract.test.ts +87 -0
- package/src/__tests__/world-inspection.test.ts +37 -0
- package/src/__tests__/world-internal-identity.unit.test.ts +17 -0
- package/src/__tests__/world-managed-roundtrip.unit.test.ts +98 -0
- package/src/__tests__/world-scheduling.m4.test.ts +52 -0
- package/src/__tests__/world-unique-roundtrip.unit.test.ts +357 -0
- package/src/__tests__/world.test-d.ts +156 -0
- package/src/buffer-pool.ts +357 -0
- package/src/commands.ts +644 -0
- package/src/component-default-fallback.ts +236 -0
- package/src/component-schema.ts +92 -0
- package/src/component-value-validate.ts +135 -0
- package/src/component.ts +1268 -0
- package/src/entity-handle.ts +104 -0
- package/src/entity.ts +140 -0
- package/src/errors/query-and-component-errors.ts +200 -0
- package/src/errors/relationship-errors.ts +198 -0
- package/src/errors/sprite-and-shared-errors.ts +231 -0
- package/src/errors/validation-errors.ts +506 -0
- package/src/errors.ts +1464 -0
- package/src/execution/shared-kernel.ts +392 -0
- package/src/externalization/index.ts +170 -0
- package/src/index.ts +414 -0
- package/src/plugin-service.ts +35 -0
- package/src/projection/index.ts +318 -0
- package/src/query/query.ts +606 -0
- package/src/relationship-index.ts +175 -0
- package/src/resource.ts +106 -0
- package/src/schedule-token.ts +40 -0
- package/src/schedule.ts +846 -0
- package/src/shared-ref-store.ts +418 -0
- package/src/shared.ts +23 -0
- package/src/storage/archetype-graph.ts +166 -0
- package/src/storage/archetype.ts +78 -0
- package/src/storage/change-detection.ts +231 -0
- package/src/storage/column.ts +247 -0
- package/src/storage/table.ts +153 -0
- package/src/time.ts +122 -0
- package/src/unique-ref-store.ts +278 -0
- package/src/world-change-journal.ts +84 -0
- package/src/world-component-access.ts +1421 -0
- package/src/world-component-storage.ts +1178 -0
- package/src/world-core.ts +74 -0
- package/src/world-entity-lifecycle.ts +539 -0
- package/src/world-internal.ts +75 -0
- package/src/world-scheduling.ts +499 -0
- package/src/world.ts +1399 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// @forgeax/engine-ecs — Entity handle.
|
|
2
|
+
//
|
|
3
|
+
// Encoding: u32 = (generation << 24) | (index & 0xFFFFFF)
|
|
4
|
+
// - index: 24 bits — supports up to 16_777_215 simultaneous entities.
|
|
5
|
+
// - generation: 8 bits — retirement when gen exceeds 255 (gen 255 usable);
|
|
6
|
+
// index permanently retired when bumped generation would reach 256.
|
|
7
|
+
//
|
|
8
|
+
// Key difference from @forgeax/engine-ecs: generation does NOT wrap 255 → 0.
|
|
9
|
+
// When generation would exceed 255 (256), the index is permanently retired
|
|
10
|
+
// from the free list to prevent handle aliasing (D-08).
|
|
11
|
+
//
|
|
12
|
+
// feat-20260623-asset-handle-generation M2 / w4: encodeEntity/decodeEntity/
|
|
13
|
+
// entityIndex/entityGeneration are now thin wrappers over the shared gen-slot
|
|
14
|
+
// codec in @forgeax/engine-types (pack / unpackSlot / unpackGen). The
|
|
15
|
+
// `EntityIndexOverflowError` throw and `ENTITY_NULL_RAW` sentinel stay in ecs
|
|
16
|
+
// (D-1). Constants re-export codec values for backward-compatible names.
|
|
17
|
+
|
|
18
|
+
import { MAX_GEN, MAX_SLOT, pack, unpackGen, unpackSlot } from '@forgeax/engine-types';
|
|
19
|
+
import { EntityIndexOverflowError } from './errors';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Branded `number` representing an Entity handle. Stored as a JS `number` but
|
|
23
|
+
* holds the u32 bit pattern (generation << 24) | index.
|
|
24
|
+
*
|
|
25
|
+
* The phantom `__entity` brand prevents accidental mixing with other numbers.
|
|
26
|
+
*
|
|
27
|
+
* Naming: the type-space alias is `EntityHandle` (the branded number that
|
|
28
|
+
* identifies a row). The same-named value-space `Entity` re-exported from the
|
|
29
|
+
* package barrel is the id=0 component token (see `./entity`).
|
|
30
|
+
*/
|
|
31
|
+
export type EntityHandle = number & { readonly __entity: unique symbol };
|
|
32
|
+
|
|
33
|
+
/** Maximum representable entity index (2^24 - 1 = 16_777_215). Re-exports codec MAX_SLOT. */
|
|
34
|
+
export const ENTITY_MAX_INDEX = MAX_SLOT;
|
|
35
|
+
|
|
36
|
+
/** Maximum representable generation (2^8 - 1 = 255). Re-exports codec MAX_GEN. */
|
|
37
|
+
export const ENTITY_MAX_GENERATION = MAX_GEN;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sentinel u32 value reserved for the "null entity" slot in `entity`-typed
|
|
41
|
+
* component fields.
|
|
42
|
+
*
|
|
43
|
+
* The encoding `(gen << 24) | index` yields `0xFFFFFFFF` only at the very
|
|
44
|
+
* last valid (gen=255, index=0xFFFFFF) entity, which retires permanently on
|
|
45
|
+
* its first despawn (D-08). Carving out this single bit pattern as the null
|
|
46
|
+
* sentinel costs at most one slot at the far edge of the entity space.
|
|
47
|
+
*
|
|
48
|
+
* Stored u32 column reads compare against `ENTITY_NULL_RAW` first; the
|
|
49
|
+
* column-level `Entity | null` decode lives in `world.readRow`.
|
|
50
|
+
*
|
|
51
|
+
* Scene-as-World-Blueprint anchor (feat-20260514 w12, R-8 lockdown):
|
|
52
|
+
* This module (`packages/ecs/src/entity.ts`) is the canonical export site
|
|
53
|
+
* for the entity null sentinel. Downstream consumers (M2 instantiate
|
|
54
|
+
* layer 3 fallback for `'entity'`-typed component fields, w22) must import
|
|
55
|
+
* from the package barrel:
|
|
56
|
+
*
|
|
57
|
+
* import { ENTITY_NULL_RAW } from '@forgeax/engine-ecs'
|
|
58
|
+
*
|
|
59
|
+
* The raw `0xFFFFFFFF` literal MUST NOT be duplicated at consumer sites
|
|
60
|
+
* (charter proposition 1: SSOT lives here). The decoded JS-side value is
|
|
61
|
+
* `null` (returned by `world.get(e, C).<entityField>`). Layer 3 default
|
|
62
|
+
* for `'entity'` keyword fields stores `ENTITY_NULL_RAW` into the u32
|
|
63
|
+
* column, which decodes back to `null` on read. ecs-managed-buffer feat
|
|
64
|
+
* export verification (w12 grep): see `packages/ecs/src/index.ts` line
|
|
65
|
+
* re-exporting this constant alongside `ENTITY_MAX_GENERATION /
|
|
66
|
+
* ENTITY_MAX_INDEX`. No add-only fallback re-export is required at this
|
|
67
|
+
* time.
|
|
68
|
+
*/
|
|
69
|
+
export const ENTITY_NULL_RAW = 0xffffffff;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Encode (index, generation) into a u32 entity handle.
|
|
73
|
+
*
|
|
74
|
+
* Delegates to shared codec `pack(index, generation)` after ecs-specific
|
|
75
|
+
* overflow validation. The `>>> 0` anti-ToInt32 guard is inherited from the
|
|
76
|
+
* codec (D-7 hard constraint).
|
|
77
|
+
*
|
|
78
|
+
* @throws EntityIndexOverflowError when `index > ENTITY_MAX_INDEX` or `index < 0`.
|
|
79
|
+
*/
|
|
80
|
+
export function encodeEntity(index: number, generation: number): EntityHandle {
|
|
81
|
+
if (index < 0 || index > ENTITY_MAX_INDEX) {
|
|
82
|
+
throw new EntityIndexOverflowError(index);
|
|
83
|
+
}
|
|
84
|
+
return pack(index, generation) as EntityHandle;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Decode a u32 entity handle into its (index, generation) pair via shared codec. */
|
|
88
|
+
export function decodeEntity(entity: EntityHandle): { index: number; generation: number } {
|
|
89
|
+
const e = entity as unknown as number;
|
|
90
|
+
return {
|
|
91
|
+
index: unpackSlot(e),
|
|
92
|
+
generation: unpackGen(e),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Extract just the index slot from an entity handle via shared codec. */
|
|
97
|
+
export function entityIndex(entity: EntityHandle): number {
|
|
98
|
+
return unpackSlot(entity as unknown as number);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Extract just the generation slot from an entity handle via shared codec. */
|
|
102
|
+
export function entityGeneration(entity: EntityHandle): number {
|
|
103
|
+
return unpackGen(entity as unknown as number);
|
|
104
|
+
}
|
package/src/entity.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// @forgeax/engine-ecs - Entity component (feat-20260602-archetype-stores-full-packed-entity M1 / w1).
|
|
2
|
+
//
|
|
3
|
+
// Entity identity is modelled as a real id=0 ECS component `Entity`, whose sole
|
|
4
|
+
// field `self` stores the full 32-bit packed entity handle (generation << 24 |
|
|
5
|
+
// index). Every archetype carries this column unconditionally (it is essential
|
|
6
|
+
// and cannot be removed), so an entity's own handle is read through the exact
|
|
7
|
+
// same query / read path as Transform / MeshRenderer columns:
|
|
8
|
+
//
|
|
9
|
+
// const query = world.query({ read: [Transform] }).unwrap();
|
|
10
|
+
// for (const row of query) row.entity;
|
|
11
|
+
// world.get(e, Entity).unwrap().self === e
|
|
12
|
+
//
|
|
13
|
+
// This retires the "entity is an archetype side-array + three-step rebuild"
|
|
14
|
+
// implementation detail (index slot -> generation lookup -> encodeEntity) in
|
|
15
|
+
// favour of a single uniform column read.
|
|
16
|
+
//
|
|
17
|
+
// id=0 structural guarantee: `Entity` MUST be the first `defineComponent`
|
|
18
|
+
// evaluated in the process so the owner identity assigns it 0. The package
|
|
19
|
+
// barrel (packages/ecs/src/index.ts) force-evaluates this module before any
|
|
20
|
+
// other component-defining module and asserts the invariant fail-fast.
|
|
21
|
+
// See plan-strategy D-1 / D-6b (LP-1): the hard-coded 0 + barrel forced
|
|
22
|
+
// registration + startup throw is the locked design; no UECS-style runtime
|
|
23
|
+
// token lookup is introduced (the hot-path archetype column key stays a numeric
|
|
24
|
+
// owner-assigned component identity).
|
|
25
|
+
//
|
|
26
|
+
// charter mapping: P4 (consistent abstraction -- reading the entity handle is
|
|
27
|
+
// reading any other column) + P3 (id=0 drift surfaces as a structured startup
|
|
28
|
+
// throw, never a silent runtime mis-id) + P1 (single top-level import surface).
|
|
29
|
+
|
|
30
|
+
import type { ComponentId } from './component';
|
|
31
|
+
import { componentId, defineComponent } from './component';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The id=0 essential `Entity` component. Its single `self` field carries the
|
|
35
|
+
* full packed entity handle for the row it sits on (written at spawn time).
|
|
36
|
+
*
|
|
37
|
+
* Naming convention (feat-20260611-ecs-storage-naming-ssot):
|
|
38
|
+
*
|
|
39
|
+
* - Value-space `Entity` (this const) is the id=0 component token. It is
|
|
40
|
+
* looked up by name (`'Entity'`) at component-registration time and used
|
|
41
|
+
* as a value (`world.spawn`, `world.get(entity, Entity)`).
|
|
42
|
+
* - Type-space `EntityHandle` (the branded number, exported from `../entity`)
|
|
43
|
+
* is the row-identifier handle type. It is used in `: EntityHandle`
|
|
44
|
+
* annotations.
|
|
45
|
+
*
|
|
46
|
+
* The two no longer share a name -- the prior intentional coexistence (a
|
|
47
|
+
* single `Entity` symbol carrying both meanings via TS namespace merging) was
|
|
48
|
+
* dropped because two-roles-one-name created repeated AI-user confusion when
|
|
49
|
+
* reading `: Entity` annotations (is this the handle or the token?). The id=0
|
|
50
|
+
* component token's name (the literal string `'Entity'`) is preserved for
|
|
51
|
+
* runtime stability; only the type-space alias was renamed.
|
|
52
|
+
*
|
|
53
|
+
* @example Read an entity's own handle through a query:
|
|
54
|
+
* const query = world.query({ read: [Transform] }).unwrap();
|
|
55
|
+
* for (const row of query) {
|
|
56
|
+
* const handle = row.entity;
|
|
57
|
+
* }
|
|
58
|
+
*
|
|
59
|
+
* @example Use `world.get` as a general liveness probe:
|
|
60
|
+
* const r = world.get(e, Entity);
|
|
61
|
+
* if (!r.ok) { // r.error.code === 'stale-entity' for a despawned handle
|
|
62
|
+
* }
|
|
63
|
+
*/
|
|
64
|
+
export const Entity = defineComponent('Entity', {
|
|
65
|
+
// Layer-2 default is never observed: `world.spawn` always overwrites `self`
|
|
66
|
+
// with the freshly encoded handle for the row. `null` is the type-correct
|
|
67
|
+
// "no handle yet" placeholder (`'entity'` decodes to `EntityHandle | null`).
|
|
68
|
+
self: { type: 'entity', default: null },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Marks an entity as disabled.
|
|
73
|
+
*
|
|
74
|
+
* Queries exclude this tag by default. Add `Disabled` to a query's `with`
|
|
75
|
+
* tuple when the query must inspect or re-enable disabled entities.
|
|
76
|
+
*/
|
|
77
|
+
export const Disabled = defineComponent('Disabled', {});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* SSOT for component ids that are essential to every archetype. Every archetype
|
|
81
|
+
* carries the columns named by these ids unconditionally (they cannot be added
|
|
82
|
+
* or removed via `addComponent` / `removeComponent`). Currently only `Entity`
|
|
83
|
+
* is essential -- the row-identity column that lets every entity read its own
|
|
84
|
+
* packed handle (`world.get(e, Entity).self === e`) through the same column
|
|
85
|
+
* path as any other component (feat-20260602 / charter P4).
|
|
86
|
+
*
|
|
87
|
+
* Frozen so consumers cannot mutate the SSOT. The barrel
|
|
88
|
+
* (`packages/ecs/src/index.ts`) re-exports this constant so the AI-facing
|
|
89
|
+
* `import { ESSENTIAL_COMPONENT_IDS } from '@forgeax/engine-ecs'` works.
|
|
90
|
+
*
|
|
91
|
+
* Physical location is `packages/ecs/src/entity.ts` -- the module that owns
|
|
92
|
+
* the `defineComponent('Entity', ...)` call, so reading the owner identity
|
|
93
|
+
* immediately after registration is well-defined. tweak-20260612-ecs-concept-
|
|
94
|
+
* compression lifted this file back from the historical `components/entity.ts`
|
|
95
|
+
* after `entity-handle.ts` freed up the `entity.ts` slot.
|
|
96
|
+
*/
|
|
97
|
+
export const ESSENTIAL_COMPONENT_IDS: ReadonlyArray<ComponentId> = Object.freeze([
|
|
98
|
+
componentId(Entity),
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Fold the essential component ids (currently `[componentId(Entity)]`) into a caller-
|
|
103
|
+
* supplied id list. Returns a NEW array; never mutates the input.
|
|
104
|
+
*
|
|
105
|
+
* - If `ids` already contains every essential id, returns a deduped copy
|
|
106
|
+
* (idempotent under repeated folding).
|
|
107
|
+
* - Otherwise, returns `[...essential, ...ids]` with duplicates of the
|
|
108
|
+
* essential ids removed.
|
|
109
|
+
*
|
|
110
|
+
* The empty input maps to `[componentId(Entity)]` -- the bare-archetype shape that a
|
|
111
|
+
* `world.spawn()` (no components) materialises.
|
|
112
|
+
*
|
|
113
|
+
* Single SSOT consumed by both `archetypeKey` (string-key fold) and
|
|
114
|
+
* `createArchetype` (column-build fold) so the two sites can never disagree
|
|
115
|
+
* on which ids are essential. Hot-path correctness is on `createArchetype`'s
|
|
116
|
+
* side: misalignment between key and columns silently drops fields.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* foldEssentials([2, 5, 7]) // [componentId(Entity), 2, 5, 7]
|
|
120
|
+
* foldEssentials([componentId(Entity), 2, 5]) // [componentId(Entity), 2, 5]
|
|
121
|
+
* foldEssentials([componentId(Entity), componentId(Entity)]) // [componentId(Entity)]
|
|
122
|
+
* foldEssentials([]) // [componentId(Entity)]
|
|
123
|
+
*/
|
|
124
|
+
export function foldEssentials(ids: ReadonlyArray<ComponentId>): ComponentId[] {
|
|
125
|
+
const seen = new Set<ComponentId>();
|
|
126
|
+
const out: ComponentId[] = [];
|
|
127
|
+
for (const essential of ESSENTIAL_COMPONENT_IDS) {
|
|
128
|
+
if (!seen.has(essential)) {
|
|
129
|
+
seen.add(essential);
|
|
130
|
+
out.push(essential);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const id of ids) {
|
|
134
|
+
if (!seen.has(id)) {
|
|
135
|
+
seen.add(id);
|
|
136
|
+
out.push(id);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returned via `Result.err` from `world.removeComponent` when the caller tries
|
|
3
|
+
* to remove an essential (undeletable) component
|
|
4
|
+
* (feat-20260602-archetype-stores-full-packed-entity M1 / w3, plan-strategy
|
|
5
|
+
* D-3). The only essential component today is the id=0 `Entity` component: every
|
|
6
|
+
* archetype carries it unconditionally as the row's own packed handle, so
|
|
7
|
+
* removing it is structurally meaningless. The code name is deliberately
|
|
8
|
+
* generic (`remove-essential-component`, not entity-specific) so a future second
|
|
9
|
+
* essential component reuses it without a rename.
|
|
10
|
+
*
|
|
11
|
+
* `.code = 'remove-essential-component'`
|
|
12
|
+
* `.detail = { componentName }`
|
|
13
|
+
* `.hint` — names the essential component + states it cannot be removed.
|
|
14
|
+
*/
|
|
15
|
+
export class RemoveEssentialComponentError extends Error {
|
|
16
|
+
override readonly name = 'RemoveEssentialComponentError';
|
|
17
|
+
readonly code = 'remove-essential-component' as const;
|
|
18
|
+
readonly hint: string;
|
|
19
|
+
readonly expected: string;
|
|
20
|
+
readonly detail: { readonly componentName: string };
|
|
21
|
+
|
|
22
|
+
constructor(componentName: string) {
|
|
23
|
+
const hint = `Component "${componentName}" is essential (every entity carries it unconditionally) and cannot be removed. Despawn the entity instead if you want to retire it.`;
|
|
24
|
+
const expected = 'non-essential component';
|
|
25
|
+
super(
|
|
26
|
+
`removeComponent: essential component cannot be removed.\n` +
|
|
27
|
+
` code: remove-essential-component\n` +
|
|
28
|
+
` component: ${componentName}\n` +
|
|
29
|
+
` expected: ${expected}\n` +
|
|
30
|
+
` hint: ${hint}`,
|
|
31
|
+
);
|
|
32
|
+
this.hint = hint;
|
|
33
|
+
this.expected = expected;
|
|
34
|
+
this.detail = { componentName };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returned via the `Result` err branch when `instantiate` encounters a
|
|
40
|
+
* SceneAsset entity whose `components` map references a component name that was
|
|
41
|
+
* never passed to `defineComponent`.
|
|
42
|
+
*
|
|
43
|
+
* `.code = 'component-not-defined'`
|
|
44
|
+
* `.detail.name` — the offending component name.
|
|
45
|
+
*
|
|
46
|
+
* Promoting this to a class (rather than a bare object literal) keeps the
|
|
47
|
+
* scene-instantiate failure surface inside the `EcsError` class union, so the
|
|
48
|
+
* documented two-level narrow `cause instanceof EcsError` actually matches it
|
|
49
|
+
* (docs/feedbacks/2026-06-03 §6.2 Tier 4.2). `expected` / `hint` accept
|
|
50
|
+
* per-call overrides because the parent-passthrough (ChildOf) site needs a
|
|
51
|
+
* distinct message from the generic entity-component site.
|
|
52
|
+
*/
|
|
53
|
+
export class ComponentNotDefinedError extends Error {
|
|
54
|
+
override readonly name = 'ComponentNotDefinedError';
|
|
55
|
+
readonly code = 'component-not-defined' as const;
|
|
56
|
+
readonly hint: string;
|
|
57
|
+
readonly expected: string;
|
|
58
|
+
readonly detail: { readonly name: string };
|
|
59
|
+
|
|
60
|
+
constructor(componentName: string, opts?: { expected?: string; hint?: string }) {
|
|
61
|
+
const expected = opts?.expected ?? `component '${componentName}' defined before instantiate`;
|
|
62
|
+
const hint =
|
|
63
|
+
opts?.hint ??
|
|
64
|
+
`define the component via defineComponent('${componentName}', ...) before instantiating this SceneAsset`;
|
|
65
|
+
super(
|
|
66
|
+
`instantiate: component not defined.\n` +
|
|
67
|
+
` code: component-not-defined\n` +
|
|
68
|
+
` component: ${componentName}\n` +
|
|
69
|
+
` expected: ${expected}\n` +
|
|
70
|
+
` hint: ${hint}`,
|
|
71
|
+
);
|
|
72
|
+
this.hint = hint;
|
|
73
|
+
this.expected = expected;
|
|
74
|
+
this.detail = { name: componentName };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returned via `Result.err` from `world.spawn` / `world.addComponent` /
|
|
80
|
+
* `world.instantiateScene` / `Commands.spawn` when the caller-supplied
|
|
81
|
+
* data payload carries a key that is not declared in the target component's
|
|
82
|
+
* schema. The pre-fix behaviour silently dropped unknown keys inside
|
|
83
|
+
* `fillComponentDefaults` (which walked schema keys, never raw keys), so a
|
|
84
|
+
* typo like `MeshRenderer { material: h }` (singular legacy field name; the
|
|
85
|
+
* current schema has `materials: array<...>`) produced an empty-defaults row
|
|
86
|
+
* + an invisible / mid-grey entity downstream. Surfacing the typo at the
|
|
87
|
+
* spawn boundary collapses a class of "renders wrong, looks like a graphics
|
|
88
|
+
* bug" reports into a single explicit error.
|
|
89
|
+
*
|
|
90
|
+
* `.code = 'spawn-data-unknown-field'`
|
|
91
|
+
* `.detail = { component, field, knownFields }`
|
|
92
|
+
* `.hint` — names the offending field and lists the schema's known fields.
|
|
93
|
+
*/
|
|
94
|
+
export class SpawnDataUnknownFieldError extends Error {
|
|
95
|
+
override readonly name = 'SpawnDataUnknownFieldError';
|
|
96
|
+
readonly code = 'spawn-data-unknown-field' as const;
|
|
97
|
+
readonly hint: string;
|
|
98
|
+
readonly expected: string;
|
|
99
|
+
readonly detail: {
|
|
100
|
+
readonly component: string;
|
|
101
|
+
readonly field: string;
|
|
102
|
+
readonly knownFields: readonly string[];
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
constructor(componentName: string, fieldName: string, knownFields: readonly string[]) {
|
|
106
|
+
const sortedKnown = [...knownFields].sort();
|
|
107
|
+
const expected = `field name in {${sortedKnown.join(', ')}}`;
|
|
108
|
+
const hint =
|
|
109
|
+
`'${fieldName}' is not a schema field of '${componentName}'. ` +
|
|
110
|
+
`Known fields: ${sortedKnown.join(', ')}. ` +
|
|
111
|
+
`Check for a typo or a stale single-vs-plural rename (e.g. 'material' vs 'materials').`;
|
|
112
|
+
super(
|
|
113
|
+
`${componentName}: spawn data carries unknown field.\n` +
|
|
114
|
+
` code: spawn-data-unknown-field\n` +
|
|
115
|
+
` component: ${componentName}\n` +
|
|
116
|
+
` field: ${fieldName}\n` +
|
|
117
|
+
` expected: ${expected}\n` +
|
|
118
|
+
` hint: ${hint}`,
|
|
119
|
+
);
|
|
120
|
+
this.hint = hint;
|
|
121
|
+
this.expected = expected;
|
|
122
|
+
this.detail = { component: componentName, field: fieldName, knownFields: sortedKnown };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export type QuerySpanUnavailableReason = 'optional-data' | 'row-change-filter' | 'sparse-component';
|
|
126
|
+
|
|
127
|
+
export class QueryDescriptorConflictError extends Error {
|
|
128
|
+
override readonly name = 'QueryDescriptorConflictError';
|
|
129
|
+
readonly code = 'query-descriptor-conflict' as const;
|
|
130
|
+
readonly expected = 'each component occupies one descriptor role';
|
|
131
|
+
readonly hint: string;
|
|
132
|
+
readonly detail: { readonly componentName: string; readonly roles: readonly string[] };
|
|
133
|
+
|
|
134
|
+
constructor(componentName: string, roles: readonly string[]) {
|
|
135
|
+
const hint = `Remove ${componentName} from all but one of: ${roles.join(', ')}.`;
|
|
136
|
+
super(`Query descriptor roles conflict for ${componentName}.\n hint: ${hint}`);
|
|
137
|
+
this.hint = hint;
|
|
138
|
+
this.detail = { componentName, roles };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export class QueryDataRequiresFieldsError extends Error {
|
|
143
|
+
override readonly name = 'QueryDataRequiresFieldsError';
|
|
144
|
+
readonly code = 'query-data-requires-fields' as const;
|
|
145
|
+
readonly expected = 'a component with at least one data field';
|
|
146
|
+
readonly hint: string;
|
|
147
|
+
readonly detail: { readonly componentName: string };
|
|
148
|
+
|
|
149
|
+
constructor(componentName: string) {
|
|
150
|
+
const hint = `Move tag ${componentName} to with or without.`;
|
|
151
|
+
super(`Query data access requires fields on ${componentName}.\n hint: ${hint}`);
|
|
152
|
+
this.hint = hint;
|
|
153
|
+
this.detail = { componentName };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export class QuerySpanUnavailableError extends Error {
|
|
158
|
+
override readonly name = 'QuerySpanUnavailableError';
|
|
159
|
+
readonly code = 'query-span-unavailable' as const;
|
|
160
|
+
readonly expected = 'a descriptor whose rows form contiguous table ranges';
|
|
161
|
+
readonly hint = 'Use row iteration or split the query.';
|
|
162
|
+
readonly detail: { readonly reason: QuerySpanUnavailableReason };
|
|
163
|
+
|
|
164
|
+
constructor(reason: QuerySpanUnavailableReason) {
|
|
165
|
+
super(`Query spans are unavailable: ${reason}.\n hint: Use row iteration or split the query.`);
|
|
166
|
+
this.detail = { reason };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export class QueryIterationInvalidatedError extends Error {
|
|
171
|
+
override readonly name = 'QueryIterationInvalidatedError';
|
|
172
|
+
readonly code = 'query-iteration-invalidated' as const;
|
|
173
|
+
readonly expected: string;
|
|
174
|
+
readonly hint = 'Use deferred Commands for structural mutation, then restart iteration.';
|
|
175
|
+
readonly detail: {
|
|
176
|
+
readonly expectedStructureEpoch: number;
|
|
177
|
+
readonly actualStructureEpoch: number;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
constructor(expectedStructureEpoch: number, actualStructureEpoch: number) {
|
|
181
|
+
const expected = `structure epoch ${expectedStructureEpoch}`;
|
|
182
|
+
super(
|
|
183
|
+
`Query iteration was invalidated by structure epoch ${actualStructureEpoch}.\n hint: ${'Use deferred Commands for structural mutation, then restart iteration.'}`,
|
|
184
|
+
);
|
|
185
|
+
this.expected = expected;
|
|
186
|
+
this.detail = { expectedStructureEpoch, actualStructureEpoch };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export class QueryIterationActiveError extends Error {
|
|
191
|
+
override readonly name = 'QueryIterationActiveError';
|
|
192
|
+
readonly code = 'query-iteration-active' as const;
|
|
193
|
+
readonly expected = 'one active iterator per Query';
|
|
194
|
+
readonly hint = 'Complete the active iterator or create an independent Query.';
|
|
195
|
+
readonly detail = {};
|
|
196
|
+
|
|
197
|
+
constructor() {
|
|
198
|
+
super('Query already has an active iterator.\n hint: Complete it before iterating again.');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// feat-20260531-ecs-relationship-abstraction-bidirectional-sync M2 — closed-
|
|
3
|
+
// union evolution +4 (plan-strategy D-5). Adds 4 `relationship-*` kebab codes
|
|
4
|
+
// (27 -> 31, add-only minor per AGENTS.md Error model evolution contract):
|
|
5
|
+
//
|
|
6
|
+
// - relationship-self-cycle (cycle / ancestor walk hit)
|
|
7
|
+
// - relationship-mirror-component-not-registered (defineComponent gate a)
|
|
8
|
+
// - relationship-mirror-field-type-mismatch (defineComponent gate b)
|
|
9
|
+
// - relationship-detach-mismatch (removeChild parent arg mismatch)
|
|
10
|
+
//
|
|
11
|
+
// `relationship-exclusive-violation` is intentionally NOT a member: exclusive
|
|
12
|
+
// re-add is an automatic reparent (a success path, D-1 style), not an error.
|
|
13
|
+
// Every detail object is a discriminated payload narrowed via EcsErrorDetail.
|
|
14
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returned via `Result.err` from `world.addChild` / `world.reparent` (M3) when
|
|
18
|
+
* a hierarchy write would form a cycle — either the child is its own parent
|
|
19
|
+
* (self-loop) or the proposed parent is already a descendant of the child
|
|
20
|
+
* (ancestor-walk hit). The `.detail` carries both the offending child entity
|
|
21
|
+
* and the ancestor entity that closed the cycle so AI users can locate the
|
|
22
|
+
* loop without re-walking the graph.
|
|
23
|
+
*
|
|
24
|
+
* `.code = 'relationship-self-cycle'`
|
|
25
|
+
* `.detail = { component, entity, ancestor }`
|
|
26
|
+
* `.hint` — names the child + ancestor that would close the cycle.
|
|
27
|
+
*/
|
|
28
|
+
export class RelationshipSelfCycleError extends Error {
|
|
29
|
+
override readonly name = 'RelationshipSelfCycleError';
|
|
30
|
+
readonly code = 'relationship-self-cycle' as const;
|
|
31
|
+
readonly hint: string;
|
|
32
|
+
readonly expected: string;
|
|
33
|
+
readonly detail: {
|
|
34
|
+
readonly component: string;
|
|
35
|
+
readonly entity: number;
|
|
36
|
+
readonly ancestor: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
constructor(component: string, entity: number, ancestor: number) {
|
|
40
|
+
const hint = `Linking entity ${entity} via "${component}" would close a cycle through ancestor ${ancestor}. Reparent to an entity that is not a descendant of ${entity}.`;
|
|
41
|
+
const expected = 'acyclic parent chain';
|
|
42
|
+
super(
|
|
43
|
+
`relationship: cycle detected.\n` +
|
|
44
|
+
` code: relationship-self-cycle\n` +
|
|
45
|
+
` component: ${component}\n` +
|
|
46
|
+
` entity: ${entity}\n` +
|
|
47
|
+
` ancestor: ${ancestor}\n` +
|
|
48
|
+
` expected: ${expected}\n` +
|
|
49
|
+
` hint: ${hint}`,
|
|
50
|
+
);
|
|
51
|
+
this.hint = hint;
|
|
52
|
+
this.expected = expected;
|
|
53
|
+
this.detail = { component, entity, ancestor };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Thrown by `defineComponent` (feat-20260602 M2) when a component declares a
|
|
59
|
+
* `relationship.mirror` naming a component that has not yet been defined
|
|
60
|
+
* (AC-09). AI users defineComponent the mirror before the holder (mirror-then-
|
|
61
|
+
* holder order).
|
|
62
|
+
*
|
|
63
|
+
* The `.code` literal `relationship-mirror-component-not-registered` is kept
|
|
64
|
+
* unchanged across the M2 migration (deliberate terminology trade-off:
|
|
65
|
+
* external `.code` stability over wording precision); only the `.hint` text
|
|
66
|
+
* drops the register/registered phrasing in favour of defineComponent ordering
|
|
67
|
+
* guidance.
|
|
68
|
+
*
|
|
69
|
+
* `.code = 'relationship-mirror-component-not-registered'`
|
|
70
|
+
* `.detail = { component, mirror }`
|
|
71
|
+
* `.hint` — names the holder + the undefined mirror component.
|
|
72
|
+
*/
|
|
73
|
+
export class RelationshipMirrorComponentNotRegisteredError extends Error {
|
|
74
|
+
override readonly name = 'RelationshipMirrorComponentNotRegisteredError';
|
|
75
|
+
readonly code = 'relationship-mirror-component-not-registered' as const;
|
|
76
|
+
readonly hint: string;
|
|
77
|
+
readonly expected: string;
|
|
78
|
+
readonly detail: { readonly component: string; readonly mirror: string };
|
|
79
|
+
|
|
80
|
+
constructor(component: string, mirror: string) {
|
|
81
|
+
const hint = `Component "${component}" declares relationship.mirror = "${mirror}", but "${mirror}" has not been defined yet. defineComponent the mirror component before the holder (define them in mirror-then-holder order).`;
|
|
82
|
+
const expected = `mirror component "${mirror}" registered`;
|
|
83
|
+
super(
|
|
84
|
+
`relationship: mirror component not registered.\n` +
|
|
85
|
+
` code: relationship-mirror-component-not-registered\n` +
|
|
86
|
+
` component: ${component}\n` +
|
|
87
|
+
` mirror: ${mirror}\n` +
|
|
88
|
+
` expected: ${expected}\n` +
|
|
89
|
+
` hint: ${hint}`,
|
|
90
|
+
);
|
|
91
|
+
this.hint = hint;
|
|
92
|
+
this.expected = expected;
|
|
93
|
+
this.detail = { component, mirror };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Thrown by `defineComponent` (feat-20260602 M2) when the
|
|
99
|
+
* `relationship.field` on the mirror component is missing or its schema type
|
|
100
|
+
* is not the only legal back-reference storage shape `array<entity>`
|
|
101
|
+
* (AC-11 b). AI users declare the mirror field as `'array<entity>'`.
|
|
102
|
+
*
|
|
103
|
+
* `.code = 'relationship-mirror-field-type-mismatch'`
|
|
104
|
+
* `.detail = { component, mirror, field, actualType }`
|
|
105
|
+
* `.hint` — names the holder + mirror field + the type observed.
|
|
106
|
+
*/
|
|
107
|
+
export class RelationshipMirrorFieldTypeMismatchError extends Error {
|
|
108
|
+
override readonly name = 'RelationshipMirrorFieldTypeMismatchError';
|
|
109
|
+
readonly code = 'relationship-mirror-field-type-mismatch' as const;
|
|
110
|
+
readonly hint: string;
|
|
111
|
+
readonly expected: string;
|
|
112
|
+
readonly detail: {
|
|
113
|
+
readonly component: string;
|
|
114
|
+
readonly mirror: string;
|
|
115
|
+
readonly field: string;
|
|
116
|
+
readonly actualType: string;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
constructor(component: string, mirror: string, field: string, actualType: string) {
|
|
120
|
+
const hint = `Component "${component}" mirror "${mirror}".${field} has type "${actualType}"; the reverse-list field must be declared as 'array<entity>'.`;
|
|
121
|
+
const expected = "mirror field type === 'array<entity>'";
|
|
122
|
+
super(
|
|
123
|
+
`relationship: mirror field type mismatch.\n` +
|
|
124
|
+
` code: relationship-mirror-field-type-mismatch\n` +
|
|
125
|
+
` component: ${component}\n` +
|
|
126
|
+
` mirror: ${mirror}\n` +
|
|
127
|
+
` field: ${field}\n` +
|
|
128
|
+
` actualType: ${actualType}\n` +
|
|
129
|
+
` expected: ${expected}\n` +
|
|
130
|
+
` hint: ${hint}`,
|
|
131
|
+
);
|
|
132
|
+
this.hint = hint;
|
|
133
|
+
this.expected = expected;
|
|
134
|
+
this.detail = { component, mirror, field, actualType };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Returned via `Result.err` from `world.removeChild` (M3) when the `parent`
|
|
140
|
+
* argument does not match the child's current relationship parent (the child
|
|
141
|
+
* lacks the relationship component, or it points at a different parent). The
|
|
142
|
+
* `.detail` carries the expected (argument) parent + the actual current parent
|
|
143
|
+
* so AI users can reconcile their model.
|
|
144
|
+
*
|
|
145
|
+
* `.code = 'relationship-detach-mismatch'`
|
|
146
|
+
* `.detail = { component, child, expectedParent, actualParent }`
|
|
147
|
+
* `actualParent === ENTITY_NULL_RAW` (0) signals the child has no relationship.
|
|
148
|
+
* `.hint` — names the child + the parent mismatch.
|
|
149
|
+
*/
|
|
150
|
+
export class RelationshipDetachMismatchError extends Error {
|
|
151
|
+
override readonly name = 'RelationshipDetachMismatchError';
|
|
152
|
+
readonly code = 'relationship-detach-mismatch' as const;
|
|
153
|
+
readonly hint: string;
|
|
154
|
+
readonly expected: string;
|
|
155
|
+
readonly detail: {
|
|
156
|
+
readonly component: string;
|
|
157
|
+
readonly child: number;
|
|
158
|
+
readonly expectedParent: number;
|
|
159
|
+
readonly actualParent: number;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
constructor(component: string, child: number, expectedParent: number, actualParent: number) {
|
|
163
|
+
const hint = `removeChild(${expectedParent}, ${child}) via "${component}": child's current parent is ${actualParent}, not ${expectedParent}. Detach from the actual parent or re-read the current relationship.`;
|
|
164
|
+
const expected = `child's "${component}" parent === ${expectedParent}`;
|
|
165
|
+
super(
|
|
166
|
+
`relationship: detach parent mismatch.\n` +
|
|
167
|
+
` code: relationship-detach-mismatch\n` +
|
|
168
|
+
` component: ${component}\n` +
|
|
169
|
+
` child: ${child}\n` +
|
|
170
|
+
` expectedParent: ${expectedParent}\n` +
|
|
171
|
+
` actualParent: ${actualParent}\n` +
|
|
172
|
+
` expected: ${expected}\n` +
|
|
173
|
+
` hint: ${hint}`,
|
|
174
|
+
);
|
|
175
|
+
this.hint = hint;
|
|
176
|
+
this.expected = expected;
|
|
177
|
+
this.detail = { component, child, expectedParent, actualParent };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Returned when callers attempt to mutate an engine-maintained target list. */
|
|
182
|
+
export class RelationshipTargetReadonlyError extends Error {
|
|
183
|
+
override readonly name = 'RelationshipTargetReadonlyError';
|
|
184
|
+
readonly code = 'component-field-invalid-value' as const;
|
|
185
|
+
readonly expected = 'relationship source mutation';
|
|
186
|
+
readonly hint: string;
|
|
187
|
+
readonly detail: {
|
|
188
|
+
readonly component: string;
|
|
189
|
+
readonly operation: string;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
constructor(component: string, operation: string) {
|
|
193
|
+
const hint = `Component "${component}" is an engine-maintained relationship target. Mutate its source component instead of ${operation}.`;
|
|
194
|
+
super(`[RelationshipTargetReadonlyError component-field-invalid-value] ${hint}`);
|
|
195
|
+
this.hint = hint;
|
|
196
|
+
this.detail = { component, operation };
|
|
197
|
+
}
|
|
198
|
+
}
|